450 likes | 479 Views
Effective Java, Chapter 4: 3 rd Edition, Classes and Interfaces. Last Updated: Fall 2018. Agenda. Material From Joshua Bloch Effective Java: Programming Language Guide Cover Items 14 through 20 “Classes and Interfaces” Chapter Was Items 12 through 18 in 1 st Edition Moral:
E N D
Effective Java, Chapter 4: 3rd Edition, Classes and Interfaces Last Updated: Fall 2018
Agenda • Material From Joshua Bloch • Effective Java: Programming Language Guide • Cover Items 14 through 20 • “Classes and Interfaces” Chapter • Was Items 12 through 18 in 1st Edition • Moral: • Inheritance requires careful programming
Item 15: 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 16: In Public Classes, Use Accessors, Not Public Fields • Avoid code such as: class Point { public double x; public double y; } • No possibility of encapsulation • Also, public mutable fields are not thread safe • 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
Example: Questionable Use of Immutable Public Fields public final class Time { private static final int HOURS_PER_DAY = 24; private static final int MINUTES_PER_HOUR = 60; public final int hour; // Not possible to change rep for class public final int minute; // But we can check invariants, since fields are final public Time ( int hour, int minute ) { if (hour < 0 || hour >= HOURS_PER_DAY) throw new IllegalArgumentException(“Hour: “ + hour); if (minute < 0 || minute >= MINUTES_PER_HOUR) throw new IllegalArgumentException(“Minute: “ + minute); this.hour = hour; this.minute = minute; } // Remainder omitted }
Item 17: 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
Example: Class Complex public final class Complex { private final double re; private final double im; public Complex (double re, double im) { this.re = re; this.im = im;} // Accessors with no corresponding mutators public double realPart() { return re; } public double imaginaryPart() { return im; } // Note standard “producer” pattern public Complex add (Complex c) { return new Complex (re + c.re, im + c.im); } // similar producers for subtract, multiply, divide // implementations of equals() and hashCode() use Double class }
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
Example: Static Factory for Complex Class // Note that this version is no longer a final class public class Complex { private final double re; private final double im; // private constructor shuts off subclassing // package friendly constructor would allow local subclassing private Complex (double re, double im) { this.re = re; this.im = im; } // Static factory – could have lots of possible implementations public static Complex valueOf (double re, double im) { return new Complex (re, im); } // Remainder as before }
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 • Note what this says about mutability • Performance advantage often illusory!
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 Poly 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) • StringBuffer (Deprecated Companion Mutable Class) • Static factories can cache frequently used items
Item 18: Favor Composition over Inheritance • Issue ONLY for implementation inheritance • Interface inheritance does NOT have these problems • Interface inheritance is better for lots of reasons… • 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 // This is very common, but broken public class InstrumentedHashSet<E> extends HashSet<E> private int addCount = 0; // add() calls public InstrumentedHashSet() {} public InstrumentedHashSet(Collection<? extends E> c) { super(c); } public boolean add(E o) { addCount++; return super.add(o); } public boolean addAll(Collection<? extends E> c) { addCount += c.size(); return super.addAll(c); } // accessor method to get the count public int addCount() { return addCount; } }
Broken Example, continued • So, what’s the problem? InstrumentedHashSet<String> s = new InstrumentedHashSet<String>(); s.addAll(Arrays.asList(“Snap”, “Crackle”, “Pop”)); • What does addCount() return? • 3? • 6? • Internally, HashSet 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 InstrumentedHashSet
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 // Note that an InstrumentedSet IS-A Set public class InstrumentedSet<E> implements Set<E> { private final Set<E> s; private int addCount = 0; public InstrumentedSet (Set<E> s) { this.s = s} public boolean add(E o) { addCount++; return s.add(o); } public boolean addAll (Collection<? extends E> c) { addCount += c.size(); return s.addAll(c); } // forwarded methods from Set interface }
A More Elegant Version // Wrapper class – uses composition in place of inheritance public class InstrumentedSet<E> extends ForwardingSet<E> { private int addCount = 0; public InstrumentedSet (Set<E> s) { super(s); } @Override public boolean add(E e) { addCount++; return super.add(e); } @Override public boolean addAll(Collection <? extends E> c) { addCount += c.size(); return super.add(c); } public int getAddCount() { return addCount; } } // Reusable Forwarding Class public class ForwardingSet<E> implements Set<E> { private final Set<E> s; public ForwardingSet (Set<E> s) { this.s = s;} public void clear() { s.clear();} public boolean contains(E o) { return s.contains(o);} // plus forwards of all the other Set methods }
This is Cool! • Consider temporarily instrumenting a Set • Note that Set is an interface static void f(Set<Dog> s) { InstrumentedSet<Dog> myS = new InstrumentedSet<Dog> (s); // use myS instead of s // all changes are reflected in s! }
A Variation That Doesn’t Work // Note that this uses the “basic” version, // but extending a “ForwardingCollection” doesn’t // work either, for the same reason public class InstrumentedCollection<E> implements Collection<E> { private final Collection<E> c; private int addCount = 0; public InstrumentedCollection (Collection<E> c) { this.c = c} public boolean add(E o) {…} public boolean addAll (Collection<? extends E> 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<Dog> s = new HashSet<Dog>(); InstrumentedCollection<Dog> t = new InstrumentedCollection<Dog>(s); s.equals(t) // t 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 19: Design and Document for Inheritance • Or else prohibit it. • First, document effects of overriding any method • Document “self use”, as in InstrumentedHashSet 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. • TEST for inheritance • How? By writing subclasses
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 20: 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 // Concrete implementation built on abstract skeleton static List<Integer> intArrayAsList(final int[] a) { if (a == null) throw new NPE(…); // Note anonymous class 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 22: 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 23: 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) { // Gag! Roll-your-own dispatching! case RECTANGLE: return length*width; case CIRCLE: return Math.PI*(radius * radius); default: throw new AssertionError(); } }
Item 23 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 24: 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
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)