Skip to content

一、JAVA基础

1、JDK 和 和 JRE 的区别是什么?

JDK(Java Development Kit)和JRE(Java Runtime Environment)都是Java平台的组件,它们的主要区别在于功能和用途不同。

JDK是Java开发工具包,它包含了Java编译器(javac)、Java虚拟机(JVM)和Java类库等开发工具和组件,它为Java开发者提供了完整的开发环境和工具,可以用于开发Java应用程序、Applet和Java Web应用等。

JRE是Java运行时环境,它包含了Java虚拟机(JVM)和Java类库等基本运行环境,用于执行Java应用程序和Applet等,但不包含Java编译器和其他开发工具,因此不能用于Java应用程序的开发。

简单来说,JDK包含了Java开发所需要的所有工具和组件,而JRE只包含了Java运行时所需的基本组件。如果你需要开发Java应用程序,那么必须安装JDK,如果只需要运行Java应用程序,则只需要安装JRE即可。

2、static 关键字是什么意思?Java 中是否可以覆盖(override) 一个 private 或者是 static 的方法?

static关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。Java 中 static 方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。static 方法跟类的任何实例都不相关,所以概念上不适用。

3、是否可以在 static 环境中访问非 static 变量?

static 变量在 Java 中是属于类的,它在所有的实例中的值是一样的。当类被 Java 虚拟机载入的时候,会对 static 变量进行初始化。如果你的代码尝试不用实例来访问非 static 的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。

4、Java 支持的数据类型有哪些?什么是自动拆装箱?

Java 支持的数据类型可以分为两类:基本类型引用类型

Java 的基本类型包括:

  1. 整数类型
  • byte 包装类型:java.lang.Byte 默认值:0 占用位数:8 位
  • short 包装类型:java.lang.Short 默认值:0 占用位数:16 位
  • int 包装类型:java.lang.Integer 默认值:0 占用位数:32 位
  • long 包装类型:java.lang.Long 默认值:0L 占用位数:64 位
  1. 浮点数类型
  • float 包装类型:java.lang.Float 默认值:0.0f 占用位数:32 位
  • double 包装类型:java.lang.Double 默认值:0.0d 占用位数:64 位
  1. 字符类型
  • char 包装类型:java.lang.Character 默认值:'\u0000'(空字符) 占用位数:16 位
  1. 布尔类型
  • boolean 包装类型:java.lang.Boolean 默认值:false 占用位数:虚拟机规范没有明确规定,但常见实现中为 1 位(逻辑上),实际存储时可能为 8 位(字节)

image-20240624134054684

Java 的引用类型包括:

  1. 类类型:class;
  2. 接口类型:interface;
  3. 数组类型:array;
  4. 枚举类型:enum。

自动拆装箱(AutoBoxing/Unboxing)是Java 5.0 引入的新特性,它可以自动地完成基本类型和相应的包装类型之间的转换。其中,自动装箱(AutoBoxing)指的是将基本类型自动转换为对应的包装类型,而自动拆箱(AutoUnboxing)则指的是将包装类型自动转换为对应的基本类型。

例如,可以将一个int类型的值直接赋值给Integer类型的变量,Java会自动将int类型的值转换为对应的Integer类型;反之,也可以将一个Integer类型的值直接赋值给int类型的变量,Java会自动将Integer类型的值转换为对应的int类型。

自动拆装箱的使用可以简化代码,使得Java开发变得更加方便和易读。但需要注意的是,频繁的自动拆装箱操作可能会影响程序的性能,因此在需要高性能的场景下,最好手动进行拆装箱操作。

5、Java 中的方法覆盖(Overriding) 和方法重载(Overloading) 是什么意思?

Java 中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问。

6、接口和抽象类的区别是什么?

Java 提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:

  • 接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。
  • 类可以实现很多个接口,但是只能继承一个抽象类
  • 类如果要实现一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声
  • 明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
  • 抽象类可以在不提供接口方法实现的情况下实现接口。
  • Java 接口中声明的变量默认都是 final 的。抽象类可以包含非 final 的变量。
  • Java 接口中的成员函数默认是 public 的。抽象类的成员函数可以是 private,protected 或者是 public。
  • 接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含 main方法的话是可以被调用的。

1、接口(Interface)

定义方式

  • 使用 interface 关键字定义。
  • 接口中的所有方法默认是 public 和 abstract 的。
  • 接口中的成员变量默认是 public static final 的(常量)。

实现方式

  • 使用 implements 关键字实现接口。
  • 一个类可以实现多个接口,从而实现多重继承。

使用场景

  • 用于定义一组方法,表示某个类可以实现的功能。

  • 更加适合定义行为规范(即“能做什么”)。

2、抽象类(Abstract Class)

定义方式

  • 使用 abstract 关键字定义。
  • 抽象类中可以包含抽象方法(没有实现的方法)和具体方法(有实现的方法)。
  • 可以包含成员变量和构造方法。

继承方式

  • 使用 extends 关键字继承抽象类。
  • 一个类只能继承一个抽象类(Java 中的单继承机制)。

使用场景

  • 用于表示类之间的继承关系(即“是什么”)。
  • 适用于需要共用代码和提供部分实现的情况。

image-20240624135034571

7、什么是值传递和引用传递?

对象被值传递,意味着传递了对象的一个副本。因此,就算是改变了对象副本,也不会影响源对象的值。对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象所做的改变会反映到所有的对象上。

Java 中的实际情况

ava 只有值传递,但因为引用类型的参数传递的是引用的副本,通过这个引用可以修改对象的状态,所以有时看起来像是引用传递。

1、值传递(Pass by Value)

值传递是将实际参数的值的副本传递给函数或方法。在函数内部对参数的修改不会影响到实际参数的值。

基本数据类型:Java 中的基本数据类型(如 int、char、float 等)通过值传递。方法内部对参数的修改不会影响原始值。

java
public static void main(String[] args) {
    int a = 5;
    modifyValue(a);
    System.out.println("After modifyValue: " + a); // 输出 5
}

public static void modifyValue(int x) {
    x = 10;
}

在这个例子中,modifyValue 方法接收的是 a 的副本,修改 x 的值不会影响到 a 的值。

2、引用传递(Pass by Reference)

引用传递是将实际参数的引用(地址)传递给函数或方法。方法内部对参数的修改会影响到实际参数的值。

引用数据类型:在 Java 中,对象和数组都是引用类型,通过值传递传递的是引用的副本。虽然传递的是引用的副本,但通过这个引用可以修改对象的内部状态,从而影响原始对象。

java
public static void main(String[] args) {
    int[] arr = {1, 2, 3};
    modifyArray(arr);
    System.out.println("After modifyArray: " + arr[0]); // 输出 10
}

public static void modifyArray(int[] array) {
    array[0] = 10;
}

在这个例子中,modifyArray 方法接收的是 arr 的引用的副本,但这个引用仍然指向同一个数组对象,因此修改数组的内容会影响原始数组。

8、Java反射

Java 反射(Reflection)是 Java 提供的一种强大的工具,通过它可以在运行时获取类的详细信息(如类的成员变量、方法、构造函数等)并且可以动态调用对象的方法和访问对象的字段。反射提供了一种在运行时操作对象的机制,使得 Java 程序更具灵活性和动态性。

使用反射获取类的信息

要使用反射,首先需要获取类的 Class 对象。获取 Class 对象的方法有多种:

  • 使用 .class 属性:

  • java
    Class<?> clazz = String.class;
  • 使用 getClass() 方法:

  • java
    String str = "Hello, World!";
    Class<?> clazz = str.getClass();
  • 使用 Class.forName() 方法:

  • java
    try {
        Class<?> clazz = Class.forName("java.lang.String");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

获取类的详细信息

  • 获取类名

  • java
    Class<?> clazz = String.class;
    System.out.println("Class name: " + clazz.getName());
  • 获取构造方法

  • java
    Constructor<?>[] constructors = clazz.getConstructors();
    for (Constructor<?> constructor : constructors) {
        System.out.println("Constructor: " + constructor);
    }
  • 获取方法

  • java
    Method[] methods = clazz.getMethods();
    for (Method method : methods) {
        System.out.println("Method: " + method);
    }
  • 获取字段

  • java
    Field[] fields = clazz.getFields();
    for (Field field : fields) {
        System.out.println("Field: " + field);
    }

动态调用方法和访问字段

  • 动态调用方法

  • java
    try {
        Method method = clazz.getMethod("substring", int.class);
        String result = (String) method.invoke("Hello, World!", 7);
        System.out.println("Result: " + result);
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
  • 动态访问字段

  • java
    class Example {
        public String field = "example";
    }
    
    Example example = new Example();
    Class<?> exampleClass = example.getClass();
    try {
        Field field = exampleClass.getField("field");
        String value = (String) field.get(example);
        System.out.println("Field value: " + value);
        field.set(example, "new value");
        System.out.println("Updated Field value: " + example.field);
    } catch (NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
    }

反射的使用场景

  1. 框架和库:许多 Java 框架和库(如 Spring、Hibernate)广泛使用反射来动态创建对象和调用方法。

  2. 序列化和反序列化:反射用于序列化和反序列化对象,例如 Java 的 ObjectInputStream 和 ObjectOutputStream。

  3. 测试工具:单元测试框架如 JUnit 使用反射来调用测试方法。

    1. 动态代理:Java 的动态代理机制依赖反射来创建代理实例。

反射的优缺点

优点

提高了程序的灵活性和动态性。

允许在运行时操作对象和调用方法。

支持创建通用库和框架。

缺点

性能开销较大,比直接调用方法和访问字段慢。

安全性较低,可能绕过访问控制检查。

代码可读性和可维护性较差。

9、Java注解

Annotation(注解)是 Java 提供的一种对元程序中元素关联信息和元数据(metadata)的途径 和方法。Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的 Annotation 对象,然后通过该 Annotation 对象来获取注解中的元数据信息。

1、4 种标准元注解

元注解的作用是负责注解其他注解。 Java5.0 定义了 4 个标准的 meta-annotation 类型,它们被 用来提供对其它 annotation 类型作说明。

  1. @Target 修饰的对象范围

    @Target说明了Annotation所修饰的对象范围: Annotation可被用于 packages、types(类、 接口、枚举、Annotation 类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数 和本地变量(如循环变量、catch 参数)。在 Annotation 类型的声明中使用了 target 可更加明晰 其修饰的目标

  2. @Retention 定义 被保留的时间长短

    Retention 定义了该 Annotation 被保留的时间长短:表示需要在什么级别保存注解信息,用于描 述注解的生命周期(即:被描述的注解在什么范围内有效),取值(RetentionPoicy)由:

    1. SOURCE:在源文件中有效(即源文件保留)
    2. SOURCE:在源文件中有效(即源文件保留)
    3. RUNTIME:在运行时有效(即运行时保留)
  3. @Documented 描述-javadoc

    @ Documented 用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因 此可以被例如 javadoc 此类的工具文档化。

  4. @Inherited 阐述了某个被标注的类型是被继承的

    @Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一 个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该 class 的子类。

2、注解处理器

​ 如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中, 很重要的一部分就是创建于使用注解处理器。Java SE5 扩展了反射机制的 API,以帮助程序员快速 的构造自定义注解处理器。下面实现一个注解处理器。

java
 /1*** 定义注解*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
 /**供应商编号*/
public int id() default -1;
/*** 供应商名称*/
 public String name() default ""

/** * 供应商地址*/
 public String address() default "";
}
//2:注解使用
public class Apple {
    @FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西省西安市延安路")
    private String appleProvider;

    public void setAppleProvider(String appleProvider) {
        this.appleProvider = appleProvider;
    }

    public String getAppleProvider() {
        return appleProvider;
    }
}

/3*********** 注解处理器 ***************/
public class FruitInfoUtil {
    public static void getFruitInfo(Class<?> clazz) {
        String strFruitProvicer = "供应商信息:";
        Field[] fields = clazz.getDeclaredFields(); //通过反射获取处理注解

        for (Field field : fields) {
            if (field.isAnnotationPresent(FruitProvider.class)) {
                FruitProvider fruitProvider = (FruitProvider) field.getAnnotation(FruitProvider.class);
                //注解信息的处理地方 
                strFruitProvicer = " 供应商编号:" + fruitProvider.id() + " 供应商名称:" +
                    fruitProvider.name() + " 供应商地址:" + fruitProvider.address();
                System.out.println(strFruitProvicer);
            }
        }
    }
}
public class FruitRun {
    public static void main(String[] args) {
        FruitInfoUtil.getFruitInfo(Apple.class);

        /***********输出结果***************/
        // 供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延
    }
}

10、Java序列化

1、保存(持久化)对象及其状态到内存或者磁盘

Java 平台允许我们在内存中创建可复用的 Java 对象,但一般情况下,只有当 JVM 处于运行时, 这些对象才可能存在,即,这些对象的生命周期不会比 JVM 的生命周期更长。但在现实应用中, 就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。 Java 对象序列化就能够帮助我们实现该功能。

2、序列化对象以字节数组保持-静态成员不保存

使用 Java 对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装 成对象。必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对 象序列化不会关注类中的静态变量。

3、序列化用户远程对象传输

除了在持久化对象时会用到对象序列化之外,当使用 RMI(远程方法调用),或在网络中传递对象时, 都会用到对象序列化。Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用。

4、Serializable 实现序列化

在 Java 中,只要一个类实现了 java.io.Serializable 接口,那么它就可以被序列化。

5、ObjectOutputStream 和 ObjectInputStream 对对象进行序列化及反序列化

通过 ObjectOutputStream 和 ObjectInputStream 对对象进行序列化及反序列化。

6、writeObject 和 readObject 自定义序列化策略

在类中增加 writeObject 和 readObject 方法可以实现自定义序列化策略。

7、序列化 ID

虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个 类的序列化 ID 是否一致(就是 private static final long serialVersionUID)

8、序列化并不保存静态变量

序列化子父类说明 要想将父类对象也序列化,就需要让父类也实现 Serializable 接口。

9、Transient 关键字阻止该变量被序列化到文件中

  1. 在变量声明前加上 Transient 关键字,可以阻止该变量被序列化到文件中,在被反序列 化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
  2. 服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串 等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在 客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的 数据安全。

11、Java复制

将一个对象的引用复制给另外一个对象,一共有三种方式。第一种方式是直接赋值,第二种方式 是浅拷贝,第三种是深拷贝。所以大家知道了哈,这三种概念实际上都是为了拷贝对象。

1、直接赋值复制

直接赋值。在 Java 中,A a1 = a2,我们需要理解的是这实际上复制的是引用,也就是 说 a1 和 a2 指向的是同一个对象。因此,当 a1 变化的时候,a2 里面的成员变量也会跟 着变化。

2、浅复制(复制引用但不复制引用的对象)

浅拷贝是指创建一个新对象,这个新对象的字段值与原对象的字段值相同。如果字段是基本类型,则复制其值;如果字段是引用类型,则复制引用,不复制引用指向的对象。因此,原对象和新对象共享同一个引用对象的实例。

java
class Person implements Cloneable {
    String name;
    int age;
    Address address;

    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", address=" + address + "}";
    }
}

class Address {
    String city;

    public Address(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Address{city='" + city + "'}";
    }
}

public class ShallowCopyExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("New York");
        Person original = new Person("John", 25, address);
        Person copy = (Person) original.clone();

        System.out.println("Original: " + original);
        System.out.println("Copy: " + copy);

        // 修改复制对象的地址
        copy.address.city = "San Francisco";

        System.out.println("After modification:");
        System.out.println("Original: " + original);
        System.out.println("Copy: " + copy);
    }
}

3、深复制(复制对象和其应用对象)

深拷贝是指创建一个新对象,同时复制所有字段及其引用对象的副本。新对象与原对象完全独立,任何对新对象的修改都不会影响原对象,反之亦然。

java
class Person implements Cloneable {
    String name;
    int age;
    Address address;

    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person cloned = (Person) super.clone();
        cloned.address = (Address) address.clone(); // 深拷贝
        return cloned;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", address=" + address + "}";
    }
}

class Address implements Cloneable {
    String city;

    public Address(String city) {
        this.city = city;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Address{city='" + city + "'}";
    }
}

public class DeepCopyExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("New York");
        Person original = new Person("John", 25, address);
        Person copy = (Person) original.clone();

        System.out.println("Original: " + original);
        System.out.println("Copy: " + copy);

        // 修改复制对象的地址
        copy.address.city = "San Francisco";

        System.out.println("After modification:");
        System.out.println("Original: " + original);
        System.out.println("Copy: " + copy);
    }
}

image-20240624140558082

4、序列化(深 clone 一中实现)

在 Java 语言里深复制一个对象,常常可以先使对象实现 Serializable 接口,然后把对 象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。

12、获取计算机核数

java
Runtime.getRuntime().availableProcessors();

13、Lambda +Stream+链式调用+Java8函数式编程带走

Runnable

image-20210904002008291

Function

image-20210904002018721

Consumer

image-20210904002047963

Supplier

image-20210904002059323

BiConsumer

image-20210904002110443

小总结

image-20210904002122555

14、equal 和 hashcode 作用和区别?

1、hashCode()和equals()的作用是什么?

hashCode()和equals()的作用其实是一样的,目的都是为了再java里面比较两个对象是否相等一致

2、hashCode()和equals()的区别是什么?

  1. equals()既然已经实现比较两个对象的功能了,为什么还需要hashCode()呢? 因为重写的equals()里一般比较的较为全面和复杂(它会对这个对象内所以成员变量一一进行比较),这样效率很低,而通过hashCode()对比,则只要生成一个hash值就能比较了,效率很高
  2. 那hashCode的效率这么高,为啥还要用equals()呢? 因为hashCode()并不是完全可靠,非常有可能的情况是,两个完全不同的对象的hash值却是一样的。
    1. equals()相等的两个对象他们的hashCode()肯定相等,即equals()绝对可靠
    2. hahsCode()相同的两个对象,它们的equals()不一定相同。即用hashCode()比较相同的时候不靠谱
    3. hashCode()不同的两个对象,他们的那么equals()肯定不同。即用hashCode()比较不同的时候肯定靠谱

3、hashCode()和equals使用的注意事项

  1. 对于需要大量并且快速的对比的话如果都用equals()去做显然效率太低,所以解决方案为:每当需要比较的时候,首先用hahsCode()去对比,而如果hashCode()不一样,则两个对象肯定不一样,此时就没有必要再用equals()比较了;如果hashCode()相同,则这两个对象有可能相同,这时候再去比较这两个对象的equals(),如若equals()也相同,则表示这两个真的相同的,这样既大大提高了效率,又保证了准确性。
  2. 事实上,我们平时用的集合框架中的hashMap、hashSet,hashTable 中对key的比较就是使用上述这种方法
  3. 而Obejct默认的equals和HashCode方法返回的是对象的地址相关信息。所以当我们通过new关键字创建了两个内容相同的对象,虽然他们的内容相同,但是他们在内存中分配的地址不同,导致它们的hashCode()不同,这肯定不是我们想要的。所以当我们要将某个类应用到集合中去的时候,就必须重写equals()方法和hashCode()方法

15、equals、==、hashcode相关的理解

在没有重写的情况下equals和==是一样的作用,都是比较内存地址

java
// Object.java
public boolean equals(Object obj) {
    return (this == obj);
}
Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.、
每当重写hashCode方法时,通常都需要重写该方法

==truehashcode一定相等

hashcodetrue==不一定相等,存在hash冲突的情况

我们经常使用的String比较都是用的equals,为什么没有比较内存地址,是因为String底层重写了equals方法,Integer同理。

java
// String.java
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;
}

16、String str = new String("abc");创建了几个对象,为什么?

  1. 创建一个字符串对象,存储在堆内存(常量池)中,其内容为"abc"。
  2. 创建一个指向该对象的引用变量str,存储在栈内存中。

因此,使用new String("abc")创建了两个对象:一个是字符串对象,一个是引用变量。这是因为字符串是不可变的,因此在创建时,需要为每个不同的字符串创建一个新的对象。

17、String为啥要用final修饰

在Java中,String是一个不可变的类,即一旦创建了一个String对象,就不能更改它的内容。这就是为什么String类中的方法都会返回一个新的String对象,而不是修改原来的对象。

为了确保String对象的不可变性,Java语言设计者使用了final关键字来修饰String类。使用final关键字可以使String类的实例变量成为常量,即在创建后不能再修改,从而保证了String对象的不可变性。

使用final关键字还可以提高代码的安全性和可读性。如果某个方法参数被声明为final String类型,那么在方法中就不能修改这个参数,这可以防止参数被意外地修改,从而提高了代码的安全性。此外,使用final关键字还可以让代码更易于理解和维护,因为它能够明确表明某个变量或对象是不可修改的。

简单说就是 使用final关键字修饰String类可以确保其不可变性,提高代码的安全性和可读性。