The book Effective Java is like a Java Bible for many programmers. It is a good book, I've learned a lot from it and I've spotted only a couple of unclear or wrong parts.
From a different point of view, it might also serve as a “What Went Wrong” description of the Java language and libraries. Let me quote some paragraphs from the book without further comments.
For example, the constructor
BigInteger(int, int, Random)
, which returns aBigInteger
that is probably prime, would have been better expressed as a static factory method namedBigInteger.probablePrime
.
Unfortunately, the standard collection implementations such as
HashMap
do not have factory methods as of release 1.6, but you can put these methods in your own utility class.
Unfortunately, the JavaBeans pattern has serious disadvantages of its own. Because construction is split across multiple calls, a JavaBean may be in an inconsistent state partway through its construction.
The
newInstance
method always attempts to invoke the class’s parameterless constructor, which may not even exist. You don’t get a compile-time error if the class has no accessible parameterless constructor.
This program gets the right answer, but it is much slower than it should be, due to a one-character typographical error. The variable
sum
is declared as aLong
instead of along
.
Finalizers are unpredictable, often dangerous, and generally unnecessary. Their use can cause erratic behavior, poor performance, and portability problems.
Don’t be seduced by the methods
System.gc
andSystem.runFinalization
. They may increase the odds of finalizers getting executed, but they don’t guarantee it. The only methods that claim to guarantee finalization areSystem.runFinalizersOnExit
and its evil twin,Runtime.runFinalizersOnExit
. These methods are fatally flawed and have been deprecated.
If an uncaught exception is thrown during finalization, the exception is ignored, and finalization of that object terminates.
The four classes cited as examples of the explicit termination method pattern (
FileInputStream
,FileOutputStream
,Timer
, andConnection
) have finalizers that serve as safety nets in case their termination methods aren’t called. Unfortunately these finalizers do not log warnings. Such warnings generally can’t be added after an API is published, as it would appear to break existing clients.
If a subclass implementor overrides a superclass finalizer but forgets to invoke it, the superclass finalizer will never be invoked.
The equals implementation for
Timestamp
does violate symmetry and can cause erratic behavior ifTimestamp
andDate
objects are used in the same collection or are otherwise intermixed. This behavior of theTimestamp
class was a mistake and should not be emulated.
For example,
java.net.URL
’sequals
method relies on comparison of the IP addresses of the hosts associated with the URLs. Translating a host name to an IP address can require network access, and it isn’t guaranteed to yield the same results over time. This can cause the URLequals
method to violate theequals
contract and has caused problems in practice.
Note: The defined behavior forequals
is known to be inconsistent with virtual hosting in HTTP.
The
Cloneable
interface was intended as a mixin interface for objects to advertise that they permit cloning. Unfortunately, it fails to serve this purpose. Its primary flaw is that it lacks aclone
method, andObject
’s clone method is protected.
If a class implements
Cloneable
,Object
’s clone method returns a field-by-field copy of the object; otherwise it throwsCloneNotSupportedException
. This is a highly atypical use of interfaces and not one to be emulated.
The general intent is that, for any object
x
, the expressionx.clone() != x
will betrue
, and the expressionx.clone().getClass() == x.getClass()
will betrue
, but these are not absolute requirements. While it is typically the case thatx.clone().equals(x)
will be true, this is not an absolute requirement.
For example, consider the
BigDecimal
class, whosecompareTo
method is inconsistent withequals
.
Several classes in the Java platform libraries violate the advice that public classes should not expose fields directly. Prominent examples include the
Point
andDimension
classes in thejava.awt
package. Rather than examples to be emulated, these classes should be regarded as cautionary tales. The decision to expose the internals of theDimension
class resulted in a serious performance problem that is still with us today.
This was not well understood in the early days of the Java platform, so the
String
class does have a copy constructor, but it should rarely, if ever, be used.
It was not widely understood that immutable classes had to be effectively final when
BigInteger
andBigDecimal
were written, so all of their methods may be overridden. Unfortunately, this could not be corrected after the fact while preserving backward compatibility. If you write a class whose security depends on the immutability of aBigInteger
orBigDecimal
argument from an untrusted client, you must check to see that the argument is a “real”BigInteger
orBigDecimal
, rather than an instance of an untrusted subclass.
There are several classes in the Java platform libraries, such as
java.util.Date
andjava.awt.Point
, that should have been immutable but aren’t.
There are a number of obvious violations of this principle in the Java platform libraries. For example, a stack is not a vector, so
Stack
should not extendVector
. Similarly, a property list is not a hash table, soProperties
should not extendHashtable
.
In the case of
Properties
, the designers intended that only strings be allowed as keys and values, but direct access to the underlyingHashtable
allows this invariant to be violated.
Constructors must not invoke overridable methods, directly or indirectly. If you violate this rule, program failure will result. The superclass constructor runs before the subclass constructor, so the overriding method in the subclass will get invoked before the subclass constructor has run.
If you decide to implement
Serializable
in a class designed for inheritance and the class has areadResolve
orwriteReplace
method, you must make thereadResolve
orwriteReplace
method protected rather than private. If these methods are private, they will be silently ignored by subclasses.
There are several constant interfaces in the Java platform libraries, such as
java.io.ObjectStreamConstants
. These interfaces should be regarded as anomalies and should not be emulated.
For example, it is illegal to create an array of a generic type, a parameterized type, or a type parameter.
It also means that you can get confusing warnings when using varargs methods in combination with generic types. This is because every time you invoke a varargs method, an array is created to hold the varargs parameters. If the element type of this array is not reifiable, you get a warning. There is little you can do about these warnings other than to suppress them, and to avoid mixing generics and varargs in your APIs.
It would be nice if the language did the same kind of type inference when invoking constructors on generic types as it does when invoking generic methods.
It doesn’t seem right that we can’t put an element back into the list that we just took it out of.
The next thing to notice is that the value type of the
favorites
Map
is simplyObject
. In other words, theMap
does not guarantee the type relationship between keys and values, which is that every value is of the type represented by its key. In fact, Java’s type system is not powerful enough to express this.
Enum constructors aren’t permitted to access the enum’s static fields, except for compile-time constant fields. This restriction is necessary because these static fields have not yet been initialized when the constructors run.
Inexplicably, the authors of the
ObjectOutputStream
API did not take advantage of theSerializable
interface in declaring thewrite
method. The method’s argument type should have beenSerializable
rather thanObject
. As it stands, an attempt to callObjectOutputStream.write
on an object that doesn’t implementSerializable
will fail only at runtime, but it didn’t have to be that way.
When in doubt, look to the Java library APIs for guidance. While there are plenty of inconsistencies – inevitable, given the size and scope of these libraries – there is also a fair amount of consensus.
The behavior of this program is counterintuitive because selection among overloaded methods is static, while selection among overridden methods is dynamic.
The confusing behavior demonstrated by the previous example came about because the
List<E>
interface has two overloadings of theremove
method:remove(E)
andremove(int)
.
The rules that determine which overloading is selected are extremely complex. They take up thirty-three pages in the language specification, and few programmers understand all of their subtleties.
For example, the
String
class exports two overloaded static factory methods,valueOf(char[])
andvalueOf(Object)
, that do completely different things when passed the same object reference. There is no real justification for this, and it should be regarded as an anomaly with the potential for real confusion.
Because of the unfortunate decision to retrofit
Arrays.asList
as a varargs method in release 1.5, this program now compiles without error or warning. Running the program, however, produces output that is both unintended and useless.
Every invocation of a varargs method causes an array allocation and initialization.
More generally, if a concrete class has no associated interface, then you have no choice but to refer to it by its class whether or not it represents a value. The
Random
class falls into this category.
Consider the
getSize
method in thejava.awt.Component
class. The decision that this performance-critical method was to return aDimension
instance, coupled with the decision thatDimension
instances are mutable, forces any implementation of this method to allocate a newDimension
instance on every invocation.
The “semantic gap” between what the programmer writes and what the CPU executes is far greater than in traditional statically compiled languages, which makes it very difficult to reliably predict the performance consequences of any optimization.
One example of an exception that fails this test is
CloneNotSupportedException
. It is thrown byObject.clone
, which should be invoked only on objects that implementCloneable
. In practice, thecatch
block almost always has the character of an assertion failure. The checked nature of the exception provides no benefit to the programmer, but it requires effort and complicates programs.
For example, instead of a
String
constructor,IndexOutOfBoundsException
could have had a constructor that looks like this… Unfortunately, the Java platform libraries do not make heavy use of this idiom, but it is highly recommended.
The libraries provide the
Thread.stop
method, but this method was deprecated long ago because it is inherently unsafe – its use can result in data corruption.
Let the client synchronize externally where it is appropriate. In the early days of the Java platform, many classes violated these guidelines. For example,
StringBuffer
instances are almost always used by a single thread, yet they perform internal synchronization.
Prior to release 1.5, the double-check idiom did not work reliably because the semantics of the
volatile
modifier were not strong enough to support it.
In an ironic twist, the
ThreadGroup
API is weak from a thread safety standpoint. To get a list of the active threads in a thread group, you must invoke theenumerate
method, which takes as a parameter an array large enough to hold all the active threads. TheactiveCount
method returns the number of active threads in a thread group, but there is no guarantee that this count will still be accurate once an array has been allocated and passed to theenumerate
method. If the thread count has increased and the array is too small, theenumerate
method silently ignores any threads for which there is no room in the array.
Relying on the default deserialization mechanism can easily leave objects open to invariant corruption and illegal access.
It will dictate the serialized form forever. This is not just a theoretical problem. It happened to several classes in the Java platform libraries, including
BigInteger
.
Note also that defensive copying is not possible for final fields. To use the
readObject
method, we must make thestart
andend
fields nonfinal.
Do not use the
writeUnshared
andreadUnshared
methods. They are typically faster than defensive copying, but they don’t provide the necessary safety guarantee.
I didn't finish reading the book so I might add other interesting quotes later.
Diskuse je zrušena z důvodu spamu.