Java基础|this和super、静态和动态绑定、equals与hashCode和toString有所不知道的细节

1 this关键字和super关键字

(1)this关键字

this 关键字有 3 个用途:一是引用隐式参数(即引用自己的成员变量);二是调用自己其它的构造器;三是代表本身类的一个对象。

(2)super关键字

super 关键字也有 3 个用途:一是调用超类的方法;二是调用超类的构造器;三是代表超类的一个对象。

(3)二者相似之处

在调用构造器时,两个关键字的使用方式相似。调用构造器的语句只能作为另一个构造器的第一条语句出现。

(4)示例代码

/**
 * @version $Id: Father.java, v 0.1 2017年6月1日 下午8:21:22
 */
public class Father {
    private String name;
    private int    age;

    /**
     * 默认构造器
     */
    public Father() {
    }

    public Father(String name) {
        //调用本类中的其他构造器
        this(name, 20);
    }

    public Father(String name, int age) {
        //引用隐式参数
        this.name = name;
        this.age = age;
    }

    //父类中的方法
    public void printInfo(String name, int age) {
        System.out.println("name = " + name);
        System.out.println("age = " + age);
    }

    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;
    }
}
/**
 * 
 * @version $Id: Child.java, v 0.1 2017年6月1日 下午8:30:03
 */
public class Child extends Father {
    private String sex;

    public Child() {
    }

    public Child(String sex) {
        //[super]调用超类构造器
        super("tianci", 20);
        //[super]调用超类方法;[super]代表超类对象
        super.printInfo(super.getName(), super.getAge());

        //[this]引用隐式参数
        this.sex = sex;
        //[this]代表本类的对象
        this.printInfo(this.getSex());
    }

    //子类中的方法
    public void printInfo(String sex) {
        System.out.println("sex = " + sex);
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

2 方法调用(静态绑定)和(动态绑定)

⑴静态绑定

程序执行前方法已经被绑定(也就是说在编译过程中就已经知道这个方法到底是哪个类中的方法)。针对 Java 简单的可以理解为程序编译期的绑定。

⑵动态绑定

程序在运行期间判断对象的类型,调用适当的方法(也就是说在编译过程中并不知道这个方法是哪个类中的方法,但在程序运行期间通过一定的机制可以确定个方法是哪个类的方法,并正确的调用)。动态绑定的过程:❶虚拟机提取对象的实际类型的方法表;❷虚拟机搜索方法签名;❸调用方法。

⑶ Java 中的静态绑定和动态绑定体现

动态绑定针对的范畴只是对象的方法
② Java 当中的方法只有finalstaticprivate构造方法是静态绑定,其它方法都是动态绑定。
③ Java 中对属性采取静态绑定。

⑷Java 的编译与运行

  • ①Java 的编译过程是将 Java 源文件编译成字节码( JVM 可执行代码,即 .class 文件)的过程,在这个过程中 Java 是不与内存打交道的,在这个过程中编译器会进行语法的分析,如果语法不正确就会报错。

  • ② Java 的运行过程是指 JVM(Java 虚拟机)装载字节码文件并解释执行。在这个过程才是真正的创立内存布局,执行 Java 程序。Java 字节码的执行有两种方式: ❶即时编译方式:解释器先将字节编译成机器码,然后再执行该机器码;❷解释执行方式:解释器通过每次解释并执行一小段代码来完成 Java 字节码程序的所有操作。

⑸Java 静态绑定和动态绑定特点

  • ①静态绑定:可以让我们在编译期就发现程序中的错误,而不是在运行期。静态绑定对系统开销较小,提高了程序运行效率。
  • ②动态绑定:而对方法采取动态绑定是为了实现多态,以效率为代价来实现多态。动态绑定可以让我们对现存的代码无需修改,就可以对其功能进行扩展。

3 equals与hashCode和toString方法

⑴ equals

'=='双等号两种应用

 ❶比较基本数据类型,如果两个值相同,则结果为 true,否则 false.
 ❷比较引用类型时,如果引用指向内存中的同一对象(即两个变量引用同一个对象时),结果为 true,否则 false。

equals

  • Object 类中 equals 方法
    作用是判断两个变量是否具有相同的引用,如果是结果为 true,否则false,从这一点来说 equals 本质实现方式使用了 ==。

  • 覆写 equals 方法
    一般我们有这样的需求:只比较两个对象的内容是否相同,如果两个对象的内容都相同,就认为这两个对象相等。想达到这一目的,我们就需要自己覆写 equals 方法。java常用的类基本都默认覆写了equals方法来实现比较内容相同。

  • 覆写 equals 方法建议
    在自己定义的类中编写一个完美的 equals 方法的建议:
    ❶显式参数命名为 otherObject ,稍后将它转换成另一个叫做 other 的变量。
    ❷检测 this 与 otherObject 是否引用同一个对象:

	if(this==otherObject) return true;

❸检测 otherObject 是否为 null,如果为 null,返回 false:

	if(null==otherObject) return false;

❹比较 this 与 otherObject 是否属于同一类。如果 equals 的语义在每个子类中有所改变,就是用 getClass 检测:

	if(this.getClass()!=otherObject.getClass()) return false;

如果所有的子类都拥有统一的语义,就是用 instanceof 检测:

	if(!(otherObject instanceof ClassName)) return false;

❺将 otherObject 转换为相应的类类型变量:

	ClassName other = (ClassName)otherObject;

❻接下来对所有需要比较的域进行比较。使用 == 比较基本类型域,使用equals 比较对象域。如果所有的域都匹配,返回 true,否则返回 false。

return field1==other.field1&&Objects.equals(field2,other.field2)&&...;
如果子类中重新定义 equals,就要在其中包含调用:
super.equals(other);

(2) hashCode

① Object 类中 hashCode 方法

由于 hashCode 方法定义在 Object 类中,因此每个对象都有一个默认的散列码,其值是一个整型数值,为对象的存储地址。

② 覆写 hashCode 方法

  • 覆写hashCode方法原则。当我们在自定义的类覆写equals方法的时候,就必须覆写hashCode方法。

  • 为什么要遵循覆写hashCode原则
    1 HashCode 存在的作用:当对象存储在哈希表中(如 Hashtable,HashMap)时,哈希表中查找对象时,先依据 HashCode 确定对象所在的链表,再依据 equals 方法在链表中确定对象是否存在。


2 覆写 equals() 时,要覆写 hashCode() 原因:

  • <1>JavaSE 规范约束:可查看 Object 的 hashCode() 方法,注释文档。归纳起来就是以下3条:
两个对象相同,hashCode 一定相同;
hashCode 不同,两个对象一定不相同;
hashCode 相同,两个对象不一定相同。
  • <2>覆写的本质意义:
若对象不会保存在哈希表结构中,那么是不需要覆写 hashCode();

若在 HashMap 和 Hashtable 里面如果是作为 value 而不是作为key 的话,也是不必覆写 hashCode()。
至于 HashSet,实际上它只是忽略value 的 HashMap,每次 HashSet.add(obj) 其实就是 HashMap.put(obj, dummyObject)。

为了保证对象做为 key 时,每次 get 的时候 HashMap(包括HashTable,HashSet)既要看 equals 是不是 true,
也要看 hash code 是不是 true;put 的时候也是要判断 equals() 和 hash code() 是不是都是 true,来进行添加和获取操作。
重写了 equals() 改变了判等依据,所以要对应重写 hashCode() 改变对应的判等依据,否则无法保持一致性造成错误。

⑶ toString

① Object 类中 toString 方法

Object 类定义了 toString 方法,因此每个对象都有一个默认的 toString 方法,打印输出的默认内容为:对象所属的类名和散列码。

② 覆写toString方法

一般对自定义的类,覆写 toString 方法,方便打印我们需要看到的内容。

⑷ equals 与 hashCode 和 toString 用例

父类代码:

import java.io.Serializable;
import java.time.LocalDate;
import java.util.Objects;
/**
 * 
 * @version $Id: Employee.java, v 0.1 2017年5月18日 下午6:32:19 */
public class Employee implements Serializable {
    /**  */
    private static final long serialVersionUID = -1213732035543208949L;

    private String            name;
    private double            salary;
    private LocalDate         hireDay;

    public Employee(String name, double salary, int year, int month, int day) {
        this.name = name;
        this.salary = salary;
        hireDay = LocalDate.of(year, month, day);
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public LocalDate getHireDay() {
        return hireDay;
    }

    public void raiseSalary(double byPrecent) {
        double raise = salary * byPrecent / 100;
        salary += raise;
    }

    //equals方法
    /*编写完整equals方法步骤:
     * 检测this 和 otherObject是否引用同一个对象
     * 检测otherObject是否为null
     * 检测this 和 otherObject是否为同一个类
     * 将otherObject转换为相应的类类型变量
     * 使用Objects.equals()比较域对象
     * 
    */
    public boolean equals(Object otherObject) {
        // 检测this 和 otherObject是否引用同一个对象
        if (this == otherObject)
            return true;
        // 检测otherObject是否为null
        if (null == otherObject)
            return false;
        //检测this 和 otherObject是否为同一个类
        if (this.getClass() != otherObject.getClass())
            return false;
        //将otherObject转换为相应的类类型变量
        Employee other = (Employee) otherObject;
        // 使用Objects.equals()比较域对象
        return Objects.equals(name, other.name) && Objects.equals(salary, other.salary) && Objects.equals(hireDay, other.hireDay);
    }

    //hashCode方法
    public int hashCode() {
        //由所有对象的散列码组合组合一个散列码
        return Objects.hash(name, salary, hireDay);
    }

    /** 
     * toString 方法
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "Employee [name=" + name + ", salary=" + salary + ", hireDay=" + hireDay + "]";
    }
}   

子类代码:

import java.util.Objects;

/**
 * @version $Id: Manager.java, v 0.1 2017年5月18日 下午6:58:53
 */
public class Manager extends Employee {
    /**  */
    private static final long serialVersionUID = 5401329903146485794L;
    /**
     * @param name
     * @param salary
     * @param year
     * @param month
     * @param day
     * 继承中,当基类中没有无参数的构造器时(换句话说,基类中只提供了有参数的构造器),
     * 那么子类也要提供对应的含参数构造器,否则编译出错。在子类 的构造器中我们使用super
     * 关键字,实现对基类的构造器的调用。
     */
    public Manager(String name, double salary, int year, int month, int day) {
        //使用super关键字调用基类的构造器
        super(name, salary, year, month, day);
        bonus = 0;
    }

    private double bonus;

    public double getSalary() {
        //使用super关键字调用基类的方法
        double baseSalary = super.getSalary();
        return baseSalary + bonus;
    }

    public void setBonus(double bonus) {
        //this 关键字引用隐式参数
        this.bonus = bonus;
    }

    //equals方法
    public boolean equals(Object otherObject) {
        //如果子类中重新定义equals,就要在其中包含调用: super.equals(otherObject);
        if (!super.equals(otherObject))
            return false;
        //调用父类的super.equals(otherObject)检测通过,只需检测子类中独有的域值
        Manager other = (Manager) otherObject;
        return Objects.equals(bonus, other.bonus);
    }

    //hashCode方法
    public int hashCode() {
        return super.hashCode() + 17 * new Double(bonus).hashCode();
    }

    /** 
    * @see java.lang.Object#toString()
    */
    @Override
    public String toString() {
        return super.toString() + "[bonus=" + bonus + "]";
    }
}

测试代码:

/**
 * @version $Id: Test.java, v 0.1 2017年5月18日 下午7:10:04
 */
public class Test {
    public static void main(String[] args) {
        Employee alice1 = new Employee("Alice Adams", 7500, 1987, 12, 5);
        Employee alice2 = alice1;
        Employee alice3 = new Employee("Alice Adams", 7500, 1987, 12, 5);
        Employee bob = new Employee("Bob Brandson", 5000, 1989, 10, 1);

        System.out.println("--------==-------");
        System.out.println(alice1 == alice2);
        System.out.println(alice1 == alice3);
        System.out.println("-----父类equals----");
        System.out.println(alice1.equals(alice2));
        System.out.println(alice1.equals(alice3));
        System.out.println(alice1.equals(bob));
        System.out.println("-----子类equals-----");
        Manager carl = new Manager("Carl", 5000, 1987, 12, 10);
        Manager boss = new Manager("Carl", 5000, 1987, 12, 10);
        Manager boss2 = new Manager("Carl", 5000, 1987, 12, 10);
        boss2.raiseSalary(5000);
        System.out.println(carl.equals(boss));
        System.out.println(boss.equals(boss2));
        System.out.println("----hashCode值----");
        //不同对象的hashCode值可能相同
        System.out.println(alice1.hashCode());
        System.out.println(alice2.hashCode());
        System.out.println(alice3.hashCode());
        System.out.println("----hashCode值----");
        System.out.println(bob.hashCode());
        System.out.println(carl.hashCode());
        System.out.println(boss.hashCode());
        System.out.println(boss2.hashCode());
    }
}

测试结果:

--------==-------
true
false
-----父类equals----
true
true
false
-----子类equals-----
true
false
----hashCode值----
-916556984
-916556984
-916556984
------------------
-731457450
1306331297
1306331297
1492420577

⑸ 自定义类覆写 equals、hashCode、toString方法时建议

  • 1.java.util.Objects (jdk7)
    static int hash(Object… objects)
    返回一个散列码,由提供的所有对象的散列码组合得到。
    static int hashCode(Object a)
    如果a为null,返回0;否则返回a.hashCode();
    static boolean equals(Object a,Object b)
    如果a和b都为null,返回true;如果其中之一为null,返回false;否则返回a.equals(b)
  • 2.针对数组的一些工具类方法在java.util.Arrays类中。
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不甩锅的码农

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

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

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

打赏作者

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

抵扣说明:

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

余额充值