`
z_jiankun
  • 浏览: 159844 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

J2SE(TM) 5.0专题 之 语言特性 (二)

阅读更多

原文:http://www.blogjava.net/sean/archive/2005/01/12/256.html

 

[类型安全的枚举]

 

在介绍J2SE(TM) 5.0中引入的类型安全枚举的用法之前,我想先简单介绍一下这一话题的背景。

 

我们知道,在C中,我们可以定义枚举类型来使用别名代替一个集合中的不同元素,通常是用于描述那些可以归为一类,而又有限数量的类别或者概念,如月份、颜色、扑克牌、太阳系的行星、五大洲、四大洋、季节、学科、四则运算符,等等。它们通常看上去是这个样子:

 

typedef enum {SPRING, SUMMER, AUTUMN, WINTER} season;

 

实质上,这些别名被处理成int常量,比如0代表SPRING1代表SUMMER,以此类推。因为这些别名最终就是int,于是你可以对它们进行四则运算,这就造成了语意上的不明确。

 

Java一开始并没有考虑引入枚举的概念,也许是出于保持Java语言简洁的考虑,但是使用Java的广大开发者对于枚举的需求并没有因为Java本身没有提供而消失,于是出现了一些常见的适用于Java的枚举设计模式,如int enumtypesafe enum,还有不少开源的枚举API和不开源的内部实现。

 

我大致说一下int enum模式和typesafe enum模式。所谓int enum模式就是模仿C中对enum的实现,如:

 

public class Season {

    public static final int SPRING = 0;

    public static final int SUMMER = 1;

    public static final int AUTUMN = 2;

    public static final int WINTER = 3;

}

 

这种模式跟C中的枚举没有太多本质上的区别,C枚举的局限它基本上也有。而typesafe enum模式则要显得健壮得多:

 

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");

}

 

后一种实现首先通过私有的构造方法阻止了对该类的继承和显式实例化,因而我们只可能取得定义好的四种Season类别,并且提供了方便的toString()方法获取有意义的说明,而且由于这是一个完全意义上的类,所以我们可以很方便的加入自己的方法和逻辑来自定义我们的枚举类。

 

最终,Java决定拥抱枚举,在J2SE(TM) 5.0中,我们看到了这一变化,它所采用的设计思路基本上就是上面提到的typesafe enum模式。它的语法很简单,用一个实际的例子来说,要定义一个枚举,我们可以这样写:

 

public enum Language {CHINESE, ENGLISH, FRENCH, HUNGARIAN}

 

接下来我们就可以通过Language.ENGLISH来使用了。呃这个例子是不是有点太小儿科了,我们来看一个复杂点的例子。使用Java的类型安全枚举,我们可以为所有枚举元素定义公用的接口,然后具体到每个元素本身,可以针对这些接口实现一些特定的行为。这对于那些可以归为一类,又希望能通过统一的接口访问的不同操作,将会相当方便。通常,为了实现类似的功能,我们需要自己来维护一套继承关系或者类似的枚举模式。借用Java官网上的例子:

 

public enum Operation {

    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);

}

 

在这个枚举中,我们定义了四个元素,分别对应加减乘除四则运算,对于每一种运算,我们都可以调用eval()方法,而具体的方法实现各异。我们可以通过下面的代码来试验上面这个枚举类:

 

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));

    }

}

 

怎么样,使用枚举,我们是不是能够很方便的实现一些有趣的功能?其实说穿了,Java的类型安全枚举就是包含了有限数量的已生成好的自身实例的一种类,这些现成的实例可以通过类的静态字段来获取。

 

 

[可变长度参数]

 

顾名思义,可变长度参数就是指在方法的参数体中,只要定义恰当,我们可以使用任意数量的参数,类似于使用数组。在J2SE(TM) 5.0中,一个新的语法被引入,就是在参数类型名称后面加上"...",表示该方法可以接受多个该类型的参数。需要说明的是可变长度参数必须放在参数列表的最后,且一个方法只能包含一个这样的参数。在方法体内部,这样的参数被当作数组处理,看上去代码应该类似这个样子:

 

public String testVararg(String... args) {

    StringBuilder sb = new StringBuilder();

    for (String str : args) {

        sb.append(str);

    }

    return sb.toString();

}

 

这样的方法签名跟你写成testVararg(String[] args)的区别在于:在调用时,你不再需要传入一个包装好的String数组,你只需要简单的写一连串String参数,以逗号隔开即可,就如同这个方法正好有一个重载的版本是接受那么多个String参数一样。

 

 

[静态引入]

 

所谓静态引入就是指除了引入类之外,我们现在又多了一种选择:引入某个类的静态字段。如:

 

import static java.lang.Math.PI;

或者

import static java.lang.Math.*;

 

这样我们在接下来的代码中,当我们需要使用某个被引入的静态字段时,就不用再写上前面的类名了。当然,出现名字冲突时,跟原来的类引入一样,还是需要前缀以示区分。我个人认为这个新语言元素意义不大。当引入太多静态字段后,代码会变得难以阅读和维护。由于静态字段的名字通常不如类名那么具有描述性,我认为原先在静态字段前写上类名才是更好的选择。不过,毕竟每个人的喜好和需求不同,如果你觉得它对你有用,既然提供了,那么就用咯。

 

 

[元数据(注解)]

 

注解是J2SE(TM) 5.0引入的重要语言元素,它所对应的JSRJSR 175,我们先来看看JSR 175的文档对注解的说明:

 

注解不会直接影响程序的语义,而开发和部署工具则可以读取这些注解信息,并作相应处理,如生成额外的Java源代码、XML文档、或者其他将与包含注解的程序一起使用的物件。

 

在之前的J2SE版本中,我们已经使用到了一部分早期的注解元素,如@deprecated等。这些元素通常被用于产生HTMLJavadoc。在J2SE(TM) 5.0中,注解被正式引入,且推到了Java历史上前所未有的高度。

 

现在,注解不仅仅被用来产生Javadoc,更重要的,注解使得代码的编译期检查更加有效和方便,同时也增强了代码的描述能力。有一些注解是随着J2SE(TM) 5.0一起发布的,我们可以直接使用。除此之外,我们也可以很方便的实现自定义的注解。在此基础上,很多以前我们只能靠反射机制来完成的功能也变得更加容易实现。

 

我们来看现成的有哪些有用的注解:

 

首先是@Override,这个注解被使用在方法上,表明这个方法是从其父类继承下来的,这样的写法可以很方便的避免我们在重写继承下来的方法时,不至于不小心写错了方法签名,且悄悄的溜过了编译器,造成隐蔽性相当高的bug

 

其次是@Deprecated,表明该项(类、字段、方法)不再被推荐使用。

 

还有一个@SuppressWarnings,表明该项(类、字段、方法)所涵盖的范围不需要显示所有的警告信息。这个注解需要提供参数,如unchecked等等。

 

下面我通过一个例子向大家说明这些现成的注解的用法:

 

public class Main {

    @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关键字。J2SE(TM) 5.0支持三种形式的注解:不带参数的标记注解、带一个参数的注解和带多个参数的完整注解。下面分别举例说明:

 

标记注解,类似@Deprecated,如:

 

@interface SomeEmptyAnnotation {}

 

单个参数的注解,如:

 

@interface MySingleElementAnnotation {

    String value();

}

 

以及多个参数的注解,如:

 

@interface MyAnnotationForMethods {

    int index();

    String info();

    String developer() default "Sean GAO";

}

 

我们可以看到,注解的定义跟interface的定义相当类似,我们还可以指定默认值。对于这些注解,我们也可以为其添加注解,所谓“注解的注解”。比方讲,我们通常会使用@Target指定注解的作用对象,以及用@Retention指定注解信息写入的级别,如源代码、类文件等等。举个例子:

 

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.SOURCE)

public @interface SignedMethod {

}

 

在使用时,我们需要在注解名称前面写上@,然后()中指定参数值,如:

 

@MyAnnotationForMethods (

        index = 1,

        info = "This is a method to test MyAnnotation.",

        developer = "Somebody else"

)

public void testMethod1() {

    // ...

}

 

注解的最大作用在于它在源代码的基础上增加了有用的信息,使得源代码的描述性更强。这些信息可以被代码之外的工具识别,从而可以很方便的增加外部功能,以及减少不必要的相关代码/文件维护。这里我想简单提一个超出J2SE(TM) 5.0范畴的话题:在未来的EJB 3.0规范中会有相当多的对注解的应用,让我们预览一下将来的无状态会话bean用注解来定义会是什么样子:

 

@Stateless public class BookShelfManagerBean {

    public void addBook(Book aBook) {

        // business logic goes here...

    }

    public Collection getAllBooks() {

        // business logic goes here...

    }

    // ...

}

 

我们甚至不用写任何接口和部署描述符,这些工作将完全由外部工具通过读取注解加上反射来完成,这不是很好吗?

 

 

[C风格格式化输出]

 

Java总算也有类似Cprintf()风格的方法了,方法名同样叫作printf(),这一特性依赖于前边提到的可变长度参数。举个例子来说,我们现在可以写:

 

System.out.printf("%s has a value of %d.%n", someString, a);

 

怎么样,看上去还不错吧?需要注意的是Java为了支持多平台,新增了%n标示符,作为对\n的补充。有关Java格式化输出的具体语法,请参考java.util.FormatterAPI文档。

 

 

[结语]

 

在这一篇介绍性的文章中,我们一起领略了J2SE 5.0带来的新的语言元素,不知道大家是否也跟笔者一样,感受到了这些新特性在提高我们的开发效率上所作的巨大努力。其实不只是语言元素,J2SE(TM) 5.0的发布在其他很多方面都作了不小的改进,包括虚拟机、新的API类库等等,性能和功能上都有大幅提升。

 

对于主要靠J2EE吃饭的朋友来讲,也许真正意义上要在工作中充分利用这些新的元素,恐怕要等主流的J2EE服务器都支持J2EE(TM) 5.0的那一天了,对此我充满期待。

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics