跳至主要內容

java基础高频面试题2

fangzhipeng约 2885 字大约 10 分钟

"==" 和 "equals" 的区别

"==" 和 "equals" 都可以用于比较 Java 中的对象,但是它们之间有一些区别。

关系操作符号“==”

基本数据类型

在Java中有八种基本数据类型:

  • 浮点型:float(4 byte), double(8 byte)
  • 整型:byte(1 byte), short(2 byte), int(4 byte) , long(8 byte)
  • 字符型: char(2 byte)
  • 布尔型: boolean(JVM规范没有明确规定其所占的空间大小,仅规定其只能够取字面值”true”和”false”)

对于基本数据类型的变量,变量直接存储的是“值”。因此,在使用关系操作符 “== ”来进行比较时,比较的就是“值”本身。要注意的是,浮点型和整型都是有符号类型的(最高位仅用于表示正负,不参与计算【以 byte 为例,其范围为 -2^7 ~ 2^7 - 1,-0即-128】),而char是无符号类型的(所有位均参与计算,所以char类型取值范围为0~2^16-1)。

举个例子:

int a = 5;
int b = 5;
System.out.println(a == b); // true

引用类型变量

在Java中,引用类型的变量存储的并不是“值”本身,而是与其关联的对象在内存中的地址。如果使用== 操作符去判断引用类型的变量,则比较的内容是判断两个变量的引用是否相等,即它们是否指向同一个对象。如果两个变量的引用指向同一个对象,那么它们之间的 == 比较结果为 true。如果两个引用指向不同的对象,那么它们之间的 ==比较结果为 false。例如:

String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");

System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // false

在上述示例中,str1 和 str2 都指向常量池中的同一个字符串对象,因此它们之间的 == 比较结果为 true。而 str3 则指向一个新创建的字符串对象,所以它们之间的 == 比较结果为 false。

"equals"

在初学Java的时候,很多人会说在比较对象的时候,“==”操作符是比较两个变量的内存地址,equals()是比较对象的内容,其实不然。

在Object类中,equals()方法是比较两个对象的内存地址是否相等,代码如下:

public boolean equals(Object obj){
    return (this == obj);
}

为什么会有人把equals方法当做是比较两个内容的比较呢?是因为在String、Double等封装类中,已经重写了(ov了Object类的equals()方法。比如在String内中,它比较的是对字符串内容的比较:

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

在String类的方法中,先比较的是字符串地址是否相等,如果相等,则字符串内容肯定是相等;然后地址不相等,则比较字符串内容是否相等。

在实际的开发中,我们对对象内容的比较,通常都需要重写equals()方法。当我们重写equals()方法,需要遵循以下几个规则:

  1. 自反性:对于任何非null的引用值x,x.equals(x)应返回true。
  2. 对称性:对于任何非null的引用值x和y,如果x.equals(y)返回true,则y.equals(x)也应返回true。
  3. 传递性:对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,则x.equals(z)也应返回true。
  4. 一致性:对于任何非null的引用值x和y,如果用于比较的对象信息没有被修改,则x.equals(y)的多次调用应始终返回相同的结果。
  5. 非空性:对于任何非null的引用值x,x.equals(null)应返回false。

"equals" 和hashcode的关系

当我们在重写equals()方法时,通常也需要重写hashCode()方法。这是因为我们在使用基于散列的数据结构,比如如哈希表,以及一些集合类(如HashSet、HashMap)时,hashCode()方法的返回值将被用作对象的索引。

当我们重写equals()方法时,经常选择使用对象的一些属性进行比较,以确定两个对象是否相等。在这种情况下,为了保持一致性,我们需要使用相同的属性来计算hashCode()值。

如果我们没有重写hashCode()方法,那么hashCode()的默认行为是使用对象的内存地址计算哈希码,这与equals()方法的比较内容无关。这将导致具有相等内容的对象,通过hashCode()方法计算出来的哈希码可能不相等,这将导致在存储和查找对象时出现问题。

所以,正确地重写hashCode()方法是很重要的。以下是一些在重写hashCode()方法时需要注意的规则:

  1. 一致性:在对象的生命周期中,只要对象的属性没有发生改变,那么hashCode()应始终返回相同的值。
  2. 相等性:如果两个对象根据equals()方法比较相等,那么它们的hashCode()方法应返回相同的值。
  3. 分布均匀性:尽量避免不同对象返回相同的hashCode()值,以减少哈希冲突的概率,提高散列存储结构的性能。

一种常用的方式来重写hashCode()方法是,根据对象的每个属性计算一个哈希码,然后将这些哈希码组合在一起,以获得最终的哈希码。这可以通过使用乘法和加法等算法来实现。如果某个属性可以为null,则需要特殊处理,以避免空指针异常。

比如在String类中,hashCode()方法的定义如下:

 public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

java中的异常处理机制

Java中的异常处理机制旨在捕获和处理程序中可能发生的异常情况,以确保程序的正常执行。

异常类层次结构

在Java中,Exception、Error和Throwable是一个类层次结构中的相关类。

          <<class>>      
          Throwable           
          ----------------      
          |              |      
    <<class>>     <<class>>       
   Exception     Error         
          |
  ------------------             
  |                |
checked    unchecked

Throwable是所有异常类的根类,它位于异常类的最顶层。它定义了可以被抛出和捕获的异常对象的基本功能。Throwable有两个重要的子类:Exception和Error。

  • Exception是可控制的异常类,通常表示程序中可预见的异常情况,可以被捕获并进行相应处理。它是Throwable的一个子类。

    • Exception又分为两种类型:checked异常和unchecked异常。checked异常需要在代码中进行处理或声明,否则编译器会报错;unchecked异常不要求在代码中进行处理或声明。
    • 自定义异常:Java允许开发者自定义异常类,以便更好地抽象和组织程序中可能发生的异常情况。自定义异常类通常继承自Exception或RuntimeException类
  • Error是不可控制的严重问题的异常类,通常表示系统的严重错误或问题,大多数情况下不会被程序显式地捕获和处理,而是由Java运行时环境(JVM)来处理。Error也是Throwable的一个子类。

异常处理语句

Java语言中提供了一些列的异常处理关键字和处理模板。

image-20231121231235887
  • 抛出异常,使用throw语句。可以使用throw语句在程序中主动抛出异常,从而触发异常处理机制。
  • 声明异常:使用throws关键字,在方法签名中明确指定可能抛出的异常类型,这样调用者就能清楚地知道需要处理哪些异常。
  • 捕获异常,常使用try-catch-finally异常处理语句模板
    • try块:try块用于包裹可能发生异常的代码片段。当try块中的代码发生异常时,异常将被抛出,程序流程将跳转到catch块或finally块。
    • catch块:catch块用于捕获并处理指定类型的异常。catch块可以捕获多个异常类型,并按照顺序处理异常。
    • finally块:finally块用于定义无论是否发生异常都需要执行的代码,例如释放资源。finally块在try块或catch块执行完毕后执行。

举个例子,演示了try-catch-finally的用法:

public class ExceptionExample {
    public static void main(String[] args) {
        try {
         
            int result = divide(10, 0);
            System.out.println("结果:" + result);
        } catch (ArithmeticException e) {
         
            System.out.println("除零错误:" + e.getMessage());
        } finally {
        
            System.out.println("执行finally块");
        }
    }
    
    public static int divide(int num1, int num2) {
        return num1 / num2;
    }
}

在上述代码中,定义了一个divide方法,用于计算两个数的除法操作。由于除数为0会产生算术异常(ArithmeticException),我们使用try-catch语句对可能发生异常的代码块进行了包裹。

  • 在try代码块中,我们调用了divide方法并将结果存储在result变量中。

  • 如果在此过程中发生了异常,程序会立即跳转到catch代码块中,并执行与异常类型匹配的处理代码。在

  • 无论是否发生异常,finally代码块中的代码始终会执行。

执行上述代码,输出如下:

除零错误:/ by zero
执行finally块

Java的异常处理机制是Java程序设计中非常重要的一个方面,它能够有效地处理程序中可能发生的异常情况,并提供了良好的可读性和可维护性。通过合理地使用try-catch-finally块,对异常进行合适的处理,防止程序因为出现异常而奔溃,是程序的稳定性和健壮性的一种保护机制。

JDK、JRE、JVM的区别和联系

JDK(Java Development Kit)是Java开发工具包,提供了一套完整的开发工具,包括编译器(javac)、调试器(jdb)、打包工具(jar)等。使用JDK可以进行Java应用程序的开发和构建,生成可执行的Java应用程序。JDK还提供了各种开发文档和示例代码,帮助开发者学习和使用Java。

JRE(Java Runtime Environment)是Java运行时环境,是在目标机器上运行Java应用程序所需的最小环境。它包含了JVM(Java Virtual Machine)和Java类库。当用户在目标机器上运行Java应用程序时,需要先安装JRE。JRE只提供了Java应用程序的运行环境,不包含开发工具。

JVM(Java Virtual Machine)是Java虚拟机,是Java平台的核心部分。它是一个抽象的计算机,可以在不同的操作系统上运行Java程序,实现了Java的跨平台特性。JVM解析Java字节码,并将其转换为底层操作系统可以执行的机器码。它还具有内存管理、垃圾回收等功能,确保Java应用程序的安全、高效执行。

他们之间的关系如下:

image-20231121233545642
  • JDK包含JRE,因为在开发Java应用程序时需要运行Java程序来进行测试和调试。
  • JRE包含JVM,因为在运行Java应用程序时需要虚拟机来解释和执行Java字节码。
方志朋_官方公众号
方志朋_官方公众号