为什么JDK 9中把String的char[]改成了byte[]?

典型回答 在Java 9之前,字符串内部是由字符数组char[] 来表示的。 1 2 /** The value is used for character storage. */ private final char value[]; 由于Java内部使用UTF-16,每个char占据两个字节,即使某些字符可以用一个字节(LATIN-1)表示,但是也仍然会占用两个字节。所以,JDK 9就对他做了优化。 这就是Java 9引入了"Compact String“的概念: 每当我们创建一个字符串时,如果它的所有字符都可以用单个字节(Latin-1)表示,那么将会在内部使用字节数组来保存一半所需的空间,但是如果有一个字符需要超过8位来表示,Java将继续使用UTF-16与字符数组。 Latin1(又称ISO 8859-1)是一种字符编码格式,用于表示西欧语言,包括英语、法语、德语、西班牙语、葡萄牙语、意大利语等。它由国际标准化组织(ISO)定义,并涵盖了包括ASCII在内的128个字符。 Latin1编码使用单字节编码方案,也就是说每个字符只占用一个字节,其中第一位固定为0,后面的七位可以表示128个字符。这样,Latin1编码可以很方便地与ASCII兼容。 那么,问题来了 ,所有字符串操作时,它如何区分到底用Latin-1还是UTF-16表示呢? 为了解决这个问题,对String的内部实现进行了另一个更改。引入了一个名为coder的字段,用于保存这些信息。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 /** * The value is used for character storage. * * @implNote This field is trusted by the VM, and is a subject to * constant folding if String instance is constant. Overwriting this * field after construction will cause problems. * * Additionally, it is marked with {@link Stable} to trust the contents * of the array. No other facility in JDK provides this functionality (yet). * {@link Stable} is safe here, because value is never null. */ @Stable private final byte[] value; /** * The identifier of the encoding used to encode the bytes in * {@code value}. The supported values in this implementation are * * LATIN1 * UTF16 * * @implNote This field is trusted by the VM, and is a subject to * constant folding if String instance is constant. Overwriting this * field after construction will cause problems. */ private final byte coder; ...

March 22, 2026 · 2 min · santu

字符串常量是什么时候进入到字符串常量池的?

典型回答 ✅字符串常量池是如何实现的? 字符串常量池中的常量有两种来源,一种是字面量会在编译期先进入到Class常量池,然后再在运行期进去到字符串池,还有一种就是在运行期通过intern将字符串对象手动添加到字符串常量池中。 那么,Class常量池中的常量,是在什么时候被放进到字符串池的呢? Java 的类加载过程要经历加载(Loading)、链接(Linking)、初始化(Initializing)等几个步骤,在链接这个步骤,又分为验证(Verification)、准备(Preparation)以及解析(Resolution)等几个步骤。 在 Java 虚拟机规范及 Java语言规范中都提到过: 《The Java Virtual Machine Specification》 5.4 Linking: For example, a Java Virtual Machine implementation may choose to resolve each symbolic reference in a class or interface individually when it is used (“lazy” or “late” resolution), or to resolve them all at once when the class is being verified (“eager” or “static” resolution) 《The Java Language Specification》 12.3 Linking of Classes and Interfaces ...

March 22, 2026 · 1 min · santu

String中intern的原理是什么?

典型回答 字符串常量池中的常量有两种来源: 1、字面量会在编译期先进入到Class常量池,然后再在运行期进去到字符串池, 2、在运行期通过intern将字符串对象手动添加到字符串常量池中。 字面量:https://www.yuque.com/hollis666/ec96i7/pwmmw153wb4f2cgq#AfDwW intern的作用是这样的: 如果字符串池中已经存在一个等于该字符串的对象,intern()方法会返回这个已存在的对象的引用。 如果字符串池中没有等于该字符串的对象,intern()方法会将该字符串添加到字符串池中,并返回对新添加的字符串对象的引用。 1 2 String s = new String("Hollis") + new String("Chuang"); s.intern(); 所以,无论何时通过intern()方法获取字符串的引用,都会得到字符串池中的引用,这样可以确保相同的字符串在内存中只有一个实例。 很多人以为知道以上信息,就算是了解intern了,那么请回答一下这个问题: 1 2 3 4 5 6 7 8 9 10 11 public static void main(String[] args) { String s1 = new String("a"); s1.intern(); String s2 = "a"; System.out.println(s1 == s2); // false String s3 = new String("a") + new String("a"); s3.intern(); String s4 = "aa"; System.out.println(s3 == s4);// true } 大家可以在 JDK 1.7以上版本中尝试运行以上两段代码,就会发现,s1 == s2的结果是 false,但是s3 == s4的结果是 true。 ...

March 22, 2026 · 2 min · santu

Java中Timer实现定时调度的原理是什么?

典型回答 Java中的Timer类是一个定时调度器,用于在指定的时间点执行任务。JDK 中Timer类的定义如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Timer { /** * The timer task queue. This data structure is shared with the timer * thread. The timer produces tasks, via its various schedule calls, * and the timer thread consumes, executing timer tasks as appropriate, * and removing them from the queue when they're obsolete. */ private final TaskQueue queue = new TaskQueue(); /** * The timer thread. */ private final TimerThread thread = new TimerThread(queue); } 以上就是Timer中最重要的两个成员变量: ...

March 22, 2026 · 3 min · santu

try中return A,catch中return B,finally中return C,最终返回值是什么?

典型回答 最终的返回值将会是C! 因为finally块总是在try和catch块之后执行,无论是否有异常发生。如果finally块中有一个return语句,它将覆盖try块和catch块中的任何return语句。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 //无异常情况 public static String getValue(){ try{ return "A"; }catch (Exception e){ return "B"; }finally { return "C"; } } //有异常情况 public static String getValue(){ try{ System.out.println(1/0); return "A"; }catch (Exception e){ return "B"; }finally { return "C"; } } 所以在这种情况下,无论try和catch块的执行情况如何,finally块中的return C;总是最后执行的语句,并且其返回值将是整个代码块的返回值。 ...

March 22, 2026 · 2 min · santu

final、finally、finalize有什么区别

典型回答 final、finally、finalize有什么区别?这个问题就像周杰、周杰伦和周星驰之间有啥关系的问题一样。其实没啥关系,放在一起比较无非是名字有点像罢了。 final、finally和finalize是Java中的三个不同的概念。 final:用于声明变量、方法或类,使之不可变、不可重写或不可继承。 finally:是异常处理的一部分,用于确保代码块(通常用于资源清理)总是执行。 finalize:是Object类的一个方法,用于在对象被垃圾回收前执行清理操作,但通常不推荐使用。 final final是一个关键字,可以用来修饰变量、方法和类。分别代表着不同的含义。 final变量:即我们所说的常量,一旦被赋值后,就不能被修改。 1 2 3 4 final int x = 100; // x = 200; // 编译错误,不能修改final变量的值 public static final String AUTHOR_NAME = "Hollis"; final方法:不能被子类重写。 1 2 3 public final void show() { // ... } final类:不能被继承。 1 2 3 public final class MyFinalClass { // ... } finally finally是一个用于异常处理,它和try、catch块一起使用。无论是否捕获或处理异常,finally块中的代码总是执行(程序正常执行的情况)。通常用于关闭资源,如输入/输出流、数据库连接等。 1 2 3 4 5 6 7 try { // 可能产生异常的代码 } catch (Exception e) { // 异常处理代码 } finally { // 清理代码,总是执行 } ✅finally中代码一定会执行吗? ...

March 22, 2026 · 1 min · santu

为什么建议自定义一个无参构造函数

典型回答 Java中的构造函数分为无参和有参。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Person { private String name; private int age; // 无参构造器 public Person() { this.name = "Unknown"; this.age = 0; } // 有参构造器 public Person(String name, int age) { this.name = name; this.age = age; } // ...其他方法... } 不管有参还是无参,都是为了做对象的初始化的。无参的就是给对象的成员变量设置默认值。有参的就是根据我们的参数进行初始化。 ...

March 22, 2026 · 1 min · santu

为什么Java中的main方法必须是public static void的?

典型回答 在Java中,想必所有人都不会对main方法感到陌生,main方法是Java应用程序的入口方法。程序运行时,要执行的第一个方法就是main方法。 我们创建的main方法的形式都是一样的: 1 2 3 public static void main(String[] args) { } 首先都是public的、都是static的,返回值都是void,方法名都是main,入参都是一个字符串数组。 以上的方法声明中,唯一可以改变的的部分就是方法的参数名,你可以把args改成任意你想要使用的名字。 main方法是JVM执行的入口,为了方便JVM调用,所以需要将他的访问权限设置为public,并且静态方法可以方便JVM直接调用,无需实例化对象。 因为JVM的退出其实是不完全依赖main方法的,所以JVM并不会接收main方法的返回值,所以给main方法定义一个返回值没有任何意义。所以main方法的返回值为void。 为了方便main函数可以接受多个字符串参数作为入参,所以他的形参类型被定义为String[]。 为什么 main 方法是公有的(public)? Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。 default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。 private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类) public : 对所有类可见。使用对象:类、接口、变量、方法 protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类) 以上四种控制符都可以用来修饰方法,但是被修饰的方法的访问权限就不同了。 而对于main方法来说,我们需要通过JVM直接调用他,那么就需要他的限定符必须是public的,否则是无法访问的。 为什么 main 方法是静态的(static)? static是静态修饰符,被他修饰的方法我们称之为静态方法,静态方法有一个特点,那就是静态方法独立于该类的任何对象,它不依赖类特定的实例,被类的所有实例共享。只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。 而对于main方法来说,他的调用过程是经历了类加载、链接和初始化的。但是并没有被实例化过,这时候如果想要调用一个类中的方法。那么这个方法必须是静态方法,否则是无法调用的。 为什么 main 方法没有返回值(void)? 如果大家对于C语言和C++语言有一定的了解的话,就会知道,像 C、C++ 这种以 int 为 main 函数返回值的编程语言。 ...

March 22, 2026 · 1 min · santu

为什么不建议使用异常控制业务流程

典型回答 在《Effecitive Java》中,作者提出过,不建议使用异常来控制业务流程。很多人 不理解,啥叫用异常控制业务流程。 给大家举个简单的例子,在解决幂等问题时,我们有的人会这么做,先插入,然后再捕获唯一性约束冲突异常,再反查,返回幂等。如: 1 2 3 4 5 6 7 8 9 10 11 public existingData insertData(Data data) { try { // 尝试插入数据 return dataRepository.insert(data); } catch (DuplicateKeyException e) { // 捕获唯一性约束冲突异常 Data existingData = dataRepository.findByUniqueKey(data.getUniqueKey()); // 返回已存在的数据,以实现幂等性 return existingData; } } 这么做非常不建议,主要由以下几个问题: 1、存在性能问题:在Java中,异常的生成和处理是昂贵的,因为它涉及到填充栈跟踪信息。频繁地抛出和捕获异常会导致性能下降。 ...

March 22, 2026 · 1 min · santu

为什么这段代码在JDK不同版本中结果不同

典型回答 (本文并不算一道面试题,因为面试的时候很少有人问,但是这个对于理解intern的原理是比较有帮助的,所以就写了。然后有人反馈自己代码执行和我文中的不一样,可能的原因有很多,比如JDK版本不同、操作系统不同、本地编译过的其他代码也有影响等。故而如果现象不一致,可以使用一些在线的Java代码执行工具测试,如:https://www.bejson.com/runcode/java/ 。) 以下代码中,在JDK 1.8中,JDK 11及以上版本中执行后结果不是一样的。 1 2 3 4 String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4); 你会发现,在JDK 1.8中,以上代码得到的结果是true,而JDK 11及以上的版本中结果却是false。 那么,再稍作修改呢?在目前的所有JDK版本中,执行以下代码: 1 2 3 4 String s3 = new String("3") + new String("3");// ① s3.intern();// ② String s4 = "33"; System.out.println(s3 == s4);// ③ 得到的结果也是true,你知道为什么嘛? 看这篇文章之前,请先阅读以下文章,先确保自己了解了intern的原理!!! ✅String中intern的原理是什么? 出现上述现象,肯定是因为在JDK 11 及以上的版本中,“11"这个字面量已经被提前存入字符串池了。那什么时候存进去的呢?(这个问题,全网应该没人提过) 经过我七七四十九天的研究,终于发现了端倪,就在以下代码中:Source.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public enum Source { /** 1.0 had no inner classes, and so could not pass the JCK. */ // public static final Source JDK1_0 = new Source("1.0"); /** 1.1 did not have strictfp, and so could not pass the JCK. */ // public static final Source JDK1_1 = new Source("1.1"); /** 1.2 introduced strictfp. */ JDK1_2("1.2"), /** 1.3 is the same language as 1.2. */ JDK1_3("1.3"), /** 1.4 introduced assert. */ JDK1_4("1.4"), /** 1.5 introduced generics, attributes, foreach, boxing, static import, * covariant return, enums, varargs, et al. */ JDK5("5"), /** 1.6 reports encoding problems as errors instead of warnings. */ JDK6("6"), /** 1.7 introduced try-with-resources, multi-catch, string switch, etc. */ JDK7("7"), /** 1.8 lambda expressions and default methods. */ JDK8("8"), /** 1.9 modularity. */ JDK9("9"), /** 1.10 local-variable type inference (var). */ JDK10("10"), /** 1.11 covers the to be determined language features that will be added in JDK 11. */ JDK11("11"); } 看到了么,xdm,在JDK 11 的源码中,定义了"11"这个字面量,那么他会提前进入到字符串池中,那么后续的intern的过程就会直接从字符串池中获取到这个字符串引用。 ...

March 22, 2026 · 2 min · santu

留言给博主