1. Classes, Objects, Packages

In the previous chapters we used classes as containers for static methods. We did not intentionally build new objects with new. In this chapter, the following exercises deal with creating new objects, object references, and the special null reference.

Prerequisites

  • Know the difference between object type and reference type

  • be able to use `new

  • know the purpose and basic operation of the automatic garbage collector

  • be able to pass objects to methods and return them

  • be able to build packages

  • Be able to import types

  • be able to separate equivalence and identity

  • understand problem with null reference

Data types used in this chapter:

1.1. Creating objects

For the following examples we use the classes Point and Polygon from the package java.awt. The java.awt package contains various classes, many for graphical interfaces. However, the point and polygon are not graphical, and we will not program graphical interfaces. The Java types for points and polygons, however, are nice simple data types that have publicly accessible object variables as well as well-understood object methods, so they are quite suitable for our first exercises. We will not use any other data types from the java.awt package in the exercise book.

1.1.1. Draw polygons ⭐

A polygon is a closed set of lines. The Java library provides a class java.awt.Polygon for polygons, which we can "feed" with points.

Captain CiaoCiao goes by ship to the vicinity of the Bermuda Triangle and looks for the water creature Undine. But the Bermuda Triangle is dangerous and full of killer squids. The sailors must avoid the area at all costs. What would be good now is a map …​

Task:

  1. Create a new class BermudaTriangle with a main(…​) method.

  2. Create a java.awt.Polygon object.

  3. A polygon consists of points added with a method. What is the name of this method?

  4. Create a triangle for the mysterious Bermuda triangle. Keep the coordinates in the range from 0 to 50.

  5. If the position of the ship is a point, how can you find out if a chosen point is inside the triangle?

  6. Nest two loops for 0 <= x < 50 and 0 <= y < 50, creating a rectangular grid. Output an octopus exactly when the x-y coordinate hits a point that is in the polygon, and a rainbow otherwise. Insert the condition operator: print(is_in_the_polygon ? "\uD83D\uDC19" : "\uD83C\uDF08"); Here is_in_the_polygon stands symbolically for the test whether the point is in the polygon.

  7. Optional: Do the points on the line belong to the interior? What about the points themselves? Do they belong to the interior or the exterior of the polygon?

Starting with Java 9, there is the module system. By default, only the types from java.base module are included, and this does not contain GUI types. AWT types are part of desktop module java.desktop. If you use modules (a file module-info.java exists in the main directory of the application), you have to include the java.desktop module:

module com.tutego.bermuda {
  requires java.desktop;
}

1.2. Working with references

After creating an object with new we get back a reference on the instance. This reference can be passed to other methods, and methods can also return references.

1.2.1. Quiz: The short life of points ⭐

Given the following program code, where Point is from java.awt:

Point p, q, r;
p = new Point();
q = p;
Point s = new Point();
p = new Point();
s = new Point();
// How many objects are left?

Question:

  • How many reference variables are declared?

  • How many objects are created?

  • How many objects are referenced at the end of lines at the comment? What can the automatic garbage collection remove?

1.2.2. Build triangles ⭐

Captain CiaoCiao is sure that the dangerous places in the Bermuda Triangle can be random — he must be prepared for anything.

Task:

  • Create a new static method in the existing BermudaTriangle class:

    static Polygon resetWithRandomTriangle( Polygon polygon ) {
      // return initialized triangle
    }

    This method should first clear the passed java.awt.Polygon, which could still contain points, then fill it with a random triangle and return it at the end.

  • Write another static method which returns a new random triangle:

    static Polygon createRandomTriangle() {
      // return random triangle
    }

1.2.3. Quiz: == vs. equals(…​) ⭐

How will the compiler or runtime environment react?

public class EqualsOperatorOrMethod {

 public static void main( String[] args ) {

   int number1 = 1234;
   int number2 = 1234;

   if ( number1 == number2 )
     System.out.print( "==" );

   if ( number1.equals( number2 ) )
     System.out.print( "equals" );
 }
}

Will the screen print ==, or equals, or will neither of the condition statements catch so there is no output, or is there even a compiler error?

1.2.4. Quiz: Protect against NullPointerException ⭐

If a reference variable could be assigned with null, we need a way to check for null to prevent the program from running into a NullPointerException.

We are looking for a condition statement that should check if

  1. a string string is neither null .

  2. nor empty.

In other words, test whether a string instance exists and the string has at least one character. Which of the condition statements tests that correctly?

  1. if ( ! string.isEmpty() && string != null )

  2. if ( string != null & ! string.isEmpty() ) .

  3. if ( string != null && ! string.isEmpty() ) .

  4. if ( ! (string == null || string.isEmpty()) )

  5. if ( ! (string == null | string.isEmpty()) )

  6. if ( Objects.requireNonNull( string ) && ! string.isEmpty() ) .

  7. if ( Objects.requireNonNull( string ) != null ) .

There are two properties of Java to consider for a solution:

  1. The evaluation takes place from left to right.

  2. The logical operators && and || are short-circuit operators: If the result is determined, the other expressions do not have to be evaluated.[1]

1.3. Suggested solutions

1.3.1. Draw polygons

com/tutego/exercise/oop/BermudaTriangle.java
java.awt.Polygon bermuda = new java.awt.Polygon();

// Dimensions of the Bermuda triangle
bermuda.addPoint( 10, 40 );
bermuda.addPoint( 20, 5 );
bermuda.addPoint( 40, 20 );

// Inside the Bermuda triangle?
System.out.println( bermuda.contains( 25, 25 ) );  // true

final int DIMENSION = 50;
final String OCTOPUS = "\uD83D\uDC19";
final String RAINBOW = "\uD83C\uDF08";

// For every coordinate pair test if inside triangle
for ( int y = 0; y < DIMENSION; y++ ) {
  for ( int x = 0; x < DIMENSION; x++ )
    System.out.print( bermuda.contains( x, y ) ? OCTOPUS : RAINBOW );
  System.out.println();
}

Following the same pattern as a java.awt.Point is built, we build a java.awt.Polygon. We store the reference to the newly built object in a variable bermuda. We add three points to the polygon at the end. That the method is called addPoint(…​) can be found in the Java documentation, or via the autocompletion iappears after the point (.) following bermuda. The method is now passed x and y coordinates; it was not in the assignment that the values must be random, so we enter three static pairs.

That the method for testing is called contains(…​) can again be read from the Java documentation or just guessed in the autocompletion of the IDE. It is always a good idea to translate what you want from the object into English and then search for these verbs in the method list or Javadoc. Methods are always verbs, contains is a good example. You can see this quite well with the Point, it also has methods like move or translate.

The last step is the two nested loops. We know this already. We go with the outer loop over all rows and then with the inner loop over the row itself (x-axis). The condition operator makes the query quite simple. With contains(…​) we then test whether apoints are in the polygon or not. If the points are in the polygon, we draw an octopus, otherwise the rainbow. For the Unicode symbols there are speaking constants.

We are not able to make any statements about the points on the line or the vertices; they neither clearly belong to the polygon, nor are they clearly outside the polygon, but lie on the edge of the polygon.

1.3.2. Quiz: The short life of points

In total, four reference variables are declared (p, q, r, s) and four objects are built. If we want to know how many objects are created, we just have to count the number of occurrences of new.

Not every reference variable is initialized. The variable r remains uninitialized; we would not be allowed to access it, because r is not even pre-initialized with the null reference.

Of the four constructed objects, three remain in the end, because the first initialization of the point s is overwritten by the last line. This means that the point built at Point s = new Point(); is no longer referenced and can be cleared away by the garbage collector. Also the variable p is reinitialized, nevertheless the first generated Point remains in memory, because the variable q also points to the object and thus saved it.

1.3.3. Build triangles

com/tutego/exercise/oop/BermudaTriangle2.java
private static final int DIMENSION = 50;

static Polygon resetWithRandomTriangle( Polygon poly ) {
  poly.reset();

  Random random = ThreadLocalRandom.current();
  poly.addPoint( random.nextInt( DIMENSION ), random.nextInt( DIMENSION ) );
  poly.addPoint( random.nextInt( DIMENSION ), random.nextInt( DIMENSION ) );
  poly.addPoint( random.nextInt( DIMENSION ), random.nextInt( DIMENSION ) );

  return poly;
}

static Polygon createRandomTriangle() {
  return resetWithRandomTriangle( new Polygon() );
}

Let’s start with the method resetWithRandomTriangle(Polygon poly), which takes a polygon and returns the reference to exactly that polygon as well. Since the passed polygon might in principle already have elements, we want to remove all elements. Here a look at the documentation is necessary, which leads us to the method reset(). Then we create three random points as usual and add them to the polygon. Finally, we return the polygon.

Basically, one can ask why a method that expects a polygon also returns this polygon. resetWithRandomTriangle(…​) could well return void, because no new polygon object is built inside. However, methods that have returns are fundamentally better than methods that return nothing. Because if we get something back, it is an expression, and expressions are handy.

The use is well seen in the second method createRandomTriangle(), because it can refer back to the previous method resetWithRandomTriangle(…​) and pass a new polygon there. Since resetWithRandomTriangle(…​) also returns this passed polygon directly, we can end the method with a one-liner. If resetWithRandomTriangle(…​) would return nothing, we would have to introduce an intermediate variable, which we first assign with the new polygon and finally return via return. We can save this intermediate variable if resetWithRandomTriangle(…​) returns the argument at the same time.

1.3.4. Quiz: == vs. equals(…​)

Primitive data types are not reference types, consequently no single method can be called on the primitive data types. It is not allowed in Java to put a dot after the primitive element and then call a method. The situation is different if the primitive data types are automatically converted into so-called wrapper objects by the compiler, but that is another story. Consequently, there is a compiler error.

1.3.5. Quiz: Protect against NullPointerException

About the individual conditions:

if ( ! string.isEmpty() && string != null )

The expression first checks whether string is empty and then whether string is null. Since the evaluation is done from left to right and length is checked here first, this notation leads to a NullPointerException if string == null and avoids none.

if ( string != null & ! string.isEmpty() )

&& and || are short-circuit operators, and they have a counterpart & and | that do not operate on the short-circuit principle, i.e., execute all parts. So this also yields a NullPointerException if string is null, because of the method call.

if ( string != null && ! string.isEmpty() )

This check is correct. The order is correct, first it tests if string is not equal to null. If the result is false, the right part is not evaluated at all. This avoids a NullPointerException.

if ( ! (string == null || string.isEmpty()) )

Logical expressions can be negated twice, leading to the same result. In this case, the individual logical expressions are reversed, an AND becomes an OR and an OR becomes an AND. In addition, the whole expression is negated — the keyword is boolean algebra. Thus this if statement is also correct. It depends on the context whether this simplifies the readability or not, in this special case probably not.

if ( (string == null | string.isEmpty()) )

The | operator leads to the evaluation of both sides: A NullPointerException occurs if string is null.

if ( Objects.requireNonNull( string ) && ! string.isEmpty() )

There is a compiler error with this notation, because Objects.requireNonNull(…​) returns a reference, but logical operations are only allowed with boolean.

if ( Objects.requireNonNull( string ) != null )

There is no test whether the string also contains characters; this is not a feature of requireNonNull(…​). Besides, using it would be counterproductive, because if the string would be null, we would get an exception, which is exactly what we want to avoid. You only use requireNonNull(…​) if you want to throw an exception deliberately to report faulty parameters.


1. https://docs.oracle.com/javase/specs/jls/se16/html/jls-15.html#jls-15.23 and https://docs.oracle.com/javase/specs/jls/se16/html/jls-15.html#jls-15.24