Java Types

Today introduces Java types. During this, keep in mind that Java is a pass-by-value language. Values are always copied when passed to and from method calls. Unlike C++ there is no pass-by-reference option, so in one sense, Java has less options to get confused about. However, all of Java's objects are based on reference type variables. We discuss what this means below.

Java's two (or 3) kinds of types

Java has two kinds of types: (1) primitive types, and (2) reference types (sometimes called non-primitives). When we talk about "objects" in this class, it involves the reference types.

  1. Primitive Types: char, int, float, double, and so on. These are mostly the same as the primitive types you know from C++. Primitive types are not considered "objects" in Java parlance. For example, with the declaration int x = 5;, we would say that x is not an object.
  2. Reference Types: these hold references (memory addresses) to objects, pointing to the objects on the heap. There are two main reference types that you'll need:
    • Array Types: these are similar to dynamic arrays in C++. Arrays are considered to be "objects" in Java parlance. So, for example, with the declaration double[] A = new double[10];, we would say that A is an object.
    • Class Types: these objects are defined by classes, which are similar to struct's in C++, but with the key distinction that in addition to member data, we may have member functions. Instances of a class (i.e. values in a program whose type is that class) are considered "objects" in Java parlance — in fact, they are the "objects" in Java's object-oriented programming. For example, if Foo is a class (think "struct") and we have the declaration Foo f = new Foo();, we would say that f is an object. Normally, if we use the term "object" we are referring to class objects.

Primitive types
int i = 7;
double x = 4.32;
char c = 'q';
boolean b = i*x > 28.5;
int j = (int)x;
byte b = (byte)255;
Array objects
int[] A = new int[2];
A[0] = 5;
A[1] = 7;
String[] B = new String[3];
B[0] = "hi";
B[1] = "bye";
B[2] = "wow";
Class objects
String s1 = "Groot";
String s2 = "I am" + s1;
Scanner s = new Scanner(System.in);
String s3 = s.next();
String s4 = s3.toLowerCase();
String iAmEmpty = new String();

Important! Now, here's the truly important thing to know: values of a primitive type are basically the same as in C++; picture a box with the value written in the box. Assignment copies the contents of the right-hand side box into the left-hand side box. Function arguments are passed by value, just as they are by default in C++. Variables referring to arrays and class objects work similar to pointers in C++ (although they are called references in Java and are used differently as we'll see later); picture a little box with an arrow pointing to the box that actually has values written inside.

public class Ex1 {

  public static void foo(int a, int[] b, String c) {
    // do something here!
  }

  public static void main(String[] args) {
    int i = 4, j;
    int[] A = {3,5,7}, B;
    String s = "hey", t;
    j = i;
    B = A;
    t = s;
    foo(i,A,s);
  }
}  
 
This depicts the state of affairs at the comment:
// do something here!
Notice that we always have pass-by-value semantics in Java, so passing a reference type by value means copying a reference to point to the same thing. This is the same as the behavior we saw with pointers in C++, but not with its plain struct variables (they'd copy the entire struct into a new struct). Notice in Java that our reference types look like C++ structs, but they behave like C++ pointers during variable assignment.
 


Because variables of arrays and class objects are reference types, they behave differently than the primitive types when they are not initialized. For example:

int k;
String s;

The variable k is an uninitialized primitive type. Unlike in C/C++ where uninitialized primitives still have values (just not reliable since you didn't initialize it), Java's compiler will give you an error if you try to use that uninitialized primitive without first assigning it a value. Class types are different, however. Since it does not yet refer to any object, it is a null-reference. It's just like a NULL-pointer from C++. The literal for the null-reference is simply null. Thus, you can make a test like:

if( s != null ) {
   ...
}
or set a reference deliberately to null like:
s = null;

More Details on Reference Types

O'Reilly's textbook we suggest for this class has a great overview of reference types. Don't be afraid to read more!

Primitive Types

The primitive types of Java... are not very interesting.

Primitive types: [ boolean, char, byte, short, int, long, float, double ]

What's up with the byte type? Well, Java's char type is actually 16-bits, because Java uses Unicode rather than ASCII, so that strings can contain characters from many different languages. While that's pretty transparent to us as programmers, it leaves us without the char-byte equivalence we rely upon in C/C++. Therefore, Java has a type byte that is an 8-bit signed integer — just like C/C++'s char type.

They're very similar to C++: int, double, char, and boolean (instead of bool) are the common ones. There is also: byte, long, short (which are integral), and float (which is floating-point, like double, but with less precision). Operators are the same as well: +,-,*,/,%. All the rules about integer division/modulus still apply.

So what happens when this is run?

int i = 13;
int j = 7;
int k = 2;
double m = 0.0;
System.out.println("values: " + (i%j) + " " + (j/k) + " " + (j+m)/k);

boolean a = true;
boolean b = false;
System.out.println("values: " + (a+b));
System.out.println("values: " + (a || a) + " " + (a || b) + " " + (b || b) + " "
          + (a && a) + " " + (a && b) + " " + (b && b));

char x = 'y';
char y = 'x';
System.out.println("values: " + x + " " + y + " " + (x+y));

What would happen if we took out the () inside the printlns?

Conversions.

Just as with C++, types can be converted to one another, but with more restrictions.

Widening conversions are converting a type to a "larger" type, like int to double. There are 19 possible:

These can be done automatically:

int i = 3;
double d = i;

the int 3 is widened to a double. Widening usually doesn't lose information.

Narrowing can lose information and precision, and requires a "cast". There are 22 Narrowings

float fmin = Float.NEGATIVE_INFINITY;
float fmax = Float.POSITIVE_INFINITY;
System.out.println("long: " + (long)fmin + ".." + (long)fmax);
System.out.println("int: " + (int)fmin + ".." + (int)fmax);
System.out.println("short: " + (short)fmin + ".." + (short)fmax);
System.out.println("char: " + (int)(char)fmin + ".." + (int)(char)fmax);
System.out.println("byte: " + (byte)fmin + ".." + (byte)fmax);

Everything can be converted to a string. That's what is happening when you do this: "STRING " + 3; There are a number of other conversions that we'll talk about later on.

When do these conversions happen?

  1. During assignment, as we saw with widening.
  2. During function calls:
  3. public static double foo(double argh) {
      return (argh*argh)/(argh-1);
    }
    
    public static void main(String[] args) {
      int i = 3;
      System.out.println("foo: " + foo(i));
    }  
  4. Convert to String types when using the + operator (e.g., String+int will convert to String concatenation)
  5. When casting: int i = (int)3.3;
  6. Numeric promotion. This is what happens when you multiple an int by a double. The int is promoted to a double, and then the multiplication occurs.

Strings

As with C++, strings are not a "primitive type", but are objects of class String. As you have already seen, you can make string constants by putting text in quotes. String stringVariable = "String Constant";

One important difference is you can NOT treat a string syntactically like an array. stringVariable[1]= 'p'; will cause your compiler to yell at you. This is a very Java-like thing: since String is a class, you use function calls to do everything you'd ever want to do with it. Java Strings have a function called .charAt(int), which will retrieve a copy of the character at a certain index in the string. Additionally, Strings have a large number of other useful functions, including .length(), .indexOf(), .substring(), .toUpperCase() and many, many more. It's worth taking a look in your reference book or online to see all the things Strings can do.

As with C++, the '+' operator with Strings does concatenation - i.e. glues the strings together.

String s = "he", t = "llo";
String w = s + t; // now w is "hello"

What's crucially different, however, is that an expression of the form String + value is evaluated by converting value into a String and then doing concatenation. Thus, "r" + 3 results in "r3". The + operator is left-associative, so "r" + 3 + 2 gives "r32", but 3 + 2 + "r" gives "5r".

One last important point about strings: they are immutable, meaning they can't be modified. So, if we have String s = "hello world"; and we want to capitalize the first letter, we can't simply modify the first letter of the String object s refers to. Instead, we have to make a new String that looks like what we want.

s = s.substring(0,1).toUpperCase() + s.substring(1);

This makes a new string "h" from s, then makes a new string "H" from the first, then makes a new string "ello world" from s, then makes a new string by concatenation, then assigns s to point to the result. What happens to the original s, "h", "H" and "ello world" you might ask? They are orphaned! They sit out there on the heap and are no longer referred-to/pointed-to by anything. In C++ this would be a disaster, but not in Java. Java has automatic garbage collection, which means the virtual machine automatically detects and reclaims orphaned memory! That's a huge burden off your shoulders as a programmer!

Arrays

In java, there are only dynamic arrays. We declare variable x to be a reference to an array of objects of type Foo with the syntax: Foo[] x; However, as with C++, this x doesn't have an actual array to point to yet. To allocated an array of n objects of type Foo and set xto point to it, we make the assignment: x = new Foo[n]; For example:

int[] joe;         // this declares joe as an array reference
joe = new int[10]; // this allocates (on the heap) an array of 10 ints and sets joe to point to it
Of course, we can do both at once like this:
int[] joe = new int[10];
It's the same for other types:
boolean[] aBoolArray = new boolean[10];
Java arrays are, for the most part, like C++'s dynamic arrays. However, you can initialize them with syntax reminiscent of C++'s static array initializers, like this:
String[] aStringArray = { "array", "of", "String" };
... which is nice.

Access is done as with C++. Note, however, that we can always use .length to arrive at the length of an array. This is not a function, as with Strings, but is an attribute of the array:

int[] ia = new int[101];
for (int i = 0; i < ia.length; i++) {
  ia[i] = i;
}
int sum = 0;
for (int i = 0; i < ia.length; i++) {
  sum += ia[i];
}
System.out.println(sum);

If we change one of the < to a <=, our C++ experience tells us to expect either a Segmentation Fault or a silent bit of weirdness, which is kind of horrible. Instead we get a message about an ArrayIndexOutOfBoundsException, and an indication of which line caused it to happen, which seems strangely wordy, but much, much more useful.

Multidimensional arrays work just the same, but are easier to allocate:

int[][] anArr = new int[10][9];                // An array with 10 rows and 9 columns
for (int i = 0; i < anArr.length; i++) {       // anArr.length is 10, just like C++
  for (int j = 0; j < anArr[i].length; j++) {  // anArr[i].length is 9, just like C++
    anArr[i][j]=0;
  }
}

Problems