41-咸鱼学Java-Java中的反射和类加载机制

反射

反射的核心Class类

在Java中,每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。
对于Class类有几点要注意的:
1.这个class不能人为手动生成,只能由系统生成。
2.这个JVM(Java虚拟机)中每个类的Class类只能有一个。
3.任何一个类都有一个隐含的静态成员变量class,这个class就指向JVM中的那个类的Class。

获取Class类的方法

    Student a = new Student();
    //1.通过对象名.getClass()
    Class c1 = a.getClass();
    //2.通过类名.class
    //任何一个类都有一个隐含的静态成员变量class
    Class c2 = Student.class;
    //3.通过Class对象的静态方法forName(),路径必须全包名
    Class c3=Class.forName("time20180525.Student");
    System.out.println(c1 == c2);
    System.out.println(c2 == c3);

结果
true
true
说明获取的都是同一个Class对象

Class对象中的常用方法

MethodName 作用
getName() 获得类的完整路径
getDeclaredFields() 获得类的所有属性(包括私有成员变量)
getDeclaredMethods() 获得类的所有方法
getConstructors() 得到构造函数
newInstance() 获得对象
getSuperClass() 获得其父类的Class
getClassLoader() 获得其类加载器

反射的缺点-安全性

我们可以通过反射获取到一个private属性,并且更改他。

    //不安全之处 , 可以通过反射更改private
    //1.获取属性(c1为Student的Class)
    Field name = c1.getDeclaredField("name");
    //2.设可见性
    name.setAccessible(true);
    //3.获得对象
    Student temp = (Student) c1.newInstance();
    System.out.println(temp.getName());
    //4.修改值
    name.set(temp, "hehe");
    System.out.println(temp.getName());

通过上面这一些操作就会更改掉Student中的私有变量name,这就破坏了类中的封装性。

类加载机制

类加载器的目标

类加载器的目标显而易见就是类了,那么他是如何区分类的呢,这个就涉及了.class文件中的一个标志魔数了,SUN公司规定了每个class文件都必须以0xCAFEBABE开头。

类加载器的过程

这里写图片描述
所以其在加载的时候会先进行从底向上的查询,再从上到下的尝试加载。

类加载器的机制-双亲委派机制

如果一个类加载器收到了类加载的请求,它第一步不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完全这个加载请求时,子加载器才会尝试自己去加载。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            //第一步查看它是否已经被加载
            Class c = findLoadedClass(name);
            //如果没加载
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //查看其有没有父类,如果有父类,将其委派给父类加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        //如果没有父类,说明已经到了最顶层BootstrapClassLoader,进行加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                        //捕获无此类的异常
                }
                //如果父类还是没加载成功
                if (c == null) {

                    long t1 = System.nanoTime();
                    //基类ClassLoader中,此方法抛出ClassNotFoundException(name);异常
                    //如果是ClassLoader的子类,findClass就会被重写,重写为查找路径
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            //生成Class类,和defineClass方法相同,不过此方法用于加载当前类的基类(父类)。
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

这是ClassLoader类中的loadClass方法,我们从中就可以看到双亲委派机制的代码实现。
除了这个方法,其还有两个方法很重要,分别为findClassdefineClass其中findClass为寻找类的方法,defineClass为类加载器从.class文件加载字节码生成Class对象的方法。
所以用户在写自己的类加载器的时候不能重写loadClass方法,因为重写此方法就会破坏双亲委派模型,只需要重写findClass即可。

猜你喜欢

转载自blog.csdn.net/qq_38345606/article/details/80462485