31.String 是 Java 基本数据类型吗?可以被继承吗?
String 是 Java 基本数据类型吗?
不是。
Java 中的基本数据类型只有 8 个:byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引⽤类型(reference type)。
String 是⼀个⽐较特殊的引⽤数据类型。
String 类可以继承吗?
不⾏。String 类使⽤ final 修饰,是所谓的不可变类,⽆法被继承。
在Java中,String被设计为不可变类。当我们提到"不可变",意味着一旦一个对象被实例化后,它的状态就无法再被改变。对于String对象而言,这就意味着无论你如何对String进行操作,如拼接、截取或者修改某个字符,实际上都是在生成一个新的String对象,而原对象不会被改变。
例如,当你执行如下代码时:
String str = "abcd";
str = str + "edl";
在这段代码执行后,原字符串"abcd"并不会被修改,而是会生成一个新的字符串"abcdedl",并且变量str将指向这个新的对象。
那么为什么Java中的String要设计成不可变的呢?主要有以下几个原因:
- 安全性:由于String的不可变性,我们不需要担心字符串在多线程环境下的安全性问题。因为无论如何都不会有一个线程去修改另一个线程正在使用的String对象。
- 缓存机制:由于String的不可变性,Java为了提高性能,会将相同的字符串对象缓存起来,这样可以节省内存空间并提高运行效率。
- 哈希值:String的不可变性确保了它的哈希值在创建后不会发生改变,这能够保证在一些基于哈希值的数据结构(如HashSet和HashMap)中的正确性和稳定性。
32.String 和 StringBuilder、StringBuffer 的区别?
- String:String 的值被创建后不能修改,任何对 String 的修改都会引发新的 String 对象的⽣成
- StringBuffer:跟 String 类似,但是值可以被修改,使⽤ synchronized 来保证线程安全。
- StringBuilder:StringBuffer 的⾮线程安全版本,性能上更⾼⼀些。
在Java中,String、StringBuffer和StringBuilder都被用来封装字符串,并提供了一系列的操作字符串在Java中,String、StringBuffer和StringBuilder都被用来封装字符串,并提供了一系列的操作字符串对象的方法。其中,String是不可变的,一旦一个String对象被实例化后,它的状态就无法再被改变。然而,当我们需要对字符串进行修改时,就需要使用到StringBuffer和StringBuilder类。
具体来说,StringBuffer和StringBuilder都具备可变性,允许我们对其内容进行修改。主要的区别在于,StringBuffer的方法是线程安全的,也就是说在任何时候只有一个线程能访问StringBuffer的方法。这一点使得StringBuffer在多线程环境下非常安全可靠。但是,由于其线程安全性,相对来说,其性能会略低于StringBuilder。
另一方面,StringBuilder的方法并不是线程安全的,不能同步访问。这也就意味着在多线程环境下使用StringBuilder需要额外的同步控制以防止并发问题。但是因为其非线程安全的特性,StringBuilder在执行速度上通常会比StringBuffer更优。
因此,在选择使用StringBuffer还是StringBuilder时,我们需要考虑到线程安全与性能之间的权衡。如果你的应用场景对线程安全有较高要求或者多线程环境下需要频繁进行字符串操作,那么建议使用StringBuffer;反之,如果追求更高的执行效率,并且不需要保证线程安全,那么可以选择使用StringBuilder。
33.String str1 = new String(“abc”)和 String str2 = “abc” 和 区别?
两个语句都会去字符串常量池中检查是否已经存在 “abc”,如果有则直接使⽤,如果没有则会在常量池
中创建 “abc” 对象。
但是不同的是,String str1 = new String(“abc”) 还会通过 new String() 在堆⾥创建⼀个 “abc” 字
符串对象实例。所以后者可以理解为被前者包含。
String s = new String(“abc”)创建了⼏个对象?
很明显,⼀个或两个。如果字符串常量池已经有“abc”,则是⼀个;否则,两个。
当字符创常量池没有 “abc”,此时会创建如下两个对象:
- ⼀个是字符串字⾯量 “abc” 所对应的、字符串常量池中的实例
- 另⼀个是通过 new String()创建并初始化的,内容与"abc"相同的实例,在堆中。
34.String 不是不可变类吗?字符串拼接是如何实现的?
String 的确是不可变的,“ + ”的拼接操作,其实是会⽣成新的对象。
例如:
String a = "hello " ;
String b = “world!” ;
String a b = a + b ;
在jdk1.8 之前,a 和 b 初始化时位于字符串常量池,ab 拼接后的对象位于堆中。经过拼接新⽣成了
String 对象。如果拼接多次,那么会⽣成多个中间对象。
内存如下:
在Java8 时JDK 对“+”号拼接进⾏了优化,上⾯所写的拼接⽅式会被优化为基于 StringBuilder 的
append ⽅法进⾏处理。Java 会在编译期对“+”号进⾏处理。
下⾯是通过 javap -verbose 命令反编译字节码的结果,很显然可以看到 StringBuilder 的创建和
append ⽅法的调⽤。
stack = 2 , locals = 4 , args_size = 1
0 : ldc # 2 // String hello
2 : astore_1
3 : ldc # 3 // String world!
5 : astore_2
6 : new # 4 // class java/lang/StringBuilder
9 : dup
1 0 : invokespecial # 5 // Method java/lang/StringBuilder."<init>":()V
1 3 : aload_1
1 4 : invokevirtual # 6 // Method java/lang/StringBuilder.append:
(Ljava/lang/String;)Ljava/lang/StringBuilder;
1 7 : aload_2
1 8 : invokevirtual # 6 // Method java/lang/StringBuilder.append:
(Ljava/lang/String;)Ljava/lang/StringBuilder;
2 1 : invokevirtual # 7 // Method java/lang/StringBuilder.toString:
()Ljava/lang/String;
2 4 : astore_3
2 5 : return
也就是说其实上⾯的代码其实相当于:
String a = "hello " ;
String b = "world!" ;
StringBuilder s b = new StringBuilder();
s b . append ( a ) ;
s b . append ( b ) ;
String a b = s b . toString ();
此时,如果再笼统的回答:通过加号拼接字符串会创建多个 String 对象,因此性能⽐ StringBuilder
差,就是错误的了。因为本质上加号拼接的效果最终经过编译器处理之后和 StringBuilder 是⼀致
的。
当然,循环⾥拼接还是建议⽤ StringBuilder,为什么,因为循环⼀次就会创建⼀个新的
StringBuilder 对象,⼤家可以⾃⾏实验。
35.intern ⽅法有什么作⽤?
JDK 源码⾥已经对这个⽅法进⾏了说明:
* < p >
* When the intern method i s invoked , i f the pool already contains a
* string equal t o this { @code String } object a s determined b y
* the { @link #equals( Object)} method , then the string from the pool i s
* returned . Otherwise , this { @code String } object i s added t o the
* pool and a reference t o this { @code String } object i s returned .
* < p >
意思也很好懂:
如果当前字符串内容存在于字符串常量池(即 equals()⽅法为 true,也就是内容⼀样),直接返
回字符串常量池中的字符串
否则,将此 String 对象添加到池中,并返回 String 对象的引⽤