1 最小化类和成员的可访问性

(1)封装

  • 封装对组成系统的组件进行解耦,从而允许这些组件独立开发,测试,优化,使用,理解和修改。
  • 封装提高了软件的复用性,因为组件间的耦合度低使得它们不仅在开发环境,而且在别的环境也能变得有用。
  • 封装降低了开发大型系统的风险,因为即使系统不可用了,但这些独立的组件却有可能仍可用。

(2)对于成员(域,方法,嵌套类,或者嵌套接口),都有四种可能的访问级别

  • private:成员只能被声明它的顶级类访问。
  • default:成员可以被声明它的包下面的所有类访问。
  • protected:成员可以被声明它的类的子类访问,同时,声明它的包下面的所有类也可以访问它。
  • public:成员可以在任何地方被访问。

(3)模块

  • Java 9在中,作为模块系统(module system)引入了两种额外的隐式访问级别。
  • 一个模块就是一组包,就像一个包是一组类一样。
  • 一个模块可以通过在它的模块声明(一般包含在module-info.java的源文件中)的导出声明显式地导出它的一些包。
  • 模块的非导出包的公有和受保护成员在模块外部是无法访问的,而在模块内部,访问性不受导出声明的影响。

(4)demo

// Potential security hole!
public static final Thing[] VALUES = { ... }; // 方案1
private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES =
Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES)) ; // 方案2
private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values() {
return PRIVATE_VALUES.clone();
}

2 在公有类中使用访问方法,而不是公有域

(1)概述

  • 如果一个类在包外可以被访问,就应该提供访问方法,以此来保留改变类内部展示的灵活性。
  • 如果一个类是包级私有或者是个私有嵌套类,那么暴露它的数据域也没有什么本质上的错误

(2)demo

// Degenerate classes like this should not be public!
class Point {
public double x;
public double y;
} // Encapsulation of data by accessor methods and mutators
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;
}
}
  • 假设需要限制x,y的范围为[1,100],第一种方式是无法实现的,你可能说可以在外部代码限制,但实际上这破环了单一职责原则。

3 使可变性最小化

(1)不可变类的5个原则

  • 不要提供修改对象状态的方法(没有setter方法)
  • 确保这个类不能被拓展(设置为final,防止被继承)
  • 所有域设置为final,只能赋值一次且无法修改
  • 所有域设置为私有
  • 确保对任何可变组件(引用属性)的互斥访问:对引用属性进行保护性拷贝。

(2)demo

// Immutable complex number class
public final class Complex {
private final double re;
private final double im;
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
public double realPart() {
return re;
}
public double imaginaryPart() {
return im;
}
// plus代表相加返回新的对象,函数式编程,状态不可变。
// add代表相加返回this对象,命令式编程,状态可变。
public Complex plus(Complex c) {
return new Complex(re + c.re, im + c.im);
}
public Complex minus(Complex c) {
return new Complex(re - c.re, im - c.im);
}
public Complex times(Complex c) {
return new Complex(re * c.re - im * c.im, re * c.im + im * c.re);
}
public Complex dividedBy(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 47 to find out why we use compare instead of ==
return Double.compare(c.re, re) == 0 && Double.compare(c.im, im) == 0;
}
@Override
public int hashCode() {
return 31 * Double.hashCode(re) + Double.hashCode(im);
}
@Override
public String toString() {
return "(" + re + " + " + im + "i)";
}
}

(3)优势

  • 不可变对象天生就是线程安全的,它们不要求同步。
  • 不可变对象可以自由地被共享
  • 不可变类可以提供静态工厂将频繁被请求的实例缓存起来,从而避免重复创建现有实例。(Integer的-128~127)
  • 永远都不用进行保护性拷贝(降低内存占用)
  • 不仅可以共享不可变对象,它们的内部信息也可以被共享。(BigInteger的实现符号使用int,数据使用int[],取反操作不需要clone数组)
  • 不可变对象为其它对象提供了大量的构件
  • 不可变对象提供了免费的失败原子机制(根本不会改变)

(4)缺点

  • 对于每个不同的值都需要一个对应的对象

(5)设计原则

  • 类应该都是不可变的,除非有个很好的理由需要它们是可变的。
  • 如果一个类不能做成不可变,那就尽可能限制它的可变性。(如:CountDownLatch,当计数变为0就不能再使用了。)
  • 构造器应该完全初始化对象,并建立好不变性。

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);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
} // jdk7
InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.addAll(Arrays.asList({"Snap", "Crackle", "Pop"})); // 结果为3 // jdk9
InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.addAll(Arrays.asList({"Snap", "Crackle", "Pop"})); // 结果为6 // jdk升级后,HashSet内部addAll方法依赖于add方法

(2)组合

// 包装类:装饰者模式
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.addAll(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(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();
}
}
  • Guava就为所有的集合接口提供了转发类,无需自己实现转发类。
  • SELF问题:包装者对象不适用于回调框架,因为在回调框架里,对象需要将自身引用传给别的对象,以便别的对象在后续进行调用。因为被包装对象并不知道它的包装者,它将一个引用传给它自己(this)同时回调也避开了包装者。
  • 只有当类B和类A是“is-a”的关系时,类B才应该继承类A。

5 若要设计继承,则提供文档说明,否则禁止继承

  • 必须在这个类的文档里为可覆盖方法说明它的自用性(self-use):对于每个公有方法或受保护方法,文档里都必须指明这个方法调用了哪些可覆盖方法,是以什么顺序调用的,每个调用的结果是如何影响接下来的处理过程。
  • 测试一个用于被继承的类的唯一方式是编写子类:经验表明,三个子类就足以测试一个可扩展的类了。而且这些子类应该由父类作者之外的人来编写。
  • 一个类允许被继承时:
    • 构造器一定不能调用可覆盖方法:构造器里调用不可覆盖的方法,即私有方法,final方法和静态方法,则是安全的。
    • 实现Cloneable接口或Serializable接口时,clone方法或readObject方法,都不应该直接或间接地调用一个可覆盖方法。
    • 实现Serializable接口且拥有readResolve方法或writeReplace方法时,需要设置为受保护的,而不是私有的。
  • 防止子类化:
    • final修饰类
    • 构造器设置为私有并提供公有的静态工厂创建方法

6 接口优于抽象类

(1)接口优势

  • 现有类可以很容易地被改造以实现一个新的接口。

  • 接口是定义混合类型(mixins)的理想选择。

  • 接口构造非层次结构的框架。

  • 通过包装者类模式,使用接口使得安全地增强类的功能成为可能。

  • 混合模式demo

public interface Singer {
AudioClip sing(Song s);
}
public interface Songwriter {
Song compose(int chartPosition);
} // 既是歌手又是作曲人
public interface SingerSongwriter extends Singer, Songwriter {
AudioClip strum();
void actSensitive();
}

(2)接口

  • 域只允许用public static修饰
  • 方法:
    • private static修饰
    • public static修饰
    • public default修饰
public interface Interface {
public static int test = 0; public static void publicTest() {
privateTest();
} private static void privateTest() {
System.out.println(test);
} public default void defaultTest() {
System.out.println("defaultTest");
} public abstract void abstractTest();
} public class InterfaceImpl implements Interface {
public void extendTest() {
defaultTest();
} @Override
public void abstractTest() {
System.out.println("abstractTest");
} public static void main(String[] args) {
InterfaceImpl extend = new InterfaceImpl();
extend.extendTest();
extend.defaultTest(); System.out.println(Interface.test);
Interface.publicTest();
}
}

(3)接口与实现类结合使用:模板类

  • 模板实现类在基本接口方法上实现剩余的非基本接口方法(模板方法模式)。

  • demo1:继承模板类

// AbstractList是模板类,基于它可以非常容易实现新的类
// 适配器模式:int[] -> List<Integer>
static List<Integer> intArrayAsList(int[] a) {
Objects.requireNonNull(a);
return new AbstractList<>() {
@Override
public Integer get(int i) {
return a[i];
}
@Override
public Integer set(int i, Integer val) {
int oldVal = a[i];
a[i] = val;
return oldVal;
}
@Override
public int size() {
return a.length;
}
};
}
  • demo2:模板类编写
// Map.Entry最主要的两个方法:getKey和getValue(必需实现) setValue(可选,只有可修改的Map才需要实现)
// hashCode、equals和toString方法可以由模板类提供默认实现
public abstract class AbstractMapEntry<K,V> implements Map.Entry<K,V> {
// Entries in a modifiable map must override this method
@Override
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<?,?> e = (Map.Entry) o;
return Objects.equals(e.getKey(), getKey()) && Objects.equals(e.getValue(), getValue());
} // Implements the general contract of Map.Entry.hashCode
@Override
public int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
} @Override
public String toString() {
return getKey() + "=" + getValue();
}
}

7 接口拓展

  • java8之后,jdk中的集合相关接口都添加了lambda的支持,但是这只对继承模式的实现类有效,对于采用组合模式的拓展类可能导致问题(装饰者模式)。比如:本来是需要加锁的,但是新加入的default方法并没有加锁,就可能导致并发问题。
  • 所以说对于接口的拓展(添加默认方法或者添加抽象方法)都需要慎重,实在没有好的实现不如新创建一个接口去进行拓展,避免因为疏忽和造成不可预测的错误。

8 接口只用来定义类型

  • 通过常量接口模式来使用接口是很糟糕的:如果一个非final类实现了一个常量接口,那么它的所有子类的命名空间都将被接口的常量污染。
public interface PhysicalConstants {
static final double AVOGADROS_NUMBER = 6.022_140_857e23;
static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;
static final double ELECTRON_MASS = 9.109_383_56e-31;
}
  • 替代方案
public class PhysicalConstants {
private PhysicalConstants() { } // Prevents instantiation
public static final double AVOGADROS_NUMBER = 6.022_140_857e23;
public static final double BOLTZMANN_CONST =1.380_648_52e-23;
public static final double ELECTRON_MASS = 9.109_383_56e-31;
}

9 优先使用类层次,而不是标签类

  • 标签类

    • 混乱的模板代码:枚举、标签域和switch语句等
    • 标签类实例负担许多不相关的域,导致内存占用增加。
    • switch-case可能导致运行时错误
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(shape);
}
}
}
  • 类层次

    • 代码清晰简单
    • 域都不可变
    • 消除运行时失败
abstract class Figure {
abstract double area();
} class Circle extends Figure {
final double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
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;
}
@Override
double area() {
return length * width;
}
}

10 优先考虑静态成员类

(1)嵌套类

  • 嵌套类是定义在另一个类中的类。
  • 一个嵌套类的存在应该是为了服务它的外围类。
  • 如果一个嵌套类还可以用于别的上下文,那么它就应该是个顶层类
  • 分类:
    • 静态成员类
    • 非静态成员类
    • 匿名类
    • 局部类

(2)静态成员类

  • 可视为普通类
  • 可访问外围类的所有成员(包括private修饰的)
  • 一般作为一个公有的辅助类,只有与它的外围类一起用时才有意义。
public class Calculator{
public static enum Operation{
PLUS,MINUS;
}
}
  • 私有静态成员类通常被用来展示代表外围类对象的组件。
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>{
static class Node<K,V> implements Map.Entry<K,V>{}
}
  • demo
public class OuterClass {
private static int outTest = 1; public static class InnerClass {
private int inTest; public void test() {
System.out.println(outTest);
} } public static void main(String[] args) {
InnerClass innerClass = new OuterClass.InnerClass();
innerClass.test();
}
}

(3)非静态成员类

  • 非静态成员类的每个实例都是隐式地和它的外围类的实例关联在一起。(内存占用比较高,因为它拥有外围类实例的引用)
  • 在非静态成员类的实例方法内部,你可以调用外围实例的方法或通过标识了this的构造来获取外围实例的引用。
  • 非静态成员类的一个通常的用法是,定义一个适配器 ,而这个适配器让外围类的实例被看成是某个不相关类的实例。
// 如:List -> Iterator
public class MySet<E> extends AbstractSet<E> {
// Bulk of the class omitted
@Override
public Iterator<E> iterator() {
return new MyIterator();
}
private class MyIterator implements Iterator<E> { }
}
  • demo
public class OuterClass {
private int outTest = 1; public class InnerClass {
private int inTest; public void test() {
System.out.println(outTest);
// System.out.println(OuterClass.this.outTest);
} public OuterClass getOuterClass(){
return OuterClass.this;
}
} public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
InnerClass innerClass = outerClass.new InnerClass();
innerClass.test();
}
}

(4)匿名类

  • 匿名类可以没有名字。
  • 匿名类并不是它的外围类的一个成员。
  • 匿名类出现在静态的上下文当中,也不能拥有除了常量型变量的任何的静态成员,这些常量型变量是final的基本类型或初始化常量表达式的字符串属性。
  • 匿名类需要在声明的时候初始化。
  • 匿名类无法使用instanceof来判断。
  • 匿名类无法继承或实现其他接口了。
  • 匿名类的常见用法是实现静态工厂方法,参见第6节的demo1。
  • 匿名类是创建小的函数对象和处理对象的首选方式(现在被lambda取代)
public static void main(String[] args) {
try(AutoCloseable myCloseable = new AutoCloseable() {
@Override
public void close() {
System.out.println("close success!");
}
};) {
System.out.println("use myCloseable");
} catch (Exception e) {
e.printStackTrace();
}
}

(5)局部类

  • 局部类与局部变量一样,只存在局部作用域。
public static void main(String[] args) {
class A{
private int test = 1;
};
A a = new A();
System.out.println(a.test);;
}

(6)用途

  • 如果一个嵌套类必须在方法外部可见,或者放在方法内部会显得太长时,就使用成员类。
  • 如果成员类的实例需要拥有该类的外围类的引用,就将其做成非静态;不然,就将其做成静态。
  • 假设一个类应当在方法内部,若你需要只从一个地方创建实例而且已经存在一个类型能说明这个类的特征,那么将其做成匿名类;否则,就将其做成局部类。

参考:

Effective Java 读书笔记(三):类与接口的更多相关文章

  1. Effective Java 读书笔记之三 类和接口

    一.使类和成员的可访问性最小化 1.尽可能地使每个类或者成员不被外界访问. 2.实例域决不能是共有的.包含公有可变域的类不是线程安全的. 3.除了公有静态final域的特殊情形之外,公有类都不应该包含 ...

  2. 《Effective Java》读书笔记 - 4.类和接口

    Chapter 4 Classes and Interfaces Item 13: Minimize the accessibility of classes and members 一个好的模块设计 ...

  3. Effective Java读书笔记--类和接口

    1.使类和成员的可访问性最小化不指定访问级别,就是包私有.protected = 包私有 + 子类一般private不会被访问到,如果实现了Serializable,可能会泄露.反射.final集合或 ...

  4. Effective java读书笔记

    2015年进步很小,看的书也不是很多,感觉自己都要废了,2016是沉淀的一年,在这一年中要不断学习.看书,努力提升自己 计在16年要看12本书,主要涉及java基础.Spring研究.java并发.J ...

  5. Effective Java读书笔记完结啦

    Effective Java是一本经典的书, 很实用的Java进阶读物, 提供了各个方面的best practices. 最近终于做完了Effective Java的读书笔记, 发布出来与大家共享. ...

  6. [Effective Java]第四章 类和接口

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  7. Effective Java 读书笔记之五 枚举和注解

    Java1.5中引入了两个新的应用类型家族,新的类为枚举类型,新的接口为注解类型. 一.用enum代替int常量 1.枚举值由一组固定的常量组成合法值的类型. 二.用实例域代替序数 1.不要根据枚举的 ...

  8. 《Effective Java》笔记 使类和成员的可访问性最小化

    类和接口 第13条 使类和成员的可访问性最小化 1.设计良好的模块会隐藏所有的实现细节,把它的API与实现清晰的隔离开来,模块之间只通过它们的API进行通信,一个模块不需要知道其他模块的内部工作情况: ...

  9. 初读"Thinking in Java"读书笔记之第九章 --- 接口

    抽象类和抽象方法 abstract void f();抽象方法是仅有声明而没有方法体的方法. 包含抽象方法的类叫做抽象类,如果一个类包含了一个抽象方法,则该类必须限定为抽象类. 抽象类和抽象方法可以使 ...

  10. Effective Java 读书笔记(一):使用静态工厂方法代替构造器

    这是Effective Java第2章提出的第一条建议: 考虑用静态工厂方法代替构造器 此处的静态工厂方法并不是设计模式,主要指static修饰的静态方法,关于static的说明可以参考之前的博文&l ...

随机推荐

  1. 调用百度地图API的总结

    因为项目要用到百度地图,所以先摸索了一下,各种功能官方都有文档,点击可查看,文章的话我就直接写我用到的功能例子了,要用可以直接复制粘贴~ 一.主要涉及到的几个接口(先申请密钥): 1.技术一:坐标转换 ...

  2. OpenJudge计算概论-找和为K的两个元素

    /*============================================================== 找和为K的两个元素 总时间限制: 1000ms 内存限制: 65536 ...

  3. androidStudio: ERROR: Error occurred while communicating with CMake server.

    遇到此错误的原因是cmake服务器协议版本不匹配: 解决方案: 1:直接更新android studio看能否解决: 2:如果解决不了,那么将androidstudio,ndk ,cmake,grad ...

  4. c# combbox删除最后一个item界面不更新

    如果c#的combbox删除的item是当前选中的,而且是最有一个(如果不是最后一个,我也不知道会不更新,没有去测试,但是可以收到跟新到其他的item),它不会自动清空显示,而是保存当前选中. 所以要 ...

  5. PorterDuffXfermode之PorterDuff.Mode.SRC_IN

    package com.loaderman.customviewdemo.view; import android.content.Context; import android.graphics.B ...

  6. Linux -- GCC Built-in functions for atomic memory access

    下列内建函数旨在兼容Intel Itanium Processor-specific Application Binary Interface, section 7.4. 因此,这些函数区别于普通的G ...

  7. ubuntu 16.04 修改网卡显示名称

    ~# sudo nano /etc/default/grub找到:GRUB_CMDLINE_LINUX=""改为:GRUB_CMDLINE_LINUX="net.ifna ...

  8. 雨田家园 delphi 拆分字符串

    最近在使用Delphi开发一种应用系统的集成开发环境.其中需要实现一个字符串拆分功能,方法基本原型应该是:procedure SplitString(src: string ; ch: Char; v ...

  9. centos6.8yum 安装mysql

    1:查看是否已有mysql版本 rpm -qa | grep mysql 删除mysql 账号和用户组 删除/etc/my.cnf 2:有的话就删除 rpm -e mysql-...... 或者 co ...

  10. 定期删除IIS日志文件

    服务器中由于监控的需要会经常生成很多日志文件,比如IIS日志文件(C:\inetpub\logs\LogFiles),一个稍微有流量的网站,其日志每天可以达到上百兆,这些文件日积月累会严重的占用服务器 ...