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 当中的方法只有final,static,private和构造方法是静态绑定,其它方法都是动态绑定。
③ 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类中。