【问题标题】:How to sort a list by a private field?如何按私有字段对列表进行排序?
【发布时间】:2019-02-08 12:13:05
【问题描述】:

我的实体类如下所示:

public class Student {

   private int grade;

   // other fields and methods
 }

我就是这样使用它的:

List<Student> students = ...;

考虑到它是私有字段,我如何按gradestudents 进行排序?

【问题讨论】:

  • 你有那个领域的吸气剂吗?
  • 我真的尽量不使用它们,因为它们通常是一种不好的做法。
  • @Fanta 谁告诉你getters are usually a bad practice?
  • “Getter 和 setter 通常是坏习惯。” 是也不是 - 它们是包含业务逻辑的类的坏习惯。你这里有一个数据传输对象,只是一个数据结构,这里需要getter/Setter。
  • 我不会称 getter 为“坏”做法,但它们大多无用,因为与没有 getter 的暴露字段相比,它们不亚于或多于具有约束力的合同。可悲的是,IDE 通常会因为您不使用这种冗余结构而主动惩罚您(例如,自动重构通常只检查 getter 而从不直接检查字段)。绝大多数专家认为吸气剂的所谓“优势”大多是妄想。

标签: java list sorting oop encapsulation


【解决方案1】:

当你想保持成绩不公开时,你可以这样做:

students = students.stream().sorted((s1, s2) -> {
        try {
            Field f = s1.getClass().getDeclaredField("grade");
            f.setAccessible(true);
            int i = ((Integer)f.getInt(s1)).compareTo((Integer) f.get(s2));
            f.setAccessible(false);
            return i;
        } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
            e.printStackTrace();
        }
        return 0;
    }).collect(Collectors.toList());

【讨论】:

    【解决方案2】:

    您有以下选择:

    1. 使grade可见
    2. grade定义一个getter方法
    3. 定义一个Comparator 内部 Student
    4. 使Student实现Comparable
    5. 使用反射(在我看来这不是一个解决方案,它是一个解决方法/hack

    解决方案示例3:

    public class Student {
        private int grade;
    
        public static Comparator<Student> byGrade = Comparator.comparing(s -> s.grade);
    }
    

    并像这样使用它:

    List<Student> students = Arrays.asList(student2, student3, student1);
    students.sort(Student.byGrade);
    System.out.println(students);
    

    这是我最喜欢的解决方案,因为:

    • 您可以轻松定义多个Comparators
    • 代码不多
    • 您的字段保持私有和封装

    解决方案示例4:

    public class Student implements Comparable {
        private int grade;
    
        @Override
        public int compareTo(Object other) {
            if (other instanceof Student) {
                return Integer.compare(this.grade, ((Student) other).grade);
            }
            return -1;
        }
    }
    

    您可以像这样对任何地方进行排序:

    List<Student> students = Arrays.asList(student2, student3, student1);
    Collections.sort(students);
    System.out.println(students);
    

    此解决方案的方面:

    • 这定义了,按grade 排序代表学生的自然顺序
    • 一些预先存在的方法会自动排序(如TreeMap

    【讨论】:

    • 你甚至可以实现Comparable接口
    • “可比较是个好主意!”也许,也许不是……这将决定学生的*自然顺序。有人可能会争辩说学生的成绩不弥补一个好... ;o)
    • 我喜欢解决方案 3:一个聪明的功能代码示例。但是为什么叫它“定义一个内部类”呢?
    • @user1708042 一个错误 - 它可能是一个内部类,但最好使用 tge java-8 表示法。我更新了我的答案,谢谢!
    • 我最喜欢的是选项 3——在 Student 中定义一个 Comparator。
    【解决方案3】:

    JDK 1.8 提供的一个选项是使用streamsorted()方法,它不需要实现Comparable 接口。 您需要为字段grade 实现访问器(getter)方法

    public class Student {
    
    private int grade;
    
    public int getGrade() {
        return grade;
    }
    
    public Student setGrade(int grade) {
        this.grade = grade;
        return this;
    }}
    

    如果拥有 unsortedStudentList,您就可以像下面的代码那样对其进行排序:

    List<Student> sortedStudentList = unsortedStudentList
                 .stream()
                 .sorted(Comparator.comparing(Student::getGrade))
                 .collect(Collectors.toList());
    

    此外,sorted() 方法还允许您根据其他字段(如果有)对学生进行排序。例如,为学生考虑一个字段name,在这种情况下,您希望根据成绩和姓名对 studentList 进行排序。所以Student 类会是这样的:

    public class Student {
    
    private int grade;
    private String name;
    
    public int getGrade() {
        return grade;
    }
    
    public Student setGrade(int grade) {
        this.grade = grade;
        return this;
    }
    
    public String getName() {
        return name;
    }
    
    public Student setName(String name) {
        this.name = name;
        return this;
    }} 
    

    根据两个字段进行排序:

     List<Student> sortedStudentList = unsortedStudentList
                  .stream()
                  .sorted(Comparator.comparing(Student::getGrade)
                  .thenComparing(Comparator.comparing(Student::getName)))
                  .collect(Collectors.toList());
    

    当第一个比较器比较两个相等的对象时,第二个比较器开始发挥作用。

    【讨论】:

      【解决方案4】:

      一般来说,如果您需要取决于学生成绩的行为,则必须可以访问此信息 - 添加允许其他代码访问它的方法或属性。

      因此,最简单的解决方法是:

      public class Student implements IStudent {
      
          ...
          private int grade;
          ...
          // other fields and methods
      
          public int getGrade() {
              return grade;
          }
      }
      

      你也应该扩展接口IStudent :)

      但是,如果您仅需要它进行排序,则可以采用其他答案中已经提出的想法:实现 Comparable 接口。这样您就可以隐藏grade,并在int compareTo 方法中使用它。

      【讨论】:

      • @TimothyTruckle 为什么不自己回答有关 DTO 的问题? +1
      • @TimothyTruckle 为什么实体会出错?
      • “为什么实体会出错” - 恕我直言,Entity 仍然是某种(增强的?)DTO。跨度>
      • @TimothyTruckle 我不确定你在说什么。从 DDD 的角度来看,这些是完全不同的世界。具有 getter 或 setter 的 DTO 仅取决于您的工具/基础设施 - 如果您从技术角度不需要它们,则不要添加它们。对于实体,您只需尝试创建一个合理的对象模型,并在从业务角度来看有意义的地方包含 getter/setter。
      • "implemented Comparable Interface" 这将决定学生的“自然顺序”。有人可能会争辩说学生的成绩并不好... ;o )
      【解决方案5】:

      我相信这里最好的选择是创建一个需要排序列表的Comparator,因为您可能需要按其他地方的其他字段进行排序,这会增加您的域类:

      List<Student> sorted = list.stream()
          .sorted(Comparator.comparingInt(o -> o.grade))
          .collect(Collectors.toList());
      

      【讨论】:

        【解决方案6】:

        如果您确实需要按您无权访问的字段进行排序,可以使用 reflection

        private static int extractGrade(Student student) {
            try {
                Field field = Student.class.getDeclaredField("grade");
                field.setAccessible(true);
                return field.getInt(student);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        
        public static void main(String[] args) {
            Comparator<Student> studentComparator = Comparator.comparingInt(DemoApplication::extractGrade);
            List<Student> students = Arrays.asList(new Student(1), new Student(10), new Student(5));
            students.sort(studentComparator);
        }
        

        但我不得不说这种方法有点不安全。

        除非绝对必要,否则不要使用它。例如,最好使用 getter 方法访问给定字段。

        此外,如果您在模块路径上针对 Java 9+ 运行此代码,您可能会遇到问题(您可能会抛出 InaccessibleObjectException)。

        关于实现Comparable

        来自可比docs

        此接口对实现它的每个类的对象进行总排序。这种排序被称为类的自然排序,类的{@code compareTo}方法被称为它的自然比较方法

        但是Student自然排序可能是什么? 名?姓?他们的组合?

        对于数字这个问题很容易回答,但对于像Student 这样的类则不然。

        所以我认为Students 不应该是Comparable,他们是人类而不是日期或数字。而且你不能说谁更大,谁平等,谁更小。

        【讨论】:

        • 我相信,当你想通过我确定的特征(比如成绩)对任何类型的课程进行排序时,在这个例子中,它变成了 Comparable 候选。关键是当对象中有不止一种类型的排序时。在这种情况下,任何排序都不会是自然的,我不会使用推断来这样做 - 在这种情况下,我将在实现比较功能的类中有一个 Comparator 字段。跨度>
        【解决方案7】:

        之前提到但未作为示例显示的另一个选项是实现特殊的Comparator 以按等级进行比较。

        这个例子由一个实现接口IStudent的类Student、一个StudentGradeComparator和一个使用样本数据的小类Main组成。

        更多解释以代码cmets给出,请阅读

        /**
         * A class that compares students by their grades.
         */
        public class StudentGradeComparator implements Comparator<IStudent> {
        
            @Override
            public int compare(IStudent studentOne, IStudent studentTwo) {
                int result;
                int studentOneGrade = studentOne.getGrade();
                int studentTwoGrade = studentTwo.getGrade();
        
                /* The comparison just decides if studentOne will be placed
                 * in front of studentTwo in the sorted order or behind
                 * or if they have the same comparison value and are considered equal
                 */
                if (studentOneGrade > studentTwoGrade) {
                    /* larger grade is considered "worse", 
                     * thus, the comparison puts studentOne behind studentTwo
                     */
                    result = 1;
                } else if (studentOneGrade < studentTwoGrade) {
                    /* smaller grade is considered "better"
                     * thus, the comparison puts studentOne in front of studentTwo
                     */
                    result = -1;
                } else {
                    /* the students have equal grades,
                     * thus, there will be no swap 
                     */
                    result = 0;
                }
        
                return result;
            }
        }
        

        您可以在Listsort(Comparator&lt;? super IStudent&gt; comparator) 方法中应用此类:

        /**
         * The main class for trying out the sorting by Comparator
         */
        public class Main {
        
            public static void main(String[] args) {
                // a test list for students
                List<IStudent> students = new ArrayList<IStudent>();
        
                // create some example students
                IStudent beverly = new Student("Beverly", 3);
                IStudent miles = new Student("Miles", 2);
                IStudent william = new Student("William", 4);
                IStudent deanna = new Student("Deanna", 1);
                IStudent jeanLuc = new Student("Jean-Luc", 1);
                IStudent geordi = new Student("Geordi", 5);
        
                // add the example students to the list
                students.add(beverly);
                students.add(miles);
                students.add(william);
                students.add(deanna);
                students.add(jeanLuc);
                students.add(geordi);
        
                // print the list in an unordered state first
                System.out.println("———— BEFORE SORTING ————");
                students.forEach((IStudent student) -> {
                    System.out.println(student.getName() + ": " + student.getGrade());
                });
        
                /*---------------------------------------*
                 * THIS IS HOW YOU APPLY THE COMPARATOR  *
                 *---------------------------------------*/
                students.sort(new StudentGradeComparator());
        
                // print the list ordered by grade
                System.out.println("———— AFTER SORTING ————");
                students.forEach((IStudent student) -> {
                    System.out.println(student.getName() + ": " + student.getGrade());
                });
            }
        }
        

        为了完整起见,这里是接口IStudent及其实现类Student

        public interface IStudent {
        
            String getName();
            int getGrade();
        
        }
        
        
        /**
         * A class representing a student
         */
        public class Student implements IStudent {
        
            private String name;
            private int grade;
        
            public Student(String name, int grade) {
                this.name = name;
                this.grade = grade;
            }
        
            public String getName() {
                return name;
            }
        
            public void setName(String name) {
                this.name = name;
            }
        
            public int getGrade() {
                return grade;
            }
        
            public void setGrade(int grade) {
                this.grade = grade;
            }
        
        }
        

        【讨论】:

          【解决方案8】:

          Getter 并不是一个坏习惯,它们正是针对您的问题而设计的:访问私有字段以读取它们。
          添加一个getter,你可以这样做:

          studentsList.stream().sorted((s1, s2) -> s1.getGrade()compareTo(s2.getGrade)).collect(Collectors.toList())  
          

          更新:如果你真的想保持成绩不公开,你需要实现 Comparable 并覆盖比较方法。

          【讨论】:

            【解决方案9】:

            您的类可以实现Comparable 接口。然后,您可以轻松地对列表进行排序:

            public class Student implements IStudent, Comparable<Student>
            {
              ...
            
              private int grade;
              ...
            
              @Override
              public int compareTo(Student other)
              {
                return (grade - other.grade);
              }
            
            }
            
            public class Section
            {
              private List<IStudent> studentsList;
            
              ...
            
              public void sortStudents()
              {
                studentsList.sort(null);
              }
            
            }
            

            【讨论】:

              【解决方案10】:

              为Student类实现Comparable interface,实现int compareTo(T o)方法。这样您就可以将等级属性保密。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2019-06-28
                • 2011-07-09
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多