Java:反射机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L2VQe5KA-1596890077591)(F:\JianShu_material\Java\图片\反射\反射机制.png)]

1. Java反射机制概述

1.1 动态语言与反射

动态语言

是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构

主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang

反射

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息,我们可以通过这个对象看到类的结构。

这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射

1.2 反射提供功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法,包括私有
  • 在运行时处理注解
  • 生成动态代理

1.3 反射相关API

  • java.lang.Class:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造器
  • … …

2. 理解Class类并获取Class实例

2.1 java.lang.Class类的理解

  1. 类的加载过程
    • 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)
    • 接着我们使用java.exe命令对某个字节码文件进行解释运行,即把某个字节码文件加载到内存中,此过程称为类的加载
    • 加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例
  2. 换句话说,Class的实例就对应着一个运行时类
  3. 加载到内存中的运行时类,会缓存一定的时间,在此时间之前,我们可以通过不同的方式来获取此运行时类

2.2 获取Class实例

四种方法获取Class类的实例:

  • 方式一:调用运行时类的属性:.class
  • 方式二:通过运行时类的对象,调用getClass()
  • 方式三:调用Class的静态方法:forName(String classPath)
  • 方式四:使用类的加载器:ClassLoader (了解)
//导入的包有:import org.junit.Test;

public class ReflectionTest1 {
    @Test
    public void test1() throws ClassNotFoundException {
        //方式一:调用运行时类的属性:.class
        Class clazz1 = String.class;
        System.out.println(clazz1);//class java.lang.String

        //方式二:通过运行时类的对象,调用getClass()
        String s1 = new String();
        Class clazz2 = s1.getClass();
        System.out.println(clazz2);//class java.lang.String

        //方式三:调用Class的静态方法:forName(String classPath)
        Class clazz3 = Class.forName("java.lang.String");
        System.out.println(clazz3);//class java.lang.String

        System.out.println(clazz1 == clazz2);//true
        System.out.println(clazz1 == clazz3);//true

        //方式四:使用类的加载器:ClassLoader (了解)
        ClassLoader classLoader = ReflectionTest1.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("java.lang.String");
        System.out.println(clazz4);//class java.lang.String
        System.out.println(clazz1 == clazz4);//true
    }
}

除了类以外,接口、数组、枚举、注解基本数据类型、void也可以作为Class类的实例

3. 类的加载与ClassLoader的理解

3.1 类的加载

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-61uN2r6E-1596890077594)(F:\JianShu_material\Java\图片\反射\001.png)]

3.2 ClassLoader的理解

类加载器的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口

JVM 规范定义了如下类型的类的加载器:

  • 引导类加载器:该加载器无法获取
  • 扩展类加载器
  • 系统类加载器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-970aRHEH-1596890077601)(F:\JianShu_material\Java\图片\反射\002.png)]

//导入的包有:import org.junit.Test;

public class ClassLoaderTest {
    @Test
    public void test1(){
        //对于自定义类:使用系统类加载器进行加载
        ClassLoader classLoader1 = ClassLoaderTest.class.getClassLoader();

        //调用系统加载器的getParent():获取扩展类加载器
        System.out.println(classLoader1);//sun.misc.Launcher$AppClassLoader@18b4aac2
        ClassLoader classLoader2 = classLoader1.getParent();

        //调用扩展类加载器的getParent():无法获取引导类加载器
        //引导类加载器主要负责加载Java的核心类库,无法加载自定义类
        System.out.println(classLoader2);//sun.misc.Launcher$ExtClassLoader@a09ee92
        ClassLoader classLoader3 = classLoader2.getParent();
        System.out.println(classLoader3);//null
    }
}

4. 创建运行时类的对象

通过反射,创建运行时类的对象

//导入的包有:import org.junit.Test;

public class ClassLoaderTest {
    @Test
    public void test2() throws IllegalAccessException, InstantiationException {
        Class<String> clazz = String.class;
        /*
        * newInstance():调用此方法,创建对应的运行时类的对象,其内部调用了运行时类的空参构造器
        *
        * 要想此方法正常的创建运行时类的对象,要求
        * 1.运行时类必须提供空参的构造器
        * 2.空参的构造器的访问权限足够,通常设置为public
        */
        //由于创建Class类的对象时,规定了泛型,所以此处自动转换类型
        String obj = clazz.newInstance();
        System.out.println(obj);
    }
}

5. 获取运行时类的完整结构

5.1 获取属性结构

当我们创建好Class对象后,可以获得其所有属性

//导入的包有:import org.junit.Test;import java.lang.reflect.Field;import java.lang.reflect.Modifier;

public class Test1 {
    @Test
    public void test1(){
        Class clazz = String.class;

        //获取属性结构
        //getFields():获取当前运行时类及其父类中声明为public访问权限的属性
        Field[] fields = clazz.getFields();
        for(Field f : fields){
            System.out.println(f);
        }

        //getDeclaredFields():获取当前运行时类中声明的所有属性,不包含父类中声明的属性
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields){
            System.out.println(f);
        }
    }
    @Test
    public void test2(){
        Class clazz = String.class;
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields){
            //1.获取权限修饰符
            int modifier = f.getModifiers();
            System.out.println(Modifier.toString(modifier));

            //2.获取数据类型
            Class type = f.getType();
            System.out.println(type.getName());

            //3.获取变量名
            String fName = f.getName();
            System.out.println(fName);
        }
    }
}

5.2 获取方法结构

当我们创建好Class对象后,可以获得其所有方法

//导入的包有:import org.junit.Test;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.lang.reflect.Modifier;

public class Test1 {
    @Test
    public void test3(){
        Class clazz = String.class;
        
        //getMethods():获取当前运行时类一起所有父类中声明为public权限的方法
        Method[] methods = clazz.getMethods();
        for(Method m : methods){
            System.out.println(m);
        }
        
        //getDeclaredMethods():获取当前运行时类中声明的所有方法,不包含父类中声明的方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for(Method m : declaredMethods){
            System.out.println(m);
        }
    }
}

我们也可以获取方法的权限修饰符,返回值类型,方法名,参数,注解,异常,由于使用不多,这里不展开叙述

5.3 获取构造器结构

当我们创建好Class对象后,可以获得其构造器

//导入的包有:import org.junit.Test;import java.lang.reflect.Constructor;

public class Test1 {
    @Test
    public void test4(){
        //getConstructors():获取当前运行时类中声明为public的构造器
        Class clazz = String.class;
        Constructor[] constructors = clazz.getConstructors();
        for(Constructor c : constructors){
            System.out.println(c);
        }

        //getDeclaredConstructors():获取当前运行时类中声明的所有构造器
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for(Constructor c : declaredConstructors){
            System.out.println(c);
        }
    }
}

还可以获得运行时类的父类及其父类的泛型、接口、所在包、注解,这里不在一一演示

6. 调用运行时类的指定结构

6.1 调用运行时类的指定属性

//导入的包有:import org.junit.Test;import java.lang.reflect.Field;

//现在假设有一Person类,具有一般类所具有的属性,方法,构造器
public class ReflectionTest2 {
    @Test
    public void test1() throws Exception {
        Class clazz = Person.class;

        //创建运行时类的对象
        Person p = (Person) clazz.newInstance();

        //1.getDeclaredField(String name):获取运行时类中指定变量名的属性
        Field name = clazz.getDeclaredField("name");
        //还有一种方式获取运行时类中的指定变量,但一般不使用,因为只能获得Public属性
      //Field name = clazz.getFueld("name")


        //2.保证当前属性时可访问的
        name.setAccessible(true);
        //3.获取、设置指定对象的此属性
        name.set(p,"Tom");
        //4.输出当前属性的值
        System.out.println(name.get(p));
    }
}

6.2 调用运行时类的指定方法

//导入的包有:import org.junit.Test;import java.lang.reflect.Method;

//现在假设有一Person类,具有一般类所具有的属性,方法,构造器
public class ReflectionTest2 {
    @Test
    public void test2() throws Exception {
        Class clazz = Person.class;

        //创建运行时类的对象
        Person p = (Person) clazz.newInstance();
        /*
         1.获取某个指定方法
         getDeclaredMethod():参数1:指明获取方法的名称,参数2:指明获取的方法的形参列表
         */
        Method show = clazz.getDeclaredMethod("show", String.class);
        //2.保证当前方法是可访问的
        show.setAccessible(true);
        /*
        3.调用方法的invoke():参数1:方法的调用者,参数2:给方法形参赋值的实参
        invoke()的返回值即为对应类中调用的方法的返回值
        若调用运行时类的静态方法,则传入的参数1为Person.class
         */
        Object returnValue = show.invoke(p, "China");
    }
}

还可以调用运行时类的指定构造器,但是用非常少,这里不再演示

7. 反射的应用:动态代理

7.1 代理模式概述

代理设计模式的原理

  • 使用一个代理将对象包装起来, 然后用该代理对象取代原始对象
  • 任何对原始对象的调用都要通过代理
  • 代理对象决定是否以及何时将方法调用转到原始对象上

代理模式分为静态和动态

  • 静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理
  • 动态代理,是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象,可以更加灵活和统一的处理众多的方法

7.2 静态代理举例

interface ClothFactory{
    void produceCloth();
}
//代理类
class ProxyClothFactory implements ClothFactory{

    private ClothFactory factory;//用被代理对象进行实例化
    public ProxyClothFactory(ClothFactory factory){
        this.factory = factory;
    }
    @Override
    public void produceCloth() {
        System.out.println("代理工厂做准备工作");
        factory.produceCloth();
        System.out.println("代理工厂做后续工作");
    }
}
//被代理类
class NikeClothFactory implements ClothFactory{

    @Override
    public void produceCloth() {
        System.out.println("Nike工厂生产一批运动服");
    }
}
public class StaticProxyTest {
    public static void main(String[] args) {
        //创建被代理类的对象
        NikeClothFactory nike = new NikeClothFactory();
        //创建代理类的对象
        ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nike);

        proxyClothFactory.produceCloth();
    }
}
/*代理工厂做准备工作
Nike工厂生产一批运动服
代理工厂做后续工作*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值