I'm new to java and love the Stream API.
I got this loop:
List<FileTree> expanded = new ArrayList<>();
for(FileTree item : tree){
if(item.getType().equals("tree")){
expanded.addAll(getTreeOfSubStudPackage(item.getName()));
}
else{
expanded.add(item);
}
}
And I wonder if this could be converted to a neat stream. My first guess was the following:
tree.stream().map(fileTree -> {
if(fileTree.getType().equals("tree")){
return getTreeOfSubStudPackage(fileTree.getName());
}
else{
return fileTree;
}
}).collect(Collectors.toList());
It compiles fine. But is this recommended, have I even implemented a No-Go or is there a even nicer way?
And last but not least: Is there a overhead in .stream() that would make this improvement worthless?
Appendix
List<FileTree> getTreeOfSubStudPackage(...){
//...
}
class FileTree {
private String name;
private String type;
private String mode;
private String id;
//... Public Getter And Setter ...
}
you need to do map and then flatMap.
Arrays.asList(item) is to add the element to List to be flat in the next flatMap
tree.stream()
.map(item -> item.getType().equals("tree") ? getTreeOfSubStudPackage(item.getName()) : Arrays.asList(item))
.flatMap(Collection::stream)
.collect(Collectors.toList());
Related
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());
Now I have an object:
public class Room{
private long roomId;
private long roomGroupId;
private String roomName;
... getter
... setter
}
I want sort list of rooms by 'roomId', but in the meantime while room objects has 'roomGroupId' greator than zero and has same value then make them close to each other.
Let me give you some example:
input:
[{"roomId":3,"roomGroupId":0},
{"roomId":6,"roomGroupId":0},
{"roomId":1,"roomGroupId":1},
{"roomId":2,"roomGroupId":0},
{"roomId":4,"roomGroupId":1}]
output:
[{"roomId":6,"roomGroupId":0},
{"roomId":4,"roomGroupId":1},
{"roomId":1,"roomGroupId":1},
{"roomId":3,"roomGroupId":0},
{"roomId":2,"roomGroupId":0}]
As shown above, the list sort by 'roomId', but 'roomId 4' and 'roomId 1' are close together, because they has the same roomGroupId.
This does not have easy nice solution (maybe I am wrong).
You can do this like this
TreeMap<Long, List<Room>> roomMap = new TreeMap<>();
rooms.stream()
.collect(Collectors.groupingBy(Room::getRoomGroupId))
.forEach((key, value) -> {
if (key.equals(0L)) {
value.forEach(room -> roomMap.put(room.getRoomId(), Arrays.asList(room)));
} else {
roomMap.put(
Collections.max(value, Comparator.comparing(Room::getRoomId))
.getRoomId(),
value
.stream()
.sorted(Comparator.comparing(Room::getRoomId)
.reversed())
.collect(Collectors.toList())
);
}
});
List<Room> result = roomMap.descendingMap()
.entrySet()
.stream()
.flatMap(entry -> entry.getValue()
.stream())
.collect(Collectors.toList());
If you're in Java 8, you can use code like this
Collections.sort(roomList, Comparator.comparing(Room::getRoomGroupId)
.thenComparing(Room::getRoomId));
If not, you should use a comparator
class SortRoom implements Comparator<Room>
{
public int compare(Room a, Room b)
{
if (a.getRoomGroupId().compareTo(b.getRoomGroupId()) == 0) {
return a.getRoomId().compareTo(b.getRoomId());
}
return a.getRoomGroupId().compareTo(b.getRoomGroupId();
}
}
and then use it like this
Collections.sort(roomList, new SortRoom());
I have an Entity with multiple nested lists like this :
public class DataFile {
#Id
private int id;
private List<DataObject> datas = new ArrayList<>();
}
public class DataObject {
#Id
private String type;
private List<DataValue> values = new ArrayList<>();
}
public class DataValue {
#Id
private int id;
private String dataValue;
private LocalDateTime dataDate = LocalDateTime.now();
}
If I want a specific dataValue with DataObject.type = "speType" and DataValue.id = 2, I need this:
String value = dataFile.getDatas().forEach(t -> {
if(t.getType().equals("speType")){
t.getValues().forEach(v -> {
if(v.getId(2))
return v.getDataValue();
});
}
});
Is it possible to create a simple method ?
Thanks
For a pure "stream" solution :
dataFile.getDatas()
.stream()
.filter(t -> t.getType().equals("speType"))
.flatMap(t -> t.getValues().stream())
.filter(v -> v.getId()==2)
.map(DataValue::getDataValue)
.findFirst();
I assumed that by "if(v.getId(2))" you meant "if(v.getId()==2)", if not you can easily change the code above.
You could try something like that :
Optional<String> optValue =
dataFile.getDatas()
.stream()
.filter(t -> t.getType().equals("specType"))
.flatMap(t -> t.getValues().stream()
.filter(v -> v.getId() == 2)
.map(DataValue::getDataValue)
)
.findFirst();
Stream.findFirst() allows to exit of the processing early as soon as one element of it matches to the conditions. Similarly to what you did in your actual code.
Note that Stream.findFirst() returns an Optional.
Either unwrap the object after the terminal operation such as :
String value = optValue.orElse("default value");
Or do in this stream itself :
String value =
...
.findFirst();
.orElse("defaultValue")
public class Product {
private String name;
private List<Image> images;
//getters and setters
public class Images {
private String url;
private List<Thumbnail> thumbnails;
// getters and setters
public class Thumbnail{
//getters and setters
private String url;
}
}
}
}
I have this class. I need to fetch product name, url of the first image and url of the first thumbnail. I also need to make sure product, images and thumbnails are non-empty.
This is how I am trying to do it:
Optional.ofNullable(product).ifPresent(productData -> {
response.setProductName(productData.getName());
Optional.ofNullable(productData.getImages()).ifPresent(images -> {
images.stream().findFirst().ifPresent(image -> {
response.setImageUrl(image.getUrl());
Optional.ofNullable(image.getThumbnails()).ifPresent(thumbnails -> {
thumbnails.stream().findFirst().ifPresent(thumbnail -> {
response.getThumbnailUrl();
});
});
});
});
});
Is there a better way?
You shouldn't ever have null lists. An empty list represents the same thing without having to introduce a null check. That lets you get rid of two ifPresent checks.
Optional.ofNullable(product).ifPresent(productData -> {
response.setProductName(productData.getName());
productData.getImages().stream().findFirst().ifPresent(image -> {
response.setImageUrl(image.getUrl());
image.getThumbnails().stream().findFirst().ifPresent(thumbnail -> {
response.getThumbnailUrl();
});
});
});
Optionals and streams aren't really doing you any favors. Don't use them just because they exist. Consider just using regular if statements.
if (product == null) {
return;
}
if (!product.getImages().isEmpty()) {
Image image = product.getImages().get(0);
response.setImageUrl(image.getUrl());
if (!image.getThumbnails().isEmpty()) {
response.getThumbnailUrl();
}
}
Alternatively, you could simulate stream().findFirst().ifPresent() with for loops that break after the first iteration.
if (product == null) {
return;
}
for (Image image: product.getImages()) {
response.setImageUrl(image.getUrl());
for (Thumbnail thumbnail: image.getThumbnails()) {
response.getThumbnailUrl();
break;
}
break;
}
#John's answer is the correct one. If you can, return empty lists.
It might also be that you want to distinguish between not having any items and not having a result (in your case it doesn't make much sense, but we're talking hypothetically). Then return Optional<List<T>> instead of returning null then converting it. Then #Johannes' answer is the correct one.
Another way of thinking about the problem is, if you have no control over the return values, to convert it to a stream to chain the calls:
Optional.ofNullable(possiblyNullProduct).stream()
.peek(product -> response.setProductName(product.getName()))
.map(Product::getImages)
.filter(Objects::nonNull)
.map(images -> images.stream().findFirst())
.filter(Optional::isPresent).map(Optional::get)
.peek(image -> response.setImageUrl(image.getUrl())
.map(Image::getThumbnails)
.filter(Objects::nonNull)
.map(thumbnails -> thumbnails.stream().findFirst())
.filter(Optional::isPresent).map(Optional::get)
.forEach(thumbnail -> response.getThumbnailUrl());
Optional::stream was added in Java 9.
This is just another solution, by no means a better solution. I would welcome any comments on the performance.
Another option to get the first item of each list is to convert to an Optional and back into a Stream:
.map(Product::getImages)
.filter(Objects::nonNull)
.flatMap(List::stream).findFirst().stream() // <- changed here
.peek(image -> ...)
And you can also change out the last three lines in a similar way:
.map(Image::getThumbnails)
.filter(Objects::nonNull)
.flatMap(List::stream).findFirst() // <- from here
.ifPresent(thumbnail -> response.getThumbnailUrl());
Yes, flatMap will help here:
Optional<Product> optProduct = Optional.ofNullable(product);
optProduct.ifPresent(p -> response.setProductName(p.getName()));
Optional<Image> optImage = optProduct.flatMap(p -> Optional.ofNullable(p.getImages()))
.stream().flatMap(il -> il.stream()).findFirst();
optImage.ifPresent(i -> response.setImageUrl(i.getUrl()));
optImage.flatMap(i -> Optional.ofNullable(i.getThumbnails()))
.stream().flatMap(tl -> tl.stream()).findFirst()
.ifPresent(t -> response.getThumbnailUrl()); // WTF?
Node that Optional.stream() was added in Java 9.
I don't think the Optionals are helping much here. I would clean up the code by abstracting the iteration logic, like this:
private <T> void findFirst(List<T> list, Consumer<T> action) {
if (list != null) {
list.stream()
.findFirst()
.ifPresent(action);
}
}
if (product != null) {
response.setProductName(productData.getName());
findFirst(productData.getImages(), image -> {
response.setImageUrl(image.getUrl());
findFirst(image.getThumbnails(), thumbnail -> response.setThumbnailUrl(thumbnail.getUrl()));
});
}
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.