再看泛型
来源:互联网 发布:统计汇总报表软件 编辑:程序博客网 时间:2024/04/30 13:41
简单泛型
1.元组:它是将一组对象直接打包存储于其中的一个单一对象,这个容器对象允许读取其中元素,但是不允许向其中存放新的对象。(这个概念也称为数据传送对象,或信使。)
代码例子:
//一个2维元组public class TwoTuple<A,B> { //用final确保了A,B不能改变的赋值的安全性 public final A first; public final B second; public TwoTuple(A a, B b) { first = a; second = b; } public String toString() { return "(" + first + ", " + second + ")"; }}//一个3维元组public class ThreeTuple<A,B,C> extends TwoTuple<A,B> { public final C third; public ThreeTuple(A a, B b, C c) { super(a, b); third = c; } public String toString() { return "(" + first + ", " + second + ", " + third +")"; }}
泛型方法
1.与使用泛型类时的不同是(必须创建对象的时候指定类型参数的值),使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型,这称为类型参数推断。
2.类型推断对赋值操作是有效的,其他时候病不起作用,如果你将一个泛型方法调用的结果作为参数,传递给另一个方法,这时编译器并不会执行类型推断,在这种情况下,编译器认为:调用泛型方法后,其返回值被赋给一个Object类型的变量。
3.在泛型方法中,可以显示地指明类型:必须在点操作符与方法名之间插入参数类型尖括号,如果是在定义该方法的类的内部,必须在点操作之前使用this关键字,如果是使用static的方法,必须在点操作符之前加上类名。
代码例子:
public class New { public static <K,V> Map<K,V> map() { return new HashMap<K,V>(); } public static <T> List<T> list() { return new ArrayList<T>(); } public static <T> LinkedList<T> lList() { return new LinkedList<T>(); } public static <T> Set<T> set() { return new HashSet<T>(); } public static <T> Queue<T> queue() { return new LinkedList<T>(); } public static void test1(Map<String,List<String>> map){}; public static void test2(List<String> list){}; public static void main(String[] args) { //正常写法 Map<String,List<String>> map1=new HashMap<String, List<String>>(); List<String> list1=new ArrayList<String>(); //利用类型推断就避免了重复的泛型参数列表 Map<String, List<String>> map2= New.map(); List<String> list2= New.list(); //下面语句会编译错误,因为类型推断对方法参数赋值不起作用 test1(New.map()); test2(New.list()); //可以显示的指定类型 test1(New.<String,List<String>>map()); test2(New.<String>list()); }}
4.由于擦除的影响,泛型方法的重载用了桥接设计模式来完成,这过程是由虚拟机来实现的。
代码例子:
public class Test<T>{ T age; public void set(T age) { this.age=age; } public T get() { return age; }}public class Test1 extends Test<Integer>{ public void set(Integer age) { System.out.println("子类"); this.age=age; } public Integer get() { System.out.println("子类"); return age; } public static void main(String[] args) { Test<Integer> test=new Test1(); //编译错误 test.set(new Object()); //编译通过 test.set(5); System.out.println(test.get()); }}
运行结果:
子类
子类
5
疑问:由于擦除了父类类型参数为Object,相对于子类set里的参数是Integer明显不符合方法重写,但又从那行编译错误的结果来看又不是重载,但为什么子类这样写还能覆盖父类的方法呢?答案JVM用桥接方式帮其子类实现了方法的重写。
实际代码:
public class Test{ Object age; public void set(Object age) { this.age=age; } public Object get() { return age; }}public class Test1 extends Test<Integer>{ public void set(Integer age) { System.out.println("子类"); this.age=age; } public Integer get() { System.out.println("子类"); return age; } //以下两个方法使用JVM生成,其中两个get方法是可以共存的,但如果是人写的话,编译器是不允许共存这两个一模一样的方法,这就是编译器聪明之处,对于两个set的方法可以看出,是JVM自定重载了这个set方法,又再调用另一个set方法,从而实现对父类的重写,这就是所谓的"桥接"。 public void set(Object age) { set((Integer)age); } public Object get() { return age; } public static void main(String[] args) { Test<Integer> test=new Test1(); //编译错误 test.set(new Object()); //编译通过 test.set(5); System.out.println(test.get()); }}
檫除的神秘之处
1.一般而言编译器对泛型有两种处理方式:
- Code specialization。在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型list,可能需要针对string,integer,float产生三份目标代码。
- Code sharing。对每个泛型类只生成唯一的一份目标代码;该泛型类的所有实例都映射到这份目标代码上,在需要的时候执行类型检查和类型转换。
2.相对于C++采用Code specialization而言,Java采用了Code sharing处理泛型,就是利用所谓”擦除机制”来实现Code sharing目的,这种好处有以下两个: - Java编译器利用擦除机制会为每一个泛型类实例共用一份原生的执行代码,执行代码中integer list和string list是两种相同同的类型。这样会减少代码膨胀(code bloat),
- 由于Java编译器利用擦除机制会为每一个泛型类实例共用一份原生的执行代码,故引用类型系统中,可以减少空间的浪费,因为引用类型集合中元素本质上都是一个指针
3.Java泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。
代码例子:
class Frob {}class Fnorkle {}class Quark<Q> {}class Particle<POSITION,MOMENTUM> {}public class LostInformation { public static void main(String[] args) { List<Frob> list = new ArrayList<Frob>(); Map<Frob,Fnorkle> map = new HashMap<Frob,Fnorkle>(); Quark<Fnorkle> quark = new Quark<Fnorkle>(); Particle<Long,Double> p = new Particle<Long,Double>(); //由于擦除机制,下面输出会丢失具体的类型信息 System.out.println(Arrays.toString( list.getClass().getTypeParameters())); System.out.println(Arrays.toString( map.getClass().getTypeParameters())); System.out.println(Arrays.toString( quark.getClass().getTypeParameters())); System.out.println(Arrays.toString( p.getClass().getTypeParameters())); }}
运行结果:
[E]
[K, V]
[Q]
[POSITION, MOMENTUM]
疑问:泛型类型参数是什么时候被擦除呢?先从下面代码实例入手:
代码例子:
//第一个例子:public class Test{ //编译器报的错误:Method test(ArrayList<String>) has the same erasure test(ArrayList<E>) as another method in type Test public void test(ArrayList<String> list){System.out.println("beyondboy");}; public void test(ArrayList<Integer> list){System.out.println("beyondboy");};}//第二个例子public class Test1<T>{ public static void main(String[] args) { ArrayList<String> arrayList=new ArrayList<String>(); arrayList.add("123"); //编译错误 arrayList.add(123); }}
第一个例子很明显两个方法因擦除后一模一样而产生的冲突,第二个例子如果按照第一种来看,不是擦除后变成原生的吗?其原生不是装Object元素的吗?为什么添加元素时还会报错呢?其实编译器在泛型类型参数擦除之前,会先进行类型检查,上面两个例子擦除后代码如下:
//第一个例子:public class Test{ public void test(ArrayList list){System.out.println("beyondboy");}; public void test(ArrayList list){System.out.println("beyondboy");};}//第二个例子public class Test1<T>{ public static void main(String[] args) { //擦除后的原生容器,所装的元素将会是Object类 ArrayList arrayList=new ArrayList(); arrayList.add("123"); //编译错误 arrayList.add(123); }}
可见泛型类型参数是在编译之前并在代码进行检查之后被擦除。
擦除机制的过程:如图
4.擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用,这经常被称为”迁移兼容性”,因此Java泛型不仅必须支持向后兼容性,即现有的代码和类文件仍旧合法,并且继续保持其之前的含义,而且还要支持迁移兼容性,使得类库按照它们自己的步调变为泛型的,并且当某个类库变为泛型时,不会破坏依赖于它的代码和应用程序。(如果不采用这种方式的迁移途径,所有构建了很长时间的类库就需要与希望迁移到Java的泛型上的开发者们说再见了。)
5.因为擦除在方法体中移除了类型信息,所以在运行时的问题就是边界(对传递进来的值进行额外的编译器检查,并插入对传递出去的值的转型。):即对象进入和离开方法的地点,这些正是编译器在编译期执行类型检查并插入转型代码的地点。那两者代码比较就可以知道了。
代码例子:
非泛型原码:
public class SimpleHolder { private Object obj; public void set(Object obj) { this.obj = obj; } public Object get() { return obj; } public static void main(String[] args) { SimpleHolder holder = new SimpleHolder(); holder.set("Item"); String s = (String)holder.get(); } }
非泛型字节码:
public class beyondboy.SimpleHolder { public beyondboy.SimpleHolder(); Code: 0: aload_0 1: invokespecial #10 // Method java/lang/Object."<init>":()V 4: return public void set(java.lang.Object); Code: 0: aload_0 1: aload_1 2: putfield #18 // Field obj:Ljava/lang/Object; 5: return public java.lang.Object get(); Code: 0: aload_0 1: getfield #18 // Field obj:Ljava/lang/Object; 4: areturn public static void main(java.lang.String[]); Code: 0: new #1 // class beyondboy/SimpleHolder 3: dup 4: invokespecial #24 // Method "<init>":()V 7: astore_1 8: aload_1 9: ldc #25 // String Item 11: invokevirtual #27 // Method set:(Ljava/lang/Object;)V 14: aload_1 15: invokevirtual #29 // Method get:()Ljava/lang/Object; 18: checkcast #31 // class java/lang/String 21: astore_2 22: return}
泛型原码:
public class GenericHolder<T> { private T obj; public void set(T obj) { this.obj = obj; } public T get() { return obj; } public static void main(String[] args) { GenericHolder<String> holder = new GenericHolder<String>(); holder.set("Item"); String s = holder.get(); } }
泛型字节码:
public class beyondboy.GenericHolder<T> { public beyondboy.GenericHolder(); Code: 0: aload_0 1: invokespecial #12 // Method java/lang/Object."<init>":()V 4: return public void set(T); Code: 0: aload_0 1: aload_1 2: putfield #23 // Field obj:Ljava/lang/Object; 5: return public T get(); Code: 0: aload_0 1: getfield #23 // Field obj:Ljava/lang/Object; 4: areturn public static void main(java.lang.String[]); Code: 0: new #1 // class beyondboy/GenericHolder 3: dup 4: invokespecial #30 // Method "<init>":()V 7: astore_1 8: aload_1 9: ldc #31 // String Item 11: invokevirtual #33 // Method set:(Ljava/lang/Object;)V 14: aload_1 15: invokevirtual #35 // Method get:()Ljava/lang/Object; 18: checkcast #37 // class java/lang/String 21: astore_2 22: return}
擦除的补偿
现在我们都知道擦除会丢失了在泛型代码中执行某些 操作的能力,这将会使任何确切类型信息的操作都将会无法工作,但有时必须通过引入类型标签对擦除进行补偿,这意味着你需要显示的传递你的类型的Class对象,以便你可以在类型表达式中使用它。
代码例子:
需解决以下问题的代码例子:
public class Erased<T> { private static final int SIZE = 100; public void test(){ //编译错误 if(arg instanceof T) {} //编译错误 T var = new T(); //编译错误 T[] array = new T[SIZE]; // 编译会发生警告 T[] array1 = (T[])new Object[SIZE]; } }
第一个问题解决方案:
public class ClassTypeCapture<T> { Class<T> kind; public ClassTypeCapture(Class<T> kind) { this.kind = kind; } public boolean f(Object arg) { //只要对象是kind的自身实例或其子类实例,就返回true return kind.isInstance(arg); } public static void main(String[] args) { ClassTypeCapture<String> ctt1 = new ClassTypeCapture<String>(String.class); System.out.println(ctt1.f(new String())); System.out.println(ctt1.f(new Integer(2))); ClassTypeCapture<Integer> ctt2 = new ClassTypeCapture<Integer>(Integer.class); System.out.println(ctt2.f(new String())); System.out.println(ctt2.f(new Integer(2))); } }
第二个问题解决的方案:
abstract class GenericWithCreate<T> { final T element; GenericWithCreate() { element = create(); } abstract T create(); } class X {} class Creator extends GenericWithCreate<X> { X create() { return new X(); } void f() { System.out.println(element.getClass().getSimpleName()); } } public class CreatorGeneric { public static void main(String[] args) { Creator c = new Creator(); c.f(); } }
第三个问题的解决方案:
public class GenericArrayWithTypeToken<T> { private T[] array; @SuppressWarnings("unchecked") public GenericArrayWithTypeToken(Class<T> type, int sz) { array = (T[])Array.newInstance(type, sz); } public void put(int index, T item) { array[index] = item; } public T get(int index) { return array[index]; } public T[] rep() { return array; } public static void main(String[] args) { GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<Integer>( Integer.class, 10); Integer[] ia = gai.rep(); } }
通配符
1.由于数组内建了协变类型以及编译期和运行时的检查,故可以向到处类型的数组赋予基类型的数组引用,而泛型就知道任何有关这方面的信息,它一个拒绝泛型对象向上转型一个具体泛型引用(如果泛型引用使用了通配符是可以引用其对象,但会丢失掉向其中传递任何对象的能力。)
代码例子:
数组向上转型
class Fruit {}class Apple extends Fruit {}class Jonathan extends Apple {}class Orange extends Fruit {}public class CovariantArrays { public static void main(String[] args) { //子类数组赋予基类引用 Fruit[] fruit = new Apple[10]; fruit[0] = new Apple(); fruit[1] = new Jonathan(); try { // 编译通过,但运行时会抛出存储异常 fruit[0] = new Fruit(); } catch(Exception e) { System.out.println(e); } try { // 编译通过,但运行时会抛出存储异常 fruit[0] = new Orange(); } catch(Exception e) { System.out.println(e); } } }
泛型向上转型
class Fruit {}class Apple extends Fruit {}class Jonathan extends Apple {}class Orange extends Fruit {}public class NonCovariantGenerics{ public static void main(String[] args) { // 编译错误 List<Fruit> flist = new ArrayList<Apple>(); //编译虽然通过了,但会丢失掉向其中传递任何对象能力 List<? extends Fruit> list=new ArrayList<Apple>(); //编译报错 list.add(Object); }}
自定义的泛型
class Fruit {}class Apple extends Fruit {}class Jonathan extends Apple {}class Orange extends Fruit {}public class Holder<T> { private T value; public Holder() {} public Holder(T val) { value = val; } public void set(T val) { value = val; } public T get() { return value; } public boolean equals(Object obj) { return value.equals(obj); } public static void main(String[] args) { Holder<Apple> Apple = new Holder<Apple>(new Apple()); Apple d = Apple.get(); Apple.set(d); //异常 //!Holder<Fruit> Fruit = Apple; Holder<? extends Fruit> fruit = Apple; Fruit p = fruit.get(); d = (Apple)fruit.get(); try { //编译通过,但会抛出类转换异常 Orange c = (Orange)fruit.get(); } catch(Exception e) { System.out.println(e); } //向上转型后,失去了向其中传递任何对象的能力,故不能调用 fruit.set(new Apple()); fruit.set(new Fruit()); //因为其参数是Object类,故不受泛型影响 System.out.println(fruit.equals(d)); } }
2.超类型通配符,其边界就放松了在可以向方法传递的参数上所作的限制。
代码例子:
public class Test1<T>{ public static void main(String[] args) { List<? super Number> list=new ArrayList<Number>(); //编译通过 list.add(new Integer(2)); list.add(new Double(1.5)); }}
3.泛型用法绩点区别:
- 如果向接受”确切“泛型类型(没有通配符)的方法传递一个原生Holder引用,就会得到一个警告,因为确切的参数期望得到在原生类型中并不存在的信息,如果先接受一个无界通配符引用的方法传递一个原生Holder引用就不会发生警告,但不会有任何可以确定返回类型的类型信息,
- 对于有边界的通配符来说,如果想要从泛型参数中返回类型确定的返回值,可以用”? extends type”的泛型参数方法,但如果想要向泛型参数传递类型确定的参数,就是可以用”? super type”的泛型参数方法。
- 对于原生参数的方法来说,向其原生的方法传入对象仅会产生警告,而对于使用通配符的方法来说,会丢掉向其参数的方法传入对象的能力。
代码例子:
//泛型类public class Holder<T> { private T value; public Holder() {} public Holder(T val) { value = val; } public void set(T val) { value = val; } public T get() { return value; } public boolean equals(Object obj) { return value.equals(obj); } }public class Wildcards { //原生参数 static void rawArgs(Holder holder, Object arg) { //发生警告 holder.set(arg); //发生同样的警告 holder.set(new Wildcards()); // 编译错误 T t = holder.get(); // 编译通过 Object obj = holder.get(); } // 用通配符替代原生 static void unboundedArg(Holder<?> holder, Object arg) { //编译错误 holder.set(arg); //同样报错 holder.set(new Wildcards()); // 编译错误 T t = holder.get(); //编译通过 Object obj = holder.get(); } //确切泛型参数 static <T> T exact1(Holder<T> holder) { T t = holder.get(); return t; } //确切泛型参数,不过受限于后面的参数限制 static <T> T exact2(Holder<T> holder, T arg) { holder.set(arg); T t = holder.get(); return t; } //带有上边界的通配符 static <T> T wildSubtype(Holder<? extends T> holder, T arg) { //编译错误 holder.set(arg); //编译通过 T t = holder.get(); return t; } //带有下界的泛型参数 static <T> void wildSupertype(Holder<? super T> holder, T arg) { holder.set(arg); //编译错误 T t = holder.get(); //编译通过 Object obj = holder.get(); } public static void main(String[] args) { Holder raw = new Holder<Long>(); //原生 raw = new Holder(); //具体泛型的引用实参 Holder<Long> qualified = new Holder<Long>(); //通配符实参 Holder<?> unbounded = new Holder<Long>(); //带有上界的实参 Holder<? extends Long> bounded = new Holder<Long>(); Long lng = 1L; rawArgs(raw, lng); rawArgs(qualified, lng); rawArgs(unbounded, lng); rawArgs(bounded, lng); unboundedArg(raw, lng); unboundedArg(qualified, lng); unboundedArg(unbounded, lng); unboundedArg(bounded, lng); //发生警告 Object r1 = exact1(raw); Long r2 = exact1(qualified); //必须返回Object,否则会编译错误 Object r3 = exact1(unbounded); Long r4 = exact1(bounded); //发生警告 Long r5 = exact2(raw, lng); Long r6 = exact2(qualified, lng); //编译错误 Long r7 = exact2(unbounded, lng); //编译错误 Long r8 = exact2(bounded, lng); //编译发生警告 Long r9 = wildSubtype(raw, lng); //编译通过 Long r10 = wildSubtype(qualified, lng); // 编译错误 Object r11 = wildSubtype(unbounded, lng); //编译通过 Long r12 = wildSubtype(bounded, lng); //发生警告 wildSupertype(raw, lng); wildSupertype(qualified, lng); //编译错误 wildSupertype(unbounded, lng); //编译错误 wildSupertype(bounded, lng); } }
4.捕获转换:如果向一个使用
public class CaptureConversion { static <T> void f1(Holder<T> holder) { T t = holder.get(); System.out.println(t.getClass().getSimpleName()); } static void f2(Holder<?> holder) { //调用这个时,会捕捉具体类型参数 f1(holder); } @SuppressWarnings("unchecked") public static void main(String[] args) { Holder raw = new Holder<Integer>(1); //编译会发生警告 f1(raw); //无编译警告 f2(raw); Holder rawBasic = new Holder(); //编译会发生警告 rawBasic.set(new Object()); //无编译警告 f2(rawBasic); Holder<?> wildcarded = new Holder<Double>(1.0); f2(wildcarded); } }
问题
1.一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口,(但是如果一个类实现两个的同种原生,编译却可以通过)。
代码例子:
//第一种情况interface Payable<T> {}class Employee implements Payable<Employee> {}//编译报错class Hourly extends Employee implements Payable<Hourly> {} //第二种情况interface Payable<T> {}class Employee implements Payable {}//编译通过class Hourly extends Employee implements Payable {}
自限定的类型
1.自限定类型泛型(
//自限定泛型类class SelfBounded<T extends SelfBounded<T>> { T element; SelfBounded<T> set(T arg) { element = arg; return this; } T get() { return element; } } class A extends SelfBounded<A> {} //编译也通过 class B extends SelfBounded<A> {} class C extends SelfBounded<C> { C setAndGet(C arg) { set(arg); return get(); } } class D extends SelfBounded{} //编译错误 class E extends SelfBounded<D> {} //发生警告 class F extends SelfBounded {} public class SelfBounding { //自限定泛型方法 static <T extends SelfBounded<T>> T f(T arg){return arg.set(arg).get();} public static void main(String[] args) { A a = new A(); a.set(new A()); a = a.set(new A()).get(); a = a.get(); C c = new C(); c = c.setAndGet(new C()); } }
2.参数协变:方法参数类型会随子类而变化,要做到这一步,可以使用自限定泛型类来实现。
代码例子:
//非泛型类class OrdinarySetter { void set(Base base) { System.out.println("OrdinarySetter.set(Base)"); }}class DerivedSetter extends OrdinarySetter { void set(Derived derived) { System.out.println("DerivedSetter.set(Derived)"); }} public class OrdinaryArguments { public static void main(String[] args) { Base base = new Base(); Derived derived = new Derived(); DerivedSetter ds = new DerivedSetter(); ds.set(derived); //因其方法重载了,故编译能通过 ds.set(base); }}//自限定实现参数协变interface SelfBoundSetter<T extends SelfBoundSetter<T>> { void set(T arg);}interface Setter extends SelfBoundSetter<Setter> {}public class SelfBoundingAndCovariantArguments { void testA(Setter s1, Setter s2, SelfBoundSetter sbs) { s1.set(s2); //编译报错 // s1.set(sbs); }}
异常
1.由于擦除的原因,将泛型应用于异常是非常受限的,catch语句不能捕获泛型类型的异常,因为在编译器和运行时都必须知道异常的确切类型,泛型类也不能直接或间接继承自Throwable,但可以编写随检查型异常的类型而发生变化的泛型代码。
代码例子:
interface Processor<T,E extends Exception> { void process(List<T> resultCollector) throws E;}class ProcessRunner<T,E extends Exception>extends ArrayList<Processor<T,E>> { List<T> processAll() throws E { //根据T参数收集结果 List<T> resultCollector = new ArrayList<T>(); //遍历容器里面的对象 for(Processor<T,E> processor : this) processor.process(resultCollector); return resultCollector; }} class Failure1 extends Exception {}class Processor1 implements Processor<String,Failure1> { static int count = 3; public void process(List<String> resultCollector) throws Failure1 { if(count-- > 1) resultCollector.add("Hep!"); else resultCollector.add("Ho!"); if(count < 0) throw new Failure1(); }} class Failure2 extends Exception {}class Processor2 implements Processor<Integer,Failure2> { static int count = 2; public void process(List<Integer> resultCollector) throws Failure2 { if(count-- == 0) resultCollector.add(47); else { resultCollector.add(11); } if(count < 0) throw new Failure2(); }} public class ThrowGenericException { public static void main(String[] args) { ProcessRunner<String,Failure1> runner = new ProcessRunner<String,Failure1>(); for(int i = 0; i < 3; i++) runner.add(new Processor1()); try { System.out.println(runner.processAll()); } catch(Failure1 e) { System.out.println(e); } ProcessRunner<Integer,Failure2> runner2 = new ProcessRunner<Integer,Failure2>(); for(int i = 0; i < 3; i++) runner2.add(new Processor2()); try { System.out.println(runner2.processAll()); } catch(Failure2 e) { System.out.println(e); } }}
混型
1.混型其中一个好处是它们将特性和行为一致地应用于多个类之上,如果想在混型类中修改某些东西,作为一种意外的好处,这些修改将会应用于混型所应用的所有类型之上(相对于C++可以用泛型来轻易实现,Java使用较复杂的接口编程来实现)。
代码例子:
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 getSerialNumber(); }class SerialNumberedImp implements SerialNumbered { private static long counter = 1; private final long serialNumber = counter++; public 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) { value = val; } public String get() { return value; }}class Mixin extends BasicImpimplements 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 Mixins { public static void main(String[] args) { Mixin mixin1 = new Mixin(), mixin2 = new Mixin(); mixin1.set("test string 1"); mixin2.set("test string 2"); System.out.println(mixin1.get() + " " + mixin1.getStamp() + " " + mixin1.getSerialNumber()); System.out.println(mixin2.get() + " " + mixin2.getStamp() + " " + mixin2.getSerialNumber()); }}
2.混型和装饰器模式存在一定的区别,对于装饰器模式来说,方法调用会受到一定的局限,尽管其可以添加多个层,但是最后一层才是实际的类型,也就是说只有最后一层的方法是被暴露的,而混型的类型是可以应用所有被混合类型的方法,其方法调用显然会更宽泛。
- 再看泛型
- 再看泛型
- 再看泛型
- 有空再看
- 再看review
- 再看PPTP
- 再看BPEL
- 再看红楼
- 再看西游记
- 再看BPEL
- 再看BPEL
- 再看BPEL
- 再看BPEL
- 再看BPEL
- 再看BPEL
- 再看BPEL
- 再看BPEL
- 再看BPEL
- IOS获取键盘高度
- IO多路复用之epoll总结
- 实习感受。
- 天声人語 20150306
- 经常使用的Linux命令总结
- 再看泛型
- $.读书笔记 #程序员修炼之道# %注重实效的偏执%
- arcgis api for javascript 访问时弹出登录界面
- 把ppt幻灯片格式的转成pdf格式
- 插入时间的问题 date
- void及void指针含义的深刻解析
- 深入理解IOPS(本章主要讲基于传统机械磁盘-HDDs的存储系统)
- 【亚马逊AWS】入门级别实践
- Mocks Aren't Stubs