解析JavaSE的继承和多态
作者:小小茶花女
1. 继承
1. 子类继承了父类,获得父类的全部Field和方法。
子类Student类继承父类,将可以获得父类的全部Field和方法
public class Person { public int age; public void show(){ System.out.println("调用了父类中的show()方法"); } }
public class Student extends Person { public static void main(String[] args) { Student student = new Student(); student.name = "张三"; student.age=18; // 调用父类中的show()方法 student.show(); } }
2. 子类继承了父类,额外增加新的Field和方法
继承了父类中的属性和方法,同时新增一个show1()方法
public class Person { public int age; public void show(){ System.out.println("调用了父类中的show()方法"); } }
public class Student extends Person { public static void show1(){ System.out.println("调用了子类中的show1()方法"); } public static void main(String[] args) { Student student = new Student(); // 调用父类中的show()方法 student.show(); // 调用子类中的show1()方法 student.show1(); } }
3. 子类继承了父类,重写父类中的方法
1、这种子类包含与父类同名方法的现象被称为方法重写,也被称为方法覆盖(Override)。可以说子类重写了父类的方法,也可以说子类覆盖了父类的方法。
public class Person { public int age; public void show(){ System.out.println("调用了父类中的show()方法"); } }
public class Student extends Person { public void show(){ System.out.println("调用了子类中的show()方法"); } public static void main(String[] args) { Student student = new Student(); // 调用了子类中的show()方法 student.show(); } }
2、注意:方法的重写要遵循“两同两小一大”规则
(1) “两同”即方法名相同、形参列表相同;
(2) “两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
(3) “一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等;
(4) 尤其需要指出的是,覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法;
public class Person { public int age; public void show(){ System.out.println("调用了父类中的show()方法"); } }
public class Student extends Person { // 编译报错,因为父类中的show()方法不是static修饰的,子类就不能使用static修饰 public static void show(){ System.out.println("调用了子类中的show()方法"); } }
3、注意:如果父类方法具有private访问权限,则该方法对其子类是隐藏的,因此其子类无法访问该方法,也就是无法重写该方法。
如果子类中定义了一个与父类private方法具有相同的方法名、相同的形参列表、相同的返回值类型的方法,依然不是重写,只是在子类中重新定义了一个新方法。
public class Person { public int age; private void show(){ System.out.println("调用了父类中的show()方法"); } }
public class Student extends Person { // show()方法是private方法,子类不可以访问该方法,因此可以添加static关键字 public static void show(){ System.out.println("调用了子类中的show()方法"); } }
4、注意:当子类覆盖了父类方法后,访问的便是子类中被覆盖的方法,将无法访问父类中被覆盖的方法。
4. super限定,在子类调用父类中被覆盖的方法
1、如果需要在子类方法中调用父类被覆盖的实例方法,则可使用super限定来调用父类被覆盖的实例方法。
public class Person { public int age; public void show(){ System.out.println("调用了父类中的show()方法"); } }
public class Student extends Person { public void show(){ System.out.println("调用了子类中的show()方法"); } public void test(){ // 调用了父类中的show()方法 super.show(); } public static void main(String[] args) { Student student = new Student(); student.test(); } }
2、如果需要在子类方法中调用父类被覆盖的类方法,则可使用super限定来调用父类被覆盖的类方法。
public class Person { public int age; public static void show(){ System.out.println("调用了父类中的show()方法"); } }
public class Student extends Person { public static void show(){ System.out.println("调用了子类中的show()方法"); } public void test(){ // 调用了父类中的show()方法 Person.show(); } public static void main(String[] args) { Student student = new Student(); student.test(); } }
3、super是Java提供的一个关键字,super用于限定该对象调用它从父类继承得到的Field或实例方法。
正如this不能出现在static修饰的方法中一样,super也不能出现在static修饰的方法中。static修饰的方法是属于类的,该方法的调用者可能是一个类,而不是对象,因而super限定也就失去了意义。
public class Person { public int age; public void show(){ System.out.println("调用了父类中的show()方法"); } }
public class Student extends Person { public void show(){ System.out.println("调用了子类中的show()方法"); } public void test(){ // 调用了父类中的show()方法 super.show(); } public static void main(String[] args) { Student student = new Student(); student.test(); // 编译报错,因为super不能出现在static修饰的方法中 super.show(); } }
4、如果子类定义了和父类同名的Field,则会发生子类Field隐藏父类Field的情形。
在正常情况下,子类里定义的方法默认会访问到子类中定义的Field,无法访问到父类中被隐藏的Field,在子类定义的实例方法中可以通过super来访问父类中被隐藏的Field
public class Person { public int age=5; }
public class Student extends Person { private int age = 10; public void getOwner(){ System.out.println(age); } public void getBase(){ System.out.println(super.age); } public static void main(String[] args) { Student student = new Student(); // 10 student.getOwner(); // 5 student.getBase(); } }
如果在某个方法中访问名为age的Field,但没有显式指定调用者,则系统查找a的顺序为:
(1) 查找该方法中是否有名为age的局部变量;
(2) 查找当前类中是否包含名为age的Field;
(3) 查找age的直接父类中是否包含名为age的Field,依次上溯age的所有父类,直到java.lang.Object类,如果最终不能找到名为age的Field,则系统出现编译错误。
5、子类不会获得父类的构造器,但子类构造器里可以调用父类构造器的初始化代码
(1) 在一个构造器中调用另一个重载的构造器使用this调用来完成,在子类构造器中调用父类构造器使用super调用来完成;
(2) 如果在构造器中使用super,则super用于限定该构造器初始化的是该对象从父类继承得到的Field,而不是该类自己定义的Field;
public class Person { public String name; public int age; public Person(String name){ this.name = name; } public Person(String name,int age){ this.name = name; this.age = age; } }
public class Student extends Person { private String color; // super调用的是其父类的构造器,而this调用的是同一个类中重载的构造器 // 使用super调用父类构造器也必须出现在子类构造器执行体的第一行,所以this调用和super调用不会同时出现 public Student(String name,int age){ super(name); this.age = age; } public Student(String name,int age,String color){ // 通过super调用父类构造器完成初始化过程 this(name,age); this.color = color; } public static void main(String[] args) { Student student = new Student("张三",18,"红色"); } }
2. 多态
Java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态。
public class Parent { public int book=10; public void test(){ System.out.println("父类中的test()方法"); } public void parent(){ System.out.println("父类中的parent()方法"); } }
public class Sun extends Parent { public String book = "这是一本好书"; public void test(){ System.out.println("子类中的test方法"); } public void sun(){ System.out.println("子类中的sun方法"); } public static void main(String[] args) { // 编译时类型和运行时类型一致,不存在多态 Parent parent = new Parent(); // 10 System.out.println(parent.book); // 父类中的test()方法 parent.test(); // 父类中的parent()方法 parent.parent(); // 编译时类型和运行时类型一致,不存在多态 Sun sun = new Sun(); // 这是一本好书 System.out.println(sun.book); // 子类中的test方法 sun.test(); // 子类中的sun方法 sun.sun(); // 编译时类型和运行时类型不一致,存在多态 // p变量的编译时类型是Parent,而运行时类型是Sun Parent p = new Sun(); // 对象的Field则不具备多态性 // 10 System.out.println(p.book); // 子类中的test方法 p.test(); // 父类中的parent()方法 p.parent(); // Parent类中没有sun()方法,编译报错 p.sun(); } }
当把一个子类对象直接赋给父类引用变量时,例如上面的Parent p = new Sun();,这个p引用变量的编译时类型是Parent,而运行时类型是sun :
(1) Java允许把一个子类对象直接赋给一个父类引用变量,无须任何类型转换,这种被称为向上转型,向上转型由系统自动完成;
(2) 当调用该引用变量的test方法时,实际执行的是Sun类中覆盖后的test方法,这就是多态;当运行时调用该引用变量的方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征,这就可能出现:相同类型的变量、调用同一个方法时呈现出多种不同的行为特征,这就是多态。
(3) 与方法不同的是,对象的Field则不具备多态性,通过引用变量来访问其包含的实例Field时,系统总是试图访问它编译时类型所定义的Field,而不是它运行时类型所定义的Field;
3. 引用变量的强制类型转换
编写Java程序时,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制类型转换成运行时类型。
(1) 基本类型之间的转换只能在数值类型之间进行,这里所说的数值类型包括整数型、字符型和浮点型。但数值类型和布尔类型之间不能进行类型转换。
(2) 引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出现错误。
考虑到进行强制类型转换时可能出现异常,因此进行类型转换之前应先通过instanceof运算符来判断是否可以成功转换。
(1) instanceof运算符的前一个操作数通常是一个引用类型变量,后一个操作数通常是一个类或者接口,它用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是,则返回true,否则返回false;
(2) 在使用instanceof运算符时需要注意:instanceof运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则会引起编译错误;
(3) instanceof运算符的作用是:在进行强制类型转换之前,首先判断前一个对象是否是后一个类的实例,是否可以成功转换,从而保证代码更加健壮;
(4) instanceof和(type)是Java提供的两个相关的运算符,通常先用instanceof判断一个对象是否可以强制类型转换,然后再使用(type)运算符进行强制类型转换,从而保证程序不会出现错误;
public class Main { public static void main(String[] args) { Object object = "hello"; if(object instanceof String){ String s = (String) object; } } }
4. 面试题
1、Java中实现多态的机制是什么?
Java中的多态靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。
2、谈谈你对多态的理解?
多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在程序运行期间才能决定。
因为在程序运行时才确定具体的类,这样,不用修改源代码,就可以让引用变量绑定到各种不同的对象上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!