Keep in mind that Java was developed in 1991-1995 when C++ was a much different language. Exceptions (which made RAII necessary) and templates (which made it easier to implement smart pointers) were "new-fangled" features. Most C++ programmers had come from C and were used to doing manual memory management.
So I doubt that Java's developers deliberately decided to abandon RAII. It was, however, a deliberate decision for Java to prefer reference semantics instead of value semantics. Deterministic destruction is difficult to implement in a reference-semantics language.Deterministic destruction is difficult to implement in a reference-semantics language.
So why use reference semantics instead of value semantics?
Because it makes the language a lot simpler.
- There is no need for a syntactic distinction between
Foo
andFoo*
or betweenfoo.bar
andfoo->bar
. - There is no need for overloaded assignment, when all assignment does is copy a pointer.
- There is no need for copy constructors. (There is occasionally a need for an explicit copy function like
clone()
. Many objects just don't need to be copied. For example, immutables don't.) - There is no need to declare
private
copy constructors andoperator=
to make a class noncopyable. If you don't want objects of a class copied, you just don't write a function to copy it. - There is no need for
swap
functions. (Unless you're writing a sort routine.) - There is no need for C++0x-style rvalue references.
- There is no need for (N)RVO.
- There is no slicing problem.
- It's easier for the compiler to determine object layouts, because references have a fixed size.
The main downside to reference semantics is that when every object potentially has multiple references to it, it becomes hard to know when to delete it. You pretty much have to have automatic memory management.
Java chose to use a non-deterministic garbage collector.
Can't GC be deterministic?
Yes, it can. For example, the C implementation of PythonPython uses reference counting. And later added tracing GC to handle the cyclic garbage where refcounts fail.
But refcounting is horribly inefficient. Lots of CPU cycles spent updating the counts. Even worse in a multi-threaded environment (like the kind Java was designed for) where those updates need to be synchronized. Much better to use the null garbage collectornull garbage collector until you need to switch to another one.
You could say that Java chose to optimize the common case (memory) at the expense of non-fungible resources like files and sockets. Today, in light of the adoption of RAII in C++, this may seem like the wrong choice. But remember that much of the target audience for Java was C (or "C with classes") programmers who were used to explicitly closing these things.
But what about C++/CLI "stack objects"?
They're just syntactic sugar for Dispose
(original linkoriginal link), much like C# using
. However, it doesn't solve the general problem of deterministic destruction, because you can create an anonymous gcnew FileStream("filename.ext")
and C++/CLI won't auto-Dispose it.