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/peopleAs 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.
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.
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.
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 }
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