Casting and Converting Objects and Primitive Types
One thing you discover quickly about Java is how finicky it is about the information it will handle. Like Morris, the perpetually hard-to-please cat in the old 9Lives Cat Food commercials, Java methods and constructors require things to take a specific form and won't accept alternatives.
When you are sending arguments to methods or using variables in expressions, you must use variables of the right data types. If a method requires an int, the Java compiler responds with an error if you try to send a float value to the method. Likewise, if you're setting up one variable with the value of another, they must be of the same type.
There is one area where Java's compiler is decidedly flexible: Strings. String handling in println() methods, assignment statements, and method arguments is simplified by the concatenation operator (+). If any variable in a group of concatenated variables is a string, Java treats the whole thing as a String. This makes the following possible:
float gpa = 2.25F; System.out.println("Honest, dad, my GPA is a " + (gpa+1.5));
Using the concatenation operator, a single string can hold the text representation of multiple objects and primitive data in Java.
Sometimes you'll have a value in your Java class that isn't the right type for what you need. It might be the wrong class or the wrong data type, such as a float when you need an int.
You use casting to convert a value from one type to another.
Casting is the process of producing a new value that has a different type than its source.
Although the concept of casting is reasonably simple, the usage is complicated by the fact that Java has both primitive types (such as int, float, and boolean) and object types (String, Point, ZipFile, and the like). This section discusses three forms of casts and conversions:
Casting between primitive types, such as int to float or float to double
Casting from an instance of a class to an instance of another class, such as Object to String
Casting primitive types to objects and then extracting primitive values from those objects
When discussing casting, it can be easier to think in terms of sources and destinations. The source is the variable being cast into another type. The destination is the result.
Casting Primitive Types
Casting between primitive types enables you to convert the value of one type to another primitive type. It most commonly occurs with the numeric types, and there's one primitive type that can never be used in a cast. Boolean values must be either true or false and cannot be used in a casting operation.
In many casts between primitive types, the destination can hold larger values than the source, so the value is converted easily. An example would be casting a byte into an int. Because a byte holds values from 128 to 127 and an int holds from 2,100,000 to 2,100,000, there's more than enough room to cast a byte into an int.
You often can automatically use a byte or a char as an int; you can use an int as a long, an int as a float, or anything as a double. In most cases, because the larger type provides more precision than the smaller, no loss of information occurs as a result. The exception is casting integers to floating-point values; casting an int or a long to a float, or a long to a double, can cause some loss of precision.
A character can be used as an int because each character has a corresponding numeric code that represents its position in the character set. If the variable i has the value 65, the cast (char)i produces the character value "A." The numeric code associated with a capital A is 65, according to the ASCII character set, and Java adopted this as part of its character support.
You must use an explicit cast to convert a value in a large type to a smaller type, or else converting that value might result in a loss of precision. Explicit casts take the following form:
In the preceding example, typename is the name of the data type to which you're converting, such as short, int, or float. value is an expression that results in the value of the source type. For example, in the following statement, the value of x is divided by the value of y, and the result is cast into an int in the following expression:
int result = (int)(x / y);
Note that because the precedence of casting is higher than that of arithmetic, you have to use parentheses here; otherwise, the value of x would be cast into an int first and then divided by y, which could easily produce a different result.
Instances of classes also can be cast into instances of other classes, with one restriction: The source and destination classes must be related by inheritance; one class must be a subclass of the other.
Some objects might not need to be cast explicitly. In particular, because a subclass contains all the same information as its superclass, you can use an instance of a subclass anywhere a superclass is expected.
For example, consider a method that takes two arguments, one of type Object and another of type Component. You can pass an instance of any class for the Object argument because all Java classes are subclasses of Object. For the Component argument, you can pass in its subclasses, such as Button, Container, and Label.
This is true anywhere in a program, not just inside method calls. If you had a variable defined as class Component, you could assign objects of that class or any of its subclasses to that variable without casting.
This is true in the reverse, so you can use a superclass when a subclass is expected. There is a catch, however: Because subclasses contain more behavior than their superclasses, there's a loss in precision involved. Those superclass objects might not have all the behavior needed to act in place of a subclass object. For example, if you have an operation that calls methods in objects of the class Integer, using an object of class Number won't include many methods specified in Integer. Errors occur if you try to call methods that the destination object doesn't have.
To use superclass objects where subclass objects are expected, you must cast them explicitly. You won't lose any information in the cast, but you gain all the methods and variables that the subclass defines. To cast an object to another class, you use the same operation as for primitive types:
In this case, classname is the name of the destination class, and object is a reference to the source object. Note that casting creates a reference to the old object of the type classname; the old object continues to exist as it did before.
The following example casts an instance of the class VicePresident to an instance of the class Employee; VicePresident is a subclass of Employee with more information:
Employee emp = new Employee(); VicePresident veep = new VicePresident(); emp = veep; // no cast needed for upward use veep = (VicePresident)emp; // must cast explicitly
As you'll see when you begin working with graphical user interfaces during Week 2, "The Java Class Library," casting one object is necessary whenever you use Java2D graphics operations. You must cast a Graphics object to a Graphics2D object before you can draw text or graphics onscreen. The following example uses a Graphics object called screen to create a new Graphics2D object called screen2D:
Graphics2D screen2D = (Graphics2D)screen;
Graphics2D is a subclass of Graphics, and both are in the java.awt package. You explore the subject fully during Day 13, "Using Color, Fonts, and Graphics."
In addition to casting objects to classes, you also can cast objects to interfaces, but only if an object's class or one of its superclasses actually implements the interface. Casting an object to an interface means that you can call one of that interface's methods even if that object's class does not actually implement that interface.
Converting Primitive Types to Objects and Vice Versa
One thing you can't do under any circumstance is cast from an object to a primitive data type, or vice versa.
Primitive types and objects are very different things in Java, and you can't automatically cast between the two.
As an alternative, the java.lang package includes classes that correspond to each primitive data type: Float, Boolean, Byte, and so on. Most of these classes have the same names as the data types, except that the class names begin with a capital letter (Short instead of short, Double instead of double, and the like). Also, two classes have names that differ from the corresponding data type: Character is used for char variables and Integer for int variables.
Using the classes that correspond to each primitive type, you can create an object that holds the same value. The following statement creates an instance of the Integer class with the integer value 7801:
Integer dataCount = new Integer(7801);
After you have an object created in this manner, you can use it as you would any object (although you cannot change its value). When you want to use that value again as a primitive value, there are methods for that, as well. For example, if you wanted to get an int value from a dataCount object, the following statement would be apt:
int newCount = dataCount.intValue(); // returns 7801
A common translation you need in programs is converting a String to a numeric type, such as an integer. When you need an int as the result, this can be done by using the parseInt() class method of the Integer class. The String to convert is the only argument sent to the method, as in the following example:
String pennsylvania = "65000"; int penn = Integer.parseInt(pennsylvania);
The following classes can be used to work with objects instead of primitive data types: Boolean, Byte, Character, Double, Float, Integer, Long, Short, and Void. These classes are commonly referred to as object wrappers because they provide an object representation that contains a primitive value.
If you try to use the preceding example in a program, your program won't compile. The parseInt() method is designed to fail with a NumberFormatException error if the argument to the method is not a valid numeric value. To deal with errors of this kind, you must use special error-handling statements, which are introduced during Day 7, "Threads, Exceptions, and Assertions."
Java 2 version 1.5 introduces autoboxing and unboxing, a new language feature that makes it much easier to work with primitive types and the objects that represent the same kind of value.
Autoboxing automatically converts a primitive type to an object, and unboxing converts in the other direction.
If you write a statement that uses an object where a primitive type is expected, or vice versa, the value will be converted so that the statement executes successfully.
This is a marked departure from all preceding versions of the language.
As a demonstration, the following statements can't be compiled by the rules of Java 2 version 1.4:
Float f1 = new Float(12.5F); Float f2 = new Float(27.2F); System.out.println("Lower number: " + Math.min(f1, f2));
When you attempt to compile a class containing these statements, the compiler stops with an error message stating that the Math.min() method requires two float primitive values as arguments, rather than Float objects.
If you enable Java's version 1.5 support, the statements compile successfully. The Float objects are unboxed into primitive values automatically when the Math.min() method is called.
Unboxing an object works only if the object has a value. If no constructor has been called to set up the object, compilation fails with a NullPointerException error.
By default, the SDK compiler does not support autoboxing, unboxing, or the other new features of Java 2 version 1.5. You can enable this support by running the compiler with the flag -source 1.5, as in the following command:
javac -source 1.5 Outliner.java