I'm having simple DataStructure
public class DataStructure {
private String key;
private String value;
//get, set
}
And I need to return value from `List' based on key and I want to do it Java8 way, with streams. I think code speaks for himself:
public class Main {
public static void main(String args[]) {
List<DataStructure> dataList = new ArrayList<>();
dataList.add(new DataStructure("first", "123"));
dataList.add(new DataStructure("second", "456"));
System.out.println(findValueOldSchool(dataList, "third")); //works ok
System.out.println(findValueStream(dataList, "third")); //throws NoSuchElementException
}
static String findValueOldSchool(List<DataStructure> list, String key) {
for (DataStructure ds : list) {
if (key.equals(ds.getKey())) {
return ds.getValue();
}
}
return null;
}
static String findValueStream(List<DataStructure> list, String key) {
return list.stream()
.filter(ds -> key.equals(ds.getKey()))
.findFirst()
.get().getValue();
}
}
How can I modify findValueStream() to not throw NoSuchValueException while I search for non existing key? I don't want to return Optional<String> because this method is already used in a lot of places in project. And, ofcourse I'v tried map, ifPresent, anyMatch, just can't find the right way to do it.
You shall use Stream.findFirst with an Optional.orElse like :
static String findValueStream(List<DataStructure> list, String key) {
return list.stream() // initial Stream<DataStructure>
.filter(ds -> key.equals(ds.getKey())) // filtered Stream<DataStructure>
.map(DataStructure::getValue) // mapped Stream<String>
.findFirst() // first Optional<String>
.orElse(null); // or else return 'null'
}
Note: The above uses the Stream.map to map the stream of DataStructure to a corresponding stream of value.
use orElse to return a default value if the Optional has an empty state:
This also means you'll need to map to DataStructure::getValue first like so:
return list.stream()
.filter(ds -> key.equals(ds.getKey()))
.findFirst()
.map(DataStructure::getValue)
.orElse(null);
just replacing get with orElse will not suffice:
return list.stream()
.filter(ds -> key.equals(ds.getKey()))
.findFirst()
.orElse(null)
.getValue();
As this time you'll get a NullPointerException instead of a NoSuchElementException in the case of an empty optional.
Related
I'm trying to filter a list of objects say BOLReference based on a key which is present in the inner object of one of the List of Objects of this BOLReference.
List<BOLReference> bolRef = complianceMongoTemplate.find(findQuery, BOLReference.class);
Optional<BOLReference> bref = bolRef.stream().filter(br -> br.getWorkflowExceptions().stream()
.filter(bk -> bk.getBusinessKeyValues().get(businessKey)
.equalsIgnoreCase("ABCD1234"))).findFirst();
While doing so I'm getting error as :
Cannot convert from Stream<WorkFlowExceptions> to boolean
My BOLReference looks like below:
private String ediTransmissionId;
private List<WorkflowExceptions> workflowExceptions;
And my WorkflowExceptions looks like:
private String category;
private Map<String,String> businessKeyValues;
Optional<BOLReference> bref = bolRef.stream()
.filter(br ->
br.getWorkflowExceptions().stream()
.anyMatch(bk ->
bk.getBusinessKeyValues().get(businessKey)
.equalsIgnoreCase("ABCD1234")))
.findFirst();
Failing anyMatch I think.
A filter clause does not return a boolean but a stream. This causes a problem because your first filter expects a boolean result, but gets a stream from the second filter. You can use anyMatch instead to achieve the desired result.
You can make stream expressions more readable by formatting them on more lines and extracting complex logic into methods.
You could fix & refactor it like this:
String businessKey = null;
String targetValue = "ABCD1234";
Optional<BOLReference> bref = bolReferences.stream()
.filter(br -> br. hasBusinessKey(businessKey, targetValue))
.findFirst();
And:
public class BOLReference {
// ...
public boolean hasBusinessKey(String key, String value) {
return getWorkflowExceptions().stream()
.anyMatch(wfe -> wfe.hasBusinessKey(key, value));
}
}
And:
public class WorkflowExceptions {
// ...
public boolean hasBusinessKey(String key, String value) {
return getBusinessKeyValues().get(key).equalsIgnoreCase(value);
}
}
How can I convert Optional List object from one type to another, for an example
Optional<List<ProductMultipleOptionViewModel>> productOptionType1 // One type
Optional<List<ProductMultipleOption>> productOptionType2 // Other type
ProductMultipleOptionViewModel
Type 1
#Introspected
public record ProductMultipleOptionViewModel(
ProductOptionViewModel productOption,
String optionName) {
}
Type 2
#Introspected
public record ProductMultipleOption(
ProductOptionViewModel productOption,
String optionName) {
}
I want to convert from Optional<List<ProductMultipleOption>>to other Optional<List<ProductMultipleOptionViewModel>>. I tried the below code
Optional<List<ProductMultipleOptionViewModel>> conveertItem = Optional.ofNullable(product.getProductMultipleOption())
.orElseGet(null)
.stream()
.map(option -> {
return new ProductMultipleOptionViewModel(
ProductOptionViewModel.valueOf(//Access the option value//), //access the option value//
);
})
.collect(Collectors.toList());
With the above code, I am not able to access the option value inside map method
If product.getProductMultipleOption() is null return null or empty list.
You should rarely use Optional and Collections (like List or Set) together. Instead you should work with empty Collections. Also keep in mind that Optionals should not really be used for conditional logic, but rather as return values.
Either using a normal if statement:
List<ProductMultipleOptionViewModel> conveertItem = new ArrayList<>();
if (product.getProductMultipleOption() != null) {
for(ProductMultipleOption option : product.getProductMultipleOption()) {
conveertItem.add(new ProductMultipleOptionViewModel(
ProductOptionViewModel.valueOf(option)
));
}
}
Another variant:
List<ProductMultipleOption> list = product.getProductMultipleOption();
if (list == null) {
list = Collections.emptyList();
}
List<ProductMultipleOptionViewModel> conveertItem = new ArrayList<>();
for(ProductMultipleOption option : list) {
conveertItem.add(new ProductMultipleOptionViewModel(
ProductOptionViewModel.valueOf(option)
));
}
Or if you really want to use Streams and Optionals (matter of taste):
List<ProductMultipleOptionViewModel> conveertItem = Optional.ofNullable(product.getProductMultipleOption())
.map(List::stream)
.orElseGet(Stream::empty)
.map(option -> new ProductMultipleOptionViewModel(
ProductOptionViewModel.valueOf(option)
))
.collect(Collectors.toList());
Now that's a lot of code for simply converting a nullable List. So why not return an empty List in the first place? Change product.getProductMultipleOption() to something like this:
public List<ProductMultipleOption> getProductMultipleOption() {
List<ProductMultipleOption> list = ...; // your current logic for getting the list
return list == null ? Collections.emptyList() : list;
}
That way you never have to worry about null checks. Because you're simply working with an empty collection wherever you're calling getProductMultipleOption().
It helps to think about dealing with nulls/empty optionals separately from dealing with the list. The code below deals with nulls using the Optional.map() method, which returns an empty optional (of the appropriate return type) if the given argument is empty; otherwise, it applies the mapping function on the list.
Optional<List<ProductMultipleOptionViewModel>> convertedItem =
Optional.ofNullable(product.getProductMultipleOption())
.map(list -> list.stream()
.map(option -> new ProductMultipleOptionViewModel(
option.productOption(),
option.optionName()))
.collect(Collectors.toList()));
Might not be the best way to do whatever you're doing... but to answer your question if you're trying to work with what you've got and keep it minimal:
private List<ProductMultipleOption> getProductOptionViewModelList() {
/* simulating return of a list that could be null. */
return null;
}
private Optional<List<ProductMultipleOption>> getProductMultipleOptionNull() {
/* simulating return of an optional list. */
return Optional.empty();
}
private static class ProductOptionViewModel { }
public record ProductMultipleOptionViewModel(
ProductOptionViewModel productOption,
String optionName) {
}
public record ProductMultipleOption(
ProductOptionViewModel productOption,
String optionName) {
}
/*
Create your own methods to convert the models.
Replace the variables with whichever method is available to get the name:
(inputOption.productOption, inputOption.optionName)
(inputOption.productOption(), inputOption.optionName())
. (inputOption.getProductOption(), inputOption.getOptionName())
*/
private ProductMultipleOptionViewModel convertToMultipleOptionViewModel(
ProductMultipleOption inputOption) {
return new ProductMultipleOptionViewModel(
inputOption.productOption,
inputOption.optionName);
}
private ProductMultipleOption convertToMultipleOption(
ProductMultipleOptionViewModel inputOption) {
return new ProductMultipleOption(
inputOption.productOption,
inputOption.optionName);
}
/*
If the list you're getting is Optional<List<ProductOptionViewModel>>
and you want List<ProductMultipleOptionViewModel>
*/
List<ProductMultipleOptionViewModel> convertedFromOptionalList =
getProductMultipleOptionNull()
.stream()
.flatMap(Collection::stream)
.map(this::convertToMultipleOptionViewModel)
.toList();
/*
If the list you're getting is List<ProductOptionViewModel>
and you want List<ProductMultipleOptionViewModel>
*/
List<ProductMultipleOptionViewModel> convertedFromNullableList = Optional
.ofNullable(getProductOptionViewModelList())
.stream()
.flatMap(Collection::stream)
.map(this::convertToMultipleOptionViewModel)
.toList();
/*
If for some reason you're trying to get the list as
Optional<List<ProductOptionViewModel>> you can wrap
them with Optional.of() :
*/
Optional<List<ProductMultipleOptionViewModel>> convertedFromOptionalList = Optional
.of(Optional.ofNullable(getProductOptionViewModelList())
.stream()
.flatMap(Collection::stream)
.map(this::convertToMultipleOptionViewModel)
.toList());
Optional<List<ProductMultipleOptionViewModel>> convertedFromNullableList = Optional
.of(getProductMultipleOptionNull()
.stream()
.flatMap(Collection::stream)
.map(this::convertToMultipleOptionViewModel)
.toList());
I am having the below given data structure.
In the given code block, Im trying to get Object Dc from the given HashMap dealCommits.
The piece of code will work if the map has got the object associated with the given key (cNumber) is available.
My problem is how to handle the null pointer in line (//5) when the given key is not found in the map.
It will be a great help if somebody can throw some light to amend this stream based code to handle the exception.
public class Dc {
private String cNumber;
private Optional<List<pTerms>> pTerms;
}
public class Dd {
private String cParty;
//Dc:cNumber is the Key
private Optional<Map<String,Dc>> dealCommits;
}
public class PTerms {
private String pType;
}
public String check(String tMonth,String cNumber,Dd dDetails)
{
Optional<DealPricingTerms> dealPricingTerms = dDetails
.getDealCommits().get()
.get(cNumber)
.getPTerms().get().stream() //5
.filter(dealPricingTerm ->
tMonth.equals(dealPricingTerm.getDeliveryPeriod()))
.findFirst();
return dealPricingTerms.isPresent()? "Success" : "failed";
}
You shouldn't call get() on your Optionals - and when you use Optional.map you have a nice way to wrap a null result from a Map get into another Optional:
Optional<PTerms> dealPricingTerms = dDetails
.getDealCommits().map(c -> c.get(cNumber))
.flatMap(dc -> dc.getPTerms())
.map(l -> l.stream())
.flatMap(s ->
s.filter(dealPricingTerm ->
tMonth.equals(dealPricingTerm.getDeliveryPeriod()))
.findFirst());
You have to add filter to check null before your condition filter.
.filter(Objects::nonNull)
for example,
List<String> carsFiltered = Optional.ofNullable(cars)
.orElseGet(Collections::emptyList)
.stream()
.filter(Objects::nonNull) //filtering car object that are null
.map(Car::getName) //now it's a stream of Strings
.filter(Objects::nonNull) //filtering null in Strings
.filter(name -> name.startsWith("M"))
.collect(Collectors.toList()); //back to List of Strings
I have a list of valid values for a type:
Set<String> validTypes = ImmutableSet.of("TypeA", "TypeB", "TypeC");
From a given list I want to extract the first value which has a valid type. In this scenario I would write something of this sort:
public class A{
private String type;
private String member;
}
List<A> classAList;
classAList.stream()
.filter(a -> validTypes.contains(a.getType()))
.findFirst();
However I would like to give preference to TypeA, i.e. if classAList has TypeA and TypeB, I want the object which has typeA. To do this one approach I've is:
Set<String> preferredValidTypes = ImmutableSet.of("TypeA");
classAList.stream()
.filter(a -> preferredValidTypes.contains(a.getType()))
.findFirst()
.orElseGet(() -> {
return classAList.stream()
.filter(a -> validTypes.contains(a.getType()))
.findFirst();
}
Is there a better approach?
filter list by type, order by type, collect to list, then just get first element
List<A> collect = classAList.stream()
.filter(a -> validTypes.contains(a.getType()))
.sorted(Comparator.comparing(A::getType))
.collect(Collectors.toList());
System.out.println(collect.get(0));
You can use a custom comparator like:
Comparator<A> comparator = (o1, o2) -> {
if (preferredValidTypes.contains(o1.getType()) && !preferredValidTypes.contains(o2.getType())) {
return 1;
} else if (!preferredValidTypes.contains(o1.getType()) && preferredValidTypes.contains(o2.getType())) {
return -1;
} else {
return 0;
}
};
to sort the list and then findFirst from that list with your conditiion.
i don't like the answers already given which use Comparator. Sorting is an expensive operation. You can do it with one walk through the list. Once you find a preferred value, you can break out, otherwise you continue to the end to find a valid.
In this case anyMatch can provide the possibility to break out from the stream processing:
MyVerifier verifier=new MyVerifier(validTypes,preferredValidTypes);
classAList.stream()
.anyMatch(verifier);
System.out.println("Preferred found:"+verifier.preferred);
System.out.println("Valid found:"+verifier.valid);
public static class MyVerifier implements Predicate<A> {
private Set<String> validTypes;
private Set<String> preferredValidTypes;
A preferred=null;
A valid=null;
public MyVerifier(Set<String> validTypes, Set<String> preferredValidTypes) {
super();
this.validTypes = validTypes;
this.preferredValidTypes = preferredValidTypes;
}
#Override
public boolean test(A a) {
if(preferred==null && preferredValidTypes.contains(a.getType())) {
preferred=a;
// we can stop because we found the first preferred
return true;
} else if(valid==null && validTypes.contains(a.getType())) {
valid=a;
}
return false;
}
}
One can, of course, define two lists, one with all valid types, and one with the preferred types.
However, here is another approach. Define one list, or actually, a Map, with the keys being the valid types, and the boolean values being whether the type is preferred.
Map<String, Boolean> validTypes = ImmutableMap.of(
"TypeA", false,
"TypeB", false,
"TypeC", true
);
Using AtomicReference
One option is the following:
AtomicReference<A> ref = new AtomicReference<>();
listOfAs.stream()
.filter(t -> validTypes.containsKey(t.getType()))
.anyMatch(t -> validTypes.get(ref.updateAndGet(u -> t).getType()));
AtomicReference now contains a preferred A if available, or another valid A, or if the stream is empty, then it contains null. This stream operation short-circuits if an A with a preferred type is found.
The drawback of this option is that it creates side-effects, which is discouraged.
Using distinct()
Another suggestion would be the following. It uses the same map structure, using a boolean to indicate which values are preferred. However, it does not create side effects.
Map<Boolean, A> map = listOfAs.stream()
.filter(t -> validTypes.containsKey(t.getType()))
.map(t -> new Carrier<>(validTypes.get(t.getType()), t))
.distinct()
.limit(2)
.collect(Collectors.toMap(Carrier::getKey, Carrier::getValue));
It works as follows.
filter discards any element that is not a valid type.
Then, each element is mapped to a Carrier<Boolean, A> instance. A Carrier is a Map.Entry<K, V> which implements its equals and hashCode methods regarding only the key; the value does not matter. This is necessary for the following step,
distinct(), which discards any duplicate element. This way, only one preferred type and only one valid type is found.
We limit the stream to have 2 elements, one for each boolean. This is because the stream, which is lazy, stops evaluating after both booleans are found.
At last, we collect the Carrier elements into a Map.
The map contains now the following elements:
Boolean.TRUE => A with a preferred type
Boolean.FALSE => A with a valid type
Retrieve the appropriate element using
A a = map.getOrDefault(true, map.get(false)); // null if not found
Well you have to take care into account that sorting is stable, that is equal elements will appear in the same order as the initial source - and you need that to correctly get the first element from that List<A> that will satisfy your requirement, thus:
String priorityType = "TypeB";
Stream.of(new A("TypeA", "A"),
new A("TypeB", "B"),
new A("TypeC", "C"))
.sorted(Comparator.comparing(A::getType, Comparator.comparing(priorityType::equals)).reversed())
.filter(x -> validTypes.contains(priorityType))
.findFirst()
.orElseThrow(RuntimeException::new);
In Java8 you can use streams:
public static Carnet findByCodeIsIn(Collection<Carnet> listCarnet, String codeIsIn) {
return listCarnet.stream().filter(carnet -> codeIsIn.equals(carnet.getCodeIsin())).findFirst().orElse(null);
}
Additionally, in case you have many different objects (not only Carnet) or you want to find it by different properties (not only by cideIsin), you could build an utility class, to ecapsulate this logic in it:
public final class FindUtils {
public static <T> T findByProperty(Collection<T> col, Predicate<T> filter) {
return col.stream().filter(filter).findFirst().orElse(null);
}
}
public final class CarnetUtils {
public static Carnet findByCodeTitre(Collection<Carnet> listCarnet, String codeTitre) {
return FindUtils.findByProperty(listCarnet, carnet -> codeTitre.equals(carnet.getCodeTitre()));
}
public static Carnet findByNomTitre(Collection<Carnet> listCarnet, String nomTitre) {
return FindUtils.findByProperty(listCarnet, carnet -> nomTitre.equals(carnet.getNomTitre()));
}
public static Carnet findByCodeIsIn(Collection<Carnet> listCarnet, String codeIsin) {
return FindUtils.findByProperty(listCarnet, carnet -> codeIsin.equals(carnet.getCodeIsin()));
}
}
If you have preferred valid types in other collection so you can follow this code.
Map<String,A> groupByType = classAList
.stream()
/* additional filter to grouping by valid types.*/
//.filter(a->validTypes.contains(a.getType()))
.collect(Collectors.toMap(A::getType, Function.identity(),(v1, v2)->v1));
then use:
A result = preferredValidTypes
.stream()
.map(groupByType::get)
.findFirst()
.orElseThrow(RuntimeException::new);
or just group by preferred valid types
A result2 = classAList
.stream()
.filter(a -> preferredValidTypes.contains(a.getType()))
.collect(Collectors.toMap(A::getType, Function.identity(), (v1, v2) -> v1))
.entrySet()
.stream()
.findFirst()
.map(Map.Entry::getValue)
.orElseThrow(RuntimeException::new);
Given i want to filter a List of Key-Value objects.
My (Document)-Object from the example below looks like this
{
"attributeEntityList" : [
{key: 'key1', value: 'somevalue1'},
{key: 'key2', value: 'somevalue2'},
{key: 'key3', value: 'somevalue3'}
]
}
When I pass in a list of the following keys ["key1", "key2", "key3"], I expect my function to return the whole given List of attributes.
When I pass in a list of the following keys ["key1", "key2"], I expect my function to return a list of Attributes with the given key-names.
When I pass in a list of the following keys ["key1", "key2", "faultyKey"], I expect my function to return an Empty list.
My imperative-style solution looks like this and it works okay:
private List<AttributeEntity> getAttributeEntities(List<String> keys, Document value) {
final List<AttributeEntity> documentAttributeList = value.getAttributeEntityList();
final List<AttributeEntity> resultList = new ArrayList<>();
for(String configKey: keys){
boolean keyInAttribute = false;
for(AttributeEntity documentAttribute : documentAttributeList){
if(configKey.equals(documentAttribute.getAttribute_key())){
keyInAttribute = true;
resultList.add(documentAttribute);
break;
}
}
if(!keyInAttribute){
resultList.clear();
break;
}
}
return resultList;
}
For education and fun (and maybe better scaling) I'd like to know how to convert this piece of Code into a solution using the new Java 8 streaming-api.
This is what I came up with, converting my pre-Java8-code to Java8.
To my eyes it looks much more concise and it's shorter. But it does not, what I expect it to do :/
I'm realy struggling implementing the third bulletpoint of my requirements.
It always returns all (found) Attributes, even when i pass in a not existant key.
private List<AttributeEntity> getAttributeEntities(List<String> keys, Document value) {
final List<AttributeEntity> documentAttributeList = value.getAttributeList();
return documentAttributeList.stream()
.filter(attribute ->
keys.contains(attribute.getAttribute_key())
).collect(Collectors.toList());
}
I'm thinking of implementing my own custom Collector.
Since my Collector should only return the List, when the collected results contain each given key at least once.
Any other Idea on how to achieve that?
This solution passes my tests.
But it feel's like i'm putting the cart before the horse.
It's neither concise nor short or elegant any more.
private List<AttributeEntity> getAttributeEntities(List<String> keys, Document value) {
final List<AttributeEntity> documentAttributeList = value.getAttributeList();
return documentAttributeList.stream()
.filter(attribute ->
keys.contains(attribute.getAttribute_key())
)
.collect(Collectors.collectingAndThen(Collectors.toList(), new Function<List<AttributeEntity>, List<AttributeEntity>>() {
#Override
public List<AttributeEntity> apply(List<AttributeEntity> o) {
System.out.println("in finisher code");
if (keys.stream().allMatch(key -> {
return o.stream().filter(attrbiute -> attrbiute.getAttribute_key().equals(key)).findAny().isPresent();
})) {
return o;
} else {
return new ArrayList<AttributeEntity>();
}
}
}));
}
First of all I must say that I'm also new at Java 8 features, so I'm not familiar with everything, and not very used to functional programming. I tried a different approach, dividing it all into some methods.
Here it is:
public class Main {
private static List<AttributeEntity> documentAttributeList;
static {
documentAttributeList = new ArrayList<>();
documentAttributeList.add(new AttributeEntity("key1", "value1"));
documentAttributeList.add(new AttributeEntity("key2", "value2"));
documentAttributeList.add(new AttributeEntity("key3", "value3"));
}
public static void main(String[] args) {
Main main = new Main();
List<AttributeEntity> attributeEntities = main.getAttributeEntities(Arrays.asList("key1", "key2"));
for (AttributeEntity attributeEntity : attributeEntities) {
System.out.println(attributeEntity.getKey());
}
}
private List<AttributeEntity> getAttributeEntities(List<String> keys) {
if(hasInvalidKey(keys)){
return new ArrayList<>();
} else {
return documentAttributeList.stream().filter(attribute -> keys.contains(attribute.getKey())).collect(toList());
}
}
private boolean hasInvalidKey(List<String> keys) {
List<String> attributeKeys = getAttributeKeys();
return keys.stream().anyMatch(key -> !attributeKeys.contains(key));
}
private List<String> getAttributeKeys() {
return documentAttributeList.stream().map(attribute -> attribute.getKey()).collect(toList());
}
}
If a document can never have multiple attributes with the same name, I think you can do it like this (don't have a compiler handy to try):
Map<String, AttributeEntity> filteredMap=value.getAttributeEntityList().stream()
.filter(at->keys.contains(at.getKey()))
.collect(toMap(at->at.getKey(), at->at));
return filteredMap.keySet().containsAll(keys)
? new ArrayList<>(filteredMap.values())
: new ArrayList<>();
If multiple attributes per name are allowed, you would have to use groupingBy instead of toMap. You can, of course, rewrite this with collectingAndThen but I think it would be less clear.
I came up with something.
I don't know if it it the most elegant solution but at least it works and i can reason about it.
private List<AttributeEntity> getAttributeEntities(List<String> keys, Document value) {
final List<AttributeEntity> documentAttributeList = value.getAttributeList();
boolean allKeysPresentInAnyAttribute = keys.stream()
.allMatch(key ->
documentAttributeList.stream()
.filter(attrbiute ->
attrbiute.getAttribute_key().equals(key)
)
.findAny()
.isPresent()
);
if (allKeysPresentInAnyAttribute) {
return documentAttributeList.stream()
.filter(attribute ->
keys.contains(attribute.getAttribute_key())
)
.collect(Collectors.toList());
}
return new ArrayList<>();
}
Any hints or comments greatly appreciated.