Java的反射机制初步(Java基础复习归纳系列)

目录

一、通过反射查看类信息

1.获得Class对象

2.获取Class中的信息

二、使用反射生成并操作对象

1.创建对象

2.调用方法

3.访问Field


一、通过反射查看类信息

        Java程序中的许多对象在运行时都会出现两种类型:编译时类型和运行时类型,如Person p = new Student(),p变量编译时类型为Person,而运行时类型为Student。我们常常需要在程序运行时获取对象和类的真实信息,这就有两种可能:

  • 一是在编译和运行时完全知道类型的具体信息,可以直接使用instanceof运算符进行判断。instanceof运算符的前一个操作数通常是一个引用型变量,后一个操作数可以是一个类或接口,它用于判断前面的对象是否是后面的类或者其子类、实现类的实例。如:

public class InstanceofTest {
	public static void main(String[] args) {
		// 声明hello时使用Object类,则hello的编译类型是Object,但hello变量的实际类型是String
		Object hello = "Hello";
		// 以下三条语句的返回结果均为true
		// String是Object类的子类,可以进行instanceof运算。
		System.out.println("字符串是否是Object类的实例:" + (hello instanceof Object));
		System.out.println("字符串是否是String类的实例:" + (hello instanceof String));
		// String实现了Comparable接口,所以返回true。
		System.out.println("字符串是否是Comparable接口的实例:" + (hello instanceof Comparable));
	}
}
  • 二是在编译时根本无法知道该对象可能属于哪些类,程序只能依靠运行时信息来发现该对象和类的真实信息,这就需要用到反射。

1.获得Class对象

        每个类在被加载之后系统都会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类。获得Class对象通常有3种方式:

  1. 使用 static Class<?> forName(String className) throws ClassNotFoundException返回与带有给定字符串名的类或接口相关联的 Class 对象。其中,className必须是某个类的全限定类名(即需添加完整包名)。

  2. 调用某个类的class属性来获取该类对应的Class对象。如:Person.class。

  3. 调用某个对象getClass()方法。该方法是java.lang.Object包中的一个方法,所有对象均可调用,该方法会返回该对象所属类对应的Class对象。

package reflect;

public class TestGetClass {
	public static void main(String[] args) throws ClassNotFoundException {
		Person p = new Person();
		System.out.println(Class.forName("reflect.Person").getName());
		System.out.println(Person.class.getName());
		System.out.println(p.getClass().getName());
	}
}

class Person {

}

运行结果:

其中,b.方法由于在编译阶段就可以检查需要访问的Class对象是否存在,且无需调用方法,所以更加安全,性能更好。

2.获取Class中的信息

        Class类中提供了大量的实例方法来获得该Class对象的所对应类的详细信息,包括:获取Class对应类所包含的构造方法、获取Class对应类所包含的方法、获取Class对应类所包含的Field、获取Class对应类所包含的Annotation、获取Class对应类所包含的内部类、获取Class对应类所继承的父类和所实现的接口、获取Class对应类的修饰符、所在包以及类名等基本信息,除此之外,还有方法可以判断该类是否为接口、枚举、注释类型等。

import java.lang.reflect.Constructor;

public class GetClassInfo {

	public static void main(String[] args) throws NoSuchMethodException, SecurityException {
		Class c = Student.class;
		// 返回对应形参为String类型的public构造方法
		System.out.println(c.getConstructor(String.class));
		// 返回无形参的构造方法,无论其访问权限为何
		System.out.println(c.getDeclaredConstructor());
		// 返回所有的构造方法,无论其访问权限为何
		Constructor[] csts = c.getDeclaredConstructors();
		for (Constructor cst : csts) {
			System.out.println(cst);
		}

	}
}

class Student {
	private String id;

	private Student() {

	}

	public Student(String id) {
		this.id = id;
	}
}

运行结果:


二、使用反射生成并操作对象

        java.lang.reflect包java 1.8之前及java 1.8的改变:

     

        在java.lang.reflect包中包含了Method类、Constructor类和Field类,这三个类都实现了java.lang.reflect.Member接口。程序可以通过Method对象来执行对应的方法,通过Constructor对象来调用对应的构造方法创建实例,通过Field对象直接访问并修改对象的属性值。

1.创建对象

        通过反射创建对象有两种方式:

  1. 直接使用Class对象的newInstance()方法。该方式需要Class对象对应的类有默认的构造方法。

  2. 先使用Class对象通过getConstructor()方法获取指定的Constructor对象,再调用Constructor对象的newInstance()方法创建Java对象。通过这种方法可以选择指定的构造方法来创建实例。     

import java.util.*;
import java.io.*;
import java.lang.reflect.Constructor;

/**
 * @Title ObjectPoolFactory.java
 * @Description TODO 
 * @Author 15643
 * @Time 2018年8月28日 上午10:25:24
 * @Other obj.txt中的内容:a=java.util.Date
 */
public class ObjectPoolFactory {
	// 定义一个对象池,前面是对象名,后面是实际对象
	private Map<String, Object> objectPool = new HashMap<>();

	// 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
	private static Object createObject(String clazzName)
			throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		// 根据字符串来获取对应的Class对象
		Class<?> clazz = Class.forName(clazzName);
		// 使用clazz对应类的默认构造器创建实例
		return clazz.newInstance();
	}

	// 该方法根据指定文件来初始化对象池,并根据配置文件来创建对象
	public void initPool(String fileName)
			throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		try (FileInputStream fis = new FileInputStream(fileName)) {
			Properties props = new Properties();
			props.load(fis);
			for (String name : props.stringPropertyNames()) {
				// 根据键值对的值创建一个对象,并将对象添加到对象池中
				objectPool.put(name, createObject(props.getProperty(name)));
			}
		} catch (IOException ex) {
			System.out.println("读取" + fileName + "异常");
		}

	}

	public Object getObject(String name) {
		// 从objectPool中取出指定name对应的对象。
		return objectPool.get(name);
	}

	public static void main(String[] args) throws Exception {
		ObjectPoolFactory pf = new ObjectPoolFactory();
		pf.initPool("obj.txt");
		System.out.println(pf.getObject("a"));
		// 直接在这里示范了,使用指定的构造方法创建对象
		Constructor<?> c = pf.getObject("a").getClass().getConstructor(long.class);
		System.out.println(c.newInstance(1000));
	}
}

         运行结果:

2.调用方法

       

        在Class类里面有四个用来获取方法的方法,getMethod()、getDeclaredMethod()、getDeclaredMethods()、getMethods(),前两个返回Method对象,后两个返回Method对象数组。

        Method类的常用方法:  

Object  invoke(Object obj, Object... args)

对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。obj - 从中调用底层方法的对象,args - 执行方法时传入的实参
boolean  equals(Object obj)       将此 Method 与指定对象进行比较。
String  getName()        以 String 形式返回此 Method 对象表示的方法名称。
Class<?>[]  getParameterTypes()按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。
Class<?>  getReturnType() 返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型。
import java.lang.reflect.*;

public class TestMethod {

	public static void main(String[] args) throws NoSuchMethodException, SecurityException, InvocationTargetException,
			InstantiationException, IllegalAccessException {
		String field = "id";
		Class<?> cl = Employee.class;
		Object obj = cl.newInstance();
		// 使用get+首字母大写的Field来匹配方法
		Method getMt = cl.getMethod("get" + field.substring(0, 1).toUpperCase() + field.substring(1));
		Method setMt = cl.getMethod("set" + field.substring(0, 1).toUpperCase() + field.substring(1), String.class);
        // 使用invoke()调用Method对象对应表示的方法
		setMt.invoke(obj, "100001");
		System.out.println("StudentID is " + getMt.invoke(obj));
	}

}

class Employee {
	private String id;

	public Employee() {

	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

}

        若程序需要调用某个对象的private方法,则可以先用Method对象调用setAccessible(boolean flag),当flag为true时,则指示反射的对象在使用时应该取消 Java 语言访问权限检查。值为 false 则指示反射的对象应该实施 Java 语言访问权限检查。 (setAccessible()方法是属于AccessibleObject类的,由于它是Method、Constructor、Field的父类,所以它们都可以调用该方法从而通过反射来调用私有的方法、属性等。)

3.访问Field

        Field类提供了两组方法来访问、设置Field的值:

getXxx(Object obj)获取obj对象的该Field的属性值。Xxx对应8个基本类型,若为引用类型,则为get(Object obj)方法
setXxx(Object obj,Xxx value)将obj对象的该Field设置成value值。Xxx对应8个基本类型,若为引用类型,则为set(Object obj,Object value)方法
import java.lang.reflect.*;

public class FieldTest {
	public static void main(String[] args) throws Exception {
		Class cl = Employee.class;
		Object p = cl.newInstance();
		// 获取Person的名为id的Field,使用getDeclaredField无视访问控制符获取field
		Field idField = cl.getDeclaredField("id");
		// 设置通过反射访问该Field时取消访问权限检查,否则IllegalAccessException
		idField.setAccessible(true);
		idField.set(p, "1000001");
		System.out.println(p);
	}
}
class Employee {
	private String id;
	public Employee() {
    }
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}	
	@Override
	public String toString() {
		return "StudentID is "+id;
	}
}

        参考资料:《Java疯狂讲义》

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值