Lecture 9: Classes and objects
Objectives
- Construct, use, and pass objects as arguments
- Use
null
references appropriately - Write classes and use objects
In the last lecture, we discussed how Java handles memory and scope between variables, methods, and fields (variables declared in a class outside of a method). In this lecture, we’ll (finally!) go over classes and show how fields are used to represent object state. This is the last lecture of the semester.
Why are classes and objects useful in computation?
- Large, complex projects worked on by many people in parallel
- Ability to reuse code others have written, by extending it and making it your own
- Other examples?
Classes vs objects
We have been defining lots of classes all semester – in fact, to be able to run any code in Java, we need a main
method inside of some arbitrary class! However, we were never really using classes so far for their intended purpose: to group related data and methods about some user-defined type (the class) in one place.
An example class: our own String
s
We saw how Java has a String
class that likely uses some array-like structure to store a sequence of characters, and then provides hundreds of methods to operate on this data. Imagine, however, no one had defined such a class. Let’s define our own FakeString
class to illustrate how they might have done this.
FakeString
class together
You probably ended up with something like this:
public class FakeString {
}
You probably ended up with something like this:
public class FakeString {
char[] letters = new char[255];
int actualLength = 0;
}
FakeString
class have?
You probably ended up with something like this:
public class FakeString {
char[] letters = new char[255];
int actualLength = 0;
public void addLetter(char letter){
[actualLength] = letter;
letters++;
actualLength}
public char charAt(int i){
return letters[i];
}
public boolean isFull(){
return (letters.length == acutalLength);
}
}
This is all very useful, but how do we actually create a FakeString
object and call the methods above on it?
Constructors
In Java, classes are a blueprint for what their respective objects look like. We need a way actually create these objects, and we do that with a special method called a constructor:
public class FakeString {
char[] letters = new char[255];
int actualLength = 0;
public FakeString(char[] lettersIn){
for(int i = 0; i < lettersIn.length; i++)
addLetter(lettersIn[i]);
}
public FakeString(int length){
= new char[length];
letters }
...
}
A constructor has the same name as the class and no return type, and it’s job is to create a default object on the heap using the fields. It keeps track of the memory address of this new object and uses the new
keyword to reserve space on the heap for this new object.
Above, we see two constructors – the compiler can distinguish between them because their arguments are different. We can now use these constructors to generate FakeString
objects, perhaps in another class:
public class Driver {
public static void main(String[] args){
char[] purple = {'p', 'u', 'p', 'l', 'e'};
= new FakeString(purple);
FakeString color1
= new FakeString(10);
FakeString color2
.addLetter('Q');
color1.addLetter('Q');
color2}
}
Once we have created the color1
and color2
objects, we can then call methods on them like addLetter
, using the dot operator.
Default constructors and default values
It turns out that even if we didn’t write those two constructors, Java would have given us a default constructor for free:
public class Driver {
public static void main(String[] args){
= new FakeString();
FakeString colorDefault }
}
The default constructor takes no arguments, and initializes all fields to their default values. But, what is a default value?
It also turns out you can define the fields of a class without initializing them, for instance:
public class Person {
// will use default values
String name;
int age;
String[] previousPhoneNumbers;
// will use default values in the array
int[] nums = new int[3];
// will use the actual values you chose
double income = 1000.22;
String employer = "GWU";
int[] randoms = {3, 2};
}
Default values are either 0, false
, or null
, depending on the type – we saw this when creating arrays and only specifying their sizes. null
is a value for any complex/reference type that represents an invalid/non-existant memory address.
You can also use default values in regular constructors; it depends whether or not you want to specify any values when you specify the fields.
Note: If you start to write your own constructor(s) as above, you will lose the default constructor – Java’s expectation is you know what you’re doing and need something more specific than the default behavior.
The this
keyword
Java allows any method that is associated with an object to retrieve its memory address using the this
keyword, if you are inside the class:
public class FakeString {
char[] letters = new char[255];
int actualLength = 0;
public boolean isLongerThanFive(){
if (this.actualLength > 5)
return true;
return false;
}
...
}
The code above would work just the same without the this
keyword, but it’s useful to disambiguate between arguments and fields:
public class Person {
String name;
int age;
double salary
public Person(String name, int age, double salaryIn){
this.name = name;
= age; // this compiles but is a conceptual error
age = salaryIn;
salary }
}
In the example above, this.name = name;
successfully updats the name
field of the class with the incoming name
arguemnt. However, age = age;
only updates the local argument age
; this local variable shadows the field because it has the same name. One would probably want this.age = age;
instead, or to give the two variables unique names like salary
vs salaryIn
.
Anytime a method is called on an object in Java, you should write this
as another local variable to that method on the stack.
The static
keyword
Another keyword that can now make sense is static
, which can be applied to fields to indicate that all objects of that class share that same field. For example:
public class GW_Student {
String name;
int GWID;
static String school = "Colonials"; // GW's old nickname, see https://en.wikipedia.org/wiki/George_Washington_Revolutionaries
public GW_Student(String name, int GWID){
this.name = name;
this.GWID = GWID;
}
public void updateSchool(String newSchool){
= newSchool;
school }
public String toString(){
return name + " " + GWID + " " + school;
}
}
public class Driver {
public void runMe(){
= new GW_Student("Ravi", "G123876");
GW_Student ravi = new GW_Student("Jane", "G666666");
GW_Student jane = new GW_Student("Jorge", "G123876");
GW_Student jorge System.out.println(ravi.toString());
System.out.println(jane.toString());
System.out.println(jorge.toString());
}
}
Now, imagine we wanted to update this software system to change Colonials
to the new name, Revolutionaries
. We could, in theory, update all of these objects individually with the updateSchool
method:
.updateSchool("Revolutionaries");
ravi.updateSchool("Revolutionaries"); // unnecessary
jane.updateSchool("Revolutionaries"); // unnecessary jorge
But this is annoying to type, and even if we loop it we’re still wasting a String
on all the objects that are just storing the same value. Instead, because school
is static
, we can just call updateSchool
on any single object, and the change will be visible across all of them, since they all share this single field.
static
methods
static
can also be applied to a method to indicate you can call the method without having any objects created yet. These work together, in that static
methods can only access static
fields (and their own local variables) and not regular fields, and static
methods can only call other static
methods in the same class.
This was why most of the methods you saw this semester were static
, because we were calling them from main
, which is also static
. Why is main
static
? Recall that when you type in the terminal java HelloWord
there was no constructor/object ever created in that example.
Visibility
The last piece of syntax that we have yet to cover this semester is visibility, which refers to the public
modifiers you’ve been seeing. There are a few other visibility flavors that we won’t cover this semester.
For now, when you see public
, that means that code outside of that class can use the method and/or see the field. For example, if updateSchool
wasn’t public
, we could not have called it in the Driver
class.
Visibility is orthogonal to the static
modifier.
Next class
We’ll trace through code that uses constructors to create objects