6.3 多态
6.3.1 多态的概念
1、什么是多态?
多态:多种形态,多种类型的形式
两个角度:
(1)一个父类的变量,可以赋值给它各种子类的对象
换句话说,一个父类的变量,可以在运行时体现为多种不同的子类对象
==> 编译时都是父类类型的变量,运行时是各种子类的对象类型
(2)一个子类对象,可以赋值给不同类型的父类变量,
在编译时,编译器识别的类型也不同,可以用.访问的成员也不同。
==> 运行时是同一个子类的类型,但是编译时却呈现为不同的父类类型。
/*
(1)声明父类:Person类
- 包含属性:姓名,年龄,性别,属性私有化,
- 包含get/set方法
- 重写toString方法:例如:姓名:张三,年龄:23,性别:男
*/
public class Person {
private String name;
private int age;
private char gender;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
'}';
}
}
/*
(2)声明子类:Student类,继承Person类
- 新增属性:score成绩,属性私有化,
- 包含get/set方法
- 重写toString方法:例如:姓名:张三,年龄:23,性别:男,成绩:89
*/
public class Student extends Person {
private int score;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return super.toString() + ",成绩:" + score;
}
}
/*
(3)声明子类:Teacher类,继承Person类
- 新增属性:salary薪资,属性私有化,
- 包含get/set方法
- 重写toString方法:例如:姓名:张三,年龄:23,性别:男,薪资:10000
*/
public class Teacher extends Person {
private double salary;
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return super.toString() +",薪资:" + salary;
}
}
public class TestPerson {
public static void main(String[] args) {
Student s = new Student();
s.setName("熊大");
s.setAge(18);
s.setGender('男');
s.setScore(100);
Teacher t = new Teacher();
t.setName("胖虎");
t.setAge(24);
t.setGender('男');
t.setSalary(20000);
Person p = s;//左边是Person类型,右边s是Student,Student是Person的子类
System.out.println(p);//Person{name='熊大', age=18, gender=男},成绩:100
p = t;//此时左边是Person类型,右边t是Teacher,Teacher是Person的子类
System.out.println(p);//Person{name='胖虎', age=24, gender=男},薪资:20000.0
//(1)p变量可以赋值不同子类对象
System.out.println("------------------------");
//(2)同一个学生对象,可以赋值给不同的父类变量
Student student = new Student();
Person person = student;
Object object = student;
System.out.println(student);
System.out.println(person);
System.out.println(object);
}
}
6.3.2 多态引用的概念和表现
2、多态引用的概念?
当父类的变量指向子类的对象时,就叫做多态引用。
父类类型 变量名 = 子类的对象;
3、多态引用后的表现:
编译时类型和运行时类型不一致。
编译时:看左边,是父类类型
运行时:看右边,是子类类型
编译时如果按父类类型处理的话,那么只能访问父类中声明的成员,不能访问子类“扩展”的成员。
运行时是按照实际类型处理的,看new对象的类型,如果是子类的话,并且调用方法时,
那么一定执行子类重写的方法体。如果子类没有重写,仍然执行父类中的方法体。
4、多态的弊端和好处:
(1)编译时,只能调用父类声明的方法,不能调用子类扩展的方法
(2)实现方法的动态绑定
运行时,看“子类”,如果子类重写了方法,一定是执行子类重写的方法体;变量引用的子类对象不同,执行的方法就不同,实现动态绑定。代码编写更灵活、功能更强大,可维护性和扩展性更好了。
当然,如果子类没有重写该方法,那么仍然执行从父类继承的方法。
public class TestPolymorphism {
public static void main(String[] args) {
Person p = new Student();//多态引用
//编译时看左边,是Person类型
p.setName("张三");
p.setGender('男');
p.setAge(23);
//问题:编译时无法访问子类扩展的成员
// p.setScore(100);
System.out.println(p.toString());
//按照编译时,p是Person类型,编译器会去Person类中看是否有toString方法
//运行时,按照new的类型处理,运行时以Student类型处理,执行Student类中重写的toString
Person p2 = new Teacher();
p2.setName("李四");
p2.setAge(24);
p2.setGender('女');
//问题:编译时无法访问子类扩展的成员
// p2.setSalary(15000);
System.out.println(p2.toString());//自动调用Teacher的toString方法
}
}
6.3.3 多态的应用
1、没有多态:死板,不便于维护和扩展
案例:
(1)声明一个Dog类,包含public void eat()方法,输出“狗狗啃骨头”
(2)声明一个Cat类,包含public void eat()方法,输出“猫咪吃鱼仔”
(3)声明一个Person类,
- 包含姓名和宠物属性
- 包含喂宠物吃东西的方法 public void feed(),实现为调用宠物对象.eat()方法
public class Dog {
public void eat(){
System.out.println("狗狗啃骨头");
}
}
public class Cat {
public void eat(){
System.out.println("猫咪吃鱼仔");
}
}
public class Person {
private String name;
private Dog dog;//宠物是狗
public void feed(){//喂养
dog.eat();
}
}
/*
思考:
如果这个代码写完了,用户认为他不喜欢养狗,喜欢养猫。
如果修改成Cat,另一些用户,有喜欢养狗的。
*/
2、有了多态:灵活、便于维护和扩展
案例:
(1)声明一个Pet类,包含public void eat()方法,输出“吃~~~”
(2)声明一个Dog类,继承Pet类,重写public void eat()方法,输出“狗狗啃骨头”
(3)声明一个Cat类,继承Pet类,重写public void eat()方法,输出“猫咪吃鱼仔”
(3)声明一个Person类,
- 包含姓名和宠物属性
- 包含喂宠物吃东西的方法 public void feed(),实现为调用宠物对象.eat()方法
(4)声明一个Person2类,
- 包含姓名和宠物数组属性
- 包含喂宠物吃东西的方法 public void feed(),实现为遍历数组,调用宠物对象.eat()方法
(5)声明一个PetShop类
-包含public Pet buy(String type),根据用户要求的宠物类型不同,返回不同对象
public class Pet {
private String nickname;
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public void eat() {
//啥也不写也可以
System.out.println(nickname + "吃~~");
}
}
public class Dog extends Pet{
@Override
public void eat(){
System.out.println(getNickname() + "狗狗啃骨头");
}
}
public class Cat extends Pet{
@Override
public void eat() {
System.out.println(getNickname() + "吃鱼仔");
}
}
public class Person {
private String name;
private Pet pet;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Pet getPet() {
return pet;
}
public void setPet(Pet pet) {
this.pet = pet;
}
public void feed(){
pet.eat();
//动态邦定,根据pet变量赋值的对象不同,
// 赋值给Dog,就调用Dog的eat方法
// 赋值给Cat,就调用Cat的eat方法
}
}
public class TestPerson {
public static void main(String[] args) {
Person p = new Person();
p.setName("胖虎");
Dog dog = new Dog();
dog.setNickname("旺财");
p.setPet(dog);
p.feed();
Cat cat = new Cat();
cat.setNickname("小白");
p.setPet(cat);
p.feed();
}
}
public class Person2 {
private String name;
private Pet[] pets;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Pet[] getPets() {
return pets;
}
public void setPets(Pet[] pets) {
this.pets = pets;
}
public void feed(){
for(int i=0; i<pets.length; i++){
pets[i].eat();
/*
pets数组的元素,可能是Dog,或Cat,或Pet的其他子类对象
// 如果是Dog对象,就调用Dog的eat方法
// 如果是Cat对象,就调用Cat的eat方法
*/
}
}
}
public class TestPerson2 {
public static void main(String[] args) {
Person2 person2 = new Person2();
person2.setName("熊大");
Pet[] arr = new Pet[2];
arr[0] = new Dog();
arr[1] = new Cat();
arr[0].setNickname("小黑");
arr[1].setNickname("小白");
person2.setPets(arr);
person2.feed();
}
}
public class PetShop {
public Pet buy(String type){
switch (type){
case "狗":
return new Dog();
case "猫":
return new Cat();
}
return null;
}
}
public class TestPetShop {
public static void main(String[] args) {
PetShop ps = new PetShop();
Pet dog = ps.buy("狗");
dog.setNickname("旺财");
dog.eat();
Pet cat = ps.buy("猫");
cat.setNickname("小白");
cat.eat();
}
}
TestPerson.java 的运行结果:
TestPerson2.java 的运行结果
3、多态的好处:灵活、便于维护、扩展、实现方法的动态绑定
1、要声明几个不同类型的图形类,例如:有圆、矩形、三角形等
2、然后要把不同图形类的对象放在一起,按照面积从小到大排序
public class Graphic {
public double area(){
return 0.0;//只是为了保证语法不报错
//因为方法的返回值类型是double,方法体中必须有return 值;的语句结束方法
//否则编译报错。
}
}
public class Circle extends Graphic {
private double radius;
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public double area(){
return Math.PI * radius * radius;
}
@Override
public String toString() {
return "Circle{" +
"radius=" + radius +
",area = " + area() +
'}';
}
}
public class Rectangle extends Graphic {
private double length;
private double width;
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double area(){
return length * width;
}
@Override
public String toString() {
return "Rectangle{" +
"length=" + length +
", width=" + width +
", area=" + area() +
'}';
}
}
public class Triangle extends Graphic {
private double a;
private double b;
private double c;
public double getA() {
return a;
}
/*public void setA(double a) {
this.a = a;
}*/
public double getB() {
return b;
}
/*public void setB(double b) {
this.b = b;
}*/
public double getC() {
return c;
}
/*public void setC(double c) {
this.c = c;
}*/
public void setBase(double a, double b, double c){
if(a>0 && b>0 && c>0 && a+b>c && b+c>a && a+c>b) {
this.a = a;
this.b = b;
this.c = c;
}
}
public double area(){
double p = (a+b+c)/2;
return Math.sqrt(p * (p-a) * (p-b) * (p-c));//海伦公式
}
@Override
public String toString() {
return "Triangle{" +
"a=" + a +
", b=" + b +
", c=" + c +
",area = " + area() +
'}';
}
}
public class TestGraphic {
public static void main(String[] args) {
Circle c = new Circle();
c.setRadius(2.5);
Rectangle r = new Rectangle();
r.setLength(2);
r.setWidth(3);
Triangle t = new Triangle();
t.setBase(3,4,5);
/*Circle[] arr = new Circle[3];
arr[0] = c;
arr[1] = r;//报错,因为r是Rectangle类型,和Circle没有继承关系
arr[2] = t;//报错,因为t是Triangle类型,和Circle没有继承关系*/
Graphic[] arr = new Graphic[3];//创建数组对象,不是图形对象
arr[0] = c;//Graphic是Circle的父类,arr[0]是Graphic类型,c是Circle类型
//父类>子类,子类对象可以赋值给父类的变量,元素。
arr[1] = r;
arr[2] = t;
//遍历输出这些图形
System.out.println("排序之前:");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
//排序
for(int i=1; i<arr.length; i++){
for(int j=0; j<arr.length-i; j++){
if(arr[j].area() > arr[j+1].area()){
Graphic temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
System.out.println("排序之后:");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
6.3.4 向上转型和向下转型
总结:向上转型和向下转型都是针对编译器来说的,对象的运行时类型从头到尾都不会发生改变
1、向上转型和向下转型
1、回忆:
基本数据类型的:
(1)自动类型转换
int a = 15;
double d = a;//自动类型提升
//把a里面的15copy(复制)了一份,放到d中,
//因为a里面的15是以4个字节表示,
//而double需要8个字节,所以要对4个字节的15的二进制进行处理,
//用8个字节表示。
(2)强制类型转换
double d = 1.5;
int a = (int)d;//强制类型转换
//因为d里面的1.5是以8个字节存储,而int类型的a只有4个字节的空间
//需要把复制的1.5的8个字节,处理成4个字节才能放进去
2、向上转型和向下转型
(1)向上转型:可以自动完成,也可以强制完成
自动完成向上转型:
当把子类的对象赋值给父类的变量时,就自动完成向上转型。
Dog d = new Dog();
Animal a = d;//向上转型
//把d变量中存储的首地址,复制了一份给a,字节数没有变化。
//发生变化的是编译器接下来的态度不同
//编译时看d是Dog类型,看a是Animal类型,它们里面存储的地址值完全相同
//如果此时通过a.成员,只能调用Animal里面有的成员,不能调用Dog类扩展的成员。
向上转型的弊端:编译时失去调用子类扩展的成员的能力
为什么要这样写?
为了多态引用,通过父类的变量管理子类的对象。
强制完成向上转型:
(父类类型)子类对象
(父类类型)子类变量
(2)向下转型:只能强制完成
把父类的变量值赋值给子类变量时,必须强制完成。
Animal a = new Dog();
Dog d = (Dog)a;//强制完成,向下上转型
//a变量和d变量中存储的地址值是完全一致的,
//只是编译器识别的a和d的类型不同
向下转型的好处:又可以调用子类扩展的成员
结论:无论是向上转型还是向下转型,对象的本质类型(运行时类型,new)从头到尾都没有发生过变化。
只是编译器在识别变量的类型时,按照不同的类型处理而已。
package com.panghu.zhuanxing;
public class Animal {
public void eat(){
System.out.println("吃~~");
}
}
package com.panghu.zhuanxing;
public class Cat extends Animal {
public void catchMouse(){
System.out.println("猫抓老鼠");
}
}
package com.panghu.zhuanxing;
public class Dog extends Animal {
public void watchHouse(){
System.out.println("狗狗看家");
}
@Override
public void eat(){
System.out.println("狗狗啃骨头");
}
}
package com.panghu.zhuanxing;
//哈士奇
public class Huskie extends Dog {
}
public class TestClassCast {
public static void main(String[] args) {
Dog d = new Dog();
Animal a = d;
System.out.println(a);
System.out.println(d);
// com.atguigu.cast.Dog@1b6d3586
// com.atguigu.cast.Dog@1b6d3586
//a.watchHouse();//编译报错,因为此时a被识别为Animal
d.watchHouse();//编译通过
Animal[] animals = new Animal[2];
animals[0] = d;//向上转型,自动完成
animals[1] = new Cat();//也是向上转型,自动完成
/*
编译时,animals[0]和animals[1] 都是以Animal类型处理
*/
animals[0].eat();//编译时,寻找Animal类中eat()方法
//运行时,如果Dog重写了eat()方法,就执行重写的方法
animals[1].eat();//编译时,寻找Animal类中eat()方法
//运行时,如果Cat重写了eat()方法,就执行重写的方法
// ((Animal)d).watchHouse();//报错
//强制把d在编译时向上转型为Animal类型
//非要让编译器把d类型按照Animal处理
Dog dog = (Dog) animals[0];//向下转型
dog.watchHouse();
}
}
2、ClassCastException 和 instanceof
3、ClassCastException异常
当对象的运行时类型(真实的类型,new的类型) <= 接收的变量的类型,赋值可以成功,
不满足<=的关系,运行时就会发生ClassCastException异常。
Dog类型 < Animal类型(父子类,子类<父类)
Dog类型 = Dog类型(一样是相等)
Huskie类型 < Animal类型
Huskie类型 < Dog类型
Huskie对象可以赋值给Huskie类型、Dog类型和Animal类型
Dog类型对象可以赋值给Dog类型和Animal类型
Animal类型可以只能赋值给Animal类型
4、如何避免这个异常呢?
if(变量 instanceof 类型A)条件满足,再把这个变量向下转为这个类型
这个instanceof的作用是判断某个变量中的对象的运行时类型(new)是否<=类型A,成立,就返回true
public class TestClassCastException {
public static void main(String[] args) {
Animal[] animals = new Animal[2];
animals[0] = new Dog();//向上转型,自动完成
animals[1] = new Cat();//向上转型,自动完成
Dog dog = (Dog) animals[0];//向下转型
dog.watchHouse(); //狗狗看家
if(animals[0] instanceof Cat) {
Cat cat = (Cat) animals[0];//向下转型,编译时,看animals[0]是Animal类型,
//cat是Cat类型,它们是父子类关系,可以,允许向下转型,编译通过
//如果没有if条件,运行时报错ClassCastException类型转换异常
}
Animal animal = new Animal();
if(animal instanceof Dog) {
Dog d2 = (Dog) animal;//向下转型
//编译时,animal是Animal类型,Dog和它是父子类关系,允许向下转型
//编译通过,如果没有if条件,运行时报错ClassCastException类型转换异常
}
Animal a2 = new Huskie();//向上转型
Dog d3 = (Dog) a2;//向下转型
//因为a2编译时是Animal类型,Dog<Animal向下转型,编译通过
//a2中对象的运行时类型是Huskie,huskie<Dog,运行不会报错
}
}
3、getClass
package com.panghu.zhuanxing;
public class TestClass {
public static void main(String[] args) {
Huskie h = new Huskie();
Animal a1 = h;
Dog d1 = h;
//获取一个变量中对象的运行时类型,用getClass方法
System.out.println(h.getClass());//Huskie
System.out.println(a1.getClass());//Huskie
System.out.println(d1.getClass());//Huskie
//instanceof判断的是对象的运行时类型 <= xx类型成立吗
System.out.println(h instanceof Huskie);//true
System.out.println(h instanceof Animal);//true
System.out.println(h instanceof Dog);//true
System.out.println(a1 instanceof Huskie);//true
System.out.println(a1 instanceof Animal);//true
System.out.println(a1 instanceof Dog);//true
System.out.println(d1 instanceof Huskie);//true
System.out.println(d1 instanceof Animal);//true
System.out.println(d1 instanceof Dog);//true
Animal a2 = new Cat();
System.out.println(a2 instanceof Huskie);//false
System.out.println(a2 instanceof Animal);//true
System.out.println(a2 instanceof Cat);//true
System.out.println(a2 instanceof Dog);//false
Animal a3 = new Dog();
System.out.println(a3 instanceof Huskie);//false Dog > Huskie
System.out.println(a3 instanceof Animal);//true
System.out.println(a3 instanceof Cat);//false
System.out.println(a3 instanceof Dog);//true
Animal a4 = new Animal();
System.out.println(a4 instanceof Huskie);//false
System.out.println(a4 instanceof Animal);//true
System.out.println(a4 instanceof Cat);//false
System.out.println(a4 instanceof Dog);//false
}
}
6.3.5 虚方法
1、虚方法:可以被子类重写的方法称为虚方法。
2、当我们通过“对象.虚方法”的格式调用一个方法时,要注意如下的原则:
(1)先看这个对象的“编译时类型”是什么,先从编译时类型(或其父类)中寻找匹配的方法
匹配:
A:方法名相同
B:实参列表要么和形参列表完全一致,要么可以兼容
以实参的“编译时类型”与形参的类型匹配
如果可以找到匹配的方法,编译通过,否则就编译报错。
(2)运行时,在看这个对象的“运行时类型new”是什么,看运行时类型是否重写了刚刚匹配的方法,
如果有重写,就一定执行重写的方法体,
如果没有重写,依然执行刚刚匹配的方法。
3、如果调用的方法不是虚方法(例如,后面会学习的静态方法等不能被子类重写的方法)
或通过 “super.方法”格式发起的方法调用,不适用于上面的规则。
public class TestVirtualMethod {
public static void main(String[] args) {
Father f = new Father();
Son s = new Son();
Daughter d = new Daughter();
MyClass my = new MySub();//多态引用
my.method(f);//my对象.method虚方法,运行结果是sub--father
/*
(1)编译时
my对象的编译时类型是MyClass,在MyClass类中找到匹配的方法
实参是f,f的编译时类型是Father类型,
在MyClass类中有没有名字是method,形参类型是Father的方法,可以找到。
public void method(Father f) {
System.out.println("father");
}
(2)运行时
my对象的运行时类型,看new的类型,是MySub类型。
看一下MySub类中是否有对刚刚匹配的方法进行重写。
发现有重写:
public void method(Father d) {//这个和父类的 method(Father f)是重写关系
System.out.println("sub--father");
}
只要是又重写,就执行重写的方法体。
运行结果是sub--father
*/
my.method(s);
/*
(1)编译时
my对象的编译时类型是MyClass,在MyClass类中找到匹配的方法
实参是s,s的编译时类型是Son类型,
在MyClass类中有没有名字是method,形参类型是Son的方法,可以找到。
public void method(Son s) {
System.out.println("son");
}
(2)运行时
my对象的运行时类型,看new的类型,是MySub类型。
看一下MySub类中是否有对刚刚匹配的方法进行重写。
发现没有重写:
如果没有重写,依然执行刚刚匹配的方法。
运行结果是 son
*/
my.method(d);
/*
(1)编译时
my对象的编译时类型是MyClass,在MyClass类中找到匹配的方法
实参是d,d的编译时类型是Daughter类型,
在MyClass类中有没有名字是method,形参类型是Daughter的方法,没有找到。
在MyClass类中有没有名字是method,形参类型可以兼容Daughter的方法,可以找到。
public void method(Father f) {
System.out.println("father");
}
(2)运行时
my对象的运行时类型,看new的类型,是MySub类型。
看一下MySub类中是否有对刚刚匹配的方法进行重写。
发现有重写:
public void method(Father d) {//这个和父类的 method(Father f)是重写关系
System.out.println("sub--father");
}
只要是又重写,就执行重写的方法体。
运行结果是sub--father
*/
System.out.println("-------------------");
MySub sub = new MySub();
sub.method(d);//对象.虚方法
/*
(1)编译时,
看sub的编译时类型,是MySub类型,直接去MySub类中匹配方法。
实参d是Daughter类型,可以找到最匹配的方法
public void method(Daughter d) {
System.out.println("daughter");
}
(2)运行时
看sub的运行时类型,仍然是MySub类型,和编译时类型一致,
刚刚匹配谁,就执行谁。
*/
sub.method(s);
/*
(1)编译时
sub的编译时类型是MySub,在MySub类中找匹配的方法。
此时参与匹配的方法:
public void method(Son s) {
System.out.println("son");
}
public void method(Father d) {
System.out.println("sub--father");
}
public void method(Daughter d) {
System.out.println("daughter");
}
最终和method(Son s)匹配上了。
(2)运行时
sub的运行时类型仍然是MySub,刚刚匹配谁就执行谁
*/
}
}
class MyClass{
public void method(Father f) {
System.out.println("father");
}
public void method(Son s) {
System.out.println("son");
}
}
class MySub extends MyClass{
public void method(Father d) {//这个和父类的 method(Father f)是重写关系
System.out.println("sub--father");
}
public void method(Daughter d) {//这个和父类的两个method是重载关系
System.out.println("daughter");
}
}
class Father{
}
class Son extends Father{
}
class Daughter extends Father{
}
6.3.6 xx.成员变量访问只看xx的编译时类型(面试题)
当多态引用时,通过xx.成员变量方式访问成员变量,此时只看xx的编译时类型。
public class TestVariable {
public static void main(String[] args) {
Father f = new Son();
System.out.println("f.a = " + f.a);//1
//f的编译时类型是Father
Son s = new Son();
System.out.println("s.a = " + s.a);//2
//s的编译时类型是Son
/*
无论是f还是s对象,堆内存中都有2个a。
*/
System.out.println("((Son)f).a = " + ((Son)f).a );//对f向下转型
System.out.println("((Father)s).a = " + ((Father)s).a);//对s向上转型
}
}
class Father{
int a = 1;
}
class Son extends Father{
int a = 2;//子类声明了一个和父类重名的成员变量
//这种情况,实际开发中是要极力避免的
}
//原则:xx.成员变量,只看xx的编译时类型
//编译时类型是什么?(1)如果没有()强转的语法,那么变量的编译时类型就是声明它的时候左边的类型
// (2)如果有()强转的语法,那么就以()中强转的类型为准
public class Test1 {
public static void main(String[] args) {
A a = new B();//多态引用
System.out.println(a.num);//1
//a的编译时类型A
System.out.println(((B)a).num);//2
//((B)a)的编译时类型B
System.out.println(((A)((B)a)).num);//1
//((A)((B)a))的编译时类型A
System.out.println("-------------------");
B b = new B();//本态引用
System.out.println(b.num);//2
//b的编译时类型B
System.out.println(((A)b).num);//1
//((A)b)的编译时类型A
System.out.println(((B)((A)b)).num);//2
//((B)((A)b))的编译时类型B
}
}
class A{
int num = 1;
}
class B extends A{
int num = 2;
}
6.3.7 通过方法访问成员变量遵循就近原则
通过方法去访问成员变量时,该如何确定是哪个成员变量呢?
(1)先确定运行时执行哪个方法
(2)再看方法体中的代码,变量遵循就近原则
public class Father {
private int a = 1;
private int b = 1;
public int getA() {
return a;//此处的a一定是Father类声明的a
}
public int getB() {
return b;//此处的b一定是Father类声明的b
}
}
public class Son extends Father {
private int a = 2;
private int b = 2;
private int c = 2;
@Override
public int getA() {
return a;//此处的a一定是Son类声明的a,就近原则
}
public int getC() {
return c;//此处的c一定是Son类声明的c
}
}
public class TestMethodVariable {
public static void main(String[] args) {
Father f = new Son();
System.out.println(f.getA());//2
/*
f的编译时类型是Father,去Father匹配,可以找到getA()
f的运行时看Son类,有重写,执行重写的
f.getA()运行时Son类中getA()
*/
System.out.println(f.getB());//1
/*
f的编译时类型是Father,去Father匹配,可以找到getB()
f的运行时类型是Son,但是Son类中没有重写getB()
f.getB()运行时Father类的getB()
*/
// System.out.println(f.getC());//报错
/*
f.getC()为什么报错?
因为f的编译时类型是Father,
此时Father类中没有getC()方法,
编译不通过
*/
System.out.println(((Son)f).getA());//2
/*
((Son)f)的编译时类型是Son,去Son类中匹配的,
((Son)f)的运行时类型也是Son,仍然执行Son的getA()
*/
System.out.println(((Son)f).getB());//1
/*
((Son)f)的编译时类型是Son,去Son类中找没找到,可以找到父类中getB()
((Son)f)的运行时类型也是Son,因为Son类没有重写,仍然执行父类中getB()
*/
System.out.println(((Son)f).getC());//2
/*
((Son)f)的编译时类型是Son,去Son类中找getC(),找到了
((Son)f)的运行时类型也是Son,仍然执行Son类中getC()
*/
}
}
标签:Java,void,System,笔记,学习,类型,println,public,out
From: https://blog.51cto.com/u_16213911/7057045