Working with Java Primitive Data Types and String APIs

The topics for this section are the following:

Declare and Initialize Variables

Java has a number of primitive data types, these are portable across all computer platforms that support Java. Primitive data types are special data types built into the Java language, they are not objects created from a class. Object type String is often thought as a primitive data type but it is an Object. All Java's six number types are signed which means they can be positive or negative. Always append a f when you want a floating-point number otherwise it will be a double.

Data Type Wrapper Value
boolean Boolean A value indicating true or false
byte Byte An 8-bit integer (signed)
char Character A single unicode character (16-bit unsigned)
double Double A 64-bit floating-point number (signed)
float Float A 32-bit floating-point number (signed)
int Int A 32-bit integer (signed)
long Long A 64-bit integer (signed)
short Short A 16-bit integer (signed)

Primitive data types sizes, and default values can be seen in the below table.


Type
Size in Bits
Size in Bytes
Default value
Minimum Range
Maximum Range
boolean
8
1
false
n/a
n/a
char
16
2
'\u0000'
\u0000
\uFFFF
byte
8
1
0
-128
127
short
16
2
0
-2E15
-2E15-1
int
32
4
0
-2E31
-2E31-1
long
64
8
0L
-2E63
-2E63-1
float
32
4
0.0f
Not needed
Not needed
double
64
8
0.0d
Not needed
Not needed

When you declare a variable you must use the data type and the name (which must be a valid identifier), you can declare multiple variables on a single line if they are the same data type. Java is a strongly typed programming language which means that every variable must be known at compile time, this identifies any errors in the code when compiling, there are two types of variables primitive or reference (pointer to an object).

A variable is just a storage location and has the following naming constraints

You have the option to initialize a variable or leave it uninitialized, we get different behaviors when we attempt to use the uninitiailzed variable depending on what type of variable or array we are dealing with, the behavior also depends on the scope level at which we declared the variable.

There are two types of variable in Java

Instance Variable Declared within the class but outside any method, constructor or any other initializer block
Local Variable

Declared within a method (or argument list of a method)

Instance/Local Variable Type Action
Instance Primitive Will be initialized with its default value (see Java Primitive Data types above for default values)
Instance Object

Will be initialized with null

Instance Array Not Initialized - will have value null
If Initialized - will be given default values
Local Primitive All primitive must be initialized otherwise you will get a compiler error
Local Object

All object type be initialized otherwise you will get a compiler error, you can initialize a object with null to stop the compiler complaining

String name = null;

Local Array Not Initialized - will have value null
If Initialized - will be given default values

A quick example to explain the table above

Example

public class varTest {
   public static void main(String[] args) {

      test t1 = new test();
      t1.testMethod();
   }
}

class test {
   int instance_variable;         // OK not to initialize this as it will get the default value 0

   // Test constructor
   test() {
      System.out.println("instance_variable is " + instance_variable);
   }

   void test() {
      int method_variable;        // Must initialize this variable otherwise the compiler will complain.
      System.out.println("method_count: " + method_count);
   }
}

Note: when you try to compile this, the compiler will error stating that method_count might not have been initialized, remember that you should initialize memember variables

Some code example of initalizing variables

boolean example boolean t = true;
boolean p = false;
boolean q = 0;   // Compiler error
char example

char a = 'a';
char b = '@';
char letterN = '\u004E';   // The letter N
char c = (char) 70000;     // casting is required because 7000 is out of range
char d = 70000;            // ERROR: casting rquired

byte example byte b = 0x41;             // display 65
byte c = 4;                // display 4
byte d = 0b1111111;        // display 127
short example short a = 10;
short b = 107;
int example int a = 1;
int b = 10;
int c = 10,000;   // ERROR: because of the comma
int d = 10_000;   // Perfectly ok to use underscores
long example long l1 = 110599L;
long l2 = 0xFFFFl;   // Note the lower case l
float example float f1 = 23.467890;       // ERROR: Compiler error loss of precision
float f1 = 23.467890f;      // Note the suffix f at the end
float f2 = 23.467890F;      // Note the suffix F at the end
float f3 = 23.465e02F;      // Using exponential
float f4 = 1_000.000_000F;  // Using underscores
double example double d1 = 987.987d;
double d1 = 987.987D;
double d2 = 987.987;           // OK, because the literal is a double
double d3 = 100_987.100_987;   // Using underscores

Note: literals with decimals will default to double, not a float
Bad Examples
byte b8 = 0b_00000001;     // Cannot have underscore directly after 0b 
char c8 = 0x_007F; // Cannot have underscore directly after 0x
int i8 = 1000000_; // Underscore cannot be at end of literal
long d8 = 1000000_L; // Underscore cannot be prior to suffix (L/l,F/f,D/d)
float f8 = _1000.000000; // Underscore cannot be at start of literal
double l8 = 1.0000000_e10; // Underscore cannot prefix exponential in literal

Java will allow you to narrow or widen variables in specific circumstances, also be careful of using signed and unsigned variables

Narrowing - Compiler errors due to not fitting thus data lost
byte b1 = 128; 
char c1 = 65536;
short s1 = 32768;
Widening - all ok due to fitting into the wider variable
int i1 = 10; 
long l1 = i1;
float f1 = i1;
double d1 = i1;

Casting allows you to tell the compiler that you know what you are doing in regard to narrowing or widening, generally it's more commonly used when narrowing as data loss may occur for example casting a float variable to a int will cause a loss of data (decimal part). Normally try not to use a cast because you are implying that you know of risks.

Also note that if you cast it can cause a overflow or a underflow

Casting Examples

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

float a = 100.001f;
int b = (int) b;       // Explicit cast, a float can loose info as an int

Note: with explicit casting you let the compiler know that you know that you may loose info

When casting primitive data types you can use the below:

Wrapper Classes

Wrapper classes serve two primary purposes

There is a wrapper class for every primitive, the one for int is Integer, for float is Float and so on. below is a table detailing the wrapper classes and their constructor arguments

Primitive Wrapper Class Constructor Arguments
boolean Boolean boolean or String
byte Byte byte or String
char Character char
double Double double or String
float Float float, double or String
int Integer int or String
long Long long or String
short Short short or String

A note before I show some examples is that wrapper classes are immutable, thus they cannot be changed, also a Boolean object can't be used like a boolean primitive.

Creating Integer i1 = new Integer(42);
Integer i2 = new Integer("42");
Character c1 = new Character('c');
Creating using valueOf() method

Integer i3 = Integer.valueOf("101011", 2);     // converts 101011 to 43

Float f2 = Float.valueOf("3.14f");             // assign 3.14 to Float object f2

Creating using parseXXX() method

String s1 = "42";

int i1 = Integer.parseInt(s1);                 // converts the String to a int primitive

Using Wrappers

String num1 = "42";
String num2 = "58";

System.out.println("Total: " + (num1 + num2)); // results in 4258 not what we was hoping for

Integer i1 = new Integer(num1);                // convert the String value into a primitive value
Integer i2 = new Integer(num2);

System.out.println("Total: " + (i1 + i2));     // results in 100 what we wanted

System.out.println("Total: " + (Integer.parseInt(num1) + Integer.parseInt(num2))); // another way

Using toString()

String num1 = "42";

Integer i1 = new Integer(num1);

System.out.println("toString reports: " + i1.toString());     // now print out a String

Each class has many methods for data integrity, for example the Character class has the following methods: isDefined, isDigit, isLowerCase, isUpperCase and so on.

Lastly regarding wrappers is to mention boxing and unboxing. Autoboxing is the automatic conversion that the Java compiler makes between the primitive types and their corresponding object wrapper classes. For example, converting an int to an Integer, a double to a Double, and so on. If the conversion goes the other way, this is called unboxing.

There are a few rules rearding boxing, the Java compiler applies autoboxing when a primitive value is:

and unboxing rules, the Java compiler applies unboxing when an object of a wrapper class is

Boxing and Unboxing example
public class Test {

    static int a = 5;
    static Integer A = 10;
    
    public static void main(String[] args) {
        boxing(a);                              // passing a int which will boxed into a Integer
        unboxing(A);                            // passing a Integer which will be unboxed into a int
    }

    static void boxing(Integer a) {
        System.out.println("Boxing: " + a);
    }

    static void unboxing(int a) {
        System.out.println("Unboxing: " + a);
    }
}

Identify the scope of variable

Scope is the region of a program a variable is visable and thus be used, there are several scopes

Scope Qualifier
Class {DefinedClassType}
Instance this
Method none
Loop Block none
Block including exception block none
Scope Qualifier

Some scope examples

Basic scope
public class Person {
    static String name = "UNKNOWN";  // Class Variable
    String instanceName = "UNKNOWN"; // Instance Variable;
    String age = "25";  // Instance Variable initialized;

    // Single Parameter Constructor.
    public Person(String age) {
        // Constructors are perfect examples of how a method parameter
        // name can have same name as class or instance variable name.

        // In this constructor,  the Person instance age does not get
        // set at all, because age not qualified by 'this'.
        // This is a common mistake and may be tested on exam
        age = age;
    }

    // Two Args Constructor
    public Person(String name, String age) {
        // constructor parameters are named and typed the same as the
        // class variable 'name' and the instance variable 'age'

        // Correctly setting object's age using 'this' qualifier
        this.age = age;

        // instanceName is an instance variable and has different
        // name from the parameter which will be assigned to it, so
        // this not required, but good practice to use it

        // Also setting static variable name in same assignment
        // statement
        this.instanceName = Person.name = name;
    }
    // Simple setter for age
    public void setAge(String age) {
        // method sets instance variable age to the parameter passed.
        this.age = age;
    }

    // Simple setter for instanceName
    public void setInstanceName(String instanceName) {
        this.instanceName = instanceName;
    }
    
    // Simple get decade to explain loop scope
    public String getDecade(int age) {
      // a method local variable
      String decadeString = "";
      
      // A method local variable
      int decadeNumver = age/10;
      
      // A method local variable
      int j = 0;
      for (int i = j = 0; i < (decadeNumber + 1); i++, j++) {
        // local loop block variable named decade, scope is loop
        String decade = "Decade " + (age / 10);
        
        // i variable is local to the loop
        if (i == (decadeNumber)) {
           decadeString = decade;
        }
      }
      int modyear = age % 10;
      decadeString += ", Year " + modyear;
      return decadeString;
    }
}
Nested Classes and scope
public class NestedScope {
    public static void main(String[] args) {
        // local variable i declared and initialized
        int i = 10;
        class locallyDefinedClass {
            {
                // i from method scope still visible in nested local class
                System.out.println("value of i BEFORE LOOP  " + i);
                System.out.println("---------------");

                // Because this for loop is within a local class,this is valid
                for (int i = 0; i< 5; i++) {
                    System.out.println("value of i during FIRST loop " + i);
                }
                System.out.println("---------------");

                // the local variable i from previous loop has gone out of scope
                // so it is ok to create another local variable in the second loop
                // declaration of the same name.
                for (int i = 5; i > 0; i--) {
                    System.out.println("value of i during SECOND loop " + i);
                }
                System.out.println("---------------");

                //  assign local loop variable j to local variable i from the
                // surrounding method of the nested class,
                for (int j = i; j < 15; j++) {
                    System.out.println("value of i,j during THIRD loop " + i + "," + j);
                }
                System.out.println("value of i AFTER LOOP  " + i);
            }
        }
        System.out.println("value of i before local class instantiated " + i);
        new locallyDefinedClass();
        System.out.println("value of i after local class instantiated " + i);
    }
}

Use local variable type inference

There is a new feature called Local Variable Type Inference (LVTI) from Java 10 onwards that allows you to reduce the verbosity of the code, you can only use it for local variables inside a method body, note that var is not a data type. Note that var itself is not a keyword and can be used for class names and var names.

You cannot use LVTI for the following:

When and when not to use vars
public class VarTest {
    public static void main(String[] args) {
        var p1 = new Person();
        p1.setName("Paul");
        System.out.println(p1);

        // Adding some other var declarations:
        // i is inferred to be an int, since it's assigned a literal int
        var i = 1;

        // An array can be assigned to an LVTI variable
        var aVarArray = new int[3];

        // Valid to assign a method return value to an LVTI variable
        var methodVal = p1.getName();

        // OK to assign a null object to LVTI variable but not literal null
        // Note that i am using var as a variable name which is valid
        Object nullObject = null;
        var var = nullObject;

        // Invalid var declarations:

//        // cannot use var declaration in a compound statement
//        var j = 0, k = 0;
//
//        // again, cannot use var declaration in a compound statement
//        var m, n = 0;
//
//        // Cannot declare a var variable without also initializing it
//        var someObject;
//
//        // Cannot assign null to var variable, type cannot be inferred
//        var newvar = null;
//
//        // Cannot use array initializer in var declaration/initialization
//        var myArray = {"A", "B"};
//
//        // Cannot have an array of var
//        var[]newarray = new int[2];
    }
}

Create and Manipulate Strings

Once a string object is created it can never be changed, string objects are immutable objects. String objects are created using the new keyword, or assigning text to a String variable the String class has many constructors.

String creation examples

// Using the new keyword means a String object will be created
// there are many String constructors that can be used
String s = new String();
s = "Using the new keyword";

String s = new String("Using a one liner");

// the below creates the string in the string constant pool (see below for more information)
// this actually makes Java more efficient
String s = "Using the shorthand version";

To make Java more efficient the JVM sets aside a special area of memory called the "String constant pool", When the compiler encounters a String literal, it checks the pool to see if an identical String already exists. If a match is found, the reference to the new String is directed to the existing String and no new String object is created (The existing String simply has an additional reference). To stop stop any problems in the String pool, the String class is marked final nobody can override the behaviors of any of the String methods, so you can be assured that the String objects you are counting on to be immutable will in fact be immutable, this is called interning

As I stated above String objects can never be changed, what happens when you appear to change a string object is that a new string object is created and the string variable points to the new string object, the old string object is deposed of by the garbage collector.

Strings are immutable

String s = new String("Using a one liner");

s = s.concat("you are not supposed to be able to change this");

Note: what happens is the following

  1. A new string object is created using the original string with the concatenation
  2. the string variable s now points to the new string object
  3. the old string object is removed by the garbage collector as long as nothing else is attached to it.

There are a number of categories for String methods

Category Methods
Comparison equals
equalsIgnoreCase
contentEquals
compareTo
isEmpty
isBlank
Text Searches contains
equalsIgnoreCase
endsWith
indexOf
lastIndexOf
matches
startsWith
Text Manipulation concat
join
replace
replaceAll
replaceFirst
split
substring
subsequence
Text Transformation chars
codePoints
format
lines
repeat
strip
stripLeading
stripTrailing
toCharArray
toLowerCase
toUpperCase
trim
valueOf

Here are some of the more commonly used String methods, check the documentation for a full list

charAt

String x = "Hello World!";
System.out.println("Character 3 in String x is " + x.charAt(2));

Note: The charAt returns the character at a specific index, remember we start at zero

compareTo

String t = "Hello";
String t1 = "World";
String t2 = t;

System.out.println( t + " compareTo " + t2 + " results in " + t.compareTo(t2));   // results in 0 (match)
System.out.println( t + " compareTo " + t1 + " results in " + t.compareTo(t1));   // results in -15

Note: a reult of zero means that they are equal, a negative values means it is less than the string passed, and a positive values means it is more than the value passed

concat

String x = "Taxi";

System.out.println( x.concat(" cab");
System.out.println("Original String x has now been changed : " + x);

Note: concat method does not update x as there was no assignment

equals

String x = "hello";

if ( x.equalsIgnoreCase("hello")
   System.out.prinln("String x is equal to Hello, hello, etc");

Note: see equals for more information on this method

equalsIgnoreCase

String x = "hello";

if ( x.equalsIgnoreCase("Hello")
   System.out.prinln("String x is equal to Hello, hello, etc");

Note: the equalsIgnoreCase returns true if the value is the same as the argument passed, otherwise false

getChars

# getChars(int srcBegin, int srcEnd, char[] dest, int destBegin)

String h = "Hello World";
char[] ca = new char[5];

h.getChars(0, 5, ca, 0);
System.out.println(ca);

indexOf and lastIndexOf String s1 = "abcdefghijklmabcdefghijklm";

System.out.println("String is: " + s1 + "\n");

System.out.println("first c is found @ position: " + s1.indexOf('c') );
System.out.println("fgh is found @ position: " + s1.indexOf("fgh") );
System.out.println("fgh is found @ position (skip first 7 characters): " + s1.indexOf("fgh", 7) );

System.out.println("unknown $ is not found: " + s1.indexOf('$') );

System.out.println("last c is found @ position: " + s1.lastIndexOf('c') );
intern

String t1 = "abcdefghiklmnopqrstuvwxyz";
String t2;

t2 = t1.intern();   // see note below

if ( t1 == t2)
   System.out.println("t1 and t2 are the same object");

if ( t1.equals(t2) )
   System.out.println("t1 and t2 have the same contents");

Note: when using intern you are looking in the string constant pool a special area in the heap memory, you can force a string to be added to the pool by calling the intern method on the string

length

String x = "this is a very long string that hopefully will have a total of 76 characters";

System.out.println("String x has " + x.length() + " Character");

Note: return the length of a String (includes whitespace)

regionMatches # regionMatches(start offset, string, start offset of the subregion, number of characters to compare)
# regionMatches(ignore case, start offset, string, start offset of the subregion, number of characters to compare)

String t = "Hello";
String match = "hello";

if ( t.regionMatches(0, match, 0, 5) )
   System.out.println("A perfect match");
else
   System.out.println("Not a perfect match");

if ( t.regionMatches(true, 0, match, 0, 5) )
   System.out.println("A perfect match");
else
   System.out.println("Not a perfect match");
}
replace

String x = "XXaXX";

System.out.println("String before: " + x);
System.out.println("String replaced: " + x.replace('X','x'));

substring

String x = "1234567890";

System.out.println("Part of String x: " + x.substring(5));    // start from position 5 return rest of string
System.out.println("Part of String x: " + x.substring(5,8));  // start from position 5 return upto character 8

Note: used to return part of a string

toLowerCase toUpperCase

String lower = "all lower case";
String upper = "ALL UPPER CASE";

System.out.println("String lower is now upper case: " + lower.toUpperCase());
System.out.println("String upper is now lower case: " + upper.toLowerCase());

toString

String x = "Hello World!";

System.out.println("toString method returns: " + x.toString());

Note: all objects in Java have a toString method, normally returns a meaningful way to describe the object

trim

String x = "     x     ";

System.out.println("Blank spaces removed from x: " + x.trim());

Note: trim removes any leading or trailing blank spaces

valueOf int i = 20;
boolean b = true;
double d = 100.20;

System.out.println("integer string is: " + String.valueOf(i) );
System.out.println("boolean string is: " + String.valueOf(b) );
System.out.println("double string is: " + String.valueOf(d) );

Manipulate data using the StringBuffer class and its methods

If you modify a String object many times you end up will lots of abandoned String objects in the String pool, this is where the StringBuffer comes in as it is not immutable, which means they can be modified over and over without leaving any abandoned objects. A common use for StringBuffers is file I/O, especially if we are talking large amounts of data.

StringBuffer

StringBuffer sb1 = new StringBuffer();              // this will have an initial capacity of 16 characters
StringBuffer sb2 = new StringBuffer(10);            // this will have a capacity of 10 characters

StringBuffer sb3 = new StringBuffer("Hello World!");  // the capacity will be string size 12 plus 16 = 32

System.out.println("StringBuffer sb3 is: " + sb3);    // Just use it like a String

Again the StringBuffer class has many methods, here are some of the more common ones

append

StringBuffer sb = new StringBuffer("abcd");

System.out.println("Appends efgh to StringBuffer sb :" + sb.append("efgh");
System.out.println("StringBuffer sb is now: " + sb);

Note: There are 10 overloaded append methods which allow you to add various data type values

capacity

StringBuffer sb1 = new StringBuffer();
StringBuffer sb1 = new StringBuffer(10);
StringBuffer sb1 = new StringBuffer("Hello World");

System.out.prntln("StringBuffer sb1(character count): " + sb1);     // displays 0
System.out.prntln("StringBuffer sb2(character count): " + sb2);     // displays 0
System.out.prntln("StringBuffer sb3(character count): " + sb3);     // displays 11

System.out.prntln("StringBuffer sb1(capacity): " + sb1);     // displays 16
System.out.prntln("StringBuffer sb2(capacity): " + sb2);     // displays 10
System.out.prntln("StringBuffer sb3(capacity): " + sb3);     // displays 27

Note: increasing the size is a performance hit, so if you do change the StringBuffer make sure it has the capacity already so it does not need to grow

charAt, setCharAt, getChars These are the same the String methods above
ensureCapacity

StringBuffer sb1 = new StringBuffer();
sb1.ensureCapacity(75);                 // minimum capacity will be 75 characters

Note: if the character length is greater then the method ensures a capacity that is the greater of the number specified as an argument or twice the original capacity plus 2.

delete

## sb.delete(start position, end position)
StringBuffer sb = new StringBuffer("1234567890");

System.out.println("Inserted text into StringBuffer sb: " + sb.delete(4, 6));  / displays 12347890

insert

## sb.insert(start, data type) there are many diffrent data types i.e boolean, char, String, etc
StringBuffer sb = new StringBuffer("1234567890");

System.out.println("Inserted text into StringBuffer sb: " + sb.insert(4, "----"));

Note: again the offset is zero based

length

StringBuffer sb1 = new StringBuffer();
StringBuffer sb2 = new StringBuffer(10);
StringBuffer sb3 = new StringBuffer("Hello World");

System.out.println("StringBuffer sb1: " + sb1.length());     // displays 0 
System.out.println("StringBuffer sb2: " + sb2.length());     // displays 0
System.out.println("StringBuffer sb3: " + sb3.length());     // displays 11

Note: the length method only returns the character count not the size of the StringBuffer - see capacity

reverse

StringBuffer sb = new StringBuffer("1234567890");

System.out.println("Reversed StringBuffer sb:" + sb.reverse());

setLength

StringBuffer sb1 = new StringBuffer("Hello World");
sb1.setLength(5);
System.out.prntln("sb1: " + sb1);     // displays Hello, it truncated the original

Note: setLength will truncate any characters if the capacity is less than the String contained inside it

toString

StringBuffer sb = new StringBuffer("1234567890");

System.out.println("StringBuffer sb:" + sb.toString());