Most programming languages have data types. Java has several data types. The first characteristic of a data type is whether it's a primitive or an object. Anything that's not a primitive is an object of some sort.
Primitive means that no class exists to define a variable of that type. Java supports a number of primitive data types. In the code samples in previous chapters, you saw several variables whose data types were primitive. Anywhere you saw int
or boolean
, those were primitive data types (usually called “primitives”). Primitives fall into several broad categories. From the language's point of view, the categories don't exist, but they do help people keep the primitives straight from one another.
An integer primitive is one whose value can be only an integer. It cannot be a real number (that is, one with a fractional value). The numbers 0, 1, 16, and 37 are all integers. The number 12.34 is not an integer. The distinguishing feature between the various integer primitives is how many bits make up each one. Because a type made of more bits can hold a bigger number, the practical effect of the different number of bits is to increase the maximum value of that type. The possible detrimental effect of increasing the number of bits is to consume more memory. Often, it doesn't matter, but it's good practice to use data elements no larger than you need.
Integer primitives are signed (unless you use the unsigned
keyword, which we discuss more in a moment), so their values range from a negative number to a positive number.
Table 3-1 shows the details of the various integer primitives.
Do you need even bigger numbers? Real-world applications sometimes need numbers even bigger than the maximum value of long
. In those cases, Java provides a class called BigInteger
. A number represented by a BigInteger
object can be of any size, because the BigInteger
class allocates enough bytes to store a representation of any number. It's a little tricky to use and operations on BigInteger
objects are slow, but it's sometimes the only way. BigInteger
is not a primitive.
Real numbers have decimal points and values after the decimal point. Even if that value is 0, and even if you didn't type a decimal point and anything after it, the decimal point and the zeros after it are there behind the scenes. They must exist so that operations on the value can compare to the full value, so the JVM (the Java Virtual Machine, which is the program that runs your programs) fills them in even if you don't.
Note 0
and 0.00
are different values in Java. To the average person, they both mean zero. To a scientist or mathematician, the one indicates greater precision than the other, but they still both mean zero. But to a compiler, they are different data types and have nothing to do with one another.
Java supports two real primitives, float
and double
. float
gets its name from the idea of a floating point number. The decimal point can move, so it's said to “float.” double
gets its name because it takes twice the storage space of a float. Table 3-2 shows the details of float
and double
.
Again, sometimes even that isn't enough. For those cases, Java provides a class called BigDecimal
. As with BigInteger
, it can be tricky to use, and operations on it are slow. But when you absolutely have to have a number bigger than a double, use BigDecimal
. As with BigInteger
, BigDecimal
is not a primitive.
The boolean
data type indicates whether something is true or false. In fact, those two words (true
and false
) are the only two values boolean
data types can have.
Note true
and false
are reserved words in Java. You can't use them for anything other than the value of a boolean variable. For example, trying to create an int
called true
throws an error.
The char
data type holds two 8-bit bytes and is meant to represent characters. It's stored as an unsigned 16-bit integer with a minimum value of 0 and a maximum value of 65,535. However, you should never use a char
to store a number, because that can lead to confusion. Use char
variables to hold individual characters, and you'll avoid trouble.
So why is the maximum so big when relatively few characters exist? Well, when you look at all the character sets in use all around the world, 65,535 isn't so unreasonable. In fact, it's not enough when dealing with traditional Chinese.
Tip If you're curious about how to handle characters from other languages, look up Unicode, which is a standard that defines all the world's characters. For more on handling multi-byte characters, look up variable-width encoding.
String
is a type that has some of the characteristics of both a primitive and an object. Strictly speaking, it is an object; that is, a String
class defines it. A String
object is a sequence of characters (and Java provides utilities for turning a String
object into a collection of char
primitives and for making a String
object from such a collection). It's often handy to work on the collection (the string) rather than on each character, so Java (and nearly all other programming languages) provides a String
object.
Java offers special support for the String
class that lets String
objects act a little like primitives. In particular, you can create a String
object by using the equals sign (=
), and you can concatenate String
objects with the plus sign (+
), as shown in Listing 3-1. Concatenation applies only to strings, by the way. If you use a plus symbol with the other data types, you either get an error (try adding two boolean
values and you'll see it) or you get the mathematical plus operation that you usually associate with the plus symbol.
String myString = "my string";
String yourString = "your string";
String ourString = myString + " " + yourString;
System.out.println(myString);
System.out.println(yourString);
System.out.println(ourString);
The output is in Listing 3-2.
my string
your string
my string your string
Notice that the value of ourString
consists of the concatenation of three values: myString
, " "
, and yourString
. " "
is a String
literal. That's another way String
differs from other objects; no other object (remember, primitives aren't objects) can have a literal.
All the primitives and the String
class can have literal values. A literal is a constant value that corresponds to a particular data type. Table 3-2 provides examples for each of the primitive data types and the String
class.
Note Declaring a float literal requires appending f to the end of the literal. Otherwise, the compiler tries to make it into a double value and then complains that it can't cast from double to float. Similarly, you can put d at the end of a double literal's declaration. However, that is redundant because double is the default floating-point type.
Literals pop up all over the place, often without anyone stopping to think about it (after all, we're usually trying to get the computer to do something). Every time we write a loop that starts at 0 and counts to some value, we use a literal (in that case, an integer literal). A number of other literals are common in nearly all programming languages and tasks. In set theory (which informs a lot of programming and especially database programming), the only three values that matter are 0, 1, and many. Consequently, 0 and 1 appear over and over again throughout all kinds of software. The empty string (""
—sometimes handy for comparing String objects) and the single space (" "
—handy for linking String objects without inventing a new and large word in the process) also appear often.
Variables of type char
can have several special values. First, singe quotation marks ('), double quotation marks ("
), and backslashes () all have to be marked as special (that's called escaping a character), so that the JVM knows you want one of those characters. Otherwise, it would process all your single quotations as the beginning or end of a char
and all your double quotations as the beginning or end of a String
object. The backslash character has to be escaped because it is the escape character. If it couldn't itself be escaped, then every backslash would indicate an escaped character, which would be a real problem. Escaped characters (often called escape sequences because they consist of at least two characters—the escape character and at least one other character) are also used for non-graphical characters (often called control characters). There's even a character called Bell. It never appears, but it can (if your computer enables it) make your computer beep. The Bell (or Alert) character's escape sequence is a
. All of that might not make sense, so let's consider some examples in Listing 3-3 to make things clearer.
// Let's start with double quotation marks
// The following line throws an error because meow isn't defined
System.out.println("My cat says, "meow."");
// so the line has to be
System.out.println("My cat says, "meow."");
// which will produce this output: My cat says, "meow."
// And now single quotation marks
// The following line throws an error because ' never has a matching '
System.out.println("' is my favorite character");
// so the line has to be
System.out.println("' is my favorite character");
// which will produce this: ' is my favorite character
// (my actual favorite character is ;)
// And now the backslash itself
// The following line throws an error because it's missing a closing "
// (The compiler takes " as a literal " and then can't find an end to the string.)
System.out.println("I want a ");
// so the line has to be
System.out.println("I want a \");
// which will produce this: I want a
Java also supports several special escape sequences (all beginning with a backslash). Table 3-3 shows each escape sequence and describes its effect. It begins with the three we already covered.
The newline character (
) sees a lot of use for things like creating readable error messages, separating lines in files, and other output. Tabs are less common but still sometimes used (to line up pieces of output to make things easier to read). The others are much more rarely used, because software developers hardly ever create unstructured output these days. When programmers used punch cards for input and line printers for output, programmers made extensive use of the form feed and start-of-line characters. We still produce plenty of output, of course, but it's usually HTML, XML, or some other structured format.
In addition to being possible values for variables of type char, String
objects can also contain escape sequences. Many String
objects contain a newline sequence or two. Listing 3-4 shows a short example.
String errorMessage = "The whatsit didn't work!
Check the doohickey.";
System.out.println(errorMessage);
That code snippet produces the output in Listing 3-5.
The whatsit did't work!
Check the doohickey.
Each of the primitives (remember, String
is an object rather than a primitive) has a corresponding class that provides several useful abilities that are otherwise unavailable to a primitive. You can think of the wrapper classes as each being a package of useful abilities wrapped around a primitive of the corresponding type. Table 3-4 shows the corresponding class for each primitive:
The most commonly used methods of those classes are the various parse
, Value
, and valueOf
methods, because those methods let you turn a String
into a primitive. Each class also supports toString
methods to turn primitives into String
objects. Listing 3-6 shows what you can do with an int
primitive.
// declare an int
int myInt = 1;
// and a String that contains a number
String myString = "1";
// turn myInt into a String
String myIntString = Integer.toString(myInt);
// turn myString into an int
int myStringInt = Integer.parseInt(myString);
// turn myString into an Integer
// be prepared for an exception if myString does not hold a number
Integer myStringInteger = new Integer(myString);
// and then turn myStringInteger into an int
int myOtherStringInt = myStringInteger.intValue();
// Now for more unusual things
// convert an int to a float (perhaps for further floating-point work)
float myFloat = new Integer(myInt).floatValue();
// convert an int to a byte
// be prepared for an exception if the value is out of byte's range
byte myByte = new Integer(myInt).byteValue();
// convert an int to a long
// no need to worry about an exception this time
long myLong = new Integer(myInt).longValue();
// just for fun, get the binary string representation of myInt
// creates a String object that holds "1"
String myIntBinary = Integer.toBinaryString(myInt);
The other numeric primitives (byte
, short
, long
, float
, and double
) all work in a similar way. You can convert any of them to any other, though you might have to handle an exception if the conversion can't be done with the value you provide. For example, if you have an int variable that holds a value of 300, converting it to a byte gives an error, because a byte can't hold that value. We cover this concept in greater detail in the next chapter, when discussing casting values.
The Boolean
and Character
classes work a little differently. You can still convert strings to boolean
or char
values and vice-versa, but you can't convert boolean
and char
primitives into other primitives. Also, the Boolean
class includes an equals
method, and the Character
class includes many methods for dealing with issues such as characters that are meant to be read from right to left (for example, characters from Arabic and Hebrew) and other special issues that relate only to characters. All of those operations are fairly unusual, though, so we don't cover them. If you want to learn more about them (and good for you if you do), look at the Javadoc for the Boolean
and Character
classes. As you might recall from Chapter 1, JavaDoc is a special kind of documentation that is built into the code itself. Oracle (the company that makes Java) provides extensive JavaDoc for all the standard Java libraries. You can find the JavaDoc for the Boolean class at http://download.oracle.com/javase/7/docs/api/java/lang/Boolean.html
and the JavaDoc for the Character class at http://download.oracle.com/javase/7/docs/api/java/lang/Character.html
. You can find all the JavaDoc for Java 7 at http://download.oracle.com/javase/7/docs/api/
(as you can see, the other topics are branches of the overall API documentation).
An array is a data structure that holds a group of variables under a single identifier. Java supports arrays for both primitives and objects. Square brackets after a variable's name indicate that it is an array. Listing 3-7 shows several arrays and how to manipulate them.
int[] a; // array declaration without assignment
a = new int[2]; // specify the length of an existing array
int[] b = {1, 2, 3, 4}; // array declaration with assignment
int bLength = b.length; // how to get the length of an array
// arrays start at 0, not 1
b[0] = 2; // have to reassign each value in the array individually
Let's look at the code one line at a time. The first line creates an array of int
primitives but doesn't assign anything to it (a
is null at that point). The second line shows how to set an array to be a particular length. If a
had values, its values would be replaced by the default values of the new type (0 in this case). The third line shows how to create an array with a set of starting values. You can use that block assignment syntax only when creating an array, not when assigning new values to an existing array. The fourth line shows how to get the length of an array. The last line shows how to reassign one of the values in an array and shows that array addresses start at 0. (Most programming languages start counting at 0.)
Now let's consider an array of objects in Listing 3-8, which reveals some interesting things.
Integer[] myIntegers = new Integer[4];
for (int i = 0; i < myIntegers.length; i++) {
myIntegers[i] = new Integer(i);
}
Again, let's go line by line. As you can see, the syntax for creating an array of objects differs a bit from that for primitives. The part to the left of the equal sign looks the same (the kind of object, the array indicator, and the name of the variable), but the part to the right of the equal sign differs by having the new
keyword before the type of the item going into the array. To create a new instance of an object, we usually use the new
keyword (though other ways exist), which calls a constructor for the object. However, all of the Integer class's constructors require an object (either an int
or a String
object that holds an integer value), so this array ends up holding four null references. (We dive into what null means in a moment.)
This listing also shows how to loop through an array and, in this case, create an object for each of the null objects. Notice that we have to use the new
keyword again, even though we used it when we created the array. Because we got four null references rather than actual objects from the original assignment, we have to create new ones here. In this case, we end up with four Integer
objects having values of 0, 1, 2, and 3.
Java provides a convenience class for arrays. The Arrays
class consists of many static methods to do handy things such as copy and sort arrays. For example, Listing 3-9 shows one way to sort an array of ints.
int[] a = {5, 4, 3, 2};
// at the top of the program, we had to import
// java.util.Arrays for this to work correctly
Arrays.sort(a);
for (int i = 0; i < a.length; i++) {
System.out.println(a[i]);
}
The result is 2, 3, 4, 5 (each on its own line) in the console.
Java includes a value that isn't anything: null
. It refers to a memory address that has not been assigned. In Java terms, that means it refers to an object or primitive that has not been created. As I mentioned in the “Arrays” section, when you create an array without specifying its values, you are creating a collection of null
values. They have no memory address, no corresponding primitive or object exists for them, and so they are null
. That might sound like a problem, and the whole concept of null
often causes novice programmers some trouble. You can keep it straight by remembering that a null
is a non-existent reference.
The oft-maligned null
has its uses (otherwise, it wouldn't exist—programmers are pragmatic people, most of the time). For example, we often compare an object to null to be sure that something exists before we try to use it. If the graphics library is supposed to give us an object of type Color
and we get null
instead, we have a problem. So we might compare that Color
value to null
to ensure that we are getting a Color
object and do something useful (such as trying another way or at least logging an error) if not.
Also, it's common practice to create a variable in one place and assign it in another place. In between the creation and assignment, the value of that variable might be null
(it also might not be null because some primitives, such as int
, have default values). This technique is handy because we might want to assign different values to the variable based on some logic. For example, a series of if-else
statements or a switch
statement might contain code to assign the value of a variable. Let's consider a small example (from a minesweeper game), shown in Listing 3-10.
public getMineIcon(int x, int y) {
// x and y are the position within the game grid
int numberOfAdjacentMines = getNumberOfAdjacentMines(x, y);
MineIcon mineIcon = null; // here's our null
if (numberOfAdjacentMines == 1) {
mineIcon = new MineIcon(1);
} else if (numberOfAdjacentMines == 2) {
mineIcon = new MineIcon(2);
}
// and so on up to 8
return mineIcon;
}
In this case, we can assume a MineIcon
class exists and that its constructor will take two integer values and return an icon (a type of image) that shows the value of that integer argument. Because a square in a minesweeper game can have no more than eight neighboring mines, we stop at eight. We also let the method return null
, to represent the case where a square has no neighboring mines. Consequently, the code that calls this method then has to know what to do with a null
value. As it happens, the setIcon
method that we need to use accepts a null
argument as a way of saying that no icon needs to be set.
We create a full-blown minesweeper program in Chapter 7, “Creating a User Interface.” When we do, you see a different way to use set the mine icon, but it still uses a null
reference. For now, just remember that null
is just another value (though a special one that indicates a non-existent reference) and that it can be used for purposes other than just checking for missing objects.
An enumeration (often written as “enum”) is a data type that consists of a fixed number of constants. For example, if you are writing a game that involves navigation, you might have an enumeration to define the four cardinal directions, similar to the one shown in Listing 3-11.
public enum Direction {
NORTH, EAST, SOUTH, WEST;
}
As Listing 3-11 shows, the declaration of an enumeration consists of the enum
keyword, a name for the enumeration, and the values that comprise the enumeration. The values are just names and have no type of their own. That works because we need unique identifiers but don't need a type for each one.
The value of enumerations is that they are type-safe (meaning that it can't be confused with another type—enums used to be created with integers, so confusing an enum with an integer was a real problem). Without enumerations, we'd have to set up constants in a different way—usually with integers, as shown in Listing 3-12.
public static final int NORTH = 0;
public static final int EAST = 1;
public static final int SOUTH = 2;
public static final int WEST = 3;
Caution Do not write collections of constants this way. They can be compared to int values, and that's almost certain to be the wrong thing to do. That's why Java has enumerations.The enumeration value, Direction.NORTH
, can't be treated as an integer (not even by accident), whereas the constant, NORTH
, can be. Also, constants are static and final (and often public). Enumerations remove the need for those modifiers, which gives us greater flexibility when using enumerations.
Each enum
is actually a class. (You might have noticed that its declaration syntax is similar to a class.) All enum
objects implicitly extend java.lang.Enum
. That lets us make more meaningful enums than just lists of constants. To continue with our example, we can set the degrees for each direction, as shown in Listing 3-13.
public enum Direction {
NORTH (0),
EAST (90),
SOUTH (180),
WEST (270);
private final int degrees;
Direction(int degrees) {
this.degrees = degrees;
}
public int getDegrees() {
return degrees;
}
}
From there, we can write code to get the additional information associated with each value in the enumeration, as shown in Listing 3-14.
for (Direction d : Direction.values()) {
System.out.println(d + " is " + d.degrees + "degrees.");
}
We can also write methods that work with the enumeration's values. For example, we can write a method that, given a direction in degrees, tells you which cardinal direction is closest. Listing 3-15 shows one way to write such a method.
public static Direction findCardinalDirection (int degrees) {
if (degrees < 45) {
return NORTH;
} else if (degrees < 135) {
return EAST;
} else if (degrees < 225) {
return SOUTH;
} else if (degrees < 315) {
return WEST;
} else {
return NORTH;
}
}
You might also notice the different syntax for iterating through a for loop. That syntax is not unique to enumerations, but it is especially handy for them. We cover these alternate ways of using a for
loop when we get to looping in a later chapter.
As you can see, an enumeration is far more powerful than a simple list of constants. It's also safer, because no one can confuse Direction.North
with 0
.
Listing 3-16 shows the full code for the Direction
enumeration.
package com.bryantcs.examples.enumExample;
public enum Direction {
NORTH (0),
EAST (90),
SOUTH (180),
WEST (270);
private final int degrees;
Direction(int degrees) {
this.degrees = degrees;
}
public int getDegrees() {
return degrees;
}
// static because it doesn't rely on a particular direction
public static Direction findCardinalDirection (int degrees) {
if (degrees < 45) {
return NORTH;
} else if (degrees < 135) {
return EAST;
} else if (degrees < 225) {
return SOUTH;
} else if (degrees < 315) {
return WEST;
} else {
return NORTH;
}
}
}
Now that we have our completed enumeration, we need a test program to see how it works. Listing 3-17 shows a program class that does the job.
package com.bryantcs.examples.enumExample;
public class EnumExample {
public static void main(String[] args) {
int[] compassPoints = {22, 77, 144, 288};
for (int i = 0; i < compassPoints.length; i++) {
System.out.println(compassPoints[i] + " degrees is (very roughly) "
+ Direction.findCardinalDirection(compassPoints[i]));
}
for (Direction d : Direction.values()) {
System.out.println(d + " is " + d.getDegrees() + " degrees.");
}
}
}
EnumExample
produces the output in the console shown in Listing 3-18.
22 degrees is (very roughly) NORTH
77 degrees is (very roughly) EAST
144 degrees is (very roughly) SOUTH
288 degrees is (very roughly) WEST
NORTH is 0 degrees.
EAST is 90 degrees.
SOUTH is 180 degrees.
WEST is 270 degrees.
In this chapter, we learned the basics about the various data types that are available in Java. We found out:
boolean
data type workschar
variables and String
objects work and a bit about how they can interactWe make repeated use of all these concepts and techniques throughout the rest of the book. As we work more with Java, you'll do a lot with primitives, the String
object, primitive and String
literals, and arrays. Enumerations appear less often in most code, but they offer important advantages (most notably type safety) when we need them.
18.218.187.108