【JavaSE ⑤】 P165 ~ P194 抽象方法,抽象类,发红包案例(1),接口,接口内容,多接口实现和父类继承,多态,向上转型,向下转型

抽象

抽象的概念

例如父类是一个图形,有一个计算面积的方法。而子类是三角形,正方形,圆形等。计算面积的方法各不相同,父类的计算面积方法就是一个抽象方法。
即如果父类当中的方法不确定如何进行{}方法体实现,那么这就应该是一个抽象方法。

抽象方法和抽象类的格式

public abstract class Fu{
	public abstract void eat();
}

抽象方法: 就是加上abstract关键字,然后去掉大括号,分号结束
抽象类:抽象方法所在的类必须是抽象才行。在class之前写上abstract即可
抽象类中也可以写普通方法

抽象方法和抽象类的使用

  1. 不能直接new抽象类对象
  2. 必须用一个子类继承抽象父类
  3. 子类必须覆盖重写抽象父亲当中所有的抽象方法。除非子类也是抽象类
    覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号{}
  4. 创建子类对象进行使用

抽象方法和抽象类的注意事项

Alt + Enter在子类继承红线处,自动重写父的抽象方法体

  1. 抽象类不能创建对象,编译报错。
    只能创建其非抽象子类的对象
  2. 抽象类中,可以有构造方法(要实现方法体),是供子类创建对象时,初始化父类成员使用的。
    因为子类构造中默认有super();或者自己指定,所以父类构造器中初始化变量就可以执行。
  3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
  4. 抽象类中的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。

● 练习

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,那么接口中可以包含的内容有:

  1. 常量
  2. 抽象方法(只定义,不实现)
    如Java8,额外包含
  3. 默认方法
  4. 静态方法
    Java9,额外包含
  5. 私有方法

接口的使用

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(){
	默认方法1methodCommon();
}
public default void methodDefault2(){
	默认方法2methodCommon();
}
private void methodCommon(){
	
}

两个静态方法调一个私有静态方法

public static void methStatic1(){
	静态方法1methodStaticCommon();
}
public static void methStatic2(){
	静态方法2methodStaticCommon();
}
pirvate static void methodStaticCommon(){

}

继承父类并实现多个接口

  1. 接口中没有静态代码块或构造方法
static{
	……
}

(复习:第一次用到本类时,静态代码块执行唯一的一次。一般用来一次性的对静态成员变量赋值。静态内容总是优先于非静态,静态代码比构造方法先执行。)
shift + F6 重命名类
Ctrl + C/V 复制整个类
实现接口时 Alt + Enter
选要实现的方法
Java中所有类都是Object的直接子类或间接子类

  1. 一个类的直接父类是唯一的,但是一个类可以同时实现多个接口
public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB{
	//覆盖重写所有抽象方法
}
  1. 如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。

❓怎么判断是否重复,返回值和名,参数列表全一致吗?(方法的三要素)

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;
    }
}
  1. 如果实现类没有覆盖重写所有接口与当中的所有抽象方法,那么实现类就必须是一个抽象类。
  2. 如多个接口中,存在重复的默认方法,实现类一定要对冲突的默认方法进行覆盖重写。
  3. 一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法因为继承优先于接口实现,也不用重写冲突的方法
public class Zi extends Fu implements MyInterface{
	
}

注意:

  1. 多个父接口当中的抽象方法如果重复,没关系。因为没有方法体,实现类中重写一次即可。抽象类作为子类或子接口可以不重写该方法
  2. 多个父接口当中的默认方法如果重复,有关系。子接口必须进行默认方法的覆盖重写,(而且带着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("狗看家");
    }
}
  1. 对象的向上转型,其实就是多态写法:
    Animal animal = new Cat();含义:右侧创建一个子类对象,把它当做父类来看待使用。注意,此时,类对象不可以调用子类Cat特有的方法(因为对于成员方法,编译看左,运行看右。父类Animal没有子类特有的方法,编译出错)
格式:父类名 对象名 = new 子类名称();

创建了一只猫,当做动物看待,没问题。
注意事项:向上转型一定是安全的。从小范围转向了大范围。(动物范围比猫更广)
类似于: double num = 100; //√,整数默认类型为int,int取值范围 < double 范围。int -> double, 自动类型转换。从小范围的猫,向上转换成为更大范围的动物
对象的向上转型,就是父类引用指向子类对象

 Animal animal = new Cat();
 animal.eat();//猫吃鱼
  1. 对象的向下转型,其实是一个还原的动作。
    格式: 子类名称 对象名 = (子类名称)父类对象;
    含义:将父类对象,还原成为本来的子类对象。
    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();
    }
}
打开笔记本
打开鼠标
点击
关闭鼠标
打开键盘
打字
关闭键盘
打开键盘
打字
关闭键盘
打开鼠标
点击
关闭鼠标
关闭笔记本
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值