Java 主要实践了面向对象的编程范式,在语言设计中有很多继承、多态和封装的考虑。
1.数据类型
基础数据类型都是小写命名的,例如 int,而对应的面向对象往往都有封装,例如 Integer。
当数据处理是一次性的,彼此没有联系时,适合使用基础类型,当数据需要被管理,在不同类中传递迭代有时还要做类型转换时,适合使用封装类型。从设计上看,基础类型比较高效,但传递过程是值传递的,管理中要注意使用方式。
基础类型和封装类型的区别:
- 基础类型按值传递,封装类型按引用传递;
- 基础类型一般存储在栈中,封装类型是对象,创建和存储一般在堆上;
方法中声明的变量是局部变量,调用方法时会为其创建一个方法栈,变量存放在方法栈中。如果是基本类型变量,变量名和值都存放在虚拟机栈中;如果是引用变量,声明的变量放在虚拟机栈中,变量所指对象放在堆内存中存储。
类中声明的变量是全局变量,放在堆中。如果是基本类型变量,变量名和值都放在堆内存中;如果是引用变量,声明的变量放在堆内存中,变量所指对象也存储在堆内存中。
举例int[] array = new int[]{1};,此时创建了一个对象array,对象中的一个元素是基础类型 1,此时对象和基础类型 1 都存储在堆内存中。
- 基础类型使用高效,封装类型有统一通用方法,减少开发成本;
基础类型分为三类
- 数值型
- 整数:byte, short, int, long
- 浮点:float, double
- 字符型:char
- 布尔型:boolean
| 基本类型 | 封装类型 | 占用字节数 | 缺省值 | 封装类型缓存池 |
|---|---|---|---|---|
| boolean | Boolean | 1 | false | JVM 编译期转换为 int,true 转换为 1,false 转换为 0 |
| byte | Byte | 8 | 0(byte) | [-128, 127] |
| char | Character | 16 | \u0000(空 "") | [0, 127](\u0000 to \u007F) |
| short | Short | 16 | 0(short) | [-128, 127] |
| int | Integer | 32 | 0 | [-128, 127](可以通过 VM 参数 -XX:AutoBoxCacheMax=200 修改缓存上限) |
| float | Float | 32 | 0.0f | - |
| long | Long | 64 | 0L | [-128, 127] |
| double | Double | 64 | 0.0d | - |
float 和 double 没有封装类型缓存池的原因是在一个范围中浮点型数值是无限的,无法缓存
在大数据场景下,为了提高精度和表达范围,java.math 提供了 BigDecimal 和 BigInteger 的类。
2.封装类型
创建方法:
- 通过封装类构造方法创建:变量名=new 封装类构造方法(参数);
Integer i = new Integer(2);
- 通过值转换方法创建:变量名=封装类.valueOf(参数);
Integer i = Integer.valueOf(2);
通用方法:
- compareTo()
数值上比较两个对象,等于返回
0,小于返回-1,大于返回1。
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 的 compareTo 源码。
- equals()
比较两个对象,
a.equals(b)当且仅当b非 null,且被比较的对象值相同时返回 true。
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
上面是 Integer 的 equals 源码,传入对象 obj 为 null 或者比较二者 value 不相等时都返回 false。
- toString() 将对象用字符串形式表示。
public static String toString(int i, int radix) {
if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
radix = 10;
/* Use the faster version */
if (radix == 10) {
return toString(i);
}
char buf[] = new char[33];
boolean negative = (i < 0);
int charPos = 32;
if (!negative) {
i = -i;
}
while (i <= -radix) {
buf[charPos--] = digits[-(i % radix)];
i = i / radix;
}
buf[charPos] = digits[-i];
if (negative) {
buf[--charPos] = '-';
}
return new String(buf, charPos, (33 - charPos));
}
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
static int stringSize(int x) {
for (int i=0; ; i++)
if (x <= sizeTable[i])
return i+1;
}
以上是 Integer 的 toString 源码,Integer 支持了进制表达的转换相对复杂,缺省是十进制转换,支持 二 到 三十六 进制。如果是十进制,使用 DigitOnes 和 DigitTens
构建字符数组,其他进制使用 digits 构建字符数组(0-9,a-z)
- parseXXX() 解析字符串参数。这里的源码实现比较复杂,不在此解析。
拆装箱: 用于在基础类型和封装类型之间转换,避免开发者关注类型转换细节降低开发速度。
- 装箱:通过调用包装器 valueOf 方法实现
Integer a = 10;自动创建了 Integer 封装类型将基础类型 10 封装在对象中 - 拆箱:通过调用包装器 xxxValue 方法实现
Integer a = 10; int i = a.intValue();将封装类型对象 a 的封装值 10 解析出来,赋值给基础类型 i。
拆箱过程要注意 null 值,这种情况找不到对象,无法取值。
3.类型转换
boolean 类型与其他 7 种基础类型数据间不能转换;
其他 7 种基础类型数据可以互相转换,过程中可能损失精度。转换排序 double > float > long > int > short > byte;
当较低顺位类型转换为较高顺位类型时,没有精度损失,可以自动转换 隐式,无需操作;
反之出现了精度损失,需要使用转换符操作 显示;
byte b;
int i = 128;
double d = 3.14;
b = (byte) i; // i 是 257,强制转换出现损失,b 变成 -128
i = (int) d; // d 是 3.14,强制转换出现损失,i 变成 3
4.缓存池
下面是各个基础类型对应封装类型的 Cache 实现源码
private static class ByteCache {
private ByteCache(){}
static final Byte cache[] = new Byte[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Byte((byte)(i - 128));
}
}
private static class CharacterCache {
private CharacterCache(){}
static final Character cache[] = new Character[127 + 1];
static {
for (int i = 0; i < cache.length; i++)
cache[i] = new Character((char)i);
}
}
private static class ShortCache {
private ShortCache(){}
static final Short cache[] = new Short[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Short((short)(i - 128));
}
}
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() {}
}
private static class LongCache {
private LongCache(){}
static final Long cache[] = new Long[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
下面举例缓存池对于程序使用的影响
Integer a1 = 12;
Integer a2 = 12;
Integer b1 = 129;
Integer b2 = 129;
System.out.println(a1 == a2); // true 缓存返回
System.out.println(b1 == b2); // false 未缓存,创建返回了不同的对象
5.总结
Java 提供 8 种基础数据结构,并对应支持了 8 种封装数据结构,提供通用方法以提高开发效率,实现自动装箱和拆箱,并缓存了一些封装数据结构的常用对象。