Почему ArrayList не инициируется? Java

Так было создано свойство класса Students:

ArrayList<Integer> marks;

Далее оно появляется только в методе того же класса:

private static Students createStudent(){
    Scanner in = new Scanner(System.in);
    var st = new Students();
    out.print("Введите имя учащегося: ");
    st.name=in.next();
    out.print("Введите фамилию учащегося: ");
    st.surname= in.next();
    out.print("Введите номер и букву класса учащегося: ");
    st.numClass = in.next();
    out.print("Введите возраст учащегося: ");
    st.age = in.nextInt();
    in.nextLine();
    out.print("Введите оценки учащегося(через пробел): ");
    String marks = in.nextLine();
    String[] marksAsStr=marks.split(" ");
    st.marks = new ArrayList<Integer>(marksAsStr.length); 
    for (int i = 0; i < marksAsStr.length; i++){
        st.marks.set(i, Integer.parseInt(marksAsStr[i])); // Во время работы программы здесь появляется исключение: Index 0 out of bounds for length 0
    }
    int sum = 0;
    for(int i : st.marks) sum += i; // st.marks is always empty
    st.gpa = (double) sum /st.marks.size(); // division by zero
    out.print("Введите классного руководителя  учащегося: ");
    st.classroomTeacher = in.next();
    out.println("Студент был добавлен в эл. дневник");
    return st;
}

Хотя я задал размер массива(st.marks = new ArrayList(marksAsStr.length)), он все же остается пустым. Так в чем проблема?


Ответы (1 шт):

Автор решения: Nowhere Man

Сходная проблема была описана почти двумя годами раньше в вопросе Создать ArrayList<Integer> с фиксированной длиной Java, и в принципе её решение описано в комментарии @insolor:

  • st.marks = new ArrayList<Integer>(marksAsStr.length); создаст пустой список с заданной ёмкостью (НЕ размером!), для которого вызов метода List::set(int index, E element) для замены элемента по указанному индексу не будет иметь смысла, пока в список не добавлены какие-то элементы
  • для добавления элементов в пустой список нужно использовать методы:
    • List::add(E element), чтобы добавлять элементы в конец списка
    • List::add(int index, E element), чтобы добавлять элементы по указанному индексу, например, list.add(0, elem) всегда будет добавлять элементы в начало списка.

Также хотелось бы обратить внимание на другие возможные проблемы в приведённом коде:

  • Смешанный ввод с использованием Scanner::next / Scanner::nextLine, в частности ввод имени / фамилии при помощи Scanner::next предполагает, что имя и фамилия в общем случае состоят из ровно одного слова.
  • Использование прямого присваивания полям объекта, вместо использования хотя бы сеттеров:
Foo foo = new Foo();
foo.setBar("bar");
foo.setBaz(123);

Хотя лучше было бы использовать конструктор с указанием всех полей:

private static String readString(Scanner in, String msg) {
    out.print(msg);
    return in.nextLine();
}

private static Student createStudent(){
    Scanner in = new Scanner(System.in);
    String name = readString(in, "Введите имя учащегося: ");
    String surname = readString(in, "Введите фамилию учащегося: ");
    String numClass = readString(in, "Введите номер и букву класса учащегося: ");
    int age = Integer.parseInt(readString(in, "Введите возраст учащегося: "));

    // сканер строки оценок
    Scanner inMarks = new Scanner(readString(in, "Введите оценки учащегося(через пробел): "));
    List<Integer> marks = new ArrayList<>();
    double total = 0d;
    while (inMarks.hasNextInt()) {
        int mark = inMarks.nextInt();
        marks.add(mark);
        total += mark; 
    }
    // для пустого списка вернуть 0
    double gpa = marks.isEmpty() ? 0d : total / marks.size();

    String teacher = readString(in, "Введите имя классного руководителя учащегося: ");

    out.println("Данные введены верно, студент будет добавлен в эл. дневник");

    return new Student(name, surname, numClass, age, marks, gpa, teacher);
}

Или же использовать шаблон Строитель (Builder), в котором дополнительно были бы инкапсулированы обработка оценок и вычисление средней оценки:

public static class StudentBuilder {
    private String name, surname, numClass, teacher;
    private int age;
    private List<Integer> marks;

    public StudentBuilder name(String name) { this.name = name; return this;}
    public StudentBuilder surname(String surname) { this.surname = surname; return this;} 
    public StudentBuilder numClass(String numClass) { this.numClass = numClass; return this;}
    public StudentBuilder teacher(String teacher) { this.teacher = teacher; return this;}
    public StudentBuilder age(String age) { this.age = Integer.parseInt(age); return this;}

    public StudentBuilder marks(String marks) { 
        this.marks = Arrays.stream(marks.split(" "))
            .map(Integer::valueOf)
            .toList(); 
        return this;
    }

    private double gpa() {
        return null == marks ? 0d : marks.stream().mapToInt(Integer::intValue).average().orElse(0d); 
    }

    public Student build() {
        return new Student(name, surname, numClass, age, marks, gpa(), teacher);
    }
}
private static Student createStudent(){
    Scanner in = new Scanner(System.in);
    Student student = new StudentBuilder()
        .name(readString(in, "Введите имя учащегося: "))
        .surname(readString(in, "Введите фамилию учащегося: "))
        .numClass(readString(in, "Введите номер и букву класса учащегося: "))
        .age(readString(in, "Введите возраст учащегося: "))
        .marks(readString(in, "Введите оценки учащегося(через пробел): "))
        .teacher(readString(in, "Введите имя классного руководителя учащегося: "))
        .build();

    out.println("Данные введены верно, студент будет добавлен в эл. дневник");

    return student;
}
→ Ссылка