1、使类和成员的可访问性最小化
封装是软件设计的基本原则之一,它的好处就是解除组成系统的各个模块之间的耦合关系,使得这些模块可以独立地开发、测试、优化、使用、理解 修改,同时可以有效地进行性能调节。
(1)可见性
A、私有的
B、包级私有的
C、受保护的
D、公有的
(2)实例域不能是公有的
包含公有可变域的类并不是线程安全的
(3)公有静态final域
A、常量通常用公有静态域来暴露,大写加下划线
B、如果final域包含可变对象的引用,便具有非final域的所有缺点,虽然引用自身不能被修改,但是所引用对象的内容却可以被修改,这会导致灾难性的后果
C、类具有公有的静态final数据域,或者返回这种域的访问方法,几乎总是错误的,这是安全漏洞的一个常见根源。
//potential security hole
public static final Thing[] VALUES = {...};
改进方法一
private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Thing> VALUES =
Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
改进方法二
private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values(){
return PRIVATE_VALUES.clone();
}
2、在公有类中使用访问方法而非公有域
(1)杜绝退化类
数据域是public,没有提供访问方法
public class Point {
public double x;
public double y;
}
如果类可以在它所在的包外进行访问,或提供访问方法,以保留将来改变该类的内部表示法的灵活性。如果共有类暴露了它的数据域,要想在将来改变其内部表示法是不可能的,因为公有类的客户端代码已经遍布各处了。
public class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX(){
return x;
}
public double getY(){
return y;
}
public void setX(double x){
this.x = x;
}
public void setY(double y){
this.y = y;
}
}
(2)原则
公有类永远都不应该暴露可变的域。
3、使可变量最小化
(1)使类变为不可变的规则
A、不要提供任何会修改对象状态的方法
B、保证类不会被扩展
C、使所有域都是final的
D、使所有的域都称为私有的
E、确保对任何可变组件的互斥访问
永远不要用客户端提供的对象引用来初始化可变对象的域
(2)函数风格
该函数返回一个运算结果,该运算并不会改变它的状态。一般不可变类都返回一个新的运算实例,而非修改原来的实例。
public final class Complex {
private final double re;
private final double im;
private Complex(double re, double im) {
this.re = re;
this.im = im;
}
public static Complex valueOf(double re, double im) {
return new Complex(re, im);
}
public static Complex valueOfPolar(double r, double theta) {
return new Complex(r * Math.cos(theta),
r * Math.sin(theta));
}
public static final Complex ZERO = new Complex(0, 0);
public static final Complex ONE = new Complex(1, 0);
public static final Complex I = new Complex(0, 1);
// Accessors with no corresponding mutators
public double realPart() { return re; }
public double imaginaryPart() { return im; }
public Complex add(Complex c) {
return new Complex(re + c.re, im + c.im);
}
public Complex subtract(Complex c) {
return new Complex(re - c.re, im - c.im);
}
public Complex multiply(Complex c) {
return new Complex(re * c.re - im * c.im,
re * c.im + im * c.re);
}
public Complex divide(Complex c) {
double tmp = c.re * c.re + c.im * c.im;
return new Complex((re * c.re + im * c.im) / tmp,
(im * c.re - re * c.im) / tmp);
}
@Override public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Complex))
return false;
Complex c = (Complex) o;
// See page 43 to find out why we use compare instead of ==
return Double.compare(re, c.re) == 0 &&
Double.compare(im, c.im) == 0;
}
@Override public int hashCode() {
int result = 17 + hashDouble(re);
result = 31 * result + hashDouble(im);
return result;
}
private int hashDouble(double val) {
long longBits = Double.doubleToLongBits(re);
return (int) (longBits ^ (longBits >>> 32));
}
@Override public String toString() {
return "(" + re + " + " + im + "i)";
}
}
(3)优缺点
A、线程安全,可以被自由共享
B、可以提供静态工厂方法,缓存实例,提升性能
C、无需clone方法,也不该有clone方法
缺点就是:
对于每个不同的值都需要一个单独的对象
(4)坚决不要为每个get方法提供set方法
除非有很好的理由让类成为可变的类,否则就应该是不可变的
4、组合优先于继承
(1)继承破坏封装
public class InstrumentedHashSet<E> extends HashSet<E> {
// The number of attempted element insertions
private int addCount = 0;
public InstrumentedHashSet() {
}
public InstrumentedHashSet(int initCap, float loadFactor) {
super(initCap, loadFactor);
}
@Override public boolean add(E e) {
addCount++;
return super.add(e);
}
//超类方法可能是自引用的,super.addAll引用了add方法,因为子类覆盖了add方法,将改为调用子类的add方法,因而count被重复计数了
@Override public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
public static void main(String[] args) {
InstrumentedHashSet<String> s =
new InstrumentedHashSet<String>();
s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
System.out.println(s.getAddCount());
}
}
(2)组合
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(Object o) { return s.contains(o); }
public boolean isEmpty() { return s.isEmpty(); }
public int size() { return s.size(); }
public Iterator<E> iterator() { return s.iterator(); }
public boolean add(E e) { return s.add(e); }
public boolean remove(Object o) { return s.remove(o); }
public boolean containsAll(Collection<?> c)
{ return s.containsAll(c); }
public boolean addAll(Collection<? extends E> c)
{ return s.addAll(c); }
public boolean removeAll(Collection<?> c)
{ return s.removeAll(c); }
public boolean retainAll(Collection<?> c)
{ return s.retainAll(c); }
public Object[] toArray() { return s.toArray(); }
public <T> T[] toArray(T[] a) { return s.toArray(a); }
@Override public boolean equals(Object o)
{ return s.equals(o); }
@Override public int hashCode() { return s.hashCode(); }
@Override public String toString() { return s.toString(); }
}
(2)组合不是委托
InstrumentedHashSet把HashSet包装了起来,成为包装类,属于组合的方式,但不是委托,委托要求包装对象把自身传递给被包装对象。
同时包装类不适合在回调框架里头用。
5、要么为继承而设计,要么禁止
(1)构造器决不能调用可被覆盖的方法
无论是直接调用,还是间接调用
public class Super {
// Broken - constructor invokes an overridable method
public Super() {
overrideMe();
}
public void overrideMe() {
}
}
public final class Sub extends Super {
private final Date date; // Blank final, set by constructor
Sub() {
date = new Date();
}
// Overriding method invoked by superclass constructor
@Override public void overrideMe() {
System.out.println(date);
}
public static void main(String[] args) {
Sub sub = new Sub();
sub.overrideMe();
}
}
打印两次,第一次为null,因为overrideMe被super构造器调用的时候,Sub构造器还没有机会初始化date域。
(2)clone还是readObject方法都不可以调用可覆盖的方法
实现Cloneable或者Serializable接口时。
对于readObject方法,覆盖版本的方法将在子类状态被反序列化之前被运行
对于clone方法,覆盖版本的方法则是在子类的clone方法有机会修正被克隆对象的状态之前被运行
6、接口优于抽象类
(1)接口允许我们构造非层次结构的类型框架
接口是定义mixin的理想选择,避免组合爆炸,类层次臃肿,因为Java是单继承,因而接口优于抽象类
(2)骨架实现
public abstract class AbstractMapEntry<K,V>
implements Map.Entry<K,V> {
// Primitive operations
public abstract K getKey();
public abstract V getValue();
// Entries in modifiable maps must override this method
public V setValue(V value) {
throw new UnsupportedOperationException();
}
// Implements the general contract of Map.Entry.equals
@Override public boolean equals(Object o) {
if (o == this)
return true;
if (! (o instanceof Map.Entry))
return false;
Map.Entry<?,?> arg = (Map.Entry) o;
return equals(getKey(), arg.getKey()) &&
equals(getValue(), arg.getValue());
}
private static boolean equals(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
}
// Implements the general contract of Map.Entry.hashCode
@Override public int hashCode() {
return hashCode(getKey()) ^ hashCode(getValue());
}
private static int hashCode(Object obj) {
return obj == null ? 0 : obj.hashCode();
}
}
7、接口只用于定义类型
(1)常量接口是对接口的不良使用
public interface ObjectStreamConstants {
/**
* Magic number that is written to the stream header.
*/
final static short STREAM_MAGIC = (short)0xaced;
/**
* Version number that is written to the stream header.
*/
final static short STREAM_VERSION = 5;
/* Each item in the stream is preceded by a tag
*/
/**
* First tag value.
*/
final static byte TC_BASE = 0x70;
...
}
(2)工具类版本
public class PhysicalConstants {
private PhysicalConstants() { } // Prevents instantiation
// Avogadro's number (1/mol)
public static final double AVOGADROS_NUMBER = 6.02214199e23;
// Boltzmann constant (J/K)
public static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
// Mass of the electron (kg)
public static final double ELECTRON_MASS = 9.10938188e-31;
}
现在一般建议用枚举。
8、类层次优于标签类
(1)标签类太过臃肿
class Figure {
enum Shape { RECTANGLE, CIRCLE };
// Tag field - the shape of this figure
final Shape shape;
// These fields are used only if shape is RECTANGLE
double length;
double width;
// This field is used only if shape is CIRCLE
double radius;
// Constructor for circle
Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
// Constructor for rectangle
Figure(double length, double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
double area() {
switch(shape) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError();
}
}
}
(2)类层次简洁
abstract class Figure {
abstract double area();
}
类层次
class Circle extends Figure {
final double radius;
Circle(double radius) { this.radius = radius; }
double area() { return Math.PI * (radius * radius); }
}
class Rectangle extends Figure {
final double length;
final double width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double area() { return length * width; }
}
class Square extends Rectangle {
Square(double side) {
super(side, side);
}
}
杜绝了switch case遗漏的可能,同时也可以便于扩展。
9、用函数对象表示策略
(1)只使用一次
Arrays.sort(stringArray,new Comparator<String>(){
public int compare(String s1,String s2){
return s1.length() - s2.length();
}
});
(2)使用多次
class Export{
private static class StrLenCmp implements Comparator<String>,Serializable{
public int compare(String s1,String s2){
return s1.length() - s2.length();
}
}
public static final Comparator<String> STRING_LENGTH_COMPARATOR = new StrLenCmp();
}
使用匿名类的方式,将会在每次执行调用的时候创建一个新的实例,如果它被重复执行,则考虑将函数对象存储到一个私有的静态final域中,并重用它。
10、优先考虑静态成员类
(1)嵌套类
嵌套类指被定义在另一个类的内部的类,其存在的目的是只为其外围类提供服务(外围类的辅助类)。除A外,其他都可以称为内部类(inner class)。
A、静态成员类(static member class)
常见用法是作为公有的辅助类。
public class Calculator {
public static enum Operation{
PLUS,MINUS;
}
public static class OpEnum{
public static final String PLUS = "+";
public static final String MINUS = "-";
}
}
//for demo
System.out.println(Calculator.Operation.MINUS);
System.out.println(Calculator.OpEnum.MINUS);
JDK实例
public class LinkedList<E> extends AbstractSequentialList<E>
…;
private static class Entry<E> {
E element;
Entry<E> next;
Entry<E> previous;
Entry(E element, Entry<E> next, Entry<E> previous) {
this.element = element;
this.next = next;
this.previous = previous;
}
}
…;
}
B、非静态成员类(nonstatic member class)
其每一个实例都隐含着与外围类的一个外围实例相关联,常见的用法就是定义一个Adapter,允许外部类的实例被看做是另外一个不相关的类的实例。
成员类的显著特性就是成员类能访问它的外部类实例的任意字段与方法。方便一个类对外提供一个公共接口的实现是成员类的典型应用。
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
private class Itr implements Iterator<E> {
………;
}
public Iterator<E> iterator() {
return new Itr();
}
}
以JDK Collection类库为例,每种Collection类必须提供一个与其对应的Iterator实现以便客户端能以统一的方式遍历任一Collection实例。每种Collection类的Iterator实现就被定义为该Collection类的成员类。
注意,如果声明成员类不要求访问外围实例,则用static修饰,否则每一个实例都将包含一个额外的指向外围对象的引用,保存这引用要消耗时间和空间,并且会导致外围实例在符合垃圾回收时却仍然得以保留。
C、匿名类(annoymous class)
没有类名的局部类就是匿名类。用一条语句完成匿名类的定义与实例创建。在使用的同时被声明和实例化。
可以出现在代码中任何允许出现表达式的地方,当且仅当匿名类出现在非静态环境中时,才有外围实例。
常见的用法就是用来动态创建函数对象和过程对象(Runnable,Thread,TimerTask)。
queryBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String day = dateBtn.getCurrentSimpleDateFormat().format(dateBtn.getDate());
Integer col = Integer.valueOf(box.getSelectedItem().toString());
ShifenTableGUI gui = new ShifenTableGUI(day, col);
gui.show();
}
});
Arrays.sort(stringArray,new Comparator<String>(){
public int compare(String s1,String s2){
return s1.length() - s2.length();
}
});
创建过程对象
public static void main(String[] args){
Thread[] threads = new Thread[THREADS_COUNTS];
for(int i=0;i<THREADS_COUNTS;i++){
threads[i] = new Thread(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+",start");
for(int j=0;j<10000;j++){
increase();
}
latch.countDown();
}
});
threads[i].start();
}
//等待所有累加线程都结束
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(race);
}
D、局部类(local class)
四种嵌套类中使用最少的类,在任何可以声明局部变量的地方,都可以使用。
对一个静态成员类,去掉其声明中的“static”关键字,将其定义移入其外部类的静态方法或静态初始化代码段中就成为了局部静态成员类。对一个成员类,将其定义移入其外部类的实例方法或实例初始化代码中就成为了局部成员类。
内部类只在定义它的代码段中可见,不能在它所属代码段之外的代码中使用;因此也就没有public/private/default权限修饰符(无意义);不能以局部类形式定义一个接口。局部类只在其所属代码段中可见,定义这样的接口无意义;局部类类名不能与其外部类类名重复。
public class Outer {
private int instanceField;
private static int staticField;
//define a local member class in instance code block
{
int localVirable1 = 0;
final int localVirable2 = 1;
class Inner1 {
public Inner1() {
//can access its outer class' field and method directly
instanceField = 1;
//use OuterClass.this to get its corresponding outer class instance
Outer.this.instanceField = 1;
//can not access the not final local virable in its containing code block
//System.out.print(localVirable1);
//can access the final local virable in its containing code block
System.out.print(localVirable2);
}
}
//local class can not have privilege modifier
/*public class inner2 {
}*/
}
// define a local static member class in static code block
static {
class Inner2 {
public Inner2() {
staticField = 1;
//can not access instance field and method in a local static member class
//intanceField = 2;
}
}
}
public void intanceMethod() {
//define a local class in its out class' instance method
class Inner3 {
}
//local class is visible only in its containning code block
//Outer.Inner2 inner2;
}
private static void staticMethod() {
//define a local static member class in its out class' static method
class Inner4 {
public Inner4() {
staticField = 2;
}
}
//can not define a interface as a local class
/*interface I {
}*/
}
}