Java data types II

xingyun86 2021-5-30 849

In this part of the Java tutorial, we continue covering data types of Java. We cover wrapper classes, boxing and unboxing, default values, conversions, and promotions.

Java wrapper classes

Wrapper classes are object representations of primitive data types. Wrapper classes are used to represent primitive values when an Object is required. For example, Java collections only work with objects. They cannot take primitive types. Wrapper classes also include some useful methods. For example, they include methods for doing data type conversions. Placing primitive types into wrapper classes is called boxing. The reverse process is called unboxing.

As a general rule, we use wrapper classes when we have some reason for it. Otherwise, we use primitive types. Wrapper classes are immutable. Once they are created, they cannot be changed. Primitive types are faster than boxed types. In scientific computing and other large scale number processing, wrapper classes may cause significant performance hit.

Primitive typeWrapper classConstructor arguments
byteBytebyte or String
shortShortshort or String
intIntegerint or String
longLonglong or String
floatFloatfloat, double or String
doubleDoubledouble or String
charCharacterchar
booleanBooleanboolean or String
Table: Primitive types and their wrapper class equivalents

The Integer class wraps a value of the primitive type int in an object. It contains constants and methods useful when dealing with an int.

com/zetcode/IntegerWrapper.java
package com.zetcode;

public class IntegerWrapper {

    public static void main(String[] args) {

        int a = 55;
        Integer b = new Integer(a);

        int c = b.intValue();
        float d = b.floatValue();

        String bin = Integer.toBinaryString(a);
        String hex = Integer.toHexString(a);
        String oct = Integer.toOctalString(a);

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(d);

        System.out.println(bin);
        System.out.println(hex);
        System.out.println(oct);
    }
}

This example works with the Integer wrapper class.

int a = 55;

This line creates an integer primitive data type.

Integer b = new Integer(a);

An Integer wrapper class is created from the primitive int type.

int c = b.intValue();
float d = b.floatValue();

The intValue() method converts the Integer to int. Likewise, the floatValue() returns a float data type.

String bin = Integer.toBinaryString(a);
String hex = Integer.toHexString(a);
String oct = Integer.toOctalString(a);

These three methods return a binary, hexadecimal, and octal representation of the integer.

$ java IntegerWrapper.java
55
55
55
55.0
110111
37
67

This is the program output.

Collections are powerful tools for working with groups of objects. Primitive data types cannot be placed into Java collections. After we box the primitive values, we can put them into collections.

com/zetcode/Numbers.java
package com.zetcode;

import java.util.ArrayList;
import java.util.List;

public class Numbers {

    public static void main(String[] args) {

        List<Number> ls = new ArrayList<>();

        ls.add(1342341);
        ls.add(new Float(34.56));
        ls.add(235.242);
        ls.add(new Byte("102"));
        ls.add(new Short("1245"));

        for (Number n : ls) {

            System.out.println(n.getClass());
            System.out.println(n);
        }
    }
}

In the example, we put various numbers into an ArrayList. An ArrayList is a dynamic, resizable array.

List<Number> ls = new ArrayList<>();

An ArrayList instance is created. In angle brackets we specify the type that the container will hold. The Number is an abstract base class for all five numeric primitive types in Java.

ls.add(1342341);
ls.add(new Float(34.56));
ls.add(235.242);
ls.add(new Byte("102"));
ls.add(new Short("1245"));

We add five numbers to the collection. Notice that the integer and the double value are not boxed; this is because for integer and double types the compiler performs autoboxing.

for (Number n : ls) {

    System.out.println(n.getClass());
    System.out.println(n);
}

We iterate through the container and print the class name and its value of each of the elements.

$ java Numbers.java
class java.lang.Integer
1342341
class java.lang.Float
34.56
class java.lang.Double
235.242
class java.lang.Byte
102
class java.lang.Short
1245

The com.zetcode.Numbers program gives this output. Note that the two numbers were automatically boxed by the compiler.

Java boxing

Converting from primitive types to object types is called boxingUnboxing is the opposite operation. It is converting of object types back into primitive types.

com/zetcode/BoxingUnboxing.java
package com.zetcode;

public class BoxingUnboxing {

    public static void main(String[] args) {

        long a = 124235L;

        Long b = new Long(a);
        long c = b.longValue();

        System.out.println(c);
    }
}

In the code example, we box a long value into a Long object and vice versa.

Long b = new Long(a);

This line performs boxing.

long c = b.longValue();

In this line we do unboxing.

Java autoboxing

Java 5 introduced autoboxing. Autoboxing is automatic conversion between primitive types and their corresponding object wrapper classes. Autoboxing makes programming easier. The programmer does not need to do the conversions manually.

Automatic boxing and unboxing is performed when one value is primitive type and other is wrapper class in:

  • assignments
  • passing parameters to methods
  • returning values from methods
  • comparison operations
  • arithmetic operations
Integer i = new Integer(50);

if (i < 100) {
   ...
}

Inside the square brackets of the if expression, an Integer is compared with an int. The Integer object is transformed into the primitive int type and compared with the 100 value. Automatic unboxing is done.

com/zetcode/Autoboxing.java
package com.zetcode;

public class Autoboxing {

    private static int cube(int x) {

        return x * x * x;
    }

    public static void main(String[] args) {

        Integer i = 10;
        int j = i;

        System.out.println(i);
        System.out.println(j);

        Integer a = cube(i);
        System.out.println(a);
    }
}

Automatic boxing and automatic unboxing is demonstrated in this code example.

Integer i = 10;

The Java compiler performs automatic boxing in this code line. An int value is boxed into the Integer type.

int j = i;

Here an automatic unboxing takes place.

Integer a = cube(i);

When we pass an Integer to the cube() method, automatic unboxing is done. When we return the computed value, automatic boxing is performed, because an int is transformed back to the Integer.

Java language does not support operator overloading. When we apply arithmetic operations on wrapper classes, automatic boxing is done by the compiler.

com/zetcode/Autoboxing2.java
package com.zetcode;

public class Autoboxing2 {

    public static void main(String[] args) {

        Integer a = new Integer(5);
        Integer b = new Integer(7);

        Integer add = a + b;
        Integer mul = a * b;

        System.out.println(add);
        System.out.println(mul);
    }
}

We have two Integer values. We perform addition and multiplication operations on these two values.

Integer add = a + b;
Integer mul = a * b;

Unlike languages like Ruby, C#, Python, D or C++, Java does not have operator overloading implemented. In these two lines, the compiler calls the intValue() methods and converts the wrapper classes to ints and later wraps the outcome back to an Integer by calling the valueOf() method.

Java autoboxing and object interning

Object intering is storing only one copy of each distinct object. The object must be immutable. The distinct objects are stored in an intern pool. In Java, when primitive values are boxed into a wrapper object, certain values (any boolean, any byte, any char from 0 to 127, and any short or int between -128 and 127) are interned, and any two boxing conversions of one of these values are guaranteed to result in the same object.

According to the Java language specification, these are minimal ranges. So the behaviour is implementation dependent. Object intering saves time and space. Objects obtained from literals, autoboxing and Integer.valueOf() are interned objects while those constructed with new operator are always distinct objects.

The object intering has some important consequences when comparing wrapper classes. The == operator compares reference identity of objects while the equals() method compares values.

com/zetcode/Autoboxing3.java
package com.zetcode;

public class Autoboxing3 {

    public static void main(String[] args) {

        Integer a = 5; // new Integer(5);
        Integer b = 5; // new Integer(5);

        System.out.println(a == b);
        System.out.println(a.equals(b));
        System.out.println(a.compareTo(b));

        Integer c = 155;
        Integer d = 155;

        System.out.println(c == d);
        System.out.println(c.equals(d));
        System.out.println(c.compareTo(d));
    }
}

The example compares some Integer objects.

Integer a = 5; // new Integer(5);
Integer b = 5; // new Integer(5);

Two integers are boxed into Integer wrapper classes.

System.out.println(a == b);
System.out.println(a.equals(b));
System.out.println(a.compareTo(b));

Three different ways are used to compare the values. The == operator compares the reference identity of two boxed types. Because of the object interning, the operation results in true. If we used the new operator, two distinct objects would be created and the == operator would return false. The equals() method compares the two Integer objects numerically. It returns a boolean true or false (a true in our case.)

Finally, the compareTo() method also compares the two objects numerically. It returns the value 0 if this Integer is equal to the argument Integer; a value less than 0 if this Integer is numerically less than the argument Integer; and a value greater than 0 if this Integer is numerically greater than the argument Integer.

Integer c = 155;
Integer d = 155;

We have another two boxed types. However, these values are greater than the maximum value interned (127); therefore, two distinct objects are created. This time the == operator yields false.

$ java Autoboxing3.java
true
true
0
false
true
0

This is the output of the program.

Java null type

Java has a special null type. The type has no name. As a consequence, it is impossible to declare a variable of the null type or to cast to the null type. The null represents a null reference, one that does not refer to any object. The null is the default value of reference-type variables. Primitive types cannot be assigned a null literal.

In different contexts, the null means an absence of an object, an unknown value, or an uninitialized state.

com/zetcode/NullType.java
package com.zetcode;

import java.util.Random;

public class NullType {

    private static String getName() {

        Random r = new Random();
        boolean n = r.nextBoolean();

        if (n == true) {

            return "John";
        } else {

            return null;
        }
    }

    public static void main(String[] args) {

        String name = getName();

        System.out.println(name);

        System.out.println(null == null);

        if ("John".equals(name)) {

            System.out.println("His name is John");
        }
    }
}

We work with the null value in the program.

private static String getName() {

    Random r = new Random();
    boolean n = r.nextBoolean();

    if (n == true) {

        return "John";
    } else {

        return null;
    }
}

In the getName() method we simulate a situation that a method can sometimes return a null value.

System.out.println(null == null);

We compare a two null values. The expression returns true.

if ("John".equals(name)) {

    System.out.println("His name is John");
}

We compare the name variable to the "John" string. Notice that we call the equals() method on the "John" string. This is because if the name variable equals to null, calling the method would lead to NullPointerException.

$ java NullType.java
null
true
$ java NullType.java
null
true
$ java NullType.java
John
true
His name is John

We execute the program three times.

Java default values

Uninitialized fields are given default values by the compiler. Final fields and local variables must be initialized by developers.

The following table shows the default values for different types.

Data typeDefault value
byte0
char'\u0000'
short0
int0
long0L
float0f
double0d
Objectnull
booleanfalse
Table: Default values for uninitialized instance variables

The next example will print the default values of the uninitialized instance variables. An  is a variable defined in a class for which each instantiated object of the class has a separate copy.

com/zetcode/DefaultValues.java
package com.zetcode;

public class DefaultValues {

    static byte b;
    static char c;
    static short s;
    static int i;
    static float f;
    static double d;
    static String str;
    static Object o;

    public static void main(String[] args) {

        System.out.println(b);
        System.out.println(c);
        System.out.println(s);
        System.out.println(i);
        System.out.println(f);
        System.out.println(d);
        System.out.println(str);
        System.out.println(o);
    }
}

In the example, we declare eight member fields. They are not initialized. The compiler will set a default value for each of the fields.

static byte b;
static char c;
static short s;
static int i;
...

These are instance variables; they are declared outside any method. The fields are declared static because they are accessed from a static main() method. (Later in the tutorial we will talk more about static and instance variables.)

$ java DefaultValues.java
0

0
0
0.0
0.0
null
null

This is the output of the program.

Java type conversions

We often work with multiple data types at once. Converting one data type to another one is a common job in programming. The term type conversion refers to changing of an entity of one data type into another. In this section, we will deal with conversions of primitive data types. Reference type conversions will be mentioned later in this chapter. The rules for conversions are complex; they are specified in chapter 5 of the Java language specification.

There are two types of conversions: implicit and explicit. Implicit type conversion, also known as coercion, is an automatic type conversion by the compiler. In explicit conversion the programmer directly specifies the converting type inside a pair of round brackets. Explicit conversion is called type casting.

Conversions happen in different contexts: assignments, expressions, or method invocations.

int x = 456;
long y = 34523L;
float z = 3.455f;
double w = 6354.3425d;

In these four assignments, no conversion takes place. Each of the variables is assigned a literal of the expected type.

int x = 345;
long y = x;

float m = 22.3354f;
double n = m;

In this code two conversions are performed by Java compiler implicitly. Assigning a variable of a smaller type to a variable of a larger type is legal. The conversion is considered safe, as no precision is lost. This kind of conversion is called implicit widening conversion.

long x = 345;
int y = (int) x;

double m = 22.3354d;
float n = (float) m;

Assigning variables of larger type to smaller type is not legal in Java. Even if the values themselves fit into the range of the smaller type. In this case it is possible to loose precision. To allow such assignments, we have to use the type casting operation. This way the programmer says that he is doing it on purpose and that he is aware of the fact that there might be some precision lost. This kind of conversion is called explicit narrowing conversion.

byte a = 123;
short b = 23532;

In this case, we deal with a specific type of assignment conversion. 123 and 23532 are integer literals, the a, b variables are of byte and short type. It is possible to use the casting operation, but it is not required. The literals can be represented in their variables on the left side of the assignment. We deal with implicit narrowing conversion.

private static byte calc(byte x) {
...
}
byte b = calc((byte) 5);

The above rule only applies to assignments. When we pass an integer literal to a method that expects a byte, we have to perform the casting operation.

Java numeric promotions

Numeric promotion is a specific type of an implicit type conversion. It takes place in arithmetic expressions. Numeric promotions are used to convert the operands of a numeric operator to a common type so that an operation can be performed.

int x = 3;
double y = 2.5;
double z = x + y;

In the third line we have an addition expression. The x operand is int, the y operand is double. The compiler converts the integer to double value and adds the two numbers. The result is a double. It is a case of implicit widening primitive conversion.

byte a = 120;
a = a + 1; // compilation error

This code leads to a compile time error. In the right side of the second line, we have a byte variable a and an integer literal 1. The variable is converted to integer and the values are added. The result is an integer. Later, the compiler tries to assign the value to the a variable. Assigning larger types to smaller types is not possible without an explicit cast operator. Therefore we receive a compile time error.

byte a = 120;
a = (byte) (a + 1);

This code does compile. Note the usage of round brackets for the a + 1 expression. The (byte) casting operator has a higher precedence than the addition operator. If we want to apply the casting on the whole expression, we have to use round brackets.

byte a = 120;
a += 5;

Compound operators perform implicit conversions automatically.

short r = 21;
short s = (short) -r;

Applying the + or - unary operator on a variable a unary numberic promotion is performed. The short type is promoted to int type. Therefore we must use the casting operator for the assignment to pass.

byte u = 100;
byte v = u++;

In case of the unary increment ++, decrement -- operators, no conversion is done. The casting is not necessary.

Java boxing, unboxing conversions

Boxing conversion converts expressions of primitive type to corresponding expressions of wrapper type. Unboxing conversion converts expressions of wrapper type to corresponding expressions of primitive type. Conversions from boolean to Boolean or from byte to Byte are examples of boxing conversions. The reverse conversions, e.g. from Boolean to boolean or from Byte to byte are examples of unboxing conversions.

Byte b = 124;
byte c = b;

In the first code line, automatic boxing conversion is performed by the Java compiler. In the second line, an unboxing conversion is done.

private static String checkAge(Short age) {
...
}
String r = checkAge((short) 5);

Here we have boxing conversion in the context of a method invocation. We pass a short type to the method which expects a Short wrapper type. The value is boxed.

Boolean gameOver = new Boolean("true");
if (gameOver) {
    System.out.println("The game is over");
}

This is an example of an unboxing conversion. Inside the if expression, the booleanValue() method is called. The method returns the value of a Boolean object as a boolean primitive.

Object reference conversion

Objects, interfaces, and arrays are reference data types. Any reference can be cast to the Object. Object type determines which method is used at runtime. Reference type determines which overloaded method will be used at compile time.

An interface type may only be converted to an interface type or to Object. If the new type is an interface, it must be a super interface of the old type. A class type may be converted to a class type or to an interface type. If converting to a class type, the new type must be a super class of the old type. If converting to an interface type, the old class must implement the interface. An array may be converted to the class Object, to the interface Cloneable or Serializable, or to an array.

There are two types of reference variable casting: downcasting and upcasting. Upcasting (generalization or widening) is casting from a child type to a parent type. We are casting an individual type to a common type. Downcasting (specialization or narrowing) is casting from a parent type to a child type. We are casting a common type to an individual type.

Upcasting narrows the list of methods and properties available to an object, and downcasting can extend it. Upcasting is safe, but downcasting involves a type check and can throw a ClassCastException.

com/zetcode/ReferenceTypeConverion.java
package com.zetcode;

import java.util.Random;

class Animal {}
class Mammal extends Animal {}
class Dog extends Animal {}
class Cat extends Animal {}


public class ReferenceTypeConversion {

    public static void main(String[] args) {

        // upcasting
        Animal animal = new Dog();
        System.out.println(animal);

        // ClassCastException
        // Mammal mammal = (Mammal) new Animal();

        var returned = getRandomAnimal();

        if (returned instanceof Cat) {

            Cat cat = (Cat) returned;
            System.out.println(cat);
        } else if (returned instanceof Dog) {

            Dog dog = (Dog) returned;
            System.out.println(dog);
        } else if (returned instanceof Mammal) {

            Mammal mammal = (Mammal) returned;
            System.out.println(mammal);
        } else {

            Animal animal2 = returned;
            System.out.println(animal2);
        }
    }

    private static Animal getRandomAnimal() {

        int val = new Random().nextInt(4) + 1;

        Animal anim = switch (val) {

            case 2 -> new Mammal();
            case 3 -> new Dog();
            case 4 -> new Cat();
            default -> new Animal();
        };

        return anim;
    }
}

The example performs reference type conversions.

// upcasting
Animal animal = new Dog();
System.out.println(animal);

We cast from a child type Dog to a parent type Animal. This is upcasting and it is always safe.

// ClassCastException
// Mammal mammal = (Mammal) new Animal();

Downcasting from an Animal to a Mammal leads to a ClassCastException.

var returned = getRandomAnimal();

if (returned instanceof Cat) {

    Cat cat = (Cat) returned;
    System.out.println(cat);
} else if (returned instanceof Dog) {

    Dog dog = (Dog) returned;
    System.out.println(dog);
} else if (returned instanceof Mammal) {

    Mammal mammal = (Mammal) returned;
    System.out.println(mammal);
} else {

    Animal animal2 = returned;
    System.out.println(animal2);
}

In order to perform legal downcasting, we need to check the type of the object with the instanceof operator first.

private static Animal getRandomAnimal() {

    int val = new Random().nextInt(4) + 1;

    Animal anim = switch (val) {

        case 2 -> new Mammal();
        case 3 -> new Dog();
        case 4 -> new Cat();
        default -> new Animal();
    };

    return anim;
}

The getRandomAnimal() returns a random animal using Java's swith expression.

Java string conversions

Performing string conversions between numbers and strings is very common in programming. The casting operation is not allowed because the strings and primitive types are fundamentally different types. There are several methods for doing string conversions. There is also an automatic string conversion for the + operator.

More about string conversions will be covered in the Strings chapter of this tutorial.

String s = (String) 15; // compilation error
int i = (int) "25"; // compilation error

It is not possible to cast between numbers and strings. Instead, we have various methods for doing conversion between numbers and strings.

short age = Short.parseShort("35");
int salary = Integer.parseInt("2400");
float height = Float.parseFloat("172.34");
double weight = Double.parseDouble("55.6");

The parse methods of the wrapper classes convert strings to primitive types.

Short age = Short.valueOf("35");
Integer salary = Integer.valueOf("2400");
Float height = Float.valueOf("172.34");
Double weight = Double.valueOf("55.6");

The valueOf() method returns the wrapper classes from primitive types.

int age = 17;
double weight = 55.3;
String v1 = String.valueOf(age);
String v2 = String.valueOf(weight);

The String class has a valueOf() method for converting various types to strings.

Automatic string conversions take place when using the + operator and one operator is a string, the other operator is not a string. The non-string operand to the + is converted to a string.

com/zetcode/AutomaticStringConversion.java
package com.zetcode;

public class AutomaticStringConversion {

    public static void main(String[] args) {

        String name = "Jane";
        short age = 17;

        System.out.println(name + " is " +  age + " years old.\n");
    }
}

In the example, we have a String data type and a short data type. The two types are concatenated using the + operator into a sentence.

System.out.println(name + " is " +  age + " years old.");

In the expression, the age variable is converted to a String type.

$ java AutomaticStringConversion.java
Jane is 17 years old.

This is the example output.

In this part of the Java tutorial, we have covered wrapper classes, boxing and unboxing, default values, conversions, and promotions.


×
打赏作者
最新回复 (0)
只看楼主
全部楼主
返回