https://blog.csdn.net/bingduanlbd/article/details/8424243/
https://blog.csdn.net/My_TrueLove/article/details/51289217
Java Class类理解:
首先,Class是一个java类,跟Java API中定义的诸如Thread、Integer类、我们自己定义的类是一样,也继承了Object(Class是Object的直接子类)。总之,必须明确一点,它其实只是个类,只不过名字比较特殊。更进一步说,Class是一个java中的泛型类型。
对于我们自己定义的类,我们用类来抽象现实中的某些事物,比如我们定义一个名称为Car的类来抽象现实生活中的车,然后可以实例化这个类,用这些实例来表示我的车、你的车、黄的车、红的车等等。
好了,现在回到Class 类上来,这个类它抽象什么了?它的实例又表示什么呢?
在一个运行的程序中,会有许多类和接口存在。我们就用Class这个来来表示对这些类和接口的抽象,而Class类的每个实例则代表运行中的一个类。例如,运行的程序有A、B、C三个类,那么Class类就是对A、B、C三个类的抽象。所谓抽象,就是提取这些类的一些共同特征,比如说这些类都有类名,都有对应的hashcode,可以判断类型属于class、interface、enum还是annotation。这些可以封装成Class类的域,另外可以定义一些方法,比如获取某个方法、获取类型名等等。这样就封装了一个表示类型(type)的类。
需要注意的是,这个特殊的Class类没有公开的构造函数,那怎么获取Class类的实例呢?有几个途径。
1. 当Java虚拟机载入一个类的时候,它就会自动创建一个Class类的实例来表示这个类。例如,虚拟机载入Car这个来的时候,它就会创建一个Class类的实例。然后可以通过以下方法获得这个Class对象:
java.lang.Class classObj = ClassName.class;
2. 可以通过调用类加载器(ClassLoader)的defineClass()方法来得到一个实例。这个方法接受一个byte数组,载入这个byte数组否成的class类,同时实例化一个Class对象。
3. ClassName.class( ) ClassName.getClass( )
很遗憾直到现在我才认识到 Class 的意义,不敢说这次接触就彻底弄懂了 Class
类,但至少目前所理解的内容可以帮助我更好的分析其他问题了。
都说学习技术最好的方法就是查看源码和官方 API ,那么这次我也这么来,虽然我技术一般,但是格调还是要跟上的!我发现了一篇比较好的从源码角度分析 Class
类的博客,在这里推荐给大家,感兴趣的可以跟着这篇文章把 Class
类的源码过一遍,一定会有收获的。当然,您现在不看也没关系,因为推荐的博客中只有一点是我接下来要强调的,我会在后面给大家概括出来。点我跳转
先来看看 Class
类的构造方法(有删减):
/*
* Private constructor.
* Only the Java Virtual Machine
* creates Class objects.
*/
private Class(ClassLoader loader) {
classLoader = loader;
}
根据注释可知,Class 类的构造方法是私有的,只有 Java 虚拟机可以创建该类的对象,因此我们无法在代码中显式地声明一个 Class 对象。这就是我要强调的内容,其对于后面内容的理解十分重要。
由类或类对象得到 Class 类的对象
自定义一个类 MyClass,并声明该类的对象:
class MyClass{}
MyClass mClass1 = new MyClass();
在上面说过,Class
类的构造方法是私有的,只有 java 虚拟机可以调用该方法创建该类的对象。也就是说我们无法像定义普通类对象一样,通过 new 直接创建 Class
类的对象。
但是,我们依然可以通过其他方式得到 Class 类的对象
通过类的静态成员表示。每个类都有一个隐含的静态成员class,表示如下:
Class c1 = MyClass.class;
通过类对象的 getClass()
方法。由1不难理解,既然存在静态变量,那么通过对象的 getter 方法,就可以获取静态成员class:
Class c2 = mClass1.getClass();
通过 Class
类的静态方法 forName()
方法获取 Class
的对象。区别于通过 new 创建对象(编译时静态加载),在开发时如果我们需要动态加载我们的功能模块,该方法可以帮助我们实现在程序运行时类的动态加载。
try {
//注意,forName()需要传入类的全路径
//如果当前类与参数类在同一包下即可省略包名
mClass = Class.forName("custom.OtherClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
如果我们的程序中没有 OtherClass 这个类,使用 Class.forName() 动态加载时,在程序编译时刻是不会报错的,只有在运行时刻检测到没有该类才会出错。
类的动态加载和静态加载是 Java 中一个重要的知识点,但因为我们开发时通常都使用 IDE, 其帮助我们自动实现了程序的编译和运行,使得我们常常忽略了程序的编译时和运行时状态。说着说着,我发现这块我有必要再写一篇博客记录一下,今天就先不讨论了。
由 Class 类的对象得到类的对象
我们可以通过类或类对象得到 Class
类的对象,反过来,我们也可以由 Class
类的对象得到类的对象:
MyClass mClass2 = (MyClass)c2.newInstance();
需要提醒您的是:c2.newInstance()
需要调用 MyClass
类的无参构造方法!如果 MyClass
类中存在显示的有参构造方法,会覆盖默认的无参构造方法,同时又没有显示的声明无参构造方法,那么执行这段代码时会直接导致程序Crash掉。下面为大家演示一下:
示例代码,请看注释:
/**
* Created by Mr Rui on 2016/5/1.
*/
public class TestDemo {
public static void main(String args[]){
//得到 Class 对象 mClass
Class mClass = MyClass.class;
try {
// 由 mClass 实例化 MyClass 的对象,
// 需要调用 MyClass 的无参构造方法
MyClass mMyClass =
(MyClass) mClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyClass{
//有参构造方法,会覆盖默认的无参构造方法
public MyClass(String s){}
}
程序异常:
java.lang.InstantiationException: MyClass
at java.lang.Class.newInstance(Class.java:427)
at TestDemo.main(TestDemo.java:12)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.NoSuchMethodException: MyClass.()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 6 more
解决办法就是在 MyClass 中的显示的添加一个 无参构造方法,就不再提供示例了。
针对这一点,相信有许多人在使用第三方框架或者开源库时,遇到过因为在类中添加了带参数的构造方法而导致程序出错的情况!针对这个,我的理解是:有些框架是基于反射实现的,它根据我们传入的类对象,使用其 newInstance()
方法获取 Class
对象,进而进行后续的反射操作(不在本文的讨论范围)。可是因为我们无意覆盖了默认的无参构造方法,导致程序无法正常获取 Class
对象,所以就出错了。说到这儿,您应该能理解其中缘由了吧!
需要注意的是,这个特殊的Class类没有公开的构造函数,那怎么获取Class类的实例呢?有几个途径。
1. 当Java虚拟机载入一个类的时候,它就会自动创建一个Class类的实例来表示这个类。例如,虚拟机载入Car这个来的时候,它就会创建一个Class类的实例。然后可以通过以下方法获得这个Class对象:
java.lang.Class classObj = ClassName.class;
2. 可以通过调用类加载器(ClassLoader)的defineClass()方法来得到一个实例。这个方法接受一个byte数组,载入这个byte数组否成的class类,同时实例化一个Class对象。
3. ClassName.class( ) ClassName.getClass( )
现在来分析一下Class类的源码(java.lang.Class):
public final
class Class
java.lang.reflect.GenericDeclaration,
java.lang.reflect.Type,
java.lang.reflect.AnnotatedElement {…
ClassClass
紧接着定义了几个静态变量:
private static final int ANNOTATION= 0x00002000;
private static final int ENUM = 0x00004000;
private static final int SYNTHETIC = 0x00001000;
接着定义一个本地方法registerNatives(),并在静态块中调用:
private static native void registerNatives();
static {
registerNatives();
}
私有的构造函数:
private Class() {}
访问修饰符是private,程序员是无法直接调用这个构造函数,只能通过JVM来调用它,构造一个Class实例。
public String toString() {
return (isInterface() ?"interface " : (isPrimitive() ? "" : "class"))
+ getName();
}
这是Class对象实例的字符串表示方法,应该不陌生。那么,它返回什么东西呢?
>如果这个Class对象实例所表示的是一个Java类,则返回class full_classname.
例如java.lang.Math.java这个类,它所对应的Class实例的toString方法返回的就是class java.lang.Math
>如果是接口,将class改成interface。还有一种特殊情况,如果Class实例表示的是void类型,则发挥void。如果是基本类型,一样的返回基本类型的名称。
静态方法forName:
public static Class> forName(String className)
throws ClassNotFoundException{
returnforName0(className,true, ClassLoader.getCallerClassLoader());
}
根据给定的类名参数className,查找与className相对应的Class实例,然后加载、连接该实例对象,之后返回这个Class实例。其中例如以下代码段将输出:
class java.lang.Thread
public class ClassTest {
/**
* @param args
*/
public static void main(String[] args) {
try {
System.out.println( Class.forName("java.lang.Thread") );
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.out.println("No ClassNamed java.lang.Thread");
}
}
}
forName方法重载:
public static Class> forName(String name, boolean initialize,
ClassLoaderloader)
throws ClassNotFoundException
{
if (loader ==null) {
SecurityManager sm = System.getSecurityManager();
if (sm !=null) {
ClassLoader ccl = ClassLoader.getCallerClassLoader();
if (ccl !=null) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
returnforName0(name, initialize,loader);
}
注意到这个forName重载方法中多了两个方法参数,其中initialize这个boolean类型指定是否要初始化对应的Class实例,loader指定加载Class实例的加载器。留意这个方法可能抛出的异常还是比较多的,比如连接失败、找不到对应的类等等。
forName0本地方法:
private static native Class> forName0(String name,boolean initialize,
ClassLoaderloader)
throws ClassNotFoundException;
这是一个本地方法,在前面的静态方法forName的两个版本中都调用了这个本地方法。
newInstance()方法:
public T newInstance() // T是个泛型参数,是Class实例所表示的Java类
throws InstantiationException,IllegalAccessException
{
if (System.getSecurityManager()!=null) {
checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader());
}
return newInstance0(); //这是一个本地方法,也在Class类中定义
}
注意这是一个实例方法,必须由Class类的实例对象调用。例如,有一个代表java.lang.Thread类的Class实例对象objec1,也就是说,泛型参数T此时就是Thread,object1这个实例代表Thread这个类。好了,现在调用object1的newInstance方法,即object1.newInstance(),此时这个调用将返回一个Thread类的对象。简单验证:
public class ForName {
/**
* @paramargs
* @throwsIllegalAccessException
* @throwsInstantiationException
*/
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Class> c = null ;
try {
c = Class.forName("java.lang.Thread");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Thread thread = (Thread) c.newInstance(); //类型转化一下
System.out.println(thread.getId());
}
}
在我机子中,上述代码输出 8 。即c.newInstance产生一个ID为8的新线程。
getClassLoader:
public ClassLoader getClassLoader() {
ClassLoader cl = getClassLoader0();
if (cl ==null)
return null; // Bootstrap
SecurityManager sm = System.getSecurityManager();
if (sm !=null) {
ClassLoader ccl = ClassLoader.getCallerClassLoader();
if (ccl !=null && ccl != cl&& !cl.isAncestor(ccl)) {
sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
return cl;
}
//Package-private to allow ClassLoader access
native ClassLoadergetClassLoader0(); // 本地方法
这个方法返回该Class对象代表的类的类加载器。如果类的加载器是Bootstrap,则返回null。下面的代码输出:The ClassLoader of Thread Class is Bootstrap
Class> classObj= Thread.class;
ClassLoader loader = classObj.getClassLoader();
if (loader ==null) {
System.out.println("TheClassLoader of Thread Class is Bootstrap");
} else {
System.out.println(loader);
}
获取父类方法:getSuperclass()
public native Class super T> getSuperclass();
这是一个本地方法,这里的逻辑有点饶,方法返回的是这个Class对象所代表的Java类的父类对应的的Class 对象。
例如: Thread.class.getSuperclass()将返回一个代表Thread类的Class对象,Thread.class.getSuperclass().toString()则输出这个Class对象的字符串表示:classjava.lang.Object。其实这里的关系无非就是说Thread的超类是Object。只是饶了Class对象这个弯子,至于这么绕有什么好处,还没有深刻体会