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