COMP 14, Fall 1998

Prasun Dewan[1]

5. Primitive Types

Reference: Chapter 2, Appendix A2.3.

In ordinary life we tend to “type” things and people so to get a better understanding of the world. For instance we, might distinguish the student type from the professor type. The distinction is based on the fact that, unlike a student, a professor is allowed to teach. Thus, there are rules regarding what a member of a type can do. The same rules apply to all members of a type.

The notion of programming language types is very similar. Consider the following values:

2 3 “hello world” “goodbye world”.

The values 2 and 3 are of the same type, int, and the values “hello world” and “goodbye world” are of the same type, String. The reason two values are of the same type is because the exact same set of operations can be applied to them. If two values are of different types, there exists at least one operation that can be applied to one and not the other. Thus, we know that 3and “hello world” are of different types because you can subtract an integer from the former but not the latter:

3 – 2 //legal operation

“hello world” – 1 //illegal operation

Thus a programming language type is a description of a set of Java values, called instances of the type, consisting of :

  • a name,
  • a set of predefined instances or constants ,
  • a set of operations that can be applied to the instances.

This definition of a type looks like our definition of a class. The concepts of a class and type are very similar. In fact, in some object-based languages such as Smalltalk, there is no difference between them. In Java, however, a type is a more general concept. The difference between the two is somewhat subtle[2], and therefore, for now, we will consider them to be identical concepts.

Let us look at some of the basic or primitivetypes provided by Java. To distinguish them from the other types, Java begins their names with a lowercase letter. We will first look first at the names and constants of these types, and then look at the operations that can be applied to their instances.

Names and Constants

int

This type corresponds to the set of integers you have studies in Mathematics; a Java value of type int is an integer. However, not every integer is a Java int. This is because integers are whole numbers in the range:

- .. + 

However, a Java int must fit in memory for the computer to process it, and computer memory is finite (though large). The range of integers mapped to Java ints, is, thus, determined by the size of the memory slot allocated to each int. In Java, this slot consists of 32 binary digits or bits. This means that a range of 232 integers can be mapped to Java ints. Half of the numbers in this range (231 ) are negative integers, one is zero, and remaining (231 - 1) are positive integers. Thus, Java ints are integers in the range:

-231 .. (231 – 1)

You do not have to memorize the values of the smallest and largest integers, since these are stored in the Java named constants, Integer.MIN_VALUE and Integer.MAX_VALUE, respectively.

An int is named by its decimal representation:

2 1 234 -3 +10 -2147483648 2147483647

double

The type double describes a subset of the set of real numbers you have studied in Mathematics. Recall, that a real is a number that can have a fractional part The name of this type indicates that the memory slot allocated for its instances is twice the size of an int memory slot, that is, it is 64 bits long. The Java predefined constants, Double.MIN-VALUE and Double.MAX_VALUE define the absolute value of the smallest (non-zero) double value and largest double value, respectively.

Like int constants, double constants can be specified in Java by their decimal representation. However, we must ensure that their representations do not conflict with the representations of equivalent integers. Thus we cannot use, for instance, the representation:

2

for the double value,2, since it denotes the int value, 2, and Java must be able to uniquely type each value so that it knows what operations can be applied to it.

Therefore, in the decimal representation of doubles, a dot is required, even if there is no fractional part. The following are valid double constants:

2.2 2.0 0.22 02.20 -0.22 +2.2 .2 2.

We can also use a scientific representation of these numbers:

0.22E+1 2.2E0 22E-1 - 0.22E+1

where:

xEy = x * 10y

As shown above, the same real number can be represented in several ways. The standard or floating point representation is a scientific representation in which the number to the left of the exponent is a fraction beginning with a non-zero digit. Thus, the following is the standard representation of 2.2:

.22E1

and the following is the standard representation of .022:

.22E-1

The digits to the left of the exponent are called the mantissa and to the right are called the exponent.[3]

boolean

Often your program must test if some condition has been met. The boolean type defines two literals, true and false, for indicating the result of such a test..

char

The computer must often process characters. Most programs accept character input and produce character output. Moreover, some programs, such as a spelling checker, perform most of their computations in terms of characters. Characaters are the building blocks for the strings we have seen before.

This type defines a variety of characters including the English letters (both lowercase, a..z, and uppercase, A..Z), the decimal digits 0..9; “whitespace” characters such as blank and tab; separators such as comma , semicolon, and newline; and other characters you see on your keyboard. We denote a character literal by including its print representation in quotes:

‘A’ ‘Z’ ‘1’ ‘0’ ‘ ‘

Two consecutive single-quotes denote a special character called the null character:[4]

‘’

How do we represent the single-quote character itself? We could enclose it in single-quotes:

‘’’

However, Java would match the first two single-quotes as the null character, and think you have an extra single-quote character. So we will use the following representation to denote a single-quote:

‘\’’

Thus, instead of enclosing one character within quotes, we have enclosed a two-character escape sequence. The first character, \, or backslash, is an escape character here, telling Java to escape from its normal rules and process the next character in a special way.

Java defines escape sequences to represent characters that cannot use the normal representation or for which the normal representation may not be readable. A literal cannot have a new line character in it, so \n denotes the new line character. A backslash after the first quote denotes special processing, not the backlash character itself, so \\ denotes the backlash character. Typing a backspace after a single-quote would erase the single-quote, so \b denotes the backspace character. You can represent the tab character it by entering a tab between quotes:

‘‘

but this representation can be mistaken as the space character. So \t denotes a tab character. Similarly, you can represent the double quote character as

‘”’

but it may be mistaken for two null characters. So \” denotes the double quote character. The following table summarizes our discussion:

Escape Sequence / Character Denoted
\’ / ‘
\n / new line
\b / back space
\\ / \
\t / tab
\” / “

Figure 1 Some Useful Java Escape Sequences

Java allocates 16 bits for storing a character. As a result, it can support as many as 216 different characters, which is useful since we would like to represent characters of all current languages; and some of them such as Chinese have a large character set. It stores a non-negative integer code for each character. As programmers, we do not have to concern ourselves with the exact integer assigned to each character. However, as discussed later, we need to know something about the relative order in certain subsets of the character set.

Other Primitive Types

32 bits for an int and 64 bits for a double may be too little or too much for the integers and reals you wish to process in your program. Therefore, Java provides additional types with fewer and more bits. The following table summarizes the names of the Java primitive types, the kinds of values they store, and the size of the memory slot allocated to their instances:

Primitive Type / Values Stored / Size in bits
int / integers / 32
short / integers / 16
char / Non-negative, or unsigned, integer codes / 16
byte / integers / 8
double / reals / 64
float / reals / 32

Figure 2 Java Primitive Types

In this course, you can mostly ignore the additional types presented here: short, long, and float.

Operations on Primitive Types

For each type we have mentioned so far, we have only described the predefined constants. To fully explain a type, we must also look at the operations that can be performed on its instances. Most of the operations we will see here are binary operations, that is, operations that take two operands. A few are unary operations, taking a single operand.

int Arithmetic Operations

These are the operations we use in Mathematics for adding, subtracting, multiplying, and dividing integers. The following table describes the symbol used for naming each operation, the action it takes, and the types of the operand and result.

Name / Action / Operands & Result Type
(Signature)
+ / add / int, int -> int
- / subtract / int, int -> int
- / subtract from 0 / int -> int
* / multiply / int, int -> int
/ / int quotient / int, int -> int
% / int remainder / int, int -> int

Figure 3: Arithmetic Operations on ints

In this table:

int, int -> int

indicates that the operation takes two integer operands and produces one int result. A description such as this of the types of the operands and result of an operation is called the signature of the operation.[5]

The difference between the two – operations is that one is a unary minus, returning the result of subtracting its operand from 0 while the other is the regular, binary -. Thus:

- i==0 - i

where i is an integer variable.

The only non-intuitive operation here is /. In Java:

5/2 == 2

Thus the result of the division is the integer quotient you get from the division. To find the remainder from the division, we use %:

5 % 2== 1

Thus, the following equality does not hold true:

x ==(x/y)*y

Instead, the following equality holds true:

x == (x / y) * y + (x % y)

double Arithmetic Operations

These are like the previous operations, except that they take double operands and produce double results:

Name / Action / Operands & Result Type
(Signature)
+ / add / double, double -> double
- / subtract (unary) / -> double
- / subtract / double, double -> double
* / multiply / double, double -> double
/ / divide / double, double -> double

Figure 4: Arithmetic Operations on doubles

Thus, for every double operation here, there is a corresponding int operation that has the same name and performs “same” computation. For instance, the + operation in both cases adds the two operands.

Why not treat each pair of corresponding operations as a single operation? These are different operations in that different instructions are executed to perform them. For instance, the two + operations process different representations of their operands and results and detect overflow differently. An overflow occurs when the result of an operation is too big or too small to fit in the slot allocated for it. To determine if an overflow occurs, the int + must compare the result with Integer.MAX_VALUE and Integer.MIN_VALUE, while the double + must compare it with Double.MAX_VALUE and Double.MIN_VALUE. [6] The differences are even more evident when we consider the two / operations. The double. does a (more) precise division.

5/2==2

5.0/2.0 == 2.5

There is no % operation for doubles.

Thus, each of + ,-, *, and / is an overloaded operations with different implementations for ints and doubles rather than a single operation that handles both types.

Mixed Types: Automatic Conversion and Explicit Casting

What if we wanted to invoke an arithmetic operation on an int and a double:

5 / 2.0

Since the arithmetic operations provided by Java do not accept mixed types, one of the two operands must be converted to the type of the other. Java automatically converts the operand whose type is narrower. A type T1 is narrower than another type T2 if:

Set of instances of T1  Set of instances of T2

or, in other words, if every instance of T1 can be mapped to an instance of T2. [7]

Thus, in the example above, Java would convert the expression to:

5.0 / 2.0

and the call the double /.

What if we wanted integer division instead, that is, wanted to convert the double to the narrower type, int? In this case, we have to explicitly cast the double to an int by prefixing the name of the narrower type within parentheses:

5 / (int) 2.0

When you cast a double as an int, Java truncates the double, that is, gets rid of the fractional part.[8] Thus:

5 / (int) 2.0 == 5 / 2 == 2

5 / (int) 2.9 == 5 / 2 == 2

When you want to deal with whole numbers, truncation is desirable, but in other situations it is not. It is for this reason that Java does not automatically do the conversion for you. By explicitly casting, you are telling it you know what you are doing and you are willing to accept any negative consequences.

We can use mixed types also in assignment. Thus:

double d = 5;

int i = (int) 5.5;

In the first assignment, the rhs is automatically converted to the wider type of the lhs. In the second case, the explicit cast truncates the rhs and assigns it to the narrower type of the lhs. Assigning a value to a variable is very similar to passing a value as an argument, as we saw earlier, since an argument is just a special kind of variable.

Relational Operations

Java also defines a set of overloaded relational operations with both int and double implementations. Each of these operations does a test involving two int or double operands and returns a boolean indicating the result of the test.

Name / Action / Signature of int implementation / Signature of double implementation
== / equal? / int, int -> boolean / double, double -> boolean
!= / not equal? / int, int -> boolean / double, double -> boolean
greater than? / int, int -> boolean / double, double -> boolean
less than? / int, int -> boolean / double, double -> boolean
>= / greater than or equal? / int, int -> boolean / double, double -> boolean
<= / less than or equal? / int, int -> boolean / double, double -> boolean

Figure 5: Relational Operations on ints and doubles

Thus:

5 == 5 == true

5 == 4 == false

5 >= 4 == true

5 != 5 == false

5 <= 5 == true

Note that we use the two equal symbols, pronounced “equal equal”, for equality rather than a single equal because the latter has been reserved for assignment. It is very easy to confuse them, so you have to be careful you use the right one. Many subtle errors can be attributed to using = instead of ==.[9]

The relational operations can be applied to values of types other than ints and booleans, as shown below.

Name / Action / Generalized Signature
== / equal? / T, T -> boolean
!= / not equal? / T, T -> boolean
greater than? / OrderedT, OrderedT -> boolean
less than? / OrderedT, OrderedT -> boolean
>= / greater than or equal? / OrderedT, OrderedT -> boolean
<= / less than or equal? / OrderedT, OrderedT -> boolean

Figure 6 Generalized Signatures

They == and != operations can be applied to values of any primitive type. Thus, the following are legal:

true == true == true

false != false == false

The result is always a boolean but both operands have to be of the same (arbitrary) primitive type[10], T, as indicated by the signature:

T, T -> boolean

We cannot apply other relational operations to arbitrary types, since their operands must be ordered. Thus:

truefalse

is illegal since boolean values in Java are not ordered.[11] Values of all other primitive types are considered ordered.

Ordering Characters

Clearly, it makes sense to order Java values that are numbers, but what why order characters? In ordinary life, we do order characters, when we learn the alphabet, and more important, when we search directories. It is to support such searches that programming languages order the elements in the character set. The integer code, or ordinal number, assigned to a character is its position in this set. We do not need to know the exact ordinal number assigned to a character. It is sufficient to know that:

  • The null character, ‘’, is assigned the ordinal number 0.
  • The digits are in order.
  • The uppercase letters, ‘A’ .. ‘Z’, are in order.
  • The lowercase letters, ‘a’ .. ‘z’, are in order.
  • Letters of other alphabets are in order.

Thus, we know that:

‘1’ > ‘0’==true

‘B’ > ‘A’==true

‘a’ > ‘b’==false

c >= ‘’==true

where c is a character variable holding an arbitrary character value.

Based on the information above, we cannot compare elements from different ordered lists. Thus, we cannot say whether:

‘A’ > ‘a’

‘A’ > ‘0’

Like other programming languages, Java lets you find out the exact ordinal number of a character by casting it as an int. Thus:

System.out.println ( (int) ‘B’)

will print out the ordinal number of ‘B’. This cast is always safe, since char (16 unsigned bits) is a narrower type than int (32 bits). Therefore, when context demands ints, Java automatically performs the cast. Thus:

int i = ‘A’;

and:

‘B’ – ‘A’

computes the difference between the integer codes of the two characters, returning 1. Java lets you directly perform all the int arithmetic operations on characters, and uses their ordinal numbers in the operations. Usually we do not look at absolute values or sums of ordinal number - the differences are more important, as we see below.

You can also convert an ordinal number to a character:

char c = (char) intCodeOfB;

We had to perform a cast because not all integers are ordinal numbers of characters, just as not all doubles are integers. For instance, the following assignment makes no sense:

char c = (char) -1

since ordinal numbers are non-negative values. Java simply truncates the 32 bit signed value into a 16 unsigned value, much as it truncates a double with a fraction part to an int without a fraction. Again, by explicitly casting the value you are telling Java that you know what you are doing and are accepting the consequences of the truncation.