How does this compile?

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
12
down vote

favorite
3












I'm writing a function that takes a list of keyExtractor functions to produce a Comparator (imagine we had an object with many many properties and wanted to be able to arbitrarily compare by a large number of properties in any order).



import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

class Test
public static <T, S extends Comparable<S>> Comparator<T> parseKeysAscending(List<Function<T, S>> keyExtractors)
if (keyExtractors.isEmpty())
return (a, b) -> 0;
else
Function<T, S> firstSortKey = keyExtractors.get(0);
List<Function<T, S>> restOfSortKeys = keyExtractors.subList(1, keyExtractors.size());
return Comparator.comparing(firstSortKey).thenComparing(parseKeysAscending(restOfSortKeys));



public static void main(String args)
List<Extractor<Data, ?>> extractors = new ArrayList<>();
extractors.add(new Extractor<>(Data::getA));
extractors.add(new Extractor<>(Data::getB));

Comparator<Data> test = parseKeysAscending(
extractors.stream()
.map(e -> e)
.collect(Collectors.toList()));





class Extractor<T, S extends Comparable<S>> implements Function<T, S>
private final Function<T, S> extractor;

Extractor(Function<T, S> extractor)
this.extractor = extractor;


@Override
public S apply(T t)
return extractor.apply(t);



class Data
private final Integer a;
private final Integer b;

private Data(int a, int b)
this.a = a;
this.b = b;


public Integer getA()
return a;


public Integer getB()
return b;




There are three main points of confusion for me:



1). If I don't define the Extractor class, this will not compile. I cannot directly have Functions or some sort of functional interface.



2). If I remove the identity function mapping line ".map(e -> e)", this will not type check.



3). My IDE says my function is accepting a List of Functions of the type Data -> ? which doesn't comply with the bounds of the parseKeysAscending function.










share|improve this question























  • There does seem to be some wonky stuff going on here. Question worth asking to me, since my IDE also optimizes out the mapping but then immediately refuses to compile. What version of Java are you using?
    – Makoto
    3 hours ago










  • I am using jdk 1.8.0_181
    – Billy the Kid
    3 hours ago














up vote
12
down vote

favorite
3












I'm writing a function that takes a list of keyExtractor functions to produce a Comparator (imagine we had an object with many many properties and wanted to be able to arbitrarily compare by a large number of properties in any order).



import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

class Test
public static <T, S extends Comparable<S>> Comparator<T> parseKeysAscending(List<Function<T, S>> keyExtractors)
if (keyExtractors.isEmpty())
return (a, b) -> 0;
else
Function<T, S> firstSortKey = keyExtractors.get(0);
List<Function<T, S>> restOfSortKeys = keyExtractors.subList(1, keyExtractors.size());
return Comparator.comparing(firstSortKey).thenComparing(parseKeysAscending(restOfSortKeys));



public static void main(String args)
List<Extractor<Data, ?>> extractors = new ArrayList<>();
extractors.add(new Extractor<>(Data::getA));
extractors.add(new Extractor<>(Data::getB));

Comparator<Data> test = parseKeysAscending(
extractors.stream()
.map(e -> e)
.collect(Collectors.toList()));





class Extractor<T, S extends Comparable<S>> implements Function<T, S>
private final Function<T, S> extractor;

Extractor(Function<T, S> extractor)
this.extractor = extractor;


@Override
public S apply(T t)
return extractor.apply(t);



class Data
private final Integer a;
private final Integer b;

private Data(int a, int b)
this.a = a;
this.b = b;


public Integer getA()
return a;


public Integer getB()
return b;




There are three main points of confusion for me:



1). If I don't define the Extractor class, this will not compile. I cannot directly have Functions or some sort of functional interface.



2). If I remove the identity function mapping line ".map(e -> e)", this will not type check.



3). My IDE says my function is accepting a List of Functions of the type Data -> ? which doesn't comply with the bounds of the parseKeysAscending function.










share|improve this question























  • There does seem to be some wonky stuff going on here. Question worth asking to me, since my IDE also optimizes out the mapping but then immediately refuses to compile. What version of Java are you using?
    – Makoto
    3 hours ago










  • I am using jdk 1.8.0_181
    – Billy the Kid
    3 hours ago












up vote
12
down vote

favorite
3









up vote
12
down vote

favorite
3






3





I'm writing a function that takes a list of keyExtractor functions to produce a Comparator (imagine we had an object with many many properties and wanted to be able to arbitrarily compare by a large number of properties in any order).



import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

class Test
public static <T, S extends Comparable<S>> Comparator<T> parseKeysAscending(List<Function<T, S>> keyExtractors)
if (keyExtractors.isEmpty())
return (a, b) -> 0;
else
Function<T, S> firstSortKey = keyExtractors.get(0);
List<Function<T, S>> restOfSortKeys = keyExtractors.subList(1, keyExtractors.size());
return Comparator.comparing(firstSortKey).thenComparing(parseKeysAscending(restOfSortKeys));



public static void main(String args)
List<Extractor<Data, ?>> extractors = new ArrayList<>();
extractors.add(new Extractor<>(Data::getA));
extractors.add(new Extractor<>(Data::getB));

Comparator<Data> test = parseKeysAscending(
extractors.stream()
.map(e -> e)
.collect(Collectors.toList()));





class Extractor<T, S extends Comparable<S>> implements Function<T, S>
private final Function<T, S> extractor;

Extractor(Function<T, S> extractor)
this.extractor = extractor;


@Override
public S apply(T t)
return extractor.apply(t);



class Data
private final Integer a;
private final Integer b;

private Data(int a, int b)
this.a = a;
this.b = b;


public Integer getA()
return a;


public Integer getB()
return b;




There are three main points of confusion for me:



1). If I don't define the Extractor class, this will not compile. I cannot directly have Functions or some sort of functional interface.



2). If I remove the identity function mapping line ".map(e -> e)", this will not type check.



3). My IDE says my function is accepting a List of Functions of the type Data -> ? which doesn't comply with the bounds of the parseKeysAscending function.










share|improve this question















I'm writing a function that takes a list of keyExtractor functions to produce a Comparator (imagine we had an object with many many properties and wanted to be able to arbitrarily compare by a large number of properties in any order).



import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

class Test
public static <T, S extends Comparable<S>> Comparator<T> parseKeysAscending(List<Function<T, S>> keyExtractors)
if (keyExtractors.isEmpty())
return (a, b) -> 0;
else
Function<T, S> firstSortKey = keyExtractors.get(0);
List<Function<T, S>> restOfSortKeys = keyExtractors.subList(1, keyExtractors.size());
return Comparator.comparing(firstSortKey).thenComparing(parseKeysAscending(restOfSortKeys));



public static void main(String args)
List<Extractor<Data, ?>> extractors = new ArrayList<>();
extractors.add(new Extractor<>(Data::getA));
extractors.add(new Extractor<>(Data::getB));

Comparator<Data> test = parseKeysAscending(
extractors.stream()
.map(e -> e)
.collect(Collectors.toList()));





class Extractor<T, S extends Comparable<S>> implements Function<T, S>
private final Function<T, S> extractor;

Extractor(Function<T, S> extractor)
this.extractor = extractor;


@Override
public S apply(T t)
return extractor.apply(t);



class Data
private final Integer a;
private final Integer b;

private Data(int a, int b)
this.a = a;
this.b = b;


public Integer getA()
return a;


public Integer getB()
return b;




There are three main points of confusion for me:



1). If I don't define the Extractor class, this will not compile. I cannot directly have Functions or some sort of functional interface.



2). If I remove the identity function mapping line ".map(e -> e)", this will not type check.



3). My IDE says my function is accepting a List of Functions of the type Data -> ? which doesn't comply with the bounds of the parseKeysAscending function.







java generics java-8 functional-programming comparator






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 1 hour ago









Federico Peralta Schaffner

19.3k32862




19.3k32862










asked 3 hours ago









Billy the Kid

784




784











  • There does seem to be some wonky stuff going on here. Question worth asking to me, since my IDE also optimizes out the mapping but then immediately refuses to compile. What version of Java are you using?
    – Makoto
    3 hours ago










  • I am using jdk 1.8.0_181
    – Billy the Kid
    3 hours ago
















  • There does seem to be some wonky stuff going on here. Question worth asking to me, since my IDE also optimizes out the mapping but then immediately refuses to compile. What version of Java are you using?
    – Makoto
    3 hours ago










  • I am using jdk 1.8.0_181
    – Billy the Kid
    3 hours ago















There does seem to be some wonky stuff going on here. Question worth asking to me, since my IDE also optimizes out the mapping but then immediately refuses to compile. What version of Java are you using?
– Makoto
3 hours ago




There does seem to be some wonky stuff going on here. Question worth asking to me, since my IDE also optimizes out the mapping but then immediately refuses to compile. What version of Java are you using?
– Makoto
3 hours ago












I am using jdk 1.8.0_181
– Billy the Kid
3 hours ago




I am using jdk 1.8.0_181
– Billy the Kid
3 hours ago












4 Answers
4






active

oldest

votes

















up vote
3
down vote



accepted










It works for me without the Extractor class and also without calling map(e -> e) in the stream pipeline. Actually, streaming the list of extractors isn't needed at all if you use the correct generic types.



As to why your code doesn't work, I'm not completely sure. Generics is a tough and flaky aspect of Java... All I did was adjusting the signature of the parseKeysAscending method, so that it conforms to what Comparator.comparing actually expects.



Here's the parseKeysAscending method:



public static <T, S extends Comparable<? super S>> Comparator<T> parseKeysAscending(
List<Function<? super T, ? extends S>> keyExtractors)

if (keyExtractors.isEmpty())
return (a, b) -> 0;
else

Function<? super T, ? extends S> firstSortKey = keyExtractors.get(0);
List<Function<? super T, ? extends S>> restOfSortKeys =
keyExtractors.subList(1, keyExtractors.size());

return Comparator.comparing(firstSortKey)
.thenComparing(parseKeysAscending(restOfSortKeys));




And here's a demo with the call:



List<Function<? super Data, ? extends Comparable>> extractors = new ArrayList<>();
extractors.add(Data::getA);
extractors.add(Data::getB);

Comparator<Data> test = parseKeysAscending(extractors);

List<Data> data = new ArrayList<>(Arrays.asList(
new Data(1, "z"),
new Data(2, "b"),
new Data(1, "a")));

System.out.println(data); // [[1, 'z'], [2, 'b'], [1, 'a']]

data.sort(test);

System.out.println(data); // [[1, 'a'], [1, 'z'], [2, 'b']]


The only way to make the code compile without warnings was to declare the list of functions as List<Function<Data, Integer>>. But this works only with getters that return Integer. I'm assuming that you might want to compare any mix of Comparables, i.e. the code above works with the following Data class:



public class Data 
private final Integer a;
private final String b;

private Data(int a, String b)
this.a = a;
this.b = b;


public Integer getA()
return a;


public String getB()
return b;


@Override
public String toString()
return "[" + a + ", '" + b + "']";




Here's the demo.






share|improve this answer


















  • 1




    I still don't understand where the problem was or is (it compiled with java-11 that I have and use), but as a side note I think that method can be shorten to public static <T, S extends Comparable<S>> Comparator<T> parseKeysAscending(List<Function<T, S>> keyExtractors) return keyExtractors.stream() .collect(Collector.of( () -> (Comparator<T>) (x, y) -> 0, Comparator::thenComparing, Comparator::thenComparing));
    – Eugene
    2 hours ago










  • Two interesting observations for your code Federico: 1). This doesn't work for me if I remove the stream in the call 2). I am getting a "cannot resolve method" in the recursive call yet it still successfully runs.
    – Billy the Kid
    2 hours ago











  • This looks like what I am going for. I think this is also a great example for me to understand contravariance/covariance in Java through the wildcard type. This may be IDE specific, but using the method as written gives me an unchecked method exception. If I use the intermediate "extractor" class as before, this warning goes away. Also, doesn't this still mean our method is accepting some type "S extends Comparable<? super S>"? I don't think there should be any way to call this with data that has both a string and integer field without getting the unchecked warning.
    – Billy the Kid
    42 mins ago











  • @BillytheKid Honestly, I'm not getting that exception/compilation error, so I cannot tell. Usually, Eclipse compiler is known to have issues with type inference (i.e. covariance/contravariance, generic types. lambdas and method references all mixed together). As to the unchecked warning, I don't think there's a way to get rid of it.
    – Federico Peralta Schaffner
    16 mins ago


















up vote
3
down vote













After Federico corrected me (thank you!) this is a single method you could do it with:



public static <T, S extends Comparable<? super S>> Comparator<T> test(List<Function<T, S>> list) 
return list.stream()
.reduce((x, y) -> 0,
Comparator::thenComparing,
Comparator::thenComparing);



And usage would be:



// I still don't know how to avoid this raw type here
List<Function<Data, Comparable>> extractors = new ArrayList<>();
extractors.add(Data::getA); // getA returns an Integer
extractors.add(Data::getB); // getB returns a String

listOfSomeDatas.sort(test(extractors));





share|improve this answer
















  • 2




    Very nice, this does make sense as the original recursive function follows a pretty standard fold-like pattern.
    – Billy the Kid
    19 mins ago










  • I still like list.stream().map(Comparator::comparing).reduce(Comparator::thenComparing).orElse((Comparator<T>) (a, b) -> 0) more than this, but +1 for keeping all this generics nightmare simple.
    – Federico Peralta Schaffner
    12 mins ago

















up vote
1
down vote














  1. If I don't define the Extractor class, this will not compile. I cannot directly have Functions or some sort of functional interface.



No, you can. You can define any Function<X, Y> by a lambda, or method reference, or anonymous class.



List<Function<Data, Integer>> extractors = List.of(Data::getA, Data::getB);



  1. If I remove the identity function mapping line .map(e -> e), this will not type check.



It still will, but the result may not be suitable for the method. You can always define generic parameters explicitly to make sure that everything goes as you expect.



extractors.<Function<Data, Integer>>stream().collect(Collectors.toList())


But there is no need here:



Comparator<Data> test = parseKeysAscending(extractors);





share|improve this answer





























    up vote
    0
    down vote













    Regarding your original code in the question, you can remove Extractor class and use a raw Comparable:



    List<Function<Data, Comparable>> extractors = new ArrayList<>();

    extractors.add(Data::getA);
    extractors.add(Data::getB);

    @SuppressWarnings("unchecked")
    Comparator<Data> test = parseKeysAscending(extractors);


    P.S. I also don't know how to get rid of the raw type here...





    share




















      Your Answer





      StackExchange.ifUsing("editor", function ()
      StackExchange.using("externalEditor", function ()
      StackExchange.using("snippets", function ()
      StackExchange.snippets.init();
      );
      );
      , "code-snippets");

      StackExchange.ready(function()
      var channelOptions =
      tags: "".split(" "),
      id: "1"
      ;
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function()
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled)
      StackExchange.using("snippets", function()
      createEditor();
      );

      else
      createEditor();

      );

      function createEditor()
      StackExchange.prepareEditor(
      heartbeatType: 'answer',
      convertImagesToLinks: true,
      noModals: false,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: 10,
      bindNavPrevention: true,
      postfix: "",
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      );



      );













       

      draft saved


      draft discarded


















      StackExchange.ready(
      function ()
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52596151%2fhow-does-this-compile%23new-answer', 'question_page');

      );

      Post as a guest






























      4 Answers
      4






      active

      oldest

      votes








      4 Answers
      4






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes








      up vote
      3
      down vote



      accepted










      It works for me without the Extractor class and also without calling map(e -> e) in the stream pipeline. Actually, streaming the list of extractors isn't needed at all if you use the correct generic types.



      As to why your code doesn't work, I'm not completely sure. Generics is a tough and flaky aspect of Java... All I did was adjusting the signature of the parseKeysAscending method, so that it conforms to what Comparator.comparing actually expects.



      Here's the parseKeysAscending method:



      public static <T, S extends Comparable<? super S>> Comparator<T> parseKeysAscending(
      List<Function<? super T, ? extends S>> keyExtractors)

      if (keyExtractors.isEmpty())
      return (a, b) -> 0;
      else

      Function<? super T, ? extends S> firstSortKey = keyExtractors.get(0);
      List<Function<? super T, ? extends S>> restOfSortKeys =
      keyExtractors.subList(1, keyExtractors.size());

      return Comparator.comparing(firstSortKey)
      .thenComparing(parseKeysAscending(restOfSortKeys));




      And here's a demo with the call:



      List<Function<? super Data, ? extends Comparable>> extractors = new ArrayList<>();
      extractors.add(Data::getA);
      extractors.add(Data::getB);

      Comparator<Data> test = parseKeysAscending(extractors);

      List<Data> data = new ArrayList<>(Arrays.asList(
      new Data(1, "z"),
      new Data(2, "b"),
      new Data(1, "a")));

      System.out.println(data); // [[1, 'z'], [2, 'b'], [1, 'a']]

      data.sort(test);

      System.out.println(data); // [[1, 'a'], [1, 'z'], [2, 'b']]


      The only way to make the code compile without warnings was to declare the list of functions as List<Function<Data, Integer>>. But this works only with getters that return Integer. I'm assuming that you might want to compare any mix of Comparables, i.e. the code above works with the following Data class:



      public class Data 
      private final Integer a;
      private final String b;

      private Data(int a, String b)
      this.a = a;
      this.b = b;


      public Integer getA()
      return a;


      public String getB()
      return b;


      @Override
      public String toString()
      return "[" + a + ", '" + b + "']";




      Here's the demo.






      share|improve this answer


















      • 1




        I still don't understand where the problem was or is (it compiled with java-11 that I have and use), but as a side note I think that method can be shorten to public static <T, S extends Comparable<S>> Comparator<T> parseKeysAscending(List<Function<T, S>> keyExtractors) return keyExtractors.stream() .collect(Collector.of( () -> (Comparator<T>) (x, y) -> 0, Comparator::thenComparing, Comparator::thenComparing));
        – Eugene
        2 hours ago










      • Two interesting observations for your code Federico: 1). This doesn't work for me if I remove the stream in the call 2). I am getting a "cannot resolve method" in the recursive call yet it still successfully runs.
        – Billy the Kid
        2 hours ago











      • This looks like what I am going for. I think this is also a great example for me to understand contravariance/covariance in Java through the wildcard type. This may be IDE specific, but using the method as written gives me an unchecked method exception. If I use the intermediate "extractor" class as before, this warning goes away. Also, doesn't this still mean our method is accepting some type "S extends Comparable<? super S>"? I don't think there should be any way to call this with data that has both a string and integer field without getting the unchecked warning.
        – Billy the Kid
        42 mins ago











      • @BillytheKid Honestly, I'm not getting that exception/compilation error, so I cannot tell. Usually, Eclipse compiler is known to have issues with type inference (i.e. covariance/contravariance, generic types. lambdas and method references all mixed together). As to the unchecked warning, I don't think there's a way to get rid of it.
        – Federico Peralta Schaffner
        16 mins ago















      up vote
      3
      down vote



      accepted










      It works for me without the Extractor class and also without calling map(e -> e) in the stream pipeline. Actually, streaming the list of extractors isn't needed at all if you use the correct generic types.



      As to why your code doesn't work, I'm not completely sure. Generics is a tough and flaky aspect of Java... All I did was adjusting the signature of the parseKeysAscending method, so that it conforms to what Comparator.comparing actually expects.



      Here's the parseKeysAscending method:



      public static <T, S extends Comparable<? super S>> Comparator<T> parseKeysAscending(
      List<Function<? super T, ? extends S>> keyExtractors)

      if (keyExtractors.isEmpty())
      return (a, b) -> 0;
      else

      Function<? super T, ? extends S> firstSortKey = keyExtractors.get(0);
      List<Function<? super T, ? extends S>> restOfSortKeys =
      keyExtractors.subList(1, keyExtractors.size());

      return Comparator.comparing(firstSortKey)
      .thenComparing(parseKeysAscending(restOfSortKeys));




      And here's a demo with the call:



      List<Function<? super Data, ? extends Comparable>> extractors = new ArrayList<>();
      extractors.add(Data::getA);
      extractors.add(Data::getB);

      Comparator<Data> test = parseKeysAscending(extractors);

      List<Data> data = new ArrayList<>(Arrays.asList(
      new Data(1, "z"),
      new Data(2, "b"),
      new Data(1, "a")));

      System.out.println(data); // [[1, 'z'], [2, 'b'], [1, 'a']]

      data.sort(test);

      System.out.println(data); // [[1, 'a'], [1, 'z'], [2, 'b']]


      The only way to make the code compile without warnings was to declare the list of functions as List<Function<Data, Integer>>. But this works only with getters that return Integer. I'm assuming that you might want to compare any mix of Comparables, i.e. the code above works with the following Data class:



      public class Data 
      private final Integer a;
      private final String b;

      private Data(int a, String b)
      this.a = a;
      this.b = b;


      public Integer getA()
      return a;


      public String getB()
      return b;


      @Override
      public String toString()
      return "[" + a + ", '" + b + "']";




      Here's the demo.






      share|improve this answer


















      • 1




        I still don't understand where the problem was or is (it compiled with java-11 that I have and use), but as a side note I think that method can be shorten to public static <T, S extends Comparable<S>> Comparator<T> parseKeysAscending(List<Function<T, S>> keyExtractors) return keyExtractors.stream() .collect(Collector.of( () -> (Comparator<T>) (x, y) -> 0, Comparator::thenComparing, Comparator::thenComparing));
        – Eugene
        2 hours ago










      • Two interesting observations for your code Federico: 1). This doesn't work for me if I remove the stream in the call 2). I am getting a "cannot resolve method" in the recursive call yet it still successfully runs.
        – Billy the Kid
        2 hours ago











      • This looks like what I am going for. I think this is also a great example for me to understand contravariance/covariance in Java through the wildcard type. This may be IDE specific, but using the method as written gives me an unchecked method exception. If I use the intermediate "extractor" class as before, this warning goes away. Also, doesn't this still mean our method is accepting some type "S extends Comparable<? super S>"? I don't think there should be any way to call this with data that has both a string and integer field without getting the unchecked warning.
        – Billy the Kid
        42 mins ago











      • @BillytheKid Honestly, I'm not getting that exception/compilation error, so I cannot tell. Usually, Eclipse compiler is known to have issues with type inference (i.e. covariance/contravariance, generic types. lambdas and method references all mixed together). As to the unchecked warning, I don't think there's a way to get rid of it.
        – Federico Peralta Schaffner
        16 mins ago













      up vote
      3
      down vote



      accepted







      up vote
      3
      down vote



      accepted






      It works for me without the Extractor class and also without calling map(e -> e) in the stream pipeline. Actually, streaming the list of extractors isn't needed at all if you use the correct generic types.



      As to why your code doesn't work, I'm not completely sure. Generics is a tough and flaky aspect of Java... All I did was adjusting the signature of the parseKeysAscending method, so that it conforms to what Comparator.comparing actually expects.



      Here's the parseKeysAscending method:



      public static <T, S extends Comparable<? super S>> Comparator<T> parseKeysAscending(
      List<Function<? super T, ? extends S>> keyExtractors)

      if (keyExtractors.isEmpty())
      return (a, b) -> 0;
      else

      Function<? super T, ? extends S> firstSortKey = keyExtractors.get(0);
      List<Function<? super T, ? extends S>> restOfSortKeys =
      keyExtractors.subList(1, keyExtractors.size());

      return Comparator.comparing(firstSortKey)
      .thenComparing(parseKeysAscending(restOfSortKeys));




      And here's a demo with the call:



      List<Function<? super Data, ? extends Comparable>> extractors = new ArrayList<>();
      extractors.add(Data::getA);
      extractors.add(Data::getB);

      Comparator<Data> test = parseKeysAscending(extractors);

      List<Data> data = new ArrayList<>(Arrays.asList(
      new Data(1, "z"),
      new Data(2, "b"),
      new Data(1, "a")));

      System.out.println(data); // [[1, 'z'], [2, 'b'], [1, 'a']]

      data.sort(test);

      System.out.println(data); // [[1, 'a'], [1, 'z'], [2, 'b']]


      The only way to make the code compile without warnings was to declare the list of functions as List<Function<Data, Integer>>. But this works only with getters that return Integer. I'm assuming that you might want to compare any mix of Comparables, i.e. the code above works with the following Data class:



      public class Data 
      private final Integer a;
      private final String b;

      private Data(int a, String b)
      this.a = a;
      this.b = b;


      public Integer getA()
      return a;


      public String getB()
      return b;


      @Override
      public String toString()
      return "[" + a + ", '" + b + "']";




      Here's the demo.






      share|improve this answer














      It works for me without the Extractor class and also without calling map(e -> e) in the stream pipeline. Actually, streaming the list of extractors isn't needed at all if you use the correct generic types.



      As to why your code doesn't work, I'm not completely sure. Generics is a tough and flaky aspect of Java... All I did was adjusting the signature of the parseKeysAscending method, so that it conforms to what Comparator.comparing actually expects.



      Here's the parseKeysAscending method:



      public static <T, S extends Comparable<? super S>> Comparator<T> parseKeysAscending(
      List<Function<? super T, ? extends S>> keyExtractors)

      if (keyExtractors.isEmpty())
      return (a, b) -> 0;
      else

      Function<? super T, ? extends S> firstSortKey = keyExtractors.get(0);
      List<Function<? super T, ? extends S>> restOfSortKeys =
      keyExtractors.subList(1, keyExtractors.size());

      return Comparator.comparing(firstSortKey)
      .thenComparing(parseKeysAscending(restOfSortKeys));




      And here's a demo with the call:



      List<Function<? super Data, ? extends Comparable>> extractors = new ArrayList<>();
      extractors.add(Data::getA);
      extractors.add(Data::getB);

      Comparator<Data> test = parseKeysAscending(extractors);

      List<Data> data = new ArrayList<>(Arrays.asList(
      new Data(1, "z"),
      new Data(2, "b"),
      new Data(1, "a")));

      System.out.println(data); // [[1, 'z'], [2, 'b'], [1, 'a']]

      data.sort(test);

      System.out.println(data); // [[1, 'a'], [1, 'z'], [2, 'b']]


      The only way to make the code compile without warnings was to declare the list of functions as List<Function<Data, Integer>>. But this works only with getters that return Integer. I'm assuming that you might want to compare any mix of Comparables, i.e. the code above works with the following Data class:



      public class Data 
      private final Integer a;
      private final String b;

      private Data(int a, String b)
      this.a = a;
      this.b = b;


      public Integer getA()
      return a;


      public String getB()
      return b;


      @Override
      public String toString()
      return "[" + a + ", '" + b + "']";




      Here's the demo.







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited 1 hour ago

























      answered 2 hours ago









      Federico Peralta Schaffner

      19.3k32862




      19.3k32862







      • 1




        I still don't understand where the problem was or is (it compiled with java-11 that I have and use), but as a side note I think that method can be shorten to public static <T, S extends Comparable<S>> Comparator<T> parseKeysAscending(List<Function<T, S>> keyExtractors) return keyExtractors.stream() .collect(Collector.of( () -> (Comparator<T>) (x, y) -> 0, Comparator::thenComparing, Comparator::thenComparing));
        – Eugene
        2 hours ago










      • Two interesting observations for your code Federico: 1). This doesn't work for me if I remove the stream in the call 2). I am getting a "cannot resolve method" in the recursive call yet it still successfully runs.
        – Billy the Kid
        2 hours ago











      • This looks like what I am going for. I think this is also a great example for me to understand contravariance/covariance in Java through the wildcard type. This may be IDE specific, but using the method as written gives me an unchecked method exception. If I use the intermediate "extractor" class as before, this warning goes away. Also, doesn't this still mean our method is accepting some type "S extends Comparable<? super S>"? I don't think there should be any way to call this with data that has both a string and integer field without getting the unchecked warning.
        – Billy the Kid
        42 mins ago











      • @BillytheKid Honestly, I'm not getting that exception/compilation error, so I cannot tell. Usually, Eclipse compiler is known to have issues with type inference (i.e. covariance/contravariance, generic types. lambdas and method references all mixed together). As to the unchecked warning, I don't think there's a way to get rid of it.
        – Federico Peralta Schaffner
        16 mins ago













      • 1




        I still don't understand where the problem was or is (it compiled with java-11 that I have and use), but as a side note I think that method can be shorten to public static <T, S extends Comparable<S>> Comparator<T> parseKeysAscending(List<Function<T, S>> keyExtractors) return keyExtractors.stream() .collect(Collector.of( () -> (Comparator<T>) (x, y) -> 0, Comparator::thenComparing, Comparator::thenComparing));
        – Eugene
        2 hours ago










      • Two interesting observations for your code Federico: 1). This doesn't work for me if I remove the stream in the call 2). I am getting a "cannot resolve method" in the recursive call yet it still successfully runs.
        – Billy the Kid
        2 hours ago











      • This looks like what I am going for. I think this is also a great example for me to understand contravariance/covariance in Java through the wildcard type. This may be IDE specific, but using the method as written gives me an unchecked method exception. If I use the intermediate "extractor" class as before, this warning goes away. Also, doesn't this still mean our method is accepting some type "S extends Comparable<? super S>"? I don't think there should be any way to call this with data that has both a string and integer field without getting the unchecked warning.
        – Billy the Kid
        42 mins ago











      • @BillytheKid Honestly, I'm not getting that exception/compilation error, so I cannot tell. Usually, Eclipse compiler is known to have issues with type inference (i.e. covariance/contravariance, generic types. lambdas and method references all mixed together). As to the unchecked warning, I don't think there's a way to get rid of it.
        – Federico Peralta Schaffner
        16 mins ago








      1




      1




      I still don't understand where the problem was or is (it compiled with java-11 that I have and use), but as a side note I think that method can be shorten to public static <T, S extends Comparable<S>> Comparator<T> parseKeysAscending(List<Function<T, S>> keyExtractors) return keyExtractors.stream() .collect(Collector.of( () -> (Comparator<T>) (x, y) -> 0, Comparator::thenComparing, Comparator::thenComparing));
      – Eugene
      2 hours ago




      I still don't understand where the problem was or is (it compiled with java-11 that I have and use), but as a side note I think that method can be shorten to public static <T, S extends Comparable<S>> Comparator<T> parseKeysAscending(List<Function<T, S>> keyExtractors) return keyExtractors.stream() .collect(Collector.of( () -> (Comparator<T>) (x, y) -> 0, Comparator::thenComparing, Comparator::thenComparing));
      – Eugene
      2 hours ago












      Two interesting observations for your code Federico: 1). This doesn't work for me if I remove the stream in the call 2). I am getting a "cannot resolve method" in the recursive call yet it still successfully runs.
      – Billy the Kid
      2 hours ago





      Two interesting observations for your code Federico: 1). This doesn't work for me if I remove the stream in the call 2). I am getting a "cannot resolve method" in the recursive call yet it still successfully runs.
      – Billy the Kid
      2 hours ago













      This looks like what I am going for. I think this is also a great example for me to understand contravariance/covariance in Java through the wildcard type. This may be IDE specific, but using the method as written gives me an unchecked method exception. If I use the intermediate "extractor" class as before, this warning goes away. Also, doesn't this still mean our method is accepting some type "S extends Comparable<? super S>"? I don't think there should be any way to call this with data that has both a string and integer field without getting the unchecked warning.
      – Billy the Kid
      42 mins ago





      This looks like what I am going for. I think this is also a great example for me to understand contravariance/covariance in Java through the wildcard type. This may be IDE specific, but using the method as written gives me an unchecked method exception. If I use the intermediate "extractor" class as before, this warning goes away. Also, doesn't this still mean our method is accepting some type "S extends Comparable<? super S>"? I don't think there should be any way to call this with data that has both a string and integer field without getting the unchecked warning.
      – Billy the Kid
      42 mins ago













      @BillytheKid Honestly, I'm not getting that exception/compilation error, so I cannot tell. Usually, Eclipse compiler is known to have issues with type inference (i.e. covariance/contravariance, generic types. lambdas and method references all mixed together). As to the unchecked warning, I don't think there's a way to get rid of it.
      – Federico Peralta Schaffner
      16 mins ago





      @BillytheKid Honestly, I'm not getting that exception/compilation error, so I cannot tell. Usually, Eclipse compiler is known to have issues with type inference (i.e. covariance/contravariance, generic types. lambdas and method references all mixed together). As to the unchecked warning, I don't think there's a way to get rid of it.
      – Federico Peralta Schaffner
      16 mins ago













      up vote
      3
      down vote













      After Federico corrected me (thank you!) this is a single method you could do it with:



      public static <T, S extends Comparable<? super S>> Comparator<T> test(List<Function<T, S>> list) 
      return list.stream()
      .reduce((x, y) -> 0,
      Comparator::thenComparing,
      Comparator::thenComparing);



      And usage would be:



      // I still don't know how to avoid this raw type here
      List<Function<Data, Comparable>> extractors = new ArrayList<>();
      extractors.add(Data::getA); // getA returns an Integer
      extractors.add(Data::getB); // getB returns a String

      listOfSomeDatas.sort(test(extractors));





      share|improve this answer
















      • 2




        Very nice, this does make sense as the original recursive function follows a pretty standard fold-like pattern.
        – Billy the Kid
        19 mins ago










      • I still like list.stream().map(Comparator::comparing).reduce(Comparator::thenComparing).orElse((Comparator<T>) (a, b) -> 0) more than this, but +1 for keeping all this generics nightmare simple.
        – Federico Peralta Schaffner
        12 mins ago














      up vote
      3
      down vote













      After Federico corrected me (thank you!) this is a single method you could do it with:



      public static <T, S extends Comparable<? super S>> Comparator<T> test(List<Function<T, S>> list) 
      return list.stream()
      .reduce((x, y) -> 0,
      Comparator::thenComparing,
      Comparator::thenComparing);



      And usage would be:



      // I still don't know how to avoid this raw type here
      List<Function<Data, Comparable>> extractors = new ArrayList<>();
      extractors.add(Data::getA); // getA returns an Integer
      extractors.add(Data::getB); // getB returns a String

      listOfSomeDatas.sort(test(extractors));





      share|improve this answer
















      • 2




        Very nice, this does make sense as the original recursive function follows a pretty standard fold-like pattern.
        – Billy the Kid
        19 mins ago










      • I still like list.stream().map(Comparator::comparing).reduce(Comparator::thenComparing).orElse((Comparator<T>) (a, b) -> 0) more than this, but +1 for keeping all this generics nightmare simple.
        – Federico Peralta Schaffner
        12 mins ago












      up vote
      3
      down vote










      up vote
      3
      down vote









      After Federico corrected me (thank you!) this is a single method you could do it with:



      public static <T, S extends Comparable<? super S>> Comparator<T> test(List<Function<T, S>> list) 
      return list.stream()
      .reduce((x, y) -> 0,
      Comparator::thenComparing,
      Comparator::thenComparing);



      And usage would be:



      // I still don't know how to avoid this raw type here
      List<Function<Data, Comparable>> extractors = new ArrayList<>();
      extractors.add(Data::getA); // getA returns an Integer
      extractors.add(Data::getB); // getB returns a String

      listOfSomeDatas.sort(test(extractors));





      share|improve this answer












      After Federico corrected me (thank you!) this is a single method you could do it with:



      public static <T, S extends Comparable<? super S>> Comparator<T> test(List<Function<T, S>> list) 
      return list.stream()
      .reduce((x, y) -> 0,
      Comparator::thenComparing,
      Comparator::thenComparing);



      And usage would be:



      // I still don't know how to avoid this raw type here
      List<Function<Data, Comparable>> extractors = new ArrayList<>();
      extractors.add(Data::getA); // getA returns an Integer
      extractors.add(Data::getB); // getB returns a String

      listOfSomeDatas.sort(test(extractors));






      share|improve this answer












      share|improve this answer



      share|improve this answer










      answered 37 mins ago









      Eugene

      61.5k986143




      61.5k986143







      • 2




        Very nice, this does make sense as the original recursive function follows a pretty standard fold-like pattern.
        – Billy the Kid
        19 mins ago










      • I still like list.stream().map(Comparator::comparing).reduce(Comparator::thenComparing).orElse((Comparator<T>) (a, b) -> 0) more than this, but +1 for keeping all this generics nightmare simple.
        – Federico Peralta Schaffner
        12 mins ago












      • 2




        Very nice, this does make sense as the original recursive function follows a pretty standard fold-like pattern.
        – Billy the Kid
        19 mins ago










      • I still like list.stream().map(Comparator::comparing).reduce(Comparator::thenComparing).orElse((Comparator<T>) (a, b) -> 0) more than this, but +1 for keeping all this generics nightmare simple.
        – Federico Peralta Schaffner
        12 mins ago







      2




      2




      Very nice, this does make sense as the original recursive function follows a pretty standard fold-like pattern.
      – Billy the Kid
      19 mins ago




      Very nice, this does make sense as the original recursive function follows a pretty standard fold-like pattern.
      – Billy the Kid
      19 mins ago












      I still like list.stream().map(Comparator::comparing).reduce(Comparator::thenComparing).orElse((Comparator<T>) (a, b) -> 0) more than this, but +1 for keeping all this generics nightmare simple.
      – Federico Peralta Schaffner
      12 mins ago




      I still like list.stream().map(Comparator::comparing).reduce(Comparator::thenComparing).orElse((Comparator<T>) (a, b) -> 0) more than this, but +1 for keeping all this generics nightmare simple.
      – Federico Peralta Schaffner
      12 mins ago










      up vote
      1
      down vote














      1. If I don't define the Extractor class, this will not compile. I cannot directly have Functions or some sort of functional interface.



      No, you can. You can define any Function<X, Y> by a lambda, or method reference, or anonymous class.



      List<Function<Data, Integer>> extractors = List.of(Data::getA, Data::getB);



      1. If I remove the identity function mapping line .map(e -> e), this will not type check.



      It still will, but the result may not be suitable for the method. You can always define generic parameters explicitly to make sure that everything goes as you expect.



      extractors.<Function<Data, Integer>>stream().collect(Collectors.toList())


      But there is no need here:



      Comparator<Data> test = parseKeysAscending(extractors);





      share|improve this answer


























        up vote
        1
        down vote














        1. If I don't define the Extractor class, this will not compile. I cannot directly have Functions or some sort of functional interface.



        No, you can. You can define any Function<X, Y> by a lambda, or method reference, or anonymous class.



        List<Function<Data, Integer>> extractors = List.of(Data::getA, Data::getB);



        1. If I remove the identity function mapping line .map(e -> e), this will not type check.



        It still will, but the result may not be suitable for the method. You can always define generic parameters explicitly to make sure that everything goes as you expect.



        extractors.<Function<Data, Integer>>stream().collect(Collectors.toList())


        But there is no need here:



        Comparator<Data> test = parseKeysAscending(extractors);





        share|improve this answer
























          up vote
          1
          down vote










          up vote
          1
          down vote










          1. If I don't define the Extractor class, this will not compile. I cannot directly have Functions or some sort of functional interface.



          No, you can. You can define any Function<X, Y> by a lambda, or method reference, or anonymous class.



          List<Function<Data, Integer>> extractors = List.of(Data::getA, Data::getB);



          1. If I remove the identity function mapping line .map(e -> e), this will not type check.



          It still will, but the result may not be suitable for the method. You can always define generic parameters explicitly to make sure that everything goes as you expect.



          extractors.<Function<Data, Integer>>stream().collect(Collectors.toList())


          But there is no need here:



          Comparator<Data> test = parseKeysAscending(extractors);





          share|improve this answer















          1. If I don't define the Extractor class, this will not compile. I cannot directly have Functions or some sort of functional interface.



          No, you can. You can define any Function<X, Y> by a lambda, or method reference, or anonymous class.



          List<Function<Data, Integer>> extractors = List.of(Data::getA, Data::getB);



          1. If I remove the identity function mapping line .map(e -> e), this will not type check.



          It still will, but the result may not be suitable for the method. You can always define generic parameters explicitly to make sure that everything goes as you expect.



          extractors.<Function<Data, Integer>>stream().collect(Collectors.toList())


          But there is no need here:



          Comparator<Data> test = parseKeysAscending(extractors);






          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited 12 mins ago

























          answered 20 mins ago









          Andrew Tobilko

          21.8k83876




          21.8k83876




















              up vote
              0
              down vote













              Regarding your original code in the question, you can remove Extractor class and use a raw Comparable:



              List<Function<Data, Comparable>> extractors = new ArrayList<>();

              extractors.add(Data::getA);
              extractors.add(Data::getB);

              @SuppressWarnings("unchecked")
              Comparator<Data> test = parseKeysAscending(extractors);


              P.S. I also don't know how to get rid of the raw type here...





              share
























                up vote
                0
                down vote













                Regarding your original code in the question, you can remove Extractor class and use a raw Comparable:



                List<Function<Data, Comparable>> extractors = new ArrayList<>();

                extractors.add(Data::getA);
                extractors.add(Data::getB);

                @SuppressWarnings("unchecked")
                Comparator<Data> test = parseKeysAscending(extractors);


                P.S. I also don't know how to get rid of the raw type here...





                share






















                  up vote
                  0
                  down vote










                  up vote
                  0
                  down vote









                  Regarding your original code in the question, you can remove Extractor class and use a raw Comparable:



                  List<Function<Data, Comparable>> extractors = new ArrayList<>();

                  extractors.add(Data::getA);
                  extractors.add(Data::getB);

                  @SuppressWarnings("unchecked")
                  Comparator<Data> test = parseKeysAscending(extractors);


                  P.S. I also don't know how to get rid of the raw type here...





                  share












                  Regarding your original code in the question, you can remove Extractor class and use a raw Comparable:



                  List<Function<Data, Comparable>> extractors = new ArrayList<>();

                  extractors.add(Data::getA);
                  extractors.add(Data::getB);

                  @SuppressWarnings("unchecked")
                  Comparator<Data> test = parseKeysAscending(extractors);


                  P.S. I also don't know how to get rid of the raw type here...






                  share











                  share


                  share










                  answered 18 secs ago









                  Oleksandr

                  7,07533366




                  7,07533366



























                       

                      draft saved


                      draft discarded















































                       


                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function ()
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52596151%2fhow-does-this-compile%23new-answer', 'question_page');

                      );

                      Post as a guest













































































                      Comments

                      Popular posts from this blog

                      What does second last employer means? [closed]

                      Installing NextGIS Connect into QGIS 3?

                      One-line joke