06-第六章 this 继承 和 多态

day16课堂笔记

1、this 
1.1、this是一个关键字,是一个引用,保存内存地址指向自身。
1.2、this可以使用在实例方法中,也可以使用在构造方法中。
1.3、this出现在实例方法中其实代表的是当前对象。
1.4、this不能使用在静态方法中。
1.5、this. 大部分情况下可以省略,但是用来区分局部变量和实例变量的时候不能省略。
1.6、this() 这种语法只能出现在构造方法第一行,表示当前构造方法调用本类其他的
构造方法,目的是代码复用。

2、总结所有的变量怎么访问,总结所有的方法怎么访问!!!!
总结一下到目前为止我们在一个类中都接触过什么了。

 

复制代码
// editPlus中蓝色是关键字
// 黑色是标识符
// System.out.println("Hello World!"); 
// 以上代码中:System、out、println都是标识符。
// 在 editplus中的红色字体,表示这个类是SUN的JDK写好的一个类。
public class Test{

    // 静态变量
    static Student stu = new Student();

    // 入口
    public static void main(String[] args){
        
        //拆分为两行
        Student s = Test.stu;
        s.exam();

        //合并代码
        Test.stu.exam();

        System.out.println("Hello World!");
    }
}

class Student{

    // 实例方法
    public void exam(){
        System.out.println("考试。。。。。");
    }
}
复制代码

 

3、继承extends
3.1、什么是继承,有什么用?
继承:在现实世界当中也是存在的,例如:父亲很有钱,儿子不用努力也很有钱。
继承的作用:
基本作用:子类继承父类,代码可以得到复用。(这个不是重要的作用,是基本作用。)
主要(重要)作用:因为有了继承关系,才有了后期的方法覆盖和多态机制。

3.2、继承的相关特性
① B类继承A类,则称A类为超类(superclass)、父类、基类,
B类则称为子类(subclass)、派生类、扩展类。
class A{}
class B extends A{}
我们平时聊天说的比较多的是:父类和子类。
superclass 父类
subclass 子类

② java 中的继承只支持单继承,不支持多继承,C++中支持多继承,
这也是 java 体现简单性的一点,换句话说,java 中不允许这样写代码:
class B extends A,C{ } 这是错误的。

③ 虽然 java 中不支持多继承,但有的时候会产生间接继承的效果,
例如:class C extends B,class B extends A,也就是说,C 直接继承 B,
其实 C 还间接继承 A。

④ java 中规定,子类继承父类,除构造方法不能继承之外,剩下都可以继承。
但是私有的属性无法在子类中直接访问。(父类中private修饰的不能在子类中
直接访问。可以通过间接的手段来访问。)

⑤ java 中的类没有显示的继承任何类,则默认继承 Object类,Object类是
java 语言提供的根类(老祖宗类),也就是说,一个对象与生俱来就有
Object类型中所有的特征。

⑥ 继承也存在一些缺点,例如:CreditAccount 类继承 Account 类会导致它
们之间的耦合度非常高,Account 类发生改变之后会马上影响到 CreditAccount 类

 

复制代码
// 分析以下程序存在什么问题?代码臃肿。代码没有得到重复利用。
public class ExtendsTest01{
    public static void main(String[] args){
        // 创建普通账户
        Account act = new Account();
        act.setActno("1111111");
        act.setBalance(10000);
        System.out.println(act.getActno() + ",余额" + act.getBalance());

        // 创建信用账户
        CreditAccount ca = new CreditAccount();
        ca.setActno("2222222");
        ca.setBalance(-10000);
        ca.setCredit(0.99);
        System.out.println(ca.getActno() + ",余额" + ca.getBalance() + ",信誉度" + ca.getCredit());
    }
}

// 银行账户类
// 账户的属性:账号、余额
class Account{
    // 属性
    private String actno;
    private double balance;

    // 构造方法
    public Account(){
    
    }
    public Account(String actno, double balance){
        this.actno = actno;
        this.balance = balance;
    }

    // setter and getter
    public void setActno(String actno){
        this.actno = actno;
    }
    public String getActno(){
        return actno;
    }
    public void setBalance(double balance){
        this.balance = balance;
    }
    public double getBalance(){
        return balance;
    }
}

// 其它类型的账户:信用卡账户
// 账号、余额、信誉度
class CreditAccount{
    // 属性
    private String actno;
    private double balance;
    private double credit;

    // 构造方法
    public CreditAccount(){
    
    }

    // setter and getter方法
    public void setActno(String actno){
        this.actno = actno;
    }
    public String getActno(){
        return actno;
    }
    public void setBalance(double balance){
        this.balance = balance;
    }
    public double getBalance(){
        return balance;
    }

    public void setCredit(double credit){
        this.credit = credit;
    }
    public double getCredit(){
        return credit;
    }
    
}
复制代码
复制代码
// 使用继承机制来解决代码复用问题。
// 继承也是存在缺点的:耦合度高,父类修改,子类受牵连。
public class ExtendsTest02{
    public static void main(String[] args){
        // 创建普通账户
        Account act = new Account();
        act.setActno("1111111");
        act.setBalance(10000);
        System.out.println(act.getActno() + ",余额" + act.getBalance());

        // 创建信用账户
        CreditAccount ca = new CreditAccount();
        ca.setActno("2222222");
        ca.setBalance(-10000);
        ca.setCredit(0.99);
        System.out.println(ca.getActno() + ",余额" + ca.getBalance() + ",信誉度" + ca.getCredit());
    }
}

// 银行账户类
// 账户的属性:账号、余额
class Account{ // 父类
    // 属性
    private String actno;
    private double balance;

    // 构造方法
    public Account(){
    
    }
    public Account(String actno, double balance){
        this.actno = actno;
        this.balance = balance;
    }

    // setter and getter
    public void setActno(String actno){
        this.actno = actno;
    }
    public String getActno(){
        return actno;
    }
    public void setBalance(double balance){
        this.balance = balance;
    }
    public double getBalance(){
        return balance;
    }
}

// 其它类型的账户:信用卡账户
// 账号、余额、信誉度
class CreditAccount extends Account{ //子类

    // 属性
    private double credit;

    // 构造方法
    public CreditAccount(){
    
    }

    public void doSome(){
        //错误: actno 在 Account 中是 private 访问控制
        //System.out.println(actno);
        // 间接访问
        //System.out.println(this.getActno());
        System.out.println(getActno());
    }

    // setter and getter方法
    public void setCredit(double credit){
        this.credit = credit;
    }
    public double getCredit(){
        return credit;
    }
    
}
复制代码

 

 

 

 

day17课堂笔记

1、继承extends

1.1、测试:子类继承父类之后,能使用子类对象调用父类方法吗?
可以,因为子类继承了父类之后,这个方法就属于子类了。
当然可以使用子类对象来调用。

1.2、在实际开发中,满足什么条件的时候,我可以使用继承呢?
凡是采用“is a”能描述的,都可以继承。
例如:
Cat is a Animal:猫是一个动物
Dog is a Animal:狗是一个动物
CreditAccount is a Account:信用卡账户是一个银行账户
....

假设以后的开发中有一个A类,有一个B类,A类和B类确实也有重复的代码,
那么他们两个之间就可以继承吗?不一定,还是要看一看它们之间是否能够
使用is a来描述。

class Customer{
String name; // 名字
// setter and getter
}

class Product{
String name; // 名字
// setter and getter
}

class Product extends Customer{

}

以上的继承就属于很失败的。因为:Product is a Customer,是有违伦理的。

1.3、任何一个类,没有显示继承任何类,默认继承Object,那么Object类当中有
哪些方法呢?老祖宗为我们提供了哪些方法?

以后慢慢的大家一定要适应看JDK的源代码(多看看牛人写的程序自己才会变成牛人。)
先模仿后超越。
java为什么比较好学呢?
是因为Java内置了一套庞大的类库,程序员不需要从0开始写代码,程序员可以
基于这套庞大的类库进行“二次”开发。(开发速度较快,因为JDK内置的这套库
实现了很多基础的功能。)

例如:String是SUN编写的字符串类、System是SUN编写的系统类。
这些类都可以拿来直接使用。

JDK源代码在什么位置?
C:\Program Files\Java\jdk-13.0.2\lib\src.zip

你现在能看懂以下代码了吗?
System.out.println("Hello World!");
System.out 中,out后面没有小括号,说明out是变量名。
另外System是一个类名,直接使用类名System.out,说明out是一个静态变量。
System.out 返回一个对象,然后采用“对象.”的方式访问println()方法。

我们研究了一下Object类当中有很多方法,大部分看不懂,其中有一个叫做toString()
的,我们进行了测试,发现:
System.out.println(引用);
当直接输出一个“引用”的时候,println()方法会先自动调用“引用.toString()”,然后
输出toString()方法的执行结果。

复制代码
class A
{
}

class B
{
}

class C extends A
{
}

class D extends B
{
}

// 语法错误
// java只允许单继承。不允许多继承。java是简单的。C++支持多重继承。
// C++更接近现实一些。因为在现实世界中儿子同时继承父母两方特征。
/*
class E extends A, B
{
}
*/

class X
{
}

class Y extends X
{
}

class M extends X
{
}

// 其实这也说明了Z是继承X和Y的。
// 这样描述:Z直接继承了Y,Z间接继承了X
class Z extends Y
{
}

/*

    Z继承了Y
    Y继承了X
    X继承了Object

    Z对象具有Object对象的特征(基因)。

    Object是所有类的超类。老祖宗。类体系结构中的根。
    java这么庞大的一个继承结构,最顶点是:Object
*/
复制代码
复制代码
/*
测试:子类继承父类之后,能使用子类对象调用父类方法吗
    实际上以上的这个问题问的有点蹊跷!!!!!
    哪里蹊跷?“能使用子类对象调用父类方法”
    本质上,子类继承父类之后,是将父类继承过来的方法归为自己所有。
    实际上调用的也不是父类的方法,是他子类自己的方法(因为已经继承过来了
    就属于自己的。)。
        
*/
public class ExtendsTest04{
    public static void main(String[] args){
        // 创建子类对象
        Cat c = new Cat();
        // 调用方法
        c.move();
        // 通过子类对象访问name可以吗?
        System.out.println(c.name);
    }
}

// 父类
//class Animal extends Object {
class Animal{
    // 名字(先不封装)
    String name = "XiaoHua"; //默认值不是null,给一个XiaoHua

    // 提供一个动物移动的方法
    public void move(){
        System.out.println(name + "正在移动!");
    }
}

// Cat子类
// Cat继承Animal,会将Animal中所有的全部继承过来。
class Cat extends Animal{
}
复制代码
复制代码
//默认继承Object,Object类中有哪些方法呢?
/*
public class Object {
     
     // 注意:当源码当中一个方法以“;”结尾,并且修饰符列表中有“native”关键字
     // 表示底层调用C++写的dll程序(dll动态链接库文件)
    private static native void registerNatives();

     // 静态代码块
    static {
          // 调用registerNatives()方法。
        registerNatives();
    }

     // 无参数构造方法
    @HotSpotIntrinsicCandidate
    public Object() {}

     // 底层也是调用C++
    @HotSpotIntrinsicCandidate
    public final native Class<?> getClass();

     // 底层也是调用C++
    @HotSpotIntrinsicCandidate
    public native int hashCode();

     // equals方法你应该能看懂。
     // public是公开的
     // boolean 是方法的返回值类型
     // equals 是一个方法名:相等
     // (Object obj) 形参
     // 只不过目前还不知道这个方法存在的意义。
    public boolean equals(Object obj) {
         //方法体
       return (this == obj);
    }
    
     // 已有对象a,想创建一个和a一模一样的对象,你可以调用这个克隆方法。
     // 底层也是调用C++
    @HotSpotIntrinsicCandidate
    protected native Object clone() throws CloneNotSupportedException;

     // 一会我们可以测试一下toString()方法。
     // public表示公共的
     // String 是返回值类型,toString()方法执行结束之后返回一个字符串。
     // toString 这是方法名。
     // () 表示形参个数为0
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    @HotSpotIntrinsicCandidate
    public final native void notify();

    @HotSpotIntrinsicCandidate
    public final native void notifyAll();

    public final void wait() throws InterruptedException {
        wait(0L);
    }

    public final native void wait(long timeoutMillis) throws InterruptedException;

    public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
        if (timeoutMillis < 0) {
            throw new IllegalArgumentException("timeoutMillis value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0 && timeoutMillis < Long.MAX_VALUE) {
            timeoutMillis++;
        }

        wait(timeoutMillis);
    }
    @Deprecated(since="9")
    protected void finalize() throws Throwable { }
}
*/
public class ExtendsTest05 {

    // ExtendsTest05默认继承Object
    // ExtendsTest05类当中是有toString()方法
    // 不过toString()方法是一个实例方法,需要创建对象才能调用。
    /*
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    */
    
    public static void main(String[] args){

        // 分析这个代码可以执行吗?
        //ExtendsTest05.toString();

        // 先new对象
        ExtendsTest05 et = new ExtendsTest05();
        String retValue = et.toString();

        // 2f92e0f4 可以“等同”看做对象在堆内存当中的内存地址。
        // 实际上是内存地址经过“哈希算法”得出的十六进制结果。
        System.out.println(retValue); // ExtendsTest05@2f92e0f4

        // 创建对象
        Product pro = new Product();

        String retValue2 = pro.toString();
        System.out.println(retValue2); // Product@5305068a

        // 以上两行代码能否合并为一行!!!可以
        System.out.println(pro.toString()); //Product@5305068a

        // 如果直接输出“引用”呢???????
        System.out.println(pro); //Product@5305068a

        System.out.println(100);
        System.out.println(true);
        // Product@5305068a
        System.out.println(pro); // println方法会自动调用pro的toString()方法。
    }
}

class Product{
    /*
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    */
}
复制代码

 

 



2、方法覆盖

2.1、什么时候考虑使用方法覆盖?
父类中的方法无法满足子类的业务需求,子类有必要对继承过来的方法进行覆盖。

2.2、什么条件满足的时候构成方法覆盖?
第一:有继承关系的两个类
第二:具有相同方法名、返回值类型、形式参数列表
第三:访问权限不能更低。
第四:抛出异常不能更多。

2.3、关于Object类中toString()方法的覆盖?
toString()方法存在的作用就是:将java对象转换成字符串形式。
大多数的java类toString()方法都是需要覆盖的。因为Object类中提供的toString()
方法输出的是一个java对象的内存地址。

至于toString()方法具体怎么进行覆盖?
格式可以自己定义,或者听需求的。(听项目要求的。)

2.4、方法重载和方法覆盖有什么区别?

方法重载发生在同一个类当中。

方法覆盖是发生在具有继承关系的父子类之间。

方法重载是一个类中,方法名相同,参数列表不同。

方法覆盖是具有继承关系的父子类,并且重写之后的方法必须和之前的方法一致:
方法名一致、参数列表一致、返回值类型一致。

 

复制代码
/*
    当前程序存在的问题(设计上的问题)????
        鸟儿在执行move()方法的时候,最好输出的结果是:“鸟儿在飞翔”
        但是当前的程序在执行move()方法的时候输出的结果是:"动物在移动!"
        很显然Bird子类从Animal父类中继承过来的move()方法已经无法满足子类的业务需求。

*/
public class OverrideTest01{
    public static void main(String[] args){
        // 创建鸟对象
        Bird b = new Bird();
        // 让鸟儿移动
        b.move();

        // 创建Cat类型对象
        Cat c = new Cat();
        c.move();
    }
}

// 父类 
class Animal{

    // 移动
    public void move(){
        System.out.println("动物在移动!");
    }

}

// 子类
class Bird extends Animal{

    // 子类继承父类中,有一些“行为”可能不需要改进,有一些“行为”可能面临着必须改进。
    // 因为父类中继承过来的方法已经无法满足子类的业务需求。

    // 鸟儿在移动的时候希望输出鸟儿在飞翔!!!!
}

class Cat extends Animal{
    // 猫在移动的时候,我希望输出:猫在走猫步!!!!!!
}
复制代码

 

复制代码
/*
    回顾一下方法重载!!!!
        什么时候考虑使用方法重载overload?
            当在一个类当中,如果功能相似的话,建议将名字定义的一样,这样
            代码美观,并且方便编程。
        
        什么条件满足之后能够构成方法重载overload?
            条件一:在同一个类当中
            条件二:方法名相同
            条件三:参数列表不同(个数、顺序、类型)

    --------------------------------------------------------------------------------

    什么时候我们会考虑使用“方法覆盖”呢?
        子类继承父类之后,当继承过来的方法无法满足当前子类的业务需求时,
        子类有权利对这个方法进行重新编写,有必要进行“方法的覆盖”。
    
    方法覆盖又叫做:方法重写(重新编写),英语单词叫做:Override、Overwrite,都可以。
    比较常见的:方法覆盖、方法重写、override

    重要结论:
        当子类对父类继承过来的方法进行“方法覆盖”之后,
        子类对象调用该方法的时候,一定执行覆盖之后的方法。

    当我们代码怎么编写的时候,在代码级别上构成了方法覆盖呢?
        条件一:两个类必须要有继承关系。
        条件二:重写之后的方法和之前的方法具有:
                    相同的返回值类型、
                    相同的方法名、
                    相同的形式参数列表。
        条件三:访问权限不能更低,可以更高。(这个先记住。)
        条件四:重写之后的方法不能比之前的方法抛出更多的异常,可以更少。(这个先记住)
    
    这里还有几个注意事项:(这几个注意事项,当学习了多态语法之后自然就明白了!)
        注意1:方法覆盖只是针对于方法,和属性无关。
        注意2:私有方法无法覆盖。
        注意3:构造方法不能被继承,所以构造方法也不能被覆盖。
        注意4:方法覆盖只是针对于“实例方法”,“静态方法覆盖”没有意义。

*/
public class OverrideTest02{
    public static void main(String[] args){
        Bird b = new Bird();
        b.move();
        b.sing(1000); //Animal sing....

        Cat c = new Cat();
        c.move();
    }
}

class Animal{
    public void move(){
        System.out.println("动物在移动!");
    }

    public void sing(int i){
        System.out.println("Animal sing....");
    }
}

class Bird extends Animal{

    // 对move方法进行方法覆盖,方法重写,override
    // 最好将父类中的方法原封不动的复制过来。(不建议手动编写)
    // 方法覆盖,就是将继承过来的那个方法给覆盖掉了。继承过来的方法没了。
    public void move(){
        System.out.println("鸟儿在飞翔!!!");
    }

    //protected表示受保护的。没有public开放。
    // 错误:正在尝试分配更低的访问权限; 以前为public
    /*
    protected void move(){
        System.out.println("鸟儿在飞翔!!!");
    }
    */

    //错误:被覆盖的方法未抛出Exception
    /*
    public void move() throws Exception{
        System.out.println("鸟儿在飞翔!!!");
    }
    */

    // 分析:这个sing()和父类中的sing(int i)有没有构成方法覆盖呢?
    // 没有,原因是,这两个方法根本就是两个完全不同的方法。
    // 可以说这两个方法构成了方法重载吗?可以。
    public void sing(){
        System.out.println("Bird sing.....");
    }
}

class Cat extends Animal{

    // 方法重写
    public void move(){
        System.out.println("猫在走猫步!!!");
    }
}
复制代码
复制代码
//方法覆盖比较经典的案例
//一定要注意:方法覆盖/重写的时候,建议将父类的方法复制粘贴,这样比较保险。
public class OverrideTest03{
    public static void main(String[] args){
        // 创建中国人对象
        // ChinaPeople p1 = new ChinaPeople("张三");// 错误原因:没有这样的构造方法
        ChinaPeople p1 = new ChinaPeople();
        p1.setName("张三");
        p1.speak();

        // 创建美国人对象
        // AmericPeople p2 = new AmericPeople("jack"); // 错误原因:没有这样的构造方法
        AmericPeople p2 = new AmericPeople();
        p2.setName("jack");
        p2.speak();
    }
}

//
class People{
    // 属性
    private String name;
    // 构造
    public People(){}
    public People(String name){
        this.name = name;
    }
    //setter and getter
    public void setName(String name){
        this.name = name;    
    }
    public String getName(){
        return name;
    }
    // 人都会说话
    public void speak(){
        System.out.println(name + "....");
    }
}

// 中国人
class ChinaPeople extends People{

    // 中国人说话是汉语
    // 所以子类需要对父类的speak()方法进行重写
    public void speak(){
        System.out.println(this.getName() + "正在说汉语");
    }
}


// 美国人
class AmericPeople extends People{
    // 美国人说话是英语
    // 所以子类需要对父类的speak()方法进行重写
    public void speak(){
        System.out.println(getName() + " speak english!");
    }
}
复制代码
复制代码
/*
    关于Object类中的toString()方法
        1、toString()方法的作用是什么?
            作用:将“java对象”转换成“字符串的形式”。

        2、Object类中toString()方法的默认实现是什么?
            public String toString() {
                return getClass().getName() + "@" + Integer.toHexString(hashCode());
            }
            toString: 方法名的意思是转换成String
            含义:调用一个java对象的toString()方法就可以将该java对象转换成字符串的表示形式。

        3、那么toString()方法给的默认实现够用吗?
*/
public class OverrideTest04{
    public static void main(String[] args){
        // 创建一个日期对象
        MyDate t1 = new MyDate();
        // 调用toString()方法(将对象转换成字符串形式。)
        // 问:你对这个输出结果满意吗?不满意,希望输出:xxxx年xx月xx日
        // 重写MyDate的toString()方法之前的结果
        //System.out.println(t1.toString()); //MyDate@28a418fc 

        // 重写MyDate的toString()方法之后的结果
        System.out.println(t1.toString());

        // 大家是否还记得:当输出一个引用的时候,println方法会自动调用引用的toString方法。
        System.out.println(t1);

        MyDate t2 = new MyDate(2008, 8, 8);
        System.out.println(t2); //2008年8月8日

        //创建学生对象
        Student s = new Student(1111, "zhangsan");
        // 重写toString()方法之前
        //System.out.println(s); //Student@87aac27
        // 重写toString()方法之后
        // 输出一个学生对象的时候,可能更愿意看到学生的信息,不愿意看到对象的内存地址。
        System.out.println(s.toString());
        System.out.println(s);
    }
}

// 日期类
class MyDate {
    private int year;
    private int month;
    private int day;
    public MyDate(){
        this(1970,1,1);
    }
    public MyDate(int year,int month,int day){
        this.year = year;
        this.month = month;
        this.day = day;
    }
    public void setYear(int year){
        this.year = year;
    }
    public int getYear(){
        return year;
    }
    public void setMonth(int month){
        this.month = month;
    }
    public int getMonth(){
        return month;
    }
    public void setDay(int day){
        this.day = day;
    }
    public int getDay(){
        return day;
    }

    // 从Object类中继承过来的那个toString()方法已经无法满足我业务需求了。
    // 我在子类MyDate中有必要对父类的toString()方法进行覆盖/重写。
    // 我的业务要求是:调用toString()方法进行字符串转换的时候,
    // 希望转换的结果是:xxxx年xx月xx日,这种格式。
    // 重写一定要复制粘贴,不要手动编写,会错的。
    public String toString() {
        return year + "年" + month + "月" + day + "日";
    }
}

class Student{
    int no;
    String name;
    public Student(int no, String name){
        this.no = no;
        this.name = name;
    }
    // 重写  方法覆盖
    public String toString() {
        return "学号:" + no + ",姓名:" + name;
    }
}
复制代码

 

 

 

 

 

3、多态的基础语法

3.1、向上转型和向下转型的概念。

向上转型:子--->父 (upcasting)
又被称为自动类型转换:Animal a = new Cat();

向下转型:父--->子 (downcasting)
又被称为强制类型转换:Cat c = (Cat)a; 需要添加强制类型转换符。
什么时候需要向下转型?
需要调用或者执行子类对象中特有的方法。
必须进行向下转型,才可以调用。
向下转型有风险吗?
容易出现ClassCastException(类型转换异常)
怎么避免这个风险?
instanceof运算符,可以在程序运行阶段动态的判断某个引用指向的对象
是否为某一种类型。
养成好习惯,向下转型之前一定要使用instanceof运算符进行判断。

不管是向上转型还是向下转型,首先他们之间必须有继承关系,这样编译器就不会报错。

3.2、什么是多态。
多种形态,多种状态,编译和运行有两个不同的状态。
编译期叫做静态绑定。
运行期叫做动态绑定。
Animal a = new Cat();
// 编译的时候编译器发现a的类型是Animal,所以编译器会去Animal类中找move()方法
// 找到了,绑定,编译通过。但是运行的时候和底层堆内存当中的实际对象有关
// 真正执行的时候会自动调用“堆内存中真实对象”的相关方法。
a.move();

多态的典型代码:父类型的引用指向子类型的对象。(java中允许这样写代码!!!)

3.3、什么时候必须进行向下转型?
调用子类对象上特有的方法时。

 

复制代码
// 动物类:父类
public class Animal{

    // 移动的方法
    public void move(){
        System.out.println("动物在移动!!!");
    }

}
复制代码

 

复制代码
// 猫类,子类
public class Cat extends Animal{

    // 对move方法进行重写
    public void move(){
        System.out.println("cat走猫步!");
    }

    // 猫除了move之外,应该有自己特有的行为,例如抓老鼠。
    // 这个行为是子类型对象特有的方法。
    public void catchMouse(){
        System.out.println("猫正在抓老鼠!!!!");
    }

}
复制代码
复制代码
// 鸟儿类,子类
public class Bird extends Animal{

    // 重写父类的move方法
    public void move(){
        System.out.println("鸟儿在飞翔!!!");
    }

    // 也有自己特有的方法
    public void sing(){
        System.out.println("鸟儿在歌唱!!!");
    }

}
复制代码
// Dog并没有继承Animal
// Dog不是Animal的子类
public class Dog{

}
复制代码
/*
    多态的基础语法:
        1、学习多态基础语法之前,我们需要普及两个概念:
            第一个:向上转型
                子 ---> 父(自动类型转换)
            第二个:向下转型
                父 ---> 子(强制类型转换,需要加强制类型转换符)

            注意:
                java中允许向上转型,也允许向下转型。

                *****(五颗星)无论是向上转型,还是向下转型,
                        两种类型之间必须有继承关系,没有继承关系编译器报错。
                
                以后在工作过程中,和别人聊天的时候,要专业一些,说
                向上转型和向下转型,不要说自动类型转换,也不要说强制
                类型转换,因为自动类型转换和强制类型转换是使用在基本
                数据类型方面的,在引用类型转换这里只有向上和向下转型。
        
        2、多态指的是:
            父类型引用指向子类型对象。
            包括编译阶段和运行阶段。
            编译阶段:绑定父类的方法。
            运行阶段:动态绑定子类型对象的方法。
            多种形态。
        
        3、个别同学有点混乱了:
            java中只有“类名”或者“引用”才能去“点”
            类名.
            引用.
            万变不离其宗,只要你想“点”,“点”前面要么是一个类名,要么是一个引用。
        
        4、什么时候必须使用“向下转型”?
            不要随便做强制类型转换。
            当你需要访问的是子类对象中“特有”的方法。此时必须进行向下转型。
*/
public class Test01{

    public static void main(String[] args){

        Animal a1 = new Animal();
        a1.move(); //动物在移动!!!

        Cat c1 = new Cat();
        c1.move(); //cat走猫步!

        Bird b1 = new Bird();
        b1.move(); //鸟儿在飞翔!!!

        // 代码可以这样写吗?
        /*
            1、Animal和Cat之间有继承关系吗?有的。
            2、Animal是父类,Cat是子类。
            3、Cat is a Animal,这句话能不能说通?能。
            4、经过测试得知java中支持这样的一个语法:
                父类型的引用允许指向子类型的对象。
                Animal a2 = new Cat();
                a2就是父类型的引用。
                new Cat()是一个子类型的对象。
                允许a2这个父类型引用指向子类型的对象。
        */
        Animal a2 = new Cat();
        Animal a3 = new Bird();

        // 没有继承关系的两个类型之间存在转型吗?
        // 错误: 不兼容的类型: Dog无法转换为Animal
        // Animal a4 = new Dog();

        // 调用a2的move()方法
        /*
            什么是多态?
                多种形态,多种状态。
            分析:a2.move();
                java程序分为编译阶段和运行阶段。
                先来分析编译阶段:
                    对于编译器来说,编译器只知道a2的类型是Animal,
                    所以编译器在检查语法的时候,会去Animal.class
                    字节码文件中找move()方法,找到了,绑定上move()
                    方法,编译通过,静态绑定成功。(编译阶段属于静态绑定。)
                再来分析运行阶段:
                    运行阶段的时候,实际上在堆内存中创建的java对象是
                    Cat对象,所以move的时候,真正参与move的对象是一只猫,
                    所以运行阶段会动态执行Cat对象的move()方法。这个过程
                    属于运行阶段绑定。(运行阶段绑定属于动态绑定。)

            多态表示多种形态:
                编译的时候一种形态。
                运行的时候另一种形态。
        */
        a2.move(); //cat走猫步!
        
        // 调用a3的move()方法
        a3.move(); //鸟儿在飞翔!!!

        // ======================================================================
        Animal a5 = new Cat(); // 底层对象是一只猫。

        // 分析这个程序能否编译和运行呢?
        // 分析程序一定要分析编译阶段的静态绑定和运行阶段的动态绑定。
        // 只有编译通过的代码才能运行。没有编译,根本轮不到运行。
        // 错误: 找不到符号
        // why??? 因为编译器只知道a5的类型是Animal,去Animal.class文件中找catchMouse()方法
        // 结果没有找到,所以静态绑定失败,编译报错。无法运行。(语法不合法。)
        //a5.catchMouse(); 
        
        // 假设代码写到了这里,我非要调用catchMouse()方法怎么办?
        // 这个时候就必须使用“向下转型”了。(强制类型转换)
        // 以下这行代码为啥没报错????
        // 因为a5是Animal类型,转成Cat,Animal和Cat之间存在继承关系。所以没报错。
        Cat x = (Cat)a5;
        x.catchMouse(); //猫正在抓老鼠!!!!

        // 向下转型有风险吗?
        Animal a6 = new Bird(); //表面上a6是一个Animal,运行的时候实际上是一只鸟儿。
        /*
            分析以下程序,编译报错还是运行报错???
                编译器检测到a6这个引用是Animal类型,
                而Animal和Cat之间存在继承关系,所以可以向下转型。
                编译没毛病。

                运行阶段,堆内存实际创建的对象是:Bird对象。
                在实际运行过程中,拿着Bird对象转换成Cat对象
                就不行了。因为Bird和Cat之间没有继承关系。
            
            运行是出现异常,这个异常和空指针异常一样非常重要,也非常经典:
                java.lang.ClassCastException:类型转换异常。
            
            java.lang.NullPointerException:空指针异常。这个也非常重要。
        */
        //Cat y = (Cat)a6;
        //y.catchMouse();

        // 怎么避免ClassCastException异常的发生???
        /*    
            新的内容,运算符:
                instanceof (运行阶段动态判断)
            第一:instanceof可以在运行阶段动态判断引用指向的对象的类型。
            第二:instanceof的语法:
                (引用 instanceof 类型)
            第三:instanceof运算符的运算结果只能是:true/false
            第四:c是一个引用,c变量保存了内存地址指向了堆中的对象。
                假设(c instanceof Cat)为true表示:
                    c引用指向的堆内存中的java对象是一个Cat。
                假设(c instanceof Cat)为false表示:
                    c引用指向的堆内存中的java对象不是一个Cat。
            
            程序员要养成一个好习惯:
                任何时候,任何地点,对类型进行向下转型时,一定要使用
                instanceof 运算符进行判断。(java规范中要求的。)
                这样可以很好的避免:ClassCastException
        */
        System.out.println(a6 instanceof Cat); //false

        if(a6 instanceof Cat){ // 如果a6是一只Cat
            Cat y = (Cat)a6;  // 再进行强制类型转换
            y.catchMouse();
        }
    }
}
复制代码
复制代码
/*
    这个代码的疑问?
        肉眼可以观察到底层到底是new Bird()还是new Cat()!!
        我们为什么还要进行instanceof的判断呢!!!!

        原因是:
            你以后可能肉眼看不到。
*/
public class Test02{
    public static void main(String[] args){
        Animal x = new Bird();
        Animal y = new Cat();

        if(x instanceof Bird){
            Bird b = (Bird)x;
            b.sing();
        } else if(x instanceof Cat){
            Cat c = (Cat)x;
            c.catchMouse();
        }


        if(y instanceof Bird){
            Bird b = (Bird)y;
            b.sing();
        } else if(y instanceof Cat){
            Cat c = (Cat)y;
            c.catchMouse();
        }
    }
}
复制代码
复制代码
public class Test03{
    public static void main(String[] args){
        // main方法是程序员A负责编写。
        AnimalTest at = new AnimalTest();
        at.test(new Cat());
        at.test(new Bird());
    }
}
复制代码
复制代码
public class Test03{
    public static void main(String[] args){
        // main方法是程序员A负责编写。
        AnimalTest at = new AnimalTest();
        at.test(new Cat());
        at.test(new Bird());
    }
}
复制代码
复制代码
public class AnimalTest{
    
    // test方法是程序员B负责编写。
    // 这个test()方法的参数是一个Animal
    public void test(Animal a){ // 实例方法
        // 你写的这个方法别人会去调用。
        // 别人调用的时候可能给你test()方法传过来一个Bird
        // 当然也可能传过来一个Cat
        // 对于我来说,我不知道你调用的时候给我传过来一个啥。
        if(a instanceof Cat){
            Cat c = (Cat)a;
            c.catchMouse();
        }else if(a instanceof Bird){
            Bird b = (Bird)a;
            b.sing();
        }
    }

}
复制代码

 

 

 

 

 

day18课堂笔记

1、多态在开发中有什么作用?
非常重要:五颗星。。。。(多态你会天天用,到处用!!!!)

多态在开发中的作用是:
降低程序的耦合度,提高程序的扩展力。

public class Master{
public void feed(Dog d){}
public void feed(Cat c){}
}
以上的代码中表示:Master和Dog以及Cat的关系很紧密(耦合度高)。导致扩展力很差。

public class Master{
public void feed(Pet pet){
pet.eat();
}
}
以上的代表中表示:Master和Dog以及Cat的关系就脱离了,Master关注的是Pet类。
这样Master和Dog以及Cat的耦合度就降低了,提高了软件的扩展性。

复制代码
// 所有宠物的父类
public class Pet{

    // 吃的行为(这个方法可以不给具体的实现。)
    public void eat(){
    
    }


}
复制代码
复制代码
// 主人类
public class Master{

    /*
    // 假设主人起初的时候只是喜欢养宠物狗狗
    // 喂养宠物狗狗
    public void feed(Dog d){
        d.eat();
    }

    // 由于新的需求产生,导致我们“不得不”去修改Master这个类的代码
    public void feed(Cat c){
        c.eat();
    }
    */
    
    // 能不能让Master主人这个类以后不再修改了。
    // 即使主人又喜欢养其它宠物了,Master也不需要修改。
    // 这个时候就需要使用:多态机制。
    // 最好不要写具体的宠物类型,这样会影响程序的扩展性。
    public void feed(Pet pet){ 
        // 编译的时候,编译器发现pet是Pet类,会去Pet类中找eat()方法,结果找到了,编译通过
        // 运行的时候,底层实际的对象是什么,就自动调用到该实际对象对应的eat()方法上。
        // 这就是多态的使用。
        pet.eat();
    }

}

/*

    注意这里的分析:
        主人起初的时候只喜欢养宠物狗狗
        随着时间的推移,主人又喜欢上养“猫咪”
        在实际的开发中这就表示客户产生了新的需求。
        作为软件的开发人员来说,必须满足客户的需求。
        我们怎么去满足客户的需求呢?
            在不使用多态机制的前提下,目前我们只能在Master类中添加一个新的方法。
    
    思考:软件在扩展新需求过程当中,修改Master这个类有什么问题?
        一定要记住:软件在扩展过程当中,修改的越少越好。
        修改的越多,你的系统当前的稳定性就越差,未知的风险就越多。

        其实这里涉及到一个软件的开发原则:
            软件开发原则有七大原则(不属于java,这个开发原则属于整个软件业):
                其中有一条最基本的原则:OCP(开闭原则)

        什么是开闭原则?
            对扩展开放(你可以额外添加,没问题),对修改关闭(最好很少的修改现有程序)。
            在软件的扩展过程当中,修改的越少越好。
    

    高手开发项目不是仅仅为了实现客户的需求,还需要考虑软件的扩展性。

    什么是软件扩展性?
        假设电脑中的内存条部件坏了,我们可以买一个新的插上,直接使用。
        这个电脑的设计就考虑了“扩展性”。内存条的扩展性很好。
    
    面向父类型编程,面向更加抽象进行编程,不建议面向具体编程。
    因为面向具体编程会让软件的扩展力很差。

*/
复制代码
复制代码
public class Cat extends Pet{

    //
    public void eat(){
        System.out.println("猫咪喜欢吃鱼,吃的很香!!!");
    }
}
复制代码
复制代码
// 宠物狗狗类
public class Dog extends Pet{
    //
    public void eat(){
        System.out.println("狗狗喜欢啃骨头,吃的很香。");
    }
}
复制代码
复制代码
public class YingWu extends Pet{

    //重写eat方法
    public void eat(){
        System.out.println("鹦鹉喜欢吃小虫子,吃的很香!!!");
    }

}
复制代码
复制代码
/*
    测试多态在开发中的作用
*/
public class Test{
    public static void main(String[] args){
        // 创建主人对象
        Master zhangsan = new Master();
        // 创建宠物对象
        Dog zangAo = new Dog();
        // 主人喂
        zhangsan.feed(zangAo);
        // 创建宠物对象
        Cat xiaoHua = new Cat();
        // 主人喂
        zhangsan.feed(xiaoHua);
        // 创建宠物对象
        YingWu yingWu = new YingWu();
        // 主人喂
        zhangsan.feed(yingWu);
    }
}
复制代码

 

复制代码
/*
编写程序实现乐手弹奏乐器。乐手可以弹奏不同的乐器从而发出不同的声音。
可以弹奏的乐器包括二胡、钢琴和琵琶。
    实现思路及关键代码:
        1)定义乐器类Instrument,包括方法makeSound()
        2)定义乐器类的子类:二胡Erhu、钢琴Piano和小提琴Violin
        3)定义乐手类Musician,可以弹奏各种乐器play(Instrument i)
        4)定义测试类,给乐手不同的乐器让他弹奏
*/
public class Homework{
    public static void main(String[] args){
        /*
        // 创建各种乐器对象
        Erhu erhu = new Erhu();
        Piano piano = new Piano();
        Violin violin = new Violin();
        // 创建乐手对象
        Musician musician = new Musician();
        // play
        musician.play(erhu);
        musician.play(piano);
        musician.play(violin);
        */

        /*
        // 创建各种乐器对象
        Instrument erhu = new Erhu();
        Instrument piano = new Piano();
        Instrument violin = new Violin();
        // 创建乐手对象
        Musician musician = new Musician();
        // play
        musician.play(erhu);
        musician.play(piano);
        musician.play(violin);
        */

        // 创建乐手对象
        Musician musician = new Musician();
        // play
        musician.play(new Erhu());
        musician.play(new Piano());
        musician.play(new Violin());
    }
}

// 第一种写法
/*
// 乐手
class Musician{
    // 实例变量
    Instrument i;
    // 构造方法
    public Musician(){
    }
    public Musician(Instrument i){
        this.i = i;
    }
    // play()方法
    public void play(){
        i.makeSound();
    }
}
*/

// 第二种写法(更符合题意)
// 乐手
class Musician{

    // 乐手的名字
    //private String name;

    public void play(Instrument i){
        // 编译阶段makeSound()方法是Instrument的。
        // 运行阶段这个makeSound()方法就不一定是谁的了。
        i.makeSound();
    }
}

// 乐器父类
class Instrument{
    // 乐器发声
    public void makeSound(){
    
    }
}

// 子类
class Erhu extends Instrument{
    public void makeSound(){
        System.out.println("二胡的声音!!!");
    }
}
// 子类
class Piano extends Instrument{
    public void makeSound(){
        System.out.println("钢琴的声音!!!");
    }
}
// 子类
class Violin extends Instrument{
    public void makeSound(){
        System.out.println("小提琴的声音!!!");
    }
}
复制代码

 

 



面向对象的三大特征:
封装、继承、多态

真的是一环扣一环。

有了封装,有了这种整体的概念之后。
对象和对象之间产生了继承。
有了继承之后,才有了方法的覆盖和多态。

这里提到了一个软件开发原则:
七大原则最基本的原则:OCP(对扩展开放,对修改关闭)
目的是:降低程序耦合度,提高程序扩展力。
面向抽象编程,不建议面向具体编程。

2、解释之前遗留的问题

私有方法无法覆盖。

方法覆盖只是针对于“实例方法”,“静态方法覆盖”没有意义。(这是因为方法覆盖通常和多态联合起来)

总结两句话:
私有不能覆盖。
静态不谈覆盖。

在方法覆盖中,关于方法的返回值类型。
什么条件满足之后,会构成方法的覆盖呢?
1、发生具有继承关系的两个类之间。
2、父类中的方法和子类重写之后的方法:
具有相同的方法名、相同的形式参数列表、相同的返回值类型。

学习了多态机制之后:
“相同的返回值类型”可以修改一下吗?
对于返回值类型是基本数据类型来说,必须一致。
对于返回值类型是引用数据类型来说,重写之后返回值类型可以变的更小(但意义不大,实际开发中没人这样写。)。

3、super关键字

super能出现在实例方法和构造方法中。

super的语法是:“super.”、“super()”

super不能使用在静态方法中。

super. 大部分情况下是可以省略的。

super.什么时候不能省略呢?
父类和子类中有同名属性,或者说有同样的方法,
想在子类中访问父类的,super. 不能省略。

super() 只能出现在构造方法第一行,通过当前的构造方法去调用“父类”中
的构造方法,目的是:创建子类对象的时候,先初始化父类型特征。

super的使用:
super.属性名 【访问父类的属性】
super.方法名(实参) 【访问父类的方法】
super(实参) 【调用父类的构造方法】

 

复制代码
/*
    1、方法覆盖需要和多态机制联合起来使用才有意义。
        Animal a = new Cat();
        a.move();
        要的是什么效果?
            编译的时候move()方法是Animal的。
            运行的时候自动调用到子类重写move()方法上。
        
        假设没有多态机制,只有方法覆盖机制,你觉得有意义吗?
            没有多态机制的话,方法覆盖可有可无。

            没有多态机制,方法覆盖也可以没有,如果父类的方法无法满足
            子类业务需求的时候,子类完全可以定义一个全新的方法。
        
        方法覆盖和多态不能分开。

    2、静态方法存在方法覆盖吗?
        多态自然就和对象有关系了。
        而静态方法的执行不需要对象。
        所以,一般情况下,我们会说静态方法“不存在”方法覆盖。
        不探讨静态方法的覆盖。

*/
public class OverrideTest05{
    public static void main(String[] args){
        // 静态方法可以使用“引用.”来调用吗?可以
        // 虽然使用“引用.”来调用,但是和对象无关。
        Animal a = new Cat(); //多态
        // 静态方法和对象无关。
        // 虽然使用“引用.”来调用。但是实际运行的时候还是:Animal.doSome()
        a.doSome();
        
        Animal.doSome();
        Cat.doSome();
    }
}

class Animal{
    // 父类的静态方法
    public static void doSome(){
        System.out.println("Animal的doSome方法执行!");
    }
}

class Cat extends Animal{
    // 尝试在子类当中对父类的静态方法进行重写
    public static void doSome(){
        System.out.println("Cat的doSome方法执行!");
    }
}
复制代码

 

复制代码
// 经过测试,你记住就行。
// 私有方法不能覆盖。
public class OverrideTest06{

    // 私有方法
    private void doSome(){
        System.out.println("OverrideTest06's private method doSome execute!");
    }

    // 入口
    public static void main(String[] args){
        // 多态
        OverrideTest06 ot = new T();
        ot.doSome(); //OverrideTest06's private method doSome execute!
    }
}

/*
// 在外部类中无法访问私有的。
class MyMain{
    public static void main(String[] args){
        OverrideTest06 ot = new T();
        //错误: doSome() 在 OverrideTest06 中是 private 访问控制
        //ot.doSome();
    }
}
*/

// 子类
class T extends OverrideTest06{
    // 尝试重写父类中的doSome()方法
    // 访问权限不能更低,可以更高。
    public void doSome(){
        System.out.println("T's public doSome method execute!");
    }
}
复制代码
复制代码
public class OverrideTest07{
    public static void main(String[] args){
        // 一般重写的时候都是复制粘贴。不要动。不要改。    
    }
}

class Animal{
    /*
    public double sum(int a, int b){
        return a + b;
    }
    */
    
    /*
    public long sum(int a, int b){
        return a + b;
    }
    */

    /*
    public int sum(int a, int b){
        return a + b;
    }
    */
}

class Cat extends Animal{
    // 重写
    // 错误: Cat中的sum(int,int)无法覆盖Animal中的sum(int,int)
    /*
    public int sum(int a, int b){
        return a + b;
    }
    */
    
    /*
    public double sum(int a, int b){
        return a + b;
    }
    */

    //错误: Cat中的sum(int,int)无法覆盖Animal中的sum(int,int)
    /*
    public long sum(int a, int b){
        return a + b;
    }
    */
}

// 父类
class MyClass1{
    
    public Animal getAnimal(){
        return null;
    }
}

// 子类
class MyClass2 extends MyClass1{

    // 重写父类的方法
    /*
    public Animal getAnimal(){
        return null;
    }
    */

    // 重写的时候返回值类型由Animal变成了Cat,变小了。(可以,java中允许)
    /*
    public Cat getAnimal(){
        return null;
    }
    */

    // 重写的时候返回值类型由Animal变成了Object。变大了。(不行,java中不允许)
    /*
    public Object getAnimal(){
        return null;
    }
    */
}
复制代码

 

 
posted @   小carlos  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示