目录
1.int a = 1, int b = 1, Integer c = 1, Integer d = 1;四个区别和联系,以及c和d是同一个吗?
2.为什么重写HashCode必须重写euqals,两者之间的关系?
3.创建对象的方式有哪些
4.重写和重载的区别
5.抽象类和接口的区别
6.String的不可变原则
7.浅拷贝和深拷贝
本专栏全是博主自己收集的面试题,仅可参考,不能相信面试官就出这种题目。
1.int a = 1, int b = 1, Integer c = 1, Integer d = 1;四个区别和联系,以及c和d是同一个吗?
a
和 b
是基本数据类型 int
的 变量,它们直接存储在栈(stack)上,并且每个变量都有自己的存储空间。
c
和 d
是 Integer
类型的 对象,它们存储在堆(heap)上,。多个 Integer
对象被创建并且它们的值相同,Java 可能会使它们引用相同的对象,从而节省内存。因此c
和 d
可能会引用相同的 Integer
对象。所以可能是同一个
联系:c
和 d
都是通过自动装箱从基本数据类型 int
转换而来的 Integer
对象。
区别:a、b是变量,存储在栈上;c和d是对象,是Integer类的实例,可以调用方法。
2.为什么重写HashCode必须重写euqals,两者之间的关系?
解答问题之前,回忆一下,什么是HashCode和equals,及其作用
HashCode是在Java中用于获取对象的唯一标识符的方法。它是根据对象的内容生成的一个整数值。对象的hashCode()方法被调用时,它返回的是对象内存地址的哈希码。哈希码可以用于在哈希表等数据结构中快速定位对象。
注意点:
- 每一次运行对象内容即使一致,但hash值不一定一致
- 运行过程里,new的两个对象,内容相等 ≠ 哈希码相等
- 不同的对象可能会生成相同的哈希码,概率极低
那么为什么需要重写hashcode呢?
我也不太清楚,但是重写hashcode可以让两个内容相同的对象拥有同一个哈希码!
例子:
public class Student {
int age;
String sex;
String name;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.age;
result = prime * result + (this.sex == null ? 0 : this.sex.hashCode());
result = prime * result + (this.name == null ? 0 : this.name.hashCode());
return result;
}
在回忆一下equals,在Java中,equals()
方法被设计用来比较对象的用于比较对象的内容或状态是否相等!
因此,疑问出来了,既然equals()方法比较的是内容或状态,那么是否hashCode()值相等,两个对象通过
equals()
方法被认为是相等吗?
解析:
在 Java 中,当我们重写 hashCode()
方法时,通常需要同时重写 equals()
方法,这是为了维护 hashCode()
和 equals()
方法之间的协定,确保对象在集合(如哈希表)中正确地工作。
-
hashCode() 值相等:
- 如果两个对象的
hashCode()
值相等,那么根据 Java 的约定,这是equals()
方法返回true
的必要条件。也就是说,如果两个对象通过equals()
方法相等,它们的hashCode()
值必须相等。
- 如果两个对象的
-
hashCode() 值不相等:
- 如果两个对象的
hashCode()
值不相等,那么按照 Java 的规定,这两个对象不应该通过equals()
方法返回true
。即使equals()
方法返回true
,也会违反hashCode()
方法的契约,这可能导致在使用哈希集合(如HashMap
、HashSet
等)时出现不一致的行为,甚至会导致数据结构的错误操作。
- 如果两个对象的
总结:HashCode()方法,HashCode方法通过内容一致得出相同的哈希码
equals()方法,通过内容判断对象是否相同。
概述:一个类有三个属性,原本只通过一个属性重写了hashcode()和equals()方法,但是我将hashcode()方法,通过2个属性得出该对象的哈希值,可equals()方法还是根据一个属性判断,这并不安全,也会出现bug。
3.创建对象的方式有哪些
一般有5种:
第一种:常见的使用构造方法new对象
第二种:使用工厂设计模型,根据你需要的,自动创建出来。
// 定义汽车类
class Car {
private String model;
private int year;
public Car(String model, int year) {
this.model = model;
this.year = year;
}
// 省略 getter 和 setter 方法
}
// 定义汽车工厂类
class CarFactory {
// 工厂方法,根据型号和年份创建汽车对象
public static Car createCar(String model, int year) {
//----- 判断条件 -----
return new Car(model, year);
}
}
// 在主程序中使用工厂方法创建对象
public class Main {
public static void main(String[] args) {
// 使用工厂方法创建汽车对象
Car myCar = CarFactory.createCar("Toyota Camry", 2023);
}
}
第三种:克隆
克隆又分两种,一种浅克隆、一种深克隆,使用接口Cloneable和方法clone()
区别在于一个类中如果含有其他类,那么浅克隆,只能克隆表面,包含的类无法克隆,反之,深克隆可以做到!
class Teacher implements Cloneable{
private String name;
private int age;
private Student student;
}
public class Test1 {
public static void Shallow_cloning(){
Teacher teacher = new Teacher();
teacher.setAge(19);
teacher.setName("李华");
Student student = new Student();
student.setName("喜喜");
teacher.setStudent(student);
Teacher teacher1 = teacher.clone();
}
第四种:通过反射
反射机制允许在运行时检查类的信息,并动态地创建类的对象,可以调用类的构造方法来实现对象的创建
Class<?> clazz = Class.forName("指定的被反射的类");
类名 obj = (类名) clazz.getDeclaredConstructor().newInstance();
第五种:反序列化
通过反序列化可以从存储设备(如文件、数据库)中读取对象的字节流,并将其转换回对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream("文件名"));
MyClass obj = (MyClass) in.readObject();
4.重写和重载的区别
重写:在面向对象的继承中,子类可以通过重写父类的方法来实现自己的版本。重写指的是子类定义了一个与父类中相同名称和参数列表(签名)的方法,并且返回类型和抛出的异常类型也必须与父类中的方法一致。重写的方法需要加上 @Override
注解(可选),这样可以让编译器帮助检查是否正确地重写了父类的方法
重载:重载是指在同一个类中,可以定义多个方法名相同但参数列表不同(包括参数类型、参数个数或参数顺序)的方法。重载的方法彼此之间的签名必须不同,返回类型可以不同,但通常情况下只有返回类型不同是不够的。
总结:
- 重写发生在子类继承父类的过程中,用于实现多态性,要求方法签名相同。
- 重载发生在同一个类中,用于提供多个同名方法以应对不同的参数情况,要求方法签名不同。
5.抽象类和接口的区别
抽象类:不能被实例化,都是被继承使用,可以有抽象方法(没有具体实现的方法),也可有具体的实现方法,可以有构造方法,被子类调用。
接口:定义了一组没有具体实现的方法,一个类可以实现多个接口,
- 接口中的方法默认是
public abstract
的,属性默认是public static final
的(Java 8 之后接口中可以有默认方法和静态方法)。 - 类通过
implements
关键字来实现接口,并提供接口中定义的所有方法的具体实现。
6.String的不可变原则
在Java中,String
类被设计为不可变的。有人说,我一开始让String类型指向"abc",然后再指向"wer" 不是发生了改变吗?
不不不,实际上是创建了一个新的字符串对象,而不是修改原来的对象。旧的字符串对象将被Java的垃圾回收机制清理掉。
为什么是不可变的呢?
线程安全角度:字符串不可变性确保了字符串对象在多线程环境下是安全的,不需要额外的同步操作。因为字符串一旦创建,它的值不会改变,所以不会出现多个线程同时修改一个字符串对象的情况。
哈希值:字符串被广泛用作 HashMap
和 HashSet
的键。由于字符串不可变,可以安全地缓存它们的哈希值,提高了哈希表的性能。
安全敏感操作:在安全敏感的环境中,不可变字符串确保了关键信息(如密码)不会被意外修改。
或许有人说,如果需要频繁更改,岂不是很麻烦!
不不不,因为字符串池(String Pool)存在。字符串池是一种特殊的内存区域,用于存储字面量字符串,以便多个字符串引用可以共享相同的实例,从而节省内存。
7.浅拷贝和深拷贝
之前我们在描述创建对象的时候,以及提及到了浅拷贝和深拷贝
拷贝:将一组数据原封不动的转移到另容器。
例如:字符串拷贝和数组拷贝
String original = "Hello";
String copied = new String(original);
String original = "Hello";
String copied = original.substring(0); // 从索引0开始,复制到字符串末尾
int[] original = {1, 2, 3, 4, 5};
int[] copied = original.clone(); //用数组的 clone() 方法
但是对象也是一组数据,因此也可以拷贝
浅拷贝:通过对象实现了 Cloneable
接口,重写clone()方法
class Teacher2 implements Cloneable{
private String name;
private int age;
@Override
protected Teacher2 clone(){
Teacher2 t1 = null;
try {
t1 = (Teacher2) super.clone();
// 克隆引用类型
t1.setStudent(t1.getStudent().clone());
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
return t1;
}
}
public static void Shallow_cloning(){
Teacher2 teacher = new Teacher();
teacher.setAge(19);
teacher.setName("李华");
//克隆
Teacher2 teacher1 = teacher.clone();
}
而所谓的深克隆就是针对一种类的有其他对象的存在,例如:
class Teacher implements Cloneable{
private String name;
private int age;
private Student student; //存在一个对象Student
}
这种情况下,即使克隆,其中的student还是原来的,只是共享了数据。
解决方法有三种:
- 所有引用属性都实现克隆,整个对象就变成了深克隆。
- 使用 JDK 自带的字节流序列化和反序列化对象实现深克隆。(略)
- 使用第三方工具实现深克隆,比如 Apache Commons Lang 库。(略)
引用属性都实现克隆方法:
public class CloneDemo {
public static void main(String[] args) {
Person p1 = new Person();
p1.setName("张三");
p1.setAge(18);
// 引用类型
Address address = new Address();
address.setCity("北京");
p1.setAddress(address);
// 克隆 p1 对象
Person p2 = p1.clone();
// 对比引用类型的地址值是否相同
System.out.println(p1.getAddress() == p2.getAddress()); // false
}
}
@Getter
@Setter
class Person implements Cloneable {
private String name;
private int age;
private Address address; // 引用类型
@Override
public Person clone() {
Person person = null;
try {
person = (Person) super.clone();
// 克隆引用类型
person.setAddress(person.getAddress().clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return person;
}
}
@Getter
@Setter
class Address implements Cloneable {
private String city;
@Override
public Address clone() {
Address address = null;
try {
address = (Address) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return address;
}
}