Oracle Certified Professional (OCP) Java Programmer, 1.3 - Assignments ☕

|

Stack and Heap

  • Local variables (method variables) live on the stack.
  • Objects and their instance variables live on the heap.

Literals and Primitive Casting (Objective 1.3)

  • Integer literals can be decimal, octal (prefix of zero e.g. 013), or hexadecimal (prefix of 0x e.g. 0x3d).
  • Literal numbers are implicitly ints.
  • Literals for longs end in L or l.
  • 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.
float f = 23.467890; // Compiler error, possible loss of precision
float g = 49837849.029847F; // OK has the suffix "F"
  • A char is a single text character
  • It is a 16-bit unsigned integer under the hood (range from 0 to 65535)
  • A char literal is represented as
  • a character inside single quotes: ‘d’
  • it can be represented by it’s Unicode character code: a hex-value prefixed by \u
//all represent character a
char c1 = 'a';
char c2 = '\u0061';
char c3 = 97;
System.out.println(c1 + "" + c2 + "" + c3); //prints: a a a

Scope (Objectives 1.3 and 7.6)

  • Scope refers to the lifetime of a variable.
  • There are four basic scopes:
    1. Static variables live basically as long as their class lives.
    2. Instance variables live as long as their object lives.
    3. Local variables live as long as their method is on the stack; however, if their method invokes another method, they are temporarily unavailable.
    4. Block variables (e.g. in a for block or an if block) live until the block completes.

Basic Assignments (Objectives 1.3 and 7.6)

  • 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.
byte a = 3; // No problem, 3 fits in a byte
byte b = 8; // No problem, 8 fits in a byte
byte c = b + c; // Wont compile. Explicit cast to byte required
  • Widening usually is done by an implicit cast. Widening is putting a smaller thing into a bigger container e.g. byte => int
  • Narrowing requires an explicit cast. Narrowing is the putting of a large-value into a smaller container e.g. int => byte
  • When the value of a primitive being narrowed is too large for the type, the low order bits are assigned to it.
long l = 130L;		//binary: 0000 0000 1000 0010
//takes least significant byte 1000 0010 which is minus 126 in 2’s complement
byte b = (byte)l;		

System.out.println(b is  + b); //Prints: b is -126
  • Compound assignments (e.g. +=) perform an automatic cast in some cases e.g.
byte a =3, c =8;
a += c; //no need for explicit cast to byte
  • A reference variable holds the bits that are used to refer to an object.
  • Reference variables can refer to subclasses of the declared type; but not to superclasses.

Using a Variable or Array Element That Is Uninitialized and Unassigned (Objectives 1.3 and 7.6)

  • Instance variables are always initialized with a default value.
  • Local/automatic/method variables are NEVER given a default value. If you attempt to use one before initializing it, you’ll get a compiler error.

  • When an array is instantiated, elements get default values.

Passing Variables into Methods (Objective 7.3)

  • Methods can take primitives and/or object references as arguments.

  • Method arguments are always copies, pass-by-value.
  • An object reference passed as an argument into a method allows the method to change the state of the object it points to.
  • A primitive argument is an unattached copy of the original primitive.

  • Shadowing occurs when two variables with different scopes share the same name. This leads to hard-to-find bugs, and hard-to-answer exam questions.

Array Declaration, Construction, and Initialization (Obj. 1.3)

  • Arrays can hold primitives or objects, but the array itself is always an object.

  • You must include the size of an array when you construct it (using new), unless you are creating an anonymous array.
  • When you construct an array of objects, the elements are NOT automatically created for you, it merely has null references in it.
Animal [] pets = new Animal[3]; //3 null references of type Animal
  • Arrays are indexed beginning with zero.
  • An ArrayIndexOutOfBoundsException occurs if you use a bad index value.

  • Arrays have a length variable whose value is the number of array elements, the last index you can access is always one less than the length of the array.

  • Multidimensional arrays are just arrays of arrays.
int[ ][ ] myArray = new int[3][1];
myArray[0] = new int[2];
myArray[0][0] = 6;
myArray[1] = new int[3];
myArray[1][0] = 9;
myArray[1][2] = 9;
  • You only have to declare the first dimension of a multi-dimension array:
int[][] myArray =  new int[3][];
  • You can declare, create and initialise an array in one statement in 2 ways:
int x = 2;

int[] dots = {6,x,8}; //single dimensional
int[][] scores = {{5,2,4,7}, {9,2}, {3,4}}; //multidimensional

dots = new int[] {6,2,8};	//anonymous array assigned to ref variable
f.takesAnArray(new int[] {7,7,8,2,5});

//anonymous array can be used as a just-in-time method argument
  • An array of primitives can accept any value that can be promoted implicitly to the array’s declared type e.g. a byte variable can go into an int array.
  • An array of objects can hold any object that passes the IS-A (or instanceof) test for the declared type of the array. e.g. if Horse extends Animal, then a Horse object can go into an Animal array.

  • If you assign an array of primitives to a previously declared array reference, the array you’re assigning must be the same type and the same dimension as the reference you’re assigning it to. e.g.
int[] big;
byte[]smaller = new byte[5];
big = small; //compiler error, incompatible types!

int[] oneD = new int[6];
int[][] twoD = new int[3][];
oneD = twoD; 	//compiler error, different dimensioning!
twoD[0] = oneD;	/* OK, twoD is an int array and oneD’s first element is expecting an int array*/
  • There is unusual syntax where you can create an anonymous array, and assign it to a dimension of another array in one statement e.g.
int[] it = new int[][]1[0];
is the same as
int[][] anon = new int[][]1;
int[] it = anon[0];
  • You can assign an array of objects of one type to a previously declared array reference if it passes the IS-A test. e.g.
Car[] cars;
Honda[] hondas = new Honda[5];
cars = hondas; // OK because Honda is a type of Car
  • For comparison of arrays:
  • Arrays.equals(): two arrays are considered equal if both arrays contain the same number of elements, and all corresponding pairs of elements in the two arrays are equal.
  • .equals() on a primitive array: you actually calling .equals() of Object, so only when the two references point to the same object, they are considered equal.
class Test {  
     public static void main(String... args)  
     {  
         int[]a = new int[]{1};  
         int[]b = new int[]{1};  
         int[] c = a;  
         System.out.println(a.equals(b));   //false  
         System.out.println(a.equals(c));   //true  
         System.out.println(Arrays.equals(a, b)); //true  
     }  
}

Initialization Blocks (Objectives 1.3 and 7.6)

  • Static initialization blocks run once, when the class is first loaded. Classes are loaded starting with the class at the head of hierarchy e.g. Bird in example below.
  • Instance initialization blocks run every time a new instance is created. They run after all super-constructors and before the constructor’s code has run.
  • If multiple instance initialisation blocks exist in a class, they follow the rules stated above, AND they run in the order in which they appear in the source file.
class Bird {
	static { System.out.print("b1 ");}
	{ System.out.print("b2 "); }

	public Bird() { System.out.print("b3 "); }
}
class Raptor extends Bird {
	static { System.out.print("r1 "); }

	public Raptor() { System.out.print("r2 "); }

	{ System.out.print("r3 "); }

	static { System.out.print("r4 "); }
}
class Hawk extends Raptor {
	static {System.out.print("h1 ");}

	public static void main(String[] args) {
		System.out.print("pre ");
		new Hawk();
		System.out.println("hawk ");
	}
}

Prints: b1 r1 r4 h1 pre b2 b3 r3 r2 hawk
  • The compiler performs a single class initialization routine that combines all the static variable initializers and all of the static initializer blocks, in the order that they appear in the class declaration. This single initialization procedure is run automatically, one time only, when the class is first loaded.
public class ExamLabBoard{
    static int door=1; //executes first

    static int cam=capture(); //executes second

    static{ door+=2;} //executes third

    public static int capture(){ return door;}

    public static void main(String args[]){
         System.out.printf("cam="+cam); //cam=1
    }
}
  • If you make a mistake in an initialization block, the JVM can throw an ExceptionInInitializationError.

Using Wrappers (Objective 3.1)

  • Wrappers have two main functions:
  • To wrap primitives so that they can be handled like objects
  • To provide utility methods for primitives (usually conversions)
  • Wrapper values are immutable: once assigned, they CANNOT be changed.
  • All Wrapper classes are final.

  • The three most important method families are:
  • xxxValue() Method takes no arguments, returns a primitive. Every Wrapper class has 6 versions of this e.g. byteValue, shortValue(), intValue()..
  • parseXxx(String s) Static method that takes a String, returns a primitive, throws NumberFormatException. Can be supplied with second parameter radix for Short, Byte, Integer, Long to change number system representation e.g. binary - 2
  • valueOf(String s)/valueOf(primitive) Static method that takes a String/primitive, returns a wrapped object, throws NumberFormatException. Can be supplied with second parameter radix for Short, Byte, Integer, Long to change number system representation REMEMBER: if any of above methods have a primitive type in its name, then it returns a primitive e.g. parseInt(), intValue().
Integer i = new Integer(42); 	// make a new wrapper object
byte b = i.byteValue(); 		// convert i's value to a byte

int int1 = Integer.parseInt("00000011", 2); /* Converts a string representing a binary number to an int. int1 = 3*/

Integer i2 = Integer.valueOf("34"); // creates an Integer
Integer i2 = Integer.valueOf(34); // creates an Integer
  • Wrapper constructors can take a String or its associated primitive, except for Character, which can only take a char.
  • new Boolean(“anyValue”) or new Boolean(null) will create a Boolean with the value of false.
  • new Boolean(“TRuE”) is not case-sensitive.
  • There is a Boolean.parseBoolean(String) in Java 6.
  • There is NO Character.parseChar(char).

Boxing (Objective 3.1)

  • As of Java 5, boxing allows you to convert primitives to wrappers or to convert wrappers to primitives automatically.
  • Wrapper objects are found “meaningful equivalent” with equals() if they are of the same type and have the same value.
Integer x = 343;
long long343 = 343L;
System.out.println(x.equals(long343))//false.long343 is boxed to Long
System.out.println(x.equals(343));//true. 343 is boxed to an Integer
  • Use == to see if the two wrapper objects are the same objects.
Integer x = new Integer(567);
Integer y = x;
Integer z = new Integer(567);

if(x==y) System.out.println("x==y");  //true
if(x==z) System.out.println("x==z"); //false
  • Using == with wrappers created through boxing is tricky: two instances of the following wrapper objects will always be == when their primitive values are the same:
  • Boolean
  • Byte
  • Character from \u0000 to \u007f (7f is 127 in decimal)
  • Short and Integer from -128 to 127

Advanced Overloading (Objectives 1.5 and 5.4)

  • Primitive widening uses the “smallest” method argument possible.
  • Java 5’s designers decided that the most important rule should be that pre-existing code should function the way it used to, so the compiler will choose the older style before it chooses the newer style, so when choosing suitable method arguments:

  • Widening beats boxing
class AddBoxing {
	static void go(Integer x) {
		System.out.println("Integer");
	}
	static void go(long x) {
		System.out.println("long");
	}

	public static void main(String [] args) {
		int i = 5;
		go(i); // which go() will be invoked? Prints: long
	}
}

  • Widening beats var-args
class AddVarargs {
	static void go(int x, int y) {
		System.out.println("int,int");
	}

	static void go(byte... x) {
		System.out.println("byte... ");
	}

	public static void main(String[] args) {
		byte b = 5;
		go(b,b); // which go() will be invoked? Prints: int, int
	}
}
  • Boxing beats var-args, think of var-args method as more like a catch-all method that is used only as a last resort.
class BoxOrVararg {

	static void go(Byte x, Byte y){
		System.out.println("Byte, Byte");
	}

	static void go(byte... x) {
		System.out.println("byte... ");
	}

	public static void main(String [] args) {
		byte b = 5;
		go(b,b); // which go() will be invoked? Prints: Byte, Byte
	}
}
  • You can box and then widen. (byte b is boxed to a Byte, Byte is widened to an Object.)
class BoxAndWiden {
	static void go(Object o) {
		Byte b2 = (Byte) o; // ok - it's a Byte object
		System.out.println(b2);
	}

	public static void main(String [] args){
		byte b = 5;
		go(b); // can this byte turn into an Object? Prints: 5
	}
}

  • You can widen and use var-args, and you can box and use var-arg.
class Vararg {
	static void wide_vararg(long... x){
		System.out.println("long...");
	}

	static void box_vararg(Integer... x){ 	   
		System.out.println("Integer...");
	}

	public static void main(String [] args){
		int i = 5;
		wide_vararg(i,i); // needs to widen and use var-args
		box_vararg(i,i); // needs to box and use var-args
	}
}
//Prints:
//long...
//Integer...
  • If there are two overloaded methods: one that can take a primitive var-args, the other that can take a Wrapper var-args, you can only call the method if you pass it an primitive array or Wrapper array.
class test {
	public static void tMeth(Integer... i){
		System.out.print("A");
	}

	public static void tMeth(int... i){
		System.out.print("B");
	}

	public static void main(String args[]){
		tMeth(1); //compiler error
		tMeth(new Integer(1)); //compiler error
		tMeth(1,2); //compiler error
		tMeth(new Integer(1), new Integer(2)); //compiler error
		tMeth(new int[]{1,2,3}); //compiles
		tMeth(new Integer[]{new Integer(1), new Integer(2)}); //compiles
	}  
}
  • You CANNOT widen from one wrapper type to another e.g. widening of Integer to Long is not possible as IS-A test fails.
  • You CANNOT widen and then box. (An int can’t become a Long.)

Garbage Collection (Objective 7.4)

  • In Java, garbage collection (GC) provides automated memory management, to free as much space on the heap as possible.
  • The purpose of GC is to delete objects that can’t be reached, an object is eligible for deletion when no live thread can reach it (no more references to an object).

  • Only the JVM decides when to run the GC, you can only suggest it.
  • You CANNOT know the GC algorithm for sure.

  • Java applications can run out of memory.

  • Islands of objects are when two instances have references to each other; but their references are not accessible externally. So even though each object still has a valid reference, there will be no way for any live thread to access either object. Islands of objects can be garbage collected.

  • You can request garbage collection with the static call System.gc(); or Runtime.getRuntime().gc(). Garbage collection has evolved to a stage where it is recommended not to bother invoking it in your code ever.

  • The finalize() method is guaranteed to run once and only once before the garbage collector deletes an object, its inherited from Object.
  • As you CANNOT count on the GC to delete an object, you CANNOT be guaranteed that finalize() will run, so essential code should NOT be put in there.

  • You can uneligibilize an object for GC from within finalize() if an object reference is created to that object.
  • finalize() is never invoked more than once by a JVM for any given object.

Related Posts