On this page:
14.1 Overloading Constructors and Methods
14.2 Overriding Methods
14.3 Field and Method Modifiers
14.3.1 Visibility Modifiers
14.3.2 The static modifier
14.3.2.1 static fields
14.3.2.2 static methods
14.3.3 The final Modifier
14.3.3.1 final Fields
14.3.3.2 final methods
14.4 int vs Integer
14.5 Definition:   Primitive vs. Reference
6.8

Lecture 28: Java Details, FYI

This course has focused on a number of key programming ideas. This section serves two purposes:

In general, features were introduced when the solved an interesting new programming problem (e.g. interfaces for capturing the idea of method signatures shared across classes, abstract classes for the idea of method definitions shared across classes, field assignment for objects with memory, and so on). There are, of course, many many more features in Java. Knowing all of them isn’t necessary to be a good programmer, or indeed to understand object-oriented programming concepts like methods and inheritance.

However, there is a set of vocabulary and common features that you may see in the future when reading Java programs. It’s useful to be exposed to the vocabulary and keywords so that you have a point of reference when reading other programs.

You will be tested on some of this material, but it will be a very small proportion of your final exam grade. This course is about programming with objects more than it is about the specifics of Java (though it is, to some extent, about both).

14.1 Overloading Constructors and Methods

We saw that we could write constructors that took different numbers of arguments, and Java would call the correct one. For example, the ATweet constructor could be called with or without an initial number of likes. This idea – that the language selects the correct method to call based on the signature, is called overloading. It works both for constructors and for methods, though we only used it for constructors this quarter.

Overloading can also be based on the types of parameters. For example, we could define a distance method on Point with zero arguments, which computes the distance to the origin, one with a Point argument that computes the distance between two points, and another with a single double argument that treats the value as both the x and y coordinate of the other point:

class Point {
double x, y;
Point(double x, double y) {
this.x = x;
this.y = y;
}
double distance() {
return this.distance(new Point(0, 0));
}
double distance(double d) {
return this.distance(new Point(d, d));
}
double distance(Point other) {
return Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2));
}
}
class ExamplesPoint {
boolean testdist(Tester t) {
t.checkInexact(new Point(3, 4).distance(), 5.0, 0.01);
t.checkInexact(new Point(3, 4).distance(0), 5.0, 0.01);
t.checkInexact(new Point(3, 4).distance(new Point(6, 8)), 5.0, 0.01);
return true;
}
}

Java knows that in the first test, the zero-argument distance method should be called, in the second test, the distance method with the double argument should be called, and in the third test, the distance method with a Point argument should be called.

14.2 Overriding Methods

We saw, with abstract classes, two different kinds of interactions between subclasses and superclasses:

The latter is the simplest case of overriding a method, which is when a subclass defines the same method as a superclass. In the case of an abstract method, this is quite similar to implementing a method from an interface.

However, Java also allows the overriding of methods with definitions. So we could, for example, write a method that overrides the shared definition of retweet in a subclass like TextTweet if we wanted to provide a different implementation for that class. Here’s a simple example, where the tests illustrate the overriding behavior:

class Super {
String m() {
return "In Super";
}
}
class Sub1 extends Super {
String m() {
return "In Sub1";
}
}
class Sub2 extends Super {
}
class ExamplesOverride {
boolean testOverride(Tester t) {
t.checkExpect(new Sub1().m(), "In Sub1");
t.checkExpect(new Sub2().m(), "In Super");
t.checkExpect(new Super().m(), "In Super");
return true;
}
}
14.3 Field and Method Modifiers

You can read about this more in the official Java documentation.

We’ve seen the keywords public, private, and static at various points in the quarter. There are other keywords that are related to these—protected and final—in that they appear in the same position (before a field or method declaration). These all have different meanings, and fall into separate categories. Since they all go in the same place, but do different things, it’s useful to explicitly call out the contrasts.

It’s also worth noting that, with the exception of static, none of these would have helped us accomplish anything this quarter. Their use mostly comes from organizational benefits in large programs. It’s useful to know about them for that reason, since you’ll certainly read code in the future that uses them, but they aren’t crucial for any programming concept we’ve addressed.

14.3.1 Visibility Modifiers

public, private, and protected are access modifiers on fields and methods. They affect in which classes a particular field or method can be referred to.

14.3.2 The static modifier

The modifer static means something slightly different on fields and on methods, so we’ll tackle them one at a time.

14.3.2.1 static fields

We’ve typically thought of memory as consisting of a stack, for pending method calls, and a heap, for objects that have been created. Along with these two areas of memory, there is also a space for static fields, also called class fields. For each class in the program, there is space set aside for any static fields defined within that class. Those fields are not created per-instance or per-object, but have only a single copy in all of memory, stored in that static space.

This means, for example, that if we update a static field, any other context that looks up the same static field will see the update. This can let us share state across many objects. For example, one way to ensure that each created Tweet has a new id would have been to add a static field to ATweet:

abstract class ATweet {
static int nextId;
String content;
String tweetId;
public ATweet(String content) {
ATweet.nextId += 1;
this.content = content;
this.tweetId = "" + ATweet.nextId;
}
}

The above example would have a single field, called nextId, that would be updated each time the constructor for ATweet was called. Note that we can refer to the field using the class name – we wrote ATweet.nextId. We could also use this.nextId, but I find this somewhat confusing, because it makes it hard to tell at the point of use whether a field is static or not.

Static fields are also called class fields, and non-static fields are often called instance fields.

14.3.2.2 static methods

When a method is declared static, it can be called without an accompanying value for this. In addition, since code within a static method doesn’t have a value bound for the this parameter, implicitly calling methods without an object won’t work, because there is no value to provide for this. If you find yourself in this situation, you probably need to rethink something about the structure of your program, or you forgot to make a method static.

Static methods are useful for helper functions that logically are not tied to a particular object. Many built-in methods are static, like Math.min or Files.readAllLines. These methods can be used without first constructing an object.

Like instance methods, static methods can be called by using a class’s name instead of a particular object. For example:

class Help {
static void countArgs(String[] args) {
System.out.println("There were " + args.length + " args");
}
}
class C {
public static void main(String[] args) {
Help.countArgs(args);
}
}
14.3.3 The final Modifier

The final modifer behaves differently on fields and on methods, so it’s worth tackling them separately.

14.3.3.1 final Fields

Fields declared final cannot be changed once initialized. They can be assigned within a constructor, or with a default value in the class definition, but any field assignment in another context will fail. It’s actually quite useful to make fields final when they really ought to stay the same for the whole lifetime of the object. For example, in ATweet, a number of fields should never change – the id and user of a Tweet are set when it is created and can’t change, for example. If any code tried to change these fields that were declared final, it would cause an error:

class Tweet {
final String id;
final User user;
String content;
int retweetCount;
Tweet(String id, User user, String content) {
this.id = id;
this.user = user;
this.content = content;
this.retweetCount = 0;
}
 
/* Creates a new tweet with the same content as this one, but with the given user. The id of the new tweet should be this tweet's ID with "-rtN" appended, where N is the number of times this tweet has been retweeted */
Tweet retweet(User user) {
// The next line is wrong – we should be making a new id for the // retweet, not changing the id of this tweet this.id = this.id + "-retweet" + this.retweetCount;
// error: cannot assign a value to final variable id return new Tweet(this.id, user, this.content);
}
}

This error would stop us (or anyone else working on the code in the future) from making mistakes involving assigning to fields that we don’t intend to change.

14.3.3.2 final methods

A method declared with final cannot be overridden in subclassses:

class Super {
final String m() {
return "In Super";
}
}
class Sub1 extends Super {
String m() { // error: m() in Sub1 cannot override m() in Super return "In Sub1";
}
}
class Sub2 extends Super {
}
class ExamplesOverride {
boolean testOverride(Tester t) {
t.checkExpect(new Sub1().m(), "In Sub1");
t.checkExpect(new Sub2().m(), "In Super");
t.checkExpect(new Super().m(), "In Super");
return true;
}
}

This could be useful for avoiding mistakes where a subclass replaces an important, shared implementation provided by a superclass. For example, it was important that in the quote method in the tweets examples, the retweetCount was incremented and that QuoteTweets were constructed in a particular way. It could be useful to make that quote method final to avoid any subclass handling quote differently.

14.4 int vs Integer

In class when we introduced generics, we used the Integer type in lieu of the type int. It turns out that for all the primitive types we’ve used so far—int, char, double, long—there is a corresponding class. That is, there exist built-in classes Integer, Character, Double, and Long.

These classes have useful static helper methods—like Double.parseDouble—and also can be instantiated. For example, we could write new Double(1.2) and a new object of class Double would be created. For example, consider this code:

class ExamplesDouble {
boolean testD(Tester t) {
double d = 12;
Double d2 = new Double(12);
}
}

When the testD method runs, it produces the following:

That is, we can think of these objects as containing a single (private) field that will never change, and contains the numeric value.

One main use of these objects is actually for generics. Java doesn’t let us instantiate a generic type with a primitive. For example, its an error to write:

interface AnyList<T> { }
class Link<T> implements AnyList<T> { }
class Empty<T> implements AnyList<T> { }
class ExamplesList {
AnyList<int> l = new Link<int>(); // error: unexpected type }

But we can write:

interface AnyList<T> { }
class Link<T> implements AnyList<T> { }
class Empty<T> implements AnyList<T> { }
class ExamplesList {
AnyList<Integer> l = new Link<Integer>();
}

There’s no particularly good motivation for this from the programmer’s perspective. It’s simply a restriction in the way Java works.

In many cases, these objects behave just the same as their primitive value counterparts – printing, arithmetic, and so on work mostly as you’d hope. The converse isn’t true, because primitive values like int and double do not have methods defined on them, so it’s an error to write (5).equals(10). Since many built-in operations rely on or return the primitive types, and the object versions are needed for working with generics, we unfortunately have to live with the reality that both are used.

14.5 Definition: Primitive vs. Reference

A piece of vocabulary that is often used to distinguish object types from types like int and double is primitive vs. reference type. This goes for all types – any programmer-created class, like ATweet, along with built-in ones, like Integer or String, and array types, are all collectively categorized as reference types. Non-object values are called primitive types.

The Java Documentation has a full accounting of the different primitive types, which go a little bit beyond what we used, but they are straightforward extensions:

https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html