Java中Class对象详述

RTTI(运行时类型信息):

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

如下实例:

上方类图分析

基类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.classBoolean.TYPE
char.classCharacter.TYPE
byte.classByte.TYPE
short.classShort.TYPE
int.classInteger.TYPE
long.classLong.TYPE
float.classFloat.TYPE
double.classDouble.TYPE
void.classVoid.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<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)查找带有给定名称的资源
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");
      }
   }

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

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程界小明哥

请博主喝瓶水,博主持续输出!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值