Below are the two lines of my code snippet:
List<String> listDevs = Arrays.asList("alvin", "Alchemist", "brutus", "larsen", "jason", "Kevin");
listDevs.sort(Comparator.comparing(String::length)); //This works fine
listDevs.sort(String::compareToIgnoreCase); //This works fine
But (out of expermient) when I try to write
listDevs.sort(Comparator.comparing(String::compareToIgnoreCase));
The compiler throws error
Cannot make a static reference to the non-static method
compareToIgnoreCase(String) from the type String
Similar happens to the below code
listDevs.sort(Comparator.comparing(String::compareTo));
I understand the error and that it works fine if I remove the Comparator.comparing (as shown above).
But my point is, how does this line works?
listDevs.sort(Comparator.comparing(String::length));
I believe I am missing something. I have read this thread. Is this the same scenario?
Comparator.comparing expects a Function which describes a comparable property of the elements. So String::length is sufficient as length() is a property of the String evaluating a String to an int (that’s why comparingInt is preferable here).
In contrast, String.compareToIgnoreCase and String.compareTo are comparison methods. They compare two String objects. So references to them are sufficient where a Comparator is expected, but not where a property Function is expected.
It’s like you have a factory saying “Gimme an engine, and we build a car for you” and you are trying to give them a complete car. While that existing car is valid where a car is expected, there is no sense in passing it to the factory to built a car.
Unfortunately, the current compiler implementation is very bad at reporting error with functional signatures. You will almost always see messages like “Cannot make a static reference to the non-static method …” when signatures mismatch.
The sort method expected a Comparator.
When you do this, you are indeed providing one.
listDevs.sort(Comparator.comparing(String::length));
Same happens here(but a bit non-intuitive):
listDevs.sort(String::compareToIgnoreCase)
listDevs.sort((left, right) -> left.compareToIgnoreCase(right)); // same thing as above
That's exactly the definition of a Comparator - take two Strings and return an int.
The line that you say how come this works: listDevs.sort(Comparator.comparing(String::length)); is actually pretty simple.
Comparator.comparing takes a Function that transforms your input type into something that is Comparable. In your case takes a String and returns an Integer; which is Comparable.
JLS says Compile-Time Declaration of a Method Reference of ReferenceType :: [TypeArguments] Identifier can be interpreted in different ways.
Given a targeted function type with n parameters, a set of potentially applicable methods is identified:
ReferenceType :: [TypeArguments] Identifier has two different arities, n and n-1, are considered, to account for the possibility that this form refers to either a static method or an instance method.
A method reference expression of the form ReferenceType :: [TypeArguments] Identifier can be interpreted in different ways. If Identifier refers to an instance method, then the implicit lambda expression has an extra parameter with type of this compared to if Identifier refers to a static method. It is possible for ReferenceType to have both kinds of applicable methods, so the search algorithm described above identifies them separately, since there are different parameter types for each case.
Comparator.comparing method accept a Function<T,R extends Comparable<? super R>>. when you use String::compareToIgnoreCase that will reports error,because it has two parameters one is implicit this another is a comparing string of method parameter, so it is more like a BiFunction<String,String,Integer> not a Function<String,Integer>.
BiFunction<String, String, Integer> comparator = String::compareToIgnoreCase;
// you can't assign a BiFunction to a Function
// because one is incompatiable with another.
Function<String,Integer> function = comparator;
Stream.sort method accept a Comparator, and Comparator is more like a BiFunction<T,T,Integer> so it is compatiable with String::compareToIgnoreCase. on the other hand, they can be interchangeable. for example:
Comparator<String> primary = String::compareToIgnoreCase;
BiFunction<String, String, Integer> comparator1 = primary::compare;
Comparator<String> comparator2 = comparator1::apply;
you can using comparing(String::toLowerCase) instead, it is equalivent to String::compareToIgnoreCase, for example:
// String::compareToIgnoreCase
listDevs.sort(String::compareToIgnoreCase);
// comparing(String::toLowerCase)
listDevs.sort(comparing(String::toLowerCase))
Related
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
I am having trouble understanding the syntax for a method reference, where there are two parameters a and b, and the reference is to a method of a on b.
For example I understand how
Arrays.sort(personArray, comparators::compareByName);
is equivalent to
Arrays.sort(personArray, (o1, o2) -> comparators.compareByName(o1, o2));
because in that case the lambda parameters match the method call parameters (o1, o2).
Howevever for this lambda
stream.sorted((o1, o2) -> o1.compareToIgnoreCase(o2));
my IDE tells me that is equivalent to:
stream.sorted(String::compareToIgnoreCase);
and I am not finding a rule for replacing that syntax: a.method(b) with a method reference.
For example, what if there are three or more parameters to the lambda? Is that legal? Does the first parameter become the method target, and the remaining become the parameters?
I think you're looking for JLS section 15.13.3, which includes:
If the form is ReferenceType :: [TypeArguments] Identifier, the body of the invocation method similarly has the effect of a method invocation expression for a compile-time declaration which is the compile-time declaration of the method reference expression. Run-time evaluation of the method invocation expression is as specified in §15.12.4.3, §15.12.4.4, and §15.12.4.5, where:
The invocation mode is derived from the compile-time declaration as specified in §15.12.3.
If the compile-time declaration is an instance method, then the target reference is the first formal parameter of the invocation method. Otherwise, there is no target reference.
If the compile-time declaration is an instance method, then the arguments to the method invocation expression (if any) are the second and subsequent formal parameters of the invocation method. Otherwise, the arguments to the method invocation expression are the formal parameters of the invocation method.
Note the last two bullets, basically.
For example, what if there are three or more parameters to the lambda? Is that legal? Does the first parameter become the method target, and the remaining become the parameters?
Yup :)
I would give a couple of examples here, for those who find Oracle documentation a bit hard to take in.
Imagine you need a reference to a Comparator instance:
.sorted(String::compareTo)
String::compareTo is identical to:
(String a, String b) -> a.compareTo(b);
Because, as Jon explained, a method reference will be transformed to a lambda that will expect 2 parameters. The actual arbitrary object passed in the stream as a first argument, and one more parameter(since Comparator expects int compare(T o1, T o2)).
Another case:
.map(Employee::getSalary)
In this case map expects: Function. Function requires implementation of R apply(T var1) - a method with 1 argument. In this case the only parameter that will be passed to the lambda is the actual arbitrary object - instance on Employee.
To sum up - depending on the compile time context, method reference to arbitrary object will always be "transformed" into a lambda that expects that object as a first parameter + any number of parameters that the target method requires in the same corresponding order.
public class helloworld {
public static void main(String[] args) {
String text = "Hello World";
l(text);
int n = 0;
l("--------------------------");
l(n);
}
public static void l(Object obj) {
System.out.println(obj);
}
}
I wrote this simple program in Java and it worked. Now I am confused that if all the data types (int, char, double etc.) come under Object, then why do we specify which data type we want to accept when we pass values?
I mean we can always use the data type Object as used in the function l. Is there a specific reason why people don't always use Object as their data type to pass values?
There is an implicit conversion defined between all primitive types and their respective object counterparts:
int -> Integer
char -> Character
etc...
This is called autoboxing.
Is there a specific reason why people don't always use "Object" as their data type to pass values?
Since Java is strongly typed, you cannot do a whole lot with Object.
E.g. try this:
static Object add(Object a, Object b) {
return a + b; // won't compile
}
This is because methods, operators, etc. available to use depend on the static type of the variable.
println can accept Object because it only needs to call the toString method. If you only need the limited functionality provided by the methods in Object, then sure, you can use it as a type. This is rarely the case, however.
For the primitives you mentioned, they are not really objects, they will simply be boxed to their representation as an object. An int would become an Integer, a long would become a Long etc.
Read this article about Autoboxing in java.
As for your question
Is there a specific reason why people don't always use "Object" as
their data type to pass values?
If you specify Object as the parameter of your method you won't be able to call the methods the real object contains without doing a cast. For example, if you have a custom object AnyObject that contains a method anyMethod, you won't be able to call it without casting the object to AnyObject.
It will also be unsafe as you will be able to pass any type of object to a method which may not be designed to function properly with any of these types. A method containing only System.out.println is not representative of a real use case, it will work with any object simply because by default the println will call the toString method which is already defined in an Object.
While it does look like a function that appears to accept all types of parameters, you will have to deal with these
The function signature becomes less informative.
No more overloading
You have to do a lot of type checking and casting in the function body to avoid run time errors.
Although the method seemingly accepts all objects, you would never know the actual subset of them until you see the method definition.
The function body might end up having more code to eliminate the wrong types than for its real goal. For example, your function only prints the value. Imagine a function that predominantly does some integer operation.
Increases the probability of run time errors, as the compiler cannot throw errors for missing casts.
I am working on this project currently. It works surprisingly well.
Yet, after re-reading the README again, I started to wonder about how to document something that is bugging me...
To quote the example, and forgetting for a moment that exceptions can be thrown, it reads:
Files.list(somePath).map(Path::toRealPath).forEach(System.out::println)
OK. Now, the method of Path involved is this one. Of course, we do not pass any LinkOption.
Again: let's forget for a moment that it throws any exception.
Stream's .map() takes a Function as an argument. This interface, for Function<T, R>, is defined as:
R apply(T t);
But the method I am using accepts no arguments. At a first glance, it does not seem to match a Function, right? Except that...
It can be written as:
path -> path.toRealPath()
It therefore looks like the mechanism used is somewhat able to invoke a method on the "stream object" if the method reference has no argument, or something like that...
I'd like to document this accordingly, and I am missing something here.
What am I missing?
Non-static methods have the receiver (this) object as an implicit first argument. Therefore, Class::nonStaticMethod has one more argument than you might expect.
Java Language Specification Section 15.13.1, Compile-Time Declaration of a Method Reference:
Second, given a targeted function type with n parameters, a set of potentially applicable methods is identified:
If the method reference expression has the form ReferenceType :: [TypeArguments] Identifier, the potentially applicable methods are the member methods of the type to search that have an appropriate name (given by Identifier), accessibility, arity (n or n-1), and type argument arity (derived from [TypeArguments]), as specified in §15.12.2.1.
Two different arities, n and n-1, are considered, to account for the possibility that this form refers to either a static method or an instance method.
I came across the following snippet of code that uses generics.
public class Generics<T> {
public static <T> T replaceIfNull(T objectToCheck, T defaultValue) {
return objectToCheck == null ? defaultValue : objectToCheck;
}
public static <T> boolean CheckIfNull(T objectToCheck) {
return objectToCheck == null ? true : false;
}
}
I am having a difficult time truly understanding how generics work, formed and used. I have a high level understanding, meaning that I know the definition of generics. And by the definition my interpretation of this code snippet is that replaceIfNull method checks for null values of any object and then returns a default value (whatever that is). And that CheckIfNull method is similar, in that it checks null value for any object.
But how does this work? Why is the method formed with <T>, which seems to be a type and then there is T following. I do not understand this syntax, <T> T means? And how does T become a type in the parameters? How come this method, for example, could not be written as
public static Object replaceIfNull(Object objectToCheck, Object defaultValue) {
return objectToCheck == null ? defaultValue : objectToCheck;
}
Thank you in advance for your clarification.
Let's start with answering your last question. The method rewrite with Object instead of generics has two drawbacks. First, it will not work with primitives (which might not be a real drawback in this case, since you are checking against null, and primitives cannot take null values, but still...). Second, it will require casting. If , for example, you use this method on two strings, like replaceIfNull(myString, "Default Value"), then you would expect to get a String as an output, right? But instead the method declared to be returning Object; so there is no way for compiler to know that it will return a String, and you will have to do casting every time you use it: `String result = (String) replaceIfNull(myString, "Default Value");' Generics were introduced specifically to fix this situation.
You can think of generics as templates; whatever type you put in the angle braces will be used later in the code whenever you use type parameter. So, <T> means: "Here will go some type; replace T with it everywhere in the code".
And the last question is about the method signature. I think here you mixed up two different methods of using generics - on a class level, and on a method level. Since you've already introduced the type parameter on the class level, there is no need to do it again on a method level, so I think you can safely remove <T> from method declaration.
I suggest you read a more detailed explanation on generics here: http://docs.oracle.com/javase/tutorial/java/generics/
The subject of Generics is very broad and you can read about it extensively on Oracle's website or on Stack Overflow.
Why is the method formed with <T>, which seems to be a type and then there is T following.
This is a generic method. The <T> declares a new type variable with no bounds.
// v return type
public static <T> T replaceIfNull(T objectToCheck, T defaultValue) {
// ^ new type variable ^ type variable used as a type
Within the method body, since the type variable has no bounds, T can, at most, be interpreted as Object. You're only going to have access to method declared in Object on expressions of type T.
Outside the method, ie. in invocation contexts, the type variable T will receive a concrete type value. That is, it will either infer it from its invocation context or it will be provided explicitly.
For example, in
replaceIfNull(someStringVar, otherStringVar);
the type variable T will be bound to String, so all usages of T will be interpreted as String. You could therefore do
String notNull = replaceIfNull(someStringVar, " not null ");
You could also provide the type argument explicitly
Generics.<String>replaceIfNull(nullVar, " not null ");
and now again the type variable will be bound to String.
Note that the type variable T declared at the type level
public class Generics<T>
is completely different from the type variable T declared in the method.