什么是类?可以理解为。class文件

某种意义上来说,java有两种对象:实例对象和Class对象。每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。Java使用Class对象执行其RTTI(运行时类型识别,Run-Time Type Identification),多态是基于RTTI实现的

每一个类都有一个Class对象,每当编译一个新类就产生一个Class对象,基本类型 (boolean, byte, char, short, int, long, float, and double)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class)

 System.out.println(int.class.getName());System.out.println(char.class.getName());System.out.println(short.class.getName());System.out.println(long.class.getName());System.out.println(byte.class.getName());System.out.println(float.class.getName());System.out.println(double.class.getName());System.out.println(boolean.class.getName());System.out.println(void.class.getName());System.out.println(char[].class.getName());//[CSystem.out.println(char[][].class.getName());//[[C

 Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。一个类被加载到内存并供我们使用需要经历如下三个阶段:

请简述 一个类被加载到内存并供我们使用 的过程:

  1. 加载,这是由类加载器(ClassLoader)执行的。通过一个类的全限定名来获取其定义的二进制字节流(Class字节码),将这个字节流所代表的静态存储结构转化为方法去的运行时数据接口,根据字节码在java堆中生成一个代表这个类的java.lang.Class对象。

  2. 链接。在链接阶段将验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求,为静态域分配存储空间并设置类变量的初始值(默认的零值),并且如果必需的话,将常量池中的符号引用转化为直接引用。

  3. 初始化。到了此阶段,才真正开始执行类中定义的java程序代码。用于执行该类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行初始化。

所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。当程序创建第一个对类的静态成员的引用时,就会加载这个类。使用new创建类对象的时候也会被当作对类的静态成员的引用。因此java程序程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。这一点与许多传统语言都不同。动态加载使能的行为,在诸如C++这样的静态加载语言中是很难或者根本不可能复制的。

  在类加载阶段,类加载器首先检查这个类的Class对象是否已经被加载。如果尚未加载,默认的类加载器就会根据类的全限定名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良java代码。一旦某个类的Class对象被载入内存,我们就可以它来创建这个类的所有对象。

如何获得Class对象

有三种获得Class对象的方式:

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

Class.forName 和getClass()

我们先看看如下的例子:

package com.cry;
class Dog {static {System.out.println("Loading Dog");}
}
class Cat {static {System.out.println("Loading Cat");}
}
public class Test {public static void main(String[] args){System.out.println("inside main");new Dog();System.out.println("after creating Dog");try {Class cat=Class.forName("com.cry.Cat");} catch (ClassNotFoundException e) {System.out.println("Couldn't find Cat");}System.out.println("finish main");}
}
/* Output:inside mainLoading Dogafter creating DogLoading Catfinish main*/

上面的Dog、Cat类中都有一个静态语句块,该语句块在类第一次被加载时候被执行。这时会有相应的信息打印出来,告诉我们这个类什么时候被加载了。从输出中可以看到,Class对象仅在需要的时候才被加载,static初始化是在类加载时进行的。

  Class.forName方法是Class类的一个静态成员。forName在执行的过程中发现如果类Dog还没有被加载,那么JVM就会调用类加载器去加载Dog类,并返回加载后的Class对象。Class对象和其他对象一样,我们可以获取并操作它的引用。在类加载的过程中,Dog类的静态语句块会被执行。如果Class .forName找不到你要加载的类,它会抛出ClassNotFoundException异常。

  Class.forName的好处就在于,不需要为了获得Class引用而持有该类型的对象,只要通过全限定名就可以返回该类型的一个Class引用。如果你已经有了该类型的对象,那么我们就可以通过调用getClass()方法来获取Class引用了,这个方法属于根类Object的一部分,它返回的是表示该对象的实际类型的Class引用:

class Dog {static {System.out.println("Loading Dog");}
}
public class Test {public static void main(String[] args) {System.out.println("inside main");Dog d = new Dog();System.out.println("after creating Dog");Class c = d.getClass();System.out.println("finish main");}
}
/* Output:inside mainLoading Dogafter creating Dogfinish main*/

利用new操作符创建对象后,类已经装载到内存中了,所以执行getClass()方法的时候,就不会再去执行类加载的操作了,而是直接从java堆中返回该类型的Class引用

类字面常量

  java还提供了另一种方法来生成对Class对象的引用。即使用类字面常量,就像这样:Cat.class,这样做不仅更简单,而且更安全,因为它在编译时就会受到检查(因此不需要置于try语句块中)。并且根除了对forName()方法的调用,所有也更高效。类字面量不仅可以应用于普通的类,也可以应用于接口、数组及基本数据类型。

注意:基本数据类型的Class对象和包装类的Class对象是不一样的:

Class c1 = Integer.class;
Class c2 = int.class;
System.out.println(c1);
System.out.println(c2);
System.out.println(c1 == c2);
/* Output
class java.lang.Integer
int
false
*/

但是在包装类中有个一个字段TYPE,TYPE字段是一个引用,指向对应的基本数据类型的Class对象,如下所示,左右两边相互等价: 
 
  
  用.class来创建对Class对象的引用时,不会自动地初始化该Class对象(这点和Class.forName方法不同)。类对象的初始化阶段被延迟到了对静态方法或者非常数静态域首次引用时才执行:

class Dog {static final String s1 = "Dog_s1";static  String s2 = "Dog_s2";static {System.out.println("Loading Dog");}
}
class Cat {static String s1 = "Cat_s1";static {System.out.println("Loading Cat");}
}
public class Test {public static void main(String[] args) throws ClassNotFoundException {System.out.println("----Star Dog----");Class dog = Dog.class;System.out.println("------");System.out.println(Dog.s1);System.out.println("------");System.out.println(Dog.s2);System.out.println("---start Cat---");Class cat = Class.forName("com.cry.Cat");System.out.println("-------");System.out.println(Cat.s1);System.out.println("finish main");}
}
/* Output:
----Star Dog----
------
Dog_s1
------
Loading Dog
Dog_s2
---start Cat---
Loading Cat
-------
Cat_s1
finish main*/

  从上面我们可以看到,如果仅使用.class语法来获得对类的Class引用是不会引发初始化的。但是如果使用Class.forName来产生引用,就会立即进行了初始化,就像Cat所看到的。

  如果一个字段被static final修饰,我们称为”编译时常量“,就像Dog的s1字段那样,那么在调用这个字段的时候是不会对Dog类进行初始化的。因为被static和final修饰的字段,在编译期就把结果放入了常量池中了。但是,如果只是将一个域设置为static 或final的,还不足以确保这种行为,就如调用Dog的s2字段后,会强制Dog进行类的初始化,因为s2字段不是一个编译时常量。

通过javap -c -v对Dog的字节码进行反汇编:

{static final java.lang.String s1;flags: ACC_STATIC, ACC_FINALConstantValue: String Dog_s1static java.lang.String s2;flags: ACC_STATICcom.cry.Dog();flags:Code:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 3: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       5     0  this   Lcom/cry/Dog;static {};flags: ACC_STATICCode:stack=2, locals=0, args_size=00: ldc           #2                  // String Dog_s22: putstatic     #3                  // Field s2:Ljava/lang/String;5: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;8: ldc           #5                  // String Loading Dog10: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V13: returnLineNumberTable:line 6: 0line 9: 5line 10: 13
}

  从上面可以看出s1在编译后被ConstantValue属性修饰 ConstantValue: String Dog_s1,表示即同时被final和static修饰。而s2并没有被ConstantValue修饰,因为它不是一个编译时常量。在static{}中表示类的初始化操作,在操作中我们看到只有s2字段进行了赋值,而却没有s1的踪影,因此调用s1字段是不会触发类的初始化的。

小结

  一旦类被加载了到了内存中,那么不论通过哪种方式获得该类的Class对象,它们返回的都是指向同一个java堆地址上的Class引用。jvm不会创建两个相同类型的Class对象:

package com.cry;
class Cat {static {System.out.println("Loading Cat");}
}
public class Test {public static void main(String[] args) throws ClassNotFoundException {System.out.println("inside main");Class c1 = Cat.class;Class c2= Class.forName("com.cry.Cat");Class c3=new Cat().getClass();Class c4 =new Cat().getClass();System.out.println(c1==c2);System.out.println(c2==c3);System.out.println("finish main");}
}
/* Output:
inside main
-------
Loading Cat
true
true
finish main*/

从上面我们可以看出执行不同获取Class引用的方法,返回的其实都是同一个Class对象。

  其实对于任意一个Class对象,都需要由它的类加载器和这个类本身一同确定其在就Java虚拟机中的唯一性,也就是说,即使两个Class对象来源于同一个Class文件,只要加载它们的类加载器不同,那这两个Class对象就必定不相等。这里的“相等”包括了代表类的Class对象的equals()、isAssignableFrom()、isInstance()等方法的返回结果,也包括了使用instanceof关键字对对象所属关系的判定结果。所以在java虚拟机中使用双亲委派模型来组织类加载器之间的关系,来保证Class对象的唯一性。


泛型Class引用

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

    public final class Class<T> implements java.io.Serializable,GenericDeclaration,Type,AnnotatedElement {
   Class<Integer> c1 = int.class;c1=Integer.class;//c1=Double.class; 编译报错

虽然int.class和Integer.class指向的不是同一个Class对象引用,但是它们基本类型和包装类的关系,int可以自动包装为Integer,所以编译器可以编译通过。

泛型中的类型可以持有其子类的引用吗?不行:

Class<Number> c1 = Integer.class;  //编译报错
  • 1

虽然Integer继承自Number,但是编译器无法编译通过。

为了使用泛化的Class引用放松限制,我们还可以使用通配符,它是Java泛型的一部分。通配符的符合是”?“,表示“任何事物“:

     Class<?> c1 = int.class;c1= double.class;

Class

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

通配符?不仅可以与extend结合,而且还可以与super关键字相结合,表示被限定为某种类型,或该类型的任何父类型:

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

向Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查。


Class类的方法

方法名 说明
forName() (1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。
(2)为了产生Class引用,forName()立即就进行了初始化。
Object-getClass() 获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。
getName() 取全限定的类名(包括包名),即类的完整名字。
getSimpleName() 获取类名(不包括包名)
getCanonicalName() 获取全限定的类名(包括包名)
isInterface() 判断Class对象是否是表示一个接口
getInterfaces() 返回Class对象数组,表示Class对象所引用的类所实现的所有接口。
getSupercalss() 返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。
newInstance() 返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。
getFields() 获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。
getDeclaredFields 获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。

import java.lang.reflect.Field;
interface I1 {
}
interface I2 {
}
class Cell{public int mCellPublic;
}
class Animal extends  Cell{private int mAnimalPrivate;protected int mAnimalProtected;int mAnimalDefault;public int mAnimalPublic;private static int sAnimalPrivate;protected static int sAnimalProtected;static int sAnimalDefault;public static int sAnimalPublic;
}
class Dog extends Animal implements I1, I2 {private int mDogPrivate;public int mDogPublic;protected int mDogProtected;private int mDogDefault;private static int sDogPrivate;protected static int sDogProtected;static int sDogDefault;public static int sDogPublic;
}
public class Test {public static void main(String[] args) throws IllegalAccessException, InstantiationException {Class<Dog> dog = Dog.class;//类名打印System.out.println(dog.getName()); //com.cry.DogSystem.out.println(dog.getSimpleName()); //DogSystem.out.println(dog.getCanonicalName());//com.cry.Dog//接口System.out.println(dog.isInterface()); //falsefor (Class iI : dog.getInterfaces()) {System.out.println(iI);}/*interface com.cry.I1interface com.cry.I2*///父类System.out.println(dog.getSuperclass());//class com.cry.Animal//创建对象Dog d = dog.newInstance();//字段for (Field f : dog.getFields()) {System.out.println(f.getName());}/*mDogPublicsDogPublicmAnimalPublicsAnimalPublicmCellPublic  //父类的父类的公共字段也打印出来了*/System.out.println("---------");for (Field f : dog.getDeclaredFields()) {System.out.println(f.getName());}/** 只有自己类声明的字段mDogPrivatemDogPublicmDogProtectedmDogDefaultsDogPrivatesDogProtectedsDogDefaultsDogPublic*/}
}

getName、getCanonicalName与getSimpleName的区别:

getSimpleName:只获取类名 
getName:类的全限定名,jvm中Class的表示,可以用于动态加载Class对象,例如Class.forName。 
getCanonicalName:返回更容易理解的表示,主要用于输出(toString)或log打印,大多数情况下和getName一样,但是在内部类、数组等类型的表示形式就不同了。

public class Test {private  class inner{}public static void main(String[] args) throws ClassNotFoundException {//普通类System.out.println(Test.class.getSimpleName()); //TestSystem.out.println(Test.class.getName()); //com.cry.TestSystem.out.println(Test.class.getCanonicalName()); //com.cry.Test//内部类System.out.println(inner.class.getSimpleName()); //innerSystem.out.println(inner.class.getName()); //com.cry.Test$innerSystem.out.println(inner.class.getCanonicalName()); //com.cry.Test.inner//数组System.out.println(args.getClass().getSimpleName()); //String[]System.out.println(args.getClass().getName()); //[Ljava.lang.String;System.out.println(args.getClass().getCanonicalName()); //java.lang.String[]//我们不能用getCanonicalName去加载类对象,必须用getName//Class.forName(inner.class.getCanonicalName()); 报错Class.forName(inner.class.getName());}
}

https://blog.csdn.net/dufufd/article/details/80537638

Class类 和 class对象(运行时的类型信息)相关推荐

  1. Java 反射 —— 运行时的类型信息

    1. 反射机制的由来 RTTI 机制可以告知某个对象的确切类型,但有一个前提,该类型在编译时必须已知(编译器在编译时打开和检查 .class 文件以获取类型信息).似乎是个很宽松的限制,但假如你获取了 ...

  2. 类和对象运行时在内存里是怎么样的?各种变量、方法在运行时是怎么交互的?

    转载自   类和对象运行时在内存里是怎么样的?各种变量.方法在运行时是怎么交互的? 在回答这个问题之前先了解一下Java的一些基础知识. 我们知道Java程序运行在虚拟机环境里,那我们先看一下虚拟机的 ...

  3. Java对象运行时在内存中的情况

    Java对象运行时在内存中的情况 您可能已经知道,一旦创建了一个对象,它就只是堆中的一系列字节.您可能对Java对象在内存中的情况感到好奇吗? 1.变量 以下是"Base"(B)类 ...

  4. 运用delphiXE RTTI在运行时动态获取信息及获取某个TComponent类或TObject类的RttiType信息的案例

    运用delphiXE RTTI在运行时动态获取信息及获取某个TComponent类或TObject类的RttiType信息的案例 一.理解RTTI 先看看官方文档:http://docwiki.emb ...

  5. java中编译类型的方法 和 运行时的类型方法 有什么区别

    1:引言 这是在复习多态当中,看到不太理解的东西, 就是 Java编译类型和运行类型 2:多态 多态首先得是在 有继承关系和方法重写的类当中:指同一个方法在被调用时,由于对象不同则会有不同的效果(). ...

  6. 如何获取注解中的值_如何在运行时利用注解信息

    注解( annontation )是 Java 1.5 之后引入的一个为程序添加元数据的功能.注解本身并不是魔法,只是在代码里添加了描述代码自身的信息,至于如何理解和使用这些信息,则需要专门的解析代码 ...

  7. SQL 运行时性能统计信息的获取

    打个招呼,这一篇可能不适合 CRUD Boy/Girl. 纯做增删改查的 SQL 编码师可能会觉得偏难. 假设如下的存储过程,有两段 SQL 查询组成.执行时发现,响应很慢. 请问你该怎么办? 有同学 ...

  8. Python3运行时查看线程信息

    前一篇文章说了一下在是用Python2的情况下怎样查看运行时线程信息,今天查资料的时候发现,原来在Python3中引入了 faulthandler 模块,可以简化我们很多工作,甚至,如果不需要保持进程 ...

  9. 类CL_ABAP_TYPEDESCR,动态取得运行时类型

    有时候我们要在程序运行的时候取得某个内表或者某个结构它的属性或者它的字段的属性,可能通过类CL_ABAP_TYPEDESCR和它的子类取得指定内表的属性. 类CL_ABAP_TYPEDESCR和它的子 ...

最新文章

  1. [C] 图的深度优先遍历
  2. Decode()函数使用技巧
  3. 互联网项目开始时需要去谈的产品需求分析:
  4. R语言排序 -- sort() order() rank()
  5. 处理FTP上传成功推理
  6. 10-1 channel
  7. Flask常见问题记录
  8. (原创)无废话C#设计模式之十九:Observer
  9. 生成树协议计算机网络,【图片】生成树协议奇怪!我错哪了两边都绿了呀【计算机网络吧】_百度贴吧...
  10. 算法速学速用大辞典 pdf_随机梯度蒙特卡洛算法-重要性采样
  11. 非线性规划求解器 Lingo, Matlab 使用心得,及使用的求解算法
  12. MATLAB基础学习系列二——矩阵
  13. Cartographer实时显示三维点云地图
  14. ret2shellcode 的泄露puts@got表
  15. 带蒙版的安卓剪辑软件_安卓手机上的视频剪辑软件哪款好?
  16. 【WebDriver】WebDriverWait 用法代码
  17. Django Ajax总结
  18. 初二因式分解奥数竞赛题_初中数学因式分解含答案竞赛题精选
  19. Golang 获取今天和昨天零点的时间
  20. 智力题---100个球两个人轮流拿,每次最多拿n个,谁拿到最后一个球获胜

热门文章

  1. 二叉树后序遍历(非递归)
  2. EasyExcel——采用自定义拦截器设置单元格列宽
  3. c语言课程教学团队介绍,计算机科学技术系计算机基础课程教学团队简介
  4. “1+X”云计算平台运维与开发职业技能等级认证教程(持续更新)
  5. 云安全 | 云原生应用程序保护平台 CNAPP
  6. 四、Centos 7.6安装Mysql 5.7.28
  7. 真想说再见,隐身帝们
  8. Alamofire+HandyJSON+泛型封装的简单离散式网络框架
  9. PIC16F887 单片机 智能小夜灯 DS18B20 DS1302 AT24C02 EEPROM
  10. kube-explorer