Java从5.0推出了很多新的东西,其中包含了标题所示两大利器。现在开始分别介绍一下:
【Annotation】
注解,用于为一些信息添加额外解释信息时使用,类似元数据的概念,用于描述数据的数据。这个东西在设计基础性框架的时候非常有用,你可以通过定义一些注解,让应用层程序员能够将自己的代码嵌入并运行在你的框架中。当没有Annotation的时候,框架通常需要应用层开发人员写配置文件,有了这个,开发人员在编写代码时直接加上注解即可,如Spring,Hibernate等。直接上例子吧:
package cn.test; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ServiceImpl { public String value(); }
通过关键字@interface定义注解,自定义注解默认是继承自java.lang.annotation.Annotation。其中@Retention @Target也是两个注解,这两个是Java本身提供的专门用于注解我们自己注解的注解(好绕口啊),就是元注解。@Retention表示注解的存在范围,此处表明注解会存在于运行期,即字节码中。RetentionPolicy还有一个值为SOURCE,表明注解仅会存在于源码中供编译器使用,最典型的就是注解@Override,这个覆写注解仅仅存在于源码中,为编译器使用。@Target注解,表明该注解的使用对象,我们这里设置为成员变量,就是这个注解只可注解成员变量,ElementType还有很多其他值,都是顾名思义的。
注解中只能定义“接口”, 这个“接口”用于想注解中设置值和取值。如下,我们使用上面的注解:
// 订单服务对象 @ServiceImpl(value="cn.test.OrderSeviceImpl") private OrderService orderService;
此处有个小窍门,如果你的注解中只用名称为value()的接口,在使用注解的时候,可以直接传值,而不用指明名称:
// 订单服务对象 @ServiceImpl("cn.test.OrderSeviceImpl") private OrderService orderService;
这种方式仅限于注解中只用一个value()接口的情况,当注解中有多个接口,则所有接口必须都要通过指定名称的形式赋值并且通过逗号分隔。我们如上定义并在实际业务代码中使用了注解,那这个注解如何起作用呢?注解是结合反射使用的,我们接着看例子:
package cn.test; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class TestAnnotation { // 订单服务对象 @ServiceImpl("cn.test.OrderSeviceImpl") private OrderService orderService; public OrderService getOrderService() { return orderService; } public void setOrderService(OrderService orderService) { this.orderService = orderService; } public static void main(String[] args) { TestAnnotation ta = getInstance(); ta.getOrderService().placeOrder("apple"); } public static TestAnnotation getInstance(){ TestAnnotation ta = new TestAnnotation(); Field[] fields = ta.getClass().getDeclaredFields(); if(null != fields && fields.length > 0){ for(Field field : fields){ if(field.isAnnotationPresent(ServiceImpl.class)){ String serviceImplClassName = field.getAnnotation(ServiceImpl.class).value(); String fieldName = field.getName(); String fieldSetMethodName = "set" + String.valueOf(fieldName.charAt(0)).toUpperCase(); if(fieldName.length() > 1){ fieldSetMethodName += fieldName.substring(1); } field.getType(); try { Method fieldSetMethod = ta.getClass().getMethod(fieldSetMethodName, new Class[]{field.getType()}); fieldSetMethod.invoke(ta, new Object[]{Class.forName(serviceImplClassName).newInstance()}); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } return ta; } }
上例中,我们通过注解将订单服务的一个实现对象注册到订单服务接口类型的引用中,并且成功调用了订单服务接口的相应方法。利用这一段代码,应用层开发人员可以很容易将一个服务实现注入到服务接口中,这是注解和反射的一个很经典的应用。
【Enum】
Java编码中,你经常需要定义一系列同一类型意义相近的常量,我们来看一个“老水果店”的应用,水果店对象根据传入水果的名称返回单价,如果没有单价,则抛出异常:
package cn.test; public class OldFruitShop { public static final String APPLE = "apple"; public static final String PEAR = "pear"; public static final String ORANGE = "orange"; public static final double APPLE_PRICE = 6.5; public static final double PEAR_PRICE = 5.5; public static final double ORANGE_PRICE = 4.5; public double getPrice(String fruit){ if(APPLE.equals(fruit)){ return APPLE_PRICE; }else if(PEAR.equals(fruit)){ return PEAR_PRICE; }else if(ORANGE.equals(fruit)){ return ORANGE_PRICE; }else{ throw new IllegalArgumentException("水果" + fruit + "不在售卖范围内!"); } } }
这个例子可以很好的工作,但其存在一个不好的地方就是,使用者必选在运行程序后,才知道这个水果是否在售卖范围内(通过抛异常的形式)。在用户使用这个对象接口的时候,用户可以传入任何水果,程序编译都可以通过。这种设计方式没有问题,但易用性差,通过将水果做成枚举,我们可以避免这个问题:
以下的Fruit为自定义枚举,自定义枚举默认继承自java.lang.Enum类:
package cn.test; public enum Fruit { APPLE, PEAR, ORANGE; }
package cn.test; public class NewFruitShop { public static final double APPLE_PRICE = 6.5; public static final double PEAR_PRICE = 5.5; public static final double ORANGE_PRICE = 4.5; public double getPrice(Fruit fruit){ if(null == fruit){ throw new IllegalArgumentException("请传入正确的水果!"); } if(Fruit.APPLE == fruit){ return APPLE_PRICE; }else if(Fruit.PEAR == fruit){ return PEAR_PRICE; }else if(Fruit.ORANGE == fruit){ return ORANGE_PRICE; } return 0; } }
通过定义水果枚举,“新水果店”的接口方法只能接受水果枚举类型,用户不能随意传递参数。如果随意传递,编译器无法通过。所以用户在编写代码时就可以知道水果店有哪些水果售卖了。通过枚举,我们将出现异常的时间点从运行期前移到编译期(对于传入null参数这种情况无法控制)。而编程的最佳实践就是让异常出现的越早越好。这是枚举的功劳。上述例子的一个改进版本时,将水果单价也集成在水果枚举定义中,这样代码的封装性就更好了:
package cn.test; public enum Fruit { /** * 这几个常量就是这个枚举类仅有的几个对象了。 */ APPLE(6.5), PEAR(5.5), ORANGE(4.5); private double price; /** * 枚举类型不支持外部再创建对象,所以其构造函数必须为private! * @param price */ private Fruit(double price){ this.price = price; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } }
package cn.test; public class NewFruitShop { public double getPrice(Fruit fruit){ if(null == fruit){ throw new IllegalArgumentException("请传入正确的水果!"); } return fruit.getPrice(); } }
从枚举定义中我们可以看到,枚举的构造函数必须为private的,因为枚举类内容刚开始就必须定义枚举的实例,这些实例是这个枚举类型仅有的几个实例,枚举不支持外部再创建实例。