使用 Java 8 自定义排序

Custom Sort using Java 8

考虑下面的 dto class -

public class RoleDto {

  private String name;

  RoleDto(String name) {
    this.name = name;
  }
}

它的实例将包含类似 -

的名称
Business Practitioner - Expert
Developer - Expert
Developer - Master
Business Practitioner - Master
Business Practitioner
Developer
Developer - Professional
Business Practitioner - Professional 

我想对实例进行排序,顺序为 -

Business Practitioner - Expert
Business Practitioner - Master
Business Practitioner - Professional
Business Practitioner

Developer - Expert
Developer - Master
Developer - Professional
Developer

我用过下面的代码-

ArrayList<RoleDto> roles = new ArrayList<>();
roles.add(new RoleDto("Business Practitioner - Expert"));
roles.add(new RoleDto("Developer - Expert"));
roles.add(new RoleDto("Developer - Master"));
roles.add(new RoleDto("Business Practitioner - Master"));
roles.add(new RoleDto("Business Practitioner"));
roles.add(new RoleDto("Developer"));
roles.add(new RoleDto("Developer - Professional"));
roles.add(new RoleDto("Business Practitioner - Professional"));

List<RoleDto> sorted = roles.stream().sorted(comparing(r -> r.getName(),
    comparing((String s) -> !s.contains("-")))).collect(toList());

for(RoleDto r : sorted)
System.out.println(r.getName());

但是输出是

Business Practitioner - Expert
Developer - Expert
Developer - Master
Business Practitioner - Master
Developer - Professional
Business Practitioner - Professional
Business Practitioner
Developer

有人可以帮我实现预期的结果吗

我建议将您的 RoleD 更改为具有两个属性:String role 和 String level

现在排序列表的代码如下所示:

Comparator<RoleDto> compareByRoleAndLevel = Comparator
                    .comparing(RoleDto::getRole)
                    .thenComparing(RoleDto::getLevel);
 
List<RoleDto> sortedRoles = roles.stream()
                .sorted(compareByRoleAndLevel)
                .collect(Collectors.toList());

你实际上并没有比较名字,比较只是检查名字是否包含“-”,这就是为什么你在所有不做的(随机的)之前得到所有做的(随机顺序)命令)。如果你可以更改 Dto,请参阅@Orr 的答案,否则你可以在适当的位置使用名称 split/compare,如下所示:

    final List<RoleDto> sorted =
        roles.stream()
            .sorted(
                comparing(
                    RoleDto::getName,
                    comparing((String s) -> s.split(" - ")[0])
                        .reversed()
                        .thenComparing(s -> s.split(" - ").length)
                        .reversed()
                        .thenComparing(s -> s.split(" - ")[1])))
            .collect(toList());

反转可能看起来有点不直观,但实际上每次反转都会反转之前的所有比较,所以第一次比较会反转两次。要跳过它,您可以改写:

    final List<RoleDto> sorted =
        roles.stream()
            .sorted(
                comparing(
                    RoleDto::getName,
                    comparing((String s) -> s.split(" - ")[0])
                        .thenComparing(s -> -s.split(" - ").length)
                        .thenComparing(s -> s.split(" - ")[1])))
            .collect(toList());

(注意比较中间的减号)

这里似乎有两个维度的数据在起作用。

  • 业务职能
    “业务从业者”&“程序员”。
  • 熟练程度
    “专家”、“大师”、“专业”,隐含“标准”、“基础”、“新手”或一些这样的(显示为无文字)。

试图将此类值表示为文本很笨拙,并且会忽略 Java 中为此目的内置的功能。

如果在 compile-time 处已知所有值,则为每个维度创建一个枚举。枚举是 class 定义的方式,以便在加载 class 时自动实例化和命名多个对象。

public enum Function { BUSINESS_PRACTITIONER, PROGRAMMER ; }
public enum Proficiency { EXPERT, MASTER, PROFESSIONAL, NOVICE; }

将它们合并为一个 class。 class 的主要目的是透明且不可变地传输数据。所以我们可以简单地定义为record.

public record Role( Function function , Proficiency proficiency ) { }

我们希望 Role 的实例分两级排序,首先按 Function,然后按 Proficiency。所以我们需要在我们的记录Role.

上实现Comparable接口
public record Role( Function function , Proficiency proficiency ) implements Comparable<Role> { … }

每个对象的排序顺序是声明命名对象的顺序。所以我们可以写出必要的compareTo method using a Comparator object. We define that Comparator object using convenience methods comparing and thenComparing, passing each a method reference。我们使用 static 将此对象标记为单例,因为频繁 re-use.

private static Comparator < Role > comparator =
        Comparator
                .comparing( Role :: function )
                .thenComparing( Role :: proficiency );

我们在 compareTo 方法中使用该比较器。

@Override public int compareTo ( Role other ) { return Role.comparator.compare( this , other ); }

我们像这样实例化一个角色对象:

Role progPro = new Role( Function.PROGRAMMER , Proficiency.PROFESSIONAL );

如果需要的话,我们可以把所有可能的角色做一个集合。

NavigableSet < Role > allPossibleRoles = new TreeSet();
for ( Function function : Function.values() )
{
    for ( Proficiency proficiency : Proficiency.values() )
    {
        allPossibleRoles.add( new Role( function , proficiency ) );
    }
}
System.out.println( "allPossibleRoles = " + allPossibleRoles );

我们可以看到这些是按照问题规定的顺序列出的。

allPossibleRoles = [Role[function=BUSINESS_PRACTITIONER, proficiency=EXPERT], Role[function=BUSINESS_PRACTITIONER, proficiency=MASTER], Role[function=BUSINESS_PRACTITIONER, proficiency=PROFESSIONAL], Role[function=BUSINESS_PRACTITIONER, proficiency=NOVICE], Role[function=PROGRAMMER, proficiency=EXPERT], Role[function=PROGRAMMER, proficiency=MASTER], Role[function=PROGRAMMER, proficiency=PROFESSIONAL], Role[function=PROGRAMMER, proficiency=NOVICE]]

最后,我们需要发出在问题中看到的文本。

修改枚举 classes 以获取显示名称的参数。

public enum Function
{
    BUSINESS_PRACTITIONER( "Business Practitioner" ), PROGRAMMER( "Programmer" );

    private final String displayName;

    // Constructor
    Function ( final String displayName ) { this.displayName = displayName; }

    public String getDisplayName ( ) { return displayName; }
}

还有另一个枚举。

public enum Proficiency
{
    EXPERT( "Expert" ), MASTER( "Master" ), PROFESSIONAL( "Professional" ), NOVICE( "" );

    private final String displayName;

    // Constructor
    Proficiency ( String displayName ) { this.displayName = displayName; }

    public String getDisplayName ( ) { return displayName; }
}

修改 Role 记录以使用 getDisplayName 方法生成所需格式的文本。

import java.util.Comparator;

public record Role( Function function , Proficiency proficiency ) implements Comparable < Role >
{
    @Override
    public int compareTo ( Role other ) { return Role.comparator.compare( this , other ); }

    private static Comparator < Role > comparator =
            Comparator
                    .comparing( Role :: function )
                    .thenComparing( Role :: proficiency );

    public String getDisplayName ( )
    {
        String x = this.function.getDisplayName();
        String y = this.proficiency.getDisplayName().isBlank() ? "" : " - ";
        String z = this.proficiency.getDisplayName();
        return x + y + z;
    }
}

用法示例。

NavigableSet < Role > allPossibleRoles = new TreeSet();  // NavigableSet/TreeSet keeps elements sorted.
for ( Function function : Function.values() )
{
    for ( Proficiency proficiency : Proficiency.values() )
    {
        allPossibleRoles.add( new Role( function , proficiency ) );
    }
}

// Dump to console.
for ( Role role : allPossibleRoles )
{
    System.out.println( "role.getDisplayName() = " + role.getDisplayName() );
}
role.getDisplayName() = Business Practitioner - Expert
role.getDisplayName() = Business Practitioner - Master
role.getDisplayName() = Business Practitioner - Professional
role.getDisplayName() = Business Practitioner
role.getDisplayName() = Programmer - Expert
role.getDisplayName() = Programmer - Master
role.getDisplayName() = Programmer - Professional
role.getDisplayName() = Programmer