Java编程思想读书笔记——类型信息

第十四章 类型信息

14.1 为什么需要RTTI

RTTI(Run-Time Type Identification):运行时类型识别。
从数组中取出元素是RTTI最基本的使用形式。

14.2 Class对象

Class对象包含了与类有关的信息。
每一个类都有一个Class对象,在编写并编译后,被保存在同名的.class文件中。
而需要生成该类的对象时,则是由JVM使用”类加载器”的子系统来加载。所有的类是在其第一次使用(第一次创建对象或使用相应的静态成员或方法)时,动态加载到JVM当中。
类加载器首先检查该类的Class对象是否已经加载,如尚未加载,则默认的类加载器会根据类名查找.class文件,在经验证后载入内存,以用于创建该类的所有对象。

package com.mzm.chapter14;

/**
 * 构造器实际上是类的静态方法
 * new操作符创建对象可看作是对类的静态成员的引用
 * 
 */
public class SweetShop {

    public static void main(String[] args){
        System.out.println("inside main");
        new Candy();
        System.out.println("After creating Candy");

        try{
            Class.forName("com.mzm.chapter14.Gum");
        } catch (ClassNotFoundException e) {
            System.out.println("Couldn't find Gum");
        }
        System.out.println("After Class.forName(\"Gum\")");

        new Cookie();
        System.out.println("After creating Cookie");
    }
}

class Candy{
    static {
        System.out.println("Loading Candy");
    }
}

class Gum{
    static {
        System.out.println("Loading Gum");
    }
}

class Cookie{
    static {
        System.out.println("Loading Cookie");
    }
}

Class.forName()方法是根据这个类的类全名(包名+类名)获取Class对象。这一可以不必通过创建该类的对象的方式来获取相应的Class对象。如果根据给定的类全名找不到该类,则会抛出ClassNotFoundException异常。

如果在已经持有一个类的对象的情况下,可以通过这一对象的getClass()获取所属类的Class对象。

Class对象有一系列方法
getName():获取该类的类全名(包名 + 类名);
getSimpleName():获取该类的类名;
getCanonicalName():获取该类的类全名;
getInstances():获取该类实现的所有接口对应的Class对象;
getSuperClass():获取该类的父类的Class对象;
newInstance():创建该类的对象,使用该法创建该类对象时,该类必须有默认的构造器。

14.2.1 类字面常量

还可以根据类字面常量来获取一个类的Class对象。如:

ClassName.class

类字面常量可用于普通类、接口、数组以及基本数据类型。此外,基本数据类型的包装类,还有一个标准字段TYPE,是对应基本数据类型的Class对象的引用。

基本数据类型类字面常量 对应包装类的TYPE字段
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

与使用Class.forName()方法不同,使用类字面常量获取Class对象,不会自动初始化Class对象。

package com.mzm.chapter14;

import java.util.Random;

/**
 * 
 */
public class ClassInitialization {
    public static Random rand = new Random(47);

    public static void main(String[] args) throws ClassNotFoundException {
        //不会立即执行初始化
        Class initable = Initable.class;
        System.out.println("After creating Initable ref");

        //编译期常量,无需初始化,就可读取
        System.out.println(Initable.staticFinal);
        //非编译期常量,需要初始化,才可以读取
        System.out.println(Initable.staticFinal2);

        System.out.println(Initable2.staticNonFinal);

        //立即执行初始化
        Class initable3 = Class.forName("com.mzm.chapter14.Initable3");
        System.out.println("After creating Initable3 ref");
        System.out.println(Initable3.staticNonFinal);
    }
}

class Initable{
    static final int staticFinal = 47;
    static final int staticFinal2 = 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");
    }
}
14.2.2 泛化的Class引用

泛化的Class引用,为其所指向的类进行了限定。而普通的Class引用则没有此限定。如:

Class<Integer> genericIntClass = Integer.class;
// genericIntClass = Double.class;

让泛化的Class引用放松限制:

Class intClass = int.class;
intClass = double.class;

限定范围

Classextends Number> bounded = int.class;

可以指向任何继承自Number类的子类的Class对象。利用这一Class对象的newInstance()方法创建的对象的类型是确定的,即Integer类型。

ClassInteger> up = Number.class;

可以指向任何衍生此类的父类的Class对象。但是利用这一Class对象的newInstance()方法创建的对象是Object类型。

14.2.3 新的转型语法

cast()方法接受参数对象,并将其转型为Class引用的类型。

package com.mzm.chapter14;

/**
 * 
 */
public class ClassCasts {

    public static void main(String[] args){
        Building b = new House();
        Class houseType = House.class;
        House h = houseType.cast(b);
        h = (House) b;
    }
}

class Building{

}

class House extends Building{

}

14.3 类型转换前先做检查

RTTI的三种形式:
1) 传统的类型转化,由RTTI确保类型转换的正确性,如果错误,则抛出ClassCastException;
2) 代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。
3) 关键字instanceof,返回一个boolean值,用于判断对象是不是某一类的实例。一般要查找某种类型,可以使用instanceof来遍历所有对象。

Class.isInstance()方法提供了一种动态测试对象的途径。

Class<Type> type = Type.class;
boolean b = type.isInstance(obj); 

Class.isAssignableFrom(),接受一个Class对象作为参数,判断传入的Class对象的类型是否是其后代。

14.4 注册工厂

工厂方法设计模式,将对象的创建工作交给类自己去完成。

package com.mzm.chapter14;

/**
 * 
 */
public interface Factory {

    T create();
}

任何需要使用工厂来创建对象的类,可在其内部包含一个静态的Factory类,并实现这一接口,然后返回该类对象即可。

14.5 instanceof与Class的等价性

在查询类型信息时,使用instanceof与Class对象的isInstance()方法所得结果一致。

14.6 反射:运行时的类信息

反射主要是希望获取不在自身程序空间中的类的信息。
Class类与java.lang.reflect类库一起对反射提供了支持。java.lang.reflect类库包括Field、Method以及Constructor类,每个类都实现了Member接口。

RTTI与反射的区别:RTTI在编译时打开和检查.class文件,反射是在运行时打开和检查.class文件。

14.6.1 类方法提取器
package com.mzm.chapter14;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.regex.Pattern;

/**
 * 
 * Created by 蒙卓明 on 2017/11/12.
 */
public class ShowMethods {
    private static String usage = "usage:\n" +
                    "ShowMethods qualified.class.name\n" +
                    "To show all methods in class or:\n" +
                    "ShowMethods qualified.class.name word\n" +
                    "To search for methods involving 'word'";

    //使用正则表达式匹配方法中的命名修饰词,如"java.reflect."
    private static Pattern p = Pattern.compile("\\w+\\.");

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println(usage);
            System.exit(0);
        }

        int lines = 0;
        try {
            Class c = Class.forName(args[0]);
            Method[] methods = c.getMethods();
            Constructor[] ctors = c.getConstructors();
            if (args.length == 1) {
                for (Method method : methods) {
   //删除命名修饰词                 System.out.println(p.matcher(method.toString()).replaceAll(""));
                }
                for (Constructor ctor : ctors) {
                    System.out.println(p.matcher(ctor.toString()).replaceAll(""));
                }
                lines = methods.length + ctors.length;
            } else {
                for (Method method : methods) {
                    if(method.toString().indexOf(args[1]) != -1){
                        System.out.println(p.matcher(method.toString()).replaceAll(""));
                        lines++;
                    }
                }
                for (Constructor ctor : ctors) {
                    if(ctor.toString().indexOf(args[1]) != -1){
                        System.out.println(p.matcher(ctor.toString()).replaceAll(""));
                        lines++;
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            System.out.println("No such class: " + e);
        }
    }
}

14.7 动态代理

代理提供了额外的或不同操作,而插入的用来代替”实际”对象的对象。其充当中间人的角色。

package com.mzm.chapter14;

/**
 * 代理Demo
 * 
 */
public class SimpleProxyDemo {

    public static void consumer(Interface iface){
        iface.doSomething();
        iface.somrthingElse("bonobo");
    }

    public static void main(String[] args){
        consumer(new RealObject());
        consumer(new SimpleProxy(new RealObject()));
    }
}

interface Interface {
    void doSomething();
    void somrthingElse(String arg);
}

class RealObject implements Interface {

    @Override
    public void doSomething() {
        System.out.println("doSomething");
    }

    @Override
    public void somrthingElse(String arg) {
        System.out.println("somethingElse " + arg);
    }
}

class SimpleProxy implements Interface {

    private Interface proxied;

    public SimpleProxy(Interface proxied){
        this.proxied = proxied;
    }

    @Override
    public void doSomething() {
        System.out.println("SimpleProxy doSomething");
        proxied.doSomething();
    }

    @Override
    public void somrthingElse(String arg) {
        System.out.println("SimpleProxy somethingElse " + arg);
        proxied.somrthingElse(arg);
    }
}

首先是必须存在一个接口,真实对象和代理对象实现同一接口,代理对象包含一个给定的接口成员,在构造方法中将真实对象传入,这是静态代理。

动态代理,可以动态地创建代理并动态地处理对所代理方法的调用。在动态代理上所有调用都会重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。

package com.mzm.chapter14;


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

/**
 * 动态代理Demo
 * 
 */
public class SimpleDynamicProxy {

    public static void consumer(Interface iface){
        iface.doSomething();
        iface.somrthingElse("bonobo");
    }

    public static void main(String[] args){
        //真实对象
        RealObject real = new RealObject();
        consumer(real);

        //代理对象
        //使用Proxy的newProxyInstance()方法创建,需要三个参数:
        //类加载器,可从已加载对象获取
        //代理实现的接口列表
        //调用处理器,即InvocationHandler接口的一个实现
        Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(),
                new Class[]{Interface.class}, new DynamicProxyHandler(real));
        consumer(proxy);
    }
}

/**
 * 调用处理器
 */
class DynamicProxyHandler implements InvocationHandler {

    //真实对象
    private Object proxied;

    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }


    /**
     * 揭示调用的类型并确定相应的对策
     * @param proxy 真实对象
     * @param method 代理方法
     * @param args 方法参数
     * @return 调用真实对象的对应方法
     * @throws Throwable
     */
    @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 arg : args) {
                System.out.println(" " + arg);
            }
        }
        return method.invoke(proxied, args);
    }
}
package com.mzm.chapter14;

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

/**
 * 使用动态代理探查指定方法
 * Created by 蒙卓明 on 2017/11/12.
 */
public class SelectingMethods {

    public static void main(String[] args){
        SomeMethods proxy = (SomeMethods) Proxy.newProxyInstance(SomeMethods.class.getClassLoader(),
                new Class[]{SomeMethods.class}, new MethodSelector(new Implemetation()));
        proxy.boring1();
        proxy.boring2();
        proxy.interesting("bonobo");
        proxy.boring3();
    }
}

class MethodSelector implements InvocationHandler {

    private Object proxied;

    public MethodSelector(Object proxied){
        this.proxied = proxied;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //获取指定方法名,探查指定方法
        if(method.getName().equals("interesting")){
            System.out.println("Proxy detected the interesting method");
        }
        return method.invoke(proxied, args);
    }
}

interface SomeMethods {
    void boring1();
    void boring2();
    void interesting(String arg);
    void boring3();
}

class Implemetation implements SomeMethods {

    @Override
    public void boring1() {
        System.out.println("boring1");
    }

    @Override
    public void boring2() {
        System.out.println("boring2");
    }

    @Override
    public void interesting(String arg) {
        System.out.println("interesting " + arg);
    }

    @Override
    public void boring3() {
        System.out.println("boring3");
    }
}

14.8 空对象

空对象是策略模式的一种特例。它与null的区别在于,它是一个实际的对象,只是其相应的字段取默认值而已。

区别空对象与其他实际对象,可以定义一个标志接口,比如Null:

package com.mzm.chapter14;

/**
 * 
 */
public interface Null {


}
package com.mzm.chapter14;

/**
 * Created by 蒙卓明 on 2017/11/14.
 */
public class Human {

    public final String first;
    public final String last;
    public final String address;

    public Human(String first, String last, String address){
        this.first = first;
        this.last = last;
        this.address = address;
    }

    public String toString() {
        return "Human: " + first + " " + last + " " + address;
    }

    /**
     * 人的空对象
     */
    public static class NullHuman extends Human implements Null {

        public NullHuman() {
            super("None", "None", "None");
        }

        public String toString() {
            return "NullHuman";
        }
    }

    /**
     * 人的空对象只有一个
     */
    public static final Human Null = new NullHuman();
}

判断该类的对象是不是一个空对象,只需要查看它的类型是否是Null类型即可(instanceof)。

空对象还可以交由动态代理来创建。首先必须要有具体对象和空对象抽象出来的接口:

package com.mzm.chapter14;

import java.util.List;

/**
 * 具体对象和空对象的抽象接口
 * 
 */
public interface Robot {

    String name();
    String model();
    List operations();
    class Test {
        public static void test(Robot r) {
            if(r instanceof Null){
                System.out.println("[Null Robot]");
            }
            System.out.println("Robot name: " + r.name());
            System.out.println("Robot model: " + r.model());
            for(Operation operation : r.operations()){
                System.out.println(operation.description());
                operation.command();
            }
        }
    }
}
package com.mzm.chapter14;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.List;

/**
 * 通过动态代理创建按空对象
 * Created by 蒙卓明 on 2017/11/16.
 */
public class NullRobot {

    public static Robot newNullRobot(Classextends Robot> type) {
        return (Robot) Proxy.newProxyInstance(NullRobot.class.getClassLoader(), new Class[]{Null.class, Robot.class},
                new NullRobotProxyHandler(type));
    }

    public static void main(String[] args){
        Robot[] robots = {new SnowRemovalRobot("SnowBee"), newNullRobot(SnowRemovalRobot.class)};
        for(Robot robot : robots){
            Robot.Test.test(robot);
        }
    }
}

class NullRobotProxyHandler implements InvocationHandler {

    private String nullName;
    private Robot proxied = new NRobot();

    NullRobotProxyHandler(Classextends Robot> type) {
        nullName = type.getSimpleName() + " NullRobot";
    }

    private class NRobot implements Null, Robot {

        @Override
        public String name() {
            return nullName;
        }

        @Override
        public String model() {
            return nullName;
        }

        @Override
        public List operations() {
            return Collections.emptyList();
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(proxied, args);
    }
}

空对象的判断还是一样查看其是否是标志接口Null的对象。

14.8.1 模拟对象与桩

14.9 接口与类型信息

接口并非是对解耦的一种无懈可击的保障。
假定有一个A接口,一个类B实现了这个接口,但是同时还有另外的方法。

package com.mzm.chapter14;

/**
 * 
 */
public interface A {
    void f();
}
package com.mzm.chapter14;

/**
 * Created by 蒙卓明 on 2017/11/16.
 */
public class InterfaceViolation {

    public static void main(String[] args){
        A a = new B();
        a.f();
        //a.g();
        System.out.println(a.getClass().getSimpleName());
        if(a instanceof B){
            B b = (B) a;
            b.g();
        }
    }
}

class B implements A {

    @Override
    public void f() {

    }

    public void g() {

    }
}

显然可以通过判断类型的方法,来调用B类对象中,除A接口定义方法之外的方法(g()方法)。
要想实现这一目的,可以使用包访问权限。

package com.mzm.chapter14;

import com.mzm.chapter14.packageaccess.HiddenC;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 
 */
public class HiddenImplementation {

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException,
            InvocationTargetException {
        A a = HiddenC.makeA();
        a.f();
        System.out.println(a.getClass().getSimpleName());
        //设定包访问权限后,正常方式已经无法访问
        /*if(a instanceof C) {
            C c = (C) a;
            c.g();
        }*/

        //但是通过反射,可以访问所有方法
        callHiddenMethod(a, "g");
        callHiddenMethod(a, "u");
        callHiddenMethod(a, "v");
        callHiddenMethod(a, "w");
    }

    static void callHiddenMethod(Object a, String methodName) throws NoSuchMethodException,
            InvocationTargetException, IllegalAccessException {
        Method g = a.getClass().getDeclaredMethod(methodName);
        g.setAccessible(true);
        g.invoke(a);
    }
}

但是如果知道方法名,通过反射可以调用所有方法。

即便是发布编译后的代码,仍然可以通过反编译的手段获取方法名,然后通过反射的方式调用所有方法。
即便是将接口实现为私有内部类也是一样,同理匿名类也是一样。

package com.mzm.chapter14;

import java.lang.reflect.InvocationTargetException;

/**
 * 
 */
public class InnerImplementation {

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        A a = InnerA.makeA();

        HiddenImplementation.callHiddenMethod(a, "g");
        HiddenImplementation.callHiddenMethod(a, "u");
        HiddenImplementation.callHiddenMethod(a, "v");
        HiddenImplementation.callHiddenMethod(a, "w");
    }
}

class InnerA {
    private static class Cc implements A {

        @Override
        public void f() {
            System.out.println("public C.f()");
        }

        public void g() {
            System.out.println("public C.g()");
        }

        void u() {
            System.out.println("package C.u()");
        }

        protected void v() {
            System.out.println("protected C.v()");
        }

        private void w() {
            System.out.println("private C.w()");
        }
    }

    public static A makeA() {
        return new Cc();
    }
}

利用反射可以获取和修改类的私有字段,当然final修饰的无法修改。

package com.mzm.chapter14;

import java.lang.reflect.Field;

/**
 * 
 */
public class ModifyingPrivateFields {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        WithPrivateFinalField pf = new WithPrivateFinalField();

        System.out.println(pf);
        Field f = pf.getClass().getDeclaredField("i");
        f.setAccessible(true);
        System.out.println("f.getInt(pf): " + f.getInt(pf));
        f.setInt(pf, 47);
        System.out.println(pf);

        //final修饰的字段无法修改
        f = pf.getClass().getDeclaredField("s");
        f.setAccessible(true);
        System.out.println("f.get(pf): " + f.get(pf));
        f.set(pf, "No, you're not!");
        System.out.println(pf);

        f = pf.getClass().getDeclaredField("s2");
        f.setAccessible(true);
        System.out.println("f.get(pf): " + f.get(pf));
        f.set(pf, "No, you're not!");
        System.out.println(pf);
    }
}

class WithPrivateFinalField {
    private int i = 1;
    private final String s = "I'm totally safe";
    private String s2 = "Am I safe?";
    public String toString(){
        return "i = " + i + ", " + s + ", " + s2;
    }
}

你可能感兴趣的:(java)