美文网首页
第四章类和接口

第四章类和接口

作者: 后来丶_a24d | 来源:发表于2020-03-11 11:00 被阅读0次

目录

  • 使类和成员的可访问性最小化
  • 在公共类中使用访问方法而不是公共属性
  • 最小化可变性
  • 组合优于继承
  • 如果使用继承则设计,并文档说明,否则不该使用
  • 接口优于抽象类
  • 为后代设计接口
  • 接口仅用来定义类型
  • 优先使用类层次而不是标签类
  • 优先考虑静态成员类
  • 将源文件限制为单个顶级类

类和接口

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

  • 经验法则很简单:让每个类或成员尽可能地不可访问
  • 除了作为常量的公共静态final属性之外,公共类不应该有公共属性。 确保public static final属性引用的对象是不可变的。
  • 如果一个类只在本包中访问,不需要被外包访问,则直接声明成class即可,而如果一个类需要被外包访问,则必须声明成public class。

在公共类中使用访问方法而不是公共属性

// 错误eg
class Point {
    public double x;
    public double y;
}
// 正确eg
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; }

}
  • 由于这些类的数据属性可以直接被访问,因此这些类不提供封装的好处(条目 15)。 如果不更改API,则无法更改其表示形式,无法强制执行不变量,并且在访问属性时无法执行辅助操作。 坚持面向对象的程序员觉得这样的类是厌恶的,应该被具有私有属性和公共访问方法的类

最小化可变性

  • 不可变对象很简单。 一个不可变的对象可以完全处于一种状态,也就是被创建时的状态。不可变对象没有多线程问题。
  • 不可变类的主要缺点是对于每个不同的值都需要一个单独的对象
  • 如果一个类不能设计为不可变类,那么也要尽可能地限制它的可变性。
  • 不可变类应该遵循
  1. 不要提供修改对象状态的方法
  2. 确保这个类不能被继承
  3. 把所有属性设置为final,private
  4. 确保对任何可变组件的互斥访问(返回new对象)
  5. 有关序列化,如果让自己的不可变类实现序列化,就必须显式提供 readObject 或者 readResolve,否则反序列化可能会产生新的实例
  • 类例子
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 double realPart() {

        return re;

    }

    public double imaginaryPart() {

        return im;

    }

    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)";

    }
}
  • 不可变类的缺点
  • 在特定的情况下,存在潜在的性能问题,比如执行一个多步骤操作,每个步骤都会产生一个新的对,但除了最后的结果之外其他的对象最终都会被丢弃,就会有性能问题
  • 所以应该使一些小的值对象成为不可变的
  • 如果发生了性能问题,才应该为不可变的类提供公有的可变配套版

组合优于继承

  • 继承是强大的,但它是有问题的,因为它违反封装。 只有在子类和父类之间存在真正的子类型关系时才适用。 即使如此,如果子类与父类不在同一个包中,并且父类不是为继承而设计的,继承可能会导致脆弱性。 为了避免这种脆弱性,使用合成和转发代替继承,特别是如果存在一个合适的接口来实现包装类。 包装类不仅比子类更健壮,而且更强大。
  • 以下这段代码统计次数会出问题,addAll会调用add方法所以被重复加了,因为HashSet的addAll会掉add方法
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;
    }
}
  • 用组合则没问题, 因为Set的addAll调用add方法并没有被覆盖
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();
    }
}

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;
    }
}

如果使用继承则设计,并文档说明,否则不该使用

接口优于抽象类

  • 要实现由抽象类定义的类型,类必须是抽象类的子类。 因为Java只允许单一继承,所以对抽象类的这种限制严格限制了它们作为类型定义的使用

为后代设计接口

  • 许多新的默认方法被添加到Java 8的核心集合接口中,主要是为了方便使用lambda表达式
  • 接口加默认方法设计初衷不是为了新增方法用

接口仅用来定义类型

  • 接口只能用于定义类型。 它们不应该仅用于导出常量。

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

  • 接口加多态实现

优先考虑静态成员类

  • 如果嵌套类的实例可以与其宿主类的实例隔离存在,那么嵌套类必须是静态成员类
  • 集合接口(如Set和List)的实现通常使用非静态成员类来实现它们的迭代器

将源文件限制为单个顶级类

  • 源文件有多个顶级类,可能不同源文件会有重复
  • 如果试图将多个顶级类放入单个源文件中,请考虑使用静态成员类(条目 24)作为将类拆分为单独的源文件的替代方法
public class Test {

    public static void main(String[] args) {

        System.out.println(Utensil.NAME + [Dessert.NAME](http://Dessert.NAME));

    }

    private static class Utensil {

        static final String NAME = "pan";

    }

    private static class Dessert {

        static final String NAME = "cake";

    }

}

参考文章

相关文章

  • effective java 第二周

    第四章 类和接口 类和接口是 java 程序设计语言的核心,这一章主要阐述一些指导原则,以助于设计出更好的类和接口...

  • Kotlin实战阅读笔记---4

    第四章:类、对象和接口 Kotlin的声明默认是final和public的。 嵌套的类默认并不是内部类,他们并没有...

  • 第四章类和接口

    目录 使类和成员的可访问性最小化 在公共类中使用访问方法而不是公共属性 最小化可变性 组合优于继承 如果使用继承则...

  • 抽象类和接口的区别

    抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么,类可以实现多个接口么。 1、抽象类和接口都不能直接...

  • 类和接口

    在类和接口中,讲讲你对封装的理解? 一个组件的好坏,在于该组件在于其他组件交互时,隐藏其内部数据和其他实现细节的程...

  • 类和接口

    13,使类和成员的可访问性最小化 要设计良好的模块与设计不好的模块,最重要的因素在于,这个模块对外部的其他模块而言...

  • 类和接口

    类与类的关系 继承关系,只能单继承,但是可以多层继承 类与接口的关系 实现关系,可以单实现,也可以多实现,还可以在...

  • java的final关键字

    ——修饰类、接口和抽象类 final可以修饰方法,属性,类!但是不能修饰接口,抽象类;因为 接口和抽象类本身就是...

  • 第四章 类、对象和接口

    Kotlin的类和接口和Java还是有一些区别的。例如:接口可以包含属性声明。与Java不同,Kotlin的声明默...

  • 第四章、类和接口(一)

    第十三条、使类和成员的可访问性最小化 设计良好的模块会隐藏所有的实现细节,把它的API和它的实现清晰地隔离开来。然...

网友评论

      本文标题:第四章类和接口

      本文链接:https://www.haomeiwen.com/subject/kskqjhtx.html