一、反射机制——概述&应用场景
概念: 1、java反射机制是在运行状态中,对于任意一个类(class文件),都能够知道这个类的所有属性和方法; 2、对于任意一个对象,都能够调用它的任意方法和属性; 3、这种动态获取的信息以及动态调用对象的方法和功能称为java语言的反射机制。 4、反射技术出现于JDK1.2版本。 5、反射应用一般需要有接口和配置文件。 6、反射技术也可以理解为对类的解剖。 反射一般用于框架的开发及应用 学习框架:1、看这个框架是干什么的;2、看这个框架的配置文件怎么用;3、学框架中常用对象的用法。二、反射机制——细节&Class 对象
1、什么是字节码文件? 例如: 当我们在程序中使用到Person这个类的时候,首先要从硬盘上把Person这个类的二进制代码加载到内存中,然后通过这个字节码创建出一个一个的对象。 当我们用到Date这个类,Math这个类,那么这两个类也会加载进内存。 那么这时,内存中就有了3个字节码。 那么,每一个字节码就是 Class 的实例对象。 例如: Person p1 = new Person(); Person p2 = new Person(); Date Math Class cls1 = Date.class //Date.class就是一个字节码 Class cls2 = Person.class //Person.class就是一个字节码 Class cls3 = Math.class 2、 Class 类描述 java.lang.Object -java.lang.Class<T> public final class Class<T> extends Object implements Serializable, GenericDeclaration, Type, AnnotatedElement Class 类的实例表示正在运行的 Java 应用程序中的类和接口。 该类可以获取.class 字节码文件中的所有内容。 3、九个预定义Class实例对象 基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。 例如: Class cls1 = boolean.class Class cls2 = int.class Class cls3 = void.class ...... 只要是在源程序中出现的类型,都有各自的Class实例对象。三、反射机制——获取 Class 对象的三种方式以及一些小细节
想要对字节码文件进行解剖,必须要有字节码文件对象。 如何获取字节码文件对象呢?字节码文件可以理解为这个 Class 对象。 获取 Class 对象的三种方式: 1、获取字节码对象的方式一: Object类中的getClass()方法; 想要用这种方式,必须要明确具体的类,并创建对象。 2、方式二: 任何数据类型都具备都具备一个静态的属性 .class ,可以用它来获取其对应的 Class 对象。 相对简单,但还是需要明确用到类中的静态成员。 3、方式三: 只要通过给定的类的字符串名称就可以获取该类,这样更为扩展。 可以用 Class类中的方法来完成。 该方法就是 Class 类中的静态方法: static Class forName(String className); 4、示例: String str = "abc"; 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 cls1、cls2、cls3都表示Class的一个实例对象。都是 String 类加载进内存内存后的一份字节码,而且该字节码只有一个。 5、 Class 类中有一些方法: boolean isPrimitive():判定指定的 Class 对象是否表示一个基本类型。 例如: System.out.println(String.class.isPrimitive());//结果是false System.out.println(int.class.isPrimitive());//结果是true System.out.println(int.class == Integer.class);//结果是false;因为int是基本数据类型,Integer是类类型 System.out.println(int[].class.isPrimitive());//结果是false;因为数组是引用数据类型 boolean isArray():判定此 Class 对象是否表示一个数组类。 示例: System.out.println(int[].class.isArray());//结果是true 6、 Integer 类中有一个字段摘要: static Class<Integer> TYPE :表示基本类型 int 的 Class 实例。 示例: System.out.println(int.class == Integer.TYPE);//结果是true 示例代码:class ClassDemo { public static void main(String[] args) throws ClassNotFoundException { getClassObject_3(); } /* 获取字节码对象的方式一: Object类中的getClass()方法; 想要用这种方式,必须要明确具体的类,并创建对象。 */ public static void getClassObject_1() { Person p = new Person(); Class clazz = p.getClass(); Person p1 = new Person(); Class clazz1 = p1.getClass(); System.out.println(clazz==clazz1);//true } /* 方式二: 任何数据类型都具备都具备一个静态的属性 .class 来获取其对应的 Class 对象。 相对简单,但还是需要明确用到类中的静态成员。 */ public static void getClassObject_2() { Class clazz = Person.class; Class clazz1 = Person.class; System.out.println(clazz==clazz1);//true } /* 方式三: 只要通过给定的类的字符串名称就可以获取该类,这样更为扩展。 可以用 Class类中的方法来完成。 该方法就是forName(String className); */ public static void getClassObject_3() throws ClassNotFoundException { String className = "Person"; Class clazz = Class.forName(className); System.out.println(clazz);//class Person } }
四、反射机制——获取 Class 中的构造函数
1、 Class 类中获取指定字节码文件中构造函数的方法(该方法只能获取 public 修饰的):
Constructor<T> getConstructor(Class<?>... parameterTypes); 获取单个
2、获取所有 public 构造方法:
Constructor<?>[] getConstructors();
3、获取指定的构造方法,包括私有的:
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes);
4、获取所有的构造方法,包括私有的:
Constructor<?>[] getDeclaredConstructors();
5、获取一个 Class 对象后,可以调用 Class 对象的 newInstance();方法创建该Class所表示的类的空参数的实例对象。
如果要创建一个非空参数的实例对象,只能先获取到对应的构造方法对象后,才能通过指定的构造方法对象创建对象。
步骤:
(1)获取Class对象;
(2)调用Classd对象的getConstructor方法(以上四个中的一个),并返回 Constructor 对象;
(3)调用Constructor对象的newInstance方法。
6、示例代码:import java.lang.reflect.*; class ReflectDemo { public static void main(String[] args) throws Exception { createNewObject_2(); } public static void createNewObject() throws ClassNotFoundException, IllegalAccessException, InstantiationException { String name = "Person"; Class clazz = Class.forName(name); //创建空参数的对象 Object obj = clazz.newInstance(); } public static void createNewObject_2() throws Exception { String name = "Person"; Class clazz = Class.forName(name); //获取指定构造函数对象。 Constructor constructor = clazz.getConstructor(String.class, int.class); //通过该构造器的newInstance方法进行对象的初始化。 Object obj = constructor.newInstance("小明", 32); } }
五、反射机制——获取 Class 中的字段
Class 类中获取字段(包括私有)的方法:
Field getDeclaredField(String name); 获取指定字段
Field[] getDeclaredFields(); 获取所有字段对象
Field getField(String name); 和 Field[] getFields(); 只能获取 public 修饰的字段。
1、获取字段,并给字段赋值
步骤:
1、获取Class对象;
2、调用Class对象的getField方法(以上四个中的一个),并返回Field对象;
3、创建字节码文件所表示的类对象obj;
4、给指定对象obj字段赋值。
2、暴力反射:
当字节码文件中某些字段被private修饰后,通常不可以被访问;
这时,可以调用该字段对象的setAccessible方法,并设置为true;
这时就可以访问该字段了。
3、setAccessible方法:
void setAccessible(boolean flag) 将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
该方法是 AccessibleObject 类的一个方法。 AccessibleObject 类是 Constructor 、 Field 、 Method 的共同父类。
那么对于私有构造函数和私有函数也可以调用该方法进行暴力反射。
示例代码:import java.lang.reflect.*; class ReflectDemo2 { public static void main(String[] args) throws Exception { getFieldDemo(); } public static void getFieldDemo() throws Exception { Class clazz = Class.forName("Person"); //获取私有字段 Field field = clazz.getDeclaredField("age"); //对私有字段的访问取消权限检查(暴力访问) field.setAccessible(true); //创建空参数的对象 Object obj = clazz.newInstance(); //设置字段的值 field.set(obj,89); //获取obj对象中的field字段。 Object o = field.get(obj); System.out.println("field: "+field); //private int Person.age System.out.println("obj: "+obj); //Person@6d06d69c System.out.println("o: "+o); //89 } }
六、反射机制——获取 Class 中的方法
Class 类中同样有获取字节码文件所表示的类的方法,获取方式与 Constructor 和 Field 相同。
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getMethod(String name, Class<?>... parameterTypes)
Method[] getMethods()
不同的是,获取到 Method 对象后,调用的是 Method 对象的 invoke()方法去调用字节码文件类的方法。
代码:import java.lang.reflect.*; class ReflectDemo3 { public static void main(String[] args) throws Exception { getMethodDemo_3(); } public static void getMethodDemo() throws Exception { Class clazz = Class.forName("Person"); //Method[] methods = clazz.getMethods();//获取所有公有的方法(包括继承的方法) Method[] methods = clazz.getDeclaredMethods();//只获取本类中的所有方法,包括私有方法。不包括继承的方法。 for(Method method : methods) { System.out.println(method); } } public static void getMethodDemo_2() throws Exception { Class clazz = Class.forName("Person"); Method method = clazz.getMethod("show",null); System.out.println(method); //Object obj = clazz.newInstance(); Constructor constructor = clazz.getConstructor(String.class, int.class); Object obj = constructor.newInstance("小明",22); method.invoke(obj,null);//调用show()方法 } public static void getMethodDemo_3() throws Exception { Class clazz = Class.forName("Person"); Method method = clazz.getMethod("paramMethod",String.class,int.class); Object obj = clazz.newInstance(); method.invoke(obj,"小强",55); } }
七、反射机制——反射练习
加入反射技术,优化day08电脑主板示例代码:
配置文件信息:
pci1=SoundCard pci2=NetCardimport java.io.File; import java.io.FileInputStream; import java.util.Properties; public class ReflectTest { public static void main(String[] args) throws Exception { MainBoard mb = new MainBoard(); mb.run(); //mb.usePCI(new SoundCard());//以前是直接new一个PCI传入方法参数。 //利用反射技术,首先加载进一个配置文件 File configFile = new File("pci.properties"); Properties prop = new Properties(); FileInputStream fis = new FileInputStream(configFile); prop.load(fis); //扫描这个配置文件,并获取配置文件信息,也就是类名 for (int x = 0; x < prop.size(); x++) { String className = prop.getProperty("pci"+(x+1)); //创建Class对象 Class clazz = Class.forName(className); //创建PCI对象 PCI p = (PCI)clazz.newInstance(); //调用方法 mb.usePCI(p); } fis.close(); } }
八、对接收数组参数的成员方法进行反射
示例代码:该代码存在问题点:import java.lang.reflect.*; class Demo { public static void main(String[] args)throws Exception { String className = args[0]; Method mainMethod = Class.forName(className).getMethod("naim",String[].class); mainMethod.invoke(null,new String[]{"111","222","333"});//这样传入参数会出现参数个数异常 //mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});//正确写法 //mainMethod.invoke(null,(Object)new String[]{"111","222","333"});//正确写法二 } } class Test { public static void main(String[] args) { for (int x=0 ;x<args.length ;x++ ) { System.out.println(args); } } }
1、运行时需要传入一个字符串参数: java Demo Test
2、JDK1.5后,增加了可变参数的新特性,但是还需要兼容JDK1.4版本;当调用一个参数为数组类型的方法时,传入的数组将会自动拆包为一个一个的元素。
该例中,mainMethod.invoke(null,new String[]{"111","222","333"});参数中的String类型的数组会被拆包成一个一个的String类型的元素;
所以运行结果会出现参数个数异常。
应该改为:mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});
或者改为:mainMethod.invoke(null,(Object)new String[]{"111","222","333"});九、数组与Object的关系及其反射类型
如果两个数组的元素类型,以及数组的维数都是相同的,则这两个数值的字节码文件是同一个。
代码示例:class Demo2 { 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());//true //System.out.println(a1.getClass() == a3.getClass());//编译失败,因为不能进行比较 //System.out.println(a1.getClass() == a4.getClass());//编译失败,因为不能进行比较 System.out.println(a1.getClass().getSuperclass());//class java.lang.Object } }
十、数组的反射应用
import java.lang.reflect.*; class Demo3 { public static void main(String[] args) { int[] arr = {2,5,8}; printObject(arr); } public static void printObject(Object obj) { Class cls = obj.getClass(); if(cls.isArray()) { int len = Array.getLength(obj); for (int x=0 ;x<len ;x++ ) { System.out.println(Array.get(obj,x)); } } else { System.out.println(obj); } } }