注解
注解也叫元数据,是一种代码级别的说明;
它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。注解是以‘@注解名’在代码中存在的,根据注解参数的个数,我们可以将注解分为:标记注解、单值注解、完整注解三类。
作用分类:
1.编写文档:通过代码里标识的元数据生成文档(doc文档);
2.代码分析:通过代码里标识的元数据对代码进行分析(使用反射);
3.编译检查:通过代码里标识的元数据能让编译器实现基本的编译检查。
注解的应用结构:
注解类:
@interface A{
}
应用了“注解类”的类:
@A
class B{
}
对“应用了注解类的类”进行反射操作的类
Class C{
B.class.isAnnotionPresent(A.class);
A a=B.class.getAnnotion(A.class);
}
在Java编译器编译时,它会识别在源代码里添加的注解是否还会保留,这就是RetentionPolicy。下面是Java定义的RetentionPolicy枚举:
编译器的处理有三种策略:
1.将注解保留在编译后的类文件中,并在第一次加载类时读取它
2.注解保留在编译后的类文件中,但是在运行时忽略它
3.按照规定使用注解,但是并不将它保留到编译后的类文件中
RetentionPolicy枚举值有三种,分别为:
CLASS
编译器将把注释记录在类文件中,但在运行时 VM 不需要保留注释。
RUNTIME
编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。
SOURCE
编译器要丢弃的注释。
自定义注解:
package csing.net;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
public @interface itCastAnnotation {
}
为注解添加属性
package csing.net;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
public @interface itCastAnnotation {
//为注解添加属性。
String color();
}
package csing.net;
//为注解添加属性值;当只有单一属性时,也可只用写属性值即可。
@itCastAnnotation(color="red")
public class AnnotationDemo {
/**
* @param args
*/
public static void main(String[] args) {
//如果itCastAnnotation.class类型的注释存在于AnnotationDemo.class则返回true
if(AnnotationDemo.class.isAnnotationPresent(itCastAnnotation.class)){
//如果存在AnnotationDemo.class该元素指定类型 的注释则返回Annotation
itCastAnnotation itcast=(itCastAnnotation)(AnnotationDemo.class).getAnnotation(itCastAnnotation.class);
System.out.println(itcast);
//打印属性值;
System.out.println(itcast.color());
}
}
}
泛型
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序的非法输入。
注意:如集合的泛型为Integer,可通过反射骗过编译器向该集合中存储字符串。泛型是给编译器使用的,编译通过后集合即去除了泛型标识。
参数化类型与原始类型的兼容性:
参数化类型可以引用原始类型的对象,编译器报告警告:
Collection<String> str=new Vector();
原始类型引用参数类型的对象,编译报告警告:
Collection str=new Vector<String>();
参数化类型不考虑参数的继承关系:
Vector<String> v=new Vector<Object>(); //错误
Vector<Object> v=new Vector<String>(); //错误
泛型中如果使用?通配符,不能调用与参数类型有关的方法。限定通配符总是包括自己。
通过反射获得泛型的实际类型参数
package csing.net;
import java.lang.reflect.*;
import java.util.*;
public class selfGeneric {
/**
* @param args
*/
public static void main(String[] args)throws Exception {
//取得方法;
Method applyMethod=selfGeneric.class.getMethod("applyGeneric", Vector.class);
//返回Method对象返回的方法的形参类型。
Type[] t=applyMethod.getGenericParameterTypes();
ParameterizedType pType=(ParameterizedType)t[0];
//返回原始参数类型;
System.out.println(pType.getRawType());
//返回实际类型参数;
System.out.println(pType.getActualTypeArguments()[0]);
}
public static void applyGeneric(Vector<Date> vd){
}
}
类加载器
类加载器负责加载java类的字节码到java虚拟机中。
java虚拟机使用java类的方式如下:java源程序(.java文件)经过java编译器编译后转换成java字节码(.class文件),类加载器负责读取java字节代码,并转换成java.lang.Class的一个实例,每个这样的实例用来标识一个java类。
类加载器父子关系与管辖范围
java类加载器的部分方法和说明:
方法 | 说明 |
---|---|
getParent() | 返回该类加载器的父类加载器。 |
loadClass(String name) | 加载名称为 name 的类,返回的结果是 java.lang.Class 类的实例。 |
findClass(String name) | 查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。 |
findLoadedClass(String name) | 查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class 类的实例。 |
defineClass(String name, | 把字节数组 b 中的内容转换成 Java 类,返回的结果是 java.lang.Class 类的实例。这个方法被声明为 final 的。 |
resolveClass(Class<?> c) | 链接指定的 Java 类。 |
实例:
package csing.net;
public class ClassLoaderDemo {
/**
* @param args
*/
public static void main(String[] args) {
//获得本类使用的类加载器的名称;
String loaderStr=ClassLoaderDemo.class.getClassLoader().getClass().getName();
//打印本类类加载器;
System.out.println(loaderStr);
//获得本类使用的类加载器;
ClassLoader classLoader=ClassLoaderDemo.class.getClassLoader();
//打印本类加载器和其父类类加载器;
while(classLoader!=null){
System.out.println(classLoader.getClass().getName());
classLoader=classLoader.getParent();
}
System.out.println(classLoader);
}
}
类加载器的委托机制
当java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
1.首先当前线程的类加载器去加载线程中的第一个类;
2.如果类A中引用了类B,java虚拟机将使用加载类A的类加载器去加载类B;
3.可以直接调用ClassLoader.loadClass()方法指定某个类加载器去加载某个类。
类加载器加载类时,先委托给其最上级类加载器,再依次向下用父类加载器,当所有父类加载器没有加载到类时,回到发起者加载器,还加载不了时即抛异常。
自定义类加载器
自定义类加载器需继承ClassLoader,自定义的类加载器会调用loadClass()方法使用父类加载器,如果父类加载器不能加载自定义类,则调用自身的findClass()方法,所以自定义的类加载器继承父类的ClassLoader后需重写findClass方法。
自定义类加载器示例
package csing.net;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class otherClassLoader extends ClassLoader {
private String name; // 类加载器的名字
private String path = "d://"; // 加载类的路径
private final String fileType = ".class"; // .class文件扩展名
public otherClassLoader(String name) {
super();// 让系统加载器成为该类的加载器的父类加载器
this.name = name;
}
public otherClassLoader(ClassLoader parent, String name) {
super(parent); // 显示指定该类加载器的父加载器
this.name = name;
}
@Override
public String toString() {
return this.name;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
/**
* 读取class文件作为二进制流放入到byte数组中去
* @param name
* @return
*/
private byte[] loadClassData(String name) {
InputStream in = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
try {
name = name.replace(".", "\\");
in = new BufferedInputStream(new FileInputStream(new File(path
+ name + fileType)));
baos = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = in.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return data;
}
/**
* JVM调用的加载器的方法
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = this.loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
public static void main(String[] args) throws Exception {
otherClassLoader loader1 = new otherClassLoader("loader1");
System.out.println("before");
loader1.setPath("H:\\advjava\\ReflectDemo\\bin\\");
test(loader1);
}
public static void test(ClassLoader loader) throws Exception{
Class<?> clazz = loader.loadClass("cslib.hello");
Object object = clazz.newInstance();
System.out.println(object);
}
}
代理
代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式的思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象。这些额外的操作通常需要与实际对象进行通信。
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。
JVM可以在运行器件动态生成类的字节码,这种动态生成的类往往被用作代理类,即动态代理。JVM生成的动态类必须实现一个或多个接口,所以动态生成的类只能用作具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,该子类可以用作该类的代理,所以要为一个没有实现接口的类生成动态代理类,可以使用CGLIB。
代理类的各个方法通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置上加上系统代码:
1.在调用目标方法之前;
2.在调用目标方法之后;
3.在调用目标方法前后;
4.在处理目标方法异常的catch块中。
创建动态代理类并查看方法列表信息:
package csing.net;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
public class ProxyDemo {
/**
* @param args
*/
public static void main(String[] args) {
//获得代理类的字节码;
Class<?> classProxy=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println("<-------------------Constructor------------------>");
//获得代理类的名称;
System.out.println(classProxy.getName());
//获得代理类的构造方法;
Constructor[] constructors=classProxy.getConstructors();
//遍历代理类的构造方法;
for(Constructor con:constructors){
String name=con.getName();
StringBuilder sb=new StringBuilder(name);
sb.append('(');
//获得代理类构造方法的参数;
Class[] cla=con.getParameterTypes();
//遍历参数并取得参数名打印;
for(Class cl:cla){
sb.append(cl.getName()+',');
}
if(cla!=null){
sb.deleteCharAt(cla.length-1);
}
sb.append(')');
System.out.println(sb.toString());
}
System.out.println("<-------------------Parameters.------------------>");
//取得代理类的方法;
Method[] methods=classProxy.getMethods();
//遍历代理类的方法;
for(Method con:methods){
//获得方法名称;
String name=con.getName();
StringBuilder sb=new StringBuilder(name);
sb.append('(');
//获得方法的参数并遍历打印;
Class[] cla=con.getParameterTypes();
for(Class cl:cla){
sb.append(cl.getName()+',');
}
if((cla!=null)&&(cla.length!=0)){
sb.deleteCharAt(cla.length-1);
}
sb.append(')');
System.out.println(sb.toString());
}
}
}