------- android培训、java培训、期待与您交流! ----------
反射是从JDK1.2就有的新特性,以后学习框架都要用到反射技术。
要了解反射,就要先了解Class这个类。
Class,是一个类,注意首字母是大写的。
Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性, 至于这个属性的值是什么,则由这个类的实例对象来确定,不同的实例对象有不同的属性值。 Java程序中的各个Java类,它们属于同一类事物,可以用一个类来描述这类事物,这个类的名字就是Class(注意与小写class关键字的区别)。 Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属于的包名,字段名称的列表、方法名称的列表,等等。 学习反射,首先要明白Class这个类。
API文档说:Class
类的实例表示正在运行的 Java 应用程序中的类和接口。
再换一个角度思考:
众多的人用一个什么类表示? 人 → Person类
众多的Java类用一个什么类表示? Java类 → Class
Java程序中的各个Java类属于同一类事物,描述这类事物的Java类就是Class类。
提问:
Person类的实例是张三、李四这样一个个人,Class类的各个实例对象又分别对应什么呢?
如何得到各个字节码对应的实例对象,请下面的小程序:
首先,一共有三种方式获取Class类的实例对象,看下面的代码:
public class ReflectTest { public static void main(String[] args) throws ClassNotFoundException { String str = "获取Class类的实例对象"; Class cls1 = str.getClass(); Class cls2 = String.class; Class cls3 = Class.forName("java.lang.String"); System.out.println(cls1 == cls2); //true System.out.println(cls2 == cls3); //true } }
从上面可见,有三种方式得到各个字节码对应的实例对象(Class类型):
另外,有九个预定义Class实例对象:
API文档描述:“有九种预定义的 Class
对象,表示八个基本类型和 void。这些类对象由 Java 虚拟机创建,
与其表示的基本类型同名,即 boolean
、byte
、char
、short
、int
、long
、float
和double
。”
参见Class.isPrimitive()方法:判定指定的 Class
对象是否表示一个基本类型。
public class ReflectTest { public static void main(String[] args) throws ClassNotFoundException { String str = "获取Class类的实例对象"; Class cls1 = str.getClass(); 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 System.out.println(int[].class.isPrimitive()); // false } }
视频17 说了反射的基石 —— Class类,现在才真正讲反射的概念。
反射:就是把Java类中的各种成分映射成相应的Java类。
Java类中有什么啊?
有:它所在那个包的信息,可以通过getPackage()方法得到;
它具有哪些方法,可以通过getMethod()方法得到;它的成员变量,可以通过getFileld()方法得到。
学反射,就是把Java类中各种各样的东西,解析成为一个类。
例如Method这个类,
对于 System.exit
(int status)
、System.getProperties() ,它们都是方法,
都是看做是Method方法类的实例对象,
我们可以这样思考,就像下面这样:
Method → methodeObj1 → System.exit
(int status)
Method → methodeObj2 → System.getProperties()
它们其实就相当于Methode这个类中的methodobj1,obj2。
public class ReflectTest2 { public static void main(String[] args) throws ClassNotFoundException, SecurityException, NoSuchMethodException { Method methodObj1 = System.class.getMethod("exit", int.class); System.out.println(methodObj1); // public static void java.lang.System.exit(int) Method methodObj2 = System.class.getMethod("getProperties", null); System.out.println(methodObj2); // public static java.util.Properties java.lang.System.getProperties() } }
像上面这样,我们得到了Method这个类的对象,就相当于得到了System这个类的一个方法
PPT上的内容: 1.反射就是把Java类中的各种成分映射成对应的Java类。 例如,一个Java类中用一个Class类的对象来表示, 一个类中组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来标表示, 就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。 表示Java类的Class类显然要提供一系列的方法, 来获得其中的变量,方法,构造方法,修饰符,包等信息, 这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。 2.一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示, 通过调用Class类的方法可以得到这些实例对象后, 得到这些实例对象后有什么用呢? 怎么用呢?这正是学习和应用反射的要点。
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(); 该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。 该方法内部的具体代码要怎么写呢? 用了缓存机制来保存默认构造方法的实例对象。
Constructor : Constructor类代表某个类中的一个构造方法。
API文档描述: Constructor
提供关于类的单个构造方法的信息以及对它的访问权限。
import java.lang.reflect.*; public class ConstructorDemo { public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { Constructor constructor = String.class .getConstructor(StringBuffer.class); String str1 = new String(new StringBuffer("abc")); //通常方式 String str2 = (String) constructor.newInstance(new StringBuffer("abc")); //反射方式 //调用获得的方法时,要用到上面相同类型的实例对象 System.out.println("str1: " + str1 + " ; str2: " + str2); } }
注意:
(1) 构造方法没有顺序,你不能getConstructors(2)这样去得到,但可以通过参数类型来获取。
(2) JDK1.5后有可变参数的新特性,你可以写多个参数,getConstructor(Class<?>... parameterTypes) ;
如果是1.5之前,你可以通过传一个数组来传多个参数,getConstructor(Class[] parameterTypes) 。
有了构造方法,能够干嘛呢?
可以得到得到那个类,得到修饰符,最重要的是可以得到实例对象。
注意看下面:
Constructor constructor1 = String.class.getConstructor(StringBuffer.class); String str2 = (String)constructor1.new Instance(new StringBuffer("abc"));
并不会执行等号右边的语句,所以它只知道constructor1是一个构造方法,
而不知道具体是什么类的构造方法。
第二句,根据这个构造方法new Instance创建的实例,得到的是一个Object,要强制转换为String类型才行。
如果第二句传给constructor1的参数是"abc"这样一个String,
编译能够通过,但运行时会有类型不匹配的异常,因为这个constructor1是接收StringBuffer.class的构造方法。
关键:要注意,得到方法的时候要传入类型,调用方法时也需要传入同样类型的对象。
//请留意:Class和Constructor两个类,都有newInstance()方法的。 String obj = (String)Class.forName("java.lang.String").newInstance();
API文档:Class的newInstance()方法,如同用一个带有一个空参数列表的 new
表达式实例化该类。
该方法内部的具体代码是怎样写的呢?用到了缓存机制来保持默认构造方法的实例对象。
Constructor已经有newInstance()了,为什么Class还要有呢?
其实不要上面这个newInstance()方法也没有影响,
但我们平时是通过 class → constructor → obj 这样来得到对象的,
它这里就给我们一个便利,让我们得到空参数构造方法时,可以省去了中间constructor那步。
通过阅览源码可知,反射会导致程序性能下降。
Field类 Field类代表某个类中的一个成员变量。 演示用eclipse自动生成Java类的构造方法 问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量呢? 类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象呢? 所以字段fieldX代表的是x的定义,而不是具体的x变量。
import java.lang.reflect.*; public class ReflectTest { public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { ReflectPoint pt1 = new ReflectPoint(3, 5); Field fieldY = pt1.getClass().getField("y"); // filedY的值是多少?是5? 错!fieldY不是对象身上的变量,而是类上,要用它去取某个对象上对应的值。 System.out.println(fieldY.get(pt1)); // 输出5 //fieldY只代表类身上的成员变量,不代表某个对象的值。 } }
public class ReflectPoint { private int x; public int y; public ReflectPoint(int x, int y) { super(); this.x = x; this.y = y; } }
暴力反射:无视一个类中私有成员变量、构造方法、方法等等访问权限修饰符,用反射技术提供的方法,"抢"到这些私有的资源。
import java.lang.reflect.*; public class ReflectTest { public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { ReflectPoint pt1 = new ReflectPoint(3, 5); /***************************************************************/ //Field fieldX = pt1.getClass().getField("x"); //System.out.println(fieldX); //如果ReflectPoint的x是private修饰,那就会抛NoSuchFieldException异常。 /***************************************************************/ //Field fieldX = pt1.getClass().getDeclaredField("x"); //System.out.println(fieldX.get(pt1)); //会抛IllegalAccessException异常,"让你看见但不让你用" /***************************************************************/ Field fieldX = pt1.getClass().getDeclaredField("x"); fieldX.setAccessible(true); System.out.println(fieldX.get(pt1)); //3 } }
题目:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的“b”改为“a”。
import java.lang.reflect.Field; public class ReflectTest { public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException { MyConstructor mc = new MyConstructor(); changeStringValue(mc); System.out.println(mc); } private static void changeStringValue(Object obj) throws IllegalArgumentException, IllegalAccessException { Field[] fields = obj.getClass().getFields(); for (Field field : fields) { if (field.getType() == String.class) { String oldValue = (String) field.get(obj); String newValue = oldValue.replace('b', 'a'); field.set(obj, newValue); } } } }
public class MyConstructor { private int x; private int y; public String a = "bed"; public String b = "aaa"; public String c = "abc"; public String d = "beg"; @Override public String toString() { return "MyConstructor [a=" + a + ", b=" + b + ", c=" + c + ", d=" + d + ", x=" + x + ", y=" + y + "]"; } }
字节码有几份?一份。
所以,只要是比较字节码就用==比。
Method类 1.Method类代表某个类中的一个成员方法。 2.得到类中的某一个方法: 例子: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对象对应的是一个静态方法! 3.JDK1.4 和 JDK1.5 的invoke方法的区别: JDK1.5 : public Object invoke(Object obj,Object...args); JDK1.4 :public Object invoke(Object obj,Object[ ] args),即按JDK1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中每个元素分别对应被……
import java.lang.reflect.*; public class MethodDemo { public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { String str = new String("ABC"); Method methodCharAt1 = String.class.getMethod("charAt", int.class); System.out.println(methodCharAt1.invoke(str, 2)); // 输出C,invoke就是调用的意思 Method methodCharAt2 = String.class.getMethod("valueOf", char.class); System.out.println(methodCharAt2.invoke(null, 'C')); // 输出C } }
invoke是Method类对象身上的方法,
上面的程序,通过得到String类中的charAt方法,然后调用它。
画圆的方法用到圆心和半径,是圆身上的方法。
只有圆自己知道自己怎么画。
例子2:
列车司机刹车,司机只是给列车发信息,要列车自己才知道自己怎么刹住。
上面两个例子,就是“专家模式”的例子:谁有这个数据,谁就应该拥有操作这个数据的方法。
import java.lang.reflect.*; public class MethodDemo { public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { String str = new String("ABC"); Method methodCharAt1 = String.class.getMethod("charAt", int.class); System.out.println(methodCharAt1.invoke(str, 2)); // JDK1.5做法 System.out.println(methodCharAt1.invoke(str, new Object[] { 2 })); // JDK1.4做法 } }
按照1.4的做法,多个参数的时候
import java.lang.reflect.*; public class MethodDemo { public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { String str = new String("ABCDEFG"); Method methodIndexOf = String.class.getMethod("indexOf", new Class[] { String.class, int.class}); //注意new Class[] System.out.println(methodIndexOf.invoke(str, new Object[] { "C",new Integer(1)})); //注意这里 } }
目标: 写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。 用普通方式调完后,要明白为什么要用反射的方式去调? 问题: 启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args), 通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢? 按JDK1.5的语法,整个数组是一个参数,而按JDK1.4的语法, 数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时, javac会到底按照哪种语法进行处理呢? JDK1.5肯定要兼容JDK1.4的语法,会按JDK1.4的语法进行处理, 即把数组打散成为若干个单独参数。 所以,给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{"XXX"}), javac只把它当作JDK1.4的语法进行理解,而不把它当作JDK1.5的语法理解, 因此会出现参数类型不对的问题。 解决方法: mainMethod.invoke(null,new Object[]{new String[]{"XXX"}}); mainMethod.invoke(null,(Object)new String[]{"XXX"}); //编译器会作特殊处理, 编译时不把参数当作数组看待,也就不会把数组打散成若干个参数了
import java.lang.reflect.*; public class ReflectMainDemo { public static void main(String[] args) throws SecurityException, NoSuchMethodException, ClassNotFoundException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { String startingClassName = args[0]; Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class); mainMethod.invoke(null, (Object) new String[] { "111", "222", "333" }); } } class TestArguments { public static void main(String[] args) { for (String arg : args) { System.out.println(arg); } } }
上面程序,输入的参数是:TestArguments
平时我们这样调:TestArguments.main(new String[]{});
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。 代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。 基本类型的一维数组可以被当作Object类型使用,不能当作Object[] 类型使用; 非基本类型的一位数组,既可以当作Object类型使用,又可以当作Object[]类型使用。 Arrays.asList()方法处理int[]和String[]时的差异。 Array工具类用于完成对数组的反射操作。 思考题:怎么得到数组中的元素类型?
数组也是一种类型,API文档:具有相同的元素类型及维数的每一个数组,反射出来的字节码都是相同的。
public class ReflectArrayDemo { public static void main(String[] args) { int[] a1 = new int[3]; int[] a2 = new int[4]; int[][] a3 = new int[2][3]; String[] a4 = new String[4]; System.out.println(a1.getClass() == a2.getClass()); // System.out.println(a1.getClass() == a3.getClass()); 不能通过编译 // System.out.println(a1.getClass() == a3.getClass()); 不能通过编译 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 = a4; // Object[] aObj3 = a1; //不能转换,因为a1装的是int,是基本类型,不是Object Object[] aObj4 = a3; Object[] aObj5 = a4; } }
看上面的代码:
Object
的运行时类/得到对象的字节码。
public String getName():以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。 如果此类对象表示一个数组类,则名字的内部形式为:表示该数组嵌套深度的一个或多个 '[' 字符加元素类型名。元素类型名的编码如下: Element Type Encoding boolean Z byte B char C class or interface Lclassname; double D float F int I long J short S 类或接口名 classname 是上面指定类的二进制名称。 示例: String.class.getName() returns "java.lang.String" byte.class.getName() returns "byte" (new Object[3]).getClass().getName() returns "[Ljava.lang.Object;" (new int[3][4][5][6][7][8][9]).getClass().getName() returns "[[[[[[[I"中括号表示数组,I表示int。
而不是返回它的哈希码:System.out.println(new String[] { "a", "b", "c" }); //[Ljava.lang.String;@1bab50a
这时候可以使用Arrays里面的asList方法,将数组转换为List。
import java.util.Arrays; public class ReflectArrayDemo { public static void main(String[] args) { int[] a1 = new int[] { 1, 2, 3 }; String[] a4 = new String[] { "a", "b", "c" }; System.out.println(a1); // [I@1e5e2c3 System.out.println(a4); // [Ljava.lang.String;@18a992f System.out.println(Arrays.asList(a1)); // [[I@18a992f] // 对于int,转换成List后,List后只装了原来那个数组对象,还是输出哈希码 System.out.println(Arrays.asList(a4)); // [a, b, c] // 对于字符串,成功了 } }
Q:为什么int类型的数组不能用asList方法输出元素呢?
A:首先,String数组符合了JDK1.4的语法,asList(Object[] a),因此String数组的情况就被当作一个数组来处理。
而int数组不是Object[]数组,只能按JDK1.5的语法处理,asList(T... a),整个数组被当作一个对象,
就输出了这个 数组对象 的哈希码了。
如果我们想通过反射方式,得到数组的length、其中的值,我们应该怎么做?
可以通过 java.lang.reflect.Array; 这个类,看下面的例子程序。
import java.lang.reflect.*; import java.util.Arrays; public class ReflectArrayDemo { public static void main(String[] args) { String[] str = new String[] { "111", "222", "333" }; int[] a = new int[] { 1, 2, 3 }; printObject(str); printObject(a); } 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); } } }
输出:
111 222 333 1 2 3
Object[] b = new Object[]{"a",1}; System.out.println(b[0].getClass().getName());//java.lang.String
import java.util.*; public class ReflectTest2 { public static void main(String[] args) { Collection collections = new ArrayList(); //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()); // ArrayList的size是4,HashSet的size是3 } }
该例中,如果是 ArrayList则size是4,HashSet则size是3。
简单概括:
ArrayList类似于数组,它有顺序,不是指大小顺序,而是指排序顺序。
HashSet则是先判断有没有这个对象,有就不放,要么你先把有的那个对象删掉再放。
详细说明:hashCode方法与HashSet类
现在我们改写ReflectPoint的equals和hashcode方法,只要x/y相等,就是同一个点。
public class ReflectPoint { private int x; public int y; public ReflectPoint(int x, int y) { super(); this.x = x; this.y = y; } @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; } }
HashSet 是先比较hashCode,相等再进一步比较equals。
ArrayList则是按顺序存放,不会进行什么比较操作。
只有把你的对象存到哈希集合(不仅仅指HashSet)中,
你的hashCode才有价值,才会按照hashCode进行区域分放。
你不存到哈希集合中,你也就不需要修改hashCode了。
面试题:请说一下equals和hashCode的作用。
比较好的译文:简述Java内存泄漏
落实到HashSet的情况,就是下面这个例子:
package Package1; import java.util.Collection; import java.util.HashSet; public class HashCodeDemo { public static void main(String[] args) { Collection collections = new HashSet(); ReflectPoint pt1 = new ReflectPoint(3, 3); collections.add(pt1); pt1.setY(7); collections.remove(pt1); System.out.println(collections.size()); // 输出1,没有被remove掉,因为改完后hashCode改变了 } }
另外一道面试题:Java中有内存泄露吗?为什么?
就举上面这个例子,ReflectPoint pt1加入到collections中后,
被修改了成员变量Y,导致它的哈希码改变了,
当你要把它从collections中remove的时候,不能remove掉,
pt1这是还持有ReflectPoint(3,7)的引用,而Java虚拟机这种情况下垃圾收集器是不会把ReflectPoint(3,7)从内存中释放的,
ReflectPoint(3,7)不会被回收,并占用着内存空间,它就是该被回收又没有被回收的对象。
这样就产生了内存泄漏了。
了这么多反射类,到底有什么用? 用来做框架.
1. 框架与框架要解决的核心问题 我做房子卖给用户住,用户自己安装门窗和空调等等,我做的房子就是框架, 用户需要使用我的框架,把门窗插入进我提供的框架。 框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。 2. 框架要解决的核心问题 ·我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢? 我写框架程序怎样能调用到你以后写的类(门窗)呢? ·因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象, 而要用反射方式来做。 3. 综合案例 (1)先直接用new语句创建ArrayList和HashSet的实例对象, 演示用eclipse自动生成ReflectPoint类的equals和hashCode方法,比较两个集合的运行结果差异。 (2)然后改为采用“配置文件+反射”的方式创建ArrayList和HashSet的实例对象,比较观察运行结果差异。 (3)引入了eclipse对资源文件的管理方式的讲解。
你调用别人的类
别人的类调用你的类(也算是你用别人的)
工具类与框架的性质,就类似于 你自己在里面装修 和 开发商给你的房子 的区别。
明白了HashSet和ArrayList的区别,又知道了hashCode方法的作用,
接下来就把那个程序改为反射的方式来做,改为从配置文件读取。
import java.io.*; import java.util.*; import java.lang.reflect.*; public class ReflectTest2 { public static void main(String[] args) throws Exception { InputStream ips = new FileInputStream("config.properties"); Properties props = new Properties(); props.load(ips); ips.close(); String className = props.getProperty("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 文件:
className=java.util.ArrayList注意是放在 项目的文件夹中,跟src同目录的,不是放src里面。
------- android培训、java培训、期待与您交流! ----------