第四章 继承和多态


第四章

面向对象特征之二:继承

继承

概念

从现有类创建子类(衍生)过程, 子类要继承父类的所有成员

继承原因

父类不够用, 需要功能更强的子类

继承方法

class 子类 extends 父类 {}

继承内容

子类 继承 父类的所有成员. 构造器除外

  • 属性描述事物特征, 方法描述事物的行为动作.

  • 什么东西能被继承 : 只有具有描述性的东西才继承.

  • 属性,方法和内部类可以继承(因为它们是成员)

  • 静态块,非静态块, 构造器不能继承(因为它们不是成员) *

  • 只有非静态方法具有多态性. *

  • static 关键字和多态是背道而驰.

思考

如果A类被B类继承,B类又被C类继承, 在所有类中都有一个方法test(), 创建C类对象,调用test()方法,执行的是哪个类的方法? 在C类中有几个test()方法?

执行的是C类中的方法, 因为在C类中重写了.

  • 从测试类角度, 看待C类, 只有一个

  • 从C类的内部角度看, 有2个test()方法, 一个是this.test(), 另外一个是super.test()

  • 从继承的概念来看, C类中有3个test()方法.

/**
* 继承 : 从现有类创建子类, 现有类称为父类, 基类(子类在父类的基础上进行扩展), 超类(因为在子类使用super关键字限定父类成员, 官方叫法).
* 子类一旦继承父类, 会继承父类的所有成员!!!!(构造器除外)
* 为什么要继承所有成员? 因为子类必须是一个完整的父类.
*
* 为什么要继承 ? 因为父类不够用, 需要子类扩展一下, 再来使用.
*
* 父类中的私有成员, 也可以被子类继承, 但是私有成员只允许在本类中访问,
* 子类不是本类, 所以在子类中不能直接访问继承来的私有属性
* 必须在父类中提供公共的get/set方法, 让子类继承后通过get/set来间接访问\\
*
* 多重继承 : 一个子类有多个直接父类, java不支持, 因为有可能出现方法冲突!!!
* 多层继承 : 一个子类有一个直接父类, 若干间接父类. java支持
*
* 方法覆盖(override) : 子类中重写父类继承来的方法, 因为父类方法不好用.
* 条件 :
*     1) 方法签名要完全一致, 包括返回值类型, 方法名, 参数列表(类型一致, 个数一致, 顺序一致)
*     2) 子类方法的访问控制修饰符要大于等于父类的.(因为子类是父类的扩展, 父类该有的特征和行为,
*         在子类中也必须要全部具备)
*
* super 关键字 : 用于标识从父类继承的成员.
*
*     @Override // 注解 : 特殊的注释, 特殊在于可以被编译器和JVM识别.
*     // @Override注解的作用就是说明这个方法是要覆盖, 请编译器帮助我们作方法覆盖条件的检查.
*     // 如果有问题, 请提前编译出错.
*/
public class Person {

   private String name;
   private int age;
   private String gender;

   public String getName() {
       return name;
  }

   public void setName(String name) {
       this.name = name;
  }

   public int getAge() {
       return age;
  }

   public void setAge(int age) {
       this.age = age;
  }

   public String getGender() {
       return gender;
  }

   public void setGender(String gender) {
       this.gender = gender;
  }

   public void eat(String some) {
       System.out.println("吃" + some);
  }

   public String say() {
       return "姓名 : " + name + ", 年龄 : " + age + ", 性别 : " + gender;
  }
}


******************************************************************

public class Student extends Person {

   /*
   String name;
   int age;
   String gender;
   */
   String school;

   /*
   public void eat(String some) {
       System.out.println("吃" + some);
   }

   public String say() {
       return "姓名 : " + name + ", 年龄 : " + age + ", 性别 : " + gender;
   }
   */

   public void study() {
       //System.out.println(name + "学生在上课"); // 虽然父类私有属性继承了, 表达了所有权, 但是没有直接使用权
       System.out.println(this.getName() + "学生在上课"); // this.表明子类真的继承了, 为我所有
       System.out.println(super.getName() + "学生在上课"); // super.只是标明它的来源.
  }

   @Override public String say() { // 重写后的say()才能真正的表达对象的详细信息
       //return "姓名 : " + getName() + ", 年龄 : " + getAge() + ", 性别 : " + getGender() + ", 学校 : " + school;
       //super就是超级的意思, 特指父类.
       return super.say() + ", 学校 : " + school; // super.调用便于父类的修改, 对子类几乎没有影响
  }

}

***********************************************************************
package com.atguigu.javase.inheritence;

public class Teacher extends Person {

   double salary;

   @Override // 注解 : 特殊的注释, 特殊在于可以被编译器和JVM识别.
   // @Override注解的作用就是说明这个方法是要覆盖, 请编译器帮助我们作方法覆盖条件的检查.
   // 如果有问题, 请提前编译出错.
   public String say() {
       return super.say() + ", 工资 : " + salary;
  }
}

*********************************************************************
   public class PersonTest {

   public static void main(String[] args) {
       Teacher t = new Teacher();
       t.setName("佟刚");
       t.setAge(40);
       t.setGender("男");
       t.salary = 800;

       //System.out.println(t.super.say()); // 绝对不可以的.
       System.out.println(t.say());
  }

   public static void main1(String[] args) {
       Student stu = new Student();
       //stu.name = "小明";
       stu.setName("小明");
       stu.setAge(20);
       stu.setGender("男");
       stu.school = "atguigu";

       System.out.println(stu.getName());
       System.out.println(stu.getAge());
       System.out.println(stu.getGender());
       System.out.println(stu.school);

       stu.eat("饺子"); // 执行父类中的方法.
       stu.study();
       System.out.println(stu.say()); // 执行子类中的重写方法
  }
}

 

方法的重写(override)

概念

方法覆盖(override) : 子类中重写父类继承来的方法, 因为父类方法不能满足需要.

条件
  1. 方法签名要完全一致, 包括返回值类型, 方法名, 参数列表(类型一致, 个数一致, 顺序一致)

  2. 子类方法的访问控制修饰符要大于等于父类的.(因为子类是父类的扩展, 父类该有的特征和行为,在子类中也必须要全部具备)

  3. 方法都必须是没有static修饰的

四种访问权限修饰符

Java权限修饰符public、protected、private置于类的成员定义前,用来限定对象对该类对象成员的访问权限。

修饰符类内部同一个包其他包子类任何地方
private Yes      
default Yes Yes    
protected Yes Yes Yes  
public Yes Yes Yes Yes

对于class的权限修饰只可以用public和default。

  1. public类可以在任意地方被访问。

  2. default类只可以被同一个包内部的类访问。

package com.yewu.javase.inheritance;

public class Base {
       private int f1 = 1;
       int f2 = 2;
       protected  int f3 = 3;
       public  int f4 = 4;
       private  void  fm1() {System.out.println("in fm1() f1=" + f1);}
       void fm2() {System.out.println("in fm2() f2=" + f2);}
       protected  void  fm3() {System.out.println("in fm3() f3=" + f3);}
       public void fm4() {System.out.println("in fm4() f4=" + f4);}
}
***************************************************************************
package com.yewu.javase.inheritance;

public class Sub1 extends Base{
   public void test () {
       //System.out.println(f1);//private
       System.out.println(f2);//default 同包
       System.out.println(f3);//protected 同包子类
       System.out.println(f4);//public 全局

       //fm1()//私有无法访问
       fm2();
       fm3();
       fm4();
  }

}
*********************************************************************************
   package com.yewu.javase.other;

import com.yewu.javase.inheritance.Base;

public class sub2 extends Base {
   public void test1 () {
       //System.out.println(f1);private
       //System.out.println(f2);default,不同包子类
       System.out.println(f3);//private, 不同包子类
       System.out.println(f4);

       //fm1 ();
       //fm2 ();
       fm3();
       fm4();
  }
}

 

关键字super

在Java类中使用super来调用父类中的指定操作:

  • super可用于访问父类中定义的属性

  • super可用于调用父类中定义的成员方法

  • super可用于在子类构造方法中调用父类的构造器

注意:

  • 尤其当子父类出现同名成员时,可以用super进行区分

  • super的追溯不仅限于直接父类

  • super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识

/**
* 在Java类中使用super来调用父类中的指定操作:
*     super可用于访问父类中定义的属性. super.属性名
*     super可用于调用父类中定义的成员方法 : super.方法名(...);
*     super可用于在子类构造方法中调用父类的构造器 : super(...)
* 注意:
*     尤其当子父类出现同名成员时,可以用super进行区分
*     super的追溯不仅限于直接父类, 指访问属性和方法
*     super和this的用法相像,this代表本类对象的引用,
*     super代表父类的内存空间的标识
*
* 访问控制修饰符 :
*     private         本类
*     default         本类     本包其他类
*     protected       本类     本包其他类     其他包的子类
*     public         全局
*
* 父类的构造器会被子类调用.
* super(...) 调用必须是构造器中的第一行
* 在子类构造器中的第一行一定有一个默认的super()语句, 作用是直接调用父类无参构造器
* 所有的子类的所有的构造器都要默认调用父类无参构造器
* 也可以显式的通过super(...)调用父类的有参构造器
*
* 子类构造器中的第一行必须是super(....)或者this(...), 二者不能共存.
* super(...) 作用是直接调用父类构造器
* this(...) 作用是间接调用父类构造器
*
* 结论 : 子类构造器中一定会先调用父类构造器.
*
* this.属性或方法 访问属性和方法可以先访问本类中的属性,如果本类没有此属性则从父类中继续查找 访问父类中的属性和方法
* this(...) 调用构造器只能调用本类构造器,必须放在构造器的首行
* super(...) 调用直接父类构造器,必须放在子类构造器的首行, 如果直接父类中没有此构造器, 编译出错!!!
* this表示当前对象
* super只是一个标识
* 思考:
* 1).为什么super(…)和this(…)调用语句不能同时在一个构造器中出现?
* 2).为什么super(…)或this(…)调用语句只能作为构造器中的第一句出现?
*/
public class Person {

   private String name;
   private int age;
   private String gender;

   public Person() {
       System.out.println("Person()...");
  }

   public Person(String name, int age, String gender) {
       this.name = name;
       this.age = age;
       this.gender = gender;
       System.out.println("Person(String,int,String)..."); // 3
  }

   public String getName() {
       return name;
  }

   public void setName(String name) {
       this.name = name;
  }

   public int getAge() {
       return age;
  }

   public void setAge(int age) {
       this.age = age;
  }

   public String getGender() {
       return gender;
  }

   public void setGender(String gender) {
       this.gender = gender;
  }

   public String say() {
       return "姓名 : " + name + ", 年龄 : " + age + ", 性别 : " + gender;
  }

   public void sayHello() {
       System.out.println("打个招呼");
  }
}

************************************************************************
 

调用父类的构造器

父类的构造器会被子类调用

调用父类构造器方法
  • super(...) 调用必须是构造器中的第一行

  • 在子类构造器中的第一行一定有一个默认的super()语句, 作用是直接调用父类无参构造器

  • 所有的子类的所有的构造器都要默认调用父类无参构造器

  • 也可以显式的通过super(...)调用父类的有参构造器

注意事项
  • 子类构造器中的第一行必须是super(....)或者this(...), 二者不能共存.

  • super(...) 作用是直接调用父类构造器

  • this(...) 作用是间接调用父类构造器

  • 结论 : 子类构造器中一定会先调用父类构造器.

this和super使用规则
  • this.属性或方法 访问属性和方法可以先访问本类中的属性,如果本类没有此属性则从父类中继续查找 访问父类中的属性和方法

  • this(...) 调用构造器只能调用本类构造器,必须放在构造器的首行

  • super(...) 调用直接父类构造器,必须放在子类构造器的首行, 如果直接父类中没有此构造器, 编译出错!!!

  • this表示当前对象

  • super只是一个标识

    思考

    1).为什么super(…)和this(…)调用语句不能同时在一个构造器中出现?

    如果同时出现,super(. . . )直接调用父类构造, this(. . . )间接调用父类构造,多次不确定调用父类构造

     

    2).为什么super(…)或this(…)调用语句只能作为构造器中的第一句出现?

    保证子类构造晚于父类构造,先执行的是父类构造

     

    3)子类构造器中的第一行语句必须是什么? 为什么?

    默认是super();

    可以修改为要么是super(...) 要么是this(...)

    super(...) 作用是直接显式调用父类构造器

    this(...) 作用是间接调用父类构造器

    因为子类构造器中必须要有先对父类构造器的调用

     

  public class Chinese extends Person {

   private String shuxiang; // 特有属性
   //int age; // 子类中的同名属性不会有覆盖效果, 和父类继承的属性是共存关系

   public Chinese() {
       //super(); // 在子类构造器中的第一行一定有一个默认的super()语句, 作用是直接调用父类无参构造器
       //super("张三", 30, "男"); // 显式地直接地调用了父类的有参构造器
       this("张三", 30, "男", "猪"); // 调用本类重载的构造器
       /*
       this.setName("张三");
       this.setAge(30);
       this.setGender("男");
       */
       //this.shuxiang = "猪";
       System.out.println("Chinese()..."); // 1
  }

   // 子类全参构造器
   public Chinese(String name, int age, String gender, String shuxiang) {
       super(name, age, gender); // 显式的直接调用父类有参构造器
       /*
       this.setName(name);
       this.setAge(age);
       this.setGender(gender);
       */
       this.shuxiang = shuxiang;
       System.out.println("Chinese(Sting,int,String,String)..."); // 2
  }

   public String getShuxiang() {
       return shuxiang;
  }

   public void setShuxiang(String shuxiang) {
       this.shuxiang = shuxiang;
  }

   public void spring() { // 特有方法
       System.out.println(this.getName() + " 欢欢喜喜过大年");
  }

   @Override
   public String say() {
       return super.say() + ", 属相 : " + shuxiang;
  }

   @Override
   public void sayHello() {
       System.out.println("吃了吗?");
  }

}

 

子类对象实例化过程

 

 

  

面向对象特征之三:多态

概念

多态:子类对象的多种父类形态, 因为间接父类有很多.

本态: 子类对象的子类形态

 

从右向左看, 右面是子类对象, 把子类对象当作父类类型的对象来使用

从左向右看, 左面是父类类型的引用, 指向多种不同子类类型的对象

 

右面是实际new出来的对象, 称为运行时类型.

左面的引用只是编译时看重的, 称为编译时类型.

如果编译时类型和运行时类型不一致就出现多态.

总结:什么是多态?

从右向左看, 子类对象的多种父类形态

从左向右看, 父类类型的引用指向多种不同子类类型的对象

通过多态引用体现多态性

本态引用、多态引用

本态引用:创建子类对象赋值于子类类型的引用变量

多态引用:创建子类对象赋值于父类类型的引用变量

 

多态引用 :

本质就是把子类对象 "看作是" 父类类型的对象, 只是视角的变化

Person p = new Chinese("张三", 30, "男", "猪");

在Java中体现多态

父类 引用 = new 子类();

多态的本质就是 把子类对象 "当作是" 父类类型的对象来使用

虚拟方法调用

在父类表明此类型的事物应该具有这样的行为, 但是具体 怎么行为又不明确.

在子类中此行为就可以具体化了, 在运行中要以具体的为准

多态副作用

在多态的情况下, 不可以再使用子类特有成员.

//p.spring(); // 多态副作用 : 多态引用时, 子类特有成员无法直接访问.

虚方法调用

通过多态引用调用覆盖方法.

特点
  1. 编译时要检查父类类型(确认父类类型的事物是否具备此行为)

  2. 运行时要动态绑定具体子类类型(子类类型中的此行为更具体和完美)

p.sayHello(); // 虚拟方法调用. 多态引用调用覆盖方法, 执行谁的方法取决于new

// 1) 编译时检查父类类型. 2) 运行时动态绑定实际的子类类型 注意

  • 能用多态绝不本态

  • 绝对多的频繁使用子类特有成员时才用本态.

public class Person {

   private String name;
   private int age;
   private String gender;

   public Person() {
  }

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

   public String getName() {
       return name;
  }

   public void setName(String name) {
       this.name = name;
  }

   public int getAge() {
       return age;
  }

   public void setAge(int age) {
       this.age = age;
  }

   public String getGender() {
       return gender;
  }

   public void setGender(String gender) {
       this.gender = gender;
  }

   public String say() {
       return "姓名 : " + name + ", 年龄 : " + age + ", 性别 : " + gender;
  }

   public void sayHello() { // 虚拟方法调用 : 唯一作用就是骗过编译器
       System.out.println("打个招呼"); // 方法体没有执行
  }

}
***********************************************************************
public class American extends Person {

   private boolean hasGun; // 特有属性

   public American() {}

   public American(String name, int age, String gender, boolean hasGun) {
       super(name, age, gender);
       this.hasGun = hasGun;
  }

   public void setHasGun(boolean hasGun) {
       this.hasGun = hasGun;
  }

   public boolean isHasGun() {
       return hasGun;
  }

   public void shoot() {
       System.out.println("you not good, shoot you!!!!");
  }

   @Override
   public String say() {
       return super.say() + ", 有枪 : " + hasGun;
  }

   @Override
   public void sayHello() {
       System.out.println("How are you?");
  }

}
*****************************************************************************
public class Chinese extends Person {

   private String shuxiang;

   public Chinese() {
  }

   public Chinese(String name, int age, String gender, String shuxiang) {
       super(name, age, gender);
       this.shuxiang = shuxiang;
  }

   public String getShuxiang() {
       return shuxiang;
  }

   public void setShuxiang(String shuxiang) {
       this.shuxiang = shuxiang;
  }

   public void spring() { // 特有方法
       System.out.println(this.getName() + " 欢欢喜喜过大年");
  }

   @Override
   public String say() {
       return super.say() + ", 属相 : " + shuxiang;
  }

   @Override
   public void sayHello() {
       System.out.println("吃了吗?");
  }

}
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
public class PersonTest {

   public static void main(String[] args) {
       // 多态应用, 使用父类类型创建数组, 可以保存任意本类及子类对象, 多态数组
       Person[] arr = new Person[5];
       arr[0] = new Chinese("王五", 20, "男", "牛");
       arr[1] = new American("John", 30, "male", true);
       arr[2] = new Person("某人", 28, "未知");
       arr[3] = new American("Rose", 16, "female", false);
       arr[4] = new Chinese("李四", 40, "女", "虎");
       // 数组的价值就在于 "统一"
       for (int i = 0; i < arr.length; i++) {
           System.out.println(arr[i].say());
      }
       // 排序
       for (int i = 0; i < arr.length - 1; i++) {
           for (int j = 0; j < arr.length - 1 - i; j++) {
               if (arr[j].getAge() > arr[j + 1].getAge()) {
                   Person tmp = arr[j];
                   arr[j] = arr[j + 1];
                   arr[j + 1] = tmp;
              }
          }
      }
       System.out.println("**************************");
       for (Person person : arr) {
           System.out.println(person.say());
      }
  }

   public static void main1(String[] args) {
       // Chinese ch = new Chinese("张三", 30, "男", "猪"); // 本态引用
       // 多态引用 : 本质就是把子类对象 "看作是" 父类类型的对象, 只是视角的变化
       Person p = new Chinese("张三", 30, "男", "猪");
       //p.spring(); // 多态副作用 : 多态引用时, 子类特有成员无法直接访问.

       p.sayHello(); // 虚拟方法调用. 多态引用调用覆盖方法, 执行谁的方法取决于new
       // 1) 编译时检查父类类型. 2) 运行时动态绑定实际的子类类型

       System.out.println(p.getName());
       //p.spring();
       p = new American("Jack", 35, "male", true);
       //p.shoot();
       p.sayHello(); // 调用子类的方法
  }
}
造型
public class PersonTest {

   // 多态参数方法, 可以接收任意Person及其子类对象为其参数
   // 兼容性好, 可以兼容一个大的家族.
   public static void test(Person p) {
       p.sayHello(); // p 究竟是什么? 是模糊的.
       //p.spring(); // 多态副作用!!!!
       // 造型有风险!!! , 必须要判断
       // p instanceof Chinese, 判断p引用指向的对象实体 是否是 右面的Chinese类型的一个实例(对象)
       // 对于对象的类型的判断应该是从最子类到最父类
       if (p instanceof Peking) {
          ((Peking)p).playBird();
      } else if (p instanceof Chinese) { //(p是中国人) {
           Chinese c = (Chinese) p; // 对象引用类型的转换不是强制的. 称为造型 本质是切换对象的视角
           c.spring();
      } else if (p instanceof American) {
          ((American) p).shoot();
      } else {
           System.out.println("普通Person人");
      }
  }

   public static void main(String[] args) {
       Chinese ch = new Chinese("张三", 30, "男", "牛");
       American am = new American("Jack", 32, "male", true);
       Person p = new Person();
       Peking pk = new Peking("李四", 40, "男", "狗");
       test(ch);
       test(am);
       test(p);
       test(pk);
  }

   public static void main1(String[] args) {
       Person p = new Chinese("张三", 30, "男", "蛇");
       p.sayHello(); // 以运行时的真实对象为准
       p = new American("John", 32, "male", true);
       p.sayHello(); // 虚拟方法调用!!!
  }

}

 

多态小结
  1. 前提

  • 需要存在继承或者实现关系

  • 要有覆盖操作

  1. 成员方法

  • 编译时:要查看引用变量所属的类中是否有所调用的方法。(编译时检查父类类型)

  • 运行时:调用实际对象所属的类中的重写方法。

    (运行时执行子类类型)

  1. 成员变量

  • 不具备多态性,只看引用变量所属的类。

Object类

概念
  • Object类是所有java类的根父类

  • 如果在类的声明中未使用extends关键字指明其父类,则默认父类为Object类

NO.方法名称类型描述
1 public Object() 构造 构造方法
2 public boolean equals(Object obj) 普通 对象内容比较 this对象和obj对象
3 public int hashCode() 普通 获取对象的Hash码
4 public String toString() 普通 对象打印时调用
equals方法

含义:equals方法用于比较2个对象的内容是否相等.

作用:

a.equals(b), 如果相等返回true, 否则返回 false

如果2个对象的equals为true, 说明2个对象的内容是相等的, 这2个对象的哈希码应该一样, 表达特征性

hashCode方法

含义:hashCode是获取某个对象的哈希码, 哈希码也称为散列码或特征码.

作用:

如果2个对象的equals为false 说明2个对象的内容是不等的, 这2个对象的哈希码应该不同, 表达散列性

/**
* boolean equals(Object obj) : 用于比较当前对象this和参数中的对象obj的内容是否相等.
* int hashCode() : 返回当前对象的哈希码.
*     哈希码也称为散列码, 不同的对象在内存要有不同的码来区分
*     哈希码也称为特征码, 根据内容计算的码值,和属性值密切相关.
* 2个对象的equals为true, 说明这2个对象的内容是相等的, 内容相等, 哈希码必须一样, 体现特征码
* 2个对象的equals为false, 说明2个对象的内容不等, 哈希码必须不一样, 表现散列性.
*
* 如果要在类中重写equals则必须重写hashCode
* 如果要在类中重写hashCode则必须重写equals
*
*/
public class Point extends Object {

   private int x;
   private int y;

   public Point() {}

   public Point(int x, int y) {
       this.x = x;
       this.y = y;
  }

   public int getX() {
       return x;
  }

   public void setX(int x) {
       this.x = x;
  }

   public int getY() {
       return y;
  }

   public void setY(int y) {
       this.y = y;
  }

   public String say() {
       return "x : " + x + ", y : " + y;
  }

   /* 这个方法是Object父类中的equals方法. 这个方法很烂, 因为它根本 就没有比较对象的内容
   在子类中要想真正达到这个方法描述的效果, 就必须重写.
   public boolean equals(Object obj) {
       return (this == obj);
   }
   */

   @Override
   public boolean equals(Object obj) { // 在子类中要想真正达到这个方法描述的效果, 就必须重写.
       if (obj instanceof Point) {
           Point p2 = (Point) obj;
           if (this.x == p2.x && this.y == p2.y) {
               return true;
          }
      }
       return false;
  }

   /** 这是Object父类中的默认的hashCode(), 返回的码值是根据对象的真实地址进行散列算法,得出的. 具有绝对散列性
    * 默认的这个方法也不行, 只表达了散列性而不具备特征性.
    * native表示这个方法并不是由java编写的, 而是由C/C++编写成的.dll文件中的函数.
    public native int hashCode();
    */

   // 必须重写hashCode才能表达正确含义.
   @Override
   public int hashCode() {
       // 要想具有特征性, 必须要让所有属性都参与运算.
       // 要想具有散列性, 又得让它有些变化.
       return Integer.parseInt(x * 10 + "" + y * 10);
  }

   /* 这是父类中的toString() , 不能反映对象的详细信息. 要想让对象在打印更清晰, 必须 重写
   public String toString() {
       return getClass().getName() + "@" + Integer.toHexString(hashCode());
   }
   */

   @Override
   public String toString() {
       return "x : " + x + ", y : " + y;
  }

}
******************************************
public class PointTest {

   public static void main(String[] args) {
       Object p1 = new Point(30, 20);
       Object p2 = new Point(30, 20);
       System.out.println(p1 == p2); // 比较的是2个引用中的地址值.
       System.out.println(p1.equals(p2)); // 真正比较内容的是equals方法.
       System.out.println(p2.equals(p1)); // 真正比较内容的是equals方法.

       System.out.println(p1.hashCode());
       System.out.println(p2.hashCode());

       //p1 = null;
       System.out.println(p1); // 只要是打印对象的引用, 就会调用到toString()
       //System.out.println(p1.toString());
       String str = "abcd";
       str += p1; // 字符串和对象拼接时, 也会自动调用它的toString()
       System.out.println(str);

       //System.out.println("hello" == new java.sql.Date(222));

  }
}
 
posted @ 2022-05-01 00:50  叶舞  阅读(84)  评论(0)    收藏  举报