How to write code simply using stream api - java

I have the following classes:
#Getter
public class SomeClass implements Serializable {
private LocalDate date;
private String smth;
List<PairKeyValue> quotaParams;
}
The class PairKeyValue is just:
#Getter
public class PairKeyValue implements Serializable {
private String key;
private String value;
}
I want to do the following:
1) Check if in SomeClass's date equals sysdate then check value under key="somekey" in list<PairKeyValue> is equals to 1 (somekey = 1) then left it in list.
2) Check if in SomeClass's date NOT equals sysdate then check value under key="somekey" in List<PairKeyValue> is equals to 0 (somekey = 0) then left it in list.
3) And ignore other values.
So in the end I need a filtered list of only current values within SomeClass.
I have my realization but I don't like it is not using only stream API:
availableQuotes = ArrayList();
if (CollectionUtils.isNotEmpty(availableQuotes)) {
availableQuotes = availableQuotes
.stream()
.filter(this::checkDate).collect(toList());
}
private boolean checkDate (SomeClass someClass){
if (someClass.getDate().equals(LocalDate.now())) {
return checkDuration(someClass, "0");
} else {
return checkDuration(someClass, "1");
}
}
private boolean checkDuration (SomeClass someClass, String param){
List<PairKeyValue> quotaParams = someClass.getPairKeyValues().stream()
.filter(spKeyValue -> spKeyValue.getKey().equals("duration"))
.filter(spKeyValue -> spKeyValue.getValue().equals(param))
.collect(toList());
return (CollectionUtils.isNotEmpty(quotaParams));
}
I know it looks awful and I know it can be more readable so please help.

If I understood your question right, the last 2 functions can be resumed to the following:
availableQuotes = availableQuotes.stream()
.filter(availableQuote -> availableQuote.getQuotaParams().stream()
.anyMatch(quotaParam -> quotaParam.getKey().equals("duration")
&& quotaParam.getValue().equals(availableQuote.getDate().equals(LocalDate.now()) ? "0" : "1")))
.collect(Collectors.toList());
I mostly took your code and re-arranged it into a single filter.

Related

How to return a parameter on the basis on another parameter in list of object

I have the following InventoryItem class:
#AllArgsConstructor
#Getter
public class InventoryItem {
private String name;
private int amount;
// other properties, getters, etc.
}
And I have an Inventory object that contains a List of InventoryItems.
I want to obtain the amount of the item where the name is equal to the given name.
I am trying to use streams for that purpose:
inventoryItems.stream().filter(item -> item.getName().equals(name));
But it is returning the whole item, I only want the amount. How can I do that?
I am new to java so do not have idea.
Using JDK 14:
I have implemented the below code using records instead of creating POJO's.
Note (As a suggestion): As I can see in your given code, you are using some annotations(#AllArgsConstructor #Getter,..) for auto creation of constructors, getters and setters, so for that boiler-plate code, records seems to be a good option which is available from java 14 onwards.
Records in java : As of JDK 14, we can replace our data classes with records. Records are immutable classes that require only the type and name of fields. We do not need to create constructor, getters, setters, override toString() methods, override hashcode and equals methods.(records javadoc)
Please find the code below:
public class Test {
public static void main(String[] args) {
String nameToFind = "item1";
int amount = 0;
record InventoryItem(String name,int amount){}
Optional<Integer> optional = List.of(new InventoryItem("item1",100),
new InventoryItem("item2",200),
new InventoryItem("item3",300))
.stream().filter(x -> x.name().equals(nameToFind)).map(InventoryItem::amount).findFirst();
if(optional.isPresent()){
amount = optional.get();
}
System.out.println(amount);
}
filter operation is an intermediate operation. We need one of terminal operation to get the result. Below are couple of ways to get the Amount
import java.util.Arrays;
import java.util.List;
class InventoryItem {
String name;
float amount;
public InventoryItem(String name, float amount) {
this.name = name;
this.amount = amount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getAmount() {
return amount;
}
public void setAmount(float amount) {
this.amount = amount;
}
}
public class ItemAmount {
public static void main(String[] args) {
List<InventoryItem> inventoryItems = Arrays.asList(new InventoryItem("A", 100f), new InventoryItem("B", 50f));
//method-1
float amount = inventoryItems.stream().filter(item -> item.getName().equals("B")).findFirst().get().getAmount();
System.out.println("Amount of B item is: " + amount);
//method-2
List<InventoryItem> matchList = inventoryItems.stream().filter(item -> item.getName().equals("B")).collect(Collectors.toList());
matchList.forEach(i -> System.out.println("Item: " + i.getName() + ", Amount: " + i.getAmount()));
}
}
Use the map() function, append it after the filter.
Optional<int> result = inventoryItems.stream().filter(item -> item.getName().equals(name)).map(item -> item.getAmount()).findFirst();
There are to issues:
Your stream lacks a terminal operation which is needed to obtain the result of the stream execution.
To amount from an InventoryItem you need to apply map() operation.
That's how you can generate a list of amounts that correspond to the items having the given name:
public List<Integer> getAmountsByName(List<InventoryItem> inventoryItems,
String name) {
return inventoryItems.stream()
.filter(item -> item.getName().equals(name))
.map(InventoryItem::getAmount)
.toList(); // for Java 16+ or collect(Collectors.toList()) for earlier versions
}
In case if names of items are expected to be unique, then you can apply findFirst() as a terminal operation, which returns an Optional as a result (because resulting value might not be present).
There are many possibilities how you can dial the Optional, for instance you can use orElse() to provide the default value, or orElseThrow() to throw an exception if a result was not found (sometimes it makes sense to return an Optional, and deal with it in the calling method).
That's how it might look like:
public int getAmountByName(List<InventoryItem> inventoryItems,
String name) {
return inventoryItems.stream()
.filter(item -> item.getName().equals(name))
.map(InventoryItem::getAmount)
.findFirst()
.orElse(-1); // in case if an item with the target name isn't present return zero
// .orElseThrow(); // alternatively you can throw an exception depending on your needs
}

MongoDb Java filter for value in list

I have a database object that contains a field which is also an object, like this:
public class PrdVersionOverviewDbData {
public static final String FIELD_SPECIFICATION_ENTRY_POINTER = "specificiationEntryPointer";
#BsonProperty(FIELD_SPECIFICATION_ENTRY_POINTER)
private MongodbSpecificationEntryPointer specificiationEntryPointer;
[...]
}
Now, I have a List<MongodbSpecificationEntryPointer>, and I want to look up all PrdVersionOverviewDbData where the specificiationEntryPointer is any of the list. Spontaneously, I tried solving that with an in Filter like this:
Bson filter = Filters.in(PrdVersionOverviewDbData.FIELD_SPECIFICATION_ENTRY_POINTER, specificiationEntryPointerList);
However, for some strange reason, running that filter always only returned me an empty list.
By contrast, the following logic gives me the desired result:
public List<PrdVersionOverviewDbData> readPrdVersionsContainingAnyOfSpecificationPointers(
List<MongodbSpecificationEntryPointer> specificationPointerList
) {
List<PrdVersionOverviewDbData> allPrdVersionOverviews = readObjectList();
List<PrdVersionOverviewDbData> prdVersionOverviewsContainingSpecificationPointer = new ArrayList<>();
for (PrdVersionOverviewDbData prdVersionOverviewDbData : allPrdVersionOverviews) {
if(prdVersionOverviewDbData.containsAny(specificationPointerList)) {
prdVersionOverviewsContainingSpecificationPointer.add(prdVersionOverviewDbData);
}
}
return prdVersionOverviewsContainingSpecificationPointer;
}
public class PrdVersionOverviewDbData{
public static final String FIELD_SPECIFICATION_ENTRY_POINTER = "specificiationEntryPointer";
#BsonProperty(FIELD_SPECIFICATION_ENTRY_POINTER)
private MongodbSpecificationEntryPointer specificiationEntryPointer;
[...]
public boolean containsAny(
List<MongodbSpecificationEntryPointer> specificationPointerList
) {
return specificationPointerList.contains(specificiationEntryPointer);
}
}
That is, of course, very inefficient since it first needs to read all the data from MongoDB and then iterate over it. So my question is: What am I doing wrong with the filters? And what would be the proper way to do this?
I have now figured out what caused this and how to fix it.
In essence, Filters.in is the correct filter to use for this kind of situation. The problem is that for some Objects, MongoDB has trouble comparing them. This may be related to said objects being children of abstract classes, but I am not 100% sure about that.
Anyway, how I managed to fix this was that instead of comparing by MongodbSpecificationEntryPointer (which is, in essence, just a wrapper for an ObjectId), I compared directly by the ObjectId, so now my logic looks somewhat like this:
private static final String FIELD_PATH_SPECIFICATION_POINTER_OBJECT_ID
= PrdVersionOverviewDbData.FIELD_SPECIFICATION_ENTRY_POINTER
+ "."
+ MongodbEntryPointer.MONGO_DB_ID_FIELD;
public List<PrdVersionOverviewDbData> getPrdVersionOverviewsInSpecifications(
List<Specification> specificationsInFolder
) {
List<ObjectId> specificationObjectIdList
= toSpecificationObjectIdList(specificationsInFolder);
return readObjectListWithFieldContainingAny(
FIELD_PATH_SPECIFICATION_POINTER_OBJECT_ID,
specificationObjectIdList
);
}
public List<T> readObjectListWithFieldContainingAny(
String fieldName,
Iterable<?> fieldValue
) {
Bson filter = Filters.in(fieldName, fieldValue);
return readObjectList(filter);
}
private List<ObjectId> toSpecificationObjectIdList(
List<Specification> specificationsInFolder
) {
return specificationsInFolder.stream()
.map(Specification::getObjectId)
.collect(Collectors.toList());
}

Merging few collections of different objects into one

I got several collections of objects I'm receiving from external API. For this example, lets say they look like this. In real scenario I can't modify those classes.
#Data
public class ExternalResourceA {
private LocalDate date;
private String type;
}
#Data
public class ExternalResourceB {
private LocalDate date;
private String id;
}
And I'm having my own class, that combines those two based on few business rules that are not important here. Also, same as above, this is generated class, can't edit it. All I can do is write wrapper class and translate it later to original one.
#Data
public class MyResource {
private LocalDate date;
private String type;
private String id;
}
For this example, let's say this is the data I'm getting from API
private List<ExternalResourceA> externalCollectionA() {
final List<ExternalResourceA> collection = new ArrayList<>();
final var today = LocalDate.now();
for(int i = 0 ; i < 36; i++) {
collection.add(new ExternalResourceA(today.minusMonths(i), "type" + i));
}
Collections.shuffle(collection);
return collection;
}
private List<ExternalResourceB> externalCollectionB() {
final List<ExternalResourceB> collection = new ArrayList<>();
final var today = LocalDate.now();
for(int i = 0 ; i < 36; i++) {
collection.add(new ExternalResourceB(today.minusMonths(i), "id" + i));
}
Collections.shuffle(collection);
return collection;
}
Now, I need to combine data from ExternalResourceA and ExternalResourceB from year 2019 and save it into MyResource. Some of data might be missing, for example I got A for march 2019, but I dont have B for march 2019.
I managed to do that like this
void filterResources() {
final var resourceAin2019 = getAFrom2019(externalCollectionA());
final var resourceBin2019 = getBFrom2019(externalCollectionB());
final List<MyResource> myCollection = new ArrayList<>();
for(int i = 0; i <= 12; i++) {
final var resource = new MyResource();
findA(resourceAin2019, i).ifPresent(res -> {
resource.setDate(res.getDate());
resource.setType(res.getType());
});
findB(resourceBin2019, i).ifPresent(res -> {
resource.setId(res.getId());
});
myCollection.add(resource);
}
myCollection.forEach(System.out::println);
}
private Optional<ExternalResourceA> findA(List<ExternalResourceA> list, int index) {
return list.size() > index ? Optional.ofNullable(list.get(index)) : Optional.empty();
}
private Optional<ExternalResourceB> findB(List<ExternalResourceB> list, int index) {
return list.size() > index ? Optional.ofNullable(list.get(index)) : Optional.empty();
}
private List<ExternalResourceA> getAFrom2019(List<ExternalResourceA> resource) {
return resource.stream()
.filter(res -> res.getDate().isAfter(LocalDate.parse("2019-01-01") || res.getDate().isEqual(LocalDate.parse("2019-01-01")))
.sorted(Comparator.comparing(ExternalResourceA::getDate))
.collect(Collectors.toList());
}
private List<ExternalResourceB> getBFrom2019(List<ExternalResourceB> resource) {
return resource.stream()
.filter(res -> res.getDate().isAfter(LocalDate.parse("2019-01-01") || res.getDate().isEqual(LocalDate.parse("2019-01-01")))
.sorted(Comparator.comparing(ExternalResourceB::getDate))
.collect(Collectors.toList());
}
And it kinda works but even in this simple example, there is a lot of almost identical functions, that just operate on other classes. In real scenario, this will grow even more, as I got much more structures I need to deal with. I'm wondering if there isn't possibility to make this much cleanier and simplier?
Edit#
After further checking, my solution isn't working as I expected, its simplier to show than explain, here is result when I'm missing data from ExternalResourceB on March 2019, end empty value appears on last element instead.

Add null-like value to String in Java

I am writing test method like setTask(Task task). And Task object has several fields, e.g.
public String vehicle;
Method setTask should be used in different test-cases, so I'd like to have an options for this field to accept values:
null - the method should not do anything in this particulare case;
some string value - e.g. "", "Hello, World!", "Iso Isetta", ...
random - a value that indicates (as well as null indicates "no changes") that a random value should be selected for a drop-down list corresponding to this field.
So what can I do to make String to be SpecialString which could accept values null, random & some string value? (BTW: I don't want to set it to string value "RANDOM", and chech whether the value is equal to "RANDOM"-string)
UPDATE: I don't mean random like random value from a set of values, I mean random as well as null and this is for setTask() to handle random (select random from drop-down), and not to pass a random string from a set of values.
Pseudocode:
Task task = new Task();
task.vehicle = random; // as well as null
setTask(task)
in setTask(Task task):
if (task.vehicle == null) {
//skip
} else if (task.vehicle == random) {
// get possible values from drop-down list
// select one of them
} else {
// select value from drop-down list which is equal to task.vehicle
}
Don't assign a fixed String but use a Supplier<String> which can generate a String dynamically:
public Supplier<String> vehicleSupplier;
This, you can assign a generator function as you request:
static Supplier<String> nullSupplier () { return () -> null; }
static Supplier<String> fixedValueSupplier (String value) { return () -> value; }
static Supplier<String> randomSupplier (String... values) {
int index = ThreadLocalRandom.current().nextInt(values.length) -1;
return index > 0 && index < values.length ? values[index] : null;
}
In use, this looks like:
task.setVehicleSupplier(nullSupplier()); // or
task.setVehicleSupplier(fixedValueSupplier("value")); // or
task.setVehicleSupplier(randomSupplier("", "Hello, World!", "Iso Isetta"));
and you can get the String by
String value = task.vehicleSupplier().get();
or hide the implementation in a getter function
class Task {
// ...
private Supplier<String> vehicleSupplier;
public void setVehicleSupplier(Supplier<String> s) {
vehicleSupplier = s;
}
public String getVehicle() {
return vehicleSupplier != null ? vehicleSupplier.get() : null;
}
// ...
}
What you may want to do is to create an object that wraps a string as well as some information about whether or not it's a special value. Something along the lines of...
public class Special<T> {
public enum Type {
NOTHING, RANDOM, SPECIFIC
}
private final Type type;
private final T specificValue;
public Special(Type type, T specificValue) {
this.type = type;
this.specificValue = specificValue;
}
public Type getType() {
return type;
}
public T getSpecificValue() {
if (type != SPECIFIC) {
throw new IllegalStateException("Value is not specific");
}
return specificValue;
}
}
The class above could be used like so:
Special<String> a = new Special<>(Special.Type.NOTHING, null);
Special<String> b = new Special<>(Special.Type.SPECIFIC, "Hello");
if (b.getType() == Special.Type.RANDOM) {
// do something
}else if (b.getType() == Special.Type.SPECIFIC) {
String val = b.getSpecificValue();
// do something else
}
A slightly more polished variant of the thing above is probably the best way, but there is a way, a much uglier way, to do it using nothing but a String field.
What you could do is to have a "magical" string instance that behaves differently from all other string instances, despite having the same value. This would be done by having something like
static final String SPECIAL_VALUE_RANDOM = new String("random");
Note the use of the String constructor, which ensures that the string becomes a unique, non-interned instance. You can then say if (vehicle == SPECIAL_VALUE_RANDOM) { ... } (note the use of == instead of .equals()) to check if that specific instance (rather than any other string that says "random") was used.
Again, this is not a particularly good way of doing this, especially if you intend to do this more than once ever. I would strongly suggest something closer to the first way.

How to compare value of array of objects?

There is a collection of 20 objects of a POJO class. I Want to write a method that return objects with distinct value. Now this is my Pogo class
class Student {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName( String firstName ) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName( String lastName ) {
this.lastName = lastName;
}
}
Now i want some method which returns unique last names values. I could not understand which logic i have to put in this.
If you are using something like Eclipse, you can right-click the source and select Source > "Generate hashCode() and equals()...". Doing so will yield something like this:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (firstName == null) {
if (other.firstName != null)
return false;
} else if (!firstName.equals(other.firstName))
return false;
if (lastName == null) {
if (other.lastName != null)
return false;
} else if (!lastName.equals(other.lastName))
return false;
return true;
}
Then, you'll want to add your objects to an instance of Set, maybe HashSet. Sounds like you can just return the populated Set then.
See also this.
EDIT: Note that I am not suggesting to put all of this on the Student class. The code shown above goes on Student, but the method that returns the set of distinct students goes somewhere else.
EDIT 2: If you are only interested in unique last names, you could modify hashCode() and equals() to not consider first name, but I concede that this would be quite unintuitive and recommend to avoid this in any circumstance other than an academic exercise. So, more correct might be to layer on an instance of Comparator that only considers last name--see doc and this.
You can use an Arraylist, it has a built in function called .contains() which checks if the arrayList contains a specific value. So you would create an arrayList of last names and if it doesn't exist in the array list, just add it. See http://docs.oracle.com/javase/1.5.0/docs/api/java/util/ArrayList.html#contains(java.lang.Object)
You can try to use Set, if you need to get only one field, or Map, if you need to know object(student) with this field.
If you need to know all distinct Students (pair: first name + surname), you need to override getHashCode() and equals methods and use HashSet, HashMap
An easy way (for a beginner) to do this is just create a new array (same size of the input array). Then to loop through your array then compare every value to every other value in the array. If you can't find a match, then put this value in the new array.
Pseudo code:
public static Student[] GetUniqueLastNames(Student[] students){
Student[] retArray;//new array
for(i = 0; i < students.size; i++){
unique = true
for(j=0; j < students.size; j++){
if(i != j){//make sure its not comparing the same value
if(students[i].lastname.equals(students[j].lastname)){
unique = false
break
}
}
}
if(unique){
retArray[i] = students[i]
}
}
return retArray
}
Note: There are far better ways of doing this, but this is a nice basic way to do it if you're learning Java (or programming in general).
If you don't care about keeping the order of the objects, you can use a set:
public static <S extends Student> Collection<S> uniqByLastName(Collection<S> source){
TreeSet<S> result = new TreeSet<S>(new Comparator<S>() {
#Override
public int compare(S s1, S s2) {
return s1.getLastName().compareTo(s2.getLastName());
}
});
result.addAll(source);
return result;
}
If you care about the order
public static <S extends Student> Collection<S> uniqByLastName(Collection<S> source){
Collection<S> result = new ArrayList<S>();
Set<String> addedStudents = new HashSet<String>();
for(S student : source){
String lastName = student.getLastName();
if(!addedStudents.contains(lastName)){
result.add(student);
addedStudents.add(lastName);
}
}
return result;
}
If you want to modify the collection without returning a new one
public static <S extends Student> void uniqByLastName(Collection<S> source){
Set<String> addedStudents = new HashSet<String>();
Iterator<S> iterator = source.iterator();
while(iterator.hasNext()){
S student = iterator.next();
String lastName = student.getLastName();
if(addedStudents.contains(lastName)){
iterator.remove();
} else {
addedStudents.add(lastName);
}
}
}
If you are using Java 8, you can use lambda expression to solve it. Using following code snippet should solve your problem:
list.stream().collect(Collectors.toMap(Student::getLastName, p -> p, (p, q) -> p)).values();
Note: it will return first student with a given last name and as you might have already guessed, you don't need to override equals and hashcode.

Categories

Resources