【effective Java读书笔记】注解(一)

【effective Java读书笔记】注解(一)

前言:

为什么许多面试Java都喜欢用这本书上的知识?

原因很简单,读懂这本书,其实考java深入一些就是考源码分析,这本书把jdk优秀的源码许多地方都有所涉及,并形成了自己的总结。我们只需要吸收这部分优秀的代码理念。

阅读本文之前,如果对注解了解较少,可以阅读这篇文章了解基本的概念。

什么情况下我们会自定义注解?书中提及的是工具铁匠,也就是给开发人员编写开发工具的开发人员会用到。但是,这并不妨碍我们学会理解使用它。


一、注解优先于命名模式

我们平时写代码看到注解的人多,关注的可能并不多。

先说命名模式的概念:一种命名方式,通过命名区分方法的作用。

书中提及命名模式的三个缺点:

1、命名错误导致失败,但并不会错误提示。给人造成错误的安全感。通俗点说,也就是,看上去一切正常。

我的理解:

大概,从这我们可以猜测,命名模式的使用,其实无外乎也是通过反射去遍历,根据约束条件获取到所约束的方法的名字,例如Test开头的约定为测试类。然后执行这些方法。

当然,如果我们命名的时候把Test手误打成了Tset,顺序颠倒的命名,那么肯定是找不到这个方法,更不用说执行这个方法了。

假如是个单元测试方法,方法都没执行,也就不存在错误信息报错,那么我们就得到了错误安全感。

2、前面提到的方法的命名模式,类级别的无法确保他们只用于相应的元素上。什么意思呢?

我的理解:也就是如果找到这个需要执行的类,那么其中所有的方法都要执行测试方法、无关名称。原理依然是反射,通过反射执行命名模式约束的类的所有方法。相当于无差别伤害了。

3、没有提供将参数与程序元素关联起来的好方法。

反之也就是注解的三个优点,具体会在后续的例子中提及。

看一个demo:

写一个简单的注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
写一个上述注解的使用类:

public class Sample {
	@Test
	public static void m1(){}
	public static void m2(){}
	@Test
	public static void m3(){
		throw new RuntimeException("Boom");
	}
	public static void m4(){}
	@Test
	public void m5(){}
	public static void m6(){}
	@Test
	public static void m7(){
		throw new RuntimeException("Crash");
	}
	public static void m8(){}
}
运行使用注解的类的方法:

public class RunTests {
	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		int tests = 0;
		int passed = 0;
		Class testClass = Class.forName(args[0]);
		for (Method m:testClass.getDeclaredMethods()) {
			if (m.isAnnotationPresent(Test.class)) {
				tests++;
				try {
					m.invoke(null);
					passed++;
				}catch(InvocationTargetException wrappedExc){
					Throwable exc = wrappedExc.getCause();
					System.out.println(m+"failed"+exc);
				} catch (Exception e) {
					System.out.println("INVALID @Test"+m);
				}
			}
		}
		System.out.printf("Passed:%d,Failed:%d%n",passed,tests-passed);
	}
}
运行结果如下:

public static void com.annotation.Sample.m3()failedjava.lang.RuntimeException: Boom

INVALID @Testpublic void com.annotation.Sample.m5()

public static void com.annotation.Sample.m7()failedjava.lang.RuntimeException: Crash

Passed:1,Failed:3

结果说明:

第1行,第3行是我抛出的异常m3,m7,第2行是无法唤醒这个方法。因为这个方法不是静态的。也就是说其实我们并没有获取到实例对象,只是获取到类。所以只能唤醒静态方法。

回到刚刚说的三个缺点,三个优点:

第一点、错误的安全感。使用@Test如果使用成@Tset那么编译直接报错。问题解决!

第二点、我们可以执行Sample类,唤醒Sample类中所有指定@Test的方法执行。问题解决!

第三点、根据入参约束程序,见下一个举例。

看修改后的demo:

注解稍有不同:添加一个泛型约束的值,主要是后面会根据入参确定值的类型

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
	Class<? extends Exception> value(); 
}
注解的使用类:(运算条件异常ArithmeticException)
public class Sample2 {
	@ExceptionTest(ArithmeticException.class)
	public static void m1() {
		int i = 0;
		i = i / i;
	}

	@ExceptionTest(ArithmeticException.class)
	public static void m2() {
		int[] a = new int[0];
		int i = a[1];
	}

	@ExceptionTest(ArithmeticException.class)
	public static void m3() {
	}
}
运行使用注解的类的方法:

public class RunTests2 {
	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		int tests = 0;
		int passed = 0;
		Class testClass = Class.forName(args[0]);
		for (Method m:testClass.getDeclaredMethods()) {
			if (m.isAnnotationPresent(ExceptionTest.class)) {
				tests++;
				try {
					m.invoke(null);
					System.out.printf("Test %s failed:no exception%n",m);
				}catch(InvocationTargetException wrappedExc){
					Throwable exc = wrappedExc.getCause();
					Class<? extends Exception> excType = m.getAnnotation(ExceptionTest.class).value();
					if (excType.isInstance(exc)) {
						passed++;
					}else {
						System.out.printf("Test %s failed:no expected %s,got %s%n",m,excType.getName(),exc);	
					}	
				} catch (Exception e) {
					System.out.println("INVALID @Test"+m);
				}
			}
		}
		System.out.printf("Passed:%d,Failed:%d%n",passed,tests-passed);
	}
}
运行结果:

Test public static void com.annotation.Sample2.m3() failed:no exception

Test public static void com.annotation.Sample2.m2() failed:no expected java.lang.ArithmeticException,gotjava.lang.ArrayIndexOutOfBoundsException: 1

Passed:1,Failed:2

结果说明:

与改进前代码的差别差异说明:关键代码如下

Class<? extends Exception> excType = m.getAnnotation(ExceptionTest.class).value();
					if (excType.isInstance(exc)) {
						passed++;
					}
取出入参即ArithmeticException.class,用它判断异常。第三个优点到此结束。
一点点扩展:注解参数灵活多样,可以是数组,例如:

注解如下:参数为数组

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest2 {
	Class<? extends Exception>[] value(); 
}
注解使用如下:

public class Sample3 {
	@ExceptionTest2({IndexOutOfBoundsException.class,NullPointerException.class})
	public static void doublyBad() {
		List<String> list = new ArrayList<String>();
		//在第5个位置添加空
		list.addAll(5,null);
	}
}
注解执行如下:

public class RunTests3 {
	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		int tests = 0;
		int passed = 0;
		Class testClass = Class.forName(args[0]);
		for (Method m:testClass.getDeclaredMethods()) {
			if (m.isAnnotationPresent(ExceptionTest2.class)) {
				tests++;
				try {
					m.invoke(null);
					System.out.printf("Test %s failed:no exception%n",m);
				}catch(InvocationTargetException wrappedExc){
					Throwable exc = wrappedExc.getCause();
					Class<? extends Exception>[] excType = m.getAnnotation(ExceptionTest2.class).value();
					int oldPassed = passed;
					for (Class<? extends Exception> e:excType) {
						if (e.isInstance(exc)) {
							passed++;
							System.out.printf("Test %s passed: %s%n",m,exc);
							break;
						}
					}
					if (passed==oldPassed) {
						System.out.printf("Test %s failed: %s %n",m,exc);
					}
				} 
			}
		}
		System.out.printf("Passed:%d,Failed:%d%n",passed,tests-passed);
	}
}
执行结果:

Test public static void com.annotation.Sample3.doublyBad() passed: java.lang.IndexOutOfBoundsException: Index: 5, Size: 0

Passed:1,Failed:0

关键代码如下:获取到入参数组类型,并逐个取出,符合异常类型的通过。

Class<? extends Exception>[] excType = m.getAnnotation(ExceptionTest2.class).value();
					int oldPassed = passed;
					for (Class<? extends Exception> e:excType) {
						if (e.isInstance(exc)) {
							passed++;
							System.out.printf("Test %s passed: %s%n",m,exc);
							break;
						}
					}


二、坚持使用OverRide注解(现代的ide都已经集成此功能)

简单提及一下,OverRide注解是基于方法签名(参数类型、顺序、长度),一致则使用OverRide。

唯一的例外则是集成父类抽象方法,例如抽象父类Person有抽象方法say,子类集成父类则不需要使用OverRide。当然此处现代编译器同样会添加OverRide注解。

父类:

public abstract class Person {
	abstract void say();
}
子类:

public class Bob extends Person {
	@Override
	void say() {
	}
}
书中说此种方式可以不强制注解的原因是因为如果不覆盖,编译器也会提示必须对这个抽象方法say具体化。













评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值