Java 反射

Java 反射

Posted by 金志宏 on September 22, 2018

Java 反射

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

​ Java的反射机制允许编程人员在对类未知的情况下,获取类相关信息的方式变得更加多样灵活,调用类中相应方法,是Java增加其灵活性与动态性的一种机制。

优点:

  • 反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力。
  • 通过反射机制可以让程序创建和控制任何类的对象,无需提前硬编码目标类。
  • 使用反射机制能够在运行时构造一个类的对象、判断一个类所具有的成员变量和方法、调用一个对象的方法。
  • 反射机制是构建框架技术的基础所在,使用反射可以避免将代码写死在框架中。

缺点:

  • Java反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。而且,如何使用反射决定了性能的高低。如果它作为程序中较少运行的部分,性能将不会成为一个问题。

  • 使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,则最好不要使用反射。

  • 反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑结构不能被识别,代码产生的效果与之前会产生差异。

1. 怎么反射

//一个Model对象 球
public class Ball {

    protected String name;

    public Ball() {
        name = "球";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Ball{" +
                "name='" + name + '\'' +
                '}';
    }
}
public static void main(String[] args) throws Exception {
  			// 根据类全限定名返回 Class 对象 等同于 cls = Ball.class; 
        Class<?> cls = Class.forName("blackhold.Ball");
  			// 实例化对象
        Object obj = cls.newInstance();
        Ball ball = (Ball) obj;
        System.out.println(ball.toString());
    }

//输出
Ball{name='球'}

这里注意,被反射的对象使用 newInstance()实例化时必须拥有默认构造器(无参构造器)否则会报错java.lang.NoSuchMethodException: blackhold.Ball.<init>()

2. Class的方法

2.1 forName

根据类名返回类的对象

// 参数 className 为全限定名
public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

// 方法返回与给定字符串名的类或接口的Class对象,使用给定的类加载器。
// name -- 这是所需类的完全限定名称
// initialize -- 这说明这个类是否必须初始化
// loader -- 这是必须加载的类的类加载器
public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        Class<?> caller = null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // Reflective call to get caller class is only needed if a security manager
            // is present.  Avoid the overhead of making this call otherwise.
            caller = Reflection.getCallerClass();
            if (sun.misc.VM.isSystemDomainLoader(loader)) {
                ClassLoader ccl = ClassLoader.getClassLoader(caller);
                if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
                    sm.checkPermission(
                        SecurityConstants.GET_CLASSLOADER_PERMISSION);
                }
            }
        }
        return forName0(name, initialize, loader, caller);
    }

2.2 newInstance

创建类的实例

public T newInstance()
        throws InstantiationException, IllegalAccessException
        {
        		//......
        }

2.3 getClassLoader

获得类的加载器

public ClassLoader getClassLoader() {
        ClassLoader cl = getClassLoader0();
        if (cl == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
        }
        return cl;
    }
    public static void main(String[] args) throws Exception {
      	// 双亲委派模式 不是所有类加载器都可以获得 比如 java.*开头的类 String jvm的实现中已经保证了
      	//必须由bootstrp来加载。
        getClassLoader("java.lang.String");
        getClassLoader("blackhold.Ball");
    }

		public static void getClassLoader (String className) throws Exception {
        Class<?> cls = Class.forName(className);
        ClassLoader classLoader =  cls.getClassLoader();
        System.out.println(classLoader);
    }

//输出
null
sun.misc.Launcher$AppClassLoader@18b4aac2

2.4 asSubclass

​ asSubclass用于窄化未知的Class类型的范围,而instanceof用于判断一个对象引用是否是一个超类或者接口的子类/实现类,如果试图将instanceof用于Class类型的判断会引起编译错误。

我们添加2个Ball的子类

public class BasketBall extends Ball {
    public BasketBall() {
        name = "篮球";
    }
}

public class FootBall extends Ball {
    public FootBall() {
        name = "足球";
    }
}

public static void main(String[] args) throws Exception {       
        solution2(FootBall.class);
        solution2(BasketBall.class);
        solution2(String.class);
    }

public static void solution2(Class<?> cls) throws Exception {
  			//窄化未知的Class类型的范围 如果不是Ball类型则抛出异常
        Object obj = cls.asSubclass(Ball.class).newInstance();
        Ball ball = (Ball) obj;
        System.out.println(ball.toString());
    }

//输出
Ball{name='足球'}
Ball{name='篮球'}
Exception in thread "main" java.lang.ClassCastException: class java.lang.String

2.5 cast

把对象转换成代表类或是接口的对象

2.6 getClasses 和 getDeclaredClasses

getClasses :返回一个数组,数组中包含该类中所有内部公共类和内部接口类的对象

getDeclaredClasses :返回一个数组,数组中包含该类中所有内部类和内部接口类的对象

2.7 getSuperclass 和 getInterfaces

getSuperclass : 获得当前类继承的父类的名字

public static void main(String[] args) throws Exception {
  			System.out.println(new FootBall().getClass().getSuperclass());
        System.out.println(new Ball().getClass().getSuperclass());
        System.out.println(new Object().getClass().getSuperclass());
}

//输出
class blackhold.Ball
class java.lang.Object
null

getInterfaces : 返回数组:获得当前类实现的类或是接口

添加接口,扩展FootBall

public interface Play {
    void playBall();
}

public class FootBall extends Ball implements Play {

    private String play;

    public FootBall() {
        name = "足球";
        play = "踢";
    }

    @Override
    public void playBall() {
        System.out.println( play + name );
    }

}

public class BasketBall extends Ball implements Play {

    private String play;

    public BasketBall() {
        name = "篮球";
        play = "打";
    }

    @Override
    public void playBall() {
        System.out.println( play + name );
    }
}


public static void main(String[] args) throws Exception {

        getInterfaces(FootBall.class);
    }

public static void getInterfaces(Class<?> cls) {
        Class<?>[] clsInterfaces = cls.getInterfaces();
        for(Class<?> c : clsInterfaces){
            System.out.println(c.getName());
        }
    }

//输出 
blackhold.Play

2.8 getPackage

获得类的包名

2.9 getSimpleName

获得类的名字

2.10 getAnnotations 和 getAnnotation

getAnnotations : 返回该类所有的公有注解对象

getDeclaredAnnotations:返回该类所有的注解对象

getAnnotation:根据注解类型获得注解对象

2.11 getFields 和 getDeclaredFields

  • getFields 获得所有公共属性
  • getDeclaredFields 获得所有属性

2.12 getDeclaredMethods 和 getMethods

getDeclaredMethods:获取的是类自身声明的所有方法,包含public、protected和private方法。

getMethods:获取的是类的所有共有方法,这就包括自身的所有public方法,和从基类继承的、从接口实现的所有public方法。

2.13 getConstructors 和 getDeclaredConstructors()

getConstructors:获得该类的所有公有构造方法

getDeclaredConstructors:获得该类所有构造方法

在阅读Class类文档时发现一个特点,以通过反射获得Method对象为例,一般会提供四种方法,getMethod(parameterTypes)、getMethods()、getDeclaredMethod(parameterTypes)和getDeclaredMethods()。getMethod(parameterTypes)用来获取某个公有的方法的对象,getMethods()获得该类所有公有的方法,getDeclaredMethod(parameterTypes)获得该类某个方法,getDeclaredMethods()获得该类所有方法。带有Declared修饰的方法可以反射到私有的方法,没有Declared修饰的只能用来反射公有的方法。其他的Annotation、Field、Constructor也是如此。