美文网首页
Java 反射

Java 反射

作者: wind_sky | 来源:发表于2019-08-07 19:26 被阅读0次

一. 什么是反射

反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。当我们编写完一个Java项目之后,每个java文件都会被编译成一个.class文件,这些Class对象承载了这个类的所有信息,包括父类、接口、构造函数、方法、属性等,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。

二. 反射有哪些功能

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

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

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

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

  • 运行时改变一个类的属性的值;

  • 在运行时调用任意一个对象的方法;

  • 生成动态代理。

三. 如何使用相关API

1. 反射相关的类

Class —— 使用反射的最基本类

Constructor —— 表示一个类的构造器的类

Method —— 表示一个类的方法的类

Field —— 表示一个类属性的类

Member —— 成员是一种接口,反映有关单个成员(字段或方法)或构造方法的标识信息

Modifier —— 表示成员权限修饰符的类

Proxy —— 提供动态地生成代理类和类实例的静态方法

2. 各主要类的常用API
(1)Class 类

获取Class实例

Class clazz = TestClass.class;      //通过类名.class
 
Class clazz = obj.getClass();       //通过对象的getClass方法
 
Class clazz = Class.forName("com.whx.test.TestClass");  //通过Class的forName方法,传入一个类的全限定名

获取一个对象的父类与实现的接口

Class<?> parentClass = clazz.getSuperclass();   // 取得父类  

Class<?> intes[] = clazz.getInterfaces();       // 获取所有的接口

获取某个类中的构造方法

Constructor<T> con = clazz.getConstructor(Class<?>... parameterTypes);  //根据给定参数获得构造方法对象,仅public
 
Constructor<T> con = clazz.getDeclaredConstructor(Class<?>... parameterTypes);  // 根据给定参数获得构造方法对象,所有
 
Constructor<?>[] cons = clazz.getConstructors();    // 获取到所有public构造方法对象
 
Constructor<?>[] cons = clazz.getDeclaredConstructors();    // 获取到所有构造方法对象

获取某个类中的属性

Field field = clazz.getDeclaredField(String name);      //根据属性名获取到属性对象,任意访问权限
 
Field field = clazz.getField(String name);      //根据属性名获取到属性对象,仅public
 
Field[] field = clazz.getDeclaredFields();      //获得当前类的所有属性
 
Field[] field = clazz.getFields();      //获得所有public属性,包括继承而来的public属性

获取某个类中的方法

Method method = clazz.getMethod(String name, Class<?>... parameterTypes);   //根据方法名和参数类型获得方法对象,仅能获取public方法
 
Method method = clazz.getDeclaredMethod(String name, Class<?>... parameterTypes);   //根据方法名和参数类型获得方法对象

Method[] methods = clazz.getMethods();  //获取所有public方法,包括父类的public方法

Method[] methods = clazz.getDeclaredMethods();  //获取当前类的所有方法
(2)Method类

获取方法的相关信息

int modifiers = method.getModifiers();      // 获取方法的访问权限
System.out.print(Modifier.toString(temp));  // 输出访问修饰符
 
Class<?> returnType = method.getReturnType();   // 方法返回类型
System.out.print(returnType.getName());         
 
String name = method.getName();     // 方法名
 
Class<?> params[] = method.getParameterTypes();     // 所有参数类型
 
Parameter[] parameters = method.getParameters();    // 所有参数对象
 
Class<?>[] exceptionTypes = method.getExceptionTypes();     // 方法抛出的所有异常类型

通过反射调用私有方法

//1. 获取 Class 类实例
TestClass testClass = new TestClass();
Class clazz = testClass.getClass();

//2. 获取私有方法
//第一个参数为要获取的私有方法的名称
//第二个为要获取方法的参数的类型,参数为 Class...,没有参数就是null
//方法参数也可这么写 :new Class[]{String.class , int.class}
Method privateMethod = clazz.getDeclaredMethod("privateMethod", String.class, int.class);

//3. 开始操作方法
if (privateMethod != null) {
    //获取私有方法的访问权
    //只是获取访问权,并不是修改实际权限
    privateMethod.setAccessible(true);

    //使用 invoke 反射调用私有方法
    //privateMethod 是获取到的私有方法
    //testClass 要操作的对象
    //后面两个参数传实参
    privateMethod.invoke(testClass, "Java Reflect ", 666);
}

反射调用公有方法:

与上面类似,不过如果可能还是直接通过实例调用吧 = _ =

(3)Field类

获取属性的相关信息

int mo = field.getModifiers();      //权限修饰符,包括static、final等
 
Class<?> type = field.getType();    //类型, 返回全限定名
 
String name = field.getName();      //属性名

获取属性的值

Object obj = field.get(Object o);   //获取类的成员对象,有时需要进行强转
 
//基本类型提供了诸如getInt、getBoolean等方法
int num = field.getInt(Object o);
...
 
//所有这些get方法都可能抛出IllegalArgumentException, IllegalAccessException异常
//如果属性是private的,需要在操作之前进行如下设置
 
field.setAccessible(true);

设置属性的值

field.set(Object obj, Object value);        //第一个参数是要操作的对象,第二个参数是新值
 
//同样针对基本类型,提供了setInt、setBoolean等不需要使用封装类型如Integer的方法
field.setInt(Object obj, int value);
...

针对set方法,如果value是null的话会抛NPE,如果属性是private或者是final的将会抛出IllagalAccessException,设置 field.setAccessible(true); 即可修改。

(4)反射机制的动态代理

实例

interface Animal {
    void say(String name, String word);
}

class Cat implements Animal {
    @Override
    public void say(String name, String word) {
        System.out.println("I'm a " + name + " I say " + word);
    }
}
//如果想要完成动态代理,首先需要定义一个InvocationHandler接口的子类,以完成代理的具体操作。
class MyInvocationHandler implements InvocationHandler {
    private Object obj = null;

    public Object bind(Object obj) {
        this.obj = obj;

        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(this.obj, args);
    }
}

public class TestProxy {

    public static void main(String[] args) {
        MyInvocationHandler demo = new MyInvocationHandler();

        Animal animal = (Animal) demo.bind(new Cat());
        animal.say("cat", "meow");
    }
}
(5)基于反射的工厂模式

对于普通的工厂模式当我们在添加一个子类的时候,就需要对应的修改工厂类。 当我们添加很多的子类的时候,会很麻烦。现在我们利用反射机制实现工厂模式,可以在不修改工厂类的情况下添加任意多个子类。但是

有一点仍然很麻烦,就是需要知道完整的包名和类名,这里可以使用properties配置文件来完成。

interface Fruit {
    void eat();
}
class Apple implements Fruit {
    @Override
    public void eat() {
        System.out.println("eat apple");
    }
}

class Orange implements Fruit {
    @Override
    public void eat() {
        System.out.println("eat orange");
    }
}

class Factory {
    public static Fruit getInstance(String className) {
        Fruit fruit = null;

        try {
            fruit = (Fruit) Class.forName(className).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return fruit;
    }
}

public class TestFactory {
    public static void main(String[] args) {
        Fruit f = Factory.getInstance("com.whx.practice.reflect.Apple");

        if (f != null) {
            f.eat();
        }
    }
}

四. 使用反射的注意事项

  1. 遍历循环一个类中的属性还是属性名的时候一定要注意:Android studio2.2之后的Instant Run功能的使用会导致JavaBean对象在编译之后多产生两个属性。可以通过如下方法过滤
for(Field field : fields) {
    if (declaredFields[i].isSynthetic()) {      //属性是否是Java语言自动生成的
        continue;
    }
    /**忽略serialVersionUID**/
    if (declaredFields[i].getName().equals("serialVersionUID")) {
        continue;
    }
}
  1. 有用到反射的类不能被混淆

  2. 使用反射访问Android的API时需要注意因为不同API版本导致的兼容性问题

  3. 生成内部类的Class对象时,注意类名的写法,有一个【$】符号

Class c = Class.forName("com.whx.test.OuterClass$InnerClass");

五. 反射的优缺点

优点

  • 灵活、自由度高:不受类的访问权限限制,可以在运行时对一些类做定制化工作;

缺点

  • 性能问题:通过反射访问、修改类的属性和方法时会远慢于直接操作,但性能问题的严重程度取决于在程序中是如何使用反射的。如果使用得很少,不是很频繁,性能将不会是什么问题;

  • 安全性问题:反射可以随意访问和修改类的所有状态和行为,破坏了类的封装性,如果不熟悉被反射类的实现原理,随意修改可能导致潜在的逻辑问题;

  • 兼容性问题:因为反射会涉及到直接访问类的方法名和实例名,不同版本的API如果有变动,反射时找不到对应的属性和方法时会报异常;

相关文章

  • 博客地址

    java注解-01、java注解-02、Git面试资源java反射-01、java反射-02、java反射-03为...

  • Java反射机制入门

    Java反射机制入门 一、什么是反射 JAVA反射机制(The JAVA reflection mechanism...

  • Java基础之反射

    Java基础之—反射(非常重要)Java中反射机制详解Java进阶之reflection(反射机制)——反射概念与...

  • 反射之一

    总结内容源自一下文章粗浅看java反射机制反射机制应用实践谈谈java反射机制Java Reflection(反射...

  • 反射之二

    总结内容源自一下文章粗浅看java反射机制反射机制应用实践谈谈java反射机制Java Reflection(反射...

  • Java基础之反射

    Java基础之反射 反射基本介绍 反射的使用通过反射调用属性和方法通过反射获取配置文件 反射基本介绍 Java反射...

  • Java 反射机制

    Java 反射机制 什么是反射 Java 反射是Java语言的一个很重要的特征,它使得Java具体了“动态性”。 ...

  • 一探究竟:Java反射效率低的原因到底在哪?

    预备知识 了解 Java 反射基本用法 看完本文可以达到什么程度 了解 Java 反射原理及 Java 反射效率低...

  • 面试官说:大家都说 Java 反射效率低,你知道原因在哪里么

    预备知识 了解 Java 反射基本用法 看完本文可以达到什么程度 了解 Java 反射原理及 Java 反射效率低...

  • Java面试题之JavaSE高级

    一、Java中的反射 1.说说你对Java中反射的理解 java中的反射首先是能够获取到Java...

网友评论

      本文标题:Java 反射

      本文链接:https://www.haomeiwen.com/subject/czbadctx.html