· 泛型
· 增强的for循环
· 自动装箱和自动拆?/p>
· cd安全的枚?/p>
· 可变长度参数
· 静态引?/p>
· 元数?注解)
· C风格的格式化输出
q当中,泛型、枚丑֒注解可能会占用较大的幅Q而其余的因ؓ用法直截了当Q抑或相对简单,我就E作介绍Q剩下的留给读者去思考、去探烦了?/p>
1.4. 泛型
泛型q个题目相当大,大到完全可以p个话题写一本书。有关Java是否需要泛型和如何实现泛型的讨Z早就在JavaCqؓ传。终于,我们在J2SE(TM) 5.0中看C它。也许目前JavaҎ型的支持q算不上_理想Q但q一Ҏ的d也经以让我们欣喜一阵了?/p>
在接下来的介l中Q我们会了解刎ͼJava的泛型虽然跟C++的泛型看上去十分怼Q但其实有着相当大的区别Q有些细节的东西也相当复?臛_很多地方会跟我们的直觉背道而驰)。可以这栯Q泛型的引入在很大程度上增加了Java语言的复杂度Q对初学者尤其是个挑战。下面我们将一点一点往里挖?/p>
首先我们来看一个简单的使用泛型cȝ例子Q?/p>
ArrayList<Integer> aList = new ArrayList<Integer>();
aList.add(new Integer(1));
// ...
Integer myInteger = aList.get(0);
我们可以看到Q在q个单的例子中,我们在定义aList的时候指明了它是一个直接受Integercd的ArrayListQ当我们调用aList.get(0)Ӟ我们已经不再需要先昑ּ的将l果转换成IntegerQ然后再赋值给myInteger了。而这一步在早先的Java版本中是必须的。也怽在想Q在使用Collection时节U一些类型{换就是Java泛型的全部吗?q不止。单p个例子而言Q泛型至还有一个更大的好处Q那是使用了泛型的容器cd得更加健壮:早先QCollection接口的get()和Iterator接口的next()Ҏ都只能返回Objectcd的结果,我们可以把这个结果强制{换成MObject的子c,而不会有M~译期的错误Q但q显然很可能带来严重的运行期错误Q因为在代码中确定从某个Collection中取出的是什么类型的对象完全是调用者自p了算Q而调用者也许ƈ不清楚放qCollection的对象具体是什么类?q知道放进ȝ对象“应该”是什么类Q也不能保证攑ֈCollection的对象就一定是那个cȝ实例。现在有了泛型,只要我们定义的时候指明该Collection接受哪种cd的对象,~译器可以帮我们避免cM的问题溜C品中。我们在实际工作中其实已l看C太多的ClassCastExceptionQ不是吗?
泛型的用从q个例子看也是相当易懂。我们在定义ArrayListӞ通过cd后面?lt;>括号中的值指定这个ArrayList接受的对象类型。在~译的时候,q个ArrayList会被处理成只接受该类或其子类的对象,于是M试图其他类型的对象dq来的语句都会被~译器拒l?/p>
那么泛型是怎样定义的呢?看看下面q一D늤例代码:(其中用E代替在实际中会使用的类名,当然你也可以使用别的名称Q习惯上在这里用大写的EQ表CCollection的元素?
public class TestGenerics<E> {
Collection<E> col;
public void doSth(E elem) {
col.add(elem);
// ...
}
}
在泛型的使用中,有一个很Ҏ有的误解Q那是既然Integer是从Objectz出来的,那么ArrayList<Integer>当然是ArrayList<Object>的子cR真的是q样吗?我们仔细想一惛_会发现这样做可能会带来的问题Q如果我们可以把ArrayList<Integer>向上转型为ArrayList<Object>Q那么在往q个转了型以后的ArrayList中添加对象的时候,我们岂不是可以添加Q何类型的对象Q因为Object是所有对象的公共父类Q?q显然让我们的ArrayList<Integer>失去了原本的目的。于是Java~译器禁止我们这样做。那既然是这PArrayList<Integer>以及ArrayList<String>、ArrayList<Double>{等有没有公q父类呢?有,那就是ArrayList<?>?在这里叫做通配W。我们ؓ了羃通配W所指代的范_通常也需要这样写QArrayList<? extends SomeClass>Q这样写的含义是定义q样一个类ArrayListQ比方说SomeClass有SomeExtendedClass1和SomeExtendedClass2q两个子c,那么ArrayList<? extends SomeClass>是如下几个cȝ父类QArrayList<SomeClass>、ArrayList<SomeExtendedClass1>和ArrayList<SomeExtendedClass2>?nbsp;
接下来我们更q一步:既然ArrayList<? extends SomeClass>是一个通配的公用父c,那么我们可不可以往声明为ArrayList<? extends SomeClass>的ArrayList实例中添加一个SomeExtendedClass1的对象呢Q答案是不能。甚至你不能dM对象。ؓ什么?因ؓArrayList<? extends SomeClass>实际上代表了所有ArrayList<SomeClass>、ArrayList<SomeExtendedClass1>和ArrayList<SomeExtendedClass2>三种ArrayListQ甚臛_括未知的接受SomeClass其他子类对象的ArrayList。我们拿C个定义ؓArrayList<? extends SomeClass>的ArrayList的时候,我们q不能确定这个ArrayList具体是用哪个类作ؓ参数定义的,因此~译器也无法让这D代码编译通过。D例来Ԍ如果我们惛_q个ArrayList中放一个SomeExtendedClass2的对象,我们如何保证它实际上不是其他的如ArrayList<SomeExtendedClass1>Q而就是这个ArrayList<SomeExtendedClass2>呢?Q还记得吗?ArrayList<Integer>qArrayList<Object>的子cR)怎么办?我们需要用泛型方法。泛型方法的定义cM下面的例子:
public static <T extends SomeClass> void add (Collection<T> c, T elem) {
c.add(elem);
}
其中T代表了我们这个方法期待的那个最l的具体的类Q相关的声明必须攑֜Ҏ{中紧靠返回类型的位置之前。在本例中,它可以是SomeClass或者SomeClass的Q何子c,其说?t entends SomeClass>攑֜void关键字之?只能攑֜q里)。这h们就可以让编译器信当我们试图添加一个元素到泛型的ArrayList实例中时Q可以保证类型安全?/p>
Java泛型的最大特点在于它是在语言U别实现的,区别?a target="_blank">C# 2.0中的CLRU别。这L做法使得JRE可以不必做大的调_~点是无法支持一些运行时的类型甄别。一旦编译,它就被写MQ能提供的动态能力相当弱?/p>
个h认ؓ泛型是这ơJ2SE(TM) 5.0中引入的最重要的语a元素Q给Java语言带来的媄响也是最大。D个例子来Ԍ我们可以看到Q几乎所有的Collections API都被更新成支持泛型的版本。这样做带来的好处是显而易见的Q那是减少代码重复(不需要提供多个版本的某一个类或者接口以支持不同cȝ对象)以及增强代码的健壮?~译期的cd安全?。不q如何才能真正利用好q个Ҏ,其是如何实现自q泛型接口或类供他Z用,ƈ非那么显而易见了。让我们一起在使用中慢慢积累?/p>
1.5. 增强的for循环
你是否已l厌倦了每次写for循环旉要写上那些机械的代码Q尤其当你需要遍历数l或者CollectionQ如Q?假设在Collection中储存的对象是Stringcd?
public void showAll (Collection c) {
for (Iterator iter = c.iterator(); iter.hasNext(); ) {
System.out.println((String) iter.next());
}
}
public void showAll (String[] sa) {
for (int i = 0; i < sa.length; i++) {
System.out.println(sa[i]);
}
}
q样的代码不仅显得臃肿,而且Ҏ出错Q我x们大家在刚开始接触编E时Q尤其是C/C++和JavaQ可能多都犯过以下cM错误的一U或几种Q把for语句的三个表辑ּ序弄错;W二个表辑ּ逻辑判断不正?漏掉一些、多Z些、甚x循环);忘记Ud游标;在@环体内不心改变了游标的位置{等。ؓ什么不能让~译器帮我们处理q些l节??.0中,我们可以q样写:
public void showAll (Collection c) {
for (Object obj : c) {
System.out.println((String) obj);
}
}
public void showAll (String[] sa) {
for (String str : sa) {
System.out.println(str);
}
}
q样的代码显得更加清晰和z,不是?具体的语法很单:使用":"分隔开Q前面的部分写明从数l或Collection中将要取出的cdQ以及用的临时变量的名字,后面的部分写上数l或者Collection的引用。加上泛型,我们甚至可以把第一个方法变得更加漂亮:
public void showAll (Collection<String> cs) {
for (String str : cs) {
System.out.println(str);
}
}
有没有发玎ͼ当你需要将Collection 对于q个看上ȝ当方便的新语a元素Q当你需要在循环体中讉K游标的时候,会显得很别扭Q比方说Q当我们处理一个链表,需要更新其中某一个元素,或者删除某个元素等{。这个时候,你无法在循环体内获得你需要的游标信息Q于是需要回退到原先的做法。不q,有了泛型和增强的for循环Q我们在大多数情况下已经不用L心那些烦人的for循环的表辑ּ和嵌套了。毕竟,我们大部分时间都不会需要去了解游标的具体位|,我们只需要遍历数l或CollectionQ对? 1.6. 自动装箱/自动拆箱 所谓装,是把值类型用它们相对应的引用cd包v来,使它们可以具有对象的特质Q如我们可以把int型包装成Integercȝ对象Q或者把double包装成DoubleQ等{。所谓拆,是跟装q方向相反Q将Integer及Doubleq样的引用类型的对象重新化ؓ值类型的数据?/p>
在J2SE(TM) 5.0发布之前Q我们只能手工的处理装箱和拆。也怽会问Qؓ什么需要装和拆箱?比方说当我们试图一个值类型的数据dC个Collection中时Q就需要先把它装箱Q因为Collection的add()Ҏ只接受对?而当我们需要在E后这条数据取出来Q而又希望使用它对应的值类型进行操作时Q我们又需要将它拆成值类型的版本。现在,~译器可以帮我们自动地完成这些必要的步骤。下面的代码我提供两个版本的装箱和拆,一个版本用手工的方式Q另一个版本则把这些显而易见的代码交给~译器去完成Q?/p>
public static void manualBoxingUnboxing(int i) { 看到了吧Q在J2SE(TM) 5.0中,我们不再需要显式的d一个值类型的数据转换成相应的对象Q从而把它作为对象传l其他方法,也不必手工的那个代表一个数值的对象拆箱为相应的值类型数据,只要你提供的信息_让编译器信q些装箱/拆箱后的cd在用时是合法的Q比方讲Q如果在上面的代码中Q如果我们用的不是ArrayList 当然Q你需要够重视的是:一斚wQ对于值类型和引用cdQ在资源的占用上有相当大的区?另一斚wQ装和拆箱会带来额外的开销。在使用q一方便Ҏ的同时Q请不要忘记了背后隐藏的q些也许会媄响性能的因素?/p>
1.7. cd安全的枚?/p>
在介lJ2SE(TM) 5.0中引入的cd安全枚D的用法之前,我想先简单介l一下这一话题的背景?/p>
我们知道Q在C中,我们可以定义枚Dcd来用别名代替一个集合中的不同元素,通常是用于描q那些可以归Zc,而又具备有限数量的类别或者概念,如月份、颜艌Ӏ扑克牌、太阳系的行星、五大洲、四大洋、季节、学U、四则运符Q等{。它们通常看上Lq个样子Q?/p>
typedef enum {SPRING, SUMMER, AUTUMN, WINTER} season; 实质上,q些别名被处理成int帔RQ比?代表SPRINGQ?代表SUMMERQ以此类推。因些别名最l就是intQ于是你可以对它们进行四则运,q就造成了语意上的不明确?/p>
Java一开始ƈ没有考虑引入枚D的概念,也许是出于保持Java语言z的考虑Q但是用Java的广大开发者对于枚丄需求ƈ没有因ؓJava本n没有提供而消失,于是出现了一些常见的适用于Java的枚举设计模式,如int enum和typesafe enumQ还有不开源的枚DAPI和不开源的内部实现?/p>
我大致说一下int enum模式和typesafe enum模式。所谓int enum模式是模仿C中对enum的实玎ͼ如: public class Season { 后一U实现首先通过U有的构造方法阻止了对该cȝl承和显式实例化Q因而我们只可能取得定义好的四种SeasoncdQƈ且提供了方便的toString()Ҏ获取有意义的说明Q而且׃q是一个完全意义上的类Q所以我们可以很方便的加入自qҎ和逻辑来自定义我们的枚丄?/p>
最l,Java军_拥抱枚DQ在J2SE(TM) 5.0中,我们看到了这一变化Q它所采用的设计思\基本上就是上面提到的typesafe enum模式。它的语法很单,用一个实际的例子来说Q要定义一个枚举,我们可以q样写: public enum Language {CHINESE, ENGLISH, FRENCH, HUNGARIAN} 接下来我们就可以通过Language.ENGLISH来用了。呃…q个例子是不是有点太儿U了Q我们来看一个复杂点的例子。用Java的类型安全枚举,我们可以为所有枚丑օ素定义公用的接口Q然后具体到每个元素本nQ可以针对这些接口实C些特定的行ؓ。这对于那些可以归ؓ一c,又希望能通过l一的接口访问的不同操作Q将会相当方ѝ通常Qؓ了实现类似的功能Q我们需要自己来l护一套承关pL者类似的枚D模式。这里借用Java官方|站上的一个例子: public enum Operation { 怎么P使用枚DQ我们是不是能够很方便的实现一些有的功能?其实说穿了,Java的类型安全枚丑ְ是包含了有限数量的已生成好的自n实例的一U类Q这些现成的实例可以通过cȝ静态字D|获取?/p>
1.8. 可变长度参数 思义Q可变长度参数就是指在方法的参数体中Q只要定义恰当,我们可以使用L数量的参敎ͼcM于用数l。在J2SE(TM) 5.0中,一个新的语法被引入Q就是在参数cd名称后面加上"..."Q表CҎ可以接受多个该类型的参数。需要说明的是可变长度参数必L在参数列表的最后,且一个方法只能包含一个这L参数。在Ҏ体内部,q样的参数被当作数组处理Q看上去代码应该cMq个样子Q?/p>
public String testVararg(String... args) { q样的方法签名跟你写成testVararg(String[] args)的区别在于:在调用时Q你不再需要传入一个包装好的String数组Q你只需要简单的写一q串String参数Q以逗号隔开卛_Q就如同q个Ҏ正好有一个重载的版本是接受那么多个String参数一栗?/p>
1.9. 静态引?/p>
所谓静态引入就是指除了引入cM外,我们现在又多了一U选择Q引入某个类的静态字Dc如Q?/p>
import static java.lang.Math.PI; 或?/p>
import static java.lang.Math.*; q样我们在接下来的代码中Q当我们需要用某个被引入的静态字D|Q就不用再写上前面的cd了。当Ӟ出现名字冲突Ӟ跟原来的cd入一Pq是需要前~以示区分。我个h认ؓq个新语a元素意义不大。当引入太多静态字D后Q代码会变得难以阅读和维护。由于静态字D늚名字通常不如cd那么h描述性,我认为原先在静态字D前写上cd才是更好的选择。不q,毕竟每个人的喜好和需求不同,如果你觉得它对你有用Q既然提供了Q那么就用咯?/p>
1.10. 元数?注解) 注解是J2SE(TM) 5.0引入的重要语a元素Q它所对应的JSR是JSR 175Q我们先来看看JSR 175的文对注解的说明: 注解不会直接影响E序的语义,而开发和部v工具则可以读取这些注解信息,q作相应处理Q如生成额外的Java源代码、XML文档、或者其他将与包含注解的E序一起用的物g?/p>
在之前的J2SE版本中,我们已经使用C一部分早期的注解元素,如@deprecated{。这些元素通常被用于?a target="_blank">HTML的Javadoc。在J2SE(TM) 5.0中,注解被正式引入,且推CJava历史上前所未有的高度?/p>
现在Q注解不仅仅被用来生JavadocQ更重要的,注解使得代码的编译期查更加有效和方便Q同时也增强了代码的描述能力。有一些注解是随着J2SE(TM) 5.0一起发布的Q我们可以直接用。除此之外,我们也可以很方便的实现自定义的注解。在此基上,很多以前我们只能靠反机制来完成的功能也变得更加Ҏ实现?/p>
我们来看现成的有哪些有用的注解: 首先是@OverrideQ这个注解被使用在方法上Q表明这个方法是从其父类l承下来的,q样的写法可以很方便的避免我们在重写l承下来的方法时Q不至于不小心写错了Ҏ{Q且悄悄的溜q了~译器,造成隐蔽性相当高的bug?/p>
其次是@DeprecatedQ表明该?cR字Dc方?不再被推荐用?/p>
q有一个@SuppressWarningsQ表明该?cR字Dc方?所늛的范围不需要显C所有的警告信息。这个注解需要提供参敎ͼ如unchecked{等?/p>
下面我通过一个例子向大家说明q些现成的注解的用法Q?/p>
public class Main { 当然Q我们也完全可以写自q注解。注解定义的语法是@interface关键字。J2SE(TM) 5.0支持三种形式的注解:不带参数的标记注解、带一个参数的注解和带多个参数的完整注解。下面分别D例说明: 标记注解Q类似@DeprecatedQ如Q? 我们可以看到Q注解的定义跟interface的定义相当类|我们q可以指定默认倹{对于这些注解,我们也可以ؓ其添加注解,所?#8220;注解的注?#8221;。比方讲Q我们通常会用@Target指定注解的作用对象,以及用@Retention指定注解信息写入的别,如源代码、类文g{等。D个例子: @Target(ElementType.METHOD) 注解的最大作用在于它在源代码的基上增加了有用的信息,使得源代码的描述性更强。这些信息可以被代码之外的工兯别,从而可以很方便的增加外部功能,以及减少不必要的相关代码/文g的维护。这里我想简单提一个超出J2SE(TM) 5.0范畴的话题:在未来的EJB 3.0规范中会有相当多的对注解的应用,让我们预览一下将来的无状态会话bean用注解来定义会是什么样子: @Stateless public class BookShelfManagerBean { 我们甚至不用写Q何接口和部v描述W,q些工作完全由外部工具通过d注解加上反射来完成,q不是很好吗? 1.11. C风格格式化输?/p>
Javaȝ也有cMC的printf()风格的方法了Q方法名同样叫作printf()Q这一Ҏ依赖于前边提到的可变长度参数。D个例子来_我们现在可以写: System.out.printf("%s has a value of %d.%n", someString, a); 怎么P看上去还不错?需要注意的是JavaZ支持多^収ͼ新增?n标示W,作ؓ对\n的补充。有关Java格式化输出的具体语法Q请参考java.util.Formatter的API文?/p>
1.12. l语 在这一介l性的文章中,我们一起领略了J2SE 5.0带来的新的语a元素Q不知道大家是否也跟W者一P感受Cq些新特性在提高我们的开发效率上所作的巨大努力。其实不只是语言元素QJ2SE(TM) 5.0的发布在其他很多斚w都作了不的改进Q包括虚拟机、新的APIcd{等Q性能和功能上都有大幅提升?/p>
对于主要靠J2EE吃饭的朋友来Ԍ也许真正意义上要在工作中充分利用q些新的元素Q恐怕要{主的J2EE服务?/a>都支持J2EE(TM) 5.0的那一天了Q对此我充满期待?br />
ArrayList<Integer> aList = new ArrayList<Integer>();
aList.add(0, new Integer(i));
int a = aList.get(0).intValue();
System.out.println("The value of i is " + a);
}
public static void autoBoxingUnboxing(int i) {
ArrayList<Integer> aList = new ArrayList<Integer>();
aList.add(0, i);
int a = aList.get(0);
System.out.println("The value of i is " + a);
}
public static final int SPRING = 0;
public static final int SUMMER = 1;
public static final int AUTUMN = 2;
public static final int WINTER = 3;
}
q种模式跟C中的枚D没有太多本质上的区别QC枚D的局限它基本上也有。而typesafe enum模式则要昑־健壮得多Q?
public class Season {
private final String name;
private Season(String name) {
this.name = name;
}
public String toString() {
return name;
}
public static final Season SPRING = new Season("spring");
public static final Season SUMMER = new Season("summer");
public static final Season AUTUMN = new Season("autumn");
public static final Season WINTER = new Season("winter");
}
PLUS { double eval(double x, double y) { return x + y; } },
MINUS { double eval(double x, double y) { return x - y; } },
TIMES { double eval(double x, double y) { return x * y; } },
DIVIDE { double eval(double x, double y) { return x / y; } };
// Do arithmetic op represented by this constant
abstract double eval(double x, double y);
}
在这个枚举中Q我们定义了四个元素Q分别对应加减乘除四则运,对于每一U运,我们都可以调用eval()ҎQ而具体的Ҏ实现各异。我们可以通过下面的代码来试验上面q个枚Dc:
public static void main(String args[]) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
for (Operation op : Operation.values()) {
System.out.println(x + " " + op + " " + y + " = " + op.eval(x, y));
}
}
StringBuilder sb = new StringBuilder();
for (String str : args) {
sb.append(str);
}
return sb.toString();
}
@Deprecated
public String str;
public static void main(String[] args) {
new SubMain().doSomething();
}
public void doSomething() {
System.out.println("Done.");
}
}
class SubMain extends Main {
@Override
@SuppressWarnings("unchecked", "warning")
public void doSomething() {
java.util.ArrayList aList = new java.util.ArrayList();
aList.add(new Integer(0));
System.out.println("Done by SubMain.");
}
}
@interface SomeEmptyAnnotation {}
单个参数的注解,如:
@interface MySingleElementAnnotation {
String value();
}
以及多个参数的注解,如:
@interface MyAnnotationForMethods {
int index();
String info();
String developer() default "Sean GAO";
}
@Retention(RetentionPolicy.SOURCE)
public @interface SignedMethod {
}
在用时Q我们需要在注解名称前面写上@Q然?)中指定参数|如:
@MyAnnotationForMethods (
index = 1,
info = "This is a method to test MyAnnotation.",
developer = "Somebody else"
)
public void testMethod1() {
// ...
}
public void addBook(Book aBook) {
// business logic goes here...
}
public Collection getAllBooks() {
// business logic goes here...
}
// ...
}
]]>