Java基础面试题&知识点总结(上篇)

2023-10-25 14:27:43 浏览数 (1)

大家好,我是栗筝i,从 2022 年 10 月份开始,我持续梳理出了全面的 Java 技术栈内容,一方面是对自己学习内容进行整合梳理,另一方面是希望对大家有所帮助,使我们一同进步。得到了很多读者的正面反馈。 而在 2023 年 10 月份开始,我将推出 Java 面试题/知识点系列内容,期望对大家有所助益,让我们一起提升。 本篇是对 Java 基础系列的面试题 / 知识点的总结的上篇

1、Java基础面试题问题

Java基础面试题问题(上篇)

  • 问题 1. Object 类在 Java 中是什么样的存在?为何在 Java 中,所有的类都会继承自 Object 类?
  • 问题 2. Object 类有哪些主要的方法?每个方法的作用是什么?
  • 问题 3. Object 类 native 方法和非 native 方法区别是什么?
  • 问题 4. Java 中 == 和 equals 的有什么区别?
  • 问题 5. Java 中为什么重写 equals() 方法后,还必须重写 hashCode()?
  • 问题 6. 什么是深拷贝和浅拷贝?
  • 问题 7. Java 中的 clone() 方法默认是深拷贝还是浅拷贝?
  • 问题 8. 在实现深拷贝时,如果遇到循环引用该如何处理?
  • 问题 9. 在实现深拷贝时,对于数组和集合类应该如何处理?
  • 问题 10. Cloneable 接口在 Java 中的作用是什么?
  • 问题 11. 为什么说 Cloneable 是一个标记接口?
  • 问题 12. 什么是 Java 的序列化和反序列化?
  • 问题 13.Java 中的 Serializable 接口有什么作用?
  • 问题 14. 在 Java 中,如果一个对象的某个字段不想被序列化,应该如何处理?
  • 问题 15. 如何自定义序列化与反序列化过程?
  • 问题 16. 静态字段是否可以被序列化?为什么?
  • 问题 17. 在 Java 中,默认的序列化机制是怎样的?
  • 问题 18. Java 中的基本数据类型有哪些?
  • 问题 19. Java 中的自动装箱和拆箱是什么?
  • 问题 20. 在 Java 中什么是强制类型转换、隐式类型转换、显式类型转换?
  • 问题 21. 为什么 Java 中的字符串不可变?它有什么优势?
  • 问题 22. 什么是 Java 中的字符串池?
  • 问题 23. 简述 String str = "aaa"String str = new String("i") 一样吗 ?
  • 问题 24. Java 中有哪些创建字符串的方式?
  • 问题 25. 介绍一下 String、StringBuffer、StringBuilder 和他们之间的区别?

2、Java基础面试题解答
2.1、JavaObject类相关
  • 问题 1. Object 类在 Java 中是什么样的存在?为何在 Java 中,所有的类都会继承自 Object 类?

解答:Object 类在 Java 中被视为所有类的基础和起点。这是因为在 Java 中,所有的类都默认继承自 Object 类,无论是 Java 内置的类,还是用户自定义的类。这种设计使得所有的 Java 对象都能够调用一些基本的方法,例如 equals(), hashCode(), toString() 等,这些方法都在 Object 类中被定义。

  • 问题 2. Object 类有哪些主要的方法?每个方法的作用是什么?

解答:Object 类中的方法可以分为两类:native 方法和非 native 方法。

非 native 方法是:

  1. equals():判断与其他对象是否相等。
  2. clone():创建并返回此对象的一个副本。
  3. toString():返回该对象的字符串表示。
  4. finalize():当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

native 方法是:

  1. getClass():返回此 Object 的运行时类。
  2. hashCode():返回该对象的哈希码值。
  3. notify():唤醒在此对象监视器上等待的单个线程。
  4. notifyAll():唤醒在此对象监视器上等待的所有线程。
  5. wait(long timeout):在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
  • 问题 3. Object 类 native 方法和非 native 方法区别是什么?

解答:native 方法和非 native 方法的主要区别在于它们的实现方式和运行环境。

  1. native 方法:这些方法在 Java 代码中声明,但是它们的实现是用其他语言(通常是 C 或 C )编写的,并且在 Java 之外的环境中运行。native 方法可以访问系统特有的资源,如硬件设备接口、系统调用等。在 Java 代码中,native 方法通常用关键字 “native” 声明。
  2. 非 native 方法:这些方法完全在 Java 中声明和实现。它们在 Java 虚拟机(JVM)内部运行,并且只能访问由 JVM 提供的资源。非 native 方法不能直接访问系统资源或执行系统调用。
  • 问题 4. Java 中 == 和 equals 的有什么区别?

在 Java 中,==equals() 方法用于比较两个对象,但它们的比较方式和使用场景有所不同。

  1. ==:对于基本数据类型,== 比较的是值是否相等;对于引用类型,== 比较的是两个引用是否指向同一个对象,即它们的地址是否相同。
  2. equals():这是一个方法,不是操作符。它的行为可能会根据它在哪个类中被调用而变化。在 Object 类中,equals() 方法的行为和 == 相同,比较的是引用是否指向同一个对象。但是在一些类(如 String、Integer 等)中,equals() 方法被重写,用于比较两个对象的内容是否相等。因此,如果你想比较两个对象的内容是否相等,应该使用 equals() 方法。
  • 问题 5. Java 中为什么重写 equals() 方法后,还必须重写 hashCode()?

解答:在 Java 中,equals()hashCode() 两个方法是密切相关的。如果你重写了 equals() 方法,那么你也必须重写 hashCode() 方法,以保证两个相等的对象必须有相同的哈希码。这是因为在 Java 集合框架中,特别是哈希表相关的数据结构(如 HashMapHashSet 等)在存储和检索元素时,会使用到对象的 hashCode() 方法。

以下是 Java 中 equals()hashCode() 方法的一般约定:

  1. 如果两个对象相等(即,equals(Object) 方法返回 true),那么调用这两个对象中任一对象的 hashCode() 方法都必须产生相同的整数结果。
  2. 如果两个对象不等(即,equals(Object) 方法返回 false),那么调用这两个对象中任一对象的 hashCode() 方法,不要求必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能提高哈希表的性能。

因此,如果你重写了 equals() 方法但没有重写 hashCode() 方法,可能会导致违反上述的第一条约定,从而影响到哈希表相关数据结构的正确性和性能。


2.2、Java深拷贝浅拷贝相关
  • 问题 6. 什么是深拷贝和浅拷贝?

解答:深拷贝和浅拷贝是编程中常见的两种复制对象的方式,主要区别在于是否复制对象内部的引用对象。

  1. 浅拷贝(Shallow Copy):当进行浅拷贝时,如果对象中的字段是基本类型,会直接复制其值;如果对象中的字段是引用类型,那么只复制其引用,而不复制引用指向的对象。因此,原对象和拷贝对象会共享同一个引用对象。这就意味着,如果其中一个对象改变了这个引用对象的内容,那么另一个对象的这个引用对象的内容也会随之改变。
  2. 深拷贝(Deep Copy):当进行深拷贝时,无论对象中的字段是基本类型还是引用类型,都会创建一个新的副本。对于引用类型,会复制引用指向的对象,而不仅仅是复制引用。因此,原对象和拷贝对象不会共享任何一个引用对象。这就意味着,无论哪一个对象改变了引用对象的内容,都不会影响到另一个对象。

需要注意的是,实现深拷贝可能会比较复杂,特别是当对象的引用结构很复杂时,例如存在循环引用。此外,深拷贝可能会消耗更多的计算和存储资源。

  • 问题 7. Java 中的 clone() 方法默认是深拷贝还是浅拷贝?

解答:在 Java 中,clone() 方法默认进行的是浅拷贝。

这意味着,如果你的对象中包含了对其他对象的引用,那么 clone() 方法只会复制这个引用,而不会复制引用指向的对象。因此,原对象和克隆对象会共享这个引用指向的对象,这就是所谓的浅拷贝。

如果你想实现深拷贝,即完全复制一个新的对象,包括其引用的所有对象,那么你需要重写 clone() 方法,手动复制这些对象。

  • 问题 8. 在实现深拷贝时,如果遇到循环引用该如何处理?

解答:在实现深拷贝时,如果遇到循环引用,需要特别小心,否则可能会导致无限递归,最终导致栈溢出。

处理循环引用的一种常见方法是使用一个哈希表来跟踪已经复制过的对象。具体来说,每当你复制一个对象时,都将原对象和复制的新对象放入哈希表中。然后,在复制一个对象之前,先检查这个对象是否已经在哈希表中。如果已经在哈希表中,那么就直接返回哈希表中的复制对象,而不再进行复制。

以下是一个简单的示例:

代码语言:javascript复制
public class Node {
    public Node next;
    // ...
}

public class DeepCopy {
    private HashMap<Node, Node> visited = new HashMap<>();

    public Node clone(Node node) {
        if (node == null) {
            return null;
        }
        if (visited.containsKey(node)) {
            return visited.get(node);
        }

        Node cloneNode = new Node();
        visited.put(node, cloneNode);

        cloneNode.next = clone(node.next);

        return cloneNode;
    }
}

在这个示例中,DeepCopy 类使用了一个 visited 哈希表来跟踪已经复制过的 Node 对象。在 clone() 方法中,每次复制一个 Node 对象之前,都会先检查这个对象是否已经在 visited 哈希表中。这样就可以避免因为循环引用而导致的无限递归。

  • 问题 9. 在实现深拷贝时,对于数组和集合类应该如何处理?

解答:在实现深拷贝时,对于数组和集合类的处理需要特别注意,因为它们都可能包含引用类型的元素。

数组:如果数组的元素是基本类型,那么可以直接使用 clone() 方法或 System.arraycopy() 方法来复制数组。如果数组的元素是引用类型,那么需要遍历数组,对每个元素进行深拷贝。

代码语言:javascript复制
MyClass[] copy = new MyClass[array.length];
for (int i = 0; i < array.length; i  ) {
    copy[i] = array[i].clone();
}

集合类:对于集合类,如 ArrayListHashSet 等,需要创建一个新的集合,然后遍历原集合,对每个元素进行深拷贝,并添加到新集合中。

代码语言:javascript复制
ArrayList<MyClass> copy = new ArrayList<>();
for (MyClass item : list) {
    copy.add(item.clone());
}

需要注意的是,实现深拷贝可能会比较复杂,特别是当对象的引用结构很复杂时,例如存在循环引用。此外,深拷贝可能会消耗更多的计算和存储资源。

  • 问题 10. Cloneable 接口在 Java 中的作用是什么?

解答:Cloneable 接口在 Java 中被称为标记接口(Marker Interface),它本身并没有定义任何方法,但是它对于 Java 的对象克隆机制来说非常重要。

当一个类实现了 Cloneable 接口后,它就表明它的对象是可以被克隆的,即它的 clone() 方法可以被合法地调用。如果一个类没有实现 Cloneable 接口,但是调用了它的 clone() 方法,那么将会在运行时抛出 CloneNotSupportedException 异常。

实现 Cloneable 接口的目的是为了让 Objectclone() 方法知道它可以对这个类的对象进行字段对字段的复制。

需要注意的是,虽然 Cloneable 接口本身并没有定义任何方法,但是实现 Cloneable 接口的类通常需要重写 Object 类的 clone() 方法,以提供公开的克隆方法并实现类特定的克隆行为,例如深拷贝。

  • 问题 11. 为什么说 Cloneable 是一个标记接口?

Cloneable 接口被称为标记接口,是因为它本身并没有定义任何方法,它的作用主要是为了标记一个类的对象可以被克隆。

在 Java 中,Cloneable 接口的主要作用是告诉 Objectclone() 方法,它可以对实现了 Cloneable 接口的类的对象进行字段对字段的复制。

如果一个类没有实现 Cloneable 接口,但是调用了它的 clone() 方法,那么将会在运行时抛出 CloneNotSupportedException 异常。

因此,Cloneable 接口虽然没有定义任何方法,但是它对于 Java 的对象克隆机制来说非常重要,它是一种标记,表明一个类的对象可以被克隆。


2.3、Java序列化反序列化相关
  • 问题 12. 什么是 Java 的序列化和反序列化?

解答:Java 的序列化(Serialization)和反序列化(Deserialization)是 Java 对象持久化的一种机制。

  1. 序列化:序列化是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化过程中,对象将其当前状态写入到一个输出流中。
  2. 反序列化:反序列化是从一个输入流中读取对象的状态信息,并根据这些信息创建对象的过程。

序列化和反序列化在很多场景中都非常有用,例如:

  • 在网络通信中,序列化可以用于在网络上发送对象。
  • 在持久化数据时,序列化可以用于将对象保存到磁盘,然后在需要时通过反序列化重新创建。
  • 在 Java RMI(远程方法调用)技术中,序列化和反序列化被用于在 JVM 之间传递对象。

在 Java 中,如果一个类的对象需要支持序列化和反序列化,那么这个类需要实现 java.io.Serializable 接口。这个接口是一个标记接口,没有定义任何方法,只是用来表明一个类的对象可以被序列化和反序列化。

  • 问题 13.Java 中的 Serializable 接口有什么作用?

解答:在 Java 中,Serializable 接口是一个标记接口,用于表明一个类的对象可以被序列化和反序列化。

序列化是将对象的状态信息转换为可以存储或传输的形式的过程。反序列化则是从一个输入流中读取对象的状态信息,并根据这些信息创建对象的过程。

如果一个类实现了 Serializable 接口,那么它的对象可以被序列化,即可以将对象的状态信息写入到一个输出流中。然后,这个输出流可以被存储到磁盘,或通过网络发送到另一个运行 JVM 的机器。在需要时,可以从这个输出流中读取对象的状态信息,并通过反序列化重新创建对象。

需要注意的是,Serializable 接口本身并没有定义任何方法,它只是一个标记接口。实际的序列化和反序列化过程是由 JVM 通过一些特殊的机制来完成的。

  • 问题 14. 在 Java 中,如果一个对象的某个字段不想被序列化,应该如何处理?

在 Java 中,如果你不希望对象的某个字段被序列化,你可以使用 transient 关键字来修饰这个字段。

transient 是 Java 的一个关键字,用来表示一个字段不应该被序列化。在对象序列化的过程中,被 transient 修饰的字段会被忽略,不会被写入到输出流中。因此,这个字段的状态信息不会被持久化。

例如:

代码语言:javascript复制
public class MyClass implements Serializable {
    private int field1;
    private transient int field2;
    // ...
}

在这个例子中,field1 字段会被序列化,而 field2 字段则不会被序列化。

需要注意的是,如果一个字段被标记为 transient,那么在反序列化的过程中,这个字段的值会被初始化为其类型的默认值,例如 null0false

  • 问题 15. 如何自定义序列化与反序列化过程?

解答:在 Java 中,虽然默认的序列化机制已经足够强大,但在某些情况下,你可能需要自定义序列化过程。例如,你可能需要对某些敏感信息进行加密,或者需要以特定的格式写入对象的状态信息。

要自定义序列化过程,你可以在类中添加一个名为 writeObject() 的方法。这个方法必须接受一个 ObjectOutputStream 类型的参数,并且返回 void

代码语言:javascript复制
private void writeObject(ObjectOutputStream out) throws IOException {
    // 自定义序列化过程
}

在这个方法中,你可以自定义序列化过程。例如,你可以选择只序列化部分字段,或者对某些字段进行特殊处理。

需要注意的是,writeObject() 方法必须是 private 的,这是因为序列化机制会忽略 publicprotectedwriteObject() 方法。

同样,如果你需要自定义反序列化过程,你可以添加一个名为 readObject() 的方法:

代码语言:javascript复制
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    // 自定义反序列化过程
}

在这个方法中,你可以自定义反序列化过程。例如,你可以选择只反序列化部分字段,或者对某些字段进行特殊处理。

同样,readObject() 方法必须是 private 的。

  • 问题 16. 静态字段是否可以被序列化?为什么?

解答:静态字段不能被序列化。这是因为静态字段不属于对象,而是属于类。

在 Java 中,静态字段是类级别的,所有的对象实例共享同一个静态字段。因此,静态字段的状态不应该被看作是对象的一部分,所以在序列化对象时,静态字段会被忽略。

序列化的主要目的是为了保存对象的状态,以便在需要时可以恢复这个状态。但是,静态字段的状态是与特定的对象无关的,所以无需在序列化过程中保存和恢复。

如果你需要保存和恢复静态字段的状态,你需要通过其他方式来实现,例如,你可以在序列化和反序列化过程中手动处理静态字段。

  • 问题 17. 在 Java 中,默认的序列化机制是怎样的?

解答:在 Java 中,对象的默认序列化机制是通过实现 java.io.Serializable 接口来完成的。Serializable 是一个标记接口,它本身并没有定义任何方法,但是它告诉 Java 虚拟机(JVM)这个对象是可以被序列化的。

当一个对象被序列化时,JVM 会将该对象的类信息、类的签名以及非静态和非瞬态字段的值写入到一个输出流中。这个过程是自动的,不需要程序员进行任何特殊处理。

具体来说,序列化过程如下:

  1. 如果对象的类定义了 writeObject 方法,那么 JVM 会调用这个方法进行序列化。否则,JVM 会默认地进行序列化。
  2. JVM 会检查每个需要序列化的字段。如果字段是基本类型,那么 JVM 会直接写入其值。如果字段是引用类型,那么 JVM 会递归地对这个字段指向的对象进行序列化。
  3. 如果对象的类实现了 Externalizable 接口,那么 JVM 会调用 writeExternal 方法进行序列化。

反序列化过程与序列化过程相反。当一个对象被反序列化时,JVM 会从输入流中读取类信息和字段的值,然后根据这些信息创建新的对象。

需要注意的是,静态字段和用 transient 关键字修饰的字段不会被序列化。静态字段属于类,而不是对象。transient 关键字告诉 JVM 该字段不应该被序列化。


2.4、Java数据类型相关
  • 问题 18. Java 中的基本数据类型有哪些?

解答:Java 中的数据类型可以分为 4 类 8 种,4 类分别为:整型、浮点型、字符型和布尔型。

  1. 整型:包括 byte、short、int 和 long 四种类型,主要用于表示没有小数部分的数值,区别在于它们的取值范围和存储大小不同。

byte:占用 1 字节,取值范围为 -128 到 127

short:占用 2 字节,取值范围为 -32768 到 32767

int:占用 4 字节,取值范围为 -2147483648 到 2147483647

long:占用 8 字节,取值范围为 -9223372036854775808 到 9223372036854775807

  1. 浮点型:包括 float 和 double 两种类型,主要用于表示有小数部分的数值。

float:单精度浮点型,占用 4 字节,取值范围为 1.4E-45 到 3.4028235E38

double:双精度浮点型,占用 8 字节,取值范围为 4.9E-324 到 1.7976931348623157E308

  1. 字符型:只有 char 一种类型,主要用于表示单个字符。

char:占用 2 字节,取值范围为 0 到 65535,可以表示 Unicode 编码中的任何字符。

  1. 布尔型:只有 boolean 一种类型,主要用于表示真或假的逻辑值。

boolean:只有两个取值,即 true 和 false。

  • 问题 19. Java 中的自动装箱和拆箱是什么?

解答:自动装箱和拆箱是 Java 5.0 版本引入的新特性,主要用于基本数据类型和对应的包装类之间的自动转换。

自动装箱和拆箱是 Java 编译器的特性,它在编译阶段会自动为我们插入必要的代码来实现基本数据类型和包装类之间的转换。

  1. 自动装箱(Autoboxing):是指基本数据类型自动转换为对应的包装类。例如,将 int 类型自动转换为 Integer 类型。
代码语言:javascript复制
int i = 10;
Integer integer = i;  // 自动装箱 编译器会将这行代码转换为:Integer integer = Integer.valueOf(i);
  1. 自动拆箱(Unboxing):是指包装类自动转换为对应的基本数据类型。例如,将 Integer 类型自动转换为 int 类型。
代码语言:javascript复制
Integer integer = new Integer(10);
int i = integer;  // 自动拆箱 编译器会将这行代码转换为:int i = integer.intValue();

这两个特性使得基本数据类型和包装类在很多情况下可以互相替代,大大提高了编程的便利性。

  • 问题 20. 在 Java 中什么是强制类型转换、隐式类型转换、显式类型转换?

解答:

  1. 强制类型转换:也称为显式类型转换,是指程序员明确要求进行的类型转换。在 Java 中,可以通过在表达式前加上类型名的方式来进行强制类型转换。例如:
代码语言:javascript复制
double d = 10.5;
int i = (int) d;  // 将 double 类型转换为 int 类型
  1. 隐式类型转换:也称为自动类型转换,是指编译器在编译过程中自动进行的类型转换。在 Java 中,当我们把一个表示范围小的类型赋值给一个表示范围大的类型时,编译器会自动进行类型转换。例如:
代码语言:javascript复制
int i = 10;
double d = i;  // 将 int 类型自动转换为 double 类型
  1. 显式类型转换:和强制类型转换是同一个概念,都是指程序员明确要求进行的类型转换。

2.5、Java字符串相关
  • 问题 21. 为什么 Java 中的字符串不可变?它有什么优势?

解答:Java 中的字符串被设计为不可变的,这意味着一旦创建字符串对象,其内容无法更改。这个设计决策具有一些重要的优势:

  1. 线程安全性: 不可变字符串是线程安全的,因为多个线程可以同时访问一个字符串对象而无需担心并发修改导致的问题。这对于多线程应用程序来说是非常重要的。
  2. 安全性: 不可变字符串可以用作参数传递给方法,而不必担心方法在不经意间更改了字符串的内容。
  3. 性能优化: 因为字符串不可变,可以在运行时对其进行缓存,以减少内存占用和提高性能。例如,多个字符串变量可以共享相同的字符串字面值,从而节省内存。
  4. 哈希码缓存: 字符串的哈希码可以在创建时计算并缓存,这样在后续哈希比较(如在哈希表中查找字符串)时会更加高效。
  5. 字符串池: 不可变字符串使得字符串池的实现更容易,从而可以共享字符串字面值,减少内存占用。
  6. 安全性: 不可变字符串对于安全性是有帮助的。例如,当字符串用于密码或其他敏感数据时,不可变性可以确保这些数据不会在内存中不经意地被修改。
  7. 简化字符串操作: 不可变性简化了字符串操作。例如,当你连接两个字符串时,实际上是创建了一个新的字符串,而不是修改原始字符串。

尽管不可变字符串有很多优势,但它们也有一些劣势,例如在频繁修改字符串内容时可能会导致性能下降,因为每次修改都会创建新的字符串对象。为了解决这个问题,Java 提供了 StringBuilderStringBuffer 等可变字符串类,以便更高效地进行字符串拼接和修改。然而,在大多数情况下,不可变字符串的优点远远超过了其劣势,因此它们在 Java中得到广泛应用。

  • 问题 22. 什么是 Java 中的字符串池?

解答:Java 中的字符串池(String Pool)是 Java 堆内存中的一个特殊区域,用于存储所有由字面量创建的字符串对象。

当我们创建一个字符串字面量(例如,String str = "Hello";),JVM 首先会检查字符串池中是否已经存在 “Hello” 这个字符串。如果存在,那么 str 就会指向这个已存在的 “Hello” 字符串;如果不存在,JVM 就会在字符串池中创建一个新的 “Hello” 字符串,然后 str 会指向这个新创建的字符串。

通过这种方式,字符串池可以帮助我们节省内存,因为它允许相同的字符串字面量共享同一个存储空间。

  • 问题 23. 简述 String str = "aaa"String str = new String("i") 一样吗 ?

解答:String str = "aaa";String str = new String("aaa"); 在 Java 中并不完全相同。

  1. String str = "aaa";:这种方式创建的字符串会被放入字符串池中。如果字符串池中已经存在 “aaa” 这个字符串,那么 str 就会指向这个已存在的字符串;如果不存在,JVM 就会在字符串池中创建一个新的 “aaa” 字符串,然后 str 会指向这个新创建的字符串。
  2. String str = new String("aaa");:这种方式会在堆内存中创建一个新的字符串对象,然后 str 会指向这个新创建的对象。这时,无论字符串池中是否存在 “aaa” 这个字符串,都不会影响 str 的创建。

所以,虽然这两种方式创建的字符串内容相同,但是他们在内存中的存储位置可能不同。如果你使用 == 操作符比较这两个字符串,可能会得到 false,因为 == 操作符比较的是对象的引用,而不是内容。如果你使用 equals() 方法比较这两个字符串,会得到 true,因为 equals() 方法比较的是字符串的内容

  • 问题 24. Java 中有哪些创建字符串的方式?

解答:在 Java 中,主要有以下几种创建字符串的方式:

  1. 字符串字面量:这是最常见的创建字符串的方式,例如 String str = "Hello";。这种方式创建的字符串会被放入字符串池中。
  2. 使用 new 关键字:例如 String str = new String("Hello");。这种方式会在堆内存中创建一个新的字符串对象。
  3. 通过字符数组:例如 char[] array = {'H', 'e', 'l', 'l', 'o'}; String str = new String(array);。这种方式会创建一个新的字符串,内容是字符数组的内容。
  4. 通过 StringBuilderStringBuffer:例如 StringBuilder sb = new StringBuilder("Hello"); String str = sb.toString();。这种方式可以创建一个可变的字符串,然后再转换为不可变的 String
  5. 通过 String.format() 方法:例如 String str = String.format("Hello %s", "World");。这种方式可以创建一个格式化的字符串。

以上就是在 Java 中创建字符串的主要方式。

  • 问题 25. 介绍一下 String、StringBuffer、StringBuilder 和他们之间的区别?

解答:

  1. String:在 Java 中,String 是不可变的,也就是说一旦一个 String 对象被创建,我们就不能改变它的内容。每次对 String 类型进行修改,都会生成一个新的 String 对象。这在需要大量修改字符串时,会导致内存的大量占用和效率的降低。
  2. StringBufferStringBuffer 是线程安全的可变字符序列。每个方法都是同步的,可以被多个线程安全地调用。但是,这种线程安全带来的缺点是效率相对较低。
  3. StringBuilderStringBuilder 是一个可变字符序列,它提供了 appendinsertdeletereversesetCharAt 等方法来修改字符串。与 StringBuffer 相比,StringBuilder 不是线程安全的,因此在单线程环境下,StringBuilder 的效率更高。

总结一下,他们之间的区别主要在于:

  • String 是不可变的,而 StringBufferStringBuilder 是可变的。
  • StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的。
  • 在单线程环境下,如果需要对字符串进行大量修改,应优先考虑使用 StringBuilder。在多线程环境下,应使用 StringBuffer 来确保线程安全。如果字符串不需要修改,那么使用 String 是最好的选择。

0 人点赞