GPA calculator, implemented for a course This is from a Java course which I'm taking. The class StudentUtil, including method-declarations and comments, was provided by the instructor. I just had to finish the bodies of the two methods.

calculateGPA() computes the average performance of students. Every A-grade gives 4 points, every B-grade 3 and every C-grade 2 point.

calculateGPA() returns students which have performed within a specified range.

The StudentUtil class:

package modul;

import java.util.ArrayList;

public class StudentUtil {

    public static double[] calculateGPA(int[] studentIdList, char[][] studentsGrades) {
        double[] grades = new double[studentIdList.length];
        int i = 0;

        for (char[] studentGrade : studentsGrades) {
            double sumGrades = 0.0;

            for (char grade : studentGrade) {
                if (grade == 'A') {
                    sumGrades += 4;
                } else if (grade == 'B') {
                    sumGrades += 3;
                } else {
                    sumGrades += 2;
                }
            }
            grades[i++] = sumGrades / studentGrade.length;
        }

        return grades;
    }

    public static int[] getStudentsByGPA(double lower, double higher, int[] studentIdList, char[][] studentsGrades) {
        // perform parameter validation. Return null if passed parameters are not valid
        if (lower &lt; 0.0) {
            return null;
        }

        if (higher &gt; 4.0) {
            return null;
        }

        if (lower &gt; higher) {
            return null;
        }

        if (studentIdList.length != studentsGrades.length) {
            return null;
        }
        // invoke calculateGPA
        double[] gpas = calculateGPA(studentIdList, studentsGrades);
        ArrayList&lt;Integer&gt; ids = new ArrayList&lt;&gt;(); 

        for (int i = 0; i &lt; studentIdList.length; i++) {
            if (gpas[i] &gt;= lower &amp;&amp; gpas[i] &lt;= higher) {
                ids.add(studentIdList[i]);
            }
        }
        // construct the result array and return it. You would need an extra for loop to compute the size of the resulting array
        int[] ret = new int[ids.size()];

        for (int i = 0; i &lt; ret.length; i++) {
            ret[i] = ids.get(i);
        }

        return ret;
    }

}


My test class:

package modul;

import static java.lang.System.out;
import java.text.DecimalFormat;
import static java.lang.System.out;

public class Sandbox {

    public static void main (String[] args) throws Exception {
        int[] studentIdList = { 1001, 1002 };
        char[][] studentsGrades = { { 'A', 'A', 'A', 'B'  }, { 'A', 'B', 'B' } };

        double[] results = StudentUtil.calculateGPA(studentIdList, studentsGrades);

        for (double result : results) {
            out.printf("%.2f\n", result);
        }

        int[] ids = StudentUtil.getStudentsByGPA(3.2, 3.5, studentIdList, studentsGrades);

        for (int result : ids) {
            out.println(result);
        }
    }
}


It has passed the automated test.

But what do you think about the demand of returning null when the validation of getStudentsByGPA() fails? Would one really do that this way? Or would one throw some specific exception instead, IllegalArgumentException for example?

What do you think about my code in general? What would you have done else and why?
In order, with come code comments first:

The StudentUtil-class:

package modul;


Try to avoid naming things as misspelled words. "Module" would probably not be a good choice for a package name because "Module" has a meaning in programming in general, and since version 1.9 it even has a specific meaning in Java. This doesn't mean, however, that "Modul" is better.

import java.util.ArrayList;

public class StudentUtil {

    public static double[] calculateGPA(int[] studentIdList, char[][] studentsGrades) {
        double[] grades = new double[studentIdList.length];
        int i = 0;

        for (char[] studentGrade : studentsGrades) {
            double sumGrades = 0.0;

            for (char grade : studentGrade) {


Double for loop, great, and without incrementing variables, even better. The names "grade" and "studentGrade" leave me a bit confused though. "studentGrade" contains more than one grade? I would replace "studentGrade" with something like "singleStudentGrades".

                if (grade == 'A') {
                    sumGrades += 4;
                } else if (grade == 'B') {
                    sumGrades += 3;
                } else {
                    sumGrades += 2;
                }


Consider having an explicit if (grade == 'C') case, and making your else case throw an error. It's a simple way to make sure your input is only ever A, B, or C. Unless we want to assume that anything not given a letter grade (e.g. something never assigned a grade) defaults to a 2.

            }
            grades[i++] = sumGrades / studentGrade.length;
        }

        return grades;
    }

    public static int[] getStudentsByGPA(double lower, double higher, int[] studentIdList, char[][] studentsGrades) {
        // perform parameter validation. Return null if passed parameters are not valid
        if (lower &lt; 0.0) {
            return null;


Note about the nulls at the end of my answer

        }

        if (higher &gt; 4.0) {
            return null;
        }

        if (lower &gt; higher) {
            return null;
        }

        if (studentIdList.length != studentsGrades.length) {
            return null;
        }
        // invoke calculateGPA
        double[] gpas = calculateGPA(studentIdList, studentsGrades);
        ArrayList&lt;Integer&gt; ids = new ArrayList&lt;&gt;(); 

        for (int i = 0; i &lt; studentIdList.length; i++) {
            if (gpas[i] &gt;= lower &amp;&amp; gpas[i] &lt;= higher) {
                ids.add(studentIdList[i]);
            }
        }
        // construct the result array and return it. You would need an extra for loop to compute the size of the resulting array


I don't understand this comment - is it justification for calling ids.size()? If so, it's not quite right. You could add a counter variable that gets incremented in your if clause above. That doesn't mean that you necessarily should, though - see below

        int[] ret = new int[ids.size()];

        for (int i = 0; i &lt; ret.length; i++) {
            ret[i] = ids.get(i);
        }


Rather than this, consider List.toArray() - it'll save you the indexing. Check out this question: Converting 'ArrayList to 'String[]' in Java

        return ret;
    }

}


test-class:

package modul;

import static java.lang.System.out;
import java.text.DecimalFormat;
import static java.lang.System.out;

public class Sandbox {

    public static void main (String[] args) throws Exception {
        int[] studentIdList = { 1001, 1002 };
        char[][] studentsGrades = { { 'A', 'A', 'A', 'B'  }, { 'A', 'B', 'B' } };

        double[] results = StudentUtil.calculateGPA(studentIdList, studentsGrades);

        for (double result : results) {
            out.printf("%.2f\n", result);
        }

        int[] ids = StudentUtil.getStudentsByGPA(3.2, 3.5, studentIdList, studentsGrades);

        for (int result : ids) {
            out.println(result);
        }
    }
}


I encourage you to consider writing unit tests (I recommend JUnit). It'll make your tests easier to write and read. The above tests are ok, but I would definitely add tests to check that you get null when you expect to.

Overall:

Your code is overall quite clean and easy to follow. It's not quite production-level, but it is good for a Java course. Nice job!

Regarding returning null:

There is a large discussion around this issue. I think most people believe something along these lines: 1.) null should not be used in place of an error. This is because in general, methods should enforce their contracts loudly, i.e. if a method's job is to take a number greater than zero and return it times two, and it gets a number less than zero, it should complain. By returning null, you force programmers who use your code (if they're responsible) to write in code to handle the possible null case, because otherwise there's no guarantee that they will be aware of the error that made your method return null.

And 2.) null can be used as "no results", but it might be better to return an Option or an empty data structure instead. For more, check out this question Is it better to return NULL or empty values from functions/methods where the return value is not present?

For a class assignment, though, it may well be the case that using null is "ok" because its fast - and it doesn't require you to shift focus from whatever the point of this assignment is to spend time writing exception throwing code. Avoiding using null as an error, and instead throwing an exception, is a best practice, though, and as you grow as a programmer you may want to do so even if it's not required. Be careful though - if your code is auto-graded and it is expected that it will return null for a given input, an exception will probably cause it to be marked wrong. In this case I suggest writing code to your assignment's specification, and being content with knowing that were you writing code in the "real world", you'd do it differently