Effective Java 第三版读书笔记(类和接口)

第15条.使类和成员的可访问性最小化

对于成员有四种可访问级别:

1.私有的(private)----- 只有在声明该成员的顶层类内部才可以访问这个成员。

2.包级私有的(package-private) ----- 声明该成员的包内部的任何类都可以访问这个成员,也是默认访问级别,接口成员除外,接口成员默认的访问级别是公有的。

3.受保护的(protected) ----- 声明该成员的类的子类可以访问这个成员。

4.公有的(public)  ----- 在任何地方都可以访问该成员。

如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别。

公有类的实例域决不能是公有的。

让类具有公有的静态final数组域,或者返回这种域的访问方法,这是错误的。如果类具有这样的域或者访问方法,客户端将能够修改数组中的内容。

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

第16条.要在公有类而非公有域中使用访问方法

我们不应该使用这种方法,因为这种类的数据域是可以被直接访问的。

public class Point {
    public int x;
    public int y;
}

应该使用这种方法,如果类可以在它所在的包之外进行访问,就提供访问方法。

public class Point {

    private  int x;
    private  int y;

    public Point(int x,int y){
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

第17条.使可变性最小化

1.不要提供任何会修改对象状态的方法

2.保证类不会被扩展(声明为final)

3.声明所有的域都是final的。

4.生命所有的域都为私有的。

5.确保对于任何可变组件的互斥访问。(如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用)

6.不可变对象本质上是线程安全的,它们不要求同步。

坚决不要为每个get方法编写一个相应的set方法。除非有很好的理由要让类成为可变的类,否则它就应该是不可变的。

构造器应该创建完全初始化的对象,并建立起所有的约束关系。

第18条.复合优先于继承

与方法调用不同的是,继承打破了封装性。还需要注意,父类中的缺陷会全部被传播到子类中。

只有当子类真正是超类的子类型时,才适合用继承。

否则我们应该在新的类中增加一个私有域,它引用现有类的一个实例。这种设计被称为复合。新类中的每个实例方法都可以调用被包含的现有类实例中对应的方法,并返回它的结果。这被称为转发。

第19条.要么设计继承并提供文档说明,要么禁止继承

构造器决不能调用可被覆盖的方法。

无论是clone还是readObject,都不可以调用可覆盖的方法,不管是以直接还是间接的方式。

第20条.接口优于抽象类

我觉得归根结底,因为一个类只能继承一个抽象类,但是可以实现多个接口。而且在Java8中可以通过缺省方法实现协助。接口中不可以包含实例域或者非公有的静态成员,而且不能为Object方法(如equals和hashCode)提供缺省方法。通过对接口提供一个抽象的骨架实现类,可以把接口和抽象类的优点结合起来。接口负责定义类型,或许还提供一些缺省方法,而骨架实现类则负责实现除基本类型接口方法之外,剩下的非基本类型接口方法。这就是魔板方法。

package com.example.ownlearn;

import java.util.Map;
import java.util.Objects;

public abstract class AbstractMapEntry<K,V> implements Map.Entry<K,V> {

   public V setValue(V value){
       throw new UnsupportedOperationException();
   }

    @Override
    public boolean equals(Object obj) {
        if(obj == this)
            return true;
        if(!(obj instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry)obj;
        return Objects.equals(e.getKey(),getKey()) && Objects.equals(e.getValue(),getValue());
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
    }

    @Override
    public String toString() {
        return getKey() + "=" + getValue();
    }
}

 

第21条.为后代设计接口

Java8发行之前,如果不破坏现有的实现,是不可能给接口添加方法的。在Java8中增加了缺省方法,目的就是允许给现有的接口增加方法。但是给现有接口增加新方法还是充满风险的。缺省方法的声明中包括一个缺省实现,这是给实现了该接口但是没有实现默认方法的所有类使用的。

建议尽量避免利用缺省方法在现有接口上添加新的方法,除非有特殊需要,但就算在那样的情况下也应该慎重考虑:缺省的方法实现是否会破坏现有的接口实现。还要注意,缺省方法不支持从接口中删除方法,也不支持修改现有方法的签名。

package com.example.ownlearn;

public interface RemoveIf {
    default boolean removeif(){
        return true;
    }
}

第22条.接口只用于定义类型

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

有一种接口被称为常量接口,这种接口不包含任何方法,它只包含静态的final域,每个域都导出一个常量。

public interface ObjectStreamConstants {
    short STREAM_MAGIC = -21267;
    short STREAM_VERSION = 5;
    byte TC_BASE = 112;
    byte TC_NULL = 112;
    byte TC_REFERENCE = 113;
    byte TC_CLASSDESC = 114;
    byte TC_OBJECT = 115;
    byte TC_STRING = 116;
    byte TC_ARRAY = 117;
    byte TC_CLASS = 118;
    byte TC_BLOCKDATA = 119;
    byte TC_ENDBLOCKDATA = 120;
    byte TC_RESET = 121;
    byte TC_BLOCKDATALONG = 122;
    byte TC_EXCEPTION = 123;
    byte TC_LONGSTRING = 124;
    byte TC_PROXYCLASSDESC = 125;
    byte TC_ENUM = 126;
    byte TC_MAX = 126;
    int baseWireHandle = 8257536;
    byte SC_WRITE_METHOD = 1;
    byte SC_BLOCK_DATA = 8;
    byte SC_SERIALIZABLE = 2;
    byte SC_EXTERNALIZABLE = 4;
    byte SC_ENUM = 16;
    SerializablePermission SUBSTITUTION_PERMISSION = new SerializablePermission("enableSubstitution");
    SerializablePermission SUBCLASS_IMPLEMENTATION_PERMISSION = new SerializablePermission("enableSubclassImplementation");
    int PROTOCOL_VERSION_1 = 1;
    int PROTOCOL_VERSION_2 = 2;
}

事实上我们不应该这样做,如果要导出常量,可以有几种合理的选择方案。如果这些常量与某个现有的类或者接口紧密相关,就应该把这些常量添加到这个类或者接口中。如果这些常量最好被看做枚举类型的成员,就应该用枚举类型来导出这些常量。否则,应该使用不可实例化的工具类来导出这些常量。

package com.example.ownlearn;

public class PhysicalConstants {
    private PhysicalConstants(){}

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

从Java7开始,下划线的使用已经开始合法了,它对数字字面量的值没有影响,是为了增加数字的可读性。

第23条.类层次优于标签类

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

例如:

package com.example.ownlearn;

public class Figure {
    enum Shape { RECTANGLE , CIRCLE}
    final Shape shape;
    double length;
    double width;
    double radius;
    Figure(double redius){
        shape = Shape.CIRCLE;
        this.radius = redius;
    }
    Figure(double length,double width){
        shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width;
    }

    double area(){
        switch(shape){
            case CIRCLE:
                return Math.PI * (radius * radius);
            case RECTANGLE:
                return length * width;
                default:
                    throw new AssertionError(shape);


        }
    }

}

这种标签类有许多缺点。它们中充斥着样板代码,包括枚举声明、标签域以及条件语句。破坏了可读性。

我们可以将标签类转变为类层次。

package com.example.ownlearn;

abstract class Figure {
    abstract double area();
}
package com.example.ownlearn;

class Circle extends Figure {
    final double radius;

    Circle(double radius){this.radius = radius;}
    @Override
    double area() {
        return Math.PI * (radius * radius);
    }
}
package com.example.ownlearn;

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

代码清晰且清楚。

第24条.静态成员类优于非静态成员类

嵌套类是指定义在另一个类中的内部的类。嵌套类存在的目的应该只是为它的外围类提供服务。

嵌套类有四种:静态成员类、非静态成员类、匿名类和局部类

如果一个嵌套类需要在单个方法之外仍然是可见的,或者它太长了,不适合放在方法内部,就应该使用成员类。如果成员类的每个实例都需要一个指向其外围实例的引用,就要把成员类做成非静态的;否则,就做成静态的。假设这个嵌套类属于一个方法的内部,如果你只需要在一个地方创建实例,并且已经有了一个预置的类型可以说明这个类的特征,就要把它做成匿名类;否则,就做成局部类。

第24条.限制源文件为单个顶级类

  public static void main(String[] args) {
        System.out.println(Utensil.NAME,Dessert.NAME);
    }

假设在一个名为Utensil.java的源文件中同时定义了Utensil和Dessert;

class Utensil {
    static final String NAME = "PAN";
}

class Dessert{
    static final String NAME = "pot";
}

同时我们又在另一个名为Dessert.java的源文件中也定义了同样的两个类:

class Utensil {
    static final String NAME = "pot";
}

class Dessert{
    static final String NAME = "pie";
}

如果使用命令 javac Main.java Dessert.java 来编译程序,编译就会失败,此时编译器会提醒定义了多个Utensil和Dessert类。

如果用命令javac Main.java或者javac Main.java Utensil.java 编译程序,结果将如同没有编写Dessert.java文件一样。这显然是无法接受的。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值