注解其实是代码里面的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,程序开发人员可以在不改变原有逻辑的情况下,在源文件当中嵌入一些补充的信息。代码分析工具、开发工具、和部署工具可以通过这些补充信息进行验证或者进行部署。
注解可以被用来为程序元素(类、方法、成员变量等)设置元数据。值得指出的是,注解不影响程序代码的执行,无论增加、删除注解,代码都是始终如一的执行。如果希望程序中的注解在运行的时候起到一定的作用,只有通过某种配套的工具对注解中的信息进行访问和处理,访问和处理注解的工具统称APT(Annotation Processing Tool)。
基础注解
注解必须使用工具来处理,工具负责提取注解当中包含的元数据,工具还会根据这种元数据增加额外的功能。使用注解之前要在其前面增加@符号,并把该注解当成一个修饰符使用,用于修饰它支持的程序元素。
5个基本注解如下:
- @Override
- @Deprecated
- @SuppressWarnings
- @SafeVarargs
- @FunctionalInterface
上面5个基本注解中@SafeVarargs是Java7新增的、@FunctionalInterface是Java8新增的。
限定重写父类方法:@Override
@Override就是用来指定方法覆载,它可以强制一个子类必须覆盖父类的方法。如下程序中使用@Override指定子类Apple的info()方法必须重写父类的方法。
注意: @Override只能修饰方法不能修饰其他程序元素。
@Deprecated
@Deprecated用于表示某个程序元素(类、方法等)已过时,当其他程序使用已过时类、方法时,编译器就会给出警告。如下程序指定Apple类中的info()方法已过时,其他程序中使用Apple类的info()方法 时编译器将会给出警告。
package test;
class Apple{
@java.lang.Deprecated()
public void info()
{
}
}
public class Deprecated {
public static void main(String[] args) {
new Apple().info();
}
}
抑制编译器警告:@SuppressWarnings
@SuppressWarnings指示被该注解修饰的程序元素(以及该程序元素中的所有子元素)取消显式指定的编译器警告。
@SuppressWarnings将会一直作用到该程序元素中的所有子元素。
堆污染警告
之前的博客在学习泛型擦除的时候,介绍了如下的代码将会导致运行时异常。
package test;
import java.util.ArrayList;
import java.util.List;
class Apple{
@java.lang.Deprecated()
public void info()
{
}
}
public class Deprecated {
public static void main(String[] args) {
new Apple().info();
List list = new ArrayList<Integer>();
list.add(20);
//添加元素的引发uncheck异常
List<String> ls = list;
//这一步将会引起“未经检查的转换”,编译、运行时非常正常
System.out.print(ls.get(0));
//但是只要访问ls中的数据就会产生运行时的异常。
}
}
Java把引发这种错误的原因称为“堆污染”(Heap pollution),当把一个不带泛型的对象赋给一个带泛型的变量时,往往就会发生这种“堆污染”。
对于形参个数可变的方法,该形参的类型又是泛型,这将更容易导致“堆污染”。例如以下的工具类。
package test;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class ErrorUtils {
public static void faultyMethod(List<String>... listStrArray) {
List[] listArray = listStrArray;
List<Integer> myList = new ArrayList();
myList.add(new Random().nextInt(100));
listArray[0] = myList;
String s = listStrArray[0].get(0);
}
}
上面的程序中的粗体字代码已经发生了堆污染。由于该方法有个形参是List<String>…类型,个数可变的形参相当于数组,但Java又不支持泛型数组,因此程序只能List<String>…当成List[]处理,这里就发生了堆污染。
在Java6以及更早的版本的中,Java编译器认为faultyMethod()方法完全没有问题,既不是提示错误,也没有提示警告。
等使用该方法时,例如如下程序
package test;
import java.util.Arrays;
public class ErrorUtilsTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
ErrorUtils.faultyMethod(Arrays.asList("hello1"),Arrays.asList("world!"));
}
}
编译该程序将会引发一个unchecked警告。这个unchecked警告出现的比较“突兀”:定义faultyMethod()方法时没有警告,调用该方法时却引发了一个“警告”。
从Java7开始,Java编译器将会进行更严格的检查,Java编译器在编译ErrorUtils时就会发生一个如下的警告。
Type safety: Potential heap pollution via varargs parameter listStrArray
由此可见,Java7会在定义该方法的时候就发出堆污染警告,这样保证了开发者“更早”的注意到程序中可能出现的漏洞。
但有些时候,开发者不希望看到这个警告,则可以使用以下三种方法来“抑制”这个警告:
- 使用@SafeVarags修饰引发该警告的方法或者构造器。Java9增强了该注解,允许使用该注解修饰私有实例方法。
- 使用@SuppressWarning(“unchecked”)修饰
- 编译时使用-Xlint:varargs选项。
很明显,第三种方法一般比较少用,通常可以选择第一种或者第二种方式,尤其使用第一种方式,他是Java7专门为抑制“堆污染”警告提供的。
如果使用@SafeVarags修饰ErrorUtils类中faultyMethod()方法,则编译上面两个程序时都不会发出警告。
函数式接口与@FunctionalInterface
上面已经提到,从java8开始:如果从程序中只有一个抽象方法(可以包含多个默认方法或者多个static方法),该接口就是函数式接口。
@FunctionalInterface就是用来指定某个接口必须是函数式接口。例如,如下程序使用@FunctionalInterface修饰了函数式接口。
函数式接口就是为了Java8的Lambda表达式准备的,Java8允许使用Lambda表达式创建函数式接口的实例,因此Java8专门增加了@FunctionalInterface
@FunctionalInterface
public interface FunInterface{
static void foo(){
System.out.println("1111");
}
default void bar(){
System.out.print("2222");
}
void test();
}