15-100 Lecture 15 (Wednesday, October 5, 2005)

Today's Quiz

  1. What is a class? Please establish the role of a class specification in a program.

    A class is a type. A class specification is a document that describes the properties of a type of object, including its behaviors and other attributes. It includes instance variable specifications, method specifications, constructor specifications, &c.

  2. What is a constructor? Please establish its role within a Java program.

    A constructor is a description of how a newly created instance of a class, a.k.a., a newly created object, should be initialized. It should, for example, initialize all instance variables.

  3. Will the program below compile? If not, why not? If so, what is its output?

    It compiles. The output isn't necessarily intuitive. It prints 0, then 0, then 10. The setValue() method is basically moot because it declares a local variable, widget, and changes it. It does not change the instance variable widget (this.widget). Local variables, sometimes known as automatic variables, are created and destroyed with each method call. As a consequence, their scope is within the method and limited ot a single activation. Given an instance variable and a local variable with the same name, the local variable will hide the instance variable unless the instance variable is fully qualified, e.g., this.widget.

    class Thingy {
    
        int widget;
    
    
        public Thingy() {
          widget = 0;
        }
    
    
        public void setValue (int value) {
          int widget; 
     
          widget = value;
        }
    
    
        public void changeValue(int value) {
          widget = value;
        }
    
        
        public int getValue() {
          return widget;
        }
    
    
        public static void main (String[] args) {
          
          Thingy thingy = new Thingy();
          System.out.println (thingy.getValue());
    
          thingy.setValue(5);
          System.out.println (thingy.getValue());
    
          thingy.changeValue(10);
          System.out.println (thingy.getValue()); 
    
        }
    
    }
    
    A Student Class

    Today we want to write an object class to represent a student. This is so a universtiy can keep track of the student's academic record. The first thing we want to do inside the class is include the fields; the basic attributes of a student that we will store in our Student object. We define these attributes by specifying them as private (able to be accessed only by the Student class) and then giving them a type (String, int, etc.) and a variable name (major, year, etc.).

    
           class Student {
                   private String name; //"Last, First M", e.g. "Kesden, Gregory M"
                   private String major;
                   private String year;  //Freshman, Sophmore, Junior, Senior, Senior-plus, Masters, Doctoral, Other
                   private int units;
                   private double qpa;
    
    	       ....methods go here....
    	}
      

    Now we need a constructor that will allow instances of this Student object to be created. To create a student, we need the user to tell us the student's name and his/her major, both of which are type String. We can assume for now that if we're creating a student and know only this information, the student is a freshman. Therefore, we can assign values to all the class variables we created above inside the constructor, either by assigning them with values passed in as arguments (inside the parentheses) or assigning them with hard-coded values based on the freshman assumption:

    
           // Constructor for new freshman
           public Student (String name, String major)
           {
                   this.name = name;
                   this.major = major;
                   year = "Freshman";
                   units = 0;
                   qpa = 0.0f;
           }
      

    Java allows us to have multiple constructors for the same class. Java will use the constructor where the variables passed into the constructor match up with the argument list (ie the type of the variable and the order in which the variables are listed matter). We can demonstrate this by creating a second constructor that allows us to create a Student for whom we also know their year, their units and their QPA. In other words, a transfer student to this university. In this case, we are given more information as arguments to the constructor, and we can again assign these values to our class variables using the "this" operator to specify the class variables.

    
          // Constructor for transfer student
          public Student (String name, String year, String major, double qpa, int units) {
                  this.name = name;
                  this.major = major;
                  this.year = year;
                  this.units = units;
                  this.qpa = qpa;
           }
           public String toString(){
                 return name+"; "+year+"; "+major+"; "+qpa+"; "+units+"/n";
             // the "/" in the String above is a way to break the quotes and
    	 // perform a special function inside of a String. "/n" creates
             // a new line in the String for example, and the "/n" will not
    	 // be printed with the rest of the statement.
           }
      

    We need to create methods that allow us to update/change Students once they are created. These are called "mutator" methods because they change the class variables that we defined at the top of the Student class. Because we made the fields at the top of the class private, we need these mutator methods so users of this code can alter the Students once they are created. The mutator methods below allow us to update a Student given a new semester's QPA and units earned (the class qpa and units fields need to be updated), given a change in the Student's major, or a change in their year in school.

    
           public void addSemester (float semesterQpa, int semesterUnits) {
                   qpa = ((qpa*units) + (semesterQpa * semesterUnits)) /
                         (units+semesterUnits);
                   // The syntax below means: units = units + semesterUnits
                   units += semesterUnits;
           }
    
           public void changeMajor (String newMajor) {
                   major = newMajor;
           }
    
           public void updateYear(String newYear) {
                   year = newYear;
           }
      

    There is one more method that we need to add to this class in order to be able to run and test it using the Tester class (included at the bottom of this page). If you scroll down and look at the StudentTester class, you will notice that we are using a toString() method within our Student class.

    The toString() Method

    If we were to NOT write this toString() method within our Student class, the Java compiler would successfully compile and run our code. The reason for this is that all classes in Java are assumed to extend the Object class, which is built into Java's API (the library of classes and methods that comes standard with Java and that the compiler automatically knows about). In brief, because our class is implicitly an extension of Object, it inherits a working method called toString().

    However, because we have not specialized the toString() method to fit the details of our own Student class, the toString() method will not print anything useful (it will print a barely intelligible collection of characters and numbers--which is the defination of the toString() method that it inherited). We can override the toString() method in Object by adding our own toString() method to our Student class. The compiler will know to use ours instead of the inherited toString() method.

    
           public String toString() {
                   String retString = "";
    
                   retString = retString + name;
                   retString = retString + ", " + major;
                   retString = retString + ", " + year;
                   retString = retString + ", " + units;
                   retString = retString + ", " + qpa;
                   
                   return retString;
           }
    
    ...........................................................................
    
    
           
    			 
           // STUDENT TESTER CLASS
    
           class StudentTester {
               public static void main (String [] args) {
                   Student greg = new Student ("Kesden, Gregory M", Senior, Computer Science, 4.00, 100)
                   Student newStudent = new Student("Blogg, Joe T", "Basket Weaving, Underwater");
    
                   // The System.out.println() method implicitly calls the toString()
                   // method of whatever type is passed in as an argument
                   System.out.println(greg);
                   System.out.println(newStudent);
    											 
                   //let's test the mutator methods
                   greg.addSemester(100, 2.0)
                   greg.updateYear(Senior++);
    											 
                   //print out the updated greg info
                   System.out.println(greg);
               }       
           }
      

    It should print:

         Kesden, Gregory M; Senior; Computer Science; 4.00; 100
         Blogg, Joe T; Fresman; Basket Weaving, Underwater; 0; 0
         Kesden, Gregory M; Senior++; Computer Science;3.00; 200

    More About Inheritence

    So what do you think of when you hear the word inheritance? Money that you receive from your parents as a birthright, maybe your genetic code. Inheritance exists in Java too.

    When a class is designed as a subtype of another class, by "extending" the "base" or "parent" class, it inherits all of the parent class's methods.

    All classes are a subclass of the class Object. This provides a uniform interface across all Objects and thus all classes. When you create a class it automatically inherits several methods from the object class.

    Let's look at two methods in Object:

    
      class Object{
         public String toString(){
           //magic performed here
         }
    
         public boolean equals(Object o){
          //more magic performed here
         }
      }
      

    Now these two inherited methods aren't too useful in the grand scheme of things. The inherited equals() performs the == operation until you override the method by writing your own version of it in the subclass you are working on. (Don't remember what == does? Look below for a refresher).

    The toString() is equally as useful. The inherited toString prints out the name of the subclass and then a series of numbers and letters which tell where the reference variable, holding the instance of the class you want to go toString(), is pointing (aka where the object is being stored in the computer's memory). More commonly, it is appropriate for the toString to provided critical information about our object's state. (This usually requires the method to return a String containing most of the information that the object's instance variables are holding.)

    == and .equals()

    There's a big difference between == and equals(). A programmer needs to know which comparison to use and when. The == always does the same job. It looks at the values that two variables are holding and returns a boolean saying whether the two values are equal. This works well enough for the primitive types (such as int, double, char, long, etc.) since a variable for a primitive holds the primitive's value. However when you start using the == operator with Objects (such as Strings) you need to remember that a variable "holding" a String doesn't actually hold the String's value. Instead they hold a reference to the String and where it is in the computer's memory.

    For this reason the line ("Hi"=="Hi"); would evaluate to false. That line makes two instances of a String that says "Hi". Even thought the state of the Strings are the same the instances are different and thus == will say that these two Strings aren't the same String.

    So how do you compare two Strings to see if they have the same state or same words "inside" of them? That is what the .equals() is for. It looks at the Object's state and says whether the two states are the same.

    Since the inherited .equals() for Objects uses the == comparison you can see why it is useful to always redefine this inherited method.