javaenumscomparatorimplements

Understanding Java Enum Methods Implementation through an Example


So, I'm working on a project right now for school with a few people and one of them has committed some code that I'm really having difficulty wrapping my head around. The basis of the project is creating a music library with songs, albums, and playlists. These playlists, in particular, are arraylists of songs need different ways of sorting and thus he's implemented comparator for sorting. He did so using enums, which I understand from the perspective of just instances to represent items. Like

    public enum Suit {
       SPADES, CLUBS, HEARTS, DIAMONDS
    }

to represent different suits of a card. I also have learned you can declare methods alongside enums, which is what it looks like he did. Here is the attribute declaration area and the constructor:

 public class Playlist implements Comparator<Song>{
    
        private TotalTime aTotalTime;
        private String aName;
        private ArrayList<Song> playList;
        private Comparator<Song> aComparator;
        private enum Method{
            SortByTitle(new Comparator<Song> () {
    
                @Override
                public int compare(Song o1, Song o2) {
                    // TODO Auto-generated method stub
                    return o2.getTitle().compareTo(o1.getTitle());
                    }
                
            }),
            SortByArtist(new Comparator<Song>() {
    
                @Override
                public int compare(Song o1, Song o2) {
                    // TODO Auto-generated method stub
                    return o2.getExpectedTags().getArtist().compareTo(o1.getExpectedTags().getArtist());
                    }
                
            }),
            SortByLength(new Comparator<Song>() {
    
                @Override
                public int compare(Song o1, Song o2) {
                    // TODO Auto-generated method stub
                    return o2.getTotalSeconds()-o1.getTotalSeconds();
                }
                
            });
            private Comparator<Song> comparator;
            Method(Comparator<Song> pComparator){
                comparator = pComparator;   
            }
            public Comparator<Song> getComparator(){
                return this.comparator;
            }
        }
    
        // constructor that initializes the the playlist.
        public Playlist(String pName,Method aMethod) {
            aName = new String(pName);
            playList = new ArrayList<Song>();
            this.aComparator = aMethod.getComparator();
        }
}

I can vaguely follow what's going on here as such: We start with the constructor, which calls aMethod.getComparator(), with aMethod being the enum instance, and then aMethod.getComparator() returns the this.comparator object, which itself is declared three lines above as a private Comparator<Song> comparator. From my perspective, it looks like ithis will return the private comparator object every time and not actually change the sorting method of the Comparable interface. Any help parsing all of this would be greatly appreciated.


Solution

  • Look only at the enum definition.

    The enum definition defines 3 actual enums: SortByTitle, SortByLength, and SortByArtist - those are your SPADE, HEARTS, DIAMONDS, CLUBS; of this enum. For each value, they are initialized with a non-zero-length constructor, and the object passed is a custom impl of a comparator, but forget all that for now.

    The enumeration (heh) of enum values then ends. Why? Because semicolon.

    But the enum definition doesn't end yet; then we get private Comparator<Song> comparator;.

    Note that each individual enum value gets its own copy of this field. Each value of an enum is itself an instance of the 'class' that the enum represents. And the key point here is that this field holds different comparators for SortByArtist, SortByLength, etc.

    Therefore, Method.SortByArtist.getComparator(); returns the value of that field for the instance of the Method 'class' (enums are basically classes, with highly limited construction; only one instance per value, so 3 instances here, ever). Which is different from the value of that field for the SortByLength instance.

    The rest is just anonymous inner classes.

    This is valid java, I think it should be fairly obvious to tell, right?

    class StringByLengthComparator implements Comparator<String> {
        public int compare(String a, String b) {
            return a.length() - b.length();
        }
    }
    
    ...
    
    Comparator<String> c = new StringByLengthComparator();
    

    but we can write that with less characters in java, using the concept 'anonymous inner classes'. This works when you make a class and then intent to use this definition exactly once, and then never use it again. Let's say this 'Comparator c = ...;' line is the only place in the entire code base that you're ever going to mention StringByLengthComparator by name. Then:

    Comparator<String> c = new Conmparator<String>() {
        public int compare(String a, String b) {
            return a.length() - b.length();
        }
    };
    

    Looks funky, but it means the exact same thing. The one difference is that this class, which still exists, is not named StringByLengthComparator, but it gets a random name you needn't worry about and cannot ever use. That's okay though - after all, this was the only place we were ever gonna use this thing.

    Java lambdas.

    Note that you can make this even shorter, using lambda syntax:

    Comparator<String> c = (a, b) -> a.length() - b.length();
    

    still means the same thing (well, the object you get no longer has presence, which only matters if you do very silly things, such as relying on its object identity hashcode, or lock on the comparator itself, which are all crazy things to do. So if you don't do anything crazy, it's the same thing).

    That's what the code is doing. It's no different than first defining 3 classes that each implement Comparable<Song>, and then passing new SongsByLengthComparer() as expression as first argument.