反射
1. 反射
1)问题1
对象有编译类型和运行类型
Object obj = new java.util.Date();
编译类型:Object
运行类型(其实就是obj对象真实的类型):java.util.Date
需求:根据对象obj调用Date类中的一个方法,toLocaleString,如何来做?
obj.toLocaleString()代码在编译阶段去编译类型Object中检查是否有该方法,若没有,编译失败.
解决方案1:强制转换obj为Date类型,前提:必须知道对象的真实类型是什么?
Date d = (Date)obj;
d.toLocaleString();//YES
如果我不知道obj的真实类型,那又如何去调用toLolcaeString方法. 如何去做?
解决方案2: 使用反射
2)问题2
对象使用类描述的,但是Java中一些皆对象,其实所有的类,底层都有一个字节码对象,用什么来描述这一个个类底层的字节码对象
解决方案 : 使用反射
3.反射
反射:(reflection):在运行时期,动态地去获取类中的信息(类的信息,方法信息,构造器信息,字段等信息进行操作);
一个类中包含的信息有: 构造器,字段,方法
如何使用反射描述这些相关的信息
Class : 描述类
Method : 描述方法
Constructor :描述构造器
Field :描述字段
2. 获取类的Class实例三种方式
在反射操作某一个类之前,应该先获取这个类的字节码实例
获取字节码实例有三种方式
类名.class
类的对象.getClass()
Class.forName(“类的全限定名”)
注意 :同一个类在JVM的字节码实例只有一份
全限定名 = 包名 + 类名
1 public class User { 2 3 @Test 4 5 public void testName() throws Exception { 6 7 //1.使用类名.class 获取类的字节码实例 8 9 Class<User> clz1 = User.class; 10 11 System.out.println(clz1.toString()); 12 13 14 15 //2.对象.getClass() 16 17 User user = new User(); 18 19 Class<?> clz2 = user.getClass(); 20 21 System.out.println(clz2); 22 23 24 25 //3.Class.forName("全限定名"):用的最多 26 27 Class<?> clz3 = Class.forName("cn.sxt.reflect.User"); 28 29 System.out.println(clz3); 30 31 } 32 33 }
3. 获取九大内置类的字节码实例
对于对象来说,可以直接使用对象.getClass()或者Class.forName(className); 类名.class都可以获取Class实例.
但是我们的基本数据类型,就没有类的权限定名,也没有getClass方法.
问题: 那么如何使用Class类来表示基本数据类型的Class实例?
八大基本数据类型和 void关键字都是有 字节码实例的
byte,short,int,long,char,float,double,boolean ,void关键字
答 : 数据类型/void.class 即可
每个基本数据类型都是包装类型 如 :int ----Integer包装类型
注意: 基本数据类型和包装数据类型底层的字节码实例是不相同
1 //获取8大基本数据类型和void的字节码实例 2 3 //byte,short,int,long,char,float,double,boolean ,void关键字 4 5 public class BaiscDataTypeClassTest { 6 7 @Test 8 9 public void testName() throws Exception { 10 11 //1.获取byte的字节码实例 12 13 Class<?> byteClz = byte.class; 14 15 System.out.println(byteClz); 16 17 //.... 18 19 //获取void关键字的字节码实例 20 21 Class<?> voidClz =void.class; 22 23 System.out.println(voidClz); 24 25 26 27 28 29 //所有的基本数据类型都有包装类型 30 31 //int---Integer 32 33 //int 和Integer 的字节码是绝对不相等的 34 35 Class<?> intClz1 = int.class; 36 37 Class<?> intClz2 = Integer.class; 38 39 System.out.println(intClz1); 40 41 System.out.println(intClz2); 42 43 System.out.println(intClz1 == intClz2); 44 45 } 46 47 }
4. 获取数组类型的字节码实例
表示数组的Class实例:
String[] sArr1 = {"A","C"};
Class clz = String[].class;//此时clz表示就是一个String类型的一位数组类型
所有具有相同元素类型和维数的数组才共享同一份字节码(Class对象);
注意:和数组中的元素没有一点关系.
1 package cn.sxt.reflect._01.getclass; 2 3 4 5 import static org.junit.Assert.*; 6 7 8 9 import org.junit.Test; 10 11 12 13 //获取数组类型的字节码实例 14 15 public class ArrayClassTest { 16 17 @Test 18 19 public void testName() throws Exception { 20 21 //定义数组 22 23 int[] arr = {1,2,3}; 24 25 Class<int[]> clz1 = (Class<int[]>) arr.getClass(); 26 27 System.out.println(clz1); 28 29 Class<int[]> clz2= int[].class; 30 31 System.out.println(clz2); 32 33 34 35 36 37 int[] arr2 = {2,3,4}; 38 39 Class<int[]> clz3 = (Class<int[]>) arr2.getClass(); 40 41 System.out.println(clz3); 42 43 44 45 46 47 System.out.println(clz1 == clz2);//true 48 49 System.out.println(clz1 == clz3);//true 50 51 } 52 53 }
5. 构造函数-Constructor
1) 获取构造函数
类的构函数有 有参数构造函数,无参构造函数,公共构造函数,非公共构造函数,根据不同的构造函数 Class提供了几种获取不同构造函数的方法
Constructor<?>[] |
getConstructors() |
Constructor<?>[] |
getDeclaredConstructors() |
getConstructor(Class<?>... parameterTypes) parameterTypes : 如果构造函数有参数,传递的是参数的字节码实例 |
|
getDeclaredConstructor(Class<?>... parameterTypes) |
1 //通过字节码实例获取构造器 2 3 public class ConstructorTest { 4 5 @Test 6 7 public void testName() throws Exception { 8 9 10 11 //1.获取Student的字节码实例 12 13 Class<?> stuClz = Student.class; 14 15 16 17 //2.获取所有的公共构造函数 18 19 Constructor<?>[] cts1 = stuClz.getConstructors(); 20 21 for (Constructor<?> ct : cts1) { 22 23 System.out.println(ct); 24 25 } 26 27 System.out.println("----------------------"); 28 29 //3.获取所有的构造函数包括私有的 30 31 Constructor<?>[] cts2 = stuClz.getDeclaredConstructors(); 32 33 for (Constructor<?> ct : cts2) { 34 35 System.out.println(ct); 36 37 } 38 39 System.out.println("----------------------"); 40 41 42 43 //4.获取指定的构造函数(clz.getConstructor(...))只能获取公共的构造函数 44 45 Constructor<?> ct1 = stuClz.getConstructor(); 46 47 System.out.println(ct1); 48 49 50 51 Constructor<?> ct2 =stuClz.getConstructor(String.class); 52 53 System.out.println(ct2); 54 55 //4.获取指定的构造函数(clz.getDeclaredConstructor(...))获取的构造函数和权限没有关系 56 57 Constructor<?> ct3=stuClz.getDeclaredConstructor(String.class,int.class); 58 59 System.out.println(ct3); 60 61 } 62 63 }
2) 调用构造函数创建对象
调用构造器,创建对象
Constructor<T>类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器 常用方法: public T newInstance(Object... initargs):如调用带参数的构造器,只能使用该方式. 参数:initargs:表示调用构造器的实际参数 返回:返回创建的实例,T表示Class所表示类的类型 如果:一个类中的构造器可以直接访问,同时没有参数.,那么可以直接使用Class类中的newInstance方法创建对象. public Object newInstance():相当于new 类名(); 调用私有的构造器: |
[1] Constructor 创建对象的方法
newInstance(Object... initargs) |
[2] Class类中创建对象的方法
如果使用Class直接创建对象,必须保证类中有一个无参数公共构造函数
newInstance() |
[3]设置忽略访问权限
默认执行私有构造函数报如下错误
如果是私有构造方法,反射默认是无法直接执行的,找到父类中AccessibleObject的方法,设置为true,即可忽略访问权限
void |
setAccessible(boolean flag) |
1 //使用构造器创建对象 2 3 public class NewInstanceTest { 4 5 @Test 6 7 public void testName() throws Exception { 8 9 10 11 //1.获取Student的字节码实例 12 13 Class<?> clz = Class.forName("cn.sxt.reflect.Student"); 14 15 16 17 //1.1如果类有无参数公共构造函数,直接可以使用类的字节码实例就创建对象 18 19 Student stu0 = (Student) clz.newInstance(); 20 21 22 23 24 25 //2.获取一个参数的构造函数 26 27 Constructor<Student> ct1 = (Constructor<Student>) clz.getConstructor(String.class); 28 29 //2.1.创建对象 30 31 Student stu1 = ct1.newInstance("东方不败"); 32 33 34 35 //3.获取私有构造函数并创建对象 36 37 Constructor<Student> ct2 = (Constructor<Student>) clz.getDeclaredConstructor(String.class,int.class); 38 39 //3.1设置权限可以创建对象 40 41 ct2.setAccessible(true); 42 43 //3.2创建对象 44 45 Student stu2 = ct2.newInstance("西门吹雪",50); 46 47 } 48 49 }
6. 操作方法-Method
一个类创建对象以后,一般就要执行对象的方法等等,使用反射操作对象
首先要获取方法,再去执行
1) 获取方法和方法的执行
一个类中的方法有很多,无参,有参,静态,可变参数私有方法,等等,针对不同的方法处理,提供了不同的获取方案
[1]使用Class 获取对应的方法
Method[] |
getMethods() |
Method[] |
getDeclaredMethods() |
getDeclaredMethod(String name, Class<?>... parameterTypes)
Name : 指定的方法名称 parameterTypes : 方法参数的类型 |
[2] Method执行方法
方法获取以后就需要执行。Method对象中提供方法执行的功能
invoke(Object obj, Object... args) Obj :如果是对象方法,传指定的对象,如果是类方法,传 null Args: 方法的参数
如果方法有返回结果,可以接收 |
[3] 设置忽略访问权限
如果是私有方法,反射默认是无法直接执行的,找到父类中AccessibleObject的方法,设置为true,即可忽略访问权限
void |
setAccessible(boolean flag) |
2)方法操作的代码
[1]Student类
1 package cn.sxt.reflect._03method; 2 3 4 5 import java.util.Arrays; 6 7 8 9 public class Person { 10 11 12 13 public void hell1() { 14 15 System.out.println("我是无参数无返回值方法"); 16 17 } 18 19 20 21 public String hello2(String name) { 22 23 24 25 return "你好 :"+name; 26 27 } 28 29 30 31 private String hello3(String name,int age) { 32 33 return "我是 :"+name+",今年 :"+age; 34 35 } 36 37 38 39 40 41 42 43 public static void staticMethod(String name) { 44 45 System.out.println("我是静态方法 :"+name); 46 47 } 48 49 50 51 52 53 54 55 public static void method1(int[] intArr) { 56 57 System.out.println(Arrays.toString(intArr)); 58 59 } 60 61 62 63 public static void method2(String[] strArr) { 64 65 System.out.println(Arrays.toString(strArr)); 66 67 } 68 69 70 71 }
[2] 测试类
1 package cn.sxt.reflect._03method; 2 3 4 5 import static org.junit.Assert.*; 6 7 8 9 import java.lang.reflect.Method; 10 11 12 13 import org.junit.Test; 14 15 16 17 //获取Person类的方法 18 19 public class GetMethodTest { 20 21 22 23 @Test 24 25 public void testName() throws Exception { 26 27 // 1.获取Person字节码实例 28 29 Class<Person> clz = Person.class; 30 31 // 2.创建对象 32 33 Person p = clz.newInstance(); 34 35 36 37 // 3.获取方法(使用反射),获取所有公共方法,包含父类的公共方法 38 39 Method[] methods1 = clz.getMethods(); 40 41 for (Method method : methods1) { 42 43 System.out.println(method); 44 45 } 46 47 System.out.println("------------------------------"); 48 49 // 4.获取自己类中的所有方法(包括私有) 50 51 Method[] methods2 = clz.getDeclaredMethods(); 52 53 for (Method method : methods2) { 54 55 System.out.println(method); 56 57 } 58 59 System.out.println("------------------------------"); 60 61 // 4.获取单个指定名称的方法 62 63 Method method = clz.getMethod("hello2", String.class); 64 65 System.out.println(method); 66 67 68 69 // 4.1执行方法 70 71 Object res = method.invoke(p, "陆小凤"); 72 73 System.out.println(res); 74 75 76 77 // 5.获取私有的方法 78 79 Method hello3 = clz.getDeclaredMethod("hello3", String.class, int.class); 80 81 System.out.println(hello3); 82 83 84 85 // 5.1设置忽略访问权限 86 87 hello3.setAccessible(true); 88 89 90 91 Object res1 = hello3.invoke(p, "叶孤城", 30); 92 93 System.out.println(res1); 94 95 96 97 // 6.获取静态方法 98 99 Method staticMethod = clz.getMethod("staticMethod", String.class); 100 101 102 103 // 6.1执行静态方法 104 105 staticMethod.invoke(null, "花满楼"); 106 107 108 109 // 7.获取有整数数组参数的方法 110 111 Method method1 = clz.getMethod("method1", int[].class); 112 113 method1.invoke(null, new Object[] {new int[] { 1, 2, 3, 4 }}); 114 115 116 117 // 8.获取有整数数组参数的方法 118 119 /* 120 121 * 如果反射传递的参数是引用类型,底层有一个拆箱的功能,会将数组的元素拆成一个个参数传递过来 122 123 * 解决方案: 将数组外面在包装一层数组,如果拆箱一次,得到还是一个数组 124 125 */ 126 127 Method method2 = clz.getMethod("method2", String[].class); 128 129 method2.invoke(null,new Object[] {new String[] {"AA","BB","CC"}}); 130 131 } 132 133 }
[3]可变参数的方法执行
如果方法中有可变参数(数组),如果反射传递的可变参数是引用类型,底层有一个拆箱的功能,会将数组的元素拆成一个个参数传递过来
解决方案: 将数组外面在包装一层数组,如果拆箱一次,得到还是一个数组
1 package cn.sxt.reflect._03method; 2 3 4 5 import java.util.Arrays; 6 7 8 9 public class Person { 10 11 12 13 public static void method1(int... intArr) { 14 15 System.out.println(Arrays.toString(intArr)); 16 17 } 18 19 20 21 public static void method2(String...strArr) { 22 23 System.out.println(Arrays.toString(strArr)); 24 25 } 26 27 }
1 // 7.获取有整数数组参数的方法 2 3 Method method1 = clz.getMethod("method1", int[].class); 4 5 method1.invoke(null, new Object[] {new int[] { 1, 2, 3, 4 }}); 6 7 8 9 // 8.获取有整数数组参数的方法 10 11 /* 12 13 * 如果反射传递的参数是引用类型,底层有一个拆箱的功能,会将数组的元素拆成一个个参数传递过来 14 15 * 解决方案: 将数组外面在包装一层数组,如果拆箱一次,得到还是一个数组 16 17 */ 18 19 Method method2 = clz.getMethod("method2", String[].class); 20 21 method2.invoke(null,new Object[] {new String[] {"AA","BB","CC"}});
7. 操作字段(成员变量)-Field
类中的字段有各种数据类型和各种访问权限,针对这些情况,反射操作有对应的方法来获取和处理
1) Class中获取字段方法
Field[] |
获取当前Class所表示类中所有的public的字段,包括继承的字段. |
Field[] |
获取当前Class所表示类中所有的字段,不包括继承的字段. |
获取当前Class所表示类中 |
|
getDeclaredField(String name) :获取当前Class所表示类中该fieldName名字的字段,不包括继承的字段. |
2) Field操作设置值/获取值
给某个类中的字段设置值和获取值: 1,找到被操作字段所在类的字节码 2,获取到该被操作的字段对象 3,设置值/获取值 Field类常用方法: void setXX(Object obj, XX value) :为基本类型字段设置值,XX表示基本数据类型 void set(Object obj, Object value) :表示为引用类型字段设置值 参数: obj: 表示字段底层所属对象,若该字段是static的,该值应该设为null value: 表示将要设置的值 ------------------------------------------------------------------------------------- XX getXX(Object obj) :获取基本类型字段的值,XX表示基本数据类型 Object get(Object obj) :表示获取引用类型字段的值 参数: obj: 表示字段底层所属对象,若该字段是static的,该值应该设为null 返回:返回该字段的值. |
||
void |
设置引用类型的值,非基本数据类型 Obj: 要设置值得对象 Value : 要设置的值 |
1 package cn.sxt.reflect._04Field; 2 3 4 5 import static org.junit.Assert.*; 6 7 8 9 import java.lang.reflect.Field; 10 11 12 13 import org.junit.Test; 14 15 16 17 public class FieldTest { 18 19 20 21 @Test 22 23 public void testName() throws Exception { 24 25 //1.获取People字节码 26 27 Class<People> clz = People.class; 28 29 30 31 People p = clz.newInstance(); 32 33 34 35 //2.获取所有公共字段 36 37 Field[] fields1 = clz.getFields(); 38 39 for (Field field : fields1) { 40 41 System.out.println(field); 42 43 } 44 45 System.out.println("---------------------"); 46 47 //3.获取所有字段,和访问权限无关 48 49 Field[] fields2 = clz.getDeclaredFields(); 50 51 for (Field field : fields2) { 52 53 System.out.println(field); 54 55 } 56 57 System.out.println("---------------------"); 58 59 //3.获取指定的公共字段 60 61 Field emialField = clz.getField("emial"); 62 63 System.out.println(emialField); 64 65 66 67 //为字段设置值 68 69 emialField.set(p, "zhagnsan@qq.com"); 70 71 System.out.println(p); 72 73 //4.获取指定所有的字段,和访问权限无关 74 75 Field nameFiled = clz.getDeclaredField("name"); 76 77 System.out.println(nameFiled); 78 79 //设置忽略访问权限 80 81 nameFiled.setAccessible(true); 82 83 nameFiled.set(p, "张三"); 84 85 System.out.println(p); 86 87 88 89 90 91 //5 获取age字段 92 93 Field ageFile = clz.getDeclaredField("age"); 94 95 ageFile.setAccessible(true); 96 97 //设置忽略访问权限 98 99 ageFile.setInt(p, 18); 100 101 System.out.println(p); 102 103 104 105 } 106 107 }
8. Class的其他API方法
1 //Class字节码的其他api 2 3 @Test 4 5 public void testName() throws Exception { 6 7 8 9 //1.获取UserDaoImpl的字节码实例 10 11 Class<?> clz = Class.forName("cn.sxt.reflect._05otherapi.UserDaoImpl"); 12 13 14 15 //2.获取所有的接口 16 17 Class<?>[] interfaces = clz.getInterfaces(); 18 19 for (Class<?> intface : interfaces) { 20 21 System.out.println(intface); 22 23 } 24 25 26 27 //3.获取全限定名 28 29 System.out.println(clz.getName()); 30 31 32 33 //4.获取简单类名 34 35 System.out.println(clz.getSimpleName()); 36 37 //5.获取包 38 39 System.out.println(clz.getPackage().getName()); 40 41 }
9. JavaBean
问题: 什么是javaBean?
答: JavaBean就是一个个Java类,在java中符合JavaBean特点类才叫做JavaBean
1) JavaBean的三个特点
- JavaBean类的修饰符必须是public,也就是一个JavaBean必须是一个对应一个类文件
- JavaBean必须有无参数公共构造方法(以便于反射直接通过直接通过字节码实例创建对象)
- JavaBean中的成员变量/字段必须有get/set方法提供对应的属性
[1]JavaBean中的属性
Java类有成员变量,成员变量绝对不是属性,JavaBean中的属性是有get/set方法确定的
Get方法确定属性
public String getName() { return name; } //属性确定规则 : get方法去掉 get前缀 ,剩余部分 首字母小写 //属性名称 : name
Set方法确定属性
public void setName(String name) { this.name = name; } //属性确定规则 : set方法去掉 set前缀 ,剩余部分 首字母小写 //属性名称 : name
如果一个成员变量get/set方法都有确定的属性就只有一个
问题 :get/set方法确定确定的属性一般不就和成员变量一样啊,为什么要有属性了
答: 一般情况下,Eclipse工具有自动生成get/set方法的功能,确定的JavaBean的属性只是恰好和成员变量相同,但是成员变量不是属性
如下特殊性情况,属性和成员变量名称不同
//Filed private String firstName; //Filed private String lastName; //属性 : fullName public String getFullName() { return this.firstName + this.lastName; }
10. 小结
1.理解反射概念?反射能干啥?
反射: 在jvm运行阶段,动态的获取类的信息(字节码实例,构造器,方法,字段),动态进行对象的创建,方法执行,字段操作。
2.反射的常用类
(1) Class :所有类的字节码实例的描述
(2) Constructor :构造器
(3) Method :方法
(4) Field :字段
3.JDBC+反射 代码封装