440 likes | 563 Views
Effective Java: Classes and Interfaces. Last Updated: Fall 2008. Agenda. Material From Joshua Bloch Effective Java: Programming Language Guide Cover Items 13 through 22 Classes and Interfaces Was Items 12 through 18 in 1 st Edition Moral: Inheritance requires careful programming.
E N D
Effective Java: Classes and Interfaces Last Updated: Fall 2008
Agenda • Material From Joshua Bloch • Effective Java: Programming Language Guide • Cover Items 13 through 22 • Classes and Interfaces • Was Items 12 through 18 in 1st Edition • Moral: • Inheritance requires careful programming
Item 13: Minimize Accessibility of Classes and Members • Standard advice for information hiding/encapsulation • Decouples modules • Allows isolated development and maintenance • Java has an access control mechanism to accomplish this goal
Make Each Class or Member as Inaccessible as Possible • Standard list of accessibility levels • private • package-private (aka package friendly) • protected • public • Huge difference between 2nd and 3rd • package-private: part of implementation • protected: part of public API
Exception: Exposing Constants • Public constants must contain either primitive values or references to immutable objects • Wrong – Potential Security Hole: public static final Type[] VALUES = {…}; • Problem: • VALUES is final; entries in VALUES are not!
Exposing Constants - Solutions • Correct: private static final Type[] PRIVATE_VALUES = {…}; public static final List VALUES = Collections.unmodifiableList(Arrays.asList (PRIVATE_VALUES)); • Also Correct: private static final Type[] PRIVATE_VALUES = {…}; public static final Type[] values() { return (Type[]) PRIVATE_VALUES.clone(); }
Item 14: In Public Classes, Use Accessors, Not Public Fields • Avoid code such as: Class Point { public double x; public double y; } • No possibility of encapsulation • Use get/set methods instead: Public double getX() { return x; } Public void setX(double x) { this.x = x} • Advice holds for immutable fields as well • Limits possible ways for class to evolve
Item 15: Minimize Mutability • Reasons to make a class immutable: • Easier to design • Easier to implement • Easier to use • Less prone to error • More secure • Easier to share • Thread safe
5 Rules to Make a Class Immutable • Don’t provide any mutators • Ensure that no methods can be overridden (more detail…) • Make all fields final (more detail…) • Make all fields private • Ensure exclusive access to any mutable components (more detail…)
Rule 2: No Overridden Methods • Prevents careless or malicious subclasses from compromising the immutable behavior of the class • How to implement • Make class final (easiest) • Make methods final (allows subclassing) • Make all constructors private or package-private • Requires static factories, which are better anyway
Rule 3: Make All Fields Final • Clearly expresses intent • System enforcement of immutability • Issues with object instantiation and thread access • Sometimes, making fields final is too strong • Lazy initialization may be preferable
Rule 5: Exclusive Access to Mutable Components • Never initialize a mutable field to a client provided reference • Never return a reference to a mutable field • Make defensive copies • See Liskov – Exposing the rep
Typical Transformation • Typical method in mutable class Foo: public void foo(T1 t1, T2, t2, …) {modify “this”} • Immutable version of Foo: public Foo foo(T1 t1, T2, t2, …) { Foo f = … … return f; } • Functional programming vs. procedural programming. • See Liskov’s Poly for an example
Disadvantage: Performance • Typical approach: • Provide immutable class • Provide mutable companion class for situations where performance is an issue • Clients choose on performance needs • Example in Java Library: • String (Immutable) • StringBuilder (Companion Mutable Class) • Static factories can cache frequently used items • More on this in Liskov 15
Item 16: Favor Composition over Inheritance • Issue ONLY for implementation inheritance • Interface inheritance does NOT have these problems • Inheritance breaks encapsulation! • Difficult to evolve superclass without breaking subclasses • Difficult for superclass to maintain invariants in face of malicious/careless subclass
Example: Broken Subtype public class IHashSet extends HashSet private int addCount = 0; // add() calls public IHashSet() {} public IHashSet(Collection c) { super(c); } public boolean add(Object o) { addCount++; return super.add(o);} public boolean addAll(Collection c) { addCount += c.size(); return super.addAll(c);} }
Broken Example, continued • So, what’s the problem? IHashSet s = new IHashSet(); s.addAll(Arrays.asList(new String[] {“Snap”, “Crackle”, “Pop”})); • What does addCount() return? • 3? • 6? • Internally, HashSet’s addAll() is implemented on top of add(), which is overridden. Note that this is an implementation detail, so we can’t get it right in IHashSet
Source of Difficulty: Overridden Methods • Overriding methods can be tricky • May break the superclass invariant • Overridden method does not maintain invariant • May break subclass invariant • New methods may be added to superclass • What if new method matches subclass method? • Also, recall problems with equals() and hashCode()
Composition • Fortunately, composition solves all of these problems – even though it makes the programmer work harder // Inheritance public Class Foo extends Fie {…} // Composition public class Foo { private Fie f = …; // Note: forwarded methods …}
Revisiting the Example public class ISet implements Set { private final Set s; private int addCount = 0; public ISet (Set s) { this.s = s} public boolean add(Object o) { addCount++; return s.add(o); } public boolean addAll (Collection c) { addCount += c.size(); return s.addAll(c); } // forwarded methods from Set interface
A more elegant version (without generics – See Bloch for these) // Reusable Forwarding Class public class ForwardingSet implements Set { private final Set s; public ForwardingSet (Set s) { this.s = s;} public void clear() { s.clear();} public boolean contains(Object o) { return s.contains(o);} // plus all the other methods } // Wrapper class – public class ISet extends ForwardingSet { private int addCount = 0; public ISet (Set s) { super(s);} @Override public boolean add(Object o) { addCount++; return super.add(e); } // other instrumented methods }
This is Cool! • Consider temporarily instrumenting a Set • Note that Set is an interface static void f(Set s) { ISet myS = new ISet (s); // use myS instead of s // all changes are reflected in s! }
A Variation That Doesn’t Work public class ICollection implements Collection { private final Collection c; private int addCount = 0; public ICollection (Collection c) { this.c = c} public boolean add(Object o) {…} public boolean addAll (Collection c) {…} // forwarded methods from Collection interface public boolean equals(Object o) { return c.equals(o); } // Now, we’re dead!
This is no longer Cool! • Consider temporarily instrumenting a Set • Note: Set is a subtype of Collection Set s = new HashSet(); ICollection t = new ICollection(s); s.equals(t) // c is not a Set, so false t.equals(s) // t.c == s, so true • Issue: Set has a uniform equals contract; Collection does not (and should not…) • Keep this in mind when using this model.
Item 17: Design and Document for Inheritance • Or else prohibit it. • First, document effects of overriding any method • Document “self use”, as in IHashSet example • This is “implementation detail”, but unavoidable, since subclass “sees” implementation. • Inheritance violates encapsulation!
Efficiency May Require Hooks • protected methods may be required for efficient subclasses. • Example: • protected removeRange() method in AbstractList • Not of interest to List clients • Only of interest to implementers of AbstractList – provides fast clear() operation • Alternative – O(n^2) clear operation
Inheritance is Forever • A commitment to allow inheritance is part of public API • If you provide a poor interface, you (and all of the subclasses) are stuck with it. • You cannot change the interface in subsequent releases.
Constructors Must Not Invoke Overridable Methods // Problem – constructor invokes overridden m() public class Super { public Super() { m();} public void m() {…}; } public class Sub extends Super { private final Date date; public Sub() {date = new Date();} public void m() { // access date variable} }
What Is the Problem? • Consider the code Sub s = new Sub(); • The first thing that happens in Sub() constructor is a call to constructor in Super() • The call to m() in Super() is overridden • But date variable is not yet initialized! • Further, initialization in Super m() never happens! • Yuk!
Inheritance and Cloneable, Serializable • Since clone() and readObject() behave a lot like constructors, these methods cannot invoke overridable methods either. • Problem – access to uninitialized state • For Serializable • readResolve(), writeReplace() must be protected, not private (or they will be ignored in subclass.)
Bottom Line • Be sure you want inheritance, and design for it, or • Prohibit inheritance • Make class final, or • Make constructors private (or package-private)
Item 18: Prefer Interfaces to Abstract Classes • Existing classes can be easily retrofitted to implement a new interface • Same is not true for abstract classes due to single inheritance model in Java • Interfaces are ideal for “mixins” • Example: Comparable interface • Interfaces aren’t required to form a hierarchy • Some things aren’t hierarchical
More Interfaces • Wrapper idiom a potent combination with interfaces • See ISet example • Possible to provide skeletal implementations for interfaces • java.util does this with AbstractCollection, AbstractSet, AbstractList, and AbstractMap
Example for AbstractList static List intArrayAsList(final int[] a) { if (a == null throw new NPE(…); return new AbstractList() { public Object get(int i) { return new Integer(a[i]);} public int size() { return a.length;} public Object set(int i, Object o) { int oldVal = a[i]; a[i] = ((Integer) o).intValue(); return new Integer(oldVal);}};}
This Implementation Does a Lot! • The List interface includes many methods. • Only 3 of them are explicitly provided here. • This is an “anonymous class” example • Certain methods are overridden • Note that it is possible to add a method to an abstract class in a new release • It is not possible to add a method to an interface
Item 19: Use Interfaces Only to Define Types • Example that fails the test: • “Constant” interface – avoid public interface PhysicalConstants { static final double AVOGADROS = … …} • Why is this bad? • Users of a class that implements this interface don’t care about these constants! • Think about the client, not the implementor
Alternative to Constants in Interfaces • Constant utility class public class PhysicalConstants { public static final double AVOGADROS = … }
Item 20: Prefer Class Hierarchies to Tagged Classes //Tagged Class – vastly inferior to a class hierarchy class Figure { enum Shape { RECTANGLE, CIRCLE }; final Shape shape; // Tag field double length; double width; // for RECTANGLE double radius; // for CIRCLE Figure (double length, double width) {…} // RECTANGLE Figure (double radius) {…} // CIRCLE double area() { switch (shape) { case RECTANGLE: return length*width; case CIRCLE: return Math.PI*(radius * radius); default: throw new AssertionError(); } }
Item 20 Continued: A much better solution //Class hierarchy replacement for a tagged class abstract class Figure { // Note: NOT instantiable! abstract double area(); } class Circle extends Figure { final double radius; Circle(double rad) { radius = rad; } double area() { return Math.PI*(radius*radius); } } class Rectangle extends Figure { final double length; final double width; Rectangle (double len; double wid) { length = len; width = wid; } double area() { return length*width; } }
Item 21: Use Function Objects to Represent Strategies • In Java, can’t pass functions directly • Only Objects can be arguments • So, pass Objects to pass functions • Example: Instances of Comparator • We covered this in Liskov 8
Item 22: Favor Static Member Classes Over Nonstatic • Nested classes • Defined within another class • Four flavors • Static member class • Nonstatic member class (inner) • Anonymous class (inner) • Local class (inner)
Static vs NonStatic • Static requires no connection to enclosing instance. • Nonstatic always associated with enclosing instance • Possible performance issue • Possible desire to create by itself • Hence recommendation to favor static • See Iterator implementations
Anonymous class – (another) example // Typical use of an anonymous class Arrays.sort (args, new Comparator() { public int compare(Object o1, Object o2) { return ((String) o1).length() – (String) o2).length(); } }); Question: Does this satisfy the compare() contract?
Local classes • Declared anywhere a local variable may be declared • Same scoping rules • Have names like member classes • May be static or nonstatic (depending on whether context is static or nonstatic)