目录
包装类
包装类 | 基本类 | 说明 |
---|---|---|
Byte | byte | |
Short | short | |
Integer | int | int i = 125; Integer intObj = Integer.valueOf(i); int i2 = intObj.intValue(); |
Long | long | |
Float | float | |
Double | double | |
Boolean | boolean | |
Character | char |
包装类都实现了Serializable和Comparable接口;前六种数值型包装类还继承了Number类。
public abstract class Number implements java.io.Serializable
public final class Integer extends Number implements Comparable<Integer>
public final class Character implements java.io.Serializable, Comparable<Character>
public final class Boolean implements java.io.Serializable, Comparable<Boolean>
以Integer为例剖析包装类
- 不可变性
实例对象一旦创建即不可修改,通过以下方式实现:
a. 包装类定义为final的,不能被继承;
b. 包装类中包含一个与之匹配的基本类型,但是该类型是私有的且声明为final;
c. 没有声明setter方法;
private final int value;
不可变使得程序更为简单,避免了线程安全问题。
- 常量、缓存池、valueOf方法
包装类中定义了一些比较特别的静态变量,如数值型包装类Integer定义了最值;
@Native public static final int MIN_VALUE = 0x80000000;
@Native public static final int MAX_VALUE = 0x7fffffff;
除Float和Double外,其他包装类缓存包装类对象,如Integer中有静态内部类IntegerCache,默认缓存-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;
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() {}
}
再来看valueOf方法,当基础类数值位于缓存区时,返回的是缓存的包装类对象,否则new一个新的包装类对象缓存。因此,尽量使用valueOf方法而非new包装类实例,来获取包装类对象。
这种共享缓存对象的方式,可以节省内存空间,由于Integer具有不可变性,所以可以安全的共享。包装类这种共享对象的设计思路即为享元模式。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
- 继承Number
六种数值型包装类继承了Number抽象类,其定义了以上6种获取值的方法,可以将任意类型的基础类型转为目标基础类型。具体包装类重写这些方法时,就是将其基础类数值做了强制类型转换,如:
public short shortValue() {
return (short)value;
}
- 重写Object的方法
每种包装类都重写了根父类Object的equals、hashCode、toString方法,以Integer为例:
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
public int hashCode() {
return Integer.hashCode(value);
}
public static int hashCode(int value) {
return value;
}
Float和Double将其value值分别转换为int和long进行比较,以及计算hashCode。
- 包装类和String
除了重写Object的toString()方法,除Character外的其他包装类都有
静态的valueOf(String)方法,根据字符串返回包装类实例;
静态的parseInteger(String)方法,根据字符串返回基础类型值;
静态的toString(int)方法,根据基础类型值返回字符串;
- 实现Comparable
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
Integer中的二进制运算
位翻转
有两个位运算方法:按byte反转reverseBytes和按bit反转reverse;
public static int reverseBytes(int i) {
return ((i >>> 24) ) |
((i >> 8) & 0xFF00) |
((i << 8) & 0xFF0000) |
((i << 24));
}
以i=0x12345678为例,按byte反转结果为0x78563412;
① i >>> 24结果为0x00000012;
② i >> 8结果为0x00123456,再和0xFF00按位与,结果为0x00003400;
③ i << 8结果为0x34567800,在和0xFF0000按位与,结果为0x00560000;
④ i << 24结果为0x78000000;
⑤ 以上结果按位或运算,即为结果0x78563412.
public static int reverse(int i) {
// HD, Figure 7-1
i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;
i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;
i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;
i = (i << 24) | ((i & 0xff00) << 8) |
((i >>> 8) & 0xff00) | (i >>> 24);
return i;
}
实现思路:32bit的Integer实例——先交换相邻位;再交换相邻两位;再交换相邻四位;再交换相邻8位;再交换相邻16位。
也适用于十进制,如8个十进制数12345678进行按位反转,只需要反转到4位相邻互换;那么包装类Long的按位反转,需要执行到交换相邻32位。
① 相邻位交换:21436587;
② 相邻两位交换:43218765;
③ 相邻四位交换:87654321;
代码剖析:
① 0x55555555 = 0101 0101 0101 0101 0101 0101 0101 0101; 跟i按位与之后相当于提取到奇数位bit值;然后左移一位; i右移一位后与0x55555555按位与,相当于提取到偶数位bit值;再按位或即实现了相邻位交换;
② 0x33333333= 0011 0011 0011 0011 0011 0011 0011 0011; 思路与步骤①类似;
③ 0x0f0f0f0f= 0000 1111 0000 1111 0000 1111 0000 1111;同上;
④ 8bit交换时,变成了按字节反转。
CPU可以高效的实现位运算,要提升代码性能,利用该特性设计算法是必要的。
循环移位
循环左移和循环右移如下,需要注意真实的移位数等于(移位数 & (数值bit位数 - 1)),Integer.rotateLeft代码中"-distance"按位与 “0001 1111”,假如distance等于8,即1111 1111 1111 1111 1111 1111 1111 1000 & 0001 1111 = 11000 ,即十进制24;
public static int rotateLeft(int i, int distance) {
return (i << distance) | (i >>> -distance);
}
public static int rotateRight(int i, int distance) {
return (i >>> distance) | (i << -distance);
}
String
与包装类很类似,都具有不可变性、常量字符串、重写hashCode等。
不可变性
以一个final的内部数组表示字符串,一旦被赋值则不可修改。
String中的大部分方法是在操作这个字符数组,而且基本是通过新new String对象的方式实现的,如concat最后返回了一个新String对象。
对于频繁修改字符串的场景,每次修改都需要新建字符串,性能太低,要考虑使用StringBuilder和StringBuffer(线程安全)。
//String:
private final char value[];
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length); // 返回一个新的数组
}
//字符串拼接
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen); //获取一个字符数组buf,长度为len+otherLen,但是前len个字符与value相同
str.getChars(buf, len); //从字符的len下标开始,将str的chars数组复制到buf中
return new String(buf, true);//此处share置为true,将buf直接赋值给value,不再new新的字符数组。
}
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
//Arrays:
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
//将original内的元素从下标0开始,复制进copy数组从0开始的位置,复制的长度位指定的长度
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
字符串常量池
字符串常量在Java中相当于String实例,可以直接赋值给String变量,还可以直接调用String的各种方法。
在内存中,它们被放在一个共享的常量池内。当通过常量的形式使用字符串时,会使用该常量池内对应的String实例。
String内的intern()方法可以确保优先使用字符串常量池内的实例,如果没有则先将该实例添加到常量池。
public static void main(String[] args) {
String a = "lala";
String b = "lala";
String c = new String("lala");//不建议如此赋值
String d = new String("lala").intern();//多此一举
System.out.println(a == b);
System.out.println(c == b);
System.out.println(d == b);
}
true
false
true
hashCode
由于String具有不可变性,其hash值也就不可变。因此String中缓存了hash值。
Java中普遍使用类似的方式计算hash值。这样可以使hash与字符串中每个字符相关,并与字符所在的位置相关。
private int hash; // Default to 0
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
StringBuilder和StringBuffer
二者的结构和实现基本一致,只是StringBuffer中的append、insert、delete等方法采用synchronized进行了修饰,实现了线程安全。
来看AbstractStringBuilder类:
char[] value;//以一个非final的字符数组存储数据
int count; //记录字符数组value中真实存储的数据容量
AbstractStringBuilder() {}
// 指定value初始长度
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
public int length() {return count;}
public int capacity() {return value.length;}
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len); //确认并保障value的空间足以添加str
str.getChars(0, len, value, count); //将str的[0,len)下标的字符复制到value以count下标开始的位置
count += len;//value中有效字符数变更为count+len
return this;
}
public AbstractStringBuilder insert(int offset, String str) {
if ((offset < 0) || (offset > length()))
throw new StringIndexOutOfBoundsException(offset);
if (str == null)
str = "null";
int len = str.length();
ensureCapacityInternal(count + len);//确保数组容量足够
//将value从offset下标开始的(count-offset,即offset后所有)字符,复制到value的offset+len下标开始的位置,即向后移位了len个字符
System.arraycopy(value, offset, value, offset + len, count - offset);
//将str的字符复制到value的offset下标开始的位置,共len个字符
str.getChars(value, offset);
count += len;
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {//如果value空间不够大,则将value复制到新长度的数组,并赋值给value
value = Arrays.copyOf(value, newCapacity(minimumCapacity));
}
}
private int newCapacity(int minCapacity) {//扩容长度的确定,入参为 原数组长度+要添加的字符串长度
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;//当前字符数组长度*2+2;加2是为了长度为0时仍可用
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity; //取入参和成倍增长长度较大者,
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity) : newCapacity; //基本返回newCapacity
}
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
上述分析的数组容量扩展方式是指数扩展方式,应用于不知道最终需要多长的情况下,应用比较广泛。折中了两个方面的考虑:① 避免浪费空间;② 减少扩容频率,即内存分配次数。
StringBuilder类默认生成16个字符的字符数组。
public StringBuilder() {
super(16);
}
关于String的+和+=运算
Java编译器会转换为new StringBuilder,并在此基础上进行append操作。
但是当+或+=位于循环体内,每一次循环都会new一个新的StringBuilder对象,对造成对象过多,增加内存消耗。
因此需要在循环体外声明StringBuilder,并在循环内进行append操作。