Java能够分析类能力的程序-反射

一、为什么要学习反射

我们为什么要用反射,这主要是反射的动态性决定的,由于反射可以实现动态创建对象,这就很大程度发挥了java的灵活性,降低了程序调用的耦合性,使系统可以更加的灵活,可以更好的应对变化。而且反射式学习Java框架的基础,在Java框架的底层我们可以看到很多实现都是基于反射的。

二、我们能用反射干哪些事情

在运行时判断任意一个对象所属的类

在运行时构造任意一个类的对象

在运行时判断任意一个类所具有的成员变量和方法

在运行时调用任意一个对象的成员变量和方法

生成动态代理

跟反射相关的常用类主要有:
(1) java.lang.Class:代表一个类
(2)java.lang.reflect.Method:代表类的方法
(3) java.lang.reflect.Field:代表类的成员变量
(4)java.lang.reflect.Constructor:代表类的构造方法
下面一个个讲有什么用,怎么用。

三、怎么用

1、what‘s Class?
Class是一个类,是反射机制的源头。一般我们在创建一个对象的时候都是先通过构造一个类,在创建一个这个类的对象,而反射刚好就是这个的反过程。我们可以通过指导对象来找到它属于哪个类的,并且获取到这个类的信息。
在这里插入图片描述通过反射我们可以得到的信息:某个类的属性、方法、构造器、类实现了哪些接口、父类是什么。对于每个类而言,JRE都为其保留了一个不变的Class类型的对象,一个Class对象包含了特定某个类的有关信息。
Class本身也是一个类,每个类在JVM中只有有一个Class实例,每个Class对象对应的是一个加载到JVM中的一个.class文件,每个类的实例都会记得自己是由哪个Class实例所生成的。
2、类加载器
当我们程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。
在这里插入图片描述ClassLoader(类加载器)是用来把类(.class)装在进入内存的,JVM在运行时会产生3个类加载器组成的初始化加载器层次结构 ,如下图所示:
在这里插入图片描述

在这里插入图片描述下面我们通过程序来看一下这三个加载器的执行顺序和功能

package java_1;

import java.util.Scanner;
import java.util.*;

public class ss {

	public static void main(String[] args) throws Exception
	{
		ClassLoader loader1 = ClassLoader.getSystemClassLoader();
		System.out.println("系统类加载器为:"+loader1);
		//2.获取系统类加载器的父类加载器,即扩展类加载器
		ClassLoader loader2 = loader1.getParent();
		System.out.println("扩展类加载器:"+loader2);
		 
		//3.获取扩展类加载器的父类加载器,即引导类加载器
		ClassLoader loader3 = loader2.getParent();
		System.out.println("引导类加载器:"+loader3);
		 
		//4.测试当前类由哪个类加载器进行加载
		Class class1 = Person.class;
		ClassLoader loader4= class1.getClassLoader();
		System.out.println("当前类的类加载器:"+loader4);
		 
		//5.测试JDK提供的String类由哪个类加载器加载
		String className= "java.lang.String";
		Class class2 = Class.forName(className);
		ClassLoader loader5= class2.getClassLoader();
		System.out.println("JDK提供的String类加载器:"+loader5);

}
}
class Person{
	public Person(String name,int age) {
		this.name = name;
		this.age = age;
	}
	private String name;
	private int age;
}

运行结果:
系统类加载器为:sun.misc.Launcher$AppClassLoader@73d16e93
扩展类加载器:sun.misc.Launcher$ExtClassLoader@15db9742
引导类加载器:null
当前类的类加载器:sun.misc.Launcher$AppClassLoader@73d16e93
JDK提供的String类加载器:null

通过输出的结果我们可以看到我们自己定义的类是通过系统类加载器加载的。通过上图我们可以看到系统类加载器的上一层是拓展类加载器,再上一层是引导类加载器,引导类加载器是不允许我们访问的,从输出结果为空就可以看出。关于加载器的内容可以参考 https://blog.csdn.net/javazejian/article/details/73413292,
通过上图我们可以知道类加载器之间的关系为:
启动类加载器,由C++实现,没有父类。

拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为null。

系统类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader。

自定义类加载器,父类加载器肯定为AppClassLoader。

3、如何得到Class对象?
获取Class对象主要有4中方法
(1)用类的静态属性class,用在已知类

Class class1 = Person.class;
System.out.println(class1);

(2)用getClass()方法,用在已知类的对象

Person c = new Person("tang",12);
Class class1 = c.getClass();
		

(3)用forName()方法,已知Person类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取。

String s = " java_1.Person";
Class class1 = Class.forName(s);

(4)通过类加载器

Person c = new Person("tang",12);
String s = "java_1.Person";
ClassLoader className =c.getClass().getClassLoader();
Class class1 = className.loadClass(s);

以上四个的运行结果都是
class java_1.Person
4、java.lang.reflect包中的三个类,Method,Field,Constructor
前面说到这三个分别用于描述类的方法,域和构造器。下面用一个例子来说明他们的作用

package reflect;

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


public class ReflectionTest {
	public static void main(String[] args) {
		String name;
//		System.out.println(args.length);
//		for(int i=0;i<args.length;i++)
//		{
//			System.out.println(args[i]+" ");
//		}
		if(args.length>0)name=args[0];
		else
		{
			Scanner in = new Scanner(System.in);
			System.out.println("Enter class name(java.util.Date):");
			name = in.next();
		}
		try
		{
			Class c1 = Class.forName(name);//将字符转成类
			Class superc1 = c1.getSuperclass();//返回c1的父类
			//返回一个整型数值,用不同的位开关描述public和static这样的修饰符使用情况
			String modifiers = Modifier.toString(c1.getModifiers());
			if(modifiers.length()>0)System.out.println(modifiers+" ");
			System.out.println("class "+name);
			//判断它有父类
			if(superc1!=null&&superc1!=Object.class)System.out.print("extends"+superc1.getName());//getName.返回类的名称
			System.out.print("\n{\n");
			System.out.print("获取类的所有构造函数:\n");
			printConstructors(c1);//获取到所有的构造函数和构造函数里面的参数类型
			System.out.println();
			System.out.print("获取类的所有方法:\n");
			printMethods(c1);//获取到类中所有的方法
			System.out.println();
			System.out.print("获取类的所有域:\n");
			printFields(c1);//获取类中的域
			System.out.println("}");
		}
		catch(ClassNotFoundException e)
		{
			e.printStackTrace();
		}
		System.exit(0);
	}
	//描述类的域
	public static void printFields(Class c1) {
		// TODO Auto-generated method stub
		Field[] fields = c1.getDeclaredFields();
		for(Field f:fields)
		{
			Class type = f.getType();//这是获取域的类型
			String name = f.getName();
			System.out.println(" ");
			String modifiers = Modifier.toString(f.getModifiers());
			if(modifiers.length()>0)System.out.println(modifiers+" ");
			System.out.println(type.getName()+" "+name+";");
			
		}
		
	}
	//描述类的方法
	private static void printMethods(Class c1) {
		// TODO Auto-generated method stub
		Method[] methods = c1.getDeclaredMethods();
		for(Method m:methods)
		{
			Class retType = m.getReturnType();//获取返回的参数类型
			String name = m.getName();
			System.out.println("  ");
			String modifiers = Modifier.toString(m.getModifiers());
			if(modifiers.length()>0)System.out.println(modifiers+" ");
			System.out.println(retType.getName()+" "+name+"(");
			Class[] paramTypes = m.getParameterTypes();
			for(int j=0;j<paramTypes.length;j++)
			{
				if(j>0)System.out.println(", ");
				System.out.println(paramTypes[j].getName());
				
			}
			System.out.println(");");
		}
	}
	//描述类的构造函数
	private static void printConstructors(Class c1) {
		// TODO Auto-generated method stub
		//返回它所有的构造函数放在数组中
		Constructor[] constructors = c1.getConstructors();
		for(Constructor c:constructors)
		{
			//获得方法名
			String name = c.getName();
			System.out.println(" ");
			String modifiers = Modifier.toString(c.getModifiers());
			if(modifiers.length()>0)System.out.println(modifiers+" ");
			System.out.println(name+"(");
			Class[] paramTypes = c.getParameterTypes();//获取参数类型
			for(int j=0;j<paramTypes.length;j++)
			{
				if(j>0)System.out.println(", ");
				System.out.println(paramTypes[j].getName());
				
			}
			System.out.println("),");
		}
	}

}

方法:
<1>
(1)

Field[] getFields(),
Method[] getMethods(),
Constructor[] getConstructors()

他们分别能够返回的是一个包含Field对象、Method对象和Constructor对象的数组,包含所有的公有域,方法和构造器和从父类继承来的公有域、方法和构造器。
(2)

Field[] getDeclaredFields(),//返回包含Field对象的数组,如果这个类没有域或者是基本类型或数组类型,则返回Null
Method[] getDeclaredMethods(),//返回这个类或接口的所有方法
Constructor[] getDeclaredConstructors()//返回这个类的所有构造器

这两个的区别在于有没有返回从父类那里继承下来的

<2> String getName()
返回一个用于描述构造器、方法或域名的字符串。
比如

Constructor[] constructors = c1.getConstructors();
		for(Constructor c:constructors)
			//获得方法名
			String name = c.getName();

在这一句中就会返回构造器的名称
<3> int getModifiers()
返回一个用于描述构造器、方法或域的修饰符的整型数值,使用Modefier类中的这个方法可以分析这个返回值。

Constructor[] constructors = c1.getConstructors();
		for(Constructor c:constructors)
			String modifiers = Modifier.toString(c.getModifiers());
比如我们获取到的是一个
public Double(){
}的构造器,这个返回的就是public

<4> Class[] getParameterTypes()(只存在Method和Constructor中)
返回一个用于描述参数类型的Class对象数组

Constructor[] constructors = c1.getConstructors();
for(Constructor c:constructors)
	Class[] paramTypes = c.getParameterTypes();//获取参数类型

比如我们获得一个Public Double(int a,String b){}的构造器
则返回的是 int和String

<5>getReturnType()(只存在Method中)

Method[] methods = c1.getDeclaredMethods();
for(Method m:methods)
	Class retType = m.getReturnType();//获取返回的参数类型
	
如果在Double类中有一个 public int getDate(Sting a){}这样的方法,则返回int

<6>getType()(fields中的)

Field[] fields = c1.getDeclaredFields();
		for(Field f:fields)
			Class type = f.getType();//这是获取域的类型
如果在类中有一个private int name;
返回int

<7>setAccessible()

Person harry = new Person("Harry Hacker",18);
Class c1 = harry.getClass();
//f.setAccessible(true);
Field f = c1.getDeclaredField("name");
Object v = f.get(harry);

在这个代码中我们想要用域的字段来获取对象的值,但是在类中,域是一个private,直接用get调用时无法获取到的,上面的代码会抛出一个错误

IllegalAccessException

所以我们就可以用setAccessible()来修改访问控制
在上面添加

f.setAccessible(true);

<8>Object newInstance(Object[] args)和newInstance(),一个有参一个无参

创建 Class 对象所对应的那个类的对象: 调用 Class 对象的 newInstance() 方法

Class c = Person.class;
Object object = c.newInstance();

<9> public Object invoke(Object,implicitParameter,Object[] explicitParamenters)
调用这个来执行方法,但是invoke的参数和返回值都是Object类型的,这就意味着必须进行多次的类型转换。这样会使编译器错过检查代码的机会,所以最好不要用这种方式来执行方法,应该用接口和内部类。

四、动态代理与AOP的结合—反射典例

五、Java反射和CGLIB原理及其异同

后面学习到再补充。。。。。

在写这些内容的时候参考了这个大佬写的 https://blog.csdn.net/zwk626542417/article/details/46240797

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值