I have the following classes:
package com.test.api;
public class Driver {
private String name;
private int age;
private String gender;
// getters, setters
}
package com.test.api;
import java.util.List;
public class Vehicle {
private String name;
private String make;
private int modelyear;
private List<Driver> drivers;
// getters, setters
}
package com.test.api;
public class Data {
private String driverName;
private String vehicleName;
private String gender;
private boolean isDriverEligible;
private boolean isRecentVehicle;
// getters, setters
}
and I am currently doing this in traditional way without lamdas and streams:
private static List<Data> map(List<Vehicle> vehicleList) {
List<Data> dataList = null;
for (Vehicle vehicle : vehicleList) {
if ("BMW".equalsIgnoreCase(vehicle.getName())) {
dataList = new ArrayList<>();
for (Driver driver : vehicle.getDrivers()) {
if (driver.getAge() > 18 && vehicle.getModelyear() > 2016) {
Data data = new Data();
data.setDriverName(driver.getName());
data.setLuxuryVehicle(true);
data.setVehicleName(vehicle.getName());
data.setDriverEligible(true);
data.setRecentVehicle(true);
dataList.add(data);
}
if (driver.getAge() > 18 && vehicle.getModelyear() < 2016) {
Data data = new Data();
data.setDriverName(driver.getName());
data.setLuxuryVehicle(true);
data.setVehicleName(vehicle.getName());
data.setDriverEligible(true);
data.setRecentVehicle(false);
dataList.add(data);
}
}
List<Response> response = service.update(dataList, vehicle);
}
}
return dataList;
}
So I wanted to iterate through List of Drivers for each Vehicle and based on the conditions, I need to map some data. And then once all the data is populated to the "Data" List, I am updating it in to a service. Everything looks good but I wanted to refactor it using java 8.How can I do it?
Prior notice: If you are running this against a database, it's HIGHLY RECOMMENDED to do that with some SQL or stored procedures. Messing with data in the application is not considered a good practice unless it's absolutely necessary.
The idea of using stream is to describe filtering and mapping in distinct blocks.
Let's start simple, from the inner loop.
private static List<Data> map(List<Vehicle> vehicleList) {
List<Data> dataList = new ArrayList<>();
vehicleList.stream().filter(vehicle -> "BMW".equalsIgnoreCase(vehicle.getName())).forEach(vehicle -> {
dataList.addAll(
return vehicle.getDrivers().stream()
.filter(driver -> driver.getAge() > 18)
.map(driver -> {
Data data = new Data();
data.setDriverName(driver.getName());
data.setLuxuryVehicle(true);
data.setVehicleName(vehicle.getName());
data.setDriverEligible(true);
if (vehicle.getModelyear() > 2016) {
data.setRecentVehicle(true);
return data;
}
if (vehicle.getModelyear() < 2016) {
data.setRecentVehicle(false);
return data;
}
return null;
})
.filter(it -> it != null)
.collect(Collectors.toList())
);
List<Response> response = service.update(dataList, vehicle);
});
return dataList;
}
We have replaced for loops with a stream. Since we are going to add it into a list, we can map the elements into target elements, and then collect them and call addAll(.) to append them to the list.
Then we can make it to the outer loop:
private static List<Data> map(List<Vehicle> vehicleList) {
List<Data> dataList = new ArrayList<>();
vehicleList.stream().filter(vehicle -> "BMW".equalsIgnoreCase(vehicle.getName())).forEach(vehicle -> {
dataList.addAll(
return vehicle.getDrivers().stream()
.filter(driver -> driver.getAge() > 18)
.map(driver -> {
Data data = new Data();
data.setDriverName(driver.getName());
data.setLuxuryVehicle(true);
data.setVehicleName(vehicle.getName());
data.setDriverEligible(true);
if (vehicle.getModelyear() > 2016) {
data.setRecentVehicle(true);
return data;
}
if (vehicle.getModelyear() < 2016) {
data.setRecentVehicle(false);
return data;
}
return null;
})
.filter(it -> it != null)
.collect(Collectors.toList())
);
List<Response> response = service.update(dataList, vehicle);
});
return dataList;
}
By simply replacing the for loop with a stream and substitute it with forEach is enough. We can also filter out the unwanted elements before forEach with filter.
Here's is how I would do it:
static void inJava8(List<Vehicle> vehicleList) {
Predicate<Vehicle> isBMW = vehicle -> "BMW".equalsIgnoreCase(vehicle.getName());
Predicate<Vehicle> isRecentvehicle = vehicle -> vehicle.getModelyear() > 2016;
Map<Boolean, List<Vehicle>> isRecentToVehicleMap = vehicleList.stream()
.filter(isBMW::test)
.collect(Collectors.partitioningBy(isRecentvehicle));
isRecentToVehicleMap.get(true).stream()
.forEach(vehicle -> processVehicle(vehicle, Test::recentBMWDataCOnstructor));
isRecentToVehicleMap.get(false).stream()
.forEach(vehicle -> processVehicle(vehicle, Test::notRecentBMWDataCOnstructor));
}
public static void processVehicle(Vehicle vehicle, Function<Driver, Data> function) {
// this predicate should be at class level to avoid creating per vehicle
Predicate<Driver> isAgeEligile = driver -> driver.getAge() > 18;
List<Data> dataList = vehicle.getDrivers()
.stream()
.filter(isAgeEligile::test)
.map(function::apply)
.collect(Collectors.toList());
service.update(dataList, vehicle);
}
public static Data recentBMWDataCOnstructor(Driver driver) {
Data data = new Data();
data.setDriverName(driver.getName());
data.setLuxuryVehicle(true);
data.setVehicleName("BMW");
data.setDriverEligible(true);
data.setRecentVehicle(false);
return data;
}
public static Data notRecentBMWDataCOnstructor(Driver driver) {
Data data = new Data();
data.setDriverName(driver.getName());
data.setLuxuryVehicle(true);
data.setVehicleName("BMW");
data.setDriverEligible(true);
data.setRecentVehicle(false);
return data;
}
final Vehicle[] vehicle = new Vehicle[1];
vehicleList.stream()
.filter(v -> "BMW".equalsIgnoreCase(v.getName()))
.map(v -> {vehicle[0] = v; return v;})
.flatMap(v -> v.getDrivers().stream())
.filter(driver -> driver.getAge() > 18)
.map(driver -> {
Data data = new Data();
data.setDriverName(driver.getName());
data.setLuxuryVehicle(true);
data.setVehicleName(vehicle[0].getName());
data.setDriverEligible(true);
data.setRecentVehicle(vehicle[0].getModelyear() > 2016);
return data;
})
.collect(Collectors.toList());
Related
I want to read mulitpart/formdata, one part is application/JSON. I can't get them to Map<String,String>, Is there any way to parse Part to String?
private Map<String, String> getFormData(String path, MultiValueMap<String, Part> partMultiValueMap) {
if (partMultiValueMap != null) {
Map<String, String> formData = new HashMap<>();
Map<String, Part> multiPartMap = partMultiValueMap.toSingleValueMap();
for (Map.Entry<String, Part> partEntry : multiPartMap.entrySet()) {
Part part = partEntry.getValue();
if (part instanceof FormFieldPart) {
formData.put(partEntry.getKey(), ((FormFieldPart) part).value());
} else {
String bodyString = bufferToStr(part.content());
formData.put(partEntry.getKey(), bodyString);
}
}
return formData;
}
return null;
}
extra Flux
private String bufferToStr(Flux<DataBuffer> content){
AtomicReference<String> res = new AtomicReference<>();
content.subscribe(buffer -> {
byte[] bytes = new byte[buffer.readableByteCount()];
buffer.read(bytes);
DataBufferUtils.release(buffer);
res.set(new String(bytes, StandardCharsets.UTF_8));
});
return res.get();
}
Subscribe is async; bufferToStr value may be null?
You could do it in non-blocking way with StringDecoder
Basically you could write your code to return Mono<Map<>>
Note: I'm using Pair class here to return key-value and later collect them to Map
Pair I'm using here is from package org.springframework.data.util.Pair
public Mono<Map<String, String>> getFormData(MultiValueMap<String, Part> partMultiValueMap) {
Map<String, Part> multiPartMap = partMultiValueMap.toSingleValueMap();
return Flux.fromIterable(multiPartMap.entrySet())
.flatMap(entry -> {
Part part = entry.getValue();
if (part instanceof FormFieldPart) {
return Mono.just(
Pair.of(entry.getKey(), ((FormFieldPart) part).value()) // return Pair
);
} else {
return decodePartToString(part.content()) // decoding DataBuffers to string
.flatMap(decodedString ->
Mono.just(Pair.of(entry.getKey(), decodedString))); // return Pair
}
})
.collectMap(Pair::getFirst, Pair::getSecond); // map and collect pairs to Map<>
}
private Mono<String> decodePartToString(Flux<DataBuffer> dataBufferFlux) {
StringDecoder stringDecoder = StringDecoder.textPlainOnly();
return stringDecoder.decodeToMono(dataBufferFlux,
ResolvableType.NONE,
MimeTypeUtils.TEXT_PLAIN,
Collections.emptyMap()
);
}
I want to return Mono of Boolean from the below method but getting compilation error with flatMap().
public Mono<Boolean> isMandatory(String country, String compliance,String referenceType) {
Flux<CustomReference> customRefFlux = getCustomsReferenceRule();
return Mono.just(customRefFlux.collectList().flatMap(customRefList -> {
customRefList.stream().anyMatch(customRef -> customRef.getType()
.equalsIgnoreCase(referenceType));
}));
}
It is internally calling:
public Flux<CustomReference> getCustomsReferenceRule() {
CustomReference c1 = new CustomReference();
c1.setCrKey("ECU");
c1.setType("MRN");
CustomReference c2 = new CustomReference();
c2.setCrKey("FUE");
c2.setType("FUE");
Flux<CustomReference> custRefFlux = Flux.just(c1, c2);
return custRefFlux;
}
The POJO class
#Data
public class CustomReference {
private String type;
private String crKey;
}
You likely don't want to mix streams and collections unless needed. If I'm following this should be a simple:
public Mono<Boolean> isMandatory(String country, String compliance,String referenceType) {
Flux<CustomReference> customRefFlux = getCustomsReferenceRule();
return customRefFlux.any(customRef -> customRef.getType()
.equalsIgnoreCase(referenceType));
}
Flux<CustomReference> customRefFlux = getCustomsReferenceRule();
return customRefFlux.collectList().flatMap(customRefList -> {
List<CustomReference> l = customRefList.stream()
.filter(customRef -> customRef.getType().equalsIgnoreCase(referenceType))
.collect(Collectors.toList());
if (!l.isEmpty())
return Mono.just(Boolean.TRUE);
else
return Mono.just(Boolean.FALSE);
});
I have a list of ProductDto objects and I want to group them the similar ones using java 8 streams Collectors.groupingBy(). After grouping the records I want to combine the similar records as single productDto. To achieve this I have used map.forEach and got the expected result, but I want to avoid the forEach loop and want to know any better solution in java 8.
Below is the my Main Class code snippet.
public class GroupTest {
public static void main(String[] args) {
GroupTest t=new GroupTest();
List<ProductDto> inputDtos=t.createInputData();
List<ProductDto> resultDtos=t.getGroupedResult(inputDtos);
//writing to Excel
}
private List<ProductDto> getGroupedResult(List<ProductDto> inputDtos) {
Map<Object, List<ProductDto>> groupedMap = inputDtos.stream()
.collect(Collectors.groupingBy(ProductDto::groupSimilarProductIdentifier));
List<ProductDto> resultProductDtos=new ArrayList<>();
groupedMap.forEach((key, dtos) -> {
if (dtos.size() > 1) {
ProductDto productDto = dtos.get(0);
dtos.forEach(dto -> {
if (dto.getLap1() != null) {
productDto.setLap1(dto.getLap1());
} else if (dto.getLap2() != null) {
productDto.setLap2(dto.getLap2());
} else if (dto.getLap3() != null) {
productDto.setLap3(dto.getLap3());
}
});
resultProductDtos.add(productDto);
} else {
resultProductDtos.addAll(dtos);
}
});
return resultProductDtos;
}
private List<ProductDto> createInputData(){
List<ProductDto> dtos=new ArrayList<>();
dtos.add(new ProductDto(1L,"DELL",8,"DELL_s001",null,null));
dtos.add(new ProductDto(1L,"DELL",8,null,"DELL_s002",null));
dtos.add(new ProductDto(1L,"DELL",8,null,null,"DELL_s003"));
dtos.add(new ProductDto(1L,"HP",8,"HP_s001",null,null));
dtos.add(new ProductDto(2L,"APPLE",16,"MAC_s001",null,null));
return dtos;
}
}
This is the ProductDto class code
public class ProductDto {
private Long userId;
private String manufacter;
private int ram;
private String lap1;
private String lap2;
private String lap3;
public ProductDto(Long userId, String manufacter, int ram, String lap1, String lap2, String lap3) {
super();
this.userId = userId;
this.manufacter = manufacter;
this.ram = ram;
this.lap1 = lap1;
this.lap2 = lap2;
this.lap3 = lap3;
}
//getters and Setters
public List<Object> groupSimilarProductIdentifier() {
return Arrays.asList(userId, manufacter, ram);
}
}
Below is the screenshot image shows the input and output records. Output records is the results exactly I want it. Any alternate or better solution in java 8 which is efficient is most welcome.
After Rono comment I found the answer so posting the answer what I did in getGroupedResult method and added a new function mergetwoProduct. So this may help somebody.
Below is the code for my getGroupedResult and mergetwoProduct methods after changes.
private List<ProductDto> getGroupedResult(List<ProductDto> inputDtos) {
List<ProductDto> productdtos= new ArrayList<>(inputDtos.stream().collect(
Collectors.toMap(ProductDto::groupSimilarProductIdentifier, e -> e, (a, b) -> mergetwoProduct(a, b)))
.values());
return productdtos;
}
private ProductDto mergetwoProduct(ProductDto p1,ProductDto p2) {
if (p2.getLap1() != null) {
p1.setLap1(p2.getLap1());
} else if (p2.getLap2() != null) {
p1.setLap2(p2.getLap2());
} else if (p2.getLap3() != null) {
p1.setLap3(p2.getLap3());
}
return p1;
}
I want to create dynamic query in spring data jpa. Doing many search I can implement it, but I came across a problem when I add IN operator in where clause. I need to check id IN (longlist)
Here is my entity class
#Entity
#Table(name = "view_detail")
public class ViewDetailDom {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
#Column(name = "user_id")
private Long userId;
private String description;
Here is specification builder class and specification class
public class ViewDetailSpecificationsBuilder {
private final List<SearchCriteria> params;
public ViewDetailSpecificationsBuilder() {
params = new ArrayList<SearchCriteria>();
}
public ViewDetailSpecificationsBuilder with(String key, Operation operation, Object value) {
params.add(new SearchCriteria(key, operation, value));
return this;
}
public Specification<ViewDetailDom> build() {
if (params.size() == 0) {
return null;
}
List<Specification<ViewDetailDom>> specs = new ArrayList<Specification<ViewDetailDom>>();
for (SearchCriteria param : params) {
specs.add(new ViewDetailSpecification(param));
}
Specification<ViewDetailDom> result = specs.get(0);
for (int i = 1; i < specs.size(); i++) {
result = Specifications.where(result).and(specs.get(i));
}
return result;
}
}
public class ViewDetailSpecification implements Specification<ViewDetailDom> {
private SearchCriteria criteria = new SearchCriteria();
public ViewDetailSpecification(SearchCriteria searchCriteria) {
this.criteria.setKey(searchCriteria.getKey());
this.criteria.setOperation(searchCriteria.getOperation());
this.criteria.setValue(searchCriteria.getValue());
}
#Override
public Predicate toPredicate(Root<ViewDetailDom> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
String value = criteria.getValue().toString().replaceAll(" ", "%");
if (criteria.getOperation() != null && criteria.getOperation() != Operation.DEFAULT) {
if (criteria.getOperation() == Operation.GREATHERTHANEQUALTO) {
return builder.greaterThanOrEqualTo(root.<String>get(criteria.getKey()), value);
} else if (criteria.getOperation() == Operation.LESSTHANEQUALTO) {
return builder.lessThanOrEqualTo(root.<String>get(criteria.getKey()), value);
} else if (criteria.getOperation() == Operation.EQUAL) {
return builder.equal(root.<String>get(criteria.getKey()), value);
} else if (criteria.getOperation() == Operation.IN) {
Path<Long> view = root.<Long>get(criteria.getKey());
return view.in(criteria.getValue());
}
} else {
if (root.get(criteria.getKey()).getJavaType() == String.class) {
return builder.like(builder.lower(root.<String>get(criteria.getKey())),
"%" + value.toLowerCase() + "%");
} else {
return builder.equal(root.get(criteria.getKey()), value);
}
}
return null;
}
}
This method creates specification builder:
public ViewDetailSpecificationsBuilder createSearchSpecifications(ViewSearch view) {
ViewDetailSpecificationsBuilder builder = new ViewDetailSpecificationsBuilder();
if (StringUtils.isNotBlank(view.getName())) {
builder.with("name", Operation.DEFAULT, view.getName());
}
if (StringUtils.isNotBlank(view.getDescription())) {
builder.with("description", Operation.DEFAULT, view.getDescription());
}
return builder;
}
And finally I do this:
ViewDetailSpecificationsBuilder builder = createSearchSpecifications(view);
builder.with("userId", Operation.DEFAULT, userSessionHelper.getUserId());
builder.with("id", Operation.IN, viewids);
Specification<ViewDetailDom> spec = builder.build();
viewDetailDao.findAll(spec);
But I am getting following error:
"Unaware how to convert value [[5, 7, 8] : java.util.ArrayList] to requested type [java.lang.Long]; nested exception is java.lang.IllegalArgumentException: Unaware how to convert value [[5, 7, 8] : java.util.ArrayList] to requested type [java.lang.Long]"
I have resolved this problem in this way:
ViewDetailSpecification class:
if (criteria.getOperation() == Operation.IN) {
final List<Predicate> orPredicates = new ArrayList<Predicate>();
List<Long> viewIds = (List<Long>) criteria.getValue();
for (Long viewid : viewIds) {
orPredicates.add(builder.or(builder.equal(root.<String>get(criteria.getKey()), viewid)));
}
return builder.or(orPredicates.toArray(new Predicate[orPredicates.size()]));
}
In kotlin I have the same error, I change the ArrayList to Array, with this code:
fun values(): Array<String> {
val elems = arrayListOf<String>()
return elems.toTypedArray()
}
Try you convert ArrayList to array, for java see: make arrayList.toArray() return more specific types
I am trying to do a nested null check and then clear the values in map in the nested object if the map is not null.
The following is my hypothetical code. I am wondering if this is the right way to do it or is there a more elegant solution to this.
package exp.myJavaLab.Experiments;
import java.util.*;
public class OptionalTest {
public Inner inner;
public static void main(String[] args) {
OptionalTest testObj = new OptionalTest();
Pojo pojo1 = new Pojo();
pojo1.id = 1;
Map<String, String> dataMap = new HashMap<>();
dataMap.put("a","b");
pojo1.dataMap = dataMap;
Pojo pojo2 = new Pojo();
pojo2.id = 2;
Inner inner = new Inner();
inner.pojoList = Arrays.asList(pojo1, pojo2);
testObj.inner = inner;
System.out.println(testObj);
Optional.ofNullable(testObj.inner)
.map(Inner::getPojoList)
.ifPresent(pojos -> pojos.forEach(pojo -> {
if (pojo.getDataMap() != null) {
pojo.getDataMap().clear();
}
}));
System.out.println(testObj);
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("OptionalTest{");
sb.append("inner=").append(inner);
sb.append('}');
return sb.toString();
}
}
class Inner {
public List<Pojo> pojoList;
public List<Pojo> getPojoList() {
return pojoList;
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("Inner{");
sb.append("pojoList=").append(pojoList);
sb.append('}');
return sb.toString();
}
}
class Pojo {
public Map<String, String> dataMap;
public int id;
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("Pojo{");
sb.append("dataMap=").append(dataMap);
sb.append(", id=").append(id);
sb.append('}');
return sb.toString();
}
public Map<String, String> getDataMap() {
return dataMap;
}
public int getId() {
return id;
}
}
In my opinion Collections should never be null.
You could declare your pojoList and dataMap as private and instantiate them.
Your class then needs some add-methods. So you are sure getDataMap() never returns null:
class Pojo {
private Map<String, String> dataMap = new HashMap<>();
public Map<String, String> getDataMap() {
return dataMap;
}
public void add(String key, String value) {
dataMap.put(key, value);
}
}
Then you don't need to check for null:
Optional.ofNullable(testObj.inner)
.map(Inner::getPojoList)
.ifPresent(pojos -> pojos.forEach(pojo -> { pojo.getDataMap().clear(); }));