I have been developing in Java 6 and using guava predicates. But I want to switch to Java 8 and use java util predicates instead. I can simply convert the below method to use the predicate but is there a smart way to use Lambda expressions and reduce the number of lines of code ? Preferably remove the temp list I am creating ? I am googling for examples but all of them are very simple ones. Thanks for you help!
private Predicate<Objt1> getLocalAttributesPredicate() {
return new Predicate<Objt1>() {
#Override
public boolean apply(Objt1 input) {
AttributeType attr = cache.get(input.getAttributeID());
List<String> attrGroupids = Lists.newArrayList();
for (AttributeGroupLinkType group : attr.getAttributeGroupLink()) {
attrGroupids.add(group.getAttributeGroupID());
}
return attrGroupids.contains(localAttrGroupId) && !attrGroupids.contains(exclustionAttrGroupId);
}
};
}
Something like the following:
private Predicate<Objt1> getLocalAttributesPredicate() {
return input -> cache.get(input.getAttributeID())
.stream()
.map(group -> group.getAttributeGroupID())
.filter(id -> id.equals(localAttrGroupId))
.filter(id -> !id.equals(exclustionAttrGroupId))
.limit(1)
.count() > 0;
}
So the predicate is returned as a lambda function, and it utilises the Stream API to traverse the list and convert its contents.
Edit: applied the optimisation suggested by #Aominè, thanks.
This is how you'd do it as of Java-8:
private Predicate<Objt1> getLocalAttributesPredicate() {
return input -> {
Set<String> accumulator = ...
AttributeType attr = cache.get(input.getAttributeID());
for(AttributeGroupLinkType group : attr.getAttributeGroupLink())
accumulator.add(group.getAttributeGroupID());
return accumulator.contains(localAttrGroupId) &&
!accumulator.contains(exclustionAttrGroupId);
};
}
Note, that I've also used a Set for the accumulator as the Contains method is much faster for a Set implementation than for a List implementation.
Related
Have two classes and two corresponding lists:
class Click {
long campaignId;
Date date;
}
class Campaign {
long campaignId;
Date start;
Date end;
String type;
}
List<Click> clicks = ..;
List<Campaign> campaigns = ..;
And want to find all Clicks in clicks that:
Have a corresponding Campaign in campaigns list, i.e., Campaign with the same campaignId AND
This Campaign has type = "prospective" AND
This Campaigns.start < click.date < Campaigns.end
So far I have the following implementation (which seems confusing and complex to me):
clicks.
stream().
filter(click -> campaigns.stream().anyMatch(
campaign -> campaign.getCampaignType().equals("prospecting") &&
campaign.getCampaignId().equals(click.getCampaignId()) &&
campaign.getStart().after(click.getDate()) &&
campaign.getEnd().before(click.getDate()))).
collect(toList());
I wonder if there is simpler solution for the problem.
Well, there is a very neat way to solve your problem IMO, original idea coming from Holger (I'll find the question and link it here).
You could define your method that does the checks (I've simplified it just a bit):
static boolean checkClick(List<Campaign> campaigns, Click click) {
return campaigns.stream().anyMatch(camp -> camp.getCampaignId()
== click.getCampaignId());
}
And define a function that binds the parameters:
public static <T, U> Predicate<U> bind(BiFunction<T, U, Boolean> f, T t) {
return u -> f.apply(t, u);
}
And the usage would be:
BiFunction<List<Campaign>, Click, Boolean> biFunction = YourClass::checkClick;
Predicate<Click> predicate = bind(biFunction, campaigns);
clicks.stream()
.filter(predicate::test)
.collect(Collectors.toList());
One thing that stands out is that your 2nd requirement has nothing to do with the matching, it's a condition on campaigns only. You'd have to test if this is any better for you:
clicks.stream()
.filter(click -> campaigns.stream()
.filter(camp -> "prospecting".equals(camp.type))
.anyMatch(camp ->
camp.campaignId == click.campaignId &&
camp.end.after(click.date) &&
camp.start.before(click.date)
)
)
.collect(Collectors.toList());
Otherwise, I have never seen a streams solution which does not involve streaming the 2nd collection inside the predicate of the 1st, so you can't do much better than what you did. In terms of readability, if it looks that confusing to you then create a method that test for the boolean condition and call it:
clicks.stream()
.filter(click -> campaigns.stream()
.filter(camp -> "pre".equals(camp.type))
.anyMatch(camp -> accept(camp, click))
)
.collect(Collectors.toList());
static boolean accept(Campaign camp, Click click) {
return camp.campaignId == click.campaignId &&
camp.end.after(click.date) &&
camp.start.before(click.date);
}
Finally, 2 unrelated suggestions:
Don't use the old Date class, instead use the new java.time API's LocalDate.
If Campaign's type can only have some predefined values (like "submitted", "prospecting", "accepted"...) then an enum would be a better fit than a general String.
My 2 cents:
Since there is no much boilerplate code in OP. So it may be not possible/necessary to reduce the lines/characters in the codes. we could rewrite it to make it a little more clearly:
Map<Long, List<Campaign>> map = campaigns.stream().filter(c -> c.type.equals("prospecting"))
.collect(Collectors.groupingBy(c -> c.campaignId));
clicks.stream().filter(k -> map.containsKey(k.campaignId))
.filter(k -> map.get(k.campaignId).stream().anyMatch(c -> c.start.before(k.date) && c.end.after(k.date)))
.collect(Collectors.toList());
The code is not much shorter than original code. but it will improve performance from O(nm) to O(n+m), as #Marco13 mentioned in the comments. if you want shorter, try StreamEx
Map<Long, List<Campaign>> map = StreamEx.of(campaigns)
.filter(c -> c.type.equals("prospecting")).groupingBy(c -> c.campaignId);
StreamEx.of(clicks).filter(k -> map.containsKey(k.campaignId))
.filter(k -> map.get(k.campaignId).stream().anyMatch(c -> c.start.after(k.date) && c.end.before(k.date)))
.toList();
public List<Click> findMatchingClicks(List<Campaign> cmps, List<Click> clicks) {
List<Campaign> cmpsProspective = cmps.stream().filter(cmp -> "prospective".equals(cmp.type)).collect(Collectors.toList());
return clicks.stream().filter(c -> matchesAnyCmp(c, cmpsProspective).collect(Collectors.toList());
}
public boolean matchesAnyCmp(Click click, List<Campaign> cmps) {
return cmps.stream().anyMatch(click -> cmp.start.before(click.date) && cmp.end.after(click.date));
}
Replace fields for getters, just wrote it quick.
I'm relatively new to programming and I have been wondering for past two days how to make a Predicate that is made from a custom list of other Predicates. So I've came up with some kind of solution. Below is a code snippet that should give you an idea. Because I have written it based on solely reading various pieces of documentations I have two questions: 1/ is it a good solution? 2/ is there some other, recommended solution for this problem?
public class Tester {
private static ArrayList<Predicate<String>> testerList;
//some Predicates of type String here...
public static void addPredicate(Predicate<String> newPredicate) {
if (testerList == null)
{testerList = new ArrayList<Predicate<String>>();}
testerList.add(newPredicate);
}
public static Predicate<String> customTesters () {
return s -> testerList.stream().allMatch(t -> t.test(s));
}
}
You could have a static method that receives many predicates and returns the predicate you want:
public static <T> Predicate<T> and(Predicate<T>... predicates) {
// TODO Handle case when argument is null or empty or has only one element
return s -> Arrays.stream(predicates).allMatch(t -> t.test(s));
}
A variant:
public static <T> Predicate<T> and(Predicate<T>... predicates) {
// TODO Handle case when argument is null or empty or has only one element
return Arrays.stream(predicates).reduce(t -> true, Predicate::and);
}
Here I'm using Stream.reduce, which takes the identity and an operator as arguments. Stream.reduce applies the Predicate::and operator to all elements of the stream to produce a result predicate, and uses the identity to operate on the first element of the stream. This is why I have used t -> true as the identity, otherwise the result predicate might end up evaluating to false.
Usage:
Predicate<String> predicate = and(s -> s.startsWith("a"), s -> s.length() > 4);
Java Predicate has a nice function of AND which returns new Predicate which is evaluation of both predicates. You can add them all into one with this.
https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html#and-java.util.function.Predicate-
example :
Predicate<String> a = str -> str != null;
Predicate<String> b = str -> str.length() != 0;
Predicate<String> c = a.and(b);
c.test("Str");
//stupid test but you see the idea :)
I have a class
class ColumnTags {
String Name;
Collection<String> columnSemanticTags;
// constructor and getter and setters and other relevant attributes
}
I want to get the columnSemanticTags from a list of ColumnTags for a given name.
The corresponding method is as follows
public Collection<String> getTags(String colName, List<ColumnTags> colList)
{
Collection<String> tags = new ArrayList();
for(ColumnTag col:colList){
if(colName.equals(col.getName())){
tags = col.getColumnSemanticTags();
break;
}
}
return tags;
}
Want to convert the for loop to a java stream . I have tried
tags = colList.stream().filter(col -> colName.equals(col.getName()))
.map(col -> col.getColumnSemanticTags())
.collect(Collectors.toCollection());
I am getting compilation error. I am not aware what should be the Supplier . Have tried ArrayList::new . I have also tried casting it to ArrayList , but no success.
Can someone advice me what am I assuming wrong or what should be the expected way to handle this scenario.
With the solution , can someone explain as to why .collect() is a wrong way of tackling this solution.
public Collection<String> getTags(String colName, List<ColumnTags> colList) {
return colList.stream().filter(col -> colName.equals(col.getName()))
.map(col -> col.getColumnSemanticTags())
.findFirst().orElse(new ArrayList<>());
}
An easier way of going about this would be to simply filter a Stream to find exactly what you're looking for. If it is found, then return it, otherwise return an empty ArrayList:
return colList.stream()
.filter(c -> colName.equals(c.getName()))
.map(ColumnTag::getColumnSemanticTags)
.findFirst()
.orElseGet(ArrayList::new);
If you really want to use collect, you must call flatMap. That merges all of the lists (which are come from map(col -> col.getColumnSemanticTags())) into a single stream which contains all of the items.
List<String> tags = colList.stream()
.filter(col -> colName.equals(col.getName()))
.map(col -> col.getColumnSemanticTags())
.flatMap(collection -> collection.stream())
.collect(Collectors.toList());
I have an entity Employee
class Employee{
private String name;
private String addr;
private String sal;
}
Now i have list of these employees. I want to filter out those objects which has name = null and set addr = 'A'. I was able to achieve like below :
List<Employee> list2= list.stream()
.filter(l -> l.getName() != null)
.peek(l -> l.setAddr("A"))
.collect(Collectors.toList());
Now list2 will have all those employees whose name is not null and then set addr as A for those employees.
What i also want to find is those employees which are filtered( name == null) and save them in DB.One way i achieved is like below :
List<Employee> list2= list.stream()
.filter(l -> filter(l))
.peek(l -> l.setAddr("A"))
.collect(Collectors.toList());
private static boolean filter(Employee l){
boolean j = l.getName() != null;
if(!j)
// save in db
return j;
}
1) Is this the right way?
2) Can we do this directly in lambda expression instead of writing separate method?
Generally, you should not use side effect in behavioral parameters. See the sections “Stateless behaviors” and “Side-effects” of the package documentation. Also, it’s not recommended to use peek for non-debugging purposes, see “In Java streams is peek really only for debugging?”
There’s not much advantage in trying to squeeze all these different operations into a single Stream pipeline. Consider the clean alternative:
Map<Boolean,List<Employee>> m = list.stream()
.collect(Collectors.partitioningBy(l -> l.getName() != null));
m.get(false).forEach(l -> {
// save in db
});
List<Employee> list2 = m.get(true);
list2.forEach(l -> l.setAddr("A"));
Regarding your second question, a lambda expression allows almost everything, a method does. The differences are on the declaration, i.e. you can’t declare additional type parameters nor annotate the return type. Still, you should avoid writing too much code into a lambda expression, as, of course, you can’t create test cases directly calling that code. But that’s a matter of programming style, not a technical limitation.
If you are okay in using peek for implementing your logic (though it is not recommended unless for learning), you can do the following:
List<Employee> list2= list.stream()
.peek(l -> { // add this peek to do persistence
if(l.getName()==null){
persistInDB(l);
}
}).filter(l -> l.getName() != null)
.peek(l -> l.setAddr("A"))
.collect(Collectors.toList());
You can also do something like this:
List<Employee> list2 = list.stream()
.filter(l->{
boolean condition = l.getName()!=null;
if(condition){
l.setAddr("A");
} else {
persistInDB(l);
}
return condition;
})
.collect(Collectors.toList());
Hope this helps!
I am looking for some help in converting some code I have to use the really nifty Java 8 Stream library. Essentially I have a bunch of student objects and I would like to get back a list of filtered objects as seen below:
List<Integer> classRoomList;
Set<ScienceStudent> filteredStudents = new HashSet<>();
//Return only 5 students in the end
int limit = 5;
for (MathStudent s : mathStudents)
{
// Get the scienceStudent with the same id as the math student
ScienceStudent ss = scienceStudents.get(s.getId());
if (classRoomList.contains(ss.getClassroomId()))
{
if (!exclusionStudents.contains(ss))
{
if (limit > 0)
{
filteredStudents.add(ss);
limit--;
}
}
}
}
Of course the above is a super contrived example I made up for the sake of learning more Java 8. Assume all students are extended from a Student object with studentId and classRoomId. An additional requirement I would require is the have the result be an Immutable set.
A quite literal translation (and the required classes to play around)
interface ScienceStudent {
String getClassroomId();
}
interface MathStudent {
String getId();
}
Set<ScienceStudent> filter(
Collection<MathStudent> mathStudents,
Map<String, ScienceStudent> scienceStudents,
Set<ScienceStudent> exclusionStudents,
List<String> classRoomList) {
return mathStudents.stream()
.map(s -> scienceStudents.get(s.getId()))
.filter(ss -> classRoomList.contains(ss.getClassroomId()))
.filter(ss -> !exclusionStudents.contains(ss))
.limit(5)
.collect(Collectors.toSet());
}
Multiple conditions to filter really just translate into multiple .filter calls or a combined big filter like ss -> classRoomList.contains(ss.getClassroomId()) && !exclusion...
Regarding immutable set: You best wrap that around the result manually because collect expects a mutable collection that can be filled from the stream and returned once finished. I don't see an easy way to do that directly with streams.
The null paranoid version
return mathStudents.stream().filter(Objects::nonNull) // math students could be null
.map(MathStudent::getId).filter(Objects::nonNull) // their id could be null
.map(scienceStudents::get).filter(Objects::nonNull) // and the mapped science student
.filter(ss -> classRoomList.contains(ss.getClassroomId()))
.filter(ss -> !exclusionStudents.contains(ss))
.limit(5)
.collect(Collectors.toSet());