Chapter Three. Object-Oriented Programming

Your next step in programming effectively is conceptually simple. Now that you know how to use primitive types of data, you will learn in this chapter how to use, create, and design higher-level data types.

An abstraction is a simplified description of something that captures its essential elements while suppressing all other details. In science, engineering, and programming, we are always striving to understand complex systems through abstraction. In Java programming, we do so with object-oriented programming, where we break a large and potentially complex program into a set of interacting elements, or objects. The idea originates from modeling (in software) real-world entities such as electrons, people, buildings, or solar systems and readily extends to modeling abstract entities such as bits, numbers, colors, images, or programs.

A data type is a set of values and a set of operations defined on those values. The values and operations for primitive types such as int and double are predefined by Java. In object-oriented programming, we write Java code to define new data types. An object is an entity that holds a data-type value; you can manipulate this data-type value by applying one of the object’s data-type operations.

This ability to define new data types and to manipulate objects holding data-type values is also known as data abstraction, and leads us to a style of modular programming that naturally extends the procedural programming style for primitive types that was the basis for CHAPTER 2. A data type allows us to isolate data as well as functions. Our mantra for this chapter is this: whenever you can clearly separate data and associated tasks within a computation, you should do so.

3.1 Using Data Types

Organizing data for processing is an essential step in the development of a computer program. Programming in Java is largely based on doing so with data types known as reference types that are designed to support object-oriented programming, a style of programming that facilitates organizing and processing data.

The eight primitive data types (boolean, byte, char, double, float, int, long, and short) that you have been using are supplemented in Java by extensive libraries of reference types that are tailored for a large variety of applications. The String data type is one such example that you have already used. You will learn more about the String data type in this section, as well as how to use several other reference types for image processing and input/output. Some of them are built into Java (String and Color), and some were developed for this book (In, Out, Draw, and Picture) and are useful as general resources.

You certainly noticed in the first two chapters of this book that our programs were largely confined to operations on numbers. Of course, the reason is that Java’s primitive types represent numbers. The one exception has been strings, a reference type that is built into Java. With reference types you can write programs that operate not just on strings, but on images, sounds, or any of hundreds of other abstractions that are available in Java’s libraries or on our booksite.

In this section, we focus on client programs that use existing data types, to give you some concrete reference points for understanding these new concepts and to illustrate their broad reach. We will consider programs that manipulate strings, colors, images, files, and web pages—quite a leap from the primitive types of CHAPTER 1.

In the next section, you will take another leap, by learning how to define your own data types to implement any abstraction whatsoever, taking you to a whole new level of programming. Writing programs that operate on your own types of data is an extremely powerful and useful style of programming that has dominated the landscape for many years.

Basic definitions

A data type is a set of values and a set of operations defined on those values. This statement is one of several mantras that we repeat often because of its importance. In CHAPTER 1, we discussed in detail Java’s primitive data types. For example, the values of the primitive data type int are integers between –231 and 231 – 1; the operations defined for the int data type include those for basic arithmetic and comparisons, such as +, *, %, <, and >.

You also have been using a data type that is not primitive—the String data type. You know that values of the String data type are sequences of characters and that you can perform the operation of concatenating two String values to produce a String result. You will learn in this section that there are dozens of other operations available for processing strings, such as finding a string’s length, extracting individual characters from the string, and comparing two strings.

Every data type is defined by its set of values and the operations defined on them, but when we use the data type, we focus on the operations, not the values. When you write programs that use int or double values, you are not concerning yourself with how they are represented (we never did spell out the details), and the same holds true when you write programs that use reference types, such as String, Color, or Picture. In other words, you do not need to know how a data type is implemented to be able to use it (yet another mantra)

The String data type

As a running example, we will revisit Java’s String data type in the context of object-oriented programming. We do so for two reasons. First, you have been using the String data type since your first program, so it is a familiar example. Second, string processing is critical to many computational applications. Strings lie at the heart of our ability to compile and run Java programs and to perform many other core computations; they are the basis of the information-processing systems that are critical to most business systems; people use them every day when typing into email, blog, or chat applications or preparing documents for publication; and they have proved to be critical ingredients in scientific progress in several fields, particularly molecular biology.

We will write programs that declare, create, and manipulate values of type String. We begin by describing the String API, which documents the available operations. Then, we consider Java language mechanisms for declaring variables, creating objects to hold data-type values, and invoking instance methods to apply data-type operations. These mechanisms differ from the corresponding ones for primitive types, though you will notice many similarities.

API

The Java class provides a mechanism for defining data types. In a class, we specify the data-type values and implement the data-type operations. To fulfill our promise that you do not need to know how a data type is implemented to be able to use it, we specify the behavior of classes for clients by listing their instance methods in an API (application programming interface), in the same manner as we have been doing for libraries of static methods. The purpose of an API is to provide the information that you need to write a client program that uses the data type.

The following table summarizes the instance methods from Java’s String API that we use most often; the full API has more than 60 methods! Several of the methods use integers to refer to a character’s index within a string; as with arrays, these indices start at 0.

public class String (Java string data type)

 

String(String s)

create a string with the same value as s

 

String(char[] a)

create a string that represents the same sequence of characters as in a[]

int

length()

number of characters

char

charAt(int i)

the character at index i

String

substring(int i, int j)

characters at indices i through (j-1)

boolean

contains(String substring)

does this string contain substring ?

boolean

startsWith(String pre)

does this string start with pre ?

boolean

endsWith(String post)

does this string end with post ?

int

indexOf(String pattern)

index of first occurrence of pattern

int

indexOf(String pattern, int i)

index of first occurrence of pattern after i

String

concat(String t)

this string with t appended

int

compareTo(String t)

string comparison

String

toLowerCase()

this string, with lowercase letters

String

toUpperCase()

this string, with uppercase letters

String

replaceAll(String a, String b)

this string, with as replaced by bs

String[]

split(String delimiter)

strings between occurrences of delimiter

boolean

equals(Object t)

is this string’s value the same as t’s ?

int

hashCode()

an integer hash code

See the online documentation and booksite for many other available methods.

Excerpts from the API for Java’s String data type

The first entry, with the same name as the class and no return type, defines a special method known as a constructor. The other entries define instance methods that can take arguments and return values in the same manner as the static methods that we have been using, but they are not static methods: they implement operations for the data type. For example, the instance method length() returns the number of characters in the string and charAt() returns the character at a specified index.

Declaring variables

You declare variables of a reference type in precisely the same way that you declare variables of a primitive type, using a declaration statement consisting of the data type name followed by a variable name. For example, the statement

String s;

declares a variable s of type String. This statement does not create anything; it just says that we will use the variable name s to refer to a String object. By convention, reference types begin with uppercase letters and primitive types begin with lowercase letters.

Creating objects

In Java, each data-type value is stored in an object. When a client invokes a constructor, the Java system creates (or instantiates) an individual object (or instance). To invoke a constructor, use the keyword new; followed by the class name; followed by the constructor’s arguments, enclosed in parentheses and separated by commas, in the same manner as a static method call. For example, new String("Hello, World") creates a new String object corresponding to the sequence of characters Hello, World. Typically, client code invokes a constructor to create an object and assigns it to a variable in the same line of code as the declaration:

String s = new String("Hello, World");
A program depicts the usage of reference data type.

You can create any number of objects from the same class; each object has its own identity and may or may not store the same value as another object of the same type. For example, the code

String s1 = new String("Cat");
String s2 = new String("Dog");
String s3 = new String("Cat");

creates three different String objects. In particular, s1 and s3 refer to different objects, even though the two objects represent the same sequence of characters.

Invoking instance methods

The most important difference between a variable of a reference type and a variable of a primitive type is that you can use reference-type variables to invoke the methods that implement data-type operations (in contrast to the built-in syntax involving operators such as + and * that we used with primitive types). Such methods are known as instance methods. Invoking (or calling) an instance method is similar to calling a static method in another class, except that an instance method is associated not just with a class, but also with an individual object. Accordingly, we typically use an object name (variable of the given type) instead of the class name to identify the method. For example, if s1 and s2 are variables of type String as defined earlier, then s1.length() returns the integer 3, s1.charAt(1) returns the character 'a', and s1.concat(s2) returns a new string CatDog.

String a = new String("now is");
String b = new String("the time");
String c = new String(" the");

instance method call

return type

return value

a.length()

int

6

a.charAt(4)

char

'i'

a.substring(2, 5)

String

"w i"

b.startsWith("the")

boolean

true

a.indexOf("is")

int

4

a.concat(c)

String

"now is the"

b.replace("t", "T")

String

"The Time"

a.split(" ")

String[]

{ "now", "is" }

b.equals(c)

boolean

false

Examples of String data-type operations

String shortcuts

As you already know, Java provides special language support for the String data type. You can create a String object using a string literal instead of an explicit constructor call. Also, you can concatenate two strings using the string concatenation operator (+) instead of making an explicit call to the concat() method. We introduced the longhand version here solely to demonstrate the syntax you need for other data types; these two shortcuts are unique to the String data type.

shorthand

String s = "abc";

String t = r + s;

longhand

String s = new String("abc");

String t = r.concat(s);

The following code fragments illustrate the use of various string-processing methods. This code clearly exhibits the idea of developing an abstract model and separating the code that implements the abstraction from the code that uses it. This ability characterizes object-oriented programming and is a turning point in this book: we have not yet seen any code of this nature, but virtually all of the code that we write from this point forward will be based on defining and invoking methods that implement data-type operations.

extract file name and extension from a command-line argument

String s = args[0];
int dot = s.indexOf(".");
String base      = s.substring(0, dot);
String extension = s.substring(dot + 1, s.length());

print all lines on standard input that contain a string specified as a command-line argument

String query = args[0];
while (StdIn.hasNextLine())
{
   String line = StdIn.readLine();
   if (line.contains(query))
      StdOut.println(line);
}

is the string a palindrome?

public static boolean isPalindrome(String s)
{
   int n = s.length();
   for (int i = 0; i < n/2; i++)
      if (s.charAt(i) != s.charAt(n-1-i))
         return false;
   return true;
}

translate from DNA to mRNA (replace 'T' with 'U')

public static String translate(String dna)
{
   dna = dna.toUpperCase();
   String rna = dna.replaceAll("T", "U");
   return rna;
}

Typical string-processing code

String-processing application: genomics

To give you more experience with string processing, we will give a very brief overview of the field of genomics and consider a program that a bioinformatician might use to identify potential genes. Biologists use a simple model to represent the building blocks of life, in which the letters A, C, G, and T represent the four bases in the DNA of living organisms. In each living organism, these basic building blocks appear in a set of long sequences (one for each chromosome) known as a genome. Understanding properties of genomes is a key to understanding the processes that manifest themselves in living organisms. The genomic sequences for many living things are known, including the human genome, which is a sequence of about 3 billion bases. Since the sequences have been identified, scientists have begun composing computer programs to study their structure. String processing is now one of the most important methodologies—experimental or computational—in molecular biology.

Gene prediction

A gene is a substring of a genome that represents a functional unit of critical importance in understanding life processes. A gene consists of a sequence of codons, each of which is a sequence of three bases that represents one amino acid. The start codon ATG marks the beginning of a gene, and any of the stop codons TAG, TAA, or TGA marks the end of a gene (and no other occurrences of any of these stop codons can appear within the gene). One of the first steps in analyzing a genome is to identify its potential genes, which is a string-processing problem that Java’s String data type equips us to solve.

PotentialGene (PROGRAM 3.1.1) is a program that serves as a first step. The isPotentialGene() function takes a DNA string as an argument and determines whether it corresponds to a potential gene based on the following criteria: length is a multiple of 3, starts with the start codon, ends with a stop codon, and has no intervening stop codons. To make the determination, the program uses a variety of string instance methods: length(), charAt(), startsWith(), endsWith(), substring(), and equals().

Although the rules that define genes are a bit more complicated than those we have sketched here, PotentialGene exemplifies how a basic knowledge of programming can enable a scientist to study genomic sequences more effectively.

In the present context, our interest in the String data type is that it illustrates what a data type can be—a well-developed encapsulation of an important abstraction that is useful to clients. Before proceeding to other examples, we consider a few basic properties of reference types and objects in Java.


Program 3.1.1 Identifying a potential gene

public class PotentialGene
{
   public static boolean isPotentialGene(String dna)
   {
      // Length is a multiple of 3.
      if (dna.length() % 3 != 0) return false;

      // Starts with start codon.
      if (!dna.startsWith("ATG")) return false;

      // No intervening stop codons.
      for (int i = 3; i < dna.length() - 3; i++)
      {
         if (i % 3 == 0)
         {
            String codon = dna.substring(i, i+3);
            if (codon.equals("TAA")) return false;
            if (codon.equals("TAG")) return false;
            if (codon.equals("TGA")) return false;
         }
      }

      // Ends with a stop codon.
      if (dna.endsWith("TAA")) return true;
      if (dna.endsWith("TAG")) return true;
      if (dna.endsWith("TGA")) return true;

      return false;
   }
}

 dna  | string to analyze
codon | 3 consecutive bases

The isPotentialGene() function takes a DNA string as an argument and determines whether it corresponds to a potential gene: length is a multiple of 3, starts with the start codon (ATG), ends with a stop codon (TAA or TAG or TGA), and has no intervening stop codons. See EXERCISE 3.1.19 for the test client.


% java PotentialGene ATGCGCCTGCGTCTGTACTAG
true
% java PotentialGene ATGCGCTGCGTCTGTACTAG
false

Object references

A constructor creates an object and returns to the client a reference to that object, not the object itself (hence the name reference type). What is an object reference? Nothing more than a mechanism for accessing an object. There are several different ways for Java to implement references, but we do not need to know the details to use them. Still, it is worthwhile to have a mental model of one common implementation. One approach is for new to assign memory space to hold the object’s current data-type value and return a pointer (memory address) to that space. We refer to the memory address associated with the object as the object’s identity.

An illustration with two sections depicts the object representation.

Why not just process the object itself? For small objects, it might make sense to do so, but for large objects, cost becomes an issue: data-type values can consume large amounts of memory. It does not make sense to copy or move all of its data every time that we pass an object as an argument to a method. If this reasoning seems familiar to you, it is because we have used precisely the same reasoning before, when talking about passing arrays as arguments to static methods in SECTION 2.1. Indeed, arrays are objects, as we will see later in this section. By contrast, primitive types have values that are natural to represent directly in memory, so that it does not make sense to use a reference to access each value.

We will discuss properties of object references in more detail after you have seen several examples of client code that use reference types.

Using objects

A variable declaration gives us a variable name for an object that we can use in code in much the same way as we use a variable name for an int or double:

• As an argument or return value for a method

• In an assignment statement

• In an array

We have been using String objects in this way ever since HelloWorld: most of our programs call StdOut.println() with a String argument, and all of our programs have a main() method that takes an argument that is a String array. As we have already seen, there is one critically important addition to this list for variables that refer to objects:

• To invoke an instance method defined on it

This usage is not available for variables of a primitive type, where operations are built into the language and invoked only via operators such as +, -, *, and /.

Uninitialized variables

When you declare a variable of a reference type but do not assign a value to it, the variable is uninitialized, which leads to the same behavior as for primitive types when you try to use the variable. For example, the code

String bad;
boolean value = bad.startsWith("Hello");

leads to the compile-time error variable bad might not have been initialized because it is trying to use an uninitialized variable.

Type conversion

If you want to convert an object from one type to another, you have to write code to do it. Often, there is no issue, because values for different data types are so different that no conversion is contemplated. For instance, what would it mean to convert a String object to a Color object? But there is one important case where conversion is very often worthwhile: all Java reference types have a special instance method toString() that returns a String object. The nature of the conversion is completely up to the implementation, but usually the string encodes the object’s value. Programmers typically call the toString() method to print traces when debugging code. Java automatically calls the toString() method in certain situations, including with string concatenation and StdOut.println(). For example, for any object reference x, Java automatically converts the expression "x = " + x to "x = " + x.toString() and the expression StdOut.println(x) to StdOut.println(x.toString()). We will examine the Java language mechanism that enables this feature in SECTION 3.3.

Accessing a reference data type

As with libraries of static methods, the code that implements each class resides in a file that has the same name as the class but carries a .java extension. To write a client program that uses a data type, you need to make the class available to Java. The String data type is part of the Java language, so it is always available. You can make a user-defined data type available either by placing a copy of the .java file in the same directory as the client or by using Java’s classpath mechanism (described on the booksite). With this understood, you will next learn how to use a data type in your own client code.

Distinction between instance methods and static methods

Finally, you are ready to appreciate the meaning of the modifier static that we have been using since PROGRAM 1.1.1—one of the last mysterious details in the Java programs that you have been writing. The primary purpose of static methods is to implement functions; the primary purpose of instance (non-static) methods is to implement data-type operations. You can distinguish between the uses of the two types of methods in our client code, because a static method call typically starts with a class name (uppercase, by convention) and an instance method call typically starts with an object name (lowercase, by convention). These differences are summarized in the following table, but after you have written some client code yourself, you will be able to quickly recognize the difference.

 

instance method

static method

sample call

s.startsWith("Hello")

Math.sqrt(2.0)

invoked with

object name (or object reference)

class name

parameters

reference to invoking object and argument(s)

argument(s)

primary purpose

manipulate object’s value

compute return value

Instance methods versus static methods

The basic concepts that we have just covered are the starting point for object-oriented programming, so it is worthwhile to briefly summarize them here. A data type is a set of values and a set of operations defined on those values. We implement data types in independent modules and write client programs that use them. An object is an instance of a data type. Objects are characterized by three essential properties: state, behavior, and identity. The state of an object is a value from its data type. The behavior of an object is defined by the data type’s operations. The identity of an object is the location in memory where it is stored. In object-oriented programming, we invoke constructors to create objects and then modify their state by invoking their instance methods. In Java, we manipulate objects via object references.

To demonstrate the power of object orientation, we next consider several more examples. First, we consider the familiar world of image processing, where we process Color and Picture objects. Then, we revisit our input/output libraries in the context of object-oriented programming, enabling us to access information from files and the web.

Color

Color is a sensation in the eye from electromagnetic radiation. Since we want to view and manipulate color images on our computers, color is a widely used abstraction in computer graphics, and Java provides a Color data type. In professional publishing, in print, and on the web, working with color is a complex task. For example, the appearance of a color image depends in a significant way on the medium used to present it. The Color data type separates the creative designer’s problem of specifying a desired color from the system’s problem of faithfully reproducing it.

Java has hundreds of data types in its libraries, so we need to explicitly list which Java libraries we are using in our program to avoid naming conflicts. Specifically, we include the statement

import java.awt.Color;

at the beginning of any program that uses Color. (Until now, we have been using standard Java libraries or our own, so there has been no need to import them.)

To represent color values, Color uses the RGB color model where a color is defined by three integers (each between 0 and 255) that represent the intensity of the red, green, and blue (respectively) components of the color. Other color values are obtained by mixing the red, green, and blue components. That is, the data-type values of Color are three 8-bit integers. We do not need to know whether the implementation uses int, short, or char values to represent these integers. With this convention, Java is using 24 bits to represent each color and can represent 2563 = 224 ≈ 16.7 million possible colors. Scientists estimate that the human eye can distinguish only about 10 million distinct colors.

red

green

blue

 

255

0

0

red

0

255

0

green

0

0

255

blue

0

0

0

black

100

100

100

dark gray

255

255

255

white

255

255

0

yellow

255

0

255

magenta

9

90

166

this color

Some color values

The Color data type has a constructor that takes three integer arguments. For example, you can write

Color red      = new Color(255,   0,   0);
Color bookBlue = new Color(  9,  90, 166);

to create objects whose values represent pure red and the blue used to print this book, respectively. We have been using colors in StdDraw since SECTION 1.5, but have been limited to a set of predefined colors, such as StdDraw.BLACK, StdDraw. RED, and StdDraw.PINK. Now you have millions of colors available for your use. AlbersSquares (PROGRAM 3.1.2) is a StdDraw client that allows you to experiment with them.


Program 3.1.2 Albers squares

import java.awt.Color;

public class AlbersSquares
{
    public static void main(String[] args)
    {
       int r1 = Integer.parseInt(args[0]);
       int g1 = Integer.parseInt(args[1]);
       int b1 = Integer.parseInt(args[2]);
       Color c1 = new Color(r1, g1, b1);

       int r2 = Integer.parseInt(args[3]);
       int g2 = Integer.parseInt(args[4]);
       int b2 = Integer.parseInt(args[5]);
       Color c2 = new Color(r2, g2, b2);

       StdDraw.setPenColor(c1);
       StdDraw.filledSquare(.25, 0.5, 0.2);
       StdDraw.setPenColor(c2);
       StdDraw.filledSquare(.25, 0.5, 0.1);
       StdDraw.setPenColor(c2);
       StdDraw.filledSquare(.75, 0.5, 0.2);
       StdDraw.setPenColor(c1);
       StdDraw.filledSquare(.75, 0.5, 0.1);
    }
}

r1, g1, b1 | RGB values
    c1     | first color
r2, g2, b2 | RGB values
    c2     | second color

This program displays the two colors entered in RGB representation on the command line in the familiar format developed in the 1960s by the color theorist Josef Albers, which revolutionized the way that people think about color.


A statement and the corresponding output are shown.

As usual, when we address a new abstraction, we are introducing you to Color by describing the essential elements of Java’s color model, not all of the details. The API for Color contains several constructors and more than 20 methods; the ones that we will use are briefly summarized next.

public class java.awt.Color

 

Color(int r, int g, int b)

int

getRed()

red intensity

int

getGreen()

green intensity

int

getBlue()

blue intensity

Color

brighter()

brighter version of this color

Color

darker()

darker version of this color

String

toString()

string representation of this color

String

equals(Object c)

is this color’s value the same as c ?

See the online documentation and booksite for other available methods.

Excerpts from the API for Java’s Color data type

Our primary purpose is to use Color as an example to illustrate object-oriented programming, while at the same time developing a few useful tools that we can use to write programs that process colors. Accordingly, we choose one color property as an example to convince you that writing object-oriented code to process abstract concepts like color is a convenient and useful approach.

Luminance

The quality of the images on modern displays such as LCD monitors, plasma TVs, and cellphone screens depends on an understanding of a color property known as monochrome luminance, or effective brightness. A standard formula for luminance is derived from the eye’s sensitivity to red, green, and blue. It is a linear combination of the three intensities: if a color’s red, green, and blue values are r, g, and b, respectively, then its monochrome luminance Y is defined by this equation:

Y = 0.299 r + 0.587g + 0.114b

Since the coefficients are positive and sum to 1, and the intensities are all integers between 0 and 255, the luminance is a real number between 0 and 255.

Grayscale

The RGB color model has the property that when all three color intensities are the same, the resulting color is on a grayscale that ranges from black (all 0s) to white (all 255s). To print a color photograph in a black-and-white newspaper (or a book), we need a function to convert from color to grayscale. A simple way to convert a color to grayscale is to replace the color with a new one whose red, green, and blue values equal its monochrome luminance.

A tabular representation of the grayscale example is shown.
Color compatibility

The monochrome luminance is also crucial in determining whether two colors are compatible, in the sense that printing text in one of the colors on a background in the other color will be readable. A widely used rule of thumb is that the difference between the luminance of the foreground and background colors should be at least 128. For example, black text on a white background has a luminance difference of 255, but black text on a (book) blue background has a luminance difference of only 74. This rule is important in the design of advertising, road signs, websites, and many other applications. Luminance (PROGRAM 3.1.3) is a library of static methods that we can use to convert a color to grayscale and to test whether two colors are compatible. The static methods in Luminance illustrate the utility of using data types to organize information. Using Color objects as arguments and return values substantially simplifies the implementation: the alternative of passing around three intensity values is cumbersome and returning multiple values is not possible without reference types.

An illustration shows the compatibility example.

Having an abstraction for color is important not just for direct use, but also in building higher-level data types that have Color values. Next, we illustrate this point by building on the color abstraction to develop a data type that allows us to write programs to process digital images.


Program 3.1.3 Luminance library

import java.awt.Color;

public class Luminance
{
   public static double intensity(Color color)
   {  // Monochrome luminance of color.
      int r = color.getRed();
      int g = color.getGreen();
      int b = color.getBlue();
      return 0.299*r + 0.587*g + 0.114*b;
   }

   public static Color toGray(Color color)
   {  // Use luminance to convert to grayscale.
      int y = (int) Math.round(intensity(color));
      Color gray = new Color(y, y, y);
      return gray;
   }

   public static boolean areCompatible(Color a, Color b)
   {  // True if colors are compatible, false otherwise.
      return Math.abs(intensity(a) - intensity(b)) >= 128.0;

   }

   public static void main(String[] args)
   {  // Are the two specified RGB colors compatible?
      int[] a = new int[6];
      for (int i = 0; i < 6; i++)
         a[i] = Integer.parseInt(args[i]);
      Color c1 = new Color(a[0], a[1], a[2]);
      Color c2 = new Color(a[3], a[4], a[5]);
      StdOut.println(areCompatible(c1, c2));
   }
}

r, g, b | RGB values

y | luminance of color

a[] | int values of args[]
 c1 | first color
 c2 | second color

This library comprises three important functions for manipulating color: monochrome luminance, conversion to grayscale, and background/foreground compatibility.


% java Luminance 232 232 232    0   0   0
true
% java Luminance   9  90 166  232 232 232
true
% java Luminance   9  90 166    0   0   0
false

Digital image processing

You are familiar with the concept of a photograph. Technically, we might define a photograph as a two-dimensional image created by collecting and focusing visible wavelengths of electromagnetic radiation that constitutes a representation of a scene at a point in time. That technical definition is beyond our scope, except to note that the history of photography is a history of technological development. During the last century, photography was based on chemical processes, but its future is now based in computation. Your camera and your cellphone are computers with lenses and light-sensitive devices capable of capturing images in digital form, and your computer has photo-editing software that allows you to process those images. You can crop them, enlarge and reduce them, adjust the contrast, brighten or darken them, remove redeye, or perform scores of other operations. Many such operations are remarkably easy to implement, given a simple basic data type that captures the idea of a digital image, as you will now see.

Digital images

Which set of values do we need to process digital images, and which operations do we need to perform on those values? The basic abstraction for computer displays is the same one that is used for digital photographs and is very simple: a digital image is a rectangular grid of pixels (picture elements), where the color of each pixel is individually defined. Digital images are sometimes referred to as raster or bitmapped images. In contrast, the types of images that we produce with StdDraw (which involve geometric objects such as points, lines, circles, and squares) are referred to as vector images.

The anatomy of a digital image is depicted.

Our class Picture is a data type for digital images whose definition follows immediately from the digital image abstraction. The set of values is nothing more than a two-dimensional matrix of Color values, and the operations are what you might expect: create a blank image with a given width and height, load an image from a file, set the value of a pixel to a given color, return the color of a given pixel, return the width or the height, show the image in a window on your computer screen, and save the image to a file. In this description, we intentionally use the word matrix instead of array to emphasize that we are referring to an abstraction (a matrix of pixels), not a specific implementation (a Java two-dimensional array of Color objects). You do not need to know how a data type is implemented to be able to use it. Indeed, typical images have so many pixels that implementations are likely to use a more efficient representation than an array of Color objects. In any case, to write client programs that manipulate images, you just need to know this API:

public class Picture

 

Picture(String filename)

create a picture from a file

 

Picture(int w, int h)

create a blank w-by-h picture

int

width()

return the width of the picture

int

height()

return the height of the picture

Color

get(int col, int row)

return the color of pixel (col, row)

void

set(int col, int row, Color c)

set the color of pixel (col, row) to c

void

show()

display the picture in a window

void

save(String filename)

save the picture to a file

API for our data type for image processing

By convention, (0, 0) is the upper-leftmost pixel, so the image is laid as in the customary order for two-dimensional arrays (by contrast, the convention for StdDraw is to have the point (0,0) at the lower-left corner, so that drawings are oriented as in the customary manner for Cartesian coordinates). Most image-processing programs are filters that scan through all of the pixels in a source image and then perform some computation to determine the color of each pixel in a target image. The supported file formats for the first constructor and the save() method are the widely used PNG and JPEG formats, so that you can write programs to process your own digital photos and add the results to an album or a website. The show() window also has an interactive option for saving to a file. These methods, together with Java’s Color data type, open the door to image processing.

Grayscale

You will find many examples of color images on the booksite, and all of the methods that we describe are effective for full-color images, but all our example images in this book will be grayscale. Accordingly, our first task is to write a program that converts images from color to grayscale. This task is a prototypical image-processing task: for each pixel in the source, we set a pixel in the target to a different color. Grayscale (PROGRAM 3.1.4) is a filter that takes a file name from the command line and produces a grayscale version of that image. It creates a new Picture object initialized with the color image, then sets the color of each pixel to a new Color having a grayscale value computed by applying the toGray() method in Luminance (PROGRAM 3.1.3) to the color of the corresponding pixel in the source.


Program 3.1.4 Converting color to grayscale

import java.awt.Color;

public class Grayscale
{
   public static void main(String[] args)
   {  // Show image in grayscale.
      Picture picture = new Picture(args[0]);
      for (int col = 0; col < picture.width(); col++)
      {
         for (int row = 0; row < picture.height(); row++)
         {
            Color color = picture.get(col, row);
            Color gray  = Luminance.toGray(color);
            picture.set(col, row, gray);
         }
      }
      picture.show();
   }
}

 picture | image from file
col, row | pixel coordinates
  color  | pixel color
  gray   | pixel grayscale

This program illustrates a simple image-processing client. First, it creates a Picture object initialized with an image file named by the command-line argument. Then it converts each pixel in the picture to grayscale by creating a grayscale version of each pixel’s color and resetting the pixel to that color. Finally, it shows the picture. You can perceive individual pixels in the picture on the right, which was upscaled from a low-resolution picture (see “Scaling” on the next page).


A statement and the associated output show a grayscale picture. The statement reads “percentage java Grayscale mandrill.jpg” and the output shows the picture of a mandrill in gray scale.

A statement and the associated output show a grayscale picture. The statement reads “percentage java Grayscale darwin.jpg” and the output shows the picture of Darwin in grayscale.

Scaling

One of the most common image-processing tasks is to make an image smaller or larger. Examples of this basic operation, known as scaling, include making small thumbnail photos for use in a chat room or a cellphone, changing the size of a high-resolution photo to make it fit into a specific space in a printed publication or on a web page, and zooming in on a satellite photograph or an image produced by a microscope. In optical systems, we can just move a lens to achieve a desired scale, but in digital imagery, we have to do more work.

In some cases, the strategy is clear. For example, if the target image is to be half the size (in each dimension) of the source image, we simply choose half the pixels, say, by deleting half the rows and half the columns. This technique is known as sampling. If the target image is to be double the size (in each dimension) of the source image, we can replace each source pixel by four target pixels of the same color. Note that we can lose information when we downscale, so halving an image and then doubling it generally does not give back the same image.

An illustration with two sections depicts the scaling of a digital image.

A single strategy is effective for both downscaling and upscaling. Our goal is to produce the target image, so we proceed through the pixels in the target, one by one, scaling each pixel’s coordinates to identify a pixel in the source whose color can be assigned to the target. If the width and height of the source are ws and hs (respectively) and the width and height of the target are wt and ht (respectively), then we scale the column index by ws/wt and the row index by hs/ht. That is, we get the color of the pixel in column c and row r and of the target from column c×ws /wt and row r×hs /ht in the source. For example, if we are halving the size of an image, the scale factors are 2, so the pixel in column 3 and row 2 of the target gets the color of the pixel in column 6 and row 4 of the source; if we are doubling the size of the image, the scale factors are 1/2, so the pixel in column 4 and row 6 of the target gets the color of the pixel in column 2 and row 3 of the source. Scale (PROGRAM 3.1.5) is an implementation of this strategy. More sophisticated strategies can be effective for low-resolution images of the sort that you might find on old web pages or from old cameras. For example, we might downscale to half size by averaging the values of four pixels in the source to make one pixel in the target. For the high-resolution images that are common in most applications today, the simple approach used in Scale is effective.


Program 3.1.5 Image scaling

public class Scale
{
   public static void main(String[] args)
   {
      int w = Integer.parseInt(args[1]);
      int h = Integer.parseInt(args[2]);
      Picture source = new Picture(args[0]);
      Picture target = new Picture(w, h);
      for (int colT = 0; colT < w; colT++)
      {
         for (int rowT = 0; rowT < h; rowT++)
         {
            int colS = colT * source.width()  / w;
            int rowS = rowT * source.height() / h;
            target.set(colT, rowT, source.get(colS, rowS));
         }
      }
      source.show();
      target.show();
   }
}

   w, h    | target dimensions
  source   | source image
  target   | target image
colT, rowT | target pixel coords
colS, rowS | source pixel coords

This program takes the name of an image file and two integers (width w and height h) as command-line arguments, scales the picture to w-by-h, and displays both images.


A statement and the corresponding output for different parameters are shown.

The same basic idea of computing the color value of each target pixel as a function of the color values of specific source pixels is effective for all sorts of image-processing tasks. Next, we consider one more example, and you will find numerous other examples in the exercises and on the booksite.

Fade effect

Our final image-processing example is an entertaining computation where we transform one image into another in a series of discrete steps. Such a transformation is sometimes known as a fade effect. Fade (PROGRAM 3.1.6) is a Picture and Color client that uses a linear interpolation strategy to implement this effect. It computes n–1 intermediate pictures, with each pixel in picture i being a weighted average of the corresponding pixels in the source and target. The static method blend() implements the interpolation: the source color is weighted by a factor of 1 – i / n and the target color by a factor of i / n (when i is 0, we have the source color, and when i is n, we have the target color). This simple computation can produce striking results. When you run Fade on your computer, the change appears to happen dynamically. Try running it on some images from your photo library. Note that Fade assumes that the images have the same width and height; if you have images for which this is not the case, you can use Scale to created a scaled version of one or both of them for Fade.

A statement and the corresponding output depict the Fade effect.

Program 3.1.6 Fade effect

import java.awt.Color;

public class Fade
{
   public static Color blend(Color c1, Color c2, double alpha)
   {  // Compute blend of colors c1 and c2, weighted by alpha.
      double r = (1-alpha)*c1.getRed()   + alpha*c2.getRed();
      double g = (1-alpha)*c1.getGreen() + alpha*c2.getGreen();
      double b = (1-alpha)*c1.getBlue()  + alpha*c2.getBlue();
      return new Color((int) r, (int) g, (int) b);
   }
   public static void main(String[] args)
   {  // Show m-image fade sequence from source to target.
      Picture source = new Picture(args[0]);
      Picture target = new Picture(args[1]);
      int n = Integer.parseInt(args[2]);
      int width  = source.width();
      int height = source.height();
      Picture picture = new Picture(width, height);
      for (int i = 0; i <= n; i++)
      {
         for (int col = 0; col < width; col++)
         {
            for (int row = 0; row < height; row++)
            {
               Color c1 = source.get(col, row);
               Color c2 = target.get(col, row);
               double alpha = (double) i / n;
               Color color = blend(c1, c2, alpha);
               picture.set(col, row, color);
            }
         }
         picture.show();
      }
   }
}

   n    | number of pictures
picture | current picture
   i    | picture counter
  c1    | source color
  c2    | target color
 color  | blended color

To fade from one picture into another in n steps, we set each pixel in picture i to a weighted average of the corresponding pixel in the source and destination pictures, with the source getting weight 1 – i / n and the destination getting weight i / n. An example transformation is shown on the facing page.


Input and output revisited

In SECTION 1.5 you learned how to read and write numbers and text using StdIn and StdOut and to make drawings with StdDraw. You have certainly come to appreciate the utility of these mechanism in getting information into and out of your programs. One reason that they are convenient is that the “standard” conventions make them accessible from anywhere within a program. One disadvantage of these conventions is that they leave us dependent upon the operating system’s piping and redirection mechanism for access to files, and they restrict us to working with just one input file, one output file, and one drawing for any given program. With object-oriented programming, we can define mechanisms that are similar to those in StdIn, StdOut, and StdDraw but allow us to work with multiple input streams, output streams, and drawings within one program.

Specifically, we define in this section the data types In, Out, and Draw for input streams, output streams, and drawings, respectively. As usual, you must make these classes accessible to Java (see the Q&A at the end of SECTION 1.5).

An illustration depicts the bird’s-eye view of a java program (revisited again).

These data types give us the flexibility that we need to address many common data-processing tasks within our Java programs. Rather than being restricted to just one input stream, one output stream, and one drawing, we can easily define multiple objects of each type, connecting the streams to various sources and destinations. We also get the flexibility to assign such objects to variables, pass them as arguments or return values from methods, and create arrays of them, manipulating them just as we manipulate objects of any type. We will consider several examples of their use after we have presented the APIs.

Input stream data type

Our In data type is a more general version of StdIn that supports reading numbers and text from files and websites as well as the standard input stream. It implements the input stream data type, with the API at the bottom of this page. Instead of being restricted to one abstract input stream (standard input), this data type gives you the ability to directly specify the source of an input stream. Moreover, that source can be either a file or a website. When you call the constructor with a string argument, the constructor first tries to find a file in the current directory of your local computer with that name. If it cannot do so, it assumes the argument is a website name and tries to connect to that website. (If no such website exists, it generates a run-time exception.) In either case, the specified file or website becomes the source of the input for the input stream object thus created, and the read*() methods will read input from that stream.

public class In

 

In()

create an input stream from standard input

 

In(String name)

create an input stream from a file or website

instance methods that read individual tokens from the input stream

boolean

isEmpty()

is standard input empty (or only whitespace)?

int

readInt()

read a token, convert it to an int, and return it

double

readDouble()

...

read a token, convert it to a double, and return it

instance methods that read characters from the input stream

boolean

hasNextChar()

does standard input have any remaining characters?

char

readChar()

read a character from standard input and return it

instance methods that read lines from the input stream

boolean

hasNextLine()

does standard input have a next line?

String

readLine()

read the rest of the line and return it as a String

instance methods that read the rest of the input stream

int[]

readAllInts()

read all remaining tokens; return as array of integers

double[]

readAllDoubles()

...

read all remaining tokens; return as array of doubles

Note: All operations supported by StdIn are also supported for In objects.

API for our data type for input streams

This arrangement makes it possible to process multiple files within the same program. Moreover, the ability to directly access the web opens up the whole web as potential input for your programs. For example, it allows you to process data that is provided and maintained by someone else. You can find such files all over the web. Scientists now regularly post data files with measurements or results of experiments, ranging from genome and protein sequences to satellite photographs to astronomical observations; financial services companies, such as stock exchanges, regularly publish on the web detailed information about the performance of stock and other financial instruments; governments publish election results; and so forth. Now you can write Java programs that read these kinds of files directly. The In data type gives you a great deal of flexibility to take advantage of the multitude of data sources that are now available.

Output stream data type

Similarly, our Out data type is a more general version of StdOut that supports printing text to a variety of output streams, including standard output and files. Again, the API specifies the same methods as its StdOut counterpart. You specify the file that you want to use for output by using the one-argument constructor with the file’s name as the argument. Out interprets this string as the name of a new file on your local computer, and sends its output there. If you use the no-argument constructor, then you obtain the standard output stream.

public class Out

 

Out()

create an output stream to standard output

 

Out(String name)

create an output stream to a file

void

print(String s)

print s to the output stream

void

println(String s)

print s and a newline to the output stream

void

println()

print a newline to the output stream

void

printf(String format, ...)

print the arguments to the output stream, as specified by the format string format

API for our data type for output streams


Program 3.1.7 Concatenating files

public class Cat
{
   public static void main(String[] args)
   {
      Out out = new Out(args[args.length-1]);
      for (int i = 0; i < args.length - 1; i++)
      {
         In in = new In(args[i]);
         String s = in.readAll();
         out.println(s);
      }
   }
}

out | output stream
 i  | argument index
 in | current input stream
 s  | contents of in

This program creates an output file whose name is given by the last command-line argument and whose contents are the concatenation of the input files whose names are given as the other command-line arguments.


% more in1.txt
This is
% more in2.txt
a tiny
test.

% java Cat in1.txt in2.txt out.txt
% more out.txt
This is
a tiny
test.

File concatenation and filtering

PROGRAM 3.1.7 is a sample client of In and Out that uses multiple input streams to concatenate several input files into a single output file. Some operating systems have a command known as cat that implements this function. However, a Java program that does the same thing is perhaps more useful, because we can tailor it to filter the input files in various ways: we might wish to ignore irrelevant information, change the format, or select only some of the data, to name just a few examples. We now consider one example of such processing, and you will find several others in the exercises.

Screen scraping

The combination of the In data type (which allows us to create an input stream from any page on the web) and the String data type (which provides powerful tools for processing text strings) opens up the entire web to direct access by our Java programs, without any direct dependence on the operating system or browser. One paradigm is known as screen scraping: the goal is to extract some information from a web page with a program, rather than having to browse to find it. To do so, we take advantage of the fact that many web pages are defined with text files in a highly structured format (because they are created by computer programs!). Your browser has a mechanism that allows you to examine the source code that produces the web page that you are viewing, and by examining that source you can often figure out what to do.

...
(GOOG)</h2> <span class="rtq_
exch"><span class="rtq_dash">-</span>
NMS  </span><span class="wl_sign">
</span></div></div>
<div class="yfi_rt_quote_summary_rt_top
sigfig_promo_1"><div>
<span class="time_rtq_ticker">
<span id="yfs_l84goog">1,100.62</span>
</span> <span class="down_r time_rtq_
content"><span id="yfs_c63_goog">
...

HTML code from the web

Suppose that we want to take a stock trading symbol as a command-line argument and print that stock’s current trading price. Such information is published on the web by financial service companies and Internet service providers. For example, you can find the stock price of a company whose symbol is goog by browsing to http://finance.yahoo.com/q?s=goog. Like many web pages, the name encodes an argument (goog), and we could substitute any other ticker symbol to get a web page with financial information for any other company. Also, like many other files on the web, the referenced file is a text file, written in a formatting language known as HTML. From the point of view of a Java program, it is just a String value accessible through an In object. You can use your browser to download the source of that file, or you could use

% java Cat "http://finance.yahoo.com/q?s=goog" goog.html

to put the source into a file goog.html on your local computer (though there is no real need to do so). Now, suppose that goog is trading at $1,100.62 at the moment. If you search for the string "1,100.62" in the source of that page, you will find the stock price buried within some HTML code. Without having to know details of HTML, you can figure out something about the context in which the price appears. In this case, you can see that the stock price is enclosed between the substrings <span id="yfs_l84goog"> and </span>.

With the String data type’s indexOf() and substring() methods, you easily can grab this information, as illustrated in StockQuote (PROGRAM 3.1.8). This program depends on the web page format used by http://finance.yahoo.com; if this format changes, StockQuote will not work. Indeed, by the time you read this page, the format may have changed. Even so, making appropriate changes is not likely to be difficult. You can entertain yourself by embellishing StockQuote in all kinds of interesting ways. For example, you could grab the stock price on a periodic basis and plot it, compute a moving average, or save the results to a file for later analysis. Of course, the same technique works for sources of data found all over the web, as you can see in examples in the exercises at the end of this section and on the booksite.

Extracting data

The ability to maintain multiple input and output streams gives us a great deal of flexibility in meeting the challenges of processing large amounts of data coming from a variety of sources. We consider one more example: Suppose that a scientist or a financial analyst has a large amount of data within a spreadsheet program. Typically such spreadsheets are tables with a relatively large number of rows and a relatively small number of columns. You are not likely to be interested in all the data in the spreadsheet, but you may be interested in a few of the columns. You can do some calculations within the spreadsheet program (this is its purpose, after all), but you certainly do not have the flexibility that you have with Java programming. One way to address this situation is to have the spreadsheet export the data to a text file, using some special character to delimit the columns, and then write a Java program that reads that file from an input stream. One standard practice is to use commas as delimiters: print one line per row, with commas separating column entries. Such files are known as comma-separated-value or .csv files. With the split() method in Java’s String data type, we can read the file line-by-line and isolate the data that we want. We will see several examples of this approach later in the book. Split (PROGRAM 3.1.9) is an In and Out client that goes one step further: it creates multiple output streams and makes one file for each column.

These examples are convincing illustrations of the utility of working with text files, with multiple input and output streams, and with direct access to web pages. Web pages are written in HTML precisely so that they are accessible to any program that can read strings. People use text formats such as .csv files rather than data formats that are beholden to particular applications precisely to allow as many people as possible to access the data with simple programs like Split.


Program 3.1.8 Screen scraping for stock quotes

public class StockQuote
{
   private static String readHTML(String symbol)
   {  // Return HTML corresponding to stock symbol.
      In page = new In("http://finance.yahoo.com/q?s=" + symbol);
      return page.readAll();
   }

   public static double priceOf(String symbol)
   {  // Return current stock price for symbol.
      String html = readHTML(symbol);
      int p     = html.indexOf("yfs_l84", 0);
      int from  = html.indexOf(">", p);
      int to    = html.indexOf("</span>", from);
      String price = html.substring(from + 1, to);
      return Double.parseDouble(price.replace(",", ""));
   }

   public static void main(String[] args)
   {  // Print price of stock specified by symbol.
      String symbol = args[0];
      double price = priceOf(symbol);
      StdOut.println(price);
   }
}

symbol | stock symbol
 page  | input stream

 html | contents of page
   p  | yfs_184 index
 from | > index
  to  | </span> index
price | current price

This program accepts a stock ticker symbol as a command-line argument and prints to standard output the current stock price for that stock, as reported by the website http://finance.yahoo.com. It uses the indexOf(), substring(), and replace() methods from String.


% java StockQuote goog
1100.62
% java StockQuote adbe
70.51


Program 3.1.9 Splitting a file

public class Split
{
   public static void main(String[] args)
   {  // Split file by column into n files.
      String name = args[0];
      int n = Integer.parseInt(args[1]);
      String delimiter = ",";

      // Create output streams.
      Out[] out = new Out[n];
      for (int i = 0; i < n; i++)
         out[i] = new Out(name + i + ".txt");

      In in = new In(name + ".csv");
      while (in.hasNextLine())
      {  // Read a line and write fields to output streams.
         String line = in.readLine();
         String[] fields = line.split(delimiter);
         for (int i = 0; i < n; i++)
            out[i].println(fields[i]);
      }
   }
}

   name   | base file name
    n     | number of fields
delimiter | delimiter (comma)
    in    | input stream
   out[]  | output streams
   line   | current line
 fields[] | values in current line

This program uses multiple output streams to split a .csv file into separate files, one for each comma-delimited field. The name of the output file corresponding to the ith field is formed by concatenating i and then .csv to the end of the original file name.


% more DJIA.csv
...
31-Oct-29,264.97,7150000,273.51
30-Oct-29,230.98,10730000,258.47
29-Oct-29,252.38,16410000,230.07
28-Oct-29,295.18,9210000,260.64
25-Oct-29,299.47,5920000,301.22
24-Oct-29,305.85,12900000,299.47
23-Oct-29,326.51,6370000,305.85
22-Oct-29,322.03,4130000,326.51
21-Oct-29,323.87,6090000,320.91
...

% java Split DJIA 4
% more DJIA2.txt
...
7150000
10730000
16410000
9210000
5920000
12900000
6370000
4130000
6090000
...

Drawing data type

When using the Picture data type that we considered earlier in this section, we could write programs that manipulated multiple pictures, arrays of pictures, and so forth, precisely because the data type provides us with the capability for computing with Picture objects. Naturally, we would like the same capability for computing with the kinds of geometric objects that we create with StdDraw. Accordingly, we have a Draw data type with the following API:

public class Draw

 

Draw()

drawing commands

void

line(double x0, double y0, double x1, double y1)

void

point(double x, double y)

void

circle(double x, double y, double radius)

void

filledCircle(double x, double y, double radius)

...

control commands

void

setXscale(double x0, double x1)

void

setYscale(double y0, double y1)

void

setPenRadius(double radius)

...

Note: All operations supported by StdDraw are also supported for Draw objects.

As for any data type, you can create a new drawing by using new to create a Draw object, assign it to a variable, and use that variable name to call the methods that create the graphics. For example, the code

Draw draw = new Draw();
draw.circle(0.5, 0.5, 0.2);

draws a circle in the center of a window on your screen. As with Picture, each drawing has its own window, so that you can address applications that call for displaying multiple different drawings at the same time.

Properties of reference types

Now that you have seen several examples of reference types (Charge, Color, Picture, String, In, Out, and Draw) and client programs that use them, we discuss in more detail some of their essential properties. To a large extent, Java protects novice programmers from having to know these details. Experienced programmers, however, know that a firm understanding of these properties is helpful in writing correct, effective, and efficient object-oriented programs.

A reference captures the distinction between a thing and its name. This distinction is a familiar one, as illustrated in these examples:

type

typical object

typical name

website

our booksite

http://introcs.cs.princeton.edu

person

father of computer science

Alan Turing

planet

third rock from the sun

Earth

building

our office

35 Olden Street

ship

superliner that sank in 1912

RMS Titanic

number

circumference/diameter of a circle

π

Picture

new Picture("mandrill.jpg")

picture

A given object may have multiple names, but each object has its own identity. We can create a new name for an object without changing the object’s value (via an assignment statement), but when we change an object’s value (by invoking an instance method), all of the object’s names refer to the changed object.

The following analogy may help you keep this crucial distinction clear in your mind. Suppose that you want to have your house painted, so you write the street address of your house in pencil on a piece of paper and give it to a few house painters. Now, if you hire one of the painters to paint the house, it becomes a different color. No changes have been made to any of the pieces of paper, but the house that they all refer to has changed. One of the painters might erase what you’ve written and write the address of another house, but changing what is written on one piece of paper does not change what is written on another piece of paper. Java references are like the pieces of paper: they hold names of objects. Changing a reference does not change the object, but changing an object makes the change apparent to everyone having a reference to it.

The famous Belgian artist René Magritte captured this same concept in a painting where he created an image of a pipe along with the caption ceci n’est pas une pipe (this is not a pipe) below it. We might interpret the caption as saying that the image is not actually a pipe, just an image of a pipe. Or perhaps Magritte meant that the caption is neither a pipe nor an image of a pipe, just a caption! In the present context, this image reinforces the idea that a reference to an object is nothing more than a reference; it is not the object itself.

A picture of a pipe with a caption “Ceci n’est pas une pipe” is shown.
Aliasing

An assignment statement with a reference type creates a second copy of the reference. The assignment statement does not create a new object, just another reference to an existing object. This situation is known as aliasing: both variables refer to the same object. Aliasing also arises when passing an object reference to a method: The parameter variable becomes another reference to the corresponding object. The effect of aliasing is a bit unexpected, because it is different from that for variables holding values of a primitive type. Be sure that you understand the difference. If x and y are variables of a primitive type, then the assignment statement x = y copies the value of y to x. For reference types, the reference is copied (not the value).

Aliasing is a common source of bugs in Java programs, as illustrated by the following example:

Picture a = new Picture("mandrill.jpg");
Picture b = a;
a.set(col, row, color1);  // a updated
b.set(col, row, color2);  // a updated again

After the second assignment statement, variables a and b both refer to the same Picture object. Changing the state of an object impacts all code involving aliased variables referencing that object. We are used to thinking of two different variables of primitive types as being independent, but that intuition does not carry over to reference objects. For example, if the preceding code assumes that a and b refer to different Picture objects, then it will produce the wrong result. Such aliasing bugs are common in programs written by people without much experience in using reference objects (that’s you, so pay attention here!).

A large rectangle divided into cells depicts the concept of Aliasing.
Immutable types

For this very reason, it is common to define data types whose values cannot change. An object from a data type is immutable if its data-type value cannot change once created. An immutable data type is one in which all objects of that type are immutable. For example, String is an immutable data type because there are no operations available to clients that change a string’s characters. In contrast, a mutable data type is one in which objects of that type have values that are designed to change. For example, Picture is mutable data type because we can change pixel colors. We will consider immutability in more detail in SECTION 3.3.

Comparing objects

When applied to reference types, the == operator checks whether the two object references are equal (that is, whether they point to the same object). That is not the same as checking whether the objects have the same value. For example, consider the following code:

   Color a = new Color(160, 82, 45);
   Color b = new Color(160, 82, 45);
   Color c = b;

Now (a == b) is false and (b == c) is true, but when you are thinking about equality testing for Color, you probably are thinking that you want to test whether their values are the same—you might want all three of these to test as equal. Java does not have an automatic mechanism for testing the equality of object values, which leaves programmers with the opportunity (and responsibility) to define it for themselves by defining for any class a customized method named equals(), as described in SECTION 3.3. For example, Color has such a method, and a.equals(c) is true in our example. String also contains an implementation of equals() because we often want to test whether two String objects have the same value (the same sequence of characters).

Pass by value

When you call a method with arguments, the effect in Java is as if each argument were to appear on the right-hand side of an assignment statement with the corresponding argument name on the left-hand side. That is, Java passes a copy of the argument value from the caller to the method. If the argument value is a primitive type, Java passes a copy of that value; if the argument value is an object reference, Java passes a copy of the object reference. This arrangement is known as pass by value.

One important consequence of this arrangement is that a method cannot directly change the value of a caller’s variable. For primitive types, this policy is what we expect (the two variables are independent), but each time that we use a reference type as a method argument, we create an alias, so we must be cautious. For example, if we pass an object reference of type Picture to a method, the method cannot change the caller’s object reference (for example, make it refer to a different Picture), but it can change the value of the object, such as by invoking the set() method to change a pixel’s color.

Arrays are objects

In Java, every value of any nonprimitive type is an object. In particular, arrays are objects. As with strings, special language support is provided for certain operations on arrays: declarations, initialization, and indexing. As with any other object, when we pass an array to a method or use an array variable on the right-hand side of an assignment statement, we are making a copy of the array reference, not a copy of the array. Arrays are mutable objects—a convention that is appropriate for the typical case where we expect the method to be able to modify the array by rearranging the values of its elements, as in, for example, the exchange() and shuffle() methods that we considered in SECTION 2.1.

Arrays of objects

Array elements can be of any type, as we have already seen on several occasions, from args[] (an array of strings) in our main() implementations, to the array of Out objects in PROGRAM 3.1.9. When we create an array of objects, we do so in two steps:

• Create the array by using new and the square bracket syntax for array creation

• Create each object in the array, by using new to call a constructor

A large rectangle divided into cells depicts the representation of an array of objects.

For example, we would use the following code to create an array of two Color objects:

Color[] a = new Color[2];
a[0] = new Color(255, 255, 0);
a[1] = new Color(160, 82, 45);

Naturally, an array of objects in Java is an array of object references, not the objects themselves. If the objects are large, then we gain efficiency by not having to move them around, just their references. If they are small, we lose efficiency by having to follow a reference each time we need to get to some information.

Safe pointers

To provide the capability to manipulate memory addresses that refer to data, many programming languages include the pointer (which is like the Java reference) as a primitive data type. Programming with pointers is notoriously error prone, so operations provided for pointers need to be carefully designed to help programmers avoid errors. Java takes this point of view to an extreme (one that is favored by many modern programming-language designers). In Java, there is only one way to create a reference (with new) and only one way to manipulate that reference (with an assignment statement). That is, the only things that a programmer can do with references is to create them and copy them. In programming-language jargon, Java references are known as safe pointers, because Java can guarantee that each reference points to an object of the specified type (and not to an arbitrary memory address). Programmers used to writing code that directly manipulates pointers think of Java as having no pointers at all, but people still debate whether it is desirable to have unsafe pointers. In short, when you program in Java, you will not be directly manipulating memory addresses, but if you find yourself doing so in some other language in the future, be careful!

The statements of a program and the representation of an array with the orphaned object are shown.
Orphaned objects

The ability to assign different objects to a reference variable creates the possibility that a program may have created an object that it can no longer reference. For example, consider the three assignment statements in the figure at right. After the third assignment statement, not only do a and b refer to the same Color object (the one whose RGB values are 160, 82, and 45), but also there is no longer a reference to the Color object that was created and used to initialize b. The only reference to that object was in the variable b, and this reference was overwritten by the assignment, so there is no way to refer to the object again. Such an object is said to be orphaned. Objects are also orphaned when they go out of scope. Java programmers pay little attention to orphaned objects because the system automatically reuses the memory that they occupy, as we discuss next.

Memory management

Programs tend to create huge numbers of objects but have a need for only a small number of them at any given point in time. Accordingly, programming languages and systems need mechanisms to allocate memory for data-type values during the time they are needed and to free the memory when they are no longer needed (for an object, sometime after it is orphaned). Memory management is easier for primitive types because all of the information needed for memory allocation is known at compile time. Java (and most other systems) reserves memory for variables when they are declared and frees that memory when they go out of scope. Memory management for objects is more complicated: Java knows to allocate memory for an object when it is created (with new), but cannot know precisely when to free the memory associated with that object because the dynamics of a program in execution determine when the object is orphaned.

Memory leaks

In many languages (such as C and C++), the programmer is responsible for both allocating and freeing memory. Doing so is tedious and notoriously error prone. For example, suppose that a program deallocates the memory for an object, but then continues to refer to it (perhaps much later in the program). In the meantime, the system may have reallocated the same memory for another use, so all kinds of havoc can result. Another insidious problem occurs when a programmer neglects to ensure that the memory for an orphaned object is deallocated. This bug is known as a memory leak because it can result in a steadily increasing amount of memory devoted to orphaned objects (and therefore not available for use). The effect is that performance degrades, as if memory were leaking out of your computer. Have you ever had to reboot your computer because it was gradually getting less and less responsive? A common cause of such behavior is a memory leak in one of your applications.

Garbage collection

One of Java’s most significant features is its ability to automatically manage memory. The idea is to free the programmer from the responsibility of managing memory by keeping track of orphaned objects and returning the memory they use to a pool of free memory. Reclaiming memory in this way is known as garbage collection, and Java’s safe pointer policy enables it to do this efficiently and automatically. Programmers still debate whether the overhead of automatic garbage collection justifies the convenience of not having to worry about memory management. The same conclusion that we drew for pointers holds: when you program in Java, you will not be writing code to allocate and free memory, but if you find yourself doing so in some other language in the future, be careful!

For reference, we summarize the examples that we have considered in this section in the table below. These examples are chosen to help you understand the essential properties of data types and object-oriented programming.

A data type is a set of values and a set of operations defined on those values. With primitive data types, we worked with a small and simple set of values. Strings, colors, pictures, and I/O streams are high-level data types that indicate the breadth of applicability of data abstraction. You do not need to know how a data type is implemented to be able to use it. Each data type (there are hundreds in the Java libraries, and you will soon learn to create your own) is characterized by an API (application programming interface) that provides the information that you need to use it. A client program creates objects that hold data-type values and invokes instance methods to manipulate those values. We write client programs with the basic statements and control constructs that you learned in CHAPTERS 1 and 2, but now have the capability to work with a vast variety of data types, not just the primitive ones to which you have grown accustomed. With greater experience, you will find that this ability opens up new horizons in programming.

API

description

Color

colors

Picture

digital images

String

character strings

In

input streams

Out

output streams

Draw

drawings

Summary of data types in this section

When properly designed, data types lead to client programs that are clearer, easier to develop, and easier to maintain than equivalent programs that do not take advantage of data abstraction. The client programs in this section are testimony to this claim. Moreover, as you will see in the next section, implementing a data type is a straightforward application of the basic programming skills that you have already learned. In particular, addressing a large and complex application becomes a process of understanding its data and the operations to be performed on it, then writing programs that directly reflect this understanding. Once you have learned to do so, you might wonder how programmers ever developed large programs without using data abstraction.

Q&A

Q. Why the distinction between primitive and reference types?

A. Performance. Java provides the wrapper reference types Integer, Double, and so forth that correspond to primitive types and can be used by programmers who prefer to ignore the distinction (for details, see SECTION 3.3). Primitive types are closer to the types of data that are supported by computer hardware, so programs that use them usually run faster and consume less memory than programs that use the corresponding reference types.

Q. What happens if I forget to use new when creating an object?

A. To Java, it looks as though you want to call a static method with a return value of the object type. Since you have not defined such a method, the error message is the same as when you refer to an undefined symbol. If you compile the code

Color sienna = Color(160, 82, 45);

you get this error message:

cannot find symbol
symbol  : method Color(int,int,int)

Constructors do not provide return values (their signature has no return type)—they can only follow new. You get the same kind of error message if you provide the wrong number of arguments to a constructor or method.

Q. Why can we print an object x with the function call StdOut.println(x), as opposed to StdOut.println(x.toString())?

A. Good question. That latter code works fine, but Java saves us some typing by automatically invoking the toString() method in such situations. In SECTION 3.3, we will discuss Java’s mechanism for ensuring that this is the case.

Q. What is the difference between =, ==, and equals()?

A. The single equals sign (=) is the basis of the assignment statement—you certainly are familiar with that. The double equals sign (==) is a binary operator for checking whether its two operands are identical. If the operands are of a primitive type, the result is true if they have the same value, and false otherwise. If the operands are object references, the result is true if they refer to the same object, and false otherwise. That is, we use == to test object identity equality. The data-type method equals() is included in every Java type so that the implementation can provide the capability for clients to test whether two objects have the same value. Note that (a == b) implies a.equals(b), but not the other way around.

Q. How can I arrange to pass an array as an argument to a function in such a way that the function cannot change the values of the elements in the array?

A. There is no direct way to do so—arrays are mutable. In SECTION 3.3, you will see how to achieve the same effect by building a wrapper data type and passing an object reference of that type instead (see Vector, in PROGRAM 3.3.3).

Q. What happens if I forget to use new when creating an array of objects?

A. You need to use new for each object that you create, so when you create an array of n objects, you need to use new n + 1 times: once for the array and once for each of the n objects. If you forget to create the array:

Color[] colors;
colors[0] = new Color(255, 0, 0);

you get the same error message that you would get when trying to assign a value to any uninitialized variable:

variable colors might not have been initialized
      colors[0] = new Color(255, 0, 0);
      ^

In contrast, if you forget to use new when creating an object within the array and then try to use it to invoke a method:

Color[] colors = new Color[2];
int red = colors[0].getRed();

you get a NullPointerException. As usual, the best way to answer such questions is to write and compile such code yourself, then try to interpret Java’s error message. Doing so might help you more quickly recognize mistakes later.

Q. Where can I find more details on how Java implements references and garbage collection?

A. One Java system might differ completely from another. For example, one natural scheme is to use a pointer (machine address); another is to use a handle (a pointer to a pointer). The former gives faster access to data; the latter facilitates garbage collection.

Q. Why red, green, and blue instead of red, yellow, and blue?

A. In theory, any three colors that contain some amount of each primary would work, but two different color models have evolved: one (RGB) that has proven to produce good colors on television screens, computer monitors, and digital cameras, and the other (CMYK) that is typically used for the printed page (see EXERCISE 1.2.32). CMYK does include yellow (cyan, magenta, yellow, and black). Two different color models are appropriate because printed inks absorb color; thus, where there are two different inks, there are more colors absorbed and fewer reflected. Conversely, video displays emit color, so where there are two different-colored pixels, there are more colors emitted.

Q. What exactly is the purpose of an import statement?

A. Not much: it just saves some typing. For example, in PROGRAM 3.1.2, it enables you to abbreviate java.awt.Color with Color everywhere in your code.

Q. Is there anything wrong with allocating and deallocating thousands of Color objects, as in Grayscale (PROGRAM 3.1.4)?

A. All programming-language constructs come at some cost. In this case the cost is reasonable, since the time to allocate Color objects is tiny compared to the time to draw the image.

Q. Why does the String method call s.substring(i, j) return the substring of s starting at index i and ending at j-1 (and not j)?

A. Why do the indices of an array a[] go from 0 to a.length-1 instead of from 1 to length? Programming-language designers make choices; we live with them. One nice consequence of this convention is that the length of the extracted substring is j-i.

Q. What is the difference between pass by value and pass by reference?

Q. With pass by value, when you call a method with arguments, each argument is evaluated and a copy of the resulting value is passed to the method. This means that if a method directly modifies an argument variable, that modification is not visible to the caller. With pass by reference, the memory address of each argument is passed to the method. This means that if a method modifies an argument variable, that modification is visible to the caller. Technically, Java is a purely pass-by-value language, in which the value is either a primitive-type value or an object reference. As a result, when you pass a primitive-type value to a method, the method cannot modify the corresponding value in the caller; when you pass an object reference to a method, the method cannot modify the object reference (say, to refer to a different object), but it can change the underlying object (by using the object reference to invoke one of the object’s methods). For this reason, some Java programmers use the term pass by object reference to refer to Java’s argument-passing conventions for reference types.

Q. I noticed that the argument to the equals() method in String and Color is of type Object. Shouldn’t the argument be of type String and Color, respectively?

A. No. In Java, the equals() method is a special and its argument type should always be Object. This is an artifact of the inheritance mechanism that Java uses to support the equals() method, which we consider on page 454. For now, you can safely ignore the distinction.

Q. Why is the image-processing data type named Picture instead of Image?

A. There is already a built-in Java library named Image.

Exercises

3.1.1 Write a static method reverse() that takes a string as an argument and returns a string that contains the same sequence of characters as the argument string but in reverse order.

3.1.2 Write a program that takes from the command line three integers between 0 and 255 that represent red, green, and blue values of a color and then creates and shows a 256-by-256 Picture in which each pixel has that color.

3.1.3 Modify AlbersSquares (PROGRAM 3.1.2) to take nine command-line arguments that specify three colors and then draws the six squares showing all the Albers squares with the large square in each color and the small square in each different color.

3.1.4 Write a program that takes the name of a grayscale image file as a command-line argument and uses StdDraw to plot a histogram of the frequency of occurrence of each of the 256 grayscale intensities.

3.1.5 Write a program that takes the name of an image file as a command-line argument and flips the image horizontally.

3.1.6 Write a program that takes the name of an image file as a command-line argument, and creates and shows three Picture objects, one that contains only the red components, one for green, and one for blue.

3.1.7 Write a program that takes the name of an image file as a command-line argument and prints the pixel coordinates of the lower-left corner and the upper-right corner of the smallest bounding box (rectangle parallel to the x- and y-axes) that contains all of the non-white pixels.

3.1.8 Write a program that takes as command-line arguments the name of an image file and the pixel coordinates of a rectangle within the image; reads from standard input a list of Color values (represented as triples of int values); and serves as a filter, printing those color values for which all pixels in the rectangle are background/foreground compatible. (Such a filter can be used to pick a color for text to label an image.)

3.1.9 Write a static method isValidDNA() that takes a string as its argument and returns true if and only if it is composed entirely of the characters A, T, C, and G.

3.1.10 Write a function complementWatsonCrick() that takes a DNA string as its argument and returns its Watson–Crick complement: replace A with T, C with G, and vice versa.

3.1.11 Write a function isWatsonCrickPalindrome() that takes a DNA string as its input and returns true if the string is a Watson–Crick complemented palindrome, and false otherwise. A Watson–Crick complemented palindrome is a DNA string that is equal to the reverse of its Watson–Crick complement.

3.1.12 Write a program to check whether an ISBN number is valid (see EXERCISE 1.3.35), taking into account that an ISBN number can have hyphens inserted at arbitrary places.

3.1.13 What does the following code fragment print?

String string1 = "hello";
String string2 = string1;
string1 = "world";
StdOut.println(string1);
StdOut.println(string2);

3.1.14 What does the following code fragment print?

String s = "Hello World";
s.toUpperCase();
s.substring(6, 11);
StdOut.println(s);

Answer: "Hello World". String objects are immutable—string methods each return a new String object with the appropriate value (but they do not change the value of the object that was used to invoke them). This code ignores the objects returned and just prints the original string. To print "WORLD", replace the second and third statements with s = s.toUpperCase() and s = s.substring(6, 11).

3.1.15 A string s is a circular shift of a string t if it matches when the characters of one string are circularly shifted by some number of positions. For example, ACTGACG is a circular shift of TGACGAC, and vice versa. Detecting this condition is important in the study of genomic sequences. Write a function isCircularShift() that checks whether two given strings s and t are circular shifts of one another. Hint: The solution is a one-liner with indexOf() and string concatenation.

3.1.16 Given a string that represents a domain name, write a code fragment to determine its top-level domain. For example, the top-level domain of the string cs.princeton.edu is edu.

3.1.17 Write a static method that takes a domain name as its argument and returns the reverse domain name (reverse the order of the strings between periods). For example, the reverse domain name of cs.princeton.edu is edu.princeton. cs. This computation is useful for web log analysis. (See EXERCISE 4.2.36.)

3.1.18 What does the following recursive function return?

public static String mystery(String s)
{
   int n = s.length();
   if (n <= 1) return s;
   String a = s.substring(0, n/2);
   String b = s.substring(n/2, n);
   return mystery(b) + mystery(a);
}

3.1.19 Write a test client for PotentialGene (PROGRAM 3.1.1) that takes a string as a command-line argument and reports whether it is a potential gene.

3.1.20 Write a version of PotentialGene (PROGRAM 3.1.1) that finds all potential genes contained as substrings within a long DNA string. Add a command-line argument to allow the user to specify the minimum length of a potential gene.

3.1.21 Write a filter that reads text from an input stream and prints it to an output stream, removing any lines that consist only of whitespace.

3.1.22 Write a program that takes a start string and a stop string as command-line arguments and prints all substrings of a given string that start with the first, end with the second, and otherwise contain neither. Note: Be especially careful of overlaps!

3.1.23 Modify StockQuote (PROGRAM 3.1.8) to take multiple symbols on the command line.

3.1.24 The example file DJIA.csv used for Split (PROGRAM 3.1.9) lists the date, high price, volume, and low price of the Dow Jones stock market average for every day since records have been kept. Download this file from the booksite and write a program that creates two Draw objects, one for the prices and one for the volumes, and plots them at a rate taken from the command line.

3.1.25 Write a program Merge that takes a delimiter string followed by an arbitrary number of file names as command-line arguments; concatenates the corresponding lines of each file, separated by the delimiter; and then prints the result to standard output, thus performing the opposite operation of Split (PROGRAM 3.1.9).

3.1.26 Find a website that publishes the current temperature in your area, and write a screen-scraper program Weather so that typing java Weather followed by your ZIP code will give you a weather forecast.

3.1.27 Suppose that a[] and b[] are both integer arrays consisting of millions of integers. What does the following code do, and how long does it take?

int[] temp = a; a = b; b = temp;

Solution. It swaps the arrays, but it does so by copying object references, so that it is not necessary to copy millions of values.

3.1.28 Describe the effect of the following function.

public void swap(Color a, Color b)
{
   Color temp = a;
   a = b;
   b = temp;
}

Creative Exercises

3.1.29 Picture file format. Write a library of static methods RawPicture with read() and write() methods for saving and reading pictures from a file. The write() method takes a Picture and the name of a file as arguments and writes the picture to the specified file, using the following format: if the picture is w-by-h, write w, then h, then w × h triples of integers representing the pixel color values, in row-major order. The read() method takes the name of a picture file as an argument and returns a Picture, which it creates by reading a picture from the specified file, in the format just described. Note: Be aware that this will use up much more disk space than necessary—the standard formats compress this information so that it will not take up so much space.

3.1.30 Sound visualization. Write a program that uses StdAudio and Picture to create an interesting two-dimensional color visualization of a sound file while it is playing. Be creative!

3.1.31 Kamasutra cipher. Write a filter KamasutraCipher that takes two strings as command-line argument (the key strings), then reads strings (separated by whitespace) from standard input, substitutes for each letter as specified by the key strings, and prints the result to standard output. This operation is the basis for one of the earliest known cryptographic systems. The condition on the key strings is that they must be of equal length and that any letter in standard input must appear in exactly one of them. For example, if the two keys are THEQUICKBROWN and FXJMPSVLAZYDG, then we make the table

T H E Q U I C K B R O W N
F X J M P S V L A Z Y D G

which tells us that we should substitute F for T, T for F, H for X, X for H, and so forth when filtering standard input to standard output. The message is encoded by replacing each letter with its pair. For example, the message MEET AT ELEVEN is encoded as QJJF BF JKJCJG. The person receiving the message can use the same keys to get the message back.

3.1.32 Safe password verification. Write a static method that takes a string as an argument and returns true if it meets the following conditions, false otherwise:

• At least eight characters long

• Contains at least one digit (0–9)

• Contains at least one uppercase letter

• Contains at least one lowercase letter

• Contains at least one character that is neither a letter nor a number

Such checks are commonly used for passwords on the web.

3.1.33 Color study. Write a program that displays the color study shown at right, which gives Albers squares corresponding to each of the 256 levels of blue (blue-to-white in row-major order) and gray (black-to-white in column-major order) that were used to print this book.

An illustration of a blue colored grid layout depicts the color study.

3.1.34 Entropy. The Shannon entropy measures the information content of an input string and plays a cornerstone role in information theory and data compression. Given a string of n characters, let fc be the frequency of occurrence of character c. The quantity pc = fc / n is an estimate of the probability that c would be in the string if it were a random string, and the entropy is defined to be the sum of the quantity –pc log2 pc, over all characters that appear in the string. The entropy is said to measure the information content of a string: if each character appears the same number times, the entropy is at its minimum value among strings of a given length. Write a program that takes the name of a file as a command-line argument and prints the entropy of the text in that file. Run your program on a web page that you read regularly, a recent paper that you wrote, and the fruit fly genome found on the website.

3.1.35 Tile. Write a program that takes the name of an image file and two integers m and n as command-line arguments and creates an m-by-n tiling of the image.

3.1.36 Rotation filter. Write a program that takes two command-line arguments (the name of an image file and a real number θ) and rotates the image θ degrees counterclockwise. To rotate, copy the color of each pixel (si, sj) in the source image to a target pixel (ti, tj) whose coordinates are given by the following formulas:

ti = (sici)cos θ – (sjcj)sin θ + ci
tj = (sici)sin θ + (sjcj)cos θ + cj

where (ci, cj) is the center of the image.

3.1.37 Swirl filter. Creating a swirl effect is similar to rotation, except that the angle changes as a function of distance to the center of the image. Use the same formulas as in the previous exercise, but compute θ as a function of (si, s j), specifically π/256 times the distance to the center.

3.1.38 Wave filter. Write a filter like those in the previous two exercises that creates a wave effect, by copying the color of each pixel (si, s j) in the source image to a target pixel (ti, t j), where ti = si and tj = sj +20 sin(2 π sj / 64). Add code to take the amplitude (20 in the accompanying figure) and the frequency (64 in the accompanying figure) as command-line arguments. Experiment with various values of these parameters.

An illustration depicts four different types of image filters.

3.1.39 Glass filter. Write a program that takes the name of an image file as a command-line argument and applies a glass filter: set each pixel p to the color of a random neighboring pixel (whose pixel coordinates both differ from p’s coordinates by at most 5).

3.1.40 Slide show. Write a program that takes the names of several image files as command-line arguments and displays them in a slide show (one every two seconds), using a fade effect to black and a fade from black between images.

3.1.41 Morph. The example images in the text for Fade do not quite line up in the vertical direction (the mandrill’s mouth is much lower than Darwin’s). Modify Fade to add a transformation in the vertical dimension that makes a smoother transition.

Three statements and the corresponding output depict the process of digital zoom.

3.1.42 Digital zoom. Write a program Zoom that takes the name of an image file and three numbers s, x, and y as command-line arguments, and shows an output image that zooms in on a portion of the input image. The numbers are all between 0 and 1, with s to be interpreted as a scale factor and (x, y) as the relative coordinates of the point that is to be at the center of the output image. Use this program to zoom in on a relative or pet in some digital photo on your computer. (If your photo came from an old cell phone or camera, you may not be able to zoom in too close without having visible artifacts from scaling.)

3.2 Creating Data Types

In principle, we could write all of our programs using only the eight built-in primitive types. However, as we saw in the last section, it is much more convenient to write programs at a higher level of abstraction. Thus, a variety of data types are built into the Java language and libraries. Still, we certainly cannot expect Java to contain every conceivable data type that we might ever wish to use, so we need to be able to define our own. This section explains how to build data types with the familiar Java class.

Implementing a data type as a Java class is not very different from implementing a library of static methods. The primary difference is that we associate data with the method implementations. The API specifies the constructors and instance methods that we need to implement, but we are free to choose any convenient representation. To cement the basic concepts, we begin by considering an implementation of a data type for charged particles. Next, we illustrate the process of creating data types by considering a range of examples, from complex numbers to stock accounts, including a number of software tools that we will use later in the book. Useful client code is testimony to the value of any data type, so we also consider a number of clients, including one that depicts the famous and fascinating Mandelbrot set.

The process of defining a data type is known as data abstraction. We focus on the data and implement operations on that data. Whenever you can clearly separate data and associated operations within a program, you should do so. Modeling physical objects or familiar mathematical abstractions is straightforward and extremely useful, but the true power of data abstraction is that it allows us to model anything that we can precisely specify. Once you gain experience with this style of programming, you will see that it helps us address programming challenges of arbitrary complexity.

Basic elements of a data type

To illustrate the process of implementing a data type in a Java class, we will consider a data type Charge for charged particles. In particular, we are interested in a two-dimensional model that uses Coulomb’s law, which tells us that the electric potential at a point (x, y) due to a given charged particle is V = kq /r, where q is the charge value, r is the distance from the point to the charge, and k = 8.99 × 109 N·m2 ·C–2 is the electrostatic constant. When there are multiple charged particles, the electric potential at any point is the sum of the potentials due to each charge. For consistency, we use SI (Système International d’Unités): in this formula, N designates newtons (force), m designates meters (distance), and C represent coulombs (electric charge).

An illustration depicts the Coulomb’s law for a charged particle.
API

The application programming interface is the contract with all clients and, therefore, the starting point for any implementation. Here is our API for charged particles:

public class Charge

 

Charge(double x0, double y0, double q0)

double

potentialAt(double x, double y)

electric potential at (x, y) due to charge

String

toString()

string representation

API for charged particles (see PROGRAM 3.2.1)

To implement the Charge data type, we need to define the data-type values and implement the constructor that creates a charged particle, a method potentialAt() that returns the potential at the point (x, y) due to the charge, and a toString() method that returns a string representation of the charge.

Class

In Java, you implement a data type in a class. As with the libraries of static methods that we have been using, we put the code for a data type in a file with the same name as the class, followed by the .java extension. We have been implementing Java classes, but the classes that we have been implementing do not have the key features of data types: instance variables, constructors, and instance methods. Each of these building blocks is also qualified by an access (or visibility) modifier. We next consider these four concepts, with examples, culminating in an implementation of the Charge data type (PROGRAM 3.2.1).

Access modifiers

The keywords public, private, and final that sometimes precede class names, instance variable names, and method names are known as access modifiers. The public and private modifiers control access from client code: we designate every instance variable and method within a class as either public (this entity is accessible by clients) or private (this entity is not accessible by clients). The final modifier indicates that the value of the variable will not change once it is initialized—its access is read-only. Our convention is to use public for the constructors and methods in the API (since we are promising to provide them to clients) and private for everything else. Typically, our private methods are helper methods used to simplify code in other methods in the class. Java is not so restrictive on its usage of modifiers—we defer to SECTION 3.3 a discussion of our reasons for these conventions.

Instance variables

To write code for the instance methods that manipulate data-type values, first we need to declare instance variables that we can use to refer to these values in code. These variables can be any type of data. We declare the types and names of instance variables in the same way as we declare local variables: for Charge, we use three double variables—two to describe the charge’s position in the plane and one to describe the amount of charge. These declarations appear as the first statements in the class, not inside main() or any other method. There is a critical distinction between instance variables and the local variables defined within a method or a block that you are accustomed to: there is just one value corresponding to each local variable at a given time, but there are numerous values corresponding to each instance variable (one for each object that is an instance of the data type). There is no ambiguity with this arrangement, because each time that we invoke an instance method, we do so with an object reference—the referenced object is the one whose value we are manipulating.

A program depicts the Instance variables.
Constructors

A constructor is a special method that creates an object and provides a reference to that object. Java automatically invokes a constructor when a client program uses the keyword new. Java does most of the work: our code just needs to initialize the instance variables to meaningful values. Constructors always share the same name as the class, but we can overload the name and have multiple constructors with different signatures, just as with static methods. To the client, the combination of new followed by a constructor name (with arguments enclosed within parentheses) is the same as a function call that returns an object reference of the specified type. A constructor signature has no return type, because constructors always return a reference to an object of its data type (the name of the type, the class, and the constructor are all the same). Each time that a client invokes a constructor, Java automatically

• Allocates memory for the object

• Invokes the constructor code to initialize the instance variables

• Returns a reference to the newly created object

The anatomy of a constructor is displayed.

The constructor in Charge is typical: it initializes the instance variables with the values provided by the client as arguments.

Instance methods

To implement instance methods, we write code that is precisely like the code that we learned in CHAPTER 2 to implement static methods (functions). Each method has a signature (which specifies its return type and the types and names of its parameter variables) and a body (which consists of a sequence of statements, including a return statement that provides a value of the return type back to the client). When a client invokes an instance method, the system initializes the parameter variables with client values; executes statements until it reaches a return statement; and returns the computed value to the client, with the same effect as if the method invocation in the client were replaced with that return value. All of this is the same as for static methods, but there is one critical distinction for instance methods: they can perform operations on instance variables.

Anatomy of an instance method is displayed.
Variables within methods

Accordingly, the Java code that we write to implement instance methods uses three kinds of variables:

• Parameter variables

• Local variables

Instance variables

The first two are the same as for static methods: parameter variables are specified in the method signature and initialized with client values when the method is called, and local variables are declared and initialized within the method body. The scope of parameter variables is the entire method; the scope of local variables is the following statements in the block where they are defined. Instance variables are completely different: they hold data-type values for objects in a class, and their scope is the entire class. How do we specify which object’s value we want to use? If you think for a moment about this question, you will recall the answer. Each object in the class has a value: the code in an instance method refers to the value for the object that was used to invoke the method. For example, when we write c1.potentialAt(x, y), the code in potentialAt() is referring to the instance variables for c1.

The implementation of potentialAt() in Charge uses all three kinds of variable names, as illustrated in the diagram at the bottom of the previous page and summarized in this table:

Be sure that you understand the distinctions among the three kinds of variables that we use in implementing instance methods. These differences are a key to object-oriented programming.

variable

purpose

example

scope

parameter

to pass value from client to method

x, y

method

local

for temporary use within method

dx, dy

block

instance

to specify data-type value

rx, ry

class

Variables within instance methods


Program 3.2.1 Charged particle

public class Charge
{
   private final double rx, ry;
   private final double q;

   public Charge(double x0, double y0, double q0)
   {  rx = x0; ry = y0; q = q0;  }

   public double potentialAt(double x, double y)
   {
      double k = 8.99e09;
      double dx = x - rx;
      double dy = y - ry;
      return k * q / Math.sqrt(dx*dx + dy*dy);
   }

   public String toString()
   {
      return q + " at (" + rx + ", " + ry + ")";
   }

   public static void main(String[] args)
   {
      double x = Double.parseDouble(args[0]);
      double y = Double.parseDouble(args[1]);
      Charge c1 = new Charge(0.51, 0.63, 21.3);
      Charge c2 = new Charge(0.13, 0.94, 81.9);
      StdOut.println(c1);
      StdOut.println(c2);
      double v1 = c1.potentialAt(x, y);
      double v2 = c2.potentialAt(x, y);
      StdOut.printf("%.2e
", (v1 + v2));
   }
}

rx, ry | query point
   q   | charge

   k   | electrostatic constant
dx, dy | delta distances to query point

x, y | query point
 c1  | first charge
 v1  | potential due to c1
 c2  | second charge
 v2  | potential due to c2

This implementation of our data type for charged particles contains the basic elements found in every data type: instance variables rx, ry, and q; a constructor Charge(); instance methods potentialAt() and toString(); and a test client main().


% java Charge 0.2 0.5
21.3 at (0.51, 0.63)
81.9 at (0.13, 0.94)
2.22e+12

% java Charge 0.51 0.94
21.3 at (0.51, 0.63)
81.9 at (0.13, 0.94)
2.56e+12

Test client

Each class can define its own main() method, which we typically reserve for testing the data type. At a minimum, the test client should call every constructor and instance method in the class. For example, the main() method in PROGRAM 3.2.1 takes two command-line arguments x and y, creates two Charge objects, and prints the two charged particles along with the total electric potential at (x, y) due to those two particles. When there are multiple charged particles, the electric potential at any point is the sum of the potentials due to each charge.

An illustration depicts the electric potential at a point that is near two charged particles.

These are the basic components that you need to understand to be able to define your own data types in Java. Every data-type implementation (Java class) that we will develop has the same basic ingredients as this first example: instance variables, constructors, instance methods, and a test client. In each data type that we develop, we go through the same steps. Rather than thinking about which action we need to take next to accomplish a computational goal (as we did when first learning to program), we think about the needs of a client, then accommodate them in a data type.

The first step in creating a data type is to specify an API. The purpose of the API is to separate clients from implementations, so as to enable modular programming. We have two goals when specifying an API. First, we want to enable clear and correct client code. Indeed, it is a good idea to write some client code before finalizing the API to gain confidence that the specified data-type operations are the ones that clients need. Second, we want to be able to implement the operations. There is no point in specifying operations that we have no idea how to implement.

The second step in creating a data type is to implement a Java class that meets the API specifications. First we choose the instance variables, then we write the code that manipulates the instance variables to implement the specified constructors and instance methods.

The third step in creating a data type is to write test clients, to validate the design decisions made in the first two steps.

What are the values that define the data type, and which operations do clients need to perform on those values? With these basic decisions made, you can create new data types and write clients that use them in the same way as you have been using built-in types. You will find many exercises at the end of this section that are intended to give you experience with data-type creation.

The anatomy of a class is displayed.

Stopwatch

One of the hallmarks of object-oriented programming is the idea of easily modeling real-world objects by creating abstract programming objects. As a simple example, consider Stopwatch (PROGRAM 3.3.2), which implements the following API:

public class Stopwatch

 

Stopwatch()

create a new stopwatch and start it running

double

elapsedTime()

return the elapsed time since creation, in seconds

API for stopwatches (see PROGRAM 3.2.2)

In other words, a Stopwatch is a stripped-down version of an old-fashioned stopwatch. When you create one, it starts running, and you can ask it how long it has been running by invoking the method elapsedTime(). You might imagine adding all sorts of bells and whistles to Stopwatch, limited only by your imagination. Do you want to be able to reset the stopwatch? Start and stop it? Include a lap timer? These sorts of things are easy to add (see EXERCISE 3.2.12).

The implementation of Stopwatch uses the Java system method System.currentTimeMillis(), which returns a long value giving the current time in milliseconds (the number of milliseconds since midnight on January 1, 1970 UTC). The data-type implementation could hardly be simpler. A Stopwatch saves its creation time in an instance variable, then returns the difference between that time and the current time whenever a client invokes its elapsedTime() method. A Stopwatch itself does not actually tick (an internal system clock on your computer does all the ticking); it just creates the illusion that it does for clients. Why not just use System.currentTimeMillis() in clients? We could do so, but using the Stopwatch leads to client code that is easier to understand and maintain.

A photo of an old-fashioned stopwatch is shown. The stopwatch with the circular dial and two buttons on the top is indicating time in seconds.

The test client is typical. It creates two Stopwatch objects, uses them to measure the running time of two different computations, then prints the running times. The question of whether one approach to solving a problem is better than another has been lurking since the first few programs that you have run, and plays an essential role in program development. In SECTION 4.1, we will develop a scientific approach to understanding the cost of computation. Stopwatch is a useful tool in that approach.


Program 3.2.2 Stopwatch

public class Stopwatch
{
   private final long start;

   public Stopwatch()
   {  start = System.currentTimeMillis();  }

   public double elapsedTime()
   {
       long now = System.currentTimeMillis();
       return (now - start) / 1000.0;
   }

   public static void main(String[] args)
   {
      // Compute and time computation using Math.sqrt().
      int n = Integer.parseInt(args[0]);
      Stopwatch timer1 = new Stopwatch();
      double sum1 = 0.0;
      for (int i = 1; i <= n; i++)
         sum1 += Math.sqrt(i);
      double time1 = timer1.elapsedTime();
      StdOut.printf("%e (%.2f seconds)
", sum1, time1);

     // Compute and time computation using Math.pow().
      Stopwatch timer2 = new Stopwatch();
      double sum2 = 0.0;
      for (int i = 1; i <= n; i++)
         sum2 += Math.pow(i, 0.5);
      double time2 = timer2.elapsedTime();
      StdOut.printf("%e (%.2f seconds)
", sum2, time2);
   }
}

start | creation time

This class implements a simple data type that we can use to compare running times of performance-critical methods (see SECTION 4.1). The test client compares the running times of two functions for computing square roots in Java’s Math library. For the task of computing the sum of the square roots of the numbers from 1 to n, the version that calls Math.sqrt() is more than 10 times faster than the one that calls Math.pow(). Results are likely to vary by system.


% java Stopwatch 100000000
6.666667e+11 (0.65 seconds)
6.666667e+11 (8.47 seconds)

Histogram

Now, we consider a data type to visualize data using a familiar plot known as a histogram. For simplicity, we assume that the data consists of a sequence of integer values between 0 and n – 1. A histogram counts the number of times each value appears and plots a bar for each value (with height proportional to its frequency). The following API describes the operations:

public class Histogram

 

Histogram(int n)

create a histogram for the integer values 0 to n-1

double

addDataPoint(int i)

add an occurrence of the value i

void

draw()

draw the histogram to standard drawing

API for histograms (see PROGRAM 3.2.3)

To implement a data type, you must first determine which instance variables to use. In this case, we need to use an array as an instance variable. Specifically, Histogram (PROGRAM 3.2.3) maintains an instance variable freq[] so that freq[i] records the number of times the data value i appears in the data, for each i between 0 and n-1. Histogram also includes an integer instance variable max that stores the maximum frequency of any of the values (which corresponds to the height of the tallest bar). The instance method draw() method uses the variable max to set the y-scale of the standard drawing window and calls the method StdStats.plotBars() to draw the histogram of values. The main() method is a sample client that performs Bernoulli trials. It is substantially simpler than Bernoulli (PROGRAM 2.2.6) because it uses the Histogram data type.

By creating a data type such as Histogram, we reap the benefits of modular programming (reusable code, independent development of small programs, and so forth) that we discussed in CHAPTER 2, with the additional benefit that we separate the data. Without Histogram, we would have to mix the code for creating the histogram with the code for the managing the data of interest, resulting in a program much more difficult to understand and maintain than the two separate programs. Whenever you can clearly separate data and associated operations within a program, you should do so.


Program 3.2.3 Histogram

public class Histogram
{
    private final double[] freq;
    private double max;

    public Histogram(int n)
    {  // Create a new histogram.
       freq = new double[n];
    }

    public void addDataPoint(int i)
    {  // Add one occurrence of the value i.
       freq[i]++;
       if (freq[i] > max) max = freq[i];
    }

    public void draw()
    {  // Draw (and scale) the histogram.
       StdDraw.setYscale(0, max);
       StdStats.plotBars(freq);
    }

   public static void main(String[] args)
   {  // See Program 2.2.6.
      int n = Integer.parseInt(args[0]);
      int trials = Integer.parseInt(args[1]);
      Histogram histogram = new Histogram(n+1);
      StdDraw.setCanvasSize(500, 200);
      for (int t = 0; t < trials; t++)
         histogram.addDataPoint(Bernoulli.binomial(n));
      histogram.draw();
   }
}

freq[] | frequency counts
 max   | maximum frequency

A statement and its corresponding histogram are shown. The statement reads “percentage java Histogram 50 1000000.” The output shows a Histogram with bars arranged in the form of a bell-shaped curve.

This data type supports simple client code to create histograms of the frequency of occurrence of integers values between 0 and n-1. The frequencies are kept in an instance variable that is an array. An integer instance variable max tracks the maximum frequency (for scaling the y-axis when drawing the histogram). The sample client is a version of Bernoulli (PROGRAM 2.2.6), but is substantially simper because it uses the Histogram data type.


Turtle graphics

Whenever you can clearly separate tasks within a program, you should do so. In object-oriented programming, we extend that mantra to include data (or state) with the tasks. A small amount of state can be immensely valuable in simplifying a computation. Next, we consider turtle graphics, which is based on the data type defined by this API:

public class Turtle

 

Turtle(double x0, double y0, double a0)

create a new turtle at (x0, y0) facing a0 degrees counterclockwise from the x-axis

void

turnLeft(double delta)

rotate delta degrees counterclockwise

void

goForward(double step)

move distance step, drawing a line

API for turtle graphics (see PROGRAM 3.2.4)

Imagine a turtle that lives in the unit square and draws lines as it moves. It can move a specified distance in a straight line, or it can rotate left (counterclockwise) a specified number of degrees. According to the API, when we create a turtle, we place it at a specified point, facing a specified direction. Then, we create drawings by giving the turtle a sequence of goForward() and turnLeft() commands.

For example, to draw an equilateral triangle, we create a Turtle at (0.5, 0) facing at an angle of 60 degrees counterclockwise from the origin, then direct it to take a step forward, then rotate 120 degrees counterclockwise, then take another step forward, then rotate another 120 degrees counterclockwise, and then take a third step forward to complete the triangle. Indeed, all of the turtle clients that we will examine simply create a turtle, then give it an alternating sequence of step and rotate commands, varying the step size and the amount of rotation. As you will see in the next several pages, this simple model allows us to create arbitrarily complex drawings, with many important applications.

A section of the program and the corresponding output are shown.

Turtle (PROGRAM 3.2.4) is an implementation of this API that uses StdDraw. It maintains three instance variables: the coordinates of the turtle’s position and the current direction it is facing, measured in degrees counterclockwise from the x-axis. Implementing the two methods requires changing the values of these variables, so they are not final. The necessary updates are straightforward: turnLeft(delta) adds delta to the current angle, and goForward(step) adds the step size times the cosine of its argument to the current x-coordinate and the step size times the sine of its argument to the current y-coordinate.

A graph titled “turtle trigonometry” is displayed.

The test client in Turtle takes an integer command-line argument n and draws a regular polygon with n sides. If you are interested in elementary analytic geometry, you might enjoy verifying that fact. Whether or not you choose to do so, think about what you would need to do to compute the coordinates of all the points in the polygon. The simplicity of the turtle’s approach is very appealing. In short, turtle graphics serves as a useful abstraction for describing geometric shapes of all sorts. For example, we obtain a good approximation to a circle by taking n to a sufficiently large value.

The steps in program for turtle graphics drawing of a triangle are displayed.

You can use a Turtle as you use any other object. Programs can create arrays of Turtle objects, pass them as arguments to functions, and so forth. Our examples will illustrate these capabilities and convince you that creating a data type like Turtle is both very easy and very useful. For each of them, as with regular polygons, it is possible to compute the coordinates of all the points and draw straight lines to get the drawings, but it is easier to do so with a Turtle. Turtle graphics exemplifies the value of data abstraction.


Program 3.2.4 Turtle graphics

public class Turtle
{
   private double x, y;
   private double angle;

   public Turtle(double x0, double y0, double a0)
   {  x = x0; y = y0; angle = a0;  }

   public void turnLeft(double delta)
   {  angle += delta;  }

   public void goForward(double step)
   {  // Compute new position; move and draw line to it.
      double oldx = x, oldy = y;
      x += step * Math.cos(Math.toRadians(angle));
      y += step * Math.sin(Math.toRadians(angle));
      StdDraw.line(oldx, oldy, x, y);
   }

   public static void main(String[] args)
   {  // Draw a regular polygon with n sides.
      int n = Integer.parseInt(args[0]);
      double angle = 360.0 / n;
      double step = Math.sin(Math.toRadians(angle/2));
      Turtle turtle = new Turtle(0.5, 0.0, angle/2);
      for (int i = 0; i < n; i++)
      {
         turtle.goForward(step);
         turtle.turnLeft(angle);
      }
   }
}

 x, y | position (in unit square)
angle | direction of motion (degrees, counterclockwise from x-axis)

This data type supports turtle graphics, which often simplifies the creation of drawings.


A statement and the corresponding output are shown.

A statement and the corresponding output are shown.

A statement and the corresponding output are shown.

Recursive graphics

A Koch curve of order 0 is a straight line segment. To form a Koch curve of order n, draw a Koch curve of order n–1, turn left 60 degrees, draw a second Koch curve of order n–1, turn right 120 degrees (left –120 degrees), draw a third Koch curve of order n–1, turn left 60 degrees, and draw a fourth Koch curve of order n–1. These recursive instructions lead immediately to turtle client code. With appropriate modifications, recursive schemes like this are useful in modeling self-similar patterns found in nature, such as snowflakes.

The client code is straightforward, except for the value of the step size. If you carefully examine the first few examples, you will see (and be able to prove by induction) that the width of the curve of order n is 3n times the step size, so setting the step size to 1/3n produces a curve of width 1. Similarly, the number of steps in a curve of order n is 4n, so Koch will not finish if you invoke it for large n.

You can find many examples of recursive patterns of this sort that have been studied and developed by mathematicians, scientists, and artists from many cultures in many contexts. Here, our interest in them is that the turtle graphics abstraction greatly simplifies the client code that draws these patterns.

public class Koch
{
   public static void koch(int n, double step, Turtle turtle)
   {
      if (n == 0)
      {
         turtle.goForward(step);
         return;
      }
      koch(n-1, step, turtle);
      turtle.turnLeft(60.0);
      koch(n-1, step, turtle);
      turtle.turnLeft(-120.0);
      koch(n-1, step, turtle);
      turtle.turnLeft(60.0);
      koch(n-1, step, turtle);
   }

   public static void main(String[] args)
   {
      int n = Integer.parseInt(args[0]);
      double step = 1.0 / Math.pow(3.0, n);
      Turtle turtle = new Turtle(0.0, 0.0, 0.0);
      koch(n, step, turtle);
   }
}
Five figures depict the drawing of Koch cures with turtle graphics.
Spira mirabilis

Perhaps the turtle is a bit tired after taking 4n steps to draw a Koch curve. Accordingly, imagine that the turtle’s step size decays by a tiny constant factor each time that it takes a step. What happens to our drawings? Remarkably, modifying the polygon-drawing test client in PROGRAM 3.2.4 to answer this question leads to a geometric shape known as a logarithmic spiral, a curve that is found in many contexts in nature.

Spiral (PROGRAM 3.2.5) is an implementation of this curve. It takes n and the decay factor as command-line arguments and instructs the turtle to alternately step and turn until it has wound around itself 10 times. As you can see from the four examples given with the program, if the decay factor is greater than 1, the path spirals into the center of the drawing. The argument n controls the shape of the spiral. You are encouraged to experiment with Spiral yourself to develop an understanding of the way in which the parameters control the behavior of the spiral.

The logarithmic spiral was first described by René Descartes in 1638. Jacob Bernoulli was so amazed by its mathematical properties that he named it the spira mirabilis (miraculous spiral) and even asked to have it engraved on his tombstone. Many people also consider it to be “miraculous” that this precise curve is clearly present in a broad variety of natural phenomena. Three examples are depicted below: the chambers of a nautilus shell, the arms of a spiral galaxy, and the cloud formation in a tropical storm. Scientists have also observed it as the path followed by a hawk approaching its prey and as the path followed by a charged particle moving perpendicular to a uniform magnetic field.

One of the goals of scientific enquiry is to provide simple but accurate models of complex natural phenomena. Our tired turtle certainly passes that test!

Three photos show the examples of the Spira mirabilis in nature.

Program 3.2.5 Spira mirabilis

public class Spiral
{
   public static void main(String[] args)
   {
      int n         = Integer.parseInt(args[0]);
      double decay  = Double.parseDouble(args[1]);
      double angle  = 360.0 / n;
      double step   = Math.sin(Math.toRadians(angle/2));
      Turtle turtle = new Turtle(0.5, 0, angle/2);

      for (int i = 0; i < 10 * 360 / angle; i++)
      {
         step /= decay;
         turtle.goForward(step);
         turtle.turnLeft(angle);
      }
   }
}

  step | step size
 decay | decay factor
 angle | rotation amount
turtle | tired turtle

This code is a modification of the test client in PROGRAM 3.2.4 that decreases the step size at each step and cycles around 10 times. The angle controls the shape; the decay controls the nature of the spiral.


Two statements and their corresponding output are shown.

Two statements and their corresponding output are shown.

Brownian motion

Or perhaps the turtle has had one too many. Accordingly, imagine that the disoriented turtle (again following its standard alternating turn-and-step regimen) turns in a random direction before each step. Again, it is easy to plot the path followed by such a turtle for millions of steps, and again, such paths are found in nature in many contexts. In 1827, the botanist Robert Brown observed through a microscope that tiny particles ejected from pollen grains seemed to move about in just such a random fashion when immersed in water. This process, which later became known as Brownian motion, led to Albert Einstein’s insights into the atomic nature of matter.

Or perhaps our turtle has friends, all of whom have had one too many. After they have wandered around for a sufficiently long time, their paths merge together and become indistinguishable from a single path. Astrophysicists today are using this model to understand observed properties of distant galaxies.

Turtle graphics was originally developed by Seymour Papert at MIT in the 1960s as part of an educational programming language, Logo, that is still used today in toys. But turtle graphics is no toy, as we have just seen in numerous scientific examples. Turtle graphics also has numerous commercial applications. For example, it is the basis for PostScript, a programming language for creating printed pages that is used for most newspapers, magazines, and books. In the present context, Turtle is a quintessential object-oriented programming example, showing that a small amount of saved state (data abstraction using objects, not just functions) can vastly simplify a computation.

public class DrunkenTurtle
{
   public static void main(String[] args)
   {
      int trials = Integer.parseInt(args[0]);
      double step = Double.parseDouble(args[1]);
      Turtle turtle = new Turtle(0.5, 0.5, 0.0);
      for (int t = 0; t < trials; t++)
      {
         turtle.turnLeft(StdRandom.uniform(0.0, 360.0));
         turtle.goForward(step);
      }
   }
}
A program statement and the corresponding output depict turtle graphics.
public class DrunkenTurtles
{
   public static void main(String[] args)
   {
      int n = Integer.parseInt(args[0]);          // number of turtles
      int trials = Integer.parseInt(args[1]);     // number of steps
      double step = Double.parseDouble(args[2]);  // step size
      Turtle[] turtles = new Turtle[n];
      for (int i = 0; i < n; i++)
      {
         double x = StdRandom.uniform(0.0, 1.0);
         double y = StdRandom.uniform(0.0, 1.0);
         turtles[i] = new Turtle(x, y, 0.0);
      }
      for (int t = 0; t < trials; t++)
      {  // All turtles take one step.
         for (int i = 0; i < n; i++)
         {  // Turtle i takes one step in a random direction.
            turtles[i].turnLeft(StdRandom.uniform(0.0, 360.0));
            turtles[i].goForward(step);
         }
      }
   }
}
A program statement and the corresponding output depict the Brownian movement of multiple drunken turtles with different parameters, the number of steps and the step size.

Complex numbers

A complex number is a number of the form x + iy, where x and y are real numbers and i is the square root of –1. The number x is known as the real part of the complex number, and the number y is known as the imaginary part. This terminology stems from the idea that the square root of –1 has to be an imaginary number, because no real number can have this value. Complex numbers are a quintessential mathematical abstraction: whether or not one believes that it makes sense physically to take the square root of –1, complex numbers help us understand the natural world. They are used extensively in applied mathematics and play an essential role in many branches of science and engineering. They are used to model physical systems of all sorts, from circuits to sound waves to electromagnetic fields. These models typically require extensive computations involving manipulating complex numbers according to well-defined arithmetic operations, so we want to write computer programs to do the computations. In short, we need a new data type.

Developing a data type for complex numbers is a prototypical example of object-oriented programming. No programming language can provide implementations of every mathematical abstraction that we might need, but the ability to implement data types gives us not just the ability to write programs to easily manipulate abstractions such as complex numbers, polynomials, vectors, and matrices, but also the freedom to think in terms of new abstractions.

The operations on complex numbers that are needed for basic computations are to add and multiply them by applying the commutative, associative, and distributive laws of algebra (along with the identity i2 = –1); to compute the magnitude; and to extract the real and imaginary parts, according to the following equations:

Addition: (x + iy) + (v + iw) = (x + v) + i(y + w)

Multiplication: (x + iy) × (v + iw) = (xvyw) + i(yv + xw)

Magnitude: |x+iy|=x2+y2

Real part: Re(x + iy) = x

Imaginary part: Im(x + iy) = y

For example, if a = 3 + 4i and b =–2 + 3i, then a +b = 1 + 7i, a × b = –18 + i, Re(a) = 3, Im(a) = 4, and | a | = 5.

With these basic definitions, the path to implementing a data type for complex numbers is clear. As usual, we start with an API that specifies the data-type operations:

public class Complex

 

Complex(double real, double imag)

Complex

plus(Complex b)

sum of this number and b

Complex

times(Complex b)

product of this number and b

double

abs()

magnitude

double

re()

real part

double

im()

imaginary part

String

toString()

string representation

API for complex numbers (see PROGRAM 3.2.6)

For simplicity, we concentrate in the text on just the basic operations in this API, but EXERCISE 3.2.19 asks you to consider several other useful operations that might be included in such an API.

Complex (PROGRAM 3.2.6) is a class that implements this API. It has all of the same components as did Charge (and every Java data type implementation): instance variables (re and im), a constructor, instance methods (plus(), times(), abs(), re(), im(), and toString()), and a test client. The test client first sets z0 to 1 + i, then sets z to z0, and then evaluates

z = z2 + z0 = (1 + i)2 + (1 + i) = (1 + 2i –1) + (1 + i) = 1 + 3i
z = z
2 + z0 = (1 + 3i)2 + (1 + i) = (1 + 6i –9) + (1 + i) = –7 + 7i

This code is straightforward and similar to code that you have seen earlier in this chapter, with one exception: the code that implements the arithmetic methods makes use of a new mechanism for accessing object values.

Accessing instance variables of other objects of the same type

The instance methods plus() and times() each need to access values in two objects: the object passed as an argument and the object used to invoke the method. If we call the method with a.plus(b), we can access the instance variables of a using the names re and im, as usual, but to access the instance variables of b we use the code b.re and b.im. Declaring the instance variables as private means that you cannot access directly the instance variables from another class. However, within a class, you can access directly the instance variables of any object from that same class, not just the instance variables of the invoking object.

Creating and returning new objects

Observe the manner in which plus() and times() provide return values to clients: they need to return a Complex value, so they each compute the requisite real and imaginary parts, use them to create a new object, and then return a reference to that object. This arrangement allow clients to manipulate complex numbers in a natural manner, by manipulating local variables of type Complex.

Chaining method calls

Observe the manner in which main() chains two method calls into one compact Java expression z.times(z).plus(z0), which corresponds to the mathematical expression z2 + z0. This usage is convenient because you do not have to invent variable names for intermediate values. That is, you can use any object reference to invoke a method, even one without a name (such as one that is the result of evaluating a subexpression). If you study the expression, you can see that there is no ambiguity: moving from left to right, each method returns a reference to a Complex object, which is used to invoke the next instance method in the chain. If desired, we can use parentheses to override the default precedence order (for example, the Java expression z.times(z.plus(z0)) corresponds to the mathematical expression z(z + z0)).

Final instance variables

The two instance variables in Complex are final, meaning that their values are set for each Complex object when it is created and do not change during the lifetime of that object. We discuss the reasons behind this design decision in SECTION 3.3.

Complex numbers are the basis for sophisticated calculations from applied mathematics that have many applications. With Complex we can concentrate on developing applications programs that use complex numbers without worrying about re-implementing methods such as times(), abs(), and so forth. Such methods are implemented once, and are reusable, as opposed to the alternative of copying this code into any applications program that uses complex numbers. Not only does this approach save debugging, but it also allows for changing or improving the implementation if needed, since it is separate from its clients. Whenever you can clearly separate data and associated tasks within a computation, you should do so.

To give you a feeling for the nature of calculations involving complex numbers and the utility of the complex number abstraction, we next consider a famous example of a Complex client.


Program 3.2.6 Complex number

public class Complex
{
   private final double re;
   private final double im;

   public Complex(double real, double imag)
   {  re = real; im = imag;  }

   public Complex plus(Complex b)
   {  // Return the sum of this number and b.
      double real = re + b.re;
      double imag = im + b.im;
      return new Complex(real, imag);
   }

   public Complex times(Complex b)
   {  // Return the product of this number and b.
      double real = re * b.re - im * b.im;
      double imag = re * b.im + im * b.re;
      return new Complex(real, imag);
   }

   public double abs()
   {  return Math.sqrt(re*re + im*im);  }

   public double re()  { return re; }
   public double im()  { return im; }

   public String toString()
   {  return re + " + " + im + "i";  }

   public static void main(String[] args)
   {
      Complex z0 = new Complex(1.0, 1.0);
      Complex z = z0;
      z = z.times(z).plus(z0);
      z = z.times(z).plus(z0);
      StdOut.println(z);
   }
}

re | real part
im | imaginary part

This data type is the basis for writing Java programs that manipulate complex numbers.


% java Complex
-7.0 + 7.0i

Mandelbrot set

The Mandelbrot set is a specific set of complex numbers discovered by Benoît Mandelbrot. It has many fascinating properties. It is a fractal pattern that is related to the Barnsley fern, the Sierpinski triangle, the Brownian bridge, the Koch curve, the drunken turtle, and other recursive (self-similar) patterns and programs that we have seen in this book. Patterns of this kind are found in natural phenomena of all sorts, and these models and programs are very important in modern science.

The set of points in the Mandelbrot set cannot be described by a single mathematical equation. Instead, it is defined by an algorithm, and therefore is a perfect candidate for a Complex client: we study the set by writing a program to plot it.

The rule for determining whether a complex number z0 is in the Mandelbrot set is simple. Consider the sequence of complex numbers z0, z1, z2, . . ., zt, . . ., where zt+1 = (zt)2 + z0. For example, this table shows the first few elements in the sequence corresponding to z0 =1 + i:

t

zt

(zt)2

(zt)2 + z0 = zt+1

0

1 + i

1 + 2i + i2 = 2i

2i + (1 + i) = 1 + 3i

1

1 + 3i

1 + 6i + 9i2 = –8 + 6i

–8 + 6i + (1 + i ) = –7 + 7i

2

–7 + 7i

49 – 98i + 49i2 = –98i

–98i + (1 + i ) = 1– 97i

Mandelbrot sequence computation

Now, if the sequence | zt | diverges to infinity, then z0 is not in the Mandelbrot set; if the sequence is bounded, then z0 is in the Mandelbrot set. For many points, the test is simple. For many other points, the test requires more computation, as indicated by the examples in this table:

z0

0 + 0i

2 + 0i

1 + i

0 + i

–0.5 + 0i

–0.10 – 0.64i

z1

0

6

1 + 3i

–1 + i

–0.25

–0.30 – 0.77i

z2

0

38

–7 + 7i

i

–0.44

–0.40 – 0.18i

z3

0

1446

1 – 97i

–1 + i

–0.31

0.23 – 0.50i

z4

0

2090918

– 9407 – 193i

i

–0.40

–0.09 – 0.87i

in set?

yes

no

no

yes

yes

yes

Mandelbrot sequence for several starting points

For brevity, the numbers in the rightmost two columns of this table are given to just two decimal places. In some cases, we can prove whether numbers are in the set. For example, 0 + 0i is certainly in the set (since the magnitude of all the numbers in its sequence is 0), and 2 + 0i is certainly not in the set (since its sequence dominates the powers of 2, which diverges to infinity). In some other cases, the growth is readily apparent. For example, 1 + i does not seem to be in the set. Other sequences exhibit a periodic behavior. For example, i maps to –1 + i to –i to –1 + i to –i, and so forth. Still other sequences go on for a very long time before the magnitude of the numbers begins to get large.

To visualize the Mandelbrot set, we sample complex points, just as we sample real-valued points to plot a real-valued function. Each complex number x + i y corresponds to a point (x, y) in the plane, so we can plot the results as follows: for a specified resolution n, we define an evenly spaced n-by-n pixel grid within a specified square and draw a black pixel if the corresponding point is in the Mandelbrot set and a white pixel if it is not. This plot is a strange and wondrous pattern, with all the black dots connected and falling roughly within the 2-by-2 square centered at the point –1/2 + 0i. Large values of n will produce higher-resolution images, at the cost of more computation. Looking closer reveals self-similarities throughout the plot. For example, the same bulbous pattern with self-similar appendages appears all around the contour of the main black cardioid region, of sizes that resemble the simple ruler function of PROGRAM 1.2.1. When we zoom in near the edge of the cardioid, tiny self-similar cardioids appear!

An illustration depicts the Mandelbrot set images.

But how, precisely, do we produce such plots? Actually, no one knows for sure, because there is no simple test that would enable us to conclude that a point is surely in the set. Given a complex number, we can compute the terms at the beginning of its sequence, but may not be able to know for sure that the sequence remains bounded. There is a test that tells us for sure that a complex number is not in the set: if the magnitude of any number in its sequence ever exceeds 2 (such as for 1 + 3i), then the sequence surely will diverge.

Mandelbrot (PROGRAM 3.2.7) uses this test to plot a visual representation of the Mandelbrot set. Since our knowledge of the set is not quite black-and-white, we use grayscale in our visual representation. It is based on the function mand(), which takes a Complex argument z0 and an int argument max and computes the Mandelbrot iteration sequence starting at z0, returning the number of iterations for which the magnitude stays less than (or equal to) 2, up to the limit max.

For each pixel, the main() method in Mandelbrot computes the complex number z0 corresponding to the pixel and then computes 255 - mand(z0, 255) to create a grayscale color for the pixel. Any pixel that is not black corresponds to a complex number that we know to be not in the Mandelbrot set because the magnitude of the numbers in its sequence exceeds 2 (and therefore will go to infinity). The black pixels (grayscale value 0) correspond to points that we assume to be in the set because the magnitude did not exceed 2 during the first 255 Mandelbrot iterations.

A set of four images depict the zooming in on the set of points of a Mandelbrot set.

The complexity of the images that this simple program produces is remarkable, even when we zoom in on a tiny portion of the plane. For even more dramatic pictures, we can use color (see EXERCISE 3.2.35). And the Mandelbrot set is derived from iterating just one function f(z) = (z2 + z0): we have a great deal to learn from studying the properties of other functions as well.

The simplicity of the code masks a substantial amount of computation. There are about 0.25 million pixels in a 512-by-512 image, and all of the black ones require 255 Mandelbrot iterations, so producing an image with Mandelbrot requires hundreds of millions of operations on Complex values.

Fascinating as it is to study, our primary interest in Mandelbrot is as an example client of Complex, to illustrate that computing with a data type that is not built into Java (complex numbers) is a natural and useful programming activity. Mandelbrot is a simple and natural expression of the computation, made so by the design and implementation of Complex. You could implement Mandelbrot without using Complex, but the code would essentially have to merge together the code in PROGRAM 3.2.6 and PROGRAM 3.2.7 and, therefore, would be much more difficult to understand. Whenever you can clearly separate tasks within a program, you should do so.


Program 3.2.7 Mandelbrot set

import java.awt.Color;

public class Mandelbrot
{
   private static int mand(Complex z0, int max)
   {
      Complex z = z0;
      for (int t = 0; t < max; t++)
      {
         if (z.abs() > 2.0) return t;
         z = z.times(z).plus(z0);
      }
      return max;
   }

   public static void main(String[] args)
   {
      double xc   = Double.parseDouble(args[0]);
      double yc   = Double.parseDouble(args[1]);
      double size = Double.parseDouble(args[2]);
      int n = 512;
      Picture picture = new Picture(n, n);
      for (int i = 0; i < n; i++)
         for (int j = 0; j < n; j++)
         {
            double x0 = xc - size/2 + size*i/n;
            double y0 = yc - size/2 + size*j/n;
            Complex z0 = new Complex(x0, y0);
            int gray = 255 - mand(z0, 255);
            Color c = new Color(gray, gray, gray);
            picture.set(i, n-1-j, c);
         }
      picture.show();
   }
}

x0, y0 | point in square
  z0   | x0 + i y0
  max  | iteration limit
xc, yc | center of square
 size  | square is size-by-size
   n   | grid is n-by-n pixels
  pic  | image for output
   c   | pixel color for output

An image of a Mandelbrot set in a two by two square is shown. The three command line arguments shown are “ -.5 0 2.”

A digital image depicts the zoomed image of region of Mandelbrot set with more of gray pixels and very few black pixels.

This program takes three command-line arguments that specify the center and size of a square region of interest, and makes a digital image showing the result of sampling the Mandelbrot set in that region at a size-by-size grid of evenly spaced points. It colors each pixel with a grayscale value that is determined by counting the number of iterations before the Mandelbrot sequence for the corresponding complex number exceeds 2.0 in magnitude, up to 255.


Commercial data processing

One of the driving forces behind the development of object-oriented programming has been the need for an extensive amount of reliable software for commercial data processing. As an illustration, we consider an example of a data type that might be used by a financial institution to keep track of customer information.

Suppose that a stockbroker needs to maintain customer accounts containing shares of various stocks. That is, the set of values the broker needs to process includes the customer’s name, number of different stocks held, number of shares and ticker symbol for each stock, and cash on hand. To process an account, the broker needs at least the operations defined in this API:

public class StockAccount

 

StockAccount(String filename)

create a new account from file

double

valueOf()

total value of account dollars

void

buy(int amount, String symbol)

add shares of stock to account

void

sell(int amount, String symbol)

subtract shares of stock from account

void

save(String filename)

save account to file

void

printReport()

print a detailed report of stocks and values

API for processing stock accounts (see PROGRAM 3.2.8)

The broker certainly needs to buy, sell, and provide reports to the customer, but the first key to understanding this kind of data processing is to consider the StockAccount() constructor and the save() method in this API. The customer information has a long lifetime and needs to be saved in a file or database. To process an account, a client program needs to read information from the corresponding file; process the information as appropriate; and, if the information changes, write it back to the file, saving it for later. To enable this kind of processing, we need a file format and an internal representation, or a data structure, for the account information.

As a (whimsical) running example, we imagine that a broker is maintaining a small portfolio of stocks in leading software companies for Alan Turing, the father of computing. As an aside: Turing’s life story is a fascinating one that is worth investigating further. Among many other things, he worked on computational cryptography that helped to bring about the end of World War II, he developed the basis for the theory of computing, he designed and built one of the first computers, and he was a pioneer in artificial intelligence research. It is perhaps safe to assume that Turing, whatever his financial situation as an academic researcher in the middle of the last century, would be sufficiently optimistic about the potential impact of computing software in today’s world that he would make some small investments.

File format

Modern systems often use text files, even for data, to minimize dependence on formats defined by any one program. For simplicity, we use a direct representation where we list the account holder’s name (a string), cash balance (a floating-point number), and number of stocks held (an integer), followed by a line for each stock giving the number of shares and the ticker symbol, as shown in the example at right. It is also wise to use tags such as <Name>, <Number of shares>, and so forth to label all the information so as to further minimize dependencies on any one program, but we omit such tags here for brevity.

% more Turing.txt
Turing, Alan
10.24
4
100 ADBE
 25 GOOG
 97 IBM
250 MSFT

File format

Data structure

To represent information for processing by Java programs, we use instance variables. They specify the type of information and provide the structure that we need to clearly refer to it in code. For our example, we clearly need the following:

• A String value for the account name

• A double value for the cash balance

• An int value for the number of stocks

• An array of String values for stock symbols

• An array of int values for numbers of shares

public class StockAccount
{
   private final String name;
   private double cash;
   private int n;
   private int[] shares;
   private String[] stocks;
   ...
}

Data structure blueprint

We directly reflect these choices in the instance variable declarations in StockAccount (PROGRAM 3.2.8). The arrays stocks[] and shares[] are known as parallel arrays. Given an index i, stocks[i] gives a stock symbol and shares[i] gives the number of shares of that stock in the account. An alternative design would be to define a separate data type for stocks to manipulate this information for each stock and maintain an array of objects of that type in StockAccount.

StockAccount includes a constructor, which reads a file in the specified format and creates an account with this information. Also, our broker needs to provide a periodic detailed report to customers, perhaps using the following code for printReport() in StockAccount, which relies on StockQuote (PROGRAM 3.1.8) to retrieve each stock’s price from the web.

public void printReport()
{
   StdOut.println(name);
   double total = cash;
   for (int i = 0; i < n; i++)
   {
      int amount = shares[i];
      double price = StockQuote.priceOf(stocks[i]);
      total += amount * price;
      StdOut.printf("%4d %5s ", amount, stocks[i]);
      StdOut.printf("%9.2f %11.2f
", price, amount*price);
   }
   StdOut.printf("%21s %10.2f
", "Cash: ", cash);
   StdOut.printf("%21s %10.2f
", "Total:", total);
}

Implementations of valueOf() and save() are straightforward (see EXERCISE 3.2.22). The implementations of buy() and sell() require the use of basic mechanisms introduced in SECTION 4.4, so we defer them to EXERCISE 4.4.65.

On the one hand, this client illustrates the kind of computing that was one of the primary drivers in the evolution of computing in the 1950s. Banks and other companies bought early computers precisely because of the need to do such financial reporting. For example, formatted writing was developed precisely for such applications. On the other hand, this client exemplifies modern web-centric computing, as it gets information directly from the web, without using a browser.

Beyond these basic methods, an actual application of these ideas would likely use a number of other clients. For example, a broker might want to create an array of all accounts, then process a list of transactions that both modify the information in those accounts and actually carry out the transactions through the web. Of course, such code needs to be developed with great care!


Program 3.2.8 Stock account

public class StockAccount
{
   private final String name;
   private double cash;
   private int n;
   private int[] shares;
   private String[] stocks;

   public StockAccount(String filename)
   {  // Build data structure from specified file.
      In in = new In(filename);
      name = in.readLine();
      cash = in.readDouble();
      n = in.readInt();
      shares = new int[n];
      stocks = new String[n];
      for (int i = 0; i < n; i++)
      {  // Process one stock.
         shares[i] = in.readInt();
         stocks[i] = in.readString();
      }
   }

   public static void main(String[] args)
   {
      StockAccount account = new StockAccount(args[0]);
      account.printReport();
   }
}

  name   | customer name
  cash   | cash balance
   n     | number of stocks
shares[] | share counts
stocks[] | stock symbols

This class for processing stock accounts illustrates typical usage of object-oriented programming for commercial data processing. See the accompanying text for an implementation of printReport() and EXERCISE 3.2.22 and 4.4.65 for priceOf(), save(), buy(), and sell().


% more Turing.txt
Turing, Alan
10.24
4
100 ADBE
 25 GOOG
 97 IBM
250 MSFT

% java StockAccount Turing.txt
Turing, Alan
 100  ADBE     70.56     7056.00
  25  GOOG    502.30    12557.50
  97   IBM    156.54    15184.38
 250  MSFT     45.68    11420.00
               Cash:       10.24
               Total:   46228.12

When you learned how to define functions that can be used in multiple places in a program (or in other programs) in CHAPTER 2, you moved from a world where programs are simply sequences of statements in a single file to the world of modular programming, summarized in our mantra: whenever you can clearly separate subtasks within a program, you should do so. The analogous capability for data, introduced in this chapter, moves you from a world where data has to be one of a few elementary types of data to a world where you can define your own data types. This profound new capability vastly extends the scope of your programming. As with the concept of a function, once you have learned to implement and use data types, you will marvel at the primitive nature of programs that do not use them.

But object-oriented programming is much more than structuring data. It enables us to associate the data relevant to a subtask with the operations that manipulate that data and to keep both separate in an independent module. With object-oriented programming, our mantra is this: whenever you can clearly separate data and associated operations for subtasks within a computation, you should do so.

The examples that we have considered are persuasive evidence that object-oriented programming can play a useful role in a broad range of activities. Whether we are trying to design and build a physical artifact, develop a software system, understand the natural world, or process information, a key first step is to define an appropriate abstraction, such as a geometric description of the physical artifact, a modular design of the software system, a mathematical model of the natural world, or a data structure for the information. When we want to write programs to manipulate instances of a well-defined abstraction, we can just implement it as a data type in a Java class and write Java programs to create and manipulate objects of that type.

Each time that we develop a class that makes use of other classes by creating and manipulating objects of the type defined by the class, we are programming at a higher layer of abstraction. In the next section, we discuss some of the design challenges inherent in this kind of programming.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.216.175