第八章 面向对象编程(中级) 下

8.多态

polymorphic

多态入门

●先看一个问题
√ 问题描述 com.zzw.poly_包 Poly01.java
请编写一个程序, Master 类 中有一个 feed(喂食) 方法, 可以完成 主人给动物喂食的信息.
在这里插入图片描述


●先看一个问题
√ 使用传统的方法来解决(private属性)
在这里插入图片描述

√ 传统的方法带来的问题是什么? 如何解决?
问题是: 代码的复用性不高, 而且不利于代码维护
解决方案: 引出我要讲解的多态

多态基本介绍

方法或对象具有多种形态[多种状态], 是面向对象的第三大特征, 多态是建立在封装和继承基础之上的.

多态的具体体现

1.方法的多态 PolyMethod.java
重写和重载就体现多态

2.对象的多态(核心, 困难, 重点)
重要的几句话:
(1)一个对象的编译类型和运行类型可以不一致
(2)编译类型在定义对象时, 就确定了, 不能改变 Animal animal
(3)运行类型是可以变化的
(4)编译类型看定义时 =号 的左边, 运行类型看 =号

案例: com.zzw.poly_.objpoly_包 PolyObject.java
Animal animal = new Dog(); [animal 编译类型是Animal, 运行类型是Dog]
animal = new Cat(); [animal 的运行类型变成了 Cat, 编译类型仍然是 Animal]
在这里插入图片描述

●多态快速入门案例
使用多态的机制解决主人喂食物的问题, 走代码. Poly01.java
在这里插入图片描述

多态的方便性: 假设主人给小猪喂食米饭, 很好实现. 新建米饭, 小猪实体类, 直接调用feed方法, 传入对应的对象就行.

多态注意事项和细节讨论

com.zzw.poly_.detail_包 PolyDatail.java

多态的前提

两个对象(类)存在继承关系

多态的向上转型

1)本质: 父类的引用指向了子类的对象
2)语法: 父类类型 引用名 = new 子类类型();
3)特点: 编译类型看左边, 运行类型看右边
   可以调用父类中的所有成员(需遵守访问权限)
   不能调用子类中特有成员;
   最终运行效果看子类的具体实现!
在这里插入图片描述

访问修饰符结合继承理解
不具有继承关系 对象.属性名/方法名
1.同包不同类下, 可以访问public, protected 和 默认
2.不同包下, 可以访问public
 
具有继承关系 this.属性名/方法名
1.同胞不同类下, 可以访问public, protected 和 默认
2,不同包下, 可以访问public, protected


多态的向下转型

1)语法: 子类类型 引用名 = (子类类型) 父类引用

2)只能强转父类的引用(身份和地位), 不能强转父类的对象(本人)

3)强转, 会损失精度, 即原来能调用的父类的方法不能调用了.
在这里插入图片描述

3)要求父类的引用必须指向当前目标类型的对象在这里插入图片描述
这样就有两个引用指向了Cat对象在这里插入图片描述4)当向下转型后, 可以调用子类类型中所有的成员
在这里插入图片描述在这里插入图片描述

属性没有重写一说!

属性的值看编译类型 PolyDetail02.java

在这里插入图片描述

instanceof 比较操作符

用于判断对象的运行类型是否为XX类型或XX类型的子类型 PolyDetail03.java

课堂练习

com.zzw.poly_.exercise_包 PolyExercise01.java
请说出下面的每条语言, 哪些是正确的, 哪些是错误的, 为什么?
public class PolyExercise01 {
      public static void main(String[] args) {
            double d = 13.4;
            long l = (long)d;
            System.out.println(l);
            int in = 5;
            boolean b = (boolean) in;
            Object obj = “Hello”;            
            String objStr = (String)obj;
            System.out.println(objStr);            
            Object objPri = new Integer(5);            
            String str = (String)objPri;            
            Integer str1 = (Integer) objPri;
      }
}
public class PolyExercise01 {
      public static void main(String[] args) {
            double d = 13.4;//ok
            long l = (long)d;//ok
            System.out.println(l);//ok
            int in = 5;//ok
            boolean b = (boolean) in;//不对boolean --> int
            Object obj = “Hello”;//ok, String -> Object, 向上转型            
            String objStr = (String)obj;//ok, Object -> String, 向下转型
            System.out.println(objStr);//ok            
            Object objPri = new Integer(5);//ok, Integer -> Object, 向上转型            
            String str = (String)objPri;//不可以, 把指向Integer的父类引用转成String引用            
            Integer str1 = (Integer) objPri;//ok, Object -> Integer, 正确的向下转型
      }
}

PolyExercise02.java
只要是方法的调用, 就要看对象的运行类型.

public class PolyExercise02 {
      public static void main(String[] args) {
            Sub s = new Sub();
            System.out.println(s.count);
            s.display();
            Base b = s;
            System.out.println(b == s);//true
            System.out.println(b.count);
            b.display();
      }
}
class Base {
      int count = 10;
      public void display() {
            System.out.println(this.count);
      }
}
class Sub extends Base {
      int count = 20;
      public void display() {
            System.out.println(this.count);
      }
}
在这里插入图片描述

动态绑定机制

java重要特性: 动态绑定机制 com.zzw.poly_dynamic_包 DynamicBinding.java

public class DynamicBinding {
      public static void main(String[] args) {
            A a = new B();
            System.out.println(a.sum());
            System.out.println(a.sum1());
      }
}
class A { //父类
      public int i = 10;
      public int getI() {
            return i;
      }
      public int sum() {
            return getI() + 10;//动态绑定机制
      }
      public int sum1() {
            return i + 10;
      }
}
class B extends A { //子类
      public int i = 20;
      public int getI() {
            return i;
      }
      public int sum() {
            return i + 20;
      }
      public int sum1() {
            return i + 10;
      }
}

在这里插入图片描述

进阶: 去掉B类的sum方法, 求a.sum输出什么?
答: a引用的运行类型是B, 所以去B中找sum方法, B中没有, 就去父类A中去找, 找到了, 并且可以调用, 就调用. 调用getI方法, 这时发现子类B中也有getI方法, 这时会怎么办?

java的动态绑定机制

1.当调用对象方法的时候, 该方法会和该对象的内存地址/运行类型绑定

2.当调用对象属性时, 没有动态绑定机制, 哪里声明, 哪里调用. (即属性不会从运行类型开始查找).

在这里插入图片描述

进阶: 去掉B类的sum1方法, 求a.sum1输出什么?
答: a引用的运行类型是B, 所以去B中找sum1方法, B中没有, 就去父类A中去找, 找到了, 并且可以调用, 则调用. 由于属性没有动态绑定机制, 哪里声明, 哪里调用, 所以返回20.
难点:
1)调用子类属性时, 如果子类有, 则调用子类的, 如果子类没有, 则去找父类;
2)不能通过调用父类属性来达到调用子类属性的目的, 因为属性没有动态绑定机制, 属性不能被重写. (我自己总结的: 也就是说, 父类不能调用子类的属性).
3)属性按作用域来找, 但是作用域内没有还是按照继承机制
可以通过调用父类方法来达到调用-子类方法的目的, 因为方法被重写了, 方法可以动态绑定;
在这里插入图片描述

温故而知新

子类调用子类的,父类调用父类的

(1)属性调用是从本类编译类型开始查找
(2)方法调用是查看编译类型中是否有该方法, 如果有, 则从运行类型开始查找; 如果没有, 则报错

调用对象方法时, 首先看对象的编译类型有没有, 编译类型有, 那就从运行类型开始查找(目的是为了查看这个方法有没有被子类重写: 这里要理解重写的概念), 子类的方法必须要满足重写, 才算是有, 否则按照继承关系建立查找关系

多态数组

1)多态数组 com.zzw.poly_.polyarr_包 PloyArray.java, Person.java, Student.java, Teacher.java
数组的定义类型为父类类型, 里面保存的实际元素为子类类型

应用实例: 现有一个继承机构如下: 要求创建1个Person对象, 2个Student对象和2个Teacher对象, 统一放在数组中, 并调用每个对象的say方法.
在这里插入图片描述

public class Person {
    private String name;
    private int age;

    public String say() {
        return "姓名 " + name + "\t年龄 " + age;
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }
}
public class Student extends Person {
    private double score;

    //重写父类的say方法
    public String say() {
        return super.say() + "\t成绩 " + score;
    }

    public Student(String name, int age, double score) {
        super(name, age);
        this.score = score;
    }

    public double getScore() {
        return score;
    }
}
public class Teacher extends Person {
    private double salary;

    //重写父类的say方法
    public String say() {
        return super.say() + "\t薪水 " + salary;
    }

    public Teacher(String name, int age, double salary) {
        super(name, age);
        this.salary = salary;
    }

    public double getSalary() {
        return salary;
    }
}
public class PolyArray {
    //要求创建1个Person对象, 2个Student对象和2个Teacher对象
    // , 统一放在数组中, 并调用每个对象的say方法.
    public static void main(String[] args) {
        Person[] persons = new Person[5];

        persons[0] = new Person("jack", 10);
        persons[1] = new Student("tom", 15, 100);
        persons[2] = new Student("mary", 23, 85);
        persons[3] = new Teacher("king", 40, 20000);
        persons[4] = new Teacher("queen", 35, 25000);

        //循环遍历多态数组, 调用say方法
        for(int i = 0; i < persons.length; i++) {
            //提示: person[i] 编译类型是Person, 运行类型是根据实际情况由JVM判断
            System.out.println(persons[i].say());
        }
    }
}

应用实例升级: 如果调用子类特有的方法, 比如Teacher有一个teach方法, Student有一个study方法, 怎么调用?

public class PolyArr {
    public static void main(String[] args) {
        //现有一个继承结构如下, 要求创建1个Person对象,2个Student对象2个Teacher对象,
        //统一放在数组中, 并调用每个对象的say方法.

        Person[] persons = new Person[5];
        persons[0] = new Person("jack", 10);
        persons[1] = new Student("tom", 15, 100);
        persons[2] = new Student("mary", 23, 85);
        persons[3] = new Teacher("jack", 22, 15000);
        persons[4] = new Teacher("scott", 20, 17000);

        //循环遍历多态数组, 调用say方法
        for (int i = 0; i < persons.length; i++) {
            //提示: person[i] 编译类型是 Person, 运行类型是根据实际情况由JVM机来判断
            System.out.println(persons[i].say());//动态绑定机制
            //不能调用子类的特有的成员
            //因为在编译阶段, 能调用哪些成员, 是由编译类型来决定的
            if (persons[i] instanceof Student) {//判断person[i] 的运行类型是不是Student
                Student student = (Student) persons[i];
                student.study();//向下转型
            } else if (persons[i] instanceof Teacher) {
                Teacher teacher = (Teacher) persons[i];
                teacher.teach();
            } else if (persons[i] instanceof Person) {

            } else {
                System.out.println("你的类型有误, 请自己检查...");
            }
        }
    }
}

多态参数

方法定义的形参类型为父类类型, 实参类型允许为子类类型
应用实例1: 前面的主人喂动物
应用实例2: com.zzw.poly_.polyparameter_包 PolyParameter.java
定义员工类Employee, 包含姓名和月工资[private], 以及计算年工资getAnnual的方法. 普通工人和经理继承了员工, 经理类多了奖金bonus属性和管理manage方法, 普通工人类多了work方法, 普通工人和经理类要求分别重写getAnnual方法. 如果没说什么字段,默认private

测试类中添加一个方法showEmpAnnual(Employee e), 实现获取任何员工对象的年工资, 并在main方法中调用该方法 [e.getAnnual()]

测试类中添加一个方法testWork(). 如果是普通员工, 则调用work方法; 如果是经理, 则调用manage方法

public class Employee {
    private String name;//姓名
    private double salary;//月工资

    //计算年工资
    public Double getAnnual() {
        return 12 * salary;
    }

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }
}
public class Worker extends Employee {

    public Worker(String name, double salary) {
        super(name, salary);
    }

    public void work() {
        System.out.println("普通员工 " + getName() + " 工作中...");
    }

    public Double getAnnual() {//因为普通员工没有其它的收入, 则直接调用父类的方法即可
        return super.getAnnual();
    }
}
public class Manager extends Employee {
    private double bonus;//奖金

    public Manager(String name, double salary, double bonus) {
        super(name, salary);
        this.bonus = bonus;
    }

    public void manage() {
        System.out.println("经理 " + getName() + " 在管理工作中...");
    }

    //重写获取年薪的方法
    public Double getAnnual() {
        return super.getAnnual() + bonus;
    }
}
public class PolyParameter {
    public static void main(String[] args) {
        Worker tom = new Worker("tom", 3000);
        Manager mary = new Manager("mary", 5000, 200000);

        PolyParameter polyParameter = new PolyParameter();
        polyParameter.showEmpAnnual(tom);
        polyParameter.showEmpAnnual(mary);//动态绑定机制

        polyParameter.testWork(tom);
        polyParameter.testWork(mary);
    }

    public void showEmpAnnual(Employee e) {
        System.out.println(e.getAnnual());
    }

    public void testWork(Employee e) {
        if (e instanceof Worker) {
            Worker worker = (Worker) e;//向下转型
            worker.work();
        } else if (e instanceof Manager) {
            Manager manager = (Manager) e;//向下转型
            manager.manage();
        } else {
            System.out.println("你的类型有误...");
        }
    }
}

9.Object类详解

jdk文档
在这里插入图片描述

equals方法

==和equals的对比

== 是一个比较运算符 com.zzw.object_: Equals01.java

1.==: 既可以判断基本类型, 又可以判断引用类型 :
2.==: 如果判断基本类型, 判断的是值是否相等. 示例
3.==: 如果判断引用类型, 判断的是地址是否相等, 即判断是不是同一个对象
在这里插入图片描述

4.equals: 是Object类中的方法, 只能判断引用类型, 如何看JDK源码

5.Object类中的equals默认判断地址是否相等, 子类往往重写这个方法, 用于判断内容是否相等. 比如String和Integer.

String源码

//分析JDK源码 String类的equals方法
//重写了父类Object的equals方法, 变成了比较两个字符串的值是否相等
public boolean equals(Object anObject) {
    if (this == anObject) {//如果传进来的对象和当前对象是同一个对象
        return true;
    }
    if (anObject instanceof String) {//如果入参对象的运行类型是字符串String
        String anotherString = (String)anObject;//向下转型(强转)
        int n = value.length;//计算当前字符串的长度
        if (n == anotherString.value.length) {//如果当前字符串长度和传进来的字符串长度相等
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {//循环n次,依次比较字符串的每个字符
                if (v1[i] != v2[i])
                    return false;
                i++;
            }//如果字符都相同, 最后返回true
            return true;
        }
    }
    //如果比较的不是字符串, 返回false
    return false;
}

Object源码

//Object类的equals方法, 默认比较两个对象的值是否相等
//即比较两个对象是否是同一个对象
public boolean equals(Object obj) {
    return (this == obj);
}

Integer源码

//从源码可以看到, Integer也重写了Object的equals方法
//变成了判断两个对象的值是否相等
public boolean equals(Object obj) {
    if (obj instanceof Integer) {//如果入参对象的运行类型是Integer
        return value == ((Integer)obj).intValue();//向下转型(强转), 判断两个对象的值是否相等
    }
    return false;
}

Integer integer1 = new Integer(1000);
Integer integer2 = new Integer(1000);
// == 判断对象时, 判断的是 是否是同一个对象, 显然这里是两个对象
System.out.println(integer1 == integer2);//返回false
//Integer类重写了父类Object的equals方法, 比较的是两个对象的值是否相等
System.out.println(integer1.equals(integer2));//显然返回true

String str1 = new String("zzw");
String str2 = new String("zzw");
//显然这里是两个对象, 指向的地址不相等
System.out.println(str1 == str2);//返回false
//String重写了equals方法, 比较的是内容是否相等
System.out.println(str1.equals(str2));//这里返回true

图解

名称概念用于基本数据类型用于引用类型
==比较运算符可以,判断值是否相等可以,判断指向的是否是同一个对象
equalsObject类的方法不可以可以,默认判断两个对象是否是同一个对象。

如何重写equals方法

应用实例: 判断两个Person对象的内容是否相等, 如果两个Person对象的各个属性值都相等, 返回true, 否则返回false. src/com/zzw/Object_/EqualsExercise01.java

public class EqualsExercise01 {
    public static void main(String[] args) {
        Person mary = new Person("mary", 23, '男');
        Person mary2 = new Person("mary", 23, '男');
        //原先比较地址, 返回false
        //重写后, 比较对象属性的值, 返回true
        System.out.println(mary.equals(mary2));
    }
}

class Person {
    private String name;
    private int age;
    private char gender;

    public Person(String name, int age, char gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    //重写父类Object的equals方法
    public boolean equals(Object obj) {
        //如果传进来的和当前的是同一个对象
        if (this == obj) {
            return true;
        }
        //如果传进来的对象的运行类型是Person
        if (obj instanceof Person) {
            //向下转型, 取值
            Person person = (Person) obj;
            //依次比较属性(本类中有权访问私有属性)
            return name.equals(person.name) && age == person.age && gender == person.gender;
        }
        //如果比较的不是同一个对象, 返回false
        return false;
    }
}

课堂练习

src/com/zzw/Object_/EqualsExercise02.java

public class EqualsExercise02 {
      public static void main(String[] args) {
            Person p1 = new Person();
            p1.name = “赵志伟”;

            Person p2 = new Person();
            p2.name = “赵志伟”;

            System.out.println(p1 == p2);//false, 两个对象
            System.out.println(p1.name.equals(p2.name));//true
            System.out.println(p1.equals(p2));//false, equals()默认比较指向的地址

            String s1 = new String(“zzw”);
            String s2 = new String(“zzw”);
            System.out.println(s1.equals(s2));//true, String重写了equals, 比较的是内容
            System.out.println(s1 == s2);//false, 并不是一个对象
      }
}

class Person {
      public String name;
}

src/com/zzw/Object_/EqualsExercise03.java

public class EqualsExercise03 {
      public static void main(String[] args) {
            int it = 65;
            float fl = 65.0f;
            System.out.println("65和65.0f是否相等? " + (it == fl));//true, 比较值
            char c1 = ‘A’;
            char c2 = 12;
            System.out.println("65和’A’是否相等? " + (it == c1));//true, 字符的本质是数字
            System.out.println(“12和c2是否相等?” + (12 == c2));//true

            String s1 = new String(“hello”);
            String s2 = new String(“hello”);
            System.out.println("s1和s2是否相等? " + (s1 == s2));//false, 因为是两个对象
            System.out.println("s1是否equals s2 " + s1.equals(s2));//true, 因为String类的equals比较的是内容
      }
}

hashCode方法

java/jdk API 文档

小结:
1)提高具有哈希结构的容器的效率!
2)两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
3)两个引用,如果指向的是不同对象,则哈希值是不一样的
4)哈希值主要根据地址号来计算的,但不能将哈希值等价于地址。
5)案例演示 HashCode_.java

public class HashCode_ {
    public static void main(String[] args) {
        AA aa = new AA();
        AA aa2 = new AA();
        AA aa3 = aa;
        System.out.println("aa的哈希值=" + aa.hashCode());
        System.out.println("aa2的哈希值=" + aa2.hashCode());
        System.out.println("aa3的哈希值=" + aa3.hashCode());
    }
}
class AA {}

toString方法

√ 基本介绍
默认返回: 全类名(包名 + 类名) + @ + 哈希值的十六进制, 子类往往重写toString方法, 用于返回对象的属性信息

源码

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

√ 重写toString()方法, 打印对象或拼接对象时,都会自动调用该对象的toString形式。
案例演示:Monster【name,job,sal】,ToString.java

√ 当直接输出一个对象时,toString方法会被默认的调用
,比如System.out.println(monster); 就会默认调用 monster.toString()

public class ToString_ {
    public static void main(String[] args) {
        /**
         * Object的toString方法源码
         * (1) getClass().getName() 类的全类名(包名+类名)
         * (2) Integer.toHexString(hashCode()) 将对象的hashCode值转成16进制
         * public String toString() {
         *     return getClass().getName() + "@" + Integer.toHexString(hashCode());
         * }
         */
        Monster monster = new Monster("小旋风", "巡山的", 2000);
        System.out.println(monster.toString()
                + "\nhashCode=" + Integer.toHexString(monster.hashCode())
                + "\n" + monster.hashCode());

        System.out.println("=====当直接输出一个对象时,toString方法会被默认的调用=====");
        System.out.println(monster);//等价monster.toString()
    }
}

class Monster {
    private String name;
    private String job;
    private double sal;

    public Monster(String name, String job, double sal) {
        this.name = name;
        this.job = job;
        this.sal = sal;
    }

    //重写toString方法, 输出对象的属性
    //使用快捷键 alt + insert  -> toString键
    //重写后, 一般是把对象的属性值输出, 当然程序员也可以自己定制
    @Override
    public String toString() {
        return "Monster{" +
                "name='" + name + '\'' +
                ", job='" + job + '\'' +
                ", sal=" + sal +
                '}';
    }
}

在这里插入图片描述

finalize方法

1.当对象被回收时,系统自动调用该对象的finalize方法,子类可以重写该方法,做一些释放资源的操作。

2.什么时候被回收: 当某个对象没有任何引用时, 则jvm就认为这个对象是一个垃圾对象, 就会使用垃圾回收机制来销毁该对象. 在销毁对象前, 会先调用finalize方法.
在这里插入图片描述
在这里插入图片描述

3.垃圾回收机制的调用, 是由系统来决定(即有自己的GC算法), 为了看到这个效果, 也可以通过 System.gc() 主动触发垃圾回收机制, 测试: Car[name]

提示: 我们在实际开发中, 几乎不会使用 finalize, 所以更多就是为了应付面试.
Finalize_.java

//演示 Finalize方法
public class Finalize_ {
    public static void main(String[] args) {
        Car bmw = new Car("宝马");
        //这时, car对象就是一个垃圾, 垃圾回收器就会回收(销毁)对象. 在销毁对象前, 会调用该对象的finalize方法
        //, 程序员就可以在 finalize中, 写自己的业务代码(比如释放资源: 数据库连接/关闭文件...)
        //, 如果程序员不重写 finalize, 那么就会调用 Object类的 finalize, 即默认处理
        //, 如果程序员重写了 finalize, 就可以实现自己的逻辑
        bmw = null;
        System.gc();//主动调用垃圾回收器

        System.out.println("程序退出了");
    }
}

class Car {
    //属性是资源
    private String name;

    public Car(String name) {
        this.name = name;
    }

    //重写 finalize
    @Override
    protected void finalize() throws Throwable {
        System.out.println("我们销毁 汽车" + name);
        System.out.println("释放了某些资源...");
    }
}

10.断点调试

√ 案例1 com.zzw.debug_包 Debug01.java
看一下变量的变化情况. F8 逐行执行代码

public class Debug01 {
    public static void main(String[] args) {
        //演示逐行执行
        int sum = 0;
        for (int i = 0; i < 5; i++) {
            sum += i;
            System.out.println("i=" + i);
            System.out.println("sum=" + sum);
        }
        System.out.println("退出for循环..");
    }
}

截图 + 说明


√ 案例2 Debug02.java
看一下数组越界的异常

public class Debug02 {
    public static void main(String[] args) {
        int[] arr = {1, 10, -1};
        for (int i = 0; i <= arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println("退出for循环..");
    }
}

√ 案例3 Debug03.java
演示如何追踪源码, 看看java设计者是怎么实现的 (提高编程思想) F7 跳入 IDEA Debug 如何进入源码
小技巧: 将光标放在某个变量上, 可以看到最新的数据.
Debug 时没有Force Step Into 按钮解决方法

public class Debug03 {
    public static void main(String[] args) {
        int arr[] = {1, -1, 10, -20, 100};
        //我们想看Arrays.sort方法底层实现->debug
        Arrays.sort(arr);
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
}

截图 + 说明
在这里插入图片描述


√ 案例4 Debug04.java
演示如何直接执行到下一个断点 F9 Resume Program
小技巧:断点可以在debug过程中,动态地下断点

//演示执行到下一个断点,同时支持动态地下断点
public class Debug04 {
    public static void main(String[] args) {
        int arr[] = {1, -1, 10, -20, 100};
        //我们想看Arrays.sort方法底层实现->debug
        Arrays.sort(arr);
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println("hello100~");
        System.out.println("hello200~");
        System.out.println("hello300~");
        System.out.println("hello400~");
        System.out.println("hello500~");
    }
}

课后练习

1.使用断点调试的方法,追踪一个对象创建的过程。Person[name, age, 构造器…] 对象创建的流程分析 DebugExercise.java

//debug对象创建的过程,加深对调试的理解
public class DebugExercise {
    public static void main(String[] args) {
        //创建对象的流程
        //(1)加载Person类信息
        //(2)初始化 2.1默认初始化 2.2显式初始化 2.3构造器初始化
        //(3)返回对象的地址
        Person jack = new Person("jack", 20);
        System.out.println(jack);
    }
}

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

在这里插入图片描述

先转载类

在这里插入图片描述

跳出

在这里插入图片描述

Force Step Into Alt+Shift+F7

在这里插入图片描述

为什么输出对象默认调用对象的toString方法, 答案在String.valueOf方法里
obj.toString()会动态绑定到运行类型的Person.

在这里插入图片描述

进入

在这里插入图片描述

跳出

在这里插入图片描述

2.使用断点调试,查看动态绑定机制如何工作

Force Step Into Alt+Shift+F7

在这里插入图片描述

在这里插入图片描述

Step Into F7

在这里插入图片描述

取消B类的sum方法注释,重新Debug,观察效果

在这里插入图片描述

进入的是子类的sum方法

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~ 小团子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值