2015-07-22 5 views
1

У меня есть поток объектов (Список) и вы хотите создать новые объекты из этого потока, которые нужно вставить в Set. Тем не менее, два или более объекта во входящем списке могут иметь хеш к одному и тому же ключу в наборе, и в этом случае я хочу добавить строку из объекта n-го списка к уже существующему в наборе вместо создания нового.Условное сопоставление с новыми объектами с потоком Java

Нечто подобное, но в функциональной форме:

HashSet<ClassB> mySet = new HashSet<>(); 
for (ClassA instanceA : classAList) { 
    if (mySet.contains(ClassB.key(instanceA))) { //static method call to find the key 
     mySet.get(instanceA).appendFieldA(instanceA.getFieldA()); 
    } else { 
     mySet.add(new ClassB(instanceA)); 
    } 
} 
return mySet; 

В функциональной форме я хотя создать что-то вроде этого:

List classAList = new ArrayList<>(); 
classAList.stream() 
.map(instanceA -> new ClassB(instanceA)) 
.collect(Collectors.toSet()); 

Но тогда, конечно, игнорирует HashMap и я не» t получить для объединения полей мои множественные экземпляры ClassA, которые будут разрешены к одному и тому же ClassB. Я не знаю, как это сделать. Нужно ли игнорировать вызов map() и вместо этого создавать пользовательский сборщик? Кажется, существует несколько способов сделать это, но я новичок в Streams.

+2

Ну, проблема с той самой идеей является то, что если поведение основано на состоянии (будь то конфликтующие-хеш объект в уже Комплексе), то вы больше не делать вещи функционально , – childofsoong

+3

Что означает «mySet.get (instanceA)»? Я думаю, вы запутываете карты и наборы. Можете ли вы вставить свой фактический код? – Misha

ответ

3

Трудно понять, что вы на самом деле хотите, поскольку ваш пример кода не работает вообще. Проблема в том, что Set не работает как Map, вы не можете запросить его для содержащегося эквивалентного объекта. Кроме того, вы используете разные объекты для своих вызовов contains(…) и get(…). Кроме того, неясно, какая разница между ClassB.key(instanceA) и new ClassB(instanceA).

Давайте попробуем переопределить его:

Предположим, что мы имеем тип ключа Key и метод Key.key(instanceA) для определения кандидатов группы. Затем мы имеем ClassB, который является результирующим типом, созданным с помощью new ClassB(instanceA) для одного (или первичного экземпляра ClassA), имеющего метод .appendFieldA(…) для получения значения другого экземпляра ClassA при объединении двух членов группы. Затем, оригинал (до Java 8) код будет выглядеть следующим образом:

HashMap<Key, ClassB> myMap = new HashMap<>(); 
for(ClassA instanceA: classAList) { 
    Key key=Key.key(instanceA); 
    if(myMap.containsKey(key)) { 
     myMap.get(key).appendFieldA(instanceA.getFieldA()); 
    } else { 
     myMap.put(key, new ClassB(instanceA)); 
    } 
} 

Затем myMap.values() предоставляет вам коллекцию ClassB экземпляров.Если он должен быть Set, вы можете создать его с помощью

Set<ClassB> result=new HashSet<>(myMap.values()); 

Обратите внимание, что это также работает, когда Key и ClassB идентичны, как это, кажется, в вашем коде, но вы можете спросите себя, действительно ли вы нужны оба, экземпляр, созданный с помощью .key(instanceA) и созданных с помощью new ClassB(instanceA) ...


Это можно упростить с помощью Java API-как:

for(ClassA instanceA: classAList) { 
    myMap.compute(Key.key(instanceA), (k,b)-> { 
     if(b==null) b=new ClassB(instanceA); 
     else b.appendFieldA(instanceA.getFieldA()); 
     return b; 
    }); 
} 

или, если вы хотите выглядеть еще более функциональным стильным:

classAList.forEach(instanceA -> 
    myMap.compute(Key.key(instanceA), (k,b)-> { 
     if(b==null) b=new ClassB(instanceA); 
     else b.appendFieldA(instanceA.getFieldA()); 
     return b; 
    }) 
); 

Для решения потока, существует проблема, что функция слияния получит два экземпляра одного и того же типа, здесь ClassB, и не может получить доступ к экземпляру ClassA через окружающий контекст, как мы это сделали с решением compute выше. Для решения потока нам нужен метод в ClassB, который возвращает этот первый экземпляр ClassA, который мы передали его конструктору, скажем getFirstInstanceA(). Тогда мы можем использовать:

Map<Key, ClassB> myMap = classAList.stream() 
    .collect(Collectors.toMap(Key::key, ClassB::new, (b1,b2)->{ 
     b1.appendFieldA(b2.getFirstInstanceA().getFieldA()); 
     return b1; 
    })); 
+0

Спасибо, это точно так же, как и ваша интерпретация того, где мой пример поступил не так. В моих попытках упростить постановку проблемы я испортил оригинальный пример кода; мои извинения всем, кто заметил, что исходный код не имеет большого смысла. – tdm

1

Вы можете группировать записи в карту, которая отображает хешированный ключ в список элементов, а затем снова вызывает карту, чтобы преобразовать эту карту в набор, который вы после. Что-то вроде этого:

List classAList = new ArrayList<>(); 
classAList.stream() 
.collect(Collectors.groupingBy(instanceA -> ClassB.key(instanceB))) 
.entrySet() 
.map(entry -> entry.getValue().stream() 
    .map(instanceA -> new ClassB(instanceA)) 
    .reduce(null, (a,b) -> a.appendFieldA(b))) 
.collect(Collectors.toSet()); 
+0

Но 'appendFieldA' не принимает объекты типа' ClassB'. Кроме того, передача «null» в качестве значения идентификатора для «уменьшения» означает, что он может (текущая реализация) попытаться вызвать 'appendFieldA' на эту ссылку« null ». – Holger

+0

Ах, да, вы правы. Я не понимал, что вопрос заключается в том, чтобы положить classb в набор, но вызвать appendFieldA на classa. И для того, чтобы это сработало, вам понадобится какая-то личность classa –

Смежные вопросы