一,反射的基石(Class类)
1.1Class概述
1,java程序中的各个java类属于同一类事物,java提供了一个类来用于描述这类事物,这个类就是Class。
2,Class类代表java类,它的各个实例对象又分别对应什么呢?
答:对应各个类在内存中的字节码
3,一个类被加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,
所以他们再内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象具有相同的类型,就是Class类型。
1.2获得Class对象
1,使用Class类的forName(String className)静态方法。传入的字符串参数值是某个类的权限定类名(必须添加完整包名)
(该方法可能抛出ClassNotFoundException异常)
2,调用某个类的class属性来获取该类对应的Class对象。
3,调用某个对象的getClass()方法,该方法是java.lang.Object类中的一个方法,所有的对象都可以调用这个方法。
会返回该对象所属类对应的Class对象。
String str1 = "abc";
//三种得到字节码的方式
Class cls1 = str1.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");//抛出异常。
以上三种得到的String字节码都是同一份。
System.out.println(cls1 == cls2);//true
System.out.println(cls1 == cls3);//true
关于字节码的其他相关判断。
System.out.println(cls1.isPrimitive());// 判定指定的 <code>Class</code> 对象是否表示一个基本类型。、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
System.out.println(int[].class.isArray());//判断是否是数组
三种方式的区别:第三种在源程序时不用知道要获得的字节码的类名称,而是临时传进来的。
对反射的一个总结:反射就是把java类的各种成分映射成相应的java类。
1.3Class的获取信息的常用方法
1.3.1获取Class对应类所包含的构造器
Constructor<T> getConstructor(Class<?>...parameterTypes):
Constructor<?>[] getConstructors():
Constructor<T> getDeclaredConstructor(Class<?>...parameterTypes); 与访问权限无关
Constructor<?>[] getDeclaredConstructos();
1.3.2 获取Class对应类所包含的方法
Method getMethod(String name,Class<?>...parameterTypes);Method[] getMethods();Method getDeclaredeMethod(String name,Class<?>...parameterTypes); 与访问权限无关Method[] getDeclaredeMethods();
1.3.3访问Class对应类所包含的Field
Field getField(String name);Field[] getFields();Field getDeclaredeField(String name); 与访问权限无关Fidle getDeclaredeFields();
1.3.4获取方法和构造函数的注意要点
String.class.getMethod("String",Stirng.class);
如果需要获取第三个indexOf方法,则使用如下代码:
//前一个参数指定方法名,后面是个数可变的Class参数指定形参类型列表
String.class.getMethod("String",Stirng.class,int.class);
而获取构造器时无须传入构造器名,因为同一个类的所有构造器的名字都是相同的,所以要确定一个构造器只要指定形参列表即可,
Class.forName("java.lang.String").getConstructor(StringBuffer.class);
二,创建对象(Constructor类)
2.1第一种创建方式
2.2第二种创建方式
(1) 获取该类的Class对象(2) 利用Class对象的getConstructor()方法来获取指定的构造器(3) 调用Constructor的newInstance()方法来创建java对象。
Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
String str2 = (String)constructor1.newInstance(new StringBuffer("abc"));
//编译时不知道constructor是String的构造方法,返回的是Object所以上面要强转
通常没有必要使用反射来创建该对象,毕竟通过反射创建对象时性能要稍低一些,实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,
三,调用方法(Method类:(代表类在内存的字节码的一个成员方法))
1,得到类中的某一个方法
例子:Method methodCharAt = String.class.getMethod("charAt",int.class);
获取String类的charAt(int index);
2,方法的调用
例子:char s = methodCharAt.invoke(str1,1);
等价char s = str1.charAt(1)
注:如果传递给Method对象的invoke()方法的第一个参数为null,说明该method对象对应的是一个静态方法。
例:用反射方法执行某个类的main方法。
class TestArguments{
public static void main(String[] args){
for(String arg : args){
System.out.println(arg);
}
}
}
public class ReflectTest {
//普通方式调用main方法,
//TestArguments.main(new String[]{"111","222","333"});
//为什么要用反射的方式调用mian,不知道要调用那个类的main,等外界通过传递参数,告诉我要调用哪个类。
String startingClassNmae = args[0];
Method mainMethod = Class.forName(startingClassNmae).getMethod("main",String[].class );
//mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});
mainMethod.invoke(null,(Object)new String[]{"111","222","333"});
}
四,访问属性值(Field类:(代表字节码的某个变量))
1,Field类代表某个类中的一个成员变量
public class ReflectPoint {
private int x;
public int y;
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
ReflectPoint pt1 = new ReflectPoint(3,5);
//getField()只能得到publlic修饰的变量
Field fieldY = pt1.getClass().getField("y");//对应字节码的变量。没有对应到对象身上。
System.out.println("fieldY"+fieldY);
//field的值是public int cn.itcast.day1.ReflectPoint.y
// fieldY的值是多少?是5 错!fieldY不是对象的变量,而是类上的变量。要用它取某个对象上的Y的值
System.out.println(fieldY.get(pt1));
//private修饰的变量可以使用getDeclareField();,public也可以
Field fieldX = pt1.getClass().getDeclaredField("x");
fieldX.setAccessible(true);//暴力反射,给你看到钱,就不让你用,那就只能抢。
System.out.println(fieldX.get(pt1));
练习(Field):将任意一个对象的所有String类型的成员变量所对应的字符串内容中的“b”改成“a”;
private static void changeStringValue(Object obj) throws Exception
{
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'); //(old,new)
field.set(obj, newValue);
}
}
}
五,数组的反射。
1,具有相同维数和元素类型的数组属于同一个类型,即具有相同的class实例对象。
class ArrayReflect{
public static void main(String[] args)
{
int [] a1 = new int []{1,2,3};
int [] a2 = new int [4];
int [][] a3 = new int [2] [3];
String [] a4 = new String []{"a","b","c"};
//具有相同维数和元素类型的数组属于同一个类型,即具有相同的class实例对象。
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
}
}
2,代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class
//代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class
System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object
System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object
3,基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用,非基本类型的一维数组,
既可以当做Object类型使用,又可以当做Objcet[]类型使用。
Object aObj1 = a1;
Object aObj2 = a4;
//Object[] aObj3 = a1;//基本类型不是Object,int不属于Object。 Object[] 有一个数组,里面装的是Object
Object[] aObj4 = a3;
Object[] aObj5 = a4;
4,Array.asList()方法处理int[]和String[]时的差异。
//JDK1.4 asList(Object[] a) 当成一个Object[]处理
//JDK1.5 asList(T... a) 当成一个Object处理
System.out.println(a1);//[I@170bea5
System.out.println(a4);//[Ljava.lang.String;@f47396
System.out.println(Arrays.asList(a1));//[[I@170bea5] 为什么?jdk1.4和jdk1.5的接收参数的区别
System.out.println(Arrays.asList(a4));//[a, b, c]
5,对接收数字参数的成员方法进行反射
例:在一个类里调用另外一个类的main()方法
public class Reflect
{
public static void mian(String[] srgs)
{
//普方式调用
TestArguments.main(new String[]{"111","222","333"});
/*用反射的方式调用*/
//在运行该类时,传入一个参数作为被调用的类名,用一个变量接收被调用类名
String startingClassNmae = args[0];
//获取被调用类的main()方法
Method mainMethod = Class.forName(startingClassNmae).getMethod("main",String[].class );
//1,/下面传入的方式不对,JDK1.5兼容JDK1.4的处理方式
//因为String数组属于Object数组,按照jdk1.4的处理方式,所以相当于传了3个参数进去,所以会出现参数个数不对的错误。
//mainMethod.invoke(null,new Stirng[]{"111","222","333"});
//2,以下就是创建一个Object数组,将要传的参数作为Object数组的第一个元素,这样就想相当于传进去一个参数
//mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});
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);
}
}
}
6,Array工具类用于完成对数组的反射操作。(反射)
实例:打印一个Object对象的内容
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);
}
}
六,反射的作用(实现框架的功能)
1,对框架的认识
如房地产商造房子用户住,门窗和空调等等内部都是由用户自己安装,房地产商造毛呸房就是框架,用户需使用此框架,安好门窗等放入到房地产商提供的毛呸房(框架)。框架和工具类的区别:工具类被用户类调用,而框架是调用用户提供的类。
2,框架要解决的核心问题
因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象,而要用反射方式来做
3,实现一个简单框架步骤:
(1)创建一个配置文件config.properties,然后写入配置信息。如键值对:className=java.util.ArrayList。
(2)代码实现,加载此文件:
1将文件读取到读取流中,要写出配置文件的绝对路径。
2用Properties类的load()方法将流中的数据存入集合。
3关闭流:关闭的是读取流,因为流中的数据已经加载进内存。
(3)通过getProperty()方法获取className,即配置的值,也就是某个类名。
(4)用反射的方式,创建对象newInstance()。
(5)执行程序主体功能
实例:
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");
//通过上面获取的className类名,创建一个ArrayList对象
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());//4
}
}
4,类加载器加载资源文件
4.1,类加载器:
当一个类被使用的时候,会被类加载器加载到内存中,当然,它也可以加载普通文件。
4.2,eclipse编译,加载功能
在eclipse中保存一个.java文件后,eclipse会自动将该文件编译成.class文件,并把它放到classpath指定的目录中,
当然,eclipse也会把源程序目录下的一个非.java文件编译成.class文件,也把它放到classpath指定的目录中。
所以当classPath指定的目录下需要某个文件,那可以将该文件放在源程序目录下,eclipse会帮你复制过去。
4.3,使用类加载器加载配置文件
4.3.1使用类加载器来加载配置文件,需要先通过getClassLoader()获得类加载器,然后使用getResourceAsStream(),获得与配置文件相关的输入流。
利用类加载器来加载配置文件,需把配置文件放置的包名一起写上。这种方式只有读取功能!
InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");
4.3.2使用类提供的简便方法加载的时候,配置文件路径可以相对也可以是绝对。
InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");
InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/config.properties");
实例:
public class ReflectTest2 {
public static void main(String[] args) throws Exception {
//InputStream ips = new FileInputStream("config.properties");
//类加载器,没有OutputStream
//一定要记住,用完整的路径,但完整的路径不是硬编码,而是运算出来的
//在classpath根目录下面去逐一找这个文件,cn/itcast/day1 在cn前面不要加斜杠
//InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");
//InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");
InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/config.properties");
Properties props = new Properties();
props.load(ips);
ips.close();
String className = props.getProperty("className");
Collection collections = (Collection)Class.forName(className).newInstance();
//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());
}
}