第14章类型信息

1、运行时类型信息可以在程序运行时发现和使用类型信息。
14.1
1、当从数组中取出元素时,这种容器—实际上它将所有的事物都当做Objcet持有—会自动将结果转型回shape。这是RTTI最基本的使用形式,在Java中,所有的类型转换都是在运行时进行正确性检查的。RTTI名字的含义:在运行时,识别一个对象的类型。
14.2Class对象
1、类型信息在运行时是如何表示的。这项工作是由称为Class对象的特殊对象完成的,它包含了与类有关的信息。事实上,Class对象就是用来创建类的所有的“常规”对象的。Java使用Class对象来执行其RTTI,即使正在执行的是类似转型这样的操作。Class类还拥有大量的使用RTTI的其他方式。
2、类是程序的一部分,每个类都有一个Class对象。换言之,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用被称为“类加载器”的子系统。
3、类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载的是所谓的可信类,包括Java APPI类,它们通常是从本地盘加载的。在这条链中,通常不需要添加额外的类加载器,但如果有特殊需求(例如以某种特殊,以支持Web服务器应用,或者在网络中下载类),那么你还有一种方式可以挂接额外的类加载器。
4、所有的类都是在对其第一次使用时,动态加载到JVM中。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,即使在构造器之前并没有使用static关键字。因此,使用new操作符创建类的新对象也会被当做对类的静态成员的引用。
5、Java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载的。
6、Class对象仅在需要的时候才被加载,static初始化是在类加载时进行的。
7、如果想要在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。Class.forName()就是实现此功能的便捷途径。
8、Class的newInstance()方法是实现“虚拟构造器”的一种途径,虚拟构造器允许声明:“我不知道你的确切类型,但是无论如何要正确创建自己”。使用newInstance()来创建的类,必须带有默认的构造器。
9、

package com14;

/**
 * Created by Panda on 2018/5/14.
 */
interface HasBatteries{}
interface Waterproof{}
interface Shoots{}
class Toy{
    Toy(){}
    Toy(int i){}
}
class Fancy extends Toy implements HasBatteries,Waterproof,Shoots{
    Fancy(){super(1);}
}
public class Test1 {
    static void printTnfo(Class cc){
        System.out.println("class name:"+cc.getName()+"in interface?["+cc.isInterface()+"]");
        System.out.println("Simple name:"+cc.getSimpleName());  //不包含包名
        System.out.println("Canonical name:"+cc.getCanonicalName());  //包含包名
    }

    public static void main(String[] args) {
        Class cc= null;
        try {
            cc=Class.forName("com14.Fancy");
        }catch (ClassNotFoundException e){
            System.out.println("cant not find Fancy");
            System.exit(1);
        }

        printTnfo(cc);
        for (Class face:cc.getInterfaces()) {
            printTnfo(face);
        }
        Class up=cc.getSuperclass();
        Object obj=null;
        try {
            obj = up.newInstance();
        }catch (InstantiationException e){
            System.out.println("can not instantiate");
            System.exit(1);
        }catch (IllegalAccessException e){
            System.out.println("cannot access");
            System.out.println(1);
        }
        printTnfo(obj.getClass());
    }
}

14.2.1类字面常量
1、Java还提供了另一种来生成对Class对象的引用,即使用类字面常量。如类.class。这样做不仅简单,而且更安全,并且它根除了对forName()方法的调用,所以也更高效。
2、使用“.class”的形式,以保持与普通类的一致性。当使用“.class”来创建对Class对象的引用时,不会自动地初始化该Class对象。为了使用类而做的准备工作实际包含三个步骤:
①加载,这是由类加载器执行的。该步骤将查找字节码(通常在classpath所指定的路径中查找,但这并非是必需的),并从这些字节码中创建一个Class对象。
②链接。在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如何必需的话,将解析这个类创建的对其他类的所有引用。
③初始化。如果该类具有超类。并对其初始化,执行静态初始化器和静态初始化块。
初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行。
3、如果一个static域不是final,那么在对他访问时,总是要求在它被读取之前,要先进行连接(为这个域分配存储空间)和初始化(初始化该存储空间)
14.2.2泛化的Class引用
1、Class引用总是指向某个Class对象,它可以制造类的实例,并包含可作用于这些实例的所有方法代码。它还包含该类的静态成员,因此,Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。
2、通配符Class《?》由于平凡的Class,即便它们是等价的,并且平凡的Class不会产生编译器警告信息。Class<?>的好处是它表示你并非是碰巧或者由于疏忽,而使用了一种非具体的类引用,就是选择了非具体的类。
3、

public class Test3 {
    public static void main(String[] args) {
        Class<? extends Number> bounded =int.class;
        bounded=double.class;
        bounded=Number.class;
    }
}

4、向Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查。
5、

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Panda on 2018/5/14.
 */
class CountedInteger{
    private static long counter;
    private final long id=counter++;
    public String toString(){return Long.toString(id);}
}
public class Test4<T> {
    private Class<T> type;
    public Test4(Class<T> type){this.type=type;}
    public List<T> creat(int nElements){
        List<T> result=new ArrayList<T>();
        try{
            for (int i = 0; i <nElements ; i++) {
                result.add(type.newInstance());
            }
        }catch (Exception e){
            throw new RuntimeException(e);
        }
        return result;
    }

    public static void main(String[] args) {
        Test4<CountedInteger> countedIntegerTest4=new Test4<>(CountedInteger.class);
        System.out.println(countedIntegerTest4.creat(15));
    }
}

6、

/**
 * Created by Panda on 2018/5/14.
 */
public class Test5 {
    //toy是父类  fancy是子类
    //如果手头的是超类,那么编译器只允许声明超类引用是“某个类”
    public static void main(String[] args) throws Exception{
        Class<Fancy> fancyClass = Fancy.class;
        Toy toy = fancyClass.newInstance();
        Class<?super Fancy> up=fancyClass.getSuperclass();
        Object object=up.newInstance();
       ///  Class<Toy> up2=fancyClass.getSuperclass();  不允许
    }
}

14.2.3新的转型语法
1、

/**
 * Created by Panda on 2018/5/14.
 */
class Building{}
class House extends Building{}

public class Test6 {
    //新的转型语法  cast()语法接受参数对象,并将其转型为Class引用的类型。
    public static void main(String[] args) {
        Building building =new House();
        Class<House> houseClass=House.class;
        House house=houseClass.cast(building);
        house=(House)building;
    }
}

14.3 类型转换前的检查
1、传统的类型转换。如“(Shape)”,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException异常。
2、代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。向上转型,向下转型。
3、RTTI在Java中还有第三种形式,就是关键字instanceof,返回一个布尔值。告诉对象是不是某个特定类型的实例。
4、对instanceof有比较严格的控制:只可将其与命名类型进行比较,而不能与Class对象进行比较。
14.3.1使用类字面常量
14.3.2动态的instanceof
1、Class.isInstance()方法提供了一种动态地测试对象的途径。
14.3.3递归计数
14.4注册工厂
1、静态初始化器只有在类首先被加载的情况下才能被调用
2、工厂方法设计模式:将对象的创建工作交给类自己去完成。工厂方法可以被多态地调用,从而创建恰当类型的对象。
14.5instanceof与Class的等价性
1、

 class Base{
}
class Derived extends Base{
}
//test()方法使用了两种形式的instanceof作为参数来执行类型检查,然后获取Class引用,并用==和equals()来
//检查Class对象是否相等 instance和isInstance()生成的结果完全一样,equals()和==也一样。但是两组测试的
//结论不同。instanceof保持了类型的概念,它指的是“你是这个类嘛,或者你是这个类的派生类嘛?”而如果用==比较
//实际的class对象,就没有考虑到继承
public class Test7 {
     static void test(Object x){
         System.out.println("Testing x of type:"+x.getClass());
         System.out.println("x instanceof Base "+(x instanceof Base));
         System.out.println("x instanceof Derived "+(x instanceof Derived));
         System.out.println("Base.instance(x) "+Base.class.isInstance(x));
         System.out.println("Derived isInstance(x) "+Derived.class.isInstance(x));
         System.out.println("x.getClass==Base.class "+(x.getClass()==Base.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)));
     }

    public static void main(String[] args) {
        test(new Base());
        test(new Derived());
    }
    /**
     * Testing x of type:class com14.Base
     x instanceof Base true
     x instanceof Derived false
     Base.instance(x) true
     Derived isInstance(x) false
     x.getClass==Base.class true
     x.getClass().equals(Base.class) true
     x.getClass().equals(Derived.class) false
     Testing x of type:class com14.Derived
     x instanceof Base true
     x instanceof Derived true
     Base.instance(x) true
     Derived isInstance(x) true
     x.getClass==Base.class false
     x.getClass().equals(Base.class) false
     x.getClass().equals(Derived.class) true
     */
}

14.6反射
1、在编译时,编译器必须知道所有要通过RTTI来处理的类。
2、在运行时获取类的信息的另一个动机,便是希望提供在跨网络的远程平台上创建和运行对象的能力。被称为远程方法调用.(RMI)允许一个Java程序将对象分布到多台机器上。
3、RTTI和反射的真正区别在于:对于RTTI来说,编译器在编译时打开和检查.class文件(换句话说,可以使用“普通”方法调用对象的所有方法)。而对于反射机制来说,.class文件在编译时是不可获取的, 所以在运行时打开和检查.class文件。
14.6.1类方法提取器
1、getFileds() getMethods() getConstructors()
14.7 动态代理
1、Java的动态代理比代理的思想更向前进一步,可以动态地创建代理并动态地代理对锁代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定响应的对策。
2

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Created by Panda on 2018/5/14.
 */
class DynamicProxy implements InvocationHandler{
    private Object proxid;

    public DynamicProxy(Object proxid) {
        this.proxid = proxid;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("**** proxy:"+proxy.getClass()+",method: "+method+",args: "+args);
        if(args!=null){
            for (Object org: args) {
                System.out.println(" "+args);
            }
        }
        return method.invoke(proxid,args);
    }
}
public class SimpleDynamicProxy  {
    public static void consume(Interface inf){
        inf.doSomething();
        inf.doSomethingElse("milk");
    }

    public static void main(String[] args) {
        RealObject realObject = new RealObject();
        consume(realObject);
        Interface proxy=(Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(),
                new Class[]{Interface.class},new DynamicProxy(realObject));
        consume(proxy);
    }
    /**
     * somethingElse milk
     **** proxy:class com14.$Proxy0,method: public abstract void com14.Interface.doSomething(),args: null
     doSomeThing
     **** proxy:class com14.$Proxy0,method: public abstract void com14.Interface.doSomethingElse(java.lang.String),args: [Ljava.lang.Object;@2503dbd3
     [Ljava.lang.Object;@2503dbd3
     somethingElse milk
     */
}

14.8 空对象
1、引入空对象的思想很有用,可以接收传递给它的所代表的对象的消息,但是将返回表示位实际上并不存在任何“真实”对象的值。
14.8.1模拟对象与桩
1、空对象的逻辑变体是模拟对象和桩。与空对象一样,它们都表示在最终的程序中所使用的“实际”对象。但是,模拟对象和桩都只是假扮可以传递实际信息的存活对象,而不是像空对象那样可以成为null的一种更加智能化的替代物。
2、模拟对象和桩之间的差异在于程度不同。模拟对象往往是轻量级和自测试的,通常有很多模拟对象被创建出来是为了处理各种不同的测试情况。桩只是返回桩数据,它通常是重量级的,并且经常在测试之间被复用。桩可以根据它们被调用的方式,通过配置进行修改,因此桩是一种复杂对象,它要做很多事。然而对于模拟对象,如果需要做很多事情,通常会创建大量小而简单的模拟对象。
14.9 接口与类信息
1、interface关键字的一种重要目标就是允许程序员隔离构件,进而降低耦合性。如果编写接口,可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去—接口并非是对解耦的一种保障。
14.10 总结
1、RTTI(Run-Time Type Identification) 允许通过匿名基类的引用来发现类型信息。

猜你喜欢

转载自blog.csdn.net/panda_____/article/details/80316434
今日推荐