Please help me with the Java 8 map - filter - sort - collect code.
Info.java
public class Info {
private final String name;
private final String desc;
private String version = null;
#Override
public boolean equals(Object that) {
if (that == null) {
return false;
}
if (that instanceof Info) {
Info other = (Info) that;
return Objects.equals(this.name, other.name) &&
Objects.equals(this.desc, other.desc) &&
Objects.equals(this.version, other.version);
} else {
return false;
}
}
public boolean equalsWithoutVersion(Object that) {
if (that == null) {
return false;
}
if (that instanceof Info) {
Info other = (Info) that;
return Objects.equals(this.name, other.name) &&
Objects.equals(this.desc, other.desc);
} else {
return false;
}
}
#Override
public int hashCode() {
int hash = 13;
hash = (hash * 7) + name.hashCode();
hash = (hash * 7) + desc.hashCode();
if (version != null)
hash = (hash * 7) + version.hashCode();
return hash;
}
#Override
public String toString() {
String versionString = version == null ? "latest" : version;
return String.format("Name: %s Desc: %s Key Type: %s Version: %s", this.name, this.desc, this.keyType.name(), versionString);
}
}
Value.java
public class Value implements Comparable<Value> {
private String data;
private String version;
public Value(String version, String data) {
this.version = version;
this.data = data;
}
#Override
public int compareTo(Value o) {
return (Integer.parseInt(this.version) > Integer.parseInt(o.version)) ? -1
: (Integer.parseInt(this.version) < Integer.parseInt(o.version)) ? 1
: 0;
}
}
Cache.java
public class Cache {
private final Map<Info, Value> dataMap = new HashMap<>();
...
private Value getlatestVersionFromCache(Info info) {
List<Value> values = dataMap.entrySet().stream()
.filter(p -> p.getKey().equalsWithoutVersion(info))
.sorted(Map.Entry.comparingByValue())
.map(x::getValue))
.collect(Collectors.toList());
return values.isEmpty() ? null : values.get(0);
}
}
The goal is to obtain the latest version of a record from the map. So first I am filtering entries from the map by comparing fields of Info without version. Then I am sorting the map by value based on version. Then I am collecting the values to a list. Then I can get the first element to get the latest version.
But I am getting the following compilation error on the filter statement:
Syntax error on token ")", ElidedSemicolonAndRightBrace expected
Well I doubt about your solution. I think you can do it in simple way. so first change version type to Integer in Value class(in compareTo() method you converted it to Integer). and also change method signature to Optional<Value> in getlatestVersionFromCache() method.
also I think you don't need to sort dataMap.
private Optional<Value> getlatestVersionFromCache(Info info) {
Map<Value,Integer> result = dataMap.entrySet()
.stream()
.filter(p -> p.getKey().equalsWithoutVersion(info))
.collect(Collectors.toMap(Map.Entry::getValue, entry -> entry.getValue().getVersion(), Integer::min));
Optional<Value> latestValue = result.keySet()
.stream()
.min(Comparator.comparingInt(key -> key.getVersion()));
return latestValue;
}
Better solution is something like this:
dataMap.entrySet()
.stream()
.filter(p -> p.getKey().equalsWithoutVersion(info))
.min(Comparator.comparingInt(entry -> entry.getValue().getVersion()))
.map(Map.Entry::getValue)
.orElse(null);
You have several omissions and errors in the code you posted, but the filter statement was actually fine.
The following passes compilation:
List<Value> values = dataMap.entrySet()
.stream()
.filter(p -> p.getKey().equalsWithoutVersion(info))
.sorted(Map.Entry.comparingByValue())
.map(Map.Entry::getValue) // was .map(x::getValue)) - x is not defined anywhere, so
// I assumed you meant Map.Entry::getValue
.collect(Collectors.toList());
class Info {
public static final BiPredicate<Info, Info> IS_EQUAL_IGNORE_VERSION =
(one, two) -> one.getName().equals(two.getName()) && one.getDesc().equals(two.getDesc());
private final String name;
private final String desc;
private String version;
}
private final Map<Info, Value> dataMap = new HashMap<>();
public Value getlatestVersionFromCache(Info info) {
Value value = null;
for (Map.Entry<Info, Value> entry : dataMap.entrySet())
if (Info.IS_EQUAL_IGNORE_VERSION.test(entry.getKey(), info))
if (value == null || Integer.parseInt(entry.getValue().getVersion()) > Integer.parseInt(value.getVersion()))
value = entry.getValue();
return value;
}
Notes:
I think that use a comparator to check that two objects are queal is not correct. You can use e.g. Predicate. And define it as a static method in the target class.
Pay attention on version. It seems that it should be an integer instead of String.
In your approach, compare two string is not correct, because it is used instead of equals method.
I think that using Java8 streams only because this is a stream is alow not correct.
Related
I have a hash map like this
Map<String, AttributeValueUpdate> myMap = new HashMap<>;
The class AttributeValueUpdate looks like this:
public class AttributeValueUpdate {
private AttributeValue value;
private String action;
public static class Builder {
private AttributeValue value;
private String action;
public Builder() {
}
public AttributeValueUpdate.Builder withValue(AttributeValue value) {
this.value = value;
return this;
}
public AttributeValueUpdate.Builder withAction(String action) {
this.action = action;
return this;
}
protected void populate(AttributeValueUpdate instance) {
instance.setValue(this.value);
instance.setAction(this.action);
}
public AttributeValueUpdate build() {
AttributeValueUpdate instance = new AttributeValueUpdate();
this.populate(instance);
return instance;
}
}
}
The map has two entries
AttributeValueUpdate att1 = AttributeValueUpdate.builder().withAction("Add").withValue(new AttributeValue("sam").build();
AttributeValueUpdate att2 = AttributeValueUpdate.builder().withAction("Delete").withValue(new AttributeValue("john").build();
myMap.add("entry1", attr1);
myMap.add("entry2", atte2);
I want to modify mymap by deleting the "value field" from all the AttributeValueUpdate (which is value of the map), basically map's value field will be changed by removing "value field" of the AttributeValueUpdate object. How can I achieve this using java streams?
Java Stream API is not a friend with Map as long as it's collection-based (List, Set). You need to stream over the entries of the map.
As far as I understand, you want to remove (= make null) AttributeValue value of each AttributeValueUpdate instance (map's value). Here is the way to go assuming a constructor AttributeValueUpdate(String action):
Map<String, AttributeValueUpdate> updatedMap = myMap.entrySet().stream()
.map(entry -> {
String action = entry.getValue().getAction();
AttributeValueUpdate update = new AttributeValueUpdate(action);
return new SimpleEntry<>(entry.getKey(), update);
})
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
The easiest solution is using Map#replaceAll if you don't mind to mutate the map:
myMap.replaceAll((k, v) -> {
String action = v.getAction();
return new AttributeValueUpdate(action);
});
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 have a few Search Results Objects as given below:
public class TradeSearchResult{
private String tradeRefNo;
private String relatedTradeId;
private String custodyDate;
private String orderNumber;
private String odrQty;
private String price : 500;
}
public class CollateralTradesSearchResult{
private String excludeTradeOUT;
private String settlementStatus;
private String fundId;
private String altFundId;
private String apNumber;
private String collateralOrderNumber;
private String componenetIdentifier;
}
Now I have a Search Filter Criteria object
public class CRITERION {
protected String field; //The field denotes any field name of the either
SearchResult object
protected String operator; its will be EQUALS or NOT_EQUALS
protected String value; Value of the field.
}
Now I need to write a Dynamic Filter Method where I will pass the List of Criterion object and can pass List of either SearchResult like below
public static List<Object> applyFilter(List<CRITERION> comp, List<?> objectList){
//The CRITERION.fiedName can be same in more than one in the list
return filteredList;
}
here is one example: Consider the below List of
TradeSearchResult{
tradeRefNo : W12343;
relatedTradeId: N993093;
custodyDate : 2018-12-14;
orderNumber : 0000342343;
String odrQty : 12;
String price : 500;
},
{
tradeRefNo : W12344;
relatedTradeId: N993093;
custodyDate : 2018-12-14;
orderNumber : 0000342344;
String odrQty : 18;
String price : 600;
},
{
tradeRefNo : W12345;
relatedTradeId: N993094;
custodyDate : 2018-12-14;
orderNumber : 0000342345;
String odrQty : 20;
String price : 700;
}
Now the Criterion class is like
CRITERION{
field :relatedTradeId;
operator : EQUALS;
value :N993093;
}
{
field :relatedTradeId;
operator : EQUALS;
value :N993094;
}
{
field :orderNumber ;
operator : EQUALS;
value :0000342344;
}
It will only return one result even relatedTradeId has two filter
TradeSearchResult{
tradeRefNo : W12344;
relatedTradeId: N993093;
custodyDate : 2018-12-14;
orderNumber : 0000342344;
String odrQty : 18;
String price : 600;
}
Now in the same applyFIlter Method I can send a list of Criterion and a list of CollateralTradesSearchResult and returns filtered result.
Here is something I tried
public static List<Object> applyFilter(List<CRITERION> criList, List<?> objectList){
long startTime = Calendar.getInstance().getTimeInMillis();
Set<Object> objectSet = new HashSet<>();
for(CRITERION cri : criList){
String fieldName = cri.getFIELD();
objectList.stream().filter(p->beanProperties(p).get(fieldName).equals(cri.getVALUE())).forEachOrdered(objectSet::add);
//objectList.retainAll(objectSet);
//objectSet.clear();
}
List<Object> ret = new ArrayList<>(objectSet);
long endTime = Calendar.getInstance().getTimeInMillis();
System.out.println("Size"+ ret.size());
System.out.println("Time Taken to Search"+ String.valueOf(endTime-startTime));
return ret;
}
Hereis the beanProperties() method
public static Map<String, Object> beanProperties(Object bean) {
try {
Map<String, Object> map = new HashMap<>();
Arrays.asList(Introspector.getBeanInfo(bean.getClass(), Object.class)
.getPropertyDescriptors())
.stream()
// filter out properties with setters only
.filter(pd -> Objects.nonNull(pd.getReadMethod()))
.forEach(pd -> { // invoke method to get value
try {
Object value = pd.getReadMethod().invoke(bean);
if (value != null) {
map.put(pd.getName(), value);
}
} catch (Exception e) {
// add proper error handling here
}
});
return map;
} catch (IntrospectionException e) {
// and here, too
return Collections.emptyMap();
}
}
Any help using Stream or by any means will be helpful.
Create an interface SearchResult and make both your classes implement it. Then create this class:
public class Filter<T extends SearchResult> {
public List<T> applyFilter(List<Criterion> criteria, List<T> list) {
Map<String, Set<String>> allowedValues = new HashMap<>();
Map<String, Set<String>> prohibitedValues = new HashMap<>();
populateValues(criteria, "EQUALS", allowedValues);
populateValues(criteria, "NOT_EQUALS", prohibitedValues);
prohibitedValues.forEach((k, v) -> list.removeIf(t -> v.contains(getFieldValue(k, t))));
allowedValues.forEach((k, v) -> list.removeIf(t -> !v.contains(getFieldValue(k, t))));
return list;
}
private static void populateValues(List<Criterion> criteria, String operator, Map<String, Set<String>> values) {
criteria.stream()
.filter(c -> c.getOperator().equals(operator))
.forEach(c -> {
values.merge(c.getField(), Stream.of(c.getValue()).collect(Collectors.toSet()),
(set1, set2) -> Stream.concat(set1.stream(), set2.stream()).collect(Collectors.toSet()));
});
}
private String getFieldValue(String fieldName, T object) {
Field field;
try {
field = object.getClass().getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
e.printStackTrace();
return null;
}
field.setAccessible(true);
try {
return (String) field.get(object);
} catch (IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
}
And use it as:
Filter<TradeSearchResult> filter = new Filter<>(); //or CollateralTradesSearchResult
List<TradeSearchResult> filteredList = filter.applyFilter(criteria, searchResults);
I have a List it has multiple object is present. for every object we have to find some string which compare to Enum value How we get list of particular object
public enum ObjectType {
CONTACT("Contact"),
BANK_DETAILS("Bank-Details"),
EMPLOYMENT("Employment"),
PRODUCT("Product"),
INCOME_DETAIL("Income-Details");
ObjectType(String values) { this.values = values; }
public String getValues() { return values; }
private String values; }
for this i am using below method
List listOutput = errorsList.stream().
filter(e -> e.getObjType().contains(String.valueOf(ObjectType.values())))
.collect(Collectors.toList());
But it return 0 value are present
Try this (adding check method to enum):
public enum ObjectType {
CONTACT("Contact"),
BANK_DETAILS("Bank-Details"),
EMPLOYMENT("Employment"),
PRODUCT("Product"),
INCOME_DETAIL("Income-Details");
private String values;
ObjectType(String values) {
this.values = values;
}
public String getValues() {
return values;
}
public static boolean isValueExists(String test) {
for (ObjectType o : ObjectType.values()) {
if (o.getValues().equals(test)) {
return true;
}
}
return false;
}
}
And then use it:
List listOutput = errorsList.stream().filter(e -> ObjectType.isValueExists(e.getObjType())).collect(Collectors.toList());
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