17.Java 类继承/instanceof运算符/方法重写/Object 类/equals方法/super关键字/继承树追溯/属性方法查找顺序

继承

简述

继承使我们更容易实现类的扩展。
在编程中,如果新定义了一个 Student 类,发现已经有 Person 类包含了我们需要的属性和方法,那么 Student 类只需要继承 Persion 类即可拥有 Person 类的属性和方法。

继承使用要点

  1. 父类也称为超类、基类、派生类等
  2. Java 中只有单继承,没有像 C++ 的多继承。多继承会引起混乱,使得继承链过于复杂,系统难于维护。
  3. Java 类没有多继承,接口有多继承。
  4. 子类继承父类,可以得到父类的全部属性和方法(除了父类的构造方法),但不见得可以直接访问(比如父类的私有方法和属性)。
  5. 如果定义一个类时,没有调用 extends,则它的父类是 java.lang.Object

instanceof 运算符

instanceof 是二元运算符,左边是对象,右边是类;
当对象是右边类或者子类创建的对象时,返回为 true,否则返回 false

代码示例:测试继承和 instanceof 运算符

package cn.jungle.test;
import javax.sound.sampled.Port;
import javax.swing.*;
/**
 * 测试继承和 instanceof 运算符
 */

public class TestExtends {
    public static void main(String[] args) {
        Student s = new Student("阿jun", 175, "计算机");
        s.rest();
        s.study();
        // 定义一个新对象
        Student stu2 = new Student("阿jun",180,"互联网");

        // 测试 instanceof,返回结果为布尔值
        System.out.println(stu2 instanceof Student);         // true
        System.out.println(stu2 instanceof Person);          // true
        System.out.println(stu2 instanceof Object);          // true
        System.out.println(new Person() instanceof Student); // false
    }
}
    // 定义类
    class Person{
        String name; // 姓名
        int height;  // 身高
        public void rest(){
            System.out.println("休息一下下。");
        }
    }
    // 定义继承类
    class Student extends Person{
        String major; // 专业
        public void study(){
            System.out.println("我好菜,得抓紧学习。");
        }
        // 构造方法重载
        public Student(String name, int height, String major){
            // 天然拥有父类的属性
            this.name = name;
            this.height = height;
            this.major = major;
        }
}

方法的重写(override)/覆盖

简述

子类通过重写父类的方法,可以用自身的行为替换父类的行为。
方法的重写是实现多态的必要条件。

方法重写需要符合的三个要点:

  1. "==":方法名和形参列表相同。
  2. "<=":返回值类型和声明异常类型,子类小于等于父类。
  3. ">=":访问权限,子类大于等于父类。

代码示例:方法的重写/覆盖

package cn.jungle.test;

// 测试方法的重写/覆盖
public class TestOverride {
    public static void main(String[] args) {
        Vehicle v1 = new Vehicle();
        Vehicle v2 = new Horse();
        Vehicle v3 = new Plane();
        // 对象调用类对象
        v1.run();
        v2.run();
        v3.run();
        v1.stop();
        v2.stop();
        v3.stop();
    }
}
class Vehicle {  // 定义一个交通工具集合的类
    public void run() {
        System.out.println("开始跑。。。。。。");
    }
    public void stop() {
        System.out.println("停止不动了。。。。");
    }
}
class Horse extends Vehicle{
    // 重写父类的方法(重写继承过来的 run() 方法)
    public void run(){
        System.out.println("马儿一直不停的哒哒哒哒哒跑。");
    }
}
class Plane extends Vehicle{
    // 重写父类的方法
    public void run(){
        System.out.println("在天上飞啊飞。。。");
    }
    public void stop(){
        System.out.println("空中停留就完蛋了。");
    }
}

Object 类

简述

Object 类是 Java类 的根基类,即所有的 Java 对象都具有 Object 类的属性和方法。

注意点

  1. 当我们去打印某个对象的时候,实际上是去调用了这个对象的 toString() 方法
  2. Object 类中定义有 public String toString() 方法,其返回值为 String 类型,
  3. 由源码可知,默认会返回 “ 类名 + @ + 16进制的 hashcode ”。
  4. 在打印输出或用字符串连接对象时,会自动调用该对象的 toString() 方法。

Object 类中 toString() 方法的源码如下:

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

代码示例:toString() 和重写 toString() 方法

package cn.jungle.test;

// 测试 toString() 和重写 toString() 方法
class Person2{
    String name;
    int age;
    // toString 方法的重写
    @Override
    public String toString(){
        return name + ",年龄:" + age;
    }
}
public class TestObject {
    public static void main(String[] args) {
        Person2 p = new Person2();
        p.age= 20;
        p.name = "阿jun";
        System.out.println("info:" + p);
        TestObject t = new TestObject();
        // 打印对象:实际上是对象去调用 toString() 方法
        System.out.println(t.toString());
    }
}

equals方法

简述

“==” 的含义是比较双方是否相同。如果是基本类型则表示值相等,如果是引用类型则表示地址相等即是同一个对象。
Object 类中定义有:public boolean equals(Object obj) 方法,提供定义 “定义内容相等” 的逻辑。比如,我们在公安系统中认为身份证 id 相同的人就是同一个人,学籍系统中认为学好相同的人就是同一个人。
Object 的 equals 方法默认就是比较两个对象的 hashcode,当是同一个对象的引用时,返回 true,否则返回 false。但 equals 方法可以重写。

代码示例:测试 equals 方法和自定义重写 equals 方法

package cn.jungle.test;

// 测试 equals 方法和自定义重写 equals 方法
public class TestEquals {
    public static void main(String[] args) {
        Object obj;
        String str;

        User u1 = new User(1000,"阿jun","123456");
        User u2 = new User(1000,"阿jun修炼手册","654321");
        // 测试对比 equal()
        System.out.println(u1 == u2);   // false ,比较 u1 和 u2 两个对象内容是否相等
        System.out.println(u1.equals(u2));  // ture ,因为只比较了 id 属性
        // 定义两个 String 类对象
        String str1 = new String("sxt");
        String str2 = new String("sxt");
        // 比较两个是否是同一个对象
        System.out.println(str1 == str2);  // false,比较两个是否是同一个对象
        System.out.println(str1.equals(str2)); // true,比较两个对象的内容是否相等
    }
}
// 定义 User 类
class User{
    int id;
    String name;
    String pwd;
    // User类的构造方法重载
    public User(int id,String name,String pwd){
        super();
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }
    // equals 方法的重写
    @Override
    public boolean equals(Object obj){
        // 如果传进来的 obj 对象和我当前的 obj 对象相等,则返回 true
        if (this == obj)
            return true;
        // 如果传进来的 obj 对象为空(当前类的 obj 对象不为空),则返回 false
        if (obj == null)
            return false;
        // 如果传进来的对象类型不一样,则返回 false
        if (getClass() != obj.getClass())
            return false;
        // 将传进来的 obj 对象强制转为 User 类的对象,然后进行调用比较
        User other = (User)obj;
        // 用新定义的 other 对象传进来的 obj 对象对应 id 属性。
        // 比较传进来的 obj 对象的 id 属性是否相等
        if (id != other.id)
            return false;
        return true;
    }
}

闲谈

JDK 提供的一些类,如 String、Date、包装类等,重写了 Object 的 equals 方法,调用这些类的 equals 方法,x.equals(y),当 x 和 y 所引用的对象是同一类对象且属性内容相等时(并不一定是相同对象),返回 true,否则返回 false

super

简述

super 是直接父类对象的引用。可以通过 super来访问父类中被子类覆盖的方法或属性。
使用 super 调用普通方法,语句没有位置限制,可以在子类中随便调用。
若是构造方法的第一行代没有显式的调用 super(…) 或 this(…),
那么 Java 默认都会调用 super(), 含义是调用父类的无参数构造方法。这里的 super() 可以省略。

代码示例:super 关键字的使用

package cn.jungle.test;

// 测试 super
public class TestSuper01 {
    public static void main(String[] args) {
        // 定义 ChildClass 类的对象并调用方法
        new ChildClass().f();
    }
}
// 定义父类
class FatherClass{
    public int value;
    public void f(){
        value = 100;
        System.out.println("FatherClass 里面的 value 属性值为" + value);
    }
}
// 定义子类
class ChildClass extends FatherClass{
    public int value;
    public void f(){
        // 利用 super 调用父类对象的普通方法
        super.f();
        value = 200;
        System.out.println("ChildClass 里面的 value 属性值为" + value);
        System.out.println(value);
        // 利用 super 调用父类对象的成员变量
        System.out.println(super.value);
    }
}

继承树追溯

属性/方法查找顺序

案例:查找变量 h

  1. 查找当前类中有没有属性 h
  2. 依次上溯每个父类,查看每个父类中是否有属性 h,直到 Object
  3. 如果没有找到,则会出现编译出错。
  4. 上面步骤,只要找到 h 变量,则这个过程终止。

代码示例:测试继承树追溯

package cn.jungle.test;

// 测试继承树追溯
public class TestSuper02 {
    public static void main(String[] args) {
        System.out.println("开始创建一个 ChildClass 对象");
        // 创建 ChildClass2 类对象并调用
        new ChildClass2();
    }
}
// 定义父类
class FatherClass2{
    // 定义 FtherClass2 类的构造方法
    public FatherClass2(){
        super();     // 不写也会默认调用 super()
        System.out.println("测试子类父类的创建顺序(根据打印属性来判断):这里是创建 FatherClass");
    }
}
// 定义子类继承
class ChildClass2 extends FatherClass2{
    // 定义 ChildClass2 类的构造方法
    public ChildClass2(){
        super();
        System.out.println("测试子类父类的创建顺序(根据打印属性来判断):这里是创建 ChildClass");
    }
}

知识延展

构造方法调用顺序

  1. 构造方法第一句总是:super(…) 来调用父类对应的构造方法。
  2. 调用流程是:先向上追溯到 Object,然后再依次向下执行类的初始化块和构造方法,直到当前子类为止。
    注意点:

super() 永远位于构造方法的第一行代码
静态初始化调用顺序,与构造方法调用顺序一样,不再重复。

启发

代码审计过程中,去寻找某一个属性或者方法时,可通过方法调用的规律去不断定位,寻找最初的位置。

posted @ 2021-11-12 17:34  阿jun  阅读(226)  评论(0)    收藏  举报