Appendix C

C++ and Java Side by Side

The translation card — every construct you learned in C++ with its Java equivalent, and the four places the languages genuinely disagree

Appendix C — C++ and Java Side by Side

“What has been is what will be, and what has been done is what will be done, and there is nothing new under the sun.” — Ecclesiastes 1:9

Learn the moves, and a new language is just new vocabulary for things you already know how to do. — the thesis this course opened with

This appendix is the receipt.

For six weeks you wrote C++. In Week 7 you switch to Java, and the claim Chapter 1 made — that the skill is the sport and not the equipment — either survives that switch or it does not. This page is where you check it. Read the left column, read the right column, and count how often the only thing that changed was spelling.

Use it as a lookup while you build P6 in Chapter 7, and as a cold-open review the hour before the final in Chapter 8.

Every snippet below was compiled before it was printed — the C++ with g++ -std=c++17 -Wall -Wextra, the Java with javac. The outputs are real, not predicted.


C.1 — The Skeleton: Where main Lives

#include <iostream>
using namespace std;

int main() {
    cout << "Hello, world." << endl;
    return 0;
}
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, world.");
    }
}

Both print Hello, world. C++ lets main float free; Java has no free functions, so main must sit inside a class — and if that class is public, the filename must match it exactly (Hello.java). #include pulls in a library; import only tells Java where to find a name it already has. Java’s core (System, String, Math) needs no import at all, which is why the Java version has no header line.


C.2 — Output and Input

string name;
int age = 0;

cout << "Name: ";
getline(cin, name);

cout << "Age: ";
cin >> age;
Scanner in = new Scanner(System.in);

System.out.print("Name: ");
String name = in.nextLine();

System.out.print("Age: ");
int age = Integer.parseInt(in.nextLine());

Fed Maya and 19, both read the same two values and print Name: Age: Hello, Maya, age 19. — the prompts share a line because neither cout << nor print adds a newline. endl becomes nothing (println supplies it); cout << x << y becomes "..." + x + y; setprecision becomes printf("%.2f", x). Java needs import java.util.Scanner;. Both languages have a mixed-reading trap: C++ needs cin.ignore() between cin >> n; and a getline; Java needs you to avoid nextInt() before nextLine(), which the pattern above sidesteps by reading every line as text and parsing it.


C.3 — The Five Types, and Java’s String

int reps = 12;
double weight = 72.5;
bool ready = true;
char grade = 'A';
string name = "Maya";        // lowercase
int reps = 12;
double weight = 72.5;
boolean ready = true;        // full word
char grade = 'A';
String name = "Maya";        // capital S

Two renames — boolboolean, stringString — and everything else is identical. Naming convention flips: snake_case in this book’s C++, camelCase in its Java, PascalCase for Java classes, SCREAMING_SNAKE_CASE for constants in both.

String with a capital S is a class, not a primitive, which is what makes the next section necessary. It also changes one method you use constantly: name.substr(1, 2) in C++ takes a start and a length, while name.substring(1, 3) in Java takes a start and an end index that is not included. Both return ay — different arguments, same answer, and exactly the kind of hazard that costs you twenty minutes.


C.4 — == vs .equals()

This is the one. If you skim this whole appendix, do not skim this section.

string a = "Maya";
string b = "Maya";
cout << boolalpha << (a == b) << endl;   // true — C++ compares CONTENTS
String a = "Maya";
String b = new String("Maya");
System.out.println(a == b);        // false
System.out.println(a.equals(b));   // true

That is real output. Both Java strings hold the same four characters, and == says they are different, because == on an object asks “are these two names for the same object in memory?” — never “do these hold the same text?”

The single most expensive bug a C++ student writes in Java. No compiler error. No warning. if (name == "Maya") compiles perfectly and often even works, because Java pools identical string literals and two literals genuinely end up as one object. Then the string arrives from nextLine() instead of a literal, and that same line silently starts answering false forever. The rule, no exceptions: primitives (int, double, boolean, char) use ==; objects (String, Account, Vehicle, anything with a capital-letter type) use .equals(). Make it a typing reflex, not a decision.

The trap extends to your own classes: accountA == accountB is false for two separately-constructed accounts with identical fields, exactly as comparing two Account* was in C++.


C.5 — Constants, Casting, and Integer Division

const double PASS_MARK = 70.0;

cout << 7 / 2 << endl;                        // 3
cout << static_cast<double>(7) / 2 << endl;   // 3.5
public static final double PASS_MARK = 70.0;

System.out.println(7 / 2);            // 3
System.out.println((double) 7 / 2);   // 3.5

const becomes final; at class level, static final. Casting drops the static_cast<> ceremony for a plain (double).

The integer-division trap survived the trip intact. Two ints divided give an int in both languages, the .5 is thrown away rather than rounded, and neither compiler says a word. Java is stricter in one place: int average = 90 / 4.0; is a compile error there (possible lossy conversion from double to int), where C++ truncates quietly.


C.6 — Control Flow and the For-Each Loop

if / else if / else, switch, while, do…while, the three-part for, &&, ||, !, short-circuit evaluation, and the off-by-one hazard in i < size versus i <= size are all character-for-character identical between the two languages. Nothing to learn here, which is the point.

The one loop worth naming is the collection loop, which both languages have and each spells differently:

for (int s : scores) {        // range-based for
    cout << s << " ";
}
for (int s : scores) {        // enhanced-for
    System.out.print(s + " ");
}

Identical syntax, different names in the two communities. Read it as “for each score s in scores.” Use it whenever you are reading every element in order; drop back to the indexed for when you need i itself, or when you are writing into the collection — reassigning the loop variable changes only a local copy in both languages.


C.7 — Functions vs. Methods, and What Gets Copied

int square(int x) { return x * x; }

void try_to_double(int x)  { x = x * 2; }   // caller unchanged
void actually_double(int& x) { x = x * 2; } // caller changed
public static int square(int x) { return x * x; }

public static void tryToDouble(int x) { x = x * 2; }   // caller unchanged
// there is no int& in Java — return the new value instead

A C++ free function becomes a Java static method inside a class. static means belongs to the class, not to any object — which is why main is static, and why a static method cannot touch instance fields (there is no this).

Primitives behave identically: pass-by-value, caller untouched. Objects are where the languages part company. C++ gives you a choice — by value, by reference (&), or by pointer. Java gives you one behavior, pass-reference-by-value: the method gets a copy of the reference, which still points at your object.

public static void rename(Account a) {
    a.setOwner("Marcus");        // ✅ the caller SEES this
    a = new Account("Lin");      // ❌ the caller does NOT see this
}

The caller’s account ends up named Marcus. Calling a method on the parameter reaches the caller’s object; reassigning the parameter itself only re-points a local copy.


C.8 — Arrays, and the Difference That Actually Matters

const int CAPACITY = 5;
string entries[CAPACITY];
int count = 0;               // logical size — YOU track it

entries[count] = "P52";
count++;
String[] entries = new String[5];   // five slots, all null
System.out.println(entries.length); // 5 — the array knows

Java arrays know their own size (.length, a field, no parentheses — but String uses .length() with them, and everyone trips on that once). Java also initializes every slot: numbers to 0, boolean to false, object types to null. C++ hands you whatever bytes were there.

Then there is the bounds difference, which is not cosmetic. Read past the end of a Java array and you get, verified:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
	at BoundsCard.main(BoundsCard.java:5)

The program stops and names the bad index, the real length, and the line. You are debugging in ten seconds.

Do the same thing in C++ and it is undefined behavior — a term of art meaning the standard imposes no requirement whatsoever on what happens next. The version compiled for this page, with -Wall -Wextra, produced zero warnings, printed a meaningless number, and ran on to a normal exit. That is the whole problem: it did not crash, so nothing told you anything was wrong, and the garbage flowed into the next calculation. Other times you get a Segmentation fault; other times nothing today and a corrupted value three functions away tomorrow. Java trades a small runtime check for a crash you can read, and a crash you can read beats a wrong answer you cannot see.


C.9 — Structs, Classes, and Where the Bodies Live

struct Manuscript {          // note the semicolon
    string name;
    int century;
};

class Account {
private:                     // a block LABEL — applies until the next one
    double balance;
    string owner;
public:
    Account(string owner, double starting);
    void deposit(double amount);          // declared here...
    double get_balance() const { return balance; }
};

void Account::deposit(double amount) {    // ...defined out here
    balance += amount;
}
public class Account {
    private double balance;               // modifier on EVERY member
    private String owner;

    public Account(String owner, double starting) {
        this.owner = owner;
        this.balance = starting;
    }

    public void deposit(double amount) {  // body right here. Always.
        balance += amount;
    }

    public double getBalance() {
        return balance;
    }
}

Java has no struct — a plain data holder is just a class with public fields, or better, private fields and getters. Java also has no header file, ever, and no Account:: scope-resolution prefix: declaration and definition are always the same text, so the .h/.cpp split described in Chapter 5 has no Java counterpart.

Access control looks similar and works differently. C++ uses block labels (private: covers everything until the next label); Java puts a modifier on each member. And Java has a fourth level with no keyword: leave the modifier off and you get package-private, which is more open than private, not less. Always write it explicitly.


C.10 — Constructors, this, and the Death of the Destructor

class Node {
private:
    string label;
public:
    Node(string label) : label(label) {}       // initializer list
    ~Node() { cout << "closing " << label << endl; }   // destructor
    void set_label(string label) { this->label = label; }
};
public class Node {
    private String label;

    public Node(String label) {
        this.label = label;                    // no initializer list
    }
    // no destructor exists in the language

    public void setLabel(String label) {
        this.label = label;
    }
}

Constructors are the same idea in both: same name as the class, no return type, runs automatically. C++ prefers an initializer list (: label(label)); Java assigns in the body. this exists in both and solves the same shadowing problem — but it is a pointer in C++, so you write this->label, and a reference in Java, so you write this.label.

Destructors are gone: no ~Node(), no virtual-destructor rule, no cleanup code of any kind. Section C.11 is why.


C.11 — Pointers and delete vs. References and Garbage Collection

Account* a = new Account(100.0);
a->deposit(50.0);             // arrow through a pointer
cout << a->get_balance();
delete a;                     // YOU free it
a = nullptr;                  // YOU blank the pointer
Account a = new Account(100.0);
a.deposit(50.0);              // dot. always a dot.
System.out.println(a.getBalance());
a = null;                     // that is the entire cleanup story

Every Java variable of a class type is a reference. Not sometimes — always. That one decision erases the *, the &, the ->, and the delete. nullptr becomes null; -> becomes .; there is no address-of operator, because you never see an address.

When no reference anywhere in your program still points at an object, the garbage collector reclaims it. You do not call it and cannot schedule it. Nothing in Java corresponds to the destructor that walked a linked list in Chapter 6 deleting every node. The cost is small: Java runs somewhat slower and occasionally pauses to collect. The benefit is a whole category of bug (the leak, the double delete, the dangling pointer) that you cannot commit.


C.12 — Inheritance

class Vehicle {
protected:
    int wheels;
public:
    Vehicle(int wheels) : wheels(wheels) {}
    virtual ~Vehicle() {}                  // virtual destructor: required
    virtual void describe() const {
        cout << "A vehicle with " << wheels << " wheels." << endl;
    }
};

class Car : public Vehicle {
private:
    int passengers;
public:
    Car(int wheels, int passengers)
        : Vehicle(wheels), passengers(passengers) {}   // base ctor in the init list

    void describe() const override {
        cout << "A car carrying " << passengers << " passengers." << endl;
    }
};
public class Vehicle {
    protected int wheels;

    public Vehicle(int wheels) {
        this.wheels = wheels;
    }

    public void describe() {               // no 'virtual' — always dynamic
        System.out.println("A vehicle with " + wheels + " wheels.");
    }
}

public class Car extends Vehicle {
    private int passengers;

    public Car(int wheels, int passengers) {
        super(wheels);                     // must be the FIRST statement
        this.passengers = passengers;
    }

    @Override
    public void describe() {
        System.out.println("A car carrying " + passengers + " passengers.");
    }
}

Five translations, all mechanical. : public Vehicle becomes extends Vehicle. The base-constructor call leaves the initializer list and becomes super(...), which Java requires to be the first statement of the constructor body. virtual disappears, because every non-static Java method dispatches dynamically already. override becomes the annotation @Override — optional, and write it on every overriding method anyway, because a typo’d override compiles silently without it and simply never runs. Vehicle::describe() becomes super.describe(). And the virtual destructor translates into nothing, because Java has none.


C.13 — Object Slicing: A Bug Java Cannot Have

void inspect(Vehicle v) {     // by VALUE
    v.describe();
}

Car c(4, 5);
c.describe();                 // A car carrying 5 passengers.
inspect(c);                   // A vehicle with 4 wheels.   ❌

That is verified output, and it deserves a hard look: one object, called two ways, gave two different answers. The parameter v is not your Car — it is a brand-new Vehicle, copy-constructed from only the Vehicle-shaped portion of c. The Car parts, including the override, were sliced off, with no warning, no error, and no crash. The C++ fix is to stop copying: take const Vehicle& or Vehicle*, both of which dispatch correctly.

public static void inspect(Vehicle v) {   // a reference. No other option exists.
    v.describe();
}

Car c = new Car(4, 5);
c.describe();                 // A car carrying 5 passengers.
inspect(c);                   // A car carrying 5 passengers.   ✅

Slicing cannot happen in Java — not through care, but because there is no by-value object parameter to get wrong. A Vehicle variable holds a reference; the object at the other end is still a Car and still knows it. This is also why Vehicle[] works in Java where C++‘s Vehicle fleet[3] would slice every element, forcing polymorphic C++ collections to hold pointers.


C.14 — Abstract Classes and Interfaces

class Shape {
public:
    virtual ~Shape() {}
    virtual double area() const = 0;      // pure virtual — no body
    virtual string kind() const = 0;

    void describe() const {               // concrete, calls the pure virtuals
        cout << kind() << " area = " << area() << endl;
    }
};
public abstract class Shape {
    public abstract double area();        // no body; semicolon, not braces
    public abstract String kind();

    public void describe() {              // concrete, calls the abstract ones
        System.out.println(kind() + " area = " + area());
    }
}

Same idea, more explicit spelling. C++ infers “abstract” from at least one = 0 method; Java makes you mark the class abstract and each unfinished method abstract. Neither can be instantiated, both can hold state and constructors, and in both a finished base method may call an unfinished one — legal because by the time it runs, it is running on a concrete object.

Interfaces are the one place Java adds a keyword C++ does not have.

public interface Healable {
    void heal(int amount);
    boolean canBeHealed();
}

public class Hero extends Combatant implements Healable { ... }

A class extends exactly one class and implements as many interfaces as it likes. C++ has no interface keyword at all; its equivalent is a class in which every method is pure virtual, inherited alongside a real base:

class Healable {                          // C++'s stand-in for an interface
public:
    virtual ~Healable() {}
    virtual void heal(int amount) = 0;
    virtual bool can_be_healed() const = 0;
};

class Hero : public Combatant, public Healable { ... };

That compiles and works. It is also C++ multiple inheritance, the feature that produces the diamond problem when two bases share an ancestor. Java splits the job instead: classes carry data and you get one; interfaces carry contract only and you get any number.


C.15 — Printing an Object

Java has one answer, and this course uses it:

@Override
public String toString() {
    return String.format("[%s] balance: $%.2f", owner, balance);
}

Override it and both System.out.println(a) and "text " + a call it automatically. Skip it and you get something like Account@6d06d69c, which is Java telling you that you forgot.

C++‘s equivalent is overloading operator<< so that cout << a works. This course never asks you to do that — the C++ chapters give their classes an ordinary print_statement() or describe() method instead. Recognize operator<< when you meet it in real code; write toString() in Java, on every class, always.


C.16 — The Four Places the Languages Genuinely Disagree

Almost everything above is spelling. These four are not — they are different decisions about how a program should work, and each changes what you have to think about.

1. Memory ownership. C++ makes you the owner: you new, you delete, you write the destructor, and a mistake is a leak or a dangling pointer. Java makes the runtime the owner: you new and stop thinking about it. It is the largest difference between the languages, and C.10 and C.11 both follow from it. C++ buys predictable timing and speed; Java buys a category of bug you cannot write.

2. == on objects. In C++, == on a std::string compares contents. In Java, == on any object compares identity, and you need .equals() for contents. That is a genuine disagreement about what the operator means, not a syntax difference, and it is silent in both directions.

3. Array bounds. C++ does not check; reading past the end is undefined behavior and typically hands you a plausible-looking wrong number with no warning. Java checks every access and throws, naming the index, the length, and the line. C++ chose speed; Java chose being told.

4. Single inheritance plus interfaces, vs. multiple inheritance. C++ lets a class have several full parents, with all the ambiguity that implies. Java allows exactly one parent class and unlimited interfaces, so the data chain is never ambiguous while capabilities still cut sideways across a hierarchy. Inheritance answers “what is it?”; interfaces answer “what can it do?”

Everything else on this page — extends for : public, super for the initializer list, . for ->, final for const, boolean for bool — is vocabulary. You picked it up in about four days, and you could, because the ideas underneath it took six weeks and are already yours.

That is the whole argument of this course, and this appendix is the table it fits on.


Related: Appendix A for the OnlineGDB workflow and the language dropdown, Appendix B for pacing, Appendix D for definitions. Runnable versions live in the chapter code/ folders: code/vehicle_hierarchy.cpp and code/slicing_demo.cpp in Chapter 6, code/EqualsTest.java and code/FleetDemo.java in Chapter 7.