java基础高频面试题2
"==" 和 "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()方法,需要遵循以下几个规则:
- 自反性:对于任何非null的引用值x,x.equals(x)应返回true。
- 对称性:对于任何非null的引用值x和y,如果x.equals(y)返回true,则y.equals(x)也应返回true。
- 传递性:对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,则x.equals(z)也应返回true。
- 一致性:对于任何非null的引用值x和y,如果用于比较的对象信息没有被修改,则x.equals(y)的多次调用应始终返回相同的结果。
- 非空性:对于任何非null的引用值x,x.equals(null)应返回false。
"equals" 和hashcode的关系
当我们在重写equals()方法时,通常也需要重写hashCode()方法。这是因为我们在使用基于散列的数据结构,比如如哈希表,以及一些集合类(如HashSet、HashMap)时,hashCode()方法的返回值将被用作对象的索引。
当我们重写equals()方法时,经常选择使用对象的一些属性进行比较,以确定两个对象是否相等。在这种情况下,为了保持一致性,我们需要使用相同的属性来计算hashCode()值。
如果我们没有重写hashCode()方法,那么hashCode()的默认行为是使用对象的内存地址计算哈希码,这与equals()方法的比较内容无关。这将导致具有相等内容的对象,通过hashCode()方法计算出来的哈希码可能不相等,这将导致在存储和查找对象时出现问题。
所以,正确地重写hashCode()方法是很重要的。以下是一些在重写hashCode()方法时需要注意的规则:
- 一致性:在对象的生命周期中,只要对象的属性没有发生改变,那么hashCode()应始终返回相同的值。
- 相等性:如果两个对象根据equals()方法比较相等,那么它们的hashCode()方法应返回相同的值。
- 分布均匀性:尽量避免不同对象返回相同的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语言中提供了一些列的异常处理关键字和处理模板。
- 抛出异常,使用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应用程序的安全、高效执行。
他们之间的关系如下:
- JDK包含JRE,因为在开发Java应用程序时需要运行Java程序来进行测试和调试。
- JRE包含JVM,因为在运行Java应用程序时需要虚拟机来解释和执行Java字节码。