入门java不久,看代码经常会看到类名加.class,Class这样的用法,甚是不惑。查找资料学习,总结一下自己的理解,囊括了我最近几乎所有的疑问。首先,要明确,在java的世界中,一切皆是对象,java中的对象可以分为两种对象:Class对象和实例对象。那么,我们围绕以下几方面来解读Class对象。

Class对象是什么

带着“Class对象和实例对象是什么区别?”的问题,来看看怎么理解Class对象:
1、信息属性:从对象的作用看,Class对象保存每个类型运行时的类型信息,如类名、属性、方法、父类信息等等。在JVM中,一个类只对应一个Class对象。
2、普适性:Class对象是java.lang.Class类的对象,和其他对象一样,我们可以获取并操作它的引用。
3、运行时唯一性:每当JVM加载一个类就产生对应的Class对象,保存在堆区,类型和它的Class对象时一一对应的关系。一旦类被加载了到了内存中,那么不论通过哪种方式获得该类的Class对象,它们返回的都是指向同一个java堆地址上的Class对象引用。JVM不会创建两个相同类型的Class对象。

废了不少力气,画了下面这张图作为总结,理解完全文再看看这张图就清楚很多:
alt text

Class对象的构造

需要明白两点:什么时候被构造的?是如何被构造的?
Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。

Class对象在类加载中的作用

一个类被加载到内存并供我们使用需要经历如下三个阶段:

  • 加载,这是由类加载器(ClassLoader)执行的。通过一个类的全限定名来获取其定义的二进制字节流(Class字节码),将这个字节流所代表的静态存储结构转化为方法去的运行时数据接口,根据字节码在java堆中生成一个代表这个类的java.lang.Class对象。
  • 链接。在链接阶段将验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求,为静态域分配存储空间并设置类变量的初始值(默认的零值),并且如果必需的话,将常量池中的符号引用转化为直接引用。
  • 初始化。到了此阶段,才真正开始执行类中定义的java程序代码。用于执行该类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行初始化。

所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。当程序创建第一个对类的静态成员的引用时,就会加载这个类。使用new创建类对象的时候也会被当作对类的静态成员的引用。因此java程序程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。
在类加载阶段,类加载器首先检查这个类的Class对象是否已经被加载。如果尚未加载,默认的类加载器就会根据类的全限定名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良java代码。一旦某个类的Class对象被载入内存,就可以它来创建这个类的所有对象。

获取Class对象的方式

有三种方式

  • Class.forName(“类的全限定名”)
  • 实例对象.getClass()
  • 类名.class (类字面常量)

Class.forName(“类的全限定名”)

Class.forName方法是Class类的一个静态成员。forName在执行的过程中发现如果类还没有被加载,那么JVM就会调用类加载器去加载类,并返回加载后的Class对象。如果Class .forName找不到要加载的类,它会抛出ClassNotFoundException异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.feng;

class Book {
static {
System.out.println("Loading Book");
}
}
public class Test {
public static void main(String[] args){
System.out.println("inside main");
try {
Class book=Class.forName("com.feng.Book");
} catch (ClassNotFoundException e) {
System.out.println("Couldn't find Book");
}
System.out.println("finish main");
}
}
/* Output:
inside main
Loading Book
finish main
*/

实例对象.getClass()

如果已经有了该类型的对象,那么就可以通过调用getClass()方法来获取Class对象引用了,getClass方法属于根类Object的一部分,它返回的是表示该对象的实际类型的Class对象引用。

类名.class

类名.class 这种方式就是类字面常量,例如:Test.class,这样操作更简单,而且更安全,因为它在编译时就会受到检查,类字面量不仅可以应用于普通的类,也可以应用于接口、数组及基本数据类型。

  • 基本数据类型的Class对象和包装类的Class对象是不一样的,但是在包装类中有个一个字段TYPE,TYPE字段是一个引用,指向对应的基本数据类型的Class对象,如下所示,左右两边相互等价:
    alt text

  • 用.class来创建对Class对象的引用时,不会自动地初始化该Class对象。类对象的初始化阶段被延迟到了对静态方法或者非常数静态域首次引用时才执行。

Class类的方法

  • forName():获取Class对象的一个引用,如果引用的类还没有被JVM加载,就立刻加载并初始化。
  • getName():取全限定的类名(包括包名),即类的完整名字。
  • getSimpleName():获取类名(不包括包名)
  • getCanonicalName():获取全限定的类名(包括包名),大多数情况下和getName一样,但是在内部类、数组等类型的表示形式就不同了。
  • isInterface():判断Class对象表示的是否是一个接口
  • getInterfaces():返回Class对象数组,对应Class对象所引用的类所实现的所有接口。
  • getSupercalss() :返回Class对象,表示Class对象所引用的类所继承的直接基类。
  • newInstance():返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。
  • getFields():获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。
  • getDeclaredFields():获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。
    看到上面的方法的作用,我们大概就知道了获取到Class对象引用后可以用来做什么。Class类可以帮助我们在程序运行时分析类,获取类中的各种信息,比如属性、方法、修饰符、构造器、类名、父类和接口等等等等。
    另外,Class类也是继承了Object类的。

Class类和Java反射

什么是反射机制?

反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

反射机制如何实现?

一直以来反射技术都是Java中的闪亮点,这也是目前大部分框架(如Spring/Mybatis等)得以实现的支柱。在Java中,Class类是Java反射机制的起源和入口,可以获取与类相关的各种信息,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。

比较直白的理解是:使用反射机制,我们不需要在代码中指明我们想要创建什么类型的实例,而是让代码自动地去获取我们想要创建的实例的它的类的信息再自动去创建,这样做,我们可以极大程度地降低程序的耦合程度。

在反射包中:(1)我们常用的类主要有Constructor类,Constructor类可以表示Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象,(2) Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private),(3) Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),博客:https://blog.csdn.net/javazejian/article/details/70768369,里面的例子写的很详细细,推荐!!!

泛型Class引用

Class引用表示的就是它所指向的对象的类型,而该对象便是Class类的一个对象。在JavaSE5中,允许对Class引用所指向的Class对象的表示的类型进行限定,也就是说可以对Class对象使用泛型语法。通过泛型语法,可以让编译器强制指向额外的类型检查。
1、简单举例

1
2
3
Class<Integer> c1 = int.class;
c1=Integer.class;
//c1=Double.class; 编译报错

int.class和Integer.class指向的Class对象对应的类是基本类型和包装类的关系,int可以自动包装为Integer,所以编译器可以编译通过。

2、使用通配符”?”,可以表示任何类型

1
2
Class<?> c1 = int.class;
c1= float.class;

3、通配符?可以与extend结合

1
2
3
4
Class<? extends Number> c1 = Integer.class;
c1 = Number.class;
c1 = Double.class;
// c1=String.class; 报错,不属于Number类和其子类

4、通配符?与super关键字相结合,表示被限定为某种类型,或该类型的任何父类型

1
2
3
4
Class<? super Integer> c1 = Integer.class;
c1 = Number.class;
c1 = Object.class;
c1 = Integer.class.getSuperclass();

5、返回值泛型,用 T表示
例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 这个<T> T 可以传入任何类型的List
* 参数T
* 第一个 表示是泛型
* 第二个 表示返回的是T类型的数据
* 第三个 限制参数类型为T
* @param data
* @return
*/
private <T> T getListFisrt(List<T> data) {
if (data == null || data.size() == 0) {
return null;
}
return data.get(0);
}

T表示返回值是泛型的,传递什么类型,就返回什么类型的数据。
而单独的T就是表示限制你传递的参数类型为T类型,也就是对象实例化的时候T表示什么类型就已经定下来了。

泛型中,其他一些约定的符号,只是俗成的约定,其实用其他字母都是一样的:

1
2
3
4
5
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
N - Number(数值类型)

参考文献或转载相关:

原文链接:https://blog.csdn.net/sinat_29846389/article/details/122513297
参考文献
https://blog.csdn.net/mcryeasy/article/details/52344729
https://blog.csdn.net/qq_35029061/article/details/100550584
https://blog.csdn.net/wangguidong520/article/details/87283373
https://www.cnblogs.com/jpfss/p/9929108.html
https://blog.csdn.net/javazejian/article/details/70768369
https://www.iteye.com/blog/inter12-1288117
https://blog.csdn.net/wangguidong520/article/details/87283373