最好将类中的域标记为private,而方法标记为public。任何声明为private的内容都是对其他类来说是不可见的。这个对于继承来说也同样适用:子类不能访问超类的私有域。
然而,有些时候,人们希望超类中的某些方法允许被子类访问,或者允许子类访问超类的某个域,为此,需要将这些方法或者域声明为procted。例如,如果将超类Employee中的hireDay声明为procted,而不是私有的,Manager中的方法就可以直接访问它。
不过,Manager类中的方法只能够访问Manager对象中的hireDay域,而不能直接访问其他Employee对象中的这个域。这种限制有助于避免滥用保护极值,使得子类只能获得访问保护域的权利。
受保护的方法更具有实际意义,如果需要限制某个方法的使用,就可以将它声明为procted,这表明子类得到信任,可以正确地使用这个方法,而其他类则不行。
归纳一下Java中可见性控制的四个访问修饰符:
- 仅对本类可见 - private对所有类可见 - public对本包和所有子类可见 - protected对本包可见 - 默认,不需要修饰符
Object类是所有Java类的始祖,在Java中每个类都是由它扩展而来的,如果没有明确的指出来超类,那么Object就被认为是这个类的超类。由于在Java中,每个类都是由Object类扩展而来的,所以,熟悉这个类很重要。
Object类型的变量只能用于作为各种值的通用持有者,要相对其中的内容进行操作,还需要知道对象的原始类型,并进行相应的类型转换
Object obj =new Employee("haha",300000);
Employee e = (Employee) obj;
在Java中,只有基本类型primitive types不是对象,例如:数字、字符和布尔类型的值都不是对象。
所有的数组类型,不管是对象数组还是基本类型数组都扩展了Object类
Employee[] staff = new Employee[10];
obj = staff;
obj = new int[10];
Equals方法
object类中的equals方法用于检测一个对象是否等于另一个对象,在Object类中,这两个方法将判断两个对象是否具有相同的引用。如果连个对象具有相同的引用,它们一定是相等的。然而,对于多数类来说,这并没有什么意义,在检测中,只有在两个对象属于同一个类时候,才可能相等。
相等测试与继承如果隐式和显式参数不属于同一个类,equals方法将会如何处理呢?
Java语言要求equals方法具有下面的特性:
- 自反性:对于任何非空引用x,x.equals(x)应该返回true对称性:对于任何引用x和y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true传递性:对于任何引用x和y,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也应该返回true一致性:如果x和y引用的对象没有发生变化,返回调用x.equals(y)应该返回同样的结果。对于任意非空引用x,x.equals(null)应该返回false
然而对于对称性来说,当参数不属于同一个类的时候需要仔细考虑一下,例如:e.equals(m),这里的e是一个employee对象,m是一个manager对象,并且两个对象具有相同的姓名、薪水、雇用日期,如果在employee.equals中使用了instanceod进行检测,则返回true,然而这也意味着反过来调用:m.equals(e)也需要返回true,对称性不允许这时候返回false或者抛出异常。这就使得manager类收到了束缚。
一些人认为不应该用getClass的方法检测,不符合置换原则,有一个应用AbstractSet类的equals方法中的例子,它将检测两个集合是否具有相同的元素,AbstractSet有两个具体的实现子类:TreeSet和HashSet,它们分别使用不同的算法来实现查找集合元素的操作。无论集合采用任何方式实现,都需要对拥有任意两个集合进行比较的功能。
然而,集合石一个相当特殊的例子,应该将AbstractSet.equals声明为final,这是因为没有任何一个子类需要重新定义集合是否相等的语义。
下面是两个不同的视角:
- 如果子类能够拥有自己的相等概念,则对称性需求则强制采用getClass进行检测如果由超类决定相等的概念,那么就可以使用instance of进行检测,这样可以在不同的子类的对象之间进行相等的比较。
关于如何编写一个完美的equals方法的建议:
- 显示参数命名为otherObject,稍后需要将它转化成另一个叫做other的变量。检测this与otherObject是否引用同一个对象:
if(this == otherObject) return true;
这条语句只是一个优化。实际上,这是一个经常采用的形式,因为计算这个等式要比一个一个地比较类中的所有域付出的代价要小检测otherObject是否为null,如果为null,返回false,这项检测是很有必要的。
if(otherObject == null) return false;比较this与otherObject是否属于同一类,如果equals的语义在每个子类中有所改变,就是用getClass检测:
if(getClass() != otherObject.getClasss() return false;
如果所有的子类都拥有统一的语义,就用instance of 检测:
if (!(otherObject instanceof ClassName) return false;将otherObject转化为相应的类型变量:
ClassName other = (ClassName) otherObject现在开始对所有需要进行比较的域开始进行比较了,使用==比较基本类型与,使用equals比较对象与。如果所有的域都匹配,就返回true,否则返回false。如果在子类中重新定义了equals,就要在一种包含调用super.equals(other)。
提示:
对于数组类型的域,可以使用静态的Arrays.equals方法检测对应的数组元素是否相等。
散列码hash code是由对象导出的一个整形值,散列码是没有规律的,如果x和y是两个不同的对象,x.hashCode()与y.hashCode()基本不会相等.
package corejava.chapter5.equals;
public class HashCodeTest {
public static void main(String[] args) {
String s = "ok";
StringBuilder stringBuilder = new StringBuilder(s);
System.out.println(s.hashCode() + " " + stringBuilder.hashCode());
String t = new String("ok");
StringBuilder stringBuilder1 = new StringBuilder(t);
System.out.println(t.hashCode() + " " + stringBuilder1.hashCode());
}
}
程序运行输出结果如下:
字符串s与字符串t拥有相同的散列码,这是因为字符串的散列码是由内容导出的。而缓冲字符串stringBuilder和stringBuilder1的散列码却不相同,这是因为在String BUffer 类中没有定义hashCode方法,它的散列码是由Object类的默认hashCode方法导出的对象存储地址。
如果重新定义equals方法,就必须重新定义hashCode方法,以便于用户可以将对象插入到散列表中。
hashCode方法应该返回一个整形数值,也可以是负数,并且合理的组合实力域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。
Equals与hashCode的定义必须一致:如果x.equals(y)返回true,那么x.hashCode()就必须与y.hashCode()具有相同的值。例如,如果定义的employee.equals比较雇员的ID,那么就hashCode需要三列ID,而不是用雇员的姓名或存储地址。
注意:如果存在数组类型的域,那么可以使用静态的Arrays.hashCode方法计算一个散列码,这个散列码有数组元素组成的散列码组成。
demo 2:
step1:构建一个Employee超类package corejava.chapter5.equals;
import java.time.LocalDate;
import java.util.Objects;
public class Employee {
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;
this.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 byPercent) {
double raise = salary * byPercent / 100;
salary = raise + salary;
}
@Override
public boolean equals(Object otherObject) {
// a quick test to see if the objects are identical
if (this == otherObject) return true;
// must return false if the explicit params is null || if the class don't match ,they can't be equal
if (otherObject == null || getClass() != otherObject.getClass()) return false;
//now we know that otherObjects is a non-null employee
Employee employee = (Employee) otherObject;
//test whether the fields have identical values
return Double.compare(employee.salary, salary) == 0 && Objects.equals(name, employee.name) && Objects.equals(hireDay, employee.hireDay);
}
@Override
public int hashCode() {
return Objects.hash(name, salary, hireDay);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + ''' +
", salary=" + salary +
", hireDay=" + hireDay +
'}';
}
}
Step2:构建一个子类Manager
package corejava.chapter5.equals;
import java.util.Objects;
public class Manager extends Employee {
private double bonus;
public Manager(String name, double salary, int year, int month, int day) {
super(name, salary, year, month, day);
bonus = 0;
}
public double getSalary() {
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
@Override
public boolean equals(Object otherObjects) {
if (!super.equals(otherObjects)) return false;
Manager other = (Manager) otherObjects;
return bonus == other.bonus;
}
@Override
public int hashCode() {
return super.hashCode() + 17 * new Double(bonus).hashCode();
}
@Override
public String toString() {
return "Manager{" +
"bonus=" + bonus +
'}';
}
}
Step3:构建一个测试类
package corejava.chapter5.equals;
public class EqualsTest {
public static void main(String[] args) {
Employee CC = new Employee("CC", 30000, 2020, 12, 12);
Employee CC2 = CC;
Employee CC3 = new Employee("CC", 30000, 2022, 12, 12);
Employee bb = new Employee("bb", 40000, 2025, 11, 11);
System.out.println("CC == CC2:" + (CC == CC2));
System.out.println("CC == CC3:" + (CC == CC3));
System.out.println("CC.equals(CC2):" + (CC.equals(CC2)));
System.out.println("CC equals(CC3):" + (CC.equals(CC3)));
System.out.println("bb.toString():" + bb);
Manager car1 = new Manager("Carl Cracker", 80000, 1999, 2, 2);
Manager boss = new Manager("Carl Cracker", 80000, 1999, 2, 2);
boss.setBonus(5000);
System.out.println("boss.toString(): " + boss);
System.out.println("carl.equals(boss): " + car1.equals(boss));
System.out.println("CC.hashCode(): " + CC.hashCode());
System.out.println("CC3.hashCode(): " + CC3.hashCode());
System.out.println("bb.hashCode(): " + bb.hashCode());
System.out.println("car1.hashCode(): " + car1.hashCode());
}
}



