20

My entity class looks like this:

public class Student {

   private int grade;

   // other fields and methods
 }

and I use it like that:

List<Student> students = ...;

How can I sort students by grade, taking into account that it is a private field?

slartidan
  • 20,403
  • 15
  • 83
  • 131
Fanta
  • 248
  • 2
  • 8
  • 2
    do you have a getter for that field? – Eugene Sep 03 '18 at 12:22
  • 2
    I am really trying not to use them, since they are usually a bad practice. – Fanta Sep 03 '18 at 12:24
  • 1
    a *setter* may be.. but a *getter*, can't tell how – Eugene Sep 03 '18 at 12:24
  • 7
    @Fanta who told you that getters `are usually a bad practice`? – akortex Sep 03 '18 at 12:24
  • 2
    *"Getters and setters are usually bad habits."* Yes and no - they are bad Habit on classes containig *Business logic*. What you have here is a *Data Transfer Object*, merely a *data structure* wehere getters/Setters a needed. – Timothy Truckle Sep 03 '18 at 12:25
  • 1
    Since when did using `getters` become bad practice? Been doing Java development for 10 years+ and never heard that statement before – Popeye Sep 03 '18 at 12:25
  • 1
    @Popeye getter/setter are essential for DTOs. But with *Business objects* they enable for *Feature envy* and violations of the *tell, don't ask!* principle. – Timothy Truckle Sep 03 '18 at 12:27
  • 1
    @Fanta read about Encapsulation – Pallav Kabra Sep 03 '18 at 12:30
  • 2
    I wouldn't call getters "bad" practice but they are mostly useless because they are no less or more of a binding contract than an exposed field without the getter. Sadly, IDE's often actively punish you for not using this redundant construct (e.g. automated refactoring often inspects only getters and never fields directly). The alleged "advantages" ascribed to getters by the vast majority of its pundits are mostly delusional. – Erwin Smout Sep 03 '18 at 12:39
  • 1
    @PallavKabra I think you are the one that has to read about it. Declaring private variables so you can encapsulate your data and then using getters is like hiding things in a box of glass. The field is not public, but the method to get it is, so you are literally breaking your encapsulation concepts. – Fanta Sep 03 '18 at 20:00
  • 1
    @Fanta But in this case, you do need the property, so what's the point of hiding it when you don't want to hide it? Of course you could write a method that makes use of the private variable (like in the answers), but there's nothing inherently wrong with using a getter. This scenario is essentially a feature in C# even, where you can use a property with a public getter and private setter. – Burak Sep 17 '18 at 09:28

10 Answers10

44

You have these options:

  1. make grade visible
  2. define a getter method for grade
  3. define a Comparator inside Student
  4. make Student implement Comparable
  5. use reflection (in my opinion this is not a solution, it is a workaround/hack)

Example for solution 3:

public class Student {
    private int grade;

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

and use it like this:

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

This is my favorite solution because:

  • You can easily define several Comparators
  • It is not much code
  • Your field stays private and encapsulated

Example of solution 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;
    }
}

You can sort everywhere like this:

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

Aspects of this solution:

  • This defines, that sorting by grade represents the natural order of students
  • Some preexisting methods will automatically sort (like TreeMap)
slartidan
  • 20,403
  • 15
  • 83
  • 131
6

In general, if you need a behaviour that depends on student's grade, than this information must be accessible - add a method or a property that allows other code to access it.

A simplest fix would be thus:

public class Student implements IStudent {

    ...
    private int grade;
    ...
    // other fields and methods

    public int getGrade() {
        return grade;
    }
}

You should probably extend the interface IStudent as well :)

However, if you need this only for sorting you can go with an idea already suggested in other answers: implement Comparable interface. This way you can keep the grade hidden, and use it inside the int compareTo method.

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
  • 1
    @TimothyTruckle Why not leave an answer yourself about DTO's? +1 – Popeye Sep 03 '18 at 12:29
  • 1
    @TimothyTruckle And why would it be wrong for an entity? – BartoszKP Sep 03 '18 at 12:29
  • 1
    *"And why would it be wrong for an Entity"* - IMHO an *Entity* is still some Kind of (enhanced?) DTO. – Timothy Truckle Sep 03 '18 at 12:31
  • 1
    *"Why not leave an answer yourself about DTO's?"* I left a comment to the OP, guess my answer would go down the drain anyway... – Timothy Truckle Sep 03 '18 at 12:32
  • 2
    @TimothyTruckle I'm not sure what are you talking about. From DDD point of view, these are completely different worlds. DTOs having getters or setters depend only on your tooling/infrastructure - if you don't need them from a technical point of view, you don't add them. For entities you just try to create a sensible object model, with getters/setters included where they make sense from a business point of view. – BartoszKP Sep 03 '18 at 12:35
  • 2
    *" implemented Comparable Interface"* this will determin the *natural order" of Students. One may argue that the students grades do not make up a good one... ;o) – Timothy Truckle Sep 03 '18 at 12:35
  • 1
    *"From DDD point of view, these are completely different worlds [...] having getters or setters depend only on your tooling/infrastructure"* agreed, just saying that getters and Setters do not belong to *Business logic*. – Timothy Truckle Sep 03 '18 at 12:38
6

An option provided by JDK 1.8 is using stream library sorted()method which does not require Comparable interface to be implemented. You need to implement accessor (getter) method for field grade

public class Student {

private int grade;

public int getGrade() {
    return grade;
}

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

Then given having the unsortedStudentList you are able to sort it like the below code:

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

Also, sorted() method enables you to sort students based on other fields (if you have) as well. For instance, consider a field name for student and in this case you'd like to sort studentList based on both grade and name. So Student class would be like this:

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

To sort based on both fields:

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

Second comparator comes into play when the first one is comparing two equal objects.

Ali K. Nouri
  • 495
  • 5
  • 18
4

Implement Comparable interface for the Student class and implement the method int compareTo(T o). This way you can keep the grade property private.

Dhruvil Vaghela
  • 580
  • 6
  • 13
4

Your class could implement the Comparable interface. Then, you can easily sort the list:

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

}
Robert Kock
  • 5,795
  • 1
  • 12
  • 20
4

If you really need to sort by field you don't have access to you can use 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);
}

But I have to say that this method is kinda unsafe.

Don't use it unless absolutely necessary. It is better to give access to given field using getter method for example.

Also you might get issues if you're running this code on modulepath against Java 9+ (you can get InaccessibleObjectException thrown).

About implementing Comparable

From Comparable docs:

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.

But what might be a natural ordering for Student? First name? Last name? Their combination?

It's easy to answer this question for numbers, but not for classes like Student.

So I don't think that Students should be Comparable, they are humans and not dates or numbers. And you can't say who is greater, who is equal and who is lesser.

Denis Zavedeev
  • 7,627
  • 4
  • 32
  • 53
  • I believe that when you want to sort any kind of class by I determined characteristic like grade, in this example, it becomes a Comparable candidate. The point is when there is more than one type of sorting in an Object. In such case, none of the sorting will be _natural_ and I wouldn't use the inferface to do so - In this cases I'd have a Comparator field in the class implementing the comparison feature. – jhonata.sobrinho Sep 11 '18 at 13:04
3

Another option that was mentioned before but not shown as an example is implementing a special Comparator for a comparison by grade.

This example consists of a class Student implementing an interface IStudent, a StudentGradeComparator and a little class Main that uses sample data.

Further explanations are given as code comments, please read them

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

You can apply this class in the sort(Comparator<? super IStudent> comparator) method of a 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());
        });
    }
}

Just for completeness, here are the interface IStudent and its implementing class 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;
    }

}
deHaar
  • 17,687
  • 10
  • 38
  • 51
2

Getters are not a bad practice, they are exactly made for your problem: Accessing private fields to read them.
Add a getter and you can do:

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

Update: If you really want to keep the grade private, you need to implement Comparable and override the compare-method.

ItFreak
  • 2,299
  • 5
  • 21
  • 45
2

You can do it this way when u want to keep grade private:

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());
Z3RP
  • 328
  • 1
  • 16
1

I belive the best option here is to create a Comparator where the sorted list is needed, because you may need to sort by other fields in other places and that would inflate your domain class:

List<Student> sorted = list.stream()
    .sorted(Comparator.comparingInt(o -> o.grade))
    .collect(Collectors.toList());
Samuel Negri
  • 479
  • 4
  • 15