Does Java have an equivalent to Python's list.sorted() method for ArrayLists?

Viewed 193

I know I can call Collections.sort() on an ArrayList in Java, but I am currently trying to overload a constructor, and in the second one I would like to call the first constructor. However, I want to pass a sorted list as one of the arguments, but Java won't let me call this() after sorting the list - it requires this() to be the first line. I could just put Collections.sort(myList) in the constructor, but I don't know if this method returns the sorted list, but ideally I would like to return the sorted list without mutating the original.

Here is my code (for a period such as you would find in a school or university), including the error:

public class Period {

    private String name;
    private LocalDateTime start;
    // duration in seconds
    private int duration;
    private Lecturer lecturer;
    private ArrayList<Demonstrator> dems;
    private ArrayList<Skill> skills;
    
    public Period(String name, LocalDateTime start, int duration, Lecturer lecturer) {
        this.name = name;
        this.start = start;
        this.duration = duration;
        this.lecturer = lecturer;
        this.dems = new ArrayList<>();
        this.skills = new ArrayList<>();
    }
    
    public Period(String name, ArrayList<AvailBlock> blocks, Lecturer lecturer) {
        // this line is illegal, but shows you what I'm trying to do.
        Collections.sort(blocks);
        this(name, blocks.get(0).getStart(), ( blocks.size() * AvailBlock.LENGTH ), lecturer);
    }
    ...
}

The AvailBlock class has a start time as a LocalDateTime object, and a fixed duration (currently 15 minutes). I am trying to have the option of creating a period with a duration in seconds, or just passing in a list of AvailBlock objects and letting it figure out when the period starts and how long it should be.

Here is the beginning of the AvailBlock class:

public class AvailBlock implements Comparable {
    
    // the start time of the slot
    private LocalDateTime start;
    // Length of slots in seconds
    public static final int LENGTH = 900;
    
    public AvailBlock(LocalDateTime start) {
        this.start = start;
    }
    ...
}
2 Answers

Make a static factory method

As commented by Mayolo, I would do this work in a static method rather than in a constructor. Best to keep constructors utterly simple.

Furthermore, the first line of a constructor must be an implicit or explicit call to the super constructor. So we cannot have code such as your call to Collections.sort before making a call to this( … ). This must be the problem to which you alluded in saying "// this line is illegal, but shows you what I'm trying to do.". This Java language requirement of first-line-as-super-constructor-call is another reason to use a static factory method to solve your problem.

I would name the method from per the java.time naming conventions.

public static Period from ( … )
{
    …
    return new Period( … ) ;
}

Usage:

Period p = Period.from( … ) ;

Make a copy of the input list

You said:

I could just put Collections.sort(myList) in the constructor, but I don't know if this method returns the sorted list, but ideally I would like to return the sorted list without mutating the original.

Make a copy of the list before sorting. Then you avoid mutating the original.

To make a shallow copy, you can pass any Collection to the constructor of ArrayList.

Also, notice that since we making a copy and then sorting, your factory method can take any kind of List, not just ArrayList. Actually, any kind of Collection can be accepted.

public static Period from ( String name, Collection< AvailBlock > blocks, Lecturer lecturer )
{
    ArrayList< AvailBlock > blocksSorted = new ArrayList<>( blocks ) ;
    Collections.sort( blocksSorted );
    return new Period( name, blocksSorted.get(0).getStart(), ( blocks.size() * AvailBlock.LENGTH ), lecturer ) ;
}

List.copyOf

If you want a unmodifiable copy, pass your list to List.copyOf.


record

By the way, you might find handy the records feature added to Java 16.

A record is a brief way to write a class whose main purpose is to communicate data transparently and immutably. You merely need declare the type and name of each member field. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString.

A record can carry a static factory method as we need here in our solution.

I'd echo other's comments about not doing too much in a constructor. However, if you do want to sort inline without mutating the original list, streams would seem the most natural way to go. You can quite easily do the sorting without mutating the original list:

blocks.stream().sorted().collect(Collectors.toList())

(...which will return a sorted list which you can use either inline, or assign to a variable.)

Related