Objects 2

The "this" reference variable introduced

I am jumping ahead in the textbook to section 8.8 to introduce the this keyword. The keyword this, within a class refers to the object itself. Let's consider the Student class we developed back in Objects. Recall the steps in instantiating an object:
Student student = new Student(...);
First the object is created and student is set to a reference. Then a constructor code is applied. At the initial point of creation, Java makes available the keyword this to reference the student object
   this 
ref
student
ref
 
(name) student
ref
 
(id) student
ref
 
(gpa)
In particular this can be used within the constructor code. We are particularly interested in its usage to avoid shadowing of a data variable by a parameter. Our UML diagram reflects the fact that we now want to use the more obvious parameter names in the constructors and setters:

Student

- String name 
- int id
- double gpa

+ Student(String name, int id, double gpa)

+ void setName(String name)
+ void setId(int id)
+ void setGpa(double gpa)
+ String getName()
+ int getId()
+ double getGpa()

Recall that we cannot write
  public void setGpa(double gpa) {
    gpa = gpa;
  }
The parameter is "shadowing" the data member. This is why we introduced some artificial parameter name so that the two are separated like:
  public void setGpa(double _gpa) {
    gpa = _gpa;
  }
The this keyword allows us to reference the data member relative as this.gpa, and so we get the improved version:
  public void setGpa(double gpa) {
    this.gpa = gpa;
  }
The outcome is an improvement because we do not need to create an artificial name for the parameter to avoid shadowing. We also want to use this approach for constructor definitions.

Here is the full rewrite of the class. We will rename the class AStudent to avoid conflict with the previous version:

AStudent
public class AStudent {
  private String name;
  private int id;
  private double gpa;
 
  public AStudent(String name, int id, double gpa) {
    this.name = name;
    this.id = id;
    this.gpa = gpa;
  }
 
  public void setName(String name) {
    this.name = name;
  }
 
  public void setId(int id) {
    this.id = id;
  }
 
  public void setGpa(double gpa) {
    this.gpa = gpa;
  }
 
  // no changes for the getters
 
  public String getName() { 
    return name;
  }
 
  public int getId() { 
    return id;
  }
 
  public double getGpa() { 
    return gpa;
  }
}
select
As a simple proof of concept, run this Main test program:

PrintAStudent
public class PrintAStudent {
 
  public static void main(String[] args) {
    AStudent astudent = new AStudent("Carol Miller", 1111, 3.1);
    print(astudent);
    astudent.setGpa(2.9);
    print(astudent);
  }
 
  public static void print(AStudent student) {
    System.out.printf("student: %s, %s, %s\n", 
        student.getName(), student.getId(), student.getGpa());
  }
}
We now have a new way of writing member functions, primarily constructors and setters, whenever the parameter refers to the data member

Static Class Members

This is section 8.1 in the textbook. Important features about static member usage, at this point not present, have been added.

As it says in the textbook, a static class member belongs to the class, not objects instantiated from the class. Both member functions and data members can be static. In fact, every member function we have worked with in the main class has been static. For example:

StaticTest
public class StaticTest {
 
  private static int statVar = 10;
 
  private int dynVar = 15;
 
  public static void main(String[] args) {
 
    System.out.println("statVar = " + statVar);
 
    statVar += 10;
 
//    dynVar += 10;
 
    member();
  }
 
  public static void member() {
    System.out.println("statVar = " + statVar);
//    System.out.println("dynVar = " + dynVar);
  }
}
select
Used in this way, it gives the opportunity to share a variable among member functions. Try altering the "static" usage in the declarations of member() and statVar.

Also try uncommenting the commented lines using dynVar to confirm that a static member function can only reference local variables, parameters and static data member variables.

We also have the ability to use static in other classes, for example:

StaticClass (initial)
public class StaticClass {
 
  private int dynData;
  private static int statData;
 
  public void setDynData(int dynData) {
    this.dynData = dynData;
  }
 
  public void setStatData(int statData) {
    this.statData = statData;
  }
 
  public int getDynData() { 
    return dynData;
  }
 
  public int getStatData() {
    return statData;
  }
}
select
We can access static data fields just like non-static ones. However, NetBeans will flag this line:
this.statData = statData;
The indication is that the usage is a mismatch of sorts in that this.statData suggests that statData belongs to the object. Here is a test program which verifies that static data fields belong to the class:

TestStaticClass (initial)
public class TestStaticClass {
 
  public static void main(String[] args) {
    StaticClass obj1 = new StaticClass();
    StaticClass obj2 = new StaticClass();
 
    obj1.setDynData(1);
    obj2.setDynData(2);
 
    obj1.setStatData(11);
    obj2.setStatData(12);
 
    System.out.println("obj1.getDynData() = " + obj1.getDynData());
    System.out.println("obj1.getDynData() = " + obj2.getDynData());
 
    System.out.println("obj1.getStatData() = " + obj1.getStatData());
    System.out.println("obj2.getStatData() = " + obj2.getStatData());
  }
 
}
select
When you run this test program, you get the following, perhaps unexpected, output:
obj1.getDynData() = 1
obj1.getDynData() = 2
obj1.getStatData() = 12
obj2.getStatData() = 12
What is verified by the output is that the dynamic member data belongs to the object, but the static member data is shared by all objects of that class.

Reference to static through class name

We need to address the fact that, although correct, the usage:
this.statData
does not give the true story. In fact Java uses the notation:
ClassName.staticMember
to reference static member data, or more importantly, member functions. We start by rewriting the class as this:

StaticClass (final)
public class StaticClass {
 
  private int dynData;
  private static int statData;
 
  public void setDynData(int dynData) {
    this.dynData = dynData;
  }
 
  public static void setStatData(int statData) {
    StaticClass.statData = statData;
  }
 
  public int getDynData() { 
    return dynData;
  }
 
  public static int getStatData() {
    return statData;
  }
}
select
The 3 changes we made give a more accurate reflection of the class. In particular, the getter and setter for statData are declared static, and the parameter shadowing in prevented in the setter by using
StaticClass.statData = statData
Once the setter has been declared static, we can no longer use:
this.statData
Here's the UML diagram of our corrected class version:

StaticClass

- int dynData
- static int statData

+ void setDynData(int)
+ static void setStatData(int)
+ int getDynData()
+ static int getStatData()

Unfortunately, the author does not indicate how one should represent static members in a UML diagram, so I am making up what seems to be a reasonable way to introduce the static keyword.

Now let's fix the test program. If you take a look in NetBeans, you'll see that the usages of setStatData and getStatData are now flagged. They are technically correct, but are more correctly stated by replacing the object with the class name.

TestStaticClass (final)
public class TestStaticClass {
 
  public static void main(String[] args) {
    StaticClass obj1 = new StaticClass();
    StaticClass obj2 = new StaticClass();
 
    obj1.setDynData(1);
    obj2.setDynData(2);
 
    StaticClass.setStatData(11);
    StaticClass.setStatData(12);
 
    System.out.println("obj1.getDynData() = " + obj1.getDynData());
    System.out.println("obj1.getDynData() = " + obj2.getDynData());
 
    System.out.println(
            "StaticClass.getStatData() = " + StaticClass.getStatData());
    System.out.println(
            "StaticClass.getStatData() = " + StaticClass.getStatData());
  }
 
}
With these changes, the program output makes much more sense since it becomes obvious that the getter and setter for statData are about with the class itself, not about the instantiated objects.

Textbook Example

Countable.java  
StaticDemo.java  
This demo program is similar to ours. The author is trying to prove that the static instanceCount member in the Countable class is not tied to the objects. To "correct" this program:
  1. Change the declaration of getInstanceCount in Countable.java:
    public static int getInstanceCount()
    
  2. In StaticDemo.java, replace
    objectCount = object1.getInstanceCount();
    
    by
    objectCount = Countable.getInstanceCount();
    

Static methods without static data members

Let us consider some very common usages of common Java methods which we can now identify as being static:
int i = Integer.parseInt("15");
double d = Double.parseDouble("1.35");
double r = Math.sqrt(2);
When prefaced with the class name, the function is identified as a public static method within the class, e.g.,
public class Integer {
  public static int parseInt(String str) { ... }
  ...
}
Unlike the getter and setter methods of the previous examples, these functions are "self-contained," referencing only the incoming parameters. This means they act like stand-alone functions which are grouped into classes.

Seeing available static functions in NetBeans

You can see all the available static functions within a class easily in NetBeans. Just start keying in the class name within any function, say:
Math.
Pause after typing dot and you'll get a listing of all available static member functions.

Textbook Example

Metric.java  
MetricDemo.java  
This is the same idea as other Java static "parameter-only" member functions. The Metric class offers two choices:
double Metric.milesToKilometers(double)
double Metric.kilometersToMiles(double)
The test program inputs two double values: miles and kilos through a JOptionPane and then prints:
Metric.milesToKilometers(miles)
Metric.kilometersToMiles(kilos)

Using parseInt and parseDouble to read numbers from a keyboard

Consider this simple scenario: I want to read 4 lines keyed in consisting of:
a string with optional internal spaces
a double
another string
an int

ParseReads (wrong 1)
import java.util.Scanner;
 
public class ParseReads {
 
  public static void main(String[] args) {
    Scanner keyboard = new Scanner(System.in);
 
    String str1 = keyboard.next();
    System.out.println("str1 = " + str1);
 
    double dnum = keyboard.nextDouble();
    System.out.println("dnum = " + dnum);
 
    String str2 = keyboard.next();
    System.out.println("str2 = " + str2);
 
    int inum = keyboard.nextInt();
    System.out.println("inum = " + inum);
  }
}
All is OK if you give it the input:
xx
33.2
yy
44
But try this to observe the failure:
a string
33.2
another string
44
The keyboard.next() is not what you want, it stops at the internal blank.

Next try reading the full line, changing keyboard.next() to keyboard.nextLine().

ParseReads (wrong 2)
import java.util.Scanner;
 
public class ParseReads {
 
  public static void main(String[] args) {
    Scanner keyboard = new Scanner(System.in);
 
    String str1 = keyboard.nextLine();
    System.out.println("str1 = " + str1);
 
    double dnum = keyboard.nextDouble();
    System.out.println("dnum = " + dnum);
 
    String str2 = keyboard.nextLine();
    System.out.println("str2 = " + str2);
 
    int inum = keyboard.nextInt();
    System.out.println("inum = " + inum);
  }
}
This fails after reading the double because the second keyboard.nextLine() starts "prematurely" after reading the double and reads to the end of the line, getting empty.

The fix involves reading every line with keyboard.nextLine() and converting the numeric lines to numbers via the Integer.parseInt and Double.parseDouble static functions:

ParseReads (fixed)
import java.util.Scanner;
 
public class ParseReads {
 
  public static void main(String[] args) {
    Scanner keyboard = new Scanner(System.in);
 
    String str1 = keyboard.nextLine().trim();
    System.out.println("str1 = " + str1);
 
    String dnumStr = keyboard.nextLine().trim();
    double dnum = Double.parseDouble(dnumStr);
    System.out.println("dnum = " + dnum);
 
    String str2 = keyboard.nextLine().trim();
    System.out.println("str2 = " + str2);
 
    String inumStr = keyboard.nextLine().trim();
    int inum = Integer.parseInt(inumStr);
    System.out.println("inum = " + inum);
  }
}
Note that we have used this to remove whitespace before and after the desired input:
keyboard.nextLine().trim();

Other useful Java static methods

There are many, many useful Java static methods. We mention several here relating to arrays or ArrayLists:
void Arrays.sort(someArray)
int Arrays.binarySearch(someArray, key)
String Arrays.toString(someArray)
void Collections.sort(someArrayList)
int Collections.binarySearch(someArrayList, key)
These functions are available for arrays and ArrayLists of all standard types. The versions given for arrays assume the arrays are full; there are variants for partially-filled arrays, but we will not discuss them here.

Here are two demo programs:

StaticArrayMethods
import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;
 
public class StaticArrayMethods {
 
  public static void main(String[] args) {
    Random rand = new Random();
    Scanner keyboard = new Scanner(System.in);
 
    int[] numarray = new int[15];
    for (int i = 0; i < numarray.length; i++) {
      numarray[i] = rand.nextInt(100) + 1;
    }
 
    System.out.println("array:  " + Arrays.toString(numarray));
 
    Arrays.sort(numarray);
 
    System.out.println("sorted: " + Arrays.toString(numarray));
 
    System.out.println();
 
    System.out.print("Enter number to search for: ");
    int key = keyboard.nextInt();
 
    int pos = Arrays.binarySearch(numarray, key);
 
    if (pos < 0) {
      System.out.printf("%d not found\n", key);
    }
    else {
      System.out.printf("%d found at position %d\n", key, pos);
    }
  }
}
select

StaticArrayListMethods
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import java.util.Scanner;
 
public class StaticArrayListMethods {
 
  public static void main(String[] args) {
    Random rand = new Random();
    Scanner keyboard = new Scanner(System.in);
 
    ArrayList<Integer> numlist = new ArrayList<>();
    for (int i = 0; i < 15; i++) {
      numlist.add(rand.nextInt(100) + 1);
    }
 
    System.out.println("list:   " + numlist);
 
    Collections.sort(numlist);
 
    System.out.println("sorted: " + numlist);
 
    System.out.println();
 
    System.out.print("Enter number to search for: ");
    int key = keyboard.nextInt();
 
    int pos = Collections.binarySearch(numlist, key);
 
    if (pos < 0) {
      System.out.printf("%d not found\n", key);
    }
    else {
      System.out.printf("%d found at position %d\n", key, pos);
    }
  }
}
select

Static Constants

We can use the keyword final along with static to make a class-based constant:
public class MyClass {
  [public|private] static final VAR = VALUE;
  ...
}

private static constant

A private static constant is a value meant to be shared among the static member functions of a class, and possibly altered in one place only. These are particularly useful in the main class, since the main function itself is static, and support functions are also typically static.

Private static constants are differentiated from the type of constants you've had up to now, which may be called local constants:
public class MyClass {
  public static void main(String[] args) {
    final int MAX = 100;
    ...
  }
}
In particular, this MAX cannot be directly shared across multiple static member functions. Here is a simple example in which we intend to use MAX in both the main and report member functions and so we pass it to the report based on what we know now:

InRange (starter)
import java.util.Scanner;
 
public class InRange {
 
  public static void main(String[] args) {
    final int MAX = 100;
 
    Scanner keyboard = new Scanner(System.in);
    System.out.printf("enter an int between 1 and %s: ", MAX);
    int value = keyboard.nextInt();
 
    report(value, MAX);
  }
 
  private static void report(int value, int MAX) {
    if (value < 1 || value > MAX) {
      System.out.println ("number entered not in range");
    }
    else {
      System.out.println("OK");
    }
  }
 
}
select
If we attempt to share it by making MAX a private data member and removing the parameter usage, it is an error:
import java.util.Scanner;
 
public class InRange {
 
  private final int MAX = 100;
 
  public static void main(String[] args) {
 
    Scanner keyboard = new Scanner(System.in);
    System.out.printf("enter an int between 1 and %s: ", MAX);
    int value = keyboard.nextInt();
 
    report(value); // remove second argument
  }
 
  private static void report(int value) { // remove second parameter
    if (value < 1 || value > MAX) {
      System.out.println ("number entered not in range");
    }
    else {
      System.out.println("OK");
    }
  }
}
The fix is simply to add static to the declaration of MAX.

InRange (with shared constant)
import java.util.Scanner;
 
public class InRange {
 
  private static final int MAX = 100;
 
  public static void main(String[] args) {
    Scanner keyboard = new Scanner(System.in);
    System.out.printf("enter an int between 1 and %s: ", MAX);
    int value = keyboard.nextInt();
 
    report(value);
  }
 
  private static void report(int value) {
    if (value < 1 || value > MAX) {
      System.out.println ("number entered not in range");
    }
    else {
      System.out.println("OK");
    }
  }
 
}
select
The key ideas are:

public static constant

Perhaps even more important are public class-based constants. This usage is an exception to the rule of keeping all data members private. The reason it is OK, is that being final ensures that it cannot be changed from the outside.

public class-based constants are pervasive in Java. One classic example is to provide access to the number "pi" for mathematical calculations:
public class Math {
  public static final double PI = 3.141592653589793;
  ...
}
Here is a simple usage:

CircleArea
import java.util.Scanner;
 
public class CircleArea {
 
  public static void main(String[] args) {
    Scanner keyboard = new Scanner(System.in);
    System.out.print("enter circle radius: ");
    double radius = keyboard.nextDouble();
    if (radius < 0) {
      System.out.println("radius must be non-negative");
      System.exit(1);
    }
    double area = Math.PI * radius * radius;
    System.out.printf("area = %.2f\n", area);
  }
}
select


© Robert M. Kline