Trying to get my head round the Java 8 streams syntax with a simple example. Had a look at the other similar questions on this topic, but could not find any solutions that would match my example and would work for me.
I have a class as follow
import java.util.List;
public class Car {
private String model;
private String make;
private String carName;
private List<Specification> specification;
public Car(String model, String make, String carName, List<Specification> specification) {
this.model = model;
this.make = make;
this.carName = carName;
this.specification = specification;
}
public String getModel() {
return model;
}
public String getMake() {
return make;
}
public String getCarName() {
return carName;
}
public List<Specification> getSpecification() {
return specification;
}
}
public class Specification {
private String name;
private String value;
public String getName() {
return name;
}
public String getValue() {
return value;
}
public Specification(String name, String value) {
this.name = name;
this.value = value;
}
}
And I have the main method
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class RegisterCar {
public static void main(String[] args) throws IOException {
List<String> carNames = new ArrayList<>();
carNames.add("Audi");
carNames.add("BMW");
carNames.add("Toyota");
List<String> colour = new ArrayList<>();
colour.add("red");
colour.add("white");
}
}
I want to create a list of car object with each item of carNames. But if the car name is 'Audi' or 'BMW' it should create only one object of the class as follow
List<Car> carList = new ArrayList<>();
Specification musicSystem = new Specification("MusicSysten" , "present");
List<Specification> specList= new ArrayList<>();
specList.add(musicSystem);
carList.add(new Car("Q5", "2020", "Audi", specList));
carList.add(new Car("X2", "2020", "BMW", specList));
But if the item is 'Toyota' then it should create two object each for each color.
List<Specification> specListRed= new ArrayList<>();
specListRed.add(musicSystem);
specListRed.add(redColor);
List<Specification> specListWhite= new ArrayList<>();
specListWhite.add(musicSystem);
specListWhite.add(whiteColor);
carList.add(new Car("Camry", "2020", "Toyota", specListRed));
carList.add(new Car("Camry", "2020", "Toyota", specListWhite));
I am trying to write a common method for creating object and adding to a list. I have tried something like this, but I won't create correct two object for Toyota.
carList = carNames.stream()
.map(carName -> new Car(model, make, carName, specList))
.collect(Collectors.toList());
please suggest how can I achieve this with streams.
If you want the name "Toyota" to be used twice it should be contained in the list of names twice, since the lambda in map() is executed for every name.
But inside your map-Call: Where do you get and model & make?
First thing you need to understand is that map operation always preserves same amount (i.e. cardinality) as your input.
If you will pass 3 items - no matter which function you have, result will always be 3.
Next thing worth looking into: flatMap function. This function can transform any amount of inputs to any amount of results! This looks like what we need.
var carList = carNames.stream()
.flatMap(carName -> {
if (carName.equals("Toyota") {
return Stream.of(new Car(model, make, carName, specList1),
new Car(model, make, carName, specList2));
} else {
return Stream.of(new Car(model, make, carName, specList));
})
.collect(Collectors.toList());
Since it's all about creation and holding of object. Foucus on equals() and and hashcode() methods.
#Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Car)) {
return false;
}
Car car = (Car) o;
if (carName.equals("BMW")) {
return true;
}
if (carName.equals("Toyota")) {
return false;
}
return Objects.equals(carName , car.carName);
}
#Override
public int hashCode() {
return 1;
}
Driver methood
List<String> carNames = new ArrayList<>();
carNames.add("Audi");
carNames.add("BMW");
carNames.add("Toyota");
carNames.add("Toyota");
List<String> colours = new ArrayList<>();
colours.add("red");
colours.add("white");
Set carList = carNames.stream()
.map(carName -> {
Set ss = new HashSet();
if (carName.equals("Toyota")) {
ss.addAll(colours.stream().map(colr -> new Car("model" , "make" , carName , null)).collect(Collectors.toSet()));
} else {
ss.add(new Car("model" , "make" , carName , null));
}
return ss;
})
.collect(HashSet::new, Set::addAll, Set::addAll);
Related
I need to filter elements and then sort based on certain column. Post that I would need to find the unique entries based on combination of columns. Since it is file processing, pipe(|) is used as delimiter to denote the column value.
String s1= "12|Thor|Asgaurd|1000000|Avenger|Active"
String s2= "234|Iron man|New York|9999999|Avenger|Active"
String s3= "420|Loki|Asgaurd|||Inactive"
String s4= "12|Thor|Asgaurd Bank|1000000|Avenger HQ|Active"
Data first needs to be filtered based on the Active/Inactive status. Then it needs to be sorted based on 4th column. Lastly, the uniqueness needs to be maintained by combining column 1,2,3.
Expected Output =
"234|Iron man|New York|9999999|Avenger|Active"
"12|Thor|Asgaurd|1000000|Avenger|Active"
Creating a model class and parsing the string is the way to go, but if for some reaseon you don't want to do that you can do it this way:
import java.util.Comparator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
List<String> result = Stream.of(s1, s2, s3, s4)
.filter(s -> s.split("\\|")[5].equals("Active"))
.sorted(Comparator.comparing(e -> e.split("\\|")[4]))
.collect(Collectors.toList());
First of all you should create an Object which represents your String data. Something like this:
public class MyObject {
private int id;
private String name;
private String location;
private Integer value;
private String category;
private String state;
public MyObject(String entry) {
String[] parts = entry.split("\\|");
if (parts.length != 6) {
throw new IllegalArgumentException("entry has not 6 parts");
}
id = Integer.parseInt(parts[0]);
name = parts[1];
location = parts[2];
try {
value = Integer.parseInt(parts[3]);
} catch (NumberFormatException ignored) {
}
category = parts[4];
state = parts[5];
}
// getters
#Override
public String toString() {
return String.join("|", String.valueOf(id), name, location, String.valueOf(value), category, state);
}
}
With this you can create a Stream of objects from your Strings and to the filter, sort and distinct operations afterwards:
Collection<MyObject> result = Stream.of(s1, s2, s3, s4)
.map(MyObject::new)
.filter(o -> "Active".equals(o.getState()))
.sorted(Comparator.comparing(MyObject::getValue).reversed())
.collect(Collectors.toMap(o -> Arrays.asList(o.getId(), o.getName()),
Function.identity(), (o1, o2) -> o1, LinkedHashMap::new))
.values();
result.forEach(System.out::println);
After the map operation you filter the values by state and sort them by column 4 (value in my case). At the end you collect all the values in a map for the distinct operation. Add all values you need distinction for to the Arrays.asList(). As values the map takes all the original values (Function.identity()). For duplicates we keep the first value ((o1, o2) -> o1) and we are using a LinkedHashMap to keep the order of the items. At the end wee use only the values of the map.
If you need a List instead of a Collection use new ArrayList(result).
The result will be this:
234|Iron man|New York|9999999|Avenger|Active
12|Thor|Asgaurd|1000000|Avenger|Active
It seems like you're unable to filter while everything is string only.
Try this,
create a new model class which can hold your columns.
Ex:
class MyData{
private String name;
private String city;
private String distance;
private String organization;
private String status;
//And create Getter Setter method for all above fields.
}
Now came to your main class where you can play with your code stuff.
Map<MyData> map = new HashMap<MyData>();
MyData myData = new MyData();
myData.setName("Thor");
myData.setCity("Asgaurd");
myData.setDistance("1000000");
myData.setOrganization("Avenger");
myData.setStatus("Active");
map.put(12, myData);
//Same thing for all other data (note: use the loop for data insertion in map)
Map<String, MyData> sorted = map.entrySet().stream().sorted(comparingByValue()).collect(toMap(e -> e.getKey(), e -> e.getValue().getName(), (e1, e2) -> e2,LinkedHashMap::new));
System.out.println("map after sorting by values: " + sorted);
You can solve your task this way:
Firstly, just create POJO(Plain Old Java Object) and override the toString() method.
class MarvelPerson {
private Integer id;
private String name;
private String origin;
private Integer point = null;
private String faction;
private String status;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
public Integer getPoint() {
return point;
}
public void setPoint(Integer point) {
this.point = point;
}
public String getFaction() {
return faction;
}
public void setFaction(String faction) {
this.faction = faction;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(id);
builder.append("|");
builder.append(name);
builder.append("|");
builder.append(origin);
builder.append("|");
if(point != null) {
builder.append(point);
}
builder.append("|");
if(faction != null) {
builder.append(faction);
}
builder.append("|");
builder.append(status);
return builder.toString();
}
}
Then, you should write the parser from string to MarvelPerson. Side note: Carefully, my implementation is pretty basic, and I suppose it should be modified because I may not have foreseen some corner cases.
class PersonParser {
static MarvelPerson parse(String data) {
MarvelPerson person = new MarvelPerson();
String[] array = data.split("\\|", -1);
person.setId(Integer.parseInt(array[0]));
person.setName(array[1]);
person.setOrigin(array[2]);
if(!array[3].isEmpty()) {
person.setPoint(Integer.parseInt(array[3]));
}
if(!array[4].isEmpty()) {
person.setFaction(array[4]);
}
person.setStatus(array[5]);
return person;
}
}
And then your solution:
public class Test {
public static void main(String[] args) {
List<MarvelPerson> list = new ArrayList<>();
list.add(PersonParser.parse("12|Thor|Asgaurd|1000000|Avenger|Active"));
list.add(PersonParser.parse("234|Iron man|New York|9999999|Avenger|Active"));
list.add(PersonParser.parse("420|Loki|Asgaurd|||Inactive"));
list.add(PersonParser.parse("12|Thor|Asgaurd Bank|1000000|Avenger HQ|Actie"));
list.stream()
.filter(marvelPerson -> marvelPerson.getStatus().equals("Active"))
.sorted((o1, o2) -> o1.getPoint() <= o2.getPoint() ? 1 : -1)
.forEach(marvelPerson -> {
System.out.println(marvelPerson.toString());
});
}
}
The output to be printed:
234|Iron man|New York|9999999|Avenger|Active
12|Thor|Asgaurd|1000000|Avenger|Active
I have class like this:
public enum Type {
ONE, TWO
}
#Data
public class Car {
private String name;
private int year;
private Type type;
}
I have new object:
Car car = new Car();
And I have this data:
Map<String, String> data....
name - BMW
year - 2018
type - TWO
key and value - String
And I need set this values to object(except for reflection, I see no ways)
Field year = car.getClass().getDeclaredField("year");
year.setAccessible(true);
year.set(car, data.get("year"));//2018 as string
I get exception(differently and could not be I know):
Caused by: java.lang.IllegalArgumentException: Can not set int field com.example.mapper.Car.year to java.lang.String
Therefore, the question is - how do I correctly cast the value to the desired type to set in the field?
This is a simple example, because the real task is very long explained. If in short - I get a list of values (they are always a string) and the names of the fields in which they change (also a string) and must update the fields of the object with new values
A valid solution with minimum effort would be using a JSON library as a workaround, since they have already implemented value instantiation from strings for the most common types.
For example, using ObjectMapper:
Map<String,String> data = new HashMap<>();
data.put("year","2018");
data.put("name", "BMW");
data.put("type", "TWO");
ObjectMapper mapper = new ObjectMapper();
Car car = mapper.readValue(mapper.writeValueAsString(data), Car.class);
Reflection is indeed the way to go. You can get the type using field.getType() and then check for concrete classes using Class.isAssignableFrom():
final Class<?> type = field.getType();
if (int.class.isAssignableFrom(type)) {
typedValue = Integer.parseInt(value);
} else if (type.isEnum()) {
typedValue = Enum.valueOf((Class<Enum>) type, value);
} else {
// Assume String
typedValue = value;
}
Of course this can become almost arbitrarily complex, but here's a fully working sample for your provided values. That should give you a gist on how to proceed:
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
class CarFiller {
public static void main(String[] args) throws Exception {
Map<String, String> data = new HashMap<>();
data.put("name", "BMW");
data.put("year", "2018");
data.put("type", "TWO");
Car car = new Car();
fillField(car, "name", data);
fillField(car, "year", data);
fillField(car, "type", data);
System.out.println(car);
}
private static void fillField(Object instance, String fieldName, Map<String, String> data) throws Exception {
Field field = instance.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
String value = data.get(fieldName);
Object typedValue = null;
final Class<?> type = field.getType();
if (int.class.isAssignableFrom(type)) {
typedValue = Integer.parseInt(value);
} else if (type.isEnum()) {
typedValue = Enum.valueOf((Class<Enum>) type, value);
} else {
// Assume String
typedValue = value;
}
field.set(instance, typedValue);
}
enum Type {
ONE, TWO
}
static class Car {
private String name;
private int year;
private Type type;
public void setName(String name) {
this.name = name;
}
public void setYear(int year) {
this.year = year;
}
public void setType(Type type) {
this.type = type;
}
#Override
public String toString() {
return "Car [name=" + name + ", year=" + year + ", type=" + type + "]";
}
}
}
(See also on ideone)
I'd recommend not to use reflection everywhere it's possible. Like in your exact example.
You can create an enum class wich contains BiConsumers for each of your fields in Car class:
import java.util.Map;
import java.util.function.BiConsumer;
#Data
public class Car {
private String name;
private int year;
private Ttc.Type type;
static enum CarEnum {
name((car, value) -> car.setName(value)),
year((car, value) -> car.setYear(Integer.parseInt(value))),
type((car, value) -> car.setType(Ttc.Type.valueOf(value)));
private BiConsumer<Car, String> setValueConsumer;
CarEnum(BiConsumer<Car, String> setValueConsumer) {
this.setValueConsumer = setValueConsumer;
}
static Car createCar(Map<String, String> data) {
Car car = new Car();
data.forEach((key, value) -> valueOf(key).setValueConsumer.accept(car, value));
return car;
}
}
}
And then use it in the next way:
Map<String, String> data = new HashMap<>();
data.put("name", "BMW");
data.put("year", "2018");
data.put("type", "TWO");
Car.CarEnum.createCar(data);
Java is statically typed. Therefore you need to provide the correct type yourself. Integer.valueOf takes a String and returns an Integer.
int year = Integer.valueOf("2018");
Converting a String to an Enum works the same.
Type type = Type.valueOf("ONE");
Enum.valueOf is called in the background.
Of course you also need to add some error checking.
I'd recommand avoiding the use of reflection in such a case. You could use a different approach, e.g.
class Car
{
private final Type type;
private final String name;
private final int year;
private Car(Builder builder)
{
this.type = builder.type;
this.name = builder.name;
this.year = builder.year;
}
static class Builder
{
private Type type;
private String name;
private int year;
public Builder setType(String type)
{
this.type = Type.valueOf(type);
return this;
}
public Builder setName(String name)
{
this.name = name;
return this;
}
public Builder setYear(String year)
{
this.year = Integer.valueOf(year);
return this;
}
public Car build()
{
return new Car(this);
}
}
}
You could also add a setData method to the builder
public Builder setData(Map<String, String> data)
{
this.year = Integer.valueOf(data.get("year"));
this.type = Type.valueOf(data.get("type"));
// etc.
return this;
}
Then create a car with Car c = new Car.Builder().setData(data).build();.
I have List of stories. Using unique property(id) I want to collect keyword and targeting as list of values. Can I do this with MultiMap? Or is there other library for this?
[{
id = 1,
title = Onboarding,
keyword = new joinee,
targeting = finance
}, {
id = 1,
title = Onboarding,
keyword = training,
targeting = HR
}]
The Desired output must like this :
{
id = 1,
title = Onboarding,
keyword = [new joinee,training], //may be keywords - plural
targeting = [HR,finance]
}
Sample my tried Code as follows:
package prac;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JavaPrac {
public static void main(String[] args) {
Multimap<Integer, Map> multiMap = ArrayListMultimap.create();
List<Map> stories=new ArrayList();
Map story1=new HashMap();
story1.put("id", 1);
story1.put("title", "Onboarding");
story1.put("keyword","new joinee");
story1.put("targeting","finance");
Map story2=new HashMap();
story2.put("id", 1);
story2.put("title", "Onboarding");
story2.put("keyword","training");
story2.put("targeting","HR");
stories.add(story1);
stories.add(story2);
System.out.println(stories);
stories.forEach((story) -> {
multiMap.put((Integer) story.get("id"), story);
});
}
}
A multimap can only store multiple values per key but what you want is to combine those multiple values so that you get one element that has the same id and title as well as a collection of keywords and targeting information. Thus it would probably be best to either have something like MultiStory or already have Story contain those collections.
I'd suggest using proper objects instead of just maps but with maps and Java 8 lambdas you could use compute() etc. to build maps that contain collections and combine maps that don't.
Here's an example of how you'd do it with maps. Note that this is very bad style and an example using proper pojos will follow:
Disclaimer: example based on the OP's code, not recommended (read text above)
//Problem 1: we don't know the type of the values, i.e. we could put anything for "id" etc.
Map<String, Object> story1=new HashMap<>();
story1.put("id", 1);
story1.put("title", "Onboarding");
story1.put("keyword","new joinee");
story1.put("targeting","finance");
Map<String, Object> story2=new HashMap<>();
story2.put("id", 1);
story2.put("title", "Onboarding");
story2.put("keyword","training");
story2.put("targeting","HR");
List<Map<String, Object>> stories=new ArrayList<>();
stories.add(story1);
stories.add(story2);
Map<Integer, Map<String, Object>> combined = new HashMap<>();
stories.forEach((story) -> {
//Problem 2: because we don't know the type of the values we need a lot of nasty casts
Map<String, Object> combinedStory = combined.computeIfAbsent( (Integer)story.get( "id" ), k -> new HashMap<String, Object>() );
combinedStory.put("id", story.get( "id" ) );
combinedStory.put("title", story.get( "title" ) );
//Problem 3: the combined map would look a lot like your "story" maps but would contain different types
((List<String>)combinedStory.computeIfAbsent( "keyword", v -> new List<String>() )).add( (String)story.get("keyword") );
((List<String>)combinedStory.computeIfAbsent( "targeting", v -> new List<String>() )).add( (String)story.get("targeting") );
});
Using POJOs
Here's a greatly simplified example of how you'd do it with proper Java objects (POJOs). Note that those are meant to resemble your code as much as possible and there are a lot of other issues but addressing those would be way too much here and better designed code would be a lot larger and probably harder to understand - after all it's just meant to show you a difference.
First let's define our classes (for simplicity I made the fields public, you'd normally not do that):
class Story {
public final int id;
public String title;
public String keyword;
public String targeting;
public Story(int storyId) {
id = storyId ;
}
}
class MultiStory {
public final int id;
public String title;
public Set<String> keywords = new HashSet<>();
public Set<String> targetingInfo = new HashSet<>();
public MultiStory( int storyId ) {
id = storyId ;
}
}
Then let's reiterate the code above:
Story story1=new Story( 1 );
story1.title = "Onboarding";
story1.keyword = "new joinee";
story1.targeting = "finance";
Story story2=new Story( 1 );
story2.title = "Onboarding";
story2.keyword = "training";
story2.targeting = "HR";
List<Story> stories=new ArrayList<>();
stories.add(story1);
stories.add(story2);
Map<Integer, MultiStory> combined = new HashMap<>();
stories.forEach((story) -> {
MultiStory multiStory = combined.computeIfAbsent( story.id, v -> new MultiStory( story.id ) );
multiStory.title = story.title;
multiStory.keywords.add( story.keyword );
multiStory.targetingInfo.add( story.targeting );
});
As you can see, there are no casts needed and it's clear what fields are available (though not necessarily filled) which makes it easier to reason about the code and spot errors (the compiler can help a lot here which it couldn't to in the example that uses maps).
Here is a solution using classes to represent the story and tags:
public static void main(String[] args) {
TagsCollector app = new TagsCollector();
app.go();
}
private void go() {
List<Story> stories = createStories();
System.out.println(stories);
Map<Long, Tags> tagsById = collectTags(stories);
tagsById.forEach((aLong, tags) -> System.out.println(tags));
}
private List<Story> createStories() {
return Arrays.asList(
new Story(1, "Onboarding", "new joinee", "finance"),
new Story(1, "Onboarding", "training", "HR")
);
}
private Map<Long, Tags> collectTags(List<Story> stories) {
Map<Long, Tags> tagsById = new HashMap<>();
stories.forEach(s -> {
Tags tags = tagsById.computeIfAbsent(s.id, v -> new Tags(s));
tags.getKeywords().add(s.getKeyword());
tags.getTargetings().add(s.getTargeting());
});
return tagsById;
}
Class used to represent the Story:
public class Story {
private final long id;
private final String title;
private final String keyword;
private final String targeting;
public Story(long id, String title, String keyword, String targeting) {
this.id = id;
this.title = title;
this.keyword = keyword;
this.targeting = targeting;
}
public long getId() {
return id;
}
public String getTitle() {
return title;
}
public String getKeyword() {
return keyword;
}
public String getTargeting() {
return targeting;
}
#Override
public String toString() {
return String.format("Story %s, title=%s, keyword=%s, targeting=%s", id, title, keyword, targeting);
}
}
Class used to represent the Tags:
public class Tags {
private final long id;
private final String title;
private final List<String> keywords = new ArrayList<>();
private final List<String> targetings = new ArrayList<>();
Tags(Story story) {
this.id = story.id;
this.title = story.title;
}
public List<String> getKeywords() {
return keywords;
}
public List<String> getTargetings() {
return targetings;
}
#Override
public String toString() {
return String.format("Tags for id %s, title:%s: keywords=%s, targetings=%s", id, title, keywords, targetings);
}
}
Output
[Story 1, title=Onboarding, keyword=new joinee, targeting=finance, Story 1, title=Onboarding, keyword=training, targeting=HR]
Tags for id 1, title:Onboarding: keywords=[new joinee, training], targetings=[finance, HR]
Yes, you can do that with a Multimap. First I would define a pojo for Story in order to make things clearer:
public class Story {
private int id;
private String title;
private String keyword;
private String targeting;
//getters setters
}
Second you need to define a key with hashcode and equals.
public static class StoryKey {
private final int id;
private final String title;
public StoryKey(int id, String title) {
this.id = id;
this.title = title;
}
//getters
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StoryKey storyKey = (StoryKey) o;
if (id != storyKey.id) return false;
return title != null ? title.equals(storyKey.title) : storyKey.title == null;
}
#Override
public int hashCode() {
int result = id;
result = 31 * result + (title != null ? title.hashCode() : 0);
return result;
}
The code will look like:
ArrayListMultimap<StoryKey, Story> multiMap = ArrayListMultimap.create();
List<Story> stories = new ArrayList();
Story story1 = new Story();
story1.setId(1);
story1.setTitle("Onboarding");
story1.setKeyword("training");
story1.setTargeting("HR");
Story story2 = new Story();
story2.setId(1);
story2.setTitle("Onboarding");
story2.setKeyword("new joinee,");
story2.setTargeting("finance");
stories.add(story1);
stories.add(story2);
System.out.println(stories);
stories.
forEach((story) -> {
multiMap.put(new StoryKey(story.getId(), story.getTitle()), story);
});
multiMap.keys().forEach(key ->
System.out.println(
"id =" + key.getId() +
" title =" + key.getTitle()+
"keyword =" + multiMap.get(key).stream().map(story->story.getKeyword()).collect(Collectors.toList()).toString()+
"targeting ="+ multiMap.get(key).stream().map(story->story.getTargeting()).collect(Collectors.toList()).toString())
);
I have a list of institutionUserConnections and I will have the users.
Therefore I iterate over the institutionUserConnections list.
It can be that a user is in more than one institution.
Therefore I have 2 different user object - but it is the same user.
My question now would be how to get a unique list of users?
final List<User> users = new ArrayList<>();
for (final InstitutionUserConnection institutionUserConnection : institutionUserConnections) {
final User user = institutionUserConnection.getUser();
users.add(user);
}
[EDIT]
Actually it is hard to explain. I have also used a Set but with no success.
Here is the whole code I use:
final List<InstitutionUserConnection> institutionUserConnectionsOfUser = institutionUserConnectionService
.getActiveInstitutionUserConnectionsByUser(foundedUser);
final List<InstitutionUserConnection> institutionUserConnections = new ArrayList<>();
for (final InstitutionUserConnection institutionUserConnection : institutionUserConnectionsOfUser) {
final Institution institution = institutionUserConnection.getInstitution();
institutionUserConnections
.addAll(institutionUserConnectionService.getActiveInstitutionUserConnectionsByInstitution(institution));
}
final List<User> users = new ArrayList<>();
for (final InstitutionUserConnection institutionUserConnection : institutionUserConnections) {
final User user = institutionUserConnection.getUser();
users.add(user);
}
Maybe someone have another hint how I can solve this issue.
as suggested in the comments user HashSet, which will retain only unique reference of the object.
I could not understand your question clearly. I have design a way to get around your issue I hope it helps
class InstituteUserConnection holds insitute name and a list of Users.
listOfInsituteUserConnection will hold InstituteUserConnection.
Iterate over the listOfInsituteUserConnection and get Users and put them into Set<User>. you can check the count in the User to
cross verify and print them out.
FYI HashSet works on the principle of hashing check link for info.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
class User {
String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public int hashCode() {
return this.name.hashCode();
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof User)) {
return false;
}
User objUser = (User) obj;
System.out.println("in equals");
return this.name.equals(objUser.getName());
}
#Override
public String toString() {
return this.name;
}
}
public class InstitutionUserConnections {
String institutionName;
List<User> users;
public InstitutionUserConnections(String institutionName, List<User> users) {
this.users = users;
this.institutionName = institutionName;
}
public void addUser(User user) {
this.users.add(user);
}
public List<User> getUsers() {
return this.users;
}
public static void main(String[] args) {
User user1 = new User("user1");
User user2 = new User("user2");
List<InstitutionUserConnections> listOfInstitutionUserConnections = new ArrayList<InstitutionUserConnections>();
List<User> institute1User = Arrays.asList(new User[] { user1, user2 });
InstitutionUserConnections institute1 = new InstitutionUserConnections(
"institute1", institute1User);
List<User> institute2User = Arrays.asList(new User[] { user1 });
InstitutionUserConnections institute2 = new InstitutionUserConnections(
"institute1", institute2User);
listOfInstitutionUserConnections.add(institute1);
listOfInstitutionUserConnections.add(institute2);
Set<User> listOfUniqueUsers = new HashSet<User>();
for (InstitutionUserConnections institute : listOfInstitutionUserConnections) {
listOfUniqueUsers.addAll(institute.getUsers());
}
System.out.println("No of User in Set<User> : "
+ listOfUniqueUsers.size());
for (User user : listOfUniqueUsers) {
System.out.println(user);
}
}
}
Try this
final HashSet<User> users = new HashSet<>();
for (final InstitutionUserConnection institutionUserConnection : institutionUserConnections) {
final User user = institutionUserConnection.getUser();
users.add(user);
}
// iterate users to check unique
I would suggest to override your Equals method and use data structure you want to use,if you are eager to use List/ ArrayList or any set as well. Apache Commons Collection provide a great one line code to provide unique list.
Add Maven dependency or download jar :
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
Then simply use below line of code:
SetUniqueList uniqueList = SetUniqueList.decorate(<<Your list>>);
and override Equals and HashCode:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
YourClass tester = (YourClass) o;
if (user!= null ? !user.equals(tester.user) : tester.user!= null) return false;
return true;
}
#Override
public int hashCode() {
int result = user != null ? user.hashCode() : 0;
result = 31 * result + (anotherField!= null ? anotherField.hashCode() : 0);
return result;
}
Here's some imperative code that I'm trying to translate into functional programming code:
public class Person {
String name;
Token token;
public Person(String name, Token token) {
this.name = name;
this.token = token;
}
}
public class Token {
String id;
boolean isValid;
public Token(String id, boolean isValid) {
this.id = id;
this.isValid = isValid;
}
public String getId() { return id; }
public boolean isValid() {return isValid;}
}
public static List<Token> getTokensForPerson(String name) {...}
public static List<Person> getPeople1 (String[] names) {
List<Person> people = new ArrayList<Person> ();
for (String name: names) {
List<Token> tokens = getTokensForPerson(name);
for (Token token: tokens) {
if (token.isValid()) {
people.add(new Person(name, token));
}
}
}
return people;
}
Here's my attempt to do it the functional way.
public static List<Person> getPeople2 (String[] names) {
return Arrays.stream(names).map(name -> getTokensForPerson(name))
.flatMap(tokens -> tokens.stream().filter(token -> token.isValid))
.map(token -> new Person(name, token)) // <== compiler error here. "Cannot resolve symbol 'name'"
.collect(Collectors.toList());
}
However it doesn't compile since in the last map operation I need to refer to name to create the Person object and name is not available at that time. Any ideas?
You can move the map steps inside the flatMap:
return Arrays.stream(names)
.<Person>flatMap(
name -> getTokensForPerson(name).stream()
.filter(Token::isValid)
.map(token -> new Person(name, token)))
.collect(Collectors.toList());
This way you can access name variable as well.
StreamEx-based solution is shorter, though it requires third-party library:
return StreamEx.of(names)
.cross(name -> getTokensForPerson(name).stream())
// Here we have the stream of entries
// where keys are names and values are tokens
.filterValues(Token::isValid)
.mapKeyValue(Person::new)
.toList();
Would it be possible to create TokenExtended class extending Token, and adding the name, and return a List<TokenExtended> from getTokensForPerson instead of the List<Token>?
public class TokenExtended extends Token {
private String name;
public TokenExtended(String name, String id, boolean isValid) {
super(id, isValid);
this.name = name;
}
}
This way your code would work
Arrays.stream(names).map(name -> getTokensForPerson(name)).flatMap(tokens -> tokens.stream().filter(token -> token.isValid))
.map(token -> new Person(token.name, token)).collect(Collectors.toList());