Primitive Types and Variables in Java

Primitive Types and Variables

Welcome back to another Java tutorial! If you are following along, then you just finished playing around with numbers in DrJava’s interactions pane. In this tutorial, we will be giving some context to some of the examples from the previous tutorial. In particular, we’ll be covering Java primitive types and their role in variable creation. Let’s get started!

Table of Contents

Concepts

Before we really dig into the code, we need to talk about primitive types and their capabilities.

Java Primitive Types

In every programming language, there are data types that are built-in to the language. In languages like Java where all data types are explicit, each one has a unique keyword. These explicit keywords are used to tell the language what types we want to use.

By now we should have had some exposure to the following types:

  • int: a 32-bit signed integer
  • double: a 64-bit signed floating-point number
  • char: a 16-bit unsigned integer

In addition to these types, there are five others: byte, short, long, float, and boolean. We will likely spend most of our time in Java working with the three types we have discussed already as well as the boolean type.

For a comprehensive look at all of the primitive types, Oracle provides a complete list in their Java documentation. However, let’s run over these quickly!

The Int Primitive Type

As we’ve explored already, Java integers are 32-bit signed values, and they are denoted by the int keyword:

int someNumber = 10;

Of course, like all primitive types, integers have their limits. Since they’re 32-bit, we’re looking at a range of values from -2,147,483,648 to 2,147,483,647. That’s a lot of numbers! Of course, we can confirm that range using a handy trick in DrJava’s interactions pane:

Integer.MAX_VALUE // Prints 2,147,483,647
Integer.MIN_VALUE // Prints -2,147,483,648

Naturally, int is probably the most common integer type used for simple calculations. If you need more range, see long below.

The Double Primitive Type

Unlike int, Java doubles are 64-bit floating point values, and they are marked by the double keyword:

double someNumber = 110.55;

As a reminder, floating point numbers are just real numbers. In other words, we gain access to decimal points when we use doubles.

Since doubles are 64-bit, we have access to significantly more numbers than with integers. If we use the same interactions pane trick, we can find out just how massive the range of potential values is:

Double.MAX_VALUE // Prints 1.7976931348623157E308
Double.MIN_VALUE // Prints 4.9E-324

Keep in mind that negative exponents imply extremely small numbers—as opposed to extremely negative numbers—so we’re not exactly looking at the same kind of range as with integers.

In general, double is the default choice for floating point values in Java. The alternative is float.

The Char Primitive Type

As we’ve seen already, Java chars represent 16-bit characters, and they are marked by the char keyword:

char someCharacter = 'f';

All characters in Java are defined using single quotes. Meanwhile, double quotes are used to define strings—something we’ll chat about later.

As usual, we can find out the character range using the following code snippet:

Character.MAX_VALUE // Prints '￿'
Character.MIN_VALUE // Prints ''

To make sense of this range, we can always cast the results to an integer (more on that later):

(int) Character.MAX_VALUE // Prints 65535
(int) Character.MIN_VALUE // Prints 0

As it turns out, the char type is the only unsigned type in Java. In other words, a character can range from 0 to 65,535 where each number maps to a specific character. In order to create characters beyond this list, Java may combine pairs of characters. Check out Reverse a String in Java for some examples.

The Byte Primitive Type

When we were chatting about binary, we talked about the concept of a bit. Well, a group of eight bits makes a byte which Java supports as a primitive type. The byte type is essentially just an 8-bit number which ranges from -128 to 127. As expected, bytes are denoted by the byte keyword:

byte someByte = 20;

As always, we can confirm the range of a the byte type using the following code snippet:

Byte.MAX_VALUE // Prints 127
Byte.MIN_VALUE // Prints -128

In my experience, the byte type is useful for reading and processing raw data. In general, however, we will likely have no use for it since the range of potential values is so small.

The Short Primitive Type

The short type is another integer type, but it takes up less space than the int type. In fact, it takes up exactly half the space at 16 bits, and it is marked by the short keyword:

short someNumber = 11;

In terms of range, the short type clocks in at just half of the bits as an integer, but we can confirm that as usual:

Short.MAX_VALUE // Prints 32767
Short.MIN_VALUE // Prints -32768

For practical purposes, a short only has 65,546 possible values. Both byte and short are typically used when memory and disk space is low. Otherwise, it is safe to use int as it is the default type when declaring integers.

The Long Primitive Type

On the other end of the spectrum is the long primitive type. This type represents extremely large integers where we might want values even greater than the int type can provide. The long type is a 64-bit signed integer which means the values range into the quintillions.

Naturally, we denote longs with the long keyword:

long someBigNumber = 1013401346173L;

To demonstrate just how wide of a range a 64-bit value can have, let’s take a look at the following code snippet:

Long.MAX_VALUE // Prints 9,223,372,036,854,775,807
Long.MIN_VALUE // Prints -9,223,372,036,854,775,808

Perhaps a long would be useful for computating the distance light travels in a given time frame. After one second, light travels roughly 300 million meters. If we wrote a program to track the distance of light in real time, the int type would cap out after around 7 seconds while a long could calculate out to around 975 years. Don’t believe me? Check out this Gist which runs through the entire calculation.

The Float Primitive Type

While we often use the double which is a 64-bit floating point number type, Java supports another floating point number type called the float. Like int, however, Java defaults to double for floating point values. At any rate, we can denote a 32-bit floating point number using the float keyword:

float someNumber = 11.4f;

To get an idea of the range of a float, let’s use our trick again:

Float.MAX_VALUE // Prints 3.4028235E38
Float.MIN_VALUE // Prints 1.4E-45

As we can see, 32 bits really reduces our range and precision. If we want to use a decimal value with less precision than double while using half the space, the float type is an option.

The Boolean Primitive Type

Finally, we can cover the boolean type. To declare a boolean, we can use the boolean keyword:

boolean isBool = true;

Booleans are a bit unique because they don’t represent numeric values like all the other primitive types. In fact, our little MAX_VALUE and MIN_VALUE trick won’t work here. Instead, booleans represent true or false which we might recall from the previous lesson on logic.

We won’t spend much time on this now because it will become part of almost everything we do in Java. That said, we often won’t declare them explicitly like this. Instead, they are the result of comparisons which drive logic in code. Check out the boolean operators section below for more information.

Java Arithmetic Operators

Up to this point, we have introduced arithmetic operators at random as we build up our Java knowledge. However, we have not explained all the operators we have mentioned. In this section, we’ll take a look at a list of the most common Java operators.

The Addition Operator

As we can probably imagine, the plus sign, +, is the addition operator:

int sum = 11 + 7; // Stores 18

In this example, we add eleven to seven which stores eighteen.

The Subtraction Operator

Like addition, subtraction uses the same minus sign operator, -, as traditional math:

int difference = 5 - 4; // Stores 1

In this example, the difference between five and four is one.

The Multiplication Operator

As we move away from addition and subtraction, the arithmetic operators become slightly less obvious. In the case of multiplication, we use the asterisk, *:

int product = 2 * 3; // Stores 6

When we multiple two by three, we get a result of six.

The Division Operator

In addition to multiplication, Java supports the division operators or forward slash, /:

int quotient = 10 / 2; // Stores 5

If we divide ten by two, we get a result of five.

The Remainder Operator

If we recall, we tried five different operators for arithmetic. Four of these symbols should have been relatively easy to recognize, but one of them is likely new to us: %. This is the remainder operator and it returns the remainder of a division operation:

int remainder = 5 % 2; // Stores 1

In this example, the result is one because five divided by two is two with a remainder of one. This operator is often used to perform clever functions such as printing the last digit in an integer.

Java Relational Operators

In addition to arithmetic operators, Java supports relational or equality operators. We have already been exposed to one of these operators: ==. In addition, we can compare values using !=<, <=, >, and >=. Try some of the following examples:

6 > 7
110 >= 54
95 < 96
63 <= 100

As we have probably noticed, the result of each of these operations is a boolean value: true or false. In the future, we will see these operators being used to drive all sorts of logic.

Primitive Type Arithmetic

In the previous tutorial, we took a look at a few examples which we tried in the interactions pane of DrJava. If you have not yet tried them, now is your chance. Otherwise, we are going to discuss these examples more deeply now.

Previous Examples

Recall that in the list of examples each operation was shown twice. For example:

4 + 5
4 + 5.0

In this pair, the first computation adds two integers together and the second computation adds an integer and a floating point number together. When we complete these computations, we’ll find out that 4 + 5 yields 9 and 4 + 5.0 yields 9.0.

Unfortunately, these examples do not provide enough information to draw any serious conclusions about the results. To continue our exploration, we should attempt some subtraction and multiplication. Again, the results are similar.

It is not until we play with division that things get interesting. For instance:

1 / 2
1.0 / 2

The first example will probably give us an alarming result. Yes, you entered it correctly. In Java, 1 / 2 is 0. However, if we enter 1.0 / 2, we’ll get the result we are expecting (0.5). This should start to give us an idea of what is happening.

Truncation

When we add two integers together, we would expect the result to be an integer. This carries into all of the operations including division where something like 1 / 2 can result in a value of 0. In computer science, we call this truncation.

Truncation occurs because 32-bit integers can only hold discrete values. Instead of rounding the output, integers just drop any bits that do not fit in the 32-bit window. This is true for all of the data types, but it is often easier to notice with integers.

While truncation can be confusing and counter-intuitive, it comes in handy in some instances such as mapping—we will definitely be exposed to this later.

At any rate, as long as our types are consistent, arithmetic is pretty simple. However, if we are forced to mix compatible types such as integer and double, Java converts the entire result to the widest type. In other words, the type that has the most bits will be the result of the computation.

Type Casting

Perhaps the last topic to touch on for primitive types is this notion of type casting. We already talked about type widening where a computation gets stored in the widest type. Type casting is just the opposite.

Let’s say we had a computation that would result in a double, but we did not care about the decimal result. We can cut the precision using a typecast to integer. This is used all over the place in code, but a good example would be an implementation of rounding. Without any knowledge of control flow, we can implement rounding:

int round = (int) (7.6 + 0.5);

In this example, the number we are trying to round to the nearest whole number is 7.6. If the decimal is less than .5, we want the result to round down. Likewise, if the decimal is .5 or greater, we want the result to round-up.

By adding .5, we force 7.6 to become 8.1. The typecast then truncates the decimal point which results in our properly rounded integer. If the number was 7.4, the computation would force 7.4 to 7.9. Then the typecast would truncate the decimal.

With that in mind, we have covered just about everything we might need to know about the Java primitive types.

Practice

Now that we’ve done some reading, let’s dig into the code.

Usage of Primitive Types

In Java, every variable has to have a type. If we want to work with variables, we should get used to the syntax:

int length = 7;

Here, a variable named length is assigned the value 7. In front of the variable name, we’ll notice the word int. This is one of Java’s primitive types and it denotes a 32-bit signed integer. If we recall our discussion about bits, we’ll then be able to quickly compute the range of values for Java integers (232 or 4,294,967,296 possible values).

Limits of Primitive Types

As mentioned several times already, we can determine the maximum value of an integer using the following code snippet:

Integer.MAX_VALUE

The return value might seem confusing at first, but we will quickly realize that the value is half of the possible range. That must mean the other half of the range is composed of negative values. Try using the following as confirmation:

Integer.MIN_VALUE

For fun, let’s see what happens when we push beyond these limits:

Integer.MAX_VALUE + 1 // Prints -2147483648
Integer.MIN_VALUE - 1 // Prints 2147483647

Isn’t that odd? We’ve just observed integer wraparound for the first time. In other words, once we’ve hit the limit of a primitive type, we’ll wraparound to the other side. Keep that in mind as we move forward.

In case it wasn’t already clear, a data type which has its range split between negative and positive values is called a signed type. Likewise, a data type which has an entirely positive range is called an unsigned typed. In either case, the language interprets the bits representing a value.

If the type is signed, the most significant bit is treated as a sign bit. A sign bit of 0 usually denotes positive values while a sign bit of 1 usually denotes negative values. This is important because almost all of Java’s primitive types are strictly signed. A language like C/C++ has support for both signed and unsigned types.

Variable Arithmetic

In the interactions pane, try the following:

char letter = 'b';

Earlier, we wrote a similar line where we assigned a variable a value of 7. In that case we were working with integers. In this case, we are working with the char primitive type which can store character values. With this line, we have now stored our own value in a variable called letter. Go ahead and experiment with the various data types. For instance, we might try any of the following:

boolean hasMoney = true; 
int hour = 7; 
double height = 13.7; 
float gravity = 9.81f; 
long sixBillion = 6000000000L;

Now that we have some variables declared, try adding some of these values together and note the results. For instance:

hour + height;

The variable names do not make a lot of sense, but this is perfectly legal and will result in 20.7. However, if we try something like:

hasMoney + hour;

We will end up with an error. That is because we’re trying to add a boolean to an integer. Meanwhile, the following is completely legal in Java:

char gravity = 'g'; 
char speedOfLight = 'c'; 
gravity + speedOfLight;

We can indeed add these characters together which yields 202 or ‘Ê’. Because the char type is actually a numeric value, we can sum them like integers.

Adding characters is particularly handy if we want to compare characters for ordering. For instance, two letters can be compared alphabetically by comparing their numeric values. A complete list of all the available ASCII characters and there values can be found here.

As an added note, Java characters are 16-bit which gives them much greater variety than the 256 ASCII characters. In addition, the char primitive type is the only Java primitive type that is unsigned.

But What About Strings?

Since we’re on the topic of characters, let’s talk strings. Java has native support for strings which are sequences of characters. However, strings are not a Java primitive type. They are instead a reference type.

A reference type is a bit different from a primitive type. With primitive types, we are free to copy and compare data as needed. This makes development extremely intuitive because of this concept called value semantics. Value semantics imply that variables are immutable, so we don’t have to worry about a copy corrupting the original value.

Value Semantics

To test this concept, try the following in DrJava’s interactions pane:

int x = 5; 
int y = 5; 
y == x;

Notice that this comparison returns true as expected. Intuitively, 5 equals 5. Now try the following:

String firstName = "Leroy"; 
String lastName = "Leroy"; 
firstName == lastName;

In this example, we define two components of someone’s name: Leroy Leroy. Intuitively, we would think comparing both names would return true. After all, both names have the same letters and both names are case-sensitive. However, we get a shocking result of false.

As it turns out, the == operator does not compare the strings as expected. The reason for the bad comparison will be explained in more detail in the following tutorial, so for now try comparing more strings. For instance, we could try creating two strings and assigning them to each other:

String firstName = "Leroy"; 
String lastName = "Jenkins"; 
firstName = lastName; 
firstName == lastName;

In this case, the comparison using == results in true. Of course, the value comparison is the same, so why would it return true this time? In the next tutorial, we will take a deeper look at strings and what is really happening when we make a comparison using ==.

Be careful. If you are using an IDE or a web client compiler, you might actually find that the first string example returns true. Doing this taps into some of the optimization techniques employed by the Java Virtual Machine which pools strings for use in an application. In the next tutorial, we will dive into strings a bit deeper and discuss why this is happening, and what it means moving forward.

Series Navigation← Introduction to Coding in JavaReference Types and Objects in Java →
Advertisements

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.