我决定在我的方法中使用函数作为参数,并发现相当不愉快的事情。这是一个例子:
final Random random = new Random();
public interface Animal {
public void sleep();
}
public class Cat implements Animal {
public boolean isAffectionate() {
return random.nextBoolean();
}
public void meow() {
System.out.println("meow");
}
@Override
public void sleep() {
System.out.println("my sofa");
}
}
public class Dog implements Animal {
public boolean isAngry() {
return random.nextBoolean();
}
public void bark() {
System.out.println("woof-woof");
}
@Override
public void sleep() {
System.out.println("my carpet");
}
}
public boolean isOwnerAtHome() {
return random.nextBoolean();
}
public <T, A extends Animal> T anyAction(Class<A> clazz, Function<A, T> action)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
A animal;
animal = (A) clazz.getConstructors()[0].newInstance();
T t;
if (isOwnerAtHome()) {
t = action.apply(animal);
} else {
animal.sleep();
t = action.apply(animal);
}
return t;
}
我知道这段代码很糟糕,但问题不在于代码设计。让我们说我想继续使用lambda和类似的设计。
要查看cat活动(但仅在它生成某些内容时 - 而不是void
):
public boolean booleanCatActionToday()
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return anyAction(Cat.class, c -> c.isAffectionate());
}
要查看void
活动,我必须使用此方法:
public <A extends Animal> void voidAction(Class<A> clazz, Consumer<A> action)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Function<A, Boolean> function = animal -> {
action.accept(animal);
return true;
};
anyAction(clazz, function);
}
然后才开始:
public void voidCatActionToday()
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
voidAction(Cat.class, c -> c.meow());
}
相当混乱。即使方法(anyAction
和voidAction
)也应该有不同的名称。
使用lambdas并且没有完全重写可以减少它的混乱吗?
答案 0 :(得分:4)
通常我们使用一些语义来区分返回结果的函数和不具有这些函数的函数:
static void <A extends Animal> void action(
Class<A> type,
Consumer<? super A> theAction) {...}
static void <A extends Animal, T> query(
Class<A> type,
Function<? super A, ? extends T> theQuery) {...}
Animal.action(Cat.class, Cat::meow);
boolean result =
Animal.query(Cat.class, Cat::isAffectionate);
这是因为它们通常意味着不同的东西,因此没有理由将它们混为一谈。
如果你需要重复使用某些东西,那么你需要做一些kludgy:
Animal.query(type, a -> {
theAction.accept(a);
return null;
});
或者可能考虑以不同的方式对其进行分解:
private static <A extends Animal> Optional<A> create(
Class<A> type) throws NoSuchMethodException, InstantiationException, IllegalAccessException {
A a = type.getConstructor().newInstance();
if (!a.isOwnerHome()) {
a.sleep();
}
return a;
}
// "action"
theAction.accept(create(type));
// "query"
return theQuery.apply(create(type));
一般来说,请记住,API的内部可能不够漂亮或完美,而目标是从外部使用它很好。
假设lambdas的设计方式使Consumer<T>
以某种方式隐式转换为Function<T, Void>
。这对于想要重用一些代码的API设计人员来说很方便,但它会为这些API的用户打开一条通道,以无意义/错误的方式使用它们:
<T> void send(List<T> elements) {
elements.stream()
// using 'map' to log
// when 'peek' is designed for this purpose
.map(e -> Debug.log("sending " + e))
.forEach(this::send);
}
答案 1 :(得分:3)
也许你觉得它很复杂,因为你把它弄得很复杂:
public <T, A extends Animal> T anyAction(Supplier<A> s, Function<A, T> action) {
A animal=s.get();
if (!isOwnerAtHome()) animal.sleep();
return action.apply(animal);
}
public <A extends Animal> void anyAction(Supplier<A> s, Consumer<A> action) {
anyAction(s, a -> { action.accept(a); return null; });
}
public boolean booleanCatActionToday() {
return anyAction(Cat::new, Cat::isAffectionate);
}
public void voidCatActionToday() {
anyAction(Cat::new, Cat::meow);
}
是的,需要一个转换步骤来支持Consumer
作为Function
,但它只需要执行一次代码......
对于某些lambda表达式,让两个anyAction
方法命名不同以帮助编译器消除歧义可能会有所帮助,但这不会影响调用者的命名方案,即booleanCatActionToday
和voidCatActionToday
。我不知道你为什么用这种方式命名这些方法以及实际用途是什么,因此很难为它们建议更多有用的名称。
作为一种更通用的解决方案,您可以在代码库中使用单个实用工具方法,例如
public static <T> Function<T,Void> noResult(Consumer<T> c) {
return t -> { c.accept(t); return null; };
}
你可以static import
,你永远不需要重载方法,因为你可以调用接受Function
的方法,就像
public void voidCatActionToday() {
anyAction(Cat::new, noResult(Cat::meow));
}