java源码之Class

​ 源码的重要性不言而喻,虽然枯燥,但是也有拍案叫绝。这是我的源码系列第二弹,后续还会一直更新,欢迎交流。String源码可以看我的Java源码之String,如有不足,希望指正。

1.class这个类是什么

Class的本质也是一个类,只不过它是将我们定义类的共同的部分进行抽象,比如我们常定义的类都含有构造方法,类变量,函数,而Class这个类就是来操作这些属性和方法的。当然我们常定义的类包含的类型都可以通过Class间接的来操作。而类的类型包含一般的类,接口,枚举类型,注解类型等等。这么说可能有点太理论,我们看下面这个例子:

我们将生活中的一类事物抽象为一个类的时候,往往是因为他们具有相同的共性和不同的个性。定义一个类的作用就是将相同的共性抽离出来。一般的类都包含属性和方法(行为),下面我们定义水果和汽车这两个大类:

代码如下:

汽车类:

class Car{

    // 定义属性
private String name;
private String color; /**
* 定义两个构造方法
*/
public Car(){ } public Car(String name,String color){
this.name = name;
this.color = color;
} /**
* 定义两个普通方法(行为)
*/
public void use(){ } public void run(){ } /**
* 属性的get和set方法
* @return
*/
public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getColor() {
return color;
} public void setColor(String color) {
this.color = color;
}
}

水果类:

class Fruit{

    // 定义属性
private String name;
private int size; /**
* 定义两个构造方法
*/
public Fruit(){ } public Fruit(String name,int size){
this.name = name;
this.size =size;
} /**
* 定义两个方法(行为)
*/
public void use(){ } public void doFruit(){ } /**
* 属性的get和set方法
* @return
*/
public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getSize() {
return size;
} public void setSize(int size) {
this.size = size;
}
}

可以看到水果和汽车这两个类都有共同的部分,也就是一个类共同的部分,那就是属性和方法,而Class就是来操作我们定义类的属性和方法。

​小试牛刀:通过Class这个类来获取Fruit这个类中定义的方法;

public static void main(String[] args) {

        Fruit fruit = new Fruit();
Class fruitClass = fruit.getClass(); Method[] fruitMethods = fruitClass.getMethods();
System.out.println("方法个数:" + fruitMethods.length); for (Method method : fruitMethods) {
//得到返回类型
System.out.print("方法名称和参数:" + method.getName() + "(");
//取得某个方法对应的参数类型数组
Class[] paramsType = method.getParameterTypes();
for (Class paramType : paramsType) {
System.out.print(paramType.getTypeName() + " ");
}
System.out.print(")"); Class returnType = method.getReturnType();
System.out.println("返回类型:" + returnType.getTypeName());
}
}

运行结果:

方法个数:15
方法名称和参数:getName()返回类型:java.lang.String
方法名称和参数:setName(java.lang.String )返回类型:void
方法名称和参数:getSize()返回类型:int
方法名称和参数:setSize(int )返回类型:void
方法名称和参数:use()返回类型:void
方法名称和参数:doFruit()返回类型:void
方法名称和参数:wait()返回类型:void
方法名称和参数:wait(long int )返回类型:void
方法名称和参数:wait(long )返回类型:void
方法名称和参数:equals(java.lang.Object )返回类型:boolean
方法名称和参数:toString()返回类型:java.lang.String
方法名称和参数:hashCode()返回类型:int
方法名称和参数:getClass()返回类型:java.lang.Class
方法名称和参数:notify()返回类型:void
方法名称和参数:notifyAll()返回类型:void

这里可能有人疑惑了,Fruit类并没有定义的方法为什么会出现,如wait(),equals()方法等。这里就有必要说一下java的继承和反射机制。在继承时,java规定每个类默认继承Object这个类,上述这些并没有在Fruit中定义的方法,都是Object中的方法,我们看一下Object这个类的源码就会一清二楚:

 public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
} public final native void wait(long timeout) throws InterruptedException; public final void wait() throws InterruptedException {
wait(0);
}

而Class类中的getMethods()方法默认会获取父类中的公有方法,也就是public修饰的方法。所以Object中的公共方法也出现了。

注: 要想获得父类的所有方法(public、protected、default、private),可以使用apache commons包下的FieldUtils.getAllFields()可以获取类和父类的所有(public、protected、default、private)属性。

是不是感觉非常的强大 ,当然,使用Class来获取一些类的方法和属性的核心思想就是利用了Java反射特性。万物皆反射,可见反射的强大之处,至于反射的原理,期待我的下一个博客。

2.常用方法的使用以及源码分析

2.1构造方法

源码如下:

 private Class(ClassLoader loader) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
}

可以看到Class类只有一个构造函数,并且是私有的。也就是说不能通过new来创建这个类的实例。官方文档的解释:私有构造函数,仅Java虚拟机创建Class对象。我想可能就是为了安全,具体原因不是很了解。如果有了解的话,可以在评论区内共同的交流。

Class是怎么获取一个实例的。

那么既然这个class构造器私有化,那我们该如何去构造一个class实例呢,一般采用下面三种方式:

1.运用.class的方式来获取Class实例。对于基本数据类型的封装类,还可以采用.TYPE来获取相对应的基本数据类型的Class实例,如下的示例。

 // 普通类获取Class的实例。接口,枚举,注解,都可以通过这样的方式进行获得Class实例
Class fruitClass = Fruit.class; // 基本类型和封装类型获得Class实例的方式,两者等效的
Class intClass = int.class;
Class intClass1 = Integer.TYPE;

下面的表格两边等价:

boolean.class Boolean.TYPE
char.class Character.TYPE
byte.class Byte.TYPE
short.class Short.TYPE
int.class Integer.TYPE
long.class Long.TYPE
float.class Float.TYPE
double.class Double.TYPE
void.class Void.TYPE

但是这种方式有一个不足就是对于未知的类,或者说不可见的类是不能获取到其Class对象的。

2.利用对象.getClass()方法获取该对象的Class实例;

这是利用了Object提供的一个方法getClass() 来获取当着实例的Class对象,这种方式是开发中用的最多的方式,同样,它也不能获取到未知的类,比如说某个接口的实现类的Class对象。

Object类中的getClass()的源码如下:

public final native Class<?> getClass();

源码说明:

可以看到,这是一个native方法(一个Native Method就是一个java调用非java代码的接口),并且不允许子类重写,所以理论上所有类型的实例都具有同一个 getClass 方法。

使用:

 Fruit fruit = new Fruit();
Class fruitClass = fruit.getClass();

3.使用Class类的静态方法forName(),用类的名字获取一个Class实例(static Class forName(String className) ),这种方式灵活性最高,根据类的字符串全名即可获取Class实例,可以动态加载类,框架设计经常用到;

源码如下:

    /*
由于方法区 Class 类型信息由类加载器和类全限定名唯一确定,所以参数name必须是全限定名,
参数说明 name:class名,initialize是否加载static块,loader 类加载器
*/
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
Class<?> caller = null; // 1.进行安全检查
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
....
}
}
// 2.调用本地的方法
return forName0(name, initialize, loader, caller);
} // 3.核心的方法
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
throws ClassNotFoundException; /*
这个 forName是上述方法的重载,平时一般都使用这个 方法默认使用调用者的类加载器,将类的.class文件加载 到 jvm中
这里传入的initialize为true,会去执行类中的static块
*/
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

源码说明已在注释中说明,有些人会疑惑, static native Class<?> forName0()这个方法的实现。

这就要说到java的不完美的地方了,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展Java程序的功能。有关native的方法请移步这里

基本使用:

 Class fruitClass = Class.forName("cn.chen.test.util.lang.Fruit");

: 这种方式必须使用类的全限定名,,这是因为由于方法区 Class 类型信息由类加载器和类全限定名唯一确定,否则会抛出ClassNotFoundException的异常。

2.2一般方法以及源码分析:

Class类的一般的方法总共有六十多种,其实看到这么多方法咱也不要怂,这里面还有很多重载的方法,根据二八原则,我们平时用的也就那么几个方法,所以这里只对以下几个方法的使用和实现进行交流,其他的方法可以移步Java官方文档

2.2.1 获得类的构造方法

这个方法主要是用来了解一个类的构造方法有哪些,包含那些参数,特别是在单例的模式下。一般包含的方法如下:

  • public Constructor[] getConstructors() :获取类对象的所有可见的构造函数

  • public Constructor[] getDeclaredConstructors():获取类对象的所有的构造函数

  • public Constructor getConstructor(Class... parameterTypes): 获取指定的可见的构造函数,参数为:指定构造函数的参数类型数组,如果该构造函数不可见或不存在,会抛出 NoSuchMethodException 异常

  • public Constructor getDeclaredConstructor(Class... parameterTypes) :获取指定的构造函数,参数为:指定构造函数的参数类型数组,无论构造函数可见性如何,均可获取

基本使用:

Constructor[] constructors = fruitClass.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("获得共有的构造方法:"+constructor);
}

输出结果:

获得共有的构造方法:public cn.chen.test.util.lang.Fruit()
获得共有的构造方法:public cn.chen.test.util.lang.Fruit(java.lang.String,int)

可以看到我们前面定义的来个构造方法,都被打印出来了。注意getConstructors()只能获得被public修饰的构造方法,如果要获得被(protected,default,private)修饰的构造方法,就要使用的getDeclaredConstructors()这个方法了。接下来,修改Fruit中的一个构造方法为private:

 private  Fruit(String name,int size){
this.name = name;
this.size =size;
}

使用getConstructors()和getDeclaredConstructors()着两个方法进行测试:

       Class fruitClass = Fruit.class;
Constructor[] constructors = fruitClass.getConstructors();
Constructor[] constructors1 = fruitClass.getDeclaredConstructors(); for (Constructor constructor : constructors) {
System.out.println("获得共有的构造方法:"+constructor);
} System.out.println("=================================================");
for (Constructor constructor : constructors1) {
System.out.println("获得所有的构造方法:"+constructor);
}

输出结果:

获得共有的构造方法:public cn.chen.test.util.lang.Fruit()
===================分隔线=============================
获得所有的构造方法:public cn.chen.test.util.lang.Fruit()
获得所有的构造方法:private cn.chen.test.util.lang.Fruit(java.lang.String,int)

可以看到两者的区别。所以,反射在一定程度上破坏了java的封装特性。毕竟人无完人,语言亦是一样。

getConstructors()的源码分析:

public Constructor<?>[] getConstructors() throws SecurityException {

        // 1.检查是否允许访问。如果访问被拒绝,则抛出SecurityException。
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
return copyConstructors(privateGetDeclaredConstructors(true));
} private static <U> Constructor<U>[] copyConstructors(Constructor<U>[] arg) {
// 2.使用克隆,得到当前类的所有构造函数
Constructor<U>[] out = arg.clone();
// 3.使用ReflectionFactory构造一个对象,也是不使用构造方法构造对象的一种方式。
ReflectionFactory fact = getReflectionFactory();
// 4.遍历,将构造函数进行拷贝返回,注意在调用fact.copyConstructor(out[i])这个方法的时候,还会进行安全检查,用的就是下面的LangReflectAccess() 这个方法。
for (int i = 0; i < out.length; i++) {
out[i] = fact.copyConstructor(out[i]);
}
return out;
} private static LangReflectAccess langReflectAccess() {
if (langReflectAccess == null) {
Modifier.isPublic(1);
} return langReflectAccess;
}

通过打断点调试,可以看到下面的信息:

代码的调用逻辑在注释里已进行说明。

2.2.2 获得属性

主要获取类的属性字段,了解这个类声明了那些字段。

一般有四个方法:

  • public Field[] getFields():获取所有可见的字段信息,Field数组为类中声明的每一个字段保存一个Field 实例
  • public Field[] getDeclaredFields():获取所有的字段信息
  • public Field getField(String name) :通过字段名称获取字符信息,该字段必须可见,否则抛出异常
  • public Field getDeclaredField(String name) :通过字段名称获取可见的字符信息

基本使用:

首先我们在Fruit的类中加入一个public修饰的属性:

    public double weight;
Class fruitClass = Fruit.class;
Field[] field2 = fruitClass.getFields();
for (Field field : field2) {
System.out.println("定义的公有属性:"+field);
} Field[] fields = fruitClass.getDeclaredFields();
for (Field field : fields) {
System.out.println("定义的所有属性:"+field);
}

输出结果:

定义的公有属性:public double cn.chen.test.util.lang.Fruit.weight
========================分隔线============================
定义的所有属性:private java.lang.String cn.chen.test.util.lang.Fruit.name
定义的所有属性:private int cn.chen.test.util.lang.Fruit.size
定义的所有属性:public double cn.chen.test.util.lang.Fruit.weight

源码分析,就以getFileds()这个方法为例,涉及以下几个方法:

public Field[] getFields() throws SecurityException {
// 1.检查是否允许访问。如果访问被拒绝,则抛出SecurityException。
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
return copyFields(privateGetPublicFields(null));
} private static Field[] copyFields(Field[] arg) {
// 2. 声明一个Filed的数组,用来存储类的字段
Field[] out = new Field[arg.length];
// 3.使用ReflectionFactory构造一个对象,也是不使用构造方法构造对象的一种方式。
ReflectionFactory fact = getReflectionFactory();
// 4.遍历,将字段复制后返回。
for (int i = 0; i < arg.length; i++) {
out[i] = fact.copyField(arg[i]);
}
return out;
} public Field copyField(Field var1) {
return langReflectAccess().copyField(var1);
} // 再次检查属性的访问权限
private static LangReflectAccess langReflectAccess() {
if (langReflectAccess == null) {
Modifier.isPublic(1);
} return langReflectAccess;
}

2.2.3 获得一般方法

就是获取一个类中的方法,一般有以下几个方法:

  • public Method[] getMethods(): 获取所有可见的方法

  • public Method[] getDeclaredMethods() :获取所有的方法,无论是否可见

  • public Method getMethod(String name, Class... parameterTypes)

    参数说明:

  1. 通过方法名称、参数类型获取方法
  2. 如果你想访问的方法不可见,会抛出异常
  3. 如果你想访问的方法没有参数,传递 null作为参数类型数组,或者不传值)
  • public Method getDeclaredMethod(String name, Class... parameterTypes)
  1. 通过方法名称、参数类型获取方法
  2. 如果你想访问的方法没有参数,传递 null作为参数类型数组,或者不传值)

基本使用:

//在fruit中定义一个这样的方法
private void eat(String describe){
System.out.println("通过getMethod()方法调用了eat()方法: "+describe);
}

调用这个方法:

        Class fruitClass = Fruit.class;
Method method = fruitClass.getDeclaredMethod("eat",String.class);
method.setAccessible(true);
method.invoke(fruitClass.newInstance(),"我是该方法的参数值");

输出结果:

  通过getMethod()方法调用了eat()方法:我是该方法的参数值

分析getDeclaredMethod()涉及的源码:

public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
// 1.检查方法的修饰符
checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
// 2.searchMethods()方法的第一个参数确定这个方法是不是私有方法,第二个参数我们定义的方法名,第三个参数就是传入的方法的参数类型
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
} // 这个方法就是通过传入的方法名找到我们定义的方法,然后使用了Method的copy()方法返回一个Method的实例,我们通过操作mehtod这个实例就可以操作我们定义的方法。
private static Method searchMethods(Method[] methods,
String name,
Class<?>[] parameterTypes)
{
Method res = null;
String internedName = name.intern();
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
if (m.getName() == internedName
&& arrayContentsEq(parameterTypes, m.getParameterTypes())
&& (res == null
|| res.getReturnType().isAssignableFrom(m.getReturnType())))
res = m;
} return (res == null ? res : getReflectionFactory().copyMethod(res));
} public Method copyMethod(Method var1) {
return langReflectAccess().copyMethod(var1);
} // 检查属性的访问权限
private static LangReflectAccess langReflectAccess() {
if (langReflectAccess == null) {
Modifier.isPublic(1);
} return langReflectAccess;
}

2.2.4 判断类的类型的方法

这类型的方法顾名思义,就是来判断这个类是什么类型,是接口,注解,枚举,还是一般的类等等。部分方法如下表

boolean isAnnotation()判断是不是注解
boolean isArray() 判断是否为数组
boolean isEnum()判断是否为枚举类型
boolean isInterface() 是否为接口类型
boolean isMemberClass()当且仅当基础类是成员类时,返回“true”
boolean isPrimitive()确定指定的“类”对象是否表示原始类型。
boolean isSynthetic()如果这个类是合成类,则返回' true ';否则返回“false”。

基本用法:

// 定义一个接口:
interface Animal{
public void run();
}

判断是不是一个接口:

Class AnimalClass = Animal.class;
boolean flag = AnimalClass.isInterface();
System.out.println(flag);

输出结果:

true

源码分析isInterface():

 public native boolean isInterface();

这是一个native方法,大家都知道native方法是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。

2.2.5 toString()方法

将对象转换为字符串。字符串表示形式是字符串“类”或“接口”,后跟一个空格,然后是该类的全限定名。

基本使用:

// 这是前面定义的两个类Fruit和Car,Car是一个接口
Class fruitClass = Fruit.class;
Class AnimalClass = Animal.class;
System.out.println(AnimalClass.toString());
System.out.println(fruitClass.toString());

输出结果:

// 格式  字符串“类”或“接口”,后跟一个空格,然后是该类的全限定名
interface cn.chen.test.util.lang.Animal
class cn.chen.test.util.lang.Fruit

源码如下:

 public String toString() {
// 先是判断是接口或者类,然后调用getName输出类的全限定名
return (isInterface() ? "interface " : (isPrimitive() ? "" : "class "))
+ getName();
} public native boolean isInterface();
public native boolean isPrimitive();

追本溯源,方能阔步前行。

参考资料

https://blog.csdn.net/x_panda/article/details/17120479

https://juejin.im/post/5d4450fbe51d4561ce5a1be1

JavaSE的官方文档

看了Java的Class的源码,我自闭了的更多相关文章

  1. 在Java路上,我看过的一些书、源码和框架(转)

    原文地址:http://www.jianshu.com/p/4a41ee88bd82 物有本末,事有终始,知所先后,则近道矣 面试经历 关于Java面试,你应该准备这些知识点关于Java面试,你应该准 ...

  2. Java集合---Array类源码解析

    Java集合---Array类源码解析              ---转自:牛奶.不加糖 一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Prim ...

  3. JAVA上百实例源码以及开源项目

    简介 笔者当初为了学习JAVA,收集了很多经典源码,源码难易程度分为初级.中级.高级等,详情看源码列表,需要的可以直接下载! 这些源码反映了那时那景笔者对未来的盲目,对代码的热情.执着,对IT的憧憬. ...

  4. Java IO 之 OutputStream源码

    Writer      :BYSocket(泥沙砖瓦浆木匠) 微         博:BYSocket 豆         瓣:BYSocket FaceBook:BYSocket Twitter   ...

  5. java线程池ThreadPoolExector源码分析

    java线程池ThreadPoolExector源码分析 今天研究了下ThreadPoolExector源码,大致上总结了以下几点跟大家分享下: 一.ThreadPoolExector几个主要变量 先 ...

  6. 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分

    这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...

  7. 死磕 java集合之DelayQueue源码分析

    问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...

  8. 死磕 java集合之PriorityQueue源码分析

    问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...

  9. 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计

    问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...

随机推荐

  1. 【C++】表达式中各类数值型数据间的混合运算

    注意:以下内容摘自文献[1],修改了部分内容. 1.运算中各类型数据转换方向如下: 高 double    ←  float   ↑  ↑     | long     | ↑     | unsig ...

  2. Java IO(五)字节流 FileInputStream 和 FileOutputStream

    Java IO(五)字节流 FileInputStream 和 FileOutputStream 一.介绍 字节流 InputStream 和 OutputStream 是字节输入流和字节输出流的超类 ...

  3. java内部类简单用法

    package innerClass; /** * 特点 * 1:增强封装性,通过把内部类隐藏在外部类的里面,使得其他类不能访问外部类. * 2:增强可维护性. * 3:内部类可以访问外部的成员. * ...

  4. 01 . Tomcat简介及部署

    Tomcat简介 Tomcat背景 tomcat就是常用的的中间件之一,tomcat本身是一个容器,专门用来运行java程序,java语言开发的网页.jsp就应该运行于tomcat中.而tomcat本 ...

  5. 七个生产案例告诉你BATJ为何选择ElasticSearch!应用场景和优势!

    本文来源于公众号[胖滚猪学编程],转载请注明出处. 从今天开始,想和你一起死磕ElasticSearch,学习分布式搜索引擎,跟着胖滚猪就对了! 既然是ES的第一课,那么最重要的是让你爱上它!不想说那 ...

  6. Nginx 笔记(二)nginx常用的命令和配置文件

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 1.nginx常用的命令 (1)启动命令 在/usr/local/nginx/sbin 目录下执行 ./ ...

  7. Java实现 LeetCode 828 统计子串中的唯一字符(暴力+转数组)

    828. 统计子串中的唯一字符 我们定义了一个函数 countUniqueChars(s) 来统计字符串 s 中的唯一字符,并返回唯一字符的个数. 例如:s = "LEETCODE" ...

  8. Java实现 蓝桥杯VIP 算法训练 幂方分解

    问题描述 任何一个正整数都可以用2的幂次方表示.例如: 137=27+23+20 同时约定方次用括号来表示,即ab 可表示为a(b). 由此可知,137可表示为: 2(7)+2(3)+2(0) 进一步 ...

  9. Java实现 LeetCode 136 只出现一次的数字

    136. 只出现一次的数字 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次.找出那个只出现了一次的元素. 说明: 你的算法应该具有线性时间复杂度. 你可以不使用额外空间来实现 ...

  10. Java实现汉诺塔问题

    1 问题描述 Simulate the movement of the Towers of Hanoi Puzzle; Bonus is possible for using animation. e ...