Java排序修真:从入门到进阶,从后天生灵到祖神祖仙

本文深入浅出地讲解了Java排序的多种方法,包括基本的数组排序、字符串排序、对象排序,并介绍了Comparable和Comparator接口的使用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

由浅入深,大道至简。

一、炼精化气:int数组冒泡排序

什么,听说你修真忘了秘籍?这就带你回忆回忆,冒泡排序图解:

在这里插入图片描述

理解了冒泡排序流程之后,我们随手写一个int类型数组的冒泡排序:

public static void main(String[] args) {
    int[] array = {5, 6, 7, 12, 36, 9, 10, 4};
    for (int i = 0; i < array.length; i++) {
        for (int j = i; j < array.length; j++) {
            if (array[i] > array[j]) {
                int temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }
    }
    System.out.println(Arrays.toString(array));
}

控制台输出结果:

[4, 5, 6, 7, 9, 10, 12, 36]

Process finished with exit code 0

输出结果与我们预想的一致。

but,请思考一下,如果说我们要给String类型的字符串进行冒泡排序,又该怎么解决呢?
【此处靓仔挠头】

String[] array = {"100", "13", "21", "12", "43", "90", "01", "100"};

我们先试着把int类型的数字变成字符串,这样就能获得一个字符串数组,然后通过转型,又将字符串变成数字,像之前那样进行比较。

public static void main(String[] args) {
    String[] array = {"100", "13", "21", "12", "43", "90", "01", "100"};
    for (int i = 0; i < array.length; i++) {
        for (int j = i; j < array.length; j++) {
            int a = Integer.parseInt(array[i]);// String转int类型
            int b = Integer.parseInt(array[j]);
            if (a > b) {// 比较int类型的两个数
                String temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }
    }
    System.out.println(Arrays.toString(array));
}

输出结果

[01, 12, 13, 21, 43, 90, 100, 100]

Process finished with exit code 0

我猜你会想,这不是脱了裤子放屁,最后还是转成了int进行比较吗?

别着急,这是为之后转换真正的字符串进行铺垫。

二、炼气化神:String数组使用API进行比较——compareTo

我们直接查看String给我们提供的比较API源码:

public int compareTo(String anotherString) {
    int len1 = value.length;
    int len2 = anotherString.value.length;
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;

    int k = 0;
    while (k < lim) {
        char c1 = v1[k];
        char c2 = v2[k];
        if (c1 != c2) {
            return c1 - c2;
        }
        k++;
    }
    return len1 - len2;
}

通过源码我们不难发现,原理和之前我们的写法有着惊人的相似!

API首先获取两个字符串的长度,通过Math.min函数取其中最小的长度,将字符串转换为char类型的数组,再将char数组进行遍历比较,由此制定了String类型的比较规则。

我们使用compareTo方法玩一把:

public static void main(String[] args) {
    String[] array = {"dcba", "acdb", "cadb", "cabd", "abcd", "adcb", "cbad", "dabc"};
    for (int i = 0; i < array.length; i++) {
        for (int j = i; j < array.length; j++) {
            if (array[i].compareTo(array[j]) > 0) {
                String temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }
    }
    System.out.println(Arrays.toString(array));
}

输出结果:

[abcd, acdb, adcb, cabd, cadb, cbad, dabc, dcba]

Process finished with exit code 0

这样的排序结果还是令人比较满意的。按照首字母排列。

【满意】

那我们再多测试几组数据看看(此处忽略代码结构,仅观察参数和输出结果):

String[] array = {"a", "c", "d", "b", "cda", "ba", "cd", "ab"};
[a, ab, b, ba, c, cd, cda, d]

Process finished with exit code 0
String[] array = {"100", "13", "21", "12", "43", "90", "01", "100"};
[01, 100, 100, 12, 13, 21, 43, 90]

Process finished with exit code 0

我们发现结果开始有点不对劲!似乎不是我们期望的结果了。

在这里插入图片描述

由此我们需要重新了解compareTo方法的特性!

通过观察测试结果,再结合compareTo方法的源码,我们不难看出,这种比较方式,类似最左前缀原理。从左到右,按照字符ASCALL码大小进行排序,而完全与字符串的长度无关,也就是字符串的长度不被考虑进入比较范围。

因此,在以后使用此方法的过程中,一定要注意此方法不适用于比较长度不一的数字型字符串。

现在我们搞明白了字符串的比较规则,下一步我们要研究研究如何比较对象。

【升级】

三、炼神还虚:将对象排序——实现Comparable接口

什么?你没有对象?别闹!

【给你对象】

通常情况下,当我们谈到给对象排序,脑子里首先想到的应该是让对象实体实现Comparable接口,然后重写compareTo方法。

说干就干,我们指定指定以id进行排序:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Comparable {

    private String id;
    private String username;
    private String password;
    private int age;

    @Override
    public int compareTo(Object o) {
        return o instanceof User ? this.id.compareTo(((User) o).id) : 0;
    }
    
}
public static void main(String[] args) {
    List<User> userList = new ArrayList<>();
    //                     id    username  password  age
    userList.add(new User("009", "admin5", "123123", 12));
    userList.add(new User("005", "admin4", "321332", 12));
    userList.add(new User("003", "admin3", "123123", 12));
    userList.add(new User("004", "admin2", "123123", 15));
    userList.add(new User("002", "admin1", "123123", 16));
    Collections.sort(userList);
    userList.forEach(user -> System.out.println(user));
}

输出结果:

User(id=002, username=admin1, password=123123, age=16)
User(id=003, username=admin3, password=123123, age=12)
User(id=004, username=admin2, password=123123, age=15)
User(id=005, username=admin4, password=321332, age=12)
User(id=009, username=admin5, password=123123, age=12)

Process finished with exit code 0

没错了,是我们要的根据id排序!

接下来,我们来仿写一个Comparable接口。

四、炼虚合道:仿写Comparable接口

先写一个接口:

public interface MyComparable<T> {
    int myCompareTo(T o);
}

给实体类指定比较规则:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements MyComparable {

    private String id;
    private String username;
    private String password;
    private int age;


    @Override
    public int myCompareTo(Object o) {
        return o instanceof User ? this.id.compareTo(((User) o).id) : 0;
    }
}

测试自定义排序:

public class Test {
    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        //                     id    username  password  age
        userList.add(new User("009", "admin5", "123123", 12));
        userList.add(new User("005", "admin4", "321332", 12));
        userList.add(new User("003", "admin3", "123123", 12));
        userList.add(new User("004", "admin2", "123123", 15));
        userList.add(new User("002", "admin1", "123123", 16));

        mysort(userList);// 排序

        userList.forEach(user -> System.out.println(user));
    }

    // 指定排序方式
    public static <T extends MyComparable<? super T>> void mysort(List<T> users) {
        for (int i = 0; i < users.size(); i++) {
            for (int j = i; j < users.size(); j++) {
                T a = users.get(i);
                T b = users.get(j);
                if (a.myCompareTo(b) > 0)
                    Collections.swap(users, i, j);
            }
        }
    }
    
}

输出结果:

User(id=002, username=admin1, password=123123, age=16)
User(id=003, username=admin3, password=123123, age=12)
User(id=004, username=admin2, password=123123, age=15)
User(id=005, username=admin4, password=321332, age=12)
User(id=009, username=admin5, password=123123, age=12)

Process finished with exit code 0

不错不错, 自定义的Comparable接口也能满足我们的需求。

可即便是这样的排序方式,不禁让人产生思考:若是换一种排序规则,岂不是又要修改代码重写compareTo方法?
在这里插入图片描述

五、超凡入圣:将对象排序——实现Comparator接口

为了解决上面抛出的问题,我们可以使用Comparator接口来解决。

使用匿名内部类可以灵活地设置排序规则

public static void main(String[] args) {
    List<User> userList = new ArrayList<>();
    //                     id    username  password  age
    userList.add(new User("009", "admin5", "123123", 12));
    userList.add(new User("005", "admin4", "321332", 12));
    userList.add(new User("003", "admin3", "123123", 12));
    userList.add(new User("004", "admin2", "123123", 15));
    userList.add(new User("002", "admin1", "123123", 16));
    // 排序
    userList.sort(new Comparator<User>() {
        @Override
        public int compare(User o1, User o2) {
            return o1.getId().compareTo(o2.getId());
        }
    });
    userList.forEach(user -> System.out.println(user));
}

输出结果:

User(id=002, username=admin1, password=123123, age=16)
User(id=003, username=admin3, password=123123, age=12)
User(id=004, username=admin2, password=123123, age=15)
User(id=005, username=admin4, password=321332, age=12)
User(id=009, username=admin5, password=123123, age=12)

Process finished with exit code 0

使用Comparator接口可以不用再到实体类中指定比较规则,直接使用匿名内部类的方式指定排序规则,相较于Comparable接口更加灵活。

可这样的做法,只适用于偶尔一两次的排序,你能明白我的意思吗?如果在项目中大量地用到了排序,每次排序我们都要去写一个内部类指定排序规则,这样难免写出很多相同代码,使整个程序看起来臃肿,为了应对这样的场景,我们接着往下看。

六、因果不沾:Comparator接口——抽取多种排序规则

如果在我们的需求中,存在多种排序方式,可以直接在实体类中写内部类,通过内部类实现Comparator接口,指定多种比较规则。而在我们排序的时候,直接创建内部类对象即可。

在这里插入图片描述

在User实体类中写俩内部类:SortUserById 和 SortUserByAge,实现Comparator接口

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private String id;
    private String username;
    private String password;
    private int age;

    // 根据id排序
    class SortUserById implements Comparator<User> {
        @Override
        public int compare(User o1, User o2) {
            return o1.getId().compareTo(o2.getId());
        }
    }

    // 根据年龄排序
    class SortUserByAge implements Comparator<User> {
        @Override
        public int compare(User o1, User o2) {
            return o1.getAge() - o2.getAge();
        }
    }

}

在测试类中,我们直接new内部类实现排序

public class Test {
    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        //                     id    username  password  age
        userList.add(new User("009", "admin5", "123123", 12));
        userList.add(new User("005", "admin4", "321332", 12));
        userList.add(new User("003", "admin3", "123123", 12));
        userList.add(new User("004", "admin2", "123123", 15));
        userList.add(new User("002", "admin1", "123123", 16));

        //userList.sort(new User().new SortUserById());// 通过id排序
        userList.sort(new User().new SortUserByAge());// 通过年龄排序

        userList.forEach(user -> System.out.println(user));
    }

}

输出结果:

1、通过id排序:

User(id=002, username=admin1, password=123123, age=16)
User(id=003, username=admin3, password=123123, age=12)
User(id=004, username=admin2, password=123123, age=15)
User(id=005, username=admin4, password=321332, age=12)
User(id=009, username=admin5, password=123123, age=12)

Process finished with exit code 0

2、通过年龄排序:

User(id=009, username=admin5, password=123123, age=12)
User(id=005, username=admin4, password=321332, age=12)
User(id=003, username=admin3, password=123123, age=12)
User(id=004, username=admin2, password=123123, age=15)
User(id=002, username=admin1, password=123123, age=16)

Process finished with exit code 0

这样一次指定多种排序方式,在需要的时候直接创建排序规则,比之前的方法更加灵活和便捷!

【握草牛逼】

然后紧接着下一步,我们要解决多重排序,比如:先通过年龄排序,同年龄的情况下,再按照id排序。

七、万劫不灭:Comparator接口——多重排序

多重排序,我们只需要新建一个内部类:

// 根据年龄,再根据id排序
class SortUserByAgeThenId implements Comparator<User>{
    @Override
    public int compare(User o1, User o2) {
        int result = o1.getAge() - o2.getAge();
        if(result == 0)	// 若age相等,则比较id
            result = o1.getId().compareTo(o2.getId());
        return result;
    }
}

测试方法同上,我们再看测试结果!

User(id=003, username=admin3, password=123123, age=12)
User(id=005, username=admin4, password=321332, age=12)
User(id=009, username=admin5, password=123123, age=12)
User(id=004, username=admin2, password=123123, age=15)
User(id=002, username=admin1, password=123123, age=16)

Process finished with exit code 0

现在是不是完美了!

在这里插入图片描述

八、无所不知,无所不能:仿写Comparator

既然知道了如何使用Comparator接口,自然要自己手写一把自定义Comparator才过瘾!
在这里插入图片描述

老规矩,先写接口:

@FunctionalInterface
public interface MyComparator<T> {
    int myCompare(T o1, T o2);
}

接着写实体类和排序规则:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private String id;
    private String username;
    private String password;
    private int age;

    // 根据id排序
    class SortUserById implements MyComparator<User> {
        @Override
        public int myCompare(User o1, User o2) {
            return o1.getId().compareTo(o2.getId());
        }
    }

    // 根据年龄排序
    class SortUserByAge implements MyComparator<User> {
        @Override
        public int myCompare(User o1, User o2) {
            return o1.getAge() - o2.getAge();
        }
    }

    // 根据年龄,再根据id排序
    class SortUserByAgeThenId implements MyComparator<User> {
        @Override
        public int myCompare(User o1, User o2) {
            int result = o1.getAge() - o2.getAge();
            if (result == 0)
                result = o1.getId().compareTo(o2.getId());
            return result;
        }
    }

}

最后是测试类:

public class Test {

    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        //                     id    username  password  age
        userList.add(new User("009", "admin5", "123123", 12));
        userList.add(new User("005", "admin4", "321332", 12));
        userList.add(new User("003", "admin3", "123123", 12));
        userList.add(new User("004", "admin2", "123123", 15));
        userList.add(new User("002", "admin1", "123123", 16));

        mysort(userList, new User().new SortUserByAgeThenId());// 自定义排序

        userList.forEach(user -> System.out.println(user));
    }

    // 自定义排序
    public static <T> void mysort(List<T> users, MyComparator<T> comparator) {
        for (int i = 0; i < users.size(); i++) {
            for (int j = i; j < users.size(); j++) {
                T a = users.get(i);
                T b = users.get(j);
                if (comparator.myCompare(a, b) > 0)
                    Collections.swap(users, i, j);
            }
        }
    }

}

输出结果:

User(id=003, username=admin3, password=123123, age=12)
User(id=005, username=admin4, password=321332, age=12)
User(id=009, username=admin5, password=123123, age=12)
User(id=004, username=admin2, password=123123, age=15)
User(id=002, username=admin1, password=123123, age=16)

Process finished with exit code 0

在这里插入图片描述

很简单有木有?接下来是优化的写法。

九、天道不灭,圣人不死:使用lambda表达式,简化Comparator写法

这里我们优化的是匿名内部类。

在上文中,我们有一段代码是这样写的:

// 排序
userList.sort(new Comparator<User>() {
    @Override
    public int compare(User o1, User o2) {
        return o1.getId().compareTo(o2.getId());
    }
});

在Java 8中,我们可以使用函数式编程,Lambda表达式在Java语言中引入了一个操作符**“->”**,该操作符被称为Lambda操作符或箭头操作符。它将Lambda分为两个部分:

  • 左侧:指定了Lambda表达式需要的所有参数
  • 右侧:指定了Lambda体,即Lambda表达式要执行的功能。

于是乎,上面的方法,等同于下面的方法。

userList.sort((o1, o2) -> {
    return o1.getId().compareTo(o2.getId());
});

我们继续简化,去掉花括号:

userList.sort((o1, o2) -> o1.getId().compareTo(o2.getId()));

代码逐渐优雅起来,直接压缩成一句代码。
在这里插入图片描述

飞升——羽化登仙

掌握了上面的一系列比较方法,在应对不同场景的比较时,基本上可以做到游刃有余,立于不败之地!
在这里插入图片描述

学废没?反正我是学废了~~略略略
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骊恨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值