黑马程序员-Java反射机制
反射的概念
反射就是把java类中的各种成分映射成相应的java类。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示。
反射的基础:Class-->用来描述java中的类
如何得到各个字节码对应的实例对象
->类名.class 或者 类名.TYPE
->对象.getClass()
->Class.forName("类名")
总之,只要在源程序中出现的类型,都有各自的Class实例对象。
String str1 = "abc"; //三种获取字节码的方法 Class cls1 = str1.getClass(); Class cls2 = String.class; Class cls3 = Class.forName("java.lang.String"); System.out.println(cls1 == cls2); System.out.println(cls2 == cls3);
运行结果:true true
注意:
1. int.class = integer.TYPE
2. 数组类型的class实例对象Class.isArray()为true
3. 反射不是java5的新特性
反射的作用
反射机制最重要的内容——检查类的结构。
在java.lang.reflect包中有三个类Field、Method、Constructor分别用于描述类的域、方法和构造器。
这三个类共有方法:
getModifiers //返回一个整形数值,用不同的位开关描述public和static这样的修饰符使用状况
getName //用来返回项目的名称
构造方法的反射应用
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"));
|--反射方式:String str = (String)constructor.newInstance(new StringBuffer("abc"));
|--Class.newInstance()方法:String obj = (String)Class.forName("java.lang.String").newInstance(); //该方法内部先得到默认构造方法,然后该构造方法创建实例对象。
成员变量的反射
Field类代表某个类中的一个成员变量
示例:通过Field调用成员变量
ReflectPiont.java
package com.itheima.reflect; public class ReflectPoint { private int x; public int y ; public ReflectPoint(int x, int y) { super(); this.x = x; this.y = y; } }
ReflectTest.java
package com.itheima.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Field; public class ReflectTest { public static void main(String[] args) throws Exception { ReflectPoint rp = new ReflectPoint(3, 5); Field fieldY = rp.getClass().getField( "y"); System. out.println(fieldY.get(rp)); //结果:5 } }
注意:如果想直接通过getField方法获取私有对象,会出现如下错误
Exception in thread "main" java.lang.NoSuchFieldException : x
解决方法:
Field fieldX = rp.getClass().getDeclaredField("x"); fieldX.setAccessible(true);
这种方法称之为暴力反射,使用setAccessible(true)使private类型的成员变量也可以被获取。
成员方法的反射
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对象对应的是静态方法!
数组与Object的关系及其反射类型
1. 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
package com.itheima.reflect; import java.util.Arrays; public class ReflectTest { public static void main(String[] args) throws Exception { int [] a1 = new int[3]; int [] a2 = new int[4]; int [][] a3 = new int[2][3]; String[] a4 = new String[3]; System.out.println(a1.getClass() == a2.getClass()); //结果:true System.out.println(a1.getClass() == a4.getClass()); //结果:false System.out.println(a1.getClass() == a3.getClass()); //结果:false } }
2. 代表数组的class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
3. 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,即可以做Object类型使用,又可以当作Object[]类型使用。
4. Array.asList()方法处理int[]和String[]时的差异
public class ReflectTest { public static void main(String[] args) throws Exception { int [] a5 = new int[]{1,2,3}; String[] a6 = new String[]{"a" ,"b" ,"c" }; //直接使用System.out.println无法打印出数组的内容 System. out .println(a5); //结果:[I@18a992f System. out .println(a6); //结果:[Ljava.lang.String;@4f1d0d //通过Arrays.asList方法打印出数组的内容 System. out .println(Arrays.asList(a5)); //结果:[[I@18a992f] //原因是因为JDK1.4中为Arrays.asList(Object[] a),JDK1.5中为Arrays.asList(T... a)。 //a5是int[]类型,JDK1.4中的asList方法处理不了,JDK1.5可以处理。但是JDK1.5将 int数组整体作为一个参数进行处理。 //因此最终结果就是将 int[]进行了封装,结果类型也就成了[[I。 System. out .println(Arrays.asList(a6)); //结果:[a, b, c] } }
5. Array工具类用于完成数组的反射操作。
public class ReflectTest { public static void main(String[] args) throws Exception { String[] a1 = new String[]{"a" ,"b" ,"c" }; String a2 = "xyz"; printObject(a1); printObject(a2); } public 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); } } }
6. ArrayList和HashSet的比较及Hashcode分析。
ArrayList:
Collection collections = new ArrayList(); ReflectPoint pt1 = new ReflectPoint(3, 3); ReflectPoint pt2 = new ReflectPoint(5, 5); ReflectPoint pt3 = new ReflectPoint(3, 3); collections.add(pt1); collections.add(pt2); collections.add(pt3); collections.add(pt1); System. out.println(collections.size()); //结果:4 Collection collections = new ArrayList(); ReflectPoint pt1 = new ReflectPoint(3, 3); ReflectPoint pt2 = new ReflectPoint(5, 5); ReflectPoint pt3 = new ReflectPoint(3, 3); collections.add(pt1); collections.add(pt2); collections.add(pt3); collections.add(pt1); System. out.println(collections.size()); //结果:4
HashSet:
Collection collections = new HashSet(); ReflectPoint pt1 = new ReflectPoint(3, 3); ReflectPoint pt2 = new ReflectPoint(5, 5); ReflectPoint pt3 = new ReflectPoint(3, 3); collections.add(pt1); collections.add(pt2); collections.add(pt3); collections.add(pt1); System. out.println(collections.size()); //结果:3
分析:
当集合为HashSet时,需要通过比较hashcode值以及equals方法是否返回true决定是否放入。如果hashcode值相等并且equals方法返回true,那么就不会放入。因此,集合size为3。
hashCode()和equals()覆盖:
@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + x; result = prime * result + y; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true ; if (obj == null) return false ; if (getClass() != obj.getClass()) return false ; ReflectPoint other = (ReflectPoint) obj; if (x != other.x) return false ; if (y != other.y) return false ; return true ; }
此时,运行ReflectTest.java结果为2。
注意:
当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。
框架的概念及用反射技术开发框架的原理
1. 框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
2. 框架程序怎样调用以后写的类呢?很多时候程序无法知道被调用的类名,所以,在程序中无法直接new某个类的实例对象,而要用反射方式来做。
ReflectTest.java
public class ReflectTest { public static void main(String[] args) throws Exception { InputStream is = new FileInputStream("config.properties" ); Properties props = new Properties(); props.load(is); is.close(); String className = (String)props.get( "className"); Collection collections = (Collection)Class.forName(className).newInstance(); ReflectPoint pt1 = new ReflectPoint(3, 3); ReflectPoint pt2 = new ReflectPoint(5, 5); ReflectPoint pt3 = new ReflectPoint(3, 3); collections.add(pt1); collections.add(pt2); collections.add(pt3); collections.add(pt1); System. out.println(collections.size()); } }
config.properties文件直接放在根目录下:
|--config.properties
className = java.util.ArrayList //结果:4
|--config.properties
className = java.util.HashSet //结果:3
用类加载器的方式管理资源和配置文件
//方式一:采用类加载器进行加载,使用相对路径的方式 //InputStream is = ReflectTest.class.getClassLoader().getResourceAsStream("com/itheima/day1/config.properties"); //方式二:利用Class方式进行加载,使用相对路径的方式 //InputStream is = ReflectTest.class.getResourceAsStream("config.properties"); //方式三:利用Class方式进行加载,使用绝对路径的方式 InputStream is = ReflectTest.class .getResourceAsStream("/com/itheima/day1/config.properties");