继承和多态
继承
定义
多个类中存在存在相同的属性和行为,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只需继承这个类即可。
通过extends关键字可以实现类与类的继承:
格式: class 子类名 extends 父类名{}
被继承的类称为父类或基类、超类。
好处
提高代码的复用性:多个类相同的成员可以放到同一个类中;
提高代码的可维护性:如果功能的代码需要修改,修改一处即可,继承的类中自动被修改。
特点
1、只能单继承,不支持多继承:即一个类只能有一个父类,不能有多个父类;
2、可以多层继承: class A{};--> cass B extends A();-->class C extends B{}.
注意事项
1、子类只能继承父类中所有非私有成员(成员方法和成员变量);
2、子类不能继承父类的构造方法,但可通过super关键字去访问父类的构造方法;
3、不要为了部分功能而去继承。
案例:
1 class Father{ 2 private int num1=10; 3 public int num2=20; 4 public void show(){ 5 System.out.println(num2); 6 System.out.println(num1); 7 } 8 private void method(){ 9 System.out.println(num2); 10 System.out.println(num1); 11 12 } 13 } 14 class Son extends Father{ 15 public void function(){ 16 // System.out.println(num1);//不能继承私有的num1 17 System.out.println(num2); 18 } 19 } 20 class Extends1Demo{ 21 public static void main(String[]args){ 22 Son s=new Son(); 23 s.show(); 24 // s.method();//不能继承私有方法 25 s.function(); 26 } 27 }
继承中成员变量关系
在子类方法中访问一个变量 :
1、首先在子类局部范围找,即方法内部;
2、然后在子类的成员范围找,也就是子类的成员变量 ;
3、最后在父类的成员变量范围找(不能访问父类的局部范围) ;
4、如果还没有就报错。
1 class Father { 2 public int num = 10; 3 } 4 5 class Son extends Father { 6 public int num = 20; 7 public void show() { 8 int num = 30; 9 System.out.println(num); //30(就近原则) 10 System.out.println(this.num); //20(this修饰访问成员变量) 11 System.out.println(super.num); //10 12 } 13 }
super关键字
和this使用相识, this 代表本类当前对象的引用 ,super 代表父类存储空间的标识(也可理解为父类对象的引用)
使用方式:
访问成员变量:
this.成员变量 ;super.成员变量(访问父类的成员变量,但不能访问父类的private变量)
访问静态成员时,也可用:父类名.静态成员
访问成员方法,(注意没有"."): this(...)访问本类的构造方法,根据括号内参数来区分访问哪个;
super(...)访问父类的构造方法,根据括号内参数来区分访问哪个。
访问成员方法: this.成员方法(),super.成员方法()
继承中构造方法的关系
1、子类中的所有的构造方法默认都会访问父类中空参数的构造方法,除非显示使用super/this调用了父类或者本类的其他构造方法;
2、在类中对本类或者父类构造方法的调用,只能是在构造方法中,不能在实例方法中调用构造方法(更不能在类方法中),原因:
-->1、实例方法被调用时,说明实例对象已经被创建完了,此时不能再使用this、super去初始化本地实例或是父类实例
-->2、类方法是在本类加载的时候就已经加载了,这时候实例对象还没有被创建出来。
子类初始化之前,一定要先完成父类数据的初始化(一个对象的创建意味着它的所有的父类都会被创建出来);
子类构造方法的第一条语句:
如果是this(...)表明调用的是本类的另一个构造方法,在另一个构造方法中还可以继续使用this(...)调用本类其他的构造方法,如果有多个构造方法的话,可以继续调用下去,但是不能递归调用,最终总会有一个构造方法,第一条语句不是this()了。
构造方法不能递归调用:如下错误
1 class A { 2 public A(int i){ 3 this(1 ,2); 4 } 5 public A(int a ,int b){ 6 this(2); 7 } 8 }
子类中所有的构造方法默认都会访问父类中空参数的构造方法(注意:子类每一个构造方法的第一条语句默认都是:super();除非显示this、super);
如果父类没有空参构造,子类的构造方法中必须显示调用父类带参构造super(...);
且super(...)或者this(...)必须出现在构造方法第一条语句上,否则,会出现父类数据的多次初始化。
父类,子类代码块的执行顺序
构造代码块顺序:
父类的静态代码块-->子类的静态代码块-->父类的构造代码块-->父类构造方法-->子类构造代码块-->子类的构造方法
1 class Father{ 2 int age; 3 static{ 4 System.out.println("static code block"); 5 } 6 { 7 System.out.println("code block 1"); 8 } 9 public Father(){ 10 System.out.println("no arguments" ); 11 } 12 public Father(int age){ 13 System.out.println("hava arguments"); 14 } 15 { 16 System.out.println("code block 2"); 17 } 18 } 19 class Son extends Father{ 20 int age; 21 static{ 22 System.out.println("son static code block"); 23 } 24 { 25 System.out.println("son code block 1"); 26 } 27 public Son(){ 28 System.out.println("son -no arguments" ); 29 } 30 public Son(int age){ 31 System.out.println("son hava arguments"); 32 } 33 { 34 System.out.println("son code block 2"); 35 } 36 } 37 class FatherDemo{ 38 public static void main(String[]args){ 39 40 Son s=new Son(); 41 } 42 }
结果:
类加载过程和对象初始化过程
类加载的时候执行的是类的初始化(静态代码块、有继承关系的类,从上到下) 对象初始化过程:
1、先执行的是父类成员变量的初始化(如果父类成员是另一个对象的引用的话,则先对引用的类加载,然后成员变量初始化,然后成员变量初始化,然后构造代码块、然后构造方法;完成后,将引用赋给成员变量),再父类构造代码块,再父类构造方法。
2、然后子类成员变量的初始化(如果是另一个对象的引用的话,过程如上),子类构造代码块,子类构造方法。
一个类中不可包含自身实例对象的引用。 当此类对象初始化的时候,先加载类,然后对成员变量赋初始值,然后是显式赋值,在显式赋值的时候发现是本类的构造方法的调用,再去调构造方法; 而本类的构造方法还是对成员变量赋值,在赋值的时候还去调用构造方法,造成了构造方法的迭代
继承中成员方法的使用
子父类中存在同名和不同名的成员方法 通过子类对象去访问一个实例方法:
1、首先在子类中找(是否子类进行了重写,或者是子类特有的方法)
2、然后在父类中找(子类没有重写,而是从父类继承而来的)。
方法的重写
定义:子类中出现和父类一模一样的方法声明,称为方法覆盖(override)/重写(overwrite).
应用条件: 当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样既沿袭父类的功能,又有子类特有的内容;
方法的重写时多态实现的条件。
重写注意事项:
1、父类中私有方法不能被重写,编译报错
2、子类重写父类方法时,访问权限不能更低(后面讲),否则编译报错
3、子类重写父类方法,返回值类型可以相同,或者是父类返回值类型的子类型
4、父类的实例方法(非静态方法),子类不能重新定义为静态方法
5、父类静态方法(类方法),子类也必须通过静态方法进行“重写”(虽然编译和使用都不报错,但其实这个算不上方法重写)
6、子类中重写父类方法,按照重写的原则(访问权限不能变小,返回值同类或者子类,方法名相同,形参列表相同);否则子类中定义的同名方法就是方法的重载(继承而来的方法和子类定义的方法构成重载),重载就必须让参数列表不同。如果子类方法只与继承自父类的方法返回值不同,不能构成重载
7、子类如果想重写父类的方法,最好是让方法的签名一模一样
(重写方法的一个重要用途就是:父类的引用能够指向子类的方法,但是静态方法的“重写”,在多态中依然调用的是父类的方法,所以,从这个角度上来讲,子类对父类的静态方法的重写不能算是真正方法的重写)。
注意
父类的私有变量子类不可继承,但在子类的构造方法中,使用super调用父类的构造方法,那么,对成员变量的赋值,到底是赋给了父类的成员变量?还是子类的成员变量?
可以理解为:整个父类的对象都在子类对象中,值确实是赋给了父类的成员变量,但是子类通过方法可以使用,和自己的变量没有区别。这就是继承的好处。
final关键字
final关键字是最终的意思,可以修饰类、成员变量、成员方法
特点:
修饰类:类不能被继承(不能放在extends)之后 修饰变量,变量变成常量,只能被赋值一次,不论子类还是本类,都不能被修改;
修饰方法:方法不能被重写(子类有使用权,没有修改权);
修饰局部变量: 在方法内部,该变量不可以被改变;
在方法声明上,分基本类型和引用类型两种:
基本类型:这个参数的值不能被改变;
引用类型:这个参数指向的地址值不能被改变。
1 public void testFinalVar(final int i){ 2 //i = 1; // error,不能再对final变量赋值 3 } 4 public void testFinalVar(final String str){ 5 //str = null; //error不能再对final变量赋值 6 }
赋值情况:
类中非static的final变量(实例final变量)可以在声明的时候赋值,如果声明的时候没有赋值的话,就必须在以下两个地方赋值,一个是构造代码块中,一个是构造方法中。如果这两个地方也没有赋值的话,编译报错。
如果是static修饰的final变量(类变量)的话,则只能在两个地方赋值:声明的时候,或者是在静态代码块中。
经过验证: 若类中的final成员变量在声明时没有赋值,并且存在多个构造方法的话,则在每个构造方法中都应该显示的为这个final变量赋值(可以不同),或者可以抽取到构造代码块中进行赋值; 多个构造方法中,不能互相调用。
多态(polymorphism)
定义
多态:某一个事物,在不同时刻表现出来的不同状态。
事物的引用分为两种类型:编译时类型,声明时指定的类型;运行时类型,实际赋给这个变量的对象的类型。
如: Student s = new Student();
等号左边就是编译时的类型,s在编译时指定的类型是Student,等号右边是运行时实际赋值给这个变量的值。 如果等号左右两边的类型不一致,就有可能出现了多态。
父类引用不能调用子类特有的方法,因为使用了父类的引用,就代表站在父类的角度来看待当前的子类对象,只能看到从父类继承而来的特性,或者是子类重写的父类的方法。成员变量没有多态性,只能看到父类的成员变量。
多态的前提条件
1、有继承关系(没有继承关系的话,不同类型的变量是不能赋值的)
2、有方法重写(没有方法的重写的话,始终调用的是父类的方法)
3、有父类引用指向子类对象(不使用父类的引用的话,始终调用的是子类的方法)
成员访问特点:
成员变量
1、编译看左边,运行看左边(父类的引用始终访问的是父类的变量,不论是不是static);
2、即使子类有同名的变量,访问的也是父类的变量。
成员方法
1、编译看左边,运行看右边(父类的引用运行时访问的是子类重写的方法)
静态方法
1、编译看左边,运行看左边(父类的引用始终访问的是父类的静态方法)
2、所以前面说静态方法不能算方法的重写
总结:
成员变量和静态方法没有多态性
只有被子类重写的成员方法才有多态性