Java as a Second Language For Native Speakers of C++

(lecture title apologies to Tomasz Muldner)

These lecture notes are from The USNA Computer Science Advanced Data Structures course in the fall of 2006. They may be out of date. The current version can always be found at this location. You may also be interested in my Homepage here

"Theory of the Java Programming Language: Start with C++, throw out all the bad stuff- that leaves a very small language." -- Guy L. Steele

  1. We'll start with a little history and philosophy of Java. Why? Because if you get stuck, knowing the reasons they made design decisions might help you figure out what they are.
  2. Java is a programing language developed by Jim Gosling and Bill Joy at Sun Microsystems in the 1990s.
  3. Sun wanted to be the leader in ``intelligent'' consumer devices. They wanted every bit of electronics you have to be controlled by downloadable programs that would do things like model your behavior and do things for you (Your refrigerator tells you you're out beer, etc.)
  4. They began to design a programming language that could be used for these goals. It had to:
    1. Be robust. We can't have your microwave crashing on you.
    2. Be portable. Code that runs on one microwave needs to be able to run on a microwave from another manufacturer; easily; without compilation.
    3. Be safe. Can't allow malicious code to take over someone's appliances like some B horror movie.
  5. So they came up with the language Oak.
  6. It turns out that smart appliances were not working out as a business model. Too many technical challenges, and nobody was comfortable with the idea of their television being smarter than they were.
  7. But just as they were about to scrap the project, along came the world wide web, and someone pointed out that robust portable programs would be perfect for it. Thus was born Java
  8. How is it robust?
    1. Get rid of pointers. Pointer errors are the source of most programming bugs, so dump them.
    2. Use garbage collection. unused memory is automatically freed, so you never have memory leaks.
    3. Streamline design. It's easy to mess up in C++ because it carries all sorts of baggage from C, causing it to behave differently in different circumstances.
    4. Make it similar to C++ in syntax so programers can learn it easily.
  9. Java is portable by virtue of the Virtual Machine. Virtual Machine? what's that?
    1. Defined as ``A software emulation of a physical computing environment'' -FOLDOC
    2. To understand this, we need to talk about interpreters. The typical process of taking C++ source code and running a program is:
      1. Compile. Each line is converted to the target machine code
      2. Link. Various modules are linked together into one program.
      3. Run. The computer looks at each line of machine code and executes it.
    3. But there is another model-- the interpreter:
      1. Use a program called the interpreter to open the C++ file.
      2. The interpreter looks at a line of the C++, and converts it to the appropriate machine code. Then looks at the next line, etc.
      3. Slower, but has some advantages you'll find out about next semester.
    4. Now imagine a computer whose machine language was C++. This is not so far-fetched. In the 80s and early 90s there were machines whose native machine language was the programming language Lisp.
    5. From the point of view of the C++ program, can it tell the difference between running on the computer with C++ machine language and running in the interpreter?
    6. Thus, we can think of the interpreter as a machine, just not a real, but a virtual one. The idea of virtual machines is everywhere in CS.
    7. now all we have to do is put a C++ interpreter on every computer, and our C++ program will run everywhere without recompilation!
    8. But interpretation is usually slow, why? The process of going from C++ to machine language is slow, and we do it while the program is running.
    9. What if we took C++ and compiled it into a language that resembled most machine languages, and then interpreted that? Would that be faster?
    10. Java is compiled to Java bytecode, a machine-language-like language which is easily interpreted.
    11. Almost all web browsers have a java bytecode virtual machine (vm) buit into them, so the vm is everywhere.
    12. Viola! Portable code! Write (and compile) once. Run everywhere.
  10. Java is safe because of the virtual machine.
    1. Malicious programs are malicious because they change things on your computer that you don't want changed, or do things you done want done.
    2. The vm is the computer the Java program is running in. When the Java program ends, the vm program also ends, freeing all its resources: any changes the Java program made go insantly away.
    3. The vm can watch the behavior of the Java program. If a Java program comes from an untrusted source, the vm can prevent it from doing certain things, like writing to the hard drive or accessing the network.
  11. Does a VM make things slower? Yes, for everything there is a tradeoff. Just in time compilers help.
  12. Four well known truths of Java:
    1. Resembles C++. We want it to be easy to pick up.
    2. Everything is an Object. Simple design means more robust programs.
    3. There are no pointers. Pointers = Program Crashes.
    4. Write once, run everywhere, courtesy of the virtual machine.
  13. Four untruths about Java:
    1. Resembles C++. (Only on a superficial level. Just enough to get you in trouble.)
    2. Everything is an Object. (Not everything.)
    3. There are no pointers. (Everything is a pointer!)
    4. Write once, run everywhere. (Write once, debug everywhere).
    5. The original whitepaper
  14. Generally there are five phases
    1. Editing
    2. Compiling
    3. Loading
    4. Bytecode verification
    5. Execution
OK, enough background. Let's look at this Java stuff for real.
  1. Lets try:
    //Hello World
    class Foo {                                 //Class names are capitalized
        public static void main (String[] args) {
            System.out.printf("Hello World\n"); //different from cout, eh?
        }
    }
    
  2. Now we
    crab:~/public_html/2003-08/SI321/javabasics> javac Foo.java
    crab:~/public_html/2003-08/SI321/javabasics> java Foo
    Hello World
    
    Note that the file name is the same as the class name, and is also capitalized.
  3. Java does look syntacticly like C++. In particular:
    1. the language is made up of expressions, that generally look the same as C++
    2. Comments are the same.
    3. Types are the same (or similar).
    4. Accessing parts of objects is done with the dot '.'
    5. While and for loops are the same.
    6. Arrays start at 0
    7. many of the dirty operators ++, --, etc. are the same.
    8. Assignment is =, equality is ==.
    9. the program starts at main()
  4. Some differences:
    1. Functions are not called functions, they're called methods.
    2. main() is part of a class.
    3. With some exceptions, one class definition per file.
    4. There are two versions of most basic types, e.g. int and Integer.
    5. All exceptions must be caught.
    6. All variables are pointers, unless they're not.
    7. Garbage collection obviates free();
    8. and many more
  5. Something more complex?
    class Mystery {
        public static void main (String[] args) {
            int lo=1;
            int hi=1;
            System.out.println(lo);
            while (hi<56) {
                System.out.println("%d ",hi);
           	    hi = lo+ hi;
                lo = hi - lo;
            }
        }
    }
    
    What does that do?
  6. Each class can have its own main function. That is a major win because you can test the class in isolation without writing the entire porgram.
  7. Basic types:
    1. boolean -- true or false
    2. char -- just like C++. includes '\n' '\t' '\b' '\'' '\"' '\\'
    3. byte -- 8 bit integer.
    4. short -- 16 bit integer.
    5. int -- 32 bit integer.
    6. long -- 64 bit integer.
    7. float -- 32 bit floating point number.
    8. double -- 64 bit floating point number.
    Note, none of these are objects. For each basic numeric type, there is an object wrapper type. These are spelled with a capital letter, like other classes.
  8. Class types of numbers exist for two reasons:
    1. Some very general functions want to to any type as their argument. They can do that by declaring the argument to be any Object. Thus to pass in numbers, they have to be objects.
    2. This gives you the ability to create sub-classes of the numeric types.
    class Types {
        public static void main (String[] args) {
            long l = 42;
            Long el;
            double d= 2.718281828459045;
            Double dee;
    
            el = new Long(l);
            dee = new Double(d);
    	
            System.out.println(l + " " + el);
            System.out.println(d + " " + dee);
        }
    }
    
  9. Autoboxing
    public class Autoboxing {
        public static void main(String[] args) {
            Integer x = new Integer(3);
            int xTemp = x.intValue();
            xTemp++;
            x = new Integer(xTemp);
            System.out.println("incremented x = " + x);
            
            Integer y = 5;
            y++;
            System.out.println("incremented y = " + y);
        }
    }
    /*
     * Autoboxing2.java
     */
    import java.util.*;
    public class Autoboxing2 {
        public static void main(String[] args) {
            Vector v = new Vector();
            v.add(0, 42);
            System.out.println(v.firstElement() + " is a " + 
                               v.firstElement().getClass());
            v.add(1, 3.2);
            System.out.println(v.get(1) + " is a " + v.get(1).getClass());
        }
    
  10. Autoboxing/unboxing is slow. autoboxing and unboxing blur the distinction between primitive types and reference types, but they do not eliminate it.
  11. Strings work similarly:
    class Strings {
        public static void main (String[] args) {
            String m = "was a Roman";
            String c = "Cicero " + m;
    
            System.out.println(c);
        }
    }
    
  12. String Conversions. Almost all built in types can be converted to strings with the call toString. The number classes also have a valueOf which converts from string to that kind of number.
    class Convert {
        public static void main (String[] args) {
            Double e = new Double(2.718281828459045);
            String t = new String(e.toString());
            double d = Double.valueOf(t).floatValue();
    
            System.out.println(d); //lost some precision...
        }
    }
    
  13. Classes. Classes work the same way in Java as C++, with some syntactic changes. The basic format is: [<OptionalModifier>] class <className> [extends <superclass>] [implements <interface>]. The modifiers are:
    1. abstract. The class has abstract functions. These are functions that are not defined. Becasue of this, abstact classes cannot be used directly, but must be extended.
    2. final. The class cannot be extended.
    3. public. Other classes can use this class. The main class of a conceptual unit is public. For instance, if you wrote a Stack class, it might have other, helper classes, but Stack itself would be declared public so other classes could use it.
    4. friendly. If a class is not declared public, it is assumed to be friendly. Other classes in the same package can access this class. (more about packages later)
    5. These can be used in combination: public abstract MyClass { ...
  14. extends and implements are for inheritance (or the inheritance-like interfaces). We'll worry about that later.
  15. Instance Variables. Instance variables look much like they do in C++, but there is no public or private sections. Instead each variable is individually labelled with its modifier. Think about why. The modifiers are:
    1. public Anyone can access them.
    2. protected Only subclasses or classes in the same package can access them.
    3. private Only methods of the same class can access them.
    4. friendly Without a modifier, the variable is friendly. Only methods in the same package can access them. Plus 2 usage modifiers:
    5. static Variable is associated with the whole class, not instances of the class, like C++.
    6. final Treat the variable as a local constant.
  16. Methods. The things called functions in C++ are called methods in Java. Strictly speaking, java is more correct, as ``function'' is generally more precisely defined than its use in C++. All methods are part of classes. Why? They are defined as: [<OptionalModifier1>] [<OptionalModifier2>] <returnType> <methodname> ([<Parameters>]) { } The first modifier can be:
    1. abstract The method is not defined (it looks just like a C++ prototype). The class must be abstract.
    2. final This method cannot be overridden by a subclass.
    3. static This method is associated with all the objects of a class, not a particular instance. Double.valueOf(t) is an example of a call to a static method. In my experience, static methods cause lots of heartburn, so you'll need to watch them carefully. We'll talk about them in a bit.
    4. No modifier.
    The second modifiers can be:
    1. public Anyone can call it.
    2. protected Can be called from subclasses or classes in the same package.
    3. private Only methods of the same class can call it.
    4. friendly Without a modifier, the method is friendly. Only methods in the same package can call them.
  17. Argument types and return types are the same as C++, but no pointers. Arguments are usually called parameters. All parameters are pass by value, strictly speaking. But that can be very misleading... much more about it later.
  18. Constructors are the same as C++. They cannot be abstract, static, or final.
  19. Most operators are the same as C++. But, while Methods can be overloaded, operators cannot. Why?
  20. Automatic promotion works for Java, but not demotion- whenever precision is lost. Instead you need to perfrom an explicit cast to change the type.
    double d = 3.2;
    int i = d;  //won't work
    int j = 4;
    d = j; //will work
    i = (int)d; //will work
    e = (double)i/(double)j; //will work
    
  21. Implicit casting is allowed with strings when using the + operator:
    String s = 42; //nope
    String s = Integer.toString(42); //yep
    String s = ``int is: `` + 42; //yep. 
    
  22. Control flow are all just like C++: if, for, while, do-while, break, continue.
  23. ONE BIG DIFFERENCE. The type of the test in the ifs and whiles must be boolean.
    while (a = 1) {
      a++;   //oops a bug.  in C++ this would be hard to catch.
    }        //in java, it won't compile
    
  24. Arrays. Arrays look like C++ arrays but have one special feature: all array accesses are checked to make sure they are not out of bounds. Who does this? The virtual machine!
  25. Arrays have a built in way to get their length:
    int[] a = new int [10];
    System.out.println(a.length);
    int[][] a = new int[5][10];
    System.out.println(a[0].length);
    System.out.println(a.length);
    
  26. I/O
    1. We already know about System.out.print System.out.println and System.out.printf. Note that they are method calls on the built in object System.out.
    2. Input is slightly trickier, and there are now multiple ways to do it. There is a System.in object with a read() method, but it is designed to read one character at a time. Noone wants to deal with this so one thing to do is build a ``Scanner'' from the System.in object:
      class InputTest {
          public static void main (String[] args)  {
             java.util.Scanner stdin = new java.util.Scanner(System.in);//make the scanner 
              String line;
             while (stdin.hasNextLine()){
                 line = stdin.nextLine();
                 double d = Double.valueOf(line);
                 System.out.println(d*2);
             }	
          }
      }
      
    3. What if your line contains more than one thing? use stdin.next():
      class InputTest {
          public static void main (String[] args)  {
             java.util.Scanner stdin = new java.util.Scanner(System.in);//make the scanner 
              String line;
             while (stdin.hasNext()){
                 line = stdin.next();
                 double d = Double.valueOf(line);
                 System.out.println(d*2);
             }	
          }
      }
      
    4. What if there might be other words in between the numbers, words we don't care about? in C++, if we know how many words we could try:
      cin >> num1 >> junk >> junk >> junk >> num2;
      but if we don't know how many words there might be, we would read in everything as strings and then convert. In the java scanner, we can ask for the next integer:
      class InputTest {
          public static void main (String[] args)  {
             java.util.Scanner stdin = new java.util.Scanner(System.in);//make the scanner 
             String line;
             while (stdin.hasNext()){
                 if(stdin.hasNextInt()) {
                     int i = stdin.nextInt();
      	       System.out.println(i*2);
      	   } else 
      	       line = stdin.next();
             }	
          }
      }
      
    5. What if our numbers are separated by the word fish instead of whitespace? in C++ we would need to read in the whole line and break it apart by hand (or mess with the stream operators), but in the java scanner we can:
      class InputTest {
          public static void main (String[] args)  {
             java.util.Scanner stdin = new java.util.Scanner(System.in);//make the scanner 
             stdin.useDelimiter("\\s*fish\\s*");
             System.out.println(stdin.delimiter());
             String line;
             while (stdin.hasNext()){
                 if(stdin.hasNextInt()) {
                     int i = stdin.nextInt();
      	       System.out.println(i*2);
      	   } else 
      	       System.out.println("toss: " + stdin.next());
             }	
          }
      }
      
    6. Disadvantages: This is really wordy for something that is generally much more concise in C++.
    7. Advantages- The delimiter symbol is built in to C++. Here, WE pick it. Look what I did getting two separate strings from the input. Can't do that with >>.
  27. Formatted output.
  28. Foreach
    1. Often we set up loops like the following:
       char [] a = new char[10];
       for (int i = 0;i
       such that we would like to do that a little easier:
       
       for (char theC : a){
          System.out.printf("%c",theC);
       }
       
      This is called the "for each" loop. But the real win here is that it works not just for arrays but also for any collection that implements the iterable interface. That won't make a lot of sense right now, but it will later. For now, just be away that there are lots of things that we can iterate through.
  29. Pointers
    1. So far, it appears that I haven't shown you anything about pointers yet, but in fact I have. All variables of any type that is an object are special kind of pointers called References. That's what I mean by everything is a pointer.
    2. References are limited sorts of pointers: You can't point just anywhere, you must point to an object or to null; You can't perform pointer arithmetic. (do you know how to do pointer arithmetic? It's sinful: for(p1=a1,p2=a2;p1-a1>size;p2++=p1++);
    3. Since most everything is a reference, we don't make explicit the differnce between regular variables and pointers.
    4. This has some interesting consequences:
      class Mountain {
          public static void main (String[] args) throws java.io.IOException {
             Mountain[] m = new Mountain[10]; //creates array of REFERENCES
             int i = 0;
             java.util.Scanner stdin =  new java.util.Scanner(System.in);
             //read in some moutains
             String line;
             while (stdin.hasNextLine()) {
                 m[i]= new Mountain(stdin.next(), stdin.next());
                 i++;
             }
             //find the highest
             Mountain high = m[0];
             for (int j = 1; j <i ; j++) 
                 high = Mountain.higher(high,m[j]);
             System.out.println(high.name);
          }
      
          public String name;
          public int altitude; //in meters
      
          public Mountain (String nm, int alt) {
             name = nm;
             altitude = alt;
          }
          public Mountain () { 
             name = "frank"; altitude = 0;}
      
          public void setMountain (String nm, int alt) {
             name = nm;
             altitude = alt;
          }
          
          public static Mountain higher (Mountain a, Mountain b) {
             if (a.altitude > b.altitude) 
                 return a;
             else 
                 return b;
          }
      }
      
    5. By passing in objects by reference, we always get the whole object, and don't have to worry about what the shallow copy should do.
    6. With objects, pass by reference by default makes sense. If I hand you a book, and you tear 3 pages out and hand it back to me, I should have a book missing 3 pages.
    7. That basic types don't work that way is often considered a design flaw. The java folks did have a reason for it though- if every number was wrapped in a object, numerical calculation would be really slow. It was a compromise. Modern compiler technology may have made the compromise unecessary. oops.
    8. references are changed through the assignment operator, with the possible accumulation of garbage. But that's, ok.
  30. Static Methods. This is a good point to talk more about static methods.
    1. A static method is one that doesn't operate on a particular object. Look at the higher method above, and compare it to: high.higher(m[j]).
          public Mountain higher (Mountain b) {
             if (altitude > b.altitude) 
                 return this;
             else 
                 return b;
          }
      
      This second method is a normal member method on the Mountain class. It takes only one argument, because the other argument is implicit, whatever object it was called on:
    2. In this case, using a static method doesn't seem to make much sense but there are some times when its useful to have comparators that are static, as we'll see in a week or so.
    3. In general static methods are used in places where in C++ it wouldn't be a member function at all. main() is a great example. it makes no sense for main to be part of a particular object, but in java we HAVE NO CHOICE, so we make it a static method. That's important because it it weren't static, we could only call it when we have an object, but we couldn't have any objects because we haven't entered main yet!
    4. Basic math functions are another good example. Take pow, which raises something to an exponent. If I want to raise 4.2 to 6.5 I should call pow, but since pow has to be a member of some object, what object? If we make 4.2 into a Double, maybe we could do something like:
      Double foo = new Double(4.2);
      double bar = foo.pow(6.5);
      
      but who wants to do that all the time? It's wordy and slow. If we have a Math class that contains lots of static methods, we can avoid creating an object at all:
      double bar = Math.pow(4.2,6.5);
      
    5. A final comment about main. Since main is a member method, we can have multiple mains, one in each class. This is a good idea. Every class has its own main- this main is used to test the class. That way you can write a class that is to be used in a larger project, and test it as you write it, before you plug it in to the rest of the project.
  31. Dynamic Memory, linked lists etc.
    1. So, without explicit use of pointers, how do you things like linked lists, which include pointers?
    2. We now know that everything is a reference, so when we declare a class node with an instance variable of type node, that's ok (not circular), because the instance variable is really a reference to a node.
    class Node {
        public static void main (String[] args) {
            Node head = new Node("joe",null);
            head.next = new Node("Norbert", null);
            Node walk = head;
            while(walk!=null) {
                System.out.println(walk.name);
                walk=walk.next;
            }
        }
    
        public String name;
        public Node next;
        
        public Node (String nm, Node n) {
            name = nm;
            next = n;
        }
    }
    
  32. Ok, final test. What is the output?
    class FinalTest {
        public static void main (String[] args) {
            int i = 3, j =9;
            FinalTest i2 = new FinalTest(4), j2 = new FinalTest(12);
            i = j;
            j = 18;
            System.out.println(i);
            i2 = j2;
            j2.change(24);
            System.out.println(i2.val());
        }
    
        public int ival;
    
        public FinalTest(int i) {
            ival = i;
        }
        public void change(int i) {
            ival = i;
        }
        public int val() { return ival;}
    }
    
  33. I've previously mentioned packages several times. What are they? Packages are just one part of a complex system for organizing files:
    1. There can be only one public class per file!
    2. It is expected that the file name will be the same as the public class.
    3. Sets of related files can be grouped together in a package by placing package <name> at the begining of each file.
    4. The director containing the package must have the same name as the package.
    5. packages can have subpackages, just as directories can have subdirectories. So java.io is a package io inside a package java and is located in some directory java/io.
    6. Packages are useful to provide limited access to firendly classes.
    7. packages are also useful to include lots of code as with a library in C++. These things can be be included by hand (in fact we've already done so) but its much easier to import a package.
    8. When we say java.util.Scanner, we are refering to the class Scanner in the package java.util. If we import that package, we no longer have to use the java.io qualifier.
    9. watch for duplicate names.
    10. Java is unique in that filenames and directory structure play a specified role in the program.
  34. Making copies of objects.
    1. If everything is a reference, and assignments just change where pointers point, what do we do when we want a duplicate of an object?
    2. In C++ terminology, there is a default copy constructor which we can use to make our duplicate.
    3. In java, it is called cloning. We can call obj.clone() to make a duplicate of an object.
    4. clone() by default makes a shallow copy, i.e. it copies all fields, including references, but not the things references point to.
    5. If you want to make a deeper copy, you need to write your own version of clone for your class.
    6. Why default to shallow copy?
  35. Java is also unique in that it has a built-in automatic documentation generation mechanism.
    1. The javadoc program takes specially constructed comments and generates an HTML description of the code. http://java.sun.com/j2se/javadoc/writingdoccomments/index.html
    2. A comment that uses javadoc specifiers must begin with /**
    3. It uses the first sentence of the block as a summary.
    4. The rest of the comment is tha main description.
    5. It uses specialized tags introduced with @ for extra information.
    6. only works on public or protected classes.
    /**
     *
     * A Test of your understanding of references.
     * This class creates ints and objects containing ints.  It tests to see
     * what assignment does in each case.
     * @author Prof Crabbe
     * @see "Course home page"
     */
    public class FinalTest {
        public static void main (String[] args) {
            int i = 3, j =9;
            FinalTest i2 = new FinalTest(4), j2 = new FinalTest(12);
            i = j;
            j = 18;
            System.out.println(i);
            i2 = j2;
            j2.change(24);
            System.out.println(i2.val());
        }
    
        public int ival;
    
        /** 
         * Constructor with an argument initializing the value.
         * 
         * @param i initial height in integer meters.
         */
        public FinalTest(int i) {
            ival = i;
        }
        /**
         * Method that changes the value.
         *
         * @param i new height in integer meters.
         */
        public void change(int i) {
            ival = i;
        }
        /**
         * Method that returns the value.
         *
         * @return returns the height in integer meters.
         */
        public int val() { return ival;}
    }
    
  36. finalize
    1. Because of the garbage collector, we don't need to free up memory when done with an object, so destructors are needed less often.
    2. Some resources should be freed when an object goes away, for instance if the object opened a file, the file should be closed when the object is taken out with the garbage.
    3. To do this, we write a method called finalize to take care of this.