现在的位置: 首页 > 综合 > 正文

[投稿文章] J2SE(TM) 5.0专题 之 语言特性 //勘误信息请直接回复

2014年01月20日 ⁄ 综合 ⁄ 共 4755字 ⁄ 字号 评论关闭

[背景]

 

J2SE(TM) 5.0正式发布至今已有超过3个月的时间了,就在前不久,大概是两周之前,Sun又发布了更新过的JDK 5.0 Update 1,改掉了一些第一个版本发布之后的bug

 

由于Java社群等待这一从1.45.0版本升级已经有相当长的一段时间,大家都很关心5.0中有哪些值得关注的变化,于是blog满天飞,我也兴冲冲的加入了自己的blog系列文章。无奈这些blog文章,包括我自己的在内,通常都比较泛泛而谈,因此CSDN第二期Java电子杂志的编辑们计划做一个专题对这一话题进行一番深入。

 

作为这期电子刊物的一部分,编辑们也邀请我更系统的探讨一下:J2SE(TM) 5.0中新引入的语言特性究竟在实际中有哪些用途,以及为什么要引入这些新特性。对此我深感荣幸。我本人很乐意将我的一些也许算得上经验的Java经验跟大家分享,希望这一篇小文能对大家了解J2SE(TM) 5.0有一定帮助。

 

 

[准备工作]

 

首先,为了了解J2SE(TM) 5.0的新的语言特性,你需要下载新版的JDK,在这里可以找到下载链接:http://java.sun.com/j2se/1.5.0/download.jsp。当然,如果你已经有过手动配置Java环境的经历,我也建议你使用一个支持J2SE(TM) 5.0IDE,推荐Eclipse SDK 3.1 M4,或者NetBeans IDE 4.0。两个都是开源免费的,且很容易找到(Eclipse不用说了,NetBeans IDE 4.0有与JDK 5.0 Update 1的捆绑版)。

 

说点题外话,Java的版本号自从1.2开始,似乎就多少显得有点蹩脚。从1.2版本开始,Java (J2SE)被称作Java 2,而不是Java 1.2,现在则显得更加离奇:Java(TM) 2 Platform Standard Edition 5.0或者J2SE(TM) 5.0,而内部的版本号还是1.5.0。那么到底是12、还是5呢?来看看Sun官方网站是怎么说的:

 

Java诞生至今已有9年时间,而从第二代Java平台J2SE算起也有5个年头了。在这样的背景下,将下一个版本的版本号从1.5改为5.0可以更好的反映出新版J2SE的成熟度、稳定性、可伸缩性和安全性。

 

好吧,现在我们将面对如下一些名称,而它们指的基本上是同一个东西:

Tiger

Java(TM) 2 Platform Standard Edition 5.0

J2SE(TM) 5.0

Java version 1.5.0

在本文中,为了方便起见,我将统一使用J2SE(TM) 5.0这个名称。

 

如果你对Java各个版本的代号感兴趣,就像这里的"Tiger",可以参考如下网址:http://java.sun.com/j2se/codenames.html。透露一点:Java下一个版本(6.0)的代号是"Mustang"野马,再下一个版本(7.0)的代号是"Dolphin"海豚。

 

 

[概述]

 

J2SE(TM) 5.0引入了很多激进的语言元素变化,这些变化或多或少减轻了我们开发人员的一些编码负担,其中的大部分也必然会被应用到即将发布的J2EE(TM) 5.0中。主要的新特性包括:

 

1-      泛型

2-      增强的for循环

3-      自动装箱和自动拆箱

4-      类型安全的枚举

5-      可变长度参数

6-      静态引入

7-      元数据(注解)

8-      C风格的格式化输出

 

这当中,泛型、枚举和注解可能会占用较大的篇幅,而其余的因为用法相当直截了当,抑或相对简单,我就稍作介绍,剩下的留给读者去思考、去探索了。

 

 

[泛型]

 

泛型这个题目相当大,大到完全可以就这个话题写一本书。有关Java是否需要泛型和如何实现泛型的讨论也早就在Java社群广为流传。终于,我们在J2SE(TM) 5.0中看到了它。也许目前Java对泛型的支持还算不上足够理想,但这一特性的添加已经足以让我们欣喜一阵了。

 

在接下来的介绍中,我们会了解到:Java的泛型虽然跟C++的泛型看上去十分相似,但其实有着相当大的区别,有些细节的东西也相当难以掌握(至少很多地方会跟我们的直觉背道而驰)。可以这样说,泛型的引入在很大程度上增加了Java语言的复杂度,对初学者尤其是个挑战。我们一点一点往里挖。

 

首先我们来看一个简单的使用泛型类的例子:

 

    ArrayList<Integer> aList = new ArrayList<Integer>();

    aList.add(new Integer(1));

    // ...

    Integer myInteger = aList.get(0);

 

我们可以看到,在这个简单的例子中,我们在定义aList的时候指明了它是一个直接受Integer类型的ArrayList,当我们调用aList.get(0)时,我们已经不再需要先显式的将结果转换成Integer,然后再赋值给myInteger了。而这一步在早先的Java版本中是必须的。也许你在想,在使用Collection时节约一些类型转换就是Java泛型的全部吗?远不止。单就这个例子而言,泛型至少还有一个更大的好处,那就是使用了泛型的容器类变得更加健壮:早先,Collection接口的get()Iterator接口的next()方法都只能返回Object类型的结果,我们可以把这个结果强制转换成任何Object的子类,而不会有任何编译期的错误,但这显然很可能带来严重的运行期错误,因为在代码中确定从某个Collection中取出的是什么类型的对象完全是调用者自己说了算,而调用者也许并不清楚放进Collection的对象具体是什么类的;就算知道放进去的对象“应该”是什么类,也不能保证放到Collection的对象就一定是那个类的实例。现在有了泛型,只要我们定义的时候指明该Collection接受哪种类型的对象,编译器可以帮我们避免类似的问题溜到产品中。我们在实际工作中其实已经看到了太多的ClassCastException,不是吗?

 

泛型的使用从这个例子看也是相当易懂。我们在定义ArrayList时,通过类名后面的<>括号中的值指定这个ArrayList接受的对象类型。在编译的时候,这个ArrayList会被处理成只接受该类或其子类的对象,于是任何试图将其他类型的对象添加进来的语句都会被编译器拒绝。

 

那么泛型是怎样定义的呢?看看下面这一段示例代码:(其中用E代替在实际中将会使用的类名,当然你也可以使用别的名称,习惯上在这里使用大写的E,表示Collection的元素。)

 

    public class TestGenerics<E> {

        Collection<E> col;

        public void doSth(E elem) {

            col.add(elem);

            // ...

        }

    }

 

在泛型的使用中,有一个很容易有的误解,那就是既然Integer是从Object派生出来的,那么ArrayList<Integer>当然就是ArrayList<Object>的子类。真的是这样吗?我们仔细想一想就会发现这样做可能会带来的问题:如果我们可以把ArrayList<Integer>向上转型为ArrayList<Object>,那么在往这个转了型以后的ArrayList中添加对象的时候,我们岂不是可以添加任何类型的对象(因为Object是所有对象的公共父类)?这显然让我们的ArrayList<Integer>失去了原本的目的。于是Java编译器禁止我们这样做。那既然是这样,ArrayList<Integer>以及ArrayList<String>ArrayList<Double>等等有没有公共的一个版本来代替所有呢?有,那就是ArrayList<?>?在这里叫做通配符。我们为了缩小通配符所指代的范围,通常也需要这样写:ArrayList<? extends SomeClass>,这样写的含义是定义的是这样一类ArrayList,比方说SomeClassSomeExtendedClass1SomeExtendedClass2这两个子类,那么ArrayList<? extends SomeClass>就是如下几个类型的通配写法:ArrayList<SomeClass>ArrayList<SomeExtendedClass1>ArrayList<SomeExtendedClass2>

 

接下来我们更进一步:既然ArrayList<? extends SomeClass>是一个通配的写法,那么我们可不可以往声明为ArrayList<? extends SomeClass>ArrayList实例中添加一个SomeExtendedClass1的对象呢?答案是不能。甚至你不能添加任何对象。为什么?因为ArrayList<? extends SomeClass>实际上代表了所有ArrayList<SomeClass>ArrayList<SomeExtendedClass1>ArrayList<SomeExtendedClass2>三种ArrayList,甚至包括未知的接受SomeClass其他子类对象的ArrayList。我们拿到一个定义为ArrayList<? extends SomeClass>ArrayList的时候,我们并不能确定这个ArrayList具体是使用哪个类作为参数定义的,因此编译器也无法让这段代码编译通过。举例来讲,如果我们想往这个ArrayList中放一个SomeExtendedClass2的对象,我们如何保证它实际上不是其他的如ArrayList<SomeExtendedClass1>,而就是这个ArrayList<SomeExtendedClass2>呢?(还记得吗?ArrayList<Integer>并非ArrayList<Object>的子类。)怎么办?我们需要使用泛型方法。泛型方法的定义类似下面的例子:

 

    public static <T extends SomeClass> void add (Collection<T> c, T elem) {

        c.add(elem);

    }

 

其中T代表了我们这个方法期待的那个最终的具体的类,相关的声明必须放在方法签名的紧靠返回类型说明之前。在本例中,它可以是SomeClass或者SomeClass的任何子类,其说明<T entends SomeClass>放在void关键字之前(只能放在这里)。这样我们就可以让编译器确信当我们试图添加一个元素到泛型的ArrayList实例中时,可以保证类型安全。

 

Java泛型的最大特点在于它是在语言级别实现的,区别于C# 2.0中的CLR级别。这样的做法使得JRE可以不必做大的调整,缺点是无法支持一些运行时的类型甄别。一旦编译,它就被写死了,能都提供的动态能力相当弱。

 

个人认为泛型是这次J2SE(TM) 5.0中引入的最重要的语言元素,给Java语言带来的影响也是最大。举个例子来讲,我们可以看到,几乎所有的Collections API都被更新成支持泛型的版本。这样做带来的好处是显而易见的,那就是减少代码重复(不需要提供多个版本的某一个类或者接口以支持不同类的对象)以及增强代码的健壮性(编译期的类型安全检查)。不过如何才能真正利用好这个特性,尤其是如何实现自己的泛型接口或类供他人使用,就并非那么显而易见了。让我们一起在使用中慢慢积累。

 

 

[增强的for循环]

 

你是否已经厌倦了每次写for

【上篇】
【下篇】

抱歉!评论已关闭.