java面试题

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 对象 p1p2,它们的 name 属性都是相等的。

由于我们重写了 equals 方法,p1.equals(p2) 返回 true,表示这两个对象的内容相等。

但是,如果我们不重写 hashCode 方法,这两个对象的哈希码可能会不同,导致它们无法正确地存储在哈希表中,从而在需要使用哈希表的数据结构中可能会出现问题。

因此,为了保证对象能够正确地在哈希表等数据结构中工作,需要同时重写 equalshashCode 方法。

  • 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.03.6,在二进制中可能会变成类似 4.000000000000000000000000013.59999999999999999999999999。当你对这两个数字进行减法运算时,就会得到一个非常接近但不完全相等的结果,如 0.40000000000000000000000002,而在显示时可能会被简化为 0.40000001

这种现象在计算机科学中被称为浮点数精度问题,是由于计算机内部使用二进制表示浮点数而引起的。在处理需要高精度的十进制数值时,建议使用特定的高精度数值类型(例如 BigDecimal 类),而不是基本的浮点数类型(如 floatdouble)来避免精度问题。

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。然后分别向它们中添加了一些水果。最后,我们使用循环遍历每个列表并打印出其中的元素。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值