Java反射和泛型

115 阅读5分钟

class文件包含的内容

image.png

反射

image.png

获得Class文件的三种方式 以com.snowf.Person为例

com.snowf.Person

public class Person {

    public String name = "muse";

    protected Integer age = 1;

    private Byte sex = (byte) 1;

    Boolean isMarriage = true;

    public Person() {
    }

    public Person(String name, Integer age, Byte sex, Boolean isMarriage) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.isMarriage = isMarriage;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Byte getSex() {
        return sex;
    }

    public void setSex(Byte sex) {
        this.sex = sex;
    }

    public Boolean getMarriage() {
        return isMarriage;
    }

    public void setMarriage(Boolean marriage) {
        isMarriage = marriage;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                ", isMarriage=" + isMarriage +
                '}';
    }
}

方式一:Class personClazz = Person.class

方式二:Person person = new Person();Class personClazz = person.getClass();

方式三:Class personClazz = Class.forName("com.snowf.Person");

利用反射去创建对象 以com.snowf.Person为例

/** 首先:获得Person的字节码 */
        Class personClazz = Class.forName("com.snowf.Person");

        /** 其次:通过Class对象,创建构造方法对象 */
        Constructor constructor1 = personClazz.getConstructor(String.class, Integer.class, Byte.class,
                Boolean.class); // 初始化有参构造方法对象
        Constructor constructor = personClazz.getConstructor(); // 初始化无参构造方法

        /** 最后:通过构造方法创建对象 */
        // 调用无参数构造方法创建Person对象
        Person person = (Person) constructor.newInstance();
        person.setName("muse1");
        System.out.println("person=" + person);

        // 调用有参数构造方法创建Person对象
        Person person1 = (Person) constructor1.newInstance("muse1", 10, (byte) 1, true);
        System.out.println("person1=" + person1);

反射有关的方法

  • 获取属性的两种方法
    • getField 只能获取public的,包括从父类继承来的字段。
    • getDeclaredField 可以获取本类所有的字段,包括private的,但是不能获取继承来的字段。 (注: 这里只能获取到private的字段,但并不能访问该private字段的值,除非加上setAccessible(true))
  • 获取属性值的步骤
/** 首先:获得Person的字节码 */
       Class personClazz = Person.class;

       /** 其次:获得Person对象
(由于非静态非private的属性,使用(对象.属性)方式访问,所以反射必须先获得对象实例)*/
       Person person = (Person) personClazz.getConstructor().newInstance();

       /** 第三:通过Class对象,获得Field对象 */
       Field nameField = personClazz.getField("name");
  • 属性有关方法
System.out.println("获取字段的类型:" + nameField.getType());
System.out.println("获取字段的名字:" + nameField.getName());
System.out.println("获取字段的访问修饰符:" + Modifier.toString(nameField.getModifiers()));
System.out.println("获取字段所在类的全路径:" + nameField.getDeclaringClass().getName());

反射的应用

我们采用反射机制来实现一个工具BeanUtils,可以将一个对象属性相同的值赋值给另一个对象

public static void convertor(Object originObj, Object targetObj) throws Throwable{
       // 第一步,获得class对象
       Class orginClazz = originObj.getClass();
       Class targetClazz = targetObj.getClass();

       // 第二步,获得Field
       Field[] orginFields =  orginClazz.getDeclaredFields();
       Field[] targetFields =  targetClazz.getDeclaredFields();

       // 第三步:赋值
       for (Field originField : orginFields) {
           for (Field targetField : targetFields) {
               if (originField.getName().equals(targetField.getName())) {
                   originField.setAccessible(true);
                   targetField.setAccessible(true);
                   targetField.set(targetObj, originField.get(originObj));
               }
           }
       }
   }

泛型

  • 泛型的本质是指类型参数化
  • 允许在定义类、接口、方法时使用类型形参,当使用时指定具体类型
  • 所有使用该泛型参数的地方都被统一化,保证类型已知,如果未指定具体类型,默认是Object类型。集合体系的所有类都增加了泛型,泛型也主要用在集合

泛型类

static class ObjectTool<T> {
       private T obj;
       public T getObj() {
           return obj;
       }
       public void setObj(T obj) {
           this.obj = obj;
       }
   }

泛型方法

public <T> void show(T t) {
           System.out.println(t);
       }

泛型的上下限

  • 上限
    • 格式上限: 类型名称<? extends 类> 对象名称
    • 格式意义: 只能接受该类型及其子类
    /**
     * 【读取】
     * 如果要从集合中读取类型T的数据,并且不能写入,
     * 可以使用 ? extends 通配符;(Producer Extends)
     */
    public void testPECSextends() {
        List<Dog> dogs = Lists.newArrayList();
        dogs.add(new Dog());
        List<? extends Animal> animals = dogs;
    
        /**
         * animals是一个Animal的子类的List,由于Dog是Animal的子类,
         * 因此将dogs赋给animals是合法的,但是编译器会阻止将new Cat()加入animals。
         * 因为编译器只知道animals是Animal的某个子类的List,但并不知道究竟是哪个子类,
         * 为了类型安全,只好阻止向其中加入任何子类。
         * 事实上,不能够往一个使用了? extends的数据结构里写入任何的值。
         */
        // animals.add(new Cat()); // 编译失败
        // animals.add(new Animal()); // 编译失败
        // animals.add(new Dog()); // 编译失败
    
        /**
         * 由于编译器知道它总是Animal的子类型,因此我们总可以从中读取出Animal对象:
         */
        Animal animal = animals.get(0);
        // Dog dog = animals.get(0); // 编译失败
    }
    
  • 下限
    • 格式下限: 类型名称<? super 类> 对象名称
    • 格式意义: 只能接受该类型及其父类
    /**
    * 【写入】
    * 如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)
    * <p>
    * 如果既要存又要取,那么就不要使用任何通配符。
    */
    public void testPECSsuper() {
       List<Animal> animals = Lists.newArrayList();
       List<? super Dog> dogs = animals;
       /**
        * 这里的animals是一个Animal的超类(父类,superclass)的List。
        * 同样地,出于对类型安全的考虑,我们可以加入Dog对象或者其任何子类(如WhiteDog)对象,
        * 但由于编译器并不知道List的内容究竟是Dog的哪个超类,因此不允许加入特定的任何超类型。
        */
       dogs.add(new Dog());
       dogs.add(new WhiteDog());
       // dogs.add(new Animal()); // 编译失败
       // dogs.add(new Cat()); // 编译失败
       // dogs.add(new Object()); // 编译失败
    
       /**
        * 而当我们读取的时候,编译器在不知道是什么类型的情况下只能返回Object对象,
        * 因为Object是任何Java类的最终祖先类。
        */
       Object obj = dogs.get(0);
       // Dog dog = dogs.get(0); // 编译失败
       // Animal animal = dogs.get(0); // 编译失败
    }
    

    类型擦除和桥接方法

    泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛型信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。由于类型被擦除了,为了维持多态性,所以编译器就自动生成了桥接方法。