Java Datatypes

Java defines two types of data types: primitive types and objects. Topics covered in this lecture are

  • primitive types
  • variables
  • casting
  • operators
  • operator precedence.

Primitive Types

The following primitive types are supported:

  • Integral types
    • byte - a 8 bit signed integer. Can represent any value between -128 and 127
    • short - a 16 bit signed integer. Can represent any value between -32,768 and of 32,767
    • int - a 32 bit signed integer. Can represent any value between -$2^{31}$ and $2^{31}$ - 1
    • long - a 64 bit signed integer. Can represent any value between -$2^{63}$ and $2^{63}$ - 1
  • Floating point types (see here)
    • float - a 32 bit floating point number. A single-precision 32-bit IEEE 754 floating point
    • double - a 64 bit floating point number. A double-precision 64-bit IEEE 754 floating point
  • Character type
    • char - a single 16-bit Unicode character
  • Boolean types
    • boolean - a boolean value (true or false)

Syntax for literal values of primitive types

Integer literals are just the numbers themselves, e.g., 4, or -15. Alternatively, integer values can be specified in binary by prefixing the value with 0b or hexadecimal by prefixing the value with 0x.

In [10]:
// a short constant
return (short) 1;
Out[10]:
1
In [1]:
// an int constant
return 1;
Out[1]:
1
In [2]:
// a long constant
return 1L;
Out[2]:
1
In [38]:
// 15 in hexadecimal
return 0xF;
Out[38]:
15
In [39]:
// 15 in binary
return 0b1111;
Out[39]:
15

Numbers in binary can become quite long. For convenience Java allows _ characters to be inserted into the number to separate parts of a long number. These underscores do not change the semantics of the number.

In [3]:
// 15 in binary as an int (31 bit)
return 0b00000000_00000000_00000000_00001111;
Out[3]:
15

Negative numbers using 0b and 0x are writting with prefix -. Note that internally one bit of information is required to store the sign. For example, only 7 bits of information are available store the value. In Java the largest positive byte values is internally represented as 10000000 (128) while negative numbers are stored using the two's complement of a number. The two's complement adds one to the number and then inverts all the bits. For example, consider -1. We have -1 + 1 = 0. Zero in binary is 00000000. Then inverting the bits we get 11111111.

In [40]:
// -15 in binary
int b = -0b0001111;
return Integer.toBinaryString(b); // -15 + 1 = -14. 14 in binary (32 bits) is 000000 00000000 00000000 00001110. Inverting all the bits we get the number shown below.
Out[40]:
11111111111111111111111111110001

Floating point constants are identified by the decimal point, e.g,. 3.0 is a floating point literal. By adding a suffix f or d such literals can be identified as float or double. Scientific notation (speciying $10^n$ as en) is also allowed.

In [21]:
// a float constant
return 3.0f;
Out[21]:
3.0
In [9]:
// a double contant
return 3.0; // alternatively 3.0d
Out[9]:
3.0
In [40]:
// scientific notation
return 1.5e5;
Out[40]:
150000.0

There are only two boolean constants true and false.

In [22]:
// a boolean constant
return true;
Out[22]:
true

Variables of primitive types

Variables of a primitive type are declared using <type> <name>. For instance, long x; declares a variable called x of type long. Variables can be either initialized during declaration or explicitly at any time they have been declared. Using a variable before it has been initialized may lead to a compile time error (we will revisit this later and learn under which circumstances it is necessary to initialize a variable).

In [4]:
// no intialization -> compile time error
int x;
return x;
variable x might not have been initialized
 return x
        ^^ 
In [5]:
// initialization during declaration
int x = 1;
return x;
Out[5]:
1
In [8]:
// initialization after declaration
int x;
x = 15;
return x;
Out[8]:
15

Operators

The Java language defines several operators. Operators cannot be overloaded in Java. That is it not possible to redefine the semantics of such operators. The following operators are defined for primitive types (we will introduce additional operators when covering Classes and Objects later).

  • Arithmetic Operators
    • + is addition and - is subtraction. Both are binary (two inputs), e.g., 3 + 4 = 7
    • * is multiplication, / is division, % is modulo, e.g., 12 % 7 = 5
    • + and - can also be used as unary operators (operators with one input), e.g., -x if x is a variable of type int
  • Comparison Operators - Comparison operators return results of type boolean
    • == is equality (note that two equal signs are used, not one). Equality returns true if the compared values are equal
    • <, >, <=, >=, relational operators smaller, larger, smaller than or equal, and larger than or equal
  • Logical Operators
    • ! is not ($\neg$)
    • && is logical and ($\wedge$)
    • || is logical or ($\vee$)
  • Bit-wise Operators
    • &, |, and ^ are bit-wise or, and, and xor
    • <<, >> shift left and shift right. This preserves the sign of the number. That is, after shifting a positive number will still be positive while a negative number will be negative.
    • <<<, >>>. logical shift left and right. This just treats the value as a bit array and is oblivious to whether it is negative or positive.
  • Assignment Operators
    • = - basic assignment. Assigns a the value of an expression to a variable like this: <variable> = <expr>;
    • Combined assignment operators. Multiple of the other binary operator types can be combined with assignement: +=, -=, *=, /=, %=, &=, ^=, |= <<=, >>=, >>>=. The structure is still <variable> X= <expr>. This is a syntactic shortcut to assigning the target of the assignment to the result of applying the binary operator to the target and the result of the expression expr, e.g., x += 3 is equivalent to writing x = x + 3.
  • Increment/Decrement
    • ++ and -- increment (decrement) the value of a variable by 1. These operators exist as prefix and suffix versions, e.g., ++x (prefix) and x++ (postfix). Both versions return the value of the incremented/decremented variable. The difference is that the prefix operator returns the value after applying the increment/decrement, which the postfix operator returns the value before the operation.
  • Tenary Operator
    • The tenary operator selects among the result of two expressions based on the result of a logical condition. The general form is <condition> ? <expr1> : <expr2>. If <condition> evaluates to true then the operator evaluates to <expr1>, otherwise it evaluates to <expr2>.

Arithmetic Operators

In [18]:
// arithmetic operators
return (12 % 7) + 3;
Out[18]:
8
In [16]:
int x = 3;
int y = 7;
int z = x + y;
return z;
Out[16]:
10
In [18]:
float f1 = 3.0f;
float f2 = 5.1f;
float f3 = f1 + f2;
return f3;
Out[18]:
8.1
In [23]:
double d1 = 3.0;
double d2 = 4.1;
double d3 = d1 + d2;
return d3;
Out[23]:
7.1
In [17]:
int x = 3;
return -x;
Out[17]:
-3

Comparison Operators

In [9]:
return 3 == 3;
Out[9]:
true
In [10]:
return 3 == 5;
Out[10]:
false
In [11]:
return 3 < 5;
Out[11]:
true
In [12]:
return 'a' == 'a';
Out[12]:
true
In [13]:
// relational operators on chars use lexicographical order
return 'a' < 'b';
Out[13]:
true
In [14]:
return 'b' < 'a';
Out[14]:
false
In [15]:
// relational operators are not defined for type boolean
return false < true;
bad operand types for binary operator '<'
  first type:  boolean
  second type: boolean
 return false < true
        ^           ^ 

Logical Operators

In [16]:
return !true;
Out[16]:
false
In [17]:
return true || false;
Out[17]:
true
In [18]:
return true || true;
Out[18]:
true
In [19]:
return false || false;
Out[19]:
false
In [32]:
return true && true;
Out[32]:
true
In [33]:
return true && false;
Out[33]:
false
In [34]:
return ((!false) && true) || false;
Out[34]:
true
In [35]:
boolean x = (true && true);
boolean y = x || false;
return y;
Out[35]:
true

Bit-wise Operators

In [8]:
// shifting
byte b = 0b00000001; // 1 in binary representation
b <<= 3; // shifting to the left by 3 bits
return b == 0b00001000; // now we have 8
Out[8]:
true
In [14]:
// bit and
byte b1 = (byte) 0b00101000;
byte b2 = (byte) 0b00100000;
return Integer.toBinaryString(b1 & b2);
Out[14]:
100000
In [15]:
// bit or
byte b1 = (byte) 0b00101000;
byte b2 = (byte) 0b00100000;
return Integer.toBinaryString(b1 | b2);
Out[15]:
101000
In [21]:
// bit not
byte b1 = (byte) 0b10101000;
return Integer.toBinaryString(~b1);
Out[21]:
1010111
In [31]:
// using ints
int b = 0b01000000000000000000000000000000;
return Integer.toBinaryString(~b);
Out[31]:
10111111111111111111111111111111

Increment/Decrement

In [20]:
// prefix (return the value after increment)
int x = 0;
return ++x; // return 1
Out[20]:
1
In [22]:
// suffix (return the value before increment)
int x = 0;
return (x++ + 15); // returns 0
Out[22]:
15

Ternary operator (conditional assignment)

In [23]:
// since 5 is larger than 3 the condition is true and 15 is returned
return (5 > 3) ? 15 : -15;
Out[23]:
15
In [24]:
// another example, here the condition evaluates to false and the second value is returned
return (5 < 3) ? 15 : -15;
Out[24]:
-15

Operator Precendence

Like most programming languages Java defines a precendence order among its operators. This order determines how expressions using multiple operators are interpreted if the execution order is not made explicit through parentheses. You already know this from basic algebra, e.g., 3 + 2 * 7 is equal to 3 + (2 * 7) and not (3 + 2) * 7 because it is generally understood that multiplication has precedence over addition. Java's precedence rules are summarized in the table below.

Operator Precedence
Operators Precedence
postfix expr++ expr--
unary ++expr --expr +expr -expr ~ !
multiplicative * / %
additive + -
shift << >> >>>
relational < > <= >= instanceof
equality == !=
bitwise AND &
bitwise exclusive OR ^
bitwise inclusive OR |
logical AND &&
logical OR ||
ternary ? :
assignment = += -= *= /= %= &= ^= |= <<= >>= >>>=

For example, as expected multiplication has higher precedence than addition.

In [12]:
int x = 3 + (2 * 7);
return x;
Out[12]:
17
In [14]:
int x = 3 + 2 * 7;
return x;
Out[14]:
17

Casting

Casting allows values of one data type to be translated into another data type. In Java casted as written as (<newtype>) <expression>. For example,

int x = 4;
double y = (double) x;

Here we are casting the int value 4 as a double (floating point number).

In [37]:
int x = 4;
double y = (double) x;
return y;
Out[37]:
4.0
In [45]:
short b = 0b0000000100010011;
char c = (char) b;
return c;
Out[45]:
ē
In [47]:
char c = 'a';
return (short) c;
Out[47]:
97
In [52]:
char c = 'A';
return (short) c;
Out[52]:
65
In [53]:
char c = 'A';
short b = (short) c;
b += 32;
char c2 = (char) b;
return c2;
Out[53]:
a