Chapter 3: Java Basics - Primitive Types and String

3.1: Storage Type

* Register

Registers are inside CPU, so they are the fastest, but have very limited number. The use of them is decided by compiler. For users they doesn't exist.

* Stack

In RAM. Manipulated by stack pointer. When allocating memory, stack pointer is moved down, when releasing, stack pointer is moved up. Second fastest storage. Because to use stack the size and life time of the object must be known at compiling time, it can not be used for dynamic objects. Not used by Java for user-defined objects.

* The heap

In RAM. Where Java stores all its objects. Slower than Stack.

* Static Storage

Contains data that is available for the entire time of a program. Keyword static is used to specify it. Java objects are never placed in static storage.

3.2: Primitive Types

For small, simple objects such as integers, to create them in heap is not efficient. So Java makes it a special case: use the approaches of C and C++ to create them as automatic objects, which are not manipulated with handles:

   
   char c = 'x';

JVM determines the size of each primitive objects, which doesn't change on different machines. This makes Java more portable.

All numeric types are signed.

3.3: High-Precision Numbers

Java provides two classes for high-precision arbitrary-digit numbers: BigInteger and BigDecimal. They have all the methods as int and float, but you can not use operators.

3.4: Associate Wrapper

All primitives are created on the stack and do not have handles. If you want to manipulate them with handles, for example, add them into collections such as Vector, you have to use associate wrapper to wrap the primitive number in a non-primitive object:

   
   Character C = new Character('x');

Other wrappers:

   
   Float f = new Float(12.34);
   Integer i = new Integer(1234);
   Double d = new Double(12.34d);

They have implemented methods such as toString, hashcode, equals, etc., and they have methods to convert strings into primitive types such as Integer.parseInt. So they are more powerful than primitive objects.

However, these wrappers are immutable. They can not be modified as primitives.

High-Precision NumbersJava provides two classes for high-precision arbitrary-digit numbers: BigInteger and BigDecimal. They have all the methods as int and float, but you can not use operators.

Default Values for PrimitivesWhen a primitive type is a class member, it is guaranteed to have a default value if it is not initialized:

Boolean false

Char '\u0000' (null)

byte (byte)0

short (short)0

int 0

long 0L

float 0.0f

double 0.0d

However, primitive-type objects are only automatically initialized as a class member. If you don't initialize a local variable, Java compiler will generate an error.

3.5: Casting Primitive Objects

When compiler sees the following statements,

   
   int i = 200;
   long l = i;

it will automatically convert the integer to long integer. However, if you write a statement to make a narrowing conversion like:

   
   i = l;

compiler will refuse to make an implicit conversion - because a narrowing conversion may lose information - and prompt an error message asking for explicit casting like:

   
   i = (int)l;

3.6: Explicitly Indicate the Type of a Primitive Constant

In case that the compiler can not figure out the type of a constant, you should explicitly mark its type:

Hexadecimal Leading 0x or 0X

Octal Leading 0 (zero)

Long Trailing l or L

Float Trailing f or F

Double Trailing d or D

For example, in such a statement

   
   float f4 = 3.75e-47;

compiler will automatically take exponential numbers as doubles, so compiler will issue an error that you must use cast to convert double to float. To avoid this:

   
   float f4 = 3.75e-47f;

3.7: Automatic Primitive Type Promotion in Calculation

If you perform any mathematical or bitwise operation on primitive types lower than int, such as char, byte or short, those values will be promoted to int before the calculation, and the result will be int. So if you want to assign the result back to the lower type, you have to use cast. But compound assignments do not require casts for char, byte or short.

When two types of objects are in an expression, the result will be the higher type. For example, when you calculate a double with a float, result will be a double.

3.8: Converting Between String and byte Array

Class String has a constructor which takes a byte array as argument, and it has a getBytes method which returns a byte array.

   
   class Test {
      public static void main(String [] argu)
      {
   
          byte [] a = new byte[100];
          System.out.println("byte[] length before getBytes is " + a.length);
          a = "Yesterday once more...".getBytes();
          System.out.println("byte[] length before getBytes is " + a.length);
          String s = new String(a);
          System.out.println("String is \n" + s);
      }
   }
   

Output will be:

   
   byte[] length before getBytes is 100
   byte[] length before getBytes is 22
   String is
   Yesterday once more...

From this example you can see that the getBytes method returns a byte array, which is assigned to the left operand. The length of the array is also changed during the assignment.

3.9: Class String is Immutable

Making String immutable enables String to share on memories. For example, when you say

   
   String s1 = "Hello";
   String s2 = "Hello";

Most probably handle s1 and s2 are pointing to the same memory location containing string "Hello". That's why in most cases you can use "==" to check if two strings are equal - because "==" checks if two handles are pointing to the same address.

The mutable version of class String is StringBuffer, which provides amending methods like append and insert.

Because String is immutable, when you say

   
   String a = "aa" + "bb" + "cc";

the compiler does not just append "bb" to the end of "aa", then append "cc" to the end of "aabb". It creates a new String "aabb" from "aa" and "bb", then create another new String "aabbcc" from "aabb" and "cc". To avoid this overhead, when you have lots of appending things to do, use mutable StringBuffer:

   
   StringBuffer a = "";
   a.append("aa").append("bb").append("cc");

3.10: String Operator "+"

When operands are joined with "+" and one of them is a string, compiler will automatically turn the rest of them into strings:

   
   int x = 0, y = 1, z = 2;
   System.out.println("ABC " + x + y + z);

Output will be "ABC123" instead of "ABC3".

3.11: Length and Index of String

The length of a string is the number of characters in the string. The first character's index is 0. These two characteristics are like C++. But one thing is different: its method substring(7, 15) starts from the 7th character and ends from the 14th character, not the 15th. This way you can derive the length of the substring simply by using 15 - 7 = 8.

   
   import java.io.*;
   
   public class TestContentLength {
       public static void main(String [] args)
       {
           String string = "Content-length: 23";
           int length = 0;
           System.out.println("***" + string.substring(0, 16) + "***");
           System.out.println("Length of string is " + string.length());
           length = Integer.parseInt(string.substring(16));
   
           System.out.println("The number is " + length);
       }
   }

Output will be:

   
   ***Content-length: ***
   Length of string is 18
   The number is 23