一、小林-Java基础-面向对象
1、 接口可以包含构造函数吗?
在接口中,不可以有构造方法,在接口里写入构造方法时,编译器提示:Interfaces cannot have constructors,因为接口不会有自己的实例的,所以不需要有构造函数。
为什么呢?构造函数就是初始化class的属性或者方法,在new的一瞬间自动调用,那么问题来了Java的接口,都不能new 那么要构造函数干嘛呢?根本就没法调用
2、解释Java中的静态变量和静态方法
在Java中,静态变量和静态方法是与类本身关联的,而不是与类的实例(对象)关联。它们在内存中只存在一份,可以被类的所有实例共享。
静态变量
静态变量(也称为类变量)是在类中使用static关键字声明的变量。它们属于类而不是任何具体的对象。主要的特点:
- 共享性:所有该类的实例共享同一个静态变量。如果一个实例修改了静态变量的值,其他实例也会看到这个更改。
- 初始化:静态变量在类被加载时初始化,只会对其进行一次分配内存。
- 访问方式:静态变量可以直接通过类名访问,也可以通过实例访问,但推荐使用类名。
示例:
public class MyClass {
static int staticVar = 0; // 静态变量
public MyClass() {
staticVar++; // 每创建一个对象,静态变量自增
}
public static void printStaticVar() {
System.out.println("Static Var: " + staticVar);
}
}
// 使用示例
MyClass obj1 = new MyClass();
MyClass obj2 = new MyClass();
MyClass.printStaticVar(); // 输出 Static Var: 2
静态方法
静态方法是在类中使用static关键字声明的方法。类似于静态变量,静态方法也属于类,而不是任何具体的对象。主要的特点:
- 无实例依赖:静态方法可以在没有创建类实例的情况下调用。对于静态方法来说,不能直接访问非静态的成员变量或方法,因为静态方法没有上下文的实例。
- 访问静态成员:静态方法可以直接调用其他静态变量和静态方法,但不能直接访问非静态成员。
- 多态性:静态方法不支持重写(Override),但可以被隐藏(Hide)。
public class MyClass {
static int count = 0;
// 静态方法
public static void incrementCount() {
count++;
}
public static void displayCount() {
System.out.println("Count: " + count);
}
}
// 使用示例
MyClass.incrementCount(); // 调用静态方法
MyClass.displayCount(); // 输出 Count: 1
使用场景
- 静态变量:常用于需要在所有对象间共享的数据,如计数器、常量等。
- 静态方法:常用于助手方法(utility methods)、获取类级别的信息或者是没有依赖于实例的数据处理。
3、非静态内部类和静态内部类的区别?
区别包括:
- 非静态内部类依赖于外部类的实例,而静态内部类不依赖于外部类的实例。
- 非静态内部类可以访问外部类的实例变量和方法,而静态内部类只能访问外部类的静态成员。
- 非静态内部类不能定义静态成员,而静态内部类可以定义静态成员。
- 非静态内部类在外部类实例化后才能实例化,而静态内部类可以独立实例化。
- 非静态内部类可以访问外部类的私有成员,而静态内部类不能直接访问外部类的私有成员,需要通过实例化外部类来访问。
4、静态内部类可以直接访问外部方法,编译器是怎么做到的?
非静态内部类可以直接访问外部方法是因为编译器在生成字节码时会为非静态内部类维护一个指向外部类实例的引用。
这个引用使得非静态内部类能够访问外部类的实例变量和方法。编译器会在生成非静态内部类的构造方法时,将外部类实例作为参数传入,并在内部类的实例化过程中建立外部类实例与内部类实例之间的联系,从而实现直接访问外部方法的功能。
5、有一个父类和子类,都有静态的成员变量、静态构造方法和静态方法,在我new一个子类对象的时候,加载顺序是怎么样的?
当你实例化一个子类对象时,静态成员变量、静态构造方法和静态方法的加载顺序遵循以下步骤:
- 在创建子类对象之前,首先会加载父类的静态成员变量和静态代码块(构造方法无法被
static修饰,因此这里是静态代码块)。这个加载是在类首次被加载时进行的,且只会发生一次。 - 接下来,加载子类的静态成员变量和静态代码块。这一过程也只发生一次,即当首次使用子类的相关代码时。
- 之后,执行实例化子类对象的过程。这时会呼叫父类构造方法,然后是子类的构造方法。
具体加载顺序可以简要总结为:
- 父类静态成员变量、静态代码块(如果有)
- 子类静态成员变量、静态代码块(如果有)
- 父类构造方法(实例化对象时)
- 子类构造方法(实例化对象时)
从输出可以看出,在创建 Child 类型对象时,首先执行父类的静态块,然后是子类的静态块,最后才是父类和子类的构造函数。这清晰地展示了加载的顺序。
6、深拷贝和浅拷贝
6.1 深拷贝和浅拷贝的区别?
- 浅拷贝是指只复制对象本身和其内部的值类型字段,但不会复制对象内部的引用类型字段。换句话说,浅拷贝只是创建一个新的对象,然后将原对象的字段值复制到新对象中,但如果原对象内部有引用类型的字段,只是将引用复制到新对象中,两个对象指向的是同一个引用对象。
- 深拷贝是指在复制对象的同时,将对象内部的所有引用类型字段的内容也复制一份,而不是共享引用。换句话说,深拷贝会递归复制对象内部所有引用类型的字段,生成一个全新的对象以及其内部的所有对象。
6.2 实现深拷贝的三种方法?
在 Java 中,实现对象深拷贝的方法有以下几种主要方式:
实现 Cloneable 接口并重写 clone() 方法
这种方法要求对象及其所有引用类型字段都实现 Cloneable 接口,并且重写 clone() 方法。在 clone() 方法中,通过递归克隆引用类型字段来实现深拷贝。
class MyClass implements Cloneable {
private String field1;
private NestedClass nestedObject;
@Override
protected Object clone() throws CloneNotSupportedException {
MyClass cloned = (MyClass) super.clone();
cloned.nestedObject = (NestedClass) nestedObject.clone(); // 深拷贝内部的引用对象
return cloned;
}
}
class NestedClass implements Cloneable {
private int nestedField;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
使用序列化和反序列化
通过将对象序列化为字节流,再从字节流反序列化为对象来实现深拷贝。要求对象及其所有引用类型字段都实现 Serializable 接口。
import java.io.*;
class MyClass implements Serializable {
private String field1;
private NestedClass nestedObject;
public MyClass deepCopy() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
oos.flush();
oos.close();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (MyClass) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
class NestedClass implements Serializable {
private int nestedField;
}
手动递归复制
针对特定对象结构,手动递归复制对象及其引用类型字段。适用于对象结构复杂度不高的情况。
class MyClass {
private String field1;
private NestedClass nestedObject;
public MyClass deepCopy() {
MyClass copy = new MyClass();
copy.setField1(this.field1);
copy.setNestedObject(this.nestedObject.deepCopy());
return copy;
}
}
class NestedClass {
private int nestedField;
public NestedClass deepCopy() {
NestedClass copy = new NestedClass();
copy.setNestedField(this.nestedField);
return copy;
}
}
二、尚硅谷-基础篇
1、 HashCode 冲突
hashCode 是 Java 中的一个native方法,用于返回对象的哈希码值,定义在 Object 类中,因此所有 Java 对象都继承了这个方法。
-
hashCode返回一个int类型的哈希码,用于标识对象的存储位置。 -
默认实现通常基于对象的内存地址。
-
hashCode是Object类的方法,所有 Java 对象都可以调用。 -
为了确保对象在哈希表(如
HashMap、HashSet)中正确工作,通常需要重写hashCode和equals方法。 -
重写时需遵循:如果两个对象通过
equals方法相等,它们的hashCode也必须相同。
只有new很多对象才会出现HashCode冲突。
2、Integer 比较规则
推荐使用Integer.valueOf。