动态类型安全

  • 因为可以向JavaSE5之前的代码传递泛型容器,所以旧式代码仍旧有可能会破坏你的容器,JavaSE5的 java.util.Collections 中有一组便利工具,可以解决在这种情况下的类型检查问题,它们是:
  • 静态方法 checkedCollection() , checkedList() , checkedMap() , checkedSet() , checkedSortedMap() 和 checkedSortedSet()。
  • 这些方法每一个都会将你希望动态检查的容器当做第一个参数接受,并将你希望强制要求的类型作为第二个参数接受。
  • 受检查的容器在你试图插入类型不正确的对象时抛出 ClassCastException ,这与泛型之前的(原生)容器形成了对比,对于后者来说,当你将对象从容器中取出时,才会通知你出现了问题。在后一种情况中.你知道存在问题,但是不知道罪魁祸首在哪里,如果使用受检查的容器,就可以发现谁在试图插入不良对象。
class Cat {
}class Dog {
}class Pet{}public class CheckedList {static void oldStyleMethod(List list) {list.add(new Cat());}public static void main(String[] args) {List<Dog> dogs = new ArrayList<>();oldStyleMethod(dogs);//悄悄地接受一只catList<Dog> dogList = Collections.checkedList(new ArrayList<Dog>(), Dog.class);try {oldStyleMethod(dogList);//引发异常} catch (Exception e) {System.err.println(e);}// 派生类型工作正常List<Pet> pets = Collections.checkedList(new ArrayList<Pet>(), Pet.class);}
}
//运行结果为
java.lang.ClassCastException: Attempt to insert class generic.Cat element into collection with element type class generic.Dog
  1. 运行这个程序时,你就会发现插入一个 Cat对于 dogs来说没有任何问题 , 而 doglist立即会在这个错误类型的插入操作上炮出一个异常。
  2. 还可以看到,将导出类型的对象放置到将要检查基类型的受检查容器是没有问题的。

异常

  • 由于擦除的原因,将泛型应用于异常是非常受限的。 catch语句不能捕获泛型类型的异常,因为在编译器和运行时都必须知道异常的确切类型。
  • 泛型类也不能直接或间接继承自 Throwable(这将进一步阻止你去定义不能捕获的泛型异常)。
public interface ProcessorTest<T, E extends Exception> {void process(List<T> list) throws E;
}class ProicessRunner<T, E extends Exception> extends ArrayList<ProcessorTest<T, E>> {List<T> processAll() throws E {List<T> list = new ArrayList<>();for (ProcessorTest<T, E> processor : this) {processor.process(list);}return list;}
}class Failurel extends Exception {
}class Processor1 implements ProcessorTest<String, Failurel> {static int count = 3;@Overridepublic void process(List<String> list) throws Failurel {if (count-- >1){list.add("hep!");}else {list.add("ho!");}if (count<0){throw new Failurel();}}
}class Failure2 extends Exception{}class Processor2 implements ProcessorTest<Integer,Failure2>{static int count = 2;@Overridepublic void process(List<Integer> list) throws Failure2 {if (count-- >1){list.add(47);}else {list.add(11);}if (count<0){throw new Failure2();}}
}class ThrowGenericException{public static void main(String[] args) {ProicessRunner<String,Failurel> processors=new ProicessRunner<>();for (int i = 0; i < 3; i++) {processors.add(new Processor1());}try {System.out.println(processors.processAll());} catch (Failurel failurel) {System.err.println(failurel);}ProicessRunner<Integer,Failure2> processors1=new ProicessRunner<>();for (int i = 0; i < 3; i++) {processors1.add(new Processor2());}try {System.out.println(processors1.processAll());} catch (Failure2 failure2) {System.out.println(failure2);}}
}
//运行结果为
[hep!, hep!, ho!]
generic.Failure2
  1. ProcessorTest  执行 process() ,并且可能会抛出具有类型E 的异常。
  2. process() 的结果存储在 List<T>  list中(这被称为 收集参数)。
  3. ProcessRunner 有一个 processAll() 方法,它将执行所有的每个 Process 对象,并返回 list结果。
  4. 如果不能参数化所抛出的异常,那么由于检查异常的缘故,将不能编写出这种泛化的代码。

混型

  • 术语混型随时间的推移好像拥有了无数的含义,但是其最基本的概念是混合多个类的能力, 以产生一个可以表示混型中所有类型的类。这往往是你最后的手段,它将使组装多个类变得简单易行。
  • 混型的价值之一是它们可以将特性和行为一致地应用于多个类之上。如果想在混型类中修改某些东西,作为一种意外的好处,这些修改将会应用于混型所应用的所有类型之上。正由于此,混型有一点面向方面编程(AOP) 的味道,而方面经常被建议用来解决混型问题。

与接口混合

  • 一种更常见的推荐解决方案是使用接口来产生混型效果如下。
public interface TimeStamped {long getStamp();
}class TimeStampedImp implements TimeStamped {private final long timeStamp;public TimeStampedImp(long timeStamp) {this.timeStamp = timeStamp;}@Overridepublic long getStamp() {return timeStamp;}
}interface SerialNumbered {long getSerialNumber();
}class SerialNumberedImpl implements SerialNumbered {private static long counter = 1;private final long serialNumber = counter++;@Overridepublic long getSerialNumber() {return serialNumber;}
}interface Basic{void set(String str);String get();
}class BasicImpl implements Basic{private String string;@Overridepublic void set(String str) {this.string=str;}@Overridepublic String get() {return string;}
}class Maxin extends BasicImpl implements TimeStamped,SerialNumbered{private TimeStamped timeStamped=new TimeStampedImp(System.currentTimeMillis());private SerialNumbered serialNumbered=new SerialNumberedImpl();@Overridepublic long getStamp() {return timeStamped.getStamp();}@Overridepublic long getSerialNumber() {return serialNumbered.getSerialNumber();}
}class Minxins{public static void main(String[] args) {Maxin maxin1=new Maxin();Maxin maxin2=new Maxin();maxin1.set("test string 1");maxin2.set("test string 2");System.out.println(maxin1.get() +" "+maxin1.getStamp()+" "+maxin1.getSerialNumber());System.out.println(maxin2.get() +" "+maxin2.getStamp()+" "+maxin2.getSerialNumber());}
}//运行结果为test string 1 1594345540399 1
test string 2 1594345540399 2
  1. Maxin 类基本上是在使用代理,因此每个混入类型都要求在 Maxin 中有一个相应的域,而你必须在 Maxin 中编写所有必须的方法,将方法调用转发给恰当的对象。
  2. 这个示例使用了非常简单的类,但是当使用更复杂的混型时,代码数量会急速增加。

使用装饰器模式

  • 当你观察混型的使用方式时,就会发现混型概念好像与装饰器设计模式关系很近。装饰器经常用于满足各种可能的组合,而直接子类化会产生过多的类,因此是不实际的。
  • 装饰器模式使用分层对象来动态透明地向单个对象中添加责任。装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。
  • 某些事物是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。这使得对装饰器使用是透明的___无论对象是否被装饰,你拥有一个可以向对象发送的公共消息集。装饰器类也可以添加新方法,但是正如你所见,这将是受限的。
  • 装饰器是通过使用组合和形式化结构(可装饰物.装饰器层次结构)来实现的,而混型时基于继承的。因此可以将基于参数化类型的混型当作是一种泛型装饰器机制,这种机制不需要装饰器设计模式的继承结构。
public class BasicExamples {private String string;public String getString() {return string;}public void setString(String string) {this.string = string;}
}class Decorator extends BasicExamples {private BasicExamples basicExamples;public Decorator(BasicExamples basicExamples) {this.basicExamples = basicExamples;}@Overridepublic String getString() {return basicExamples.getString();}@Overridepublic void setString(String string) {basicExamples.setString(string);}
}class TimeStampedExamples extends Decorator {private final long timeStamp;public TimeStampedExamples(BasicExamples basicExamples) {super(basicExamples);this.timeStamp = System.currentTimeMillis();}long getTimeStamp() {return timeStamp;}
}class SerialNumberExamples extends Decorator {private static long counter = 1;private final long serialNumber = counter++;public SerialNumberExamples(BasicExamples basicExamples) {super(basicExamples);}long getSerialNumber(){return serialNumber;}
}class Decoration{public static void main(String[] args) {TimeStampedExamples timeStampedExamples=new TimeStampedExamples(new BasicExamples());TimeStampedExamples timeStampedExamples1=new TimeStampedExamples(new SerialNumberExamples(new BasicExamples()));//timeStampedExamples1.getSerialNumber();  无法使用SerialNumberExamples serialNumberExamples=new SerialNumberExamples(new BasicExamples());SerialNumberExamples serialNumberExamples1=new SerialNumberExamples(new TimeStampedExamples(new BasicExamples()));//serialNumberExamples1.getTimeStamp(); 无法使用}
}
  1. 产生自泛型的类包含所有感兴趣的方法,但是由于使用装饰器所产生的对象类型是最后被装饰的类型。
  2. 也就是说,尽管可以添加多个层,但是最后一层才是实际的类型,因此只有最后一层的方法是可视的,而混型的类型是所有被混合到一起的类型。
  3. 因此对于装饰器来说,其明显的缺陷是它只能有效地工作于装饰中的一层(最后一层),而混型方法显然会更自然一些。因此,装饰器只是对由混型提出的问题的一种局限的解决方案。

与动态代理混合

  • 可以使用动态代理来创建一种比装饰器更贴近混型的机制。通过使用动态代理,所产生的类的动态类型将会使已经混入的组合类型。
  • 由于动态代理的限制,每个被混入的类都必须是某个接口的实现。
  • 因为只有动态类型而不是非静态类型才包含所有的混入类型,因此这仍旧不如C++的方式好,因为可以在具有这些类型的对象上调用方法之前,你被强制要求必须先将这些对象向下转型到恰当的类型。但是,它明显地更接近于真正的混型。
  • 为了让Java支持混型,人们已经做了大量的工作朝着这个目标努力,包括了创建了至少一种附加语言(jam语言),它是专门用来支持混型的。

潜在类型机制

  • 在本章的开头介绍过这样的思想,即要编写能够尽可能广泛地应用多的代码。为了实现这一点,我们需要各种途径来放松对我们的代码将要作用的类型所作的限制,同时不丢失静态类型检查的好处。然后,我们就可以编写出我无需修改就可以应用于更多情况的代码,即更加泛化的代码。
  • Java泛型看起来是向这一方向迈进了一步。当你在编写或使用只是持有对象的泛型时,这些代码将可以工作于任何类型(除了基本类型,尽管正如你所见到的,自动包装机制可以克服这一点)。
  • 或者,换个角度讲,持有器泛型能够声明 我不关心你是什么类型。如果代码不关心它将要作用的类型,那么这种代码就可以真正地应用于任何地方,并因此而相当泛化。
  • 还是正如你所见到的,当要在泛型类型上执行操作(即调用Object方法之前的操作)时,就会产生问题,因为擦除要求指定可能会用到的泛型类型的边界,以安全地调用代码中的泛型对象上的具体方法。
  • 这是对 泛化 概念的一种明显的限制,因为必须限制你的泛型类型,使它们继承自特定的类,或者实现特定的接口,在某些情况下,你最终可能会使用普通类或普通接口,因为限定边界的泛型可能会和指定类或接口没有任何区别。
  • 某些编程语言提供的一种解决方案称为 潜在类型机制 或 结构化类型机制,而更古怪的术语被称为 鸭子类型机制, 即 如果它走起来像鸭子,并且叫起来也像鸭子,那么你就可以将它当作鸭子对待。 鸭子类型机制变成了一种相当流行的术语,可能是因为它不像其他术语那样承载着历史的包袱。
  • 泛型代码典型地将在泛型类型上调用少量方法,而具有潜在类型机制的语言只要求实现某个方法子集,而不是某个特定类或接口,从而放松了这种限制(并且可以产生更加泛化的代码)。
  • 正由于此,潜在类型机制使得你可以横跨类继承结构,调用不属于某个公共接口的方法。因此,实际上一段代码可以声明: 我不关心你是什么类型,只要你可以 speak() 和 sit() 即可。由于不要求具体类型,因此代码就可以更加泛化。
  • 潜在类型机制是一种代码组织和复用机制。有了它编写出的代码相对于没有它编写出的代码,能够更容易地复用。代码组织和复用是所有计算机编程的基本手段: 编写一次,多次使用,并在一个位置保存代码。因为我并未被要求去命名我的代码要操作于其上的确切接口,所以,有了潜在类型机制,我们就可以编写更少的代码,并更容易的将其应用于多个地方。
public interface Performs {void speak();void sit();
}class PerformsDog implements Performs{@Overridepublic void speak() {System.out.println("Woof!");}@Overridepublic void sit() {System.out.println("Sitting");}
}class Robot implements Performs{@Overridepublic void speak() {System.out.println("Click");}@Overridepublic void sit() {System.out.println("Clank");}
}class Communicate{static <T extends Performs> void performs(T t){t.sit();t.speak();}
}class DogsAndRobots{public static void main(String[] args) {Robot robot = new Robot();PerformsDog performsDog=new PerformsDog();Communicate.performs(performsDog);Communicate.performs(robot);}
}//运行结果为
Sitting
Woof!
Clank
Click
  1. 但是要注意,perform() 不需要使用泛型来工作,它可以被简单地指定为接受一个 Performs 对象。
class ComunicateSimply{static void perform(Performs performs){performs.speak();performs.sit();}public static void main(String[] args) {perform(new Robot());perform(new PerformsDog());}
}
//运行结果为
Click
Clank
Woof!
Sitting
  1. 在本例中,泛型不是必须的,因为这些类已经被强制要求实现 Performs 接口。

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

  • 尽管Java不支持潜在类型机制,但是这并不意味着有界泛型代码不能在不同的层次结构之间应用。
  • 也就是说,我们仍旧可以创建真正的泛型代码,但是这需要付出一些额外的努力。

反射

  1. 可以使用的一种方式是反射,如下。
public class Mime {public void walkAgainstTheWind() {}public void sit() {System.out.println("Mime sit()");}public void pushInvisibleWalls() {}@Overridepublic String toString() {return "Mime";}
}class SmartDog {public void speak() {System.out.println("Woof!");}public void sit() {System.out.println("Sitting");}public void reproduce() {}
}class CommunicateReflectively {static void performs(Object object) {Class<?> aClass = object.getClass();try {//通过反射获取 speak方法try {Method speak = aClass.getMethod("speak");speak.invoke(object);} catch (NoSuchMethodException e) {System.err.println(object + "没有 speak 方法");}//通过反射获取 sit方法try {Method sit = aClass.getMethod("sit");sit.invoke(object);} catch (NoSuchMethodException e) {System.err.println(object + "没有 sit 方法");}} catch (Exception e) {throw new RuntimeException(object.toString(), e);}}
}class LatentReflection{public static void main(String[] args) {CommunicateReflectively.performs(new SmartDog());CommunicateReflectively.performs(new Robot());CommunicateReflectively.performs(new Mime());}
}//运行结果为Woof!
Sitting
Click
Clank
Mime没有 speak 方法
Mime sit()
  1. 上例中,这些类完全是彼此分离的,没有任何公共基类(除了Object)或接口。通过反射, CommunicateReflectively.performs() 能够动态地确定所需要的方法是否可用并调用它们。它甚至能够处理 Mime只具有一个必须的方法这一事实,并能够部分实现其目标。

将一个方法应用于序列

  1. 反射提供了一些有趣的可能性,但是它将所有的类型检查都转移到了运行时,因此在许多情况下并不是我们所希望的。
  2. 如果能够实现编译期类型检查,这通常会更符合要求。但是又可能实现编译期类型检查和潜在类型机制吗?
  3. 让我们来看一个说明这个问题的示例。假设想要创建一个 apply() 方法,它能够将任何方法应用于某个序列中的所有对象。这是接口看起来并不适合的情况,因为你想要将任何方法应用于一个对象集合,而接口对于描述 任何方法存在过多的限制。
  4. 如何使用Java来实现这种需求呢?
  5. 最初,我们可以使用反射来解决这个问题,由于有了JavaSE5 的可变参数,这种方式被证明是相当优雅的。
public class Apply {static <T, S extends Iterable<? extends T>> void apply(S s, Method method, Object... args) {try {for (T t : s) {method.invoke(t, args);}} catch (Exception e) {throw new RuntimeException(e);}}
}class Shape {public void rotate() {System.out.println(this + "rotate");}public void resize(int newSize) {System.out.println(this + "resize" + newSize);}
}class Square extends Shape{}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 ApplyTest{public static void main(String[] args) throws NoSuchMethodException {List<Shape> shapes=new ArrayList<>();for (int i = 0; i < 10; i++) {shapes.add(new Shape());}    // 需要注意的通过反射获取方法必须是publicApply.apply(shapes,Shape.class.getMethod("rotate"));Apply.apply(shapes,Shape.class.getMethod("resize",int.class),5);List<Square> squares=new ArrayList<>();for (int i = 0; i < 10; i++) {squares.add(new Square());}Apply.apply(squares,Square.class.getMethod("rotate"));Apply.apply(squares,Square.class.getMethod("resize",int.class),5);FilledList<Shape> filledList=new FilledList<>(Shape.class,10);Apply.apply(filledList,Shape.class.getMethod("rotate"));FilledList<Square> squares1=new FilledList<>(Square.class,10);Apply.apply(squares1,Square.class.getMethod("rotate"));SimpleQueue<Shape> simpleQueue=new SimpleQueue();for (int i = 0; i < 5; i++) {simpleQueue.add(new Shape());simpleQueue.add(new Square());}Apply.apply(simpleQueue,Shape.class.getMethod("rotate"));}
}class SimpleQueue<T> implements Iterable<T>{private LinkedList<T> storage=new LinkedList<>();void add(T t){storage.add(t);}T get(){return storage.poll();}@Overridepublic Iterator<T> iterator() {return storage.iterator();}}
  1. 在Apply 中,我们运气很好,因为碰巧在Java中内建了一个由Java容器类库使用的 Iterable接口。
  2. 正由于此, apply() 方法可以接口任何实现了 Iterable 接口的事物,包括诸如 List 这样的所有 Collection 类。但是 它还可以接受其他任何事物,只要能够使这些事物是 Iterable 的。
  3. 在 Apply.java 中,异常被转换为 RuntimeException ,因为没有多少办法可以从这种异常中恢复___在这种情况下,它们实际上代表着程序员的错误。
  4. 注意,我们必须放置边界和通配符,以便使 Apply 和 FilledList 在所有需要的情况下都可以使用。可以试验一下,将这些边界和通配符拿出来,你就会发现某些Apply 和 FilledList 应用将无法工作。
  5. FilledList 表示有点进退两难的情况。为了使某种类型可用,它必须有默认(无参)构造器,但是 Java没有任何方式可以在编译期断言这种情况,因此这就变成了一个运行时问题。确保编译期检查的常见建议是定义一个工厂接口,它有一个可以生成对象的方法,然后 FilledList将接受这个接口而不是这个类型标记的原生工厂,而这样做的问题是在 FilledList 中使用的所有类都必须实现这个工厂接口。
  6. 唉,大多数的类都是在不了解你的接口的情况下创建的,因此也就没有实现这个接口。稍后我将展示一种使用适配器的解决方案。
  7. 但是上面所展示的使用类型标记的方法可能是一种合理的折中(至少是一种马上就能想到解决方案)。通过这种方式,使用像 FilledList 这样的东西就会非常容易,我们会马上想到要使用它而不是会忽略它。当然, 因为错误是在运行时报告的, 所以你要有把握,这些错误将在开发过程的早期出现。
  8. 注意,类型标记技术是Java文献推荐的技术,例如 Gilad Bracha 在他的论文<<Generics in Java Programming Language>> 中写道 这是一种惯用法,例如,在操作注解的新 API 中得到了广泛的应用。但是,我发现人们对这种技术的适应程度不一,有些人强烈地首选本章前面描述的工厂方式。
  9. 尽管Java 解决方案被证明很优雅,但是我们必须知道使用反射(尽管反射在最近版本的Java中已经明显地改善了)可能比非反射要慢一些,因为有太多的动作都是在运行时发生的。这不应该组织你使用这种解决方案的脚步,至少可以将其作为一种马上就能想到的解决方案(以防止陷入不成熟的优化中),但这毫无疑问是这俩种方法之间的一个差异。

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

  • 上面的示例是受益的,因为Iterable 接口已经是内建的,而它正是我们需要的。但是更一般的情况又会怎样呢?如果不存在刚好适合你的需求的接口呢?
public class Fill {static <T> void fill(Collection<T> collection, Class<? extends T> classToken, int size) {for (int i = 0; i < size; i++) {try {collection.add(classToken.newInstance());} catch (Exception e) {throw new RuntimeException(e);}}}
}class Contract {static long counter = 0;final long id =counter++;@Overridepublic String toString() {return getClass().getName()+" "+id;}
}class TitleTransfer extends Contract{public static void main(String[] args) {List<Contract> list=new ArrayList<>();Fill.fill(list,Contract.class,3);Fill.fill(list,TitleTransfer.class,2);for (Contract contract:list) {System.out.println(contract);}}
}//运行结果为
generic.Contract 0
generic.Contract 1
generic.Contract 2
generic.TitleTransfer 3
generic.TitleTransfer 4
  1. 这正是具有潜在类型机制的参数化类型机制的价值所在,因为你不受任何特定类库的创建者过去所作的设计决策的支配,因此不需要在每次碰到一个没有考虑到你具体情况的新类库时,都去重写代码(因此这样的代码才真正泛化的)。
  2. 在上面的情况中,因为Java设计者(可以理解地)没有预到对 Addable 接口的需要,所以我们被限制在 Collection 继承层次结构之内,即便 SimpleQueue 有一个add() 方法,它也不能工作。因为这会将代码限制为只能工作于 Collection ,因此这样的代码不是特别泛化。有了潜在类型机制,情况就会不同了。

用适配器仿真潜在类型机制

  • Java泛型并不没有潜在类型机制,而我们需要像潜在类型机制这样的东西去编写能够跨类边界应用的代码(也就是泛化代码)。存在某种方式可以绕过这项限制吗?
  • 潜在类型机制将在这里实现什么? 它意味着你可以编写代码声明: 我不关心我在这里使用的类型,只要它具有这些方法即可。实际上,潜在类型机制创建了一个包含所需方法的隐式接口。因此它遵循这样的规则: 如果我们手工编写了必须的接口(因为Java并没有为我们做这些事)那么它就应该能够解决问题。
  • 从我们拥有的接口中编写代码来产生我们需要的接口,这是适配器设计模式的一个典型示例。我们可以使用适配器来适配已有的接口,以产生想要的接口。如下
interface Addable<T> {void add(T t);
}public class Fill2 {public static <T> void fill(Addable<T> addable,Class<? extends T> classToken,int size){for (int i = 0; i < size; i++) {try {addable.add(classToken.newInstance());} catch (Exception e) {throw new RuntimeException(e);}}}//generator versionpublic static <T> void fill(Addable<T> addable,Generator<T> generator,int size){for (int i = 0; i < size; i++) {addable.add(generator.next());}}}
//要适应基本类型,您必须使用合成,使用组合使任何集合可添加class AddableCollectionAdapter<T> implements Addable<T>{private Collection<T> collection;public AddableCollectionAdapter(Collection<T> collection) {this.collection = collection;}@Overridepublic void add(T t) {collection.add(t);}
}class Adapter{public static <T> Addable<T> collectionAdapter(Collection<T> collection){return new AddableCollectionAdapter<T>(collection);}
}//要适应特定类型,可以使用继承,使用继承使simpleQueue可添加
class AddableSimpleQueue<T> extends SimpleQueue<T> implements Addable<T>{@Overridepublic void add(T t) {super.add(t);}
}class CoffeeDemo{}class Fill2Test{public static void main(String[] args) {List<Coffee> coffees=new ArrayList<>();Fill2.fill(new AddableCollectionAdapter<>(coffees),Coffee.class,3);//辅助方法捕获类型Fill2.fill(Adapter.collectionAdapter(coffees),Latte.class,3);for (Coffee coffee:coffees) {System.out.println(coffee);}System.out.println("--------------------");//使用改编的课程AddableSimpleQueue<Coffee> coffees1=new AddableSimpleQueue<>();Fill2.fill(coffees1,Mocha.class,4);Fill2.fill(coffees1,Latte.class,1);for (Coffee coffee:coffees1) {System.out.println(coffee);}}
}//运行结果为
Coffee 0
Coffee 1
Coffee 2
Latte 3
Latte 4
Latte 5
--------------------
Mocha 6
Mocha 7
Mocha 8
Mocha 9
Latte 10
  1. Fill2 对Collection 的要求与Fill不同,它只需要实现了 Addable 的对象,而 Addable 已经为 Fill编写了___它是我希望编译器帮我创建的潜在类型的一种体现。
  2. 在这个版本中,我还添加了一个重载的 fill(),它接受一个 Generator 而不是类型标记。 Generator 在编译期是类型安全的。编译器将确保传递的是正确的 Generator ,因此不会抛出任何异常。
  3. 第一个适配器, AddableCollectionAdapter ,可以工作于基类型 Collection,这意味着Collection 的任何实现都可以使用。这个版本直接存储 Collection 引用,并使用它来实现 add()。
  4. 如果有一个具体类型而不是继承结构的基类,那么当使用继承来创建适配器时,你可以稍微少编写一些代码,就像 AddableSimpleQueue 中看到的那样。
  5. 在 Fill2Test 的main 中,你可以看到各种不同类型的适配器在运行。首先,Collection类型是由 AddableCollectionAdapter 适配的。这个第二个版本使用了一个泛化的辅助方法,你可以看到这个泛化方法是如何捕获类型并因此而不必显式地写出来的___这是产生更优雅的代码的一种惯用技巧。
  6. 接下来,使用了预适配的 AddableSimpleQueue。注意,在这两种情况下,适配器都允许前面没有实现 Addable 的类用于 Fill2.fill()。
  7. 使用像这样的适配器看起来是对缺乏潜在类型机制的一种补偿,因此允许编写出真正的泛化代码。但是,这是一个额外的步骤,并且是类库的创建者和消费者都必须理解的事物,而缺乏经验的程序员可能还没有能掌握这个概念。潜在类型机制通过移除这个额外的步骤,使得泛化代码更容易应用,这就是它的价值。

将函数对象用作策略

  • 最后一个示例通过使用前面一节描述的适配器方式创建了真正泛化的代码。这个示例开始时是一种尝试,要创建一个元素序列的总和,这些元素可以是任何可以计算总和的类型,但是,后来这个示例使用的功能性编程风格,烟花成了可以执行通用的操作。
  • 如果只查看尝试添加对象的过程,就会看到这是在多个类中的公共操作,但是这个操作没有在任何我们可以指定的基类中表示___有时甚至可以使用 + 操作符,而其他时间可以使用某种 add 方法。
  • 这是在视图编写泛化代码的时候通常会碰到的情况,因为你想将这些代码应用于多个类上___特别是,在本例一样,作用域多个已经存在且我们不能修正的类上。即使你可以将这种情况窄化 Number 的子类,这个超类也不包括任何有关可添加性的东西。
  • 解决方案是使用 策略设计模式,这种设计模式可以产生更优雅的代码,因为它将变化的事物完全隔离到了一个函数对象中。函数对象就是在某种程序上行为像函数的对象___一般地,会有一个相关方法(在支持操作符重载的语言中,可以创建对这个方法的调用,而这个调用看起来就和普通方法调用一样)。
  • 函数对象的价值就在于,与普通方法不同,它们可以传递出去,并且还可以拥有在多个调用之间持久化的状态。当然,可以用类中的任何方法来实现与此相似的操作,但是(与使用任何设计模式一样)函数对象主要是由其目的来区别的。这里的目的就是要创建某种事物,使它的行为就像是一个可以传递出去的单个方法一样,这样,它就和策略设计模式紧耦合了,有时甚至无法区分。
  • 尽管可以发现我使用了大量的设计模式,但是在这里它们之间的界限是模糊的 我们再创建执行适配操作的函数对象,而它们将传递到用作策略的方法中。
  1. 接口部分
//接口部分
public interface Combiner <T>{T combine(T t,T y);
}interface UnaryFuncation<R,T>{R function(T t);
}interface Collector<T> extends UnaryFuncation<T,T>{//提取采集参数结果T result();
}interface UnaryPredicate<T>{boolean test(T x);
}

2,实体类部分

public class Functional {//在每个元素上调用合并器对象进行合并,运行结果最终返回public static <T> T reduce(Iterable<T> iterable, Combiner<T> combiner) {Iterator<T> iterator = iterable.iterator();if (iterator.hasNext()) {T result = iterator.next();while (iterator.hasNext()) {result = combiner.combine(result, iterator.next());}return result;}//如果iterable是空列表 或者 抛出异常return null;}//取一个函数对象并在列表中的每个对象上调用它,忽略返回值,函数 object可以充当收集参数,因此最后返回public static <T> Collector<T> forEach(Iterable<T> it,Collector<T> func){for (T t:it) {func.function(t);}return func;}//通过调用一个创建结果列表,列表中每个对象的功能对象public static <R,T> List<R> transform(Iterable<T> seq,UnaryFuncation<R,T> func){List<R> list=new ArrayList<>();for (T t:seq) {list.add(func.function(t));}return list;}//将一元谓词应用于序列中的每个项目,并返回产生真实值的项目列表public static <T> List<T> filter(Iterable<T> seq,UnaryPredicate<T> pred){List<T> result=new ArrayList<>();for (T t:seq) {if (pred.test(t)){result.add(t);}}return result;}//要使用上述通用方法,我们需要创建,功能对象以适应我们的特殊需求public static class IntegerAddr implements Combiner<Integer>{@Overridepublic Integer combine(Integer integer, Integer y) {return integer+y;}}public static class IntegerSubtracter implements Combiner<Integer>{@Overridepublic Integer combine(Integer integer, Integer y) {return integer-y;}}public static class BigDecimalAddr implements Combiner<BigDecimal>{@Overridepublic BigDecimal combine(BigDecimal bigDecimal, BigDecimal y) {return bigDecimal.add(y);}}public static class BigIntergerAdder implements Combiner<BigInteger>{@Overridepublic BigInteger combine(BigInteger bigInteger, BigInteger y) {return bigInteger.add(y);}}public static class AtomicLongAdder implements Combiner<AtomicLong>{@Overridepublic AtomicLong combine(AtomicLong atomicLong, AtomicLong y) {//不清楚这是否有意义return new AtomicLong(atomicLong.addAndGet(y.get()));}}//我们甚至可以用ulp制作UnaryFunction,最后单位public static class BigDecimalulp implements UnaryFuncation<BigDecimal,BigDecimal>{@Overridepublic BigDecimal function(BigDecimal bigDecimal) {return bigDecimal.ulp();}}public static class GreaterThan<T extends Comparable<T>> implements UnaryPredicate<T>{private T bound;public GreaterThan(T bound) {this.bound = bound;}@Overridepublic boolean test(T x) {return x.compareTo(bound) > 0;}}public static class MultiplyingIntegerCollector implements Collector<Integer>{private Integer val=1;@Overridepublic Integer function(Integer integer) {val *=val;return val;}@Overridepublic Integer result() {return val;}public static void main(String[] args) {//泛型varargs和boxing一起工作List<Integer> lists= Arrays.asList(1,2,3,4,5,6,7);Integer result=reduce(lists,new IntegerAddr());System.out.println(result);result=reduce(lists,new IntegerSubtracter());System.out.println(result);System.out.println(filter(lists,new GreaterThan<>(4)));System.out.println(forEach(lists,new MultiplyingIntegerCollector()).result());System.out.println(forEach(filter(lists,new GreaterThan<>(4)), new MultiplyingIntegerCollector()).result());MathContext mc =new MathContext(7);List<BigDecimal> lbd=Arrays.asList(new BigDecimal(1.1,mc),new BigDecimal(2.2,mc),new BigDecimal(3.3,mc),new BigDecimal(4.4,mc));BigDecimal rbd = reduce(lbd, new BigDecimalAddr());System.out.println(rbd);System.out.println(filter(lbd,new GreaterThan<>(new BigDecimal(3))));//使用bigInteger的原始生成功能List<BigInteger> lbi=new ArrayList<>();BigInteger bi=BigInteger.valueOf(11);for (int i = 0; i < 11; i++) {lbi.add(bi);bi=bi.nextProbablePrime();}System.out.println(lbi);List<AtomicLong> list=Arrays.asList(new AtomicLong(11),new AtomicLong(47),new AtomicLong(74),new AtomicLong(133));AtomicLong atomicLong = reduce(list, new AtomicLongAdder());System.out.println(atomicLong);System.out.println(transform(lbd,new BigDecimalulp()));}}
}

3运行结果为

28
-26
[5, 6, 7]
1
1
11.000000
[3.300000, 4.400000]
[11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
265
[0.000001, 0.000001, 0.000001, 0.000001]

4 总结

  1. 我们从为不同类型的函数对象定义接口开始的,这些接口都是按需创建的,因为我为每个接口都开发了不同的方法,并发现了每个接口的需求。
  2. Combiner 类是由意味不知名的作者在我的Web网站贴出的文章建议构建的。Combiner 抽象掉了将俩个对象添加在一起的具体细节,并且只是声明它们在某种程度上被结合在一起。因此,可以看到, IntegerAdder 和 IntegerSubstract 可以是 Combiner类型。
  3. UnaryFunction 接受单一的参数,并产生一个结果。这个参数和结果不需要是相同的类型。Collector 被用作 收集参数,并且当你完成时,可以从中抽取结果。 UnartPredicate 将产生一个 boolean 类型的结果。还可以创建其他类型的函数对象,但是这些已经足够说明问题了。
  4. Functional 类包含大量的泛型方法,它们可以将函数对象应用于序列。 reduce() 将 Combiner 中的函数应用于序列中的每个元素已产生单一的结果。
  5. foreach() 接受一个Collector ,并将其函数应用于每个元素,但同时会忽略每次函数调用的结果。这只能被称为是 副作用(这不是 功能型 编程风格,但仍旧是有用的),或者我们可以让 Collector 维护内部状态,从而变成了一个收集参数,就像在本例中看到的那样。
  6. transform() 通过在序列中的每个对象上调用 UnaryFunction ,并捕获调用结果,来产生一个列表。
  7. 最后 ,filter() 将 UnaryPredicate 应用到序列中的每个对象上,并将那些返回 true 的对象存储到一个list集合中。

Java编程思想__泛型(七完结)相关推荐

  1. java 编程思想笔记(七)——异常

    1.什么是异常 java中的异常指的是程序中的异常(不包括硬件异常(内存溢出等)),比如:语法错误(少写分号),除数为0,nullPoint等. 2.为啥需要异常 谁也不想代码在运行的时候,突然程序出 ...

  2. 《java编程思想》第七章 复用类

    组合:在新的类中产生现有类的对象.只是复用了现有程序代码的功能,而非形式. 继承:按照现有类的类型来创建新类,无需改变现有类的形式.采用现有类(基类)的形式,并在其中添加新代码. 都是利用现有类型生成 ...

  3. Java中的泛型 --- Java 编程思想

    前言 ​ 我一直都认为泛型是程序语言设计中一个非常基础,重要的概念,Java 中的泛型到底是怎么样的,为什么会有泛型,泛型怎么发展出来的.通透理解泛型是学好基础里面中非常重要的.于是,我对<Ja ...

  4. Java编程思想读书笔记(七)

    点击蓝字 关注我们 今天是端午节,在这里祝大家端午节安康.过节的同时也不要忘记知识储备,今天我 为大家带来了Java编程思想第七节多形性读书笔记.请大家一起品鉴,如果发现里面有啥写的不对的地方,请大家 ...

  5. Java编程思想 (1~10)

    [注:此博客旨在从<Java编程思想>这本书的目录结构上来检验自己的Java基础知识,只为笔记之用] 第一章 对象导论 1.万物皆对象 2.程序就是对象的集合 3.每个对象都是由其它对象所 ...

  6. 《JAVA编程思想》学习笔记:第8章(多态)

    目录 Java编程思想(一)第1~4章:概述 Java编程思想(二)第5章:初始化和清理 Java编程思想(三)第6章:访问权限 Java编程思想(四)第7章:复用类 Java编程思想(五)第8章:多 ...

  7. 《JAVA编程思想》学习笔记:第16章(数组)

    目录 Java编程思想(一)第1~4章:概述 Java编程思想(二)第5章:初始化和清理 Java编程思想(三)第6章:访问权限 Java编程思想(四)第7章:复用类 Java编程思想(五)第8章:多 ...

  8. 《JAVA编程思想》学习笔记:第1-4章(Java概述)

    全书目录 Java编程思想(一)第1~4章:概述 Java编程思想(二)第5章:初始化和清理 Java编程思想(三)第6章:访问权限 Java编程思想(四)第7章:复用类 Java编程思想(五)第8章 ...

  9. 《JAVA编程思想》学习笔记:第21章(并发)

    目录 Java编程思想(一)第1~4章:概述 Java编程思想(二)第5章:初始化和清理 Java编程思想(三)第6章:访问权限 Java编程思想(四)第7章:复用类 Java编程思想(五)第8章:多 ...

最新文章

  1. insightface scrfd人脸检测测试
  2. oracle索引中丢失in或out参数,oracle 11g给表建触发器错误“索引中丢失 IN 或 OUT 参数:: 1...
  3. boost::math模块两个 Lambert W 函数的最基本调用示例
  4. boost::geometry::split_rings用法的测试程序
  5. apache php5.3 配置_php-5.3+APACHE 安装配置
  6. selenium:使用已打开的chrome浏览器
  7. 阿里云 超级码力在线编程大赛初赛 第2场 题目3. 五字回文
  8. Intent在Activity间的传值
  9. 重新leetcode第1天——二叉树遍历算法讲解合集
  10. Android通过Wifi来调试你的应用
  11. 常见花材的固定的方法有哪些_固定无梁拱形屋顶的方法都有哪些呢?
  12. cad导出pdf_CAD手机看图软件中怎么将CAD图纸转为PDF/图片格式?
  13. O形橡胶密封圈设计标准
  14. 【解决方案】adb无法连接雷电模拟器问题
  15. 启动计算机加载状态监控器,状态监控器显示脱机。
  16. 显卡mx150和230哪个好_显卡mx250相当于gtx系哪个级别的 MX250相比MX150在核
  17. 三相直流电机-利用反电动势的过零点来测转子位置在讨论无转子位置
  18. WEB漏洞——SQL注入之简要SQL注入
  19. 将Qt QCheckBox 默认选中样式改为对号选中
  20. htk的使用Hcopy.exe的ERROR [+6311]和ERROR [+1014]报错解决

热门文章

  1. 人工智能AI未来趋势
  2. 在PYTHON中导入dat文件
  3. 国际投标文件进度计划P6输出内容及设置方法
  4. 依赖报错:While resolving: @vue/eslint-config-standard@6.1.oFound: eslint-plugin-vue@8.7.1
  5. 8分钟带你彻底弄懂《线性代数》
  6. 进一步理解Linux操作系统的块设备
  7. 吊打面试官!MySQL灵魂100问,你能答出多少?
  8. 零容忍遏制侵权乱象,多方共建金融消费者保护
  9. kubernetes系列之《构建企业级CICD平台(四)》
  10. 数据访问层(连接数据库)