Lambda 表達式之可讀性2 - Java Sorting

對於習慣寫SQL的朋友來講,排序是件很容易的事,通常在SQL的結尾,先後加上不同欄位名稱,就可以有排序效果。例如:

select car.size, car.NumberOfWheels
from car
order by car.size, car.NumberOfWheels

即使要改變排序的條件,先以car.NumberOfWheels再car.size,那改一改就好

select car.size, car.NumberOfWheels
from car
order by car.NumberOfWheels, car.size

但對於Java來說,就不是這麼容易的一回事。很多依賴SQL的商用開發者,可能也不記得Java是怎樣做Sorting interface的。但對於NoSQL的世代來講,Database應該視為一個Storage Engine。某些排序還是要靠Program層面做,例如傳統的Java就需要提供一個回傳-1, 0, 1的Function,以決定A應該排在B前面,還是相等,還是排在B後面。

    public static void sortExample(){
        List<Car> cars = new ArrayList<>();
        // ... many cars
        cars.sort(ChainComparator.getOldSchoolComparator());
    }

    private static Comparator<Car> getOldSchoolComparator(){
        return (a, b)->{
            Double aCarSize = a.getSize();
            Double bCarSize = b.getSize();
            if (aCarSize.compareTo(bCarSize) != 0) {
                return aCarSize.compareTo(bCarSize);
            } else { // if tied
                Integer aNumOfWheel = a.getWheels().size();
                Integer bNumOfWheel = b.getWheels().size();
                return aNumOfWheel.compareTo(bNumOfWheel);
            }
        };
    }

上述例子雖然已使用Lambda去簡化寫法,但實際上如果排序欄位很多,就會出現一個很長的表達式。而且也很難去改寫中間的先後次序,例如怎樣才能很輕易地把numOfWheel改到放在carSize前面。即使我們有辦法把分段邏輯都抽入個別Function裏面,那個If的結構也是抽不走。

在Java 8 Lambda出現後,其實Comparator也有提供新的寫法,它可以連在一起繼續延伸,讓平手、再查一下條件的情況簡化了。這也是讓Dynamic Sorting變得有可能。

    public static void sortExample(){
        List<Car> cars = new ArrayList<>();
        // ... many cars
        cars.sort(ChainComparator.getComparatorChain());
    }
	private static Comparator<Car> getComparatorCarSize(){
        return (aCar, bCar)->{
            Double aCarSize = aCar.getSize();
            Double bCarSize = bCar.getSize();
            return aCarSize.compareTo(bCarSize);
        };
    }

    private static Comparator<Car> getComparatorNumOfWheels(){
        return (aCar, bCar)->{
            Integer aNumOfWheel = aCar.getWheels().size();
            Integer bNumOfWheel = bCar.getWheels().size();
            return aNumOfWheel.compareTo(bNumOfWheel);
        };
    }

    private static Comparator<Car> getComparatorChain(){
        return ChainComparator.getComparatorCarSize()
            .thenComparing(ChainComparator.getComparatorNumOfWheels());
    }

上述的例子,可能還是沒有太體現出Lambda的好處,主要是Java型態的問題,上面那樣寫我們每次都要重複地編寫適合Car的Comparator,就變得有點囉唆。但貼心的Comparator還有提供進一步的Lambda結構。

    public static void sortExample(){
        List<Car> cars = new ArrayList<>();
        // ... many cars
        cars.sort(ChainComparator.getComparatorChain2());
    }

    private static Comparator<Double> getComparatorDouble(){
        return (aCarSize, bCarSize)->{
            return aCarSize.compareTo(bCarSize);
        };
    }

    private static Comparator<Integer> getComparatorInteger(){
        return (aNumOfWheel, bNumOfWheel)->{
            return aNumOfWheel.compareTo(bNumOfWheel);
        };
    }

    private static Comparator<Car> getComparatorChain2(){
        Comparator<Car> chainedComparator = Comparator.comparing(
            car->car.getSize(), // converter
            ChainComparator.getComparatorDouble() // reuse exisiting comparator
        );
        chainedComparator = chainedComparator.thenComparing(
            car->car.getWheels().size(), // converter
            ChainComparator.getComparatorInteger() // reuse exisiting comparator
        );
        return chainedComparator;
    }

上述的例子中,getComparatorDouble,getComparatorInteger可能是別人寫好的Comparator,它們不是針對Car來使用的。但我們還是可以經過Comparator.comparing的介面,硬把Car轉為Double或Integer,然後就可以重用別人準備好的getComparatorDouble,getComparatorInteger。

Github Code