美文网首页effective Java
《Effective Java》读书笔记 —— 类和接口

《Effective Java》读书笔记 —— 类和接口

作者: 666真666 | 来源:发表于2017-04-13 18:01 被阅读399次

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

访问修饰符:

  • private
  • protected
  • public

顶层的(非嵌套)类和接口,两种访问级别:

  • 包级私有(package-private)
  • public

成员(域、方法、嵌套类和嵌套接口)

  • private:只有在声明该成员的顶层类内部才可以访问
  • package-private:声明该成员的包内部的任何类都可以访问,是默认访问级别
  • protected:在声明类和子类中可以访问
  • public:任何地方可以访问
规则一:尽可能使每个类或者成员不被外界访问

如果一个包级私有的顶层类只是在某一个类的内部使用,就应该考虑使它成为唯一使用它的那个类的私有嵌套类。

规则二:如果方法覆盖了超类中第一个方法,子类中的访问级别就不允许低于超类的访问级别,确保任何可使用超类的地方都可以使用子类
规则三:接口中的所有方法都必须是public
规则四:实例域不能是公有的

如果域时非final的,或者是一个指向可变对象的final引用,那么一旦使这个域称为公有,就放弃了在这个域中的值进行限制的能力,也就放弃了这个域的不可变能力

包含公有可变域的类并不是线程安全的。

规则四:静态域不要是公有的(除了暴露静态常量)

要对外暴露静态域,必须是基本类型的值,或者是不可变对象。

长度非零的数组总是可变的,所以,类具有公有的静态final数组域,或者返回这种域的方法,总是不正确的。

以下错误:

public static final Tings[] VALUES = {...};

解决方案:公有数组私有化,并增加一个公有的不可变列表

private static final Tings[] VALUES = {...};
public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(VALUES));

或者:添加公有方法,返回私有数组的拷贝

private static final Tings[] VALUES = {...};
public static final Tings[] values() {
    return VALUES.clone;
}

2.在公有类中使用访问方法而非公有域

公有类永远不要暴露可变的类。

3.使可变性最小化

不可变类:其实例不能被修改的类。具体来说,每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变。

Java 平台类库中的不可变类:String、基本类型的包装类、BigInteger、BigDecimal。不可变类对应配套的可变类:StringBuilder、BitSet。本应该是不可变,但却是可变的类:Date、Point。

不可变类的优点:

  • 易于设计、实现和使用
  • 不可变对象很简单、只有一种状态,即被创建时的状态
  • 不容易出错、更加安全
  • 线程安全,不要求同步,可以被自由的共享

不可变类的缺点:

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

String对象不可变性的优缺点

  • 字符串常量池的需要.
  • 线程安全考虑
  • 类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载
  • 支持hash映射和缓存

使类成为不可变,遵循的规则:

  • 不要提供任何会修改对象状态(属性)的方法
  • 保证类不会被扩展,不会有子类,破坏该类的不可变行为
    • 如果类可以被继承会破坏类的不可变性机制,只要继承类覆盖父类的方法并且继承类可以改变成员变量值,那么一旦子类以父类的形式出现时,不能保证当前类是否可变。使所有的域都是final的。
  • 使所有的域(属性)都是final
  • 使所有的域都是private,防止客户端获得访问被域引用的可变对象的权限,并防止客户端直接修改这些对象
  • 确保对于任何可变组件的互斥访问
    • 如果类具有指向可变对象的域,必须确保该类的客户端无法获得执行这些对象的引用
    • 在构造器中,永远不要用客户端提供的对象引用来初始化这样的域
    • 在访问方法中,也不要返回该对象引用
      • 普通对象,直接new 一个新的对象
      • 数组这类复杂对象,可使用 clone方法
    • 在构造器、访问方法和 readObject 方法中请使用保护性拷贝技术

通过构造器初始化所有成员,构造器初始化成员时,需要进行深浅拷贝,如果构造器传入的对象直接赋值给成员变量,还是可以通过对传入对象的修改进而导致改变内部变量的值

     public final class ImmutableDemo {  
         private final int[] myArray;  
         public ImmutableDemo(int[] array) {  
             this.myArray = array; // wrong  
         }  
     }
     这种方式不能保证不可变性,myArray和array指向同一块内存地址,用户可以在ImmutableDemo之外通过修改array对象的值来改变myArray内部的值。
     为了保证内部的值不被修改,可以采用深度copy来创建一个新内存保存传入的值。正确做法:
     public final class MyImmutableDemo {  
         private final int[] myArray;  
         public MyImmutableDemo(int[] array) {  
             this.myArray = array.clone();   
         }   
     }

不可变类的设计

对于访问方法,一般会返回一个新的实例,而不是修改这个实例,大多数不可变类使用这种模式,称为函数的做法。

不可变的类一般会提供一些静态工厂,它们把频繁被请求的实例缓存起来,使客户端之间可以共享这些实例,而不用创建新的实例,降低内存占用和垃圾回收成本。所有基本类型的包装类和BigInteger都有这样的静态工厂。

不可变对象可以被自由共享,所以根本不需要做任何拷贝,因为拷贝始终等于原始的对象,所以不需要为不可变的类提供clone方法或者拷贝构造器。

不仅可以共享不可变对象,也可以共享它们的内部信息。

不可变对象为其他对象提供了大量的构件。

有关序列化,如果让自己的不可变类实现序列化,就必须显式提供 readObject 或者 readResolve,否则反序列化可能会产生新的实例。

尽量使用不可变类,不要为每个get方法编写一个相应的set方法

举例

说明:这个类表示一个复数,加减运算都是返回一个新的实例,而不是在原来的实例上修改,称为函数的做法。

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;
    }
    
    public Complex add(Complex c) {
        return new Complex(re + c.re, im + c.im);
    }
    
    public Complex sub(Complex c) {
        return new Complex(re - c.re, im - c.im);
    }
}

将构造函数改为私有的,并添加静态工厂来替代公有构造器

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

4.复合优先于继承

与方法调用不同的是,继承打破了封装性。子类依赖于其超类中特定功能的实现细节。所以子类必须要跟着其超类的更新而演变,导致子类很脆弱。

继承

  • 继承打破了封装性
  • 父类内部细节对于子类是可见的,继承的代码复用是一种白盒式代码复用,如果基类的实现发生改变,那么派生类也将随之改变,导致子类的行为不可预知
  • 只有两者存在is-a的关系,才使用继承,如果不是,则使用组合

组合

  • 在新的类中增加一个私有域,它引用现有类的一个实例
  • 继承必须在编译器确定继承哪个类,组合可以采用面向接口编程,类的组合关系可以在运行期确定

5.要么为继承而设计,并提供文档说明,要么就禁止继承

类的文档必须精确描述覆盖每个方法所带来影响,也就是说,覆盖的方法必须说明其自用性

类必须通过某种形式提供适当的钩子(hook),以便能够进入它的内部工作流程中,这种形式可以是精心选择的受保护的方法

6.接口优于抽象类

接口优点

  • 现有的类可以很容易被更新,以实现新的接口
  • 接口是定义mixin(混合类型)的理想选择
    • 类不可能有一个以上的父类,类层次结构中也没有适当的地方插入mixin
  • 接口允许我们构造非层次接口的类型框架

接口和抽象类区别

  • 接口里不能定义静态方法;抽象类里可以定义静态方法。
  • 接口里不包含构造器,抽象类可以包含构造器。抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
  • 接口里不能包含初始化块,但抽象类可以包含初始化块。
  • 接口里不包含已经提供实现的方法,只能包含抽象方法,;抽象类则完全可以包含普通方法。
  • 接口里只能定义静态常量,不能定义其他变量。抽象类既可以定义普通变量,也可以定义静态常量。
  • 注意:在接口里定义的接口、枚举类、变量默认都采用public static两个修饰符,不管定义时是否指定这两个修饰符,系统都会自动使用public static对他们进行修饰,同理,在抽象类里,会默认使用public abstract修饰方法。

骨架实现类

虽然接口不允许包含默认实现,但是,可通过对你导出的每个重要接口都提供一个抽象的骨架实现类,把接口和抽象类的优点结合起来。接口的作用仍然是定义类型,但是骨架实现类接管了所有与接口实现相关的工作

在选择抽象类和接口时,并不是二选一的答案,或干脆枪毙掉抽象类。其实,你可以把接口和抽象类的优点结合起来,对于你希望导出(对外提供)的每一个重要接口都提供一个抽象类(骨架实现类)。接口的作用仍然是定义类型,骨架实现类负责所有与接口实现相关的工作

7.接口只用于定义类型

当类实现接口时,接口就充当可以引用这个类的实例的类型。类实现了接口,就表明客户端对这个类的实例实施某些动作。

接口应该只被用来定义类型,不应该被用来导出常量

接口用于导出常量

常量接口:只包含静态的final域。导出常量的一种形式。常量接口模式是对接口的不良使用

缺点:

  • 实现常量接口,会导致把这样的实现细节泄漏到该类导出的API中
  • 如果非final类实现了常量接口,它的所有子类的命名空间也会被接口中的常量所”污染“。

常量接口的例子

public interface PhysicalConstants {
    static final double AAA = 0.1;
    static final double BBB = 0.1;
    static final double CCC = 0.1;
}

导出常量的合理方案:

  • 使用枚举类型导出
  • 使用不可实例化的工具类导出

工具类的方式:

public class PhysicalConstants {
    private PhysicalConstants() {};
    static final double AAA = 0.1;
    static final double BBB = 0.1;
    static final double CCC = 0.1;
}

8.类层次优于标签类

有时,可能遇到带有两种甚至更多风格的实例的类,并包含表示实例风格的标签域。

下面例子,此类表示圆形或者矩形

demo

标签类缺点:

  • 充斥样板代码,包括枚举声明、标签域以及条件语句
  • 破坏了可读性
  • 内存占用也增加
  • 实例承担着其他风格不相关的域

解决方案:子类化

定义一个包含抽象方法的抽象类,公共方法定义在抽象类

demo

9.用函数对象表示策略(策略模式)

函数指针(引用)的主要用途是实现策略模式,在Java中实现策略模式,要声明一个接口来表示该策略,并且为每个具体策略声明一个实现了该接口的类。当一个具体策略只被使用一次时,通常使用匿名类来声明和实例化这个具体策略类,当一个具体策略是设计用来重复使用的时候,它的类通常就要被实现为私有的静态成员类,并且通过公有的静态final域被导出,其类型为该策略接口。

举例:比较器函数代表一种为元素排序的策略。

Java没有提供函数指针,可以用对象引用实现此功能。

比较器实例

StringLengthComparator 实例就是用于字符串长度比较的具体策略。

StringLengthComparator 是无状态的(没有域),所以单例比较合适。

class StringLengthComparator implements Comparator<String> {
    private StringLengthComparator() {};
    private static final StringLengthComparator INSTANCE = new StringLengthComparator();
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
}

定义一个策略接口

public interface Comparator<T> {
    public int compare(T t1, T t2);
}

使用比较策略

Arrays.sort(stringArray, new Comparator(String)(){
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
})

10.优先考虑静态成员类

嵌套类:被定义在另一个类的内部的类。嵌套类存在的目的只是为了它的外围类提供服务。

嵌套类包括:

  • 静态成员类(内部类)
  • 非静态成员类(内部类)
  • 匿名类
  • 局部类

静态成员类

最简单的一种嵌套类,可看作是普通的类,可以访问外围类的所有成员,包括私有成员。

公有静态成员类常见用法,是作为公有的辅助类,仅当与它的外部类一起使用时才有意义。

私有静态成员类常见用法,用来代表外围类所代表的对象的组件。例如,Map实例,Map的内部都有一个Entry对象,对应于Map的key和value。

非静态成员类(内部类)

必须和外围类的一个实例相关联,可以用this来访问

常见用法:Adapter

如果成员类不要求访问外围实例,就要声明成静态成员类,不然每个实例都会包含一个额外的指向外围对象的引用

相关文章

  • Java基础

    JAVA开发六大原则 抽象类和接口的对比 如何去设计类和接口(Effective Java) 1、使类和成员的可访...

  • Effective Java——类和接口

    本系列文章是总结Effective Java文章中我认为最重点的内容,给很多没时间看书的朋友以最短的时间看到这本书...

  • 《Effective Java》读书笔记 —— 类和接口

    1.使类和成员的可访问性最小化 访问修饰符: private protected public 顶层的(非嵌套)类...

  • Effective Java-类和接口

    本部分内容用来指导程序员怎样才能设计出更加有用、健壮、灵活的类和接口。内容导图如下: 1.使类和成员的可访问性最小...

  • Effective Java Note(类和接口)

    类和接口 一、使类和成员的可访问性最小化 首先我们要了解一个 软件设计基本原则:封装 模块隐藏所有的实现细节,只通...

  • Effective Java(三) 类和接口

    类和接口 Tip-13 使类和成员的可访问性最小化 尽可能地使每个类或者成员不被外界访问 注意:长度非0的数组总是...

  • Effective Java-类和接口

    使类和成员的可访问性最小化 尽可能地使每个类或者成员不被外界访问 除了公有静态final域的特殊情况之外,公有类都...

  • Effective Java - 第4章 类和接口

    《Effective Java(第2版)》第4章 类和接口思维导图,文字版可见:https://mubu.com/...

  • Effective Java读书笔记--第4章 类和接口

    个人读书笔记,部分没读懂的知识点可能会简单概括或缺失,以后反复阅读后再完善。 第4章 类和接口 第13条: 使类和...

  • java成神之路---集合框架

    标签(空格分隔): java java集合类库的设计思想:“接口与实现分离” java类库中的集合接口和迭代器接口...

网友评论

    本文标题:《Effective Java》读书笔记 —— 类和接口

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