97. 重写 equals() 时是否适用任何规则?
重写 equals() 方法时,必须遵守以下规则:-
自反性— 对于任何值x,x.equals(x)必须始终返回true(其中x != null)。
-
对称性— 对于任何值x和y,仅当y.equals(x)返回true时, x.equals(y)才必须返回true。
-
传递性— 对于任何值x、y和z,如果x.equals(y)返回true并且y.equals(z)也返回true,则x.equals(z)必须返回true。
-
一致性- 对于任何值x和y ,只要用于比较两个对象的字段在每次调用之间没有更改,重复调用x.equals(y)将始终返回相同的值。
-
null 比较— 对于任何值x,调用x.equals(null)必须返回false。
98. 如果不重写 equals() 和 hashCode() 会发生什么?
在这种情况下,hashCode() 将返回一个根据存储对象的内存单元的地址生成的数字。换句话说,当对两个具有完全相同字段的对象调用原始的hashCode()方法时,结果将会不同(因为它们存储在不同的内存位置)。原来的equals()方法比较引用,即指示引用是否指向同一个对象。换句话说,比较使用==运算符,对于不同的对象它总是返回false,即使它们的字段相同。仅当比较对同一对象的引用时才返回true 。有时不重写这些方法是有意义的。例如,您希望某个类的所有对象都是唯一的 - 重写这些方法只会破坏唯一哈希码的现有保证。重要的是要了解这些方法的细微差别(无论是否被覆盖),并根据情况使用任何方法。99. 为什么只有x.equals(y)返回true才满足对称性要求?
这个问题有点奇怪。如果对象 A 等于对象 B,则对象 B 等于对象 A。如果 B 不等于对象 A,那么反过来怎么可能呢?这是常识。100.什么是HashCode冲突?你如何解决?
当两个不同的对象具有相同的HashCode时,就会发生HashCode冲突。这怎么可能?好吧,哈希码被映射到一个整数,其范围从 -2147483648 到 2147483647。也就是说,它可以是大约 40 亿个不同整数之一。这个范围很大,但不是无限的。这意味着在某些情况下,两个完全不同的对象可能具有相同的哈希码。这是极不可能的,但也是有可能的。实施不当的哈希函数可以通过返回小范围内的数字来使相同的哈希码更加频繁,从而增加冲突的机会。为了减少冲突,您需要良好地实现HashCode方法,以均匀地分散值并最大限度地减少重复值的机会。101. 如果参与 hashCode 合约的元素的值发生变化,会发生什么情况?
如果参与哈希码计算的元素发生变化,那么该对象的哈希码也应该发生变化(如果哈希函数良好)。这就是为什么您应该使用不可变对象作为HashMap中的键,因为它们的内部状态(字段)在创建后无法更改。由此可见,它们的哈希码在创建后确实会发生变化。如果你使用可变对象作为键,那么当对象的字段发生变化时,它的哈希码也会改变,你可能会丢失HashMap中相应的键值对。毕竟,它会存储在与原始哈希码关联的存储桶中,但对象发生变化后,您将在不同的存储桶中搜索它。102. 为具有 String name 和 intage 字段的 Student 类编写 equals() 和 hashCode() 方法。
public class Student {
int age;
String name;
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
final Student student = (Student) o;
if (this.age != student.age) {
return false;
}
return this.name != null ? this.name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = this.age;
result = 31 * result + (this.name != null ? this.name.hashCode() : 0);
return result;
}
}
等于():
-
首先,我们直接比较引用,因为如果引用指向同一个对象,那么继续检查相等性有什么意义呢?我们已经知道结果将是真实的。
-
我们检查 null 以及类类型是否相同,因为如果参数为 null 或其他类型,则对象不能相等,结果必定为false。
-
我们将参数转换为相同的类型(毕竟,如果它是父类型的对象怎么办)。
-
我们比较原始字段(使用=!进行比较就足够了)。如果它们不相等,我们返回false。
-
我们检查非原始字段是否为 null 并使用equals方法(String类重写了该方法,因此它将正确执行比较)。如果两个字段都为 null,或者equals 返回true,我们将停止检查,并且该方法返回true。
-
我们将哈希码的初始值设置为等于对象的年龄字段的值。
-
我们将当前哈希码乘以 31(以获得更大的值分布),然后添加非原始 String 字段的哈希码(如果它不为空)。
-
我们返回结果。
-
以这种方式重写方法意味着具有相同名称和int值的对象将始终返回相同的哈希码。
103.使用“if(objinstanceofStudent)”和“if(getClass()==obj.getClass())”有什么区别?
让我们看看每个表达式的作用:-
instanceof检查左侧的对象引用是否是右侧类型或其子类型之一的实例。
-
“getClass() == ...”检查类型是否相同。
104. 简要描述clone()方法。
clone ()方法属于Object类。其目的是创建并返回当前对象的克隆(副本)。 要使用此方法,您需要实现Cloneable标记接口:
Student implements Cloneable
并重写clone()方法本身:
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
毕竟,它在Object类中是受保护的,即它只在Student类中可见,而对外部类不可见。
105. 关于对象中的clone()方法和引用变量,需要牢记哪些特殊注意事项?
克隆对象时,仅复制原始值和对象引用的值。这意味着,如果一个对象有一个引用另一个对象的字段,那么只有该引用将被克隆——其他引用的对象将不会被克隆。这就是所谓的浅拷贝。那么,如果您需要一个完整的副本,其中每个嵌套对象都被克隆,该怎么办?如何确保这些不仅仅是引用的副本,而是占用堆中不同内存地址的不同对象的完整副本?实际上,这一切都非常简单——对于内部引用的每个类,您需要重写clone()方法并添加Cloneable标记接口。一旦执行此操作,克隆操作将不会复制对现有对象的引用,而是复制引用的对象,因为现在它们也具有复制自身的能力。例外情况
106. 错误和异常有什么区别?
异常和错误都是Throwable的子类。然而,它们也有不同之处。该错误表明主要是由于系统资源不足而发生的问题。我们的应用程序不应该看到这些类型的问题。这些错误的示例包括系统崩溃和内存不足错误。错误大多发生在运行时,因为它们未经检查。 异常是运行时和编译时可能发生的问题。这些问题通常出现在我们作为开发人员编写的代码中。这意味着这些例外情况更容易预测,也更依赖于我们。相比之下,错误更加随机,更加独立于我们。相反,它们取决于我们的应用程序运行所在的系统中的问题。107. 检查、非检查、异常、抛出和抛出之间有什么区别?
正如我之前所说,异常是开发人员编写的代码中发生的运行时或编译时错误(由于某些异常情况)。 检查是我们所说的异常,方法必须始终使用try-catch机制处理或重新抛出到调用方法。throws关键字在方法头中使用,指示该方法可能抛出的异常。换句话说,它为我们提供了一种向调用方法抛出异常的机制。 未经检查的异常不需要处理。它们往往难以预测,而且发生的可能性也较小。也就是说,如果您愿意,您可以处理它们。我们在手动抛出异常时使用throw,例如:
throw new Exception();
108.什么是异常层次结构?
异常层次结构非常广泛。这里有太多的东西无法充分描述。因此,我们只考虑它的关键分支: 在这里,在层次结构的最顶层,我们看到Throwable类,它是异常层次结构的总祖先,依次分为:- 错误——严重的、未经检查的问题。
- 异常——可以检查的异常。
109.什么是检查异常和非检查异常?
正如我之前所说:-
检查异常是您必须以某种方式处理的异常。也就是说,您必须在try-catch块中处理它们,或者将它们扔给上面的方法。为此,在方法签名中列出方法参数后,使用throws <异常类型>来指示该方法可以引发该异常。这有点像警告,通知调用方法它必须承担处理该异常的责任。
-
未经检查的异常不需要处理,因为它们在编译时不会被检查,并且通常更难以预测。它们与检查异常的主要区别在于,通过使用try-catch块或重新抛出来处理它们是可选的,而不是强制的。
101. 编写一个使用 try-catch 块来捕获和处理异常的示例。
try{ // Start of the try-catch block
throw new Exception(); // Manually throw an exception
} catch (Exception e) { // Exceptions of this type and its subtypes will be caught
System.out.println("Oops! Something went wrong =("); // Display the exception
}
102. 编写一个捕获并处理您自己的自定义异常的示例。
首先,让我们编写自己的异常类,它继承Exception并重写其以错误消息作为参数的构造函数:
public class CustomException extends Exception {
public CustomException(final String message) {
super(message);
}
}
接下来,我们将手动抛出一个并捕获它,就像我们在上一个问题的示例中所做的那样:
try{
throw new CustomException("Oops! Something went wrong =(");
} catch (CustomException e) {
System.out.println(e.getMessage());
}
再次,当我们运行代码时,我们得到以下输出:
GO TO FULL VERSION