目录
- 抽象
- 抽象的概念
- 抽象方法和抽象类的格式
- 抽象方法和抽象类的使用
- 抽象方法和抽象类的注意事项
- ● 练习
- 1. 写一个父类图形类,其中有方法,功能计算面积为抽象方法。
- 2. 抽象类继承。判断对错,没错的分析运行结果
- 3. 发红包,群内用户类作为父类,有收红包方法,群主类作为子类有发红包的方法
- 接口
- 接口的定义基本格式
- 接口的使用
- 接口的1.常量定义和使用
- 接口的2.抽象方法定义
- 接口的3.默认方法定义
- 默认方法的使用
- 接口的4.静态方法定义
- 接口的5.私有方法定义(接口内使用)(静态+非静态)
- 私有方法使用
- 继承父类并实现多个接口
- ● 练习
- 1. 写一个实现类D 实现两个接口A,B,继承一个父类C
- 多态
- 多态的格式与使用
- 多态中成员变量的使用特点
- 多态中成员方法的使用特点
- 使用多态的好处
- 对象的向上转型,向下转型
- ● 练习
- 1. 多态中成员方法调用,判断以下输出
- 2. 多态中成员变量,判断对错或结果
- 3. 多态中,成员方法间接访问成员变量。判断对错或结果
- 4. 接口,多态综合练习,接口的基本使用,对象的上下转型,以及接口作为方法的参数。
抽象
抽象的概念
例如父类是一个图形,有一个计算面积的方法。而子类是三角形,正方形,圆形等。计算面积的方法各不相同,父类的计算面积方法就是一个抽象方法。
即如果父类当中的方法不确定如何进行{}方法体实现,那么这就应该是一个抽象方法。
抽象方法和抽象类的格式
public abstract class Fu{public abstract void eat();
}
抽象方法: 就是加上abstract关键字,然后去掉大括号,分号结束
抽象类:抽象方法所在的类必须是抽象才行。在class之前写上abstract即可
抽象类中也可以写普通方法
抽象方法和抽象类的使用
- 不能直接new抽象类对象
- 必须用一个子类继承抽象父类
- 子类必须覆盖重写抽象父亲当中所有的抽象方法。除非子类也是抽象类
覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号{} - 创建子类对象进行使用
抽象方法和抽象类的注意事项
Alt + Enter在子类继承红线处,自动重写父的抽象方法体
- 抽象类不能创建对象,编译报错。
只能创建其非抽象子类的对象 - 抽象类中,可以有构造方法(要实现方法体),是供子类创建对象时,初始化父类成员使用的。
因为子类构造中默认有super();或者自己指定,所以父类构造器中初始化变量就可以执行。 - 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
- 抽象类中的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
● 练习
1. 写一个父类图形类,其中有方法,功能计算面积为抽象方法。
- 子类:正方形,三角形,圆形。分别重写其抽象方法为自己类的计算面积方法。
A:
public abstract class Graphics {public abstract double area();
}
public class Square extends Graphics{double length;double width;public Square(int length, int width){this.length = length;this.width = width;}@Overridepublic double area() {return length * width;}
}
public class Circle extends Graphics{double diameter;public Circle(double diameter){this.diameter = diameter;}@Overridepublic double area() {return Math.PI * diameter * diameter;}
}
public class Triangle extends Graphics{double high;double bottom;public Triangle(double high, double bottom){this.high = high;this.bottom = bottom;}@Overridepublic double area() {return (high * bottom) / 2;}
}
public class MainTest{public static void main(String[] args) {Square sq1 = new Square(3, 5);Triangle tr1 = new Triangle(6, 8);Circle ci1 = new Circle(8);System.out.println(sq1.area());System.out.println(tr1.area());System.out.println(ci1.area());}
}
2. 抽象类继承。判断对错,没错的分析运行结果
public abstract class A{public A(){sout("A的构造"); }public abstract void eat();public abstract void sleep();
}
public class B extends A{public B(){sout("B的构造");}@Overridepublic void eat(){sout("B吃东西");}@Overridepublic void sleep(){sout("B睡觉");}
}
public class C extends A{
}
public abstract class D{public void sleep(){sout("D睡觉");}
}
public class MainTest{public static void main(String[] args) {A a = new A();B b = new B();b.eat();D d = new D();d.sleep(); }
}
A:
● public class C extends A{ A是抽象类,C没有实现A全部的抽象方法,所以必须是抽象类
}
public class MainTest{public static void main(String[] args) {● A a = new A(); 抽象类不可以new对象B b = new B();//正确,因为B实现了抽象父类A的所有抽象方法b.eat();● D d = new D();//错,抽象类不可以new● d.sleep(); }
}
结果
A的构造
B的构造
B吃东西
3. 发红包,群内用户类作为父类,有收红包方法,群主类作为子类有发红包的方法
① 群主的一笔金额,从群主的余额中扣除,平均分成n等份,让成员领取。
② 成员领取后,保存到成员余额中
优化:③ 该类每自动生成一个对象,就自动赋值id编号,随人数增加
A:
分析:
- 发红包方法,参数发多少份,发多少钱。返回一个
ArrayList<Integer>
,把要发的钱平均分成n份,剩的余额自动存到最后一个红包。 - 收红包方法参数就是这个List集合,随机的在集合里选一个下标元素收红包。为啥要用这个集合,主要是收了红包以后要在集合里把自己收的那一份删除。
- 优化思考①:怎么快速把红包加到这些群成员的余额,建立一个群成员类的
ArrayList<Member>
集合,循环调用每个群成员对象的收红包方法。 - 优化思考②:让红包不是均等,而是随机分配金额,最小的红包不为0即可。
public class Member {static int idCount = 0;int id;int balance;public Member(){idCount++;this.id = idCount;}public Member(int balance){this.balance = balance;idCount++;this.id = idCount;}public static int getIdCount() {return idCount;}public int getId() {return id;}public int getBalance() {return balance;}void setBalance(int balance){this.balance = balance;}public void receive(ArrayList<Integer> redList){int size = redList.size();int random = (int)(Math.random() * size);//double范围[0,size) [0,size-1]Random r = new Random();int random2 = r.nextInt(size);//int范围[0,size)this.balance += redList.get(random);redList.remove(random);//直接删除这一位,list长度--。}public void show(){System.out.println("----------------------");System.out.println("群成员的id:" + this.id);System.out.println("余额:" + this.balance);}
}
群主类属于特殊的成员类
public class Leader extends Member{public Leader(int balance){this.balance = balance;}public Leader(){}public ArrayList<Integer> send(int count, int money){if(money > getBalance()){System.out.println("余额不足,请重新设置红包金额");return null;}ArrayList<Integer> redList = new ArrayList<>();//分配红包过程,因为都是整数,如果每份都是小数的话,取整。把剩下的放到最后一个红包即可int ave = money / count;int last = money - (ave * count);for (int i = 0; i < count; i++) {redList.add(ave);}int size = redList.size();redList.set(size - 1, redList.get(size - 1) + last);setBalance(getBalance() - money);return redList;}}
// for test
public class MainTest {public static void main(String[] args) {//System.out.println("请依次输入,群主,成员1");Leader l1 = new Leader(3456);Member m1 = new Member(0);Member m2 = new Member(0);Member m3 = new Member(0);ArrayList<Integer> redList = l1.send(3, 50);m1.receive(redList);m2.receive(redList);m3.receive(redList);l1.show();m1.show();m2.show();m3.show();}
}
----------------------
群成员的id:1
余额:3406
----------------------
群成员的id:2
余额:16
----------------------
群成员的id:3
余额:18
----------------------
群成员的id:4
余额:16
优化:随机金额红包,一个群里的成员全部自动抢红包。
把属性balance极其getter,setter,子类balance的有参构造,send返回值类型,receive参数从int改为double。
想要控制double小数点后位数,用printf输出,printf("%.2f",num);
public void receive(ArrayList<Double> redList){int size = redList.size();int random = (int)(Math.random() * size);//double范围[0,size) [0,size-1]//不推荐random2生成的随机数,经常不够随机//Random r = new Random();//int random2 = r.nextInt(size);//int范围[0,size)this.balance += redList.get(random);redList.remove(random);//直接删除这一位,list长度--。}public void show(){System.out.println("----------------------");System.out.println("群成员的id:" + this.id);System.out.printf("余额:" + "%.2f", this.balance);System.out.println();}
public ArrayList<Double> send(int count, double money){if(money > getBalance()){System.out.println("余额不足,请重新设置红包金额");return null;}ArrayList<Double> redList = new ArrayList<>();//红包最小金额设置0.1,最大是总金额的0.6,反正就是比一半多一点double min = 0.1;double max = money * 0.6;double last = money;//最后剩余的Random r = new Random();for (int i = 0; i < count - 1; i++) {double num = r.nextDouble()*(max - min) + min;//0.0 ~ 1.0 -> 0.0 ~ max - min -> min ~ maxlast -= num;redList.add(num);}redList.add(last);setBalance(getBalance() - money);return redList;}
----------------------
群成员的id:1
余额:3388.11
----------------------
群成员的id:2
余额:2.70
----------------------
群成员的id:3
余额:59.58
----------------------
群成员的id:4
余额:5.62
接口
接口的定义基本格式
接口就是多个类的公共规范。接口是一种引用数据类型,最重要的内容就是其中的抽象方法。
public interface 接口名称{//接口内容
}
换成了关键字interface之后,编译生成的字节码文件仍然是: .java -> .class
如果Java7,那么接口中可以包含的内容有:
- 常量
- 抽象方法(只定义,不实现)
如Java8,额外包含 - 默认方法
- 静态方法
Java9,额外包含 - 私有方法
接口的使用
1.接口不能直接使用,必须有一个“实现类”来“实现”该接口
public class 实现类名称 implements 接口名称{//……
}
2.接口的实现类必须覆盖重写(实现) 接口中所有的抽象方法
实现:去掉abstract 关键字,加上方法体大括号
3.创建实现类的对象,进行使用。
建议:实现类名:接口名 + Impl
注意:如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类就必须是抽象类。(因为抽象方法所在的类必为抽象类)
以下[括号内]
关键字意为可省略的,不写也依然存在的意思
接口的1.常量定义和使用
接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰。其实就是接口的常量
[public] [static] [final] 数据类型 常量名称 = 数据值
final:一旦赋值,不可以修改
1.接口当中的常量,可以省略public static final,但不写也是默认有
2.接口当中的常量,必须进行赋值,不能不赋值
复习:
类中成员变量,数组,有默认值。局部变量没有默认值,没赋值不可使用
访问时,接口名.常量名;
public static final int NUM_OF_MY_CLASS = 12;
3.接口中常量的名称,使用完全大写的字母,下划线分隔(推荐命名规则)
接口的2.抽象方法定义
[public] [abstract] 返回值类型 方法名称(参数列表);
注意:
1.接口中的抽象方法,修饰符必须是两个固定的关键字,[public] [abstract] (不可以private等)
2.这两个关键字修饰符,可以选择性地省略。(都不写,也默认是抽象方法)
3.方法的三要素,可以随意定义
接口的3.默认方法定义
[public] default 返回值类型方法名称(参数列表){//方法体
}
默认方法一定是public,默认方法也可以被覆盖重写(不重写也不报错)
备注:接口当中的默认方法,可以解决接口升级的问题
比如,两个实现类都实现了一个接口,但该接口后来添加了新方法。如果是抽象方法的话,就需要两实现类都重写该方法。但默认方法就可以直接继承即可,更方便
默认方法的使用
a为一个实现类对象
a.methodAbs();//调用抽象方法,实际运行的是右侧实现类(复习:因为new谁,就用谁的方法,而实现类重写了这个方法)
1.接口的默认方法,可以通过接口实现类对象,直接调用。(如实现类中没有重写,就向上找)
2.接口的默认方法,也可以被接口实现类进行覆盖重写
接口的4.静态方法定义
[public] static 返回值类型 方法名称(参数列表){方法体
}
就是将abstract或者default换成static即可,带上方法体
注意:不能通过接口实现类的对象来调用来调用接口中的静态方法:
一个类可以实现多个接口,静态方法可能冲突
- 但接口中不可以用实现类调用static方法,只能用接口名调用static方法
- 注意其中区别,已知普通类中,类名. 对象名. 都可以用来调用static方法
通过接口名称,直接调用其中的静态方法 接口名.静态方法名(参数)
接口的5.私有方法定义(接口内使用)(静态+非静态)
接口中需要抽取一个共有方法,用来解决接口中两个默认方法之间重复代码的问题
1.普通私有方法,解决多个默认方法之间重复代码的问题
private 返回值类型 方法名称(参数){方法体
}
2.静态私有方法,解决多个静态方法之间重复代码问题
private static 返回值类型 方法名(参数){方法体
}
private方法只有接口自己才能调用,不能被实现类或别人使用
私有方法使用
两个默认方法调用一个私有方法
public default void methodDefault1(){默认方法1;methodCommon();
}
public default void methodDefault2(){默认方法2;methodCommon();
}
private void methodCommon(){}
两个静态方法调一个私有静态方法
public static void methStatic1(){静态方法1;methodStaticCommon();
}
public static void methStatic2(){静态方法2;methodStaticCommon();
}
pirvate static void methodStaticCommon(){}
继承父类并实现多个接口
- 接口中没有静态代码块或构造方法
static{……
}
(复习:第一次用到本类时,静态代码块执行唯一的一次。一般用来一次性的对静态成员变量赋值。静态内容总是优先于非静态,静态代码比构造方法先执行。)
shift + F6
重命名类
Ctrl + C/V
复制整个类
实现接口时 Alt + Enter
选要实现的方法
Java中所有类都是Object的直接子类或间接子类
- 一个类的直接父类是唯一的,但是一个类可以同时实现多个接口
public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB{//覆盖重写所有抽象方法
}
- 如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
❓怎么判断是否重复,返回值和名,参数列表全一致吗?(方法的三要素)
A: 方法名和参数相同即为重复方法,与返回值无关
1)与返回值无关,返回值不同,方法名和参数相同也属于重复方法。只需要保证子类重写的方法返回值是父类方法的子类型即可,例如Object做父类返回值 只是此处子方法返回值可以是Integer但不能是Object,因为接口B中返回值Integer不可以当Object的父
(复习:重写3要素:①方法名和参数列表都相同,②只需要保证重写方法的返回值类型 <= 父类(此处是父接口)方法即可。③重写的子类方法的权限 >= 父类方法)
public interface MyInterfaceA {Object method1();
}
public interface MyInterfaceB {Integer method1();
}
public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB{@Overridepublic Integer method1(){System.out.println();return 1;}
}
2)与参数有关,若参数不同,就不是同一个方法。在实现类中都继承就需要都重写,属于重载方法。
public interface MyInterfaceA {Object method1();
}
public interface MyInterfaceB {Integer method1(int num);
}
public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB{@Overridepublic Integer method1(){System.out.println();return 1;}public Integer method1(int num){return 1;}
}
- 如果实现类没有覆盖重写所有接口与当中的所有抽象方法,那么实现类就必须是一个抽象类。
- 如多个接口中,存在重复的默认方法,实现类一定要对冲突的默认方法进行覆盖重写。
- 一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法因为继承优先于接口实现,也不用重写冲突的方法
public class Zi extends Fu implements MyInterface{}
注意:
- 多个父接口当中的抽象方法如果重复,没关系。因为没有方法体,实现类中重写一次即可。抽象类作为子类或子接口可以不重写该方法
- 多个父接口当中的默认方法如果重复,有关系。子接口必须进行默认方法的覆盖重写,(而且带着default关键字)接口中default不能省略,与普通类方法的权限修饰符
留空即(default )
不同。
● 练习
1. 写一个实现类D 实现两个接口A,B,继承一个父类C
接口A写所有可有内容(5种)的完整写法
接口B写所有可有内容的省略写法
其中A,B 中1常量名重复,1抽象方法重复, 1抽象方法只方法名,参数相同,1私有方法重复。A,B,C 1默认方法重复,1静态方法重复。B,C 1默认方法重复(分析哪些不能实现及原因)
A: 总结:一个实现类实现多个接口和一个父类的情况下
- 多个接口中常量名冲突,用接口名调用即可。实现类对象只能调不冲突的常量,不然混淆
- 多个接口中抽象方法冲突,实现类重写一次即可。抽象子类或子接口无需重写
- 多个接口中默认方法冲突,子必须重写一次。
- 一个父接口和父类的默认方法冲突,不用重写,优先使用父类该重名方法。
- 静态方法只能用接口名调用,不可以用实现类调用。复习,普通类中,类名,对象名都可以调用静态方法
- 重写时注意,若多个父接口中的重名方法返回值不同。子中重写方法的返回值类型 <= 父方法即可。此处大小指的是父子类关系,例如:Object类 > Integer类
public interface MyInterA {public static final int NUM = 10;public abstract Object abMethod1();public abstract Integer abMethod2();public default void deMethod1(){System.out.println("接口A的默认方法,A,B,C中重名");
// method1();
// method2();}public static void stMethod1(){System.out.println("接口A的静态方法,A,B,C中重名");
// method2();//method1();调不了,静态方法中调用非静态方法得new对象,但接口不能new对象}/*复习,1.静态方法中调用:①静态方法或变量,内部直接调(外部静态导入也是直接调),外部类名.* ②非静态方法,统一用对象名.调用* 2.非静态方法中调用:①内部类,不论静态或非静态,都直接调* ②外部类,静态用类名.,非静态用对象.*///因为Java8不能写这些
// private void method1(){
// System.out.println("A的重复代码,私有");
// }
// private static void method2(){
// System.out.println("A的重复代码,私有静态");
// }
}
public interface MyInterB {int NUM = 20;int BNUM = 30;Float abMethod1();Integer abMethod2();public default void deMethod1(){System.out.println("接口B的默认方法1,A,B,C中重名");
// method1();
// method2();}public default void deMethod2(){System.out.println("接口B的默认方法2,B,C中重名");}public static void stMethod1(){System.out.println("接口B的静态方法,A,B,C中重名");
// method2();//method1();调不了,静态方法中调用非静态方法得new对象,但接口不能new对象}public static void stMethod2(){System.out.println("接口B自己的静态方法");}//因为Java8不能写这些
// private void method1(){
// System.out.println("B的重复代码,私有");
// }
// private static void method2(){
// System.out.println("B的重复代码,私有静态");
// }}
public class MyClassC {void deMethod1(){System.out.println("------类C的默认方法1,A,B,C中重名------");method1();method2();System.out.println("------------------------------------");}public void deMethod2(){System.out.println("类C的默认方法2,B,C中重名");}public static void stMethod1(){System.out.println("----类C的静态方法,A,B,C中重名---");method2();System.out.println("-------------------------------");//method1();调不了,静态方法中调用非静态方法得new对象,但接口不能new对象}private void method1(){System.out.println("类C的重复代码,私有");}private static void method2(){System.out.println("类C的重复代码,私有静态");}
}
public class MyClassD extends MyClassC implements MyInterA, MyInterB{
// @Override
// public Double abMethod1() {
// return null;
// }/*复习:虽然该抽象方法,A接口返回值为Object,B接口返回值为Float,重写时子类方法返回值 <= 父类返回值即可*/@Overridepublic Float abMethod1() {System.out.println("类D重写的抽象方法1");return null;}@Overridepublic Integer abMethod2() {System.out.println("类D重写的抽象方法2");return null;}public void deMethod1(){System.out.println("默认方法1,接口A,B默认方法和类C方法重名,所以需要实现类D重写一次");System.out.println("但实际上A,B默认方法都无法调用到,只能super调用父类C的该名称方法");super.deMethod1();}}
public class Main {public static void main(String[] args) {MyClassD d1 = new MyClassD();System.out.println("因为接口中常量都为static,所以类名或实现类对象调用都可以。但在父接口常量重名时,不可用实现类对象调用,因为混淆");System.out.println(MyInterA.NUM);System.out.println(MyInterB.NUM);System.out.println(d1.BNUM);d1.abMethod1();d1.abMethod2();/*默认方法1,ABC中重名,优先使用父类C的,但是接口A,B中也重名,需要在实现类D中重写*/d1.deMethod1();/*默认方法2 ,BC中重名,优先用父类C的,D中不用重写。直接用*/d1.deMethod2();/*静态方法用类名or接口名调用*/System.out.println("======================");MyInterA.stMethod1();MyInterB.stMethod2();MyClassC.stMethod1();}
}
自己分析输出结果
因为接口中常量都为static,所以类名或实现类对象调用都可以。但在父接口常量重名时,不可用实现类对象调用,因为混淆
10
20
30
类D重写的抽象方法1
类D重写的抽象方法2
默认方法1,接口A,B默认方法和类C方法重名,所以需要实现类D重写一次
但实际上A,B默认方法都无法调用到,只能super调用父类C的该名称方法
------类C的默认方法1,A,B,C中重名------
类C的重复代码,私有
类C的重复代码,私有静态
------------------------------------
类C的默认方法2,B,C中重名
======================
接口A的静态方法,A,B,C中重名
接口B自己的静态方法
----类C的静态方法,A,B,C中重名---
类C的重复代码,私有静态
-------------------------------
多态
面向对象三大特征:封装性、继承性、多态性。
extends继承或者implements实现,是多态性的前提。
小明是一个学生,但同时也是一个人。小明是一个对象,这个对象既有学生形态,也有人类形态。
一个对象拥有多种形态,这就是:对象的多态性
多态的格式与使用
Polymorephism -> Mulit
代码中体现多态性: 父类引用指向子类对象
格式:
父类名称 对象名 = new 子类名称();
或
接口名称 对象名 = new 实现类名称();
复习前篇:
子父类重名方法,new 的是谁就用谁的方法,没有则向上找。
多态中成员变量的使用特点
public class Fu {int num = 10;
void method(){System.out.println(num);}void methodFu(){System.out.println(num);}
}
public class Zi extends Fu{int num = 20;int age = 16;void method(){System.out.println("子类method");System.out.println(num);}
}
public class Main {public static void main(String[] args) {Fu obj = new Zi();obj.method();//20,子类方法obj.methodFu();//10,父类特有方法System.out.println(obj.num);//10//直接调用,重名变量看左侧引用是谁//System.out.println(obj.age);错误写法,变量看左边引用,父类没有,不会向下找}
}
复习前篇:
重名变量:1) 直接调,= 左边既引用是谁就用谁的变量
2) 间接访问:利用类中方法,类中方法都是优先调用本类变量。调用的方法属于哪个类,就是哪个类中的变量
多态中成员方法的使用特点
在多态的代码当中,成员方法的访问规则是:
看new的是谁,就优先用谁,没有则向上找。
成员变量:编译看左,运行还看左
成员方法:编译看左,运行看右
public class Fu {int num = 10;void showNum(){System.out.println(num);}void method(){System.out.println("重名方法method");}void methodFu(){System.out.println("父类特有methodFu");}}
public class Zi extends Fu{int num = 20;int age = 16;void showNum(){System.out.println(num);}void method(){System.out.println("重名方法method");}void methodZi(){System.out.println("子类特有methodZi");}
}
分析结果:obj.methodZi()编译错误
public class Main {public static void main(String[] args) {Fu obj = new Zi();obj.method();//编译看左,Fu有该方法。运行看右,Zi有该方法,运行子类obj.methodFu();//编译看左,Fu有该方法。运行看右,Zi无该方法,就向上找,运行父类//obj.methodZi();//编译看左,Fu类没有该方法,编译出错}
}
❓扩展:若重名方法内调用重名变量
pulbic class Fu{int num = 10;void method(){System.out.println(num);//10}
}
public class Zi extends Fu{int num = 20;void method(){System.out.println(num);//20}
}
A:分析,方法所在类内的该变量,若有直接调,没有则向上找
多态情况,编译看左,Fu有方法method,运行看右Zi有method方法,运行子类方法
public static void main(String[] args){Fu obj = new Zi();obj.method();//20
}
使用多态的好处
如不用多态,只用子类,写法:
Teacher one = new Teacher();
one.work();//讲课
Assistent two = new Assistant();
two.work();//辅导
我现在唯一要做的事情,就是调用work方法,其他功能不关心
如果使用多态的写法,对比一下:
Employee one = new Teacher();
one.work();//讲课
Employee two = new Assistant();
two.work();//辅导
好处:无论右边new的时候换成哪个子类对象,等号左边调用方法都不会改变。(改代码更灵活)
对象的向上转型,向下转型
public interface Animal {void eat();
}
public class Cat implements Animal{@Overridepublic void eat() {System.out.println("猫吃鱼");}public void catchMice(){System.out.println("猫抓老鼠");}
}
public class Dog implements Animal{@Overridepublic void eat() {System.out.println("狗吃骨头");}public void door(){System.out.println("狗看家");}
}
- 对象的向上转型,其实就是多态写法:
Animal animal = new Cat();
含义:右侧创建一个子类对象,把它当做父类来看待使用。注意,此时,类对象不可以调用子类Cat特有的方法(因为对于成员方法,编译看左,运行看右。父类Animal没有子类特有的方法,编译出错)
格式:父类名 对象名 = new 子类名称();
创建了一只猫,当做动物看待,没问题。
注意事项:向上转型一定是安全的。从小范围转向了大范围。(动物范围比猫更广)
类似于: double num = 100; //√
,整数默认类型为int,int取值范围 < double 范围。int -> double, 自动类型转换。从小范围的猫,向上转换成为更大范围的动物
对象的向上转型,就是父类引用指向子类对象
Animal animal = new Cat();animal.eat();//猫吃鱼
- 对象的向下转型,其实是一个还原的动作。
格式: 子类名称 对象名 = (子类名称)父类对象;
含义:将父类对象,还原成为本来的子类对象。
Animal animal = new Cat();
本来是猫,向上转型成为动物
Cat cat = (Cat) animal;
本来是猫,已经被当做动物了,还原回来成为本来的猫
a. 必须保证对象本来创建的时候,就是猫,才能向下转型成为猫
b. 如果对象创建的时候本来不是猫(比如是狗Dog类,其他的子类,或父类本身Animal),现在非要向下转型成为猫,就会报错。 ClassCastException类转换异常
❓如何才能知道一个父类引用的对象,本来是什么子类?
格式: 对象 instanceof 类名称
这将会得到一个boolean值结果,也就是判断前面的对象不能当做后面类型的实例。向下转型前一定要用instanceof判断,因为不确定接口有几个实现类
- 注意,如果方法形参规定是父类或父接口,传实参为子类或实现类不是多态写法,但传参了也符合向上转型。也是从小范围类型赋值给大范围
- 分两步向上转型,和传参再向上转型都可以
public class Main {public static void getPet(Animal animal){if(animal instanceof Dog){System.out.println("得到的宠物是狗狗");Dog dog = (Dog)animal;// ● 向下转型,必须原对象就是Dog类,所以一定先instanceof判断dog.door();//还原后才可以调用子类特有的方法}else if(animal instanceof Cat){System.out.println("得到的宠物是猫猫");Cat cat = (Cat)animal;cat.catchMice();}}public static void main(String[] args) {//向上转型
// Dog dog1 = new Samoyed();
// Samoyed sam1 = (Samoyed)dog1;
// sam1.personality();
/*此处实验,向下转型只能还原,也不能把父类强转成子类。
转的类型必须跟开始的对象类型一致,只有引用不一致*/
// Dog dog2 = new Dog();
// Samoyed sam2 = (Samoyed)dog2;
// sam2.personality();//模拟向上转型Animal a1 = new Cat();//①向上转型Dog a2 = new Dog();Animal a3 = a2;//②分两步向上转型getPet(a2);//③子类传参给形参是父接口的,也属于向上转型getPet(new Dog());//④匿名对象传也可以a1.eat();a3.eat();//不能调用子类特有方法,如door(),catchMice();}
}
得到的宠物是狗狗
狗看家
得到的宠物是狗狗
狗看家
猫吃鱼
狗吃骨头
● 练习
1. 多态中成员方法调用,判断以下输出
public class Fu {void method(){System.out.println("父类method");}void methodFu(){System.out.println("父类特有methodFu");}
}
public class Zi extends Fu{void method(){System.out.println("子类method");}
}
public class Main {public static void main(String[] args) {//判断以下输出Fu obj = new Zi();obj.method();obj.methodFu();}
}
A:new的是谁用谁的方法,没有则向上找
子类method
父类特有methodFu
2. 多态中成员变量,判断对错或结果
public class Fu {int num = 10;void method(){System.out.println(num);}void methodFu(){System.out.println(num);}
}
public class Zi extends Fu{int num = 20;int age = 16;void method(){System.out.println("子类method");System.out.println(num);}
}
public class Main {public static void main(String[] args) {Fu obj = new Zi();obj.method();obj.methodFu();System.out.println(obj.num);System.out.println(obj.age);}
}
A: 记 成员变量:编译看左,运行看左。成员方法:编译看左,运行看右
方法属于哪个类,优先调用本类的变量,若无才向上找
public static void main(String[] args) {Fu obj = new Zi();obj.method();//编译看左,Fu,有。运行看右,Zi也有,运行子类方法obj.methodFu();//编译看左,Fu,有。运行看右,Zi无,向上找,运行父类方法System.out.println(obj.num);//直接调用,成员变量始终看左,执行父类变量● System.out.println(obj.age);//错,变量看左,父类Fu没这个变量编译错误,也不可能向下找}
子类method
20
10
10
3. 多态中,成员方法间接访问成员变量。判断对错或结果
public class Fu {int num = 10;void showNum(){System.out.println(num);}void method(){System.out.println("父类重名方法method");}void methodFu(){System.out.println("父类特有methodFu");}
}
public class Zi extends Fu{int age = 16;void showNum(){System.out.println(num);}void method(){System.out.println("子类重名方法method");}void methodZi(){System.out.println("子类特有methodZi");}
}
public class Main {public static void main(String[] args) {Fu obj = new Zi();obj.method();obj.methodFu();obj.methodZi();obj.showNum();}
}
A:
public static void main(String[] args) {Fu obj = new Zi();obj.method();//编译看左,Fu有该方法。运行看右,运行子类方法obj.methodFu();//编译看左,运行看右。Zi中没有,向上找,运行父类方法● 错 obj.methodZi();//编译看左,Fu中无,错误obj.showNum();//编译看左,运行看右。运行子类方法,但Zi中没有num,向上找。(只要Fu中num,不是private私有的就行)}
子类重名方法method
复习权限:
private -> 类内部使用
(default) 比上多一个使用区 -> 同一个包
protected 比前两多一个 -> 子类
public 比前几个多 -> 任何地方
4. 接口,多态综合练习,接口的基本使用,对象的上下转型,以及接口作为方法的参数。
A: 分析,use接口有无对电脑,极其开关机操作无关,只是使用设备时需要作为链接的标准,作为参数。鼠标键盘等属于usb设备,需要实现use接口。use接口中功能就是打开和关闭。 具体设备有自己的特有功能,类似点击打字之类。
public class Iaptop {public void powerOn(){System.out.println("打开笔记本");}public void useDevice(USB usb){usb.open();//因为多态,引用为接口,对象为实现类。不可以调用实现类特有的方法。// 复习:对于成员方法,编译看左,运行看右//所以想要调用子类特有方法,进行还原,即向下转型后调用即可if(usb instanceof Mouse){Mouse mouse = (Mouse)usb;mouse.click();}else if(usb instanceof Keyboard){//注意:不可以直接else,必须加if判断,因为不能确定接口只有这两个实现类Keyboard keyboard = (Keyboard)usb;keyboard.type();}usb.close();
// System.out.println("使用设备");}public void powerOff(){System.out.println("关闭笔记本");}
}
public interface USB {public abstract void open();public abstract void close();}
public class Mouse implements USB{@Overridepublic void open() {System.out.println("打开鼠标");}@Overridepublic void close() {System.out.println("关闭鼠标");}public void click(){System.out.println("点击");}
}
public class Keyboard implements USB{@Overridepublic void open() {System.out.println("打开键盘");}@Overridepublic void close() {System.out.println("关闭键盘");}public void type(){System.out.println("打字");}
}
public class Main {public static void main(String[] args) {Iaptop c1 = new Iaptop();c1.powerOn();//多态,4种向上转型。//usb1 和 usb3 是一步向上转型和分两步向上转型//usb2 和 usb4 是实现类传参给接口,也是向上转型USB usb1 = new Mouse();Keyboard usb2 = new Keyboard();USB usb3 = usb2;c1.useDevice(usb1);c1.useDevice(usb2);//相当于,形参是double,实参是int。从小范围到大范围传参自动类型转换c1.useDevice(usb3);c1.useDevice(new Mouse());c1.powerOff();}
}
打开笔记本
打开鼠标
点击
关闭鼠标
打开键盘
打字
关闭键盘
打开键盘
打字
关闭键盘
打开鼠标
点击
关闭鼠标
关闭笔记本