Implementation of a simple hash function
The very idea of "if that hash is already taken, use another one" leads to bugs that are really hard to find later.

It's because your hash function (oh, it is called a function, hopefully in the mathematical sense) no longer has the signature hash(studentNumber) but instead becomes hash(studentNumber, allPreviousStudentNumbers). This means you get different hashes based on how the students are sorted.

A hash function may by definition produce collisions, and that is fine. Choosing an appropriate hash function that minimizes these collisions is often crucial. In cryptography the commonly used hashes have 256 bits, which makes a collision very unlikely. I hope your teacher plans to discuss these topics in class, as these are important.

Regarding your implementation of the basic hash function. It looks a little more complicated than necessary. An experienced Java programmer will know that there is a predefined String constructor that forms a String from part of a character array. It is new String(charray, start, count). Using this, the code becomes simple:

private static int hash(char[] studentNo) {
    int first = Integer.parseInt(new String(studentNo, 0, 3));
    int second = Integer.parseInt(new String(studentNo, 3, 3));
    int third = Integer.parseInt(new String(studentNo, 6, 2));
    return first + second + third;
}


This code is almost as simple as you would describe the algorithm to a human, and that's how it should be.

One tricky thing that bites me every time I use the String class: in general there are two ways of referring to a substring, a subarray or a subsequence: either (start, end) or (start, count). Unfortunately Java uses both of them. In the String constructor I used above, it's (start, count), while in the substring method it's (start, end). Even after 20 years of programming in Java, I still have to look at the documentation to know when each of these applies.