System, but they may not be reproduced for publication



Yüklə 83 Mb.
Pdf görüntüsü
səhifə28/82
tarix19.04.2023
ölçüsü83 Mb.
#106251
1   ...   24   25   26   27   28   29   30   31   ...   82
Java A Beginner’s Guide, Eighth Edition ( PDFDrive )

circular queue reuses locations in the underlying array when elements are removed. A
noncircular queue does not reuse locations and eventually becomes exhausted. For the
sake of simplicity, this example creates a noncircular queue, but with a little thought
and effort, you can easily transform it into a circular queue.
1. Create a file called QDemo.java.
2. Although there are other ways to support a queue, the method we will use is based
upon an array. That is, an array will provide the storage for the items put into the
queue. This array will be accessed through two indices. The put index determines


where the next element of data will be stored. The get index indicates at what location
the next element of data will be obtained. Keep in mind that the get operation is
consumptive, and it is not possible to retrieve the same element twice. Although the
queue that we will be creating stores characters, the same logic can be used to store any
type of object. Begin creating the Queue class with these lines:
3. The constructor for the Queue class creates a queue of a given size. Here is the
Queue constructor:
Notice that the put and get indices are initially set to zero.
4. The put( ) method, which stores elements, is shown next:
The method begins by checking for a queue­full condition. If putloc is equal to one
past the last location in the q array, there is no more room in which to store elements.
Otherwise, the new element is stored at that location and putloc is incremented. Thus,
putloc is always the index where the next element will be stored.
5. To retrieve elements, use the get( ) method, shown next:


Notice first the check for queue­empty. If getloc and putloc both index the same
element, the queue is assumed to be empty. This is why getloc and putloc were both
initialized to zero by the Queue constructor. Then, the next element is returned. In the
process, getloc is incremented. Thus, getloc always indicates the location of the next
element to be retrieved.
6. Here is the entire QDemo.java program:



7. The output produced by the program is shown here:


8. On your own, try modifying Queue so that it stores other types of objects. For
example, have it store ints or doubles.
THE FOR-EACH STYLE FOR LOOP
When working with arrays, it is common to encounter situations in which each element
in an array must be examined, from start to finish. For example, to compute the sum of
the values held in an array, each element in the array must be examined. The same
situation occurs when computing an average, searching for a value, copying an array,
and so on. Because such “start to finish” operations are so common, Java defines a
second form of the for loop that streamlines this operation.
The second form of the for implements a “for­each” style loop. A for­each loop cycles
through a collection of objects, such as an array, in strictly sequential fashion, from
start to finish. In recent years, for­each style loops have gained popularity among both
computer language designers and programmers. Originally, Java did not offer a for­
each style loop. However, with the release of JDK 5, the for loop was enhanced to
provide this option. The for­each style of for is also referred to as the enhanced for
loop. Both terms are used in this book.
The general form of the for­each style for is shown here.
for(type itr­var : collectionstatement­block
Here, type specifies the type, and itr­var specifies the name of an iteration variable
that will receive the elements from a collection, one at a time, from beginning to end.


The collection being cycled through is specified by collection. There are various types of
collections that can be used with the for, but the only type used in this book is the
array. With each iteration of the loop, the next element in the collection is retrieved and
stored in itr­var. The loop repeats until all elements in the collection have been
obtained. Thus, when iterating over an array of size N, the enhanced for obtains the
elements in the array in index order, from 0 to N–1.
Because the iteration variable receives values from the collection, type must be the
same as (or compatible with) the elements stored in the collection. Thus, when iterating
over arrays, type must be compatible with the element type of the array.
To understand the motivation behind a for­each style loop, consider the type of for
loop that it is designed to replace. The following fragment uses a traditional for loop to
compute the sum of the values in an array:
To compute the sum, each element in nums is read, in order, from start to finish. Thus,
the entire array is read in strictly sequential order. This is accomplished by manually
indexing the nums array by i, the loop control variable. Furthermore, the starting and
ending value for the loop control variable, and its increment, must be explicitly
specified.
Ask the Expert
Q
: Aside from arrays, what other types of collections can the for­each
style for loop cycle through?
A
: One of the most important uses of the for­each style for is to cycle through the
contents of a collection defined by the Collections Framework. The Collections
Framework is a set of classes that implement various data structures, such as lists,
vectors, sets, and maps. A discussion of the Collections Framework is beyond the
scope of this book, but detailed coverage of the Collections Framework can be
found in Java: The Complete Reference, Eleventh Edition (Oracle Press/McGraw­
Hill Education, 2019).


The for­each style for automates the preceding loop. Specifically, it eliminates the need
to establish a loop counter, specify a starting and ending value, and manually index the
array. Instead, it automatically cycles through the entire array, obtaining one element
at a time, in sequence, from beginning to end. For example, here is the preceding
fragment rewritten using a for­each version of the for:
With each pass through the loop, x is automatically given a value equal to the next
element in nums. Thus, on the first iteration, x contains 1, on the second iteration, x
contains 2, and so on. Not only is the syntax streamlined, it also prevents boundary
errors.
Here is an entire program that demonstrates the for­each version of the for just
described:
The output from the program is shown here:


As this output shows, the for­each style for automatically cycles through an array in
sequence from the lowest index to the highest.
Although the for­each for loop iterates until all elements in an array have been
examined, it is possible to terminate the loop early by using a break statement. For
example, this loop sums only the first five elements of nums:
There is one important point to understand about the for­each style for loop. Its
iteration variable is “read­only” as it relates to the underlying array. An assignment to
the iteration variable has no effect on the underlying array. In other words, you can’t
change the contents of the array by assigning the iteration variable a new value. For
example, consider this program:


The first for loop increases the value of the iteration variable by a factor of 10.
However, this assignment has no effect on the underlying array nums, as the second
for loop illustrates. The output, shown here, proves this point:
Iterating Over Multidimensional Arrays
The enhanced for also works on multidimensional arrays. Remember, however, that in
Java, multidimensional arrays consist of arrays of arrays. (For example, a two­
dimensional array is an array of one­dimensional arrays.) This is important when
iterating over a multidimensional array because each iteration obtains the next array,
not an individual element. Furthermore, the iteration variable in the for loop must be
compatible with the type of array being obtained. For example, in the case of a two­
dimensional array, the iteration variable must be a reference to a one­dimensional
array. In general, when using the for­each for to iterate over an array of N dimensions,
the objects obtained will be arrays of N–1 dimensions. To understand the implications
of this, consider the following program. It uses nested for loops to obtain the elements
of a two­dimensional array in row order, from first to last.


The output from this program is shown here:
In the program, pay special attention to this line:
Notice how x is declared. It is a reference to a one­dimensional array of integers. This is


necessary because each iteration of the for obtains the next array in nums, beginning
with the array specified by nums[0]. The inner for loop then cycles through each of
these arrays, displaying the values of each element.
Applying the Enhanced for
Since the for­each style for can only cycle through an array sequentially, from start to
finish, you might think that its use is limited. However, this is not true. A large number
of algorithms require exactly this mechanism. One of the most common is searching.
For example, the following program uses a for loop to search an unsorted array for a
value. It stops if the value is found.
The for­each style for is an excellent choice in this application because searching an
unsorted array involves examining each element in sequence. (Of course, if the array
were sorted, a binary search could be used, which would require a different style loop.)
Other types of applications that benefit from for­each style loops include computing an
average, finding the minimum or maximum of a set, looking for duplicates, and so on.
Now that the for­each style for has been introduced, it will be used where appropriate
throughout the remainder of this book.
STRINGS


STRINGS
From a day­to­day programming standpoint, one of the most important of Java’s data
types is String. String defines and supports character strings. In some other
programming languages, a string is an array of characters. This is not the case with
Java. In Java, strings are objects.
Actually, you have been using the String class since 
Chapter 1
, but you did not know it.
When you create a string literal, you are actually creating a String object. For example,
in the statement
the string "In Java, strings are objects." is automatically made into a String object by
Java. Thus, the use of the String class has been “below the surface” in the preceding
programs. In the following sections, you will learn to handle it explicitly. Be aware,
however, that the String class is quite large, and we will only scratch its surface here. It
is a class that you will want to explore on its own.
Constructing Strings
You can construct a String just like you construct any other type of object: by using
new and calling the String constructor. For example:
This creates a String object called str that contains the character string "Hello". You
can also construct a String from another String. For example:
After this sequence executes, str2 will also contain the character string "Hello".
Another easy way to create a String is shown here:
In this case, str is initialized to the character sequence "Java strings are powerful."
Once you have created a String object, you can use it anywhere that a quoted string is
allowed. For example, you can use a String object as an argument to println( ), as


shown in this example:
The output from the program is shown here:
Operating on Strings
The String class contains several methods that operate on strings. Here are the general
forms for a few:
Here is a program that demonstrates these methods:


This program generates the following output:


You can concatenate (join together) two strings using the + operator. For example, this
statement
initializes str4 with the string "OneTwoThree".
Ask the Expert
Q
: Why does String define the equals( ) method? Can’t I just use ==?
A
: The equals( ) method compares the character sequences of two String
objects for equality. Applying the == to two String references simply determines
whether the two references refer to the same object.
Arrays of Strings
Like any other data type, strings can be assembled into arrays. For example:


Here is the output from this program:
Strings Are Immutable
The contents of a String object are immutable. That is, once created, the character
sequence that makes up the string cannot be altered. This restriction allows Java to
implement strings more efficiently. Even though this probably sounds like a serious
drawback, it isn’t. When you need a string that is a variation on one that already exists,
simply create a new string that contains the desired changes. Since unused String
objects are automatically garbage collected, you don’t even need to worry about what
happens to the discarded strings. It must be made clear, however, that String
reference variables may, of course, change the object to which they refer. It is just that
the contents of a specific String object cannot be changed after it is created.
Ask the Expert


Q
: You say that once created, String objects are immutable. I
understand that, from a practical point of view, this is not a serious
restriction, but what if I want to create a string that can be changed?
A
: You’re in luck. Java offers a class called StringBuffer, which creates string
objects that can be changed. For example, in addition to the charAt( ) method,
which obtains the character at a specific location, StringBuffer defines
setCharAt( ), which sets a character within the string. Java also supplies
StringBuilder, which is related to StringBuffer, and also supports strings that
can be changed. However, for most purposes you will want to use String, not
StringBuffer or StringBuilder.
To fully understand why immutable strings are not a hindrance, we will use another of
String’s methods: substring( ). The substring( ) method returns a new string that
contains a specified portion of the invoking string. Because a new String object is
manufactured that contains the substring, the original string is unaltered, and the rule
of immutability remains intact. The form of substring( ) that we will be using is
shown here:
String substring(int startIndex, int endIndex)
Here, startIndex specifies the beginning index, and endIndex specifies the stopping
point. Here is a program that demonstrates substring( ) and the principle of
immutable strings:
Here is the output from the program:


As you can see, the original string orgstr is unchanged, and substr contains the
substring.
Using a String to Control a switch Statement
As explained in 
Chapter 3
, prior to JDK 7, a switch had to be controlled by an integer
type, such as int or char. This precluded the use of a switch in situations in which one
of several actions is selected based on the contents of a string. Instead, an if­else­if
ladder was the typical solution. Although an if­else­if ladder is semantically correct, a
switch statement would be the more natural idiom for such a selection. Fortunately,
this situation has been remedied. Today, you can use a String to control a switch. This
results in more readable, streamlined code in many situations.
Here is an example that demonstrates controlling a switch with a String:
As you would expect, the output from the program is


The string contained in command (which is "cancel" in this program) is tested against
the case constants. When a match is found (as it is in the second case), the code
sequence associated with that sequence is executed.
Being able to use strings in a switch statement can be very convenient and can
improve the readability of some code. For example, using a string­based switch is an
improvement over using the equivalent sequence of if/else statements. However,
switching on strings can be less efficient than switching on integers. Therefore, it is best
to switch on strings only in cases in which the controlling data is already in string form.
In other words, don’t use strings in a switch unnecessarily.
USING COMMAND-LINE ARGUMENTS
Now that you know about the String class, you can understand the args parameter to
main( ) that has been in every program shown so far. Many programs accept what are
called command­line arguments. A command­line argument is the information that
directly follows the program’s name on the command line when it is executed. To access
the command­line arguments inside a Java program is quite easy—they are stored as
strings in the String array passed to main( ). For example, the following program
displays all of the command­line arguments that it is called with:
If CLDemo is executed like this,
you will see the following output:


Notice that the first argument is stored at index 0, the second argument is stored at
index 1, and so on.
To get a taste of the way command­line arguments can be used, consider the next
program. It takes one command­line argument that specifies a person’s name. It then
searches through a two­dimensional array of strings for that name. If it finds a match, it
displays that person’s telephone number.
Here is a sample run:
USING TYPE INFERENCE WITH LOCAL
VARIABLES
Recently, a new feature called local variable type inference was added to the Java
language. To begin, let’s review two important aspects of variables. First, all variables in
Java must be declared prior to their use. Second, a variable can be initialized with a
value when it is declared. Furthermore, when a variable is initialized, the type of the


initializer must be the same as (or convertible to) the declared type of the variable.
Thus, in principle, it would not be necessary to specify an explicit type for an initialized
variable because it could be inferred from the type of its initializer. Of course, in the
past, Java did not support such inference and all variables required an explicitly
declared type, whether they were initialized or not. Today, that situation has changed.
Beginning with JDK 10, it is now possible to let the compiler infer the type of a local
variable based on the type of its initializer, thus avoiding the need to explicitly specify
the type. Local variable type inference offers a number of advantages. For example, it
can streamline code by eliminating the need to redundantly specify a variable’s type
when it can be inferred from its initializer. It can simplify declarations in cases in which
the type is quite lengthy, such as can be the case with some class names. It can also be
helpful when a type is difficult to discern or cannot be denoted. (An example of a type
that cannot be denoted is the type of an anonymous class, discussed in 
Chapter 16
.)
Furthermore, local variable type inference has become a common part of the
contemporary programming environment. Its inclusion in Java helps keep Java up­to­
date with evolving trends in language design. To support local variable type inference,
the context­sensitive identifier var was added to Java as a reserved type name.
To use local variable type inference, the variable must be declared with var as the type
name and it must include an initializer. Let’s begin with a simple example. Consider the
following statement that declares a local double variable called avg that is initialized
with the value 10.0:
Using type inference, this declaration can also be written like this:
In both cases, avg will be of type double. In the first case, its type is explicitly
specified. In the second, its type is inferred as double because the initializer 10.0 is of
type double.
As mentioned, var was added as a context­sensitive identifier. When it is used as the
type name in the context of a local variable declaration, it tells the compiler to use type
inference to determine the type of the variable being declared based on the type of the
initializer. Thus, in a local variable declaration, var is a placeholder for the actual
inferred type. However, when used in most other places, var is simply a user­defined
identifier with no special meaning. For example, the following declaration is still valid:


In this case, the type is explicitly specified as int and var is the name of the variable
being declared. Even though it is a context­sensitive identifier, there are a few places in
which the use of var is illegal. It cannot be used as the name of a class, for example.
The following program puts the preceding discussion into action:
Here is the output:
The preceding example uses var to declare only simple variables, but you can also use
var to declare an array. For example:
Notice that neither var nor myArray has brackets. Instead, the type of myArray is
inferred to be int[ ]. Furthermore, you cannot use brackets on the left side of a var
declaration. Thus, both of these declarations are invalid:


In the first line, an attempt is made to bracket var. In the second, an attempt is made to
bracket myArray. In both cases, the use of the brackets is wrong because the type is
inferred from the type of the initializer.
It is important to emphasize that var can be used to declare a variable only when that
variable is initialized. Therefore, the following statement is wrong:
Also, remember that var can be used only to declare local variables. It cannot be used
when declaring instance variables, parameters, or return types, for example.
Local Variable Type Inference with Reference Types
The preceding examples introduced the fundamentals of local variable type inference
using primitive types. However, it is with reference types, such as class types, that the
full benefits of type inference become apparent. Moreover, local variable type inference
with reference types constitutes a primary use of this feature.
Let’s again begin with a simple example. The following declarations use type inference
to declare two String variables called myStr and mySubStr:
Recall that a quoted string is an object of type String. Because a quoted string is used
as an initializer, the type of myStr is inferred to be String. The type of mySubStr is
also inferred to be String because the type of reference returned by the substring( )
method is String.
Of course, you can also use local variable type inference with user­defined classes, as
the following program illustrates. It creates a class called MyClass and then uses local
variable type inference to declare and initialize an object of that class.


The output of the program is shown here:
In the program, pay special attention to this line:
Here, the type of mc will be inferred as MyClass because that is the type of the
initializer, which is a new MyClass object.
As mentioned, one of the primary benefits of local variable type inference is its ability to
streamline code, and it is with reference types where such streamlining is most
apparent. As you advance in your study of Java, you will find that many class types have
rather long names. For example, in 
Chapter 10
you will learn about the
FileInputStream class, which is used to open a file for input operations. Without the
use of type inference, you would declare and initialize a FileInputStream using a
traditional declaration like the one shown here:
With the use of var, it can now be written like this:
Here, fin is inferred to be of type FileInputStream because that is the type of its


initializer. There is no need to explicitly repeat the type name. As a result, this
declaration of fin is substantially shorter than writing it the traditional way. Thus, the
use of var streamlines the declaration. In general, the streamlining attribute of local
variable type inference helps lessen the tedium of entering long type names into your
program. Of course, local variable type inference must be used carefully to avoid
reducing the readability of your program and thus obscuring its meaning. In essence, it
is a feature that you should use wisely.
Using Local Variable Type Inference in a for Loop
Another place that local variable type inference can be used is in a for loop when
declaring and initializing the loop control variable inside a traditional for loop, or when
specifying the iteration variable in a for­each for. The following program shows an
example of each case:
The output is shown here:
In this example, loop control variable x in this line:
is inferred to be type double because that is the type of its initializer. Iteration variable
v in this line:


is inferred to be of type int because that is the element type of the array nums.
Some var Restrictions
In addition to those mentioned in the preceding discussion, several other restrictions
apply to the use of var. Only one variable can be declared at a time; a variable cannot
use null as an initializer; and the variable being declared cannot be used by the
initializer expression. Although you can declare an array type using var, you cannot use
var with an array initializer.
For example, this is valid:
but this is not:
As mentioned earlier, var cannot be used as the name of a class. It also cannot be used
as the name of other reference types, including an interface, enumeration, or
annotation, which are described later in this book. Here are two other restrictions that
relate to Java features also described later, but mentioned here in the interest of
completeness. Local variable type inference cannot be used to declare the exception
type caught by a catch statement. Also, neither lambda expressions nor method
references can be used as initializers.
NOTE
At the time of this writing, many readers will be using Java environments that predate
JDK 10. So that as many of the code examples as possible can be compiled and run with
older JDKs, local variable type inference will not be used by most of the programs in the
remainder of this edition of the book. Of course, going forward, you should consider the
use of local variable type inference where appropriate in your own code.
THE BITWISE OPERATORS


In 
Chapter 2
you learned about Java’s arithmetic, relational, and logical operators.
Although these are often the most commonly used, Java provides additional operators
that expand the set of problems to which Java can be applied: the bitwise operators.
The bitwise operators can be used on values of type long, int, short, char, or byte.
Bitwise operations cannot be used on boolean, float, or double, or class types. They
are called the bitwise operators because they are used to test, set, or shift the individual
bits that make up a value. Bitwise operations are important to a wide variety of
systems­level programming tasks in which status information from a device must be
interrogated or constructed. 
Table 5­1
 lists the bitwise operators.
Table 5­1 The Bitwise Operators
The Bitwise AND, OR, XOR, and NOT Operators
The bitwise operators AND, OR, XOR, and NOT are &, |, ^, and ~. They perform the
same operations as their Boolean logical equivalents described in 
Chapter 2
. The
difference is that the bitwise operators work on a bit­by­bit basis. The following table
shows the outcome of each operation using 1’s and 0’s:
In terms of one common usage, you can think of the bitwise AND as a way to turn bits
off. That is, any bit that is 0 in either operand will cause the corresponding bit in the
outcome to be set to 0. For example:


The following program demonstrates the & by turning any lowercase letter into
uppercase by resetting the 6th bit to 0. As the Unicode/ASCII character set is defined,
the lowercase letters are the same as the uppercase ones except that the lowercase ones
are greater in value by exactly 32. Therefore, to transform a lowercase letter to
uppercase, just turn off the 6th bit, as this program illustrates:
The output from this program is shown here:
The value 65,503 used in the AND statement is the decimal representation of 1111 1111
1101 1111. Thus, the AND operation leaves all bits in ch unchanged except for the 6th
one, which is set to 0.
The AND operator is also useful when you want to determine whether a bit is on or off.
For example, this statement determines whether bit 4 in status is set:
The number 8 is used because it translates into a binary value that has only the 4th bit
set. Therefore, the if statement can succeed only when bit 4 of status is also on. An
interesting use of this concept is to show the bits of a byte value in binary format.


The output is shown here:
The for loop successively tests each bit in val, using the bitwise AND, to determine
whether it is on or off. If the bit is on, the digit 1 is displayed; otherwise, 0 is displayed.
In 
Try This 5­3
, you will see how this basic concept can be expanded to create a class
that will display the bits in any type of integer.
The bitwise OR, as the reverse of AND, can be used to turn bits on. Any bit that is set to
1 in either operand will cause the corresponding bit in the result to be set to 1. For
example:
We can make use of the OR to change the uppercasing program into a lowercasing
program, as shown here:


The output from this program is shown here:
The program works by ORing each character with the value 32, which is 0000 0000
0010 0000 in binary. Thus, 32 is the value that produces a value in binary in which
only the 6th bit is set. When this value is ORed with any other value, it produces a
result in which the 6th bit is set and all other bits remain unchanged. As explained, for
characters this means that each uppercase letter is transformed into its lowercase
equivalent.
An exclusive OR, usually abbreviated XOR, will result in a set bit if, and only if, the bits
being compared are different, as illustrated here:
The XOR operator has an interesting property that makes it a simple way to encode a
message. When some value X is XORed with another value Y, and then that result is
XORed with Y again, X is produced. That is, given the sequence
then R2 is the same value as X. Thus, the outcome of a sequence of two XORs can
produce the original value.


You can use this principle to create a simple cipher program in which some integer is
the key that is used to both encode and decode a message by XORing the characters in
that message. To encode, the XOR operation is applied the first time, yielding the
cipher text. To decode, the XOR is applied a second time, yielding the plain text. Of
course, such a cipher has no practical value, being trivially easy to break. It does,
however, provide an interesting way to demonstrate the XOR. Here is a program that
uses this approach to encode and decode a short message:
Here is the output:
As you can see, the result of two XORs using the same key produces the decoded
message.
The unary one’s complement (NOT) operator reverses the state of all the bits of the
operand. For example, if some integer called A has the bit pattern 1001 0110, then ~A
produces a result with the bit pattern 0110 1001.
The following program demonstrates the NOT operator by displaying a number and its


complement in binary:
Here is the output:
The Shift Operators
In Java it is possible to shift the bits that make up a value to the left or to the right by a
specified amount. Java defines the three bit­shift operators shown here:
The general forms for these operators are shown here:

Yüklə 83 Mb.

Dostları ilə paylaş:
1   ...   24   25   26   27   28   29   30   31   ...   82




Verilənlər bazası müəlliflik hüququ ilə müdafiə olunur ©genderi.org 2024
rəhbərliyinə müraciət

    Ana səhifə