OBJECTS AND CONSTRUCTORS ======================== String s; // Step 1: declare a String variable. s = new String(); // Steps 2, 3: construct new empty String; assign it to s. At this point, s is a variable that --- ------ references an "empty" String, i.e. s |.+---->| | a String containing zero characters. --- ------ String s = new String(); // Steps 1, 2, 3 combined. s = "Yow!"; // Construct a new String; make s a reference to it. --- ---------- s |.+---->| Yow! | --- ---------- String s2 = s; // Copy the reference stored in s into s2. --- ---------- --- s |.+---->| Yow! |<----+.| s2 --- ---------- --- Now s and s2 reference the same object. s2 = new String(s); // Construct a copy of object; store reference in s2. --- ---------- --- ---------- s |.+---->| Yow! | s2 |.+---->| Yow! | --- ---------- --- ---------- Now they refer to two different, but identical, objects. Think about that. When Java executes that line, it does the following things, in the following order. - Java looks inside the variable s to see where it's pointing. - Java follows the pointer to the String object. - Java reads the characters stored in that String object. - Java creates a new String object that stores a copy of those characters. - Java stores a reference to the new String object in s2. We've seen three String constructors: (1) new String() constructs an _empty_string_--it's a string, but it contains zero characters. (2) "Yow!" constructs a string containing the characters Yow!. (3) new String(s) takes a _parameter_ s. Then it makes a copy of the object that s references. Constructors _always_ have the same name as their class, except the special constructor "stuffinquotes". That's the only exception. Observe that "new String()" can take no parameters, or one parameter. These are two different constructors--one that is called by "new String()", and one that is called by "new String(s)". (Actually, there are many more than two--check out the online Java API to see all the possibilities.) METHODS ======= Let's look at some methods that aren't constructors. s2 = s.toUppercase(); // Create a string like s, but in all upper case. --- ---------- s2 |.+---->| YOW! | --- ---------- String s3 = s2.concat("!!"); // Also written: s3 = s2 + "!!"; --- ------------ s3 |.+---->| YOW!!! | --- ------------ String s4 = "*".concat(s2).concat("*"); // Also written: s4 = "*" + s + "*"; --- ------------ s4 |.+---->| *YOW!* | --- ------------ Now, here's an important fact: when Java executed the line s2 = s.toUppercase(); the String object "Yow!" did _not_ change. Instead, s2 itself changed to reference a new object. Java wrote a new "pointer" into the variable s2, so now s2 points to a different object than it did before. Unlike in C, in Java Strings are _immutable_--once they've been constructed, their contents never change. If you want to change a String object, you've got to create a brand new String object that reflects the changes you want. This is not true of all objects; most Java objects let you change their contents. You might find it confusing that methods like "toUppercase" and "concat" return newly created String objects, though they are not constructors. The trick is that those methods calls constructors internally, and return the newly constructed Strings. I/O Classes and Objects in Java ------------------------------- Here are some objects in the System class for interacting with a user: System.out is a PrintStream object that outputs to the screen. System.in is an InputStream object that reads from the keyboard. [Reminder: this is shorthand for "System.in is a variable that references an InputStream object."] But System.in doesn't have methods to read a line directly. There is a method called readLine that does, but it is defined on BufferedReader objects. - How do we construct a BufferedReader? One way is with an InputStreamReader. - How do we construct an InputStreamReader? We need an InputStream. - How do we construct an InputStream? System.in is one. (You can figure all of this out by looking at the constructors in the online Java libraries API--specifically, in the java.io library.) Why all this fuss? InputStream objects (like System.in) read raw data from some source (like the keyboard), but don't format the data. InputStreamReader objects compose the raw data into characters (which are typically two bytes long in Java). BufferedReader objects compose the characters into entire lines of text. Why are these tasks divided among three different classes? So that any one task can be reimplemented (say, for improved speed) without changing the other two. Here's a complete Java program that reads a line from the keyboard and prints it on the screen. import java.io.*; class SimpleIO { public static void main(String[] arg) throws Exception { BufferedReader keybd = new BufferedReader(new InputStreamReader(System.in)); System.out.println(keybd.readLine()); } } Don't worry if you don't understand the first three lines; we'll learn the underlying ideas eventually. The first line is present because to use the Java libraries, other than java.lang, you need to "import" them. java.io includes the InputStreamReader and BufferedReader classes. The second line just gives the program a name, "SimpleIO". The third line is present because any Java program always begins execution at a method named "main", which is usually defined more or less as above. When you write a Java program, just copy the line of code, and plan to understand it a few days from now. PRIMITIVE TYPES =============== Not all variables are references to objects. Some variables are primitive types, which store values like "3", "7.2", "h", and "false". They are: byte: A 8-bit integer in the range -128...127. (One bit is the sign.) short: A 16-bit integer in the range -32768...32767. int: A 32-bit integer in the range -2147483648...2147483647. long: A 64-bit integer, range -9223372036854775808...9223372036854775807. double: A 64-bit floating-point number like 18.355625430920409. float: A 32-bit floating-point number; has fewer digits of precision. boolean: "true" or "false". char: A single character. long values are written with an L on the end: long x = 43L; This tells the compiler to internally write out "43" in a 64-bit format. double and float values must have a decimal point: double y = 18.0; float values are written with an f at the end: float f = 43.9f; Object types Primitive types -------------------------------------------------------------------------- Variable contains a reference value How defined? class definition built into Java How created? "new" "6", "3.4", "true" How initialized? constructor default (usually zero) How used? methods operators ("+", "*", etc.) Operations on int, long, short, and byte types. -x x * y x + y x / y <-- rounds toward zero (drops the remainder). x - y x % y <-- calculates the remainder of x / y. Except for "%", these operations are also available for doubles and floats. Floating-point division ("/") doesn't round to an integer, but it does round off after a certain number of bits determined by the storage space. The java.lang library has more operations in... - the Math class. x = Math.abs(y); // Absolute value. Also see Math.sqrt, Math.sin, etc. - the Integer class. int x = Integer.parseInt("1984"); // Convert a string to a number. - the Double class. double d = Double.parseDouble("3.14"); Converting types: integers can be assigned to variables of longer types. int i = 43; long l = 43; // Okay, because longs are a superset of ints. l = i; // Okay, because longs are a superset of ints. i = l; // Compiler ERROR. i = (int) l; // Okay. The string "(int)" is called a cast, and it casts the long into an int. In the process, high bits will be lost if l does not fit in the range -2147483648... 2147483647. Java won't let you compile "i = l" because it's trying to protect you from accidentally creating a nonsense value and a hard-to-find bug. Java requires you to explicitly cast longs to ints to show your acknowledgment that you may be destroying information. Similarly, "float f = 5.5f; double d = f;" is fine, but you need an explicit cast for "double d = 5.5; float f = (float) d;". Integers (even longs) can be directly assigned to floating-point variables (even floats) without a cast, but the reverse requires a cast because the number is truncated to an integer. Boolean Values -------------- A boolean value is either "true" or "false". Booleans have operations of their own, signified "&&" (and), "||" (or), and "!" (not). a | b || a && b | a || b | !a ==================||============================== false | false || false | false | true false | true || false | true | true | false || false | true | false true | true || true | true | Boolean values can be specified directly ("true", "false") or be created by the comparison operators "==", "<", ">", "<=", ">=", "!=" (not equal to). boolean x = 3 == 5; // x is now false. x = 4.5 >= 4.5; // x is now true. x = 4 != 5 - 1; // x is now false. x = false == (3 == 0); // x is now true.