第二章 创建和销毁对象

1、用静态工厂方法替代构造器

它只是一个返回类实例的静态方法,不同于设计模式的工厂模式。

优点

1.静态工厂方法有名称,易读、易用

一个类只能有一个带有指定签名的构造器,由于静态工厂方法有名称,所以它们不受上述限制。

public static Boolean valueOf(boolean b) {return b? Boolean.TRUE : Boolean.FALSE;
}

2.可以灵活控制新对象的创建, 如单子模式、享元模式。

方法签名方法的名字和参数列表,方法签名不包括方法的返回类型

         享元模式:(Flyweight Pattern)运用共享技术有效的支持大量细粒度的对象。

3.可以返回原返回类型的任何子类型的对象。

4.所返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值。

5.方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在。

缺点:

1. 类如果不含公有的或者受保护的构造器,就不能被子类化。

2.程序员很难发现它们,在API 文档中,它们没有像构造器那样在API 文档中明确标识出来, 因此对于提供了静态工厂方法而不是构造器的类来说,要想查明如何实例化一个类是非常困难的。

2条:遇到多个构造器参数时要考虑使用构建器

静态工厂和构造器有个共同的局限性,不能很好地扩展到大量的可选参数

1. 重叠构造器( telescoping cons tructor )模式,在这种模式下,提供的第一个构造器只有必要的参数,第二个构造器有一个可选参数,第三个构造器有两个可选参数,依此类推,最后一个构造器包含所有可选的参数。

但是当有许多参数的时候,客户端代码会很难缩写,并且仍然较难以阅读

2.JavaBeans 模式,在这种模式下,先调用一个无参构造器来创建对象,然后再调用setter 方法来设置每个必要的参数,以及每个相关的可选参数。

属性不可控

3.建造者模式。它不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个buil der 对象。然后客户端在bui lder 对象上调用类似于setter 的方法,来设置每个相关的可选参数。最后客户端调用无参的build 方法来生成通常是不可变的对象。

如果类的构造器或者静态工厂中具有多个参数,设计这种类时, Builder模式就是一种不错的选择, 特别是当大多数参数都是可选或者类型相同的时候。与使用重叠构造器模式相比,使用Bui lder 模式的客户端代码将更易于阅读和编写,构建器也比JavaBeans 更加安全。

3 条:用私有构造器或者枚举类型强化Singleton 属性

静态实例属性、静态实例方法、枚举

4 条:通过私有构造器强化不可实例化的能力

public class UtilityClass {// Suppress default constructor for noninstantiabilityprivate UtilityClass() {  //副作用,它使得该类不能被子类化。throw new AssertionError();//避免内部调用构造器} ... // Remainder omitted
}

5 条:优先考虑依赖注人来引用资源

依赖注入( dependency injection )的一种形式:词典( dictionary )是拼写检查器的一个依赖( depend ency ),在创建拼写检查器时就将词典注入( injected )其中

// Dependency injection provides flexibility and testability
public class SpellChecker {private final Lexicon dictionary;public SpellChecker(Lexicon dictionary) {this.dictionary = Objects.requireNonNull(dictionary);}public boolean isValid(String word) { ... }public List<String> suggestions(String typo) { ... }}

静态工具类和Singleton 类不适合于需要引用底层资源的类

6 条:避免创建不必要的对象

通常优先使用静态工厂方法而不是构造器,以避免创建不必要的对象。

如:

1.静态工厂方法Boolean. valueOf (String )几乎总是优先于构造器Boolean(String )

注意, 构造器Boolean(String )在Java 9 中已经被废弃了。

2. String s = new String("bikini"); // DON'T DO THIS!

改进后版本: String s = "bikini";

3. // Performance can be greatly improved!

static boolean isRomanNumeral(String s) {return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}

改进后版本

// Reusing expensive object for improved performance
public class RomanNumerals {private static final Pattern ROMAN = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");static boolean isRomanNumeral(String s) {return ROMAN.matcher(s).matches();}
}

注意,与本条目对应的是第50 条中有关“保护性拷贝”( defensive copying )的内容。在提倡使用保护性拷贝的时候,因重用对象而付出的代价要远远大于因创建重复对象而付出的代价。必要时如果没能实施保护性拷贝,将会导致潜在的Bug 和安全漏洞;而不必要地创建对象则只会影响程序的风格和性能。

7 条:消除过期的对象引用

1、程序中过期引用,清除过期引用:  elements[size] = null; // Eliminate obsolete reference

2、缓存的生命周期,长期需定期清理

3、监听器和回调:确保回调立即被当作垃圾回收的最佳方法是只保存它们的弱引用( weakreference )

8 条:避免使用终结方法和清除方法

1、终结方法(Finalizer):如果忽略在终结过程中被抛出来的未被捕获的异常,该对象的终结过程也会终止。正常情况下,未被捕获的异常将会使线程终止,并打印出战轨迹( Stack Trace ),但是,如果异常发生在终结方法之中,则不会如此,甚至连警告都不会打印出来。清除方法没有这个问题,因为使用清除方法的一个类库在控制它的线程。

java中添加 清除方法(cleaner),清除方法没有终结方法那么危险,但仍然是不可预测、运行缓慢,一般情况下也是不必要的。

2、建议用try-with-resources关闭销毁不用的对象,

9 条: try-with-resources 优先于try-finally

1、try-with-resources java7引入,要使用这个构造的资源,必须先实现AutoCloseable 接口其中包含了单个返回void 的close 方法

// try-finally is ugly when used with more than one resource!
static void copy(String src, String dst) throws IOException {InputStream in = new FileInputStream(src);try {OutputStream out = new FileOutputStream(dst);try {byte[] buf = new byte[BUFFER_SIZE];int n;while ((n = in.read(buf)) >= 0)out.write(buf, 0, n);} finally {out.close();}} finally {in.close();}
}// try-with-resources on multiple resources - short and sweet
static void copy(String src, String dst) throws IOException {try (InputStream in = new FileInputStream(src);OutputStream out = new FileOutputStream(dst)) {byte[] buf = new byte[BUFFER_SIZE];int n;while ((n = in.read(buf)) >= 0)out.write(buf, 0, n);}
}

在处理必须关闭的资源时,始终要优先考虑用try- with-resources ,而不是用try-finally 。代码简洁且不会摸出重要异常

第三章 对于所有对象都通用的方法

10 条:覆盖equals 时请遵守通用约定

覆盖equals,通常应用于值类,如String、Integer

枚举不需要覆盖equals,对于这种类,逻辑相同 和 对象等同是一回事。

equals 方法实现了等价关系( equi va lence relation ),其属性如下:

自反性( reflexive ) : 对于任何非null 的引用值x x . equals(x )必须返回true 。

对称性( symmetric ):对于任何非null 的引用值x 和y ,当且仅当y.equals(x )返回true 时', x.equals(y )必须返回true 。

传递性( transitive ) : 对于任何非null 的引用值x 、y 和z ,如果x.equals(y )返回true ,并且y.equals(z )也返回true ,那么x.equals(z )也必须返回true

一致性( consistent ) : 对于任何非nu ll 的引用值x 和y ,只要equals 的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y )就会一致地返回true,或者一致地返回false 。

对于任何非null 的引用值x, x.equals (null )必须返回false 。

11 条:覆盖equals 时总要覆盖hashCode

在每个覆盖了equals 方法的类中, 都必须覆盖hashCode 方法。如果不这样做的话,就会违反hashCode 的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作。下面是约定的内容,摘自Object 规范:

  • 在应用程序的执行期间,只要对象的equals 方法的比较操作所用到的信息没有被修改,那么对同一个对象的多次调用, hashCode 方法都必须始终返回同一个值。在一个应用程序与另一个程序的执行过程中,执行hashCode 方法所返回的值可以不一致。
  • 如果两个对象根据equals(Object )方法比较是相等的,那么调用这两个对象中的hashCode 方法都必须产生同样的整数结果。
  • 如果两个对象根据equals(Object )方法比较是不相等的,那么调用这两个对象中的hashCode 方法,则不一定要求hashCode 方法必须产生不同的结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表( hashtable )的性能。

计算hash值采用31 有个很好的特性,用移位和减法来代替乘法,可以得到更好的性能:

31 *i = ( i < < 5 ) - i 。

31 = 11111,  31*2-2 = 1000000 -10=111110=62

现代的虚拟机可以自动完成这种优化。

hashCode案例:

1、// Typical hashCode method

@Override public int hashCode() {int result = Short.hashCode(areaCode);result = 31 * result + Short.hashCode(prefix);result = 31 * result + Short.hashCode(lineNum);return result;
}

2、Objects 类有一个静态方法,它带有任意数量的对象,并为它们返回一个散列码。性能比自己的写的方法慢。

// One-line hashCode method - mediocre performance@Override public int hashCode() {return Objects.hash(lineNum, prefix, areaCode);
}

3、如果一个类是不可变的,并且计算散列码的开销也比较大, 就应该考虑把散列码缓存在对象内部,而不是每次请求的时候都重新计算散列码。Jaa 类库中许多类,比如String 和Integer ,都可以把它们的hashCode 方法返回的确切

// hashCode method with lazily initialized cached hash codeprivate int hashCode; // Automatically initialized to 0@Override public int hashCode() {int result = hashCode;if (result == 0) {result = Short.hashCode(areaCode);    result = 31 * result + Short.hashCode(prefix);result = 31 * result + Short.hashCode(lineNum);hashCode = result;} return result;
}

        不要试图从散列码计算中排除掉一个对象的关键域来提高性能。

12 条:始终要覆盖toString

提供好的t。String 实现可以便类用起来更加舒适,使用了这个类的系统也更易于调试。

在实际应用中, toString 方法应该返回对象中包含的所有值得关注的信息;可以指定具体类的格式,如电话号码,指定格式的好处是,它可以被用作一种标准的、明确的、适合人阅读的对象表示法,通常最好再提供一个相匹配的静态工厂或者构造器,以便程序员可以很容易地在对象及其字符串表示法之间来回转换。指定toString 返回值的格式也有不足之处:如果这个类已经被广泛使用,就必须始终如一地坚持这种格式。如果不指定格式,就可以保留灵活性,便于在将来的发行版本中增加信息,或者改进格式。

无论是否决定指定格式,都应该在文档中明确地表明你的意图。

13 条: 谨慎地覆盖clone

Cloneable决定了Object中受保护的clone 方法实现的行为:如果一个类实现了Cloneable, Object 的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportedException 异常。事实上,实现Cloneable 接口的类是为了提供一个功能适当的公有的clone 方法。

Java 支持协变返回类型( covariant return type ) 。换句话说,目前覆盖方法的返回类型可以是被覆盖方法的返回类型的子类了

Entry 类中的深度拷贝方法递归地调用它自身,以便拷贝整个链表(它是链表的头节点) 。虽然这种方法很灵活,如果散列桶不是很长, 也会工作得很好,但是,这样克隆一个链表并不是一种好办法,因为针对列表中的每个元素,它都要消耗一段枝空间。如果链表比较长,这很容易导致枝溢出。为了避免发生这种情况,可以在deepCopy 方法中用迭代( iteration )代替递归( recursion ) :

// Recursively copy the linked list headed by this Entry
Entry deepCopy() {return new Entry(key, value,next == null ? null : next.deepCopy());
}// Iteratively copy the linked list headed by this Entry
Entry deepCopy() {Entry result = new Entry(key, value, next);for (Entry p = result; p.next != null; p = p.next)p.next = new Entry(p.next.key, p.next.value, p.next.next);return result;
}

对象拷贝的更好的办法是提供一个拷贝构造器( copy constructor)或拷贝工厂( copy factory ); 最好利用clone 方法复制数组。

14 条:考虑实现Comparable 接口

在下面的代码中,依赖于 String 类实现了 Comparable 接口,去除命令行参数输入重复的字符串,并按照字母顺序排序, TreeSet中元素实现了 Comparable接口,根据comparaTo方法返回值进行排序

public class WordList {public static void main(String[] args) {Set<String> s = new TreeSet<>();Collections.addAll(s, args); System.out.println(s);}
}

compareTo 方法的通用约定与equals 方法的约定相似:

将这个对象与指定的对象进行比较。当该对象小于、等于或大于指定对象的时候,分别返回一个负整数、零或者正整数。如果由于指定对象的类型而无法与该对象进行比较,则抛出ClassCastException 异常。

如果你想为一个实现了Comparable 接口的类增加值组件,请不要扩展这个类;而是要编写一个不相关的类,其中包含第一个类的一个实例。然后提供一个“视图”( view )方法返回这个实例。这样既可以让你自由地在第二个类上实现compare To 方法,同时也允许它的客户端在必要的时候,把第二个类的实例视同第一个类的实例

        集合接口(Collection, Set Map )的通用约定是按照equals方法来定义的,但是有序集合使用了compareTo 方法而不是equals 方法

// Comparable with comparator construction methodsprivate static final Comparator<PhoneNumber> COMPARATOR =comparingInt((PhoneNumber pn) -> pn.areaCode).thenComparingInt(pn -> pn.prefix).thenComparingInt(pn -> pn.lineNum);public int compareTo(PhoneNumber pn) {return COMPARATOR.compare(this, pn);
}

34 条: 用enum 代替int 常量

// Enum type that switches on its own value - questionablepublic enum Operation {PLUS, MINUS, TIMES, DIVIDE;// Do the arithmetic operation represented by this constantpublic double apply(double x, double y) {switch(this) {case PLUS: return x + y;case MINUS: return x - y;case TIMES: return x * y;case DIVIDE: return x / y;}throw new AssertionError("Unknown op: "+this);}
}

有一种更好的方法可以将不同的行为与每个枚举常量关联起来:在枚举类型中声明一个抽象的appl y 方法,并在特定于常量的类主体( constant-specific class body )中,用具体的方法覆盖每个常量的抽象apply 方法。这种方法被称作特定于常量的方法实现( constant-specific method implementation ),特定于常量的方法实现可以与特定于常量的数据结合起来。 :

枚举类型中的抽象方法必须被它的所有常量中的具体方法所覆盖。


// Enum type with constant-specific class bodies and datapublic enum Operation {PLUS("+") {public double apply(double x, double y) { return x + y; }},MINUS("-") {public double apply(double x, double y) { return x - y; }},TIMES("*") {public double apply(double x, double y) { return x * y; }},DIVIDE("/") {public double apply(double x, double y) { return x / y; }};private final String symbol;Operation(String symbol) { this.symbol = symbol; }@Override public String toString() { return symbol; }public abstract double apply(double x, double y);}

每当需要一组固定常量.并且在编译时就知道其成员的时候,就应该使用枚举

42 条: Lambda 优先于匿名类

在Java 8 中,增加了函数接口( functional interface )、Lambda 和方法引用( methodreference ),使得创建函数对象(且mction object )变得很容易。与此同时,还增加了StreamApi,为处理数据元素的序列提供了类库级别的支持。

自从1997 年发布JDK 1.1 以来,创建函数对象的主要方式是通过匿名类。下面是一个按照字符串的长度对字符串列表进行排序的代码片段,它用一个匿名类创建了排序的比较函数(加强排列顺序):

函数对象:用带有单个抽象方法的接口(或者几乎不用的抽象类)作为函数类型( function type ,表示函数或者要采取的动作

// Anonymous class instance as a function object - obsolete!
Collections.sort(words, new Comparator<String>() {public int compare(String s1, String s2) {return Integer.compare(s1.length(), s2.length());}
});

在Java 8 中,允许利用Lambda 表达式( Lambda expression ,简称Lambda )创建这些接口的实例。Lambda 类似于匿名类的函数,但是比它简洁得多。以下是上述代码用Lambda 代替匿名类之后的样子。

// Lambda expression as function object (replaces anonymous class)
Collections.sort(words,  (s1, s2) -> Integer.compare(s1.length(), s2.length()));

注意, Lambda 的类型( Comparator<String >)、其参数的类型( s 1 和s2 ,两个都是String )及其返回值的类型( int ),都没有出现在代码中。编译器利用一个称作类型推导( type inference )的过程,根据上下文推断出这些类型。

Lambda 限于函数接口。如果想创建抽象类的实例,可以用匿名类来完成,而不是用Lambda。

Lambda 无法获得对自身的引用。在Lambda 中,关键字this 是指外围实例,这个通常正是你想要的。在匿名类中,关键字this 是指匿名类实例。

Lambda 没有名称和文档;如果一个计算本身不是自描述的, 或者超出了几行, 那就不要把它放在一个Lambda 中。对于Lambda而言,一行是最理想的, 三行是合理的最大极限。如果违背了这个规则,可能对程序的可读性造成严重的危害。

43 条:方法引用优先于Lambda

与匿名类相比, Lambda 的主要优势在于更加简洁。Java 提供了生成比Lambda 更简洁函数对象的方法: 方法引用( mothod reference ) 。

map.merge(key, 1, (count, incr) -> count + incr);

这行代码中使用了merge 方法,这是Java 8 版本在Map 接口中添加的。如果指定的键没有映射, 该方法就会插入指定值;如果有映射存在, merge 方法就会将指定的函数应用到当前值和指定值上,并用结果覆盖当前值。这行代码代表了merge 方法的典型用例。

从Java 8 开始, Integer(以及所有其他的数字化基本包装类型)提供了一个名为sum 的静态方法,它的作用也同样是求和。我们只要传人一个对该方法的引用,就可以更轻松地得到相同的结果:

map.merge(key ,1, Integer:: sum );

使用方法引用通常能够得到更加简短、清晰的代码。如果Lambda 太长,或者过于复杂,还有另一种选择: 从Lambda 中提取代码,放到一个新的方法中,并用该方法的一个引用代替Lambda。

只要方法引用更加简洁、清晰,就用方法引用;如果方法引用并不简洁,就坚持使用Lambda 。

service.execute(GoshThisClassNameIsHumongous::action);
service.execute(() -> action());

44 条:坚持使用标准的函数接口

只要标准的函数接口能够满足需求,通常应该优先考虑,而不是专门再构建一个新的函数接口。

java.util.Function 中共有43 个接口。别指望能够全部记住它们,但是如果能记住其中6 个基础接口,必要时就可以推断出其余接口了。基础接口作用于对象引用类型。Operator 接口代表其结果与参数类型一致的函数。Predicate接口代表带有一个参数并返回一个boolean 的函数。Function 接口代表其参数与返回的类型不一致的函数。Supplier 接口代表没有参数并且返回(或“提供”)一个值的函数。最后, Consumer 代表的是带有一个函数但不返回任何值的函数,相当于消费掉了其参数。这6 个基础函数接口概述如下:

UnaryOperator和BinaryOperator 分别对应单元算子和二元算子。

Interface

 Function Signature

 Example

UnaryOperator<T>

T apply(T t)

String::toLowerCase

BinaryOperator<T>

T apply(T t1, T t2)

BigInteger::add

Predicate<T>

boolean test(T t)

Collection::isEmpty

Function<T,R>

R apply(T t)

Arrays::asList

Supplier<T>

T get()

Instant::now

Consumer<T>

void accept(T t)

System.out::println

千万不要用带包装类型的基础函数接口来代替基本函数接口,基本类型优于装箱基本类型

如果你所需要的函数接口与Comparator 一样具有一项或者多项以下特征, 则必须认真考虑自己编写专用的函数接口,而不是使用标准的函数接口:

  • 通用,并且将受益于描述性的名称。
  • 具有与其关联的严格的契约。
  • 将受益于定制的缺省方法。

缺省方法:其允许在接口中增加新的方法,并在接口实现中可用,与正常方法不同,在方法声明之前加上default关键字,同时提供实现,因此无需修改实现类。

函数接口用@ FunctionalInterface 注解,它是是针对lambda设计的,他的接口只有一个抽象方法。

最后,不要在相同的参数位置,提供不同的函数接口来进行多次重载,这样可能在客户端导致歧义。

总而言之,既然Java 有了Lambda ,就必须时刻谨记用Lambda 来设计API 。输入时接受函数接口类型,并在输出时返回之。一般来说,最好使用j ava.util.function.Function 中提供的标准接口,但是必须警惕在相对罕见的几种情况下,最好还是自己编写专用的函数接口。

45 条:谨慎使用Stream

在Java 8 中增加了Stream API ,简化了串行或并行的大批量操作。这个API 提供了两个关键抽象: Stream (流)代表数据元素有限或无限的顺序, Stream pipeline (流管道)则代表这些元素的一个多级计算。Stream 中的数据元素可以是对象引用,或者基本类型值。它支持三种基本类型:int、long 和double 。

一个Stream pipeline 中包含一个源Stream ,接着是0 个或者多个中间操作( intermediat巳operation )和一个终止操作( terminal operation ) 。每个中间操作都会通过某种方式对Stream进行转换,例如将每个元素映射到该元素的函数,或者过滤掉不满足某些条件的所有元素。所有的中间操作都是将一个Stream 转换成另一个Stream ,其元素类型可能与输入的Stream一样,也可能不同。终止操作会在最后一个中间操作产生的Stream 上执行一个最终的计算。例如将其元素保存到一个集合中,并返回某一个元素,或者打印出所有元素等。

Stream pipeline 通常是lazy 的:直到调用终止操作时才会开始计算,对于完成终止操作不需要的数据元素,将永远都不会被计算。

在默认情况下, Stream pipeline 是按顺序运行的。要使pipelin巳并发执行,只需在该pipeline 的任何Stream 上调用parallel 方法即可,但是通常不建议这么做

// Prints all large anagram groups in a dictionary iterativelypublic class Anagrams {public static void main(String[] args) throws IOException {File dictionary = new File(args[0]);int minGroupSize = Integer.parseInt(args[1]);Map<String, Set<String>> groups = new HashMap<>();try (Scanner s = new Scanner(dictionary)) {while (s.hasNext()) {String word = s.next();groups.computeIfAbsent(alphabetize(word), (unused) -> new TreeSet<>()).add(word);}}for (Set<String> group : groups.values()) {if (group.size() >= minGroupSize)System.out.println(group.size() + ": " + group);}}private static String alphabetize(String s) {char[] a = s.toCharArray();Arrays.sort(a);return new String(a);}
}

这里使用了Java 8 中新增的computeIfAbsent 方法。这个方法会在映射中查找一个键:如果这个键存在,该方法只会返回与之关联的值。如果键不存在,该方法就会对该键运用指定的函数对象算出一个值,将这个值与键关联起来,并返回计算得到的值。computeIfAbsent方法简化了将多个值与每个键关联起来的映射实现。

// Tasteful use of streams enhances clarity and concisenesspublic class Anagrams {public static void main(String[] args) throws IOException {Path dictionary = Paths.get(args[0]);int minGroupSize = Integer.parseInt(args[1]);try (Stream<String> words = Files.lines(dictionary)) {words.collect(groupingBy(word -> alphabetize(word))).values().stream().filter(group -> group.size() >= minGroupSize).forEach(g -> System.out.println(g.size() + ": " + g));}} // alphabetize method is the same as in original version
}

即使你之前没怎么接触过Stream ,这段程序也不难理解。它在try-with-resources 块中打开词典文件,获得一个包含了文件中所有代码的Stream 。Stream 变量命名为words,是建议S tream 中的每个元素均为单词。这个Stream 中的pipe line 没有中间操作;它的终止操作将所有的单词集合到一个映射中,按照它们的字母排序形式对单词进行分组(详见第46 条) 。这个映射与前面两个版本中的是完全相同的。随后,在映射的values ()视图中打开了一个新Stream<List<String >> 。当然,这个Stream 中的元素都是换位词分组。Stream 进行了过滤,把所有分组大小小于minGroupSize 的单词都去掉了,最后,通过终止操作的forEach 打印出剩下的分组。

在没有显式类型的情况下,仔细命名Lambda参数, 这对于Stream pipeline的可读性至关重要。

Java 不支持基本类型的char Stream,最好避免利用Stream 来处理char值。

如本条目中的范例程序所示, Stream pipeline 利用函数对象(一般是Lambda 或者方法引用)来描述重复的计算,而迭代版代码则利用代码块来描述重复的计算。

下列工作只能通过代码块,而不能通过函数对象来完成:

  • 从代码块中,可以读取或者修改范围内的任意局部变量;从Lambda 则只能读取final 或者有效的final 变量[ JLS 4.12.4 ],并且不能修改任何local 变量。
  • 从代码块中,可以从外国方法中return 、break 或continue 外围循环,或者抛出该方法声明要抛出的任何受检异常;从Lambda 中则完全无法完成这些事情。

stream可以使得完成下列这些工作变得易如反掌:

  • 统一转换元素的序列
  • 过滤元素的序列
  • 利用单个操作(如添加、连接或者计算其最小值)合并元素的顺序
  • 将元素的序列存放到一个集合中,比如根据某些公共属性进行分组
  • 搜索满足某些条件的元素的序列

总之,Stream 和 迭代代码块选择合适的用,不知选哪个时,可以两个都试试。

46 条:优先选择Stream 中无副作用的函数

例:构建一张表格,显示这些单词在一个文本文件中出现的频率:

// Uses the streams API but not the paradigm--Don't do this!Map<String, Long> freq = new HashMap<>();try (Stream<String> words = new Scanner(file).tokens()) {words.forEach(word -> {freq.merge(word.toLowerCase(), 1L, Long::sum);});}

使用了Stream 、Lambda 和方法引用,并且得出了正确的答案。简而言之,这根本不是Stream 代码;只不过是伪装成Stream 代码的迭代式代码。这段代码利用一个改变外部状态(频率表)的Lambda ,完成了在终止操作的forEach 中的所有工作。forEach 操作的任务不只展示由Stream 执行的计算结果,这在代码中并非好事,改变状态的Lambda 也是如此。

// Proper use of streams to initialize a frequency tableMap<String, Long> freq;try (Stream<String> words = new Scanner(file).tokens()) {freq = words.collect(groupingBy(String::toLowerCase, counting()));
}

这个代码片段的作用与前一个例子一样,只是正确使用了Stream API ,变得更加简洁、清晰。

Stream终止操作forEach 应该只用于报告St ream 计算的结果,而不是执行计算。有时候,也可以将forEach 用于其他目的,比如将Stream 计算的结果添加到之前已经存在的集合中去。

将Stream 的元素集中到一个真正的Collection 里去的收集器比较简单。有三个这样的收集器: toList ()、toSet ()和toCollection(collectionFactory ) 。它们分别返回一个列表、一个集合和程序员指定的集合类型。

总而言之,编写Stream pipeline 的本质是无副作用的函数对象。这适用于传入Stream及相关对象的所有函数对象。终止操作中的forEach 应该只用来报告由Stream 执行的计算结果,而不是让它执行计算。为了正确地使用Stream ,必须了解收集器。最重要的收集器工厂是toList 、toSet 、toMap 、groupingBy 和joining 。

Effective java 3th-读书笔记相关推荐

  1. 《Effective java》—–读书笔记

    2015年进步很小,看的书也不是很多,感觉自己都要废了,2016是沉淀的一年,在这一年中要不断学习.看书,努力提升自己!预计在2016年要看12本书,主要涉及java基础.Spring研究.java并 ...

  2. 《Effective Java》读书笔记 Item 1:考虑静态工厂方法,而不是构造器

    众所周知,要想能获取一个类的实例,该类得要提供一个public的构造器.但是<Effective Java>书中说还有一个方法,那就是提供静态工厂方法(static factory met ...

  3. 《Effective Java》读书笔记 - 11.序列化

    Chapter 11 Serialization Item 74: Implement Serializable judiciously 让一个类的实例可以被序列化不仅仅是在类的声明中加上" ...

  4. 《Effective Java》读书笔记--创建和销毁对象

    2019独角兽企业重金招聘Python工程师标准>>> 考虑用静态工厂方法代替构造函数. 当我们在写一个工具类时,是不希望用户将该类实例化的,所以应该定义一个private的构造函数 ...

  5. 《Effective Java》 读书笔记(持续更新)

    2.1 用静态工厂方法代替构造器 静态工厂方法: 不通过 new (如:Date date = new Date();) 而是用一个静态方法来对外提供自身实例的方法叫做静态工厂方法(Static fa ...

  6. 《Effective Java》读书笔记八(异常)

    No57 只针对异常的情况才使用异常 异常应该只用于异常的情况下,它们永远不应该用于正常的控制流. No58 对可恢复的情况使用受检异常,对编程错误使用运行时异常 Java程序设计语言提供了三种可抛出 ...

  7. 《Effective Java》读书笔记 - 5.泛型

    Chapter 5 Generics Item 23: Don't use raw types in new code 虽然你可以把一个List<String>传给一个List类型(raw ...

  8. 《Effective Java》读书笔记五(枚举和注解)

    No30 用enum代替int常量 一:综述 int枚举模式,示范: // The int enum pattern - severely deficient! public static final ...

  9. 《Effective Java》读书笔记

    引言 1 代码应该被重用 ,而不是被拷贝. 2 错误应该尽早被检测出来,最好是在编译时刻. 3 接口.类.数组是引用类型(对象), 基本类型不是 第二章 创建和销毁对象 1 考虑用静态工厂方法代替构造 ...

  10. 《Effective Java》读书笔记,flutter游戏开发

    1 代码应该被重用 ,而不是被拷贝. 2 错误应该尽早被检测出来,最好是在编译时刻. 3 接口.类.数组是引用类型(对象), 基本类型不是 第二章 创建和销毁对象 1 考虑用静态工厂方法代替构造器. ...

最新文章

  1. springcloud上传文件_Spring Cloud实战:服务链路追踪Spring Cloud Sleuth
  2. Xamarin.FormsShell基础教程(4)Shell项目内容列表页面运行效果
  3. 多人合作开发的标准制定
  4. MySQL之长连接、短连接、连接池(转载:http://www.ywnds.com/?p=9801)
  5. [网络安全自学篇] 三十六.WinRAR安全缺陷复现(CVE-2018-20250)及软件自启动劫持机理
  6. Maven入门极简使用教程
  7. python嵌套列表字典_python中嵌套列表转为字典
  8. Java日期型集合排序
  9. 根据业务情况对数据校验
  10. SpringGateway与Zookeeper的Maven冲突
  11. 一只青蛙一次可以_“七夕青蛙”被玩坏了,哈哈哈哈哈哈哈哈哈哈太好玩了
  12. 项目验收文档模板(一)
  13. 国内AGV机器人厂家数目统计及区域分布分析
  14. Git恢复被删除的文件
  15. 2021年中国危险废物产量、处理量及回收利用量分析[图]
  16. php和mysql web开发 目录_PHP和MySQL Web开发(原书第5版)简介,目录书摘
  17. 保险精算--第8周作业
  18. android p蓝色壁纸,iPhone和Android的最佳蓝色系壁纸分享
  19. Ajax请求URL的写法
  20. android开机动画不播放,android 设置activity启动退出动画 | 解决设置activity 动画不生效问题...

热门文章

  1. 如何修改HOST文件 映射
  2. mysql计算时间差(时/分/秒)函数
  3. 微信小程序云开发-树洞小程序Treehole(介绍)
  4. 高效工作的法宝推荐,小小便签助你快人一步
  5. 【数学相关知识-概率和分布】
  6. 试卷分析的四个度:难度、区分度、信度、效度
  7. 文件下载协议 HTTP、FTP、P2P
  8. 电子工程师自学成才pdf_作为一名自学成才的软件工程师,我在第一个月的工作中所学到的知识
  9. 2019年9月TIOBE语言排行榜:“世界上最好的语言”正在努力保持前十的位置
  10. 背包问题的多项式时间近似解