飞道的博客

Java中Class对象详述

443人阅读  评论(0)

RTTI(运行时类型信息):

运行时类型信息可以让你在程序运行时发现和使用类型信息。主要用来运行时获取向上转型之后的对象到底是什么具体的类型。

如下实例:

上方类图分析

基类Vehicle位于顶部,Car、Bus、Bike 3个派生类向下扩展,面向对象编程很重要的目的是:让代码只操作基类的引用,就可以达到我们想要的效果,而不必去直接操作派生类;如果我们想再增加一个派生类 MotorBike,则直接对此类进行扩展即可,无需改变原来的代码逻辑。

Vehicle类


  
  1. abstract class Vehicle
  2. {
  3. abstract void run();
  4. }

Car类


  
  1. public class Car extends Vehicle
  2. {
  3. @Override
  4. void run()
  5. {
  6. System.out.println( "小汽车开动");
  7. }
  8. }

Bus类


  
  1. public class Bus extends Vehicle
  2. {
  3. @Override
  4. void run()
  5. {
  6. System.out.println( "公交车开动");
  7. }
  8. }

Bike类


  
  1. public class Bike extends Vehicle
  2. {
  3. @Override
  4. void run()
  5. {
  6. System.out.println( "自行车骑行");
  7. }
  8. }

调用


  
  1. public static void main(String[] args)
  2. {
  3. //均向上转型
  4. Vehicle vehicle1 = new Car();
  5. vehicle1.run();
  6. Vehicle vehicle2 = new Bus();
  7. vehicle2.run();
  8. Vehicle vehicle3 = new Bike();
  9. vehicle3.run();
  10. }

输出

小汽车开动
公交车开动
自行车骑行

从上面的代码可以看到,我们无需直接调用派生类,只要将派生类的实例向上转型为基类,然后通过基类来调用(派生类会丢失具体的类型)。

有个要注意的问题,不能向下转型调用,如:


  
  1. public class BYDCar extends Car
  2. {
  3. @Override
  4. void run()
  5. {
  6. System.out.println( "BYD小汽车开动");
  7. }
  8. }

  
  1. public static void main(String[] args)
  2. {
  3. //BYDCar是Car的子类,这里用了向下转型
  4. BYDCar bydCar =(BYDCar) new Car();
  5. bydCar.run();
  6. }

BYDCar是Car的子类,代码里用了向下转型,编译是通过的,不会报错,但运行会报错:

Exception in thread "main" java.lang.ClassCastException: rtti.Car cannot be cast to rtti.BYDCar
    at rtti.Test1.main(Test1.java:11)

Java中所有的类型转换都是在运行时进行正确性检查的,也就是RTTI:在运行时,识别一个对象的类型。

Class对象

在JAVA中,每个类都有一个Class对象,它用来创建这个类的所有对象,换言之,每个类的所有对象都会关联同一个Class对象。

Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。

实际上所有的类都是在对其第一次使用时动态加载到JVM中的,需要用到Class时,类加载器首先会检查这个类的Class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找到.class文件。接下来是验证阶段:加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良Java代码。

实例来看Class对象的加载


  
  1. package rtti;
  2. public class A
  3. {
  4. static {
  5. System. out.println( "A is load");
  6. }
  7. }

  
  1. package rtti;
  2. public class Test2
  3. {
  4. public static void main(String[] args)
  5. {
  6. System. out.println( "before load");
  7. new A();
  8. System. out.println( "after load");
  9. System. out.println( "before load1");
  10. try {
  11. Class a=Class.forName( "rtti.A");
  12. } catch (ClassNotFoundException e) {
  13. System. out.println( "Couldn't find A");
  14. }
  15. System. out.println( "after load1");
  16. }
  17. }

输出如下

before load
A is load
after load
before load1
after load1

从输出中可以看到,static模块在类第一次被加载时候被执行,Class对象仅在需要的时候才被加载,static初始化是在类加载时进行的。

如何验证同一个类的多个对象的Class对象是一个呢?

可以用A的Class对象与A的实例的对象的Class对象进行比较( == ),因为==用来比较引用是否相同。


  
  1. public static void main( String[] args)
  2. {
  3. A a = new A();//A的实例对象
  4. Class clazz = A.class;//A的Class对象
  5. Class clazz1 = a.getClass();//实例对象a的Class对象
  6. System.out.println(clazz==clazz1);
  7. }

运行后结果是true,所以说明同一个类的多个对象的Class对象是同一个。

获取 Class对象

1、Class.forName("类名字符串")

2、类名.class

3、实例对象.getClass()

Class.forName

forName是取得Class对象引用的一种方法,传入一个类的完整类路径也可以获得Class 对象,如果找不到你想要加载的类,就会抛出ClassNotFoundException异常,所以用try、catch来捕获这个方法可能抛出的ClassNotFoundException异常。


  
  1. public static void main(String[] args)
  2. {
  3. try {
  4. Class a=Class.forName( "rtti.A");
  5. System. out.println(a);
  6. } catch (ClassNotFoundException e) {
  7. System. out.println( "Couldn't find A");
  8. }
  9. }

输出:class rtti.A

类名.class  

这种方式叫类字面常量,这样来获取不仅更简单,还更安全,因为它在编译时就会检查。

字面常量的获取Class对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型,由于基本数据类型还有对应的基本包装类型,其包装类型有一个标准字段TYPE,而这个TYPE就是一个引用,指向基本数据类型的Class对象,其等价转换如下:

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

  
  1. public static void main(String[] args)
  2. {
  3. try {
  4. Class a=Class.forName( "rtti.A");
  5. Class a1 = A. class;
  6. System. out.println(a==a1);
  7. System. out.println(Integer.TYPE==int. class);
  8. System. out.println( Boolean.TYPE==boolean. class);
  9. System. out.println( Double.TYPE==double. class);
  10. System. out.println( Void.TYPE==void. class);
  11. } catch (ClassNotFoundException e) {
  12. System. out.println( "Couldn't find A");
  13. }
  14. }

全部输出是true,这里没有把基本类型的其他几种全部写上。

实例对象.getClass()


  
  1. public static void main(String[] args)
  2. {
  3. try {
  4. Class clazz1=Class.forName( "rtti.A");
  5. Class clazz2 = A.class;
  6. A a = new A();
  7. Class clazz3 = a.getClass(); //getClass 方式
  8. System. out.println(clazz1==clazz2);
  9. System. out.println(clazz1==clazz3);
  10. } catch (ClassNotFoundException e) {
  11. System. out.println( "Couldn't find A");
  12. }
  13. }

上面3种方法都是可以得到Class对象的引用。

Class的常用方法

forName(String name) 静态方法,传入的参数是一个类的完整类路径的字符串,返回这个类的Class 对象。
forName(String name, boolean initialize, ClassLoader loader)  使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的 Class 对象。
asSubclass(Class<U> clazz)  强制转换该 Class 对象,以表示指定的 class 对象所表示的类的一个子类。
cast(Object o) 这个方法是将传入的对象强制转换成Class 对象所代表的类型的对象。
getCanonicalName()  返回 Java Language Specification 中所定义的底层类的规范化名称。
getClasses()  返回一个包含某些 Class 对象的数组,这些对象表示属于此 Class 对象所表示的类的成员的所有公共类和接口。
getClassLoader() 返回该类的类加载器。
getConstructor(Class<?>... parameterTypes)  返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。 
 
getConstructors()  返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。
getFields() 获取Class对象的所有公有成员属性的java.lang.reflect.Field数组。
getField(String name) 按照字段名称获取公有字段的Field对象,注意name是区分大小写的。
getDeclaredFields() 获取Class对象的所有成员属性的Field数组;
getDeclaredField(String name) 按照字段名称获取所有字段的Field 对象,注意name是区分大小写的。
getMethods() 获取Class 对象的公有方法(以及从父类继承的方法,但不包含构造方法)的java.lang.reflect.Method数组
getMethod(String name,Class<?> …parameterTypes) 按照name 指定的方法名称,parameterTypes 指定的可变数组获取公有方法(以及从父类继承的方法,但不包含构造方法)的Method对象,注意name 是区分大小写的。
getDeclaredMethods() 获取Class 对象的所有方法(不包含父类继承的方法,构造方法)的Method数组。
getDeclaredMethod(String name,Class<?> …parameterTypes) 按照name指定的方法名称,parameterTypes 指定的可变数组获取所有方法(不包含父类继承的方法,构造方法)的Method对象,注意name 是区分大小写的。
getName() 以String 形式返回Class 对象所表示的实体的完整类名,基本数据类型
返回自身,数组类型(以String 数组为例)返回[Ljava.lang.String;,这个方法没有
getCanonicalName()返回的完整,但不是所有的类型都有底层的规范化名称。
getPackage() 以java.lang.reflect.Package形式返回Class对象的类所在的包,基本数
据类型、数组抛出异常。
getResource(String url) 查找带有给定名称的资源

  
  1. public class A
  2. {
  3. int a= 0;
  4. int b= 1;
  5. public void say()
  6. {
  7. System. out.println( "say");
  8. }
  9. public void hello()
  10. {
  11. System. out.println( "hello");
  12. }
  13. }

  
  1. public static void main(String[] args)
  2. {
  3. try {
  4. Class clazz1=Class.forName( "rtti.A");
  5. System. out.println(clazz1.getName()); //打印类名
  6. System. out.println(clazz1.getSimpleName()); //获取类名(不包括包名)
  7. System. out.println(clazz1.getCanonicalName()); //获取类名(包括包名)
  8. System. out.println( "------------------------");
  9. //获取自身的属性
  10. for (Field f : clazz1.getDeclaredFields()) {
  11. System. out.println(f.getName());
  12. }
  13. System. out.println( "------------------------");
  14. //方法,包含继承来的方法
  15. for (Method m : clazz1.getMethods()){
  16. System. out.println(m.getName());
  17. }
  18. } catch (ClassNotFoundException e) {
  19. System. out.println( "Couldn't find A");
  20. }
  21. }

关注下方公众号,有更多资料、实例代码、面试技巧奉上!


转载:https://blog.csdn.net/dkm123456/article/details/115732790
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场