Java反射机制
重点部分:1. 获取Class实例
2.创建运行时类的对象
3.调用运行时类的指定结构

public class ReflectionTest { //反射之前,对于Person的操作 @Test public void test1() { //1.创建Person类的对象 Person p1 = new Person("Tom", 12); //2.通过对象,调用其内部的属性、方法 p1.age = 10; System.out.println(p1.toString()); p1.show(); //在Person类外部,不可以通过Person类的对象调用其内部私有结构。 //比如:name、showNation()以及私有的构造器 } //反射之后,对于Person的操作 @Test public void test2() throws Exception{ Class clazz = Person.class; //1.通过反射,创建Person类的对象 Constructor cons = clazz.getConstructor(String.class,int.class); Object obj = cons.newInstance("Tom", 12); Person p = (Person) obj; System.out.println(p.toString()); //2.通过反射,调用对象指定的属性、方法 //调用属性 Field age = clazz.getDeclaredField("age"); age.set(p,10); System.out.println(p.toString()); //调用方法 Method show = clazz.getDeclaredMethod("show"); show.invoke(p); System.out.println("*******************************"); //通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性 //调用私有的构造器 Constructor cons1 = clazz.getDeclaredConstructor(String.class); cons1.setAccessible(true); Person p1 = (Person) cons1.newInstance("Jerry"); System.out.println(p1); //调用私有的属性 Field name = clazz.getDeclaredField("name"); name.setAccessible(true); name.set(p1,"HanMeimei"); System.out.println(p1); //调用私有的方法 Method showNation = clazz.getDeclaredMethod("showNation", String.class); showNation.setAccessible(true); String nation = (String) showNation.invoke(p1,"中国");//相当于String nation = p1.showNation("中国") System.out.println(nation); } //疑问1:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用那个? //建议:直接new的方式。 //什么时候会使用:反射的方式。 反射的特征:动态性 //疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术? //不矛盾。反射:是怎么去调私有结构 // 封装性:是建议怎么调

public class Person { private String name; public int age; @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Person(String name, int age) { this.name = name; this.age = age; } private Person(String name) { this.name = name; } public Person() { System.out.println("Person()"); } public void show(){ System.out.println("你好,我是一个人"); } private String showNation(String nation){ System.out.println("我的国籍是:" + nation); return nation; } }
获取Class的实例
Class本身也是一个类。一个加载的类在JVM中只会有一个Class实例。
获取Class类的实例(四种方法)
//1.已知具体的类,通货类的class属性获取,该方法最为可靠,程序性能最高 Class calzz = String.class; //2.已知某个类的实例,调用该实例的getClass()方法来获取Class对象 Class clazz = “某个实例”.getClass; //3. 已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException Class clazz = Class.forName("java.lang.String"); //4.其他方法 ClassLoader cl = this.getClass().getClassLoader(); Class clazz = cl.loadClass("类的全类名")

/* 关于java.lang.Class类的理解 1.类的加载过程: 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。 接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件 加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此 运行时类,就作为Class的一个实例。 2.换句话说,Class的实例就对应着一个运行时类。 3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式 来获取此运行时类。 */ //获取Class的实例的方式(前三种方式需要掌握) @Test public void test3() throws ClassNotFoundException { //方式一:调用运行时类的属性:.class Class clazz1 = Person.class; System.out.println(clazz1); //方式二:通过运行时类的对象,调用getClass() Person p1 = new Person(); Class clazz2 = p1.getClass(); System.out.println(clazz2); //方式三:调用Class的静态方法:forName(String classPath) Class clazz3 = Class.forName("com.atguigu.java.Person"); // clazz3 = Class.forName("java.lang.String"); System.out.println(clazz3); System.out.println(clazz1 == clazz2); System.out.println(clazz1 == clazz3); //方式四:使用类的加载器:ClassLoader (了解) ClassLoader classLoader = ReflectionTest.class.getClassLoader(); Class clazz4 = classLoader.loadClass("com.atguigu.java.Person"); System.out.println(clazz4); System.out.println(clazz1 == clazz4); }
哪些类型可以有Class对象

//Class实例可以是哪些结构的说明: @Test public void test4(){ Class c1 = Object.class; Class c2 = Comparable.class; Class c3 = String[].class; Class c4 = int[][].class; Class c5 = ElementType.class; Class c6 = Override.class;//Override注解 Class c7 = int.class; Class c8 = void.class; Class c9 = Class.class; int[] a = new int[10]; int[] b = new int[100]; Class c10 = a.getClass(); Class c11 = b.getClass(); // 只要数组的元素类型与维度一样,就是同一个Class System.out.println(c10 == c11); } }
类的加载与ClassLoader的理解
一、加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。
二、链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
三、初始化:
//加载、链接、类的初始化 public class ClassLoadingTest { public static void main(String[] args) { System.out.println(A.m); } } class A { static { m = 300; } static int m = 100; } //第二步:链接结束后m=0 //第三步:初始化后,m的值由<clinit>()方法执行决定 // 这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并 产生,类似于 // <clinit>(){ // m = 300; // m = 100; // }
/* 什么时候会发生类的初始化? 类的主动引用(一定会发生类的初始化) 当虚拟机启动,先初始化main方法所在的类 new一个类的对象 调用类的静态成员(除了final常量)和静态方法 使用java.lang.reflect包的方法对类进行反射调用 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类 类的被动引用(不会发生类的初始化) 当访问一个静态域时,只有真正声明这个域的类才会被初始化 当通过子类引用父类的静态变量,不会导致子类初始化 通过数组定义类引用,不会触发此类的初始化 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了) */ public class ClassLoadingTest { public static void main(String[] args) { // 主动引用:一定会导致A和Father的初始化 // A a = new A(); // System.out.println(A.m); // Class.forName("com.atguigu.java2.A"); // 被动引用 A[] array = new A[5];//不会导致A和Father的 初始化 // System.out.println(A.b);//只会初始化 Father // System.out.println(A.M);//不会导致A和 Father的初始化 } static { System.out.println("main所在的类"); } } class Father { static int b = 2; static { System.out.println("父类被加载"); } } class A extends Father { static { System.out.println("子类被加载"); m = 300; } static int m = 100; static final int M = 1; }
- 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。类加载器作用是用来把类(class)装载进内存的。
- 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
//1.获取一个系统类加载器 • ClassLoader classloader = ClassLoader.getSystemClassLoader(); • System.out.println(classloader); //2.获取系统类加载器的父类加载器,即扩展类加载器 • classloader = classloader.getParent(); • System.out.println(classloader); //3.获取扩展类加载器的父类加载器,即引导类加载器 • classloader = classloader.getParent(); • System.out.println(classloader); //4.测试当前类由哪个类加载器进行加载 • classloader = Class.forName("exer2.ClassloaderDemo").getClassLoader(); • System.out.println(classloader); //5.测试JDK提供的Object类由哪个类加载器加载 • classloader = • Class.forName("java.lang.Object").getClassLoader(); • System.out.println(classloader); //*6.关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流 • InputStream in = null; • in = this.getClass().getClassLoader().getResourceAsStream("exer2\\test.properties"); • System.out.println(in);

/** * 了解类的加载器 */ public class ClassLoaderTest { @Test public void test1(){ //对于自定义类,使用系统类加载器进行加载 ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(classLoader); //调用系统类加载器的getParent():获取扩展类加载器 ClassLoader classLoader1 = classLoader.getParent(); System.out.println(classLoader1); //调用扩展类加载器的getParent():无法获取引导类加载器 //引导类加载器主要负责加载java的核心类库,无法加载自定义类的。 ClassLoader classLoader2 = classLoader1.getParent(); System.out.println(classLoader2); ClassLoader classLoader3 = String.class.getClassLoader(); System.out.println(classLoader3); } /* Properties:用来读取配置文件。 */ @Test public void test2() throws Exception { Properties pros = new Properties(); //此时的文件默认在当前的module下。 //读取配置文件的方式一: // FileInputStream fis = new FileInputStream("jdbc.properties"); // FileInputStream fis = new FileInputStream("src\\jdbc1.properties"); // pros.load(fis); //读取配置文件的方式二:使用ClassLoader //配置文件默认识别为:当前module的src下 ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); InputStream is = classLoader.getResourceAsStream("jdbc1.properties"); pros.load(is); String user = pros.getProperty("user"); String password = pros.getProperty("password"); System.out.println("user = " + user + ",password = " + password); } }
创建运行时类的对象
创建类的对象:调用Class对象的newInstance()方法。
要求:1. 类必须有一个无参数的构造器。
2. 类的构造器的访问权限需要足够。
注:没有无参构造器也能创建类的对象,只要在操作的时候明确的调用了类中的构造器,并将参数传递进去之后,才可以实例化操作。

1)通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类 型的构造器 2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。 3)通过Constructor实例化对象。
//1.根据全类名获取对应的Class对象 String name = “gym.java.Person"; Class clazz = null; clazz = Class.forName(name); //2.调用指定参数结构的构造器,生成Constructor的实例 Constructor con = clazz.getConstructor(String.class,Integer.class); //3.通过Constructor的实例创建对应类的对象,并初始化类属性 Person p2 = (Person) con.newInstance("Peter",20); System.out.println(p2);

/** * 通过发射创建对应的运行时类的对象 * */ public class NewInstanceTest { @Test public void test1() throws IllegalAccessException, InstantiationException { Class<Person> clazz = Person.class; /* newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。 要想此方法正常的创建运行时类的对象,要求: 1.运行时类必须提供空参的构造器 2.空参的构造器的访问权限得够。通常,设置为public。 在javabean中要求提供一个public的空参构造器。原因: 1.便于通过反射,创建运行时类的对象 2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器 */ Person obj = clazz.newInstance(); System.out.println(obj); } //体会反射的动态性 @Test public void test2(){ for(int i = 0;i < 100;i++){ int num = new Random().nextInt(3);//0,1,2 String classPath = ""; switch(num){ case 0: classPath = "java.util.Date"; break; case 1: classPath = "java.lang.Object"; break; case 2: classPath = "com.atguigu.java.Person"; break; } try { Object obj = getInstance(classPath); System.out.println(obj); } catch (Exception e) { e.printStackTrace(); } } } /* 创建一个指定类的对象。 classPath:指定类的全类名 */ public Object getInstance(String classPath) throws Exception { Class clazz = Class.forName(classPath); return clazz.newInstance(); } }
重要的方法有:1.获取父类的泛型。2.运行时类实现的接口。3.运行时类声明的注解。
通过反射可以取到:
1.实现的全部接口:
public Class<?>[] getInterfaces()
public Class<? Super T> getSuperclass()
返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的Class。
public Constructor<T>[] getConstructors() //返回此 Class 对象所表示的类的所有public构造方法。
public Constructor<T>[] getDeclaredConstructors() //返回此 Class 对象表示的类声明的所有构造方法。
Constructor类中:
取得修饰符: public int getModifiers(); 取得方法名称: public String getName(); 取得参数的类型:public Class<?>[] getParameterTypes();
4.全部的方法
public Method[] getDeclaredMethods() //返回此Class对象所表示的类或接口的全部方法
public Method[] getMethods() //返回此Class对象所表示的类或接口的public的方法
Method类中:
public Class<?> getReturnType()取得全部的返回值 public Class<?>[] getParameterTypes()取得全部的参数 public int getModifiers()取得修饰符 public Class<?>[] getExceptionTypes()取得异常信息
5.全部的Field
public Field[] getFields() //返回此Class对象所表示的类或接口的public的Field。
public Field[] getDeclaredFields() //返回此Class对象所表示的类或接口的全部Field。
Field方法中:
public int getModifiers() 以整数形式返回此Field的修饰符 public Class<?> getType() 得到Field的属性类型 public String getName() 返回Field的名称。
6. Annotation相关
get Annotation(Class<T> annotationClass)
getDeclaredAnnotations()
7.泛型相关
获取父类泛型类型:Type getGenericSuperclass()
泛型类型:ParameterizedType
获取实际的泛型类型参数数组:getActualTypeArguments()
8.类所在的包
Package getPackage()
一、调用指定方法
Object invoke(Object obj,Object... args) /*说明: 1.Object对应原方法的返回值,若原方法无返回值,此时返回null。 2.若原方法是静态方法,此时形参Object obj可为null。 3.若原方法形参列表为空,则Object[] args为null 4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。*/
二、调用指定属性
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。
public Field getField(String name) //返回此Class对象表示的类或接口的指定的public的Field public Field getDeclaredField(String name) //返回此Class对象表示的类或接口的指定的Field。 //在Field中, public Object get(Object obj) 取得指定对象obj上此Field的属性内容 public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容
setAccessible方法的使用

1. Method和Field、Constructor对象都有setAccessible()方法 2. setAccessible启动和禁用访问安全检查的开关。 3. 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。 使得原本无法访问的私有成员也可以访问 4. 参数值为false则指示反射的对象应该实施Java语言访问检查。

/** * 调用运行时类中指定的结构:属性、方法、构造器 */ public class ReflectionTest { /* 不需要掌握 */ @Test public void testField() throws Exception { Class clazz = Person.class; //创建运行时类的对象 Person p = (Person) clazz.newInstance(); //获取指定的属性:要求运行时类中属性声明为public //通常不采用此方法 Field id = clazz.getField("id"); /* 设置当前属性的值 set():参数1:指明设置哪个对象的属性 参数2:将此属性值设置为多少 */ id.set(p,1001); /* 获取当前属性的值 get():参数1:获取哪个对象的当前属性值 */ int pId = (int) id.get(p); System.out.println(pId); } /* 如何操作运行时类中的指定的属性 -- 需要掌握 */ @Test public void testField1() throws Exception { Class clazz = Person.class; //创建运行时类的对象 Person p = (Person) clazz.newInstance(); //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性 Field name = clazz.getDeclaredField("name"); //2.保证当前属性是可访问的 name.setAccessible(true); //3.获取、设置指定对象的此属性值 name.set(p,"Tom"); System.out.println(name.get(p)); } /* 如何操作运行时类中的指定的方法 -- 需要掌握 */ @Test public void testMethod() throws Exception { Class clazz = Person.class; //创建运行时类的对象 Person p = (Person) clazz.newInstance(); /* 1.获取指定的某个方法 getDeclaredMethod():参数1 :指明获取的方法的名称 参数2:指明获取的方法的形参列表 */ Method show = clazz.getDeclaredMethod("show", String.class); //2.保证当前方法是可访问的 show.setAccessible(true); /* 3. 调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参 invoke()的返回值即为对应类中调用的方法的返回值。 */ Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN"); System.out.println(returnValue); System.out.println("*************如何调用静态方法*****************"); // private static void showDesc() Method showDesc = clazz.getDeclaredMethod("showDesc"); showDesc.setAccessible(true); //如果调用的运行时类中的方法没有返回值,则此invoke()返回null // Object returnVal = showDesc.invoke(null); Object returnVal = showDesc.invoke(Person.class); System.out.println(returnVal);//null } /* 如何调用运行时类中的指定的构造器 */ @Test public void testConstructor() throws Exception { Class clazz = Person.class; //private Person(String name) /* 1.获取指定的构造器 getDeclaredConstructor():参数:指明构造器的参数列表 */ Constructor constructor = clazz.getDeclaredConstructor(String.class); //2.保证此构造器是可访问的 constructor.setAccessible(true); //3.调用此构造器创建运行时类的对象 Person per = (Person) constructor.newInstance("Tom"); System.out.println(per); } }

package com.atguigu.java; /** * 静态代理举例 * * 特点:代理类和被代理类在编译期间,就确定下来了。 * */ interface ClothFactory{ void produceCloth(); } //代理类 class ProxyClothFactory implements ClothFactory{ private ClothFactory factory;//用被代理类对象进行实例化 public ProxyClothFactory(ClothFactory factory){ this.factory = factory; } @Override public void produceCloth() { System.out.println("代理工厂做一些准备工作"); factory.produceCloth(); System.out.println("代理工厂做一些后续的收尾工作"); } } //被代理类 class NikeClothFactory implements ClothFactory{ @Override public void produceCloth() { System.out.println("Nike工厂生产一批运动服"); } } public class StaticProxyTest { public static void main(String[] args) { //创建被代理类的对象 ClothFactory nike = new NikeClothFactory(); //创建代理类的对象 ClothFactory proxyClothFactory = new ProxyClothFactory(nike); proxyClothFactory.produceCloth(); } }

/** * * 动态代理的举例 */ interface Human{ String getBelief(); void eat(String food); } //被代理类 class SuperMan implements Human{ @Override public String getBelief() { return "I believe I can fly!"; } @Override public void eat(String food) { System.out.println("我喜欢吃" + food); } } class HumanUtil{ public void method1(){ System.out.println("====================通用方法一===================="); } public void method2(){ System.out.println("====================通用方法二===================="); } } /* 要想实现动态代理,需要解决的问题? 问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。 问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。 */ class ProxyFactory{ //调用此方法,返回一个代理类的对象。解决问题一 public static Object getProxyInstance(Object obj){//obj:被代理类的对象 MyInvocationHandler handler = new MyInvocationHandler(); handler.bind(obj); return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler); } } class MyInvocationHandler implements InvocationHandler{ private Object obj;//需要使用被代理类的对象进行赋值 public void bind(Object obj){ this.obj = obj; } //当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke() //将被代理类要执行的方法a的功能就声明在invoke()中 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { HumanUtil util = new HumanUtil(); util.method1(); //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法 //obj:被代理类的对象 Object returnValue = method.invoke(obj,args); util.method2(); //上述方法的返回值就作为当前类中的invoke()的返回值。 return returnValue; } } public class ProxyTest { public static void main(String[] args) { SuperMan superMan = new SuperMan(); //proxyInstance:代理类的对象 Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan); //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法 String belief = proxyInstance.getBelief(); System.out.println(belief); proxyInstance.eat("四川麻辣烫"); System.out.println("*****************************"); NikeClothFactory nikeClothFactory = new NikeClothFactory(); ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory); proxyClothFactory.produceCloth(); } }