假设我想编写一个带有列表的方法,并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>
的定义是错误的吗?
答案 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]
。