Wednesday, February 2, 2011

Chapter 16: Literals, Assignments and Variables

In this chapter we are going to take a deep look into Literals, Assignments and Variables. This topic is very important from the SCJP point of view and unfortunately is going to be pretty lengthy.

Literal Values for All Primitive Types

A primitive literal is merely a source code representation of the primitive data types—in other words, an integer, floating-point number, boolean, or character that you type in while writing code. The following are examples of primitive literals:
'b' // char literal
99 // int literal
false // boolean literal
234567.345 // double literal

Integer Literals

There are three ways to represent integer numbers in the Java language: decimal (base 10), octal (base 8), and hexadecimal (base 16). Most exam questions with integer literals use decimal representations, but the few that use octal or hexadecimal are worth studying for. Even though the odds that you’ll ever actually use octal in the real world are astronomically tiny, they were included in the exam just for fun.

Decimal Literals

Decimal integers need no explanation; you’ve been using them since school days. In the Java language, they are represented as is, with no prefix of any kind, as follows:
int length = 343;
Octal Literals
Octal integers use only the digits 0 to 7. In Java, you represent an integer in octal form by placing a zero in front of the number, as follows:

class Octal {
public static void main(String [] args) {
int six = 06; // Equal to decimal 6
int seven = 07; // Equal to decimal 7
int eight = 010; // Equal to decimal 8
int nine = 011; // Equal to decimal 9
System.out.println("Octal 010 = " + eight);
}
}

Notice that when we get past seven and are out of digits to use (we are allowed only the digits 0 through 7 for octal numbers), we revert back to zero, and one is added to the beginning of the number. You can have up to 21 digits in an octal number, not including the leading zero. If we run the preceding program, it displays the following:

Octal 010 = 8

Hexadecimal Literals

Hexadecimal (hex for short) numbers are constructed using 16 distinct symbols. Because we never invented single digit symbols for the numbers 10 through 15, we use alphabetic characters to represent these digits. Counting from 0 through 15 in hex looks like this:

0 1 2 3 4 5 6 7 8 9 a b c d e f

Java will accept capital or lowercase letters for the extra digits. You are allowed up to 16 digits in a hexadecimal number, not including the prefix 0x or the optional suffix extension L. All of the following hexadecimal assignments are legal:
int x = 0X0001;
int y = 0x7fffffff;
int z = 0xDeadCafe;

Don’t be misled by changes in case for a hexadecimal digit or the ‘x’ preceding it. 0XCAFE and 0xcafe are both legal and have the same value.

All three integer literals (octal, decimal, and hexadecimal) are defined as int by default, but they may also be specified as long by placing a suffix of L or l after the number:

long so = 110599L;
long soSo = 0xFFFFl; // Note the lowercase 'l'

Floating-Point Literals

Floating-point numbers are defined as a number, a decimal symbol, and more numbers representing the fraction.

double d = 12345678.987654321;

In the preceding example, the number 12345678.987654321 is the literal value. Floating-point literals are defined as double (64 bits) by default, so if you want to assign a floating-point literal to a variable of type float (32 bits), you must attach the suffix F or f to the number. If you don’t, the compiler will complain about a possible loss of precision, because you’re trying to fit a number into a (potentially) less precise “container.” The F suffix gives you a way to tell the compiler, “Hey, I know what I’m doing, don't bother much.”

float f = 23.467890; // Compiler error, possible loss of precision
float g = 49837849.029847F; // OK; has the suffix "F"

You may also optionally attach a D or d to double literals, but it is not necessary because this is the default behavior.

double d = 110599.995011D; // Optional, not required
double g = 987.897; // No 'D' suffix, but OK because the
// literal is a double by default

Look for numeric literals that include a comma, for example,
int x = 25,343; // Won't compile because of the comma

Boolean Literals

Boolean literals are the source code representation for boolean values. A boolean value can only be defined as true or false. Although in C (and some other languages) it is common to use numbers to represent true or false, this will not work in Java. Again, repeat after me, “Java is not C++.”

boolean t = true; // Legal
boolean f = 0; // Compiler error!

Be on the lookout for questions that use numbers where booleans are required. You might see an if test that uses a number, as in the following:

int x = 1; if (x) { } // Compiler error!
Character Literals
A char literal is represented by a single character in single quotes.
char a = 'a';
char b = '@';

You can also type in the Unicode value of the character, using the Unicode notation of prefixing the value with \u as follows:
char letterN = '\u004E'; // The letter 'N'

Remember, characters are just 16-bit unsigned integers under the hood. That means you can assign a number literal, assuming it will fit into the unsigned 16-bit range (65535 or less). For example, the following are all legal:

char a = 0x892; // hexadecimal literal
char b = 982; // int literal
char c = (char)70000; // The cast is required; 70000 is
// out of char range
char d = (char) -98; // Unusual, but legal

And the following are not legal and produce compiler errors:

char e = -29; // Possible loss of precision; needs a cast
char f = 70000 // Possible loss of precision; needs a cast

You can also use an escape code if you want to represent a character that can’t be typed in as a literal, including the characters for linefeed, newline, horizontal tab, backspace, and single quotes.

char c = '\"'; // A double quote
char d = '\n'; // A newline

Literal Values for Strings

A string literal is a source code representation of a value of a String object. For example, the following is an example of two ways to represent a string literal:
String s = "Rocky Maivia";

System.out.println("Rocky" + " Maivia");

Although strings are not primitives, they’re included in this section because they can be represented as literals—in other words, typed directly into code.

Assignment Operators

Assigning a value to a variable seems straightforward enough; you simply assign the stuff on the right side of the = to the variable on the left. Straight forward usage of = in assignments will not be part of the questions. But, you will, be tested on the trickier assignments involving complex expressions and TypeCasting. We’ll look at both primitive and reference variable assignments.

Primitive Assignments

The equal (=) sign is used for assigning a value to a variable, and it’s cleverly named the assignment operator. There are actually 12 assignment operators, but only the five most commonly used are on the exam, and they will be covered in a later chapter.

You can assign a primitive variable using a literal or the result of an expression.

Take a look at the following:
int x = 7; // literal assignment
int y = x + 2; // assignment with an expression
// (including a literal)
int z = x * y; // assignment with an expression

The most important point to remember is that a literal integer (such as 7) is always implicitly an int. Thinking back to the chapter on declarations, you’ll recall that an int is a 32-bit value. No big deal if you’re assigning a value to an int or a long variable, but what if you’re assigning to a byte variable? After all, a byte-sized holder can’t hold as many bits as an int-sized holder. Here’s where it gets weird. The following is legal,

byte b = 27;

but only because the compiler automatically narrows the literal value to a byte. In other words, the compiler puts in the cast. The preceding code is identical to the following:
byte b = (byte) 27; // Explicitly cast the int literal to a byte

It looks as though the compiler gives you a break, and lets you take a shortcut with assignments to integer variables smaller than an int.

We know that a literal integer is always an int, but more importantly, the result of an expression involving anything int-sized or smaller is always an int. In other words, add two bytes together and you’ll get an int—even if those two bytes are tiny. Multiply an int and a short and you’ll get an int. Divide a short by a byte and you’ll get...an int.

byte a = 3; // No problem, 3 fits in a byte
byte b = 8; // No problem, 8 fits in a byte
byte c = b + c; // Should be no problem, sum of the two bytes
// fits in a byte

The last line won’t compile! You’ll get an error something like this:
TestBytes.java:5: possible loss of precision
found : int
required: byte
byte c = a + b;
^
We tried to assign the sum of two bytes to a byte variable, the result of which (11) was definitely small enough to fit into a byte, but the compiler didn’t care. It knew the rule about int-or-smaller expressions always resulting in an int. It would have compiled if we’d done the explicit cast:

byte c = (byte) (a + b);

Primitive TypeCasting

TypeCasting lets you convert primitive values from one type to another. We mentioned primitive TypeCasting in the previous section, but now we’re going to take a deeper look.

Casts can be implicit or explicit. An implicit cast means you don’t have to write code for the cast; the conversion happens automatically. Typically, an implicit cast happens when you’re doing a widening conversion. In other words, putting a smaller thing (say, a byte) into a bigger container (like an int). Remember the “possible loss of precision” compiler errors we saw in the assignments section? Those happened when we tried to put a larger thing (say, a long) into a smaller container (like a short). The large-value-into-small-container conversion is referred to as narrowing and requires an explicit cast, where you tell the compiler that you’re aware of the danger and accept full responsibility.

Implicit cast:

int a = 100;
long b = a; // Implicit cast, an int value always fits in a long

An explicit casts looks like this:

float a = 100.001f;
int b = (int)a; // Explicit cast, the float could lose info

Integer values may be assigned to a double variable without explicit TypeCasting, because any integer value can fit in a 64-bit double. The following line demonstrates this:
double d = 100L; // Implicit cast

In the preceding statement, a double is initialized with a long value (as denoted by the L after the numeric value). No cast is needed in this case because a double can hold every piece of information that a long can store. If, however, we want to assign a double value to an integer type, we’re attempting a narrowing conversion and the compiler knows it:

class TypeCasting {
public static void main(String [] args) {
int x = 123.456; // illegal
}
}

If we try to compile the preceding code, we get an error something like:

%javac TypeCasting.java
TypeCasting.java:3: Incompatible type for declaration. Explicit cast
needed to convert double to int.
int x = 123.456; // illegal
1 error

In the preceding code, a floating-point value is being assigned to an integer variable. Because an integer is not capable of storing decimal places, an error occurs. To make this work, we’ll cast the floating-point number into an int:

class TypeCasting {
public static void main(String [] args) {
int x = (int)123.456; // legal cast
System.out.println("int x = " + x);
}
}

When you cast a floating-point number to an integer type, the value loses all the digits after the decimal. The preceding code will produce the following output:
int x = 123

We can also cast a larger number type, such as a long, into a smaller number type, such as a byte. Look at the following:

class TypeCasting {
public static void main(String [] args) {
long l = 56L;
byte b = (byte)l;
System.out.println("The byte is " + b);
}
}
The preceding code will compile and run fine. But what happens if the long value is larger than 127 (the largest number a byte can store)? Let’s modify the code:

class TypeCasting {
public static void main(String [] args) {
long l = 130L;
byte b = (byte)l;
System.out.println("The byte is " + b);
}
}
The code compiles fine, and when we run it we get the following:
%java TypeCasting
The byte is -126

You don’t get a runtime error, even when the value being narrowed is too large for the type. The bits to the left of the lower 8 just...go away. If the leftmost bit (the sign bit) in the byte (or any integer primitive) now happens to be a 1, the primitive will have a negative value.
Assigning Floating-Point Numbers

Floating-point numbers have slightly different assignment behavior than integer types. First, you must know that every floating-point literal is implicitly a double (64 bits), not a float. So the literal 123.45, for example, is considered a double. If you try to assign a double to a float, the compiler knows you don’t have enough room in a 32-bit float container to hold the precision of a 64-bit double, and it lets you know. The following code looks good, but won’t compile:

float f = 123.45;

You can see that 123.45 should fit just fine into a float-sized variable, but the compiler won’t allow it. In order to assign a floating-point literal to a float variable, you must either cast the value or append an f to the end of the literal. The following assignments will compile:

float f = (float) 123.45;
float g = 123.45f;
float h = 123.45F;

Assigning a Literal That Is Too Large for the Variable

We’ll also get a compiler error if we try to assign a literal value that the compiler knows is too big to fit into the variable.
byte a = 128; // byte can only hold up to 127

The preceding code gives us an error something like

TestBytes.java:5: possible loss of precision
found : int
required: byte
byte a = 128;

We can fix it with a cast:
byte a = (byte) 128;

But then what’s the result? When you narrow a primitive, Java simply truncates the higher-order bits that won’t fit. In other words, it loses all the bits to the left of the bits you’re narrowing to.

Let’s take a look at what happens in the preceding code. There, 128 is the bit pattern 10000000. It takes a full 8 bits to represent 128. But because the literal 128 is an int, we actually get 32 bits, with the 128 living in the right-most (lower-order) 8 bits. So a literal 128 is actually

00000000000000000000000010000000

Trust me; there are 32 bits up there. (If you don't, you can however count the number of digits…)

To narrow the 32 bits representing 128, Java simply lops off the leftmost (higher-order) 24 bits. We’re left with just the 10000000. But remember that a byte is signed, with the leftmost bit representing the sign (and not part of the value of the variable). So we end up with a negative number (the 1 that used to represent 128 now represents the negative sign bit). Remember, to find out the value of a negative number using two’s complement notation, you flip all of the bits and then add 1. Flipping the 8 bits gives us 01111111, and adding 1 to that gives us 10000000, or back to 128! And when we apply the sign bit, we end up with –128.
You must use an explicit cast to assign 128 to a byte, and the assignment leaves you with the value –128. A cast is nothing more than your way of saying to the compiler, “Trust me. I know what I am doing and I take full responsibility for anything weird that happens when those top bits are chopped off.”

That brings us to the compound assignment operators. The following will compile,

byte b = 3;
b += 7; // No problem: adds 7 to b (result is 10)

and is equivalent to
byte b = 3;
b = (byte) (b + 7); // Won't compile without the
// cast, since b + 7 results in an int

The compound assignment operator += lets you add to the value of b, without putting in an explicit cast. In fact, +=, -=, *=, and /= will all put in an implicit cast.

Assigning One Primitive Variable to Another Primitive Variable

When you assign one primitive variable to another, the contents of the right-hand variable are copied. For example,
int a = 6;
int b = a;

This code can be read as, “Assign the bit pattern for the number 6 to the int variable a. Then copy the bit pattern in a, and place the copy into variable b.”

So, both variables now hold a bit pattern for 6, but the two variables have no other relationship. We used the variable a only to copy its contents. At this point, a and b have identical values, but if we change the contents of either a or b, the other variable won’t be affected.

Take a look at the following example:

class TestPrimitiveAssignments {
public static void main (String [] args) {
int a = 10; // Assign a value to a
System.out.println("a = " + a);
int b = a;
b = 30;
System.out.println("a = " + a + " after change to b");
}
}

The output from this program is

%java TestPrimitiveAssignments
a = 10
a = 10 after change to b

Notice the value of a stayed at 10. The key point to remember is that even after you assign a to b, a and b are not referring to the same place in memory. The a and b variables do not share a single value; they have identical copies.

Reference Variable Assignments

You can assign a newly created object to an object reference variable as follows:
Ferrari b = new Ferrari();

The preceding line does three key things:
• Makes a reference variable named b, of type Ferrari
• Creates a new Ferrari object on the heap
• Assigns the newly created Ferrari object to the reference variable b
You can also assign null to an object reference variable, which simply means the variable is not referring to any object:

Ferrari c = null;

The preceding line creates space for the Ferrari reference variable (the bit holder for a reference value), but doesn’t create an actual Ferrari object.
As we discussed in the last chapter, you can also use a reference variable to refer to any object that is a subclass of the declared reference variable type, as follows:
public class Car {
public void doCarStuff() { }
}
public class Bmw extends Car {
public void doBmwStuff() { }
}
class Test {
public static void main (String [] args) {
Car reallyABmw = new Bmw(); // Legal because Bmw is a
// subclass of Car
Bmw reallyACar = new Car(); // Illegal! Car is not a
// subclass of Bmw
}
}

The rule is that you can assign a subclass of the declared type, but not a superclass of the declared type. Remember, a Bmw object is guaranteed to be able to do anything a Car can do, so anyone with a Car reference can invoke Car methods even though the object is actually a Bmw.
In the preceding code, we see that Car has a method doCarStuff() that someone with a Car reference might try to invoke. If the object referenced by the Car variable is really a Car, no problem. But it’s also no problem if the object is a Bmw, since Bmw inherited the doCarStuff() method. You can’t make it work in reverse, however. If somebody has a Bmw reference, they’re going to invoke doBmwStuff(), but if the object is a Car, it won’t know how to respond.

Variable Scope

Once you’ve declared and initialized a variable, a natural question is “How long will this variable be around?” This is a question regarding the scope of variables. And not only is scope an important thing to understand in general, it also plays a big part in the exam. Let’s start by looking at a class file:

class ScopeTest { // class

static int s = 343; // static variable

int x; // instance variable

{ x = 7; int x2 = 5; } // initialization block

ScopeTest() { x += 8; int x3 = 6;} // constructor

void doStuff() { // method

int y = 0; // local variable

for(int z = 0; z < 4; z++) { // 'for' code block

y += z + x;

}

}

}


As with variables in all Java programs, the variables in this program (s, x, x2, x3, y, and z) all have a scope:

• s is a static variable.

• x is an instance variable.

• y is a local variable

• z is a block variable.

• x2 is an init block variable, a type of local variable.

• x3 is a constructor variable, a type of local variable.


For the purposes of discussing the scope of variables, we can say that there are four basic scopes:


• Static variables have the longest scope; they are created when the class is loaded, and they survive as long as the class stays loaded in the Java Virtual Machine (JVM).

• Instance variables are the next most long-lived; they are created when a new instance is created, and they live until the instance is removed.

• Local variables are next; they live as long as their method remains on the stack. As we’ll soon see, however, local variables can be alive, and still be “out of scope”.

• Block variables live only as long as the code block is executing.


Scoping errors come in many sizes and shapes. One common mistake happens when a variable is shadowed and two scopes overlap. We’ll take a detailed look at shadowing in just a bit. The most common reason for scoping errors is when you attempt to access a variable that is not in scope. Let’s look at three common examples of this type of error:

• Attempting to access an instance variable from a static context (typically from main()).

class ScopeErrorsExample {

int x = 5;

public static void main(String[] args) {

x++; // won't compile, x is an 'instance' variable

}

}

• Attempting to access a local variable from a nested method.


When a method, say run(), invokes another method, say run2(), run2() won’t have access to run()’s local variables. While run2() is executing, run()’s local variables are still alive, but they are out of scope. When run2() completes, it is removed from the stack, and run() resumes execution. At this point, all of run()’s previously declared variables are back in scope. For example:

class ScopeErrorsExample {

public static void main(String [] args) {

ScopeErrorsExample s = new ScopeErrorsExample();

s.run();

}

void run() {

int y = 5;

run2();

y++; // once run2() completes, y is back in scope

}

void run2() {

y++; // won't compile, y is local to run()

}

}

• Attempting to use a block variable after the code block has completed.

It’s very common to declare and use a variable within a code block, but be careful not to try to use the variable once the block has completed:

void run3() {

for(int z = 0; z < 5; z++) {

boolean test = false;

if(z == 3) {

test = true;

break;

}

}

System.out.print(test); // 'test' is an ex-variable,

// it has ceased to be...

}


In the last two examples, the compiler will say something like this:

cannot find symbol


This is the compiler’s way of saying, “That variable you just tried to use? Well, it might have been valid in the distant past (like one line of code ago), but, I have no memory of such a variable now.”


Exam Tip: Pay extra attention to code block scoping errors. You might see them in switches, try-catches, for, do, and while loops. Be cautious and you can score points on the easiest questions of the bunch.

Using a Variable or Array Element That Is Uninitialized and Unassigned

Java gives us the option of initializing a declared variable or leaving it uninitialized. When we attempt to use the uninitialized variable, we can get different behavior depending on what type of variable or array we are dealing with (primitives or objects). The behavior also depends on the level (scope) at which we are declaring our variable. An instance variable is declared within the class but outside any method or constructor, whereas a local variable is declared within a method (or in the argument list of the method).

Local variables are sometimes called stack, temporary, automatic, or method variables, but the rules for these variables are the same regardless of what you call them. Although you can leave a local variable uninitialized, the compiler complains if you try to use a local variable before initializing it with a value.

Primitive and Object Type Instance Variables

Instance variables are variables defined at the class level. That means the variable declaration is not made within a method, constructor, or any other initializer block. Instance variables are initialized to a default value each time a new instance is created, although they may be given an explicit value after the object’s super-constructors have completed.

The list of default values for Primitives and Reference Variables are as follows:

1. Object References – NULL
2. byte, short, int, long – 0
3. float, double – 0.0
4. boolean – false
5. char – ‘\u0000’

Primitive Instance Variables

In the following example, the integer yearOfBirth is defined as a class member because it is within the initial curly braces of the class and not within a method’s curly braces:
public class BirthDayCalc {
int yearOfBirth; // Instance variable
public static void main(String [] args) {
BirthDayCalc bd = new BirthDayCalc();
bd.printYearOfBirth();
}
public void printYearOfBirth() {
System.out.println("The yearOfBirth is " + yearOfBirth);
}
}

When the program is started, it gives the variable yearOfBirth a value of zero, the default value for primitive number instance variables.

Tip: It’s a good idea to initialize all your variables, even if you’re assigning them with the default value. Your code will be easier to read; programmers who have to maintain your code will be grateful.

Object Reference Instance Variables

When compared with uninitialized primitive variables, object references that aren’t initialized are a completely different story. Let’s look at the following code:

public class Car {
private String name; // instance reference variable
public String getCarName() {
return name;
}
public static void main(String [] args) {
Car b = new Car();
System.out.println("The name is " + b.getCarName());
}
}

This code will compile fine. When we run it, the output is
The name is null

The name variable has not been explicitly initialized with a String assignment, so the instance variable value is null. Remember that null is not the same as an empty String (“”). A null value means the reference variable is not referring to any object on the heap. The following modification to the Car code runs into trouble:

public class Car {
private String name; // instance reference variable
public String getCarName() {
return name;
}
public static void main(String [] args) {
Car b = new Car();
String s = b.getCarName(); // Compiles and runs
String t = s.toLowerCase(); // Runtime Exception!
}
}

When we try to run the Car class, the JVM will produce something like this:
Exception in thread "main" java.lang.NullPointerException
at Car.main(Car.java:9)

We get this error because the reference variable name does not point (refer) to an object. We can check to see whether an object has been instantiated by using the keyword null, as the following revised code shows:

public class Car {
private String name; // instance reference variable
public String getCarName() {
return name;
}
public static void main(String [] args) {
Car b = new Car();
String s = b.getCarName(); // Compiles and runs
if (s != null) {
String t = s.toLowerCase();
}
}
}

The preceding code checks to make sure the object referenced by the variable s is not null before trying to use it. Watch out for scenarios on the exam where you might have to trace back through the code to find out whether an object reference will have a value of null. In the preceding code, for example, you look at the instance variable declaration for name, see that there’s no explicit initialization, recognize that the name variable will be given the default value of null, and then realize that the variable s will also have a value of null. Remember, the value of s is a copy of the value of name (as returned by the getCarName() method), so if name is a null reference, s will be too.

Array Instance Variables

An array is an object; thus, an array instance variable that’s declared but not explicitly initialized will have a value of null, just as any other object reference instance variable. But, if the array is initialized, what happens to the elements contained in the array? All array elements are given their default values—the same default values that elements of that type get when they’re instance variables. The bottom line: Array elements are always and I mean always given default values, regardless of where the array itself is declared or instantiated.

If we initialize an array, object reference elements will equal null if they are not initialized individually with values. If primitives are contained in an array, they will be given their respective default values. For example, in the following code, the array yearOfBirth will contain 100 integers that all equal zero by default:

public class BirthDays {

static int [] yearOfBirth = new int[100];

public static void main(String [] args) {

for(int i=0;i<100;i++)

System.out.println("yearOfBirth[" + i + "] = " + yearOfBirth[i]);

}

}


When the preceding code runs, the output indicates that all 100 integers in the array equal zero.


Local (Stack, Automatic) Primitives and Objects


Local variables are defined within a method, and they include a method’s parameters.


Exam Tip: “Automatic” is just another term for “local variable.” It does not mean the automatic variable is automatically assigned a value! The opposite is true. An automatic variable must be assigned a value in the code, or the compiler will complain.

Local Primitives

In the following class, the integer yearOfBirth is defined as an automatic variable because it is within the curly braces of a method.

public class ModifyAge {
public static void main(String [] args) {
int yearOfBirth = 2050;
System.out.println("The yearOfBirth is " + yearOfBirth);
}
}

Local variables, including primitives, always must be initialized before you attempt to use them (though not necessarily on the same line of code). Java does not give local variables a default value; you must explicitly initialize them with a value, as in the preceding example. If you try to use an uninitialized primitive in your code, you’ll get a compiler error:

public class ModifyAge {
public static void main(String [] args) {
int yearOfBirth; // Local variable (declared but not initialized)
System.out.println("The yearOfBirth is " + yearOfBirth);
}
}

Compiling produces output something like this:

%javac ModifyAge.java
ModifyAge.java:4: Variable yearOfBirth may not have been initialized.
System.out.println("The yearOfBirth is " + yearOfBirth);
1 error

To correct our code, we must give the integer yearOfBirth a value. In this updated example, we declare it on a separate line, which is perfectly valid:

public class ModifyAge {
public static void main(String [] args) {
int yearOfBirth; // Declared but not initialized
int day; // Declared but not initialized
System.out.println("You are inside the class.");
yearOfBirth = 2050; // Initialize (assign an explicit value)
System.out.println("Welcome to the yearOfBirth " + yearOfBirth);
}
}

Notice in the preceding example we declared an integer called day that never gets initialized, yet the code compiles and runs fine. Legally, you can declare a local variable without initializing it as long as you don’t use the variable, but let’s face it, if you declared it, you probably had a reason (although we have heard of programmers declaring random local variables just for sport, to see if they can figure out how and why they’re being used).

Tip:
The compiler can’t always tell whether a local variable has been initialized before use. For example, if you initialize within a logically conditional block (in other words, a code block that may not run, such as an if block or for loop without a literal value of true or false in the test), the compiler knows that the initialization might not happen, and can produce an error. The following code upsets the compiler:
public class TestInitialization {
public static void main(String [] args) {
int x;
if (args[0] != null) { // assume you know this will
// always be true
x = 7; // compiler can't tell that this
// statement will run
}
int y = x; // the compiler will die here
}
}

The compiler will produce an error something like this:
TestInitialization.java:9: variable x might not have been initialized

Because of the compiler-can’t-tell-for-certain problem, you will sometimes need to initialize your variable outside the conditional block, just to make the compiler happy. You know why that’s important if you’ve seen the bumper sticker, “When the compiler’s not happy, nobody is happy.”


Local Object References

Objects references, too, behave differently when declared within a method rather than as instance variables. With instance variable object references, you can get away with leaving an object reference uninitialized, as long as the code checks to make sure the reference isn’t null before using it. Remember, to the compiler, null is a value. You can’t use the dot operator on a null reference, because there is no object at the other end of it, but a null reference is not the same as an uninitialized reference. Locally declared references can’t get away with checking for null before use, unless you explicitly initialize the local variable to null. The compiler will complain about the following code:

import java.util.Date;
public class ModifyAge {
public static void main(String [] args) {
Date date;
if (date == null)
System.out.println("date is null");
}
}

Compiling the code results in an error similar to the following:
%javac ModifyAge.java
ModifyAge.java:5: Variable date may not have been initialized.
if (date == null)
1 error

Instance variable references are always given a default value of null, until explicitly initialized to something else. But local references are not given a default value; in other words, they aren’t null. If you don’t initialize a local reference variable, then by default, its value is...well that’s the whole point—it doesn’t have any value at all! The following local variable will compile properly:

Date date = null; // Explicitly set the local reference
// variable to null

Local Arrays

Just like any other object reference, array references declared within a method must be assigned a value before use. That just means you must declare and construct the array. You do not, however, need to explicitly initialize the elements of an array. We’ve said it before, but it’s important enough to repeat: array elements are given their default values (0, false, null, ‘\u0000’, etc.) regardless of whether the array is declared as an instance or local variable.

The array object itself, however, will not be initialized if it’s declared locally. In other words, you must explicitly initialize an array reference if it’s declared and used within a method, but at the moment you construct an array object, all of its elements are assigned their default values.

Assigning One Reference Variable to Another

With primitive variables, an assignment of one variable to another means the contents (bit pattern) of one variable are copied into another. Object reference variables work exactly the same way. The contents of a reference variable are a bit pattern, so if you assign reference variable a to reference variable b, the bit pattern in a is copied and the new copy is placed into b. If we assign an existing instance of an object to a new reference variable, then two reference variables will hold the same bit pattern—a bit pattern referring to a specific object on the heap. Look at the following code:

import java.awt.Dimension;
class ReferenceTest {
public static void main (String [] args) {
Dimension a = new Dimension(5,10);
System.out.println("a.height = " + a.height);
Dimension b = a;
b.height = 30;
System.out.println("a.height = " + a.height +
" after change to b");
}
}

In the preceding example, a Dimension object a is declared and initialized with a width of 5 and a height of 10. Next, Dimension b is declared, and assigned the value of a. At this point, both variables (a and b) hold identical values, because the contents of a were copied into b. There is still only one Dimension object—the one that both a and b refer to. Finally, the height property is changed using the b reference. Now think for a minute: is this going to change the height property of a as well? Let’s see what the output will be:

%java ReferenceTest
a.height = 10
a.height = 30 after change to b

From this output, we can conclude that both variables refer to the same instance of the Dimension object. When we made a change to b, the height property was also changed for a.
One exception to the way object references are assigned is String. In Java, String objects are given special treatment. For one thing, String objects are immutable; you can’t change the value of a String object (Don't worry just yet. We will have a whole chapter dedicated to Strings in future). But it sure looks as though you can. Examine the following code:

class TestStrings {
public static void main(String [] args) {
String x = "Java"; // Assign a value to x
String y = x; // Now y and x refer to the same
// String object

System.out.println("y string = " + y);
x = x + " Bean"; // Now modify the object using
// the x reference
System.out.println("y string = " + y);
}
}

You might think String y will contain the characters Java Bean after the variable x is changed, because Strings are objects. Let’s see what the output is:
%java TestStrings
y string = Java
y string = Java

As you can see, even though y is a reference variable to the same object that x refers to, when we change x, it doesn’t change y! For any other object type, where two references refer to the same object, if either reference is used to modify the object, both references will see the change because there is still only a single object. But any time we make any changes at all to a String, the VM will update the reference variable to refer to a different object. The different object might be a new object, or it might not, but it will definitely be a different object.
You need to understand what happens when you use a String reference variable to modify a string:
• A new string is created (or a matching String is found in the String pool), leaving the original String object untouched.
• The reference used to modify the String (or rather, make a new String by modifying a copy of the original) is then assigned the brand new String object.

So when you say
1. String s = "Anand";
2. String t = s; //Now t and s refer to the same String object
3. t.toUpperCase(); //Invoke a String method that changes the String

you haven’t changed the original String object created on line 1. When line 2 completes, both t and s reference the same String object. But when line 3 runs, rather than modifying the object referred to by t (which is the one and only String object up to this point), a brand new String object is created. And then abandoned. Because the new String isn’t assigned to a String variable, the newly created String (which holds the string “ANAND”) is toast. So while two String objects were created in the preceding code, only one is actually referenced, and both t and s refer to it. The behavior of Strings is extremely important in the exam, so we’ll cover it in much more detail in future.

Previous Chapter: Chapter 15: Stack & Heap

Next Chapter: Chapter 17: Passing Variables to Methods

No comments:

Post a Comment

© 2013 by www.inheritingjava.blogspot.com. All rights reserved. No part of this blog or its contents may be reproduced or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without prior written permission of the Author.

ShareThis

Google+ Followers

Followers