public class
where the filename matches the class namepublic static" function definitionspublic static void main(String[]
args) from the class Foo as found in the bytecode file Foo.class
$ ls Example1.class ls: cannot access 'Example1.class': No such file or directory $ javac Example1.java $ ls Example1.class Example1.class $ java Example1 81
System.out.println(·),
or if the value is used in a "+" expression where the other
argument is already a string. This is a convenience! Moreover,
you need to know this to make sense of all subsequent examples
later in today's notes.
$ javac Example4.java
Example4.java:6: error: variable x might not have been initialized
int z = x*y - 2;
^
1 error |
We mention this early on, because you may well see this message pop up. It's also interesting, because it gives us a glimpse of something important: Java's emphasis on safe code!
With arrays, which we'll talk about later, it would be infeasible for the compiler to check whether or not it is possible for a particular uninitialized array cell to be accessed by the code it is given. So, instead, the JVM silently initializes all array cells to zero, or some suitable alternative depending on the type stored in the array, when the array is allocated. Thus, array contents are always initialized. Note: this means there is a potential performance hit in the (admittedly, quite rare) situation in which you allocate a large array without intending to use every cell in the array.
Examples: nameArray or alphaCode
Examples: EmployeeInfo or CustomerList.
int x = 5;, we would say that x is
not an object.
double[] A = new double[10];, we would say that
A is an object.
Foo is a class (think "struct" for the moment)
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 values | array objects | class objects |
int i = 7; double x = 4.32; char c = 'q'; boolean b = i*x > 28.5; int j = (int)x; byte y = (byte)255; |
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"; |
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 array-objects and class-objects work just like pointers in C++ (though they are called references in Java); 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 // do something here!
comment. Notice that we always have pass-by-value
semantics in Java, but that passing a reference by value
means getting a reference that points to the same thing.
This is exactly the same as the behavior we saw with
pointers in C++!
|
Because variables of array-object or class-object type are references, we have to deal with the possibility that there is no actual object for them to refer to. For example:
int k = 0; String s = null;The variable
k is an int ... it's value is zero,
but the point is that it has a value.
The variable s, however, is a reference that
doesn't refer to any object. There is no String object
that s refers to. We call this a null-reference, and
it's just like a NULL-pointer. 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;
One important difference is you can NOT treat a string like an array. stringVariable[1]= 'p'; will cause your compiler to yell at you. However, Java Strings DO 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 at the Java 11 API documentation for String 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 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".
String a = "hello"; String b = a;then
a and b are referring to the
same object. So if we were to modify "string a", we would
also be modifying "string b". Because of the way we are used
to thinking about strings, this would probably cause lots of
confusion and errors. So by not allowing strings to be
modified, i.e. by making them immutable that problem
is avoided.
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!
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 x to 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 itOf 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;
Primitive types: boolean char byte short int long float double
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.
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?
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?
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));
}