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); //输出6 bc.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的子类,所以返回true System.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)); //此处返回true char ch='A'; System.out.println("65和A是否相等? "+(it==ch)); //此处返回true String str1=new String("hello"); String str2=new String("hello"); System.out.println("str1和str2是否相等? "+(str1==str2)); //此处返回false System.out.println("str1和str2是否相等? "+str1.equals(str2)); //此处返回true } }
总结:
当两个基本类型的变量的数值相等,使用==,结果返回true
但对于引用类型变量,它们必须指向同一个对象时,结果才返回true
String类的equal方法只要求两个字符串的字符序列相同,就返回true
5, Object类提供的toString方法总是返回“该对象实现类的类名+@+hashCode值”。
这个返回值不能真正实现“自我描述”的功能,因此如果想要自定义类能实现“自我描述”的功能,必须重写Object类的toString方法。
2013.03.21