多态
文章目录
前言
实现方法重写,深入理解继承相关概念,了解Object类,使用重写实现多态机制,使用向上、向下转型以及使用instanceof运算符
一、方法重写的概念
父类中定义的方法,无法输出子类中特有的属性
方法的重写或方法的覆盖
1)子类根据需求对从父类继承的方法
2)重写时,可以用super.方法的方式来保留父类的方法
3)构造方法不能被重写
二、方法重写的规则
1)方法名相同
2)参数列表相同
3)返回值类型相同或者是其子类
4)访问权限不能严于父类
5)父类的静态方法不能被子类覆盖为非静态方法,父类的非静态方法不能被子类覆盖为静态方法
(保持一致)
6)子类可以定义与父类同名的静态方法,以便在子类中隐藏父类的静态方法
(注:静态方法中无法使用super)
7)父类的私有方法不能被子类覆盖
8)不能抛出比父类方法更多的异常
三、方法重写与重载的比较
四、方法重写案例
public class Pet{
// 定义Dog类、Penguin类、Cat类、...等类中相同的代码
private String name;
private int health;
private int love;
public Pet() {
}
public Pet(String name, int health, int love) {
this.name = name;
this.health = health;
this.love = love;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getHealth() {
return health;
}
public void setHealth(int health) {
this.health = health;
}
public int getLove() {
return love;
}
public void setLove(int love) {
this.love = love;
}
// 定义一个方法输出对象的name、health、love属性值
public void printInfo(){
System.out.println("昵称:"+this.getName()+",健康值:"+this.getHealth()+",亲密度:"+this.getLove());
}
}
public class Dog extends Pet {
// 这里只需要定义Dog类中特有的属性
private String strain; // 品种
public Dog() {
}
public Dog(String name, int health, int love, String strain) {
super(name, health, love); // super关键字
this.strain = strain;
}
public String getStrain() {
return strain;
}
public void setStrain(String strain) {
this.strain = strain;
}
// 重新定义输出dog类对象信息的方法
public void printInfo(){
//对于name、health、love属性值,父类Pet中已经做出了输出操作,所有可以调用父类Pet中的printInfo()方法
super.printInfo();
// 加一个输出Dog类中特有属性
System.out.println("品种:"+this.getStrain());
}
}
public class Test {
public static void main(String[] args) {
// 使用有参构造方法创建Dog类对象
Dog dog1 = new Dog("来福",100,100,"哈士奇");
// 调用Pet类中的printInfo()方法输出dog1对象的信息
// 父类Pet中的printInfo()方法只能输出dog1对象的name、health、love的属性值
// 不能够输出dog1对象的strain信息,也就说明Pet类中的printInfo()方法不足以满足子类使用,这时候可以在子类中重新定义输出方法
dog1.printInfo();
}
}
五、Object类
Object类是所有类的父类
Object类被子类经常重写的方法
重写toString()方法
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
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 class StudentTest {
public static void main(String[] args) {
// 创建两个Student对象
Student student1 = new Student("张三",20);
Student student2 = new Student("张三",20);
// 直接输出两个对象
System.out.println("student1 = " + student1);
System.out.println("student2 = " + student2);
// 输出结果
// student1 = methodagain.demo02.Student@1b6d3586
// student2 = methodagain.demo02.Student@4554617c
System.out.println("------ ------ ------ ------ ------ ------");
// 通过Student类对象调用toString()方法输出信息
String result1 = student1.toString(); // Student类中没有,默认继承Object类中的toString()方法
String result2 = student2.toString();
System.out.println("result1 = " + result1);
System.out.println("result2 = " + result2);
// 输出结果
// result1 = methodagain.demo02.Student@1b6d3586
// result2 = methodagain.demo02.Student@4554617c
System.out.println("------ ------ ------ ------ ------ ------");
// getClass().getName() + '@' + Integer.toHexString(hashCode())
// 自行调用方法实现输出对象的地址值
String result3 = student1.getClass().getName() + '@' + Integer.toHexString(student1.hashCode());
System.out.println("result3 = " + result3);
// 输出结果
// result3 = methodagain.demo02.Student@1b6d3586
/*
* 上面代码的操作输出的都是对象在内存中的地址值,输出无意义
* 我们希望直接输出对象名或者通过对象名调用toString()方法,输出的是对象的所有属性值
* 由此说明,父类(Object)中的toString()方法不足以满足子类的使用,所以可以对父类中的toString()方法进行重写
*/
}
}
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
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 String toString() {
return "qwert";
}*/ // 第一次重写toString()方法
public String toString() {
return "姓名:"+this.getName()+"年龄:"+this.getAge();
} // 第二次重写toString()方法
}
public class StudentTest {
public static void main(String[] args) {
// 创建两个Student对象
Student student1 = new Student("张三",20);
Student student2 = new Student("张三",20);
// 直接输出两个对象
System.out.println("student1 = " + student1);
System.out.println("student2 = " + student2);
// 输出结果
// student1 = qwert 第一次重写输出结果
// student2 = qwert
// student1 = 姓名:张三年龄:20 第二次重写输出结果
// student2 = 姓名:张三年龄:20
System.out.println("------ ------ ------ ------ ------ ------");
// 通过Student类对象调用toString()方法输出信息
String result1 = student1.toString(); // Student类中没有,默认继承Object类中的toString()方法
String result2 = student2.toString();
System.out.println("result1 = " + result1);
System.out.println("result2 = " + result2);
// 输出结果
// result1 = qwert
// result2 = qwert
// result1 = 姓名:张三年龄:20
// result2 = 姓名:张三年龄:20
System.out.println("------ ------ ------ ------ ------ ------");
// getClass().getName() + '@' + Integer.toHexString(hashCode())
// 自行调用方法实现输出对象的地址值
String result3 = student1.getClass().getName() + '@' + Integer.toHexString(student1.hashCode());
System.out.println("result3 = " + result3);
// 输出结果
// result3 = methodagain.demo02.Student@1b6d3586
// 这里严格调用输出地址的方法,所以输出还是地址值
}
}
六、多态的概念和案例
1、多态案例
// 定义父类Pet类,默认继承Object类
public class Pet {
// 定义子类中共有的属性和方法
private String name;
private int health;
private int love;
public Pet() {
}
public Pet(String name, int health, int love) {
this.name = name;
this.health = health;
this.love = love;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getHealth() {
return health;
}
public void setHealth(int health) {
if (health > 0) {
this.health = health;
} else {
this.health = 60;
}
}
public int getLove() {
return love;
}
public void setLove(int love) {
this.love = love;
}
// 重写toString()方法
@Override
public String toString() {
return "Pet{" +
"name='" + name + '\'' +
", health=" + health +
", love=" + love +
'}';
}
// 定义一个方法输出宠物的name health love属性值
// this.getName() 与 this.name 效果一样
// 因为private修饰属性在本类中可以调用
public void printInfo() {
System.out.println("昵称:"+this.getName()+
",健康值:"+this.getHealth()+
",亲密度:"+this.getLove());
}
}
public class Dog extends Pet {
// 定义Dog类中特有的属性
private String strain;
public Dog() {
// 默认调用父类Pet类中的无参构造方法
}
public Dog(String name, int health, int love, String strain) {
super(name, health, love);
this.strain = strain;
}
public String getStrain() {
return strain;
}
public void setStrain(String strain) {
this.strain = strain;
}
@Override
public String toString() {
return "Dog{" +
"strain='" + strain + '\'' +
'}';
}
// 重写Pet类中的printInfo()方法
public void printInfo() {
// 调用父类的printInfo
super.printInfo();
// 添加输出Dog类中的strain属性值
System.out.println("品种:"+this.getStrain());
}
public void eat() {
System.out.println("狗狗吃狗粮");
}
}
public class Penguin extends Pet {
private String sex;
public Penguin() {
}
public Penguin(String name, int health, int love, String sex) {
super(name, health, love);
this.sex = sex;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Penguin{" +
"sex='" + sex + '\'' +
'}';
}
// 重写Pet父类中的printInfo()方法
public void printInfo() {
super.printInfo();
// 添加输出Penguin类中sex属性的语句
System.out.println("性别:"+this.getSex());
}
// 定义一个方法
public void swimming() {
System.out.println("企鹅会游泳");
}
}
2、向上转型
<父类型> <引用变量名> = new <子类型>();
注意:
1)此时通过父类引用变量调用的方法是子类覆盖或继承父类的方法,不是父类的方法
2)此时通过父类引用变量无法调用子类特有的方法
public class Test02 {
public static void main(String[] args) {
// 创建Dog对象
// 父类的引用(对象名/变量名)指向子类的实例(对象)
// 即:向上转型
Pet pet = new Dog("一千",100,100,"萨摩耶");
// 写的时候是从左向右写,看的时候是从右向左看
// 使用pet对象调用printInfo()方法,调用的是Dog类重写后的printInfo()方法,也能输出品种
pet.printInfo();
// 输出结果
// 昵称:一千,健康值:100,亲密度:100
// 品种:萨摩耶
System.out.println("------ ------ ------");
// 将pet引用指向Penguin类对象
pet = new Penguin("帝王",100,100,"母");
// 使用pet对象调用printInfo()方法,调用的是Penguin类重写后的printInfo()方法,也能输出性别
pet.printInfo();
// 输出结果
// 昵称:帝王,健康值:100,亲密度:100
// 性别:母
// 方法重写是实现多态的前提
}
}
3、多态的概念
同一个父类引用,指向的不同的子类实例,执行操作不一样
操作不一样,体现在子类重写的方法中,所以说方法重写是实现多态的前提
4、向下转型
<子类型> <引用变量名> = (<子类型>) <父类型的引用变量>;
注意:
在向下转型的过程中,如果没有转换为真实子类类型,会出现类型转换异常
5、instanceof运算符
public class Test03 {
public static void main(String[] args) {
Pet pet = new Dog("一千",100,100,"萨摩耶");
pet.printInfo();
// 输出结果
// 昵称:一千,健康值:100,亲密度:100
// 品种:萨摩耶
// 使用pet对象调用Dog类中特有的方法eat()
// 父类的引用无法直接调用子类中特有的方法
// pet.eat(); 报错
// 如果要调用,需要向下转型(理解为基本数据类型中的强制类型转换)
// 向下转型
// 子类的引用指向父类的引用
Dog dog = (Dog)pet;
dog.eat();
// 输出结果 狗狗吃狗粮
System.out.println("------ ------ ------");
pet = new Penguin("帝王",100,100,"母");
// ------ ------ ------ ------ ------ ------
pet.printInfo();
// 输出结果
// 昵称:帝王,健康值:100,亲密度:100
// 性别:母
Penguin penguin1 = (Penguin)pet;
penguin1.swimming();
// 输出结果 企鹅会游泳
// ------ ------ ------ ------ ------ ------
// 如果在pet指向Penguin对象后进行如下操作:
// Dog dog2 = (Dog)pet;
// dog2.eat();
/*
* 报错:ClassCastException 类型转换异常
* */
// 因为pet指向的是Penguin类对象,在向下转型中应该将其转换为Penguin对象,不能将其转换为其它子类对象
// 为了避免在向下转型中过程中,出现类型转换异常
// 可以使用instanceof运算符进行类型判断,instanceof运算符的结果是布尔值
if (pet instanceof Dog) {
Dog dog2 = (Dog)pet;
dog2.eat();
} else if(pet instanceof Penguin) {
Penguin penguin2 = (Penguin)pet;
penguin2.swimming(); // 输出结果 企鹅会游泳
}
}
}
七、抽象类与抽象方法
1、抽象方法的概念
使用abstract修饰的方法为抽象方法
2、抽象方法的特点
1)抽象方法没有方法体
2)抽象方法所在的类要定义为抽象类
3)抽象方法必须在子类中重写,如果子类不重写父类中的抽象方法,那么子类也要定义为抽象类
(一般都会在子类中重写)
3、抽象类的概念
使用abstract修饰的类为抽象类
4、抽象类的特点
1)抽象类不能直接实例化(不能直接通过new的形式来创建抽象类的引用(对象))
因为实例化抽象类没有意义,所以我们一般将父类定义为抽象类
2)抽象类中可以没有抽象方法,也可以定义抽象方法,可以有普通方法
3)如果抽象类中定义可多个抽象方法,那么子类中必须重写这个抽象类中所有的抽象方法,否则子类也要定义为抽象类