前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java反射从放弃到入门

Java反射从放弃到入门

作者头像
我是攻城师
发布2019-07-12 14:56:25
5020
发布2019-07-12 14:56:25
举报
文章被收录于专栏:我是攻城师

前言

Java反射特性提供了在运行时可以动态访问和修改类和实例内部的状态的功能。反射是Java语言里面一个高级的话题之一,使用反射我们可以在运行时轻松的内省一个类,接口以及枚举,可以获取他们的结构,方法和字段信息,即使在编译期间没法访问。最后我们也可以通过反射来实例化一个对象,调用对象的方法和修改字段的值等。

反射的应用场景

反射其实是一个非常强大的概念,虽然平时在正常的开发功能中,我们几乎很少用到,但作为Java SE里面核心的骨干特性,反射在大型框架里面非常常见,举例如下:

(1)JUnit :使用反射来解析所有带有@Test注解的方法,然后动态调用这些方法。

(2)Spring :依赖注入,包括AOP底层也需要使用反射配合动态代理才能完成切面功能。

(3)Tomcat:web容器通过反射解析web.xml文件里面的url,来正确的转发请求

(4)Eclipse 和 Intellj IDEA:方法名的自动补全功能

(5)Struts 注解或者xml配置的action请求转发

(6)Hibernate 动态Bean与数据库表的映射

实际上使用反射的框架还有很多很多,现在思考一个问题,这些框架使用反射的原因是什么?其实最主要的原因在于所有的这些框架,在运行之前是不知道我们用户自己定义的类,接口,以及各种方法和字段的,而通过反射则可以在运行时动态加载这些类,所以极大的提升了架构的灵活性。

但事物有利必有弊,反射也不是银弹,它的缺点也很显而易见:

(1)性能低。动态的加载和调用,需要额外的花费时间去解析和调用,所以性能相比正常的new要低很多。

(2)安全问题。反射会破坏封装的特性,因为它可以访问private修饰的字段和方法,所以是不安全的。

(3)维护成本高。由于反射的代码在编译期间是不会产生的任何效果的,所以对于理解和调试不太方便,只有等到运行时才能反馈效果。

这也是我们正常的编程中很少使用反射的原因,但在一些架构高度灵活的框架中,反射其实是必不可少的道具,所以我们应该权衡考虑,做到不滥用,不误用。

使用反射获取类信息

在Java里面有基本类型和引用类型两种类型,所有的类,接口,数组是引用类型,继承自父类Object类。基本类型就是boolean,byte,short,int,long,char,float,double这8种。

java.lang.Class类是完成反射的入口基础类,它提供了在运行时访问对象属性和创建对象,调用方法,字段赋值等有用的API。

为了演示方便和全面,我们创建了如下的类和接口用来测试:

1,BaseClass

代码语言:javascript
复制
package reflection.base;
public class BaseClass {
    public int baseInt;
    private static void method3(){        System.out.println("Method3");    }
    public int method4(){        System.out.println("Method4");        return 0;    }
    public static int method5(){        System.out.println("Method5");        return 0;    }
    void method6(){        System.out.println("Method6");    }

    // inner public class    public class BaseClassInnerClass{}
    //member public enum    public enum BaseClassMemberEnum{}

}

2, BaseInterface

代码语言:javascript
复制
package reflection.base;
public interface BaseInterface {
    public int interfaceInt=0;
    void method1();
    int method2(String str);
}

3,BaseImpl extends BaseClass implements BaseInterface

代码语言:javascript
复制
package reflection.base;
public class BaseImpl extends BaseClass implements BaseInterface {

    public int publicInt;
    private String privateString="private string";    public  String str;    protected boolean protectedBoolean;
    Object defaultObject;
    public BaseImpl() {    }
    public BaseImpl(int publicInt) {        this.publicInt = publicInt;    }
    private BaseImpl(String  str) {        this.str = str;    }

    @Override    public void method1() {        System.out.println("  BaseImpl method1  ");    }
    @Override    public int method2(String str) {
        System.out.println("  BaseImpl method2  ");        return 0;    }

    @Override    public int method4() {        System.out.println(" Method4 override ");
        return 0;    }
    @Override    void method6() {        System.out.println(" Method4 override ");    }
   private void method9() {        System.out.println(" Method9 override ");    }

    public class A{}
    private class B{}
    protected class C{}
    class D{}

    enum E{}
    public class F{}
    public interface G{}


}

一,获取Class对象本身

我们先来复习下Java里面获取一个实例的Class的三种方式:

(1)Class.forName()

(2) 实例的getClass()

(3) 非实例的.class 或者包装类的TYPE字段

下面来看一个使用示例:

代码语言:javascript
复制
package reflection.base;
public class TestReflection {
    public static void main(String[] args) throws Exception {

        //引用类型的获取类实例的方法
        Class cls=BaseImpl.class;//method1
        cls=new BaseImpl(5).getClass(); //method2
        cls= Class.forName("reflection.base.BaseImpl"); // method3
        System.out.println(cls.getCanonicalName());//获取类的全路径名

        //基本类型获取类实例的方法

        Class baseCls=boolean.class;        System.out.println(baseCls.getCanonicalName());
        Class baseCls2=Double.TYPE; // Double.TYPE
        System.out.println(baseCls2.getCanonicalName());
        //一维的double数组        Class baseCls3=Class.forName("[D");
        System.out.println(baseCls3.getCanonicalName());
        //二维的字符串数组        Class baseCls4=String[][].class;
        System.out.println(baseCls4.getCanonicalName());
    }
}

输出:

代码语言:javascript
复制
reflection.base.BaseImplbooleandoubledouble[]java.lang.String[][]

二,获取父类或者超类

代码语言:javascript
复制
      Class superCls=Class.forName("reflection.base.BaseImpl").getSuperclass();
        System.out.println(superCls);
       Class[] superInterface= Class.forName("reflection.base.BaseImpl").getInterfaces();
       for(Class cx:superInterface){           System.out.println(cx);       }        System.out.println(Object.class.getSuperclass());        System.out.println(String[][].class.getSuperclass());

输出如下:

代码语言:javascript
复制
class reflection.base.BaseClassinterface reflection.base.BaseInterfacenullclass java.lang.Object

三,获取所有公共成员的类

getClasses()方法可以获取所有使用public修饰的类,包括类本身,父类或者接口里面声明的类

代码语言:javascript
复制
        Class<?>[] classes = BaseImpl.class.getClasses();
        for(Class cls:classes){            System.out.println(cls);        }

输出

代码语言:javascript
复制
        interface reflection.base.BaseImpl$G        class reflection.base.BaseImpl$F        class reflection.base.BaseImpl$A        class reflection.base.BaseClass$BaseClassMemberEnum        class reflection.base.BaseClass$BaseClassInnerClass

三,获取类本身所有包含的其他类集合

getDeclaredClasses()可以获取类本身声明的不管任何权限修饰的类成员,不包含超类里面定义的类:

代码语言:javascript
复制
       Class<?>[] classes = BaseImpl.class.getDeclaredClasses();        for(Class cls:classes){            System.out.println(cls);        }

输出:

代码语言:javascript
复制
interface reflection.base.BaseImpl$Gclass reflection.base.BaseImpl$Fclass reflection.base.BaseImpl$Eclass reflection.base.BaseImpl$Dclass reflection.base.BaseImpl$Cclass reflection.base.BaseImpl$Bclass reflection.base.BaseImpl$A

四,获取包名

代码语言:javascript
复制
 System.out.println(Class.forName("reflection.base.BaseImpl").getPackage().getName());

五,获取修饰符

代码语言:javascript
复制
System.out.println(Modifier.toString(BaseImpl.class.getModifiers()));

六,获取类本身泛型参数的声明

代码语言:javascript
复制
    TypeVariable<?>[] typeParameters = Class.forName("java.util.HashMap").getTypeParameters();        for(TypeVariable<?> t : typeParameters)            System.out.println(t.getName());

输出

代码语言:javascript
复制
KV

注意这里不能获取类真正的泛型的类型,只能获取声明

七,获取所有的公共方法

获取所有的public修饰的方法,包含父类的

代码语言:javascript
复制
       Method[] methods = BaseImpl.class.getMethods();        for (Method m:methods){            System.out.println(m);        }

获取该类本身所有的public修饰的方法

代码语言:javascript
复制
 Method[] methods = BaseImpl.class.getDeclaredMethods();        for (Method m:methods){            System.out.println(m);        }

八,获取所有的构造方法

代码语言:javascript
复制
        Constructor[] methods = BaseImpl.class.getConstructors();        for (Constructor m:methods){            System.out.println(m);        }

九,获取所有的字段

代码语言:javascript
复制
        Field[] fields = BaseImpl.class.getFields();        System.out.println( Arrays.toString(fields));

十,获取所有的注解

代码语言:javascript
复制
        Annotation[] annotations = BaseImpl.class.getAnnotations();        System.out.println( Arrays.toString(annotations));

使用反射操作字段

使用反射可以获取类里面的public和private等修饰的字段,并能够访问和修改其值,下面我们通过一个例子来看一下:

get/set public 字段:

代码语言:javascript
复制
        Field field=   Class.forName("reflection.base.BaseImpl").getField("publicInt");
        System.out.println(field.getType());
        BaseImpl base=new BaseImpl(5);
        System.out.println(field.get(base));//读取字段的值
        field.setInt(base,10);//赋值
        System.out.println(field.get(base));//读取字段的值

get/set private 字段:

代码语言:javascript
复制
     Field field=   Class.forName("reflection.base.BaseImpl").getDeclaredField("privateString");
        System.out.println(field.getType());        field.setAccessible(true);        BaseImpl base=new BaseImpl(5);
        System.out.println(field.get(base));//读取字段的值
        field.set(base,"123");//赋值
        System.out.println(field.get(base));//读取字段的值

使用反射操作方法

这里以Java的HashMap作为例子,并调用了其公共的put方法:

代码语言:javascript
复制
        Method method=Class.forName("java.util.HashMap").getMethod("put",Object.class,Object.class);

        System.out.println(Arrays.toString(method.getParameterTypes()));
        Map<String,String> map=new HashMap<>();
        method.invoke(map,"k","v");
        System.out.println(map);//{k=v}

然后我们看下,如何调用某个类的私有方法:

代码语言:javascript
复制
        Method method=Class.forName("reflection.base.BaseImpl").getDeclaredMethod("method9");
        method.setAccessible(true);
        method.invoke(new BaseImpl(),null);

使用反射操作构造器

调用无参数的公共构造器:

代码语言:javascript
复制
      Constructor constructor= Class.forName("reflection.base.BaseImpl").getConstructor(null);       BaseImpl obj=(BaseImpl)constructor.newInstance();        System.out.println(obj.publicInt);

调用有参数的私有构造器:

代码语言:javascript
复制
      Constructor constructor= Class.forName("reflection.base.BaseImpl").getDeclaredConstructor(String.class);       constructor.setAccessible(true);       BaseImpl obj=(BaseImpl)constructor.newInstance("你好");        System.out.println(obj.str);

其他

此外,使用反射还可以获取方法的注解和数组字段的实例的声明,这里就不细说了,感兴趣的朋友可以自己研究下。

总结

本文主要介绍了Java里面反射是什么,以及它的应用场景和优缺点,最后结合实例给出了常见的反射调用的API例子等,除了本文中描述的反射相关的功能外,反射还可以配合动态代理来实现AOP功能或者配合类加载器来实现应用程序的加载和热加载的功能,这些功能都是比较高级的特性,在特定的场景下可以发挥很大的作用,从而使得我们应用程序更加灵活和具有扩展性。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 我是攻城师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 反射的应用场景
  • 使用反射获取类信息
  • 使用反射操作字段
  • 使用反射操作方法
  • 使用反射操作构造器
  • 其他
  • 总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档