An object is a collection of data that provides a set of methods. For example, Scanner
, which you saw in “The Scanner Class”, is an object that provides methods for parsing input. System.out
and System.in
are also objects.
Strings are objects too. They contain characters and provide methods for manipulating character data. Other data types, like Integer
, contain numbers and provide methods for manipulating number data. We will explore some of these methods in this chapter.
Not everything in Java is an object: int
, double
, char
, and boolean
are primitive types. When you declare a variable with a primitive type, Java reserves a small amount of memory to store its value. Figure 9-1 shows how the following values are stored in memory:
int
number
=
-
2
;
char
symbol
=
'!'
;
As you learned in “Accessing Elements”, an array variable stores a reference to an array. For example, the following line declares a variable named array
and creates an array of three characters:
char
[]
array
=
{
'c'
,
'a'
,
't'
};
Figure 9-2 shows them both, with a box to represent the location of the variable and an arrow pointing to the location of the array.
Objects work in a similar way. For example, this line declares a String
variable named word
and creates a String
object, as shown in Figure 9-3:
String
word
=
"dog"
;
String
objectObjects and arrays are usually created with the new
keyword, which allocates memory for them. For convenience, you don’t have to use new
to create strings:
String
word
=
new
String
(
"dog"
);
// creates a string object
String
word
=
"dog"
;
// implicitly creates a string object
Recall from “String Comparison” that you need to use the equals
method to compare strings. The equals
method traverses the String
objects and tests whether they contain the same characters.
To test whether two integers or other primitive types are equal, you can simply use the ==
operator. But two String
objects with the same characters would not be considered equal in the ==
sense. The ==
operator, when applied to string variables, tests only whether they refer to the same object.
Often when you declare an object variable, you assign it to reference an object. But sometimes you want to declare a variable that doesn’t refer to an object, at least initially.
String
name
=
null
;
int
[]
combo
=
null
;
The value null
is represented in memory diagrams by a small box with no arrow, as in Figure 9-4.
null
If you try to use a variable that is null
by invoking a method or accessing an element, Java throws a NullPointerException
:
System
.
out
.
println
(
name
.
length
());
// NullPointerException
System
.
out
.
println
(
combo
[
0
]);
// NullPointerException
For example, the methods toLowerCase
and toUpperCase
convert uppercase letters to lowercase, and vice versa. These methods are often a source of confusion, because it sounds like they modify strings. But neither these methods nor any others can change a string, because strings are immutable.
When you invoke toUpperCase
on a string, you get a new String
object as a result. For example:
String
name
=
"Alan Turing"
;
String
upperName
=
name
.
toUpperCase
();
String
name
=
"Alan Turing"
;
name
.
toUpperCase
();
// ignores the return value
System
.
out
.
println
(
name
);
String
name
=
"Alan Turing"
;
name
=
name
.
toUpperCase
();
// references the new string
System
.
out
.
println
(
name
);
String
text
=
"Computer Science is fun!"
;
text
=
text
.
replace
(
"Computer Science"
,
"CS"
);
int
i
=
5
;
System
.
out
.
println
(
i
.
equals
(
5
));
// compiler error
But for each primitive type, there is a corresponding wrapper class in the Java library. The wrapper class for int
is named Integer
, with a capital I
:
Integer
i
=
Integer
.
valueOf
(
5
);
System
.
out
.
println
(
i
.
equals
(
5
));
// displays true
Integer
x
=
Integer
.
valueOf
(
123
);
Integer
y
=
Integer
.
valueOf
(
123
);
if
(
x
==
y
)
{
// false
System
.
out
.
println
(
"x and y are the same object"
);
}
if
(
x
.
equals
(
y
))
{
// true
System
.
out
.
println
(
"x and y have the same value"
);
}
Because x
and y
refer to different objects, this code displays only “x and y have the same value”
.
String
str
=
"12345"
;
int
num
=
Integer
.
parseInt
(
str
);
int
num
=
12345
;
String
str
=
Integer
.
toString
(
num
);
The result is the String
object "12345"
.
It’s always possible to convert a primitive value to a string, but not the other way around. For example, say we try to parse an invalid string like this:
String
str
=
"five"
;
int
num
=
Integer
.
parseInt
(
str
);
// NumberFormatException
parseInt
throws a NumberFormatException
, because the characters in the string "five"
are not digits.
Now that you know about strings, arrays, and wrapper classes, we can finally explain the args
parameter of the main
method, which we have been ignoring since Chapter 1. If you are unfamiliar with the command-line interface, please read “Command-Line Interface”.
Let’s write a program to find the maximum value in a sequence of numbers. Rather than read the numbers from System.in
by using a Scanner
, we’ll pass them as command-line arguments. Here is a starting point:
import
java.util.Arrays
;
public
class
Max
{
public
static
void
main
(
String
[]
args
)
{
System
.
out
.
println
(
Arrays
.
toString
(
args
));
}
}
You can run this program from the command line by typing this:
java Max
The output indicates that args
is an empty array; that is, it has no elements:
[]
java Max 10 -3 55 0 14
[10, -3, 55, 0, 14]
The following code uses an enhanced for
loop (see “The Enhanced for Loop”) to parse the arguments and find the largest value:
int
max
=
Integer
.
MIN_VALUE
;
for
(
String
arg
:
args
)
{
int
value
=
Integer
.
parseInt
(
arg
);
if
(
value
>
max
)
{
max
=
value
;
}
}
System
.
out
.
println
(
"The max is "
+
max
);
We begin by initializing max
to the smallest (most negative) number an int
can represent. That way, the first value we parse will replace max
. As we find larger values, they will replace max
as well.
If args
is empty, the result will be MIN_VALUE
. We can prevent this situation from happening by checking args
at the beginning of the program:
if
(
args
.
length
==
0
)
{
System
.
err
.
println
(
"Usage: java Max <numbers>"
);
return
;
}
It’s customary for programs that require command-line arguments to display a usage message if the arguments are not valid. For example, if you run javac
or java
from the command line without any arguments, you will get a very long message.
As we discussed in “Validating Input”, you should never assume that program input will be in the correct format. Sometimes users make mistakes, such as pressing the wrong key or misreading instructions.
public
static
boolean
isCapitalized
(
String
str
)
{
return
Character
.
isUpperCase
(
str
.
charAt
(
0
));
}
The expression str.charAt(0)
makes two assumptions: the string object referenced by str
exists, and it has at least one character. What if these assumptions don’t hold at run-time?
We can prevent these exceptions by validating str
at the start of the method. If it’s invalid, we return before executing the rest of the method:
public
static
boolean
isCapitalized
(
String
str
)
{
if
(
str
==
null
||
str
.
isEmpty
())
{
return
false
;
}
return
Character
.
isUpperCase
(
str
.
charAt
(
0
));
}
Notice that null
and empty are different concepts, as shown in Figure 9-5. The variable str1
is null
, meaning that it doesn’t reference an object. The variable str2
refers to the empty string, an object that exists.
null
and empty stringBeginners sometimes make the mistake of checking for empty first. Doing so causes a NullPointerException
, because you can’t invoke methods on variables that are null
:
if
(
str
.
isEmpty
()
||
str
==
null
)
{
// wrong!
Checking for null
first prevents the NullPointerException
. If str
is null
, the ||
operator will short-circuit (see “Logical Operators”) and evaluate to true
immediately. As a result, str.isEmpty()
will not be called.
It might not be clear at this point why you would ever need an integer object when you can just use an int
or long
. One advantage is the variety of methods that Integer
and Long
provide. But there is another reason: when you need very large integers that exceed Long.MAX_VALUE
.
long
x
=
17
;
BigInteger
big
=
BigInteger
.
valueOf
(
x
);
String
s
=
"12345678901234567890"
;
BigInteger
bigger
=
new
BigInteger
(
s
);
BigInteger
a
=
BigInteger
.
valueOf
(
17
);
BigInteger
b
=
BigInteger
.
valueOf
(
1700000000
);
BigInteger
c
=
a
.
add
(
b
);
One challenge of programming, especially for beginners, is figuring out how to divide a program into methods. In this section, we present a design process that allows you to divide a program into methods as you go along. The process is called encapsulation and generalization. The essential steps are as follows:
Write a few lines of code in main
or another method, and test them.
When they are working, wrap them in a new method and test again.
If it’s appropriate, replace literal values with variables and parameters.
for
(
int
i
=
1
;
i
<=
6
;
i
++)
{
System
.
out
.
printf
(
"%4d"
,
2
*
i
);
}
System
.
out
.
println
();
2 4 6 8 10 12
The next step is to encapsulate the code; that is, we wrap the code in a method:
public
static
void
printRow
()
{
for
(
int
i
=
1
;
i
<=
6
;
i
++)
{
System
.
out
.
printf
(
"%4d"
,
2
*
i
);
}
System
.
out
.
println
();
}
public
static
void
printRow
(
int
n
)
{
for
(
int
i
=
1
;
i
<=
6
;
i
++)
{
System
.
out
.
printf
(
"%4d"
,
n
*
i
);
// generalized n
}
System
.
out
.
println
();
}
3 6 9 12 15 18
for
(
int
i
=
1
;
i
<=
6
;
i
++)
{
printRow
(
i
);
}
And the output looks like this:
1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24 5 10 15 20 25 30 6 12 18 24 30 36
The previous result is similar to the nested loops approach in “Nested Loops”. However, the inner loop is now encapsulated in the printRow
method. We can encapsulate the outer loop in a method too:
public
static
void
printTable
()
{
for
(
int
i
=
1
;
i
<=
6
;
i
++)
{
printRow
(
i
);
}
}
The initial version of printTable
always displays six rows. We can generalize it by replacing the literal 6
with a parameter:
public
static
void
printTable
(
int
rows
)
{
for
(
int
i
=
1
;
i
<=
rows
;
i
++)
{
// generalized rows
printRow
(
i
);
}
}
Here is the output of printTable(7)
:
1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24 5 10 15 20 25 30 6 12 18 24 30 36 7 14 21 28 35 42
That’s better, but it always displays the same number of columns. We can generalize more by adding a parameter to printRow
:
public
static
void
printRow
(
int
n
,
int
cols
)
{
for
(
int
i
=
1
;
i
<=
cols
;
i
++)
{
// generalized cols
System
.
out
.
printf
(
"%4d"
,
n
*
i
);
}
System
.
out
.
println
();
}
Now printRow
takes two parameters: n
is the value whose multiples should be displayed, and cols
is the number of columns. Since we added a parameter to printRow
, we also have to change the line in printTable
where it is invoked:
public
static
void
printTable
(
int
rows
)
{
for
(
int
i
=
1
;
i
<=
rows
;
i
++)
{
printRow
(
i
,
rows
);
}
}
When this line executes, it evaluates rows
and passes the value, which is 7
in this example, as an argument. In printRow
, this value is assigned to cols
. As a result, the number of columns equals the number of rows, so we get a square 7 × 7 table, instead of the previous 7 × 6 table.
When you generalize a method appropriately, you often find that it has capabilities you did not plan. For example, you might notice that the multiplication table is symmetric. Since , all the entries in the table appear twice. You could save ink by printing half of the table, and you would have to change only one line of printTable
:
printRow
(
i
,
i
);
// using i for both n and cols
This means the length of each row is the same as its row number. The result is a triangular multiplication table:
1 2 4 3 6 9 4 8 12 16 5 10 15 20 25 6 12 18 24 30 36 7 14 21 28 35 42 49
Generalization makes code more versatile, more likely to be reused, and sometimes easier to write.
A way of organizing code and data into objects, rather than independent methods.
A collection of related data that comes with a set of methods that operate on the data.
A data type that stores a single value and provides no methods.
An object that, once created, cannot be modified. Strings are immutable by design.
Classes in java.lang
that provide constants and methods for working with primitive types.
In Chapter 2, we defined parse as what the compiler does to analyze a program. Now you know that it means to read a string and interpret or translate it.
A process for determining what methods a class or program should have.
To wrap data inside an object, or to wrap statements inside a method.
The code for this chapter is in the ch09
directory of ThinkJavaCode2
. See “Using the Code Examples” for instructions on downloading the repository. Before you start the exercises, we recommend that you compile and run the examples.
boolean | char | int | double | String | |
boolean | |||||
char | |||||
int | |||||
double | |||||
String |
What happens when you add ""
(the empty string) to the other types; for example, "" + 5
?
You might be sick of the factorial
method by now, but we’re going to do one more version.
public
static
int
pow
(
int
x
,
int
n
)
{
if
(
n
==
0
)
return
1
;
// find x to the n/2 recursively
int
t
=
pow
(
x
,
n
/
2
);
// if n is even, the result is t squared
// if n is odd, the result is t squared times x
if
(
n
%
2
==
0
)
{
return
t
*
t
;
}
else
{
return
t
*
t
*
x
;
}
}
Write a method called myexp
that takes x
and n
as parameters and estimates ex by adding the first n
terms of this series. You can use the factorial
method from “Value-Returning Methods” or your iterative version from the previous exercise.
You can make this method more efficient by observing that the numerator of each term is the same as its predecessor multiplied by x
, and the denominator is the same as its predecessor multiplied by i
.
Use this observation to eliminate the use of Math.pow
and factorial
, and check that you get the same result.
Write a method called check
that takes a parameter, x
, and displays x
, myexp(x)
, and Math.exp(x)
. The output should look something like this:
1.0 2.708333333333333 2.718281828459045
You can use the escape sequence "\t"
to put a tab character between columns of a table.
Vary the number of terms in the series (the second argument that check
sends to myexp
) and see the effect on the accuracy of the result. Adjust this value until the estimated value agrees with the correct answer when x
is 1
.
Write a loop in main
that invokes check
with the values 0.1, 1.0, 10.0
, and 100.0
. How does the accuracy of the result vary as x
varies? Compare the number of digits of agreement rather than the difference between the actual and estimated values.
Add a loop in main
that checks myexp
with the values -0.1, -1.0, -10.0
, and -100.0
. Comment on the accuracy.
The goal of this exercise is to practice encapsulation and generalization using some of the examples in previous chapters.
Starting with the code in “Traversing Arrays”, write a method called powArray
that takes a double
array, a
, and returns a new array that contains the elements of a
squared. Generalize it to take a second argument and raise the elements of a
to the given power.
Starting with the code in “The Enhanced for Loop”, write a method called histogram
that takes an int
array of scores from 0 to (but not including) 100, and returns a histogram of 100 counters. Generalize it to take the number of counters as an argument.
The following code fragment traverses a string and checks whether it has the same number of opening and closing parentheses:
String
s
=
"((3 + 7) * 2)"
;
int
count
=
0
;
for
(
int
i
=
0
;
i
<
s
.
length
();
i
++)
{
char
c
=
s
.
charAt
(
i
);
if
(
c
==
'('
)
{
count
++;
}
else
if
(
c
==
')'
)
{
count
--;
}
}
System
.
out
.
println
(
count
);