正文
-
优先考虑流中无副作用的函数
-
优先使用 Collection 而不是 Stream 来作为方法的 返回类型
-
谨慎使用流并行
-
检查参数有效性
-
this.strategy = Objects.requireNonNull(strategy, "strategy"); private static void sort(long a[], int offset, int length) { assert a != null; assert offset >= 0 && offset <= a.length; assert length >= 0 && length <= a.length - offset; ... // Do the computation }
-
-
必要时进行防御性拷贝
-
this.strategy public final class Period { private final Date start; private final Date end; /** * @param start the beginning of the period * @param end the end of the period; must not precede start * @throws IllegalArgumentException if start is after end * @throws NullPointerException if start or end is null */ public Period(Date start, Date end) { if (start.compareTo(end) > 0) throw new IllegalArgumentException( start + " after " + end); this.start = start; this.end = end; } public Date start() { return start; } public Date end() { return end; } } Date start = new Date(); Date end = new Date(); Period p = new Period(start, end); end.setYear(78); // Modifies internals of p! 利用 Date 类是可变的这一事实很容易违反这个不变式
-
保护性拷贝(防止成员变量被破坏)
-
构造方法的防御性拷贝
-
public Period(Date start, Date end) { this.start = new Date(start.getTime()); this.end = new Date(end.getTime()); if (this.start.compareTo(this.end) > 0) throw new IllegalArgumentException( }
-
-
get方法的防御性拷贝
-
public Date start() { return new Date(start.getTime()); } public Date end() { return new Date(end.getTime()); }
-
-
成员变量定义成基本类型
-
-
-
仔细设计方法签名
-
仔细选择方法名名称
-
不要过分地提供方便的方法
-
避免过长的参数列表
目标是四个或更少的参数
-
一种方法是将方法分解为多个方法
-
创建辅助类来保存参数组
-
对象构造到方法调用采用 Builder 模式
-
-
对于参数类型,优先选择接口而不是类
-
与布尔型参数相比,优先使用两个元素枚举类型
-
-
明智审慎地使用重载
-
为什么返回值不同,不算重载
-
编译的时候没有报错,有可能运行的时候报错
-
List<Interger> list = new ArrayList<>(); void test(List<Interger> list, int e){ if(list.contines(e)){ // 自动装箱 list.remove(e); // 移出的是Index 而不是对应的值 } }
-
-
明智审慎地使用可变参数
-
性能问题
- 每次调用数组分配和初始化
-
可以使用重载来解决
-
-
返回空的数组或集合,不要返回 null
-
返回null
-
性能优势,避免分配内存
除非测量结果表明所讨论的分配是性能问题的真正原因,否则不宜担心此级别的性能
-
-
返回空的数组或集合
-
避免npe
-
空集合
Collections.emptyList()
-
认为分配零长度数组会损害性能,则可以重复返回相同的零长度数组,因为所有零长度数组都是不可变
的:-
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0]; public Cheese[] getCheeses() { return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY); }
-
-
-
-
明智审慎地返回 Optional
-
为所有已公开的 API 元素编写文档注释
-
最小化局部变量的作用域
-
for 循环优于 while 循环
-
局部变量最小号化 Iterator<Element> i = c.iterator(); while (i.hasNext()) { doSomething(i.next()); }
-
-
-
for-each 循环优于传统 for 循环
-
了解并使用库
-
static Random rnd = new Random(); static int random(int n) { return Math.abs(rnd.nextInt()) % n; }
-
如果 n 是小的平方数,随机数序列会在相当短的时间内重复
-
如果 random 方法工作正常,程序将输出一个接近 50 万的数字,但是如果运行它,你将发现它输出一个接近 666666 的数字。随机方法生成的数字中有三分之二落在其范围的下半部分
-
public static void main(String[] args) { int n = 2 * (Integer.MAX_VALUE / 3); int low = 0; for (int i = 0; i < 1000000; i++) if (random(n) < n/2) low++; System.out.println(low); }
-
-
Math.abs(rnd.nextInt())
可能会得到负数(Integer.MIN_VALUE
的绝对值超过了Integer.MAX_VALUE
),然后对这个负数取模操作可能会得到负值
-
-
选择的随机数生成器现在是 ThreadLocalRandom
-
List如何取交集
-
-
若需要精确答案就应避免使用 float 和 double 类型
-
loat 和 double 类型主要用于科学计算和工程计算。它们执行二进制浮点运算,该算法经过精心设计,能够在很 大范围内快速提供精确的近似值。但是,它们不能提供准确的结果,也不应该在需要精确结果的地方使用。float 和 double 类型特别不适合进行货币计算,因为不可能将 0.1(或 10 的任意负次幂)精确地表示为 float 或 double
-
例如,假设你口袋里有 1.03 美元,你消费了 42 美分。你还剩下多少钱? System.out.println(1.03 - 0.42); // out 0.6100000000000001
-
-
使用int 和 long
-
用明确下限来解决
- 1030 -42
-
-
使用 BigDecimal 有两个缺点:它与原始算术类型相比很不方便,而且速度要慢得多。如果你只解决一个简单的问题,后一种缺点是无关紧要的,但前者可能会让你烦恼
-
-
基本数据类型优于包装类
-
基本类型性能好
-
将 == 操作符应用于包装类型几乎都是错误的。
-
int 1 ==1 true Integer 用equals
-
jdk -128-127 默认使用同一个缓存
-
-
案例2
-
-
当使用其他类型更合适时应避免使用字符串
-
字符串是其他值类型的糟糕替代品
-
字符串是枚举类型的糟糕替代品
-
字符串是聚合类型的糟糕替代品
-
String compoundKey = className + "#" + i.next(); 这种方法有很多缺点。如果用于分隔字段的字符出现在其中一个字段中,可能会导致混乱。 要访问各个字段,你 必须解析字符串,这是缓慢的、冗长的、容易出错的过程。 你不能提供 equals、toString 或 compareTo 方法,但必须 接受 String 提供的行为。 更好的方法是编写一个类来表示聚合,通常是一个私有静态成员类
-
-
字符串不能很好地替代 capabilities
-
如果两个客户端各自决定为它们的线程本地变量使用相同的名称,它们无意中就会共享一个变
量,这通常会导致两个客户端都失败 -
没有ThreadLocal之前 = public class ThreadLocal { private ThreadLocal() { } // Noninstantiable // Sets the current thread's value for the named variable. public static void set(String key, Object value); // Returns the current thread's value for the named variable. public static Object get(String key); }
-
-
-
当心字符串连接引起的性能问题
-
要获得能接受的性能,请使用 StringBuilder 代替 String
-
public String statement() { String result = ""; for (int i = 0; i < numItems(); i++) result += lineForItem(i); // String concatenation return result; } StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH); b.append(lineForItem(i));
-
-
部分jdk是可以自动优化,并不保证优化到什么程度
-
-
通过接口引用对象
-
如果存在合适的接口类型,那么应该使用接口类型声明参数、返回值、变量和字段
- 使用
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
而不是Set<Son> sonSet = new LinkedHashSet<>();
- 使用
-
如果没有合适的接口存在,那么用类引用对象是完全合适的
-
考虑值类,如 String 和 BigInteger。值类很少在编写时考虑到多个实现。它们通常是 final 的,很少有相应的接口。使用这样的值类作为参数、变量、字段或返 回类型非常合适。而不是使用的接口
-
Integer i = Integer.valueof(10)
-
-
如果没有合适的接口,就使用类层次结构中提供所需功能的最底层的类
-
-
接口优于反射
-
明智审慎地本地方法
- 由于本地语言比 Java 更依赖于平台,因此使用本地方法的程序的可移植性较差。它们也更难调 试。如果不小心,本地方法可能会降低性能
-
明智审慎地进行优化
-
遵守被广泛认可的命名约定
-
Identifier Type Example Package or module org.junit.jupiter.api , com.google.common.collect Class or Interface Stream, FutureTask, LinkedHashMap,HttpClient Method or Field remove, groupingBy, getCrc Constant Field MIN_VALUE, NEGATIVE_INFINITY Local Variable i, denom, houseNum Type Parameter T, E, K, V, X, R, U, V, T1, T2
-
-
只针对异常的情况下才使用异常
-
try { int i = 0; while ( true ) range[i++].climb(); } catch ( ArrayIndexOutOfBoundsException e ) { } 不要利用异常终止循环 for ( Mountain m : range ) m.climb();
-
-
对可恢复的情况使用受检异常,对编程错误使用运行时异常
-
避免不必要的使用受检异常
-
优先使用标准的异常
-
不要直接重用 Exception、RuntimeException、Throwable 或者 Error
- 对待这些类要像对待抽象类一样。你无法 可靠地测试这些异常,因为它们是一个方法可能抛出的其他异常的超类。
-
IllegalArgumentException IllegalStateException NullPointerException IndexOutOfBoundsExecption ConcurrentModificationException UnsupportedOperationException 非 null 的参数值不正确 不适合方法调用的对象状态 在禁止使用 null 的情况下参数值为 null 下标参数值越界 在禁止并发修改的情况下,检测到对象的并发修改 对象不支持用户请求的方法
-
-
抛出与抽象对应的异常
-
更高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常。这种做法称为异常转译 (exception translation),如下代码所示:
-
/* Exception Translation */ try { ... /* Use lower-level abstraction to do our bidding */ } catch ( LowerLevelException e ) { throw new HigherLevelException(...); }
-
/** * Returns the element at the specified position in this list. * @throws IndexOutOfBoundsException if the index is out of range * ({@code index < 0 || index >= size()}). */ public E get( int index ) { ListIterator<E> i = listIterator( index ); try { return(i.next() ); } catch ( NoSuchElementException e ) { throw new IndexOutOfBoundsException( "Index: " + index ); } }
-
-
异常链
-
一种特殊的异常转译形式称为异常链 (exception chaining),如果低层的异常对于调试导致高层异常的问题非常有 帮助,使用异常链就很合适。低层的异常(原因)被传到高层的异常,高层的异常提供访问方法 (Throwable 的 getCause 方法)来获得低层的异常:
-
// Exception Chaining try { ... // Use lower-level abstraction to do our bidding } catch (LowerLevelException cause) { throw new HigherLevelException(cause); } /* Exception with chaining-aware constructor */ class HigherLevelException extends Exception { HigherLevelException( Throwable cause ) { super(cause); } }
-
-
-
如有可能,处理来自低层异常的最好做法是,在调用低层方法之前确保它们会成功执行,从而避免它们抛出异常。有时候,可以在给低层传 递参数之前,检查更高层方法的参数的有效性,从而避免低层方法抛出异常。
-
如果无法阻止来自低层的异常,其次的做法是,让更高层来悄悄地处理这些异常,从而将高层方法的调用者与低 层的问题隔离开来。在这种情况下,可以用某种适当的记录机制(如 java.util.logging) 将异常记录下来。这样有助于 管理员调查问题,同时又将客户端代码和最终用户与问题隔离开来。
-
-
每个方法抛出的异常都需要创建文档
-
始终要单独地声明受检异常, 并且利用 Javadoc 的@ throws 标签, 准确地记录下抛出每个异常的条件
-
示例:
-
/** * If the specified key is not already associated with a value, * attempts to compute its value using the given mapping function * and enters it into this map unless {@code null}. The entire * method invocation is performed atomically, so the function is * applied at most once per key. Some attempted update operations * on this map by other threads may be blocked while computation * is in progress, so the computation should be short and simple, * and must not attempt to update any other mappings of this map. * * @param key key with which the specified value is to be associated * @param mappingFunction the function to compute a value * @return the current (existing or computed) value associated with * the specified key, or null if the computed value is null * @throws NullPointerException if the specified key or mappingFunction * is null * @throws IllegalStateException if the computation detectably * attempts a recursive update to this map that would * otherwise never complete * @throws RuntimeException or Error if the mappingFunction does so, * in which case the mapping is left unestablished */
-
-
-
在细节消息中包含失败一捕获信息
-
当程序由于未被捕获的异常而失败的时’候,系统会自动地打印出该异常的堆栈轨迹。在堆栈轨迹中包含该异常的字符串表示法 (string representation),即它的 toString 方法的调用结果。它通常包含该异常的类名,紧随其后的是细节消息 (detail message)
-
异常类型的 toString 方法应该尽可能多地返回有关失败原因的信息,这一点特别重要。换句话说,异常的字符串表示法应该捕获失败,以便于后续进行分析。
-
为了捕获失败,异常的细节信息应该包含“对该异常有贡献”的所有参数和字段的值。 例如, IndexOutOfBoundsException 异常的细节消息应该包含下界、上界以及没有落在界内的下标值
-
/** * Constructs an IndexOutOfBoundsException. * * @param lowerBound the lowest legal index value * @param upperBound the highest legal index value plus one * @param index the actual index value */ public IndexOutOfBoundsException( int lowerBound, int upperBound, int index ) { // Generate a detail message that captures the failure super(String.format( "Lower bound: %d, Upper bound: %d, Index: %d", lowerBound, upperBound, index ) ); // Save failure information for programmatic access this.lowerBound = lowerBound; this.upperBound = upperBound; this.index = index; }
-
-
-
保持失败原子性
-
一般而言,失败的方法调用应该使对象保持在被调用之前的状态。 具有这种属性的方法被称为具有失败原子性 (failure atomic)
-
在执行操作之前检查参数的有效性
-
public Object pop() { if ( size == 0 ) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; /* Eliminate obsolete reference */ return(result); }
-
-
-
不要忽略异常
-
不要这么做
-
try { ... } catch ( SomeException e ) { } try { ... } catch ( SomeException e ) { e.printStackTrace(); }
-
-
如果选择忽略异常, catch 块中应 该包含一条注释,说明为什么可以这么做,并且变量应该命名为 ignored:
-
-
同步访问共享的可变数据
-
public class StopThread { private static Boolean stopRequested; public static void main(String[] args) throws InterruptedException { Thread backgroundThread = new Thread(() -> { int i = 0; while (!stopRequested) i++; }); backgroundThread.start(); TimeUnit.SECONDS.sleep(1); stopRequested = true; } }
-
你可能期待这个程序运行大约一秒钟左右,之后主线程将 stopRequested 设置为 true ,致使后台线程的循环终 止。但是在我的机器上,这个程序永远不会终止:因为后台线程永远在循环!
-
synchronized
和volatile
的区别
-
-
避免过度同步
-
executor 、task 和 stream 优先于线程
- 线程池的优势(面试重点)
-
相比 wait 和 notify 优先使用并发工具
-
并发集合(标准集合上提供的并发集合)ConcurrentHashMap
- 比如, 应该优先使用 ConcurrentHashMap ,而不是使用 Collections.synchronizedMap
-
同步器 CountDownLatch Semaphore CyclicBarrier Exchanger
-
-
文档应包含线程安全属性
-
不可变的 — 这个类的实例看起来是常量。不需要外部同步。示例包括 String、Long 和 BigInteger(详见第 17 条)。
-
无条件线程安全 — 该类的实例是可变的,但是该类具有足够的内部同步,因此无需任何外部同步即可并发地使 用该类的实例。例如 AtomicLong 和 ConcurrentHashMap。
-
有条件的线程安全 — 与无条件线程安全类似,只是有些方法需要外部同步才能安全并发使用。示例包括 Collections.synchronized 包装器返回的集合,其迭代器需要外部同步。
-
非线程安全 — 该类的实例是可变的。要并发地使用它们,客户端必须使用外部同步来包围每个方法调用(或调 用序列)。这样的例子包括通用的集合实现,例如 ArrayList 和 HashMap。
-
线程对立 — 即使每个方法调用都被外部同步包围,该类对于并发使用也是不安全的。线程对立通常是由于在不 同步的情况下修改静态数据而导致的。没有人故意编写线程对立类;此类通常是由于没有考虑并发性而导致 的。当发现类或方法与线程不相容时,通常将其修复或弃用。第 78 条中的 generateSerialNumber 方法在没有内 部同步的情况下是线程对立的
-
-
明智审慎的使用延迟初始化
-
不要依赖线程调度器
- 任何依赖线程调度器(Thread.yield)来保证正确性或性能的程序都可能是不可移植的。
-
优先选择 Java 序列化的替代方案
-
非常谨慎地实现 Serializable
-
一旦类的实现被发布,它就会降低更改该类实现的灵活性
-
增加了出现 bug 和安全漏洞的可能性
-
为继承而设计的类(详见第 19 条)很少情况适合实现 Serializable 接口,接口也很少情况适合扩展它
-
内部类(详见第 24 条)不应该实现 Serializable。
- 它们使用编译器生成的合成字段存储对外围实例的引用,并 存储来自外围的局部变量的值。这些字段与类定义的对应关系,就和没有指定匿名类和局部类的名称一样。因此,内 部类的默认序列化形式是不确定的。但是,静态成员类可以实现 Serializable 接口。
-
-
考虑使用自定义的序列化形式
-
保护性的编写 readObject 方法
-
反序列化过程中,修改字节流后执行反序列化
-
在readObject中检查 private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); // Check that our invariants are satisfied if (start.compareTo(end) > 0) throw new InvalidObjectException(start +" after "+ end); }
-
-
对于实例控制,枚举类型优于 readResolve
-
序列化是一种独立构造器的创建对象机制(序列化是一种以byte[]为参数的隐形构造方法)
-
经过序列化和反序列化,不是同一个对象
-
public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis() { ... } public void leaveTheBuilding() { ... } }
-
-
readResolve 特性允许你用 创建的实例代替另外一个实例[Serialization, 3.7]。对于一个正在被 反序列化的对象,如果它的类定义了一个 方法,并且具备正确的声明,那么在反序列化之后,新建 对象上的 readResolve 方法就会被调用。然后,该方法返回的对象引用将会被回收,取代新建的对象。这个特性 在绝大多数用法中,指向新建对象的引用不会再被保留,因此成为垃圾回收的对象。
-
// readResolve for instance control - you can do better! private Object readResolve() { // Return the one true Elvis and let the garbage collector // take care of the Elvis impersonator. return INSTANCE; }
-
-
引用对象必须加上transient
-
-
考虑用序列化代理代替序列化实例
-
序列化代理模式:降低序列化安全问题
-
参考EnumSet
-
import java.io.*; class User implements Serializable { private String username; private transient String password; // 不希望序列化的字段 public User(String username, String password) { this.username = username; this.password = password; } private Object writeReplace() throws ObjectStreamException { return new UserProxy(username); } private static class UserProxy implements Serializable { private String username; public UserProxy(String username) { this.username = username; } private Object readResolve() throws ObjectStreamException { // 根据用户名查找用户对象,这里简化为直接创建 return new User(username, "*****"); } } }
-
-
缺点:序列化代理模式会格外消耗性能
-