I mentor a team that uses C++. However, I have programmed in Java since Java 1.1 and have seen the language evolve.
Last year I surveyed all teams at our local regional about the language the team used. An equal number of teams used C++ and LabVIEW, but twice as many teams programmed using Java as the combined number of C++ and LabVIEW teams.
C++ predates Java by a dozen or so years. Both adopted the general syntax of the C programming language (curly braces are used for code block), similar looping structures, similar conditional structures, etc).
The goal of the C++ designers was backward compatibility with C. This design constraint carried with it a fair amount of baggage, since C is very, er, flexible (as one C instructor of mine use to say, “You can do anything in C”).
In contrast, beyond adopting the C syntax style, Java made a cleaner break from C, wanting to avoid the things about C that made it so challenging and error-prone. For example, Java has no pointers. This can be a double-edged sword. On the plus side, eliminating pointers avoids a rich and fertile area for bug production. On the bad side, you cannot directly access memory when you need to, such as when needing to access hardware registers, often essential when performing embedded programming, device driver programming, programming operating system kernels and other advanced programming. Java created Java Native Interfaces (JNI) as an accommodation, which lets you interface Java to a language that supports memory access (like C or C++).
If you were to set out to master the C++ language, then it is without question more difficult to learn and program than Java. When Java was first conceived it was a very simple language. Perhaps too simple. However, its complexity has increased over the years and, although I sometimes wonder whether it too isn’t getting too complex, I think the Java designers have generally done a much better job staying true to its architectural principles.
Java generally gets a bad wrap for being slow. Java executes byte code, which you can think of as the machine language of a notional machine that doesn’t actually exist (some organizations have actually created hardware whose machine language is Java byte code, but I digress). Java needs a Java Virtual Machine (JVM) to translate the byte code to the native machine code of the computer on which it’s running. In its early days the argument that Java was slow was well-deserved. However, in its first decade Java made great strides in improving its performance. When a Java program first runs it is converting byte code to machine code as the program executes. However, for parts of the program that are run frequently the JVM will just go ahead and compile the byte code to machine code so that it can avoid the performance penalty of the byte code to machine code translation. This is called Just-In-Time compilation. That is one of the major reasons why a Java program speeds up after it’s been run once. However, some advanced programming (such as hard real-time systems) can’t tolerate this variability in execution time.
Another double-edged sword is Java garbage collection (GC). The garbage collector is responsible for freeing heap memory that’s no longer needed. Having Java manage memory is a HUGE benefit for the programmer and it can all but eliminate another bug genre: memory leaks. In contrast, in C++ the programmer is generally responsible for managing all heap memory used by the program. The downside for Java is that, like housecleaning, garbage collection takes some time. And execution must pause during garbage collection. Early Java used to wait as long as possible before running the GC, creating a pronounced pause in execution when the GC finally ran and likely contributing to the slow performance reputation. Sun/Oracle have tinkered with GC tuning over the years and Java is much better at staying on top of its GC duties without forcing a noticeable execution pause. GC is another Java feature that presents challenges for hard real-time system programming.
The good news about C++ is that, even though the language has many obscure features, a great many of them are ones that you probably shouldn’t be using anyway if you’re writing a new program rather than, say, maintaining a computer system from 1990 or writing kernel code. Having spend so much time programming Java, I find that the best approach to writing C++ code is to write it as close as possible to how I would write it in Java (e.g, don’t use multiple inheritance), adapting to the language as appropriate (e.g., use header files, create a copy constructor (or explicitly prohibit it, so the compiler doesn’t create one for me), create a copy assignment operator (or explicitly prohibit it; same reason)).