The Students Examples

Extending Person to Student

The People example stored only a name and a date with each personal record. In practice we might want more information stored than that. Suppose we are writing a program to maintain a database of students. Along with their names and date-of-birth, we might want to store a list of marks obtained. Here is a Java class for student records:
 1  import java.util.*;
 2  import java.io.*;
 3
 4  class Student extends Person
 5  {
 6   static int count=0;
 7   static final int MAXMARKS=30;
 8   int numSubjects,studentNum;
 9   int[] marks;
10   String firstNames;
11
12   public Student(int d,int m, int y,String n1,String n2,int num,int marks[])
13   throws PersonException
14   {
15    super(d,m,y,n1);
16    numSubjects=num;
17    this.marks=marks;
18    firstNames=n2;
19    studentNum=++count+10000;
20   }
21
22   public String toString()
23   {
24    String marksString="";
25    for(int i=0;i<numSubjects;i++)
26       marksString+=" "+marks[i];
27    return "Name: "+firstNames+" "+name+
28           "\nDate of birth: "+dateOfBirth+
29           "\nStudent number: "+studentNum+
30           "\nMarks: "+marksString+"\n";
31   }
32
33   public int totalMarks()
34   {
35    int myMarks=0;
36    for(int i=0;i<numSubjects;i++)
37       myMarks+=marks[i];
38    return myMarks;
39   }
40
41   public double averageMark()
42   {
43    return (double)totalMarks()/numSubjects;
44   }
45
46   public boolean lowerTotalMarksThan(Student s)
47   {
48    return (totalMarks()<s.totalMarks());
49   }
50
51   public boolean lowerAverageMarkThan(Student s)
52   {
53    return (averageMark()<s.averageMark());
54   }
55
56  }
A copy of the Java code for this class can be found in the directory
/import/teaching/BSc/1st/ItP/people
As you can see from line 1, the class Student extends the class People, so People is a superclass of Student. This means that Student objects have a name field and a dateOfBirth field, inherited from Person. They also inherit the older and beforeAlphabetically methods from Person. This means that students can be compared using these methods, and arrays of students may be sorted using the PeopleSorter class developed in the People notes.

Students have the additional information of their marks, stored as the field marks declared on line 9 above. The marks field is an array of integers. Different students may have taken different numbers of exams, so we do not assume every student has the same number of marks, rather we have a separate field, numSubjects declared on line 8 to say how many marks a particular student has. Additionally another integer field studentNum is declared to hold a unique number for each student, and a string field firstNames is declared on line 10 to hold students' first names. The idea is that the name field inherited from Person will only store the surname. The toString method on lines 22-31 overrides the toString method inherited from Person. This means that when a Student object is printed, it is printed according to the toString method in class Student, not according to the toString method in class Person.

Here is a program, similar to the People programs, which reads in a collection of student records from a file, sorts them by name, and prints them:

1  import java.io.*;
2  import java.util.*;
3  
4  class Students
5  {
6  //  Read a number of Student records from a file, store them in an array
7  
8     static final int MAXSTUDENTS=100;
9     static final int MAXMARKS=30;
10  
11     public static void main(String[] args) throws IOException
12     {
13      String filename;
14      Student [] data;
15      int count=0;
16      BufferedReader in = Text.open(System.in),inFile;
17      for(;;)
18          try
19             {
20              System.out.print("\nEnter file name to read from: ");
21              filename=Text.readString(in);
22              inFile=Text.open(filename);
23              break;
24             }
25          catch(FileNotFoundException e)
26             {
27              System.out.println("No file of that name found");
28             }
29      data = new Student[MAXSTUDENTS];
30      try
31         {
32          for(;;)
33             try
34                {
35                 data[count++] = readStudent(inFile);
36                }
37             catch(PersonException e)
38                {
39                 System.out.println(e.getMessage());
40                 count--;
41                }
42         }
43      catch(IOException e)
44         {
45         }
46      catch(ArrayIndexOutOfBoundsException e)
47         {
48          System.out.print("Maximum number of students ("+MAXSTUDENTS);
49          System.out.println(") exceeded");
50         }
51      count--;
52      new PeopleSorter(new PeopleOrderByName()).sort(data,count);
53      System.out.println("\nThe records are:\n");
54      for(int i=0;i<count;i++)
55         System.out.println(data[i]);
56     }
57
58     public static Student readStudent(BufferedReader reader)
59     throws IOException,PersonException
60     {
61      int day,month,year,numMarks;
62      String name,marksString,dateString,nameString,firstNames;
63      StringTokenizer tokens;
64      int[] marks;
65      nameString=reader.readLine();
66      if(nameString==null)
67         throw new IOException();
68      tokens = new StringTokenizer(nameString);
69      firstNames = tokens.nextToken();
70      name=tokens.nextToken();
71      while(tokens.hasMoreTokens())
72         {
73          firstNames+=" "+name;
74          name=tokens.nextToken();
75         }
76      dateString=reader.readLine();
77      tokens = new StringTokenizer(dateString,"/");
78      day=Integer.parseInt(tokens.nextToken());
79      month=Integer.parseInt(tokens.nextToken());
80      year=Integer.parseInt(tokens.nextToken());
81      marks = new int[MAXMARKS];
82      marksString=reader.readLine();
83      tokens = new StringTokenizer(marksString);
84      numMarks=0;
85      while(tokens.hasMoreTokens())
86          marks[numMarks++]=Integer.parseInt(tokens.nextToken());
87      return new Student(day,month,year,name,firstNames,numMarks,marks);
88     }
89  
90  }
Note the sorting part on line 52 makes of the sorting classes we have already constructed. It uses sorting by name, but as the name now only stores the surname, it will cause sorting to be done by surname. The readStudent method on lines 58-88 is more complex than the readPerson method because it has to read more information. A file called students can be found in the directory /import/teaching/BSc/1st/ItP/people to test the program out.

In the Student class, some methods were declared which allow students to be compared according to their average and total marks (a student with higher total marks than another may have lower average marks if he or she has taken a smaller number of subjects). So, for example, if s1 and s2 are Student variables, s1.averageMark() gives the average mark of the student record stored in s1, while s1.lowerAverageMarkThan(s2) is true if the student record stored in s1 represents someone with lower average marks than the student reord stored in s2. This means that the Students program given above can easily be modified if we want the students to be printed out in order of average mark. We simply replace line 52 by:

new PeopleSorter(new StudentOrderByAverageMark()).sort(data,count);
where the code for StudentOrderByAverageMark is:
class StudentOrderByAverageMark implements PeopleOrder
{
 public boolean before(Person p1,Person p2)
 {
  return ((Student) p1).lowerAverageMarkThan((Student) p2);
 }
}
Note that it is necessary to have the two arguments to the method before in this class as type Person rather than type Student in order for it to have the same header as the declaration of method before in interface PeopleOrder. The arguments are converted to type Student by type conversion using (Student), so (Student) p1 is the object originally indicated as being of type Person converted back to type Student.

Class variables

On lines 6 and 7 of the Student class, you will see two int variables declared as static. A variable in a class which is declared as static is know as a class variable (the others can be called instance variables). A class variable is shared by every object of that class, whereas every object has its own copy of each instance variable. So there is only one store location called count which every Student object can access - when one Student object changes it, the rest have the same change in their count variable because they are all the same thing.

In this case, the class variable count is used to keep a count of the number of Student objects created. This count is used to produce the unique student ID number, which is set to simply the count plus 10000. This is done on line 19, and then the value in count is increased by 1 using ++.

MAXMARKS on line 7 is another class variable, but as it is declared as final it is in fact a constant, that is its value may never be changed. It is used to give the size of the array to store the marks, which has to be set to some maximum.

String tokenizers

The code to read a student record on lines 58-88 of class Students shows examples of string tokenizers. String tokenizers are the way Java breaks down a string into its component parts. Java can use readLine to read a whole line or read to read a single character. However, as we have seen it does not have an easy way to read a complete integer. If we use readLine to read a whole line, followed by parseInt to convert the line of characters to an integer (see the "Simple Arithmetic" examples) we are unable to cope with cases where more than one integer is found on the same line. We got round this by using the methods in the Text class. This class uses string tokenizers, but as you haven't seen the code for it, you wouldn't have known that.

A string tokenizer is an object of type StringTokenizer. A new one can be created using new, with the StringTokenizer method taking a string as its argument. It has a number of methods, but the most important are nextToken and hasMoreTokens. If s is a string tokenizer variable, which has been set to store a string tokenizer object using

s = new StringTokenizer(str);
where str is a variable of type String, then s.nextToken() will give the substring of the string in str which starts with the first non-blank character in the string and ends with the character before the next blank character. In this context, a "blank character" includes spaces and newlines. s.nextToken() also has the side-effect of changing the string tokenizer, so that next time s.nextToken() is called it gives the second substring in string str between blank characters. Another way of thinking of it is that s is a reader, each time s.nextToken() is called it reads a word in the string str and moves on to look at the next one. Eventually a string tokenizer will reach the end of its string. The test s.hasMoreTokens() returns true if s hasn't reached the end of its string, false otherwise. An exception is thrown is s.nextToken() is called when s has reached the end of its string.

A string tokenizer is used on lines 68-75 of class Students to read the first names and surname of a student. It is needed because people may have one or more first names, and we would like to be able to cope with any number. It is assumed the names are all on one line, so we have a line of an unknown number of names, the last name being the surname. The while loop causes the last name that was read to be joined to the first names in the case where there is at least one further name to come, so the last name read using nextToken can't be the surname.

Lines 83-86 show a string tokenizer used to read the marks. Again, this is necessary because we don't know in advance how many marks there are, but we do know they are all on one line. tokens.nextToken() returns a string representing the next mark read, and Integer.parseInt converts it to an integer. The marks[numMark++]= bit stores the integer in the cell in the array marks indexed by numMarks and then increases the value in numMarks by one.

Lines 77-80 show a variant from of string tokenizer. Here, the call to create a new string tokenizer (which is put into the same variable, tokens as the previous one created on line 68) has an extra argument, "/". If a string tokenizer is created with a second argument like this, the second argument is a string consisting of all the characters which are to be treated as separators. So here, we are creating a string tokenizer where the tokens are separated by the slash character rather than spaces. This enables us to deal with dates in the day/month/year format. If the second argument on line 77 were "/-" we could have dates in the form day-month-year as well, and if it were "/- " in that form, or the previous form where nothing but spaces was between the components of dates in the file. On lines 78-80 it is assumed the dates have exactly three components. A more complete program would be able to catch an exception that is thrown if there are not three components at that point and indicate an error in the format of the date.

More Generic Code

As a further example of generic code, here is a program which uses the previously developed PeopleFilter object to filter a list of students according to their average mark:
     1  import java.io.*;
     2  
     3  class Students4
     4  {
     5  //  Read a number of Student records from a file, store them in an array
     6  //  Filter out those whose average mark is below an input figure
     7
     8     static final int MAXSTUDENTS=100;
     9     static final int MAXMARKS=30;
    10
    11     public static void main(String[] args) throws IOException
    12     {
    13      String filename;
    14      Student [] data;
    15      int count=0;
    16      double passMark;
    17      BufferedReader in = Text.open(System.in),inFile;
    18      for(;;)
    19          try
    20             {
    21              System.out.print("\nEnter file name to read from: ");
    22              filename=Text.readString(in);
    23              inFile=Text.open(filename);
    24              break;
    25             }
    26          catch(FileNotFoundException e)
    27             {
    28              System.out.println("No file of that name found");
    29             }
    30      data = new Student[MAXSTUDENTS];
    31      try
    32         {
    33          for(;;)
    34             try
    35                {
    36                 data[count++] = Student.read(inFile);
    37                }
    38             catch(PersonException e)
    39                {
    40                 System.out.println(e.getMessage());
    41                 count--;
    42                }
    43         }
    44      catch(IOException e)
    45         {
    46         }
    47      catch(ArrayIndexOutOfBoundsException e)
    48         {
    49          System.out.print("Maximum number of students ("+MAXSTUDENTS);
    50          System.out.println(") exceeded");
    51         }
    52      count--;
    53      System.out.println("\nThe records are:\n");
    54      for(int i=0;i<count;i++)
    55         System.out.println(data[i]);
    56      System.out.print("Enter pass mark: ");
    57      passMark=Text.readDouble(in);
    58      PeopleFilter myFilter = 
    59        new PeopleFilter(new StudentChooseByHighAverageMark(passMark));
    60      count=myFilter.filter(data,count);
    61      System.out.println("\nThe records of those who have passed are:\n");
    62      for(int i=0;i<count;i++)
    63         System.out.println(data[i]);
    64     }
    65
    66  }
Note, we have taken the method which reads a student from the file which provides the interface into the Student class, as a method read which takes a BufferedReader and returns a Student object obtained from reading a record from it. The filter object is created on line 59. The code for the object which does the choosing which the filter object needs to take as its argument is:
     1  class StudentChooseByHighAverageMark implements PersonChoose
     2  {
     3  double myMark;
     4
     5   public StudentChooseByHighAverageMark(double d)
     6   {
     7    myMark=d;
     8   }
     9
    10   public boolean choose(Person p)
    11   {
    12    return ((Student) p).averageMark()>myMark;
    13   }
    14
    15  }

Matthew Huntbach

These notes were produced as part of the course Introduction to Programming as it was given in the Department of Computer Science at Queen Mary, University of London during the academic years 1998-2001.

Last modified: 13 Nov 1998