Java中基础数据类型与它们对应的包装类见下表:
原始类型 | 包装类型 |
---|---|
boolean | Boolean |
byte | Byte |
char | Character |
float | Float |
int | Integer |
long | Long |
short | Short |
double | Double |
装箱:将基础类型转化为包装类型。
拆箱:将包装类型转化为基础类型。
当基础类型与它们的包装类有如下几种情况时,编译器会自动帮我们进行装箱或拆箱:
示例代码:
Integer x = 1; // 装箱 调⽤ Integer.valueOf(1)
int y = x; // 拆箱 调⽤了 X.intValue()
看一道常见的面试题:
Integer a = 100;
Integer b = 100;
System.out.println(a == b);
Integer c = 200;
Integer d = 200;
System.out.println(c == d);
输出:
true
false
为什么第三个输出是false?看看 Integer 类的源码。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
Integer c = 200;
会调用 调⽤Integer.valueOf(200)
。而从Integer的valueOf()源码可以看到,这里的实现并不是简单的new Integer,而是用IntegerCache做一个cache。
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
}
...
}
这是IntegerCache静态代码块中的一段,默认Integer cache 的下限是-128,上限默认127。当赋值100给Integer时,刚好在这个范围内,所以从cache中取对应的Integer并返回,所以a和b返回的是同一个对象,所以==
比较是相等的,当赋值200给Integer时,不在cache 的范围内,所以会new Integer并返回,当然==
比较的结果是不相等的。
先看看什么是不可变的对象。
如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。
接着来看Java8 String类的源码:
public final class String
implements java.io.Serializable, Comparable, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
}
从源码可以看出,String对象其实在内部就是一个个字符,存储在这个value数组里面的。
value数组用final修饰,final 修饰的变量,值不能被修改。因此value不可以指向其他对象。
String类内部所有的字段都是私有的,也就是被private修饰。而且String没有对外提供修改内部状态的方法,因此value数组不能改变。
所以,String是不可变的。
那为什么String要设计成不可变的?
主要有以下几点原因:
既然我们的String是不可变的,它内部还有很多substring, replace, replaceAll这些操作的方法。这些方法好像会改变String对象?怎么解释呢?
其实不是的,我们每次调用replace等方法,其实会在堆内存中创建了一个新的对象。然后其value数组引用指向不同的对象。
主要是为了节约String占用的内存。
在大部分Java程序的堆内存中,String占用的空间最大,并且绝大多数String只有Latin-1字符,这些Latin-1字符只需要1个字节就够了。
而在JDK9之前,JVM因为String使用char数组存储,每个char占2个字节,所以即使字符串只需要1字节,它也要按照2字节进行分配,浪费了一半的内存空间。
到了JDK9之后,对于每个字符串,会先判断它是不是只有Latin-1字符,如果是,就按照1字节的规格进行分配内存,如果不是,就按照2字节的规格进行分配,这样便提高了内存使用率,同时GC次数也会减少,提升效率。
不过Latin-1编码集支持的字符有限,比如不支持中文字符,因此对于中文字符串,用的是UTF16编码(两个字节),所以用byte[]和char[]实现没什么区别。
1. 可变性
2. 线程安全
StringJoiner是 Java 8 新增的一个 API,它基于 StringBuilder 实现,用于实现对字符串之间通过分隔符拼接的场景。
StringJoiner 有两个构造方法,第一个构造要求依次传入分隔符、前缀和后缀。第二个构造则只要求传入分隔符即可(前缀和后缀默认为空字符串)。
StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
StringJoiner(CharSequence delimiter)
有些字符串拼接场景,使用 StringBuffer 或 StringBuilder 则显得比较繁琐。
比如下面的例子:
List values = Arrays.asList(1, 3, 5);
StringBuilder sb = new StringBuilder("(");
for (int i = 0; i < values.size(); i++) {
sb.append(values.get(i));
if (i != values.size() -1) {
sb.append(",");
}
}
sb.append(")");
而通过StringJoiner来实现拼接List的各个元素,代码看起来更加简洁。
List values = Arrays.asList(1, 3, 5);
StringJoiner sj = new StringJoiner(",", "(", ")");
for (Integer value : values) {
sj.add(value.toString());
}
另外,像平时经常使用的Collectors.joining(","),底层就是通过StringJoiner实现的。
源码如下:
public static Collector joining(
CharSequence delimiter,CharSequence prefix,CharSequence suffix) {
return new CollectorImpl<>(
() -> new StringJoiner(delimiter, prefix, suffix),
StringJoiner::add, StringJoiner::merge,
StringJoiner::toString, CH_NOID);
}
使用这种方式会创建两个字符串对象(前提是字符串常量池中没有 "dabin" 这个字符串对象)。
字符串常量池(String Pool)保存着所有字符串字面量,这些字面量在编译时期就确定。字符串常量池位于堆内存中,专门用来存储字符串常量。在创建字符串时,JVM首先会检查字符串常量池,如果该字符串已经存在池中,则返回其引用,如果不存在,则创建此字符串并放入池中,并返回其引用。
String类提供了一个length方法,返回值为int类型,而int的取值上限为2^31 -1。
所以理论上String的最大长度为2^31 -1。
达到这个长度的话需要多大的内存吗?
String内部是使用一个char数组来维护字符序列的,一个char占用两个字节。如果说String最大长度是2^31 -1的话,那么最大的字符串占用内存空间约等于4GB。
也就是说,我们需要有大于4GB的JVM运行内存才行。
那String一般都存储在JVM的哪块区域呢?
字符串在JVM中的存储分两种情况,一种是String对象,存储在JVM的堆栈中。一种是字符串常量,存储在常量池里面。
什么情况下字符串会存储在常量池呢?
当通过字面量进行字符串声明时,比如String s = "程序新大彬";,这个字符串在编译之后会以常量的形式进入到常量池。
那常量池中的字符串最大长度是2^31-1吗?
不是的,常量池对String的长度是有另外限制的。。Java中的UTF-8编码的Unicode字符串在常量池中以CONSTANT_Utf8类型表示。
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
length在这里就是代表字符串的长度,length的类型是u2,u2是无符号的16位整数,也就是说最大长度可以做到2^16-1 即 65535。
不过javac编译器做了限制,需要length < 65535。所以字符串常量在常量池中的最大长度是65535 - 1 = 65534。
最后总结一下:
String在不同的状态下,具有不同的长度限制。