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

动态类型安全

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.从我们拥有的接口中编写代码来产生我们需要的接口,这是适配器设计模式的一个典型示例。我们可以使用适配器来适配已有的接口,以产生想要的接口。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,537评论 18 399
  • 简单泛型 1.泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性。2.Java...
    zpauly阅读 1,227评论 0 1
  • 在经过一次没有准备的面试后,发现自己虽然写了两年的android代码,基础知识却忘的差不多了。这是程序员的大忌,没...
    猿来如痴阅读 2,808评论 3 10
  • 20160613 ~ 20160619 精进 改变生活的小习惯,来自Quora的帖子What-tiny-daily...
    老杜还在阅读 415评论 0 0
  • 显示正在运行的线程状态 ,包括pid、user、pr、time、command、cpu等等
    山的那边是什么_阅读 223评论 0 0