Как отсортировать список по приватному полю?

Мой класс сущности выглядит так:

public class Student {

   private int grade;

   // other fields and methods
 }

и использую вот так:

List<Student> students = ...;

Как можно отсортировать students по grade, учитывая, что это частное поле?

у вас есть геттер для этого поля?

Eugene 03.09.2018 14:22

Я действительно стараюсь не использовать их, потому что обычно это плохая практика.

Fanta 03.09.2018 14:24

сеттер может быть .. но добытчик, не могу сказать, как

Eugene 03.09.2018 14:24

@Fanta, кто тебе сказал, что геттеры are usually a bad practice?

akortex 03.09.2018 14:24
«Геттеры и сеттеры - это обычно дурные привычки». Да и нет - они плохие Привычка на классах, содержащих Бизнес-логика. У вас есть Объект передачи данных, просто структура данных, где нужны геттеры / сеттеры.
Timothy Truckle 03.09.2018 14:25

С каких это пор использование getters стало плохой практикой? Занимался разработкой на Java более 10 лет и никогда раньше не слышал этого утверждения.

Popeye 03.09.2018 14:25

Получатели / сеттеры @Popeye необходимы для DTO. Но с Бизнес-объекты они включают Особенности зависти и нарушения принципа скажи, не спрашивай!.

Timothy Truckle 03.09.2018 14:27

@Fanta читать об инкапсуляции

Pallav Kabra 03.09.2018 14:30

Я бы не назвал геттеры «плохой» практикой, но они по большей части бесполезны, потому что они не меньше и не больше являются связывающим контрактом, чем открытое поле без геттера. К сожалению, IDE часто активно наказывают вас за неиспользование этой избыточной конструкции (например, автоматический рефакторинг часто проверяет только геттеры, а не поля напрямую). Предполагаемые «преимущества», приписываемые добытчикам подавляющим большинством его экспертов, в основном бредовые.

Erwin Smout 03.09.2018 14:39

@PallavKabra Я думаю, что вы тот, кто должен прочитать об этом. Объявление частных переменных, чтобы вы могли инкапсулировать свои данные, а затем использовать геттеры, все равно что прятать что-то в стеклянном ящике. Поле не является общедоступным, но способ его получения является открытым, поэтому вы буквально нарушаете свои концепции инкапсуляции.

Fanta 03.09.2018 22:00

@Fanta Но в этом случае вам действительно нужно свойство, так какой смысл скрывать его, если вы не хотите его скрывать? Конечно, вы можете написать метод, использующий частную переменную (как в ответах), но нет ничего плохого в использовании геттера. Этот сценарий, по сути, является функцией даже C#, где вы можете использовать свойство с общедоступным получателем и частным установщиком.

Burak 17.09.2018 11:28
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
20
11
1 887
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

В общем, если вам нужно поведение, зависящее от оценки учащегося, эта информация должна быть доступна - добавьте метод или свойство, позволяющее другому коду получить к нему доступ.

Вот простейшее исправление:

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

Popeye 03.09.2018 14:29

@TimothyTruckle А почему это было бы неправильно для сущности?

BartoszKP 03.09.2018 14:29
"И почему это было бы неправильно для Сущности" - IMHO, сущность все еще является своего рода (улучшенным?) DTO.
Timothy Truckle 03.09.2018 14:31
"Почему бы не оставить себе ответ о DTO?" Я оставил комментарий к OP, думаю, мой ответ все равно пойдет насмарку ...
Timothy Truckle 03.09.2018 14:32

@TimothyTruckle Я не понимаю, о чем ты. С точки зрения DDD, это совершенно разные миры. DTO, имеющие геттеры или сеттеры, зависят только от вашего инструментария / инфраструктуры - если они вам не нужны с технической точки зрения, вы не добавляете их. Для сущностей вы просто пытаетесь создать разумную объектную модель с включенными геттерами / сеттерами, которые имеют смысл с точки зрения бизнеса.

BartoszKP 03.09.2018 14:35
"реализовать сопоставимый интерфейс" это определит «естественный порядок» студентов. Кто-то может возразить, что оценки студентов не являются хорошими ...; o)
Timothy Truckle 03.09.2018 14:35
«С точки зрения DDD, это совершенно разные миры, [...] в которых геттеры или сеттеры зависят только от вашего инструментария / инфраструктуры» согласился, просто сказав, что геттеры и сеттеры не принадлежат Бизнес-логика.
Timothy Truckle 03.09.2018 14:38

Геттеры - неплохая практика, они созданы именно для вашей проблемы: доступ к закрытым полям для их чтения. Добавьте геттер, и вы сможете:

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

Обновление: если вы действительно хотите сохранить частную оценку, вам необходимо реализовать Comparable и переопределить метод сравнения.

Ответ принят как подходящий

У вас есть следующие варианты:

  1. сделать grade видимым
  2. определить метод получения для grade
  3. определить ComparatorвнутриStudent
  4. сделать Student реализовать Comparable
  5. использовать отражение (на мой взгляд это не решение, это обходной путь / взломать)

Пример решения 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);

Это мое любимое решение, потому что:

  • Вы можете легко определить несколько Comparator
  • Это не так много кода
  • Ваше поле остается закрытым и инкапсулированным

Пример решения 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

MaanooAk 03.09.2018 14:31
"Сопоставимое - хорошая идея!" может быть, а может и нет ... Это определит «естественный порядок» студентов. Можно возразить, что оценки студентов не являются хорошими ...; o)
Timothy Truckle 03.09.2018 14:39

Мне нравится решение 3: умный пример функционального кода. Но почему вы называете это «определением внутреннего класса»?

user1708042 09.09.2018 22:50

@ user1708042 Ошибка - это мог быть внутренний класс, но нотация tge java-8 предпочтительнее. Я обновил свой ответ, спасибо!

slartidan 10.09.2018 06:41

Мне больше всего нравится вариант 3 - определить компаратор внутри ученика.

RWRkeSBZ 10.09.2018 17:43

Ваш компаратор byGrade должен быть общедоступным, иначе вы не сможете его использовать. Пожалуйста, измените свой ответ.

Evertude 12.09.2018 21:42

Реализуйте Сопоставимый интерфейс для класса Student и реализуйте метод int compareTo(T o). Таким образом, вы можете оставить свойство Grade конфиденциальным.

Ваш класс мог бы реализовать интерфейс 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);
  }

}

Другой вариант, который упоминался ранее, но не показан в качестве примера, - это реализация специального Comparator для сравнения по классам.

Этот пример состоит из класса Student, реализующего интерфейс IStudent, StudentGradeComparator и небольшого класса Main, который использует образцы данных.

Дальнейшие пояснения даются в виде комментариев к коду, прочтите их.

/**
 * 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;
    }
}

Вы можете применить этот класс в методе sort(Comparator<? super IStudent> comparator)List:

/**
 * 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;
    }

}

Опция, предоставляемая JDK 1.8, использует метод stream библиотеки sorted(), который не требует реализации интерфейса Comparable. Вам необходимо реализовать метод доступа (получателя) для поля grade

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 для ученика, и в этом случае вы хотите отсортировать список учеников по классу и имени. Итак, класс 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());

Второй компаратор вступает в игру, когда первый сравнивает два равных объекта.

Если вам действительно нужно сортировать по полю, к которому у вас нет доступа, вы можете использовать отражение:

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);
}

Но я должен сказать, что этот метод небезопасен.

Не используйте его без крайней необходимости. Лучше предоставить доступ к данному полю, например, с помощью метода получения.

Также у вас могут возникнуть проблемы, если вы запускаете этот код в пути к модулю для Java 9+ (вы можете получить выброс InaccessibleObjectException).

О реализации Comparable

Из Comparable документы:

This interface imposes a total ordering on the objects of each class that implements it. This ordering is referred to as the class's natural ordering, and the class's {@code compareTo} method is referred to as its natural comparison method.

Но что может быть естественный порядок для Student? Имя? Фамилия? Их сочетание?

На этот вопрос легко ответить для чисел, но не для таких классов, как Student.

Поэтому я не думаю, что Student должны быть Comparable, это люди, а не даты или числа. И нельзя сказать, кто больше, кто равен, а кто меньше.

Я считаю, что когда вы хотите отсортировать любой класс по определенным мною характеристикам, таким как оценка, в этом примере он становится сопоставимым кандидатом. Дело в том, что в объекте существует более одного типа сортировки. В таком случае никакая сортировка не будет естественный, и я бы не стал использовать интерфейс для этого - в этом случае у меня было бы поле Comparator в классе, реализующем функцию сравнения.

jhonata.sobrinho 11.09.2018 15:04

Я считаю, что лучшим вариантом здесь является создание Comparator, где необходим отсортированный список, потому что вам может потребоваться сортировка по другим полям в других местах, и это приведет к раздутию вашего класса домена:

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

Вы можете сделать это так, если хотите, чтобы оценка оставалась конфиденциальной:

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());

Другие вопросы по теме