Java进阶——详解反射机制及应用中的泛型类型的获取和曾经踩过的一个坑

引言

虽然很多人无论是做Android开发或者Java开发,或许在实际的项目开发过程中很少应用到反射甚至没有使用过,但是Java反射机制无论对于我们来说是进阶Java程序员或者Android程序员的重要一步,所以有必要学习下。

一、Java反射概述

Java反射机制(Java Reflection)是指程序在运行过程中,可以获知某个已知类或已知对象的的相关信息(包括类的方法、属性、父类等信息,还包含创建实例、判断实例类型等操作)。具体对于任意一个类来说,体现在都能够知道该类的所有属性和方法所有属性和方法;而相对于任意一个对象,这是都能够调用它的任意方法和属性。这种动态获取信息以及动态调用对象方法的功能就是Java语言的反射机制。Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。简而言之,平时我们无论是通过实例去调用方法或访问属性都属于显示调用,而反射可以理解为隐式调用,所谓隐式调用即通过已知类名解决两类问题:

  • 获取该类对应的所有属性和方法信息
  • 通过任意已知的对象调用对应的方法

二、JDK反射机制中四种重要的角色类

在JDK中主要由java.lang.reflection包下的四个类来实现:

  • Class类:代表一个类,无论是获取什么信息都是通过Class实例去调用对应的方法
  • Constructor类:代表类的构造方法
  • Field类:代表类的成员变量(属性)
  • Method类:代表类的方法

1、Class

Class 是java.lang包下的一个实体类,在Java中,所有的东西都是类(除了静态方法和基本类型),而且类同时本身也是一个对象。在Java程序运行期间,Java运行时系统时钟为所有的对象维护一个被称为运行时的类型标志。这个信息跟踪着每个对象的所属类。虚拟机利用运行时类型信息选择相应的方法执行。而Class类保存了Java类的这些信息。举个例子,通过一下代码构造User对象,那么user 是 User 类的实例,既然类也是对象,那么User 是谁的实例呢?User 是类Class的一个实例

User user=new User(2009,"CrazyMo_");

1.1、通过Class 类 的相关方法获取对应的类的类类型。

  • 通过getClass方法,获取已知实例的类类型
Class c=user.getClass();
  • 通过静态方法 forName(String className),获取已知类的类类型

forName是Class的一个静态方法,如果没有一个类的实例,但是知道这个类的名字,可以使用这个方法获得这个类的类类型。

Class cl=Class.forName("User");//参数必须是已知的类名或接口名
  • 通过T.class,其中T代表任意已知Java的类型

一个Class对象实际上表示一个类型,而这个类型不一定是一个类。比如,int不是类,但int.class是一个Class类型的对象。

Class cl1=User.class;  
Class cl2=int.class  
Class cl3=Double[].class

if(user.getClass()==User.class)//为true

1.2、newInstance()

本质上newInstance()方法只是粗暴地调用对应类的是默认的公开的构造器(没有参数的构造器)初始化新创建的对象。

 Class<?> c= Class.forName("training.reflection.PrivateMethod");//className必须为全名,包含包名
 Object obj=c.newInstance();//创建PrivateMethod的实例,PrivateMethod的默认构造方法必须是public

当然newInstance()也可以接受参数,先获取构造方法再newInstance即可。

PrivateMethod.class.getConstructor().newInstance(args);

2、Constructor

  • String getName():返回构造器的名字;

  • int getModifiers():返回修饰符;

  • int getParameterCount():返回构造器中参数的个数;

  • Class< ? >[] getParameterTypes():返回参数的类型;

3、Field

  • String getName():返回域的名字;

  • int getmodifiers():返回修饰符;

  • Class< ? > getType():返回域的类类型;
    值得注意的是Filed的getType方法获取对应的类类型仅仅针对于泛型外的大类型,因为 泛型在运行的时候是会进行类型擦除,泛型信息只存在于代码编译阶段。比如说对于List< String >和List< Integer >在运行阶段都是List.class,所以通过filed.getType()方法获取到的是外层的大类型List,而要想获取内部泛型类型则需要借助java.lang.reflect包下Type接口(Type是一个接口,它的实现类有Class,子接口有 ParameterizedType, WildcardType等)的两个子接口——参数化类型ParameterizedType通配符的类型WildcardType 来获取

    • (ParameterizedType)(Type[] getActualTypeArguments()): 返回类型的参数的实际类型数组,比如Map< String,Integer >的里的泛型类型通过getActualTypeArguments()[0]得到的是String类型 ,getActualTypeArguments()[1]则对应得到Integer类型。

    • 通配符的类型WildcardType,JAVA泛型通配符的使用规则:”PECS”,在泛型中可以通过通配符 ? 来声明一个泛型类型,也可以同extends 指定通配符的上边界(泛型中extend右边的值);也可以通过super来指定下边界(泛型中super右边的值),比如List< ? extends User > list通过getUpperBounds()方法可以得到上边界为User;而List< ? super Object > list通过getLowerBounds()方法可得到下边界则为:Object。

for (Field field : beanClass.getFields()) {
            if("mList".equals(field.getName())){
                //指定获得Bean中map属性
                //Field list;
                try {
                    //list = User.class.getField("map");
                    // 属性对应的Class如果是List或其子类
                    if (List.class.isAssignableFrom(field.getType())) {
                        //获得 Type
                        Type genericType = field.getGenericType();
                        //ParameterizedType
                        if (genericType instanceof ParameterizedType) {
                            //获得泛型类型
                            Type type = ((ParameterizedType) genericType).getActualTypeArguments()[0];//从一个泛型类型中获取第一个(对应索引为0)泛型参数的类型类。
                            //WildcardType  如果使用了通配符
                            if (type instanceof WildcardType) {
                                WildcardType wildcardType = (WildcardType) type;
                                Type[] upperBounds = wildcardType.getUpperBounds();
                                if (upperBounds.length == 1) {
                                    Type actualTypeArgument = upperBounds[0];
                                    System.out.println(field.getName()+"的大类型"+field.getType()+" mList的泛型上边界类型:" + actualTypeArgument);
                                }
                            } else {
                                System.out.println(field.getName()+"的大类型"+field.getType()+" mList的内部泛型类型:" + type);
                            }
                        } 
                    }
                } catch (SecurityException e) {
                    e.printStackTrace();
                }

            }else{
                System.out.println(field.getName()+"    field Type: "+field.getType());//TODO field.getType()不适用于泛型
            }
        }

4、Method

  • String getName():返回方法的名字;

  • int getModifiers():返回修饰符;

  • int getParameterCount():返回参数的个数;

  • Class< ? >[] getParameterTypes():返回参数的类类型;

  • Class< ? > getReturnType():返回返回类类型;

三、Java反射的优势与缺陷

一切事物都是双刃剑,反射机制也一样,利用反射我们不仅可以快速获取程序在运行时刻的内部结构。这对于程序的检查工具和调试器来说,是非常实用的功能。只需要短短的十几行代码,就可以遍历出来一个Java类的内部结构,并可以与它进行交互,包括创建新的对象和调用对象中的方法等。这种交互方式与直接在源代码中使用的效果是相同的,但又额外提供了运行时刻的灵活性包括其中的构造方法、声明的域和定义的方法等,正体现了动态创建对象和编译(动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有效地降低类之间的藕合性)但使用反射的一个最大的弊端是性能比较差,相同的操作,用反射API所需的时间大概比直接的使用要慢一两个数量级。不过现在的JVM实现中,反射操作的性能已经有了很大的提升。在灵活性与性能之间,总是需要进行权衡的,因而我们可以在适当的时机来使用反射API。

四、利用Java反射的可以做的事

利用反射的前提是:我们必须得知道完整的类名(包含包名),同一个包下可以省略包名,然后再把类的全名传入Class.forName(“className”)得到对应的Class对象classInfo(这个对象包含了这个类的内部结构的所有信息),最后再根据自己的需求使用classInfo去调用newInstance()方法创建实例getConstructors()获取构造方法getMethod方法获取对应的方法名getDeclaredField获取对应的属性

 Class c=Class.forName("className");//className必须为全名,包含包名,
 Object obj=c.newInstance();//创建对象的实例 

1、获取构造方法

方法说明
T newInstance()构造对应的实例
Constructor getConstructor(Class[] params)根据指定参数获得public构造方法
Constructor[] getConstructors()获得public的所有构造器
Constructor getDeclaredConstructor(Class[] params)根据指定参数获得public和非public的构造方法
* Constructor[] getDeclaredConstructors()*获得public的所有构造器

2、 获得类成员方法的

通过反射拿到方法信息的时,是被封装到Method类型里的,如果要调用还得通过Method实例去调用Object invoke(Object obj, Object… args),比如method.invoke(forName.newInstance(), 10);/第一个参数是实例,如果是静态,那么设置其为null,第二个参数则是传入的实参(而且注意的是传入的必须是封装的对象类型,即如果是整型的话就传new Integer[]{})

方法说明
Method[] getMethods()获得所有的public方法(包括父类的public方法)
Method[] getDeclaredMethods()获得类中所有的方法(不包括父类)
Method getDeclaredMethod(String name, Class[] params)根据方法名和参数类型,获得public和非public的方法,第二个参数代表方法签名中参数的类型,如果是一个整形形参则传递Integer.TYPE,两个则传new Class[]{Integer.TYPE,Integer.TYPE}
Method getMethod(String name, Class[] params)根据方法名,参数类型获得方法

3、获得类中成员变量(属性)

方法说明
Field[] getFields()获得类以及父类中所有声明为public的变量(protected和private修饰的变量不能获取)
Field[] getDeclaredFields()获得类(不包括父类)中所有的变量
Field getField(String name)根据变量名得到相应的public变量
Field getDeclaredField(String name)根据属性名对应的变量
package reflect;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.List;

public class Reflection {
    static class User {
        private int mId;
        public String mName;
        public List<String> mList;
        public User() {
        }
        public int getmId() {
            return mId;
        }
        public void setmId(int mId) {
            this.mId = mId;
        }

    }
    public static void main(String[] args) {
        Class<User> beanClass = User.class;
        //获得类以及父类中所有声明为public的成员变量
        System.out.println("所有public成员变量(类以及父类):");
        for (Field field : beanClass.getFields()) {
            if("mList".equals(field.getName())){
                //指定获得Bean中map属性
                //Field list;
                try {
                    //list = User.class.getField("map");
                    // 属性对应的Class如果是List或其子类
                    if (List.class.isAssignableFrom(field.getType())) {
                        //获得 Type
                        Type genericType = field.getGenericType();
                        //ParameterizedType
                        if (genericType instanceof ParameterizedType) {
                            //获得泛型类型
                            Type type = ((ParameterizedType) genericType).getActualTypeArguments()[0];
                            //WildcardType  如果使用了通配符
                            if (type instanceof WildcardType) {
                                WildcardType wildcardType = (WildcardType) type;
                                Type[] upperBounds = wildcardType.getUpperBounds();
                                if (upperBounds.length == 1) {
                                    Type actualTypeArgument = upperBounds[0];
                                    System.out.println(field.getName()+"的大类型"+field.getType()+" mList的泛型上边界类型:" + actualTypeArgument);
                                }
                            } else {
                                System.out.println(field.getName()+"的大类型"+field.getType()+" mList的内部泛型类型:" + type);
                            }
                        } 
                    }
                } catch (SecurityException e) {
                    e.printStackTrace();
                }

            }else{
                System.out.println(field.getName()+"    field Type: "+field.getType());//TODO field.getType()不适用于泛型
            }
        }
        //获得类(不包括父类)中所有的成员变量
        System.out.println("所有成员变量(不含父类):");
        for (Field field : beanClass.getDeclaredFields()) {
            System.out.println(field.getName()+"    field Type: "+field.getType());
        }
        //获得类以及父类中所有声明为public的方法
        System.out.println("所有public方法(类以及父类中):");
        for (Method method : beanClass.getMethods()) {
            String methodName = method.getName();
            System.out.println(methodName);
        }

         //获得类(不包括父类)中所有的方法
        System.out.println("所有方法(不含父类):");
        for (Method method : beanClass.getDeclaredMethods()) {
            String methodName = method.getName();
            System.out.println(methodName);
        }
    }
}

这里写图片描述
用简单的话总结下类中的属性对应反射中的Field,类中的方法则对应Method,再根据
Constructor< ? >[] constructors = beanClass.getConstructors()可获取构造方法,于是操作属性都遵循以下的语法(非public的Field/Method进行操作,需要先进行:setAccessible(true)

Method method = XX;
//在obj对象上调用函数
method.invoke(obj,...);

 Field field = XX;
//获得obj中的属性
field.get(obj);
//设置obj中的属性
field.set(obj,value);

//调用构造函数
Constructor<?>[] constructor = XX;
constructor.newInstance(...);

五、Java反射的应用

还是以前面一篇文章Java进阶——面向接口编程之柔性多态增强代码的可扩展性
为例,求根据输入的值求长方形、圆形的面积,其中计算长方形面积时要求输入长和宽,计算圆形时只需要半径即可,接下来逐步分享下以反射的思想来解这个题。

首先把他们共性的功能抽象为接口

public interface IShape {
    void input();
    float getArea();
}

此时我们已经定义了个形状接口,一般来说我们只要分别定义长方形类和圆形类并实现这个IShape接口即可,但是封装性还是不高,因为无论是求哪个形状的面积都得先输入再计算,这是一个流程,于是封装了一个流程类

public class ShapeProcess {
    private IShape ishape;
    public ShapeProcess(IShape shape){
        this.ishape=shape;
    }

    public float computeArea(){
        ishape.input();
        return ishape.getArea();
    }
}

根据不同形状实现IShape接口

public class Rectangle implements IShape {
    public Rectangle(){}
    private float width;
    private float height;

    public float getWidth() {
        return width;
    }
    public void setWidth(float width) {
        this.width = width;
    }
    public float getHeight() {
        return height;
    }
    public void setHeight(float height) {
        this.height = height;
    }

    @Override
    public void input() {
        System.out.println("请一次输入长、宽:");
        Scanner s=new Scanner(System.in);
        setHeight(s.nextFloat());
        setWidth(s.nextFloat());
        s.close();
    }

    @Override
    public float getArea() {
        return width*height;
    }
}

public class Circle implements IShape {
    private float radius;

    public Circle (){}
    public float getRadius() {
        return radius;
    }
    public void setRadius(float radius) {
        this.radius = radius;
    }

    @Override
    public void input() {
        System.out.println("请输入半径:");
        Scanner s=new Scanner(System.in);
        setRadius(s.nextFloat());
        s.close();
    }

    @Override
    public float getArea() {
        return (float) (Math.PI*radius*radius);
    }
}

使用反射来调用

//使用反射来计算面积
        Class myShape;
        try {
            myShape = Class.forName("training.duotai.Circle");
            IShape shape=(IShape) myShape.getConstructor().newInstance();
            ShapeProcess process=new ShapeProcess(shape);
            System.out.println("所求面积"+process.computeArea());
        } catch (ClassNotFoundException e1) {
            e1.printStackTrace();
        }catch (InstantiationException | IllegalAccessException
                | IllegalArgumentException | InvocationTargetException
                | NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }

六、通过反射调用类的私有成员

package training.reflection;

public class PrivateMethod {
    public PrivateMethod(){}
    @SuppressWarnings("unused")
    private int add(int x ,int y){
        return x+y;
    }
}

public static void main(String[] args) {

         Class<?> c;
        try {
            c = Class.forName("training.reflection.PrivateMethod");//className必须为全名,包含包名,
            Object obj=c.newInstance();//创建对象的实例
            Method method=c.getDeclaredMethod("add",new Class[]{Integer.TYPE,Integer.TYPE});
            method.setAccessible(true);
            System.out.println(method.invoke(obj, new Integer[]{10,20}).toString());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

七、曾经不注意踩过的一个坑

这里写图片描述

因为这个是单例,然后在调用这个方法之前已经被初始化了,反射的时候又暴力调用newInstance 来构造一个新的对象,这就形成了两个对象改变音色是后面newInstance,而播放语音的是前面初始化的。总结就是正确理解newInstance()的本质,尤其是当要反射调用单例类的私有方法时。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CrazyMo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值