Java基础9—反射

本文探讨了Java中的反射机制,包括反射与new的区别、静态编译与动态编译的概念,以及获取Class对象的四种方式。反射允许在运行时动态获取类信息,创建对象并访问/修改成员、调用方法。虽然反射提供了灵活性,但可能导致运行时错误和性能下降,通常不作为首选方案。
摘要由CSDN通过智能技术生成


在一般操作数据的时候,我们都是知道并且依赖于数据类型的,比如:

1)根据类型使用new创建对象。

2)根据类型定义变量,类型可能是基本类型、类、接口或数组。

3)将特定类型的对象传递给方法。

4)根据类型访问对象的属性,调用对象的方法。

编译器也是根据类型进行代码的检查编译的。

反射不一样,它是在运行时,而非编译时,动态获取类型的信息,比如接口信息、成员信息、方法信息、构造方法信息等,根据这些动态获取到的信息创建对象、访问/修改成员、调用方法等。

1. 反射和new

反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。

基本上效果差不多,但是new对象,无法调用该类里面私有的东西,反射反之,具体怎么做请参考java AIP,不过反射需要以牺牲性能做代价。

在不知道类名的情况下,你怎么去new?我相信很多人看到这句话都迷糊了(新手),肯定有这样的疑问,不知道类名,你怎么反射啊?

那么接下来在讲讲new和反射本质上的区别,new属于静态编译,而反射属于动态编译,意思就说只有到运行时他才会去获得该对象的实例,可能讲的有些抽象(也有可能讲的不太正确)

举例:spring框架是事先就写好的框架,他内部的处理并不知道用户要写哪些类,应为那是以后由用他的人来定的,这时候你还能在spring内部去new吗?所以用户在用的时候才去配置文件中配置类路径

1.1 静态编译和动态编译

静态编译就是在编译的时候把你所有的模块都编译进exe里去,当你启动这个exe的时候所有模块都加载进来了。你写小程序没问题,但程序一大,加载的过程(就是当你运行程序时初始化的过程)就比较费力了。。大多数ppc的硬件配置还是很一般的。

动态编译就不一样了,你编译的时候那些模块都没有编译进去,一般情况下你可以把那些模块都编译成dll,这样你启动程序(初始化)的时候这些模块不会被加载,而是在运行的时候,用到那个模块就调用哪个模块。

class类

每一个java类都会被编译成class类,在运行时才会使用的可以利用反射进行加载并使用。

1.2 获取Class对象的四种方式

如果我们动态获取到这些信息,我们需要依靠 Class 对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。Java 提供了四种方式获取 Class 对象:

1.知道具体类的情况下可以使用:

Class alunbarClass = TargetObject.class;  // Copy to clipboardErrorCopied

但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化

2.通过 Class.forName()传入类的路径获取:

Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");Copy to clipboardErrorCopied

3.通过对象实例instance.getClass()获取:

TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();Copy to clipboardErrorCopied

4.通过类加载器xxxClassLoader.loadClass()传入类路径获取:

Class clazz = ClassLoader.loadClass("cn.javaguide.TargetObject");Copy to clipboardErrorCopied

通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一些列步骤,静态块和静态对象不会得到执行

2. 反射实例

// 测试类
public class test_1 {
    @AllArgsConstructor
    @NoArgsConstructor
    @Getter
    @Setter
    @ToString
    static class Student{
        
        String name;
        int age;
        Double score;
    }
    
    public static void main(String[] args) throws Exception{
        Student z = new Student("张三",18,89d);
        String str = SimpleMapper.toString(z);
        
        Student z2 = (Student)SimpleMapper.fromString(str);	// 根据名称创建对象

        System.out.println(z2);
    }
}

// 序列化和反序列化类
public class SimpleMapper {
    
    public static String toString(Object obj){
        try{
            Class<?> cls = obj.getClass();
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(cls.getName()+"\n");
            
            for(Field f: cls.getDeclaredFields()){
                if(!f.isAccessible()){
                    f.setAccessible(true);
                }
                stringBuilder.append(f.getName() + "=" + f.get(obj).toString() + "\n"); 
            }
            return stringBuilder.toString();   
        }catch (IllegalAccessException e){
            throw new RuntimeException(e);
        }
    }

    // 类型转换,根据字段的类型,将字符串形式的值转换为了对应类型的值。
    private static void setFieldValue(Field f, Object obj, String value) throws Exception {
        Class<?> type = f.getType();
        if (type == int.class) {
            f.setInt(obj, Integer.parseInt(value));
        } else if (type == byte.class) {
            f.setByte(obj, Byte.parseByte(value));
        } else if (type == short.class) {
            f.setShort(obj, Short.parseShort(value));
        } else if (type == long.class) {
            f.setLong(obj, Long.parseLong(value));
        } else if (type == float.class) {
            f.setFloat(obj, Float.parseFloat(value));
        } else if (type == double.class) {
            f.setDouble(obj, Double.parseDouble(value));
        } else if (type == char.class) {
            f.setChar(obj, value.charAt(0));
        } else if (type == boolean.class) {
            f.setBoolean(obj, Boolean.parseBoolean(value));
        } else if (type == String.class) {
            f.set(obj, value);
        } else {
            Constructor<?> ctor = type.getConstructor(new Class[] { String.class });
            f.set(obj, ctor.newInstance(value));
        }
    }
    
    public static Object fromString(String str){
        try{
            String[] lines = str.split("\n");
            if(lines.length < 1){
                throw new IllegalStateException(str);
            }
            
            Class<?> cls = Class.forName(lines[0]);
            
            Object obj = cls.newInstance();
            
            if(lines.length > 1){
                for (int i = 1; i < lines.length; i++) {
                    String[] fv = lines[i].split("=");
                    if (fv.length != 2) {
                        throw new IllegalArgumentException(lines[i]);
                    }
                    Field f = cls.getDeclaredField(fv[0]);
                    if(!f.isAccessible()){
                        f.setAccessible(true);
                    }
                    setFieldValue(f, obj, fv[1]);
                }
            }
            return obj;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

3. 反射与泛型

在介绍泛型的时候,我们提到,泛型参数在运行时会被擦除,这里,我们需要补充一下,在类信息Class中依然有关于泛型的一些信息,可以通过反射得到。泛型涉及一些更多的方法和类。

3.1 反射获取泛型实例

static class GenericTest<U extends Comparable<U>, V>{
    U u;
    V v;
    List<String> list;
    
    public U test(List<? extends Number> numbers){
        return null;
    }
}

public static void main(String[] args) throws Exception {
    Class<?> cls = GenericTest.class;
    
    // 类的类型参数
    for(TypeVariable t: cls.getTypeParameters()){
        System.out.println(t.getName() + "extends" + Arrays.toString(t.getBounds()));
    }
    
    // 字段:泛型类型
    Field fu = cls.getDeclaredField("u");
    System.out.println(fu.getGenericType());
    
    // 字段:参数化的类型
    Field flist = cls.getDeclaredField("list");
    Type listType = flist.getGenericType();
    if (listType instanceof ParameterizedType) {
        ParameterizedType pType = (ParameterizedType) listType;
        System.out.println("raw type: " + pType.getRawType() + ",type arguments:"
                + Arrays.toString(pType.getActualTypeArguments()));
    }
    
    // 方法的泛型参数
    Method m = cls.getMethod("test", new Class[] { List.class });
    for (Type t : m.getGenericParameterTypes()) {
        System.out.println(t);
    }
}

反射虽然是灵活的,但一般情况下,并不是我们优先建议的,主要原因是:

  • 反射更容易出现运行时错误,使用显式的类和接口,编译器能帮我们做类型检查,减少错误,但使用反射,类型是运行时才知道的,编译器无能为力。

  • 反射的性能要低一些,在访问字段、调用方法前,反射先要查找对应的Field/Method,要慢一些。

简单地说,如果能用接口实现同样的灵活性,就不要使用反射。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值