Java中的类型信息

来源:互联网 发布:良品铺子淘宝 编辑:程序博客网 时间:2024/06/09 11:46

学习运行时类型信息,要与多态机制进行区分

面向对象的编程语言的目的是让我们在凡是可以使用多态的地方都使用多态机制。

import java.util.*;abstract class Shape {void draw() {System.out.println(this + ".draw()");}abstract public String toString();}class Circle extends Shape {public String toString() {return "Circle";}}class Square extends Shape {public String toString() {return "Square";}}class Triangle extends Shape {public String toString() {return "Triangle";}}public class Shapes {public static void main(String[] args) {//当把Shape对象放入List<Shape>的数组时会向上转型。//但在向上转型为Shape的时候也丢失了Shape对象的具体类型。List<Shape> shapeList = Arrays.asList(new Circle(), new Square(),new Triangle());//从数组中取出元素时,List容器(实际上它将所有的事物都当作Object持有)//回自动将结果转型回Shape  这是RTTI最基本的形式for (Shape shape : shapeList)//这个地方是多态机制来处理//shape对象实际执行什么样的代码,是由引用所指向的具体对象来决定的shape.draw();}}

如上面的代码,当把Shape对象放入List<Shape>的数组时会向上转型。但在向上转型为Shape的时候也丢失了Shape对象的具体类型。对于数组而言,它们只是Shape类的对象。

当从数组中取出元素时,这种容器—实际上它将所有的事物都当作Object持有—会自动将结果转型回Shape。Object被转型为Shape,而不是转型为Circle,Square。

接下来多态机制发挥作用。Shape对象具体执行什么样的代码,是由引用所指向的具体对象Circle,Square来决定的。通常,你希望大部分代码尽可能少的了解对象的具体类型,而是只与对象家族中的一个通用表示打交道。“多态”是面向对象编程的基本目标。

什么时候需要运行时类型信息呢?

当你碰到这样的应用场景时,想知道某个泛化引用的确切类型,从而进行某种操作。比如,我们希望选出图形中所有的圆形,就要识别到Circle这个确切的类型。

运行时类型信息 使得你可以在程序运行时发现和使用类型信息。

这样可以把你从只能在编译期执行面向类型的操作的禁锢中解脱出来。


在Java中是如何让我们在运行时识别对象和类的信息的呢?

主要有两种方式:

1. 在运行时进行强制类型转换

2. 使用反射机制,在运行时加载类,并创建对象

 

类型信息在运行时是如何表示的呢?

Class对象,它包含了与类有关的信息。Java使用Class对象来确定运行时的类型信息。

每个类都有一个Class对象(每当编写并编译了一个新类,就会产生一个Class对象,被保存在一个同名的.class文件中)。为了使用这个类的Class对象,运行这个程序的java虚拟机将使用被称为“类加载器”的子系统。

所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法。

类加载的过程:

当我们写完一个Java源文件编译成一个.class文件的时候,如果加载进JVM,就要经历一个上图所示的生命周期。

当虚拟机遇到一条new指令的时候,首先去常量池定位这个类的符号引用,如果这个类已经被加载,解析和初始化了,则直接使用(比如单例对象)。否则,就执行上述过程。当一个类对象之前已经实例化过了,再次生成一个新对象时只需要初始化了。

1. 类的加载

类的加载是通过类加载器(ClassLoader)来完成。类加载器通过一个类的全限定名来获取这个类的二进制流,也就是那个.class文件流,将一些类的静态信息(字段,方法名,常量等)放到方法区,生成这个类的Class对象。事实上,虚拟机并不一定要求这个class文件必须在本地,也没有要求必须是java语言编写的(Groovy等也可以)。这个类可以来源于网络,也可以来源于数据库,甚至我们还可以手动写一个符合规范的class文件(CGlib)。当把这个class文件加载进虚拟机就完成了类加载的过程。

2. 验证阶段

因为class文件可以通过各种途径获取,还可以不使用java语言来编写,在类加载的过程中怎么保证加载的class文件是符合规范并且不会在运行过程中引起JVM宕机呢?这就需要一个验证阶段。验证阶段完成了class文件格式的验证(比如魔数,版本号等),元数据的验证(类的继承合法性),字节码验证,符号引用验证等。当一个class文件完全通过JVM检验合格之后就可以进入准备阶段了。

3. 准备阶段

准备阶段做的事情很简单。就是为这个类的一些类变量(static)在方法区分配内存但没有初始化(初始化都是在初始化阶段完成)。但是常量字段除外(static final)。这些字段在这个阶段已经赋值并且放入方法区的常量池了。

4. 解析阶段

类的解析是虚拟机做的最复杂的一项工作。它会将一些符号引用(虚地址)替换为直接引用(实际地址),完成类的继承关系的解析,完成字段和方法的解析。还会进行各种权限的验证。当一个类的各种符号引用被替换为了直接引用。它的各种父类,接口,字段,方法完成了解析。这个类就可以进入初始化阶段了。

5. 初始化阶段

类的初始化阶段简单的说就是执行一个<init>方法。这个方法是Java虚拟机帮我们生成的。是在构造函数执行(构造函数是程序员主动调用的)前由java虚拟机调用执行。目的在于完成类实例各个变量的初始化赋值,静态代码块的执行等。当然,如果一个类没有静态代码块,也没有变量赋值,那么这个< init>方法什么也不用干了。


将class文件加载到JVM中的方法:

1. new一个类对象

2. 调用Class.forName()方法

只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。


获得Class对象引用的方法:

1. 具体对象.getClass(): 当你持有此类型的对象时,可以使用此方法

2. Class.forName():这是实现此功能的便捷途径,因为你不需要为了获得Class引用而持有该类型的对象。要放到try-catch子句中,因为如果找不到这个类,会抛出ClassNotFoundException。获得对类的引用的同时进行初始化。

3. 类字面常量。在编译期就会受到检查,无需try-catch。使用类字面常量来获得对类的引用不会引发初始化,初始化实现了“惰性”,即延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行。


强制类型转换的几种情况

1. 我们在编译时已经知道了所有的类型,所以直接进行强制类型转换

2. 进行向下转型前,如果没有其他信息可以告诉你这个对象是什么类型,那么可以使用关键字instanceof

3. 进行向下转型前,如果没有其他信息可以告诉你这个对象是什么类型,那么可以Class类的方法isInstance(obj)


0 0