抽象
抽象的概念
例如父类是一个图形,有一个计算面积的方法。而子类是三角形,正方形,圆形等。计算面积的方法各不相同,父类的计算面积方法就是一个抽象方法。
即如果父类当中的方法不确定如何进行{}方法体实现,那么这就应该是一个抽象方法。
抽象方法和抽象类的格式
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;
}
@Override
public double area() {
return length * width;
}
}
public class Circle extends Graphics{
double diameter;
public Circle(double diameter){
this.diameter = diameter;
}
@Override
public 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;
}
@Override
public 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的构造");
}
@Override
public void eat(){
sout("B吃东西");
}
@Override
public 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份,剩的余额自动存到最后一个红包。(确保总金额int类型,平均数也是int,剩余的int刚好存放到最后一个红包) - 收红包方法参数就是这个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 ~ max
last -= 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,因为实现类实现了两个接口,且接口A、B方法重名。接口B中方法返回值为Integer不可以当Object的父,所以子类方法返回值不可以是Object,必须同时满足<= 所实现接口的返回值类型
(复习:重写3要素:①方法名和参数列表都相同,②只需要保证重写方法的返回值类型 <= 父类(此处是父接口)方法即可。③重写的子类方法的权限 >= 父类方法)
public interface MyInterfaceA {
Object method1();
}
public interface MyInterfaceB {
Integer method1();
}
public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB{
@Override
public 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{
@Override
public 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
自己分析输出结果
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,
重写时子类方法返回值 <= 父类返回值即可*/
@Override
public Float abMethod1() {
System.out.println("类D重写的抽象方法1");
return null;
}
@Override
public 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();
}
}
A:-----------------------------------------------------------------------------------------------------------
因为接口中常量都为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的重复代码,私有静态
-------------------------------
2. 判断对错,并改正
a.
public interface A {
int num = 10;
}
public interface B {
int num = 20;
}
public interface C {
int num =30;
}
public class MainTest {
public static void main(String[] args) {
D obj1 = new D();
System.out.println(obj1.num);
System.out.println(A.num);
System.out.println(B.num);
System.out.println(C.num);
}
}
b.
public interface A {
int num = 10;
public abstract void repeat01();
}
public interface B {
int num = 20;
void repeat01();
}
public interface C {
int num =30;
}
public class D implements A,B,C {
@Override
public void repeat01() {
System.out.println("所实现的接口A,B都有这个抽象方法");
}
}
public abstract class E implements A,B,C{
}
public interface F extends A,B,C{
c.
public interface A {
default void repeat02(){
System.out.println("接口A的默认方法repeat02");
}
default void repeat03(){
System.out.println("接口A的默认方法repeat03");
}
}
public interface B {
default void repeat02(){
System.out.println("接口B的默认方法repeat");
}
}
public interface C {
int num =30;
}
public class Fu {
void repeat02(){
System.out.println("类Fu的方法repeat02");
}
void repeat03(){
System.out.println("类Fu的方法repeat03");
}
}
public class D extends Fu implements A,B,C {
@Override
public void repeat01() {
System.out.println("实现类D所实现的,接口A,B都有这个抽象方法");
}
}
public abstract class E implements A,B,C{
}
public interface F extends A,B,C{
}
d. 分析输出结果
public interface A {
static void repeat04(){
System.out.println("接口A的静态方法repeat04");
}
}
public interface B {
static void repeat04(){
System.out.println("接口B的静态方法repeat04");
}
}
public class Fu {
static void repeat04(){
System.out.println("类Fu的静态方法repeat04");
}
}
public interface C {
int num =30;
}
public class D extends Fu implements A,B,C {
}
public class MainTest {
public static void main(String[] args) {
D obj1 = new D();
obj1.repeat04();
A.repeat04();
B.repeat04();
Fu obj2 = new Fu();
obj2.repeat04();
Fu.repeat04();
}
}
e.
public interface A {
default void repeat03(){
System.out.println("接口A的默认方法repeat03");
}
static void repeat04(){
System.out.println("接口A的静态方法repeat04");
}
}
public interface B {
static void repeat04(){
System.out.println("接口B的静态方法repeat04");
}
}
public class Fu {
void repeat03(){
System.out.println("类Fu的方法repeat03");
}
static void repeat04(){
System.out.println("类Fu的静态方法repeat04");
}
}
public interface C {
int num =30;
}
public class D extends Fu implements A,B,C {
@Override
public void repeat01() {
System.out.println("实现类D所实现的,接口A,B都有这个抽象方法");
}
@Override
public void repeat02() {
}
}
A:-----------------------------------------------------------------------------------------------------------
总结:一个实现类实现多个接口和继承一个父类的情况下
a. MainTest 4行错
多个接口中常量名冲突,用接口名调用即可。实现类对象只能调不冲突的常量,不然混淆
b. 无错
多个接口中抽象方法冲突,实现类重写一次即可。抽象子类或子接口无需重写
c.D、E、F错
D、E、F其中必须重写repeat02,repeat03不用,写法正确。因为接口A,B中这两个默认方法冲突,所以不管是子接口还是子抽象方法还是实现类都必须重写一次。repeat03不用重写,只有一个父类和一个接口中方法重名,子类优先用父类方法即可
即多个接口中默认方法冲突,子必须重写一次。
一个父接口和父类的默认方法冲突,不用重写,优先使用父类该重名方法。
d.
**静态方法只能用接口名调用,不可以用实现类调用。**很好理解,因为接口不能new,new的都是实现类,而实现类可以实现多个接口,防止多接口静态方法重名,不允许用对象调用接口中静态方法。
复习,普通类中,类名,对象名都可以调用静态方法
public class MainTest {
public static void main(String[] args) {
D obj1 = new D();
obj1.repeat04();//调用的Fu中repeat04,如果Fu中没写则报错,跟其他几个接口的repeat04毫无关系也不算重写
A.repeat04();//调用的各自接口中的repeat04,跟其他接口毫无关系
B.repeat04();
Fu obj2 = new Fu();
obj2.repeat04();//调用的Fu中repeat04//对于用对象名调用静态方法,编译后也会转为类名调用,不推荐但也不算错
Fu.repeat04();//调用的Fu中repeat04//跟上句作用完全相同
}
}
e. D 错或者Fu中2行错
D中需要重写repeat03方法,why?
因为虽然repeat03默认方法只有接口A和D的直接父类Fu中重复,但是注意修饰符一个是public ,一个是(default)。而子类的修饰符是需要 >= 父类的,优先继承父类方法,为(default)与接口中默认方法repeat03的修饰符public 冲突,所以需要在D中重写一下。或者把Fu中的repeat03修饰符改为public,D中就不需要重写。
repeat04是静态方法,在接口中存在本来就只能用接口名调用,不存在多接口或者和实现类的直接父类之类冲突的问题。
重写时注意,若多个父接口中的重名方法返回值不同。子中重写方法的返回值类型 <= 父方法即可。此处大小指的是父子类关系,例如:Object类 > Integer类
@Override
public void repeat03() {
}
# 多态 面向对象三大特征:封装性、继承性、多态性。 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{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMice(){
System.out.println("猫抓老鼠");
}
}
public class Dog implements Animal{
@Override
public 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("Fu 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("Zi 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 showNum 10//编译看左,运行看右。运行子类方法,但Zi中没有num,向上找。(只要Fu中num,不是private私有的就行)
}
复习权限:
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{
@Override
public void open() {
System.out.println("打开鼠标");
}
@Override
public void close() {
System.out.println("关闭鼠标");
}
public void click(){
System.out.println("点击");
}
}
public class Keyboard implements USB{
@Override
public void open() {
System.out.println("打开键盘");
}
@Override
public 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();
}
}
打开笔记本
打开鼠标
点击
关闭鼠标
打开键盘
打字
关闭键盘
打开键盘
打字
关闭键盘
打开鼠标
点击
关闭鼠标
关闭笔记本