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

第四章类和接口

作者: 后来丶_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";
    
        }
    
    }
    

    参考文章

    相关文章

      网友评论

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

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