Numbers and Strings in Modern Java Programming

Last modified: April 23, 2018

In our guide to primitive data types, you saw how certain data types can be initialized with literals. While primitive data types provide a convenient syntax, there are sometimes reasons to use objects in place of primitives.

In this guide to numbers and strings, we'll look at both the Number and String class in Java. We'll show you how these wrapper objects are used to add functionality for common operations performed on string and numeric values in Java. We'll also look at supporting classes like Math and PrintStream and how they make working with numbers and strings even easier.

Why use a wrapper class?

The Number class and String class are both considered wrapper classes for primitive values. Sometimes its better to have an object representation of a primitive type. For example, if you have a method expecting an object as an argument, you could pass it an int value wrapped as a Number object.

These wrapper classes also add a lot of extra functionality for manipulating the values they represent. For example, the Number class has a toString() method for easily converting it's value to a string.

The compiler automatically wraps and unwraps values when appropriate. This is called autoboxing and will also be covered in this tutorial.

Let's look at both the Number class and String class in more detail:

The Number Class

Every numeric wrapper class is a subclass of the abstract Number class. These classes are found in the java.lang package and include:

  • Byte
  • Double
  • Float
  • Integer
  • Short
  • Long

The compiler will automatically wrap and unwrap primitive types when it makes sense. If you use a primitive where an object is expected, the compiler will automatically box the value in the appropriate wrapper class.

When should I use the Number class in Java?

Use the Number class whenever you want to represent a primitive numeric type as an object AND OR whenever you want the added functionality of these wrapper classes as demonstrated below:

public class MainClass {
    public static void main(String[] args) {
        Integer myInt = 25;

        System.out.println(myInt.floatValue());
        //prints 25.0

        System.out.println(myInt.longValue());
        //prints 25

        System.out.println(myInt.compareTo(25));
        //prints 0

        System.out.println(myInt.compareTo(26));
        //prints -1

        System.out.println(myInt.equals(25));
        //prints true

        System.out.println(myInt.equals(26));
        //prints false
    }
}

This example demonstrates some of the common functionality implemented by all subclasses of the Number class. Notice how we first create an instance of the Integer subclass via Integer myInt = 25;. We can still use literals when defining these wrapper objects.

Notice how these methods provide common functionality used by all numeric types. While methods like floatValue() converts and returns primitive data types, the compareTo() and equals() method compare values.

These subclasses also have their own static methods for easily converting values to strings, etc.:

public class MainClass {
    public static void main(String[] args) {
        Integer.valueOf("33");
        //returns an Integer object with value 33

        Integer.toString(33);
        //returns String object with value "33"

        Integer.parseInt("33");
        //returns an integer 33

        Integer.decode("33");
        //decodes string into integer
    }
}

This example demonstrates some of the static methods of the Integer subclass. While each Number subclass implements its own static methods they are similar for every subclass.

While methods like toString() return string object representations of their arguments, methods like parseInt() convert string values and return integers.

Formatting Numeric Output

The PrintStream class has two formatting methods format() and printf().

It turns out that System.out is a PrintStream object. You can use these methods to format output like this:

public class MainClass {
    public static void main(String[] args) {
        int myAge = 25;
        String myName = "Sam";

        System.out.format("My name is %s and I'm %d years old.", myName, myAge);
        //prints My name is Sam and I'm 25 years old.

        System.out.format("MY name is %s and I'm %d years old.", myAge, myAge);
        //prints My name is 25 and I'm 25 years old.
    }
}

Since System.out is a PrintStream object, we can call format() as demonstrated above.

Notice how we define an integer myAge and string myName.

The first argument of the format() function is My name is %s and I'm %d year old. This is a format string that specifies how the following arguments should be formatted.

%s formats a string value and %d formats a decimal integer value. For a complete list of these converters (and other flags), check out the java.util.Formatter package.

Note that printf() and format() are the same. Also remember that these methods implicitly convert types and work the same for primitive types and wrapper objects. This is why our second print statement works even though the int value myAge is used for both %s and %d.

Formatting Decimals

You can use the DecimalFormat class to format decimal output:

import java.text.*;
public class MainClass {
    public static void main(String[] args) {
        DecimalFormat currency = new DecimalFormat("$###.##");
        DecimalFormat leadzero = new DecimalFormat("0000000.00");

        String currencyOutput = currency.format(123.456);
        String leadingZeroOutput = leadzero.format(123.45);

        System.out.println(currencyOutput);
        //prints $123.46

        System.out.println(leadingZeroOutput);
        //prints 0000123.45
    }
}

Notice how we import java.text.*; to access the DecimalFormat class. We create two instances of DecimalFormat. While the currency formatter specifies a$###.## format, the leadzero format specifies "0000000.00".

Also notice how the formatter rounds up the currencyOutput. The formatter automatically rounds up if the pattern specifies fewer decimal places than the input.

Using Math

The Math class makes it even easier to perform more advanced arithmetic. The Math class implements many static methods for working with numbers:

public class MainClass {
    public static void main(String[] args) {
        System.out.println(Math.sqrt(144));
        //prints 12.0

        System.out.println(Math.abs(-40));
        //prints 40

        System.out.println(Math.min(22.33, 44.54));
        //prints 22.33

        System.out.println(Math.max(22.33, 44.54));
        //prints 44.54
    }
}

These are some of the operations you can perform using the Math class. Here are some more examples of using the Math class.

The String Class

Anything enclosed in double quotes is a string literal. The String class is immutable. Most of the operations you perform on a String instance return copies of that String object.

public class MainClass {
    public static void main(String[] args) {
        String helloWorld = "Hello world, this is a string";
        String goodBye = new String("goodbye!");

        System.out.println(helloWorld.length());
        //prints 29

        System.out.println(helloWorld.concat(", " + goodBye));
        //prints Hello world, this is a string, goodbye!
    }
}

Notice how we assign a string literal to helloWorld and a new instance of String to goodBye. You can initialize a new string either way.

Like the Number class, the String wrapper class provides some convenient functionality for checking string length() and combining strings with concat().

Manipulating Strings

The String class implements a bunch of commonly used methods for manipulating strings. For example, the split() method splits a string into an array of strings. It splits the string based on a regular expression string argument:

import java.util.Arrays;
public class MainClass {
    public static void main(String[] args) {
        String myString = "banana";
        String[] splitArray = myString.split("a");
        System.out.println(Arrays.toString(splitArray));
        //prints [b,n,n]
    }
}

Notice how the split() method accepts a regular expression string argument (in this case "a") and returns an array based on that match. Also notice how the target regular expression is omitted from this array.

The split() method also accepts an optional second parameter specifying the max length of the resulting array:

import java.util.Arrays;
public class MainClass {
    public static void main(String[] args) {
        String myString = "banana";
        String[] splitArray = myString.split("a", 2);
        System.out.println(Arrays.toString(splitArray));
        //prints [b, nana]
    }
}

In this example, we specify that the resulting splitArray can only have a length of 2. The string array [b, nana] is returned as a result.

Below are some other commonly used methods for manipulating strings:

public class MainClass {
    public static void main(String[] args) {
        String myString = "Hello world";

        System.out.println(myString.toUpperCase());
        //prints HELLO WORLD

        System.out.println(myString.toLowerCase());
        //prints hello world

        System.out.println(myString.trim());
        //prints hello world (all leading and trailing white space is removed)

        System.out.println(myString.indexOf("o"));
        //prints 4

        System.out.println(myString.lastIndexOf("o"));
        //prints 7

        System.out.println(myString.replace("l", "z"));
        //prints Hezzo worzd

        System.out.println(myString.equals("goodbye"));
        //prints false
    }
}

This example demonstrates the added functionality provided by the String class. While methods like indexOf() return characters at given positions, toUpperCase() transform strings.

It's important to remember that most of these methods return copies of the string. They don't transform the String instance itself. For a complete list of methods, be sure to check out the official documentation.

If you're looking for even more guidance with examples, check out Lyndsey Padget's The Do's and Don'ts of Java Strings.

Autoboxing

The compiler automatically converts between primitive types and their wrapper classes. This is called autoboxing. Autoboxing happens whenever the object wrapper version of primitive type is expected but the primitive type is provided instead. The same applies to unboxing when the unwrapped primitive type is expected but a wrapped object instance is provided.

Autoboxing allows us to do things like this:

Integer myInt = 4

Since the compiler performs autoboxing, we don't need to worry about assigning a literal int value to the Integer wrapper object type.

Conclusion

Both the Number and String class make it easier to work with the primitive types they represent. While autoboxing provides some flexibility in you having to explicitly convert between primitive types and their wrapper objects, it's important to understand the extra benefits of using these wrapper classes in Java.

You might also like: