【JavaSE】 P165 ~ P194 抽象方法,抽象类,接口,接口内容,多接口实现和父类继承,多态,向上转型,向下转型

目录

  • 抽象
    • 抽象的概念
    • 抽象方法和抽象类的格式
    • 抽象方法和抽象类的使用
    • 抽象方法和抽象类的注意事项
    • ● 练习
      • 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即可
抽象类中也可以写普通方法

抽象方法和抽象类的使用

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

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

注意:

  1. 多个父接口当中的抽象方法如果重复,没关系。因为没有方法体,实现类中重写一次即可。抽象类作为子类或子接口可以不重写该方法
  2. 多个父接口当中的默认方法如果重复,有关系。子接口必须进行默认方法的覆盖重写,(而且带着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("狗看家");}
}
  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(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();}
}
打开笔记本
打开鼠标
点击
关闭鼠标
打开键盘
打字
关闭键盘
打开键盘
打字
关闭键盘
打开鼠标
点击
关闭鼠标
关闭笔记本

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/713379.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NX二次开发:ListingWindow窗口的应用

一、概述 在NX二次开发的学习中&#xff0c;浏览博客时发现看到[社恐猫]和[王牌飞行员_里海]这两篇博客中写道有关信息窗口内容的打印和将窗口内容保存为txt,个人人为在二次开发项目很有必要&#xff0c;因此做以下记录。 ListingWindow信息窗口发送信息四种位置类型 设置Listi…

鸿蒙系统的开发与学习:一、安装工具与处理报错

前言&#xff1a; 鸿蒙系统的学习与记录。 1 、使用开发工具&#xff1a;deveco-studio 1&#xff09;这个是工具的安装 2&#xff09;这个是工具包&#xff0c;里面包含了 obpm&#xff0c;如果你装不上这个&#xff0c;可以使用工具包内部的 2、安装 官方安装教程&#xff…

前端学习第三天-css基础

1. CSS简介 从HTML被发明开始&#xff0c;样式就以各种形式存在。不同的浏览器结合它们各自的样式语言为用户提供页面效果的控制。最初的HTML只包含很少的显示属性。 随着HTML的成长&#xff0c;为了满足页面设计者的要求&#xff0c;HTML添加了很多显示功能。但是随着这些功能…

USB4之ASM2464PD与ASM2464PDX兼容与运用

首先在NVMe上运用: 一&#xff1a;ASM2464PD&#xff08;现在可以做带PD的方案&#xff09; 二&#xff1a;ASM2464PDX 1&#xff1a; Application Guide- CFX card reader NVMe SSD 2&#xff1a;ASM2464PDX Application Guide- NVMe SSD x4 with data clone 三&#xff…

C习题003:球筐投球(一排)

题目 输入样例 在这里给出一组输入。例如&#xff1a; 5 3 7 5 7 7 3 1 5 3 1 5 2 4 4 4输出样例 在这里给出相应的输出。例如&#xff1a; 12 10 12 16 8代码长度限制 16 KB 时间限制400 ms 内存限制 64 MB 栈限制 8192 KB 代码 #include<stdio.h> int main() {int…

新一代湖仓集存储,多模型统一架构,高效挖掘数据价值

星环科技TDH一直致力于给用户带来高性能、高可靠的一站式大数据基础平台&#xff0c;满足对海量数据的存储和复杂业务的处理需求。 同时在易用性方面持续深耕&#xff0c;降低用户开发和运维成本&#xff0c;让数据处理平民化&#xff0c;助力用户以更便捷、高效的方式去挖掘数…

jmeter如何请求访问https接口

添加线程组http请求 新建线程组&#xff0c;添加http请求 填入协议&#xff0c;ip&#xff0c;端口&#xff0c;请求类型&#xff0c;路径&#xff0c;以及请求参数&#xff0c;查看结果树等。 然后最关键的一步来了。 导入证书 步骤&#xff1a;获取证书&#xff0c;重新生…

基于SSM的高校竞赛和考级查询系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的高校竞赛和考级查询系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Sp…

Java中的动态代理与Spring AOP编程

第一章&#xff1a;引言 大家好&#xff0c;我是小黑&#xff0c;在Java里&#xff0c;动态代理和Spring AOP&#xff08;面向切面编程&#xff09;是两个能让代码更加灵活、更加干净的强大工具。作为一名Java程序员&#xff0c;小黑觉得掌握它们对于写出高质量的代码来说非常…

通过GitHub探索Python爬虫技术

1.检索爬取内容案例。 2.找到最近更新的。(最新一般都可以直接运行) 3.选择适合自己的项目&#xff0c;目前测试下面画红圈的是可行的。 4.方便大家查看就把代码粘贴出来了。 #图中画圈一代码 import requests import os import rewhile True:music_id input("请输入歌曲…

鸿蒙Harmony应用开发—ArkTS声明式开发(通用属性:位置设置)

设置组件的对齐方式、布局方向和显示位置。 说明&#xff1a; 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 align align(value: Alignment) 设置容器元素绘制区域内的子元素的对齐方式。 卡片能力&#xff1a; 从API…

【系统分析师】-系统配置与性能评价

1、性能指标 主频&#xff1a;又称时钟频率&#xff0c;1GHZ表示1秒有1G个时钟周期 1s10^9ns 主频外频 * 倍频 时钟周期 主频的倒数指令周期&#xff1a;取出并执行一条指令的时间 总线周期&#xff1a;一个访存储器或IO操作所用时间平均执行周期数&#xff1a;CPI表示…

如何解决幻兽帕鲁/Palworld服务器联机游戏时的丢包问题?

如何解决幻兽帕鲁/Palworld服务器联机游戏时的丢包问题&#xff1f; 等待服务器维护&#xff1a;首先&#xff0c;确保网络连接稳定&#xff0c;然后查看游戏官方或社区论坛&#xff0c;了解是否有服务器维护的消息。这是解决丢包问题的一种直接且有效的方法。 更新显卡驱动&a…

Siemens-NXUG二次开发-获取prt中体与类型、实体面与类型、实体边与类型、边上点的Tag标识[Python UF][20240302]

Siemens-NXUG二次开发-获取prt中体与类型、实体面与类型、实体边与类型、边上点的Tag标识[Python UF][20240302] 1.python uf函数1.1 NXOpen.UF.Obj.CycleObjsInPart1.2 NXOpen.UF.Obj.AskTypeAndSubtype1.3 NXOpen.UF.Modeling.AskBodyFaces1.4 NXOpen.UF.Modeling.AskFaceEdg…

RISC-V特权架构 - 机器模式下的异常处理

RISC-V特权架构 - 机器模式下的异常处理 1 进入异常1.1 从mtvec 定义的PC 地址开始执行1.2 更新CSR 寄存器mcause1.3 更新CSR 寄存器mepc1.4 更新CSR 寄存器mtval1.5 更新CSR 寄存器mstatus 2 退出异常2.1 从mepc 定义的PC 地址开始执行2.2 更新CSR 寄存器mstatus 3 异常服务程…

Android Tombstone 分析

1.什么是tombstone Tombstone是指在分布式系统中用于标记数据已被删除的记录&#xff0c;通常包含删除操作的时间戳和相关信息。 当一个动态库&#xff08;native程序&#xff09;开始执行时&#xff0c;系统会注册一些连接到 debuggerd 的signal handlers。当系统发生崩溃时…

Spark Shuffle Tracking 原理分析

Shuffle Tracking Shuffle Tracking 是 Spark 在没有 ESS(External Shuffle Service)情况&#xff0c;并且开启 Dynamic Allocation 的重要功能。如在 K8S 上运行 spark 没有 ESS。本文档所有的前提都是基于以上条件的。 如果开启了 ESS&#xff0c;那么 Executor 计算完后&a…

MySQL 表的基本操作,结合项目的表自动初始化来讲

有了数据库以后&#xff0c;我们就可以在数据库中对表进行增删改查了&#xff0c;这也就意味着&#xff0c;一名真正的 CRUD Boy 即将到来&#xff08;&#x1f601;&#xff09;。 查表 查看当前数据库中所有的表&#xff0c;使用 show tables; 命令 由于当前数据库中还没有…

基于Python3的数据结构与算法 - 09 希尔排序

一、引入 希尔排序是一种分组插入排序的算法。 二、排序思路 首先取一个整数d1 n/2&#xff0c;将元素分为d1个组&#xff0c;每组相邻量取元素距离为d1&#xff0c;在各组内直接进行插入排序&#xff1b;取第二个整数d2 d1/2&#xff0c; 重复上述分组排序过程&#xff0…

CSS 自测题 -- 用 flex 布局绘制骰子(一、二、三【含斜三点】、四、五、六点)

一点 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>css flex布局-画骰子</title><sty…