多态
1. 介绍
多态:
生活中的多态:同一种事物,由于条件/环境不同,产生的结果也不同
程序中的多态:同一个引用类型,使用不同的实例而执行不同操作
多态的具体描述:父类引用指向子类对象 Pet pet = new Dog()/new Penguin();
多态的前提条件:
1.必须有继承
2.必须有方法重写
多态涉及的转型:
1.向上转型 父类引用指向子类对象
2.向下转型 将指向子类对象的父类引用 强制转换为子类类型
多态的具体表现:
1.父类作为形参 子类作为实参
2.父类作为返回值 实际返回值为子类类型
3.父类类型的数组/集合 实际元素为子类类型
2. 父类作为形参
2.1 宠物系统v1
给宠物添加看病的方法
1.在Pet父类中添加cure方法
2.创建Master类实现携带宠物看病方法
package com.atguigu.test2;
public class Pet {
protected String name;
protected int health;
protected int 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;
}
void print(){
System.out.println("宠物的名字是:" + name + ",健康值是:" + health);
System.out.println("亲密值是:" + love);
}
public Pet(){
}
public void cure(){
System.out.println("宠物看病");
}
}
package com.atguigu.test2;
public class Dog extends Pet{
private String strain;
public String getStrain() {
return strain;
}
public void setStrain(String strain) {
this.strain = strain;
}
public void print(){
System.out.println("狗狗的品种是:" + strain);
}
public void cure(){
System.out.println("狗狗看病,吃药,吃骨头,健康值恢复");
setHealth(100);
this.setLove(100);
}
}
package com.atguigu.test2;
public class Penguin extends Pet{
private String sex;
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public void print(){
super.print();
System.out.println("企鹅的性别是:" + sex);
}
public void cure(){
System.out.println("企鹅看病,打针,吃小鱼,健康值恢复");
this.setHealth(100);
this.setLove(100);
}
}
package com.atguigu.test2;
/**
* 主人类:
* 属性:
* 名字
* 方法:
* 给宠物看病
*
*
* */
public class Master {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void toHospitalWithDog(Dog dog){
dog.cure();
}
public void toHospitalWithPenguin(Penguin penguin){
penguin.cure();
}
// 思考问题:
// 1.目前宠物系统中 只有两种宠物 我们要编写两个方法分别给不同的宠物看病
// 如果后续有更多的宠物子类,怎么办呢?
}
package com.atguigu.test2;
public class Test {
public static void main(String[] args) {
Master zs = new Master();
zs.setName("赵四");
Dog dog = new Dog();
dog.setHealth(50);
dog.setLove(50);
dog.setName("大黄");
dog.setStrain("田园犬");
System.out.println(dog.getHealth()); // 50
zs.toHospitalWithDog(dog); // 狗狗看病,吃药,吃骨头,健康值恢复
System.out.println(dog.getHealth()); // 100
System.out.println("-------------------------");
Penguin p1 = new Penguin();
p1.setHealth(20);
p1.setLove(10);
p1.setName("大白");
p1.setSex("雌");
System.out.println(p1.getHealth()); // 20
zs.toHospitalWithPenguin(p1); // 企鹅看病,打针,吃小鱼,健康值恢复
System.out.println(p1.getHealth()); // 100
}
}
2.2 宠物系统v2
使用多态优化v1版本宠物系统:父类作为形参
package com.atguigu.test3;
public class Master {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void toHospitalWithDog(Dog dog){
dog.cure();
}
public void toHospitalWithPenguin(Penguin penguin){
penguin.cure();
}
// 思考问题:
// 1.问:目前宠物系统中 只有两种宠物 我们要编写两个方法分别给不同的宠物看病
// 如果后续有更多的宠物子类,怎么办呢?
// 答:按照目前的写法 每一个宠物子类 都单独编写一个方法 来实现给宠物看病
// 后续有更多的宠物子类 我们就要编写更多的方法来实现这个功能
// 这种方式是非常不合理的 这种方式不符合设计模块中的 '开闭原则'
// 开:开放 编写的程序应该扩展开发
// 闭:关闭 编写的程序应该对修改源代码关闭
// 2.问:不能使用每个宠物单独编写一个方法这种方式实现给宠物看病 那应该如何实现呢?
// 答:应该编写一个方法 实现给所有的宠物子类看病
public void toHospitalWithPet(Pet pet){
pet.cure();
}
}
3. 父类作为返回值
2.抽奖送宠物
一等奖:送企鹅一只
二等奖:送狗狗一只
三等奖:送猫咪一只
幸运奖:送成年东北虎一只
package com.atguigu.test4;
/**
* 主人类:
* 属性:
* 名字
* 方法:
* 给宠物看病
* 抽奖送宠物
* 一等奖:送企鹅一只
* 二等奖:送狗狗一只
* 三等奖:送喵咪一只
* */
public class Master {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void toHospitalWithPet(Pet pet){
pet.cure();
}
public Penguin givePenguin(){
Penguin penguin = new Penguin();
penguin.setName("大白");
penguin.setHealth(100);
penguin.setSex("雌性");
penguin.setLove(100);
return penguin;
}
public Dog giveDog(){
Dog dog = new Dog();
dog.setStrain("田园犬");
dog.setHealth(100);
dog.setLove(100);
dog.setName("小黄");
return dog;
}
public Cat giveCat(){
return new Cat();
}
// 分析问题
// 目前我们针对每一种奖项都单独编写一个方法来对应奖项
// 后续再有更多的奖项 那么我们需要编写更多的方法来实现 这种方式非常不合适/不灵活
// 我们应该编写一个方法用于实现所有的奖项抽奖功能
public Pet givePet(String str){
if (str.equals("一等奖")){
Penguin penguin = new Penguin();
penguin.setName("大白");
penguin.setHealth(100);
penguin.setSex("雌性");
penguin.setLove(100);
return penguin;
}else if(str.equals("二等奖")){
Dog dog = new Dog();
dog.setStrain("田园犬");
dog.setHealth(100);
dog.setLove(100);
dog.setName("小黄");
return dog;
}else if(str.equals("三等奖")){
return new Cat();
}
return null;
}
public static double m1(){
int a = 20;
return a;
}
}
package com.atguigu.test4;
public class Pet {
protected String name;
protected int health;
protected int 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;
}
void print(){
System.out.println("宠物的名字是:" + name + ",健康值是:" + health);
System.out.println("亲密值是:" + love );
}
public Pet(){
}
public void cure(){
System.out.println("宠物看病");
}
}
package com.atguigu.test4;
public class Dog extends Pet{
private String strain;
public String getStrain() {
return strain;
}
public void setStrain(String strain) {
this.strain = strain;
}
@Override
public void print(){
System.out.println("狗狗的品种是:" + strain);
}
public void cure(){
System.out.println("狗狗看病,吃药,吃骨头,健康值恢复");
setHealth(100);
this.setLove(100);
}
@Override
public String toString() {
return "Dog{" +
"strain='" + strain + '\'' +
", name='" + name + '\'' +
", health=" + health +
", love=" + love +
'}';
}
}
package com.atguigu.test4;
public class Cat extends Pet {
public String toString(){
return "Cat class toString()";
}
}
package com.atguigu.test4;
public class Penguin extends Pet{
private String sex;
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public void print(){
super.print();
System.out.println("企鹅的性别是:" + sex);
}
public void cure(){
System.out.println("企鹅看病,打针,吃小鱼,健康值恢复");
this.setHealth(100);
this.setLove(100);
}
@Override
public String toString() {
return "Penguin{" +
"sex='" + sex + '\'' +
", name='" + name + '\'' +
", health=" + health +
", love=" + love +
'}';
}
}
package com.atguigu.test4;
public class Test {
public static void main(String[] args) {
Master zs = new Master();
zs.setName("赵四");
Pet pet1 = zs.givePet("一等奖"); // Penguin 对象
System.out.println(pet1); // Penguin{sex='雌性', name='大白', health=100, love=100}
Pet pet2 = zs.givePet("二等奖"); // Dog 对象
System.out.println(pet2); // Dog{strain='田园犬', name='小黄', health=100, love=100}
Pet pet3 = zs.givePet("三等奖"); // Cat 对象
System.out.println(pet3); // Cat class toString()
Pet pet4 = zs.givePet("四等奖");
System.out.println(pet4); // null
}
}
4. 父类类型集合/数组
父类类型的数组/集合 元素为子类类型
package com.atguigu.test5;
import com.atguigu.test4.Cat;
import com.atguigu.test4.Dog;
import com.atguigu.test4.Penguin;
import com.atguigu.test4.Pet;
/**
* 父类类型的数组/集合 元素为子类类型
* */
public class Test1 {
public static void main(String[] args) {
Pet[] pets = new Pet[3];
pets[0] = new Dog();
pets[1] = new Cat();
pets[2] = new Penguin();
}
}
5. 向下转型和instanceof关键字
父类引用指向子类对象 向上转型:此时可以访问子类重写父类或者继承父类(父类中)的方法
不能访问子类独有的方法 如需访问子类独有的方法 必须向下转型
向下转型:将指向子类对象的父类引用 强制转换为子类类型
不能直接将一个 指向父类对象的父类引用 强制转换为子类类型
在引用数据类型强制类型转换之前 务必使用instanceof关键字进行类型判断
package com.atguigu.test6;
/**
* 父类引用指向子类对象 向上转型:此时可以访问子类重写父类或者继承父类(父类中)的方法
* 不能访问子类独有的方法 如需访问子类独有的方法 必须向下转型
*
* 向下转型:将指向子类对象的父类引用 强制转换为子类类型
* 不能直接将一个指向父类对象的父类引用 强制转换为子类类型
*
* 在引用数据类型强制类型转换之前 务必使用instanceof关键字进行类型判断
* */
public class Test1 {
public static void main(String[] args) {
Pet pet1 = new Dog(); // 父类引用指向子类对象 向上转型
// pet1.playWithMaster(); // 无法访问子类独有的方法
Dog dog = (Dog)pet1;
dog.playWithMaster(); // 狗狗和主人玩耍~
System.out.println("---------------------");
Pet pet2 = new Pet();
// 实际开发中 为了避免引用数据类型强制转换错误
// 我们可以使用instanceof关键字进行类型判断
// 语法: 对象名 instanceof 类名
// 作用: 表示判断 左侧的对象 是否属于 右侧的类型 属于返回true 不属于返回false
if (pet2 instanceof Penguin){
Penguin penguin = (Penguin)pet2;
penguin.skating();
}else{
System.out.println("类型不匹配"); // 类型不匹配
}
String str = "abc";
System.out.println(str.equals(dog)); // false
}
}
6. 多态实现原理
我们可以通过
javap -verbose class文件名
来反编译查看java文件编译之后的内容关于多态的虚方法和非虚方法(实方法)
问:1.什么是虚方法?
答:在编译期间无法确定具体调用哪个方法的这些方法 可以被子类重写的方法 称之为虚方法
底层通过invokevirtual指令实现方法调用
问:2.什么是非虚方法?
答:在编译期间可以确定具体调用方法的这一类方法 属于非虚方法
比如 静态方法 private修饰的方法 构造方法
底层通过invokespecial指令实现方法调用
虚方法和动态绑定
每个类信息文件中都维护一个方法表,此方法表被实现为一个数组
在编译期间 只能确定等号左侧类型可以访问的方法 以及 左侧类型继承父类的方法
只有在程序执行过程中 才能确定右侧的子类类型 具体是哪个子类
确定具体子类之后才可以确定具体调用哪个子类中重写父类的方法
这个过程就是动态绑定
7. this关键字的实现
this关键字底层被Java实现为一个隐式参数 隐藏式的参数
通过 javap -verbose class文件名称 查看class文件
8. final关键字
final关键字: 单词 - 最终
适用场景:可以修饰
属性:
被final修饰的属性称之为常量 其值只能被赋值一次 且不能被改变
通常(99%)在定义的时候赋值 或者 在构造方法中赋值
以上两种赋值方式都是为了保证当前常量 在使用之前是有值的
常量名称全部大写(醒目) 多个单词使用下划线分割
静态常量:
被final修饰的属性称之为常量 常量作为一个固定值的数据 通常(99%)也没有必要存在多份
所以我们再加上static关键字修饰 静态常量
通常(99%)在定义的时候赋值 或者 在静态代码块中赋值
以上两种方式 依然是为了保证 使用静态常量之前 是有值的
方法:
被final修饰的方法可以被继承 但是不能被重写
类:
被final修饰的类不能被继承
package com.atguigu.test10;
import com.atguigu.test8.Test;
/**
*
* final关键字: 单词 - 最终
* 使用场景: 可以修饰
* 属性:被final修饰的属性称之为常量 其值只能被赋值一次 并不能被改变
* 通常(99%)在定义的时候赋值 或者 在构造方法赋值
* 以上两种赋值方式都是为了保证当前常量 在使用之前是有值的
* 常量名称全部大写 多个单词使用下划线分割
* 方法:被final修饰的方法可以被继承 但是不能被重写
* 类:被final修饰的类不能被继承
*
* */
public class Test1 {
final double PI = 3.14; // 圆周率
final String COUNTRY_NAME;
double radius; // 半径
public Test1(){
COUNTRY_NAME = "中华人民共和国";
}
public Test1(double radius){
this.radius = radius;
COUNTRY_NAME = "中国";
}
public static void main(String[] args) {
Test1 t1 = new Test1();
// t1.PI = 3.2; // 无法被修改
t1.radius = 10;
System.out.println(t1.PI * t1.radius * t1.radius); // 314.0
}
}
package com.atguigu.test10;
/**
* final关键字: 单词 - 最终
*
* 常量: 被final修饰的属性称之为常量 常量作为一个固定值的数据 通常也没有必要存在多份 所以我们再加上static关键字修饰 静态常量
*
* 静态常量:
* 通常在定义的时候赋值 或者 在静态代码块中赋值
* 以上两种方式 依然是为了保证 使用静态常量之前是有值的
* */
public class Test2 {
static final double PI = 3.14; // 圆周率
static final String COUNTRY_NAME;
static {
COUNTRY_NAME = "中华人民共和国";
}
public static void main(String[] args){
Test2 t1 = new Test2();
System.out.println(t1.PI); // 3.14
Test2 t2 = new Test2();
System.out.println(t2.PI); // 3.14
}
}
package com.atguigu.test10;
public class A {
public void m1(){
System.out.println("A class m1方法");
}
public final void m2(){
System.out.println("A class m2方法");
}
}
package com.atguigu.test10;
public class A1 extends A{
public void m1(){
super.m1();
}
// 无法重写final修饰的方法
// public void m2(){
// super.m2();
// }
public static void main(String[] args) {
A1 a1 = new A1();
a1.m1();
a1.m2();
}
}
package com.atguigu.test10;
public final class B {
}
// 无法继承final修饰的类
//class C extends B{
//
//}