String
我们都知道 Java 中的 String 类的设计是不可变的,来看下 String 类的源码。
可以看出 String 类是 final 类型的,String 不能被继承。其值 value 也就是对字符数组的封装,即 char[],其值被定义成 private final 的,说明不能通过外界修改,即不可变。
来看下面例子:
都是String a 但是值变了,这其实是一个误区,从上面看 String 的结构可以得知字符串是由字符数组构成的,a只是一个引用而已,第一次引用了 "Java",后面变成了 "test",而 substring 也是用 Arrays.copyOfRange 方法重新复制字符数组构造了一个新的字符串,所以说,这里的字符串并不是可变,只是变更了字符串引用。
通过反射,我们改变了底层的字符数组的值,实现了字符串的 “不可变” 性,这是一种骚操作,不建议这么使用,违反了 Java 对 String 类的不可变设计原则,会造成一些安全问题,但由此看出String也不是百分百不变的。
第一种: String a = "wang";
第二种: String b = new String("wang");
案例如下:
用" "创建的a,c两个字符串,==和equals比较返回都为true,这是因为a,b都指向了方法区的同一个字符串。所以,当同样的一个字符串用" "重复创建时只在方法区创建一次。
用new创建的b,d两个字符串,equals为true很简单因为equals永远比较的是值,而==为false说明两个字符串的引用不一样。用new创建的字符串每次都会在JVM堆中创建,所以c,d都对应堆中的两个不同的字符串。
substring返回的是字符串索引位置beginIndex开始,endIndex-1结束的字符串。这个方法在jdk6,7是有差异的。
String背后是由char数组构成的,在JDK6中,String包含三个字段:char value[], int offset, int count,意思很简单。
输出:c
我用的JDK8,划线部分可以看出,这其实是新建了一个字符串
intern的返回值就是该常量在常量池中的地址
其实在 jdk1.7 之前(这里的运行环境是1.8),第一个是false的。那么是什么导致的呢?答案就是这个intern的实现方式。
在jdk1.7之前的版本,调用这个方法的时候,会去常量池中查看是否已经存在这个常量了,如果已经存在,那么直接返回这个常量在常量池中的地址值,如果不存在,则在常量池中创建一个,并返回其地址值。在JDK8中intern检测到这个常量在常量池中不存在的时候,不会直接在常量池中创建该对象了,而是将堆中的这个对象的引用直接存到常量池中,减少内存开销。
如果我们将第6行代码和第7行代码的顺序调换:
这个时候在str1.intern()的时候 ,常量池中已经存在了str2这个常量,直接把str2的引用赋值给str1.intern()。而第一个是把str1的堆内存引用赋值给了str2,因此str1 = = str2的。
+详解
Java字符串在+的时候做了什么?
1. 生成class文件 javac StringInternTest.java
2. 反编译class文件 javap -verbose -p StringInternTest.class
从上面红色部分可以看出常量池中会有wang 、hongxing 、wanghongxing
在这里我们可以清楚的看见+其实本质上是调用了StringBuilder的append方法。所以
str1 +"hongxing" = new StringBuilder.append(str1).append("hongxing")
既然是出来的对象,因此str2是存在了堆中的。
因此上面的.equals()是true而==是false
concat()方法
由以上例子可以得出结论
+可以是字符串或者数字及其他基本类型数据,而concat只能接收字符串。
+左右可以为null,concat为会空指针。
可变字符串StringBuffer 和 StringBuilder 的 3 个区别
区别1:线程安全
StringBuffer:线程安全,StringBuilder:线程不安全。因为 StringBuffer 的所有公开方法都是 synchronized 修饰的,而 StringBuilder 并没有 StringBuilder 修饰。
区别2:缓冲区
可以看出,StringBuffer 每次获取 toString 都会直接使用缓存区的 toStringCache 值来构造一个字符串。而 StringBuilder 则每次都需要复制一次字符数组,再构造一个字符串。所以,缓存冲这也是对 StringBuffer 的一个优化吧,不过 StringBuffer 的这个toString 方法仍然是同步的。
额外:
一般考察Java基础的时候,大多数会有== 和 equest()方法的区别,遇到这个知识点呢,大家把它分成两种类型来看就好了,分别是数和字符串
数字(Integer和int)
因为自动拆箱功能,Integer和int比较、int和new Integer比较就是int和int比较。
equest()方法好理解,就是判断两个值是否相等,接下来我们判断==情况
解释:因为 Integer变量 指向的是 java 常量池 中的对象(int对象也是,这也是第一句话的原因,只要值相等,true,true,true), 而 new Integer() 的变量指向 堆中 新建的对象,两者在内存中的地址不同.
Integer = 150这句话会被 编译成Integer.valueOf(150),由于不在缓存-128-127之间,那么它本质就是调用new出来的