1,为什么重写 equals 还要重写 hashcode
在 Java 中,`equals` 方法用于比较两个对象的内容是否相等,而 `hashCode` 方法用于获取对象的哈希码值。
这两者之间的关系是这样的:
1. 如果两个对象相等(根据 `equals` 方法的定义),那么它们的哈希码值一定相等。
这是因为如果两个对象在 `equals` 方法返回 `true` 的情况下不具有相等的哈希码,那么它们将无法正确地被放入哈希表(如 `HashMap`、`HashSet` 等)中,这将导致在查找、插入等操作时出现错误。
2. 然而,如果两个对象的哈希码相等,并不意味着它们一定相等(根据 `equals` 方法的定义)。
这是因为哈希码可能会发生冲突,即不同的对象具有相同的哈希码。在这种情况下,哈希表会使用额外的机制来区分这些具有相同哈希码的对象。
因此,当你重写了 `equals` 方法以确保对象内容的比较是正确的时,你也应该重写 `hashCode` 方法,以保证上述两条规则得到遵守。
如果你只重写了 `equals` 而没有重写 `hashCode`,那么在将这些对象放入哈希表中时,可能会导致无法正确地找到或比较它们。
总的来说,重写 `equals` 和 `hashCode` 是为了确保对象在使用哈希表等数据结构时能够正确地工作。
示例代码:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return name.equals(person.name);
}
public static void main(String[] args) {
Person p1 = new Person("John");
Person p2 = new Person("John");
System.out.println(p1.equals(p2)); // 输出 true
}
}
在上述示例中,我们创建了一个 Person
类,它包含了一个属性 name
,并重写了 equals
方法,用于比较两个 Person
对象的内容。
在 main
方法中,我们创建了两个 Person
对象 p1
和 p2
,它们的 name
属性都是相等的。
由于我们重写了 equals
方法,p1.equals(p2)
返回 true
,表示这两个对象的内容相等。
但是,如果我们不重写 hashCode
方法,这两个对象的哈希码可能会不同,导致它们无法正确地存储在哈希表中,从而在需要使用哈希表的数据结构中可能会出现问题。
因此,为了保证对象能够正确地在哈希表等数据结构中工作,需要同时重写 equals
和 hashCode
方法。
public
表示这是一个公共方法,可以在其他类中调用。boolean
表示这个方法会返回一个布尔值,用于指示两个对象是否相等。equals
是方法的名称。(Object o)
是方法的参数列表,它接受一个类型为Object
的参数o
。这里的Object
是所有类的父类,因此这个方法可以接受任何类型的对象作为参数。-
这行代码出现在 Java 中的 `equals` 方法中,用于进行相等性比较。
具体来说:
- `o == null`: 这个条件检查了传入的对象 `o` 是否为 `null`。如果 `o` 为 `null`,那么显然当前对象和 `o` 不可能相等,所以直接返回 `false`。
- `getClass() != o.getClass()`: 这个条件检查了当前对象和传入的对象 `o` 是否属于不同的类。也就是说,它们的类型不同。这是为了确保在比较两个不同类型的对象时,直接返回 `false`,因为不同类型的对象无法相等。
如果上述两个条件中的任何一个为 `true`,那么 `equals` 方法会直接返回 `false`,表示这两个对象不相等。
这两个条件的目的是在进行相等性比较之前,先进行一些简单的快速检查,以提高程序的效率和性能。如果这些条件不满足,才会进一步比较对象的内容。
-
(Person) o
: 这是一个类型转换操作,将对象o
转换为Person
类型。这是因为equals
方法的参数类型是Object
,而我们希望在方法中比较Person
对象的属性。 -
Person person = ...
: 将转换后的Person
对象赋值给名为person
的变量。
这行代码的目的是将传入的对象 o
转换为 Person
类型,以便在后续的代码中可以比较 Person
对象的属性,从而确定它们是否相等。在进行类型转换时,需要确保 o
实际上是一个 Person
类型的对象,否则会抛出 ClassCastException
异常。
2,介绍 Java 的集合类
Java 的集合类是一组用于存储、操作和处理一组对象的框架。它们提供了各种实用的数据结构和算法,使得在处理数据时更加高效和方便。以下是一些常用的 Java 集合类:
1. **List(列表):**
- **ArrayList:** 基于动态数组实现,支持快速随机访问和动态扩展。
- **LinkedList:** 基于双向链表实现,适合在列表中间插入和删除元素。
- **Vector:** 与 ArrayList 类似,但是是线程安全的,相对较慢。2. **Set(集合):**
- **HashSet:** 基于哈希表实现,不保证元素的顺序。
- **LinkedHashSet:** 继承自 HashSet,但保留了元素的插入顺序。
- **TreeSet:** 基于红黑树实现,保证了元素的有序性。3. **Map(映射):**
- **HashMap:** 基于哈希表实现,提供了快速的查找操作。
- **LinkedHashMap:** 继承自 HashMap,保留了元素的插入顺序。
- **TreeMap:** 基于红黑树实现,保证了键的有序性。4. **Queue(队列):**
- **LinkedList:** 既可以作为列表又可以作为队列使用。
- **PriorityQueue:** 基于优先级堆实现,可以按优先级获取元素。5. **Deque(双端队列):**
- **ArrayDeque:** 基于动态数组实现,可以在两端进行高效的插入和删除操作。6. **Stack(栈):**
- **Stack:** 基于向量实现,提供了经典的栈操作。7. **Collections(集合工具类):**
- `java.util.Collections` 提供了许多静态方法,可以对集合进行操作,如排序、查找等。8. **Iterator(迭代器):**
- `java.util.Iterator` 接口允许在集合中迭代访问元素。9. **Enumeration(枚举):**
- `java.util.Enumeration` 接口也是在集合中迭代访问元素的一种方式。
这些集合类都位于 `java.util` 包中。每种集合类都有其自身的特性和适用场景,你可以根据具体需求选择合适的集合类来存储和处理数据。同时,Java 也提供了泛型来增强集合的类型安全性。
3,final 关键字的作用
常量: 在声明变量时,使用 final
关键字可以将变量声明为常量,这意味着一旦赋予初始值,就不能再修改该值。常量通常使用大写字母命名,例如:
final int MAX_VALUE = 100;
方法: 在方法声明中,使用 final
关键字表示该方法不能被子类重写(覆盖)。这是一种限制继承类修改或扩展方法行为的方式。
public final void doSomething() {
// 方法实现
}
类: 在类的声明中,使用 final
关键字表示该类不能被继承。这是为了防止其他类继承或扩展这个类,通常用于表示一个类的设计已经完成,不允许进一步的修改。
final class MyFinalClass {
// 类的成员和方法
}
变量参数(局部变量): 在方法的参数列表中,使用 final
关键字表示该参数是不可修改的局部变量。这可以帮助提高代码的可读性和安全性。
public void process(final int number) {
// number 参数在此方法中是不可修改的
}
为啥有时会出现 4.0 - 3.6 = 0.40000001 这种现象?
使用 final
关键字可以提供代码的可读性、安全性和稳定性,确保变量或方法不会在不希望被修改的情况下被修改。在一些情况下,它还可以优化代码的性能,因为编译器可以根据 final
的特性进行一些优化。
这是由于浮点数精度问题导致的。在计算机内部,浮点数采用二进制表示,而有些十进制数无法完全精确地用二进制表示。这就导致了一些舍入误差。
例如,对于十进制的 4.0
和 3.6
,在二进制中可能会变成类似 4.00000000000000000000000001
和 3.59999999999999999999999999
。当你对这两个数字进行减法运算时,就会得到一个非常接近但不完全相等的结果,如 0.40000000000000000000000002
,而在显示时可能会被简化为 0.40000001
。
这种现象在计算机科学中被称为浮点数精度问题,是由于计算机内部使用二进制表示浮点数而引起的。在处理需要高精度的十进制数值时,建议使用特定的高精度数值类型(例如 BigDecimal
类),而不是基本的浮点数类型(如 float
或 double
)来避免精度问题。
ArrayList 和 LinkedList 的区别
`ArrayList` 和 `LinkedList` 是 Java 中两种不同类型的列表实现,它们在内部数据结构和操作上有一些显著的区别。
### ArrayList:
1. **内部实现:**
- 基于动态数组实现。它使用一个数组来存储元素,可以动态扩展数组的大小。
2. **随机访问:**
- 支持高效的随机访问,因为它可以通过索引直接访问元素。
3. **插入和删除:**
- 在列表末尾进行插入和删除操作效率很高,因为它不需要移动其他元素。但在列表中间或开头进行插入和删除操作会比较慢,因为需要移动后续的元素。
4. **空间占用:**
- 由于它是基于数组实现的,所以会在一开始就分配一块连续的内存空间。
### LinkedList:
1. **内部实现:**
- 基于双向链表实现。每个元素(节点)都包含一个指向前一个元素和后一个元素的引用。
2. **随机访问:**
- 不支持高效的随机访问,因为要访问某个元素,必须从头节点开始顺序查找。
3. **插入和删除:**
- 在列表中间或开头进行插入和删除操作非常高效,因为只需要调整相邻节点的引用。但在列表末尾进行插入和删除操作会比较慢,因为需要从头开始遍历找到末尾节点。
4. **空间占用:**
- 由于它是基于链表实现的,所以不需要一开始就分配一块连续的内存空间。
### 如何选择:
- 如果你需要经常进行随机访问(按索引获取元素),并且对于中间或末尾位置的插入和删除操作并不频繁,那么 `ArrayList` 可能是一个更好的选择。
- 如果你需要经常在列表中间或开头进行插入和删除操作,而随机访问并不是主要需求,那么 `LinkedList` 可能更合适。
总的来说,选择使用哪种列表取决于你的具体需求和使用模式。如果你的应用程序需要在不同位置频繁插入和删除元素,那么 `LinkedList` 可能更适合。如果你主要需要按索引访问元素,那么 `ArrayList` 可能是一个更好的选择。
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;public class ListExample {
public static void main(String[] args) {
// 使用 ArrayList
List<String> arrayList = new ArrayList<>();
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add("Cherry");System.out.println("ArrayList:");
for (String fruit : arrayList) {
System.out.println(fruit);
}// 使用 LinkedList
List<String> linkedList = new LinkedList<>();
linkedList.add("Orange");
linkedList.add("Grapes");
linkedList.add("Mango");System.out.println("\nLinkedList:");
for (String fruit : linkedList) {
System.out.println(fruit);
}
}
}
在上面的代码中,我们首先创建了一个 ArrayList
和一个 LinkedList
。然后分别向它们中添加了一些水果。最后,我们使用循环遍历每个列表并打印出其中的元素。