First Draft Introducing Java In this lecture we will explore a bit of the Java language: I chose Java because I know it better than C++ (both, like Python, are still changing and evolving). The main point of this lecture is to show a more conventional language similar to Python (still in the same general family: object-oriented languages that use statements to compute their results) but with some differences. I have tried to discuss the most important and interesting differences between these language, broadly broken into four main areas. 1) Compilation and compile-time vs. run-time issues 2) Syntax differences 3) Static vs dynamic typing 4) Built-in vs Class Supplied Data Structures Note that you all should have Java installed on your computers (to run Eclipse) so you can use Eclipse to write and run Java programs. I suggest buying older editions of Java texbooks authored by Horstmann or Savitch (cheaper than the most current editions). ------------------------------------------------------------------------------ Compilation As with Python, Java translates programs into a special instruction set that runs on the Java Virtual Machine (JVM). In fact, the JVM is so well-known that other languages are translated to run on the JVM. Java programs run 5 times as fast as Python programs (and C++ programs run another factor of 5-10 faster) but with a price: there are important Python programming features (e.g., eval and exec) that Java does not support (and likewise, C++ doesn't support certain features that makes checking/debugging Java programs easier*). So this is an example of languages trading-off power and ease of programming for speed of execution. ----- *For example, Python checks its lists (and Java its arrays) for indexes that are out of bounds, and raises an exception if they are. Typically it reports an illegal index error at run-time (Java further reports the illegal index used; Python just says the index is illegal without reporting its value). When an illegal index is used with a C++ array, typically there is no runtime error: instead C++ just uses the information in its memory where the value at the illegal index would appear if it were in the array; what is actually there, in the computer's memory, is some random/garbage value. So the program keeps running, although it is likely the bad data will corrupt the computation. By avoiding checking indexes, C++ allows correct programs (e.g., ones doing no illegal indexing) to run faster. But incorrect programs don't fail when the error occurs: instead they keep excuting and maybe fail later, but often instead print wrong results (or worse, incorrectly buy/sell a stock, or catastrophically decide to launch a nuclear attack, because of an incorrect program). Sometimes a C++ program will crash immediately, giving no information about where it stopped: that is the best case! ----- Python reads and translates imported modules on the fly, while it is runing programs. Java breaks running programs into two separate processes: compiling programs first (translating them into JVM instructions) and then running these instructions on the JVM. Errors are categorized as to when they occur: at compile-time or at run-time. The program that does the compilation is called the Java Compiler. Certain Java errors are called syntax or compile-time errors; others are called execution or run-time errors. Actually Python has the concept of syntax errors (even if there is no separate compile-time/run-time distinction). For example in Python, if we misspell a keyword or name, or indent a block incorrectly, Python reports the error, but only when we run the program; although the Eclipse editor often flags such errors for us before we run our program: indicating that if we run it, Python will find an error. Likewise, when Java compiles a program, Java checks it for the same kinds of errors (and more; see below). But, we cannot run Java software until all its files compile correctly. ------------------------------------------------------------------------------ Important Syntactic Differences Python and Java are similar languages in that both define statements that are executed in sequences known as blocks. The syntax of Python requires that each statement is on a different line and that blocks are indented. The syntax of Java requires that statements are separated by semicolons (so one can easily have multiple statements per line or one statement covering multiple blocks) and that blocks (of more than one statement) must appear in the {} braces. Java promotes a certain format for writing blocks (so that the code is clearly indented and actually looks like Python code) but such style rules are not required of programmers writing in the language. In fact, style rules for Java and C++ are a bit different. Python Java if x < y: if (x < y) { min = x min = x; max = y max = y else: }else{ min = y min = y; max = x max = x } Note that the syntax of Java requires () parentheses in more places than Python does: like surrounding the test in an if (or while) statement. Also note that we can write this Java code as if (x < y) {min = x; max = y}else{min = y; max = x} and it has the same meaning to the Java compiler. Even the following code (indented nonsensically) if (x < y) {min = x; max = y} else{min = y; max = x} has the same meaning. A program is just a sequence of tokens with all the whitespace removed. In Python, line-endings and indentation are important. We can write the Python code on the left below as either Java code on the right: the first uses one statements in the if/else; the second uses blocks (optional, because they contain just one statement). Note the meaning of / in Java: int/int in Java is like int//int in Python, because both truncate the result to an integer. There is no Java operator to compute a float value from two int values. if x%2 == 0: if (x%2 == 0) if (x%2 == 0) { x = x//2 x = x / 2 x = x /2 else: else }else{ x = 3*x + 1 x = 3*x + 1 x = 3*x + 1 Names in Java are written in "camel" notation. In Python we wrote a_var_name and in Java we write aVarName (the capital letters are like humps on a Camel). By Java convention, most names start with a lower-case letter, but class names start with an upper-case letter. Here is an minor but interesting difference: Python interprets the expression "a < b < c" as "a < b and b < c". Java interprets this expression as "a < b < c" to compare the boolean result of the first < to c, which is likely to cause a static-typing compilation error described below. In Java we must write "a < b < c" more explicitly, as "a < b && b < c" (where && is Java's equivalent to Python's "and" operator). For commenting // is used in Java is like # is use in Python, as a line-oriented comment. Java also has multiline comments such as /* This is a multi-line comment in Java */ In Python, files contain modules or classes. In Java there is no equivalent to modules, so files contain only classes, one class per file. The name of file and the name of the class it contains must be the same. So everything that we write in Java we write in some class: there is a special method named "main" in a class which Java calls to start a program executing (similar to Python, in which we write code in a module like if __name__ == '__main__':). Note that Java divides types into primitive and reference types. Primitive types are things like integers (ints) and floating point values (double). References store references to objects constructed from classes (just like Python; all values in Python are objects). In fact Java includes classes like Int and Double that are the object equivalent of the primitive types: they are more cumbersome and slower to use, but we can use them in places where class objects must be used (not primitives: there are a few rough corners like this). Java operators work primarily on primitive types; reference types use methods to perform computations. Java classes cannot overload operators the way Python does (but C++ can). Java allows only single inheritance. Classes can refer to information in their super classes by using the keyword super. So if the inc method in the Modular_Counter subclass (as they are know in Java, the same as a derived class in Python) overrides the inc method in its Counter superclass (the same as a base class in Python) the inc method in Modular_Counter can call super.inc() to call the inc method in Counter. In Python we would write Counter.inc() instead. ------------------------------------------------------------------------------ Static and Dynamic Typing (relating to the types of values) Python is a dynamically typed language. Java (and C++) is a statically typed language. In a dynamically typed language, each name refers to an object that is of some type, but we cannot always know to what type of object a name refers to. For one example we can write x = 1 and later x = [1,2,3] so x first refers to an int and later a list of ints.* ----- *Note that ints in Java are 32 bit quantities, not arbitrarily sized integers as in Python. Java does have a type name BigInteger that stores arbitrarily large integers, but is a class we must operate on it with methods. So we end up writing code like x = BigInteger("2984248239834923"); y = x.multiply(x).add(x) (which in Python we would write as just x = 2984248239834923 and y = x*x+x) Recall that operators in Java generally work only on the primitive types. We cannot write classes that overload operators in Java as we did in Python (but can overload operators in C++) ----- For another example, if we define the following function in Python def randtype(): if random.random() < .5: return 1 else: return 1. x = randtype() Then we don't know what type of object the name x refers to: it might be int or it might be float. But we can compute type(x) to find out. So, in a dynamically typed language when we define names we don't need to specify their type: we assign them a reference to a value. Not knowing the type of value associated with a name (and having to compute it at run-time) is another reason Python runs more slowly than Java and C++. Whenever Python must do something with the value associated with a name, it must first compute its type: think of the fundamental equation of object-oriented programming. In a statically typed language like Java, we must specify the type of each name: in Java we talk about declaring (not defining names). We can write either int x; or int x = 1; the former declares x to always store an int but currently store no value, while the latter declares x to always store an int and currently store 1. We must also specify the type of parameters and local variables and specify the return type of any method, where the type name "void" means that it returns no value: Python functions returning None is a different but similar idea to Java methods having a "void" return type: note that there are no pure "functions" in Java in the Python sense, there are only methods; but static methods in Java are most like pure functions in Python, because we can call them by specifying the class and method name, much like importing a Python module and using the module and its function name. So we might see a method defined in the Math class by static int factorial (int n) { int answer = 1; for (int i = 1; i= the arrays's length (given in Java by writing x.length, which is equivalent to Python's len(x)), is checked and raises an exception. Note if we declare a Java array of length 10, it is always length 10 no matter how many values are actually stored in the array; if we are using fewer values, we must have a variable to keep track of how many values are stored in the array. Note that the compiler allows us to call lb[5].bool() because each value in the lb array is of type LikeBoolean, and we can call .bool() on any value of the LikeBoolean type (based on the interface specifying that method). Java has an isinstance function that can determine whether a value comes from a specific type (or any of its subclasses, just as in Python). Unlike Python, Java does not build in lists, sets, or dictionaries. But its standard library includes interfaces that describe these data types and classes (sometimes multiple classes) that implement these interfaces. The interfaces are List, Set, and Map (which is like a dictionary, mapping a key to a value) and example classes that we can use that implement these interfaces interfaces, are ArrayList, HashSet, and HashMap. Because these data structures are supplied as classes, there is no special syntax for using them (e.g., no {} to create maps or [] to access/set maps). So instead of code like graph[source] we must write graph.get(source). And instead of graph[from] = destinations we must write graph.put(from,destintations) Some people say, it is "only syntax". But it does make codes a bit harder to read, understand, and write, especially when the key/value is more complicated. I don't have enough time to go into how these classes are used, but here is Java code that solves the Reachability problem from Programming Assignment #1 in ICS-33. Note that Map> (for defininig the graph) defines a type which is a Map from String to a Set containing String. If we were to try to put a key or associated value in the map that violated these type specification is, the Java compiler would detect and report an error at compile-time. ------------------------------------------------------------------------------ import edu.uci.ics.pattis.introlib.Prompt; import edu.uci.ics.pattis.introlib.TypedBufferReader; import edu.uci.ics.pattis.ics23.collections.*; import java.util.StringTokenizer; import java.io.EOFException; public class Reachable { private static void printGraph(Map> edges, String label) { System.out.println("\n"+label); List sources = new ArrayList(edges.keys()); Collections.sort(sources); for (String source : sources) System.out.println(" "+source+" -> "+edges.get(source)); } private static Set reachable(Map> graphMap, String start) { Set reachable = new ArraySet(); Queue toSearch = new ArrayQueue(); toSearch.add(start); while (!toSearch.isEmpty()) { String nextToProcess = toSearch.remove(); reachable.add(nextToProcess); Set destinations = graphMap.get(nextToProcess); if (destinations != null) for (String candidate : destinations) if (!reachable.contains(candidate)) toSearch.add(candidate); } return reachable; } public static void main(String[] args) { Map> graphMap = new ArrayMap>(); TypedBufferReader tbr = new TypedBufferReader("Enter name of file with graph"); for(;;) try { StringTokenizer st = new StringTokenizer(tbr.readLine(),";"); String from = st.nextToken(); String to = st.nextToken(); Set destinations = graphMap.get(from); if (destinations == null) { destinations = new ArraySet(); graphMap.put(from,destinations); } destinations.add(to); } catch (EOFException e) {break;} printGraph(graphMap, "Graph: source -> {destination} edges"); String start = Prompt.forString("\nEnter node to start from"); System.out.println("Node(s) reachable from " + start + " = " + reachable(graphMap,start)); } }