数据类型
基本数据类型
数据类型 | 二进制位数 | 默认值 |
---|---|---|
byte | 8位 | 0 |
short | 16位 | 0 |
int | 32位 | 0 |
long | 64位 | 0L |
float | 32位 | 0.0f |
double | 64位 | 0.0d |
char | 16位 | ‘u0000’ |
boolean | ~ | false |
boolean只有两个值 true 和false,可以使用1bit 来存储,但是具体大小没有规定。JVM在编译时期,将boolean类型的数据转换为int类型,使用1来表示true,0表示false。JVM支持boolean数组,但是是通过读写byte数组来实现的。
包装类型
基本的数据类型对应的都有包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。
原始类型 | 对应的封装类 |
---|---|
byte | Byte |
short | Short |
char | Character |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
自动装箱与拆箱
自动装箱与拆箱
就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之,将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作自动装箱与拆箱,自动装箱与拆箱从JDK1.5开始引入。
自动装箱与拆箱的要点
- 自动装箱时,编译器调用
valueOf
将原始类型值转换成对象。同时自动拆箱时,编译器通过调用类似intValue()
,doubleValue()
这类的方法将对象转换成原始类型值。 - 自动装箱是将boolean值转换成Boolean对象,byte值转换成Byte对象,char转换成Character对象,float值转换成Float对象,int转换成Integer,long转换成Long,short转换成Short,自动拆箱则是相反的操作。
自动装箱实例:
Consider the following code:
List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50; i += 2)
li.add(i);
虽然我们将 i 定义为int基本类型(而不是Integer对象)添加到li,但是代码仍会编译,而不会报错。因为它会自动从i创建一个Integer对象并将该对象添加到 li 中。
编译器在运行时将原代码转换为以下代码:
List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50; i += 2)
li.add(Integer.valueOf(i));
缓存池
new Integer(123)
与 Integer.valueOf(123)
的区别在于:
- new Integer(123) 每次都会创建一个新的对象;
- Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。(Integer i = 123; 会触发自动装箱)
Integer x = new Integer(123);
Integer y = new Integer(123);
System.out.println(x == y); // false
Integer m = Integer.valueOf(123);
Integer n = Integer.valueOf(123);
System.out.println(m == n); // true
valueOf() 方法的实现,就是JVM先判断值是否在缓存池中,
- 如果在的话就直接返回缓存池中的内容;
- 如果不在,我们使用new Integer创建一个对象,并返回该对象的引用地址。
源码分析:
Integer.valueOf()中有个静态内部类IntegerCache
,里面有个常量cache[]
,也就是Integer常量池(其实就是缓存池技术,利用空间换时间的策略,也叫对象池),在常量池(对象池)中Integer已经默认创建了数值【-128-127】
的Integer缓存数据。
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;
……
}
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
基本类型对应的缓冲池:
- boolean values true and false
- all byte values
- short values between -128 and 127
- int values between -128 and 127
- char int the range \u0000 to \u007F
使用这些基本数据类型对应的包装类型时,如果该数值范围在缓冲池范围内,就可以直接使用缓冲池中的对象。
注意:最大值 127 可以通过 JVM 的启动参数 -XX:AutoBoxCacheMax=size 修改
String
源码分析
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
……
String被 final
修饰,因此它不可被继承。(Integer等包装类也不能被继承)
在Java 8中,String内部使用char数组
存储数据。
value数组 被声明为final ,也就是说value数组初始化之后就不能再引用其它数组。并且String 内部没有改变 value数组的方法,因此可以保证String不可变。
不可变的好处
- String Pool 的需要:
字符串池是方法区
中的特殊存储区。创建字符串时,如果该字符串已存在于字符串池中,则将返回现有字符串的引用,而不是创建新对象。
如果字符串是可变的,则使用一个引用更改字符串会导致其他引用的值错误。 - 缓存hash值
String 的hash值经常被用到,比如String 用作HashMap 的key。不可变的特性可以使得哈希值也不可变,因此只需要进行一次计算。这样更有效 - 安全性
- 线程安全:
String不可变性天生具备线程安全,可以在多个线程中安全地使用。
String、StringBuffer、StringBuilder
可变性 | 线程安全 | |
---|---|---|
String | 不可变 | 安全 |
StringBuffer | 可变 | 安全(内部使用 synchronized 进行同步) |
StringBuilder | 可变 | 不安全 |
String Pool
在Java语言中有八种基本数据类型和一种比较特殊得类型String
。这些类型为了使他们在运行的过程中速度更快,更节省内存,都提供了一种常量池
的概念。常量池就类似一个Java系统级别提供的缓存。
8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊,它的主要使用方法有两种:
- 直接使用
双引号
声明出来的String对象会直接存储在常量池中 - 如果不是用双引号声明的String对象,可以使用String提供的
intern方法
,intern方法会从字符串常量池中,查询当前字符串是否存在,若不存在,就会将当前字符串放入常量池中。
深入解析String#intern
字符串常量池 保存着所有字符串字面量(一个字符串字面量就是两个双引号之间的字符序列),这些字面量在编译时期就确定。还可以使用String的 intern方法在运行过程中将字符串添加到字符串常量池中。
当一个字符串调用 intern方法时,如果String Pool中已经存在一个字符串和该字符串相等(使用equals()方法判断),那么就会返回String Pool中字符串的引用;否则,就会在String Pool中添加一个新的字符串,并返回这个新字符串的引用。
//使用new String()创建两个不同的对象
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2); // false
//使用intern()方法获取同一个字符串引用
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4); // true
//使用字面量的形式创建字符串,会自动将字符串放入String Pool中
String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6); // true
new String(“abc”)
使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有"abc"字符串对象)
- "abc"属于字符串字面量,因此编译时期会在 String Pool中创建一个字符串对象,指向这个字符串字面量;
- 然后使用
new 的方式会在堆中创建一个字符串对象
。