您的位置:首页 > 其它

第十四章 类型信息RTTI Class instanceof isInstance

2016-09-12 13:41 375 查看

1.RTTI(运行时识别一个对象的类型)

动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
运行时类型信息使你能够在程序运行时发现和使用(比如对象的具体类)类型信息。
RTTI主要有两种形式
传统的RTTI一种是在编译时知道了所有的类型信息,这属于静态绑定。
另一个种是“反射机制”,允许我们在运行时发现和使用类(对象的具体类)的信息。这属于动态绑定(多态、运行时绑定)。

面向对象的基本目的是:让代码只操纵对基类的引用,这样即使(从shape)派生出一个新类来也不会对原来的代码产生影响。



注意:这个例子中的Shape接口中动态绑定了draw()方法,目的是让客户端程序员使用泛化的Shape引用来调用draw()方法。

draw()方法在所有派生类都会被覆盖,并且由于它是动态绑定的,所以即使通过泛化的Shape引用来调用,也能产生正确的行为。这就是多态。
如果某个对象出现在字符串表达式中,其toString()方法会被自动调用。

/**
* 一个泛化的Shape 由个别到普通的过程
*/
abstract class Shape{
void draw(){System.out.println(this + ".draw()");}
abstract public String toString();
}
/**
* 个别特殊的类型
*/
class Circle extends Shape{
//toString()方法
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) {
//在List中放入一个泛化的Shape类型,
List<Shape> shapeList = Arrays.asList(
new Circle(),new Square(), new Triangle());
for(Shape shape : shapeList)
/**
* 因为泛化
4000
的Shape动态绑定了draw()方法,其子类型也会覆盖此方法,也可以产生正确的行为。
* 多态,运行时识别对象的类型,调用相应方法
*/
shape.draw();
}
}
/**
Circle.draw()
Square.draw()
Triangle.draw()
*/

List中放入对象会把Shape对象的具体类型丢失。也就是说进入的都是Shape
当从List中取出元素时:这种容器——实际上会将所有的事物都当做Object持有——然后自动将结果转型为Shape.
在Java中,所有的类型转换都是在运行时进行正确检查的。RTTI:在运行时,识别一个对象的类型(然后在将泛化的类型引用转化为具体的类型)。
这个例子中:首先 Objiet转为Shape,因为只知道List<Shape>保存的都是Shape,在编译时由容器和java泛型系统来强制确保这一点;而在运行时,有类型转换操作来确保这一点。
接下来就是多态机制,Shape对象执行什么样的代码,是由引用所指向的具体对象所决定的,可以识别是因为多态。

通常,你希望大部分代码尽可能少的了解对象的具体类型,而只与一个泛化类型表示。这样的代码更容易写,读,切更便于维护;设计也更容易实现、理解和改变。

所以“多态”是面向对象的基本目标。

2.Class 对象

当我们运行一个程序时,这个程序的所有类并不是一次都全部向虚拟机加载完成的,而是当程序运行到那一块使用到那个类(比如声明了一个对象或调用了一个静态的东西)时才会将这个类加载到jvm虚拟机当中去,在这个时候,对应的类也会生成一个Class的对象,它包含了这个类的一切信息(比你想象的要多)。
类型信息在运行时有Class对象来表示,它包含了与类相关的有关信息。事实上,Class对象就是用来创建类的所有“常规”对象的。Java使用Class对象来执行其RTTI,即使正在执行的是类似转型的操作。
类加载器子系统实际上可以包含一条类加载器链,但只有一个原生类加载器;它是JVM实现的一部分。原生类加载器加载的是所谓的可信类,包括Java API类,它们通常从本地磁盘加载。通常不需要添加额外的类加载器,但如果有特殊需求(如从网络下载的类),那么可以挂接额外的类加载器。

类加载的过程:所有类都是在对其第一次使用时(static初始化是在类加载的时候进行的),动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个也证明构造器也是类的静态方法,即使构造器之前没有使用static关键字。因此,使用new操作符创建类的新对象也会被当做对类的静态成员的引用。

因此:Java程序在运行之前并非被完全加载,而是在其各个部分必需时才会被加载的。
类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。在这个类的字节码被加载时(实际上是字节码文件加载入内存),他们会接受验证,并且不包含不良代码。
特别注意:一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。

class Candy{
static {System.out.println("Loading Candy");}
}
class Gum{
static {System.out.println("Loading Gum");}
}
class Cookie{
static {System.out.println("Loading Cookie");}
}
public class SweetShop {
public static void main(String[] args) {
System.out.println("inside main");
new Candy();//如果没有.class文件,会自动生成一个同名的.class文件。
System.out.println("After creating Candy");
try{
/**
* Class对象和其他对象一样,我们可以获取并操作它的引用(类加载器)
* Class的静态成员,得到这个名字的类,返回一个Class对象的引用
*
* Class.forName("Gum")方法如果找不到要加载的类(.class文件)就会抛出异常
*/
Class.forName("Gum");
}catch (ClassNotFoundException e) {
//找不到类Gum,因为它还没有被第一次加载过(也就是说这个类的静态成员一次也没有引用过)
System.out.println("Couldn't find Gum");
}
}
}

无论何时,只要在运行时使用类型信息,就必须首先获得对恰当Class对象的引用。使用Class.forName()就可以实现。如果你有一个类的引用,可以通过getClass()获得该对象实际类型的Class引用。

interface HasBatteries{}
interface Waterproof{}
interface Shoots{}

class Toy{
Toy() {}
Toy(int i){}
}
class FancyToy extends Toy implements
HasBatteries , Waterproof, Shoots{
FancyToy() {super(1);}
}
public class ToyTest {
static void printInfo(Class cc){
System.out.println("类名: " + cc.getName()
+ " 是否接口? [" + cc.isInterface() + "]");
System.out.println("简单类名: " + cc.getSimpleName());
System.out.println("规范名: " + cc.getCanonicalName());
System.out.println();
}
public static void main(String[] args) {
Class c = null;
try {
c = Class.forName("com.yue.rtti.FancyToy");//获取一个Class对象
} catch (ClassNotFoundException e) {
System.out.println("Can't find FancyToy");
System.exit(1);
}
printInfo(c);//第一次输出信息
//getInterfaces会返回此Class对象所表示的类实现的所有接口 是数组形式 Class[]
System.out.println("以下是此类的全部接口");
for(Class face : c.getInterfaces())
printInfo(face);//逐条打印
System.out.println("以上是此类的全部的接口");
Class up = c.getSuperclass();//获取到此类的超类 即父类 基类
Object obj = null;
try {
obj = up.newInstance();//使用这个类的Class对象创建一个具体的对象,并赋予一个obj引用
} catch (InstantiationException e) {
System.out.println("Cannot instantite");
System.exit(1);
} catch (IllegalAccessException e) {
System.out.println("Cannot access");
System.exit(1);
}
printInfo(obj.getClass());//打印
}
}
/**
类名: com.yue.rtti.FancyToy 是否接口? [false]
简单类名: FancyToy
规范名: com.yue.rtti.FancyToy

以下是此类的全部接口
类名: com.yue.rtti.HasBatteries 是否接口? [true]
简单类名: HasBatteries
规范名: com.yue.rtti.HasBatteries

类名: com.yue.rtti.Waterproof 是否接口? [true]
简单类名: Waterproof
规范名: com.yue.rtti.Waterproof

类名: com.yue.rtti.Shoots 是否接口? [true]
简单类名: Shoots
规范名: com.yue.rtti.Shoots

以上是此类的全部的接口
类名: com.yue.rtti.Toy 是否接口? [false]
简单类名: Toy
规范名: com.yue.rtti.Toy
*/

2.1.类字面常量.class

FancyToy.class这样也可以获得一个class对象。这样做简单安全,因为它在编译时就会受到检查。

并且根除了forName()方法的调用,所以更高效。
类字面常量不仅可以应用于普通类,也可应用于接口、数组以及基本数据类型。
对于基本类型的包装器类,还有一个TYPE的标准字段。



当使用.class来创建对Class对象的引用时,不会自动初始化该Class对象。为了使用类而做的准备工作实际包含三个步骤,而.class不会走带三个步骤“初始化”
加载:由类加载器执行。查找字节码文件中的字节码(.class文件中)并从这些字节码中创建一个Class对象。
链接:在链接阶段将验证类中的字节码,为静态域分配存储空间(内存),如果必需,将解析这个类创建的对其他类的所有引用。
初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。【初始化存储空间】
初始化被延迟到了对静态方法(构造器隐式的是静态的)或者非常数静态域进行首次引用时才执行:
class Initable{
static final int saticFinal = 47;
static final int staticFinal2 = //注意这种情况也会被初始化的,虽然是final,但具有不确定性,非常数静态域。
ClassInitialization.rand.nextInt(1000);
static {
System.out.println("Initializing Initable");
}
}
class Initable2{
static int staticNonFinal = 147;//非常数静态域
static{
System.out.println("Initializing Initable2");
}
}
class Initable3{
static int staticNonFinal = 74;
static {
System.out.println("Initializing Initable3");
}
}
//有继承超类的情况,情况不特殊也不会被初始化
class Initable4 extends Initable3{
static final int saticFinal = 123;
static {
System.out.println("Initializing Initable4");
}
}
public class ClassInitialization {
public static Random rand = new Random(47);
public static void main(String[] args) throws ClassNotFoundException {
//得到一个Class对象 这样不会先对其初始化,而是把初始化延迟到了第一次使用其静态域时。
Class initable = Initable.class;
System.out.println("After creating Initable ref");
System.out.println(Initable.saticFinal);
//仅仅是编译器常量还不足以保证不初始化
System.out.println(Initable.staticFinal2);
//直接使用一个类的静态域(他不是final的) 其他静态域也会被初始化
System.out.println(Initable2.staticNonFinal);
//另一种方式获取Class对象 这样会首先对其初始化
Class initable3 = Class.forName("com.yue.rtti.Initable3");
System.out.println("After creating Initable3 ref");
System.out.println(Initable3.staticNonFinal);
System.out.println("继承超类的情况:"+Initable4.saticFinal);
}
}
/**
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74
继承超类的情况:123
*/

初始化有效地实现了尽可能的“惰性”。.class语法获得对的引用不会引发初始化,为了产生Class引用,Class.forName()立即进入初始化。

如果一个static final值是“编译器常量”(有可能不是 如Initable.staticFinal2),这个值不需对Initable类进行初始化就可以被获取。
如果一个static域不是final的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个类分配存储空间)和初始化(初始化存储空间)。

2.2.泛化的Class引用(有特殊到一般的过程)

Class引用总是指向某个Class对象(包含一个类的所有信息,通过类加载器加载.class文件中的字节码而创建)的。
可以使用这个引用来创建类的实例(对象),并包含可作用域这些实例的所有方法代码。还包括该类的静态成员,因

此,Class引用表示的就是它所指向的对象(Class类的一个对象)的确切类型(具体类型)。
但是使用泛型语法进行限定还可以使它的类型更具体。如下:
public class GenericClassReferences {
    public static void main(String[] args) {
        Class intClass = int.class;//普通的Class引用
        Class<Integer> genericIntClass = int.class;//使用泛型限定的Class引用,只能指向指定的引用的Class对象
//        genericIntClass = double.class;//这里会报错,因为使用了泛型进行限定
        //普通的类引用不会产生警告信息,可以指向任何其他Class对象
        intClass = double.class;
        //使用通配符来指定此泛型的限定范围
        Class<? extends Number> genericNumberClass = int.class;
    }
}

通过使用泛型语法,可以让编译器强制执行额外的类型检查。使用通配符可以稍微放松一些这种限制
通配符?表示“任何事物”。
Class<?> intClass = int.class;
Class<?>优于平凡的Class,即便他们是等价的,Class<?>表示使用了一个非具体的类引用,就选择了非具体的版本。
限定为某种类型或该类型的任何子类型,使用?与extends关键字相结合,创建一个范围。
//使用通配符来指定此泛型的限定范围
Class<? extends Number> genericNumberClass = int.class;

向Class引用添加泛型语法仅仅是为了提供编译期类型检查,使用普通的Class引用,如果犯了错误,直到运行时才会发现,显得很不方便。

class CountedInteger {
    private static long counter;
    private final long id = counter++;
    public String toString() {return Long.toString(id);}
}

public class FilledList<T> {
    public static void main(String[] args) {
        // CountedInteger类必须有一个无参构造器
        Class<CountedInteger> type = CountedInteger.class;
        try {
            //这个类必须假设与它一同工作的任何类型都有一个某人的构造器(无参构造器),否则抛出异常
            CountedInteger obj = type.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
当你将泛型语法用于Class对象时,newInstance()将返回改对象的确切类型,而不仅仅是基本的object(普通的Class引用)
//class Toy{
//	Toy() {}
//	Toy(int i){}
//}
//class FancyToy extends Toy implements
//HasBatteries , Waterproof, Shoots{
//	FancyToy(
b399
) {super(1);}
//}
public class GenericToyTest {
public static void main(String[] args) throws Exception {
Class<FancyToy> ftClass = FancyToy.class;
FancyToy fancyToy = ftClass.newInstance();
//		Class<Toy> up2 = ftClass.getSuperclass();//这样编译器会报错
//?代表某个类 他是FancyToy的超类,而不会接受Class<Toy> up2这样的声明
Class<? super FancyToy> up = ftClass.getSuperclass();
Object obj = up.newInstance();
}
}

2.3新的转型语法

class Building {}
class House extends Building {}
public class ClassCasts {
public static void main(String[] args) {
Building b = new House();
Class<House> houseType = House.class;//先获得目标转型的Class引用
House h = houseType.cast(b);//使用cast进行转型的语法
h = (House) b;//直接转型的语法
}
}

cast()方法转型比普通的转型语法多了很多额外的工作,它对于普通转型语法不能转型的情况非常有用

Class.asSubclass;允许将一个类对象(Class)转型为更加具体的类型。

3.类型转型前先做检查

RTTI形式
传统的类型转换,如“(Shape)”,由RTTI确保类型转换的正确性,如果执行错误的类型转换,就会抛出一个ClassCastException异常
类型安全的向下转型(强制类型转换),类层次结构图,由Shape转化成Circle是向下的,所以是向下转型(显式的,因为编译器不知道你要转换成什么类型)。
instanceof:检查对象是否从属于某个类
if (h instanceof Building) {
h = (House) b;
}


3.1动态的instanceof[Class.isInstance()]

Class.isInstance()方法提供了一种动态地测试对象的途径。检查某个对象是否从属于某个类。
class Building {}
class House extends Building {}
public class ClassCasts {
public static void main(String[] args) {
Building b = new House();//这里引用的实际对象是一个House,它在运行时会识别这个类型RTTI
Class<House> houseType = House.class;
Class<Building> buildType = Building.class;
House h = houseType.cast(b);//使用cast进行转型的语法
h = (House) b;//直接转型的语法
if (h instanceof Building) {
h = (House) b;
System.out.println("转型");
}
if (buildType.isInstance(b)) {//可以改写成这样
System.out.println(buildType.isInstance(b));
}
}
}

3.2递归计数法

+++这是一种方法,根据不同情况会有不同的实现。

4.注册工厂

这个东西会在设计模式中说。

5.Instanceof与Class的等价性

在查询类型信息的时候,以instanceof的形式(instanceof的形式或isInstance()的形式)与直接比较Class对象有一个很重要的差别。
class Base{}
class Derived extends Base{}
public class FamilyVsExactType {
static void test(Object x){
System.out.println("测试x的类型 " + x.getClass());
System.out.println("x instanceof Base: " + (x instanceof Base));
System.out.println("x instanceof Derived: " + (x instanceof Derived));
System.out.println("Base.isInstance(x): " + Base.class.isInstance(x));
System.out.println("Derived.isInstance(x): " + Derived.class.isInstance(x));
System.out.println("下面的输出与上面对比");
System.out.println("x.getClass() == Base.class: " + (x.getClass() == Base.class));
System.out.println("x.getClass() == Derived.class: " +(x.getClass() == Derived.class));
System.out.println("x.getClass().equals(Base.class): " + (x.getClass().equals(Base.class)));
System.out.println("x.getClass().equals(Derived.class): " + (x.getClass().equals(Derived.class)));
System.out.println("++++++++++++++++++++++++++++++++++++++++++++++");
}
public static void main(String[] args) {
test(new Base());
test(new Derived());
}
}
/**
测试x的类型 class com.yue.rtti.Base
x instanceof Base: true
x instanceof Derived: false
Base.isInstance(x): true
Derived.isInstance(x): false
下面的输出与上面对比
x.getClass() == Base.class: true
x.getClass() == Derived.class: false
x.getClass().equals(Base.class): true
x.getClass().equals(Derived.class): false
++++++++++++++++++++++++++++++++++++++++++++++
测试x的类型 class com.yue.rtti.Derived
x instanceof Base: true
x instanceof Derived: true
Base.isInstance(x): true
Derived.isInstance(x): true
下面的输出与上面对比
x.getClass() == Base.class: false
x.getClass() == Derived.class: true
x.getClass().equals(Base.class): false
x.getClass().equals(Derived.class): true
++++++++++++++++++++++++++++++++++++++++++++++
*/


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: