CSC212 Apr10

slide version

single file version

Contents

  1. Person: An example user defined class
  2. Static vs. Non-Static Members
  3. Public vs Private Members
  4. Person class: Summary So Far
  5. Public Methods
  6. Person class with the accessor methods:
  7. Constructors
  8. Using a constructor
  9. Person class defines a type
  10. The No-Argument or Default Constructor
  11. Constructor Advice
  12. Default Constructor for Person
  13. Protecting Instance Members
  14. Example
  15. Defensive Copies
  16. Copy Constructor
  17. Making a defensive copy
  18. Dot operator
  19. Evaluating dot expressions
  20. The 'this' identifier
  21. Common Mistake
  22. Common Additional Methods
  23. Equals and the Object class
  24. Equals for the Person class
  25. toString
  26. toString method for the Person class
  27. compareTo
  28. compareTo for the Person type
  29. Unified Modeling Language
  30. Another Example: MyInteger - a wrapper class
  31. Implementation of MyInteger
  32. Example
  33. Arrays
  34. Examples
  35. Two Dimensional Arrays
  36. Length property for two dimensional arrays
  37. Array Declarations with Initialization - One Dimensional
  38. Array Declarations with Initialization - Two Dimensional

Person: An example user defined class[1] [top]

To represent a "person", in a Java program, we might want to keep track of these three items

  1. first name
  2. last name
  3. age

The first two could be represented by two Java String variables and the last one by a Java int.

That's three variables to represent one person.

By defining a Java class, Person, we can create one variable to represent a Person.

Syntax:

public class Person
{




}

Static vs. Non-Static Members[2] [top]

Each instance of the Person class will have its own first and last name, and age.

So these members should not be static. (Why not?)

Public vs Private Members[3] [top]

We must also decide whether the three data members should be directly accessible to any code that uses the Person type.

As we will begin to see, it is generally more flexible to not allow direct access to the data members.

Instead, it is a better design to allow controlled access to the data members through methods.

A member (data or method) is directly accessible by a user of the class type if it has the public qualifier.

A member is not directly accessible by a user of the class type if it has the private qualifier.

Note that a method in the Person class, does have direct access to all members of any Person variable it can reference; for example, to any Person variable passed to it as a parameter.

Person class: Summary So Far[4] [top]

public class Person
{
  private String fname;
  private String lname;
  private int age;


}

Public Methods[5] [top]

Since all the data members have been made private, public methods are needed to get and/or change the values of these private members.

We can define a public "getXXX" method to return the value of private member XXX for each different private member.

These "get" methods are examples of accessor methods. Their only purpose is to provide possibly controlled access to the value of the private data members.

The "get" methods shouldn't change the private data member, just report its current value.

On the other hand, we can define a public "setXXX" method to change the value of private member XXX if we decide this is to be allowed.

Person class with the accessor methods:[6] [top]


public class Person
{
  private String fname;
  private String lname;
  private int age;

  
  public String getFname()
  {
    return fname;
  }

  
  public String getLname()
  {
    return lname;
  }

  
  public int getAge()
  {
    return age;
  }

  
  public void setAge(int ageVal)
  {
    age = ageVal;
  }

}

Constructors[7] [top]

A constructor is a special class member whose purpose is to initialize the class data members.

A constructor syntax is almost like a method, but

  1. The name of a constructor must be the same as the class name.
  2. There must not be a return type.
  3. A constructor can not be static.

Example:

    public class Person
    {
       // Private data members
       private String fname;
       private String lname;
       private int age;

       // A Constructor
       public Person(String fnameVal, String lnameVal, int ageVal)
       {
          fname = fnameVal;
          lname = lnameVal;
          age = ageVal;
       }

       // Accessor methods as before ...
     }

Using a constructor[8] [top]

A constructor is typically invoked as part of the new operator.

It is the new operator that actually allocates storage for an instance, and immediately after that, the constructor code is invoked to initialzed the newly allocated instance.

     Person p = new Person("Fred", "Flintstone", 31);

Person class defines a type[9] [top]

The Person class doesn't need a main method, but in that case, the Person class cannot be executed.

This makes sense if you think of Person as a type.

To write a program using the Person type, we should write a different application class that uses the Person type.

Here is a very simple example:

public class PersonApp
{

  public static void printPerson(Person p)
  {
    System.out.println("First name: " + p.getFname());
    System.out.println("Last name: " + p.getLname());
    System.out.println("Age: " + p.getAge());
  }
  
  public static void main(String[] args)
  {
    Person p;
    String fval, lval;
    int a;

    Scanner infile = new Scanner(System.in);
    System.out.print("First Name: ");
    fval = infile.next();
    System.out.print("Last Name: ");
    lval = infile.next();
    System.out.print("Age: ");
    a = infile.nextInt();

    p = new Person(fval, lval, a);

    System.out.println("\nPerson created:");
    printPerson(p);
  }

}

The No-Argument or Default Constructor[10] [top]

A constructor that takes no arguments is often called the default constructor.

If you write NO constructors, the Java compiler implicitly creates one for you that initializes each data member with the default value for that data member's type.

Type Default Value
int 0
double 0.0
boolean false
char '\0' (all bits are 0's)
any class type null

For example, if no constructors were written for the Person class, then we could still create and print an instance

   Person p = new Person();

   System.out.println("First Name: " + p.getFname());
   System.out.println("Last Name: " + p.getLname());
   System.out.println("Age: " + p.getAge());

// Output
First name: null
Last name: null
Age: 0

Constructor Advice[11] [top]

One can sometimes avoid having to make special cases for null values, etc, by always providing your own default constructor rather than relying on the one the compiler may implicitly provide.

For example, this code assumes no constructors were written and relies on the compiler default constructor:

...
33   System.out.println("\nPerson created:");
34   System.out.println("First Name: " + p.getFname());
35   System.out.println("Last Name: " + p.getLname());
36   System.out.println("Age: " + p.getAge());
37
38   int lastnameLength = p.getLname().length();
...

// Output
Person created:
First name: null
Last name: null
Age: 0
Exception in thread "main" java.lang.NullPointerException
	at PersonApp.main(PersonApp.java:38)

The problem is that p.getLname() has value null. So p.getLname().length() evaluates from left to right as

 p.getLname().length() is equal to null.length()

That is, the String instance to apply the length() method to is
referenced by null. 

Using null to invoke any instance method will throw the NullPointerException since null indicates NO actual instance is being referenced.

Default Constructor for Person[12] [top]

  public Person()
  {
    fname = "";
    lname = "";
    age = 0;
  }

With this default constructor instead of the compiler supplied default, no exception is thrown for the previous example:

...
33   System.out.println("\nPerson created:");
34   System.out.println("First Name: " + p.getFname());
35   System.out.println("Last Name: " + p.getLname());
36   System.out.println("Age: " + p.getAge());
37
38   int lastnameLength = p.getLname().length();
39   System.out.println("Last Name Length: " + lastnameLength);

// Output
Person created:
First name: 
Last name: 
Age: 0
Last Name Length: 0

Protecting Instance Members[13] [top]

Sometimes it is important to be able to make copies of instances.

For example, you might want to provide a getXXX method to allow users of a class to access some private member, but you don't want them to be able to change that member.

To accomplish this goal you could simply not provide a setXXX method.

However, that may not be enough if the member is of class type.

Example[14] [top]


public class Project
{
  private Person leader;
  private int projNumber;
  public Project(Person p, int proj) {
    leader = p;
    projNumber = proj;
  }
  public Person getLeader() {
    return leader;
  }

  public int getProjNumber() {
    return projNumber;
  }

}

Since there is no setLeader(Person newLeader) method, it might appear at first that a Project leader member can't be changed after the Project instance is created.

However, the following code creates a Project and then makes the leader be 3 years younger.


    1	  public static void main(String[] args)
    2	  {
    3	    Project prj = new Project(new Person("Tristram", "Shandy", 30),  101);
    4	    Person p;
    5	
    6	    p = prj.getLeader();
    7	    p.setAge(27);
    8	  }

Note: At line 7, p is a reference to prj's leader member.
and so that leader's age is changed to be 27.

Defensive Copies[15] [top]

So if omitting setXXX accessors doesn't prevent changes to data members, how do we prevent changes while providing getXXX access?

One way is to return a copy of the member rather than the member itself.

That is, the getXXX method can create a new copy of the data member and return the copy.

Copy Constructor[16] [top]

Creating a copy of a class type is easy if the class has a copy constructor.

A copy constructor for a class X is just a constructor whose parameter is also of the same type X.

Here is an example of a copy constructor for the Person class:


  public Person(Person other)
  {
    this.fname = other.fname;
    this.lname = other.lname;
    this.age = other.age;
  }

Making a defensive copy[17] [top]

Once we have the copy constructor for the Person class, the getLeader method can easily make a defensive copy:


public class Project
{
  private Person leader;
  ...
  public Person getLeader() {
     return leader;
  }


  private Person leader;
  ...
  public Person getLeader() {
    Person copy = new Person(leader);
    return copy;
  }

Now if an attempt is made to change the age of the leader:

    Project prj = new Project(new Person("Tristram", "Shandy", 30),
    101);
    Person p = prj.getLeader();
    p.setAge(27);

The Person instance returned by getLeader() is only a copy of the leader and so only the copy has the age changed. The age of the leader member of the Project instance is not changed.

Dot operator[18] [top]

Recall that an instance member (method or data member) is accessed using the dot operator with this syntax:

   instance.member

An expression can have more than one occurrence of the dot. The Person class example illustrates this:

   Person p = new Person("Barney", "Rubble", 30);

   int n = p.getLname().length();

Evaluating dot expressions[19] [top]

The dot operators should be evaluated left to right. At each evaluation the left operand of the dot should be a class instance and the right operand should be a member of that class.

    p.getLname().length()

    Left-most dot:
    p.getLname() is evaluated first
    p is an instance of Person; getLname() is a method member of Person
    Result is an instance of String with value "Barney"
 
    Second dot:

    p.getLname().length() = "Barney".length()

    "Barney".length() is evaluated second
    "Barney" is an instance of String; length() is a method member of String
    Result is the int value 6

   Note: There can't be another dot: p.getLname().length().____
   since the expression to the left of the additional dot is an int
   rather than an instance of a class.

The 'this' identifier[20] [top]

An instance method of a class can only be called using an actual instance and the dot operator.

So how does a method in a class know which instance was used to call it?

Answer:

Every instance method of any class X automatically has a variable
named 'this' of type X which is a reference to the instance used to
call the method.

For example, the accessor methods (get and set) of the Person class refer to the instance data members and so should in priniciple use the dot notation.

Here is the getAge() method which returns the age instance member:

  public int getAge()
  {
    return this.age;  
  }

Note: this.age can be abbreviated to simply
age. The compiler will check that age is a data member and
implicitly interpret age as this.age.

Common Mistake[21] [top]

Don't redeclare class instance members locally inside methods; the method can already access class data members.



    1	public class Person
    2	{
    3	  private String fname;
    4	  private String lname;
    5	  private int age;
    6	
    7	  public void setAge(int ageVal)
    8	  {
    9	    int age;  // Redeclaration here!
   10	    age = ageVal;
   11	  }
   12	
   13	}

At line 10, the compiler sees 'age' and tries to match it with a
declaration. The closest one is at line 9. 

So the local variable 'age' at line 9 is updated while the class
instance  member 'age' at line 5 is not changed.

Furthermore, as soon as setAge returns, the local variable 'age' at
line 9 is deallocated and its value is lost.

The correction is simply to not declare the local variable 'age':

    1	public class Person
    2	{
    3	  private String fname;
    4	  private String lname;
    5	  private int age;
    6	
    7	  public void setAge(int ageVal)
    8	  {
    9	    age = ageVal;
   10	  }
   11	
   12	}

Now: the compiler looks for a declaration of 'age' at line 9
and doesn't find a local declaration. So the compiler looks at the
class members and finds 'age' at line 5 and interprets the occurrence
of 'age' at line 9 to correspond to the class member.

Common Additional Methods[22] [top]

Most classes will also need to write these methods

  1. boolean equals(Object obj)
  2. String toString()

In addition, if there is some natural ordering of instances of a class X, the class will also likely benefit from implementing this method:

Equals and the Object class[23] [top]

The equals method for a class X allows you to compare instances of X with other instances of X for equality, but also with instances of other classes as well.

Of course, if the other instance is not an instance of X, equals should return false.

The parameter type for equals is Object.

A variable of type Object can reference any class instance.

So how do you tell whether the Object parameter passed to equals is an instance of Person?

Answer: use the instanceof operator.

Equals for the Person class[24] [top]


    1	  public boolean equals(Object obj)
    2	  {
    3	    if ( obj == this ) return true;
    4	    
    5	    if ( obj instanceof Person ) {
    6	      Person other = (Person) obj;
    7	      return other.getLname().equals(this.getLname()) &&
    8		     other.getFname().equals(this.getFname());
    9	    } else {
   10	      return false;
   11	    }
   12	  }

Notes:
Line 3: If obj and this are referencing the same instance, then equals
should be true

Line 5: The left operand of instanceof should be an instance
and the right operand should be a class name. The result is true if 
the referenced instance is an instance of the class; otherwise false.

Line 6: It is necessary to declare a Person variable and assign obj to
it with a cast so that the first and last names can be accessed. By
line 5, we know that obj will reference a Person, but the compiler
will still complain if we write obj.getLname(). The check
with the instanceof operator guarantees that the cast on line 6 will
succeed; that is, if line 6 is reached, the obj really does reference
a Person instance.

toString[25] [top]

The toString() method is used implicitly whenever you use System.out.print, System.out.println, or System.out.printf("%s", ...) to try to print a class instance. E.g.

  Person p = new Person("Dino", "Flintstone", 2);

  System.out.println(p);

Note: The print statement is equivalent to

  System.out.println(p.toString());

The String static method, format, is useful for writing toString() methods.

toString method for the Person class[26] [top]

Here is one possible implementation of toString() for the Person class:

  public String toString()
  {
    return String.format("First Name: %s, Last Name: %s, Age: %d",
			 fname, lname, age);
  }

compareTo[27] [top]

There are several possible ways to order instances of the Person class:

Just as you shouldn't use == to test for equality of class types, you shouldn't use <, or <=, or >, etc., to compare class instances.

Instead, a method should be used: compareTo.

Actually, compareTo, should be the name of a method that corresponds to the most natural ordering on the class instances if there are several possible orderings.

The meaning of compareTo should be

x.compareTo(y) return value Meaning of the order of x and y
negative value x is "smaller than" or "comes before" y
0 x is "equal" to y
positive value x is "greater than" or "comes after" y

compareTo for the Person type[28] [top]

We will write a compareTo for the following ordering on instances of Person.

A Person p will come before Person q if p's last name comes alphabetically before q's. Or if the last names are the same, then p will come before q if p's first name comes before q's first name.

We will take advantage of the fact that the String class already defines its own compareTo method for Strings that follows the previous table description for compareTo methods.

  public int compareTo(Person other)
  {
    if ( getLname().equals(other.getLname()) ) {
      return getFname().compareTo(other.getFname());
    } else {
      return getLname().compareTo(other.getLname());
    }
  }

Unified Modeling Language[29] [top]

UML includes several graphical notations for specifying classes and their members.

It also has notation for relationships that may exist among different classes.

Here is a UML class diagram for the Person class

Person
- fname: String
- lname: String
- age: int
+ Person()
+ Person(String first, String last, int ag)
+ getFname: String
+ getLname: String
+ getAge: int
+ setFname(String first)
+ setLname(String last)
+ setAge(int ag)
+ toString: String
+ equals(Object obj): boolean
+ compareTo(Person p): int

Notes:

  1. The top line is the name of the class.
  2. The second section is for the data members.
  3. The third section is for the methods.
  4. A '-' before a member means private
  5. A '+' before a member means public
  6. A '#' before a member means protected (discussed later)
  7. A '$" before a member means static.
  8. A ':' after a method indicates the return type
  9. If no ':' appears after a method, it has void return type.

Another Example: MyInteger - a wrapper class[30] [top]

Java already defines wrapper classes including:

  1. Integer
  2. Double
  3. Boolean

The purpose of these classes is to provide a class type that can be used in place of the basic non class types: int, double, boolean, etc, when a class type is required..

It isn't hard to write one of these classes, and for practice with the uml notation, here is a class diagram for a class MyInteger. It is so named to distinguish it from the built in Java Integer class, but it is intended to be a similar wrapper class.

MyInteger
- value: int
+ MyInteger(int n)
$+ parseInt(String s): int
+ intValue(): int
+ equals: boolean
+ compareTo(MyInteger other): int

Implementation of MyInteger[31] [top]

public class MyInteger
{
  private int value;

  public MyInteger(int n) {
    value = n;
  }
  
  public int intValue() {
    return value;
  }
  
  public static int parseInt(String s)
  throws NumberFormatException
  {
    return Integer.parseInt(s);
  }

  public boolean equals(Object obj) {
    if ( this == obj) return true;

    if ( obj instanceof MyInteger ) {
      MyInteger other = (MyIntger) obj;
      return other.value == this.value;
    } else {
      return false;
    }
  }

  public int compareTo(MyInteger m) {
    return this.value - m.value;
  }

  public String toString() {
    return String.format("%d", value);
  }

}

Example[32] [top]

The Java Vector class is similar to an array, but it grows automatically.

However, you can only put class types into a Vector, not basic types like int. That means Vector<int> is not allowed, but we can use Vector<MyInteger> as a replacement. This is where wrapper classes are useful.

    1	import java.util.Vector;
    2	
    3	public class MyIntegerApp
    4	{
    5	  
    6	  public static void main(String[] args)
    7	  {
    8	    Vector<MyInteger> v = new Vector<MyInteger>();
    9	
   10	    for(int i = 0; i < 10; i++) {
   11	      v.add(new MyInteger(i+1));
   12	    }
   13	
   14	    MyInteger n;
   15	    for(int k = 0; k < v.size(); k++) {
   16	      n = v.get(k);
   17	      System.out.println(n);
   18	    }
   19	  }
   20	
   21	}

// Output
1
2
3
4
5
6
7
8
9
10

Arrays[33] [top]

Arrays are not basic types in Java. In fact, arrays are class types.

Like any class type, an array must be created using the new operator.

Syntax:

    elementType[] variable
    variable = new elementType[integerExpression]

Here are some examples:

    int[] a = new int[10];
    double[] b = new double[15];
    String[] c = new String[20];

    a.length is 10
    b.length is 15
    c.length is 20

Note that the syntax is the same regardless of whether the element type is a basic type or a class type.

An array has a length property (instance member) whose value is the maximum number of elements that can be stored in the array.

Note that the length does not indicate how many elements have actually been stored in the array.

Examples[34] [top]

  1. Reading Person data from a file and storing in an array
  2. Searching the array to finding the age of a person, given the name.

Two Dimensional Arrays[35] [top]

Consider the char data arranged in 5 rows and 6 columns:

     p  y  r  e  d  n
     i  b  a  c  i  e
     n  o  l  w  n  e
     k  l  y  u  g  r
     w  h  i  t  e  g

This two dimensional array contains the words:

red, pink, blue, green, and white

somewhere in the rows, columns, and/or diagonally, but possibly with the characters in reverse order.

How could we declare a two dimensional array, letters, to hold these characters?



    char[][] letters;

    letters = new char[5][6];

or

    char[][] letters;

    letters = new char[5][];
    for(int r = 0; r < 5; c++) {
       letters[r] = new char[6];
    }

If only one subscript is used on letters, it indicates the entire row, which is itself an array.

Length property for two dimensional arrays[36] [top]

 char[][] letters = new char[5][6];

 Then,

 letters.length = 5
 letters[0].length = 6
 letters[1].length = 6
 letters[2].length = 6
 letters[3].length = 6
 letters[4].length = 6

The rationale for this is that a two dimensional array with 5 rows and 6 columns is represented in Java as a one dimensional array of length 5, but each element is another 1 dimensional array of length 6.

Array Declarations with Initialization - One Dimensional[37] [top]

For a one dimensional array, we can declare it and initialize it at the same time.

Examples:

   int[] ia = {10,5,3,9};  // ia.length is 4, ia[0] is 10
   String[] sa = {"cat", "pig", "dog"};  // sa.length is 3, sa[2] is "dog"

Array Declarations with Initialization - Two Dimensional[38] [top]

Two dimensional arrays can be declared with an initialization in much the same way.

    int[][] d = {{1,2,3},{4,5,6},{7,8,9},{10,11,12}};

    // d.length is 4 (4 rows)
    // d[0].length = d[1].length = ... = d[3].length = 3 (each row has
    length 3)

    // Print the elements of d in 4 rows and 3 columns
    for(int r = 0; r < d.length; r++) {  // d.length is 4
       for(int c = 0; c < d[r].length; c++) { // note d[r].length == 3
         System.out.printf("%4d", d[r][c]);
       }
       System.out.println();
    }

// Output is
   1   2   3
   4   5   6
   7   8   9
  10  11  12