Java语言中虽然提供了byte、short、int、long、char、boolean、float、double这些基本数据类型,但由于他们并不是对象,因此有些操作会受到限制,比如不能将其存入容器中等等。为此Java在java.lang包中对于上述每一种基本类型都提供了一个对应的封装类(Wrapper),这些包装类中有一个不可变的对应基本类型的成员变量(也意味着一旦设定就没法更改了)并提供了若干方法。
仍然以Integer为例,看看Wrapper类型都提供了哪些功能:
1.字符串与基本数据类型的互转:XX.parseXX方法与XX.toString(XX)静态方法
需要注意的是,待转换的字符串必须符合对应类型的规定,否则在运行期会抛出解析错误的异常:
public static void main(String[] args) {
int n1 = Integer.parseInt("666666");
int n2 = Integer.parseInt("-666666");
int n3 = Integer.parseInt(" 2333333");
System.out.println(n1);
System.out.println(n2);
}
在这段代码中,n1和n2能被正常解析出来,但在执行Integer.parseInt(” 2333333”)时,由于输入的字符串中包含空格,其不能解释为数字,所以发生异常:
Exception in thread "main" java.lang.NumberFormatException: For input string: " 2333333"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:569)
at java.lang.Integer.parseInt(Integer.java:615)
at CMain.main(CMain.java:11)
此外,Integer类还有一个重载的parseInt方法,输入的是字符串和进制数,字符串里的内容会被按照对应的进制数解析,如下列代码的运行结果就为16:
public static void main(String[] args) {
int n1 = Integer.parseInt("1100", 2);
System.out.println(n1);
}
另外,将基本数据类型转换为字符串的方法之前的文章已经提到过了,直接用”” + 的方式即可,即用空串连接上一个基本数据类型即可。当然,也可以选择使用Integer.toString(int)方法,该方法为静态方法,会返回对应基本类型的字符串表示。
public static void main(String[] args) {
String s1 = Integer.toString(23);
String s2 = Integer.toString(23, 2);
String s3 = "" + 233;
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}
//对应的结果分别是:
//23
//10111
//233
对于Integer类型,还有如下静态方法可供调用:
- 进制转换toBinaryString(int)方法
- toHexString(int)
- toOctalString(int)
2.构造方法
以int类型对应的Integer封装类为例,其源码中有如下片段:
public final class Integer extends Number implements Comparable<Integer> {
//......
private final int value;
public Integer(int value) {
this.value = value;
}
//......
public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
}
}
从中可以看出如下几点:
1. Wrapper类为final类型
2. 其中封装的基本类型也标明为final,意味着对象一旦创建,其表征的值就不能再被更改了
3. 可以用基本数据类型或字符串的方式构建一个对应的Wrapper对象
3.intValue方法:返回封装的基本数据类型。
public static void main(String[] args) {
Integer n1 = new Integer("23333");
int n = n1.intValue();
System.out.println(n);
}
4.MAX_VALUE与MIN_VALUE静态成员变量:最大值与最小值
这一点没有太多要说的,直接看Integer类的源码就知道了:
@Native public static final int MIN_VALUE = 0x80000000;
@Native public static final int MAX_VALUE = 0x7fffffff;
5.自动装箱与自动拆箱——带着坑的语法糖
- 自动装箱:将基本数据类型赋值给Wrapper类,下面两条语句其实是等价的。
自动拆箱:将Wrapper类中的数据返回给基本数据类型
public static void main(String[] args) { Integer n1 = Integer.valueOf(333); Integer n2 = 333; }
正是因为有了javac编译器的自动装拆箱,才使得我们可能写出这样的代码:
ArrayList arrayInteger = new ArrayList<Integer>;
arrayInteger.add(13);
Integer n1 = 32;
n1 = n1 + 233;
这里的13在加入arrayInteger中时会自动装箱,成为Integer类型。同理,n1也经过了自动装箱,成为了Integer类型。而当执行n1 + 233操作时,有需要对n1进行自动拆箱,加法运算结束后再通过自动装箱重新变回了Integer类型。
但是也需要注意,这种自动装拆箱机制是有弊端的:
每一次装箱都伴随着对象的创建,会耗费时空资源
对null的Wrapper类对象执行自动拆箱,会引发空引用异常。所以,尽量不要把Wrapper类对象引用设为null,宁可让其值为0。
Integer n3 = null; n3 = n3 + 233; /*上述代码执行时将引发如下异常: Exception in thread "main" java.lang.NullPointerException at CMain.main(CMain.java:12)*/
Wrapper类型对象的值缓存机制
Integer类的valueOf静态成员方法源码如下:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
而其中的IntegerCache是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; 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; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }
可以看出,在该类中确实有缓存,且该缓存至少覆盖了[-128, 127]这个区间范围。所以,当自动装箱机制使用valueOf()方法时,在该区间范围内的数变会使用缓存里的实例,返回的也是这些缓存实例的引用。但是要注意,如果不使用自动装箱机制,而是直接new Integer(2)之类的方法创建对象,则不会有任何的缓存机制。
这点我印象十分深刻:曾经在做一个项目的时候,没有看清楚其Bean类中用的是Integer而非int,结果单测时用的数据是小于127的,没有发现问题。上线后,使用一段时间后用户反馈出现问题,检查后分析就是栽在了这个所谓的缓存机制上。
public static void main(String[] args) { Integer n1 = 127; Integer n2 = 127; System.out.println(n1 == n2); System.out.println(n1.equals(n2)); System.out.println("---------------------------------"); Integer n3 = 128; Integer n4 = 128; System.out.println(n3 == n4); System.out.println(n3.equals(n4)); System.out.println("---------------------------------"); Integer n5 = new Integer(127); Integer n6 = new Integer(127); System.out.println(n5 == n6); System.out.println(n5.equals(n6)); }
三组测试的结果分别是:
true true false true false true
所以,在Java中,对127以内的Byte、Short、Integer、Long对应的基本数据类型进行自动装箱,会从内建的缓存数组中引用同一个对象,而从128开始则不再使用这种缓存机制。另外这种缓存机制仅限于自动装箱的情况,对于显示new出的对象则无此机制。