不忘初心
方得始终

Java基础-反射

反射是框架设计的灵魂

使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码)

一、先理解JVM的加载机制

首先我们了解一下JVM,什么是JVM,Java的虚拟机,java之所以能跨平台就是因为这个东西,你可以理解成一个进程,程序,只不过他的作用是用来跑你的代码的。

上图是java的内存模型,我们关注的点,一个方法区,一个栈,一个堆,初学的时候老师不深入的话只告诉你java的内存分为堆和栈,易懂点吧!

方法区存的是类的信息,不是存类对象的,建议看一下JVM内存分配,类加载器加载类是通过方法区上类的信息在堆上创建一个类的Class对象,这个Class对象是唯一的,由JVM保证唯一,之后对这个类的创建都是根据这个Class对象来操作的。

类存在方法区中,类的class对象存在于堆中,这个class对象会作为运行时创建该类对象的模版。这个class对象是唯一对应该类的,要区分所谓的实例和class对象。为什么需要class对象,你想下如果一个加载进方法区的类,在jvm运行时是动态加载进来的,没有这个class对象你思考该如何访问一个未知的类并创建对象呢?没错就是这个class作为访问接口。


假如你写了一段代码:Object o=new Object();运行了起来!

首先JVM会启动,你的代码会编译成一个.class文件,然后被类加载器加载进jvm的内存中,你的类Object加载到方法区中,创建了Object类的class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,初始化也就是代码:new Object()。

上面的流程就是你自己写好的代码扔给jvm去跑,跑完就over了,jvm关闭,你的程序也停止了。

为什么要讲这个呢?因为要理解反射必须知道它在什么场景下使用。

想想上面的程序对象是自己new的,程序相当于写死了给jvm去跑。假如一个服务器上突然遇到某个请求哦要用到某个类,哎呀但没加载进jvm,是不是要停下来自己写段代码,new一下,哦,启动一下服务器,(脑残)!

二、反射的概述

反射之中包含了一个“反”的概念,所以要想解释反射就必须先从“正”开始解释,一般而言,当用户使用一个类的时候,应该先知道这个类,而后通过这个类产生实例化对象,但是“反”指的是通过对象找到类。

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

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象。

当我们的程序在运行时,需要动态的加载一些类这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载,这样的好处对于服务器来说不言而喻,举个例子我们的项目底层有时是用mysql,有时用oracle,需要动态地根据实际情况加载驱动类,这个时候反射就有用了,假设 com.java.dbtest.myqlConnection,com.java.dbtest.oracleConnection这两个类我们要用,这时候我们的程序就写得比较动态化,通过Class tc = Class.forName(“com.java.dbtest.TestConnection”);通过类的全类名让jvm在服务器中找到并加载这个类,而如果是oracle则传入的参数就变成另一个了。这时候就可以看到反射的好处了,这个动态性就体现出java的特性了!举多个例子,大家如果接触过spring,会发现当你配置各种各样的bean时,是以配置文件的形式配置的,你需要用到哪些bean就配哪些,spring容器就会根据你的需求去动态加载,你的程序就能健壮地运行。

反射就是把java类中的各种成分映射成一个个的Java对象

例如:

一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。

其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述。

如图是类的正常加载过程:反射的原理在与class对象。

熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。

Java的反射机制的实现要借助于4个类:

class代表的是类对象
Constructor-类的构造器对象
Field-类的属性对象
Method-类的方法对象

通过这四个对象我们可以粗略的看到一个类的各个组 成部分。

在Java运行时环境中,对于任意一个类,可以知道这个类有哪些属性和方法。对于任意一个对象,可以调用它的任意一个方法。这种动态获取类的信息以及动态调用对象的方法的功能来自于Java语言的反射(Reflection)机制。

Java 反射机制主要提供了以下功能:

在运行时判断任意一个对象所属的类。
在运行时构造任意一个类的对象。
在运行时判断任意一个类所具有的成员变量和方法。
在运行时调用任意一个对象的方法

使用与不使用时的区别

不使用反射机制:直接在代码中new出自己需要的类,这种静态编译的方法会在编译的时候绑定对象,虽然这样运行起来比反射要快,但是如果一旦需要在项目中修改某个类,那么多则几百处代码都要修改一遍,而且这个类的方法和属性也有可能要涉及修改,麻烦不断,这绝对不是我们想要看到的。

使用反射机制:相比较静态编译反射机制是动态编译,在运行的时候确定类并且实例化、调用方法。反射发挥了java编程的灵活性,如果此时类需要修改,那么只需要修改最开始反射类的那一句话即可,或者如果你喜欢,甚至可以把有关反射的内容单独作为一个配置文件用xml写出来,如果需要修改,直接在配置文件中修改反射的类名即可。

使用方法:

在讲解其真正使用之前还有一个需要先解释的问题,既然Class是所有反射操作的源头,那么这个类肯定是最为重要的,而如果要想取得这个类的实例化对象,Java中定义了三种方式:

方式一:通过Object类的getClass()方法取得,基本不用:

package cn.mldn.demo;
class Person {}
public class TestDemo {
public static void main(String[] args) throws Exception {
Person per = new Person() ; // 正着操作
Class<?> cls = per.getClass() ; // 取得Class对象
System.out.println(cls.getName()); // 反着来
}
}

方式二:使用“类.class”取得,在Hibernate开发的时候会使用:

package cn.mldn.demo;
class Person {}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Person.class; // 取得Class对象
System.out.println(cls.getName()); // 反着来
}
}

方式三:使用Class类内部定义的一个static方法,主要使用:

·取得Class类对象:
public static Class<?> forName(String className) throws Class Not Found Exception;
package cn.mldn.demo;
class Person {}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
System.out.println(cls.getName()); // 反着来
}
}

那么现在一个新的问题又来了,取得了Class类的对象有什么用处呢?对于对象的实例化操作之前一直依靠构造方法和关键字new完成,可是有了Class类对象之后,现在又提供了另外一种对象的实例化方法:

·通过反射实例化对象:
public T newInstance() throws Instantiation Exception,IllegalAccessException;

范例:通过反射实例化对象:

package cn.mldn.demo;
class Person {
@Override
public String toString() {
return"Person Class Instance .";
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
Object obj = cls.newInstance() ; // 实例化对象,和使用关键字new一样
Person per = (Person) obj ; // 向下转型
System.out.println(per);
}
}

#### 那么现在可以发现,对于对象的实例化操作,除了使用关键字new之外又多了一个反射机制操作,而且这个操作要比之前使用的new复杂一些,可是有什么用?

对于程序的开发模式之前一直强调:尽量减少耦合,而减少耦合的最好做法是使用接口,但是就算使用了接口也逃不出关键字new,所以实际上new是造成耦合的关键元凶。

范例:回顾一下之前所编写的工厂设计模式:

package cn.mldn.demo;
interface Fruit {
public void eat() ;
}
class Apple implements Fruit {
public void eat() {
System.out.println("吃苹果。");
};
}
class Factory {
public static Fruit getInstance(String className) {
if("apple".equals(className)){
return new Apple() ;
}
return null;
}
}
public class FactoryDemo {
public static void main(String[] args) {
Fruit f = Factory.getInstance("apple") ;
f.eat() ;
}
}

以上为之前所编写最简单的工厂设计模式,但是在这个工厂设计模式之中有一个最大的问题:

如果现在接口的子类增加了,那么工厂类肯定需要修改,这是它所面临的最大问题,而这个最大问题造成的关键性的病因是new,那么如果说现在不使用关键字new了,变为了反射机制呢?

反射机制实例化对象的时候实际上只需要“包.类”就可以,于是根据此操作,修改工厂设计模式:

package cn.mldn.demo;
interface Fruit {
public void eat() ;
}
class Apple implements Fruit {
public void eat() {
System.out.println("吃苹果。");
};
}
class Orange implements Fruit {
public void eat() {
System.out.println("吃橘子。");
};
}
class Factory {
public static Fruit getInstance(String className) {
Fruit f = null;
try{
f = (Fruit) Class.forName(className).newInstance() ;
} catch(Exception e) {
e.printStackTrace();
}
return f ;
}
}
public class FactoryDemo {
public static void main(String[] args) {
Fruit f = Factory.getInstance("cn.mldn.demo.Orange") ;
f.eat() ;
}
}

发现,这个时候即使增加了接口的子类,工厂类照样可以完成对象的实例化操作,这个才是真正的工厂类,可以应对于所有的变化。如果单独从开发角度而言,与开发者关系不大,但是对于日后学习的一些框架技术这个就是它实现的命脉,在日后的程序开发上,如果发现操作的过程之中需要传递了一个完整的“包.类”名称的时候几乎都是反射机制作用。

以上只是利用了Class类作为了反射实例化对象的基本应用,但是对于一个实例化对象而言,它需要调用类之中的构造方法、普通方法、属性,而这些操作都可以通过反射机制完成。

使用反射机制也可以取得类之中的构造方法,这个方法在Class类之中已经明确定义了以下两个方法:

取得一个类的全部构造:

public Constructor<?>[] getConstructors() throws SecurityException

取得一个类的指定参数构造:

public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException

现在发现以上的两个方法返回的都是java.lang.reflect.Constructor类的对象。

范例:取得一个类之中的全部构造:

package cn.mldn.demo;
import java.lang.reflect.Constructor;
class Person { 
public Person() {}
public Person(String name) {}
public Person(String name,intage) {}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
Constructor<?> cons [] = cls.getConstructors() ; // 取得全部构造
for(intx = 0; x < cons.length; x++) {
System.out.println(cons[x]);
}
}
}

验证:在之前强调的一个简单Java类必须存在一个无参构造方法

范例:观察没有无参构造的情况:

package cn.mldn.demo;
class Person { // CTRL + K
private String name;
private intage;
public Person(String name,intage) {
this.name= name ;
this.age= age ;
}
@Override
public String toString() {
return "Person [name="+ name+ ", age="+ age+ "]";
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
Object obj = cls.newInstance(); // 实例化对象
System.out.println(obj);
}
}

此时程序运行的时候出现了错误提示“java.lang.InstantiationException”,因为以上的方式使用反射实例化对象时需要的是类之中要提供无参构造方法,但是现在既然没有了无参构造方法,那么就必须明确的找到一个构造方法,而后利用Constructor类之中的新方法实例化对象:

·实例化对象:

public T newInstance(Object... initargs) throws InstantiationException,IllegalAccessException,IllegalArgumentException,InvocationTargetException
package cn.mldn.demo;
import java.lang.reflect.Constructor;
class Person { // CTRL + K
private String name;
private intage;
public Person(String name,intage) {
this.name= name ;
this.age= age ;
}
@Override
public String toString() {
return "Person [name="+ name+ ", age="+ age+ "]";
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
// 取得指定参数类型的构造方法
Constructor<?> cons = cls.getConstructor(String.class,int.class) ;
Object obj = cons.newInstance("张三", 20); // 为构造方法传递参数
System.out.println(obj);
}
}

很明显,调用无参构造方法实例化对象要比调用有参构造的更加简单、方便,所以在日后的所有开发之中,凡是有简单Java类出现的地方,都一定要提供无参构造。

调用普通方法

当取得了一个类实例化对象之后,下面最需要调用的肯定是类之中的方法,所以可以继续使用Class类取得一个类中所定义的方法定义:

·取得全部方法:

public Method[] getMethods()throws SecurityException;

·取得指定方法:

public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException

发现以上的方法返回的都是java.lang.reflect.Method类的对象。

范例:取得一个类之中所定义的全部方法:

package cn.mldn.demo;
import java.lang.reflect.Method;
class Person {
private String name;
public void setName(String name) {
this.name= name;
}
public String getName() {
return name;
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
Method met [] = cls.getMethods() ; // 取得全部方法
for(intx = 0; x < met.length; x++) {
System.out.println(met[x]);
}
}
}

但是取得了Method类对象最大的作用不再于方法的列出(方法的列出都在开发工具上使用了),但是对于取得了Method类对象之后还有一个最大的功能,就是可以利用反射调用类中的方法:

·调用方法:

public Object invoke(Object obj, Object... args) throws IllegalAccessException,IllegalArgumentException,InvocationTargetException

之前调用类中方法的时候使用的都是“对象.方法”,但是现在有了反射之后,可以直接利用Object类调用指定子类的操作方法。(同时解释一下,为什么setter、getter方法的命名要求如此严格)。

范例:利用反射调用Person类之中的setName()、getName()方法:

package cn.mldn.demo;
import java.lang.reflect.Method;
class Person {
private String name;
public void setName(String name) {
this.name= name;
}
public String getName() {
return name;
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
Object obj = cls.newInstance(); // 实例化对象,没有向Person转型
String attribute = "name"; // 要调用类之中的属性
Method setMet = cls.getMethod("set"+ initcap(attribute), String.class);// setName()
Method getMet = cls.getMethod("get"+ initcap(attribute));// getName()
setMet.invoke(obj, "张三") ; // 等价于:Person对象.setName("张三")
System.out.println(getMet.invoke(obj));// 等价于:Person对象.getName()
}
public static String initcap(String str) {
return str.substring(0,1).toUpperCase().concat(str.substring(1)) ;
}
}

在日后的所有框架技术开发之中,简单Java类都是如此应用的,所以必须按照标准进行。

调用成员

类之中最后一个组成部分就是成员(Field,也可以称为属性),如果要通过反射取得类的成员可以使用方法如下:

·取得本类的全部成员:

public Field[] getDeclaredFields() throws SecurityException;

·取得指定的成员:

public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException;

这两个方法的返回值类型是java.lang.reflect.Field类的对象,下面首先观察如何取得一个类之中的全部属性。

范例:取得一个类之中的全部属性:

package cn.mldn.demo;
import java.lang.reflect.Field;
class Person {
private String name;
}
public class TestDemo {
public static void main(String[] args) throwsException {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
Field field [] = cls.getDeclaredFields() ; // 取得全部属性
for(intx = 0; x < field.length; x++) {
System.out.println(field[x]);
}
}
}

但是找到Field实际上就找到了一个很有意思的操作,在Field类之中提供了两个方法:

·设置属性内容(类似于:对象.属性= 内容):

public void set(Object obj,Object value)throws IllegalArgumentException,IllegalAccessException;

·取得属性内容(类似于:对象.属性):

public Object get(Object obj)throws IllegalArgumentException,IllegalAccessException

可是从类的开发要求而言,一直都强调类之中的属性必须封装,所以现在调用之前要想办法解除封装。

·解除封装:

public void setAccessible(boolean flag) throws SecurityException;

范例:利用反射操作类中的属性:

package cn.mldn.demo;
import java.lang.reflect.Field;
class Person {
private String name;
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class对象
Object obj = cls.newInstance(); // 对象实例化属性才会分配空间
Field nameField = cls.getDeclaredField("name") ; // 找到name属性
nameField.setAccessible(true) ; // 解除封装了
nameField.set(obj, "张三") ; // Person对象.name = "张三"
System.out.println(nameField.get(obj)); // Person对象.name
}
}
赞(0) 支持我
未经允许不得转载:woytu » Java基础-反射

相关推荐

  • 暂无文章

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址