《Thinking in Java》学习——15章泛型(三)

96
zpauly
2016.09.05 23:27* 字数 1320

动态类型安全

1.Java SE5中的java.util.Collections中有一组工具用于检查容器所持有的类型是否是我们所需要的,它们是:静态方法checkedCollection()checkedList()checkedMap()checkedSet()checkedSortedMap()checkedSortedSet()。这些方法会将你希望动态检查的容器当作第一个参数接受,并将你希望强制要求的类型作为第二个参数接受。

List<Dog> dogs = Collections.checkedList(new ArrayList<Dog>(), Dog.class);

2.因为可以向Java SE5之前的代码传递泛型容器,所以旧式代码仍旧有可能破坏你的容器,此时上述工具就可以解决在这种情况下的类型检查问题。

异常

1.由于擦除的原因,嫁给你泛型应用于异常是非常受限的。catch语句不能捕获泛型类型的异常,因为在编译期和运行时都必须知道异常的确切类型。泛型类也不能直接货或间接继承自Throwable

混型

1.混型的最基本的概念是混合多个类的能力。以产生一个可以表示混型中所有与类型的类。

一.与接口混合

1.一种常见的产生混型效果的方法是使用接口:

interface TimeStamped {
    long getStamp();
}

class TimeStampedImp implements TimeStamped {
    private final long timeStamp;
    public TimeStampedImp() {
        timeStamp = new Date().getTime();
    }
    public long getStamp() {
        return timeStamp;
    }
}

interface SerialNumbered {
    long getSerialNumbered();
}

class SerialNumberedImp implements SerialNumbered {
    private static long counter = 1;
    private final long serialNumber = counter++;
    private long getSerialNumber() {
        return serialNumber;
    } 
}

interface Basic {
    public void set(String val);
    public String get();
}

class BasicImp implements Basic {
    private String value;
    public void set(String val) {
        this.value = val;
    }
    public String get() {
        return value;
    }
}

class Mixmin extends BasicImp implements TimeStamped, SerialNumbered {
    private TimeStamped timeStamp = new TimeStampedImp();
    private SerialNumbered serialNumber = new SerialNumberedImp();
    public long getStamp() {
        return timeStamp.getStamp();
    }
    public long getSerialNumber() {
        return serialNumber.getSerialNumber();
    }
}

public class Mixmins {
    public static void main (String... args) {
        Mixmin mixmin1 = new Mixmin(), mixmin2 = new Mixmin();
        mixmin1.set("string1");
        mixmin2.set("string1");
        System.out.println(mixmin1.get() + "  " + mixmin1.getStamp() + "  " + mixmin1.getSerialNumber());
        System.out.println(mixmin2.get() + "  " + mixmin2.getStamp() + "  " + mixmin2.getSerialNumber());
    }
}

这个示例的使用方法非常简单,但是当使用更加复杂的混型时,代码量会急速增加。

二.使用装饰器模式

1.装饰器是通过使用组合和形式化结构来实现的,而混型时基于继承的。因此可以将基于参数化类型的混型当作一种泛型装饰器机制,这种机制不需要装饰器设计模式的继承结构:

class Basic {
    private String value;
    public void set(String val) {
        this.value = val;
    }
    public String get() {
        return value;
    }
}

class Decorator extends Basic {
    protected Basic basic;
    public Decorator(Basic basic) {
        this.basic = basic;
    }
    public void set(String val) {
        basic.set(val);
    }
    public String get() {
        return basic.get();
    }
}

class TimeStamped extends Decorator {
    private final long timeStamp;
    public TimeStampedImp(Basic basic) {
        super(basic);
        timeStamp = new Date().getTime();
    }
    public long getStamp() {
        return timeStamp;
    }
}

class SerialNumbered extends Decorator {
    private static long counter = 1;
    private final long serialNumber = counter++;
    public SerialNumbered(Basic basic) {
        super(basic);
    }
    private long getSerialNumber() {
        return serialNumber;
    } 
}

public class Decoration {
    public static void main(String...args) {
        TimeStamped t = new TimeStamped(new Basic());
        TimeStamped t2 = new TimeStamped(new SerialNumbered(new Basic()));

        SerialNumbered t = new SerialNumbered(new Basic());
        SerialNumbered t2 = new SerialNumbered(new TimeStamped(new Basic()));
    } 
}

对于装饰器来说,其明显的缺陷谁它只能有效地工作于装饰中的最后一层,而混型方法显然会更佳自然一些,因此,装饰器只是对由混型提出的问题的一种局限的解决方案。

三.与动态代理结合

1.可以使用动态代理来创建一种比装饰器更贴近混型模型的机制。由于动态代理的限制,每个被混入的类都必须时某个接口的实现:

class MixminProxy implements InvocationHandler {
    Map<String, Object> delegatesByMethod;
    public MixminProxy(TwoTuple<Object, Class<?>>... pairs) {
        delegatesByMethod = new HashMap<String, Object>();
        for (TwoTuple<Object, Class<?>> pair : pairs) {
            for (Method method : pair.second.getMethods()) {
                String methodName = method.getName();
                if (!delegatesByMethod.containsKey(methodName)) 
                    delegatesByMethod.put(methodName, pair.first);
            }
        }
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Object delegate = delegatesByMethod.get(methodName);
        return method.invoke(delegate, args);
    }

    public static Object newInstance(TwoTuple... pairs) {
        Class[] interfaces = new Class[pairs.length];
        for (int i = 0; i < pairs.length; i ++) {
            interfaces[i] = (Class) pairs[i].second;
        }
        ClassLoader cl = pair[0].first.getClassLoader();
        return Proxy.newProxyInstance(cl, interfaces, new MixminProxy(pairs));
    }
}

public class DynamicProxyMixmin {
    public static void main(String... args) {
        Object mixmin = MixminProxy.newInstance(
            tuple(new BasicImp(), Basic.class), 
            tuple(new TimeStampedImp(), TimeStamped.class), 
            tuple(new SerialNumberedImp(), SerialNumbered.class));
        Basic b = (Basic) mixmin;
        TimeStamped t = (TimeStamped) mixmin;
        SerialNumbered s = (SerialNumbered) mixmin;
        b.set("Hello");
        System.out.println(b.get());
        System.out.println(t.get());
        System.out.println(s.get());
    }
}

这种方案要比上面两种方式更加接近于真正的混型。

潜在类型机制

1.某些编程语言提供了一种机制——潜在类型机制
2.泛型代码典型地将在泛型类型上调用少量方法,而具有潜在类型机制的语言只要求实现某个方法的子集,而不是某个特定类或接口,从而放松了这种限制。
3.潜在类型机制是一种代码组织和复用机制。有了它编写出来的代码相对于没有它编写出的代码,能够更容易滴复用。
4.由于泛型是后期才加进Java的,因此没有任何机会可以去实现任何类型的潜在类型机制,因此Java没有对这种类型的支持。

对缺乏潜在类型机制的补偿

一.反射

在Java中使用潜在类型机制,可以使用的一种方式是反射,下面的perform()方法就是用了潜在类型机制:

class Mime {
    public void walkAgainstTheWind() {}
    public void sit() { print("Pretending to sit"); }
    public void pushInvisibleWalls() {}
    public String toString() { return "Mime"; }
}

class SmartDog {
    public void speak() { print("Woof!"); }
    public void sit() { print("Sittint"); }
    public void reproduce() {}
}

class CommunicateReflectively {
    public static void perform(Object speaker) {
        Class<?> spkr = speaker.getClass();
        try {
            try {
                Method speak = spkr.getMethod("speak");
                speak.invoke(speaker);
            } catch (NoSuchMethodException e) {
                print(speaker + "cannot speak");
            }
            try {
                Method sit = spkr.getMethod("sit");
                sit.invoke(sit);
            } catch (NoSuchMethodException e) {
                print(speaker + "cannot sit");
            }
        } catch (Exception e) {
            throw new RuntimeException(speaker.toString(), e);
        }
    }
}

public class LatentReflection {
    public static void main(String.... args) {
        CommunicateReflectively.perform(new SmartDog());
        CommunicateReflectively.perform(new Robot());
        CommunicateReflectively.perform(new Mime());
    }
}

上述代码中,这些类都是彼此分离的。

将一个方法应用于序列

1.反射虽然提供了潜在类型机制的可能性,但是它将所有类型检查都转移到了运行时。如果能够实现编译期类型检查,这通常会更加符合要求。
2.假设想要创建一个方法,它能够将任何方法应用于某个系列中的所有对象。我们可以使用上面的反射以及可变参数来解决这个问题:

class Shape {
    public void rotate() { print(this + " rotate") }
    public void resize(int newSize) {
        print(this + " resize" + newSize);
    }
}

class Square extends Shape {}

class Apply {
    public static <T, S extends Iterable<? extends T>> void apply (S seq, Method f, Object... args) {
        try {
            for (T t : seq) {
                f.invoke(t, args);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

class FilledList<T> extends ArrayList<T> {
    public FilledLIst(Class<? extends T> type, int size) {
        try {
            for (int i = 0; i < size; i ++) {
                add(type.newInstance());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

class Test {
    public static void main(String... args) throws Exception {
        List<Shape> shapes = new ArrayList<Shape>();
        for (int i = 0; i < 10; i ++)
            shapes.add(new Shape());
        Apply.apply(shapes, Shape.class.getMethod("rotate"));
        Apply.apply(shapes, Shape.class.getMethod("resize", int.class), 5);

        Apply.apply(new FilledList<Shape>(Shape.class, 10), Shape.class.getMethod("rotate"));
    }
}

尽管之中方法的解决方法背证明很优雅, 但是我们必须知道使用反射比非反射可能要慢一些,因为动作都是在运行时发生的。

三.当你并未碰巧拥有正确的接口时

1.如果具有潜在类型机制的参数化类型机制,你不会受任何特定类库的创建者过去所作的设计的支配,不想上面的代码需要适合需求的接口,因此这样的代码不是特别的“泛化”。

四.用适配器模仿潜在类型机制

1.实际上,潜在类型机制创建一个包含所需方法的隐式接口。因此它遵循这样的规则L如果我们手工编写了必需的接口,那么它就应该能够解决问题。
2.从我们拥有的接口中编写代码来产生我们需要的接口,这是适配器设计模式的一个典型示例。我们可以使用适配器来适配已有的接口,以产生想要的接口。

Web note ad 1