jdk5.0重要的语言变化:泛型(Generics)、循环增强(Enhanced for Loop)、自动封箱(Autoboxing)和解箱(Unboxing)、类型安全的枚举(Typesafe Enums)、可变参数(Varargs)、静态导入(Static Import)、注解(Annotations)。
1. 泛型(Generics)
没有使用泛型的问题:
(1)从集合中获得元素时,必须进行类型转换,这个类型转换过程比较麻烦。
(2)类型转换是不安全的,可能在运行时发生类型转换失败。
鉴于以上问题,如果能够告诉编译器集合中的类型,让编译器加入类型转换功能,编译器会保证类型转换的成功。这里值得注意的是:泛型是提供给Javac编译器使用的。编译器在编译带泛型的集合时会去掉“类型”信息,使程序运行效率不受影响,也就是说编译生成的字节码不会带有泛型的类型信息。
未使用泛型的例子:
// Removes 4-letter words from c; elements must be strings
static void expurgate(Collection c) {
for (Iterator i = c.iterator(); i.hasNext();) {
String s = (String) i.next();
if (s.length() == 4) {
i.remove();
}
}
}
使用泛型的例子:
// Removes 4-letter words from c
static void expurgate(Collection<String> c) {
for (Iterator<String> i = c.iterator(); i.hasNext();) {
if (i.next().length() == 4) {
i.remove();
}
}
}
使用泛型后好处:
(1)更加清晰和安全。
(2)没有类型转换、额外的括号和临时变量。
(3)提供编译时的类型检查和消除类型转换。
2. 循环增强(Enhanced for Loop)
没有使用循环增强的问题:(1)遍历集合比较麻烦。
(2)Iterator通常只有在获取元素时才会用到。
(3)使用Iterator容易产生错误:Iterator变量在循环中会出现3次、通常的拷贝粘贴错误。
鉴于以上问题:如果能让编译器来处理Iterator,隐藏Iterator背后的细节。
一般访问集合的例子:
void cancelAll(Collection c) {
for (Iterator i = c.iterator(); i.hasNext();) {
TimerTask tt = (TimerTask) i.next();
tt.cancel();
}
}
使用循环增强的例子:
void cancelAll(Collection c) {
for (Object o : c) {
((TimerTask) o).cancel();
}
}
结合泛型的例子:
void cancelAll(Collection<TimerTask> c) {
for (TimerTask task : c) {
task.cancel();
}
}
对数组使用循环增强:
//Returns the sum of the elements of a
int sum(int[] a) {
int result = 0;
for (int i : a) {
result += i;
}
return result;
}
使用循环增强后的好处:
(1)代码更加简洁、清晰和安全。
(2)和Iterator无关,不可能使用错误的Iterator。
(3)消除使用数组索引的错误。
(4)代码准确表达它所要做的。
3. 自动封箱(Autoboxing)和解箱(Unboxing)
没有使用Autoboxing/Unboxing的问题:(1)不能将int放入集合,必须使用Integer。
(2)在获取时转换回来比较麻烦。
鉴于以上问题:如果能够让编译器做这些事不是更好?
一般的方法:
public class Freq {
private static final Integer ONE = new Integer(1);
public static void main(String[] args) {
// Maps word (String) to frequency (Integer)
Map m = new TreeMap();
for (int i = 0; i < args.length; i++) {
Integer freq = (Integer) m.get(args[i]);
m.put(args[i], (freq == null ? ONE : new Integer(
freq.intValue() + 1)));
}
System.out.println(m);
}
}
结合自动封箱、泛型和增强循环的例子:
public class Freq {
public static void main(String[] args) {
Map<String, Integer> m = new TreeMap<String, Integer>();
for (String word : args) {
Integer freq = m.get(word);
m.put(word, (freq == null ? 1 : freq + 1));
}
System.out.println(m);
}
}
使用Autoboxing/Unboxing的好处:
(1)Autoboxing:需要一种类型的对象时,这种基本类型就自动地封装到与它相同类型的包装中。
(2)Unboxing:需要一个值时,被装箱对象中的值就被自动地提取出来,不需要调用intValue()和doubleValue()等方法。
4. 类型安全的枚举(Typesafe Enums)
在之前的版本中,表示int枚举类型的标准方式:
// int Enum Pattern - has severe problems!
public static final int SEASON_WINTER = 0;
public static final int SEASON_SPRING = 1;
public static final int SEASON_SUMMER = 2;
public static final int SEASON_FALL = 3;
这样的方式存在的问题:
(1)不是类型安全的,你必须确保它是int。
(2)没有命名空间,没有常量字符串前缀来避免与其他int枚举类型冲突。
(3)脆弱性:常量被编译到客户程序中,如果常量改变,客户必须重新编译。
(4)打印出的值不提供详细信息,打印出来的值都是一个数字。
安全类型的Enum模式的例子:
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public final class Season implements Comparable, Serializable {
private final String name;
public String toString() {
return name;
}
private Season(String name) {
this.name = name;
}
public static final Season WINTER = new Season("winter");
public static final Season SPRING = new Season("spring");
public static final Season SUMMER = new Season("summer");
public static final Season FALL = new Season("fall");
private static int nextOrdinal = 0;
private final int ordinal = nextOrdinal++;
public int compareTo(Object o) {
return ordinal - ((Season) o).ordinal;
}
private static final Season[] PRIVATE_VALUES = { WINTER, SPRING, SUMMER, FALL };
public static final List VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
private Object readResolve() {
// Canonicalize
return PRIVATE_VALUES[ordinal];
}
}
以上方法的基本想法是:使用导出自定义类型的常量,不提供public构造方法,这样可以修正前面所有的缺点,还能够添加任意的方法、域变量,能够实现接口。缺点是代码冗余过长,容易出现错误:每个常量出现3次,不能在switch语句中使用。
鉴于以上问题:这些事情由编译器来处理。
安全类型的Enum结构:
(1)编译器支持安全类型的Enum模式.
(2)类似典型的Enum --> enum Season { WINTER, SPRING, SUMMER, FALL }
(3)能够在switch语句中使用。
(4)安全类型的Enum模式的所有优点。
结合泛型和增强循环的Enum例子:
import java.util.*;
public class Card {
public enum Rank { DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE }
public enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES }
private final Rank rank;
private final Suit suit;
private Card(Rank rank, Suit suit) {
this.rank = rank;
this.suit = suit;
}
public Rank rank() { return rank; }
public Suit suit() { return suit; }
public String toString() { return rank + " of " + suit; }
private static final List<Card> protoDeck = new ArrayList<Card>();
// Initialize prototype deck
static {
for (Suit suit : Suit.values())
for (Rank rank : Rank.values())
protoDeck.add(new Card(rank, suit));
}
public static ArrayList<Card> newDeck() {
return new ArrayList<Card>(protoDeck); // Return copy of prototype deck
}
}
5. 可变参数(Varargs)
在之前的版本中,编写具有任意数量的参数的方法,必须使用数组:
Object[] arguments = {
new Integer(7),
new Date(),
"a disturbance in the Force"
};
String result = MessageFormat.format("At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.", arguments);
创建和初始化数组是件麻烦的事情,鉴于以上问题:如果由编译器来实现不是更好?
使用可变参数(Varargs)的例子:
public static String format(String pattern, Object... arguments);
String result = MessageFormat.format("At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
7, new Date(), "a disturbance in the Force");
6. 静态导入(Static Import)
在之前版本中,为了访问静态成员,必须导入来自的类的引用。例如:
double r = Math.cos(Math.PI * theta);
现在你只需在静态导入,然后就可以直接在代码中使用静态方法和静态字段,例如:import static java.lang.Math.PI; 或者 import static java.lang.Math.*;就可以使用它们了:
double r = cos(PI * theta);
7. 注解(Annotations)
注解(Annotations),也叫元数据。代码级别的说明。与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。使用自定义的注解类型:
/**
* Describes the Request-For-Enhancement(RFE) that led
* to the presence of the annotated API element.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
@Documented
public @interface RequestForEnhancement {
int id();
String synopsis();
String engineer() default "[unassigned]";
String date() default "[unimplemented]";
}
一旦定义了注解类型,就可以使用该注解了。我们可以看到这个注解类型本身也被注解了,这种注解叫做元注释。
@Retention(RetentionPolicy.RUNTIME)表示这种类型的注解被JVM保留从而使其能够通过反射在运行时读取。
@Target(ElementType.METHOD)表示这种注解只能用来注解在方法上。
@Inherited表示该注解可以被子类继承,默认不可继承。
@Documented表示javadoc自动生成文档时,也包含该注解,默认不包含。
按照惯例,注解应该写在其他修饰符的前面,例如:
@RequestForEnhancement(id = 2868724, synopsis = "Enable time-travel", engineer = "Mr. Peabody", date = "4/1/3007")
public static void travelThroughTime(Date destination) { //... }