JAVA 反射机制

反射很强大。主要是为了做工具和框架而使用的。
反射是以性能为代价提高程序可用性的手段
反射虽好,可不能乱用奥~。

反射引入:对象分类:

对象分为编译类型和运行类型

Object    obj=new  Date();

obj的编译类型是Object,运行类型是Date

Java中anything is Object
描述对象的是类。
那么肯定一个类也是对象,所以肯定有一个类来描述这些类。这个类也就是描述数据的描述数据。
称为元数据(metadata)。
这个类就是Class

反射:得到元数据的行为。
Class中有类所具有的成员。
通过反射就可以得到一个类中的包、类、字段、方法……
最常用的eclipse中的OutLine窗口就是利用反射来获取类中元素的。


Class类

Class用于描述一切类、接口、枚举。注解也是一种接口。所以也是一种类。
Class实例是指JVM中的一份字节码文件。例如:User.class、String.class
Class为了区分Class实例表示的是那个类的字节码文件,提供了泛型。
Class<T> T 表示的是字节码的类名。
获取Class的实例的三种方式:

  1. 类名.class; —>字节码文件
  2. Class.forName(String className); —>根据一个类的限定名来构建Class对象
  3. 每一个对象都有getClass()。——->可以根据对象获得对象的运行类型。

    Class类型对象如果运行类型相同,则对象会相互引用。

Class<User>  user=User.class
Class<User> user1=Class.forName(pojo.User);
System.out.println(user==user1);//true

9大内置Class实例:

9大内置Class类型:
基本数据类型没有类的全名也可以使用Class来表达基本数据的实例。
包括byte、short、int、long、char、float、double、boolean、void关键字
void关键字不常用
上述数据类型包括void都具有class属性。
Class clz=int.class

包装类与基本数据类型的class不同

Integer.class!=int.class

包装类的TYPE属性:TYPE属性表示该包装类基本数据类型的class实例

Integer.TYPE==int.class

数组的Class实例:
同数据类型、同维度的Class实例相同

String []  str={};
String [] [] str1={};
str.class==str1.class

获取构造器

Constructor:表示类中的构造器的类型
Constructor的实例表示某一个类中的构造器实例。
Constructor c表示某一个类中的构造器
常用方法:
getConstructor():返回某个类中公共的(public)构造器集合
getDeclaredConstructor():返回某个类中包括私有共有的所有构造器集合。
getConstructor(String.class):返回一个带有String类型参数公有的构造器。
getDeclaredConstructor(String.class):返回一个带有String类型参数的构造器,与访问修饰符无关。
用法:

Class  clz=User.class;
Constructor  c=clz.getConstructor();

由此可见:想要获取一个私有的构造器,就得加上Declared


利用构造器创建对象

知道了怎样获取一个类的Class实例
知道了怎样获取一个类的构造器
就可以通过构造器创建一个类的实例啦。

步骤

  1. 先找到被调用的构造器所在的字节码文件
  2. 找到被调用的指定构造器
  3. 执行构造器
1. Class<MobileCard> cla= (Class<MobileCard>)Class.forName("reflectdemo.MobileCard");
2. Constructor<MobileCard> constructor = cla.getConstructor();
   System.out.println(constructor);
//newInstance():参数:表示调用构造器的实际参数。返回:返回该class类的实例
3. MobileCard mobileCard = constructor.newInstance();

这时mobileCard就是MobileCard的一个对象了。就可以尽情使用MobileCard类中的各种方法。

还有一种更加简便的方法。
假如一个类中存在一个可以直接访问的无参构造,
就可以直接使用Class中的newInstance()

1.Class<MobileCard> cla= (Class<MobileCard>)Class.forName("reflectdemo.MobileCard");
2.MobileCard card = cla.newInstance();

这时mobileCard也是MobileCard的一个对象了。就可以尽情使用MobileCard类中的各种方法。

但是!如果一个类中只有一个带有参数的私有构造。怎么办?
一个类中如果有一个带有参数的私有构造。
即使你使用getDeclaredConstructor是没有用的。
这是错误:

这里写图片描述

一切问题都会有解决的办法。
AccessibleObject类就是针对这个问题而设计的
AccessibleObject有Constructor、Field、Method三个字类
可以忽略构造器、字段、方法的安全检查
像这样:

//得到Class实例
Class cl=MobileCard.class;
//得到构造实例
Constructor cs=cl.getDeclaredConstructor(String.class,int.class);
//设置该类私有构造器忽略安全检查
cs.setAccessible(true);
//通过实例new出本类对象
MobileCard newInstance = (MobileCard) cs.newInstance("sdf",9);
System.out.println(newInstance);
System.out.println("-----------------华丽丽的分割线------------------");

这样就能使用MobileCard的对象啦。

所以。一般的。除了单例模式以外。我们尽量保证一个类中有一个共有无参的构造方法来保证后续的开发操作和程序的延伸。


获取、调用方法

通过反射能获取一个类中的对象
能获取一个类的构造器
当然能获取一个类中的方法了。

  • getMethods():获取包括继承关系所有公共(public)方法集合。
  • getDeclaredMethods():获取不包括继承关系包括私有的所有方法。
  • getMethod(String name,Class …):获取指定方法名和参数列表的一个公共方法
  • getDeclaredMethod(String name,Class …):获取指定方法名和参数列表的不包括继承关系的一个方法

可以看出:加上Declared后只获取类本身不包括继承关系的方法。 当然重写的方法也算。
要想获得一个指定的方法,就得指定方法名和参数列表。
参数列表=参数个数+参数顺序。在方法中得指定参数列表的Class对象。

像这样:

/*
 * 通过反射得到某一个类中的方法
 */
//获取MobileCard类中所有的公共方法集合。不包括私有,包括继承。
Class asx=MobileCard.class;
Method[] methods = asx.getMethods();
for (Method method : methods) {
    System.out.println(method);
}
System.out.println("-----------------华丽丽的分割线------------------");
//获取Mobile类中所有的方法。不包括继承,包括私有
Method[] declaredMethods = asx.getDeclaredMethods();
for (Method method : declaredMethods) {
    System.out.println(method);
}
System.out.println("-----------------华丽丽的分割线------------------");
//通过方法签名来找到唯一的方法
//方法签名=方法名+参数列表
Method me=asx.getMethod("getPrice");
System.out.println(me);

如何调用方法呢?

调用方法:
使用Method中的invoke(obj,args):表示当前Method所表示的方法
参数:

  • obj:表示调用方法的所属的对象
  • args:传递的实际参数

返回:

  • 方法的返回结果。

调用私有方法同时肯定要设置检察权限啦。

使用:

    //得到MobileCard的字节码对象
    Class sdf=MobileCard.class;
    //获取setPrice方法,传入double类的对象
    Method mes=sdf.getMethod("setPrice",double.class);
    //创建MobileCard的对象
    Object instance = sdf.newInstance();
    //调用方法,传入MobileCard的对象和方法参数90。因为没有返回值,无须接收。
    mes.invoke(instance, 90);
    //获取getPrice方法的方法对象
    Method mes1=sdf.getMethod("getPrice");
    //执行getPrice的方法的方法对象,同时传入对象
    Object invoke2 = mes1.invoke(instance);
    //输出返回的值。输出为90.0
    System.out.println(invoke2);

调用静态方法:
若方法是静态的,调用时可以让obj设置为null。
像这样:

/**
* 使用反射调用静态方法
* 若方法是静态的可以将obj参数设置为null。
*/
Class zxc=MobileCard.class;
Method me1=zxc.getMethod("getCardNumber");
System.out.println(me1);
me1.invoke(null);
System.out.println("-----------------华丽丽的分割线------------------");

调用可变参数
假设一个类中有如下方法:

    public static void show(int ... args){
        System.out.println(Arrays.toString(args));
    }
    public static void show(String ... args){
        System.out.println(Arrays.toString(args));
    }

要怎么调用呢?
我们知道可变参数其实是一个数组。
对于int类型的可变参数传入数组的class实例即可

            Class ss=ReflectDemo.class;
            Method mm=ss.getMethod("show", int[].class);
            mm.invoke(null,new int[]{1,2});

但是String类型的可变参数要怎么办呢。
String是引用类型。引用类型总与基本类型不同。
其实底层不管传入的是基本数据类型还是引用数据类型。都有一个解包操作。具体可查阅API。
所以。最好每次不管传入的是不是引用数据类型,都要包装一下。
像这样:

//引用类型需要用Object包装,方法会自动解包
Method mm1=ss.getMethod("show", String[].class);
mm1.invoke(null, new Object[]{new String[]{"a","b"}});
System.out.println("-----------------华丽丽的分割线------------------");

获取、设置字段

反射如此强大,字段又算什么。

类比的:
同样有4个方法来供我们获得字段

getFields:获取所有公共字段。包括继承的字段。
getField(String name):获取指定名字的字段
getDeclareFields:获取所有字段包括私有。不包括继承的字段。
getDeclareField:获取指定名字的私有字段。

设置字段:

void setXX(Object obj,XX value )xx为基本数据类型
void set(Object obj,Object value)

像这样:

/**
* 使用反射获取字段
*/
Class sss=MobileCard.class;
Field[] fields = sss.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field);
}
Field f=sss.getDeclaredField("price");
System.out.println(f);
System.out.println("-----------------华丽丽的分割线------------------");
/*
 * 操作字段
 */
Class sb=MobileCard.class;
Object instance2 = sb.newInstance();
Field declaredField = sb.getDeclaredField("price");
declaredField.setAccessible(true);
//设置值
declaredField.setDouble(instance2, 90.0);
//获取值
Object object = declaredField.get(instance2);
System.out.println(object);
System.out.println("-----------------华丽丽的分割线------------------");

反射其他API

int getModifiers() 获取修饰符
String getName() 返回类的全限定名
getPackage() 返回包名
getSimpleName() 获得类的简单名字
getSuperClass 获取类的父类
isArray()判断该class是否为数组
isEnum() 判断该class是否为枚举

当我们要反射某个类的修饰符时,调用getModifiers()方法返回的却是数字,我们可以使用专门的修饰符类来更好地得到一个类的修饰符。
以下代码详解:

/**
* 反射的其他API
*/
int modifiers = MobileCard.class.getModifiers();
//MobileCard.class.getModifiers()返回数字,不利于查看。
//Modifier修饰符专用类
System.out.println(Modifier.toString(modifiers)); 
//输出类名
System.out.println(MobileCard.class.getName());
//输出简单类名
System.out.println(MobileCard.class.getSimpleName());
//输出包名
System.out.println(MobileCard.class.getPackage().getName());
//获取父类
System.out.println(MobileCard.class.getSuperclass());
//获取Method、Field、Constructor的信息,更多方法查阅API
System.out.println(mm.getReturnType());
System.out.println("-----------------华丽丽的分割线------------------");

反射调用泛型方法、获取泛型类型

为了规定一个类的参数类型,很多类中使用了泛型定义。
比如Arrays类中的asList()

   public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

假如我们要调用Arrays中的public static <T>List<T>asList(T...a);
要怎样调用呢。
其实泛型底层也是数组,提到数组就是引用类型,是引用类型就得包装。
步骤:

1.通过getGenericType方法获取Type类型。 该对象中包含了泛型类型信息。
2.把getGenericType返回的类型强制转为ParamterizedType;类型;
3.利用getActualTypeArguments获取参数类型中泛型类型数组。
4.TYPE[]中就存在着泛型类型的信息,遍历即可。

    Class zsw=Arrays.class;
    //泛型也是一个数组。
    Method metg=zsw.getMethod("asList", Object[].class);
    //传入泛型同样需要Object包装。实际传递的泛型同样是Object数组。
    Object invoke = metg.invoke(null, new Object[]{new Object[]{"A","B","C"}});
    System.out.println(invoke);

输出结果为:

[A, B, C]

我们要是想得到一个类的泛型类型信息,要怎么得到呢。

一切皆对象。肯定参数类型也有对应的类来表示啦~。
假设我们有如下类:

class  ooxx{
    public Map<String,Object>map;
}
    //首先得到类字节码
    Class acl=ooxx.class;
    Field f1 = acl.getField("map");
    //获取此字段的声明类型Map,但是没有带泛型信息
    Class<?> type = f1.getType();
    //获取带泛型类型的类型信息
    Type genericType = f1.getGenericType();
    //输出带泛型参数的类型信息
    System.out.println(genericType);

这样呢。输出结果如下:

java.util.Map<java.lang.String, java.lang.Object>

输出的是一个带着泛型类型的字段信息。
我们还想得到具体的泛型类型:
就得这样:

            //强转为ParameterizedType类。为了得到参数的泛型类型
            ParameterizedType type2=(ParameterizedType) genericType;
            //获取参数类型的泛型类型集合
            Type[] actualTypeArguments = type2.getActualTypeArguments();
            for (Type type3 : actualTypeArguments) {
                System.out.println(type3);
            }

输出:

class java.lang.String
class java.lang.Object

这样,就得到了具体的泛型类型的参数信息啦。

关于方法的意思。还得去API帮助文档多多查看。


到此,反射的关键知识就学完了。当然,reflect包中还有很多方法和用法等待我们去挖掘和使用。
学习了反射。就可以利用反射来做一个对象工厂。

反射实现对象工厂

很简单的例子:

public  static Object createInstance(String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
    Class cl=Class.forName(className);
    Object obj = cl.newInstance();
    return obj;
}
}

这样的反射工厂每一次返回的对象都需要我们强转为我们需要的对象。
不具有灵活性。
我们要在反射工厂里强转的话,又只能通过传入类的Class对象来保证返回的对象使我们所需要的类对象。

public  static <T>T  createInstance(Class<T>  cls) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
    return cls.newInstance();
}

这两种方法能不能结合一下呢。

其实是可以的。

public  static <T>T  createInstance(String className,Class<T>  cls) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
    Class<T> cl=(Class<T>) Class.forName(className);
    Object obj = cl.newInstance();
    //需要检查cls是否为obj的字节码对象
    if(!cls.isInstance(obj)){
        throw new IllegalArgumentException("对象和类型不兼容");
    }
    return (T) obj;
}

上面的工厂方法,通过传入类名和类的Class对象名,来既保证了对象和返回的类型相互兼容,又保证了返回的对象即为字节码相对应的对象。

完整的工厂类:

/**
 *  工具类:
 *  对象工厂,专门创建对象
 *
 * @Title ObjectFactory.java
 * @Package reflectdemo
 * @Description: TODO
 * @author Young
 * @date 2017年12月29日 下午2:15:40
 * @version V1.0
 */

public class BeanFactory {

    private BeanFactory(){

    }
    public  static <T>T  createInstance(String className,Class<T>  cls) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
        Class<T> cl=(Class<T>) Class.forName(className);
        Object obj = cl.newInstance();
        //需要检查cls是否为obj的字节码对象
        if(!cls.isInstance(obj)){
            throw new IllegalArgumentException("对象和类型不兼容");
        }
        return (T) obj;
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值