Java - First Impressions for a C++ Programmer
Steven Zeil
Moving from C++ to Java
-
First, the good news …
- You already know most of the language
- Syntax is largely the same as C++
- Semantics are similar
- You already know most of the language
-
Then, the bad news …
-
The “standard libraries” of Java and C++ are very, very different
-
-
In this lesson, I’ll highlight the differences between Java and C++ that you will need to get started.
1 Program Structure
Hello Java
The traditional starting point:
public class HelloWorld {
public static void main (String[] argv)
{
System.out.println ("Hello, World!");
}
}
-
Why is main() inside a class?
- Because Java has no standalone functions.
- All functions must be inside a class.
-
Any class with a “public static void” main function taking an array of Strings cane be executed.
Class Syntax
C++
class MailingList {
private:
struct Node {
Contact data;
Node* next;
};
Node* first;
public:
MailingList()
{
first = nullptr;
}
⋮
};
Java
public class MailingList {
private class Node {
Contact data;
Node next;
}
private Node first;
public MailingList()
{
first = null;
}
⋮
}
-
public
andprivate
are labels for each declaration, not names of “regions” -
No trailing semi-colon
Packages
A Java package is like a C++ namespace:
- A container of classes and other, smaller packages/namespaces
C++
namespace myUtilities {
class Randomizer {
⋮
};
};
Java
package myUtilities;
class Randomizer {
⋮
}
- Becomes part of the fully qualified name1 of a class
C++ | Java |
---|---|
myUtilities::Randomizer r; |
myUtilities.Randomizer r; |
We Can Have Short-Cuts
C++
using myUtilities::Randomizer
⋮
Randomizer r;
Java
import myUtilities.Randomizer;
⋮
Randomizer r;
We Can Have Shorter Short-Cuts
C++
using namespace myUtilities;
⋮
Randomizer r;
Java
import myUtilities.*;
⋮
Randomizer r;
Cultural Difference
-
C++ programmers rarely invent their own namespaces
- And use
using
to circumventstd::
- And use
-
Java programmers frequently invent packages
- Often multiple packages in a single project
- Leaving things in the untitled default package is considered the sign of a beginner
2 Program Structure == File Structure
2.1 Class == File
-
In C++, I can put a class MailingList in student.h and automobile.cpp if I want
-
Not so in Java:
A class named “
MailingList
” must be placed in a file named MailingList.java- And upper/lower case do count!
Classes are not Split into Two Files
-
C++ distinguishes between class
- declarations: usually placed in a header (.h file), and
- definitions: usually placed in a compilation unit (.cpp file)
e.g.,
mailinglist.h#ifndef MAILINGLIST_H #define MAILINGLIST_H #include <iostream> #include <string> #include "contact.h" /** A collection of names and addresses */ class MailingList { public: MailingList(); MailingList(const MailingList&); ~MailingList(); const MailingList& operator= (const MailingList&); // Add a new contact to the list void addContact (const Contact& contact); // Remove one matching contact void removeContact (const Contact&); void removeContact (const Name&); // Find and retrieve contacts bool contains (const Name& name) const; Contact getContact (const Name& name) const; // combine two mailing lists void merge (const MailingList& otherList); // How many contacts in list? int size() const; bool operator== (const MailingList& right) const; bool operator< (const MailingList& right) const; private: struct ML_Node { Contact contact; ML_Node* next; ML_Node (const Contact& c, ML_Node* nxt) : contact(c), next(nxt) {} }; int theSize; ML_Node* first; ML_Node* last; // helper functions void clear(); void remove (ML_Node* previous, ML_Node* current); friend std::ostream& operator<< (std::ostream& out, const MailingList& addr); }; // print list, sorted by Contact std::ostream& operator<< (std::ostream& out, const MailingList& list); #endif
and
mailinglist.cpp#include <cassert> #include <iostream> #include <string> #include <utility> #include "mailinglist.h" using namespace std; using namespace rel_ops; MailingList::MailingList() : first(nullptr), last(nullptr), theSize(0) {} MailingList::MailingList(const MailingList& ml) : first(nullptr), last(nullptr), theSize(0) { for (ML_Node* current = ml.first; current != nullptr; current = current->next) addContact(current->contact); } MailingList::~MailingList() { clear(); } const MailingList& MailingList::operator= (const MailingList& ml) { if (this != &ml) { clear(); for (ML_Node* current = ml.first; current != nullptr; current = current->next) addContact(current->contact); } return *this; } // Add a new contact to the list void MailingList::addContact (const Contact& contact) { if (first == nullptr) { // add to empty list first = last = new ML_Node(contact, nullptr); theSize = 1; } else if (contact > last->contact) { // add to end of non-empty list last->next = new ML_Node(contact, nullptr); last = last->next; ++theSize; } else if (contact < first->contact) { // add to front of non-empty list first = new ML_Node(contact, first); ++theSize; } else { // search for place to insert ML_Node* previous = first; ML_Node* current = first->next; assert (current != nullptr); while (contact < current->contact) { previous = current; current = current->next; assert (current != nullptr); } previous->next = new ML_Node(contact, current); ++theSize; } } // Remove one matching contact void MailingList::removeContact (const Contact& contact) { ML_Node* previous = nullptr; ML_Node* current = first; while (current != nullptr && contact > current->contact) { previous = current; current = current->next; } if (current != nullptr && contact == current->contact) remove (previous, current); } void MailingList::removeContact (const Name& name) { ML_Node* previous = nullptr; ML_Node* current = first; while (current != nullptr && name > current->contact.getName()) { previous = current; current = current->next; } if (current != nullptr && name == current->contact.getName()) remove (previous, current); } // Find and retrieve contacts bool MailingList::contains (const Name& name) const { ML_Node* current = first; while (current != nullptr && name > current->contact.getName()) { previous = current; current = current->next; } return (current != nullptr && name == current->contact.getName()); } Contact MailingList::getContact (const Name& name) const { ML_Node* current = first; while (current != nullptr && name > current->contact.getName()) { previous = current; current = current->next; } if (current != nullptr && name == current->contact.getName()) return current->contact; else return Contact(); } // combine two mailing lists void MailingList::merge (const MailingList& anotherList) { // For a quick merge, we will loop around, checking the // first item in each list, and always copying the smaller // of the two items into result MailingList result; ML_Node* thisList = first; const ML_Node* otherList = anotherList.first; while (thisList != nullptr and otherList != nullptr) { if (thisList->contact < otherList->contact) { result.addContact(thisList->contact); thisList = thisList->next; } else { result.addContact(otherList->contact); otherList = otherList->next; } } // Now, one of the two lists has been entirely copied. // The other might still have stuff to copy. So we just copy // any remaining items from the two lists. Note that one of these // two loops will execute zero times. while (thisList != nullptr) { result.addContact(thisList->contact); thisList = thisList->next; } while (otherList != nullptr) { result.addContact(otherList->contact); otherList = otherList->next; } // Now result contains the merged list. Transfer that into this list. clear(); first = result.first; last = result.last; theSize = result.theSize; result.first = result.last = nullptr; result.theSize = 0; } // How many contacts in list? int MailingList::size() const { return theSize; } bool MailingList::operator== (const MailingList& right) const { if (theSize != right.theSize) // (easy test first!) return false; else { const ML_Node* thisList = first; const ML_Node* otherList = right.first; while (thisList != nullptr) { if (thisList->contact != otherList->contact) return false; thisList = thisList->next; otherList = otherList->next; } return true; } } bool MailingList::operator< (const MailingList& right) const { if (theSize < right.theSize) return true; else { const ML_Node* thisList = first; const ML_Node* otherList = right.first; while (thisList != nullptr) { if (thisList->contact < otherList->contact) return true; else if (thisList->contact > otherList->contact) return false; thisList = thisList->next; otherList = otherList->next; } return false; } } // helper functions void MailingList::clear() { ML_Node* current = first; while (current != nullptr) { ML_Node* next = current->next; delete current; current = next; } first = last = nullptr; theSize = 0; } void MailingList::remove (MailingList::ML_Node* previous, MailingList::ML_Node* current) { if (previous == nullptr) { // remove front of list first = current->next; if (last == current) last = nullptr; delete current; } else if (current == last) { // remove end of list last = previous; last->next = nullptr; delete current; } else { // remove interior node previous->next = current->next; delete current; } --theSize; } // print list, sorted by Contact std::ostream& operator<< (std::ostream& out, const MailingList& list) { MailingList::ML_Node* current = list.first; while (current != nullptr) { out << current->contact << "\n"; current = current->next; } out << flush; return out; }
-
Java puts the entire class into one file
MailingList.javapackage mailinglist; /** * A collection of names and addresses */ public class MailingList implements Cloneable { /** * Create an empty mailing list * */ public MailingList() { first = null; last = null; theSize = 0; } /** * Add a new contact to the list * @param contact new contact to add */ public void addContact(Contact contact) { if (first == null) { // add to empty list first = last = new ML_Node(contact, null); theSize = 1; } else if (contact.compareTo(last.contact) > 0) { // add to end of non-empty list last.next = new ML_Node(contact, null); last = last.next; ++theSize; } else if (contact.compareTo(first.contact) < 0) { // add to front of non-empty list first = new ML_Node(contact, first); ++theSize; } else { // search for place to insert ML_Node previous = first; ML_Node current = first.next; assert (current != null); while (contact.compareTo(current.contact) < 0) { previous = current; current = current.next; assert (current != null); } previous.next = new ML_Node(contact, current); ++theSize; } } /** * Remove one matching contact * @param c remove a contact equal to c */ public void removeContact(Contact c) { ML_Node previous = null; ML_Node current = first; while (current != null && c.compareTo(current.contact) > 0) { previous = current; current = current.next; } if (current != null && c.equals(current.contact)) remove(previous, current); } /** * Remove a contact with the indicated name * @param name name of contact to remove */ public void removeContact(String name) { ML_Node previous = null; ML_Node current = first; while (current != null && name.compareTo(current.contact.getName()) > 0) { previous = current; current = current.next; } if (current != null && name == current.contact.getName()) remove(previous, current); } /** * Search for contacts * @param name name to search for * @return true if a contact with an equal name exists */ public boolean contains(String name) { ML_Node current = first; while (current != null && name.compareTo(current.contact.getName()) > 0) { current = current.next; } return (current != null && name == current.contact.getName()); } /** * Search for contacts * @param name name to search for * @return contact with that name if found, null if not found */ public Contact getContact(String name) { ML_Node current = first; while (current != null && name.compareTo(current.contact.getName()) > 0) { current = current.next; } if (current != null && name == current.contact.getName()) return current.contact; else return null; } /** * combine two mailing lists * */ public void merge(MailingList anotherList) { // For a quick merge, we will loop around, checking the // first item in each list, and always copying the smaller // of the two items into result MailingList result = new MailingList(); ML_Node thisList = first; ML_Node otherList = anotherList.first; while (thisList != null && otherList != null) { if (thisList.contact.compareTo(otherList.contact) < 0) { result.addContact(thisList.contact); thisList = thisList.next; } else { result.addContact(otherList.contact); otherList = otherList.next; } } // Now, one of the two lists has been entirely copied. // The other might still have stuff to copy. So we just copy // any remaining items from the two lists. Note that one of these // two loops will execute zero times. while (thisList != null) { result.addContact(thisList.contact); thisList = thisList.next; } while (otherList != null) { result.addContact(otherList.contact); otherList = otherList.next; } // Now result contains the merged list. Transfer that into this list. first = result.first; last = result.last; theSize = result.theSize; } /** * How many contacts in list? */ public int size() { return theSize; } /** * Return true if mailing lists contain equal contacts */ public boolean equals(Object anotherList) { MailingList right = (MailingList) anotherList; if (theSize != right.theSize) // (easy test first!) return false; else { ML_Node thisList = first; ML_Node otherList = right.first; while (thisList != null) { if (!thisList.contact.equals(otherList.contact)) return false; thisList = thisList.next; otherList = otherList.next; } return true; } } public int hashCode() { int hash = 0; ML_Node current = first; while (current != null) { hash = 3 * hash + current.hashCode(); current = current.next; } return hash; } public String toString() { StringBuffer buf = new StringBuffer("{"); ML_Node current = first; while (current != null) { buf.append(current.toString()); current = current.next; if (current != null) buf.append("\n"); } buf.append("}"); return buf.toString(); } /** * Deep copy of contacts */ public Object clone() { MailingList result = new MailingList(); ML_Node current = first; while (current != null) { result.addContact((Contact) current.contact.clone()); current = current.next; } return result; } private class ML_Node { public Contact contact; public ML_Node next; public ML_Node(Contact c, ML_Node nxt) { contact = c; next = nxt; } } private int theSize; private ML_Node first; private ML_Node last; // helper functions private void remove(ML_Node previous, ML_Node current) { if (previous == null) { // remove front of list first = current.next; if (last == current) last = null; } else if (current == last) { // remove end of list last = previous; last.next = null; } else { // remove interior node previous.next = current.next; } --theSize; } }
-
Function bodies are written immediately after their declaration
- To C++ programmers, these look like inline functions
2.2 Package == Directory
-
In C++, we can put our files into any directory we want, as long as we give the appropriate paths in our compilation commands
-
In Java, all items that belong in package
packageName
must be stored in a directory packageName/
2.3 Packaging and Compiling
Suppose we are building a Java project in ~/jproject.
If we have a class
public class HelloWorld {
public static void main (String[] argv)
{
System.out.println ("Hello, World!");
}
}
-
It would be stored in ~/jproject/HelloWorld.java.
/home/yourName/ |--jproject/ |--|--HelloWorld.java
-
The commands to compile and run this would be
cd ~/jproject javac HelloWorld.java java HelloWorld
/home/yourName/ |--jproject/ |--|--HelloWorld.class |--|--HelloWorld.java
Packaging and Compiling 2
Suppose we are building a Java project in ~/jproject.
If we have a class
package Foo;
public class HelloWorld {
public static void main (String[] argv)
{
System.out.println ("Hello, World!");
}
}
-
It would be stored in ~/jproject/Foo/HelloWorld.java.
/home/yourName/ |--jproject/ |--|--Foo/ |--|--|--HelloWorld.java
-
The commands to compile and run this would be
cd ~/jproject javac Foo/HelloWorld.java java Foo.HelloWorld
/home/yourName/ |--jproject/ |--|--Foo/ |--|--|--HelloWorld.class |--|--|--HelloWorld.java
Important: Notice that we compile from the project’s “base directory”, not from the directory that contains the actual
.java
files.
Packaging and Compiling 3
Suppose we are building a Java project in ~/jproject.
If we have a class
package Foo.Bar;
public class HelloWorld {
public static void main (String[] argv)
{
System.out.println ("Hello, World!");
}
}
-
It would be stored in ~/jproject/Foo/Bar/HelloWorld.java.
/home/yourName/ |--jproject/ |--|--Foo/ |--|--|--Bar/ |--|--|--|--HelloWorld.java
-
The commands to compile and run this would be
cd ~/jproject javac Foo/Bar/HelloWorld.java java Foo.Bar.HelloWorld
/home/yourName/ |--jproject/ |--|--Foo/ |--|--|--Bar/ |--|--|--|--HelloWorld.class |--|--|--|--HelloWorld.java
Is Java Just Trying to Be Annoying?
Actually, no.
-
To compile a C++ program, we have to give explicit paths to each compilation unit in our compilation commands and we need to
#include
each header, giving the correct path to it. -
The Java compiler finds the source code of our other classes that we use by looking at
- the fully qualified name (e.g.,
MyUtilities.Randomizer
), or - our
import
statements
- the fully qualified name (e.g.,
-
It just follows the package/class names to find the directory and file in which the rest of our code is located.
-
At run time, the loader acts similarly to find our compiled code (the
.class
files)
3 Swimming in Pointers
Primitives are Familiar
-
int, long, float, double
- boolean, not “bool”
- char is 16 bits, not 8, to permit the use of Unicode
-
Variables of these types behave as you would expect
int x = 2;
int y = x;
++x;
System.out.println ("x=" + x + " y=" + y);
prints “x=3 y=2”
3.1 Everything Else is a Pointer!!
void foo(java.awt.Point p) {
p.x = 1;
java.awt.Point w = p;
w.x = 2;
System.out.println ("p.x=" + p.x + " w.x=" + w.x);
}
prints “p.x=2 w.x=2”
- Why did
p.x
change value?- Because p and w are references (pointers), so
java.awt.Point w = p;
causes w to point to the same value that p does.
3.2 Lots of Allocation
Because all new class variables are really pointers, all new class values have to be created on the heap:
C++
Point p (1,2);
Java
Point p = new Point(1,2);
Arrays of Pointers
C++ programmers need to be particularly careful dealing with arrays:
C++
int a[10];
Point* p = new Point[10];
Java
int[] b = new int[10];
Point[] q = new Point[10];
for (int i = 0; i < q.length; ++i)
q[i] = new Point();
Without the loop, q would actually be an array of null pointers.
3.3 Because there are so many pointers
-
Sharing in Java is much more common than copying
- A definite cultural difference from C++, where [copying]{doc:big3} is a pervasive concern.
- If you do need a distinct copy in Java, use the clone() function
- We’ll see later that this is a “standard” function on all Java objects
-
Java features automatic garbage collection, so we don’t have to worry about deleting pointers to recover storage.
3.4 There are no “delete”s in Java!
Java features automatic garbage collection.
-
Garbage is the term for any block of memory allocated on the heap that can no longer be reached, directly or indirectly, via any valid data.
-
Java runs a process that watches for the creation of garbage and scavenges (recovers) that memory.
3.4.1 Garbage Example
Consider the following code:
C++
void printSorted(const vector<int>& v)
{
int* array = new int[v.size()];
for (int i = 0; i < v.size(); ++i)
array[i] = v[i];
sort (array, array+v.size());
for (int i = 0; i < v.size(); ++i)
cout << array[i] << endl;
}
Java
void printSorted(ArrayList<int> v)
{
int[] array = new int[v.size()];
for (int i = 0; i < v.size(); ++i)
array[i] = v.get(i);
Arrays.sort (array);
for (int i = 0; i < v.size(); ++i)
System.out.println("" + array[i]);
}
- Both functions have a local variable,
array
, that points to a newly allocated array of integers. - Neither function deletes
array
when it is done. - When we exit the function, all local variables, including the
array
pointer, are destroyed. - At that moment, the block of storage allocated on the heap becomes garbage – we have lost our only pointer to it.
- For the C++ program, we consider this to be an error – a memory leak.
- For the Java program, that’s just business as usual. We trust that, eventually, the garbage collector will find and recover that block of storage.
(If you are interested in how garbage collection works, you can read about it here.)
3.5 Pointers and Equality
Beware of ==
-
The == operator works like you would expect on primitives
int x = 23; int y = 23; if (x == y) System.out.println ("Of course!");
-
But for class objects, == is comparing addresses:
Point p = new Point(1,2); Point q = new Point(1,2); if (p == q) System.out.println ("Not gonna happen"); else System.out.println ("Surprise!");
equals
To compare objects to see if they have the same contents, use the equals function:
Point p = new Point(1,2);
Point q = new Point(1,2);
if (p.equals(q))
System.out.println ("That's better.");
else
System.out.println ("Pay no attention...");
4 Exceptions
An exception is a run-time error signal.
-
It may be signalled (thrown) by the underlying runtime system …
-
or by programmer-suppled code
-
Programs can catch exceptions and handle them …
-
or let them go and allow the program to abort
Playing Catch
Try compiling and (if successful), running each of the following:
import java.io.*;
/**
Demo of a program that may throw exceptions.
@param argv The name of a file to open for input
*/
public class OpenFile1 {
/**
Attempt to open a file
*/
static void openFile (String fileName) {
FileReader reader = new FileReader(fileName);
}
/**
Attempt to open the file whose name is given in
the first command line parmaeter
*/
public static void main (String[] argv) {
String fileName = argv[1];
openFile (fileName);
}
}
import java.io.*;
/**
Demo of a program that may throw exceptions.
@param argv The name of a file to open for input
*/
public class OpenFile2 {
/**
Attempt to open a file
*/
static void openFile (String fileName)
throws java.io.FileNotFoundException
{
FileReader reader = new FileReader(fileName);
}
/**
Attempt to open the file whose name is given in
the first command line parmaeter
*/
public static void main (String[] argv) {
String fileName = argv[0];
openFile (fileName);
}
}
import java.io.*;
/**
Demo of a program that may throw exceptions.
@param argv The name of a file to open for input
*/
public class OpenFile3 {
/**
Attempt to open a file
*/
static void openFile (String fileName)
throws java.io.FileNotFoundException
{
FileReader reader = new FileReader(fileName);
}
/**
Attempt to open the file whose name is given in
the first command line parmaeter
*/
public static void main (String[] argv) {
try {
openFile (argv[0]);
}
catch (java.io.FileNotFoundException ex)
{
System.err.println ("Something is wrong with the file: " + ex);
}
System.out.println ("All done");
}
}
using both the names of existing and non-existing files, or no name at all.
Unchecked Exceptions
Exceptions come in two main kinds: checked and unchecked
-
unchecked exceptions could arise almost anywhere
- e.g.,
NullPointerException
,ArrayIndexOutOfBoundsException
- e.g.,
-
functions need not declare that they might throw these
Checked Exceptions
checked exceptions are more specialized
-
include all programmer-defined exceptions
-
functions must declare if they can throw these
Another Cultural Difference
-
C++ actually has exceptions as well.
-
Same
throw
andtry
–catch
syntax -
Can’t declare what exceptions a function is known to throw
-
-
But they are used much more widely in Java
5 Corresponding Data Structures
5.1 The Java API
The Java API (the equivalent of the C++ std
library) is huge, but well documented
-
Focus initially on packages
java.lang
,java.io
, andjava.util
-
BTW, You can use the javadoc to generate the same kind of documentation for your own code. Example
5.2 Strings
C++ std::string : Java java.lang.String
- Strings in Java are immutable
- You cannot change the contents of a string value
- But you can compute a slightly different value and make a string variable point to the different value
C++
string s = ...
s[s.size()/2] = 'b';
s = s + s;
Java
String s = ...
s = s.substring(0,s.length()/2)
+ 'b'
+ s.substring(s.length()/2+1);
s = s + s;
If You Need to Change a Java String…
…use a StringBuilder
StringBuilder sb = new StringBuilder();
String line = input.readline();
while (line != null) {
sb.append(line);
sb.append("\n");
line = input.readline();
}
String allTheText = sb.toString();
5.3 Container Data Structures
vector : ArrayList
C++
vector<string> v;
v.push_back("foo");
cout << v[0] << endl
Java
ArrayList<String> v = new ArrayList<String>();
v.add("foo");
System.out.println (v.get(0));
list : LinkedList
C++
list<string> L;
L.push_back("foo");
cout << L.front() << endl
Java
LinkedList<String> L = new LinkedList<String>();
L.add("foo");
System.out.println (L.getFirst());
set : TreeSet
C++
set<string> s;
s.insert("foo");
cout << s.count("foo") << endl
Java
TreeSet<String> S = new TreeSet<String>();
S.add("foo");
System.out.println ("" + S.contains("foo"));
unordered_set : HashSet
C++
unordered_set<string> s;
s.insert("foo");
cout << s.count("foo") << endl
Java
HashSet<String> S = new HashSet<String>();
S.add("foo");
System.out.println ("" + S.contains("foo"));
map : TreeMap
C++
map<string,int> zip;
zip["ODU"] = 23529;
cout << zip["ODU"] << endl
Java
TreeMap<String,Integer> zip
= new TreeMap<String,Integer>();
zip.put("ODU", 23529);
System.out.println (zip.get("ODU"));
unordered_map : HashMap
C++
unordered_map<string,int> zip;
zip["ODU"] = 23529;
cout << zip["ODU"] << endl
Java
HashMap<String,Integer> zip
= new HashMap<String,Integer>();
zip.put("ODU", 23529);
System.out.println (zip.get("ODU"));
Cultural Difference
By default
-
C++ programmers tend to use containers based on binary search trees.
- Hashing-based
unordered_
… containers were added to C++ only in 2011.
- Hashing-based
-
Java programmers tend to use containers based on hashing.
- Tree-based
Tree
… containers were added to Java only afterHash
… had become entrenched as the default.
- Tree-based
1: A fully qualified name is an unambiguous name for an entity (function, variable, class, etc.) that is independent of the context in which it is used. Generally this means that when you have a hierarchy of entities contained inside other entities, you must name all of the containers, in order.
For example, the familiar C++ output stream cout
is contained in the namespace std
, so it’s fully qualified name is std::cout
. But cout
has a member function flush()
. It’s fully qualified name is std::cout.flush()
.