JavaSE| 反射
1. 类的加载、连接和初始化
* 类加载器的作用有两个:
(1)最本质的作用:加载类
(2)其他的作用:加载类路径下的资源文件
当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三个步骤来对该类进行初始化,如果没有意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或类初始化。
类的加载过程是有类加载器来完成的。
类加载器的分类
1)引导(启动)类加载(Bootstrap Classloader):根加载器; 它的代码不是Java实现的。用C/C++等原生语言编写。
它负责加载java核心类库:JRE/lib/rt.jar等和sun.boot.class.path路径下
2)扩展类加载器(Extension ClassLoader)
它是负责加载java扩展类库:jre/ext/*.jar和java.ext.dirs路径下
3)应用程序类加载器(Application Classloader)
它是负责加载Java应用程序类路径(classpath、java.class.path)下的内容
4)自定义类加载
我们如果自己写类加载器,继承ClassLoader类型,来加载
什么情况下需要自定义类加载器?
A:你的字节码是加密的,需要自定义类加载器解码才能运行
B:你的类路径是特殊的,或者类的加载顺序比较特殊的,例如tomcat就自定义类加载器
这些类加载器的关系:委托加载模式
* (3)应用程序类加载器是把(2)扩展类加载器作为“父parent”加载器
* (2)扩展类加载器把(1)引导类加载器作为“父parent”加载器; 这里不是继承。
* (4)可以选择(3)为父加载器,也可以不选,看自己的实现。
*
* 如果(3)应用程序类加载器接到一个加载任务时,那么它是按如下步骤来做的:
* (1)先在内存中搜索,这个类是否曾经被加载过了,如果加载过了,就不加载了,直接用
* (2)如果发现这个类没有加载过,那么它会把这个任务交给(2)扩展类加载器,
* (3)(2)扩展类加载器接到任务后,看它是否之前加载过,如果没有,在交给(1)引导类加载器
* (4)(1)引导类加载器接到任务后,看它是否之前加载过,如果没有,它在它负责的路径下,开始
* 搜索,看是否可以成功加载这个类。如果找到,就返回Class对象使用,如果找不到,它这个任务,
* 还给(2)扩展类加载器
* (5)(2)扩展类加载器再次接到任务后,在它负责的路径下,开始
* 搜索,看是否可以成功加载这个类。如果找到,就返回Class对象使用,如果找不到,它这个任务,
* 还给(3)应用程序类加载器
* (6)(3)应用程序类加载器再次接到任务后,在它负责的路径下,开始
* 搜索,看是否可以成功加载这个类。如果找到,就返回Class对象使用,如果找不到,
* 就报异常:ClassNotFoundException
委托模式目的:安全,避免核心类库被覆盖
内存中取判断一个类是否存在时,它的全路径: 类加载器 + 包名 + 类名。
自定义类加载器
想要让他去加载指定目录下的.class
1)自定义类加载器必须继承ClassLoader
2)重写findClass(String name)方法
步骤:
* (1)先搜索是否曾经加载过这个类; 使用Class<?> findLoadedClass(String name)搜索
* (2)如果加载器过,直接返回;如果没加载过,那么获取父加载器
* (3)让父加载器去加载这个类
* (4)如果父加载器没有加载到,那么我们自己加载
* A:读取xxx路径下的.class文件
* 把.class文件的数据读取到一个byte[]数组
* B:把这些数据生成一个Class对象
Class defineClass(String name, byte[] b, int off, int len)
public class TestClassLoader { public static void main(String[] args) { System.out.println(TestClassLoader.class.getClassLoader());//sun.misc.Launcher$AppClassLoader@18b4aac2 System.out.println(TestClassLoader.class.getClassLoader().getParent());//sun.misc.Launcher$ExtClassLoader@6bf2d08e System.out.println(TestClassLoader.class.getClassLoader().getParent().getParent()); //null /* Parent是上下级的意思,不是继承; 如果不同的位置有相同的类(比如有两个java.lang.String),那么jvm会采用双亲委派机制来加载类 有冲突就去委托它的上一级Ext去加载,它也不会马上去加载去委托它的上一级(启动类加载器),它就去加载,如果加载不到返回null给Ext; Ext加载器有就直接加载,没有就抛异常给App,App加载器去捕捉,由app这一级去加载,有就直接加载,没有就抛异常(ClassNotFoundExection)给JVM; 启动类加载器: 加载java核心类库(rt.jar, ClassLoader) ==> java.lang.String 没有对象,不是java实现对 抛出没有找到的异常给JVM lib classes 扩展类加载器Ext: 加载java扩展类库 返回null lib/ext classes 应用类加载器App: 加载环境变量classpath中的类 返回异常 ==> java.lang.String *///用cmd去测试; } }
自定义类加载器
public class MyClassLoader extends ClassLoader{ private String dirPath; public MyClassLoader(String dirPath) { super(); this.dirPath = dirPath; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { //(1)看是否加载过,如果加载过,直接返回 Class<?> clazz = findLoadedClass(name); if(clazz == null){//说明没有加载过 //(2)获取父加载器 ClassLoader parent = getParent(); try { //(3)让父加载器去加载这个类 clazz = parent.loadClass(name); } catch (Exception e) { } if(clazz == null){//父加载器的路径下没有这个类,我们自己来加载 try { //(4)到指定路径下读取.class文件,放到一个字节数组中 byte[] data = read(name); //(5)用字节数组的数据构建在一个Class对象 clazz = defineClass(name, data, 0, data.length); } catch (IOException e) { throw new ClassNotFoundException(name+"不存在"); } } } return clazz; } //name:com.atguigu.test.Student类名 //字节码文件:D:/haha/com/atguigu/test/Student.class public byte[] read(String name) throws IOException{ String path = dirPath + "/" + name.replace('.', '/') + ".class"; FileInputStream fis = new FileInputStream(path); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] data = new byte[1024]; int len; while((len = fis.read(data)) != -1){ bos.write(data, 0, len); } byte[] bs = bos.toByteArray(); fis.close(); return bs; } }
类的加载过程
* 类的加载的过程:
* (1)加载:把.class字节码的数据,读取到内存中,构建为一个Class对象。
* (2)连接:
* A:验证:是否符合标准
* 例如:字节码文件必须以cafe开头,我们称为“魔数”
* 接下来是版本号。
* B:准备:为所有的属性,赋“默认值”,注意不是显式初始化以及代码块初始化等
* 基本数据类型:
* byte,short,int,long:0
* float,double:0.0
* char:\u0000
* boolean :false
* 引用数据类型:null
* C:解析
* 把符合引用替换为直接引用
* 例如:String,替换为内存中String类型的Class对象的地址
* (3)初始化
* 类初始化,执行<clinit>(),它由
* A:类变量(静态变量)的显式初始化
* B:静态代码块
* A和B谁在上面谁先执行,并且<clinit>一个类只执行一次,如果父类没有初始化,先加载和初始化父类。
*
* 从哪里加载.class数据?(了解)
* 加载后的数据在“方法区”(静态数据区),生成一个Class对象。
*
* 大多数情况下,(1)(2)(3)都是一气呵成的。但是有的时候,(3)类的初始化可能分开。
* 如果首次使用这个类时,有如下操作,那么(1)(2)(3)就是一气呵成的:
* (1)main所在的类
* (2)new对象
* (3)使用它的静态的成员,静态的属性(非final)和静态方法
* (4)使用java.lang.reflect包下的api进行反射
* (5)子类的初始化会导致父类的初始化
*
* 如下情况,(3)步类初始化分开的:
(1)如果是使用一个类的静态的final的常量,那么不会导致类的初始化;因为,常量是在(2)连接时,已经加载到常量池。
(2)当访问一个静态域时,只有真正声明这个域的类才会被初始化,当通过子类引用父类的静态变量,不会导致子类初始化
(3)用某个类声明数组并创建数组对象时,不会导致类初始化
使用类加载器加载资源文件
类加载器的作用有两个:
(1)最本质的作用:加载类
(2)其他的作用:加载类路径下的资源文件
ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java类,即java.lang.Class类的一个实例。除此之外,ClassLoader还负责加载
Java应用所需的资源,如图像文件和配置文件等。
1、加载的类路径下的资源文件,src-->bin/classes路径下
* 步骤:
* (1)创建一个Properties对象,用来装加载好的数据,键值对(key,value);Properties是一个Map,是Hashtable的子类,特殊在key,value的类型都是String
* (2)用类加载器去加载文件
方式一:适用于JavaSE的项目
pro.load(ClassLoader.getSystemResourceAsStream("1.properties"));
方式二:适用于JavaSE项目和web项目
* A:先获取当前类的类加载器对象
当前类的名称.class.getClassLoader()
* B:再用这个类加载器对象,去加载资源文件
2、加载非类路径下的资源文件
* 步骤:
* (1)创建一个Properties对象,用来装加载好的数据,键值对(key,value)
* (2)用FileInputStream去加载
加载类路径下(例如:src下)jdbc.properties资源文件的示例代码:
@Test public void test1(){ ////这个文件的路径在src下 Properties pro = new Properties(); try { //把系统的资源文件,用字节输入流导入 pro.load(ClassLoader.getSystemResourceAsStream("11.properties")); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(pro); } @Test public void test2(){ ////这个文件的路径在src下 Properties pro = new Properties(); //获取当前类的类加载器对象 try { ClassLoader loader = TestLoaderProperties.class.getClassLoader(); //获取当前类的加载器对象-->>>类名.class.getClassLoader; pro.load(loader.getResourceAsStream("11.properties")); //再用这个类加载器对象去加载资源; } catch (IOException e) { e.printStackTrace(); } System.out.println(pro); } @Test public void test3(){ Properties pro = new Properties(); try { //这个文件的路径在src的外面 FileInputStream fis = new FileInputStream("12.properties"); //用FileInputStream( ) --> 去加载 pro.load(fis); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(pro); } @Test public void test4(){ //这个文件的路径在src下的某个包里 Properties pro = new Properties(); ClassLoader loader = TestLoaderPropertiestry { pro.load(loader.getResourceAsStream("com/atguigu/reflect/13.properties")); //与test2方式一样,只不过需要把src下报名路径写上 } catch (IOException e) { e.printStackTrace(); } System.out.println(pro); } @Test public void test5(){ Properties pro = new Properties(); //这个文件的路径在 SourceFolder下,例如config下,等价于src下; <==> 等价于test1 try { ClassLoader loader = TestLoaderProperties.class.getClassLoader(); pro.load(loader.getResourceAsStream("14.properties"));//不用加config } catch (IOException e) { e.printStackTrace(); } System.out.println(pro); }
println(getValueFromProperties("config.properties", "jdbc.driver.class"))//读取config.properties配置文件 def getValueFromConfig(key: String): String = { getValueFromProperties("config", key) } //读取condition.properties文件中的json字符串 def getValueFromCondition(key: String): String = { // 将JSON字符串进行转换 val condition: String = getValueFromProperties("condition", "condition.params.json") val jsonObj: JSONObject = JSON.parseObject(condition) jsonObj.getString(key) } //使用反射的方式读取文件; 通过fileName和key获取property的value值 /* def getValueFromProperties(fileName: String, key: String): String = { val stream: InputStream = Thread.currentThread().getContextClassLoader.getResourceAsStream(fileName) val properties: Properties = new Properties() properties.load(stream) properties.getProperty(key) }*/ // 使用国际化(i18n)组件读取配置文件,只能读取properties文件 def getValueFromProperties(fileName: String, key: String): String = { val bundle = ResourceBundle.getBundle(fileName) bundle.getString(key) }
2. 通过反射查看信息
user Emp 没有父子关系不要用继承,用反射, 对象与对象之间做关联
所有的对象都有两种类型:编译时类型和运行时类型,而很多时候对象的编译时类型和运行时类型不一致。
我们有两种方法:第一种是在编译和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接先使用instanceof运算符进行判断,再利用强制类型转换符将其转换成运行时类型的变量即可。
第二种是编译时根本无法预知该对象和类的真实信息,程序只能依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。
因为加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。事实上,所有类型都可以表示为Class的实例对象。
* Java中,每一种类型,被加载到内存(方法区)中后,都会用一个Class对象来表示。 java.lang.Class类型: * (1)Class的实例是表示“正在运行的” * (2)Class的实例可以表示:类、接口、枚举、注解、数组、基本数据类型、void 如果数组的元素类型和维度相同,那么共享同一个Class对象。 * (3)Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。 我们可以获取到每一种类型的Class对象,可以通过如下四种方式拿到:*
(1)类型名.class 如:这种方式可用来获取类加载器对象 类名.class.getClassLoader() (2)对象名.getClass(); 在java.lang.Object类中有getClass()获取对象的运行时类型的方法 (3)Class.forName(类的全名称); 类的全名称:包.类名 如Class<?> cla = Class.forName("com.atguigu.student") (4)类加载器对象.loadClass(xx) * 如何获取类加载器对象? (1) ClassLoader.getSystemClassLoader( )获取系统类加载器对象
(2)每一个Class对象.getClassLoader() * 基本数据类型和void的Class对象只能用(1)方式得到 * 数组的Class对象,可以用(1)(2)方法得到 * 其他的类、接口、枚举、注解,可以用(1)(2)(3)(4)方法得到
从Class中获取信息
Class类大致包含如下几种方法,如:包、修饰符、类名、父类、父接口、注解,及成员(属性、构造器、方法)等
反射相关的API主要是java.lang.Class和java.lang.reflect包的内容。
获取某个类的加载器
public ClassLoader getClassLoader():返回该类的类加载器。有些实现可能使用 null 来表示引导类加载器。如果此对象表示一个基本类型或 void,则返回 null。
获取包名和类型名
public Package getPackage():获取此类的包。然后可以通过Package实例对象的getName()获取包名。 public String getName():返回的是全类名;包名+类名 以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
获取类型修饰符
public int getModifiers():返回此类或接口以整数编码的 Java 语言修饰符。
修饰符由 Java 虚拟机的 public、protected、private、final、static、abstract 和 interface 对应的常量组成;它们应当使用 Modifier 类的方法来解码。
如果底层类是数组类,则其 public、private 和 protected 修饰符与其组件类型的修饰符相同。如果此 Class 表示一个基本类型或 void,则其 public 修饰符始终为 true,protected 和 private 修饰符始终为 false。如果此对象表示一个数组类、一个基本类型或 void,则其 final 修饰符始终为 true,其接口修饰符始终为 false。该规范没有给定其他修饰符的值。
//获取类型修饰符 int mod = cla.getModifiers(); //public->1 final->16 System.out.println("修饰符:"+Modifier.toString(mod));//public
获取父类或父接口
public Class<? super T> getSuperclass():返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。如果此 Class 表示 Object 类、一个接口、一个基本类型或 void,则返回 null。如果此对象表示一个数组类,则返回表示该 Object 类的 Class 对象。 public Class<?>[] getInterfaces():确定此对象所表示的类或接口实现的接口。如果此对象表示一个类,则返回值是一个数组,它包含了表示该类所实现的所有接口的对象。数组中接口对象顺序与此对象所表示的类的声明的 implements 子句中接口名顺序一致。如果此对象表示一个接口,则该数组包含表示该接口扩展的所有接口的对象。数组中接口对象顺序与此对象所表示的接口的声明的 extends 子句中接口名顺序一致。 如果此对象表示一个不实现任何接口的类或接口,则此方法返回一个长度为 0 的数组。如果此对象表示一个基本类型或 void,则此方法返回一个长度为 0 的数组。
获取内部类或外部类信息
public Class<?>[] getClasses():返回所有公共内部类和内部接口。包括从超类继承的公共类和接口成员以及该类声明的公共类和接口成员。 public Class<?>[] getDeclaredClasses():返回 Class 对象的一个数组,这些对象反映声明为此 Class 对象所表示的类的成员的所有类和接口。包括该类所声明的公共、保护、默认(包)访问及私有类和接口,但不包括继承的类和接口。 public Class<?> getDeclaringClass():如果此 Class 对象所表示的类或接口是一个内部类或内部接口,则返回它的外部类或外部接口,否则返回null。
@Test public void test1(){ System.out.println(String.class.getName()); System.out.println(char.class.getName()); //类型名 System.out.println(String.class.getPackage());//获取包名 /* public ClassLoader getClassLoader():返回该类的类加载器。有些实现可能使用 null 来表示引导类加载器。 如果此对象表示一个基本类型或 void,则返回 null。*/ System.out.println(String.class.getClassLoader()); //null 返回该类的类加载器 Class<?> clazz = String.class; int mod = clazz.getModifiers(); System.out.println(mod); //17 System.out.println(Modifier.toString(mod)); //public final System.out.println(Modifier.isPublic(mod)); //true System.out.println(Integer.class.getSuperclass()); System.out.println(String.class.getSuperclass()); //获取父类 Class<?>[] interfaces = clazz.getInterfaces(); //获取父接口 for (Class<?> inter : interfaces) { System.out.println(inter); } Class<?> clas = Map.class; Class<?>[] inners = clas.getDeclaredClasses(); for (Class<?> inner : inners) { System.out.println(inner); } Class<?> outer = Map.Entry.class.getDeclaringClass(); System.out.println(outer);
获取属性
四个方法用于访问Class对应类所包含的属性(Field): public Field[] getFields():返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。返回数组中的元素没有排序,也没有任何特定的顺序。包括继承的公共字段。 public Field getField(String name):返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。包括继承的公共字段。name 参数是一个 String,用于指定所需字段的简称。 public Field[] getDeclaredFields():返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。包括公共、保护、默认(包)访问和私有字段,但不包括继承的字段。返回数组中的元素没有排序,也没有任何特定的顺序。 public Field getDeclaredField(String name):返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
获取构造器
四个方法用于访问Class对应的类所包含的构造器(Constructor): public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):构造器名称无需指定,因为它和类名一致。parameterTypes 参数是 Class 对象的一个数组,它按声明顺序标识构造方法的形参类型。 如果此 Class 对象表示非静态上下文中声明的内部类,则形参类型作为第一个参数包括显示封闭的实例。 public Constructor<?>[] getDeclaredConstructors():它们是公共、保护、默认(包)访问和私有构造方法。 public Constructor<T> getConstructor(Class<?>... parameterTypes):指定公共构造方法 public Constructor<?>[] getConstructors():所有公共构造方法
@Test public void test2(){ //获取类型名称 Properties pro = new Properties(); try { pro.load(TestNewInstance.class.getClassLoader().getResourceAsStream("type.properties")); } catch (IOException e) { e.printStackTrace(); } String className = pro.getProperty("tuxing"); try { Class<?> clazz = Class.forName(className); /*//现在创建的是Class代表的类型的对象,这里是com.atguigu.bean.Circle Object obj = clazz.newInstance(); System.out.println(obj); //Caused by: java.lang.NoSuchMethodException: com.atguigu.bean.Circle.<init>() //因为Circle这个类没有无参构造,所以没有无参的实例初始化方法<init>() */ //获取构造器对象 //Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) //Class<?>... parameterTypes:构造器的形参列表的类型们的Class对象 //例如:public Circle(double radius) 构造器的形参列表的类型double的Class double.class Constructor<?> constructor = clazz.getDeclaredConstructor(double.class);//这里直接写,以后这个类型也可以配置 //这里创建的是Circle的对象 Object obj = constructor.newInstance(1.2);//因为这里用的是Circle的有参构造,所以要传实参 System.out.println(obj); } catch (Exception e) { e.printStackTrace(); } }
获取方法
获取泛型父类
* 获取某个类的父类: * (1)Class getSuperclass():不带泛型信息的 * (2)Type getGenericSuperclass():带泛型信息的 * * JDK1.5之前没有Type等类型,因为没有泛型。 * 但是JDK1.5之后引入了泛型,那么我们的类型就有区别,Class的对象是代表类型对象,但是不包括他们的泛型信息, * * Father<String,Integer>:ParameterizedType,参数化的类型 * * * 获取某个类型的父接口们: * (1)Class<?>[] getInterfaces() * (2)Type[] getGenericInterfaces() public static void main(String[] args) { Class clazz = Son.class; Class sc = clazz.getSuperclass(); System.out.println(sc); //class com.atguigu.reflect.Father Type type = clazz.getGenericSuperclass(); ParameterizedType pt = (ParameterizedType) type; System.out.println(pt); //com.atguigu.reflect.Father<java.lang.String, java.lang.Integer> //获取实际类型参数 Type[] ata = pt.getActualTypeArguments(); for (Type type2 : ata) { System.out.println(type2); //class java.lang.String class java.lang.Integer } }
获取注解信息
* 获取某个类型上面的注解: * (1)Annotation[] getAnnotations() * (2)<A extends Annotation> A getAnnotation(Class<A> annotationClass) public class TestAnnotation { public static void main(String[] args) { Class c = Myclass.class; Annotation annotation = c.getAnnotation(MyAnnotation.class); //形参为Class annotationClass System.out.println(annotation); //@com.atguigu.reflect.MyAnnotation(value=atguigu) MyAnnotation my = (MyAnnotation) annotation; System.out.println(my.value()); //atguigu } } @MyAnnotation(value = "atguigu") ////当配置参数只有一个,并且名称是value,可以省略value= class Myclass{ //这个配置参数value赋值时,当属性 } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) //运行时阶段;//必须滞留到运行时,才可以被反射读取 @interface MyAnnotation{ //自定义注解 String value() default "尚硅谷"; //自定义 }
3. 使用反射生成并操作对象
使用反射创建对象
@Test public void test1() throws InstantiationException, IllegalAccessException{ //1. 从配置文件里边读取类名; Properties pro = new Properties(); try { pro.load(TestNewInstance.class.getClassLoader().getResourceAsStream("type.properties")); } catch (IOException e) { e.printStackTrace(); } String className = pro.getProperty("className"); //从配置文件中读取整个key即className System.out.println(className); //com.atguigu.bean.Student try { //2. 先获取该类型的Class对象 --->> 对象名.forName(类名),类名从配置文件里边获取; Class<?> cla = Class.forName(className); //3.创建该类的对象 --> Class对象.newInstance(); Object obj = cla.newInstance(); //得到Student类的对象 //因为这里用的是Student的无参构造,所以不需要传参数 System.out.println(obj); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
获取或设置某个对象的属性值
@Test public void test1() throws Exception{ Class c = Class.forName("com.atguigu.bean.Student");//先获取Class对象 Object obj = c.newInstance(); //创建实例(这里是类Student的)对象; Field nameField = c.getDeclaredField("name"); //根据属性名,获取属性对象Field对象 nameField.setAccessible(true);//调用setAccessible方法,使得私有的等属性也可以被外部类访问 nameField.set(obj, "smile"); //调用Field对象的,void set(Object obj, Object value) 要为哪个obj对象赋值为value; System.out.println(obj); //Student [sid=0, name=smile, score=0.0] //获取属性的值 System.out.println(nameField.get(obj)); //smile }
调用方法
* 在运行时,动态的调用任意对象的任意方法,只要知道方法名和形参列表
* 步骤:
1、获取这个类型的Class对象
2、得到实例对象
3、获取要调用的方法对象
4、执行方法
@Test public void test1() throws Exception{ Class<?> clazz = Class.forName("com.atguigu.bean.Circle"); //1. 获取这个类型的Class对象。 Object obj = clazz.newInstance(); //2. 实例对象; Method getAreas = clazz.getDeclaredMethod("getArea"); //3. 获取要调用的方法对象; 两个参数-》(方法名 ,可变参数即该方法的形参列表的类型们,如果没有形参,就不传了) System.out.println(getAreas); //public double com.atguigu.bean.Circle.getArea() //4. 执行方法.invoke( ); //参数一:哪个实例对象的方法 //参数二:可变参数,该方法需要的实参列表,如果没有形参,就不传了 //返回值就是,该方法执行后的返回值,如果被执行的方法没有返回值,就返回null Object area = getAreas.invoke(obj); System.out.println(area); //0.0 //5.获取要调用的方法的对象; 参数(方法名,可变参数) Method setRadius = clazz.getDeclaredMethod("setRadius", double.class); //6.执行方法 参数(哪个实例对象的方法, 可变参数) Object value = setRadius.invoke(obj, 2.1); System.out.println(value); //null,该方法没有返回值 System.out.println(getAreas.invoke(obj)); //13.854423602330987 }
操作数组
* 以前学过一个数组的工具类:java.util.Arrays
*
* 在java.lang.reflect包下有一个Array:Array 类提供了动态创建和访问 Java 数组的方法。
*
* (1)static Object newInstance(Class<?> componentType, int length)
创建数组对象
* (2)static void set(Object array, int index, Object value)
为数组的元素赋值
(3)static Object get(Object array, int index)
获取数组的元素的值
public static void main(String[] args) { Object obj = Array.newInstance(int.class, 5); //通过反射来动态的创建一个数组 //数组的元素的类型的Class对象;可变参数,每一个值代表一个维度的长度 Array.set(obj, 0, 10); //为哪个数组对象,哪个下标,赋值 System.out.println(Array.get(obj, 0));//10 }
代理模式
* 代理模式:
* 1、静态代理
* 2、动态代理
* 不管哪种代理模式,都有三个角色:
* 1、主题(接口)
* 2、被代理者
* 3、代理者
*
* 静态代理:不适用于替不同的接口的实现类,代理同样的代理工作。
* 静态代理,一般是一个被代理者对应一个代理类。
* 使用反射来实现动态代理
*
* 三个角色:
* (1)主题:接口
* (2)被代理者
* (3)代理者
* 此时的代理者我们要分开做:
* A:代理工作处理器:这个代理类要替被代理类完成什么工作
* 代理工作处理器必须实现java.lang.reflect.InvocationHandler接口
* 实现了一个方法public Object invoke(Object proxy, Method method, Object[] args)
* 这个方法不是程序员手动调用的,而是由代理者的对象自动调用
* B:动态的创建代理者的对象
* 要动态的创建代理者的对象需要java.lang.reflect.Proxy,可以通过
* Proxy.newProxyInstance(loader, interfaces, h)来动态的创建代理者的对象
*
*
* 什么时候需要用动态代理?
* 需要替很多不同的接口的实现类,完成的代理工作是一样的时。
* 这个在Spring的框架中用到。
静态代理
package com.atguigu.proxy; public class TestStaticProxy { public static void main(String[] args) { MyClass m = new MyClass(); StaticProxy proxy = new StaticProxy(m); proxy.a(); } } interface A{ void a(); } class MyClass implements A{ // MyClass被代理者 @Override public void a() { System.out.println("我在MyClaa实现类里边重写的a方法"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } class StaticProxy implements A{ //StaticProxy代理者; private A target; //接口A的类型; public StaticProxy(A target) { super(); this.target = target; } @Override public void a() { long start = System.currentTimeMillis(); target.a(); //被代理者执行它的方法; 接口.方法; long end = System.currentTimeMillis(); System.out.println("运行时间:" + (end-start)); } }
动态代理
package com.atguigu.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class TestDynamicProxy { public static void main(String[] args) { DynamiProxy d = new DynamiProxy(); //被代理者 //代理类的类加载器对象; ClassLoader loader = d.getClass().getClassLoader(); Class<?>[] interfaces = d.getClass().getInterfaces(); //获取代理者对象; ProxyWorked pw = new ProxyWorked(d);//代理工作处理器 //创建动态代理对象; 被代理类的类加载器对象;被代理类实现的接口们;代理类代理工作处理器对象; C proxy = (C) Proxy.newProxyInstance(loader, interfaces, pw); proxy.c(); //新的接口D,它的实现类OtherD,现在要替OtherD的对象代理 OtherD o = new OtherD(); //新的被代理者 ClassLoader loader2 = o.getClass().getClassLoader(); Class<?>[] interfaces2 = o.getClass().getInterfaces(); //获取代理者对象 ProxyWorked pw2 = new ProxyWorked(o); D proxy2 = (D) Proxy.newProxyInstance(loader2, interfaces2, pw2); proxy2.b(); proxy2.c(); proxy2.d(); proxy2.e(); } } interface C{ void c(); } class DynamiProxy implements C{ @Override public void c() { System.out.println("被代理类的实现方法c"); } } //代理工作处理器,要实现InvocationHandler接口,invoke方法由代理者对象自动调用; class ProxyWorked implements InvocationHandler{ //private C c; private Object target; //被代理者,不知道是什么类型的 public ProxyWorked(Object target) { super(); this.target = target; } @Override //动态代理对象;正在执行的方法;执行代理对象的方法传入的实参 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = System.currentTimeMillis(); Object returnvalue = method.invoke(target, args); long end = System.currentTimeMillis(); System.out.println("运行时间:" + (end - start)); return returnvalue; } } interface D{ void b(); void c(); void d(); void e(); } class OtherD implements D{ @Override public void b() { System.out.println("D实现类的b方法"); } @Override public void c() { System.out.println("D实现类的c方法"); } @Override public void d() { System.out.println("D实现类的d方法"); } @Override public void e() { System.out.println("D实现类的e方法"); } }