【java】反射基础

一、理解Class

  • 理解Class

        引子 :

        a. 当我们写好程序,在磁盘上表现为xx.java文件,这个文件有着静态存储结构,譬如类名、修饰符、属性、方法、构造方法、注释等等;

        b. 当我们编译这段代码的时候,生成xx.class字节码文件,这个字节码也存储在磁盘上,也有着静态存储结构,譬如前四个字节是魔数(证明其能被JVM识别)、后四个字节是版本号、接着是访问标志、常量池、属性表、方法表等等;

        c. 那当虚拟机把字节码加载进内存的时候,是如何存储它,是如何用加载进内存的字节码来创建一个对象的呢?
        当虚拟机把字节码加载进内存的时候,会将其转换为运行时数据结构并存放于方法区中,并为之在内存中创建java.lang.Class对象,作为这个类各种数据访问的接口”,这句话就好像“当我们new一个对象的时候,会创建其运行时数据结构并存放于堆内存中,并为之在栈内存中生成其引用对象,作为这个对象各种数据访问的接口”一样。java.lang.Class就好像栈内存中存放的引用,而方法区中的存储结构就好像这个对象在堆内存中的存储结构一样。这也就意味着,每一个描述现实问题的“类”都被虚拟机抽象成了一个java.lang.Class类,当某一个类的字节码被加载进内存的时候,就表明虚拟机为java.lang.Class类创建了一个对象;再举个例子,男人女人等各种各样的人我们都可以用Person类来表示,那么各种各样的类诸如时间类、人类、动物类对于虚拟机而言就可以用java.lang.Class类来表示;再举个例子,Person类描述所有人这种类都具有姓名、性别、身高等属性都具有哭、笑等方法,而Class类描述了所有类都具有属性、方法、构造函数等。java.lang.Class就是对所有类的抽象(描述类的类,就是Class)

        总之,就好像Person是所有人的抽象表达一样,java.lang.Class也是所有类的抽象表达。

        JDK文档:Class的实例表示正在运行 的java应用程序中的类或接口(也就是RTTI,runtime type infomation,运行时类型信息)

  • 深入Class

        Class类是所有类的抽象描述,某一个类在虚拟机中的具体表现则是Class的实例化。


        当一个类被加载进内存的时候,虚拟机会自动为其创建Class对象,在当次虚拟机中,该类的Class对象有且仅有一个,这个Class对象用于该类所有对象的创建,也就是该类所有实例具有相同的Class对象(如以下代码所示)。

package reflect;

/**
 * 理解java.lang.Class对象
 * @author reliveIT
 *
 */
public class Person {
	private String name;
	private String gender;
	private int age;
	
	public Person(){}
	public Person(String name, String gender, int age){
		this.name = name;
		this.gender = gender;
		this.age = age;
	}
	
	/*
	 * 省略get/set方法
	 */
	
	public void cry(){
		System.out.println("i'm cry");
	}
	
	public void laugh(){
		System.out.println("i'm laugh");
	}
	
	public static void main(String[] args) {
		/*
		 * Person类用于描述所有人都具有的特征
		 * Class类用于描述所有类都具有的特征
		 */
		/*
		 * 当一个具体的类被加载进内存中,JVM会自动为其创建一个Class对象
		 * 以后Person类会利用clazz来创建所有对象
		 */
		Class<?> clazz = Person.class;
		/*
		 * Person类创建了一个具体的对象man和woman,而man和woman都是根据clazz对象来创建的,Person的所有实例都是根据clazz对象来创建的
		 * 也就是所有的对象都具有相同的Class实例
		 */
		Person man = new Person("zhangsan", "male", 25);
		Person woman = new Person("xiaocui", "female", 22);
		/*
		 * 输出结果:都是true
		 */
		System.out.println(man.getClass() == clazz);
		System.out.println(woman.getClass() == clazz);
	}
}

二、获取Class实例

        当一个类被加载进虚拟机中(类加载器loadClass(String name)方法),类加载器会调用defineClass(String name, byte[] data, int off, int len)方法自动创建其Class对象(这个对象在本次虚拟机中只会被创建一次,有且仅有一个实例,这个实例将会被,后续该类的所有实例对象都通过这个Class对象来完成创建),而这个对象的出现,将成为java反射萌发的基础。

        如何从虚拟机中获取这个对象?java提供了三种方法获取这个对象的实例。首先,这个对象也是一种类型,其类型为java.lang.Class。从JDK文档或者源码中可以看到Class类并没有对外提供可以用来创建对象的构造方法,但是有一个static方法forName(String name);可以返回一个Class实例,这就是获取某一个类Class对象的方法之一,也是最常用的方法之一。

  • forName

        Class.forName(String)方法,传入一个全限定类名(包名+类名),返回一个Class实例。这种方式获取对象有什么特点?我们知道类加载的过程有五个步骤,分别是加载、验证、准备、解析、初始化,三种方法之中只有这一种方法会对加载的类进行初始化,而另外两种并没有对类进行初始化,只是进行了加载这一个步骤(如果没有加载,则进行加载并返回Class对象,否则直接返回Class对象,请看上一篇博文《【java】类加载机制》),如以下例子所示。

package reflect3;

public class ClassForNameTest {
	static {
		System.out.println("forName获取Class对象将会打印我,其他方式获取Class对象将不会打印我");
	}
	
	public static void main(String[] args) throws Exception {
		//方式一:Class.forName,static代码块将会被打印
		Class clazz = Class.forName("reflect3.ClassForNameTest");
		/*
		 * //方式二:类名.class获取,static代码块不会被打印
		Class clazz2 = ClassForNameTest.class;
		//方式三:对象.getClass(),getClass方法存在Object类中,static代码块将不会被打印
		Class clazz3 = new ClassForNameTest().getClass();
		*/
	}
}

  • getClass

         getClass方法在Object类中定义,也就意味着所有的java实例都具有该方法。这个方法要求先有对象,才能返回该对象的Class,与forName方式的区别在于类加载的时候是否会进行类初始化(不是实例化或对象初始化)。

  • class

         java规定所有的运行时类型都会有一个Class对象,基本类型也不例外(byte.class, short.class, int.class, long.class, float.class, double.class, boolean.class, void.class, char.class,另外所有具有相同类型和维度的数组也共享一个Class对象,例如int[]类型数组共享int[].class对象)。

package reflect3;

public class Demo {
	public static void main(String[] args) {
		int[] arr = new int[]{1, 3, 5, 7};
		System.out.println(arr.getClass());
		System.out.println(arr.getClass() == int[].class);
	}
}

三、反射

        首先剖析一下,一个类由哪些部分组成?属性Field,构造方法Constructor,方法Method,内部类class,注解Annotation,修饰符Modifier等。

        接着思考一下,如果我们对外暴露一个接口,只要实现了这个接口的插件都可以插进来被我们调用从而工作,但是谁来实现这个插件并不知道,也不知道他实现的类名、方法名等等。这时候就需要反射动态的获取这个类的运行时类型信息,从而newInstance,invoke Method等等。

        最后查看一下,既然Class类是对所有类的抽象描述,可以获得具体某一个类的运行时类型信息,Class的实例能够表示一个类的运行时数据结构,那么了解反射的入口就是java.lang.Class类。

        注意:要想获得一个类private部分,则需要getDeclaredXxx方法;要想使用private的部分,则需要先setAccessible(rue)之后才能使用,否则异常。另外,可以获取类中某一部分的指定对象,也可以获取类中某一部分的所有对象,例如getMethod方法会获得类中指定的一个方法对象,getMethods则会返回类中除了private修饰的所有方法对象,getDeclaredMethods则会返回类中包括private修饰的所有方法对象。

  • 通过反射获初始化某一个类的实例

        如果不知道需要进行初始化的类是什么,这时候就需要进行动态初始化。通过反射,有两种方式可以动态的得到某一个类的实例。

        a. 默认构造器:首先得到Class对象,调用其newInstance方法实例化指定类,这种方式要求不知名的类提供默认构造器,否则不能进行对象初始化。

package reflect3;

/**
 * 以java.lang.String类型为例,获取String的对象
 * @author reliveIT
 *
 */
public class InstanceDemo {
	/*
	 * 有三种方式可以获取Class对象
	 */
	public static void main(String[] args) throws Exception {
		/*
		 * 第一种 输出:class java.lang.String
		 */
		Object obj = Class.forName("java.lang.String").newInstance();
		/*
		 * 第二种 输出:class java.lang.String
		 */
		Object obj2= String.class.newInstance();
		/*
		 * 第三种 输出:class java.lang.String
		 */
		Object obj3 = new String().getClass().newInstance();
		
		System.out.println(obj.getClass());
		System.out.println(obj2.getClass());
		System.out.println(obj3.getClass());
	}
}
        b. 指定构造器:首先得到Class对象,通过其getConstructor方法返回指定构造器对象(Constructor类),利用返回的构造器对象的newInstance方法。通过这种方式,只要原程序中实现了的构造器都可以通过反射得到并调用,从而构造实例。
package reflect2;

import java.lang.reflect.Constructor;

import javax.swing.JFrame;

public class JFrame_Reflect_Demo {
	public static void main(String[] args) throws Exception {
		/*
		 * 1. 得到Class对象
		 * 2. 通过Class对象得到指定Constructor对象
		 * 3. 通过Constructor对象的newInstance方法构造实例
		 */
		Class<?> jFrameClazz = Class.forName("javax.swing.JFrame");
		Constructor<?> cons = jFrameClazz.getDeclaredConstructor(String.class);
		JFrame jFrame = (JFrame)cons.newInstance("反射得到JFrame实例");
		jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jFrame.setSize(150, 150);
		jFrame.setVisible(true);
	}
}

  • 通过反射获得某一个类的构造器

        Constructor类表示类的构造器。Class类表示所有类的抽象,一个类被抽象称为由属性、构造器、方法、注解、内部类等部分组成,而Constructor类则表示类其中的构造器部分。Class类中有getConstructor方法,该方法传入原程序中构造器声明参数类型的Class,返回一个指定的Constructor对象。例如,源程序中构造器要求传入一个字符串和整数,则getConstructor方法获取Construcor对象的时候,则要求传入String.class和int.class,如以下代码示例。

package reflect2;

import java.lang.reflect.Constructor;

public class ConstructorDemo {
	public static void main(String[] args) throws Exception {
		Class strClazz = Class.forName("java.lang.String");
		Constructor<String> cons = strClazz.getConstructor(byte[].class);
		String str = cons.newInstance("okc".getBytes());
		System.out.println(str + "--" + str.getClass().getName());
	}
}

  • 通过反射调用某一个类的方法

        Method类表示类的方法。通过Class对象的getMethod方法得到指定方法对象之后,用该方法对象的invoke方法则可以使用原类中定义的方法功能。需要指出的是invoke需要传入一个原类的实例作为参数,如果调用原类中静态方法,则传入null即可。

        1. 调用普通方法

        2. 调用私有方法

        3. 调用无参方法

        4. 调用静态方法

  • 通过反射修改某一个实例的属性

        Field类表示类的属性。通过Class对象的getField方法得到指定的属性,该方法返回一个Field对象,调用该对象的get和set方法能获得和修改原对象中的属性。当然,private修饰的属性,则需要先setAccessible(rue)之后才操作,否则异常。 

        通过反射还可以获取其他很多东西,关键在于理解,理解之后看一下JDK文档,基本就通了。  

四、实践——反射实现简单框架

        例子:通过配置文件动态的实例化用户配置的类。

        配置文件:只要是Collection的子类,则均可配置被使用:

#className=java.util.HashSet
className=java.util.ArrayList
        简单框架代码:

package reflect2;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Collection;
import java.util.Properties;

public class ReflectDemo {
	public static void main(String[] args) {
		try {
			reflect();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static void reflect() throws Exception {
		File file = new File("res/tmp.properties");
		if(!file.exists())
			throw new FileNotFoundException("配置文件未找到,请检查!");
		
		FileInputStream fis = new FileInputStream(file);
		Properties p = new Properties();
		p.load(fis);
		fis.close();
		
		String className = p.getProperty("className");
		Collection<String> c = (Collection<String>)Class.forName(className).newInstance();
		
		System.out.println(c.getClass().getTypeName());
	}
}

        反射其实很多内容,反射如果了解深入,对于后面框架部分的学习很有帮助。

附注:

       本文如有错漏,烦请不吝赐教,不胜感激!




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值