Java中Class对象详述

RTTI(运行时类型信息):

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

如下实例:

Java中Class对象详述_第1张图片

上方类图分析

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

Vehicle类

abstract class Vehicle
{
   abstract void run();
}

Car类

public class Car extends Vehicle
{

   @Override
   void run()
   {
      System.out.println("小汽车开动");
   }

}

Bus类

public class Bus extends Vehicle
{

   @Override
   void run()
   {
      System.out.println("公交车开动");
   }

}

Bike类

public class Bike extends Vehicle
{

   @Override
   void run()
   {
      System.out.println("自行车骑行");
   }

}

调用

   public static void main(String[] args)
   {
      //均向上转型
      Vehicle vehicle1 = new Car();
      vehicle1.run();
      Vehicle vehicle2 = new Bus();
      vehicle2.run();
      Vehicle vehicle3 = new Bike();
      vehicle3.run();
   }

输出

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

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

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

public class BYDCar extends Car
{
   @Override
   void run()
   {
      System.out.println("BYD小汽车开动");
   }
}
 public static void main(String[] args)
   {
      //BYDCar是Car的子类,这里用了向下转型
      BYDCar bydCar =(BYDCar)new Car();
      bydCar.run();
   }

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对象的加载

package rtti;

public class A
{
   static {
      System.out.println("A is load");
   }
}
package rtti;

public class Test2
{
   public static void main(String[] args)
   {
      System.out.println("before load");
      new A();
      System.out.println("after load");
      
      System.out.println("before load1");
      try {
         Class a=Class.forName("rtti.A");
      } catch (ClassNotFoundException e) {
         System.out.println("Couldn't find A");
      }
      System.out.println("after load1");
   }
}

输出如下

before load
A is load
after load
before load1
after load1

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

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

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

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

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

获取 Class对象

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

2、类名.class

3、实例对象.getClass()

Class.forName

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

   public static void main(String[] args)
   {
      try {
         Class a=Class.forName("rtti.A");
         System.out.println(a);
      } catch (ClassNotFoundException e) {
         System.out.println("Couldn't find A");
      }
   }

输出: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
 public static void main(String[] args)
   {
      try {
         Class a=Class.forName("rtti.A");
         Class a1 = A.class;
         System.out.println(a==a1);
         
         System.out.println(Integer.TYPE==int.class);
         System.out.println(Boolean.TYPE==boolean.class);
         System.out.println(Double.TYPE==double.class);
         System.out.println(Void.TYPE==void.class);
         
      } catch (ClassNotFoundException e) {
         System.out.println("Couldn't find A");
      }
   }

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

实例对象.getClass()

   public static void main(String[] args)
   {
      try {
         Class clazz1=Class.forName("rtti.A");
         Class clazz2 = A.class;
         A a = new A();
         Class clazz3 = a.getClass();//getClass 方式
         System.out.println(clazz1==clazz2);
         System.out.println(clazz1==clazz3);
       
      } catch (ClassNotFoundException e) {
         System.out.println("Couldn't find A");
      }
   }

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

Class的常用方法

forName(String name) 静态方法,传入的参数是一个类的完整类路径的字符串,返回这个类的Class 对象。
forName(String name, boolean initialize, ClassLoader loader)  使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的 Class 对象。
asSubclass(Class 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) 查找带有给定名称的资源
public class A
{
   int a=0;
   int b=1;
   
   public void say()
   {
      System.out.println("say");
   }
   
   public void hello()
   {
      System.out.println("hello");
   }
}
public static void main(String[] args)
   {
      try {
         Class clazz1=Class.forName("rtti.A");
         
         System.out.println(clazz1.getName());//打印类名
         System.out.println(clazz1.getSimpleName());//获取类名(不包括包名)
         System.out.println(clazz1.getCanonicalName());//获取类名(包括包名)
         System.out.println("------------------------");
         //获取自身的属性
         for (Field f : clazz1.getDeclaredFields()) {
            System.out.println(f.getName());
         }
         System.out.println("------------------------");
         //方法,包含继承来的方法
         for (Method m :  clazz1.getMethods()){
            System.out.println(m.getName());
         }

         
      } catch (ClassNotFoundException e) {
         System.out.println("Couldn't find A");
      }
   }

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

Java中Class对象详述_第2张图片

你可能感兴趣的:(java,java)