----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------
1、反射的基础---Class
Java程序中的各个java类属于同一类事物,描述这类事物的java类名就是Class。
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是字节码,不同的类的字节码是不同的,这一个个的空间分别用一个个的对象来表示,这些对象显示具有相同的类型。
如何得到各个字节码对应的实例对象:
类名.class,例如:System.class;
对象.getClass(),例如:new Date().getClass();
Class.forName(类名),例如:Class.forName("java.util.Date");
九个预定义的Class实例对象:
八个基本类型、void
String str1 = "abc";
Class cls1 =str1.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");
System.out.println(cls1 == cls2);//true
System.out.println(cls1 == cls3);//true
System.out.println(cls1.isPrimitive());//false
System.out.println(int.class.isPrimitive());//true
System.out.println(int.class == Integer.class);//false
System.out.println(int.class == Integer.TYPE);//true
数组类型的Class类型对象:
Class.isArray();
总之,只要在源程序中出现的类型,都有各自的Class实例对象,例如:int[],void。
2、反射
反射就是把java类中的各种成分映射成相应的java类。
1>Constructor类:
Constructor类代表某个类中的一个构造方法。
得到某个类的所有构造方法
Constructor[] constructors = Class.forName("java.lang.string").getConstructors();
得到某个类的某个构造方法
Constructor constructor = Class.forName("java.lang.string").getConstructor(StringBuffer.class);
创建实例对象:
通常方式:
String str = new String(new StringBuffer("abc"));
反射方式:
Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
String str1 = (String) constructor1.newInstance(new StringBuffer("abc"));//得到构造器需要传递指定类型,调用构造器创建实例时也需要传递相应类型,如果此处参数为“abc”,将抛出参数不匹配异常。
Class.newInstance()方法:
String str2 = (String)Class.forName("java.lang.String").newInstance();
该方法首先得到默认的构造方法,然后用该构造方法创建实例对象,用缓存机制来保存默认构造方法的实例对象,所以反射比较消耗性能。
2>Field类:
Filed类代表某个类中的一个成员变量。
ReflectPoint rp1 = new ReflectPoint(3,5);
//Field为类上的成员变量,而不是指对象上的成员变量
Field fieldY = rp1.getClass().getField("y");
System.out.println(fieldY.get(rp1));
//获取私有属性值
Field fieldX = rp1.getClass().getDeclaredField("x");
fieldX.setAccessible(true);//设置属性可访问
System.out.println(fieldX.get(rp1));
3>Method类:
Method类代表某个类中的一个成员方法。
得到类中的某个方法:
Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
调用方法:
通常方式
System.out.println(str.charAt(1));
反射方式
System.out.println(charAt.invoke(str, 1));
注意:如果传递给Method对象的invoke()方法的第一个参数为null,说明Method对象对应的是一个静态方法。
4>用反射方式执行某个类中的main方法:
问题:
启动java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式调用main方法时,如何为invoke方法传递数组?按jdk1.5的语法,整个数据就是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,jdk为实现向下兼容,要按照jdk1.4的语法进行处理,即把数组打散成若干个单独的参数。所以在给main方法传递参数时,不能使用代码mainMethod。invoke(null,new String[]{"xxx"}),此时会出现参数类型不对的问题。
解决办法:
class TestArgument
{
public static void main(String[] args)
{
for(String arg : args)
{
System.out.println(arg);
}
}
}
//通常方式
TestArgument.main(new String[]{"111","222","333"});//反射方式
Method mainMethod = Class.forName("cn.itcast.day1.TestArgument").getMethod("main", String[].class);
//以下语句将抛出非法参数异常
//mainMethod.invoke(null, new String[]{"111","222","333"});
//以下语句将字符串数组包装成一个对象,将其打包传入main方法将避免异常
mainMethod.invoke(null, new Object[]{new String[]{"111","222","333"}});
mainMethod.invoke(null, (Object)new String[]{"111","222","333"});
5>数组的反射:
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
基本类型的一维数组可以被当做Object类型使用,但不能当做Object[]类型使用,非基本类型的一维数组既可以当做Object类型使用,也可以当做Object[]类型使用。
Arrays.asList()方法处理int[]和String[]时的差异:asList()方法形参为Object[],传入int[]时将其作为一个object整体,而传入String[]可解析为一个object[]数组。
Array工具类用于完成对数组的反射操作。
获取参数的类型:
Object[] argument = new Object[]{1,"abc"};
System.out.println(argument[0].getClass().getName());//输出java.lang.Integer
int[] a1 = new int[]{1,2,3};
int[] a2 = new int[4];
int[][] a3 = new int[3][4];
String[] a4 = new String[]{"a","b","c"};
System.out.println(a1.getClass() == a2.getClass());//true
//以下两条语句编译不通过
// System.out.println(a1.getClass() == a3.getClass());//false
// System.out.println(a1.getClass() == a4.getClass());//false
System.out.println(a1.getClass().getName());//输出[I
System.out.println(a1.getClass().getSuperclass().getName());//输出java.lang.Object
System.out.println(a4.getClass().getSuperclass().getName());//输出java.lang.Object
Object obj1 = a1;
Object obj2 = a3;
Object obj3 = a4;
// Object[] obj4 = a1;//编译不通过,int是原始数据类型,所以int数组不是object数组
Object[] obj5 = a3;
Object[] obj6 = a4;
System.out.println(a1);//输出[I@7c6768
System.out.println(a4);//输出[Ljava.lang.String;@7c6768
System.out.println(Arrays.asList(a1));//输出[[I@1f33675]
System.out.println(Arrays.asList(a4));//输出[a, b, c]
//对象为数组时打印数组中各个元素,否则打印对象本身
private static void printObject(Object obj)
{
Class clazz = obj.getClass();
if(clazz.isArray())
{
int len = Array.getLength(obj);
for(int i=0; i<len; i++)
{
System.out.println(Array.get(obj, i));
}
}
else
{
System.out.println(obj);
}
}
3、ArrayList和HashSet的比较,HashCode的分析
哈希算法:将集合分成若干存储区域,每个对象可以计算出一个哈希码,将哈希码分组,每组对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储在哪个区域。
HastSet就是采用哈希算法存取对象的集合,它内部采用对某个数字n进行取余的方式对哈希码进行分组和划分对象的存储区域。Object类中定义了一个hashCode()方法来返回每个java对象的哈希码,当从HashSet集合中查找某个对象时,java系统首先调用对象的hashCode()方法获得该对象的哈希码,然后根据哈希码找到对应的存储区域,最后取出该存储区域的每个元素和该对象进行equals()方法比较,这样不用遍历集合中所有元素就可以得到结论,由此可见,HashSet集合具有很好的对象检索性能。但HashSet集合存储对象的效率相对要低些。
ReflectPoint pt1 = new ReflectPoint(3,3);
ReflectPoint pt2 = new ReflectPoint(5,5);
ReflectPoint pt3 = new ReflectPoint(3,3);
Collection<ReflectPoint> arrayList = new ArrayList<ReflectPoint>();
arrayList.add(pt1);
arrayList.add(pt2);
arrayList.add(pt3);
arrayList.add(pt1);
System.out.println(arrayList.size());//输出4
Collection<ReflectPoint> hashSet = new HashSet<ReflectPoint>();
hashSet.add(pt1);
hashSet.add(pt2);
hashSet.add(pt3);
hashSet.add(pt1);
System.out.println(hashSet.size());//输出2
pt1.y = 7;//属性值改变导致hashSet值改变,存储位置发生改变,无法删除,数据量大时将导致内存泄露
hashSet.remove(pt1);
System.out.println(hashSet.size());//输出2
4、反射的作用---实现框架功能
框架和工具类的区别:工具类是被用户的类调用,而框架则是调用用户提供的类。框架、用户类、工具类的关系犹如房子、门窗、锁的关系。
反射的作用:因为在写某些程序如框架时,无法知道被调用的类名,所以在程序中无法直接new某个类的实例对象,这就需要用到反射。
InputStream ips = new FileInputStream("config.properties");
Properties props = new Properties();
props.load(ips);//加载文件中所有键值对
String className = props.getProperty("className");
Collection arrayList = (Collection) Class.forName(className).newInstance();//利用反射根据传入的名字创建类的实例对象,在此无需知道具体类名
5、注解定义及几个基本注解
注解相当于一个标记,javac编译器,开发工具和其他程序可以用反射来了解类及各种元素上有无各种标记,根据标记类型,去做相应处理。
注解可以加在包,类,字段,方法,方法的参数及局部变量上。
几个基本注解:
@SuppressWarnings("deprecation") 抑制警告
@Deprecated 过时的
@Override 覆盖
@Retention元注解,三种取值:
RetentionPolicy.SOURCE,RetentionPolicy.CLASS,RetentionPolicy.RUNTIME,分别对应:
java源文件,class文件,内存中的字节码
@Target元注解:设置注解可应用在哪些元素上,默认值为任何元素。
6、为注解增加基本属性
1>定义基本类型的属性:
在注解类中增加String color();
在使用注解类的类中增加@MyAnnotation(color="red")
2>用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法:
MyAnnotation annotation = AnnotationTest.class.getAnnotation(MyAnnotation.class);
System.out.println(annotation.color());
3>为属性指定缺省值:
String color default "yellow";
4>设置属性值:
如果注解类中包含多个属性,那么使用此注解类时只需设置没有默认值的属性值,否则新值将覆盖默认值。
5>数组类型的属性:
int[] arrayAttr() default {1,2,3}
@MyAnnotation(arrayAttr={4,5,6})
如果数组属性中只有一个元素,属性值部分可以省略大括号。
6>枚举类型的属性:
TrafficLamp lamp();
@MyAnnotation(lamp=TrafficLamp.GREEN)
7>注解类型的属性:
MetaAnnotation annotationAttr() default @MetaAnnotation("xxx");
@MyAnnotation(annotationAttr=@MetaAnnotation("yyy"))
调用方式:
MetaAnnotation ma = myAnnotation.annotationAttr();
system.out.println(ma.value);
7、代理的概念与作用
如果要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如异常处理,日志,计算方法的运行时间,事务处理等等,这将用到代理类。
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中是使用目标类还是代理类,以后很容易切换。
交叉业务的编程即为面向方面编程(Aspect oriented program,简称AOP),AOP的目标就是使交叉业务模块化,可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的。
代理是实现AOP功能的核心和关键技术。
动态代理:
要为系统中的各个接口的类增加代理功能,需要很多的代理类,全部采用静态代理类,将是件很麻烦的事情,JVM可以在运行期间动态的生成类的字节码,即动态代理。
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB。
代理类的各个方法通常调用目标的相应方法和返回目标返回的结果外,还可以在代理方法的如下位置增加系统功能代码:
1>在调用目标方法之前
2>在调用目标方法之后
3>在调用目标方法前后
4>在调用目标方法 异常的catch块中
创建动态类的实例对象:
1>用反射获取构造方法;
2>编写一个最简单的InvocationHandle类;
3>调用构造方法创建类的实例对象,并将编写的InvocationHandle类的实例对象传进去;
// 获取动态类的构造方法,需要传入InvocationHandler对象
Constructor constructor = clazzProxy
.getConstructor(InvocationHandler.class);
class myInvocationHandler1 implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
}
Collection proxy1 = (Collection) constructor
.newInstance(new myInvocationHandler1());
System.out.println(proxy1);
proxy1.clear();
用Proxy.newProxyInstance()方法直接创建代理对象:
Collection proxy3 = (Collection) Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[] { Collection.class },
new InvocationHandler() {
ArrayList target = new ArrayList();
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
long beginTime = System.currentTimeMillis();
Object retVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + "running time is:" + (endTime - beginTime));
return retVal;
}
});
proxy3.add("zhangsan");
proxy3.add("lisi");
System.out.println(proxy3.size());
----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------