effective java第4章 类和接口

本文探讨了面向对象设计的几个核心原则,包括使类和成员的可访问性最小化,使用访问方法而非公有域,使可变性最小化,以及在公有类中优先采用复合而非继承。这些原则有助于创建更稳定、更易于维护的软件系统。

第13条 使类和成员的可访问性最小化
第一规则:尽可能地使每个类或成员不被外界访问
只有当同一个包内的另一个类真正需要访问一个成员的时候,你才应该删除private修饰符。
如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别。这样可确保任何可使用超类实例的地方也可以使用子类的实例。
长度非零的数组总是可变的,所以,类具有公有的静态final数组域,或者返回这种域的访问方法,这几乎总是错误的。修正方法如下:

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


第14条 在公有类中使用访问方法而非公有域(过)
第15条 使可变性最小化
要使类成为不可变,要遵循下面几条圆原则:
1,不要提供任何会修改对象状态的方法
2,保证类不会被扩展
3,使所有的域都是final的
4,使所有的域都是私有的
5,确保对于任何可变组件的互斥访问
第16条 复合优先于继承
对于两个类,A和B,只有当两者之间确实存在“is_a”的关系时,类B才应该扩展类A

### 《Effective Java》第 4 接口 - **使成员的可访问性最小化**:设计成员时,应尽可能降低其可访问性,遵循信息隐藏原则。对于顶层的接口,只有两种访问级别:包级私有公有。对于成员(字段、方法、嵌套嵌套接口),有四种访问级别:私有的、包级私有的、受保护的公有的。 ```java // 私有字段 private int privateField; // 包级私有方法 void packagePrivateMethod() { // 方法实现 } ``` - **在公有中使用访问方法而非公有字段**:公有不应直接暴露其字段,而应提供访问方法(getter setter),这样可以更好地控制对字段的访问,便于后续修改维护。 ```java public class Point { private double x; private double y; public double getX() { return x; } public void setX(double x) { this.x = x; } public double getY() { return y; } public void setY(double y) { this.y = y; } } ``` - **使可变性最小化**:不可变更易于设计、实现使用,不容易出错且更安全。要创建不可变,需要遵循以下规则:不提供修改对象状态的方法;保证不会被扩展(通常使用 final 修饰);所有字段都是 final 的;所有字段都是私有的;确保对任何可变组件的互斥访问。 ```java 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); } } ``` - **复合优先于继承**:继承是实现代码复用的有力手段,但它也有一些局限性,如打破了封装性,父的修改可能会影响子。复合是指在新中包含一个现有的实例,并通过该实例调用其方法。 ```java // 复合示例 public class ForwardingSet<E> implements Set<E> { private final Set<E> s; public ForwardingSet(Set<E> s) { this.s = s; } @Override public int size() { return s.size(); } // 其他方法... } ``` - **要么为继承而设计并提供文档说明,要么就禁止继承**:如果一个不是为继承而设计的,应该禁止它被继承,可以使用 final 修饰。如果要设计一个可继承的,需要提供详细的文档说明,包括哪些方法可以重写,哪些方法不能重写等。 ```java // 禁止继承的 public final class NonInheritableClass { // 的实现 } ``` - **接口优于抽象**:接口定义了一组方法的签名,而不包含实现,它可以被任何实现。抽象则既可以包含抽象方法,也可以包含具体方法。接口更灵活,一个可以实现多个接口,但只能继承一个抽象。 ```java // 接口示例 public interface MyInterface { void doSomething(); } // 实现接口 public class MyClass implements MyInterface { @Override public void doSomething() { // 方法实现 } } ``` - **为后代设计接口**:在设计接口时,要考虑到接口的扩展性,避免频繁修改接口。如果需要添加新的方法,可以提供默认方法。 ```java public interface MyInterface { void doSomething(); // 默认方法 default void doAnotherThing() { // 默认实现 } } ``` - **接口只用于定义型**:接口应该只用于定义型,而不应该包含常量。如果需要定义常量,可以使用枚举型或不可实例化的。 ```java // 错误示例:接口包含常量 public interface Constants { int MAX_VALUE = 100; } // 正确示例:使用不可实例化的定义常量 public class Constants { private Constants() { throw new AssertionError(); } public static final int MAX_VALUE = 100; } ``` - **层次优于标签**:标签是指包含多种不同型对象信息的,通过一个标签字段来区分不同的型。层次则是通过继承多态来实现不同型的对象。层次更加清晰、灵活可维护。 ```java // 标签示例 class Figure { enum Shape { RECTANGLE, CIRCLE }; final Shape shape; double length; double width; double radius; Figure(double radius) { shape = Shape.CIRCLE; this.radius = radius; } 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(); } } } // 层次示例 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; } } ### 《Effective Java》第 5 :泛型 - **请不要在新代码中使用原生态型**:原生态型是指没有指定泛型参数的泛型型,使用原生态型会失去泛型的型安全检查自动装箱拆箱等优点。 ```java // 错误示例:使用原生态型 List list = new ArrayList(); list.add("hello"); String s = (String) list.get(0); // 需要手动型转换 // 正确示例:使用泛型型 List<String> list = new ArrayList<>(); list.add("hello"); String s = list.get(0); // 无需手动型转换 ``` - **消除非受检警告**:在使用泛型时,可能会出现一些非受检警告,应该尽量消除这些警告。如果不能消除警告,但可以证明引发警告的代码是型安全的,那么可以使用 `@SuppressWarnings("unchecked")` 注解来抑制警告。 ```java @SuppressWarnings("unchecked") public static <T> T[] toArray(List<T> list) { T[] arr = (T[]) new Object[list.size()]; for (int i = 0; i < list.size(); i++) { arr[i] = list.get(i); } return arr; } ``` - **列表优先于数组**:数组是协变的,即如果 `Sub` 是 `Super` 的子型,那么 `Sub[]` 就是 `Super[]` 的子型;而泛型是不可变的,`List<Sub>` 不是 `List<Super>` 的子型。数组在运行时才进行型检查,而泛型在编译时就进行型检查。因此,在使用集合时,列表通常比数组更安全灵活。 ```java // 数组的协变问题 Object[] array = new String[10]; array[0] = 1; // 运行时抛出 ArrayStoreException // 泛型的不可变 List<String> list = new ArrayList<>(); // 编译错误:不能将 List<String> 赋值给 List<Object> // List<Object> objList = list; ``` - **优先考虑泛型型**:在设计方法时,优先使用泛型型,这样可以提高代码的型安全性可重用性。 ```java // 泛型示例 public class Box<T> { private T t; public void set(T t) { this.t = t; } public T get() { return t; } } ``` - **优先考虑泛型方法**:泛型方法可以在调用时自动推断泛型型,使代码更加简洁。 ```java // 泛型方法示例 public static <T> T getLast(List<T> list) { return list.get(list.size() - 1); } ``` - **利用有限制的通配符来提升 API 的灵活性**:有限制的通配符包括 `<? extends T>` `<? super T>`。`<? extends T>` 表示该型是 `T` 的子型,用于生产者;`<? super T>` 表示该型是 `T` 的父型,用于消费者。 ```java // 生产者使用 <? extends T> public static double sumOfList(List<? extends Number> list) { double sum = 0; for (Number n : list) { sum += n.doubleValue(); } return sum; } // 消费者使用 <? super T> public static void addNumbers(List<? super Integer> list) { for (int i = 1; i <= 10; i++) { list.add(i); } } ``` - **优先考虑型安全的异构容器**:型安全的异构容器是指可以存储不同型对象的容器,通过使用 `Class` 作为键来保证型安全。 ```java // 型安全的异构容器示例 public class Favorites { private Map<Class<?>, Object> favorites = new HashMap<>(); public <T> void putFavorite(Class<T> type, T instance) { favorites.put(type, type.cast(instance)); } public <T> T getFavorite(Class<T> type) { return type.cast(favorites.get(type)); } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值