Java 学习之路 之 基本Annotion(五十九)

从 JDK 5 开始,Java 增加了对元数据(MetaData)的支持,也就是 Annotation(注释),这种 Annotation 与前面所介绍的注释有一定的区别,也有一定的联系。所介绍 的 Annotation,其实是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用 Annotation,程序开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。

Annotation 提供了一种为程序元素设置元数据的方法,从某些方面来看,Annotation 就像修饰符一样,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被存储在 Annotation 的 “name=value” 对中。

Annotation 是一个接口,程序可以通过反射来获取指定程序元素的 Annotation 对象,然后通过 Annotation 对象来取得注释里的元数据。读者需要注意使用 Annotation 的地方,有的 Annotation 指的的是 java.lang.Annotation 接口,有的指的是注释本身。

Annotation 能被用来为程序元素(类、方法、成员变量等)设置元数据。值得指出的是,Annotation 不影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一地执行。如果希望让程序中的 Annotation 在运行时起一定的作用,只有通过某种配套的工具对 Annotation 中的信息进行访问和处理,访问和处理 Annotation 的工具统称 APT(Annotation Processing Tool)。

Annotation 必须使用工具来处理,工具负责提取 Annotation 里包含的元数据,工具还会根据这些元数据增加额外的功能。在系统学习新的 Annotation 语法之前,先看一下 Java 提供的 4 个基本 Annotation 的用法——使用 Annotation 时要在其前面增加 @ 符号,并把该 Annotation 当成一个修饰符使用,用于修饰它支持的程序元素。

4个基本的 Annotation 如下:

@Override

@Deprecated

@SuppressWarnings

@SafeVarargs

上面 4 个基本 Annotation 中的 @SafeVarargs 是 Java 7 新增的。这 4 个基本的 Annotation 都定义在 java.lang 包下,读者可以通过查阅它们的 API 文档来了解关于它们的更多细节。

1,限定重写父类方法:@Override

@Override 就是用来指定方法覆载的,它可以强制一个子类必须覆盖父类的方法。如下程序中使用 @Override 指定子类 Apple 的 info() 方法必须重写父类方法。

public class Fruit
{
    public void info()
    {
        System.out.println("水果的 info 方法...");
    }
}
class Apple extends Fruit
{
    // 使用@Override 指定下面方法必须重写父类方法
    @Override
    public void info()
    {
        System.out.println("苹果重写水果的 info 方法...");
    }
}

编译上面程序,可能丝毫看不出程序中的 @Override 有何作用,因为 @Override 的作用是告诉编译器检查这个方法,保证父类要包含一个被该方法重写的方法,否则就会编译出错。@Override 主要是帮助我们避免一些低级错误,例如把上面 Apple 类中的 info 方法不小心写成了inf(),这样的“低级错误”可能会成为后期排错时的巨大障碍。

笔者在讲解 Struts框架过程中会告诉学员定义 Action 的方法:需要继承系统的 Action 基类,并重写 execute() 方法,但由于 Struts Action 基类里包含的 execute() 方法比较复杂,经常有人出现重写 execute() 方法时方法签名写错的时候——这种错误在编译、运行时都没有任何提示,只是运行时不出现所期望的结果,这种没有任何错误提示才是最难调试的错误。如果在重写 execute() 方法时使用了 @Override 修饰,就可以轻松避免这个问题。

如果我们把 Apple 类中的 info 方法误写成 inf0,编译程序时将出现如下错误提示:

Fruit.java:23: 错误: 方法不会覆盖或实现超类型的方法
    @Overrider
    ^
1 个错误

@Override 只能作用于方法,不能作用于其他程序元素。

2,标示已过时:@Deprecated

@Deprecated 用于表示某个程序元素(类、方法等)已过时,当其他程序使用已过时的类、方法时,编译器将会给出警告。如下程序指定 Apple 类中的 info() 方法已过时,其他程序中使用 Apple 类的 info() 方法时编译器将会给出警告。

class Apple
{
    // 定义info方法已过时
    @Deprecated
    public void info()
    {
        System.out.println("Apple 的 info 方法"
    }
}
public class DeprecatedTest
{
    public static void main(String[] args)
    {
        // 下面使用 info 方法时将会被编译嚣警告
        new Apple().info();
    }
}

上面程序中的使用了 Apple 的 info() 方法,而 Apple 类中定义 info() 方法时使用了 @Deprecated 修饰,表明该方法已过时,所以将会引起编译器警告。

@Deprecated 的作用与文档注释中的 @deprecate 标记的作用基本相同,但它们的用法不同,前者是 JDK 5 才支持的注解,无须放在文档注释语法(/**...*/部分)中,而是直接用于修饰程序中的程序单元,如方法、类、接口等。

3,抑制编译器警告:@SuppressWarnings

@SuppressWarnings 指示被该 Annotation 修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告。@SuppressWarnings 会一直作用于该程序元素的所有子元素,例如,使用 @SuppressWarnings 修饰某个类取消显示某个编译器警告,同时又修饰该类里的某个方法取消显示另一个编译器警告,那么该方法将会同时取消显示这两个编译器警告。

在通常情况下,如果程序中使用没有泛型限制的集合将会引起编译器警告,为了避免这种编译器警告,可以使用 @SuppressWarnings 修饰。下面程序取消了没有使用泛型的编译器警告。

// 关闭整个类里的编译器警告
@SuppressWarnings(value="unchecked")
public class SupressWarningsTest
{
    public static void main(String[] args)
    {
        List<String> myList = new ArrayList();    //①
    }
}

程序中的代码使用 @SuppressWarnings 来关闭 SuppressWarningsTest 类里的所有编译器警告,编译上面程序时将不会看到任何编译器警告。如果删除程序中的粗体字代码,将会在程序的①处看到编译器警告。

正如从程序中第一行代码所看到的,当使用 @SuppressWarnings Annotation 来关闭编译器警告时,一定要在括号里使用 name=value 的形式为该 Annotation 的成员变量设置值。关于如何为 Annotation 添加成员变量请看后面介绍。

4,Java 7 的 “堆污染”警告与@SafeVarargs

前面介绍泛型擦除时,介绍了如下代码可能导致运行时异常。

List list = new ArrayList<Integer>();
list.add(20); // 添加元素时引发 unchecked 异常
// 下面代码引起“未经检查的转换” 的警告,编译、运行时完全正常
List<String> ls = list;    //①
// 但只要访问 ls 里的元素,如下面代码就会引起运行时异常
System.out.print(ls.get(0));

Java 把引发这种错误的原因称为 “堆污染” (Heap pollution),当把一个不带泛型的对象赋给一个带泛型的变量时,往往就会发生这种 “堆污染” ,如上面①号代码所示。

对于形参个数可变的方法,该形参的类型又是泛型,这将更容易导致 “堆污染” 。例如如下工具类:

public class ErrorUtils
{
	@SafeVarargs
	public static void faultyMethod(List<String>... listStrArray)
	{
		// Java语言不允许创建泛型数组,因此listArray只能被当成List[]处理
		// 此时相当于把List<String>赋给了List,已经发生了“擦除”
		List[] listArray = listStrArray;
		List<Integer> myList = new ArrayList<Integer>();
		myList.add(new Random().nextInt(100));
		// 把listArray的第一个元素赋为myList
		listArray[0] = myList;
		String s = listStrArray[0].get(0);
	}
}

上面程序中第 5 行代码已经发生了 “堆污染”。由于该方法有个形参是 List<String>... 类型,个数可变的形参相当于数组,但 Java 又不支持泛型数组,因此程序只能把 List<String>... 当成 List[] 处理,这里就发生了“堆污染”。

在 Java 6 以及更早的版本中,Java 编译器认为 faultyMethod() 方法完全没有问题,既不会提示错误,也没有提示警告。

等到使用该方法时,例如如下程序:

public class ErrorUtilsTest
{
	public static void main(String[] args) 
	{
		ErrorUtils.faultyMethod(Arrays.asList("Hello!")
			, Arrays.asList("World!"));    //①
	}
}

编译该程序将会在 ① 号代码处引发一个 unchecked 警告。这个 unchecked 警告出现得比较 “突兀”:定义 faultyMethod() 方法时没有任何警告,调用该方法时却引发了一个“警告”。

上面程序故意利用了“堆污染”,因此程序运行时也会在①号代码处引发 ClassCastException 异常。

从 Java 7 开始,Java 编译器将会进行更严格的检查,Java 编译器在编译 ErrorUtils 时就会发出一个如于所示的警告。

ErrorUtils.java:15: 警告: [unchecked] 参数化 vararg 类型 List<String> 的堆可能已受污染
    public static void faultyMethod(List<String>... listStrArray)
                                                       ^
1 个警告

由此可见,Java 7 会在定义该方法时就发出 “堆污染” 警告,这样保证开发者 “更早” 地注意到程序中可能存在的 “漏洞”。

但在有些时候,开发者不希望看到这个警告,则可以使用如下 3 种方式来“抑制”这个警告。

使用 @SafeVarargs 修饰引发该警告的方法或构造器。

使用 @SuppressWarnings(¨unchecked¨) 修饰。

编译时使用 -Xlint:varargs 选项。

很明显,第 3 种方式一般比较少用.通常可以选择第 1 种或第 2 种方式,尤其是使用 @SafeVarargs 修饰引发该警告的方法或构造器,它是 Java 7 专门为抑制“堆污染”警告提供的。

如果程序使用 @SafeVarargs 修饰 ErrorUtils 类中的 faultyMethod() 方法,则编译上面两个程序时都不会发出任何警告。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值