As it almost but not really matches Optional, maybe you might reconsider the logic:
Java 8 has a limited expressiveness:
Optional<Elem> element = ...
element.ifPresent(el -> System.out.println("Present " + el);
System.out.println(element.orElse(DEFAULT_ELEM));
Here the map might restrict the view on the element:
element.map(el -> el.mySpecialView()).ifPresent(System.out::println);
Java 9:
element.ifPresentOrElse(el -> System.out.println("Present " + el,
() -> System.out.println("Not present"));
In general the two branches are asymmetric.
Answer from Joop Eggen on Stack OverflowAs a stream call chain is complex make two streams - avoiding the conditional branches.
String ncourseIds = equivalentCourses.stream()
.filter(equivalentCourse -> equivalentCourse.getNcourse() != null)
.map(EquivalentCourse::getNcourse)
.map(x -> String.valueOf(x.getId()))
.collect(Collectors.joining(", "));
String pastCourseIds = equivalentCourses.stream()
.filter(equivalentCourse -> equivalentCourse.getNcourse() == null
&& equivalentCourse.getPastCourse() != null)
.map(EquivalentCourse::getPastCourse)
.map(x -> String.valueOf(x.getId()))
.collect(Collectors.joining(", "));
This also is code focusing on the resulting two strings, with an efficient joining.
By the way, if this is for an SQL string, you may use a PreparedStatement with an Array.
Embellishment as commented by @Holger:
String ncourseIds = equivalentCourses.stream()
.map(EquivalentCourse::getNcourse)
.filter(Objects::nonNull)
.map(NCourse::getId)
.map(String::valueOf)
.collect(Collectors.joining(", "));
String pastCourseIds = equivalentCourses.stream()
.filter(equivalentCourse -> equivalentCourse.getNcourse() == null)
.map(EquivalentCourse::getPastCourse)
.filter(Objects::nonNull)
.map(EquivalentCourse::getPastCourse)
.map(PastCourse::getId)
.map(String::valueOf)
.collect(Collectors.joining(", "));
You could group by condition and then remap:
public void booleanGrouping() throws Exception {
List<String> strings = new ArrayList<>();
strings.add("ala");
strings.add("ela");
strings.add("jan");
strings.stream()
.collect(
Collectors.groupingBy(s -> s.endsWith("a")) // using function Obj -> Bool not predicate
).entrySet()
.stream()
.collect(
Collectors.toMap(
e -> e.getKey() ? "Present" : "Past",
e -> e.getValue().stream().collect(Collectors.joining(""))
)
);
}
First stream group by condition, you should use equivalentCourse.getNcourse() != null second remap collections from value to string. You could introduce:
enum PresentPast{
Present, Past
PresentPast is(boolean v){
return v ? Present : Past
}
}
and change e -> e.getKey() ? "Present" : "Past" to enum based solution.
Edit:
Solution for else if:
public Map<Classifier, String> booleanGrouping() throws Exception {
List<String> strings = new ArrayList<>();
strings.add("ala");
strings.add("ela");
strings.add("jan");
// our ifs:
/*
if(!string.endsWith("n")){
}else if(string.startsWith("e")){}
final map should contains two elements
endsWithN -> ["jan"]
startsWithE -> ["ela"]
NOT_MATCH -> ["ala"]
*/
return strings.stream()
.collect(
Collectors.groupingBy(Classifier::apply) // using function Obj -> Bool not predicate
).entrySet()
.stream()
.collect(
Collectors.toMap(
e -> e.getKey(),
e -> e.getValue().stream().collect(Collectors.joining(""))
)
);
}
enum Classifier implements Predicate<String> {
ENDS_WITH_N {
@Override
public boolean test(String s) {
return s.endsWith("n");
}
},
STARTS_WITH_E {
@Override
public boolean test(String s) {
return s.startsWith("e");
}
}, NOT_MATCH {
@Override
public boolean test(String s) {
return false;
}
};
public static Classifier apply(String s) {
return Arrays.stream(Classifier.values())
.filter(c -> c.test(s))
.findFirst().orElse(NOT_MATCH);
}
}
The essential observation here is that your problem involves a non-isomorphic transformation: a single input element may map to zero, one, or two output elements. Whenever you notice this, you should immediately start looking for a solution which involves flatMap instead of map because that's the only way to achieve such a general transformation. In your particular case you can first apply filter for a one-to-zero element mapping, then flatMap for one-to-two mapping:
List<Integer> result =
IntStream.rangeClosed(1, 10)
.filter(i -> 10 % i == 0)
.flatMap(i -> i == 5 ? IntStream.of(i) : IntStream.of(i, 10 / i))
.boxed()
.collect(toList());
(assuming import static java.util.stream.Collectors.toList)
You could declare a body for a lambda. For example:
Runnable run = () -> System.out.println("Hey");
Could be
Runnable run = () -> {
System.out.println("Hey");
};
Within that body, you can create nested statements:
Runnable run = () -> {
int num = 5;
if(num == 5) {
System.out.println("Hey");
}
};
One thing you can do is to change the null list to something which results in the same output:
List<DataObj> dataObjs = Optional.ofNullable(data.getDataObjs()).orElse(Collections.singletonList(data));
dataObjs will now be a list with a single element in the case that data.getDataObjs() is null.
Now you don't need the if/else:
value = dataObjs.stream().map(dataObject -> getValue(dataObject)).filter(Objects::nonNull).findFirst().orElse(null);
I your aim is to isolate the logic of your if-else, and potentially allowing it to be replaced, maybe you could do the following : Your lambda take as input your data list, and gives you back a String value. Therefore, you can use a java.util.Function interface, like this:
Function<List<DataObj>, String> extractor = dataList
-> dataList == null? Stream.of(DEFAULT_DATA_OBJ) : dataList.stream()
.map(dataObject -> getValue(dataObject))
.filter(Objects::nonNull)
.findFirst()
.orElse(null)
Note, you still have a ternary operator (Do not see how you could do without it, because if your list can be null, you cannot even use Stream.concat to protect from empty-list). However, with that construct, the logic of your ternary operator is replaceable if you make the extractor function replaceable in your code.
Exemple:
public static void main(String... args) {
final List<DataObj> dataList = ...;
final DataObj defaultValue = ...;
Function<List<DataObj>, String> extractor = dataList
-> dataList == null? Stream.of(defaultValue) : dataList.stream()
.map(dataObject -> getValue(dataObject))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
doStuff(dataList, extractor);
// Now, if you want to change your extraction logic, do
doStuff(dataList, whatever -> "Return a constant title");
}
public static void doStuff(final List<DataObj> dataList, final Function<List<DataObj, String> titleExtractor) {
// Do stuff here
}
Some example of lambda usage:
You can group your sites by active and not active, after that you can perform some logic to both. Put the big logic into a method and then call in the map() function.
You can use filter() as well to get only a part of the stream.
public class Lambda {
static class Site {
public boolean isActive;
public boolean otherCondition;
public boolean isActive() {
return isActive;
}
public boolean isOtherCondition() {
return otherCondition;
}
public void setProperty(String property) {
}
}
public static Site someOperation(Site site) {
//some code
return site;
}
public static void main(String[] args) {
ArrayList<Site> sites = new ArrayList<>();
Map<Boolean, List<Site>> groupedSites = sites.stream().collect(Collectors.groupingBy(Site::isActive));
groupedSites.get(Boolean.TRUE).stream().filter(Site::isOtherCondition).map(Lambda::someOperation).forEach(p -> p.setProperty("something"));
}
}
I would put the complex logic into a separate method and reference to it in the map(…).
sites.stream()
.map(Foo::bar)
…;
This would use a method in a class Foo that could look like this:
public static String bar(Site site)
{
return site.toString();
}
Of course you can use arbitrary logic in the method but it should be free of side effects.
And yes, if the method used for mapping is thread safe, you can use parallel() to run the mappings in parallel.
If/else or even switch are not the "good OO design" answer. Neither are lambdas. Retrieving the status from somewhere, to make then a decision based on that - that is procedural programming; not OO.
You see, what you actually have in front of you - is a state machine. You have different states; and the point is: if your "thing" is in state A; then you want to do something ( invoke handleA() ). If your are in state B; you want to do something too ( like invoke handleB() ).
So, what you actually have is:
abstract class State {
abstract void doIt();
...
StateA extends State {
@Override
void doIt() { handleA(); }
So, from the client side, you just call doIt() on same object of class State.
Thus: if you are really interested in improving your code base, learn how to use polymorphism to get rid of your if/else/switch statements.
You can watch this video to get an idea what I am talking about.
Maybe I hadn't fully explained my problem, but my colleague proposed another solution that I really like. He suggested I make the if-else block a java.util.Supplier, and invoke it in the Entity class where I need it.
So my code went from this block of logic sprinkled everywhere:
public class ServiceImpl {
...
if (entity.getStatus) == 'A' then
finalStatus = handleA();
else if (entity.getStatus() == 'B') then
finalStatus = handleB();
else
finalStatus = handleEverythingElse();
...
}
To this nicely compacted form:
public class ServiceImpl {
finalStatus = entity.getFinalStatus(this::handleStatus);
public int handleStatus() {
return dao.getStatus();
}
}
With the implementation in my Entity class:
public class Entity {
public int handleStatus(Supplier<Integer> s) {
int finalStatus;
if (status) == 'A' then
finalStatus = handleA();
else if (status() == 'B') then
finalStatus = handleB();
else
finalStatus = supplier.get();
return status;
}
}
I hope this make sense...
No "if's, while's or for's", but no lambdas either:
return (first_number == second_number ? "PERFECT" :
first_number > second_number ? "ABUNDANT" : "DEFICIENT");
The ? : is called the conditional operator in the Java Language Specification (see 15.25. Conditional Operator ? :), but is commonly known as the ternary operator, since it is the only operator in Java with 3 parts.
How about this solution with lambdas? I'm assuming integer values, but shouldn't be too hard to switch to float or double.
int first_number = 10;
int second_number = 20;
IntBinaryOperator subtract = (n1, n2) -> n1 - n2; // Subtract second from first value
IntUnaryOperator determineSign = n -> Integer.signum(n); // Determine the sign of the subtraction
IntFunction<String> message = key -> { // Sign of 0 means value has been 0 (first_number == second_number). Sign of 1 = positive value, hence equals first_number > second_number, otherwise return the default.
Map<Integer, String> messages = new HashMap<>();
messages.put(0, "PERFECT");
messages.put(1, "ABUNDANT");
return messages.getOrDefault(key, "DEFICIENT");
};
return message.apply(determineSign.applyAsInt(subtract.applyAsInt(first_number, second_number)));
Edit: Andreas mentioned valid concerns, I agree you wouldn't do it like that. But I think its more about proving that it is possible using lambdas. :) Another method (ab)using Optional:
int first_number = 20;
int second_number = 20;
Optional<Integer> dummy = Optional.of(0); // Dummy allowing to call filter on the Optional
Predicate<Integer>isAbundant = i -> first_number > second_number; // Check if input is abundant
Predicate<Integer> isPerfect = i -> first_number == second_number; // Check if input is perfect
Supplier<String> other = () -> dummy.filter(isAbundant).map(i -> "ABUNDANT").orElse("DEFICIENT"); // Fallback, if input is not perfect. Check for abundant or return default
Supplier<String> validate = () -> dummy.filter(isPerfect).map(i -> "PERFECT").orElse(other.get()); // Check if input is perfect or use fallback
return validate.get();
You can use ifPresentOrElse (introduced in Java 9):
Optional.ofNullable(userMail)
.ifPresentOrElse(newUser::setEmail, () -> {throw new MissingServletRequestParameterException("email", "String");});
to replace this part of you code:
if( null == fbUser.getEmail() ) { newUser.setEmail(userMail); }
if( null == newUser.getEmail() ) { throw new MissingServletRequestParameterException("email", "String"); }
You can use Optionals but note that whether this looks "nicer" is arguable.
newUser.setEmail(
Optional.ofNullable(fbUser.getEmail())
.orElse(Optional.ofNullable(userMail)
.orElseThrow(() -> new MissingServletRequestParameterException("email", "String")))
);
You can write, given for instance a List<Boolean>:
if (!list.stream().allMatch(x -> x)) {
// not every member is true
}
Or:
if (list.stream().anyMatch(x -> !x)) {
// at least one member is false
}
If you have an array of booleans, then use Arrays.stream() to obtain a stream out of it instead.
More generally, for a Stream providing elements of (generic) type X, you have to provide a Predicate<? super X> to .{all,any}Match() (either a "full" predicate, or a lambda, or a method reference -- many things go). The return value of these methods are self explanatory -- I think.
Now, to count elements which obey a certain predicate, you have .count(), which you can combine with .filter() -- which also takes (whatever is) a Predicate as an argument. For instance checking if you have more than 2 elements in a List<String> whose length is greater than 5 you'd do:
if (list.stream().filter(s -> s.length() > 5).count() > 2L) {
// Yup...
}
Your problem
Your current problem is that you use directly a lambda expression. Lambdas are instances of functional interfaces. Your lambda does not have the boolean type, that's why your if does not accept it.
This special case's solution
You can use a stream from your collections of booleans here.
if (bools.stream().allMatch((Boolean b)->b)) {
// do something
}
It is actually much more powerful than this, but this does the trick I believe.
General hint
Basically, since you want an if condition, you want a boolean result.
Since your result depends on a collection, you can use Java 8 streams on collections.
Java 8 streams allow you to do many operations on a collection, and finish with a terminal operation. You can do whatever complicated stuff you want with Stream's non-terminal operations. In the end you need one of 2 things:
- use a terminal operation that returns a
boolean(such asallMatch,anyMatch...), and you're done - use any terminal operation, but use it in a
booleanexpression, such asmyStream.filter(...).limit(...).count() > 2
You should have a look at your possibilities in this Stream documentation or this one.