I'm working on a problem where you are given a String[] containing numbers that are all formatted differently - e.g. "9", "-100", "56.6", "0.12", ".12", "02.34", "000.000", etc. The idea being that you need to sort the array in descending order while maintaining the given format. There are also null values in there. I have solved the problem multiple ways, but I ran into a problem that I can't seem to wrap my head around.
I originally solved the problem using the following logic:
Comparator<String> com = (x, y) -> new BigDecimal(y).compareTo(new BigDecimal(x));
s = Stream.of(s).filter(x -> x != null).sorted(com).toArray(String[]::new);
I then tried to apply similar logic, which is where things broke down:
s = Stream.of(s).filter(x -> x != null).sorted(Comparator.comparing(BigDecimal::new).reversed()).toArray(String[]::new);
This threw the following error:
java: incompatible types: cannot infer type-variable(s) T,U
(argument mismatch; invalid constructor reference
no suitable constructor found for BigDecimal(java.lang.Object)
constructor java.math.BigDecimal.BigDecimal(char[]) is not applicable
(argument mismatch; java.lang.Object cannot be converted to char[])
constructor java.math.BigDecimal.BigDecimal(java.lang.String) is not applicable
(argument mismatch; java.lang.Object cannot be converted to java.lang.String)
constructor java.math.BigDecimal.BigDecimal(double) is not applicable
(argument mismatch; java.lang.Object cannot be converted to double)
I honestly can't make heads or tails of this. Disclaimer, I'm still pretty new to working with streams. I would appreciate any insight.
Expressions need types. For example:
Object o = 5;
'5' is an expression. Its type is int. This is valid java code, but the type of the expression remains int; the fact that its being assigned to a variable of type Object does not change this. The compiler applies auto-boxing (assumes you meant to write Object o = Integer.valueOf(5)), and thus the desugared type of that expression becomes Integer, which is a subtype of Object and therefore can be assigned to variable o.
The thing about lambdas ((x, y) -> foo) and function references (something::methodName) is that they, too, are values, and therefore they, too, need to have a type.
And therein lies the rub. Java is designed so that there aren't generalized functional types. Instead, java checks the context around the method reference or lambda and checks: Which functional interface type is required here? - and then assumes that's the type that lambda/functional type is meant to have, and only then does the compiler actually parse the content of the lambda/method ref.
It has to do it this way, after all, (x) -> x.toLowerCase().length() < 5 or whatever has to be a compiler error unless the type of x is String or something else with a toLowerCase() method, so to even begin to compile this, the compiler needs to know x is of type String but it can't know that unless the context around the x -> x.toLowerCase().length() < 5 expression gives the compiler enough info to e.g. infer that the thing is meant to be a Predicate<String>.
The compiler does this by going inside-out, then outside-in, then inside-out again: First the compiler zooms in on everything and assigns types to it (i.e. in Object o = 5;, the compiler zooms in on the 5, it knows what type that is (int), and then zooms back out and figures out that auto-boxing is needed) - that's inside-out.
With lambdas/methodrefs, the compiler doesn't go any further than 'some lambda / some methodref, with unknown types' and doesn't try to parse the content at all. With that info it continues (and e.g. figures out which of a bunch of overloaded methods you meant), and only if that determines a single unique functional interface, that interface is applied and the lambda/methodref is parsed in that light.
A functional interface is any interface where you list all methods, remove anything with a default impl, remove anything that j.l.Object already has, and if there is precisely 1 method left over - then that is a functional interface, and you can implement it using lambda syntax or methodref syntax.
Here, if you wrote:
s = Stream.of(s).filter(x -> x != null).sorted(Comparator.comparing(BigDecimal::new)).toArray(String[]::new);
All is well: BigDecimal::new is first treated as 'some methodref', then Comparator.comparing is checked, which determines that the argument has to be of type Comparator<? super X> where X isn't clear yet, so the compiler continues and eventually figures out that s is a String[], from there it determines that therefore Stream.of(s) is a Stream<String>, therefore Stream.of(s).filter(x -> x != null) is a Stream<String>, and from there that the argument to calling .sorted() on that must be Comparator<? super String>, and picks the most specific bound (String), and figures out that the argument needs to be a Function<String, Z extends Comparable<Z> where Z can be anything.
That's all very very complicated but the compiler manages to get that far, and in that light, parsed BigDecimal::new and figures it out: That is an implementation of Function<String, BigDecimal>, and BigDecimal is indeed a valid type for Z here. The code compiles and works.
Now go back to what you have (which tosses a reversed() in the mix) and you've exceeding the compiler's abilities to figure this out. It decides to go ahead and 'parse' BigDecimal::new with less info (because it needs to figure out what .reversed() does before it can figure out the context around it), so it tries its best and parses it as a Function<Object, Z extends Comparable<Z>> and BigDecimal::new doesn't 'fit' that type (as new BigDecimal(anyObject) doesn't exist).
And that's the error you see.
Solving it:
You need to tell the compiler about that context 'sooner'. In your original code, you do that by assigning the comparator to a variable - because now the type of the variable serves as context. You can do that in the second snippet too:
Comparator<String> byValue = Comparator.comparing(BigDecimal::new);
s = Stream.of(s).filter(x -> x != null).sorted(byValue.reversed()).toArray(String[]::new);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With