反射
定义
允许程序在执行时借助反射获取类的内部信息,并能直接操作任意对象的内部属性和方法。
我们可以这样理解:一个包,在包下创建一个类,在类中创建对象。
通过一个对象,找到创建该对象的类,通过所在类的声明找到对应的包名。
被视为动态语言的关键
因为反射,java才是准动态语言,不然java属于静态语言。
功能
前提:Person类中定义私有属性age,私有方法showNation。
Class clazz = Person.class;
Field age = clazz.getDeclaredField("age"); // 调用私有属性
clazz.getDeclaredMethod("showNatoin", String.class); // 调用私有方法
showNatoin.setAccessible(true);
showNatoin.invoke(p1,"中国");
运行时的7个功能
- 判断任意一个对象所属的类
- 构造任意一个类的对象
- 判断任意一个类所具有的成员变量和方法、
- 调用任意一个对象的成员变量和方法
- 获取泛型信息
- 处理注解
- 生成动态代理
初学反射2个疑问
1. 通过直接new的方式或反射的方式都可以调用公共的结构,开发时用哪个?
编译时,我们还不确定要创建哪些类的对象,此处理解为一个模板,让之后众多对象去套用。
先让后端服务器代码运行启动,此时前端发送给后端数据,后端满足前端发送的需求再去创建对象。
2. 反射机制和面向对象的封装性是否矛盾,如何看待两个技术?
当然不矛盾,这里理解为,封装性是有可能将私有的方法在公共的方法中调用,建议我们去调用公共的方法。比如单例模式,构造器是私有的,我们直接通过封装创建一个对象就可,没必要在调用私有的构造方法。强调建议怎么调用。
反射是可以强制的去调用私有的属性法法,再去做封装性已经封装好的事情。强调能不能调用
java.lang.Class类的理解
类的加载过程:
程序经过javac.exe命令后编译生成一个或多个字节码文件(.class)
接着使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存,这个第二个过程就成为类的加载。*加载在内存中的类,就称为运行时类(作为Class的一个实例),对应着一个运行时类。
万事万物皆对象
以往通过类造对象,现在类也作为大Class的对象
任何一个类都是对象
对于类中的静态结构,直接通过类去调用,而不用创建类的对象去调用。类本身也是一个对象,还是通过对象去调用的。
Class的对象有哪些?
类,接口,数组,枚举,注解,基本数据类型int…,void
@Test
public void test4(){
Class<Object> c1 = Object.class;
Class<Comparable> c2 = Comparable.class;
Class<String[]> c3 = String[].class;
Class<int[][]> c4 = int[][].class;
Class<ElementType> c5 = ElementType.class;
Class<Override> c6 = Override.class;
Class<Integer> c7 = int.class;
Class<Void> c8 = void.class;
int[] a = new int[10];
int[] b = new int[100];
Class<? extends int[]> c10 = a.getClass();
Class<? extends int[]> c11 = b.getClass();
System.out.println(c10 == c11);
System.out.println(c1+""+c2+""+c3+""+c4+""+c5+""+c6+""+c7+""+c8);
}
获取Class的实例
- 类名.class
Class clazz1 = Person.class;
- 类创建的对象名.getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();// 获取这个对象是哪个类创建的
- 调用Class的静态方法Class.forName()
Class clazz3 = Class.forName("com.reflect.Person");
- 获取类的加载器对象去加载类getClassLoader().loadClass()
Class clazz4 = Person.class.getClassLoader().loadClass("com.reflect.Person"); // 合并写法
类的加载与类的加载器
此处知识仍需深入理解,仍需深入理解
// TO DO
读取配置文件(两种方法)
配置文件jdbc.properties如下:
username=彤
password=123456
- 通过字节码输入流读取配置文件.properties,调用load()方法读取,配置文件默认识别为当前module下,代码如下:
@Test
/**
* 法1:读取配置文件,配置文件默认识别为当前module下
*/
public void test5() throws Exception {
Properties properties = new Properties();
FileInputStream fis = new FileInputStream("jdbc.properties");// 此时的文件在当前的模块下module
properties.load(fis);
String username = properties.getProperty("username");
String password = properties.getProperty("password");
System.out.println("用户:"+username+"\t密码:"+password);
}
- 通过获取类的加载器获取源流,调用load()方法读取,配置文件默认识别为当前module的src目录下,而不是当前module下,代码如下:
/**
* 读取配置文件的方式2:ClassLoader(),配置文件默认识别为当前module的src目录下,而不是当前module下
* @throws IOException
*/
@Test
public void test6() throws IOException {
Properties properties = new Properties();
ClassLoader classLoader = ClassLoader.class.getClassLoader(); // 获取类的加载器
InputStream inputStream = classLoader.getResourceAsStream("src\\jdbc.properties");
properties.load(inputStream);
String username = properties.getProperty("username");
String password = properties.getProperty("password");
System.out.println(username+"\t"+password);
}
创建运行时类的对象
调用newInstance()方法正常创建运行时类的对象,须满足
- 运行时类必须提供空参的构造器
- 空参的构造器的访问权限得够。通常设为public (通过反射创建运行时类的对象,javaBean通常要去提供空参的构造器)
/**
* 创建运行时类的对象
* @throws IllegalAccessException
* @throws InstantiationException
*/
@Test
public void test1() throws IllegalAccessException, InstantiationException {
Class<Person> clazz = Person.class;
// Person类中必须声明公共的空参构造器
Person obj = clazz.newInstance(); // 调用newInstance()方法创建对应的运行时类的对象,本质上只有构造器才能创建对象
System.out.println(obj);
}
反射的动态性
实现:封装一个指定全类名就能创建该类的对象的方法,这里使用随机值switch-case语句来指定可能是哪一个全类名(目的:体现反射的动态性)
@Test
public void test2() throws Exception {
int num = new Random().nextInt(3); // 这里随机生成3个数来生成随机的对象
String classPath = "";
switch (num){
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "com.reflect.Person";
break;
}
System.out.println(num);
Object obj = getInstance(classPath); // 通过指定类classPath路径名创建类的对象
System.out.println(obj);
}
/**
* 功能:创建一个指定类的对象
* @param classPath 指定类的全类名
* @return
* @throws Exception
*/
public Object getInstance(String classPath) throws Exception {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
获取运行时类的完整结构
创建一个package,包中创建父类Creature,Person子类,,myInterface接口,MyAnnotatio注解。
Person子类中:声明私有的name、默认类型age,公共的类型id,公共的空参构造器,私有的带参构造器(加注解),默认权限的全参构造器,私有的show()方法(加注解),公共的返回String类型的display()方法,重写MyInterface中的info()方法,重写实现比较器接口的compareTo()方法.
myInterface接口中:定义info()抽象方法
MyAnnotaton注解中:String value() default “hello”;
创建另一个package,包中创建测试类FieldTest,测试如下方法:
- getFields():获取当前运行时类及其父类中声明为public访问权限的属性,包含父类中的属性
- getDeclaredFields():获取当前运行时类中声明的所有属性(与权限无关,但不包含父类中的属性
- getModifiers():权限修饰符
- getType():数据类型
- getName():变量名
- getMethons():获取当前运行时类及其所有父类中声明为public权限的方法
- getDeclaredMethods():获取当前运行时类中的所有权限的方法(不包含父类中声明的),Method[] declaredMethods = clazz.getDeclaredMethods(); 遍历declaredMethon,在循环中获取方法声明的注解Annotation[] annotations = m.getAnnotations();,遍历annotations
- getModifiers():获取权限修饰符
- getReturnType().getName():获取返回值类型
- getParameterTypes()得到数组,遍历数组:获取方法名
- getExceptionTypes():获取异常类型
- getConstructors():获取房钱运行时类中声明为public的构造器
- getDeclaredConstructors():获取当前运行时类中声明的所有属性构造器
- getSuperclass():获取运行时类的父类
- getGenericSuperclass():获取的父类带泛型
- getGenericSuperclass强转为ParameterizedType接口,再调用getActualTypeArguments()得到数组,遍历数组,代码如下:
@Test
public void test3(){
Class<Person> clazz = Person.class;
// 获取带泛型的父类
Type superclass = clazz.getGenericSuperclass();
System.out.println(superclass);
System.out.println();
// 获取带泛型的父类 的泛型
// Type genericSuperclass = clazz.getGenericSuperclass();
// ParameterizedType paramType = (ParameterizedType) genericSuperclass;
ParameterizedType paramType = (ParameterizedType) clazz.getGenericSuperclass();
Type[] actualTypeArguments = paramType.getActualTypeArguments(); // 获取泛型类型
for(Type t : actualTypeArguments){
System.out.println(t.getTypeName());
}
}
- getInterfaces():获取运行时类的接口,应用场景:动态代理,代码如下:
@Test
public void test4(){
Class<Person> clazz = Person.class;
// 获取运行时类的接口
Class<?>[] interfaces = clazz.getInterfaces();
for (Class c : interfaces){
System.out.println(c);
}
System.out.println();
// 获取运行时类 的父类实现的接口
Class<?>[] interfaces1 = clazz.getSuperclass().getInterfaces();
for (Class cc : interfaces1){
System.out.println(cc);
}
}
- getSuperclass().getInterfaces():获取运行时类的父类实现的接口
- getPackage():获取运行时类Person所在的包
- getAnnotations():获取运行时类声明的注解
调用运行时类中的指定结构(重点:方法)
操作运行时类中指定的属性
通常调用访问权限更大的getDeclaredField()方法,但不能忘记调用对象名.setAccessible(true)方法,否则会报非法访问异常IllegalAccessException。
/**
* 如何操作运行时类中指定的属性
* @throws Exception
*/
@Test
public void testField() throws Exception {
Class<Person> clazz = Person.class;
// 1.创建运行时类的对象
Person p = clazz.newInstance();
// 2.获取指定的属性
// Field id = clazz.getField("age"); // 通常不采用
Field name = clazz.getDeclaredField("name");
// 3.保证当前属性可访问不会报IllegalAccessException异常
name.setAccessible(true);
// 4.设置当前属性的值,(哪个对象的属性,属性值)
name.set(p,"lwt");
// 5.获取哪个对象当前的属性值
String pname = (String) name.get(p);
System.out.println(pname);
}
操作运行时类中指定的方法(*)
通常调用访问权限更大的getDeclaredMethon()方法,勿忘设置权限setAccessible(true)。
调用invoke()时,参数列表中写运行时类.class,返回值为null
/**
* 调用静态方法
* @throws Exception
*/
@Test
public void testStaticMethon() throws Exception {
Class<Person> clazz = Person.class;
Person p = clazz.newInstance();
Method regist = clazz.getDeclaredMethod("regist");
regist.setAccessible(true);
// 如果调用的运行时类中的方法没有返回值,则此invoke()返回null
Object returnVal = regist.invoke(Person.class);
System.out.println(returnVal); // null
}