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. Java 获取IP工具类、Vo类整理记录

    前言 日常开发中,获取ip是常用的功能,本文记录如何在Java中获取本机外网ip.地理位置,访问用户的外网ip.地理位置,以及指定外网ip的地理位置: 代码编写 1.获取访问用户外网ip,我们从访问者 ...

  2. SpringBoot—自定义线程池及并发定时任务模板

    介绍   在项目开发中,经常遇到定时任务,今天通过自定义多线程池总结一下SpringBoot默认实现的定时任务机制. 定时任务模板 pom依赖 <dependencies> <dep ...

  3. 搭建SpringCloud微服务框架:一、结构和各个组件

    搭建微服务框架(结构和各个组件) 简介 SQuid是基于Spring,SpringBoot,使用了SpringCloud下的组件进行构建,目的是想搭建一套可以快速开发部署,并且很好上手的一套微服务框架 ...

  4. SpringBoot实现微信小程序登录的完整例子

    目录 一.登录流程 二.后端实现 1.SpringBoot项目结构树 2.实现auth.code2Session 接口的封装 3.建立用户信息表及用户增删改查的管理 4.实现登录认证及令牌生成 三.前 ...

  5. Spring boot Sample 003之spring-boot-configuration-properties

    一.环境 1.1.Idea 2020.1 1.2.JDK 1.8 二.目的 通过properties文件配置spring boot 属性文件 三.步骤 3.1.点击File -> New Pro ...

  6. Java分层经验

    在学习和使用Java的过程中,我们时常要用到各种工具与技术,它们在某些时候可以大幅度地简化编程,利用好它们,可以让代码更强壮.下面的表格是我总结的关于java开发可能会用到的工具与它们在项目中扮演的角 ...

  7. break 与 continue 的作用 详解

    1.break 用break语句可以使流程跳出switch语句体,也可以用break语句在循环结构终止本层循环体,从而提前结束本层循环. 使用说明: (1)只能在循环体内和switch语句体内使用br ...

  8. Jquery拓展方法

    拓展JQuery一般有两个方法: 1.全局拓展 比如 $.ajax()就是全局函数 拓展方法: Jquery.extend(); 比如: $.extend({ hello:function(mynam ...

  9. Java实现 LeetCode 699 掉落的方块(线段树?)

    699. 掉落的方块 在无限长的数轴(即 x 轴)上,我们根据给定的顺序放置对应的正方形方块. 第 i 个掉落的方块(positions[i] = (left, side_length))是正方形,其 ...

  10. Java实现蓝桥杯凑算式(全排列)

    题目6.凑算式 凑算式 B DEF A + - + ------- = 10 C GHI (如果显示有问题,可以参见[图1.jpg]) 这个算式中AI代表19的数字,不同的字母代表不同的数字. 比如: ...