1.1 Objects and Classes
Primitive types
Primitive types are handled by value
There are operators for assignment,equality, arithmetic,and logic
There are object wrappers for primitives (Byte,Short, Integer etc.)
Reference types
A reference is a variable that stores the address of an object
–If the variable does not reference an object, then the variable has the
value null (default reference value)
• All non-primitive types are reference types
–including Strings, arrays and enums (see Oracle’s tutorial)
• The following three operators can be applied to any reference type
= for assignment
== and != for equality (identity) comparison
• The concatenation operator (+) can be applied to a reference to a String object
Other operations
Other operations deal with the object that is being referenced. The basic
actions are:
–Apply a type conversion
–Access a member field or method using the dot operator (.)
–Use the instanceof operator to verify the type of an object
Objects have to be explicitly created using new
*with the exception of strings, which can be done with string literal:
String a = “abc”;
String b = “abc”;
System.out.println(a == b); // true
String c = new String(“abc”);
String d = new String(“abc”);
System.out.println(c == d); // false
Object Deletion
Objects do not have to be explicitly deleted
• Java’s automatic garbage collection system searches for objects that are no longer referenced and can be deleted (reallocating)
• From the programmer’s viewpoint all this happens automatically and, in almost all cases, no manual intervention is required
Two objects are created True
More than one reference is created True
创建了一个具有值2000的新的Integer对象,并将对这个新对象的引用赋给了doSomethingWith方法中的局部变量i。然而,这并不影响在main方法中创建的原始Integer对象。
这段代码中并没有“多个引用”指向相同的Integer对象。doSomethingWith方法中的局部变量i是与main方法中的i不同的变量,将新对象赋给它并不会影响原始引用。
Line 7 print “2” True
Line 9 prints “2000” True
Line 4 prints “2000" False
Java中的参数传递机制
在Java中,基本数据类型是按值传递的,而对象引用也是按值传递的。在这个特定的例子中,Integer是一个对象,但它的值是不可变的。当你将一个Integer对象传递给方法时,你实际上是将对象引用的副本传递给了方法。在方法内部,虽然你可以通过引用修改对象的属性(如果是可变对象),但重新分配引用并不会影响原始引用指向的对象。
尽管在doSomethingWith方法内部修改了引用 i,重新分配了新的Integer对象,但这并不会影响main方法中的原始引用指向的对象。
在main方法中原始引用指向的Integer对象并没有被修改。
Two objects are deleted True
创建了一个Integer对象,其值为2,然后将该对象的引用分配给i。
doSomethingWith方法被调用,传递了这个Integer对象的引用。在doSomethingWith方法中,输出了原始对象的值(2),然后创建了一个新的Integer对象(值为2000),并将该对象的引用赋给了局部变量i。
需要注意的是,这里创建的第二个Integer对象(值为2000)仅在doSomethingWith方法的作用域内存在,它不会影响到main方法中的原始Integer对象。所以,实际上只有一个Integer对象(值为2)在整个程序执行期间。
Two objects are created False
More than one reference is created True
将 peter 引用传递给 doSomethingWith 方法中的参数 p。此时,p 和 peter 引用同一个 Person 对象。
Line 7 prints “Peter" True
Line 9 prints “Bob" True
Line 4 prints “Peter” False
Two objects are deleted False
code demo:
public class MethodParamsQuizOne {
public static void main(String[] args) {
Integer i = new Integer(2);
doSomethingWith(i);
System.out.println(i);
}
static void doSomethingWith(Integer i) {
System.out.println(i);
i = new Integer(2000);
System.out.println(i);
}
}
public class MethodParamsQuizOneNotDeprecated {
public static void main(String[] args) {
Integer i = Integer.valueOf(2);
doSomethingWith(i);
System.out.println(i);
}
static void doSomethingWith(Integer i) {
System.out.println(i);
i = Integer.valueOf(2000);
System.out.println(i);
}
}
public class MethodParamsQuizTwo {
public static void main(String[] args) {
Person peter = new Person(“Peter”);
doSomethingWith(peter);
System.out.println(peter);
}
static void doSomethingWith(Person p) {
System.out.println§;
p.setName(“Bob”);
System.out.println§;
}
}
1.2 Classes and Inheritance
Classes
Objects are defined in terms of classes. Knowing the type or class of an
object tells you a lot about what it does.
• We expect bicycles to have two wheels, handlebars and pedals
• Object-oriented systems use a class hierarchy to define classes in terms of other classes
–Mountain bikes, racing bikes and tandems are all types of bicycle
–They are all subclasses of the bicycle class. The bicycle class is the
superclass of mountain bikes, racing bikes and tandems.
Inheritance
Each subclass inherits the state and methods of its superclass
–Mountain bikes, racing bikes and Tandems share some behaviour. e.g.
braking and changing pedalling speed.
• Subclasses can also add state and methods not available to their superclass
–Tandems have two seats and an extra set of handlebars
• Subclasses can also override (or specialise) inherited methods
–a mountain bike might override the changeGear method of its superclass to use extra gears
The inheritance tree or hierarchy can be as deep as necessary (without
limit). Methods and state are inherited down through the levels.
• The Object class is at the top of class hierarchy. Each class is its
descendant (directly or indirectly).
• A variable of type Object can hold a reference to any object (any
instance of a class or an array)
• Object provides behaviour that is common to all objects running in the
Java Virtual Machine.
– for example, all classes inherit equals, hashCode and toString
Benefits of inheritance
Superclass code can be reused by many subclasses, each of which can
provide specialised behaviour based on common superclass elements
• Abstract superclasses can define, and may partially implement,common behaviour
–Subclasses complete the details
• Classes and inheritance hierarchies convey important type information
–Do not use inheritance just to acquire behaviour
–Respect the “is-a” relationship, it does not just mean “behaves-like”
public class Vehicle {
private String brand;
private String owner;
public Vehicle(String vBrand, String name) {
brand = vBrand;
owner = name;
}
public String getBrand() { return brand; }
public String getOwner() {
return owner;
}
}
public class Car extends Vehicle {
private int numberOfDoors;
public Car(String vBrand, String name, int noOfDoors) {
// call the Vehicle class constructor
super(vBrand, name);
numberOfDoors = noOfDoors;
}
public int getNumberOfDoors(){
return numberOfDoors;
}
}
public class PetrolCar extends Car {
private int tankCapacity;
public PetrolCar(String vBrand, String name,
int noOfDoors, int capacity) {
// call the superclass constructor
super(vBrand, name, noOfDoors);
tankCapacity = capacity;
}
public int getTankCapacity() { return tankCapacity; }
}
public class UseVehicle {
public static void main(String[] args) {
PetrolCar pCar = new PetrolCar(“Ferrari”, “John Smith”, 2, 80);
System.out.println(pCar.getBrand());
System.out.println(pCar.getOwner());
System.out.println(pCar.getNumberOfDoors());
System.out.println(pCar.getTankCapacity());
}
}
public class ElectricCar extends Car {
private String batteryType;
public ElectricCar(String vBrand, String owner, int noOfDoors,
String battery) {
super(vBrand, owner, noOfDoors);
batteryType = battery;
}
public String getBatteryType() {
return batteryType;
}
}
public class UseVehicle {
public static void main(String[] args) {
…
ElectricCar eCar = new ElectricCar(“Tesla”, “Charlie Chaplin”,
4, “Lithium”);
System.out.println(eCar.getBrand());
System.out.println(eCar.getOwner());
System.out.println(eCar.getNumberOfDoors());
System.out.println(eCar.getBatteryType ());
System.out.println(eCar.getTankCapacity());
// line above gives compilation error!!
}
}
The previous slide gives the compilation error:
Method getTankCapacity() is undefined for the type ElectricCar
• This is correct – ElectricCars don’t have tankCapacity!
–ElectricCar and PetrolCar are derived from the same superclass but do not have identical methods.
• Note the use of super() to call the constructor of the superclass
–must be the first statement in a constructor.
Overriding behaviour
We wish to add a printDetails method to the preceding classes that prints brand, owner, and other information relevant to the type of Vehicle
–The method should print details specific to the type of object on which it is invoked
• So, subclasses of Vehicle will override the printDetails method
–They can re-use the behaviour of their superclass printDetails method
and print additional information relevant to the subclass
Vehicle printDetails method inherited by all subclasses
public class Vehicle {
…
public void printDetails() {
System.out.println(“Brand: “ + brand);
System.out.println(“Owner Name: “ + owner);
}
}
public class Car extends Vehicle {
…
// override printDetails
public void printDetails() {
// call Vehicle printDetails
super.printDetails();
// and specialise behaviour
System.out.println(“Number of Doors: “ +
numberOfDoors);
}
}
public class PetrolCar extends Car {
…
// override printDetails
public void printDetails() {
// call Car printDetails
super.printDetails();
// and specialise behaviour
System.out.println(“Tank Capacity: “ + tankCapacity);
}
}
Reference assignment (lhs = rhs) means lhs reference refers to the same object as rhs
–For successful assignment (lhs = rhs), either
» lhs reference is an inclusive supertype of rhs, or
» rhs must be cast by type conversion down to its lhs subclass
»use instanceof to check that casting will succeed
»E.g.
Vehicle vehicle = new Vehicle(“Toyota”, “John Smith”);
PetrolCar pCar = new PetrolCar(“Jaguar”, “Nick Cook”, 4, 100);
if (vehicle instanceof PetrolCar) {
pCar = (PetrolCar) vehicle;
}
Dynamic Binding
Method overriding works by dynamic binding
–Each object has a table of its methods
–At run-time Java searches the table for the correct version of an overridden method
–The compiler does not know which method will be called
–The compiler can only check that the type of the reference variable
provides access to a given method
• A final method cannot be overridden, for example:
public final void printDetails() {… }
–Prevents subclass overriding of printDetails
–Gives some efficiency gain (reducing run-time search) and is a design
declaration
To overload a method, declare a new method with the same name but a
different parameter list. For example:
void printDetails(PrintStream stream) { … }
–The method name is overloaded.
–The number and types of the parameters determines which method is
executed
• In contrast, overridden methods have identical parameter lists
–The run-time instance of the object determines which version of an
Inherit a method
–Subclass accepts the superclass implementation of a method (e.g. Vehicle getBrand)
• Add a method
–Subclass provides a new method that does not exist in any superclass (e.g.PetrolCar getTankCapacity)
• Override a method
–Subclass provides a different implementation of a superclass method (e.g.PetrolCar printDetails)
• Overload a method
–Subclass provides a method with the same name but different parameters
public class UseVehicle {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle(“Skoda”, “Steve Riddle”);
Car car = new Car(“BMW “, “Jane Watt”, 4);
PetrolCar pCar = new PetrolCar(“Ferrari”, “John Smith”, 2, 80);
ElectricCar eCar = new ElectricCar(“Tesla”, “Charlie Chaplin”, 4,
“Lithium”);
System.out.println(pCar.getBrand());
System.out.println(pCar.getOwner());
System.out.println(pCar.getNumberOfDoors());
System.out.println(pCar.getTankCapacity());
System.out.println(”-------------------------”);
System.out.println(eCar.getBrand());
System.out.println(eCar.getOwner());
System.out.println(eCar.getNumberOfDoors());
System.out.println(eCar.getBatteryType ());
// Can I do the following:
//System.out.println(eCar.getTankCapacity());
/*
vehicle.printDetails();
System.out.println(“-------------------------”);
car.printDetails();
System.out.println(“-------------------------”);
pCar.printDetails();
System.out.println(“-------------------------”);
eCar.printDetails();
System.out.println(“-------------------------”);
/
/
vehicle = pCar;
vehicle.printDetails();
System.out.println(“-------------------------”);
/
/
printHierarchy(vehicle);
printHierarchy(car);
printHierarchy(pCar);
printHierarchy(eCar);
*/
}
private static void printHierarchy(Vehicle p) {
System.out.println(p.getBrand()
- " is: " + p.getClass());
System.out.println(p.getBrand() - " is a Electric Car: "
- (p instanceof ElectricCar));
System.out.println(p.getBrand() - " is a Petrol Car: "
- (p instanceof PetrolCar));
System.out.println(p.getBrand() - " is a car: "
- (p instanceof Car));
System.out.println(p.getBrand() - " is a Vehicle: "
- (p instanceof Vehicle));
System.out.println(p.getBrand() - " is a Object: "
- (p instanceof Object));
}
}
1.3 The Object Class
public class Object {
public Object() { … }
public final Class<? extends Object> getClass() { … }
public final void notify() { … }
public final void notifyAll() { … }
public final void wait() { … }
public final void wait(long timeout) { … }
public final void wait (long timeout, int nanos){ … }
protected Object clone() { … }
protected void finalize() { … }
public boolean equals(Object obj)
{ return this == obj; }
public int hashCode() { … }
public String toString() { … }
Methods provided by Object
Object provides the following final methods that cannot be overridden
– getClass
» Returns a representation of an object’s run-time class (name etc. and
hook into Java reflection API)
– notify, notifyAll and wait
» Used for concurrency control
• Object provides the following methods that can be overridden
– clone, finalize
– equals and hashCode
– toString
• Every class written in Java inherits the above methods
Do not override clone or finalize unless you are sure you know what you
are doing
• By default the protected clone method throws
CloneNotSupportedException
– If you write a class that implements Cloneable then you should override
clone with a method that returns a copy of the object on which it is
invoked
– Do not do this unless you understand Bloch-EJ Item 13!
• finalize is called by the garbage collector to release non-memory
resources
– Overriding finalize is “unpredictable, often dangerous and generally
unnecessary” (see Bloch-EJ Item 8)
By default, equals returns true if two references refer to the same object
(the identity test)
– Override equals if logical equivalence is required
• hashCode returns an integer value for an object to support hashtable data structures
– You must override hashCode if you override equals
• toString returns a String representation of an object
– It is good practice to override toString
• Next, we will look at this, instanceof, boolean operators and boolean
expressions that are useful when overriding equals and hashCode
• Then we will look at overriding equals, hashCode and toString
The this reference
this is a reference to the current object
– Provides access to shadowed members
» member fields with the same names as method parameters (e.g.
this.brand = brand;)
– Can be used to detect aliasing
» when a method parameter may refer to the object on which the
method is invoked, in which case
this == parameter returns true
– Supports constructor chaining
public Vehicle(String vBrand) { this (vBrand, null); }
The instanceof operator
The instanceof operator is a run-time test for the type of a referenced
object
ref instanceof ClassName
is true if ref refers to an instance of ClassName
• If ref is null, ref instanceof ClassName is false (null is not an instance of any class)
• If ref instanceof ClassName is true, it is true for all superclasses of
ClassName
• So, what does ref instanceof Object evaluate to?
• Use instanceof before a type conversion to check that the conversion
will succeed
The if/else statement:
boolean b;
if (cond) {
b = boolExpr1;
}
else {
b = boolExpr2;
}
Can be replaced by the conditional boolean expression:
boolean b = cond ? boolExpr1 : boolExpr2;
• Conditional boolean expressions can be combined with boolean operators
• Use boolean expressions to clarify code (not for complex evaluations)
Equality versus identity
The default (Object) implementation of equals tests for identity not logical equivalence
o1.equals(o2) if and only if o1 == o2
Consider:
Vehicle a = new Vehicle(“Toyota”, “John Smith”);
Vehicle b = new Vehicle(“Toyota”, “John Smith”);
a and b are logically the same vehicle but they do not refer to the same
object. So, by default:
a.equals(b) returns false
• If logical equivalence is important, override equals
equals must implement the following equivalence relation:
- It is reflexive: x.equals(x) is true
- It is symmetric: if x.equals(y) then y.equals(x)
- It is transitive: if x.equals(y) and y.equals(z) then x.equals(z)
- It is consistent: for multiple invocations, x.equals(y) is consistently true or consistently false unless x or y changes independently between invocations
- For non-null x, x.equals(null) is false
The default equals implementation satisfies the contract
Overriding implementations must also satisfy it
public boolean equals(Object rhs) {
// reflexivity
if (this == rhs) return true;
// non-nullity
if (!(rhs instanceof Vehicle)) return false;
// consistency
Vehicle p = (Vehicle) rhs;
return (brand == null
? p.brand == null
: brand.equals(p.brand))
&& (owner == null
? p. owner == null
: owner.equals(p.owner));
}
如果当前对象的 brand 属性为 null,则检查另一个对象 p 的 brand 属性是否也为 null,如果是,则返回 true,否则继续比较两个对象的 brand 属性是否相等。
如果当前对象的 brand 属性不为 null,则使用 brand.equals(p.brand) 来比较当前对象的 brand 属性和另一个对象 p 的 brand 属性是否相等。
If there is an additional attribute for Vehicle like prodcutionYear
(which is an int) then we can add the following:
&& productionYear == p.productionYear
General equals method pattern
- Test for identity, return true if this == rhs
- Test for non-nullity and type
!(rhs instanceof ThisClass) - Cast rhs to the correct type
- Compare all significant fields of this with rhs fields
• Use equals for object fields
• Arrays.equals for arrays, Arrays.deepEquals for nested arrays
• Use == for byte, short, int, long, boolean and char
• Use Float.floatToIntBits for float
• Use Double.doubleToLongBits for double
Inheriting a contradiction
- Two vehicles with the same brand and owner are the same vehicle
- Two vehicles with the same brand and owner and different door number are different vehicles. Therefore:
- {“Toyota”, “John Smith”} and {“Toyota”, “John Smith”, 2}
are the same vehicle, and so are {“Toyota”, “John Smith”} and
{“Toyota”, “John Smith”, 4} - {Toyota”, “John Smith, 2} and {Toyota”, “John Smith, 4}
are not the same vehicle
• Solution: either door number is not significant, or every vehicle must have a door number
• Lesson:
–A program realises a model/design
–A program cannot fix a broken model
–the Vehicle hierarchy is broken – it is an anti-pattern
equals and inheritance
The equals method pattern does not by itself guarantee compliance with
the equals contract
– In general, it is not possible to extend an instantiable class and add a
new field and at the same time comply with the equals contract
» If the new field is used in the test for logical equivalence, then it will
break symmetry or transitivity
Solutions:
– Prevent instantiation of superclasses (make all but the ultimate subclasses
abstract)
– Maintain all state (member fields) in the superclass
– Use composition instead of inheritance
• See Bloch-EJ Item 10
Hash function
• A hash function computes an integer hash code from an object
– A good hash function generates different hash codes for different objects
• An object’s hash code is used by hash-based collections for fast look-up of objects
• If an object’s hashCode and equals methods are incompatible, then the object will not work correctly with hash-based collections
Always override hashCode when you override equals
• The hashCode contract is:
- It must be consistent: return the same integer as long as no information used in equals is modified
- If x.equals(y)
then x.hashCode() == y.hashCode() - If !x.equals(y) then it is not required that x.hashCode() != y.hashCode
()
But distinct integer results for unequal objects is desirable
• (2) is violated if you override equals but do not override hashCode
Hash function recipe
• Recipe for a hash function, from Bloch- EJ Item 11
• It gives few collisions for distinct objects
- Choose an initial value and a multiplier (for example, 17 and 37; use prime numbers)
- Set hash code (hc) to the initial value
- For each field used to test for equality in the equals method, update the value of hc as follows:
hc = multiplier * hc + field.hashCode()
• For primitive values use their int equivalent or their wrapper class
hashCode, e.g. if field is an int, we use the field itself:
hc = multiplier * hc + field
• Use Arrays.hashCode(field) or Arrays.deepHashCode(field) for array fields
• See Bloch-EJ Item 11 for a complete explanation of the recipe and
caching hash codes etc.
• See standard Javadocs for explanation of Long and Double hashCode()
– Provides further insight into some of the issues
public int hashCode() {
int hc = 17;
int multiplier = 37;
hc = multiplier * hc
- (brand == null ? 0 :brand.hashCode());
hc = multiplier * hc - (owner == null ? 0 : owner.hashCode());
return hc;
}
If there is an additional attribute for Vehicle like prodcutionYear(which is an int), and if this attribute is used as part of the equals method, then we can add the following:hc = multiplier * hc + productionYear
• There is no need to override equals:
– When logical equivalence is not important
– When each instance of a class is inherently unique
– When a superclass overrides equals and the inherited behaviour is
correct (see Collections)
– When a class is private or package-private and you know equals will
never be invoked
• If you do not override equals, do not override hashCode
Overriding toString
• It is good practice to override toString
• toString is automatically invoked when using the string concatenator
(+) or when calling println etc.
• If only for debugging, users of objects will do:
System.out.println(anObject)
• So give them something more informative than:
YourClassName@
• When practical, return all the interesting information contained in the
object (its state)
• See Bloch-EJ Item 12
public String toString() {
return brand + " - " +
owner;
}
Consider providing a valueOf method
• Users will consider the return value of toString to be part of the interface/contract for a class
– They will try to use the returned string programmatically (parsing it to
obtain information about object state)
– Document whether the format is subject to change
• For value classes, it is recommended that you specify (and fix) the format of the return value
• If you specify the format of the return value and the value contains all the information necessary to construct an object instance, then provide a corresponding String valueOf constructor to instantiate an object from its string representation
public static Vehicle valueOf(String vehicle) {
final String[] parts = vehicle.split(" - ");
final String brand = parts[0].equals(“null”)
? null
: parts[0];
final String owner = parts[1].equals(“null”)
? null
: parts[1];
return new Vehicle(brand, owner);
}
code demo
public class Vehicle {
private String brand;
private String owner;
public Vehicle(String vBrand, String name) {
brand = vBrand;
owner = name;
}
public Vehicle(String vBrand) {
this(vBrand, null);
}
public String getBrand() { return brand; }
public String getOwner() {
return owner;
}
public void printDetails() {
System.out.println("Brand: " + brand);
System.out.println("Owner Name: " + owner);
}
//overriding equals
public boolean equals(Object rhs) {
// reflexivity
if (this == rhs) return true;
// non-nullity
if (!(rhs instanceof Vehicle)) return false;
// consistency
Vehicle p = (Vehicle) rhs;
return (brand == null? p.brand == null: brand.equals(p.brand))
&& (owner == null? p. owner == null:
owner.equals(p.owner));
}
//overriding hashcode (using Bloch Recipe)
public int hashCode() {
int hc = 17;
int multiplier = 37;
hc = multiplier * hc
- (brand == null ? 0 : brand.hashCode());
hc = multiplier * hc - (owner == null ? 0 : owner.hashCode());
return hc;
}
//overriding toString
public String toString() {
return brand + " - " + owner;
}
public static Vehicle valueOf(String vehicle) {
final String[] parts
= vehicle.split(" - ");
final String brand = parts[0].equals(“null”)
? null
: parts[0];
final String owner = parts[1].equals(“null”)
? null
: parts[1];
return new Vehicle(brand, owner);
}
}
public class UseVehicleEquals {
public static void main(String[] args) {
final Vehicle v1 = new Vehicle(“Toyota”, “John Smith”);
final Vehicle v2 = new Vehicle(“Toyota”, “John Smith”);
final Vehicle v3 = Vehicle.valueOf(“BMW - Marta Koutny”);
System.out.println("v1 == v2: " + (v1 == v2));
System.out.println("v1.equals(v2): " + v1.equals(v2));
System.out.println(v1.toString());
System.out.println(v2.toString());
System.out.println("v1.equals(v2): " + v1.equals(v2));
System.out.println("v1.hashCode() == v2.hashCode(): "
- (v1.hashCode() == v2.hashCode()));
System.out.println(v1.toString());
System.out.println(v2.toString());
v3.printDetails();
}
}
2.1 Defensive programming and immutability
Use packages
A package is a namespace to organise and unambiguously identify related classes (qualifying a class name)
– Packages provide a scope for accessibility of classes and their members
– Classes in a package can be imported
• Use packages to provide an informal hierarchical structure to a code base
– Good practice to always declare a package for your classes
– Do not declare your own classes to be members of java or javax
packages
• Adhere to package naming conventions
– Short, lower case parts beginning with an organisation’s internet
domain
• Class member access:
– private: accessible only inside the class where the member is declared
– package-private (no modifier): accessible only from classes in the package
where the member is declared (default access)
– protected: accessible from classes in the package where the member is declared and from any subclasses of the class containing the member
– public: accessible from anywhere
Defensive programming
Good programming practices that protect you from your own programming mistakes, as well as those of others
• It ensures code correctness and reduces the number of bugs
This includes:
– minimize accessibility (Access restrictions): the correct use of the access
modifiers on packages, instance variables and methods
- check parameters
– use Exception for exceptional cases - cater for client assumptions
– defensive copying
• Make each class and each member of a class as inaccessible as possible
• For classes, favour package-private
• For members, favour private before package-private before protected
before public
• public and protected are part of the exported API and incur obligations
for future support
• Non-static member fields should always be private and preferably final
• Avoid protected, it leaks implementation detail
• See Bloch-EJ Item 15
• Check method parameters for validity (Bloch-EJ Item 49)
• For public methods
– Don’t trust the inputs
– Fail quickly and cleanly
– Document exceptions that will be thrown including unchecked exceptions
• For unexported (non-public) methods, assert the validity of parameters
– Inputs are under your control, use:
assert condition;
– If the condition is false, Java throws an AssertionError, which crashes the program. Assertions are very similar to exceptions (they will flag a problem), but unlike exceptions - they won’t suggest any alternative execution path
Defensive programming:exceptions are not for control flow
Use exceptions only for exceptional conditions (Bloch-EJ Item 69)
• Never do anything like the following:
try {
int i = 0;
while (true)
doSomethingTo(someArray[i++]);
} catch (ArrayIndexOutOfBoundsException e){
}
• Exceptions should never be used for ordinary control flow
• Well-designed APIs do not force clients to program to exceptions
• Aside: favour the use of standard exceptions (Bloch-EJ Item 72)
• Return empty arrays or collections, not nulls (Bloch-EJ Item 54)
• The following method forces its clients (i.e., code that uses this method) to cater for the return of either a Date array or a null value
public Date[] getDateArray() {
// note: dates is a List
return dates.size() == 0
? null
: (Date[]) dates.toArray();
}
• Use the following idiom instead
return dates.toArray(new Date[0]);
– Returns a zero-length array when the list is empty and eliminates the
special case
• Make defensive copies when needed (Bloch-EJ Item 50)
• Make defensive copies of each mutable parameter to a constructor or to any mutator method (such as a set method)
– Perform defensive copying before checking validity
– Do not use clone to make a defensive copy of a parameter whose type can be sub-classed by untrusted parties
• For get methods, return defensive copies of mutable internal fields
Immutability
• A class is immutable if its instances cannot be modified
– The state of each instance of an immutable is fixed at instantiation
– java.lang.String is an immutable class
– java.util.Date is not immutable (but should be!)
• To make a class immutable
– Do not provide mutator methods (setter methods) that modify object state
– Forbid method overriding
– Make all fields private and final (Joshua Bloch argues that all classes should be defined as final, unless they are designed for inheritance)
– Use defensive copying to guarantee exclusive access to any immutable components
ImmutableBook example
Are instances of the following class immutable?
public final class ImmutableBook {
private final Author author;
private final Date datePublished;
public ImmutableBook(Author author, Date datePublished) {
if (author == null)
throw new IllegalArgumentException(…);
if (datePublished == null)
throw new IllegalArgumentException(…);
this. author = author;
this. datePublished = datePublished;
}
public Author getAuthor() { return author; }
public Date getDatePublished() { return datePublished; }
}
ImmutableBook - mutable Author component
public final class Author {
private String firstName;
private String lastName;
// constructor goes here
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public void setFirstName(String firstName) {
// check parameter here
this.firstName = firstName;
}
public void setLastName(String lastName) {
// check parameter here
this.lastName = lastName;
}
}
ImmutableBook - correct implementation
public final class ImmutableBook {
private final Author author;
private final Date datePublished;
public ImmutableBook(Author author, Date datePublished) {
// throw NullPointerException for null parameters
this. author = new Author(author.getFirstName(),
author.getLastName());
this. datePublished = new Date(datePublished.getTime());
}
public Author getAuthor() {
return new Author(author.getFirstName(),
author.getLastName());
}
public Date getDatePublished() {
return (Date) datePublished.clone();
}
}
Advantages of immutability
• Immutables are simple and safe and make good building blocks
• An immutable is only ever in exactly one state
• Immutables are thread-safe
• Immutables can be cached
• There is no need to defensively copy or clone an immutable
• Mutable classes built on a foundation of immutable components are easier to maintain
• Safe as set elements or map keys
• So: minimize mutability (Bloch-EJ Item 17)
code demo
public final class Author {
private String firstName;
private String lastName;
/**
- Construct an author from the given first and last
- names.
- @param firstName the first name (part) of the name
- @param lastName the last name (part) of the name
- @throws NullPointerException if either
firstName
orlastName
is- null
- @throws IllegalArgumentException if either
firstName
orlastName
is- empty
/
public Author(String firstName, String lastName) {
setFirstName(firstName);
setLastName(lastName);
}
/* - Return the first part of the name.
- @return the first name
/
public String getFirstName() { return firstName; }
/* - Return the last part of the name.
- @return the last name
/
public String getLastName() { return lastName; }
/* - Set the first part of the name.
- @param firstName the first name
- @throws NullPointerException if
firstName
is null- @throws IllegalArgumentException if
firstName
is empty
/
public void setFirstName(String firstName) {
if (firstName.length() == 0)
throw new IllegalArgumentException(“Empty first name”);
this.firstName = firstName;
}
/*- Set the last part of the name.
- @param lastName the last name
- @throws NullPointerException if
lastName
is null- @throws IllegalArgumentException if
lastName
is empty
/
public void setLastName(String lastName) {
if (lastName.length() == 0)
throw new IllegalArgumentException(“Empty last name”);
this.lastName = lastName;
}
/*- @see java.lang.Object#equals(java.lang.Object)
/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof Author))
return false;
final Author name = (Author) obj;
// note: firstName and lastName are guaranteed
// non-null at construction, no need to check here
return firstName.equals(name.firstName)
&& lastName.equals(lastName);
}
/* - @see java.lang.Object#hashCode()
/
@Override
public int hashCode() {
int hc = 17;
hc = 37 * hc + firstName.hashCode();
return 37 * hc + lastName.hashCode();
}
/* - Returns a string representation of an author. The string
- representation is a first name and last name separated
- by a space character.
- @see java.lang.Object#toString()
- @see #valueOf for the string representation of
- an author
/
@Override
public String toString() {
return firstName + " " + lastName;
}
/* - Constructs an instance of Author from its
- string representation. The string representation
- of an Author is a first name and last name separated
- by a space character. It is assumed that first and last
- names do not themselves contain spaces.
- @param name a name in the specified string representation
- @return an instance of an Author corresponding to the given
- string
- @throws NullPointerException if
name
is null - @throws ArrayIndexOutOfBoundsException if there are not
- two component parts to
name
(first and last - names)
*/
public static Author valueOf(String name) {
final String[] parts = name.split(" ");
return new Author(parts[0], parts[1]);
}
}
public interface Book {
/**
- Get the author.
- @return the author of the book
/
public Author getAuthor();
/* - Get the publication date.
- @return the publication date of the book
*/
public Date getDatePublished();
}
public final class ImmutableBook implements Book {
private final Author author;
private final Date datePublished;
/**
- Create book with given author and datePublished.
- @param author the book author
- @param datePublished the book date of publication
- @throws NullPointerException if either
author
- or
datePublished
is null
/
public ImmutableBook(Author author, Date datePublished) {
if (author == null)
throw new IllegalArgumentException(“author is null”);
if (datePublished == null)
throw new IllegalArgumentException(“datePublished is null”);
this.author = new Author(author.getFirstName(),
author.getLastName());
this.datePublished = new Date(datePublished.getTime());
}
/* - @see uk.ac.ncl.teach.ex.immut.Book#getAuthor()
/
public Author getAuthor() {
return new Author(author.getFirstName(),
author.getLastName());
}
/* - @see uk.ac.ncl.teach.ex.immut.Book#getDatePublished()
/
public Date getDatePublished() {
//could return new Date(datePublished.getTime());
return (Date) datePublished.clone();
}
/* - @see java.lang.Object#equals(java.lang.Object)
/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof ImmutableBook))
return false;
final ImmutableBook book = (ImmutableBook) obj;
return author.equals(book.author)
&& datePublished.equals(book.datePublished);
}
/* - @see java.lang.Object#hashCode()
/
@Override
public int hashCode() {
int hc = 17;
hc = 37 * hc + author.hashCode();
return 37 * hc + datePublished.hashCode();
}
/* - @see java.lang.Object#toString()
*/
@Override
public String toString() {
return author + " (" + datePublished + “)”;
}
}
public final class MutableBook implements Book {
private final Author author;
private final Date datePublished;
/**
- Create Book with given author and date of publication.
- @param author: the book author
- @param datePublished: the book publication date
- @throws NullPointerException if either
author
- or
datePublished
is null
/
public MutableBook (Author author, Date datePublished) {
if (author == null)
throw new IllegalArgumentException(
“null author not permitted”);
if (datePublished == null)
throw new IllegalArgumentException(
“null date not permitted”);
this.author = author;
this.datePublished = datePublished;
}
/* - @see uk.ac.ncl.teach.ex.immut.Book#getAuthor()
/
public Author getAuthor() { return author; }
/* - @see uk.ac.ncl.teach.ex.immut.Book#getDatePublished()
/
public Date getDatePublished() { return datePublished; }
/* - @see java.lang.Object#equals(java.lang.Object)
/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof MutableBook))
return false;
final MutableBook book = (MutableBook) obj;
return book.equals(book.author)
&& datePublished.equals(book.datePublished);
}
/* - @see java.lang.Object#hashCode()
/
@Override
public int hashCode() {
int hc = 17;
hc = 37 * hc + author.hashCode();
return 37 * hc + datePublished.hashCode();
}
/* - @see java.lang.Object#toString()
*/
@Override
public String toString() {
return author + " (" + datePublished + “)”;
}
}
public final class UseBook {
/**
- Main method, requires no arguments.
- @param args args are ignored
*/
public static void main(String[] args) {
final Date dop = new Date();
final Author author = new Author(“Nick”, “Cook”);
// change implementation used to ImmutableBook
// to demonstrate benefits of immutability
// final Book b
// = new MutableBook(author, dop);
final Book b
= new ImmutableBook(author, dop);
System.out.println(b);
System.out.println(“---------------------------------------”);
dop.setTime(0L);
System.out.println(“Modify through dop constructor param?”);
System.out.println(" " + b);
System.out.println(“---------------------------------------”);
author.setFirstName(“Brian”);
System.out.println(“Modify through author constructor param?”);
System.out.println(" " + b);
System.out.println(“---------------------------------------”);
final Author internalAuthor = b.getAuthor();
internalAuthor.setLastName(“Smith”);
System.out.println(“Modify through ref to author member field?”);
System.out.println(" " + b);
System.out.println(“---------------------------------------”);
Date dop2 = b.getDatePublished();
dop2.setTime(600000000L);
System.out.println(“Modify through ref to dop member field?”);
System.out.println(" " + b);
}
}
2.2 Interfaces
• An interface defines a type and a contract for the publicly exposed behaviour of the type
• A class that implements an interface, whether partially (abstract-ly) or
completely, must adhere to the contract
–The details of how the class achieves this are not specified by the interface
–An interface may be implemented by many different implementation
classes
–An implementation may implement more than one interface
The benefits of interfaces
• Interfaces insulate your users from implementation details
–Decouple interface from implementation
–Implementations of an Application Programmer Interface (API) can evolve without disruption to the existing users of the API
–Defer decisions, change implementation and at the same time maintain a usable code base
• Interfaces and interface inheritance are almost without qualification a “good thing”
–Unlike implementation inheritance
public interface Account {
// deposit throws Exception if amount < 0
void deposit(int amount);
int getAccountNumber();
int getBalance();
// transfer throws Exception if acc is in red
void transferBalance(Account acc);
// withdraw throws Exception if amount < 0
int withdraw(int amount);
}
• An interface is just a collection of method prototypes that need to be
implemented
• Interface documentation (Javadocs) should specify additional constraints
AbstractAccount partial implementation - does not implement withdrawpublic abstract class AbstractAccount implements Account {
private final int accountNumber;
private int balance;
AbstractAccount(int accountNumber) {// set accountNumber }
public void deposit(int amount) {
if (amount < 0) { // throw exception }
balance = balance + amount;
}
public int getAccountNumber() { return accountNumber; }
public int getBalance() { return balance; }
// note there is a deliberate mistake below!
public void transferBalance(Account acc) {
final AbstractAccount fromAcc = (AbstractAccount) acc;
if (fromAcc.balance < 0) {// throw exception }
balance += fromAcc.balance;
fromAcc.balance = 0;
}
void setBalance(int balance) { this.balance = balance; }
}
Abstract class can implement interfaces without even providing the implementation of interface methods.
Abstract class is used to provide common method implementation to all the subclasses or to provide default implementation.
public final class FreeAccount extends AbstractAccount {
public FreeAccount(int accNum) { super(accNum); }
public int withdraw(int amount) {
if (amount < 0) { // throw exception }
final int currentBalance = getBalance();
if (currentBalance < amount) return 0;
setBalance(currentBalance - amount);
return amount;
}
}
public final class OverdraftAccount extends AbstractAccount {
private final static int OVERDRAFT_CHARGE = 10;
public OverdraftAccount(int accNum) { super(accNum); }
public int withdraw(int amount) {
if (amount < 0) {// throw Exception }
final int newBalance = getBalance() - amount;
if (newBalance < 0) {
setBalance(newBalance - OVERDRAFT_CHARGE);
} else {
setBalance(newBalance);
}
return amount;
}
}
• What is the problem with the following implementation of transferBalance and how do we fix it?
public abstract class AbstractAccount implements Account {
…
public void transferBalance(Account acc) {
final AbstractAccount fromAcc = (AbstractAccount) acc;
if (fromAcc.balance < 0) {// throw exception }
balance += fromAcc.balance;
fromAcc.balance = 0;
}
…
}
Fixing transferBalance
Program through the API
public abstract class AbstractAccount implements Account {
…
public void transferBalance(Account acc) {
final int accBalance = acc.getBalance();
if (accBalance < 0) {// throw exception }
// following line first withdraws the balance
// from acc and then deposits it in this account
// if this == acc then the account is unchanged
deposit(acc.withdraw(accBalance));
// or if (this == acc) return;
}
…
}
Example of multiple inheritance of Interfaces
public interface Player {
void squadNumber(int number);
}
public interface Manager {
void selectTeam(Player[] players);
}
public class PlayerManager
implements Player, Manager {
…
void squadNumber(int number) {… }
void selectTeam(Player[] players) {… }
…
}
Polymorphism
• Polymorphism is Greek for “many shapes”
–In OO programming: “using the same name to refer to different implementations of a method”
• Polymorphism in Java:
–Method overloading
–Overriding of inherited methods
–Multiple implementations of the same interface
• For method overriding and for interface implementation the method to
execute is resolved dynamically at run-time
–Depends on the run-time type of the object
• Polymorphism + interfaces leads to implementation independence
Polymorphism and interfaces
• An interface reference can refer to any object that implements the interface
Measurable m; // null
m = new FreeAccount(1);
m = new CashBox();
• You cannot construct an interface:
m = new Measurable(); // will not compile!
• You can call any of the interface methods:
int measure = m.getMeasure();
• Which method is called?
• Polymorphism means you execute different versions of a method through the same interface
• The method that is executed depends on the referenced object
–The type of the object determines which method is called
• AbstractAccount.getMeasure() if m refers to a FreeAccount
• CashBox.getMeasure() if m refers to a CashBox
• Both getMeasure methods are versions of Measurable.getMeasure()
• This dynamic run-time resolution is called late binding
多态性意味着你可以通过同一个接口执行不同版本的方法。
·执行的方法取决于引用的对象
- 对象的类型决定调用哪个方法
· AbstractAccount.getMeasure(),如果m指的是自由账户
· CashBox.getMeasure(),如果m指的是CashBox
·两个getMeasure方法都是Measurable.getMeasure()的版本
这种动态的运行时解析称为后期绑定
Interfaces versus abstract classes
• There are two mechanisms for defining a type that allows multiple
implementations: interfaces and abstract classes, with two important
differences:
- An abstract class can also provide implementation, an interface
cannot* - Any implementation of the type defined by an abstract class must be a subclass of the abstract class. Whereas any class, anywhere in the
hierarchy, can implement an interface and can implement multiple
interfaces (observing multiple contracts)
• Interfaces provide looser coupling and less brittle code
• In general, declare an interface for any publicly exposed types (but see later)
• Refer to objects by their interfaces whenever possible (Bloch-EJ Item 64)
–Program through the interface and only use or cast to an implementation when absolutely necessary
• Use interfaces only to define types (Bloch-EJ Item 22)
–Do not use interfaces just to declare constants
»Associate constants with a type or use a non-instantiable utility class
• Prefer interfaces to abstract classes (Bloch-EJ Item 20)
–It is easy to retrofit one or more of your existing classes to implement a
new interface without damaging the type hierarchy
»In general, the same is not true of retrofitting existing classes to extend a new abstract class
–Interfaces can be used to define mixins
–Interfaces support non-hierarchical type frameworks
–Interfaces allow safe enhancements, abstract classes enforce inheritance
• When possible, it is a good idea to provide an abstract implementation of any non-trivial interface
–This may be a partial implementation
Interfaces: downsides/caveats
• There are almost no downsides to using interfaces! But be aware:
- Defining a top-level interface declares the type to be part of your publicly accessible API
– It is possible to have private and/or protected interfaces but only when
nested in a class - In general, it is impossible to modify an interface without breaking all existing programs that use the interface.
– Implementations of the previous version of the interface do not, by
definition, implement the new modified version
– In contrast, it is possible to evolve classes (including abstract classes)
without breaking existing code
• These dependencies are not a problem during the initial development of code
When not to define an interface
• Do not define a top-level interface for a type that is not intended to be
public
–It is possible to subsequently extract an interface
»IDEs provide refactoring support to do this
• It is not necessary to define an interface for a public type that will not be or is unlikely to be extended
–In this case, prevent inheritance
–Again, if you change your mind it is possible to refactor and extract an
interface for the type
code demo
public interface Account {
/**
- Deposits money in the account.
- @param amount the amount of money to deposit
- @throws IllegalArgumentException if
amount < 0
/
void deposit(int amount);
/*- Returns the account number.
- All accounts must have an account number
- which uniquely identifies an account at a bank.
- @return the account number
/
int getAccountNumber();
/* - Returns the account balance.
- @return the balance of the account
/
int getBalance();
/* - Transfers the balance of the given account
- to this account. It is
- forbidden to transfer the balance from
- an overdrawn account.
- @param acc the account to transfer the balance from
- @throws NullPointerException if
acc
- is
null
- @throws IllegalArgumentException if
acc
is overdrawn
/
void transferBalance(Account acc);
/*- Withdraws money from the account.
- @param amount the amount of money requested
- @return the amount of money withdrawn which
- will be zero if there are
- insufficent funds in the account
- for the requested withdrawal
- @throws IllegalArgumentException
- if
amount < 0
*/
int withdraw(int amount);
}
public abstract class AbstractAccount implements Account {
/* Neither of the following member fields is directly accessible to
- any subclass
/
private final int accountNumber;
private int balance;
// package-private
AbstractAccount(int accountNumber) {
this.accountNumber = accountNumber;
}
/* - @see java.lang.Object#toString()
/
public String toString() {
return "account " + accountNumber + " : " + balance;
}
/* - @see uk.ac.ncl.teach.ex.acc.Account#deposit(int)
/
public void deposit(int amount) {
if (amount < 0)
throw new IllegalArgumentException(
"Negative deposit: " + amount);
balance = balance + amount;
}
/* - @see uk.ac.ncl.teach.ex.acc.Account#getAccountNumber()
/
public int getAccountNumber() {
return accountNumber;
}
/* - @see uk.ac.ncl.teach.ex.acc.Account#getBalance()
/
public int getBalance() {
return balance;
}
/* - @see
uk.ac.ncl.teach.ex.acc.Account#transferBalance(uk.ac.ncl.teach.ex.acc.Account)
*/
public void transferBalance(Account acc) {
// this method has a deliberate mistake
final AbstractAccount fromAcc
= (AbstractAccount) acc;
if (fromAcc.balance < 0)
throw new IllegalArgumentException("acc: " + acc
- " has negative balance");
balance += fromAcc.balance;
fromAcc.balance = 0;
}
// to set the account balance as cannot access
// balance directly
// rely on subclasses to maintain invariants
void setBalance(int balance) {
this.balance = balance;
}
}
package uk.ac.ncl.teach.ex.acc;
/**
- FreeAccount - bank account with no overdraft and no charges.
- @author Nick Cook <nick.cook@ncl.ac.uk>
- @version $Revision: 1498 $
- $Date: 2010-11-09 15:39:26 +0000 (Tue, 09 Nov 2010) $
- Copyright © 2007 Newcastle University, UK
*/
public final class FreeAccount extends AbstractAccount {
/**
* @see AbstractAccount#AbstractAccount(int)
*/
public FreeAccount(int accountNumber) {
super(accountNumber);
}
/**
* A withdrawal that puts the account into
* the red will not be permitted.
* @see uk.ac.ncl.teach.ex.acc.Account#withdraw(int)
*/
public int withdraw(int amount) {
if (amount < 0)
throw new IllegalArgumentException(
"Negative withdrawal: " + amount);
final int currentBalance = getBalance();
if (currentBalance < amount)
return 0;
setBalance(currentBalance - amount);
return amount;
}
}
package uk.ac.ncl.teach.ex.acc;
/**
- OverdraftAccount - bank account that allows overdrafts that incur a charge.
- @author Nick Cook <nick.cook@ncl.ac.uk>
- @version $Revision: 1498 $
- $Date: 2010-11-09 15:39:26 +0000 (Tue, 09 Nov 2010) $
- Copyright © 2007 Newcastle University, UK
*/
public final class OverdraftAccount extends AbstractAccount {
private final static int OVERDRAFT_CHARGE = 10;
/**
* @see AbstractAccount#AbstractAccount(int)
*/
public OverdraftAccount(int accountNumber) {
super(accountNumber);
}
/**
* Withdraw allows accounts to go into to the red and imposes
* a fixed charge for withdrawals from overdrawn accounts.
*
* @see uk.ac.ncl.teach.ex.acc.Account#withdraw(int)
*/
public int withdraw(int amount) {
if (amount < 0)
throw new IllegalArgumentException(
"Negative withdrawal: " + amount);
final int newBalance = getBalance() - amount;
if (newBalance < 0) {
setBalance(newBalance - OVERDRAFT_CHARGE);
} else {
setBalance(newBalance);
}
return amount;
}
}
public class UseAccount {
public static void main(String[] args) {
final Account acc1 = new FreeAccount(1);
final Account acc2 = new OverdraftAccount(2);
final Account acc3 = new FreeAccount(3);
Account acc4 = new OverdraftAccount(4);
acc1.deposit(100);
System.out.println(acc1);
System.out.println(acc2);
System.out.println(acc3);
System.out.println(acc4);
final int acc2Withdraw = acc2.withdraw(20);
System.out.println("withdrew " + acc2Withdraw
- " from: " + acc2);
final int acc3Withdraw = acc3.withdraw(10);
System.out.println("withdrew " + acc3Withdraw - " from: " + acc3);
// testTransfer(acc3, acc1);
// acc4 = acc3;
// testTransfer(acc4, acc3);
}
private static void testTransfer(Account toAcc,Account fromAcc) {
System.out.println(“Before transfer:”);
System.out.println(" toAcc is " + toAcc);
System.out.println(" fromAcc is " + fromAcc);
toAcc.transferBalance(fromAcc);
System.out.println(“After transfer:”);
System.out.println(" toAcc is " + toAcc);
System.out.println(" fromAcc is " + fromAcc);
}
}