2

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;
    }
    ...
}
3
  • 2
    If you find yourself doing nontrivial work in a constructor, you might consider making it a static factory method instead. Generally, when I call a constructor, I expect it to do nothing more than assign some fields, do some basic error checking, and the absolute minimum amount of work necessary to get a working instance of the class back to me. Commented Jul 6, 2021 at 15:58
  • Thank you. I have since created a third constructor but it is looking a little messy - I will look into static factory method as I am not familiar with it. Commented Jul 6, 2021 at 16:02
  • @SilvioMayolo "Create a managed, sorted copy of this list of objects" is a perfectly ordinary task for some manager object. Commented Jul 6, 2021 at 16:13

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks very much for the in-depth reply. So, if I have something like x = new ArrayList<SomeType>(someCollection); that would be like python's x = someCollection.copy()? A shallow copy is exactly what I want in this case. That record thing sounds very useful, I'll look into that.
@Donagh I do not know Python. But yes, passing a Collection such as a List to the constructor of ArrayList produces a new List/Collection containing references to all the same objects in the original. For example, if you start with a list of ten references to ten objects, after passing to the constructor, you’ll have two lists, each with ten references for a total of twenty references, but the same original ten objects, each of the ten objects being referenced twice, once from each list (original list, and new list).
thanks. Yes, that's what Python's .copy() method does - I can't remember how deep copy is done in Python, but that's not what I was looking for anyway. Thanks for the clarification (I've actually always wondered if new ArrayList<>() can take an argument, so now I know!).
0

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.)

1 Comment

Good to know, but I feel like the previous suggestions might be a bit more readable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.