私の仲間のStackOverflow著名人からのラムダで考えるいくつかの助けが必要です。
リストのリストのリストを選択して、グラフの奥深くにある一部の子を収集する標準的なケース。このボイラープレートでLambdas
が役立つ素晴らしい方法は何ですか?
public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<ContextInfo>();
final StandardServer server = getServer();
for (final Service service : server.findServices()) {
if (service.getContainer() instanceof Engine) {
final Engine engine = (Engine) service.getContainer();
for (final Container possibleHost : engine.findChildren()) {
if (possibleHost instanceof Host) {
final Host host = (Host) possibleHost;
for (final Container possibleContext : Host.findChildren()) {
if (possibleContext instanceof Context) {
final Context context = (Context) possibleContext;
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
list.add(info);
}
}
}
}
}
}
return list;
}
リスト自体はJSON
としてクライアントに送られることに注意してください。そのため、返されるものに注目しないでください。ループを削減できるいくつかのきちんとした方法でなければなりません。
私の仲間の専門家が作成するものを見て興味があります。複数のアプローチが推奨されます。
[〜#〜] edit [〜#〜]
findServices
および2つのfindChildren
メソッドは配列を返します
編集-ボーナスチャレンジ
「重要ではない部分」が重要であることが判明しました。実際には、Host
インスタンスでのみ使用可能な値をコピーする必要があります。これは、すべての美しい例を台無しにしているようです。国家をどのように進めますか?
final ContextInfo info = new ContextInfo(context.getPath());
info.setHostname(Host.getName()); // The Bonus Challenge
かなり深くネストされていますが、例外的に難しくはないようです。
最初の観察は、forループがストリームに変換される場合、flatMap
を使用してネストされたforループを単一のストリームに「フラット化」できることです。この操作は単一の要素を取り、ストリーム内の任意の数の要素を返します。調べてみると、StandardServer.findServices()
がService
の配列を返すので、Arrays.stream()
を使用してこれをストリームに変換します。 (Engine.findChildren()
とHost.findChildren()
についても同様の仮定をしています。
次に、各ループ内のロジックはinstanceof
チェックとキャストを実行します。これは、ストリームを使用してfilter
操作としてモデル化でき、instanceof
の後に、同じ参照をキャストして返すだけのmap
操作が続きます。これは実際には何もしませんが、静的型付けシステムが例えば_Stream<Container>
_を_Stream<Host>
_に変換できるようにします。
これらの変換をネストされたループに適用すると、次の結果が得られます。
_public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<ContextInfo>();
final StandardServer server = getServer();
Arrays.stream(server.findServices())
.filter(service -> service.getContainer() instanceof Engine)
.map(service -> (Engine)service.getContainer())
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(possibleHost -> possibleHost instanceof Host)
.map(possibleHost -> (Host)possibleHost)
.flatMap(Host -> Arrays.stream(Host.findChildren()))
.filter(possibleContext -> possibleContext instanceof Context)
.map(possibleContext -> (Context)possibleContext)
.forEach(context -> {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
list.add(info);
});
return list;
}
_
しかし、待ってください、まだあります。
最後のforEach
操作は、map
をContext
に変換する少し複雑なContextInfo
操作です。さらに、これらはList
に収集されるため、リストを作成して空にしてリストを作成する代わりに、コレクターを使用してこれを行うことができます。これらのリファクタリングを適用すると、次の結果になります。
_public List<ContextInfo> list() {
final StandardServer server = getServer();
return Arrays.stream(server.findServices())
.filter(service -> service.getContainer() instanceof Engine)
.map(service -> (Engine)service.getContainer())
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(possibleHost -> possibleHost instanceof Host)
.map(possibleHost -> (Host)possibleHost)
.flatMap(Host -> Arrays.stream(Host.findChildren()))
.filter(possibleContext -> possibleContext instanceof Context)
.map(possibleContext -> (Context)possibleContext)
.map(context -> {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
})
.collect(Collectors.toList());
}
_
私は通常、複数行のラムダ(最後のmap
操作など)を回避しようとするため、Context
を取り、ContextInfo
。これによりコードが短くなることはありませんが、明確になると思います。
[〜#〜] update [〜#〜]
しかし、待ってください、まだまだあります。
service.getContainer()
への呼び出しを独自のパイプライン要素に抽出しましょう:
_ return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.filter(container -> container instanceof Engine)
.map(container -> (Engine)container)
.flatMap(engine -> Arrays.stream(engine.findChildren()))
// ...
_
これにより、instanceof
のフィルタリングと、それに続くキャストによるマッピングが繰り返されます。これは合計3回行われます。他のコードも同様のことをする必要があるようですので、このビットのロジックをヘルパーメソッドに抽出すると良いでしょう。問題は、filter
がストリーム内の要素の数を変更できる(一致しない要素を削除する)が、その型を変更できないことです。また、map
は要素のタイプを変更できますが、その数は変更できません。数とタイプの両方を変更できるものはありますか?はい、それは古い友人flatMap
です!したがって、ヘルパーメソッドは要素を受け取り、異なるタイプの要素のストリームを返す必要があります。その戻りストリームには、単一のキャストされた要素が含まれる(一致する場合)か、空になります(一致しない場合)。ヘルパー関数は次のようになります。
_<T,U> Stream<U> toType(T t, Class<U> clazz) {
if (clazz.isInstance(t)) {
return Stream.of(clazz.cast(t));
} else {
return Stream.empty();
}
}
_
(これは、コメントのいくつかで言及されているC#のOfType
構成に大まかに基づいています。)
作業中に、メソッドを抽出してContextInfo
を作成します。
_ContextInfo makeContextInfo(Context context) {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
}
_
これらの抽出後、パイプラインは次のようになります。
_ return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.flatMap(container -> toType(container, Engine.class))
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.flatMap(possibleHost -> toType(possibleHost, Host.class))
.flatMap(Host -> Arrays.stream(Host.findChildren()))
.flatMap(possibleContext -> toType(possibleContext, Context.class))
.map(this::makeContextInfo)
.collect(Collectors.toList());
_
もっといいと思うし、恐ろしい複数行ステートメントlambdaを削除した。
更新:ボーナスチャレンジ
もう一度、flatMap
はあなたの友達です。ストリームのテールを取得し、テールの前の最後のflatMap
に移行します。そのようにすると、Host
変数はまだスコープ内にあり、makeContextInfo
を受け取るように変更されたHost
ヘルパーメソッドに渡すことができます。
_ return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.flatMap(container -> toType(container, Engine.class))
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.flatMap(possibleHost -> toType(possibleHost, Host.class))
.flatMap(Host -> Arrays.stream(Host.findChildren())
.flatMap(possibleContext -> toType(possibleContext, Context.class))
.map(ctx -> makeContextInfo(ctx, Host)))
.collect(Collectors.toList());
_
これは、JDK 8ストリーム、メソッド参照、ラムダ式を使用したコードの私のバージョンです。
server.findServices()
.stream()
.map(Service::getContainer)
.filter(Engine.class::isInstance)
.map(Engine.class::cast)
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(Host.class::isInstance)
.map(Host.class::cast)
.flatMap(Host -> Arrays.stream(Host.findChildren()))
.filter(Context.class::isInstance)
.map(Context.class::cast)
.map(context -> {
ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
})
.collect(Collectors.toList());
このアプローチでは、フィルター述部のifステートメントを置き換えます。 instanceof
チェックをPredicate<T>
に置き換えることができることを考慮してください
Predicate<Object> isEngine = someObject -> someObject instanceof Engine;
次のように表現することもできます
Predicate<Object> isEngine = Engine.class::isInstance
同様に、キャストはFunction<T,R>
に置き換えることができます。
Function<Object,Engine> castToEngine = someObject -> (Engine) someObject;
これはほとんど同じです
Function<Object,Engine> castToEngine = Engine.class::cast;
また、forループのリストに手動で項目を追加することは、コレクターに置き換えることができます。実動コードでは、Context
をContextInfo
に変換するラムダを別のメソッドに抽出でき(またそうすべきです)、メソッド参照として使用します。
@EdwinDalorzoの回答に触発されました。
public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<>();
final StandardServer server = getServer();
return server.findServices()
.stream()
.map(Service::getContainer)
.filter(Engine.class::isInstance)
.map(Engine.class::cast)
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(Host.class::isInstance)
.map(Host.class::cast)
.flatMap(Host -> mapContainers(
Arrays.stream(Host.findChildren()), Host.getName())
)
.collect(Collectors.toList());
}
private static Stream<ContextInfo> mapContainers(Stream<Container> containers,
String hostname) {
return containers
.filter(Context.class::isInstance)
.map(Context.class::cast)
.map(context -> {
ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
info.setHostname(hostname); // The Bonus Challenge
return info;
});
}
い以上の最初の試み。これが判読できるようになるまでには何年もかかるでしょう。より良い方法でなければなりません。
findChildren
メソッドは、もちろんfor (N n: array)
構文で機能する配列を返しますが、新しいIterable.forEach
メソッドでは機能しないことに注意してください。 Arrays.asList
でラップする必要がありました
public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<ContextInfo>();
final StandardServer server = getServer();
asList(server.findServices()).forEach(service -> {
if (!(service.getContainer() instanceof Engine)) return;
final Engine engine = (Engine) service.getContainer();
instanceOf(Host.class, asList(engine.findChildren())).forEach(Host -> {
instanceOf(Context.class, asList(Host.findChildren())).forEach(context -> {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
list.add(info);
});
});
});
return list;
}
ユーティリティメソッド
public static <T> Iterable<T> instanceOf(final Class<T> type, final Collection collection) {
final Iterator iterator = collection.iterator();
return () -> new SlambdaIterator<>(() -> {
while (iterator.hasNext()) {
final Object object = iterator.next();
if (object != null && type.isAssignableFrom(object.getClass())) {
return (T) object;
}
}
throw new NoSuchElementException();
});
}
そして最後に、Iterable
のLambda対応の実装
public static class SlambdaIterator<T> implements Iterator<T> {
// Ya put your Lambdas in there
public static interface Advancer<T> {
T advance() throws NoSuchElementException;
}
private final Advancer<T> advancer;
private T next;
protected SlambdaIterator(final Advancer<T> advancer) {
this.advancer = advancer;
}
@Override
public boolean hasNext() {
if (next != null) return true;
try {
next = advancer.advance();
return next != null;
} catch (final NoSuchElementException e) {
return false;
}
}
@Override
public T next() {
if (!hasNext()) throw new NoSuchElementException();
final T v = next;
next = null;
return v;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
たくさんの配管と、間違いなくバイトコードの5倍。より良い方法でなければなりません。