Joshua Bloch, Effective Java, 3rd Edition, Item 28, uses this contrast between arrays and generic lists to motivate preferring lists in generic APIs. ↩︎
Java Language Specification SE 21, §10.5 Array Store Exception, specifies the runtime store check that throws ArrayStoreException when a value is not assignment-compatible with the array’s actual component type. ↩︎
Java Language Specification SE 21, §4.7 Reifiable Types, explains which types are fully available at runtime; arrays are reifiable only when their component type is reifiable. ↩︎
Java Language Specification SE 21, §4.6 Type Erasure, defines how parameterized types and type variables are erased. ↩︎
Java Language Specification SE 21, §15.10.1 Array Creation Expressions, makes array creation with a non-reifiable component type a compile-time error. ↩︎
Java Language Specification SE 21, §4.7 Reifiable Types, identifies parameterized types other than unbounded wildcard forms as non-reifiable. ↩︎
publicclassDerivedResourceHog : MyResourceHog { // Have its own disposed flag. privatebool disposed = false;
protectedoverridevoidDispose(bool isDisposing) { // Don't dispose more than once. if (disposed) return; if (isDisposing) { // TODO: free managed resources here. } // TODO: free unmanaged resources here. // Let the base class free its resource. // Base class is responsible for calling // GC.SuppressFinalize() base.Dispose(isDisposing); // Set derived class disposed flag: disposed = true; } }
// DON'T DO THIS! publicclassBadClass { // Store a reference to a global object: privatestaticreadonly List<BadClass> FinalizedList = new List<BadClass>(); privatestring msg;
publicBadClass(string msg) { this.msg = msg; }
~BadClass() { // Add this object to the list. // This object is reachable, no longer garbage. // It's back! FinalizedList.Add(this); } }
protectedoverridevoidOnPaint(PaintEventArgs e) { // Bad. Created the same font every paint event. using (Font MyFont = new Font("Arial", 10.0f)) { e.Graphics.DrawString(DataTime.Now.ToStirng(), MyFont, Brushes.Black, new PointF(0, 0)); } base.OnPaint(e); }
系统会频繁调用 OnPaint(),而每次调用时,都会创建新的 Font 对象,但是这并没有必要,因为实际上这些对象都是一样的,因此垃圾回收器总是得回收旧的 Font。GC 的执行时机与程序所分配的内存数量以及分配的频率有关,如果总是分配内存,那么 GC 的工作压力就比较大,这自然会降低程序效率。[3]
反之,将 Font 对象从局部变量改为成员变量,那么就可以复用同一个 Font:
1 2 3 4 5 6 7
privatereadonly Font myFont = new Font("Arial", 10.0f);
静态类型检查意味着编译器会把类型不符的用法找出来,这也令应用程序在运行期能够少做一些类型检查。然而有的时候还必须在运行期检查对象的类型,比如,如果所使用的框架已经在方法签名里把参数写成了 Object,那么可能就得先将该参数转成其他类型(例如其他的类或接口),然后才能继续编写代码。有两种办法能实现转换,一是使用 as 运算符,二是通过强制类型转换 (cast) 来绕过编译器的类型检查。在这之前,可以先通过 is 判断该操作是否合理,然后再使用 as 运算符 或执行强制类型转换。[2]
在这两种方法中,应该优先考虑第一种办法,这样做要比盲目地进行类型转换更加安全,且在运行的时候更有效率。as 及 is 运算符不会考虑由用户所定义的转换。[3]只有当运行期的类型与要转换到的类型相符合时,该操作才能顺利地执行。这种类型转换操作很少会为了类型转换而构建新的对象(但若用 as 运算符把装箱的值类型转换成未装箱且可以为 null 的值类型,则会创建新的对象)。[4]
下面来看一个例子。如果需要把 object 对象转换为 MyType 实例,那么可以这样写:
1 2 3 4 5 6 7 8 9 10 11 12
object o = Factory.GetObject(); // Version one: MyType t = 0as MyType;
if(t!= null) { // work with t, it's a MyType } else { // report the failure }
此外,也可以这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
object o = Factory.GetObject(); // Version one: try { MyType t; t = (MyType) o; if (t != null) { // work with t, it's a MyType } } catch(InvalidCastException) { // report the conversion failure }
大家应该会觉得第一种写法比第二种更简单,而且更好理解。由于第一种写法不需要使用 try/catch 结构,因此程序的开销与代码量都比较低。如果采取第二种写法,那么不仅要捕获异常,而且还得判断t是不是 null。强制类型转换在遇到 null 的时候并不抛出异常,这导致开发者必须处理两种情况:一种是 o 本来就为 null,因此强制转换后所得的 t 也是 null;另一种是程序因 o 无法类型转换为 MyType 而抛出异常。如果采用第一种写法,那么由于 as 操作在这两种特殊情况下的结果都是 null,因此只需要用 if (t!= null) 来概括处理就可以了。[5]
as 运算符与强制类型转换之间的最大区别在于如何对待由用户所定义的转换逻辑。as 与 is 运算符只会判断待转换的那个对象在运行期是何种类型,并据此做出相应的处理,除了必要的装箱与取消装箱操作,它们不会执行其他操作。如果待转换的对象既不属于目标类型,也不属于由目标类型所派生出来的类型,那么 as 操作就会失败。反之,强制类型转换操作则有可能使用某些类型转换逻辑来实现类型转换,这不仅包含由用户所定义的类型转换逻辑,而且还包括内置的数值类型之间的转换。例如可能发生从 long 至 short 转换,这种转换可能导致信息丢失。[6]
// Broken - Inappropriate use of inheritance! publicclassInstrumentedHashSet<E> extendsHashSet<E> { // The number of attempted element insertions privateintaddCount=0;
public String toString() { return s.toString(); } }
Set 接口的存在使得 InstrumentedSet 类的设计成为可能,因为 Set 接口保存了 HashSet 类的功能特性。前文的基于继承的方法只适用于单个具体类,并且对于超类中所支持的每个构造器都要求有一个单独的构造器,与此不同的是,这里的包装类 (wrapper class) 可以被用来包装任何 Set 实现,并且可以结合任何先前存在的构造器一起工作:[8]
1 2
Set<Instant> times = newInstrumentedSet<>(newTreeSet<>(cmp)); Set<E> s = newInstrumentedSet<>(newHashSet<>(INIT_CAPACITY));
只有当子类真正是超类的子类型 (subtype) 时,才适合用继承。对于 A、B 两类,只有两个类有 “is-a” 的关系时,B 才应该扩展 A。若想用 B 扩展 A,就应该问问自己:每个 B 是否 is an A?如果不能肯定,那么就不应该进行扩展。如果没有 is-a 的关系,通常情况下,B 包含 A 的一个私有实例,并暴露一个较小的、较简单的 API:A 本质上不是 B 的一部分,只是它的实现细节。[11]
Effective Java Item 18 uses the InstrumentedHashSet example to show how overriding both add and addAll can double-count when the superclass implementation self-uses another overridable method. ↩︎
Effective Java Item 18 treats superclass self-use as an implementation detail unless it is explicitly documented for subclassing. ↩︎
Effective Java Item 18 describes the superclass-new-method hazard for subclasses that enforce invariants by overriding only the methods known at the time. ↩︎
Java Language Specification SE 21, §8.4.8.3 Requirements in Overriding and Hiding, defines overriding constraints such as return-type substitutability; Effective Java Item 18 uses those rules to explain subclass evolution hazards. ↩︎
Effective Java Item 18 names the composition-and-forwarding alternative and uses it to move dependence from superclass implementation details to a wrapped instance. ↩︎
Oracle Java SE 21 API, Set, defines the interface contract that lets a wrapper work with multiple implementations; Guava, ForwardingSet, documents a reusable forwarding base for this pattern. ↩︎
Effective Java Item 18 states the subtype/is-a test for implementation inheritance and recommends composition when the relationship is not a true subtype relation. ↩︎
Oracle Java SE 21 API, Stack, shows Stack extending Vector and recommends Deque for LIFO stack use; Properties documents its Hashtable inheritance and notes put / putAll can compromise the string-only property model. ↩︎
Oracle Java SE 21 API, Properties, documents that inherited put and putAll may be applied to Properties and that using them with non-String keys or values can compromise calls to other property APIs. ↩︎
Effective Java Item 18 summarizes the rule: use inheritance only for true subtype relationships and prefer composition plus forwarding when a suitable interface can support a wrapper. ↩︎
public Object pop() { if (size == 0) thrownewEmptyStackException(); return elements[--size]; }
/** * Ensure space for at least one more element, roughly doubling the capacity each time the array needs to grow. */ privatevoidensureCapacity() { if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); } }
publicclassRule6 { publicstaticvoidmain(String[] args) { Stackstack=newStack(); for (inti=0; i < 10; i++) stack.push("1"); for (inti=0; i < 10; i++) stack.pop(); // break point System.out.println("complete"); // break point } }
// Can you spot the "memory leak"? classStack { privatestaticfinalintDEFAULT_INITIAL_CAPACITY=16; private Object[] elements; privateintsize=0;
publicStack() { elements = newObject[DEFAULT_INITIAL_CAPACITY]; }
public Object pop() { if (size == 0) thrownewEmptyStackException(); return elements[--size]; }
/** * Ensure space for at least one more element, roughly doubling the capacity each time the array needs to grow. */ privatevoidensureCapacity() { if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); } }
public Object pop() { if (size == 0) thrownewEmptyStackException(); Objectresult= elements[--size]; elements[size] = null; return result; }
/** * Ensure space for at least one more element, roughly doubling the capacity each time the array needs to grow. */ privatevoidensureCapacity() { if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); } }
Joshua Bloch, Effective Java, 3rd Edition, Item 7, frames obsolete object references as a Java memory-management issue even in the presence of garbage collection. ↩︎↩︎↩︎↩︎
Oracle Java SE 17 Troubleshooting Guide, Troubleshoot Memory Leaks, describes memory leaks as long-running growth problems that can lead to frequent garbage collection and OutOfMemoryError, and recommends heap-oriented diagnostics. ↩︎↩︎↩︎
Joshua Bloch, Effective Java, 3rd Edition, Item 7, uses the stack example to explain obsolete object references and the need to null out slots that leave the logical active range. ↩︎↩︎↩︎↩︎
Oracle Java SE 21 API, WeakReference, documents weak references, and Oracle Java SE 21 API, WeakHashMap, documents map entries whose keys are weakly held. ↩︎
// Hideously slow program! Can you spot the object creation? publicstaticvoidmain(String[] args) { Longsum=0L; for (longi=0; i < Integer.MAX_VALUE; i++) { sum += i; } System.out.println(sum); }
Joshua Bloch, Effective Java, 3rd Edition, discusses avoiding unnecessary object creation, object reuse, object pools, and the boundary with defensive copying in the “Creating and Destroying Objects” chapter. ↩︎↩︎↩︎↩︎↩︎
Oracle Java SE 21 API, String(String original), documents that the constructor creates a new string representing the same character sequence as the original. ↩︎
Oracle Java SE 21 API, Boolean, marks Boolean constructors as deprecated for removal and directs callers toward parseBoolean or valueOf. ↩︎
Joshua Bloch, Effective Java, 3rd Edition, Item 83, frames lazy initialization as an optimization that adds complexity and should be used judiciously. ↩︎
Oracle Java SE 21 API, Map.keySet(), documents the backed-view semantics: changes are reflected between the map and the returned set; the API contract supports the view relationship, not a portable object-identity guarantee across calls. ↩︎
Oracle Java SE 21 API, Connection.close(), documents that closing a JDBC connection releases database and JDBC resources; this supports treating database connections as heavyweight resources whose reuse is managed by connection-pooling infrastructure. ↩︎
Joshua Bloch, Effective Java, 3rd Edition, Item 4, discusses noninstantiable utility classes; Oracle Java SE 21 API pages such as Math and Collections are standard-library examples centered on static utility operations. ↩︎
Oracle, Java Language Specification §8.1.1.1 abstract Classes, defines abstract classes as classes that cannot be directly instantiated, while Item 4 of Effective Java explains why abstract is the wrong signal for utility classes that should not be instantiated or extended. ↩︎
Oracle Java SE 21 API, AssertionError, documents the error type; in this idiom, throwing it is a fail-fast guard for accidental constructor calls, while the private constructor is the mechanism that closes the normal construction path. ↩︎
// Singleton with public final field publicclassElvis{ publicclassfinalElvisINSTANCE=newElvis(); privateElvis(){ ... } publicvoidleaveTheBuilding(){ ... } }
私有的构造器仅被调用一次,用来实例化共有的静态 final 域 Elvis.INSTANCE。由于缺少共有的或者受保护的构造器,所以保证了Elvis的全局唯一性:一旦 Elvis 类被实例化,只会存在一个Elvis实例,不多也不少。[3]
// readResovle method to preserve singleton property private Object readResolve(){ // Return the one true Elvis and let the garbage collector take care of the Elvis impersonator return INSTANCE; }
Oracle Java SE 21 API, AccessibleObject.setAccessible(boolean), documents suppressing Java language access checks, which is why a private constructor is not an absolute reflection boundary. ↩︎
var HighestSellingProduct = someObject.DoSomeWork(anotherParameter);
查看代码的人会根据自己的理解认定该变量类型,可能恰好与变量在运行期的真实类型相符。但编译器不会像人那样思考,而是根据声明判定其在编译期的类型。若用 var 进行声明,编译器会推断其类型,而开发者看不到编译器推断的类型。因此,他们所认定的类型可能不符,这会在代码的维护中导致错误的修改,并产生一些本来可以避免的 Bug。