2

Issue: I am trying to check if I can sort the list (ArrayList of data type Object), using 2 or more criteria's. Please note I am aware of using Comparator with thenComparing feature. I wish to check if there is a way to sort with 2 or more criteria w/o having to use a custom data type where I can easily use Comparator feature. For 1 criteria sorting, the below code works.

In this case if I do the below, the IntelliJ IDE immediately gives a error saying - 'Cannot resolve method 'get(int)' for o.get(3)

.sorted(Comparator.comparing(o -> o.get(3).toString()).thenComparing(...)

I have also referred to many threads in this forum sample - Link1 Link2

Code (This works well for single criteria)

List<List<Object>> listOutput = new ArrayList<>();
.........
.........
listOutput = listOutput
                    .stream()
                    .sorted(Comparator.comparing(o -> o.get(3).toString()))
                    .collect(Collectors.toList());

Added (Details of Object)

(Note)

String dataType - exchange, broker,segment, coCode

LocalDate dataType - tradeDate

LocalTime dataType - tradeTime

double dataType - sellPrice, buyPrice

List<Object> lineOutput = new ArrayList<>();
lineOutput.add(exchange);
lineOutput.add(broker);
lineOutput.add(segment);
lineOutput.add(tradeDate);  // Entry Date
lineOutput.add(tradeTime);   // Entry Time
lineOutput.add(coCode);
lineOutput.add(sellPrice - buyPrice);   // Profit / Loss 
listOutput.add(lineOutput); // Add line to Output
iCoder
  • 1,406
  • 6
  • 16
  • 35
  • if the error is `Cannot resolve method get` **may be** because you do not have a `get` for the type that `o` is defined? – Eugene Nov 20 '17 at 09:12
  • The get method works fine if I just sort for 1 criteria as shown in code above. The moment I try extending i.e. sort for 2 or more with thenComparing, the IDE gives the error. – iCoder Nov 20 '17 at 09:19

2 Answers2

6

It seems that the compiler can not infer the correct types (I've tried javac 8 and 9 with the same effect). The only way I could make it work is specifying the types directly via casting:

list.stream()
    .sorted(
       Comparator.comparing((List<Object> o) -> o.get(3).toString())
                 .thenComparing((List<Object> x) ->  x.get(3).toString()));
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • Thanks for your guidance. I wouldn't have a clue that IDE / Compiler had this quirk of requiring a casting. – iCoder Nov 20 '17 at 09:56
  • 1
    Don’t use type casts, use explicitly typed lambdas: `Comparator.comparing((List o) -> o.get(3).toString()).thenComparing(o -> …)`. Note how the first lambda being typed provides the type for the second (and all subsequent) invocation, allowing it to use an implicit lambda… – Holger Nov 20 '17 at 09:57
  • @Holger Thank you for your time by providing that insight. – iCoder Nov 20 '17 at 11:06
2

I have also had these problems a couple of times, especially with Comparator. Eclipse and/or the java compiler have trouble infering the types correctly. As Holger pointed out, this is not a bug but working as specified: The types in the Comparator cannot be inferred solely by the expected type of sorted's parameter. You have to type manually/explicitly to give it enough info to compile:

List<List<Object>> listOutput = new ArrayList<>();
listOutput = listOutput.stream()
                       .sorted(Comparator.comparing((List<Object> o) -> o.get(3).toString())
                                         .thenComparing(o -> o.get(2).toString()))
                       .collect(Collectors.toList());

In the subsequent thenComparings the type is then correctly recognized.

Malte Hartwig
  • 4,477
  • 2
  • 14
  • 30
  • its interesting that if you replace `Object` with some actual class like `User` for example - this still fails - you would need to explicitly cast twice – Eugene Nov 20 '17 at 09:50
  • Thank you Malte, appreciate your help. The inputs by Eugene did the work for me. – iCoder Nov 20 '17 at 09:55
  • 1
    This is not an issue of Eclipse or javac; it’s precisely, how the specification has defined it; type inference by target type does not work through chained method invocations. Using an explicitly type lambda expression or an exact method reference (to a non-generic method) can provide enough information to infer the actual types for the first expression, then, having exact types for the first expression, which is the receiver for the subsequent invocation, allows to infer the types of the next invocation. – Holger Nov 20 '17 at 10:29
  • @iCoder make sure to use Eugene's updated answer, which is type safe. – Malte Hartwig Nov 20 '17 at 10:55
  • @MalteHartwig Thanks, have incorporated the changes – iCoder Nov 20 '17 at 11:05