Java基础(七)——面向对象_多态、内部类、其它
来源:互联网 发布:淘宝旺旺生成链接 编辑:程序博客网 时间:2024/05/22 06:13
1, 多态(Polymorphism)
Java引用变量有两个类型:一个是编译时的类型,一个是运行时的类型,编译时的类型由声明该变量时使用的类型决定,运行时的类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就会出现所谓的多态。
2, 多态的演示:
class BaseClass{public int book = 6;public void base(){System.out.println("父类的普通方法");}public void test(){System.out.println("父类的被覆盖的方法");}}class SubClass extends BaseClass{public String book = "Java核心技术"; //重新定义一个book实例属性覆盖父类的book实例属性public void test(){System.out.println("子类的覆盖父类的方法");}public void sub(){System.out.println("子类的普通方法");}public static void main(String[] args) {BaseClass bc = new BaseClass(); //编译时类型和运行时类型完全一样,不存在多态System.out.println(bc.book); //输出6bc.base(); //两次都执行BaseClass的方法bc.test();System.out.println();SubClass sc = new SubClass(); //编译时类型和运行时类型完全一样,不存在多态System.out.println(sc.book); //输出“Java核心技术”sc.base(); //调用从父类继承到的base方法sc.test(); //调用当前类的test方法System.out.println();BaseClass ploymorphicBc = new SubClass(); //编译时和运行时的类型不同,多态System.out.println(ploymorphicBc.book); //输出6,表明访问的是父类属性ploymorphicBc.base(); //调用父类继承到的base方法ploymorphicBc.test(); //调用当前类的test方法//ploymorphicBc.sub(); //编译出错,因为变量编译时是BaseClass类型,BaseClass类没有sub方法}}运行结果:
2.1,说明:子类其实是一种特殊的父类,因此Java允许把一个子类对象直接赋给一个父类引用变量,无需任何类型转换,或者被称为向上转型(upcasting),
向上转型由系统自动完成。
当把一个子类对象直接赋给父类引用变量,例如上面的BaseClassploymorphicBc = new SubClass();这个ploymorphicBc引用变量的编译时类型是BaseClass,而运行时类型是SubClass,当运行时调用该引用变量的方法时,其方法行为总是像子类方法的行为,而不是像父类的方法行为,这将出现相同类型的变量、执行同一个方法时呈现出不同的行为特征,这就是多态。
2.2,注意:
引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。因此,编写Java代码时,引用变量只能调用声明该变量时所用类里包含的方法。例如我们通过Object p = new Person()代码定义一个变量p,则这个p只能调用Object类的方法,而不能调用Person类里定义的方法。
属性不具备多态性。通过引用变量来访问其包含的实例属性时,系统总是试图访问它编译时所定义的属性,而不是它运行时类所定义的属性。
3, 多态的总结
3.1,多态的体现:
父类引用指向自己的子类对象;
父类引用也可以接收自己子类的对象;
3.2,多态的前提:
必须是类与类之间有关系,要么继承,要么实现;
通常还有一个前提:覆盖;
3.3,多态的好处:
多态的出现大大提高了程序的扩展性;
3.4,多态的弊端:
提高了扩展性,但是只能使用父类的引用访问父类中的成员。
3.5,在多态中成员函数的特点:
在编译时期:参阅引用变量所属的类中是否有调用的方法,如果有,编译通过,如果没有则编译失败。
在运行时期:参阅对象所属类中是否有调用的方法。
简单总结就是:在多态中调用成员函数时,编译看左边,运行看右边。
在多态中,成员变量的特点:
无论编译还是运行,都参考左边(引用型变量所属的类)。
在多态中,静态成员函数的特点:
无论编译还是运行,都参考左边。
示例代码:
class Fu{void method1(){System.out.println("fu method_1");}void method2(){System.out.println("fu method_2");}static void method4(){System.out.println("fu method_4");}}class Zi extends Fu{void method1(){System.out.println("zi method_1");}void method3(){System.out.println("zi method_3");}static void method4(){System.out.println("zi method_4");}}class DuoTaiDemo2 {public static void main(String[] args) {Fu f = new Zi();f.method1();f.method2();//f.method3(); //编译不通过f.method4(); //打印“fu method_4”Zi z = new Zi();z.method4();}}
4, 引用变量的强制类型转换
编写Java程序时,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它实际所引用对象确实包含该方法。
如果需要让这个引用变量来调用它运行时类型的方法,则必须把它强制类型转换成运行时类型,强制类型转换需要借助类型转换运算符。
类型转换运算符是(),类型转换运算符的用法如下:(type)variable,这种用法可以将variable变量转换成一个type类型的变量。
注意:引用类型之间的转换只能把一个父类变量转换成子类类型,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出错。如果试图把一个父类实例转换子类类型,则必须这个对象实际上是子类实例才行(即编译时类型为父类类型,而运行时类型是子类类型),否则将在运行时引发ClassCastException异常。
当把子类对象赋给父类引用变量时,被称为向上转型(upcasting),这种转型总是可以成功的,这也是从另一个侧面证实了子类是一种特殊的父类,这种转型只是表明这个引用变量的编译类型是父类,但实际执行它的方法时,依然表现出子类对象的行为方式,但把一个父类对象赋给子类引用变量时,就需要进行强制类型转换,而且还可能在运行时产生ClassCastException异常,使用instanceof运算符可以让强制类型转换更安全。
5, instanceof运算符:
instanceof运算符的前一个操作数通常是一个引用类型的变量,后一个操作数是一个类(也可以是接口,可以把接口理解成一种特殊的类),它用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是,则返回true,否则返回false。
注意:instanceof运算符前面的操作数的编译类型要么与后面的类相同,要么是后面类的父类,否则会引起编译错误。
示例代码:
class InstanceofDemo{public static void main(String[] args){Object hello = "Hello";//String是Object的子类,所以返回trueSystem.out.println("字符串是否是Object类的实例:"+(hello instanceof Object));System.out.println("字符串是否是String类的实例:"+(hello instanceof String));System.out.println("字符串是否是Math类的实例:"+(hello instanceof Math));System.out.println("字符串是否是Comparable接口的实例:"+(hello instanceof Comparable));String a = "Hello";//string类既不是Math类,也不是Math类的父类,所以下面的编译出错,报错:不可转换的类型//System.out.println("字符串是否是Math类的实例:"+(a instanceof Math));}}运行结果:
instanceof运算符的作用是:在执行强制类型转换之前,首先判断前一个对象是否是后一个类的实例,是否可以成功转换,从而保证代码更加健。
instanceof和(type)是Java提供的两个相关的运算符,通常先用instanceof判断一个对象是否可以强制类型转换,然后再使用(type)运算符进行强制类型转换,
从而保证程序不会出现错误。
6, 内部类
大部分时候,我们把类定义在一个独立的程序单元。在某些情况下,我们把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类(有时也叫嵌套类),包含内部类的类也被称为外部类(有时也叫宿主类)。内部类主要有如下作用:
》》内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许用一个包中的其他类访问该类。
》》内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以相互访问。但外部类不能访问内部类的实现细节,例如内部类的属性。
》》匿名内部类适合用于创建那些仅需要一次使用的类。
6.1,非静态内部类和静态内部类
先看示例代码:
class Outer{private int Num=3;//定义一个内部类class Inner{void function(){System.out.println("Inner:"+Num); //内部类中可以直接访问外部类的内容(Num)}} /*class Inner{int Num=4;void function(){int Num=6;System.out.println("Inner:"+Num); //输出6,以局部变量为主System.out.println("Inner:"+this.Num); //输出4,以当前类成员为主System.out.println("Inner:"+Outer.this.Num); //输出6,此种方式为默认方式}}*/void method(){Inner in=new Inner();in.function();}}class Outer2{private static int Num=3;static class Inner2 //静态内部类{void function2(){System.out.println("Inner2:"+Num);}}}class Outer3{private static int Num=3;static class Inner3 //静态内部类{static void function(){System.out.println("Inner2:"+Num);}}}class InnerClassDemo {public static void main(String[] args) {//第一种访问方式,通过外部类访问内部类的成员Outer ou=new Outer();ou.method();//第二种访问方式,直接访问内部类成员Outer.Inner in=new Outer().new Inner();in.function();/*第二种访问方式仅出现在面试中,实际开发很少使用,因为内部类作为外部类的成员,经常会被私有化(private)*///在外部类中访问静态内部类的非静态成员(实例看Outer2)new Outer2.Inner2().function2();//在外部类中访问静态内部类的静态成员(实例看Outer3)Outer3.Inner3.function();}}运行结果:
6.1.1,内部类访问规则:
6.1.1.1,内部类可以直接访问外部类的成员,包括私有;
内部类可以访问外部类的成员,是因为内部类中持有一个外部类的引用,格式:外部类名.this.
6.1.1.2,外部类要访问内部类,必须建立内部类的对象;
6.1.2,访问格式:
6.1.2.1,当内部类定义在外部类的成员位置上,而且非私有,可以在外部类中直接定义内部类对象,格式:
外部类名.内部类名变量名=外部类对象.内部类对象
Outer.Innerin=new Outer().new Inner();
6.1.2.2,当内部类在成员位置上,就可以被成员修饰符所修饰
比如,private:将内部类在外部类中封装
static:内部类就具有static特性
当内部类被static修饰后,只能直接访问外部类中的static成员,出现访问局限
在外部其他类中,如何直接访问static内部类的非静态成员呢?
new Outer2.Inner2().function2();
在外部其他类中,如何直接访问static内部类的静态成员呢?
Outer3.Inner3.function();
注意:当内部类中定义了静态成员,该内部类必须是静态的。
当外部类中的静态方法访问内部类时,内部类也必须是static的
6.1.3,内部类的应用:
当描述事物时,事物的内部还有事物,该事物用内部类来描述,因为内部事物在使用外包园事物的内容。
6.2,局部内部类
内部类定义在局部时
6.2.1,不可以被成员修饰符修饰
6.2.2,可以直接访问外部类中的成员,因为还持有外部类中的引用
但是不可以访问它所在局部中的变量,只能访问被final修饰的局部变量
示例代码:
class Outer{int x=3;void method(){final int y=4; //在内部类中访问局部变量,需要被声明为最终类型class Inner{void function(){System.out.println(Outer.this.x);System.out.println(y);}}new Inner().function();}void method2(final int a){final int y=4; //在内部类中访问局部变量,需要被声明为最终类型class Inner{void function(){System.out.println(a);}}new Inner().function();}}class InnerClassDemo2 {public static void main(String[] args) {new Outer().method();Outer out=new Outer();out.method2(7); //这是method2方法进栈,运算完以后出栈,虽a被final修饰,但对下一次调用method2无影响out.method2(8); //method2再一次进栈,此时的a与上一次的a不同}}运行结果:
6.3,匿名内部类
6.3.1,匿名内部类其实就是内部类的简写格式;
6.3.2,定义匿名内部类的前提:内部类必须是继承一个类或者实现一个接口
6.3.3,匿名内部类的格式:new 父类或者接口(){定义子类的内容};
6.3.4,其实匿名内部类就是一个匿名子类对象,而且这个对象有点胖,可以理解为带内容的对象
6.3.5,匿名内部类中定义的方法最好不超过3个
示例代码:
abstract class AbsDemo{abstract void show();}/*//一般内部类class Outer{int x=3;class Inner extends AbsDemo{void show(){System.out.println("show:"+x);}}public void function(){new Inner().show();}}*///匿名内部类class Outer{int x=3;public void function(){new AbsDemo(){void show(){System.out.println("x==="+x);}void abc(){System.out.println("haha");}}.show();new AbsDemo(){void show(){System.out.println("x==="+x);}void abc(){System.out.println("haha");}}.abc();/*AbsDemo a=new AbsDemo(){void show(){System.out.println("x==="+x);}void abc(){System.out.println("haha");}};a.show(); //可以//a.abc(); //编译失败,AbsDemo类中没有定义abc() */}}class InnerClassDemo3 {public static void main(String[] args) {new Outer().function(); }}运行结果:
总结:今天自学的内容在上面列出,知识点不是很多,代码量稍多,看代码区理解知识点比单纯的看知识点清单效果要好的多。
1, 理解好多态,用自己的话总结起来,就是“父类引用指向子类对象,编译时看左边(父类),运行时看右边(子类对象)”,
这样就好理解为什么调用在子类有在父类没有的方法时,编译出现错误了。另外注意,属性不具备多态性!
2, 虽然内部类不常用,但是作为Java的一部分,还是要掌握好,例如静态内部类,还是那句话,静态不能访问非静态。
还有各种内部类的访问方式,这个在面试中出现率很高,掌握是必须的。
3, 理解匿名内部类,以及匿名内部类的用法,匿名内部类适合用于创建那些仅需要一次使用的类。
还有匿名内部类必须继承一个类或者实现一个接口,
实现多个接口不可以。
4, 想到了一个问题,关于Object类中的equals方法。这个问题在黑马官方论坛上被多人多次提到,在这里我得加强一下理解,看示例代码:
class TestEqual {public static void main(String[] args) {int it=65;float ft=65.0f;System.out.println("65和65.0f是否相等? "+(it==ft)); //此处返回truechar ch='A';System.out.println("65和A是否相等? "+(it==ch)); //此处返回trueString str1=new String("hello");String str2=new String("hello");System.out.println("str1和str2是否相等? "+(str1==str2)); //此处返回falseSystem.out.println("str1和str2是否相等? "+str1.equals(str2)); //此处返回true}}
总结:
当两个基本类型的变量的数值相等,使用==,结果返回true
但对于引用类型变量,它们必须指向同一个对象时,结果才返回true
String类的equal方法只要求两个字符串的字符序列相同,就返回true
5, Object类提供的toString方法总是返回“该对象实现类的类名+@+hashCode值”。
这个返回值不能真正实现“自我描述”的功能,因此如果想要自定义类能实现“自我描述”的功能,必须重写Object类的toString方法。
2013.03.21
- Java基础(七)——面向对象_多态、内部类、其它
- 面向对象_多态、内部类、其它
- “黑马程序员”java面向对象基础_内部类
- 黑马程序员——【Java】面向对象——内部类_匿名内部类
- 【java基础】——java面向对象(下)—多态、内部类、异常、包
- Java基础(面向对象三——多态、内部类)
- JAVA面向对象基础:内部类
- Java面向对象基础--内部类
- 黑马程序员——面向对象_内部类
- 黑马程序员---Java基础---面向对象:多态、内部类、异常
- 面向对象_内部类
- 黑马程序员_面向对象_多态_内部类
- Java基础09天 学习笔记_面向对象(内部类,异常,RuntimeException, 自定义异常)
- 黑马程序员——java基础----面向对象(三)内部类
- 黑马程序员——java基础(面向对象)抽象、接口、内部类
- Java基础——面向对象——继承、抽象类、接口、多态、包、内部类、异常等
- Java面向对象06-多态,内部类
- Java面向对象——(匿名)内部类
- 关于usb_modeswitch编译时错误的问题
- DirectShow 多媒体编程,必须了解的基础知识
- EOC学习笔记
- mysql 分组取最大值的同时获取包含最大值的行的其他字段
- 在ubuntu Dash界面中创建图标
- Java基础(七)——面向对象_多态、内部类、其它
- test
- 【linux】soft or hard lockup
- 第一次周总结
- Linux学习----根目录下主要文件夹(一)
- makefile要点
- pathmunge详解
- c++ 输出二位数组中对角线上的值
- MFC 如何收缩对话框