Java学习总结:反射机制

目录

一、反射概念

二、获取Class类对象

三、获取构造方法

四、获取成员变量

五、获取成员方法

六、反射main方法

七、通过反射运行配置文件内容

八、通过反射越过泛型检查


在之前学习JDBC+Servlet+JSP时,连接数据库时写了一段加载配置文件的代码

其中就涉及到了类加载机制和反射机制

一、反射概念

反射(Reflection)是程序的自我分析能力,通过反射可以确定类有哪些方法、有哪些构造方法以及有哪些成员变量;对于任意一个对象,都能够调用它的任意一个方法和属性;Java提供了反射机制,通过反射机制能够动态读取一个类的信息;能够在运行时动态加载类,而不是在编译器。反射可以应用于框架开发,它能够从配置文件中读取配置信息动态加载类、并创建对象。

我们都知道Java面向对象,所以在Java中万物皆为对象(静态的成员、普通数据类型除外)。也就是说类也是对象!类是java.lang.Class类的实例对象。

反射就是把java类中的各种成分映射成一个个的Java对象

比如一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把这些部分映射成一个个对象。(其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述)

加载过程:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。

其中获取Class对象涉及到java.lang.object

获取构造方法成员变量、成员方法等涉及到java.lang.reflect

 

二、获取Class类对象

Class类比较特殊。Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象(包括基本数据类型)。
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。

获取Class对象有三种方法:

1)Object.getClass();

2)任何数据类型(包括基本数据类型)都有一个“静态”的class属性

3)通过Class类的静态方法:forName (String  className)  (常用)

import com.hyxy.entity.User;


public class Reflection {
	public static void main(String[] args) {
		//方法一
		User user1=new User();//此时产生了一个User对象,以及一个Class对象
		Class userClass=user1.getClass();
		System.out.println(userClass.getName());
		
		//方法二
		Class userClass1=User.class;
		System.out.println(userClass==userClass1);
		
		//方法三
		try {
			Class userClass2 = Class.forName("com.hyxy.entity.User");
			System.out.println(userClass2==userClass);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
}

运行结果:

可见,在运行期间,一个类,只有一个Class对象产生。

第一种方法没什么意义,对象都有了也就不需要反射了;

第二种方法需要导入类的包,依赖太强,不导包就抛编译错误;

一般都第三种方法获取Class对象,可以传入字符串也可写在配置文件中等多种方法。

 

三、获取构造方法

通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员;
 

1、获取构造方法:
1)批量的方法:

public Constructor[] getConstructors():所有"公有的"构造方法
public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
     
2).获取单个的方法,并调用:

public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;        

调用构造方法: Constructor类的newInstance(Object... initargs)方法,它的返回值是T类型,所以newInstance是创建了一个构造方法的声明类的新实例对象。并为之调用。

User类

package com.hyxy.entity;

public class User {
	public String name;
	public int age;
	
	//默认的构造方法
	User(char c){
		System.out.println("默认的构造方法 c="+c);
	}
	
	//无参构造方法
	public User(){
		System.out.println("调用了无参构造方法");
	}
	
	//一个参数的构造方法
	public User(String name){
		System.out.println("姓名"+name);
	}
	
	//多个参数的构造方法
	public User(String name,int age){
		System.out.println("姓名"+name+" 年龄"+age);
	}

	//受保护的构造方法
	protected User(int age){
		System.out.println("受保护的构造方法 age="+age);
	}
	
	//私有构造方法
	private User(boolean n){
		System.out.println("私有的构造方法 n="+n);
	}
}

测试类:

import java.lang.reflect.Constructor;

public class Reflection1 {
	public static void main(String[] args) throws Exception {
		//1、加载Class对象
		Class clazz=Class.forName("com.hyxy.entity.User");
		
		System.out.println("----------所有公共构造方法----------");
		Constructor[] conArr=clazz.getConstructors();
		for(Constructor c:conArr){
			System.out.println(c);
		}
		System.out.println("----------所有构造方法----------");
		conArr=clazz.getDeclaredConstructors();
		for(Constructor c:conArr){
			System.out.println(c);
		}
		System.out.println("----------获取无参构造方法----------");
		Constructor con=clazz.getConstructor(null);
		//因为是无参的构造方法所以类型是一个null,不写也可以.返回的是描述这个无参构造函数的类对象.
		System.out.println("con="+con);
		Object obj=con.newInstance();
		
		System.out.println("----------获取私有构造方法并调用----------");
		con=clazz.getDeclaredConstructor(boolean.class);
		System.out.println(con);
		con.setAccessible(true);//忽略访问修饰符访问
		obj=con.newInstance(true);
	}
	
}

运行结果:

 

四、获取成员变量

1、批量的

1)Field[] getFields():获取所有的"公有字段"
2)Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;

2、获取单个的

1)public Field getField(String fieldName):获取某个"公有的"字段;
2)public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)

设置字段的值:Field 类的 public void set(Object obj,Object value) 方法
obj:要设置的字段所在的对象;value:要为字段设置的值;

User类:

package com.hyxy.entity;

public class User {
	public User(){}
	public String name;
	protected int age;
	char sex;
	private String uid;

	public String toString() {
		return "User [name=" + name + ", age=" + age + ", sex=" + sex + ", uid=" + uid + "]";
	}
}

测试类:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

import com.hyxy.entity.User;

public class Reflection2 {
	public static void main(String[] args) throws Exception {
		//1、加载Class对象
		Class clazz=Class.forName("com.hyxy.entity.User");
		
		System.out.println("----------获取所有公有的字段----------");
		Field[] fieldArray = clazz.getFields();
		for(Field f : fieldArray){
			System.out.println(f);
		}
		System.out.println("----------获取所有字段----------");
		fieldArray =clazz.getDeclaredFields();
		for(Field f : fieldArray){
			System.out.println(f);
		}
		System.out.println("----------获取公有字段并调用----------");
		Field f = clazz.getField("name");
		System.out.println(f);
		//获取一个对象
		Object obj = clazz.getConstructor().newInstance();//相当于User user=new User();
		//为字段设置值
		f.set(obj, "joda");//为User对象中的name属性赋值,相当于user.name = "joda";
		//验证
		User user = (User)obj;
		System.out.println("姓名:" + user.name);

		System.out.println("----------获取私有字段并调用----------");
		f = clazz.getDeclaredField("uid");
		System.out.println(f);
		f.setAccessible(true);//暴力反射,解除私有限定
		f.set(obj, "123123");
		System.out.println(user);

	}
	
}

运行结果:

五、获取成员方法


1、批量的:

 public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
 public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)

2、获取单个的:

public Method getMethod(String name,Class<?>... parameterTypes); name : 方法名;Class ... : 形参的Class类型对象
public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
 
调用方法: Method --> public Object invoke(Object obj,Object... args):
    obj :要调用方法的对象;args:调用方式时所传递的实参

User类:

package com.hyxy.entity;

public class User {
	public void method1(String s){
		System.out.println("调用了公有的,String参数的method1(): s = " + s);
	}
	protected void method2(){
		System.out.println("调用了受保护的,无参的method2()");
	}
	void method3(){
		System.out.println("调用了默认的,无参的method3()");
	}
	private String method4(int i){
		System.out.println("调用了私有的,并且返回类型为String,int参数的method4(): i = " + i);
		return "method4 return";
	}

}

测试类:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import com.hyxy.entity.User;

public class Reflection2 {
	public static void main(String[] args) throws Exception {
		//1、加载Class对象
		Class clazz=Class.forName("com.hyxy.entity.User");
		
		System.out.println("----------获取所有的公有方法----------");
		clazz.getMethods();
		Method[] methodArray = clazz.getMethods();
		for(Method m : methodArray){
			System.out.println(m);
		}
		System.out.println("----------获取所有的方法----------");
		methodArray = clazz.getDeclaredMethods();
		for(Method m : methodArray){
			System.out.println(m);
		}
		System.out.println("----------获取公有的method1()方法----------");
		Method m = clazz.getMethod("method1", String.class);
		System.out.println(m);
		//实例化一个User对象
		Object obj = clazz.getConstructor().newInstance();
		m.invoke(obj, "joda");
		
		System.out.println("----------获取私有的method4()方法----------");
		m = clazz.getDeclaredMethod("method4", int.class);
		System.out.println(m);
		m.setAccessible(true);//解除私有限定
		Object result = m.invoke(obj, 20);
		System.out.println("返回值:" + result);
	}
}

invoke方法其实就相当于通过setVar()来设置字段值,不过同时还会调用该方法

运行结果:

六、反射main方法

User类:

package com.hyxy.entity;

public class User {
	public static void main(String[] args) {
		System.out.println("main方法执行了");
	}
}

注意:反射的是User的main方法

测试类:

import java.lang.reflect.Method;

import com.hyxy.entity.User;

public class Reflection2 {
	public static void main(String[] args) {
		try {
			//1、获取Student对象的字节码
			Class clazz = Class.forName("com.hyxy.entity.User");
			//2、获取main方法
			 Method methodMain = clazz.getMethod("main", String[].class);//第一个参数:方法名称,第二个参数:方法形参的类型,
			//3、调用main方法
			// methodMain.invoke(null, new String[]{"a","b","c"});
			 //第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数
			 //这里拆的时候将  new String[]{"a","b","c"} 拆成3个对象,所以需要将它强转。
			 methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一
			// methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

七、通过反射运行配置文件内容

user类:

package com.hyxy.entity;

public class User {
	public void show(){
		System.out.println("show()");
	}
}

配置文件propertities.txt:

className = com.hyxy.entity.User
methodName=show

测试类:

import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;
import com.hyxy.entity.User;

public class Reflection3 {
	public static void main(String[] args) throws Exception {
		//通过反射获取Class对象
		Class stuClass = Class.forName(getValue("className"));
		//2获取show()方法
		Method m = stuClass.getMethod(getValue("methodName"));
		//3.调用show()方法
		m.invoke(stuClass.getConstructor().newInstance());
	
	}
	
	//此方法接收一个key,在配置文件中获取相应的value
	public static String getValue(String key) throws IOException{
		Properties pro = new Properties();//获取配置文件的对象
		FileReader in = new FileReader("propertites.txt");//获取输入流
		pro.load(in);//将流加载到配置文件对象中
		in.close();
		return pro.getProperty(key);//返回根据key获取的value值
	}
}

当我们升级这个系统时,不需要User类了,而需要新写一个User2类时,这时只需要更改propertities.txt的文件内容就可以了,测试类的代码就不需要改动了。

八、通过反射越过泛型检查

泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的

例如有一个String泛型的集合,可以利用这个特点向集合中添加一个Integer类型的值

import java.lang.reflect.Method;
import java.util.ArrayList;

import com.hyxy.entity.User;

public class Reflection4 {
	public static void main(String[] args) throws Exception {
		ArrayList<String> strList = new ArrayList<>();
		strList.add("a");
		strList.add("b");
		//	strList.add(1); 编译错误
		
		//获取ArrayList的Class对象,反向的调用add()方法,添加数据
		Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象
		//获取add()方法
		Method m = listClass.getMethod("add", Object.class);
		//调用add()方法
		m.invoke(strList, 1);
		
		//遍历集合
		for(Object obj : strList){
			System.out.println(obj+",类型:"+obj.getClass().getName());
		}
	}
}

运行结果:

是不是很神奇:D


参考资料:

1、https://edu.csdn.net/course/play/6025/113181

2、https://blog.csdn.net/sinat_38259539/article/details/71799078

3、https://blog.csdn.net/weixin_36586120/article/details/80398583

猜你喜欢

转载自blog.csdn.net/qq_39192827/article/details/85564179