原创

Java高级编程---反射机制


1.反射机制是什么

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制

反射允许静态语言在运行时检查、修改程序的结构与行为。在静态语言中,使用一个变量时,必须知道它的类型。在Java中,变量的类型信息在编译时都保存到了class文件中,这样在运行时才能保证准确无误;换句话说,程序在运行时的行为都是固定的。如果想在运行时改变,就需要反射这东西了。总之:反射可以赋予jvm动态编译的能力,否则类的原数据信息只能用静态编译的方式实现

实现Java反射机制的类都位于:

  • java.lang.Class
  • java.lang.reflect.Method
  • java.lang.reflect.Field
  • java.lang.reflect.Constructor

2.获取Class实例的方式:4种方式

java.lang.Class类时整个反射技术中最重要的对象,所有的其他反射对象都是通过Class类得到到,因此可以说Class是整个反射的源头。Class对象是外部访问该类对象的入口,在虚拟机加载完每一个类的同时都会对应生成一个相应的Class对象,它就像每个类的镜子一样,可以从该Class中获取到对应类的所有结构。获取Class实例的方法如下:

  1. 调用运行时类的属性:.class
  2. 通过运行时类的对象,调用getClass()
  3. 调用Class的静态方法:forName(String classPath)【多】
  4. 使用类的加载器:ClassLoader【了解】

首先,先创建一个Person类方便演示:

package com.lys.java;

public class Person {
	private String name;
	public int age;
	
	public Person(){
		System.out.println("Person()");
	}
	
	public Person(String name, int age){
		this.name = name;
		this.age = age;
	}
	
	// 私有构造方法
	private Person(String name){
		this.name = name;
	}
	
	public void show(){
		System.out.println("你好,我是一个人");
	}
	
	private String showNation(String nation){
		System.out.println("我的国籍是:" + nation);
		return nation;
	}
	
	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;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
}

测试:

package com.lys.java;

public class ReflectionTest {
	public static void main(String[] args) throws Exception {
		new ReflectionTest().test1();
	}
	
	// 获取Class的实例的方式:4种方式
	public void test1() throws ClassNotFoundException{
		//方式一:调用运行时类的属性:.class
		Class<Person> clazz1 = Person.class;
		System.out.println("方法一:" + clazz1);
		
		//方式二:通过运行时类的对象,调用getClass()
		Person p1 = new Person();
		Class clazz2 = p1.getClass();
		System.out.println("方法二:" + clazz2);
		
		//方式三:调用Class的静态方法:forName(String classPath)【多】
		//       形参str:指的是你所要获取Class所对应的类的全名
		Class clazz3 = Class.forName("com.lys.java.Person");
		System.out.println("方法三:" + clazz3);
		
		System.out.println("方法一与方法二获取是否相同:" + clazz1 == clazz2);
		System.out.println("方法一与方法三获取是否相同:" + clazz1 == clazz3);
		
		//方式四:使用类的加载器:ClassLoader【了解】
		ClassLoader classLoader = ReflectionTest.class.getClassLoader();
		Class clazz4 = classLoader.loadClass("com.lys.java.Person");
		System.out.println("方法四:" + clazz4);
	}
}

方法一:class com.lys.java.Person
Person()
方法二:class com.lys.java.Person
方法三:class com.lys.java.Person
方法一与方法二获取是否相同:true
方法一与方法三获取是否相同:true
方法四:class com.lys.java.Person

【说明】:发现4种方法的结果都是相同的!

3.原先创建类对象与反射创建类对象

首先,回顾一下我们之前创建类对象的过程,然后,在熟悉一下反射的创建步骤:

// 原先创建类对象
public void test1(){
	Person p1 = new Person("Tom", 12);
	p1.age = 10;
	System.out.println(p1.toString());
	p1.show();
}

// 利用反射创建类对象
public void test2() throws Exception{
	Class clazz = Person.class;
	
	// 1.通过反射,创建Person类对象
	Constructor cons = clazz.getConstructor(String.class, int.class);
	Object obj = cons.newInstance("Tom", 12);
	Person p = (Person) obj;
	System.out.println(obj.toString());
	
	// 2.通过反射,调用对象指定的属性与方法
	// 2.1调用属性
	Field age = clazz.getDeclaredField("age");
	age.set(p,10);
	System.out.println(obj.toString());
	
	// 2.2调用方法
	Method show = clazz.getDeclaredMethod("show");
	show.invoke(p);
	
	System.out.println("**************************************");
	
	// 3.通过反射,可以调用Person类的私有结构
	// 3.1调用私有构造方法
	Constructor cons1 = clazz.getDeclaredConstructor(String.class);
	cons1.setAccessible(true);
	Person p1 = (Person) cons1.newInstance("ROY");
	System.out.println(p1);
	
	// 3.2调用私有属性
	Field name = clazz.getDeclaredField("name");
	name.setAccessible(true);
	name.set(p1, "ROY_NEW");
	System.out.println(p1);
	
	// 3.3调用私有方法
	Method showNation = clazz.getDeclaredMethod("showNation", String.class);
	showNation.setAccessible(true);
	showNation.invoke(p1, "中国");	
	}

【输出】:

Person [name=Tom, age=10]
你好,我是一个人
Person [name=Tom, age=12]
Person [name=Tom, age=10]
你好,我是一个人
**************************************
Person [name=ROY, age=0]
Person [name=ROY_NEW, age=0]
我的国籍是:中国

【注意】

方法newInstance() 无参数时:创建对应运行时类的对象,内部调用了运行时类的空参构造器

  1. 运行时类必须提供空参的构造器
  2. 空参构造器的访问权限得够。通常,设置为public

通常在javabean中要求提供一个public的空参构造器的原因:

  1. 便于通过反射,创建运行时类的对象
  2. 便于子类继承运行时类时,默认调用super()时,保证父类有此构造器

4.体验反射的动态性

我们随机进行5次测试,来创建3种java中不同的类对象:

package com.lys.java;

import java.util.Random;

public class NewInstanceTest {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException {
		new NewInstanceTest().test1();
	}
	
	// 随机测试
	public void test1(){
		for(int i = 1; i <= 5; i++ ){
			int num = new Random().nextInt(3);
			String classPath = "";
			switch (num) {
			case 0:
				classPath = "java.util.Date";
				break;
			case 1:
				classPath = "java.lang.Object";
				break;
			case 2:
				classPath = "com.atguigu.java.Person";
				break;
			}

			try {
				System.out.println("-----第" + i + "次测试-----");
				Object obj = getInstance(classPath);
				System.out.println(obj);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/*
	 * 创建一个指定类的对象。
	 * classpath:指定类的全类名
	 * */
	public Object getInstance(String classpath) throws Exception{
		Class clazz = Class.forName(classpath);
		return clazz.newInstance();
	}
}
-----第1次测试-----
Person()
Person [name=null, age=0]
-----第2次测试-----
Fri Sep 11 11:00:15 CST 2020
-----第3次测试-----
java.lang.Object@5c647e05
-----第4次测试-----
Person()
Person [name=null, age=0]
-----第5次测试-----
Person()
Person [name=null, age=0]

5.准备工作---创建新的类与接口(等待下面的练习)

【注意】:尽可能的创建更多权限的属性和方法进行练习。

新的Person类创建:

package com.lys.java1;

@MyAnnotation(value="hi")
public class Person extends Creature<String> implements Comparable<String>,MyInterface{
	private String name;
	int age;
	public int id;
	
	public Person(){}
	
	@MyAnnotation(value="abc")
	private Person(String name){
		this.name = name;
	}
	
	Person(String name, int age){
		this.name = name;
		this.age = age;
	}
	
	@MyAnnotation
	private String show(String nation){
		 System.out.println("我的国籍是:" + nation);
		 return nation;
	 } 
	 
	 public String display(String interests, int age) throws NullPointerException, ClassCastException{
		 return interests + age;
	 }
	 
	@Override
	public void info(){
		System.out.println("我是一个人");
	}
	
	@Override
	public int compareTo(String o) {
		return 0;
	}
	
	private static void showDesc(){
		System.out.println("我是一个可爱的人");
	}
}

Person父类的创建:

package com.lys.java1;

import java.io.Serializable;

public class Creature<T> implements Serializable {
	private char gender;
	public double weight;
	
	private void breath(){
		System.out.println("生物呼吸");
	}
	
	public void eat(){
		System.out.println("生物吃东西");
	}
}

定义一个接口:MyInterface

package com.lys.java1;

public interface MyInterface {
	void info();
}
package com.lys.java1;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
	String value() default "hello";
}

6.获取属性

反射获取对象属性时,常用的方法:

  1. getFields():获取当前运行时类及其父类中声明为public的访问权限属性
  2. getDeclaredFields():获取当前运行时类中声明的所有属性(不包含父类中)
  3. getModifiers():获取权限修饰符
  4. getType():获取数据类型
  5. getName():获取变量名
package com.lys.java2;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import com.lys.java1.Person;

public class FieldTest {
	public static void main(String[] args) {
		new FieldTest().test1();
	}
	
	public void test1(){
		Class clazz = Person.class;
		
		// 获取属性
		// 1.getFields():获取当前运行时类及其父类中声明为public的访问权限属性
		Field[] fields = clazz.getFields();
		for(Field f : fields){
			System.out.println(f);
		}
		System.out.println();
		
		// 2.getDeclaredFields():获取当前运行时类中声明的所有属性(不包含父类中)
		Field[] declaredfields = clazz.getDeclaredFields();
		for(Field f : declaredfields){
			System.out.println(f);
		}
	}
	
	
	public void test2(){
		Class clazz = Person.class;
		Field[] declaredfields = clazz.getDeclaredFields();
		for(Field f : declaredfields){
			// 1.权限修饰符
			int modifer = f.getModifiers();
//			System.out.println(modifer + "--->" + Modifier.toString(modifer));	
			System.out.println(Modifier.toString(modifer));	
			
			// 2.数据类型
			Class type = f.getType();
			System.out.println(type.getName() + "\t");
			
			// 3.变量名
			String fName = f.getName();
			System.out.println(fName);
			System.out.println();
		}
	}
}

【测试1的输出】:

public int com.lys.java1.Person.id
public double com.lys.java1.Creature.weight

private java.lang.String com.lys.java1.Person.name
int com.lys.java1.Person.age
public int com.lys.java1.Person.id

【测试2的输出】:

private
java.lang.String
name


int	
age

public
int	
id

7.获取方法

反射获取对象方法时,常用的方法:

  1. getMethods():获取当前运行时类及其父类中声明为public权限的方法
  2. getDeclaredMethods():获取当前运行时类中声明的所有方法(不包含父类)
  3. getAnnotations():获取方法声明的注解
  4. getModifiers():获取权限修饰符
  5. getReturnType():获取返回值类型
  6. getName():获取方法名
  7. getParameterTypes():获取形参
  8. getExceptionTypes():获取异常
package com.lys.java2;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import com.lys.java1.Person;

public class MethodTest {
	public static void main(String[] args) {
		new MethodTest().test1();
	}
	
	public void test1(){
		Class clazz = Person.class;
		
		//getMethods():获取当前运行时类及其父类中声明为public权限的方法
		Method[] methods = clazz.getMethods();
		for(Method m : methods){
			System.out.println(m);
		}
		System.out.println();
		
		//getDeclaredMethods():获取当前运行时类中声明的所有方法(不包含父类)
		Method[] declaredmethods = clazz.getDeclaredMethods();
		for(Method m : declaredmethods){
			System.out.println(m);
		}
	}
	
	public void test2(){
		Class clazz = Person.class;
		Method[] declaredmethods = clazz.getDeclaredMethods();
		for(Method m : declaredmethods){
			//1.获取方法声明的注解
			Annotation[] annos = m.getAnnotations();
			for(Annotation a : annos){
				System.out.println(a);
			}
			//2.获取权限修饰符
			System.out.print(Modifier.toString(m.getModifiers()) + "\t");
			//3.返回值类型
			System.out.print(m.getReturnType().getName() + "\t");
			//4.方法名
			System.out.print(m.getName());
			System.out.print("(");
			//5.形参列表
			Class[] parameterTypes = m.getParameterTypes();
			if(!(parameterTypes == null && parameterTypes.length == 0)){
				for(int i = 0; i < parameterTypes.length; i++){
					if(i == parameterTypes.length - 1){
						System.out.print(parameterTypes[i].getName() + "args_" + i);
						break;
					}
					System.out.print(parameterTypes[i].getName() + " args_ " + i + ",");
				}
			}
			System.out.print(")");
			//6.抛出的异常
			Class[] exceptionTypes = m.getExceptionTypes();
			if(exceptionTypes.length > 0){
				System.out.print("throws");
				for(int i = 0; i < exceptionTypes.length; i++){
					if(i == exceptionTypes.length - 1){
						System.out.print(exceptionTypes[i].getName());
					}
					System.out.print(exceptionTypes[i].getName() + ",");
				}
			}
			System.out.println();	
		}
	}
}

【测试1的输出】:

public int com.lys.java1.Person.compareTo(java.lang.String)
public int com.lys.java1.Person.compareTo(java.lang.Object)
public java.lang.String com.lys.java1.Person.display(java.lang.String,int) throws java.lang.NullPointerException,java.lang.ClassCastException
public void com.lys.java1.Person.info()
public void com.lys.java1.Creature.eat()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

public int com.lys.java1.Person.compareTo(java.lang.String)
public int com.lys.java1.Person.compareTo(java.lang.Object)
public java.lang.String com.lys.java1.Person.display(java.lang.String,int) throws java.lang.NullPointerException,java.lang.ClassCastException
private static void com.lys.java1.Person.showDesc()
private java.lang.String com.lys.java1.Person.show(java.lang.String)
public void com.lys.java1.Person.info()

【测试2的输出】:

public	int	compareTo(java.lang.Stringargs_0)
public volatile	int	compareTo(java.lang.Objectargs_0)
public	java.lang.String	display(java.lang.String args_ 0,intargs_1)throwsjava.lang.NullPointerException,java.lang.ClassCastExceptionjava.lang.ClassCastException,
@com.lys.java1.MyAnnotation(value=hello)
private	java.lang.String	show(java.lang.Stringargs_0)
public	void	info()
private static	void	showDesc()

8.了解其他常见操作

  1. 方法set():参数1:指明设置哪个对象的属性; 参数2:将此属性值设置为多少;
  2. 方法get():参数1:指明设置哪个对象的属性; 参数2:将此属性值设置为多少;
  3. setAccessible():保证当前属性可访问
  4. 略。。。
package com.lys.java2;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import com.lys.java1.Person;

public class ReflectionTest {
	public static void main(String[] args) throws Exception {
		new ReflectionTest().testField2();
	}
	
	//【了解,不好】
	public void testField() throws Exception{
		Class clazz = Person.class;
		
		//创建运行时类的对象
		Person p = (Person) clazz.newInstance();
		
		//获取指定的属性:要求运行时类中属性声明为public【不常用】
		Field id = clazz.getField("id");
		
		/*设置当前属性的值
		  方法set():
		 	参数1:指明设置哪个对象的属性; 
		 	参数2:将此属性值设置为多少;
		*/
		id.set(p, 1001);
		
		/*获取当前属性的值
		  方法get():
		 	参数1:指明设置哪个对象的属性; 
		 	参数2:将此属性值设置为多少;
		*/
		int pId = (int) id.get(p);
		System.out.println(pId);
	}
	
	//如何操作运行时类中的指定属性【重要】
	public void testField1() throws Exception{
		Class clazz = Person.class;
		//创建运行时类的对象
		Person p = (Person) clazz.newInstance();
		
		//1.getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
		Field name = clazz.getDeclaredField("name");
		
		//2.setAccessible():保证当前属性可访问
		name.setAccessible(true);
		//3.获取、设置指定对象是可访问的
		name.set(p, "Tom");
		
		System.out.println(name.get(p));
	}
	
	
	public void testField2() throws Exception{
		Class clazz = Person.class;
		//创建运行时类的对象
		Person p = (Person) clazz.newInstance();
		
		/*1.获取指定的某个方法:
		 *	 getDeclaredMethod():
		 * 		参数1:方法名称;
		 * 		参数2:指定方法的形参类型
		 */
		Method show = clazz.getDeclaredMethod("show", String.class);
		show.setAccessible(true);
		//2.调用方法
		Object returnValues = show.invoke(p, "CHN");
		System.out.println(returnValues);
		
		System.out.println("********如何调用静态方法*********");
		
		//private static void showDesc():静态方法
		Method showDesc = clazz.getDeclaredMethod("showDesc");
		showDesc.setAccessible(true);
		Object returnVal = showDesc.invoke(Person.class);
		System.out.println(returnVal);
	}
}

【输出】:略

package com.lys.java2;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import com.lys.java1.Person;

public class OtherTest {
	
	public static void main(String[] args) {
		System.out.println("------------测试1------------");
		new OtherTest().test1();
		System.out.println("------------测试2------------");
		new OtherTest().test2();
		System.out.println("------------测试3------------");
		new OtherTest().test3();
		System.out.println("------------测试4------------");
		new OtherTest().test4();
		System.out.println("------------测试5------------");
		new OtherTest().test5();
		System.out.println("------------测试6------------");
		new OtherTest().test6();
		System.out.println("------------测试7------------");
		new OtherTest().test7();
	}
	
	
	public void test1(){
		Class clazz = Person.class;
		//getConstructors():获取当前运行时类中声明为public的构造器
		Constructor[] constructors = clazz.getConstructors();
		for(Constructor c : constructors){
			System.out.println(c);
		}
		
		System.out.println();
		//getDeclaredConstructors():获取当前运行时类中声明的所有构造器
		Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
		for(Constructor c : declaredConstructors){
			System.out.println(c);
		}
	}
	
	// 获取运行时类的父类
	public void test2(){
		Class clazz = Person.class;
		
		Class superclass = clazz.getSuperclass();
		System.out.println(superclass);
	}	
	
	// 获取运行时类的带泛型的父类
	public void test3(){
		Class clazz = Person.class;
		
		Type genericSuperclass = clazz.getGenericSuperclass();
		System.out.println(genericSuperclass);
	}	
	
	// 获取运行时类的带泛型的父类的泛型
	public void test4() {
		Class clazz = Person.class;

		Type genericSuperclass = clazz.getGenericSuperclass();
		ParameterizedType paramType = (ParameterizedType) genericSuperclass;
		// 获取泛型类型
		Type[] actualTypeArguments = paramType.getActualTypeArguments();
		System.out.println(actualTypeArguments[0]);
		System.out.println(actualTypeArguments[0].getTypeName());
		System.out.println(((Class)actualTypeArguments[0]).getName());
	}
	
	// 获取运行时实现的接口
	public void test5() {
		Class 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 c : interfaces1){
			System.out.println(c);
		}
	}
	
	// 获取运行时类所在的包
	public void test6() {
		Class clazz = Person.class;

		Package pack = clazz.getPackage();
		System.out.println(pack);
	}
	
	// 获取运行时类声明的注解
	public void test7() {
		Class clazz = Person.class;

		Annotation[] annotations = clazz.getAnnotations();
		for(Annotation annos : annotations){
			System.out.println(annos);
		}
	}
}

【输出】:

------------测试1------------
public com.lys.java1.Person()

com.lys.java1.Person(java.lang.String,int)
private com.lys.java1.Person(java.lang.String)
public com.lys.java1.Person()
------------测试2------------
class com.lys.java1.Creature
------------测试3------------
com.lys.java1.Creature<java.lang.String>
------------测试4------------
class java.lang.String
java.lang.String
java.lang.String
------------测试5------------
interface java.lang.Comparable
interface com.lys.java1.MyInterface

interface java.io.Serializable
------------测试6------------
package com.lys.java1
------------测试7------------
@com.lys.java1.MyAnnotation(value=hi)

9.反射的功能及优缺点

反射的功能

  1. 在运行时判断一个对象所属的类
  2. 在运行时判断一个类的对象
  3. 在运行时判断一个类所具有的成员变量和方法
  4. 在运行时判断一个对象的成员变量和方法
  5. 生成动态代理

反射的优点

  • Java的反射机制就是增加程序的灵活性,避免将程序写死到代码里
  • 可以通过反射查看对象中所包含的属性和方法等信息,便于更好的解读
  • 可以调用类对象中不被允许访问的代码

反射的缺点

  • 运用反射会使我们的软件的性能降低,复杂度增加
  • 反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
  • 恶意的攻击

10.Java封装特性与反射机制是否发生冲突?

我们知道,封装、继承、多态是Java的三大特性。而我们通过上面的哪些举例练习也能发现:通过反射,我们可以轻而易举的获取对私有成员的操作权利,那么这就是所谓的封装性遭受到破坏了吗?我们先来回忆一下“封装”是什么?

封装:简单理解就是将一个类的功能暴露给外部,但是将内部实现细节隐藏起来。换个说话就是类的外部只需要知道我给你提供了哪些功能即可,关于这些功能是是如何实现的你不需要知道,也不让你知道。java的封装机制就有效的在代码的编码阶段防止外部类窥探功能接口的内部原理和实现细节。

封装是在编码阶段的概念,而反射机制是在运行阶段的概念,有的时候人们为了想要了解类中更加细节的代码模块,可以通过反射的方式来了解,更加深刻的体会整个代码的工作过程。当然,也存在一些不遵守规矩的人,想通过反射获取所有操作权限进行恶意更改代码,或者寻找漏洞进行攻击,造成一定的安全隐患。

Java
  • 作者:李延松(联系作者)
  • 发表时间:2020-09-12 09:34
  • 版本声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 公众号转载:请在文末添加作者公众号二维码

评论

留言