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