看了Java的Class的源码,我自闭了
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)
参数说明:
- 通过方法名称、参数类型获取方法
- 如果你想访问的方法不可见,会抛出异常
- 如果你想访问的方法没有参数,传递
null
作为参数类型数组,或者不传值)
- public Method getDeclaredMethod(String name, Class... parameterTypes)
- 通过方法名称、参数类型获取方法
- 如果你想访问的方法没有参数,传递
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
看了Java的Class的源码,我自闭了的更多相关文章
- 在Java路上,我看过的一些书、源码和框架(转)
原文地址:http://www.jianshu.com/p/4a41ee88bd82 物有本末,事有终始,知所先后,则近道矣 面试经历 关于Java面试,你应该准备这些知识点关于Java面试,你应该准 ...
- Java集合---Array类源码解析
Java集合---Array类源码解析 ---转自:牛奶.不加糖 一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Prim ...
- JAVA上百实例源码以及开源项目
简介 笔者当初为了学习JAVA,收集了很多经典源码,源码难易程度分为初级.中级.高级等,详情看源码列表,需要的可以直接下载! 这些源码反映了那时那景笔者对未来的盲目,对代码的热情.执着,对IT的憧憬. ...
- Java IO 之 OutputStream源码
Writer :BYSocket(泥沙砖瓦浆木匠) 微 博:BYSocket 豆 瓣:BYSocket FaceBook:BYSocket Twitter ...
- java线程池ThreadPoolExector源码分析
java线程池ThreadPoolExector源码分析 今天研究了下ThreadPoolExector源码,大致上总结了以下几点跟大家分享下: 一.ThreadPoolExector几个主要变量 先 ...
- 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分
这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...
- 死磕 java集合之DelayQueue源码分析
问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...
- 死磕 java集合之PriorityQueue源码分析
问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...
- 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计
问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...
随机推荐
- 【C++】表达式中各类数值型数据间的混合运算
注意:以下内容摘自文献[1],修改了部分内容. 1.运算中各类型数据转换方向如下: 高 double ← float ↑ ↑ | long | ↑ | unsig ...
- Java IO(五)字节流 FileInputStream 和 FileOutputStream
Java IO(五)字节流 FileInputStream 和 FileOutputStream 一.介绍 字节流 InputStream 和 OutputStream 是字节输入流和字节输出流的超类 ...
- java内部类简单用法
package innerClass; /** * 特点 * 1:增强封装性,通过把内部类隐藏在外部类的里面,使得其他类不能访问外部类. * 2:增强可维护性. * 3:内部类可以访问外部的成员. * ...
- 01 . Tomcat简介及部署
Tomcat简介 Tomcat背景 tomcat就是常用的的中间件之一,tomcat本身是一个容器,专门用来运行java程序,java语言开发的网页.jsp就应该运行于tomcat中.而tomcat本 ...
- 七个生产案例告诉你BATJ为何选择ElasticSearch!应用场景和优势!
本文来源于公众号[胖滚猪学编程],转载请注明出处. 从今天开始,想和你一起死磕ElasticSearch,学习分布式搜索引擎,跟着胖滚猪就对了! 既然是ES的第一课,那么最重要的是让你爱上它!不想说那 ...
- Nginx 笔记(二)nginx常用的命令和配置文件
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 1.nginx常用的命令 (1)启动命令 在/usr/local/nginx/sbin 目录下执行 ./ ...
- Java实现 LeetCode 828 统计子串中的唯一字符(暴力+转数组)
828. 统计子串中的唯一字符 我们定义了一个函数 countUniqueChars(s) 来统计字符串 s 中的唯一字符,并返回唯一字符的个数. 例如:s = "LEETCODE" ...
- Java实现 蓝桥杯VIP 算法训练 幂方分解
问题描述 任何一个正整数都可以用2的幂次方表示.例如: 137=27+23+20 同时约定方次用括号来表示,即ab 可表示为a(b). 由此可知,137可表示为: 2(7)+2(3)+2(0) 进一步 ...
- Java实现 LeetCode 136 只出现一次的数字
136. 只出现一次的数字 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次.找出那个只出现了一次的元素. 说明: 你的算法应该具有线性时间复杂度. 你可以不使用额外空间来实现 ...
- Java实现汉诺塔问题
1 问题描述 Simulate the movement of the Towers of Hanoi Puzzle; Bonus is possible for using animation. e ...