Java-反射学习笔记

10 篇文章 0 订阅

Java-反射学习笔记

一、反射的概述

1.1 什么是反射

​ 反射是Java的特征之一,是一种间接操作目标对象的机制,核心是JVM在运行的时候才动态加载类,并且对于任意一个类,都能够知道这个类的所有属性和方法,调用方法/访问属性,不需要提前在编译期知道运行的对象是谁,他允许运行中的Java程序获取类的信息,并且可以操作类或对象内部属性。程序中对象的类型一般都是在编译期就确定下来的,而当我们的程序在运行时,可能需要动态的加载一些类,这些类因为之前用不到,所以没有加载到jvm,这时,使用Java反射机制可以在运行期动态的创建对象并调用其属性,它是在运行时根据需要才加载。

2.2 反射的原理

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

2.3 反射的优缺点

​ 1、优点:使用反射,我们就可以在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

​ 2、(1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;(2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

2.4 反射的使用场景与用途

​ 在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。当然,也不是所有的都适合反射,之前就遇到一个案例,通过反射得到的结果与预期不符。阅读源码发现,经过层层调用后在最终返回结果的地方对应用的权限进行了校验,对于没有权限的应用返回值是没有意义的缺省值,否则返回实际值起到保护用户的隐私目的。

​ java的反射机制主要提供以下几种用途:

  1. 反编译:.class–>.java

  2. 通过反射机制访问java对象的属性,方法,构造方法等

  3. 当我们在使用IDE,比如Ecplise时,当我们输入一个对象或者类,并想调用他的属性和方法是,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。

  4. 反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。

    比如,加载数据库驱动的,用到的也是反射。

Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动

二、Java反射API

​ Java反射API由Java反射核心API和辅助Java反射API的组成,核心API位于java.lang包,是正在运行的Java应用程序中的类与接口的Class类;辅助Java反射的API的API位于java.lang.reflect包,其常用类包含:

Java.lang.Class;

Java.lang.reflect.Constructor;

Java.lang.reflect.Field;

Java.lang.reflect.Method;

Java.lang.reflect.Modifier;

三、反射的基本使用

3.1 反射核心类-Class类

获取Class类的实例有三种方法,分别为:

1、Object–>getClass

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

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

public class TestDemo {
	
		public static void main(String[] args) {
			
				// 方法一 可以通过该实例变量提供的getClass()方法获取
				Student stu1 = new Student();
				Class stuClass = stu1.getClass();
				System.out.println(stuClass.getName());
				
				// 方法二:直接通过一个class的静态变量class获取
				Class stuClass2 = Student.class;
				
				System.out.println("stuClass==stuClass2:" + (stuClass == stuClass2 ));
				
				// 方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取
				try {
					Class stuClass3 = Class.forName("org.xiaoyi.lesson16.test.Student");
					System.out.println(stuClass== stuClass3);
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
		}

}

​ 运行结果如下:

​ 注意,在运行期间,一个类,只有一个Class对象产生,所以打印结果都是true;

​ 三种方式中,常用第三种,第一种对象都有了还要反射干什么,第二种需要导入类包,依赖太强,不导包就抛编译错误。一般都使用第三种,一个字符串可以传入也可以写在配置文件中等多种方法。

​ 通过上面三种方法的任意一种方法获取Class对象,然后根据对象获取类中的一些信息,

方法用途
asSubclass(Class clazz)把传递的类的对象转换成代表其子类的对象
Cast把对象转换成代表类或是接口的对象
getClassLoader()获得类的加载器
getClasses()返回一个数组,数组中包含该类中所有公共类和接口类的对象
getDeclaredClasses()返回一个数组,数组中包含该类中所有类和接口类的对象
forName(String className)根据类名返回类的对象
getName()获得类的完整路径名字
newInstance()创建类的实例
getPackage()获得类的包
getSimpleName()获得类的名字
getSuperclass()获得当前类继承的父类的名字
getInterfaces()获得当前类实现的类或是接口

参考代码如下:

public class TestDemo02 {

	public static void main(String[] args) {

		Class stuClass = Student.class;
		// 获得类的名称(全名)
		System.out.println("类的名称:" + stuClass.getName());
		// 获得类的简单名称
		System.out.println("类的简单名称:" + stuClass.getSimpleName());
		// 获得类的包名
		if (stuClass.getPackage() != null) {
			System.out.println("类的包名称:" + stuClass.getPackage().getName());
		}
		// 获得类的修饰符
		System.out.println("类的修饰符:" + stuClass.getModifiers());
		// 判断是否是接口
		System.out.println("是否为接口:" + stuClass.isInterface());
		// 判断是否为数组
		System.out.println("是否为数组:" + stuClass.isArray());

		try {
			// 使用Class构建对象
			Student stu = (Student) stuClass.newInstance(); // 相当于执行new
															// Student()它的局限是:只能调用public的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。
		} catch (InstantiationException | IllegalAccessException e) {
			e.printStackTrace();
		}

	}

}

​ 运行结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-URZ2Z60v-1571383440736)(F:/Java/023_反射/day16-反射(补充)]/images/1571324337890.png)

3.2 反射辅助类-Field类

​ Field代表类的成员变量(成员变量也称为类的属性),我们先看看如何通过Class实例获取字段信息。Class类提供了以下几个方法来获取字段:

  • Field getField(name):根据字段名获取某个public的field(包括父类)

  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)

  • Field[] getFields():获取所有public的field(包括父类)

  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

    其具体用法如下代码:

public class TestDemo03 {
	
	
	public static void main(String[] args) throws Exception {
		
		 Class empClass = Emp.class;
		 // 获得public字段 empno
		 Field f = empClass.getField("empno");
		 System.out.println(f);
		 System.out.println(f.getName()); // 获得字段名称
		 System.out.println(f.getType()); // 获得字段类型
		 System.out.println(f.getModifiers()); // 获得字段访问修饰符
		 System.out.println(Modifier.isFinal(f.getModifiers())); // 字段是否为final
		 System.out.println(Modifier.isPrivate(f.getModifiers())); // 字段是否private
		 
		 
		 // 获得继承public字段 name
		 System.out.println(empClass.getField("name"));
		 // 获得private 字段 job
		 System.out.println(empClass.getDeclaredField("job"));
		 
		 // 获得字段的值
		 Object obj = new Emp("xiaoxiao");
		 Field jobField = empClass.getField("name");
		 String value =(String) jobField.get(obj);
		 System.out.println(value);
		 
		 // 设置字段的值
		 Field nameField = empClass.getDeclaredField("job");
		 nameField.setAccessible(true);
		 nameField.set(obj, "java");
		 
		 Emp emp = (Emp) obj;
		 
		 System.out.println("emp-job:" + emp.getJob());
		 
		 
	}
	
	

}


class Person{
	public String name;
	
	public Person(String name) {
		this.name = name;
	}
	
	
}

class Emp extends Person{
	
	 public Emp(String name) {
		super(name);
	}

	// 公有字段
	 public Integer empno;
	
	 // 私有字段
	 private String job;
	 
	 public String getJob() {
		 return this.job;
	 }
	 
	
}

​ 其运行结果如下:

3.3 反射辅助类-Method类

​ 我们已经能通过Class实例获取所有Field对象,同样的,可以通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method

  • Method getMethod(name, Class…):获取某个public的Method(包括父类)

  • Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)

  • Method[] getMethods():获取所有public的Method(包括父类)

  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

    其具体使用如下代码:

public class TestDemo03 {

	public static void main(String[] args) throws Exception {

		//test4();
		test5();
	}

	// 基本Method的用法
	public static void test1() throws Exception {
		Class stdClass = Student.class;
		// 获取public方法getScore,参数为String:
		Method m = stdClass.getMethod("getScore", String.class);
		System.out.println(m);
		System.out.println("获得方法名称:" + m.getName());
		System.out.println("获得方法的返回类型:" + m.getReturnType());
		System.out.println("获得参数类型:" + m.getParameterTypes());
		System.out.println("获得方法访问修饰符:" + m.getModifiers());

		// 获取继承的public方法getName,无参数:
		System.out.println(stdClass.getMethod("getName"));
		// 获取private方法getGrade,参数为int:
		System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
	}

	// 方法的调用
	public static void test2() throws Exception {

		// String对象:
		String s = "Hello world";
		// 获取String substring(int)方法,参数为int:
		Method m = String.class.getMethod("substring", int.class);
		// 在s对象上调用该方法并获取结果:
		String r = (String) m.invoke(s, 6);
		// 打印调用结果:
		System.out.println(r);

	}

	// 调用静态方法
	public static void test3() throws Exception {

		// 获取Integer.parseInt(String)方法,参数为String:
		Method m = Integer.class.getMethod("parseInt", String.class);
		// 调用该静态方法并获取结果:
		Integer n = (Integer) m.invoke(null, "12345");
		// 打印调用结果:
		System.out.println(n);

	}

	// 调用非public方法
	public static void test4() throws Exception {

		Person p = new Person();
		Class c = p.getClass();
		Method m = c.getDeclaredMethod("setName", String.class);
		m.setAccessible(true);
		m.invoke(p, "xiaoyi");
		System.out.println(p.getName());
	}
	
	
	// 多态的方法
	public static void test5() throws Exception{
		
		Method m = Person.class.getMethod("sayHello");
		m.invoke(new Student());
		
	}

}

class Person {
	private String name;

	public String getName() {
		return this.name;
	}

	private void setName(String name) {
		this.name = name;
	}
	
	
	public void sayHello() {
		System.out.println("person sayHello");
	}

}

class Student extends Person {
	public int getScore(String type) {
		return 99;
	}

	private int getGrade(int year) {
		return 1;
	}
	
	public void sayHello() {
			System.out.println("student sayHello");
	}
	
	
}	
3.4 反射辅助类-Constructor类

​ 调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例:

public class TestDemo04 {

	public static void main(String[] args) throws Exception {

		// 获取构造方法Integer(int):
		Constructor cons1 = Integer.class.getConstructor(int.class);
		// 调用构造方法:
		Integer n1 = (Integer) cons1.newInstance(123);
		System.out.println(n1);

		// 获取构造方法Integer(String)
		Constructor cons2 = Integer.class.getConstructor(String.class);
		Integer n2 = (Integer) cons2.newInstance("456");
		System.out.println(n2);

	}

}

​ 其运行结果如下:

四、动态代理

​ 有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?

​ 这是可能的,因为Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。

​ 什么叫运行期动态创建?听起来好像很复杂。所谓动态代理,是和静态相对应的,代码如下:

public class TestDemo05 {
	
	public static void main(String[] args) {
		
		// 实例化invacationHandler实例
		MyInvocationHandler handler = new MyInvocationHandler(new HelloImpl());
		// 生产代理
		Hello proxyHello = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(), new Class[] {Hello.class}, handler);
		// 通过代理执行方法
		proxyHello.sayHello("zhangsan");
		
	}
	

}

class MyInvocationHandler implements InvocationHandler {

	private Object target;

	public MyInvocationHandler(Object target) {
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println(method);
		if(method.getName().equals("sayHello")) {
				// 调用sayHello方法
				System.out.println(args[0]);
				// 通过反射执行代理对象的方法
				method.invoke(target, args);
		}
		
		return null;
	}

}

​ 执行结果:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值