注解:
1. 注解是Java 5的一个新特性。注解是插入你代码中的一种注释或者说是一种元数据(meta data)。这些注解信息可以在编译期使用预编译工具进行处理(pre-compiler tools),也可以
在运行期使用Java反射机制进行处理。下面是一个类注解的例子:
@MyAnnotation(name="someName", value = "Hello World") public class TheClass {}
在TheClass类定义的上面有一个@MyAnnotation的注解。注解的定义与接口的定义相似,下面是MyAnnotation注解的定义:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MyAnnotation { public String name(); public String value(); }
在interface前面的@符号表名这是一个注解,一旦你定义了一个注解之后你就可以将其应用到你的代码中,就像之前我们的那个例子那样。
在注解定义中的两个指示@Retention(RetentionPolicy.RUNTIME)和@Target(ElementType.TYPE),说明了这个注解该如何使用。
@Retention(RetentionPolicy.RUNTIME)表示这个注解可以在运行期通过反射访问。如果你没有在注解定义的时候使用这个指示那么这个注解的信息不会保留到运行期,这样反射就无法获取它的信息。
@Target(ElementType.TYPE) 表示这个注解只能用在类型上面(比如类跟接口)。你同样可以把Type改为Field或者Method,或者你可以不用这个指示,这样的话你的注解在类,方法和变量上就都可以使用了。
2. 类注解获取:
1).获取注解数组:
Annotation[] annotations = aClass.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
}
}
2). 获取指定注解:
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);
3. 获取方法注解:
1).获取方法注解数组:Annotation[] annotations = method.getDeclaredAnnotations();
2).获取指定的方法注解:Annotation annotation = method.getAnnotation(MyAnnotation.class);
4. 获取参数注解:
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
需要注意的是Method.getParameterAnnotations()方法返回一个注解类型的二维数组,每一个方法的参数包含一个注解数组。
5. 获取变量注解:
1).获取所有变量注解:Annotation[] annotations = field.getDeclaredAnnotations();
2).获取指定变量注解:Annotation annotation = field.getAnnotation(MyAnnotation.class);
泛型:
1. 两个典型的使用泛型的场景:
1).声明一个需要被参数化(parameterizable)的类/接口。
2).使用一个参数化类。
2. 获取泛型方法返回类型
3. 获取泛型方法参数类型
4. 获取泛型变量类型
数组
1. java.lang.reflect.Array
Java反射机制通过java.lang.reflect.Array这个类来处理数组。不要把这个类与Java集合套件(Collections suite)中的java.util.Arrays混淆,java.util.Arrays是一个提供了
遍历数组,将数组转化为集合等工具方法的类。
2. 创建一个数组:
Java反射机制通过java.lang.reflect.Array类来创建数组。
int[] intArray = (int[]) Array.newInstance(int.class, 3);
这个例子创建一个int类型的数组。Array.newInstance()方法的第一个参数表示了我们要创建一个什么类型的数组。第二个参数表示了这个数组的空间是多大。
3. 访问一个数组:
通过Java反射机制同样可以访问数组中的元素。具体可以使用Array.get(…)和Array.set(…)方法来访问。下面是一个例子:
int[] intArray = (int[]) Array.newInstance(int.class, 3);
Array.set(intArray, 0, 123);
Array.set(intArray, 1, 456);
Array.set(intArray, 2, 789);
System.out.println("intArray[0] = " + Array.get(intArray, 0));
System.out.println("intArray[1] = " + Array.get(intArray, 1));
System.out.println("intArray[2] = " + Array.get(intArray, 2));
4. 获取数组的Class对象:
1).如果不通过反射的话你可以这样来获取数组的Class对象:Class stringArrayClass = String[].class;
2).如果使用Class.forName()方法来获取Class对象则不是那么简单。比如你可以像这样来获得一个原生数据类型(primitive)int数组的Class对象:
Class intArray = Class.forName("[I");
在JVM中字母I代表int类型,左边的‘[’代表我想要的是一个int类型的数组,这个规则同样适用于其他的原生数据类型。
3).对于普通对象类型的数组有一点细微的不同:Class stringArrayClass = Class.forName("[Ljava.lang.String;");
注意‘[L’的右边是类名,类名的右边是一个‘;’符号。这个的含义是一个指定类型的数组。
4).需要注意的是,你不能通过Class.forName()方法获取一个原生数据类型的Class对象。下面这两个例子都会报ClassNotFoundException:
Class intClass1 = Class.forName("I");
Class intClass2 = Class.forName("int");
通常会用下面这个方法来获取普通对象以及原生对象的Class对象:
public Class getClass(String className){
if("int" .equals(className)) return int .class;
if("long".equals(className)) return long.class;
...
return Class.forName(className);
}
一旦你获取了类型的Class对象,你就有办法轻松的获取到它的数组的Class对象,你可以通过指定的类型创建一个空的数组,然后通过这个空的数组来获取数组的Class对象。这样
做有点讨巧,不过很有效。如下例:
Class theClass = getClass(theClassName);
Class stringArrayClass = Array.newInstance(theClass, 0).getClass();
这是一个特别的方式来获取指定类型的指定数组的Class对象。无需使用类名或其他方式来获取这个Class对象。
5).为了确保Class对象是不是代表一个数组,你可以使用Class.isArray()方法来进行校验:
Class stringArrayClass = Array.newInstance(String.class, 0).getClass();
System.out.println("is array: " + stringArrayClass.isArray());
5. 获取数组的成员类型
一旦你获取了一个数组的Class对象,你就可以通过Class.getComponentType()方法获取这个数组的成员类型。成员类型就是数组存储的数据类型。例如,数组int[]的成员类型就是一个
Class对象int.class。String[]的成员类型就是java.lang.String类的Class对象。下面是一个访问数组成员类型的例子:
String[] strings = new String[3];
Class stringArrayClass = strings.getClass();
Class stringArrayComponentType = stringArrayClass.getComponentType();
System.out.println(stringArrayComponentType);
下面这个例子会打印“java.lang.String”代表这个数组的成员类型是字符串。
动态代理:
1. 创建代理
你可以通过使用Proxy.newProxyInstance()方法创建动态代理。newProxyInstance()方法有三个参数:
1).类加载器(ClassLoader)用来加载动态代理类。
2).一个要实现的接口的数组。
3).一个InvocationHandler把所有方法的调用都转到代理上。
InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(MyInterface.class.getClassLoader(),new Class[] { MyInterface.class },handler);
在执行完这段代码之后,变量proxy包含一个MyInterface接口的的动态实现。所有对proxy的调用都被转向到实现了InvocationHandler接口的handler上。
2. InvocationHandler接口
在前面提到了当你调用Proxy.newProxyInstance()方法时,你必须要传入一个InvocationHandler接口的实现。所有对动态代理对象的方法调用都会被转向到InvocationHandler接口的
实现上,下面是InvocationHandler接口的定义:
public interface InvocationHandler{
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
下面是它的实现类的定义:
public class MyInvocationHandler implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//do something "dynamic"
}
}
传入invoke()方法中的proxy参数是实现要代理接口的动态代理对象。通常你是不需要他的。
invoke()方法中的Method对象参数代表了被动态代理的接口中要调用的方法,从这个method对象中你可以获取到这个方法名字,方法的参数,参数类型等等信息。关于这部分内容可以查阅之前有关Method的文章。
Object数组参数包含了被动态代理的方法需要的方法参数。注意:原生数据类型(如int,long等等)方法参数传入等价的包装对象(如Integer, Long等等)。
3. 常见用例
动态代理常被应用到以下几种情况中
数据库连接以及事物管理
单元测试中的动态Mock对象
自定义工厂与依赖注入(DI)容器之间的适配器
类似AOP的方法拦截器
动态类加载或重载:
1. 类加载器
所有Java应用中的类都是被java.lang.ClassLoader类的一系列子类加载的。因此要想动态加载类的话也必须使用java.lang.ClassLoader的子类。
一个类一旦被加载时,这个类引用的所有类也同时会被加载。类加载过程是一个递归的模式,所有相关的类都会被加载。但并不一定是一个应用里面所有类都会被加载,与这个被加载类的引用链无关的类是
不会被加载的,直到有引用关系的时候它们才会被加载。
2. 类加载体系
在Java中类加载是一个有序的体系。当你新创建一个标准的Java类加载器时你必须提供它的父加载器。当一个类加载器被调用来加载一个类的时候,首先会调用这个加载器的父加载器来加载。如果父加载器
无法找到这个类,这时候这个加载器才会尝试去加载这个类。
3. 类加载,类加载器加载类的顺序如下:
1).检查这个类是否已经被加载。
2).如果没有被加载,则首先调用父加载器加载。
3).如果父加载器不能加载这个类,则尝试加载这个类。
实现一个有重载类功能的类加载器,它的顺序与上述会有些不同。类重载不会请求的他的父加载器来进行加载。在后面的段落会进行讲解。
4. 动态类加载
动态加载一个类十分简单。你要做的就是获取一个类加载器然后调用它的loadClass()方法。
5. 动态类重载
6. 自定义类重载