反射(重点)
能够分析类能力的程序成为反射,反射是灵活的,通过修改配置文件,源代码自动创建响应类型的对象
背景
进程和线程的一个区别就是有没有主方法,有主方法才可以独立启动。如servlet就没有主方法,需要依赖tomcat(servlet容器)的主方法启动。servlet的doget等方法不是静态方法,也就是说我们要实例化对象才可以调用方法,tomcat无法通过new的方式来实例化对象进而调用方法(通过扫描文件来获取文件的路径,但是即使扫描获取到它所在的路径也无法使用new)这种情况下,反射产生了。
类的生命周期
java源代码通过jacac编译成为java字节码文件,再通过java运行成为类对象,类\对象通过new实例化对象,最后通过gc(java垃圾回收机制)卸载对象(释放对象的堆空间,类还在)
获取指定类的类对象(反射的第一步)
如图三个阶段,分别有三个方法,每个阶段调用各自的方法都可以获取class类对象
方法一:Class.forName(“类全名”)————最常用,还没进入内存就可以获取想要类的类对象 类全名为包名+类名
方法二:类名.class——————通过类的成员变量
方法三:对象.getClass()——————通过实例化对象的方法
获取Class对象的方式的场景
Class.forName(“类全名”):多用于配置文件,将类名定义在配置文件中,读取配置文件,加载类。如JDBC读取Mysql的文件
类名.class:多用于参数的传递
对象名.getClass():多用于对象获取类对象
package com.qcby.demo.reflect;
public class TestReflectPerson {
public static void main(String[] args) throws ClassNotFoundException {
//1.Class.forname()
Class<?> class1 = Class.forName("com.qcby.demo.reflect.TestReflectPerson");
System.out.println(class1);
//2.类名.class
Class class2 = TestReflectPerson.class;
System.out.println(class2);
//3.对象名.getClass()
Class class3 = new TestReflectPerson().getClass();
System.out.println(class3);
System.out.println(class1==class2);
System.out.println(class2==class3);
}
}
总结:同一个类加载器加载的文件在一次程序运行过程中,只会被加载一次,无论使用那种方式获得的类对象都是同一个
class对象(类对象)常见功能
Person类
package com.qcby.demo.reflect;
public class Person {
private String name;
private int age;
public String sex;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {//无参构造很重要,反射是通过无参构造方法来进行是实例化的
}
private Person(String name) {
this.name = name;
}
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;
}
private void test(String... strings){//可变参数
}
public static void main(String[] args) {
Person p =new Person();
p.test("sd","sd");//可以输入任意个参数
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
成员变量对象
getDeclaredFields获取成员变量对象
//获取所有访问权限的成员变量对象
Field[] fields = pcla.getDeclaredFields();
for (Field field:fields){
System.out.println(field);
}
//获取公共访问权限的成员变量对象
Field[] fields1 = pcla.getFields();
for (Field field:fields1){
System.out.println(field);
}
设置值:set(Object obj,Object value)
获取值:get(Object obj)
对于私有权限修饰符的成员变量获取设置值,需要使用setAccessible(true)暴力反射 --忽略访问权限修饰符的安全检查
例子
Person类在上面
package com.qcby.demo.reflect;
import org.omg.CORBA.ARG_OUT;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class TestField {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Class pcla = Person.class;
Field sex = pcla.getDeclaredField("sex");
System.out.println(sex);
Field name = pcla.getDeclaredField("name");
System.out.println(name);
Person person = new Person();
//获取公共成员变量对象的值
Object value =sex.get(person);
System.out.println(value);
//设置公共成员变量对象的值
sex.set(person,"张三");
Object value1 =sex.get(person);
System.out.println(value1);
//暴力反射 --忽略访问权限修饰符的安全检查
name.setAccessible(true);
//获取任意访问权限的成员变量的值
Object value2 =name.get(person);
System.out.println(value2);
}
}
成员方法对象
getDeclaredMethods获取成员方法对象
执行方法:invoke(Object object,Object… args) ,Object… 表示可变参数
获取方法名:getName()
对于私有权限修饰符的成员方法获取设置值,需要使用setAccessible(true)暴力反射 --忽略访问权限修饰符的安全检查
例子
Person类在上面
package com.qcby.demo.reflect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TestMethod {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class pcla =Person.class;
//获取方法对象
Method getAge = pcla.getDeclaredMethod("getAge");
Method setAge = pcla.getDeclaredMethod("setAge", int.class);
Person p =new Person();
System.out.println(setAge.getName());
setAge.invoke(p,400);
//方法调用
Object res = getAge.invoke(p);
System.out.println(res);
}
}
构造方法对象
getDeclaredConstructors获取构造方法对象
newInstance()Class pcla =Person.class;
//获取公共的构造方法对象
Constructor[] con = pcla.getConstructors();
//获取所有的构造方法对象
Constructor[] cons = pcla.getDeclaredConstructors();
创建对象:newInstance()
newInstance()如果是想利用无参构造方法去创建对象,可以直接使用类对象来创建,跳过构造方法对象
对于私有权限修饰符的构造方法获取设置值,需要使用setAccessible(true)暴力反射 --忽略访问权限修饰符的安全检查
例子
Person类在上面
package com.qcby.demo.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class TestContructor {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class pcla =Person.class;
//获取公共的构造方法对象
Constructor[] con = pcla.getConstructors();
for (Constructor constructor:con){
System.out.println(constructor);
}
System.out.println("....");
//获取所有的构造方法对象
Constructor[] cons = pcla.getDeclaredConstructors();
for (Constructor constructor:cons){
System.out.println(constructor);
}
//获取无参构造方法对象
Constructor con1 = pcla.getDeclaredConstructor();
//使用公共无参构造方法创建对象
Object obj = con1.newInstance();
System.out.println(obj);
//获取有参构造方法对象
Constructor cons2 =pcla.getDeclaredConstructor(String.class,int.class);
Object obj1 = cons2.newInstance("张三", 29);
System.out.println(obj1);
}
}
可变参数
private void test(String... strings){//可变参数
}
public static void main(String[] args) {
Person p =new Person();
p.test("sd","sd");//可以输入任意个参数
}
…可以表示输入任意个参数
例子
package com.qcby.demo.reflect;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {//无参构造很重要,反射是通过无参构造方法来进行是实例化的
}
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 void test(String... strings){//可变参数
}
public static void main(String[] args) {
Person p =new Person();
p.test("sd","sd");//可以输入任意个参数
}
}
package com.qcby.demo.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class TestReflectPerson {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
//1.获取类对象
Class tclass = Class.forName("com.qcby.demo.reflect.Person");
//2.通过类对象获取成员变量
System.out.println("获取Person的所有属性对象");
Field[] fields =tclass.getDeclaredFields();
for (Field field:fields){
System.out.println(field);
}
System.out.println("获取Person的属性对象");
Field age = tclass.getDeclaredField("age");
System.out.println(age);
//3.通过类对象获取构造方法
System.out.println("获取Person的所有构造方法对象");
Constructor[] declaredConstructors = tclass.getDeclaredConstructors();
for (Constructor constructor:declaredConstructors){
System.out.println(constructor);
}
System.out.println("获取Person的无参构造方法对象");
Constructor declaredConstructorconstructor = tclass.getDeclaredConstructor();
System.out.println(declaredConstructorconstructor);
System.out.println("获取Person的有参构造方法对象");
Constructor declaredConstructorconstructor1 = tclass.getDeclaredConstructor(String.class,int.class);
//注意int不是类,当是java反射做了优化(9个预先定义好的Class对象代表8个基本类型和void),int.class不等于Integer.class
System.out.println(declaredConstructorconstructor1);
//4.通过类对象获取成员方法
System.out.println("获取Person的所有成员方法对象");
Method[] methods = tclass.getDeclaredMethods();
for (Method method:methods){
System.out.println(method);
}
System.out.println("获取Person的setAge方法对象");
Method method = tclass.getDeclaredMethod("setAge",int.class);
System.out.println(method);
}
}
getName()与getSimpleName()
getName():获取的类的全名称
getSimpleName():获取类的名称
属性文件(创建对象,读取内容)
内容以等号连接形如key=value形式,文件后缀为.properties
- 第一步:配置文件
- 第二步:输入流加载到 Map集合 properties
- 第三步:通过Map集合 的getProperty(左边键值) 获取value(右边)
- 第四步:通过完整类名 value ,forName();//获取类型
- 最后一步: 实例化 Object obj = a.newInstance( );
reflect.className = com.qcby.demo.reflect.Student
reflect.MethodName = getAge
加载配置文件,获取配置文件内的数据
package com.qcby.demo.reflect;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class TestConfig {
public static void main(String[] args) throws IOException {
//创建properties对象
Properties properties =new Properties();
//加载配置文件(通过流的形式,加载成输入流)
InputStream resourceAsStream = Person.class.getClassLoader().getResourceAsStream("com/qcby/demo/reflect/refconfig");
properties.load(resourceAsStream);
//获取配置文件中的数据
String className = properties.getProperty("reflect.className");
String methodName = properties.getProperty("reflect.MethodName");
System.out.println(className);
System.out.println(methodName);
}
}
综合例子
Student类,路径如上图所示,写在配置文件中
package com.qcby.demo.reflect;
public class Student {
public void eat(){
System.out.println("eat");
}
public void sleep(){
System.out.println("sleep");
}
}
package com.qcby.demo.reflect;
import com.company.Main;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Properties;
public class CommonReflect {
//获取该类的名称
public static void getClassName(Object object){
String simpleName = object.getClass().getSimpleName();
System.out.println("类的名称为"+simpleName);
}
//获取该类的成员变量名称
public static void getField(Object object){
Field[] declaredFields = object.getClass().getDeclaredFields();
for (Field field:declaredFields){
System.out.println("成员变量名称为"+field);
}
}
//获取该类的成员方法
public static void getMethod(Object object){
Method[] declaredMethods = object.getClass().getDeclaredMethods();
for (Method method:declaredMethods){
System.out.println("成员方法为"+method);
}
}
//调用该类的成员方法,以get为例
public static void getAction(Object object) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
Field[] declaredFields = object.getClass().getDeclaredFields();
for (Field field:declaredFields){
String fieldName = field.getName();
//toUpperCase()全部转换成大写
//获取首字母大写
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String methodName = "get" +firstLetter+fieldName.substring(1);
Method method = object.getClass().getMethod(methodName);
System.out.println("get的输出结果"+method.invoke(object));
}
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {
Properties properties =new Properties();
//加载配置文件(通过流的形式,加载成输入流)
InputStream resourceAsStream = Person.class.getClassLoader().getResourceAsStream("com/qcby/demo/reflect/refconfig");
properties.load(resourceAsStream);
String className = properties.getProperty("reflect.className");
System.out.println(className);
// Person person = Person.class.getConstructor(String.class, int.class).newInstance("张三", 20);
if (className!=null && !"".equals(className)) {
Object person = Class.forName(className).newInstance();
getClassName(person);
getField(person);
getMethod(person);
getAction(person);
}
}
}
反射的优缺点
1、优点:
静态编译:
在编译时确定类型,绑定对象,即通过。
动态编译:
运行时确定类型,绑定对象。动态编译最大限度的发挥了java的灵活性,体现了多态的应用,有利于降低类之间的耦合性。
一句话,反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。
2、缺点:
是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。