包装类
包装类基本知识
Java中八种基本数据类型并不是对象,是为了将基本数据类型和对象之间实现互相转化,Java为每一个基本数据类型提供了相应的包装类。
JAVA是面向对象的语言,但是并不是“纯面向对象”的,因为经常用到的基本数据类型就不是对象,在实际的操作中需要将基本数据类型转化成为对象,以便于操作。
Java在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类。
在八个基本数据类型中,除了Integer和Character,其它六个的类名和基本数据类型一致,首字母大写
Number类是抽象类,因此它的抽象方法,所有子类都需要提供实现。Number类提供了抽象方法,如图。表明了所有的“数字型”包装类都可以相互转型。
包装类的用途:
1、作为和基本数据类型对应的类型存在,方便涉及到对象的操作,如Object[],集合等等
2、包含每种基本数据类型的相关属性如最大值,最小值等。
自动装箱和拆箱
自动装箱(autoboxing)和拆箱(unboxing),将基本数据类型和包装类自动转换。
自动装箱
基本类型的数据处于需要对象的环境中时,会自动转为“对象”。
如Integer i = 10;编译器会自动的转成Integer i = Integer.valueOf(10),这就是JAVA的自动装箱。
自动拆箱
需要一个值的时候,对象会自动的转成基本数据类型,没有必要再去显式调用intValue()、doubleValue()等转型方法。
Integer i = Integer.valueOf(10); int j = i;编译器会自动转成:int j = i.intValue();这个过程就是自动拆箱。
包装类的缓存问题
整型和char类型对应的包装类,在自动装箱时,对于-128~127之间的值会进行缓存处理,目的是为了提高效率。
缓存的原理:如果数据在-128~127这个区间,类在加载的时候就已经为该区间的每个数值创建了对象,并将这256个对象存放到一个名为cache的数组中,每当自动装箱过程发生时(或者手动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);
}
/**
1. IntegerCache类为Integer类的一个静态内部类,仅供Integer类使用。由下面的源码我们可以看到,静态代码块的目的就是初始化数组cache的,这个过程
会在类加载时完成
2. 一般情况下 IntegerCache.low为-128,IntegerCache.high为127,
*/
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 Integer()方法。
- 自动拆箱调用的是xxxValueOf()方法。
- 包装类在自动装箱时为了提高效率,对于-128~127之间的值会进行缓存处理,超过范围后,对象之间不能再使用==进行数值比较,应该使用equals方法。
字符串类
String类代表不可变的字符序列(final),StringBuilder和StringBuffer类代表可变字符序列。
String类对象是不可变的Unicode字符序列,不可变对象指的是,对象内部的成员变量的值无法再修改。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/**
字符串内容全部存储到 value[ ]数组中,而变量 value 是 final 类型的,也就
是常量(即只能被赋值一次)。 这就是“不可变对象”的典型定义方式
*/
}
String 类的简单使用
/**
substring()是对字符串的截取操作,
但本质是读取原字符串内容生成了新的字符串
*/
public static void main(String[ ] args) {
String s1 = new String("abcdef");
String s2 = s1.substring(2, 4);
// 打印:ab199863
System.out.println(Integer.toHexString(s1.hashCode()));
// 打印:c61, 显然 s1 和 s2 不是同一个对象
System.out.println(Integer.toHexString(s2.hashCode()));
}
/**
在遇到字符串常量之间的拼接时,编译器会做出优化,即在编译期间就会完成字符串的
拼接。
*/
public static void main(String[ ] args) {
//编译器做了优化,直接在编译的时候将字符串进行拼接
String str1 = "hello" + " java";//相当于 str1 = "hello java";
String str2 = "hello java";
System.out.println(str1 == str2);//true
String str3 = "hello";
String str4 = " java";
//编译的时候不知道变量中存储的是什么,所以没办法在编译的时候优化
String str5 = str3 + str4;
System.out.println(str2 == str5);//false
}
StringBuffer和StringBuilder
StringBuffer和StringBuilder都是可变的
- StringBuffer 线程安全,做线程同步检查,效率较低
- StringBuilder 线程不安全,不做线程同步检查,效率较高。推荐使用
常用的方法
重载的 **public **StringBuilder append(…)方法
可以为该 StringBuilder 对象添加字符序列,仍然返回自身对象。
方法 **public **StringBuilder delete(**int **start,**int **end)
可以删除从 start 开始到 end-1 为止的一段字符序列,仍然返回自身对象。
方法 **public **StringBuilder deleteCharAt(**int **index)
移除此序列指定位置上的 char,仍然返回自身对象。
重载的 **public **StringBuilder insert(…)方法
可以为该 StringBuilder 对象在指定位置插入字符序列,仍然返回自身对象。
方法 **public **StringBuilder reverse()
用于将字符序列逆序,仍然返回自身对象。
方法 **public **String toString() 返回此序列中数据的字符串表示形式。
和 String 类含义类似的方法
public static void main(String[ ] args) {
/**StringBuilder*/
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 7; i++) {
sb.append((char) ('a' + i));//追加单个字符
}
System.out.println(sb.toString());//转换成 String 输出
sb.append(", I like learning");//追加字符串
System.out.println(sb.toString());
/**StringBuffer,下面的方法同样适用 StringBuilder*/
StringBuffer sb2 = new StringBuffer("hello");
sb2.insert(0, "好").insert(0, "你");//插入字符串
System.out.println(sb2);
sb2.delete(0, 2);//删除子字符串
System.out.println(sb2);
sb2.deleteCharAt(0).deleteCharAt(0);//删除某个字符
System.out.println(sb2.charAt(0));//获取某个字符
System.out.println(sb2.reverse());//字符串逆序
}
总结
String经过初始化后,就不会改变其内容了,对String字符串的操作实际上是对其副本(原始拷贝)的操作,原来的字符串不会改变。如:String s = "a"; s = s + "b";
实际上是原来的“a”字符串已经丢弃了,重新产生一个字符串s + “b”(“ab”)。如果多次执行这些改变,会导致大量副本字符串对象存留在内存中,降低效率,如果这种操作放在循环中,会极大的影响程序的时间和空间性能。甚至会造成服务器的浪费。而StringBuilder和StringBuffer类是对原始字符串本身操作的,可以对字符串进行修改而不产生副本或者减少生成副本。
枚举
JDK1.5引入了枚举类型,枚举类型的定义包括枚举声明和枚举体。
所有的枚举类型隐性地继承自 java.lang.Enum。枚举实质上还是类!而每个被枚举的成员实质就是一个枚举类型的实例,他们默认都是 public static final 修饰的。可以直接通过枚举类型名使用它们
public enum Season {
SPRING,
SUMMER,
AUTUMN,
WINTER
}
需要定义一组常量时,可以使用枚举类型。
尽量不要使用枚举的高级特性,事实上高级特性都可以使用普通类来实现,没有必要引入枚举,增加程序的复杂性!