This section begins with a discussion of theNumberclass in thejava.langpackage, its subclasses, and the situations where you would use instantiations of these classes rather than the primitive number types.
This section also presents the
PrintStreamandDecimalFormatclasses, which provide methods for writing formatted numerical output.
Finally, the
Mathclass injava.langis discussed. It contains mathematical functions to complement the operators built into the language. This class has methods for the trigonometric functions, exponential functions, and so forth.
Author: strong
Strings
Strings, which are widely used in Java programming, are a sequence of characters. In the Java programming language, strings are objects.
The Java platform provides the
Stringclass to create and manipulate strings.
Creating Strings
The most direct way to create a string is to write:
In this case, “Hello world!” is a string literal—a series of characters in your code that is enclosed in double quotes. Whenever it encounters a string literal in your code, the compiler creates aString greeting = “Hello world!”;
Stringobject with its value—in this case,Hello world!.
As with any other object, you can create
Stringobjects by using thenewkeyword and a constructor. TheStringclass has 11 constructors that allow you to provide the initial value of the string using different sources, such as an array of characters:
The last line of this code snippet displayschar[] helloArray = { ‘h’, ‘e’, ‘l’, ‘l’, ‘o’, ‘.’};
String helloString = new String(helloArray);
System.out.println(helloString);
hello.
Note: TheStringclass is immutable, so that once it is created aStringobject cannot be changed. TheStringclass has a number of methods, some of which will be discussed below, that appear to modify strings. Since strings are immutable, what these methods really do is create and return a new string that contains the result of the operation.
String Length
Methods used to obtain information about an object are known as accessor methods. One accessor method that you can use with strings is thelength()method, which returns the number of characters contained in the string object. After the following two lines of code have been executed,lenequals 17:
A palindrome is a word or sentence that is symmetric—it is spelled the same forward and backward, ignoring case and punctuation. Here is a short and inefficient program to reverse a palindrome string. It invokes theString palindrome = “Dot saw I was Tod”;
int len = palindrome.length();
StringmethodcharAt(i), which returns the ith character in the string, counting from 0.
Running the program produces this output:public class StringDemo {
public static void main(String[] args) {
String palindrome = “Dot saw I was Tod”;
int len = palindrome.length();
char[] tempCharArray = new char[len];
char[] charArray = new char[len];// put original string in an array of chars
for (int i = 0; i < len; i++) {
tempCharArray[i] = palindrome.charAt(i);
}// reverse array of chars
for (int j = 0; j < len; j++) {
charArray[j] = tempCharArray[len – 1 – j];
}String reversePalindrome = new String(charArray);
System.out.println(reversePalindrome);
}
}
To accomplish the string reversal, the program had to convert the string to an array of characters (firstdoT saw I was toD
forloop), reverse the array into a second array (secondforloop), and then convert back to a string. TheStringclass includes a method,getChars(), to convert a string, or a portion of a string, into an array of characters so we could replace the firstforloop in the program above with
palindrome.getChars(0, len – 1, tempCharArray, 0);
Concatenating Strings
TheStringclass includes a method for concatenating two strings:
This returns a new string that is string1 with string2 added to it at the end.string1.concat(string2);
You can also use the
concat()method with string literals, as in:
Strings are more commonly concatenated with the“My name is “.concat(“Rumplestiltskin”);
+operator, as in
which results in“Hello,” + ” world” + “!”
The“Hello, world!”
+operator is widely used in
which printsString string1 = “saw I was “;
System.out.println(“Dot ” + string1 + “Tod”);
Such a concatenation can be a mixture of any objects. For each object that is not aDot saw I was Tod
String, itstoString()method is called to convert it to aString.
Note: The Java programming language does not permit literal strings to span lines in source files, so you must use the+concatenation operator at the end of each line in a multi-line string. For example,
Breaking strings between lines using theString quote = “Now is the time for all good ” +
“men to come to the aid of their country.”;
+concatenation operator is, once again, very common in
Creating Format Strings
You have seen the use of theprintf()andformat()methods to print output with formatted numbers. TheStringclass has an equivalent class method,format(), that returns aStringobject rather than aPrintStreamobject.
Using
String’sstaticformat()method allows you to create a formatted string that you can reuse, as opposed to a one-time print statement. For example, instead of
you can writeSystem.out.printf(“The value of the float variable is %f, while the value of the ” +
“integer variable is %d, and the string is %s”, floatVar, intVar, stringVar);
String fs;
fs = String.format(“The value of the float variable is %f, while the value of the ” +
“integer variable is %d, and the string is %s”, floatVar, intVar, stringVar);
System.out.println(fs);
Numbers and Strings
Numbers
This section begins with a discussion of theNumberclass (in thejava.langpackage) and its subclasses. In particular, this section talks about the situations where you would use instantiations of these classes rather than the primitive data types. Additionally, this section talks about other classes you might need to work with numbers, such as formatting or using mathematical functions to complement the operators built into the language.
Strings
Strings, which are widely used in Java programming, are a sequence of characters. In the Java programming language, strings are objects. This section describes using theStringclass to create and manipulate strings. It also compares theStringandStringBuilderclasses.
Generics Introduction
In any nontrivial software project, bugs are simply a fact of life. Careful planning, programming, and testing can help reduce their pervasiveness, but somehow, somewhere, they’ll always find a way to creep into your code. This becomes especially apparent as new features are introduced and your code base grows in size and complexity.
Fortunately, some bugs are easier to detect than others. Compile-time bugs, for example, tell you immediately that something is wrong; you can use the compiler’s error messages to figure out what the problem is and fix it, right then and there. Runtime bugs, however, can be much more problematic; they don’t always surface immediately, and when they do, it may be at a point in time that’s far removed from the actual cause of the problem.
Generics add stability to your code by making more of your bugs detectable at compile time. Some programmers choose to learn generics by studying the Java Collections Framework; after all, generics are heavily used by those classes. However, since we haven’t yet covered collections, this chapter will focus primarily on simple “collections-like” examples that we’ll design from scratch. This hands-on approach will teach you the necessary syntax and terminology while demonstrating the various kinds of problems that generics were designed to solve.
A Simple Box Class
Let’s begin by designing a nongenericBoxclass that operates on objects of any type. It need only provide two methods:add, which adds an object to the box, andget, which retrieves it:public class Box {Since its methods accept or returnprivate Object object;
public void add(Object object) {
this.object = object;
}public Object get() {
return object;
}
}
Object, you’re free to pass in whatever you want, provided that it’s not one of the primitive types. However, should you need to restrict the contained type to something specific (likeInteger), your only option would be to specify the requirement in documentation (or in this case, a comment), which of course the compiler knows nothing about:
Thepublic class BoxDemo1 {public static void main(String[] args) {
// ONLY place Integer objects into this box!
Box integerBox = new Box();integerBox.add(new Integer(10));
Integer someInteger = (Integer)integerBox.get();
System.out.println(someInteger);
}
}
BoxDemo1program creates anIntegerobject, passes it toadd, then assigns that same object tosomeIntegerby the return value ofget. It then prints the object’s value (10) to standard output. We know that the cast fromObjecttoIntegeris correct because we’ve honored the “contract” specified in the comment. But remember, the compiler knows nothing about this — it just trusts that our cast is correct. Furthermore, it will do nothing to prevent a careless programmer from passing in an object of the wrong type, such asString:
Inpublic class BoxDemo2 {public static void main(String[] args) {
// ONLY place Integer objects into this box!
Box integerBox = new Box();// Imagine this is one part of a large application
// modified by one programmer.
integerBox.add(“10”); // note how the type is now String// … and this is another, perhaps written
// by a different programmer
Integer someInteger = (Integer)integerBox.get();
System.out.println(someInteger);
}
}
BoxDemo2we’ve stored the number 10 as aString, which could be the case when, say, a GUI collects input from the user. However, the existing cast fromObjecttoIntegerhas mistakenly been overlooked. This is clearly a bug, but because the code still compiles, you wouldn’t know anything is wrong until runtime, when the application crashes with aClassCastException:Exception in thread “main”
java.lang.ClassCastException:
java.lang.String cannot be cast to java.lang.Integer
at BoxDemo2.main(BoxDemo2.java:6)
If the
Boxclass had been designed with generics in mind, this mistake would have been caught by the compiler, instead of crashing the application at runtime.
Generic Types
Let’s update our
Boxclass to use generics. We’ll first create a generic type declaration by changing the code “public class Box” to “public class Box<T>“; this introduces one type variable, namedT, that can be used anywhere inside the class. This same technique can be applied to interfaces as well. There’s nothing particularly complex about this concept. In fact, it’s quite similar to what you already know about variables in general. Just think ofTas a special kind of variable, whose “value” will be whatever type you pass in; this can be any class type, any interface type, or even another type variable. It just can’t be any of the primitive data types. In this context, we also say thatTis a formal type parameter of theBoxclass./**
* Generic version of the Box class.
*/
public class Box<T> {private T t; // T stands for “Type”
public void add(T t) {
this.t = t;
}public T get() {
return t;
}
}
As you can see, we’ve replaced all occurrences of
ObjectwithT. To reference this generic class from within your own code, you must perform a generic type invocation, which replacesTwith some concrete value, such asInteger:
You can think of a generic type invocation as being similar to an ordinary method invocation, but instead of passing an argument to a method, you’re passing a type argument —Box<Integer> integerBox;
Integerin this case — to theBoxclass itself. Like any other variable declaration, this code does not actually create a newBoxobject. It simply declares thatintegerBoxwill hold a reference to a “BoxofInteger“, which is howBox<Integer>is read.
An invocation of a generic type is generally known as a parameterized type.
To instantiate this class, use the
newkeyword, as usual, but place<Integer>between the class name and the parenthesis:
integerBox = new Box<Integer>();
Or, you can put the entire statement on one line, such as:
Box<Integer> integerBox = new Box<Integer>();
Once
integerBoxis initialized, you’re free to invoke itsgetmethod without providing a cast, as inBoxDemo3:
Furthermore, if you try adding an incompatible type to the box, such aspublic class BoxDemo3 {public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.add(new Integer(10));
Integer someInteger = integerBox.get(); // no cast!
System.out.println(someInteger);
}
}
String, compilation will fail, alerting you to what previously would have been a runtime bug:BoxDemo3.java:5: add(java.lang.Integer) in Box<java.lang.Integer>
cannot be applied to (java.lang.String)
integerBox.add(“10”);
^
1 error
It’s important to understand that type variables are not actually types themselves. In the above examples, you won’t find
T.javaorT.classanywhere on the filesystem. Furthermore,Tis not a part of theBoxclass name. In fact during compilation, all generic information will be removed entirely, leaving onlyBox.classon the filesystem. We’ll discuss this later in the section on Type Erasure
Also note that a generic type may have multiple type parameters, but each parameter must be unique within its declaring class or interface. A declaration of
Box<T,T>, for example, would generate an error on the second occurrence ofT, butBox<T,U>, however, would be allowed.
Type Parameter Naming Conventions
By convention, type parameter names are single, uppercase letters. This stands in sharp contrast to the variable naming conventions that you already know about, and with good reason: Without this convention, it would be difficult to tell the difference between a type variable and an ordinary class or interface name.
The most commonly used type parameter names are:
You’ll see these names used throughout the Java SE API and the rest of this tutorial.
- E – Element (used extensively by the Java Collections Framework)
- K – Key
- N – Number
- T – Type
- V – Value
- S,U,V etc. – 2nd, 3rd, 4th types
Generic Methods and Constructors
Type parameters can also be declared within method and constructor signatures to create generic methods and generic constructors. This is similar to declaring a generic type, but the type parameter’s scope is limited to the method or constructor in which it’s declared.
Here we’ve added one generic method, named/**
* This version introduces a generic method.
*/
public class Box<T> {private T t;
public void add(T t) {
this.t = t;
}public T get() {
return t;
}public <U> void inspect(U u){
System.out.println(“T: ” + t.getClass().getName());
System.out.println(“U: ” + u.getClass().getName());
}public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.add(new Integer(10));
integerBox.inspect(“some text”);
}
}
inspect, that defines one type parameter, namedU. This method accepts an object and prints its type to standard output. For comparison, it also prints out the type ofT. For convenience, this class now also has amainmethod so that it can be run as an application.
The output from this program is:
T: java.lang.Integer
U: java.lang.String
By passing in different types, the output will change accordingly.
A more realistic use of generic methods might be something like the following, which defines a static method that stuffs references to a single item into multiple boxes:
public static <U> void fillBoxes(U u, List<Box<U>> boxes) {To use this method, your code would look something like the following:
for (Box<U> box : boxes) {
box.add(u);
}
}Crayon red = …;The complete syntax for invoking this method is:
List<Box<Crayon>> crayonBoxes = …;
Here we’ve explicitly provided the type to be used asBox.<Crayon>fillBoxes(red, crayonBoxes);
U, but more often than not, this can be left out and the compiler will infer the type that’s needed:
This feature, known as type inference, allows you to invoke a generic method as you would an ordinary method, without specifying a type between angle brackets.Box.fillBoxes(red, crayonBoxes); // compiler infers that U is Crayon
Generic Bounded Type Parameters
There may be times when you’ll want to restrict the kinds of types that are allowed to be passed to a type parameter. For example, a method that operates on numbers might only want to accept instances ofNumberor its subclasses. This is what bounded type parameters are for.
To declare a bounded type parameter, list the type parameter’s name, followed by the
extendskeyword, followed by its upper bound, which in this example isNumber. Note that, in this context,extendsis used in a general sense to mean either “extends” (as in classes) or “implements” (as in interfaces).
By modifying our generic method to include this bounded type parameter, compilation will now fail, since our invocation of/**
* This version introduces a bounded type parameter.
*/
public class Box<T> {private T t;
public void add(T t) {
this.t = t;
}public T get() {
return t;
}public <U extends Number> void inspect(U u){
System.out.println(“T: ” + t.getClass().getName());
System.out.println(“U: ” + u.getClass().getName());
}public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.add(new Integer(10));
integerBox.inspect(“some text”); // error: this is still String!
}
}
inspectstill includes aString:
Box.java:21: <U>inspect(U) in Box<java.lang.Integer> cannot
be applied to (java.lang.String)
integerBox.inspect(“10”);
^
1 error
To specify additional interfaces that must be implemented, use the
&character, as in:
<U extends Number & MyInterface>
Generic Subtyping
As you already know, it’s possible to assign an object of one type to an object of another type provided that the types are compatible. For example, you can assign anIntegerto anObject, sinceObjectis one ofInteger‘s supertypes:Object someObject = new Object();In object-oriented terminology, this is called an “is a” relationship. Since an
Integer someInteger = new Integer(10);
someObject = someInteger; // OK
Integeris a kind ofObject, the assignment is allowed. ButIntegeris also a kind ofNumber, so the following code is valid as well:public void someMethod(Number n){
// method body omitted
}someMethod(new Integer(10)); // OK
someMethod(new Double(10.1)); // OK
The same is also true with generics. You can perform a generic type invocation, passing
Numberas its type argument, and any subsequent invocation ofaddwill be allowed if the argument is compatible withNumber:Box<Number> box = new Box<Number>();
box.add(new Integer(10)); // OK
box.add(new Double(10.1)); // OK
Now consider the following method:
public void boxTest(Box<Number> n){What type of argument does it accept? By looking at its signature, we can see that it accepts a single argument whose type is
// method body omitted
}
Box<Number>. But what exactly does that mean? Are you allowed to pass inBox<Integer>orBox<Double>, as you might expect? Surprisingly, the answer is “no”, becauseBox<Integer>andBox<Double>are not subtypes ofBox<Number>.
Understanding why becomes much easier if you think of tangible objects — things you can actually picture — such as a cage:
// A cage is a collection of things, with bars to keep them in.
interface Cage<E> extends Collection<E>$$
Note: TheCollectioninterface is the root interface of the collection hierarchy; it represents a group of objects. Since a cage would be used for holding a collection of objects (the animals), it makes sense to include it in this example.
A lion is a kind of animal, so
Lionwould be a subtype ofAnimal:interface Lion extends Animal {}
Lion king = …;
Where we need some animal, we’re free to provide a lion:
Animal a = king;
A lion can of course be put into a lion cage:
Cage<Lion> lionCage = …;
lionCage.add(king);
and a butterfly into a butterfly cage:
interface Butterfly extends Animal {}
Butterfly monarch = …;
Cage<Butterfly> butterflyCage = …;
butterflyCage.add(monarch);
But what about an “animal cage”? English is ambiguous, so to be precise let’s assume we’re talking about an “all-animal cage”:
Cage<Animal> animalCage = …;This is a cage designed to hold all kinds of animals, mixed together. It must have bars strong enough to hold in the lions, and spaced closely enough to hold in the butterflies. Such a cage might not even be feasible to build, but if it is, then:
animalCage.add(king);
animalCage.add(monarch);
Since a lion is a kind of animal (
Lionis a subtype ofAnimal), the question then becomes, “Is a lion cage a kind of animal cage? IsCage<Lion>a subtype ofCage<Animal>?”. By the above definition of animal cage, the answer must be “no”. This is surprising! But it makes perfect sense when you think about it: A lion cage cannot be assumed to keep in butterflies, and a butterfly cage cannot be assumed to hold in lions. Therefore, neither cage can be considered an “all-animal” cage:animalCage = lionCage; // compile-time errorWithout generics, the animals could be placed into the wrong kinds of cages, where it would be possible for them to escape.
animalCage = butterflyCage; // compile-time error
Wildcards
Earlier we mentioned that English is ambiguous. The phrase “animal cage” can reasonably mean “all-animal cage”, but it also suggests an entirely different concept: a cage designed not for any kind of animal, but rather for some kind of animal whose type is unknown. In generics, an unknown type is represented by the wildcard character “?“.
To specify a cage capable of holding some kind of animal:
Cage<? extends Animal> someCage = …;Read “
? extends Animal” as “an unknown type that is a subtype ofAnimal, possiblyAnimalitself”, which boils down to “some kind of animal”. This is an example of a bounded wildcard, whereAnimalforms the upper bound of the expected type. If you’re asked for a cage that simply holds some kind of animal, you’re free to provide a lion cage or a butterfly cage.
Note: It’s also possible to specify a lower bound by using thesuperkeyword instead ofextends. The code<? super Animal>, therefore, would be read as “an unknown type that is a supertype ofAnimal, possiblyAnimalitself”. You can also specify an unknown type with an unbounded wilcard, which simply looks like<?>. An unbounded wildcard is essentially the same as saying<? extends Object>.
While
Cage<Lion>andCage<Butterfly>are not subtypes ofCage<Animal>, they are in fact subtypes ofCage<? extends Animal>:someCage = lionCage; // OKSo now the question becomes, “Can you add butterflies and lions directly to
someCage = butterflyCage; // OK
someCage?”. As you can probably guess, the answer to this question is “no”.someCage.add(king); // compiler-time errorIf
someCage.add(monarch); // compiler-time error
someCageis a butterfly cage, it would hold butterflies just fine, but the lions would be able to break free. If it’s a lion cage, then all would be well with the lions, but the butterflies would fly away. So if you can’t put anything at all intosomeCage, is it useless? No, because you can still read its contents:void feedAnimals(Cage<? extends Animal> someCage) {Therefore, you could house your animals in their individual cages, as shown earlier, and invoke this method first for the lions and then for the butterflies:
for (Animal a : someCage)
a.feedMe();
}
feedAnimals(lionCage);Or, you could choose to combine your animals in the all-animal cage instead:
feedAnimals(butterflyCage);
feedAnimals(animalCage);
Type Erasure
When a generic type is instantiated, the compiler translates those types by a technique called type erasure — a process where the compiler removes all information related to type parameters and type arguments within a class or method. Type erasure enables Java applications that use generics to maintain binary compatibility with Java libraries and applications that were created before generics.
For instance,
Box<String>is translated to typeBox, which is called the raw type — a raw type is a generic class or interface name without any type arguments. This means that you can’t find out what type ofObjecta generic class is using at runtime. The following operations are not possible:
The operations shown in bold are meaningless at runtime because the compiler removes all information about the actual type argument (represented by the type parameterpublic class MyClass<E> {
public static void myMethod(Object item) {
if (item instanceof E) { //Compiler error
…
}
E item2 = new E(); //Compiler error
E[] iArray = new E[10]; //Compiler error
E obj = (E)new Object(); //Unchecked cast warning
}
}
E) at compile time.
Type erasure exists so that new code may continue to interface with legacy code. Using a raw type for any other reason is considered bad programming practice and should be avoided whenever possible.
When mixing legacy code with generic code, you may encounter warning messages similar to the following:
This can happen when using an older API that operates on raw types, as shown in the followingNote: WarningDemo.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
WarningDemoprogram:
Recompiling withpublic class WarningDemo {
public static void main(String[] args){
Box<Integer> bi;
bi = createBox();
}/**
* Pretend that this method is part of an old library,
* written before generics. It returns
* Box instead of Box<T>.
*/
static Box createBox(){
return new Box();
}
}
-Xlint:uncheckedreveals the following additional information:
WarningDemo.java:4: warning: [unchecked] unchecked conversion
found : Box
required: Box<java.lang.Integer>
bi = createBox();
^
1 warning