反射与类加载机制

在一般数据操作的时候,我们都知道依赖数据类型的,比如:new对象的时候;根据类型定义变量,接口,类,数组;等等。

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

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

1.Class类

每个已加载的类在内存都有意分类信息,所有类的父类Object有一个方法,可以获取对象的Class对象:

    public final native Class<?> getClass();

获取Class对象并不一定需要实例化对象,可以通过类名先来获取Class对象

Class<Date> cls = Date.class

 接口也有Class对象:

Class<Comparable> cls = Comparable.class

基本数据类型没有getClass方法,但是有对应的Class对象 ,类型参数为对应的包装类型:

        Class<Integer> intCls = int.class;
        Class<Byte> byteClass = byte.class;
        Class<Character> charCls = char.class;
        Class<Boolean> boolCls = boolean.class;

对于数组,每种类型都有对应数组类型的Class对象,每个维度一个:

        String[] strArr = new String[10];
        int[][] towintArr=new int[3][2];
        int[] oneIntArr=new int[10];
        Class<String[]> strArrCls = (Class<String[]>) strArr.getClass();
        Class<int[][]> towIntArrCls = (Class<int[][]>) towintArr.getClass();
        Class<int[]> oneIntArrCls = (Class<int[]>) oneIntArr.getClass();

Class类中还有一个静态方法forName,可以根据类名直接加载Class,获取Class对象:

        try {
            Class<?> cls = Class.forName("java.util.Map");
            System.out.println(cls);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

可以看出上述有三种方法取得Class对象:

1.类名.Class

2.实例化对象.getClass();

3.Class.forName("包名+类名");

有了Class对象之后,就可以了解到关于类型的很多信息。

1.1 名称信息

    public String getName()//返回Java内部使用的真正的名称
    public String getSimpleName()//返回不带包信息的名称
    public String getCanonicalName()//返回带包名称的信息

 

可以看出"["开头的表示数组,有几个“["就是几维数组,I表示int类型,L表示接口或者类。

1.2属性信息

类中定义的属性,可以用反射进行获取,用Field表示

    public Field[] getFields() throws SecurityException//获取类中全部属性,包括其父类的
    public Field getField(String name) throws NoSuchFieldException, SecurityException//根据名称获取属性
    public Field[] getDeclaredFields() throws SecurityException//返回本类属性,包括public,但不包括父类的
    public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException//通过指定名获取
class Person{
    public String name;
    public int age;
    private String address;

}
class student extends Person{
    public String studentID;
    public String school;
    private int achievement;
}
public class Test {
   
    public static void main(String[] args) {
        Class<student> cls = student.class;
        Field[] fields = cls.getFields();
        for (Field field :
                fields) {
            System.out.println(field.getName());
        }
    }
}

上述的代码运行的结果为:

studentID

school

 name

age

class Person{
    public String name;
    public int age;
    private String address;

}
class student extends Person{
    public String studentID;
    public String school;
    private int achievement;
}
public class Test {
    
    public static void main(String[] args) {
        Class<student> cls = student.class;
         Field[]fields1 = cls.getDeclaredFields();
         for (Field field :
         fields1) {
         System.out.println(field.getName());
         }
    }
}

上述代码运行结果为:

studentID

school

achievement

上述两段代码的运行结果就很好的比较了getFileds()与getDeclaredFields()的不同用法。

1.3方法信息

    public Method[] getMethods() throws SecurityException//返回所有的public方法,包括父类的
    public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException//通过指定名返回方法
    public Field[] getDeclaredFields() throws SecurityException//返回所有方法,不包括父类
    public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException//通过指定名返回方法
class Person{
   public void PublicPerson(){ }
   void DefaultPerson(){}
   protected void ProtectPerson(){}
   private void PrivatePerson(){}

}
class student extends Person{
    public void PublicStudent(){ }
    void DefaultStudent(){}
    protected void ProtecStudentt(){}
    private void PrivateStudent(){}
}
public class Test {
    private static final int _1MB = 1024*1024;
    public static void main(String[] args) {
        Class<student> cls = student.class;
        Method[] methods = cls.getMethods();
        for (Method method :
                methods) {
            System.out.println(method.getName());
        }
        System.out.println("-------------");
        Method[]methods1 = cls.getDeclaredMethods();
        for (Method method :
                methods1) {
            System.out.println(method.getName());
        }
    }
}

 

上述代码的运行结果很好的比较了getMethods()与getDeclaredMethods();

1.4创建对象和构造方法

Class类中还有一个方法,可以创造实例化对象

    public T newInstance() throws InstantiationException, IllegalAccessException

该方法会调用类的默认构造方法,如果该类没有该构造方法,会抛出异常。

class Person{
  private String name;
  private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

public class Test {
    private static final int _1MB = 1024*1024;
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        Class<Person> cls = Person.class;
       Person person = cls.newInstance();
        System.out.println(person);
    }
}

那当没有默认的构造方法时,但又想通过反射获取实例化对象,那么就需要通过反射获取构造方法,再通过构造方法实例化对象。

所以Class类中获取构造方法的方法:

    public Constructor<?>[] getConstructors() throws SecurityException//返回所有public构造方法
    public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException//获取指定参数的public构造方法
    public Constructor<?>[] getDeclaredConstructors() throws SecurityException//返回所有构造方法
    public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException//通过指定参数返回构造方法

有了构造方法可以通过构造方法实例化对象:

class Person{
  private String name;
  private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
       Constructor<Person> stringConstructor = Person.class.getConstructor(new Class[]{String.class,int.class});
       Person person = stringConstructor.newInstance("张三",23);
        System.out.println(person);
    }
}

运行结果:Person{name='张三', age=23}

就这样通过构造方法实例化了对象。

 class Application {
    public static String toString(Object obj) {
        try {
            Class<?> cls = obj.getClass();
            StringBuilder sb = new StringBuilder();
            sb.append(cls.getName() + "\n");
            for (Field f : cls.getDeclaredFields()) {
                if (!f.isAccessible()) //如果对象不可以访问
                {
                    f.setAccessible(true);//表示忽略Java的访问检查机制,以允许读写非public的字段
                }
                sb.append(f.getName() + "=" + f.get(obj).toString() + "\n");//获取指定对象中该字段的值
            }
            return sb.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 IllegalArgumentException(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);
        }
    }
}
public class simpleMapperDemo {
    static class Student {
        String name;
        int age;
        Double score;

        public Student() {
        }

        public Student(String name, int age, Double score) {
            super();
            this.name = name;
            this.age = age;
            this.score = score;
        }

        @Override
        public String toString() {
            return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";
        }
    }

    public static void main(String[] args) {
        Student zhangsan = new Student("张三", 18, 89d);
        String str = Application.toString(zhangsan);
        Student zhangsan2 = (Student) Application.fromString(str);
        System.out.println(zhangsan2);
    }
}



//上述代码节选自 Java编程逻辑

上面的代码就是将一个对象变成字符串,再通过字符串创建一个对象新对象。

核心代码就是toString()与fromString()两个方法。

2.类加载机制

我们都知道类的信息都在Class文件中描述,但是,最终需要加载到虚拟机中才可以使用。

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以形成被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

字节码文件加载到内存中创建Class对象就需要ClassLoader来加载其他类

Java运行时,会根据类的完全限定名寻找并加载类,寻找的方式就是在系统类和指定类路径中寻找,如果是class文件的根目录,则直接查看是否有对应的子目录以及文件。

2.1 类加载器

从Java虚拟机的角度来讲,类加载器一般就分为两种,一种是启动类加载器;另一种就是其它类加载器。

但是其实还可以划分的更细,一般程序运转时基本会用到三个类加载器;

1.启动类加载器(Bootstrap  ClassLoader):这个加载器是Java虚拟机的一部分,是由C++实现的,它负责加载Java的基础类,主要是<JAVA_HOME>/lib/rt.jar;日常用的Java类库,如:String、ArrayList就在该包内。

2.扩展类加载器(Extension  ClassLoader):这个加载器的实现类是sun.misc.Launcher$ExtClassLoader,他会负责加载Java的一些扩展类,一般是<JAVA_HOMA>/lib/ext目录中的jar包。

3.应用程序类加载器(Application  ClassLoader):这个加载器的实现类是sun.misc.Launcher$AppClassLoader,它负责加载应用程序的类,包括自己写的和引入的第三方类库,即所有在类路径中指定的类。

这三个类加载器是父子委派关系,Application  ClassLoader的父亲是Extension  ClassLoader;Extension  ClassLoader的父亲是Bootstrap  ClassLoader。

2.2双亲委派模型

类加载过程:

1.判断是否已经加载过了,加载过了,直接返回Class对象,一个类只会被ClassLoader加载一次;

2.没有加载过,先让父ClassLoader去加载,加载成功,就返回得到的Class对象;

3.父类加载器没有加载成功时,自己尝试加载类。

上述过程就是一个双亲委派模型。

利用双亲委派模型就可以避免Java类库被覆盖的问题。

比如:自己定义了一个ArrayList,通过双亲委派模型,就只会被启动类加载器加载,避免自己定义的类覆盖Java库类。

ClassLoader是一个抽象类,上述说过Application ClassLoader以及 Extension ClassLoader都实现类;

首先Class类下面有一个方法获取实际加载它的ClassLoader:

    public ClassLoader getClassLoader()

ClassLoader有一个方法,可以获取它的父ClassLoader:

    public final ClassLoader getParent()

因为BootStrap ClassLoader是C++写的,没有实现类,所以返回为null;

public class Test {

    public static void main(String[] args) {
      Class<?>cls = Test.class;
        System.out.println(cls.getClassLoader());
        System.out.println(cls.getClassLoader().getParent());
        System.out.println(cls.getClassLoader().getParent().getParent());
    }
}

结果:

sun.misc.Launcher$AppClassLoader@18b4aac2

sun.misc.Launcher$ExtClassLoader@4554617c

null

ClassLoader有一个静态方法,可以获取默认的系统类加载器;

    public static ClassLoader getSystemClassLoader()

ClassLoader中加载类的方法;

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

需要说明的是loadClass()不会执行类的代码块;

public class Test {
   public static class Hello{
        static {
            System.out.println("Hello World");
        }
    }

    public static void main(String[] args) {
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        try {
            
            Class<?> cls = cl.loadClass("Test$Hello");
            
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

结果并没有执行静态代码块中的内容。

public class Test {
   public static class Hello{
        static {
            System.out.println("Hello World");
        }
    }

    public static void main(String[] args) {


        try {
            Class<?> cls = Class.forName("Test$Hello");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}

但是使用forName加载类的时候,就会执行。

 

好了,敲代码了

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值