This question already has answers here:
Very confused by Java 8 Comparator type inference
(4 answers)
Closed 4 years ago.
I'm prepping up for the Java 8 certificate and the following has me puzzled a littlebit, maybe someone can help me with this?
In the example, a Squirrel class is modelled. It has a name and a weight. Now you can make a Comparator class to sort this thing using both fields. So first sort by name and then by weight. Something like this:
public class ChainingComparator implements Comparator<Squirrel> {
public int compare(Squirrel s1, Squirrel s2) {
Comparator<Squirrel> c = Comparator.comparing(s -> s.getSpecies());
c = c.thenComparingInt(s -> s.getWeight());
return c.compare(s1, s2);
}
}
So far so good.. but then the puzzling part. Underneath the code example, they state that you can write this in one single line by using method chaining. Maybe I misunderstand, but when I chain the comparing and the thenComparing parts, I get a compile error. It's got to do with the types of objects that are compared (first String, then int).
Why does it work when I put in an intermediate variable and not when chaining? And is it possible to chain at all?
As you chain both, the compiler cannot infer the type argument of the returned comparator of comparing()because it depends on the returned comparator of thenComparingInt() that itself cannot be inferred.
Specify the type in the lambda parameter of comparing() (or use a method reference) and it solves the inference issue as the returned type of comparing() could so be inferred. :
Comparator<Squirrel> c = Comparator.comparing((Squirrel s) -> s.getSpecies())
.thenComparingInt(s -> s.getWeight());
Note that specifying the type in the lambda parameter of thenComparingInt() (or using a method reference) such as :
Comparator<Squirrel> c = Comparator.comparing(s -> s.getSpecies())
.thenComparingInt((Squirrel s) -> s.getWeight());
will not work as the receiver (here the return type of the chained method) is not considered in the inference type computation.
This JDK 8 tutorial/documentation explains that very well :
Note: It is important to note that the inference algorithm uses only
invocation arguments, target types, and possibly an obvious expected
return type to infer types. The inference algorithm does not use
results from later in the program.
Yes, it is possible - chain comparing(...) with thenComparing(...) and with compare(...) using method references instead of lambda expressions:
public int compare(Squirrel s1, Squirrel s2) {
return Comparator.comparing(Squirrel::getSpecies)
.thenComparing(Squirrel::getWeight)
.compare(s1, s2);
}
Why does it work this way? I can't explain it better than Brian in his answer to a similar question.
Also, this can be rewritten with a 1 line (assuming you have a List of Squirrel that you want to sort):
list.sort(Comparator.comparing(Squirrel::getSpecies).thenComparing(Squirrel::getWeight));
Related
This question already has answers here:
Why does this generic java method accept two objects of different type?
(4 answers)
Closed 4 years ago.
Say I have this code:
class Demo
{
static <T> T pick(T a1, T a2)
{
return a2;
}
public static void main(String[] args)
{
pick("d", 123);
}
}
From what I learned, it seems like I have stated that the two parameters a1, a2 and the return type of pick must be under the same generic type T.
So why is the compiler allowing me to pass a String and an Integer to pick?
Both String and Integer are subclasses of Object, along with every other type in Java. When compiling a generic method (or class), Java attempts to find the closest supertype shared between every instance of the generic type. This part never fails, because Object exists. But if a generic type is resolved to Object, it may not be useful anymore.
So what’s the point in using generics here if the compiler will let any types to be used? It’s all to do with the return type. Assuming your definition of pick(), what do you think will happen when you try to compile each of these lines?
Object o = pick("Hello", 123); // 1
String s = pick("Hello", 123); // 2
String s = pick("Hello", "world"); // 3
Integer i = pick("Hello", 123); // 4
Integer i = pick(123, 456); // 5
int i = pick(123, 456); // 6
1 compiles just fine, but then you’ve lost any useful type information. This is what would happen if you didn’t use generics at all, and instead just used Object for everything. It’s you’d have had to do before Java 5, along with plentiful casting and exception catching.
2 and 4 won’t compile:
error: incompatible types: inferred type does not conform to upper bound(s)
Since the two arguments to pick() share only Object as a common supertype, T becomes Object and an Object is returned and you can’t assign an Object to a String or an Integer.
3 works just fine though. Both arguments have the same type, so T is easily determined to be String. 5 works for similar reasons.
6 also works, but not because T becomes int. int is a primitive type, so it can’t be used in a generic. When attempting to resolve the generic type T, the compiler first autoboxes the primitive arguments into a ‘real’ class (Integer in this case). This also happens for 4 and 5, and even when you simply assign a literal like Integer i = 123;. The difference here is that the result (an Integer) is unboxed back to an int so that it can be assigned to i.
In your example implementation of pick(), the return value should have the same type as the second parameter. If your API specifies that the result is always derived primarily from that parameter, you could use two generic types:
static <T, U> T pick(U a1, T a2) {
return a2;
}
With this addition, 2 still fails to compile, but 4 works as you’d expect.
The compiler will walk down the inheritance tree of a1 and a2 to find a common ancestor, in this case Object. Part of the reason you aren't seeing that is that you are discarding the return value. The following two versions won't compile:
String choice = pick("d", 123);
and
Integer choice = pick("d", 123);
The following will, however:
Object choice = pick("d", 123);
I want to replace lambda expression by method reference in the below example :
public class Example {
public static void main(String[] args) {
List<String> words = Arrays.asList("toto.", "titi.", "other");
//lambda expression in the filter (predicate)
words.stream().filter(s -> s.endsWith(".")).forEach(System.out::println);
}
}
I want to write a something like this :
words.stream().filter(s::endsWith(".")).forEach(System.out::println);
is it possible to transform any lambda expression to method reference.
There is no way “to transform any lambda expression to method reference”, but you can implement a factory for a particular target type, if this serves recurring needs:
public static <A,B> Predicate<A> bind2nd(BiPredicate<A,B> p, B b) {
return a -> p.test(a, b);
}
with this, you can write
words.stream().filter(bind2nd(String::endsWith, ".")).forEach(System.out::println);
but actually, there’s no advantage. Technically, a lambda expression does exactly what you want, there’s the minimum necessary argument transformation code, expressed as the lambda expression’s body, compiled into a synthetic method and a method reference to that synthetic code. The syntax
s -> s.endsWith(".") also is already the smallest syntax possible to express that intent. I doubt that you can find a smaller construct that would still be compatible with the rest of the Java programming language.
You can use selectWith() from Eclipse Collections. selectWith() takes a Predicate2 which takes 2 parameters instead of a Predicate. The second parameter to selectWith() gets passed as the second parameter to the Predicate2 every time it's called, once per item in the iterable.
MutableList<String> words = Lists.mutable.with("toto.", "titi.", "other");
words.selectWith(String::endsWith, ".").each(System.out::println);
By default Eclipse Collections is eager, if you want to iterate lazily then you can use asLazy()
words.asLazy().selectWith(String::endsWith, ".").each(System.out::println);
If you can't change from List:
List<String> words = Arrays.asList("toto.", "titi.", "other");
ListAdapter.adapt(words).selectWith(String::endsWith, ".").each(System.out::println);
Eclipse Collections' RichIterable has several other *With methods which work well with method references, including rejectWith(), partitionWith(), detechWith(), anySatisfyWith(), allSatisfyWith(), noneSatisfyWith(), collectWith()
Note: I am a contributor to Eclipse Collections.
I was reading this article and tried counting some words in a text file and found I could not reverse sort similarly to how it showed in listing 1 of the article.
I have some code that works though:
public class WordCounter {
public static final PrintWriter out = new PrintWriter(System.out, true);
public static void main(String... args) throws IOException {
//The need to put "", in front of args in the next line is frustrating.
try (Stream<String> lines = Files.lines(Paths.get("", args))) {
lines.parallel()
.map(l -> l.toLowerCase().replaceAll("[^a-z\\s]", "").split("\\s"))
.flatMap(Arrays::stream)
.filter(s -> !s.isEmpty())
.collect(Collectors.groupingBy(
Function.identity(), Collectors.counting()))
// Sort Map<K,V> Entries by their Integer value descending
.entrySet().parallelStream()
// MY QUESTION IS ABOUT THIS METHOD:
.sorted(
Comparator.comparing(Map.Entry::getValue, Comparator.reverseOrder()))
// --------------------------------- //
.forEachOrdered(e -> out.printf("%5d\t%s\n", e.getValue(), e.getKey()));
}
out.close();
}
}
So the article would suggest that the line:
.sorted(Comparator.comparing(Map.Entry::getValue, Comparator.reverseOrder()))
could be written as:
.sorted(Comparator.comparing(Map.Entry::getValue).reversed())
For this though, the Java compiler complains that:
Error:(46, 49) java: invalid method reference non-static method
getValue() cannot be referenced from a static context
The two comparing method signatures have the exact same first parameter and static scope, yet the former works while the latter complains about getValue being non-static.
My original thought was to write it as either:
.sorted(Map.Entry.comparingByValue())
Which compiles and runs but is not reversed. Or as:
.sorted(Map.Entry.comparingByValue().reversed())
Which again doesn't compile, giving an error message of:
Error:(48, 62) java: incompatible types: java.util.Comparator<java.util.Map.Entry<java.lang.Object,V>> cannot be converted to java.util.Comparator<? super java.util.Map.Entry<java.lang.String,java.lang.Long>>
Okay, so, that should be:
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
Which works.
I can't seem to see how to give a similar generic type specification to the Map.Entry::getValue form in my "could be written as" line though.
As to why this happens: while type inference has come leaps and bounds in Java 8, it will still only use the return target type if the return value is assigned to something.
In Java 7 we were only able to use this in an assignment context (using =) and it was a little bit clunky. In Java 8, it's less clunky and we can use it in invocation contexts (passed as a method argument, which assigns it to the formal parameter).
So the way I understand it, if the method invocation isn't used in an assignment context or invocation context, target type inference simply turns off, because it's no longer something called a poly expression (15.12, 18.5.2). So says the JLS.
In short, target type inference only works if the return value is:
assigned directly to a variable using =, as in v = foo();.
passed directly to a method, as in bar(foo()).
Once you chain a method call in, like v = foo().zap(), it stops working.
Lifted from my comment:
I can't seem to see how to give a similar generic type specification to the Map.Entry::getValue form though.
This would be Map.Entry<String, Long>::getValue.
This question already has answers here:
Java Generics: Wildcard capture misunderstanding
(7 answers)
Closed 7 years ago.
Imagine an interface like this
public interface MessageParameter<T> {
public List<T> unmarshal(byte[] array);
public int getLength(List<T> values);
}
and a consumer of that interface
public class GenericUser {
List<MessageParameter<?>> payload = new ArrayList<>();
public void run() {
byte[] byteArray = new byte[] { 1, 2 };
for (MessageParameter<?> element : payload) {
element.getLength(element.unmarshal(byteArray)); //compiler error
}
}
}
The compiler gives an error
The method getLength(List<capture#1-of ?>) in the type MessageParameter<capture#1-of ?> is not applicable for the arguments (List<capture#2-of ?>)
Clearly since I am using element in both method calls, the type of both is the same and it should be allowed. Another way to ask the same question, why is the compiler creating capture#2?? why can't it deduce that they are logically both the same capture?
Am I missing something? is there a counter-example where this code would throw a runtime exception??
My main question is not how to fix the code (although that would be interesting as well, my current solution is to use Object instead of ?), but what is the logical reason for this error? It looks to me like a shortcoming on the implementation of the compiler more than a logical limitation
The answer is that the compiler is not that smart to accept that the runtime type corresponding to ? is the same because it does not care that your one-line expression involves the same element:
element.getLength(element.unmarshal(byteArray));
is semantically similar to:
List<?> unmarshalledList = element.unmarshal(byteArray);
element.getLength(unmarshalledList);
In this case, it is not so obvious that the list unmarshalledList would surely have to have the same "any-type" as the one expected by getLength(). The above are two separate statements (even though they're contiguous). Imagine that they're not contiguous. You may have something like:
MessageParameter<?> otherElement = getOtherElement();
for (MessageParameter<?> element : payload) {
List<?> unmarshalledList = element.unmarshal(byteArray);
// unmarshalledList can be re-assigned from another parameterized type
unmarshalledList = otherElement.unmarshal(byteArray);
element.getLength(unmarshalledList); // error
}
In other words, the compiler cannot assume that the variable unmarshalledList will retain the same ? type from element when the program reaches the statement invoking getLength on the same element. It can be re-assigned to a different parameterized type in between.
I believe you're misinterpreting the meaning of ? in a generic. That symbol is known as the wildcard; and it refers to a truly unknown type. This is different from your current effort, in which it would literally be better to use Object, as your types are not completely unknown—you know that they both implement Object and can reference them as such. (? extends Object might be better in some places).
As to the reason why ? is not synonymous with Object, remember that primitives do not inherit from Object, but may be referenced with the wildcard. Therefore, after type erasure, your program cannot be certain that the two wildcards are referring to compatible entities; unless you explicitly tell it as much.
I have a list with some User objects and i'm trying to sort the list, but only works using method reference, with lambda expression the compiler gives an error:
List<User> userList = Arrays.asList(u1, u2, u3);
userList.sort(Comparator.comparing(u -> u.getName())); // works
userList.sort(Comparator.comparing(User::getName).reversed()); // works
userList.sort(Comparator.comparing(u -> u.getName()).reversed()); // Compiler error
Error:
com\java8\collectionapi\CollectionTest.java:35: error: cannot find symbol
userList.sort(Comparator.comparing(u -> u.getName()).reversed());
^
symbol: method getName()
location: variable u of type Object
1 error
This is a weakness in the compiler's type inferencing mechanism. In order to infer the type of u in the lambda, the target type for the lambda needs to be established. This is accomplished as follows. userList.sort() is expecting an argument of type Comparator<User>. In the first line, Comparator.comparing() needs to return Comparator<User>. This implies that Comparator.comparing() needs a Function that takes a User argument. Thus in the lambda on the first line, u must be of type User and everything works.
In the second and third lines, the target typing is disrupted by the presence of the call to reversed(). I'm not entirely sure why; both the receiver and the return type of reversed() are Comparator<T> so it seems like the target type should be propagated back to the receiver, but it isn't. (Like I said, it's a weakness.)
In the second line, the method reference provides additional type information that fills this gap. This information is absent from the third line, so the compiler infers u to be Object (the inference fallback of last resort), which fails.
Obviously if you can use a method reference, do that and it'll work. Sometimes you can't use a method reference, e.g., if you want to pass an additional parameter, so you have to use a lambda expression. In that case you'd provide an explicit parameter type in the lambda:
userList.sort(Comparator.comparing((User u) -> u.getName()).reversed());
It might be possible for the compiler to be enhanced to cover this case in a future release.
You can work around this limitation by using the two-argument Comparator.comparing with Comparator.reverseOrder() as the second argument:
users.sort(comparing(User::getName, reverseOrder()));
Contrary to the accepted and upvoted answer for which bounty has been awarded, this doesn't really have anything to do with lambdas.
The following compiles:
Comparator<LocalDate> dateComparator = naturalOrder();
Comparator<LocalDate> reverseComparator = dateComparator.reversed();
while the following does not:
Comparator<LocalDate> reverseComparator = naturalOrder().reversed();
This is because the compiler's type inference mechanism isn't strong enough to take two steps at once: determine that the reversed() method call needs type parameter LocalDate and therefore also the naturalOrder() method call will need the same type parameter.
There is a way to call methods and explicitly pass a type parameter. In simple cases it isn't necessary because it's inferred, but it can be done this way:
Comparator<LocalDate> reverseComparator = Comparator.<LocalDate>naturalOrder().reversed();
In the example given in the question, this would become:
userList.sort(Comparator.comparing<User, String>(u -> u.getName()).reversed());
But as shown in the currently accepted answer, anything that helps the compiler inferring type User for the comparing method call without taking extra steps will work, so in this case you can also specify the type of the lambda parameter explicitly or use a method reference User::getName that also includes the type User.
The static method Collections.reverseOrder(Comparator<T>) seems to be the most elegant solution that has been proposed. Just one caveat:
Comparator.reverseOrder() requires that T implements comparable and relies on the natural sorting order.
Collections.reverseOrder(Comparator<T>) has no restriction applied on type T