企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## 对缺乏潜在类型机制的补偿 尽管 Java 不直接支持潜在类型机制,但是这并不意味着泛型代码不能在不同的类型层次结构之间应用。也就是说,我们仍旧可以创建真正的泛型代码,但是这需要付出一些额外的努力。 ### 反射 可以使用的一种方式是反射,下面的 `perform()` 方法就是用了潜在类型机制: ```java // generics/LatentReflection.java // Using reflection for latent typing import java.lang.reflect.*; // Does not implement Performs: class Mime { public void walkAgainstTheWind() {} public void sit() { System.out.println("Pretending to sit"); } public void pushInvisibleWalls() {} @Override public String toString() { return "Mime"; } } // Does not implement Performs: class SmartDog { public void speak() { System.out.println("Woof!"); } public void sit() { System.out.println("Sitting"); } 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) { System.out.println(speaker + " cannot speak"); } try { Method sit = spkr.getMethod("sit"); sit.invoke(speaker); } catch(NoSuchMethodException e) { System.out.println(speaker + " cannot sit"); } } catch(SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException 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()); } } /* Output: Woof! Sitting Click! Clank! Mime cannot speak Pretending to sit */ ``` 上例中,这些类完全是彼此分离的,没有任何公共基类(除了 **Object** )或接口。通过反射, `CommunicateReflectively.perform()` 能够动态地确定所需要的方法是否可用并调用它们。它甚至能够处理 **Mime** 只具有一个必需的方法这一事实,并能够部分实现其目标。 ### 将一个方法应用于序列 反射提供了一些有用的可能性,但是它将所有的类型检查都转移到了运行时,因此在许多情况下并不是我们所希望的。如果能够实现编译期类型检查,这通常会更符合要求。但是有可能实现编译期类型检查和潜在类型机制吗? 让我们看一个说明这个问题的示例。假设想要创建一个 `apply()` 方法,它能够将任何方法应用于某个序列中的所有对象。这种情况下使用接口不适合,因为你想要将任何方法应用于一个对象集合,而接口不可能描述任何方法。如何用 Java 来实现这个需求呢? 最初,我们可以用反射来解决这个问题,由于有了 Java 的可变参数,这种方式被证明是相当优雅的: ```java // generics/Apply.java import java.lang.reflect.*; import java.util.*; public class Apply { public static <T, S extends Iterable<T>> void apply(S seq, Method f, Object... args) { try { for(T t: seq) f.invoke(t, args); } catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { // Failures are programmer errors throw new RuntimeException(e); } } } ``` 在 **Apply.java** 中,异常被转换为 **RuntimeException** ,因为没有多少办法可以从这种异常中恢复——在这种情况下,它们实际上代表着程序员的错误。 为什么我们不只使用 Java 8 方法参考(稍后显示)而不是反射方法 **f** ? 注意,`invoke()` 和 `apply()` 的优点是它们可以接受任意数量的参数。 在某些情况下,灵活性可能至关重要。 为了测试 **Apply** ,我们首先创建一个 **Shape** 类: ```java // generics/Shape.java public class Shape { private static long counter = 0; private final long id = counter++; @Override public String toString() { return getClass().getSimpleName() + " " + id; } public void rotate() { System.out.println(this + " rotate"); } public void resize(int newSize) { System.out.println(this + " resize " + newSize); } } ``` 被一个子类 **Square** 继承: ```java // generics/Square.java public class Square extends Shape {} ``` 通过这些,我们可以测试 **Apply**: ```java // generics/ApplyTest.java import java.util.*; import java.util.function.*; import onjava.*; public class ApplyTest { public static void main(String[] args) throws Exception { List<Shape> shapes = Suppliers.create(ArrayList::new, Shape::new, 3); Apply.apply(shapes, Shape.class.getMethod("rotate")); Apply.apply(shapes, Shape.class.getMethod("resize", int.class), 7); List<Square> squares = Suppliers.create(ArrayList::new, Square::new, 3); Apply.apply(squares, Shape.class.getMethod("rotate")); Apply.apply(squares, Shape.class.getMethod("resize", int.class), 7); Apply.apply(new FilledList<>(Shape::new, 3), Shape.class.getMethod("rotate")); Apply.apply(new FilledList<>(Square::new, 3), Shape.class.getMethod("rotate")); SimpleQueue<Shape> shapeQ = Suppliers.fill( new SimpleQueue<>(), SimpleQueue::add, Shape::new, 3); Suppliers.fill(shapeQ, SimpleQueue::add, Square::new, 3); Apply.apply(shapeQ, Shape.class.getMethod("rotate")); } } /* Output: Shape 0 rotate Shape 1 rotate Shape 2 rotate Shape 0 resize 7 Shape 1 resize 7 Shape 2 resize 7 Square 3 rotate Square 4 rotate Square 5 rotate Square 3 resize 7 Square 4 resize 7 Square 5 resize 7 Shape 6 rotate Shape 7 rotate Shape 8 rotate Square 9 rotate Square 10 rotate Square 11 rotate Shape 12 rotate Shape 13 rotate Shape 14 rotate Square 15 rotate Square 16 rotate Square 17 rotate */ ``` 在 **Apply** 中,我们运气很好,因为碰巧在 Java 中内建了一个由 Java 集合类库使用的 **Iterable** 接口。正由于此, `apply()` 方法可以接受任何实现了 **Iterable** 接口的事物,包括诸如 **List** 这样的所有 **Collection** 类。但是它还可以接受其他任何事物,只要能够使这些事物是 **Iterable** 的——例如,在 `main()` 中使用下面定义的 **SimpleQueue** 类: ```java // generics/SimpleQueue.java // A different kind of Iterable collection import java.util.*; public class SimpleQueue<T> implements Iterable<T> { private LinkedList<T> storage = new LinkedList<>(); public void add(T t) { storage.offer(t); } public T get() { return storage.poll(); } @Override public Iterator<T> iterator() { return storage.iterator(); } } ``` 正如反射解决方案看起来那样优雅,我们必须观察到反射(尽管在 Java 的最新版本中得到了显着改进)通常比非反射实现要慢,因为在运行时发生了很多事情。 但它不应阻止您尝试这种解决方案,这依然是值得考虑的一点。 几乎可以肯定,你会首先使用 Java 8 的函数式方法,并且只有在解决了特殊需求时才诉诸反射。 这里对 **ApplyTest.java** 进行了重写,以利用 Java 8 的流和函数工具: ```java // generics/ApplyFunctional.java import java.util.*; import java.util.stream.*; import java.util.function.*; import onjava.*; public class ApplyFunctional { public static void main(String[] args) { Stream.of( Stream.generate(Shape::new).limit(2), Stream.generate(Square::new).limit(2)) .flatMap(c -> c) // flatten into one stream .peek(Shape::rotate) .forEach(s -> s.resize(7)); new FilledList<>(Shape::new, 2) .forEach(Shape::rotate); new FilledList<>(Square::new, 2) .forEach(Shape::rotate); SimpleQueue<Shape> shapeQ = Suppliers.fill( new SimpleQueue<>(), SimpleQueue::add, Shape::new, 2); Suppliers.fill(shapeQ, SimpleQueue::add, Square::new, 2); shapeQ.forEach(Shape::rotate); } } /* Output: Shape 0 rotate Shape 0 resize 7 Shape 1 rotate Shape 1 resize 7 Square 2 rotate Square 2 resize 7 Square 3 rotate Square 3 resize 7 Shape 4 rotate Shape 5 rotate Square 6 rotate Square 7 rotate Shape 8 rotate Shape 9 rotate Square 10 rotate Square 11 rotate */ ``` 由于使用 Java 8,因此不需要 `Apply.apply()` 。 我们首先生成两个 **Stream** : 一个是 **Shape** ,一个是 **Square** ,并将它们展平为单个流。 尽管 Java 缺少功能语言中经常出现的 `flatten()` ,但是我们可以使用 `flatMap(c-> c)` 产生相同的结果,后者使用身份映射将操作简化为“ **flatten** ”。 我们使用 `peek()` 当做对 `rotate()` 的调用,因为 `peek()` 执行一个操作(此处是出于副作用),并在未更改的情况下传递对象。 注意,使用 **FilledList** 和 **shapeQ** 调用 `forEach()` 比 `Apply.apply()` 代码整洁得多。 在代码简单性和可读性方面,结果比以前的方法好得多。 并且,现在也不可能从 `main()` 引发异常。