如何指定一个返回另一个类型为covariance的泛型类型的java.util.Function?

时间:2018-04-20 11:37:37

标签: java generics

假设我想编写一个带有列表的方法,并Function返回另一个泛型类型,比如Optional。这种方法的一个具体示例是将函数应用于列表中的所有元素,并返回一个不会导致空Optional的所有元素的列表。

据我所知,以下是我将如何写出这种方法:

public <I, O> List<O> transform(List<I> list, Function<? super I, Optional<? extends O>> function) {
  List<O> result = new ArrayList<>();
  for (I element : list) {
    Optional<? extends O> optional = function.apply(element);
    if (optional.isPresent()) {
      result.add(optional.get());
    }
  }
  return result;
}

我为Function使用通配符参数的原因如下:

  • 如果Function的输入正好是I,我真的不在乎。传递父类型为I的任何内容都应该没问题。
  • Optional返回的Function不必是O。返回Optional <{1}}的{​​{1}}子类型应该没问题。

首先通过定义两个简单类型来测试它:

O

现在假设我们从public class Animal {} public class Cat extends Animal {} s:

列表开始
Cat

根据我上面提到的List<Cat> catList = new ArrayList<>(); 采用上述通配符的两点,我想使用以下方法将此列表转换为另一个transform列表:

Animal

当我将方法引用传递给public Optional<Cat> animalToCat(Animal cat) { return Optional.empty(); }

时,这确实有效
animalToCat

但是,如果我没有直接使用此方法参考,并且希望先将该方法存储在变量中,然后再将其传递给List<Animal> animalList = transform(catList, this::animalToCat); // works! ,该怎么办?

transform

为什么行导致Function<Animal, Optional<Cat>> function2 = this::animalToCat; List<Animal> animalList2 = transform(catList, function2); // does not compile! 编译,但导致animalList的行不会?通过反复试验,我发现我确实可以通过改变变量的类型来进行编译,我将方法引用分配给以下任何一个:

animalList2

所以似乎可以将Function<Animal, Optional<? extends Animal>> function3 = this::animalToCat; List<Animal> animalList3 = transform(catList, function3); // works Function<Cat, Optional<? extends Animal>> function4 = this::animalToCat; List<Animal> animalList4 = transform(catList, function4); // works Function<? super Cat, Optional<? extends Animal>> function5 = this::animalToCat; List<Animal> animalList5 = transform(catList, function5); // works 方法引用分配给其他animalToCat类型,这显然是Animal并返回Optional<Cat>。但是,取现有Function并将其分配给其他类型会失败:

animalToCatFunction2

我非常困惑为什么可以将animalToCatFunction3 = animalToCatFunction2; // does not compile! animalToCatFunction4 = animalToCatFunction2; // does not compile! animalToCatFunction5 = animalToCatFunction2; // does not compile! 方法引用视为返回的this::animalToCat似乎是协变类型,但是一旦将引用分配给具有特定类型的变量,行为就会突然中断。我对Optional<Cat>的定义是错误的吗?

2 个答案:

答案 0 :(得分:1)

请参阅STU's reply了解潜在原因。

要使您的示例正常工作,您需要使用更多类型参数来帮助Java类型系统:

 public void addLogToUserWithApiKey(string logMessage, string apiKey)
    {
        Log newLog = new Log();
        newLog.logID = makeLogID();
        newLog.logString = logMessage;
        newLog.logDateTime = DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToString("h:mm:ss tt");

        using (var context = new UserContext())
        {
            User logUser = checkIfUserExistsWithApiKeyandReturnUser(apiKey);
            if (logUser.Logs == null)
            {
                logUser.Logs = new Collection<Log>();

            }
            logUser.Logs.Add(newLog);

            logUser.Logs.Add(newLog);
            context.Logs.Add(newLog);
            context.SaveChanges();
        }
    }

由于传入列表的动态子类型可能与传出列表不同,因此在示例中添加另一个子类型很有用:

<A, B, C extends B, D extends A> List<B> transform(List<D> list, Function<A, Optional<C>> function) {
    List<B> result = new ArrayList<>();
    for (D element : list) {
        Optional<C> optional = function.apply(element);
        if (optional.isPresent()) {
            result.add(optional.get());
        }
    }
    return result;
}

然后示例代码为:

class Dog extends Animal {}

编辑:稍微修改了类型约束,使其与注释中的示例一起使用:

List<Dog> dogList = new ArrayList<>();
List<Animal> animalList = transform(dogList, this::animalToCat);

Function<Animal, Optional<Cat>> function2 = this::animalToCat;
List<Animal> animalList2 = transform(dogList, function2);

答案 1 :(得分:1)

我相信这不会编译的原因:

Function<Animal, Optional<Cat>> function2 = this::animalToCat;
List<Animal> animalList2 = transform(catList, function2);

然而这样做:

List<Animal> animalList = transform(catList, this::animalToCat);

方法引用具有在用户可见类型系统中无法表示的类型。

您可以将this::animalToCat视为具有“魔术”类型Function<contravariant Animal, covariant Optional<Cat>>,但只要您转换为用户定义类型Function<Animal, Optional<Cat>>,就会丢失有关方差的信息原始功能本身。

此时,您无法将Function<Animal, Optional<Cat>>分配给Function<Animal, Optional<? extends Animal>>,原因与您无法将Function<Optional<Cat>, Animal>分配给Function<Optional<? extends Animal>, Animal>的原因相同。

C.f。 Scala中的Function1[-T1, +R]