一 反射:
其实就是动态加载一个指定的类,并获取该类中的所有的内容。而且将字节码文件封装成对象,并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个类进行解剖。
反射也可以理解为把Java类中的各种成分映射成相应的Java类。 其实就是通过一个类的Class对象(字节码)把类中的各种成分映射成相应的java类的对象来使用。例如一个Java类中用一个Class的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等信息用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示Java类的Class类是显然要提供一系列方法,来获得其中的变量 方法 构造方法 修饰符 包等信息,这些信息就是用相应类的实例对象来表示,它们是是Field Method Constructor Package等等。一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。反射的好处:大大的增强了程序的扩展性
二 反射的基石-->Class类:
Java类用于描述一类事物的共性,这类事物有什么属性,没有什么属性,至于这个属性是什么,则是由这个类的实例对象来确定的,不同的实例对象又不同的属性值,Java程序中的各个Java类,它们是否属于同一类事物,是不是可以用一个类来描述这类事物呢?这个类的名字就是Class,要注意与class关键字的区别。Class类描述了哪方面的信息呢?例如:类的名字,类的访问属性,类属于的包名,字段名称的列表,方法名称的列表,等等。学习反射首先要明白Class这个类。Class即字节码,内存里面的每一份字节码就是一个Class类的的实例对象。java虚拟机里面每一个类只保存一份字节码。
1 反射的基本步骤:
1) 获得Class对象,就是获取到指定的名称的字节码文件对象。
2) 实例化对象,获得类的属性、方法或构造函数。
3) 访问属性、调用方法、调用构造函数创建对象。
2 返回得到字节码(Class类)对应的实例对象的三种方式:
1)类名.class; 例如:System.class2)对象名.getClass(); 例如:new Date().getClass()
3)Class.forName("类名") 这份字节码被曾经加载过已经在Java虚拟机当中直接返回。如果要加载的类字节码没有被加载过,那么就用类加载器去加载,把加载进来的字节码缓存到虚拟机里面。 例如: Class.forName("java.utils.Date")
4)根据字节码实例对象获取实例对象
Object obj=new Person("zhangsan",19)
clazz=obj.getClass()
Object obj = clazz.newInstance()//该实例化对象的方法调用就是指定类中的空参数构造函数,给创建对象进行初始化。当指定类中没有空参数构造函数时,该如何创建该类对象呢?请看method_2();
public static void method_2() throws Exception {
Class clazz = Class.forName("cn.kid.bean.Person");
// 既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。
// 获取一个带参数的构造器。
Constructor constructor = clazz.getConstructor(String.class, int.class);
// 想要对对象进行初始化,使用构造器的方法newInstance();
Object obj = constructor.newInstance("zhagnsan", 30);
// 获取所有构造器。
Constructor[] constructors = clazz.getConstructors();// 只包含公共的
constructors = clazz.getDeclaredConstructors();// 包含私有的
for (Constructor con : constructors) {
System.out.println(con);
}
}
问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?
答:Person类代表人,它的实例对象就是张三,李四应选择一个个具体的人,Class类代表Java类,它的各个实例对象又分别对应什么呢?
问:对应各个类型在内存中的字节码,例如Person类的字节码,ArrayList类的字节码,不同的类的字节码是不同的,所以他们在内存中的内容是不同的,这一个个的空间可分别用一个个对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?
3 九个预定义Class实例对象(八个基本类型和 void):1)八大基本类型也有其对应的Class实例对象,void也有对应的Class实例 void.class
2)参看Class.isPrimitive方法的帮助
3)int.class==Integer.TYPE
int.class.isPrimitive();-----true //判断class类是否为基本数据类型。
int[].class.isArray();-----true //判断Class类是否为数组类。
Integer.TYPE返回的是对应的基本数据类型的那份字节码。关于int 和Integer是两种不同的类型 可参考以下代码:
public class TestClass {
public static void main(String args[]) {
System.out.println(Integer.class);
System.out.println(Integer.TYPE);
System.out.println(int.class);
}
}
打印结果为:
class java.lang.Integer
int
int
提示:只要是在源程序中出现的类型,都有各自的Class实例对象,如:int[], void 等。
4 反射的用法:
1)需要获得java类的各个组成部分,首先需要获得类的Class对象,获得Class对象的三种方式:
Class.forName(classname) //用于做类加载
obj.getClass() //用于获得对象的类型
类名.class 用于获得指定的类型,传参用
2) 反射类的成员方法:
Class clazz = Person.class;
Method method = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2});
method.invoke();
3) 反射类的构造函数:
Constructor con = clazz.getConstructor(new Class[]{paramClazz1, paramClazz2,...})
con.newInstance(params...)
4) 反射类的属性:
Field field = clazz.getField(fieldName)
field.setAccessible(true)
field.setObject(value)
1.Class类里的 isArray方法,判断此Class实例对象是否为数组类型
2.总之只要在原程序中出现的类型,都有各自的Class实例对象,例如int[ ],void..........
数组的反射:
1)具有相同参数和元素类型的数组属于同一类型,即具有相同的Class实例对象
2)代表数组的Class实例对象的getSuperClass()方法返回的父类为Object对应的Class
3)基本类型的一维数组可以被当作Object类型使用,不能当作Object[ ]类型使用,非基本类型的一维数组,既可以当作Object类型使用,又可以当作Object[ ]类型使用
4)Arrays工具类用于完成对数组的反射操作,Arrays.asList()方法处理int[ ]和String[ ]时的差异。
例子代码如下 :
int[ ] a1=new int[ ]{1,2,3};
String[ ] str=new String[ ]{"a","b","c" };
System.out.println(Arrays.asList(a1));//被当做Object类
System.out.println(Arrays.asList(str)); //被当做Object[]类
打印结果为:
[[I@1cfb549]
[a,b,c]
例子代码2:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Test {
public static void main(String args[]) {
String str = "abcdefghijklmnopq";
System.out.println(str.substring(1, 3));// 1包3不包
List<String> l = (List<String>) Arrays.asList(new String[] { "aa",
"bb", "cc", "dd" });
System.out.println(l.toString());
}
}
打印结果:bc
[aa, bb, cc, dd]
5) 数组反射的应用 :
import java.lang.reflect.Array;
class arrReflectDemo {
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);
}
}
public static void main(String args[]) {
printObject(new String[] { "aa", "bb", "cc" });
printObject(new int[] { 1, 2, 3 });
}
}
6)Array与Arrays的区别:
java.lang.reflect.Array:此类提供了动态创建和访问 Java 数组的方法。
java.util.Arrays:此类包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂。前者倾向于操作数组的单个元素,后者倾向于操作整个数组。
Array工具类用于完成对数组的反射操作,Array类里面的所有方法都是静态方法。如:Array.getLength(obj); Array.get(obj, index);
数组反射的简单应用程序片段如下:
public void printObject(Object obj) {
if(obj.getClass().isArray()) {
for(int i = 0; i < Array.getLength(obj); i++) {
System.out.println(Array.get(obj, i));
}
} else {
System.out.println(obj);
}
}
四 Constructor类:
该对象内部的具体代码是怎么写的呢?用到了缓存机制来保存默认构造方法的实例对象。
获取了字节码文件对象后,最终都需要创建指定类的对象:
创建对象的两种方式(其实就是对象在进行实例化时的初始化方式):
1)调用空参数的构造函数:使用了Class类中的newInstance()方法。
2)调用带参数的构造函数:先要获取指定参数列表的构造函数对象,然后通过该构造函数的对象的newInstance(实际参数) 进行对象的初始化。
综上所述,第二种方式,必须要先明确具体的构造函数的参数类型,不便于扩展。所以一般情况下,被反射的类,内部通常都会提供一个公有的空参数的构造函数。
1 得到某个类所有的构造方法:
例子:Contructor[ ] constructor=Class.forName("java.lang.String").getConstructor();
2 得到某个构造方法:
例子:
Constructor constructor=Class.forName("java.lang.String").getConstructor(StringBuffer.class); //获取方法时要用到的类型
3 创建实例对象:
通常方式: String str=new String(new StringBuffer("abc"));
反射方式: String str=(String)constructor.newInstance(new StringBuffer("abc"));
4 Class.newInstance()方法:
例子:
String obj=(String)Class.forName("java.lang.String").new Instance(); //该方法内部先得到默认的构造方法,然后用该构造方法创建对象
五 Field类:
1 Field类代表某个类中的一个成员变量,Field实例对象只代表类字节码里面的一个变量,并不代表某一对象里面的变量本身具体的值。我们需要调用Field类中的方法get(Object obj)(注意:此方法返回类型是Object类型,很多时候我们需要做类型强制转换。)或其他对应的方法才能获得具体实例对象所对应的此字段的值。2 问题:得到的Field对象是对应到类上面的成员变量还是对应到对象的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象呢?所以字段fieldx代表的是x的定义,而不是具体的x变量。
示例代码(省略了部分代码):
ReflectPoint pt1=new ReflectPoint(3,5);
Field fieldY=pt1.getClass().getField("y");
filedY的值是多少?是5,错!fieldY不是对象身上的变量,而是类上,要用它去取某个对象上对应的值。 3 暴力反射:
类里面的字段声明为private int x;可通过,暴力反射去获得。在对象里面声明为非public的的成员变量需要用getDeclaredField(String name)才能得到Class对象中的此变量名所对应的Field实例对象。然后如果需要访问此Field实例对象所对应的具体的值,那么需要调用Field父类的方法setAccessible(true)来指示反射的对象在使用时应该取消Java语言访问检查,然后才能访问具体对象所对应的此字段的值:
Field fieldX=pt1.getClass().getDeclareField("x");
fieldX.setAccessible(true);
System.out.println(fieldX.get(pt1));
事例代码:
package com.test.Package2;
import java.lang.reflect.Field;
class ReflectPoint {
private int y;
private int x;
ReflectPoint(int x, int y) {
this.x = x;
this.y = y;
}
}
class ArrReflectDemo {
public static void main(String args[]) throws Exception {
ReflectPoint point = new ReflectPoint(1, 7);
Field field_y = Class.forName("com.test.Package2.ReflectPoint")
.getDeclaredField("y");因为Y字段是私有的,无法直接用getField方法获得,只能用getDeclaredField()方法
/* Field field_y = point.getClass().getDeclaredField("y"); */
field_y.setAccessible(true);下面输出时,由于字段是私有的,也无法直接输出,必须设为可处理的状态。
System.out.println(field_y.get(point));
Field x = Class.forName("com.test.Package2.ReflectPoint")
.getDeclaredField("x");
x.setAccessible(true);
System.out.println(x.get(point));
}
}
打印的结果是:7
1
例子:
通过反射换掉一个对象中的字段,部分代码如下:
package com.kid.reflect;
import java.lang.reflect.Field;
public class RefChangeStringValue {
public static void changeStringValue(Object obj) {
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.getType() == String.class) {
try {
field.setAccessible(true);
field.set(obj, ((String) field.get(obj)).replace('b', 'a'));
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
TestField tv = new TestField();
changeStringValue(tv);
System.out.println(tv);
}
}
class TestField {
String s = "abbbbbbbbbb";
@Override
public String toString() {
return s;
}
}
打印结果为:aaaaaaaaaaa
六 Method 类:
反射指定类中的方法:
//获取类中所有的方法。
public static void method_1() throws Exception {
Class clazz = Class.forName("cn.kid.bean.Person");
Method[] methods = clazz.getMethods();//获取的是该类中的公有方法和父类中的公有方法。
methods = clazz.getDeclaredMethods();//获取本类中的方法,包含私有方法。
for(Method method : methods) {
System.out.println(method);
}
}
//获取类中所有的方法。public static void method_1() throws Exception {
Class clazz = Class.forName("cn.kid.bean.Person");
Method[] methods = clazz.getMethods();//获取的是该类中的公有方法和父类中的公有方法。
methods = clazz.getDeclaredMethods();//获取本类中的方法,包含私有方法。
for(Method method : methods) {
System.out.println(method);
}
}
//获取指定方法;
public static void method_2() throws Exception {
Class clazz = Class.forName("cn.kid.bean.Person");
//获取指定名称的方法。
Method method = clazz.getMethod("show", int.class,String.class);
//想要运行指定方法,当然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法即可,但是方法运行必须要明确所属的对象和具体的实际参数。
Object obj = clazz.newInstance();
method.invoke(obj, 39,"hehehe");//执行一个方法
}
//想要运行私有方法。
public static void method_3() throws Exception {
Class clazz = Class.forName("cn.kid.bean.Person");
//想要获取私有方法。必须用getDeclearMethod();
Method method = clazz.getDeclaredMethod("method", null);
// 私有方法不能直接访问,因为权限不够。非要访问,可以通过暴力的方式。
method.setAccessible(true);//一般很少用,因为私有就是隐藏起来,所以尽量不要访问。
}
//反射静态方法。public static void method_4() throws Exception {
Class clazz = Class.forName("cn.kid.bean.Person");
Method method = clazz.getMethod("function",null);
method.invoke(null,null);
}
例子:
Method charAt=Class.forName("java.lang.String").getMethod("charAt",int.class);
3 调用方法
通常方法:System.out.println(str.chartAt()); 反射方式:System.out.println(chartAt.invoke(str,1));
提示: 如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么意义呢?说明该Method对象对应的是一个静态方法。 静态方法的反射调用时invoke()方法中的对象参数可以是null,如:method.invoke(null, "hello")
4.用反射的方式执行某个类中的main方法
目标:写一个程序。这个程序能够根据用户提供的类名,去执行该类的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[ ]{ "xx1","xx2" });
因为javac只能把它当作jdk1.4的语法进行理解,而不把它当作jdk1.4的语法进行理解,因此会出现参数类型不对的问题。
解决办法:
mainMethod.invoke(null,new Object[ ]{new String{ "xx1","xx2" } }); 或
mainMethod.invoke(null,(Object)new String{ "xx1","xx2" });
编译器会做特殊处理,编译时不会把参数当作数组看待,因此不会将数组打散成若干个参数。
Method类的使用,如下代码:
package com.kid.reflect;
import java.lang.reflect.Method;
public class RefMethodTest {
public static void main(String[] args) throws Exception {
String str = "hello";
Method methodCharAt = String.class.getMethod("charAt", int.class);
System.out.println(methodCharAt.invoke(str, 0));
Method methodMin = Math.class.getMethod("min", int.class, int.class);
System.out.println(methodMin.invoke(null, 3, 20));//返回两数较少的一个
ClassTest.main(new String[] { "aa", "bb" });//正常方式调用
Method methodMain = Class.forName("com.kid.reflect.ClassTest")
.getMethod("main", String[].class);//利用反射调用main方法
methodMain.invoke(null, (Object) new String[] { "hello", "world" });
methodMain.invoke(null,
new Object[] { new String[] { "hello", "world" } });
}
}
class ClassTest {
public static void main(String[] args) {
for (String arg : args) {
System.out.println(arg);
}
}
}
打印结果为:
h
3
aa
bb
hello
world
hello
world
1.框架与框架要解决的核心问题:
开发商房子卖给用户住,由用户自己安装门窗和空调,开发商做的房子就是框架,用户需要使用我的框架,吧门窗插入进开发商提供的框架中,框架工具类有区别,工具类被用户的类调用用户提供的类。
2.框架要解决的核心问题:
开发者在写框架的时候(做房子),用户可能还不会写程序。那么框架程序程序怎么调用你以后写的类(门窗)?因为在写程序时无法知道要被调用的类名,所以在程序中无法直接new某个类的实例对象了,而要用反射方式做。
3.框架通常都有配置文件Structs Spring hibernate,所以配置文件都放在classpath目录下。