<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Gaoge&#39;s island</title>
    <link>http://ggao.me/index.xml</link>
    <description>Recent content on Gaoge&#39;s island</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh-CN</language>
    <copyright>All rights reserved - 2016</copyright>
    <lastBuildDate>Wed, 28 Dec 2016 16:18:24 +0800</lastBuildDate>
    <atom:link href="http://ggao.me/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Effective Java ReadingNotes 10</title>
      <link>http://ggao.me/post/readingnotes/effective-java-readingnotes10/</link>
      <pubDate>Wed, 28 Dec 2016 16:18:24 +0800</pubDate>
      
      <guid>http://ggao.me/post/readingnotes/effective-java-readingnotes10/</guid>
      <description>&lt;p&gt;Effective Java阅读笔记(10)&lt;/p&gt;

&lt;p&gt;Effective Java第11章 序列化&lt;/p&gt;

&lt;h5&gt;74.谨慎地实现Serializable接口&lt;/h5&gt;

&lt;p&gt;实现&lt;code&gt;Serializable&lt;/code&gt;接口的代价：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;一旦一个类被发布，就大大降低了“改变这个类的实现”的灵活性。因为这个类的字节流编码成为了它导出的API的一部分，如果采用默认的序列化形式，甚至私有以及包级私有的成员都会被导出。这会迫使程序员维护这种序列形式。同时，这也违反了第&lt;code&gt;13&lt;/code&gt;条的“最低限度访问域”的建议。如果接受了默认的序列化形式，并且以后又要改变这个类的内部表示方法，则可能会导致序列化形式的不兼容，虽然在改变内部表示法的同时仍然维持着原来的序列化形式（使用&lt;code&gt;ObjectoutputStream.putFields&lt;/code&gt;和&lt;code&gt;ObjectInputStream.readFields&lt;/code&gt;）是可能的，但是过程复杂。序列化还会使类的演变收到限制，比如说受到序列版本&lt;code&gt;UID(serial version UID)&lt;/code&gt;的限制。如果不显式指定一个UID，系统会自动生成它，生成的UID受到类的名称、实现的接口的名称、以及所有公有的和受保护的成员的名称的影响。如果后期向这个类中增加或者删减了任何东西，都有可能导致这个自动生成的UID的变化，从而影响到这个类序列化/反序列化是否成功。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;增加了出现Bug和安全漏洞的可能性。反序列化就像是一个隐藏的构造器一样，这个过程中同样需要有像构造器一样建立起该类的各种条件约束，但这在反序列化中容易忘记。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;随着类的版本更新，对实现了Serializable接口的类的测试负担会加重。每一次版本变化都要测试当前版本的类到以前版本的类的序列化/反序列化是否可以兼容。除了二进制兼容，还要语义兼容。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;什么时候实现&lt;code&gt;Serializable&lt;/code&gt;接口：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;一般来说值类应该实现，而代表活动实体的类不应该实现。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;为了继承而设计的类应该尽可能少地实现，但是这个类的子类也许需要实现&lt;code&gt;Serializable&lt;/code&gt;接口。这种情况下需要这个专门为继承而设计的类提供一个无参的构造器。提供无参构造器一般来说是容易的，但是如果这个为了继承而设计的类是有状态的（也就是说不可能无参数），这时提供无参构造器比较复杂。推荐的做法是在无参构造器之外再提供额外的初始化方法和状态检测方法，调用其他方法前要有初始化，每次进入其他方法还要进行状态检测。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;内部类不应该实现序列化，因为它的默认序列化定义不清楚，而静态成员类是可以实现&lt;code&gt;Serializable&lt;/code&gt;接口序列化的。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;75.考虑使用自定义的序列化形式&lt;/h5&gt;

&lt;p&gt;如果一个对象的物理表示法等同于它的逻辑内容，可能就适合于使用默认的序列化形式。&lt;/p&gt;

&lt;p&gt;当一个对象的物理表示法与它的逻辑数据内容有实质性区别的时候，使用默认的序列化形式存在以下缺点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;它使这个类的导出&lt;code&gt;API&lt;/code&gt;永远束缚在该类的内部表示法上。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;它会消耗过多空间。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;它会消耗过多时间。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;它会引起栈溢出（在一个对象的物理表示法使用链表，需要对链表进行遍历的时候）。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;建议：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;使用&lt;code&gt;transient&lt;/code&gt;关键字标记要从默认序列化形式中省略的成员，然后在&lt;code&gt;writeObject&lt;/code&gt;中对它们进行自定义操作，同时编写readObject方法。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;对于实现了自定义序列化形式的类，使用&lt;code&gt;JavaDoc&lt;/code&gt;进行注释，尽管&lt;code&gt;readObject/writeObject&lt;/code&gt;方法是私有的也是如此。这里需要使用标签&lt;code&gt;@serial&lt;/code&gt;和&lt;code&gt;@serialData&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;自定义序列版本&lt;code&gt;UID(serial version UID)&lt;/code&gt;，这样会避免它成为不兼容的原因，同时还会提高性能。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如果自定义序列化形式的类的其他有关状态更改的方法使用了同步，那么在对象序列化上也要同步。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;76.保护性地编写readObject方法&lt;/h5&gt;

&lt;p&gt;对于序列化形成的字节流，并不都是安全的，里面可能有伪造的有害数据，对它们不加分辨地反序列化，可能会导致程序收到损害。伪造的有害数据一方面可以是不正确的字节流 ；另一方面还可能是在正确的字节流中夹带“私货”，通过“私货”可以恶意修改反序列化的对象。&lt;/p&gt;

&lt;p&gt;本条建议：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;在&lt;code&gt;readObject&lt;/code&gt;反序列化之后，检查对象成员的有效性。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;进行保护性拷贝，这里的注意点和第&lt;code&gt;39&lt;/code&gt;条一样：保护性拷贝先于参数有效性检测和避免使用&lt;code&gt;clone&lt;/code&gt;方法（但是保护性拷贝会导致这个类需要保护性拷贝的成员不能为&lt;code&gt;final&lt;/code&gt;）。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;尽管&lt;code&gt;Java1.4&lt;/code&gt;中为了阻止恶意攻击并且节省保护性拷贝的开销，在&lt;code&gt;ObjectOutputStream/ObjectInputStream&lt;/code&gt;中引入了&lt;code&gt;writeObjectUnshared/readObjectUnshared&lt;/code&gt;方法，并且比保护性拷贝更快，但是这些方法可能会受到复杂的攻击，不建议使用。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;readObject&lt;/code&gt;方法和构造器行为类似，所以对构造器的注意事项同样适用于&lt;code&gt;readObject&lt;/code&gt;方法：不要调用可被覆盖的方法。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;编写&lt;code&gt;readObject&lt;/code&gt;方法的建议：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;对于对象引用域必须保持为私有的类，要保护性地拷贝这些域中的每一个对象。不可变类的可变组件就属于这一类。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;对于任何约束条件，如果检查失败，则抛出&lt;code&gt;InvalidObjectException&lt;/code&gt;异常。这些检查动作应该跟在所有的保护性拷贝之后。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如果整个对象图在被反序列化之后必须进行检验，就应该使用&lt;code&gt;ObjectInputValidation&lt;/code&gt;接口。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;无论是直接方式还是间接方式，都不要调用类中任何可能被覆盖的方法。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;77.对于实例控制，枚举类型优先于readResolve&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;readObject&lt;/code&gt;方法实现的难点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;readResolve&lt;/code&gt;的调用是在&lt;code&gt;readObject&lt;/code&gt;之后，&lt;code&gt;readResolve&lt;/code&gt;方法会返回一个对象，取代&lt;code&gt;readObject&lt;/code&gt;反序列化的对象。也就是说存在一种可能，在&lt;code&gt;readResolve&lt;/code&gt;调用之前，&lt;code&gt;readObject&lt;/code&gt;调用之后，有人恶意地得到反序列化的新的对象，取得它的引用，进而破坏单例。因此需要单例的类的所有实例域都是&lt;code&gt;transient&lt;/code&gt;的。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;对于&lt;code&gt;readObject&lt;/code&gt;，它的可访问性值得考虑，私有意味着这个类失去了被子类化的能力；如果它是受保护或者公有的，而这个类的子类没有覆盖&lt;code&gt;readObject&lt;/code&gt;方法，反序列化会产生一个这个类（超类）的实例，可能导致&lt;code&gt;ClassCastException&lt;/code&gt;异常。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;建议使用枚举实现单例（实例控制）。但是，如果一个单例的实例在编译时还不能确定（未实例化），那么是无法使用枚举类型的。&lt;/p&gt;

&lt;h5&gt;78.考虑用序列化代理代替序列化实例&lt;/h5&gt;

&lt;p&gt;序列化代理：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;在需要序列化的类的内部创建一个私有的静态内部类，这个静态内部类同样实现&lt;code&gt;Serializable&lt;/code&gt;接口。静态内部类通过构造函数传入外围类的引用，保留外围类的逻辑状态（比如保留所有数据、约束条件），并且有&lt;code&gt;readObject&lt;/code&gt;方法。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;同样实现了&lt;code&gt;Serializable&lt;/code&gt;接口的外部类需要编写方法&lt;code&gt;writeReplace&lt;/code&gt;，返回一个&lt;code&gt;new&lt;/code&gt;出来的静态内部类（传入了自己的引用）。&lt;code&gt;writeReplace&lt;/code&gt;会在序列化的时候对写入的对象进行替换，替换为这个静态内部类。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;当反序列化的时候，不是调用外围类，而是调用静态内部类的反序列化的方法&lt;code&gt;readResolve&lt;/code&gt;，返回一个使用当初保留的外部类的全部信息构造的外部类。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;使用序列化代理的好处：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;可以像保护性拷贝方法一样阻止伪字节流的攻击以及内部域的盗用攻击。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;允许域为&lt;code&gt;final&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;允许反序列化实例有着与原始序列化实例不同的类。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;使用序列化代理的局限性：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;不能与可被客户扩展的类兼容。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;不能与对象图中包含循环的某些类兼容：因为不能从对象的序列化代理的&lt;code&gt;readResolve&lt;/code&gt;方法中调用这个对象的方法，因为这个对象还不存在。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;可能增加性能开销。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Effective Java ReadingNotes 9</title>
      <link>http://ggao.me/post/readingnotes/effective-java-readingnotes9/</link>
      <pubDate>Wed, 28 Dec 2016 10:18:24 +0800</pubDate>
      
      <guid>http://ggao.me/post/readingnotes/effective-java-readingnotes9/</guid>
      <description>&lt;p&gt;Effective Java阅读笔记(9)&lt;/p&gt;

&lt;p&gt;Effective Java第10章 并发&lt;/p&gt;

&lt;h5&gt;66.同步访问共享的可变数据&lt;/h5&gt;

&lt;p&gt;当多个线程共享可变数据的时候，每个读或者写数据的线程都必须执行同步。&lt;/p&gt;

&lt;h5&gt;67.避免过度同步&lt;/h5&gt;

&lt;p&gt;为了避免死锁和数据破坏，千万不要从同步区域内部调用外来方法，并要尽量限制同步区域内部的工作量。&lt;/p&gt;

&lt;h5&gt;68.executor和task优先于线程&lt;/h5&gt;

&lt;p&gt;现在应当优先使用&lt;code&gt;executor&lt;/code&gt;和&lt;code&gt;task&lt;/code&gt;而不是&lt;code&gt;Thread&lt;/code&gt;，因为在以前&lt;code&gt;Thread&lt;/code&gt;既是工作单元又是执行单元，但是现在二者分离，&lt;code&gt;executor&lt;/code&gt;是执行单元，&lt;code&gt;Runnable&lt;/code&gt;和&lt;code&gt;Callable&lt;/code&gt;是工作单元。&lt;/p&gt;

&lt;p&gt;使用&lt;code&gt;ScheduledThreadPool&lt;/code&gt;代替&lt;code&gt;Timer&lt;/code&gt;作为多线程的定时器。&lt;/p&gt;

&lt;p&gt;对于&lt;code&gt;ThreadPool&lt;/code&gt;：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;使用&lt;code&gt;Executor.newCachedThreadPool&lt;/code&gt;来应对一个轻负载的服务器。&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;Executor.newFixedThreadPool&lt;/code&gt;来应对一个重负载的服务器。&lt;/li&gt;
&lt;li&gt;为了最大限度控制它，直接使用&lt;code&gt;ThreadPoolExecutor&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;69.并发工具优先于wait和notify&lt;/h5&gt;

&lt;p&gt;考虑使用&lt;code&gt;java.util.concurrent&lt;/code&gt;而非&lt;code&gt;wait&lt;/code&gt;和&lt;code&gt;notify&lt;/code&gt;。如果你在维护使用&lt;code&gt;wait&lt;/code&gt;和&lt;code&gt;notify&lt;/code&gt;的代码，务必确保始终是利用标准的模式从&lt;code&gt;while&lt;/code&gt;循环内部调用&lt;code&gt;wait&lt;/code&gt;。一般情况下，你应该优先使用&lt;code&gt;notifyAll&lt;/code&gt;，而不是使用&lt;code&gt;notify&lt;/code&gt;。如果使用&lt;code&gt;notify&lt;/code&gt;，请一定要小心，以确保程序的活性。&lt;/p&gt;

&lt;h5&gt;70.线程安全性的文档化&lt;/h5&gt;

&lt;p&gt;线程安全性的几种级别：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;不可变的&lt;code&gt;immutable&lt;/code&gt;，这个类的实例是不变的，所以不需要外部的同步。例如：&lt;code&gt;String&lt;/code&gt;、&lt;code&gt;Long&lt;/code&gt;和&lt;code&gt;BigInteger&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;无条件的线程安全&lt;code&gt;unconditionally thread-safe&lt;/code&gt;,这个类的实例是可变的，但是这个类有着足够的内部同步，所以它的实例可以被并发使用，无需任何外部同步。例如：&lt;code&gt;Random&lt;/code&gt;和&lt;code&gt;ConcurrentHashMap&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;有条件的线程安全&lt;code&gt;conditionally thread-safe&lt;/code&gt;，除了有些方法为进行安全的并发而使用需要外部同步之外，这种线程安全级别与无条件的线程安全相同。例如：&lt;code&gt;Collections.synchronized&lt;/code&gt;包装返回的集合，它们的迭代器&lt;code&gt;iterator&lt;/code&gt;要求外部同步。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;非线程安全&lt;code&gt;not thread-safe&lt;/code&gt;，这个实例是可变的。为了并发地使用它们，客户必须利用自己选择的外部同步包围每个方法调用（或者调用序列）。例如：&lt;code&gt;ArrayList&lt;/code&gt;和&lt;code&gt;HashMap&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;线程对立的&lt;code&gt;thread-hostie&lt;/code&gt;，这个类不能安全地被多个线程并发使用，即使所有的方法调用都被外部同步包围。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果编写无条件的线程安全类，建议使用私有锁，隐藏内部实现，私有锁尤其适合于专门为继承而设计的类。如果编写有条件的线程安全类，一定要在&lt;code&gt;API&lt;/code&gt;文档中注明哪个方法调用序列需要外部同步，以及执行这些序列需要哪把锁。&lt;/p&gt;

&lt;h5&gt;71.慎用延迟初始化&lt;/h5&gt;

&lt;p&gt;&lt;strong&gt;除非绝对必要，否则就不要这么做。&lt;/strong&gt;如果域只在类的实例部分被访问，并且初始化这个域的开销很高，可能就值得进行延迟初始化。在大多数情况下，正常的初始化要优先于延迟初始化。&lt;/p&gt;

&lt;p&gt;四种延迟初始化的方式：&lt;/p&gt;

&lt;p&gt;1.最普通的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-Java&#34;&gt;private FieldType field;
synchronized Field getField(){
    if(field==null){
        field=computeFieldValue();
    }
    return field;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;2.对静态域的出于性能考虑的初始化方式：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-Java&#34;&gt;private static class FieldHolder{
    static final FieldType field=computeFieldValue();
}
static FieldType getField(){ return FieldHolder.field; }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;3.双重检查模式（出于性能考虑，避免初始化之后的锁定访问的开销）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-Java&#34;&gt;private volatile FieldType field;
Field getField(){
    FieldType result=field;
    if(result==null){
        synchronized(this){
            result=field;
            if(result==null){
                field=result=computeFieldValue();
            }
        }
    }
    return result;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;4.单重检查模式（双重检查模式的变式，可以接受重复初始化）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-Java&#34;&gt;private volatile FieldType field; 
private FieldType getField(){ 
    FieldType result=field; 
    if(result==null){ 
        field=result=computeFieldValue(); 
    } 
    return result; 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;72.不要依赖于线程调度器&lt;/h5&gt;

&lt;p&gt;线程只做有意义的工作，不应该忙等。&lt;/p&gt;

&lt;p&gt;不要依赖&lt;code&gt;Thread.yield&lt;/code&gt;改变优先级，因为它在不同环境下效果不同，而且实现也不同。&lt;strong&gt;对于大多数程序员来说，&lt;code&gt;Thread.yield&lt;/code&gt;的唯一用途是在测试期间人为地增加程序的并发性。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;不要使用调整线程优先级的方法，它不具有可移植性。&lt;/p&gt;

&lt;p&gt;线程优先级可以提高一个已经能够正常工作的程序的服务质量，但永远不应该用来“修正”一个本来不能工作的程序。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;要编写健壮的、相应良好的、可移植的多线程应用程序，最好的办法是确保可运行线程的平均数量不明显多于处理器的数量，这使得线程调度器没有更多的选择：它只需要运行这些可运行的线程，直到它们不再可运行。在等待的线程并不是可运行的。&lt;/strong&gt;&lt;/p&gt;

&lt;h5&gt;73.避免使用线程组&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;ThreadGroup API&lt;/code&gt;已经过时。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Effective Java ReadingNotes 8</title>
      <link>http://ggao.me/post/readingnotes/effective-java-readingnotes8/</link>
      <pubDate>Tue, 27 Dec 2016 23:18:24 +0800</pubDate>
      
      <guid>http://ggao.me/post/readingnotes/effective-java-readingnotes8/</guid>
      <description>&lt;p&gt;Effective Java阅读笔记(8)&lt;/p&gt;

&lt;p&gt;Effective Java第9章 异常&lt;/p&gt;

&lt;h5&gt;57.只针对异常的情况才使用异常&lt;/h5&gt;

&lt;p&gt;&lt;strong&gt;不要将异常用于普通的控制流，也不要编写迫使它们这么做的&lt;code&gt;API&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;

&lt;h5&gt;58.对可恢复的情况使用受检异常，对编程错误使用运行时异常&lt;/h5&gt;&lt;br&gt;

&lt;h5&gt;59.避免不必要地使用受检的异常&lt;/h5&gt;

&lt;p&gt;如果方法只是抛出单个受检的异常，仅仅一个异常就会导致该方法不得不外于&lt;code&gt;try&lt;/code&gt;块中，在这些情况下，应该问自己，是否有别的途径来避免使用受检的异常。一种方法是把这个抛出异常的方法分成两个方法，其中第一个方法返回一个boolean，表明是否应该抛出异常。&lt;/p&gt;

&lt;h5&gt;60.优先使用标准的异常&lt;/h5&gt;

&lt;p&gt;以下为最常见的可重用异常：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;IllegalArgumentException&lt;/code&gt;非&lt;code&gt;null&lt;/code&gt;的参数值不正确；&lt;code&gt;IllegalStateException&lt;/code&gt;对于方法调用而言，对象状态不合适；&lt;code&gt;NullPointerException&lt;/code&gt;在禁止使用&lt;code&gt;null&lt;/code&gt;的情况下参数值为&lt;code&gt;null&lt;/code&gt;；&lt;code&gt;IndexOutOfBoundsException&lt;/code&gt;下标参数值越界；&lt;code&gt;ConcurrentModificationException&lt;/code&gt;在禁止并发修改的情况下，检测到对象的并发修改；&lt;code&gt;UnsupportedOperationException&lt;/code&gt;对象不支持用户请求的方法。&lt;/p&gt;

&lt;h5&gt;61.抛出与抽象相对应的异常&lt;/h5&gt;

&lt;p&gt;更高层的实现应该捕获低层的异常，同时抛出可以按照高层抽象进行解释的异常，这就是异常转译。&lt;/p&gt;

&lt;p&gt;如果不能阻止或者处理来自更低层的异常，一般的做法是使用异常转译，除非低层方法碰巧可以保证它抛出的所有异常对高层也合适才可以将异常从低层传播到高层。异常链对高层和低层异常都提供了最佳的功能：它允许抛出适当的高层异常，同时又能捕获底层的原因进行失败分析。&lt;/p&gt;

&lt;h5&gt;62.每个方法抛出的异常都要有文档&lt;/h5&gt;

&lt;p&gt;始终要单独地声明受检的异常，并且利用&lt;code&gt;Javadoc&lt;/code&gt;的&lt;code&gt;@throw&lt;/code&gt;标记，准确地记录下抛出每个异常的条件，但不要使用&lt;code&gt;throws&lt;/code&gt;关键字将未受检的异常包含在方法的声明中。如果一个类中的许多方法出于同样的原因而抛出同一个异常，在该类的文档注释中对这个异常建立文档，这是可以接受的，而不是为每个方法单独建立文档。&lt;/p&gt;

&lt;h5&gt;63.在细节消息中包含能捕获失败的信息&lt;/h5&gt;

&lt;p&gt;为了捕获失败，异常的细节信息应该包含所有“对该异常有贡献”的参数和域的值。&lt;/p&gt;

&lt;h5&gt;64.努力使失败保持原子性&lt;/h5&gt;

&lt;p&gt;失败的方法调用应该使对象保持在被调用之前的状态。&lt;/p&gt;

&lt;p&gt;部分实现途径：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;设计一个不可变的对象。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在执行操作之前检查参数的有效性。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;调整计算处理过程的顺序，使得任何可能会失败的计算部分都在对象状态被修改之前发生。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;编写一段恢复代码。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;65.不要忽略异常&lt;/h5&gt;
</description>
    </item>
    
    <item>
      <title>Effective Java ReadingNotes 7</title>
      <link>http://ggao.me/post/readingnotes/effective-java-readingnotes7/</link>
      <pubDate>Tue, 27 Dec 2016 17:18:24 +0800</pubDate>
      
      <guid>http://ggao.me/post/readingnotes/effective-java-readingnotes7/</guid>
      <description>&lt;p&gt;Effective Java第8章 通用程序设计&lt;/p&gt;

&lt;h5&gt;45.将局部变量的作用域最小化&lt;/h5&gt;

&lt;ol&gt; &lt;li&gt;&lt;p&gt;在第一次使用它的地方声明；&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;几乎每个局部变量的声明都应该包含一个初始化表达式；&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;使方法小而集中。&lt;/p&gt;&lt;/li&gt; &lt;/ol&gt;

&lt;h5&gt;46.for-each循环优先于传统的for循环&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;for-each&lt;/code&gt;循环在简洁性和预防&lt;code&gt;bug&lt;/code&gt;方面有着传统的&lt;code&gt;for&lt;/code&gt;循环无法比拟的优势，并且没有性能损失。&lt;/p&gt;

&lt;p&gt;三种无法使用&lt;code&gt;for-each&lt;/code&gt;循环的情况：&lt;/p&gt;

&lt;ol&gt; &lt;li&gt;&lt;p&gt;过滤——如果需要遍历集合，并删除选定的元素，就需要使用显式的迭代器，以便可以调用它的&lt;code&gt;remove&lt;/code&gt;方法。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;转换——如果需要遍历列表或者数组，并取代它部分或者全部的元素值，就需要列表迭代器或者数组索引，以便设定元素的值。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;平行迭代——如果需要并行地遍历多个集合，就需要显式地控制迭代器或者索引变量，以便所有迭代器或者索引变量都可以得到同步前移。&lt;/p&gt;&lt;/li&gt; &lt;/ol&gt;

&lt;h5&gt;47.了解和使用类库&lt;/h5&gt;&lt;br&gt;

&lt;h5&gt;48.如果需要精确的答案，请避免使用float和double&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;float&lt;/code&gt;和&lt;code&gt;double&lt;/code&gt;类型的主要设计目的是为了科学计算和工程计算。它们执行二进制浮点运算，这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而，它们没有提供完全精确的结果，所以不应该被用于要求精确结果的场合。因为要让一个&lt;code&gt;float&lt;/code&gt;或者&lt;code&gt;double&lt;/code&gt;精确地表示&lt;code&gt;0.1&lt;/code&gt;(或者&lt;code&gt;10&lt;/code&gt;的任何负数次方值)是不可能的，&lt;code&gt;float&lt;/code&gt;和&lt;code&gt;double&lt;/code&gt;类型对于货币计算尤为不合适，因为要让一个&lt;code&gt;float&lt;/code&gt;或&lt;code&gt;double&lt;/code&gt;精确地表示&lt;code&gt;0.1&lt;/code&gt;或&lt;code&gt;10&lt;/code&gt;的任何其他负数次方值是不可能的。比如&lt;code&gt;System.out.println(2.0-1.1)&lt;/code&gt;将会打印&lt;code&gt;0.899999999999999&lt;/code&gt;,而不是你所希望的&lt;code&gt;0.9&lt;/code&gt;，这种舍入错误产生的原因是浮点数实际上是用二进制系统实现的，而分数&lt;code&gt;1⁄10&lt;/code&gt;在二进制系统中没有精确的表示，其道理就如同在十进制系统中无法精确表示&lt;code&gt;1⁄3&lt;/code&gt;一样；再比如&lt;code&gt;0.5&lt;/code&gt;在二进制系统中有精确表示，而&lt;code&gt;0.55&lt;/code&gt;在二进制系统中没有精确表示。&lt;/p&gt;

&lt;p&gt;对于任何要精确答案的计算任务，请不要用&lt;code&gt;double&lt;/code&gt;和&lt;code&gt;float&lt;/code&gt;。如果你想计算十进制小数点，并且不介意非基本类型带来的不便，那就用&lt;code&gt;BigDecimal&lt;/code&gt;；使用&lt;code&gt;BigDecimal&lt;/code&gt;还有个额外的好处就是他允许你完全控制舍入，每当一个操作涉及舍入的时候它允许你从&lt;code&gt;8&lt;/code&gt;种舍入模式中选择一种。如果你正确通过法定要求的舍入行为进行业务计算使用&lt;code&gt;BigDecimal&lt;/code&gt;时非常方便的；如果性能十分关键，并且你又不介意自己记录十进制的小数点，而且涉及数值不大，就可以使用&lt;code&gt;int&lt;/code&gt;或者&lt;code&gt;long&lt;/code&gt;；如果数值范围没有超过&lt;code&gt;9&lt;/code&gt;位的十进制数字，可以用&lt;code&gt;int&lt;/code&gt;；如果数值范围没有超过&lt;code&gt;18&lt;/code&gt;位的十进制数字，用&lt;code&gt;long&lt;/code&gt;如果范围超过了&lt;code&gt;18&lt;/code&gt;位数，那我们必须使用&lt;code&gt;BigDecimal&lt;/code&gt;。&lt;/p&gt;

&lt;h5&gt;49.基本类型优先于装箱基本类型&lt;/h5&gt;

&lt;p&gt;基本类型和装箱基本类型的区别：&lt;/p&gt;

&lt;ol&gt; &lt;li&gt;&lt;p&gt;基本类型只有值，而装箱基本类型则具有与它们的值不同的同一性。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;基本类型只有功能完备的值，而每个装箱基本类型除了它对应基本类型的所有功能值之外，还有个非功能值：&lt;code&gt;null&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;基本类型通常比装箱基本类型更节省时间和空间。&lt;/p&gt;&lt;/li&gt; &lt;/ol&gt;

&lt;p&gt;当在一项操作中混合使用基本类型和装箱基本类型时，装箱基本类型就会自动拆箱，这种情况无一例外。如果&lt;code&gt;null&lt;/code&gt;对象引用被自动拆箱，就会得到一个&lt;code&gt;NullPointerException&lt;/code&gt;异常。&lt;/p&gt;

&lt;p&gt;装箱基本类型的合理用处：&lt;/p&gt;

&lt;ol&gt; &lt;li&gt;&lt;p&gt;作为集合中的元素、键和值。你不能将基本类型放在集合中，因此必须使用装箱基本类型。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在参数化类型中，必须使用装箱基本类型作为类型参数，因为&lt;code&gt;Java&lt;/code&gt;不允许使用基本类型。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在进行反射的方法调用时，必须使用装箱基本类型。&lt;/p&gt;&lt;/li&gt; &lt;/ol&gt;

&lt;p&gt;基本类型与装箱基本类型&lt;code&gt;==&lt;/code&gt;比较出现的几种情况：&lt;/p&gt;

&lt;ol&gt; &lt;li&gt;&lt;p&gt;&lt;code&gt;Integer.valueOf()&lt;/code&gt;和&lt;code&gt;Integer.intValue()&lt;/code&gt;进行了自动装箱和自动拆箱。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;通过&lt;code&gt;==&lt;/code&gt;对两个&lt;code&gt;new&lt;/code&gt;出来的&lt;code&gt;Integer&lt;/code&gt;类型的实例进行比较时，即使表示相同的数值，由于&lt;code&gt;Integer&lt;/code&gt;类型的数据还要进行同一性的比较，因此不相等。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;通过&lt;code&gt;==&lt;/code&gt;比较两个自动装箱的&lt;code&gt;Integer&lt;/code&gt;实例时，只要两者所表示的数值相同，结果就是相等的，因为它们的同一性是相同的。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;通过&lt;code&gt;equals&lt;/code&gt;进行比较的时候，只要同类型（包括自动装箱和拆箱）代表的数值相同，就是相等的。&lt;/p&gt;&lt;/li&gt; &lt;/ol&gt;

&lt;h5&gt;50.如果其他类型更适合，则尽量避免使用字符串&lt;/h5&gt;

&lt;ol&gt; &lt;li&gt;&lt;p&gt;字符串不适合代替其他的值类型。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;字符串不适合代替枚举类型。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;字符串不适合代替聚集类型。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;字符串不适合代替能力表。&lt;/p&gt;&lt;/li&gt; &lt;/ol&gt;

&lt;h5&gt;51.当心字符串连接的性能&lt;/h5&gt;

&lt;p&gt;由于字符串不可变，为连接&lt;code&gt;n&lt;/code&gt;个字符串而重复地使用字符串连接操作符，需要&lt;code&gt;n&lt;/code&gt;的平方级的时间。考虑使用&lt;code&gt;StringBuilder&lt;/code&gt;替代&lt;code&gt;String&lt;/code&gt;。&lt;/p&gt;

&lt;h5&gt;52.通过接口引用对象&lt;/h5&gt;

&lt;p&gt;如果有合适的接口类型存在，那么对于参数、返回值、变量和域来说，就都应该使用接口类型进行声明。&lt;/p&gt;

&lt;p&gt;比如声明&lt;code&gt;Vector&lt;/code&gt;类，它是&lt;code&gt;List&lt;/code&gt;接口的实现，在声明变量的时候应该养成这样的习惯：&lt;code&gt;List&amp;lt;String&amp;gt; strs = new Vector&amp;lt;String&amp;gt;();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;而不是这样的声明：&lt;code&gt;Vector&amp;lt;String&amp;gt; strs = new Vector&amp;lt;String&amp;gt;();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;如果养成用接口作为类型的习惯，你的程序会更加灵活，当你决定更换实现时，所有要做的事只是改变构造器中类的实现，或者使用不同的静态工厂：&lt;code&gt;List&amp;lt;String&amp;gt; strs = new ArrayList&amp;lt;String&amp;gt;();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;不存在适当接口类型的部分情形：&lt;/p&gt;

&lt;ol&gt; &lt;li&gt;&lt;p&gt;值类，比如&lt;code&gt;String&lt;/code&gt;和&lt;code&gt;BigInteger&lt;/code&gt;。记住值类很少会用多个实现编写，它们通常是&lt;code&gt;final&lt;/code&gt;，并且很少有对应的接口。使用值类做参数、变量、域或者返回值类型是再合适不过的。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;对象属于框架，而框架的基本类型的类，不是接口。如果对象属于这种基于类的框架，那么就应该使用基类(通常是抽象类)来引用这个对象，而不是实现类。&lt;code&gt;Java.util.TimerTask&lt;/code&gt;抽象类就是这种情景。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;类实现接口，但是它提供接口中不存在的额外方法，如果程序依赖这些额外方法，这种类应该只被用来引用它的实例。&lt;/p&gt;&lt;/li&gt; &lt;/ol&gt;

&lt;h5&gt;53.接口优先于反射机制&lt;/h5&gt;

&lt;p&gt;反射的代价：&lt;/p&gt;

&lt;ol&gt; &lt;li&gt;&lt;p&gt;丧失了编译时类型检查的好处，包括异常检查。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;执行反射访问所需要的代码非常笨拙和冗长。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;性能损失。&lt;/p&gt;&lt;/li&gt; &lt;/ol&gt;

&lt;h5&gt;54.谨慎地使用本地方法&lt;/h5&gt;

&lt;p&gt;本地方法主要有三种用途：1.提供了“访问特定于平台的机制”的能力；2.提供了访问遗留代码库的能力，从而可以访问遗留数据；3.本地方法可以通过本地语言，编写应用程序中注重性能的部分，以提高系统的性能。&lt;/p&gt;

&lt;p&gt;本地方法的缺点：1.不安全；2.与平台相关，使用本地方法的应用程序也不再是可自由移植的；3.更难调试。&lt;/p&gt;

&lt;h5&gt;55.谨慎地进行优化&lt;/h5&gt;&lt;br&gt;

&lt;h5&gt;56.遵守普遍接受的命名惯例&lt;/h5&gt;
</description>
    </item>
    
    <item>
      <title>Effective Java ReadingNotes 6</title>
      <link>http://ggao.me/post/readingnotes/effective-java-readingnotes6/</link>
      <pubDate>Tue, 20 Dec 2016 18:18:24 +0800</pubDate>
      
      <guid>http://ggao.me/post/readingnotes/effective-java-readingnotes6/</guid>
      <description>&lt;p&gt;Effective Java阅读笔记(6)&lt;/p&gt;

&lt;p&gt;Effective Java第7章 方法&lt;/p&gt;

&lt;h5&gt;38.检查参数的有效性&lt;/h5&gt;

&lt;p&gt;对于公有的方法，要用&lt;code&gt;Javadoc&lt;/code&gt;的&lt;code&gt;@throws&lt;/code&gt;标签在文档中说明违反参数值限制时会抛出的异常。&lt;/p&gt;

&lt;p&gt;非公有的方法通常应该使用断言&lt;code&gt;assertion&lt;/code&gt;来检查它们的参数。&lt;/p&gt;

&lt;p&gt;由于无效的参数值而导致计算过程抛出的异常，与文档中标明这个方法将抛出的异常并不相符，这种情况下，应该使用异常转译&lt;code&gt;exception translation&lt;/code&gt;技术，讲计算过程中抛出的异常转换为正确的异常。&lt;/p&gt;

&lt;h5&gt;39.必要时进行保护性拷贝&lt;/h5&gt;

&lt;p&gt;如果类具有从客户端得到或者返回到客户端的可变组件，类就必须保护性地拷贝这些组件。如果拷贝的成本受到限制，并且类信任它的客户端不会不恰当地修改组件，就可以在文档中指明客户端的职责是不得修改受到影响的组件，以此来代替保护性拷贝。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public final class Period {  
    private final Date start;  
    private final Date end;  
    public Period(Date start,Date end) {  
        if(start.compareTo(end) &gt; 0){  
            throw new IllegalArgumentException(start + &#34; after &#34; + end);  
        }  
        this.start = start;  
        this.end = end;  
    }  
      
    public Date start(){  
        return start;  
    }  
      
    public Date end(){  
        return end;  
    }  
    //remainder omitted  
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;修改后：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public Period(Date start,Date end) {  
    this.start = new Date(start.getTime());  
    this.end = new Date(end.getTime());  
    if(this.start.compareTo(this.end) &gt; 0){  
        throw new IllegalArgumentException(this.start + &#34; after &#34; + this.end);  
    }

    public Date start(){  
        return new Date(start.getTime());  
    }   
    
    public Date end(){  
        return new Date(end.getTime());  
    }    
}&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;40.谨慎设计方法签名&lt;/h5&gt;

&lt;p&gt;谨慎地选择方法的名称。&lt;/p&gt;

&lt;p&gt;不要过于追求提供便利的方法。&lt;/p&gt;

&lt;p&gt;避免过长的参数列表。三种方法：1.把方法分解成多个方法，每个方法只需要这些参数的一个子集；2.创建辅助类，用来保存参数的分组；3.从对象构建到方法调用都采用&lt;code&gt;Builder&lt;/code&gt;模式。&lt;/p&gt;

&lt;p&gt;对于参数类型，要优先使用接口而不是类；对于&lt;code&gt;boolean&lt;/code&gt;参数，要优先使用两个元素的枚举类型。&lt;/p&gt;

&lt;h5&gt;41.慎用重载&lt;/h5&gt;

&lt;p&gt;&lt;strong&gt;对于重载方法的选择是静态的，而对于被覆盖的方法的选择则是动态的。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;一般情况下，对于多个具有相同参数数目的方法来说，应该尽量避免重载方法。对于涉及构造器的时候要避免同一组参数只需经过类型转换就可以被传递给不同的重载方法。如果不能就应该保证当传递同样的参数时，所有重载方法的行为必须一致。&lt;/p&gt;

&lt;h5&gt;42.慎用可变参数&lt;/h5&gt;

&lt;p&gt;在定义参数数目不定的方法时，可变参数方法是一种很方便的方式，但是它们不应该被过度滥用。为了避免某些错误，可以让该方法带有两个参数，一个是指定类型的正常参数，另一个是这种类型的&lt;code&gt;varargs&lt;/code&gt;参数。&lt;/p&gt;

&lt;h5&gt;43.返回零长度的数组或者集合，而不是null&lt;/h5&gt;

&lt;p&gt;返回类型为数组或集合的方法没理由返回&lt;code&gt;null&lt;/code&gt;，而不是返回一个零长度的数组或者集合。&lt;/p&gt;

&lt;h5&gt;44.为所有导出的API元素编写文档注释&lt;/h5&gt;

&lt;p&gt;采用一致地风格来遵循标准的约定为&lt;code&gt;API&lt;/code&gt;编写文档注释。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Effective Java ReadingNotes 5</title>
      <link>http://ggao.me/post/readingnotes/effective-java-readingnotes5/</link>
      <pubDate>Tue, 20 Dec 2016 13:18:24 +0800</pubDate>
      
      <guid>http://ggao.me/post/readingnotes/effective-java-readingnotes5/</guid>
      <description>&lt;p&gt;Effective Java阅读笔记(5)&lt;/p&gt;

&lt;p&gt;Effective Java第6章 枚举和注解&lt;/p&gt;

&lt;h5&gt;30.用enum代替int常量&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;
public enum PayrollDay {  
    MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY), WEDNESDAY(  
            PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY), FRIDAY(PayType.WEEKDAY), SATURDAY(  
            PayType.WEEKEND), SUNDAY(PayType.WEEKEND);  
    private final PayType payType;  
  
    PayrollDay(PayType payType) {  
        this.payType = payType;  
    }  
      
    double pay(double hoursWorked, double payRate) {  
        return payType.pay(hoursWorked, payRate);  
    }  
  
    private enum PayType {  
        WEEKDAY {  
            double overtimePay(double hours, double payRate) {  
                return hours &lt;= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT)  
                        * payRate / 2;  
            }  
        },  
        WEEKEND {  
            double overtimePay(double hours, double payRate) {  
                return hours * payRate / 2;  
            }  
        };  
        private static final int HOURS_PER_SHIFT = 8;  
  
        abstract double overtimePay(double hours, double payRate);  
  
        double pay(double hoursWorked, double payRate) {  
            double basePay = hoursWorked * payRate;  
            return basePay + overtimePay(hoursWorked, payRate);  
        }  
    }  
}  
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;与&lt;code&gt;int&lt;/code&gt;常量相比，枚举要易读得多，也更加安全，功能更加强大。许多枚举都不需要显式的构造器或者成员，但许多其它枚举则受益于“每个常量与属性的关联”以及“提供行为受这个属性影响的方法”。只有极少数的枚举受益于将多种行为与单个方法关联。在这种相对少见的情况下，特定于常量的方法要优先于启用自有值的枚举。如果多个枚举常量同时享有相同的行为，则考虑策略枚举。&lt;/p&gt;

&lt;h5&gt;31.用实例域代替序数&lt;/h5&gt;

&lt;p&gt;&lt;strong&gt;永远不要根据枚举的序数导出与它关联的值，而是要将它保存在一个实力域中。&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public enum Ensemble{
  SOLO(1), DUET(2), TRIO(3), QUINTET(4), SEXTET(5), 
  SEPTET(7), OCTET(8), DOUBLE_QUARTET(8), NONET(9),
  DECTET(10), TRIPLE_QUARTRT(12);
  
  private final int numberOfMusicians;
  Ensemble(int size){
    this.numberOfMusicians = size;
  }
  public int numberofMusicians(){
    return numberOfMusicians;
  }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;Enum&lt;/code&gt;规范中谈到&lt;code&gt;ordinal&lt;/code&gt;时这么写到：“大多数的程序员都不需要这个方法。它是设计成用于像&lt;code&gt;EnumSet&lt;/code&gt;和&lt;code&gt;EnumMap&lt;/code&gt;这种基于枚举的通用数据结构的。”除非你在编写的是这种数据结构，否则最好完全避免使用&lt;code&gt;ordinal&lt;/code&gt;方法。&lt;/p&gt;

&lt;h5&gt;32.用EnumSet代替位域&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;EnumSet&lt;/code&gt;类集位域的简洁和性能优势及第&lt;code&gt;30&lt;/code&gt;条中所述的枚举类型的所有的优点于一身。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;
public class Text{
    public enum TextStyle{BOLD, ITALIC, UNDERLINE, STRIKETHROUGH}
    public void applyStyles(Set&amp;lt;TextStyle&gt; styles){...}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下面是将&lt;code&gt;EnumSet&lt;/code&gt;实例传递给&lt;code&gt;applyStyles&lt;/code&gt;方法的客户端代码。&lt;code&gt;EnumSet&lt;/code&gt;提供了丰富的静态工厂来创建集合，其中一个如这个代码所示：&lt;code&gt;text.applyStyles(EnumSet.of(TextStyle.BOLD,TextStyle.ITALIC));&lt;/code&gt;&lt;/p&gt;

&lt;h5&gt;33.用EnumMap代替序数索引&lt;/h5&gt;

&lt;p&gt;序数索引是指依赖于枚举成员在枚举中的序数来进行数组索引，如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public class Plant{
    public enum Type { Fruit, Vegetables, Tree}
    private final String name;
    private final Type type;

    Plant(String name, Type type){
        this.name = name;
        this.type = type;
    }
}

Set&amp;lt;Plant&gt;[] plants = (Set&lt;Plant&gt;[]) new Set[Plant.Type.valuse().lenght]; 
//根据植物的类型，分别把所有的植物放入三个set中
for(int i = 0; i &lt; plant.lenght; i++){
   plant[i] = new HashSet&lt;Plant&gt;();
}

for(Plant p : garden){  //garden里放了所有的植物
    plant[p.type.ordinal()].add(p)  //反面教材：利用了枚举的序数来得到想要的数组索引，用户在其他地方可以不使用ordinal函数，而直接使用int值来访问，就可能出错
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;应该使用&lt;code&gt;EnumMap&lt;/code&gt;来实现，&lt;code&gt;EnumMap&lt;/code&gt;内部是采用数组实现的，具有&lt;code&gt;Map&lt;/code&gt;的丰富功能和类型安全以及数组的效率：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;Map&amp;lt;Plant.Type, Set&lt;Plant&gt;&gt; plants = new EnumMap&amp;lt;Plant.Type, Set&lt;Plant&gt;&gt;(Plant.Type.class);  //构造函数需要 键 类型的Class对象
//根据植物的类型，分别把所有的植物放入三个set中
for(Plant.Type type : Plant.Type.valuse()){
   plant.put(type, new HashSet&lt;Plant&gt;);
}

for(Plant p : garden){  //garden里放了所有的植物
    plant.get(p.type).add(p)  //用户必须使用正确的键值来访问，即Type类型
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;当需要多维关系时，可以使用&lt;code&gt;EnumMap&amp;lt;..., EnumMap&amp;lt;...&amp;gt;&amp;gt;&lt;/code&gt;。&lt;/p&gt;

&lt;h5&gt;34.用接口模拟可伸缩的枚举&lt;/h5&gt;

&lt;p&gt;虽然无法编写可扩展的枚举类型，却可以通过编写接口以及实现该接口的基础枚举类型，对它进行模拟。这样允许客户端编写自己的枚举来实现接口。如果API是根据接口编写的，那么在可以使用基础枚举类型的任何地方，也都可以使用这些枚举。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public interface Operation{
    double apply(double x,double y);
}

public enum BasicOperation implements Operation{

    PLUS(&#34;^&#34;){
        public double apply(double x,double y){ return Math.pow(x,y);}
    },
    MINUS(&#34;%&#34;){
        public double apply(double x,double y){ return x % y;}
    };

    private final String symbool;
    BasicOperation(String symbool){ this.symbool = symbool}

    @Override
    public String toString() {
        return symbool;
    }

    @Override
    public double apply(double x, double y) {
        return 0;
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以通过以下两种方法传递完整的扩展枚举类型，并使用它的元素：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;&amp;lt;T extends Enum&amp;lt;T&amp;gt; &amp;amp; Operation&amp;gt; Class&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;Collection&amp;lt;? Extends Operation&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;35.注解优先于命名模式&lt;/h5&gt;

&lt;p&gt;命名模式的缺点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;无法处理命名失误的情况；&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;无法确保它们只用于响应的程序元素上；&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;他们没有提供将参数值与程序元素关联起来的好方法。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;36.坚持使用Override注解&lt;/h5&gt;

&lt;p&gt;在每个方法声明中使用&lt;code&gt;Override&lt;/code&gt;注解来覆盖超类声明。&lt;/p&gt;

&lt;h5&gt;37.用标记接口定义类型&lt;/h5&gt;

&lt;p&gt;标记接口定义的类型是由被标记类的实例实现的；标记注解则没有定义这样的类型；标记接口可以被更加精确地进行锁定。&lt;/p&gt;

&lt;p&gt;标记注解胜过标记接口的最大优点在于，它可以通过默认的方式添加一个或者多个注解类型元素，给已被使用的注解类型添加更多的信息，它的另一个优点在于它们使更大的注解机制的一部分。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;如果想要定义类型，一定要使用接口。&lt;/strong&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>LeetCode 1 - 10</title>
      <link>http://ggao.me/post/leetcode/leetcode-1-10/</link>
      <pubDate>Mon, 19 Dec 2016 16:16:24 +0800</pubDate>
      
      <guid>http://ggao.me/post/leetcode/leetcode-1-10/</guid>
      <description>&lt;h5&gt;1. Two Sum&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-Java&#34;&gt;public class Solution {
    public int[] twoSum(int[] nums, int target) {
        int result[] = new int[2];
        Map&amp;lt;Integer, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
        for(int i = 0; i &amp;lt; nums.length; i++){
            if(map.containsKey(target - nums[i])){
                result[1] = i;
                result[0] = map.get(target - nums[i]);
                return result;
            }
            map.put(nums[i], i);
        }
        return result;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;2. Add Two Numbers&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-Java&#34;&gt;/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode result = new ListNode(0);
        ListNode temp = result;
        int sum = 0;
        while(l1 != null || l2 != null){
            sum/=10;
            if(l1 != null){
                sum += l1.val;
                l1 = l1.next;
            }
            if(l2 != null){
                sum += l2.val;
                l2 = l2.next;
            }
            temp.next = new ListNode(sum%10);
            temp = temp.next;
        }
        if(sum/10 &amp;gt;= 1){
            temp.next = new ListNode(sum/10);
        }
        return result.next;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;3. Longest Substring Without Repeating Characters&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-Java&#34;&gt;public class Solution {
    public int lengthOfLongestSubstring(String s) {
        if(0 == s.length()) return 0;
        Map&amp;lt;Character, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
        int max = 0;
        for(int i = 0, j = 0; i&amp;lt;s.length(); i++){
            if(map.containsKey(s.charAt(i))){
                j = Math.max(j, map.get(s.charAt(i))+1);
            }
            map.put(s.charAt(i), i);
            max = Math.max(max, i - j + 1);
        }
        return max;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;4. Median of Two Sorted Arrays&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-Java&#34;&gt;public class Solution {
    public double findMedianSortedArrays(int[] A, int[] B) {
        int m = A.length, n = B.length;
        int l = (m + n + 1) / 2;
        int r = (m + n + 2) / 2;
        return (getkth(A, 0, B, 0, l) + getkth(A, 0, B, 0, r)) / 2.0;
    }

    public double getkth(int[] A, int aStart, int[] B, int bStart, int k) {
        if (aStart &amp;gt; A.length - 1) return B[bStart + k - 1];            
        if (bStart &amp;gt; B.length - 1) return A[aStart + k - 1];                
        if (k == 1) return Math.min(A[aStart], B[bStart]);
    
        int aMid = Integer.MAX_VALUE, bMid = Integer.MAX_VALUE;
        if (aStart + k/2 - 1 &amp;lt; A.length) aMid = A[aStart + k/2 - 1]; 
        if (bStart + k/2 - 1 &amp;lt; B.length) bMid = B[bStart + k/2 - 1];        
    
        if (aMid &amp;lt; bMid) 
            return getkth(A, aStart + k/2, B, bStart,       k - k/2); 
        else 
            return getkth(A, aStart,       B, bStart + k/2, k - k/2);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;5. Longest Palindromic Substring&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-Java&#34;&gt;public class Solution {
    private int lo, maxLen;
    
    public String longestPalindrome(String s) {
        if (s.length() &amp;lt; 2){
            return s;
        }
    
        for (int i = 0; i &amp;lt; s.length()-1; i++) {
            extendPalindrome(s, i, i);  //assume odd length, try to extend Palindrome as possible
            extendPalindrome(s, i, i+1); //assume even length.
        }
        return s.substring(lo, lo + maxLen);
    }
    
    private void extendPalindrome(String s, int j, int k) {
        while (j &amp;gt;= 0 &amp;amp;&amp;amp; k &amp;lt; s.length() &amp;amp;&amp;amp; s.charAt(j) == s.charAt(k)) {
            j--;
            k++;
        }
        if (maxLen &amp;lt; k - j - 1) {
            lo = j + 1;
            maxLen = k - j - 1;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>Pokemon Button</title>
      <link>http://ggao.me/post/android/pokemon-button/</link>
      <pubDate>Mon, 19 Dec 2016 00:16:24 +0800</pubDate>
      
      <guid>http://ggao.me/post/android/pokemon-button/</guid>
      <description>&lt;p&gt;&lt;code&gt;PokemonButton&lt;/code&gt;是受&lt;a href=&#34;https://github.com/jd-alexander/LikeButton&#34;&gt;LikeButton&lt;/a&gt;和&lt;a href=&#34;https://github.com/frogermcs/LikeAnimation&#34;&gt;LikeAnimation&lt;/a&gt;的启发，类似&lt;code&gt;Twitter&lt;/code&gt;的&lt;code&gt;Like&lt;/code&gt;动画的一种实现。&lt;/p&gt;

&lt;p&gt;具体效果图如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://ggao.me/post/android/pokemonbutton.gif&#34; alt=&#34;pokemonbutton&#34; /&gt;&lt;/p&gt;

&lt;p&gt;该动画效果是通过手动绘制和使用&lt;code&gt;ObjectAnimator&lt;/code&gt;实现的，相较帧动画，这样的解决方案更为灵活。这篇文章只是一个简单的概述，如果想要知道更多的细节，可以在我的&lt;code&gt;GitHub&lt;/code&gt;中查看源代码——&lt;a href=&#34;https://github.com/Gaogggg/PokemonButton&#34;&gt;PokemonButton&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PokemonButton&lt;/code&gt;是一个继承&lt;code&gt;FrameLayout&lt;/code&gt;的&lt;code&gt;View&lt;/code&gt;，该&lt;code&gt;View&lt;/code&gt;托管三个&lt;code&gt;ChildView&lt;/code&gt; - &lt;code&gt;PokeBallView&lt;/code&gt;显示精灵球图形，&lt;code&gt;ImageView&lt;/code&gt;显示精灵的&lt;code&gt;Image&lt;/code&gt;，&lt;code&gt;TypeView&lt;/code&gt;呈现与精灵属性相应的点缀效果图。&lt;/p&gt;

&lt;h5&gt;PokeBallView&lt;/h5&gt;

&lt;p&gt;这个&lt;code&gt;View&lt;/code&gt;负责绘制精灵图标下面的精灵球图案。使用&lt;code&gt;Paint&lt;/code&gt;和&lt;code&gt;Path&lt;/code&gt;在&lt;code&gt;Canvas&lt;/code&gt;上绘制：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    tempCanvas.drawColor(0xffffff, PorterDuff.Mode.CLEAR);

    drawPokeBall(getWidth() / 2, getHeight() / 2, outerCircleRadiusProgress * maxCircleSize,tempCanvas);
    tempCanvas.drawCircle(getWidth() / 2, getHeight() / 2, innerCircleRadiusProgress * maxCircleSize +  paintBlack.getStrokeWidth(), maskPaint);
    canvas.drawBitmap(tempBitmap, 0, 0, null);
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;整个&lt;code&gt;View&lt;/code&gt;分为两层，外圈的精灵球图案以及内圈的擦除圆。&lt;/p&gt;

&lt;p&gt;精灵球图案分为上半球、下半球以及轮廓的绘制：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public void drawPokeBall(float cx, float cy, float radius, Canvas canvas){
    RectF outerBall = new RectF(cx-radius, cy-radius, cx+radius, cy+radius);

    RectF innerBall = new RectF(cx-radius/3,cy-radius/3,cx + radius/3,cy + radius/3);

    pathTopBall.reset();
    pathBlack.reset();
    pathBottomBall.reset();

    pathTopBall.moveTo(cx - radius,cy);
    pathTopBall.lineTo(cx - radius/3,cy);
    pathTopBall.arcTo(new RectF(innerBall),180,180);
    pathTopBall.lineTo(cx + radius,cy);
    pathTopBall.arcTo(new RectF(outerBall),0,-180);
    pathTopBall.close();

    pathBottomBall.moveTo(cx - radius, cy);
    pathBottomBall.lineTo(cx - radius/3, cy);
    pathBottomBall.arcTo(new RectF(innerBall),180,-180);
    pathBottomBall.lineTo(cx + radius,cy);
    pathBottomBall.arcTo(new RectF(outerBall),0,180);
    pathBottomBall.close();

    pathBlack.moveTo(cx - radius, cy);
    pathBlack.lineTo(cx - radius/3,cy);
    pathBlack.moveTo(cx + radius/3, cy);
    pathBlack.lineTo(cx + radius, cy);

    canvas.drawPath(pathTopBall,paintTopBall);
    canvas.drawPath(pathBottomBall,paintBottomBall);
    canvas.drawPath(pathBlack,paintBlack);

    canvas.drawCircle(cx,cy,radius/3,paintBlack);
    canvas.drawCircle(cx,cy,radius,paintBlack);
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;每个帧从清除整个画布开始，通过使用&lt;code&gt;CLEAR&lt;/code&gt;模式的画笔。然后根据给定的进度绘制内圈和外圈：&lt;code&gt;maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这就能产生一个透明孔由内向外扩大使精灵球图案消失的效果。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;View&lt;/code&gt;所用的&lt;code&gt;tempCanvas&lt;/code&gt;和&lt;code&gt;tempBitmap&lt;/code&gt;是在这里定义的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    paintBlack.setStrokeWidth(w/20);
    maxCircleSize = w / 2 - (int)paintBlack.getStrokeWidth();
    tempBitmap = Bitmap.createBitmap(getWidth(), getWidth(), Bitmap.Config.ARGB_8888);
    tempCanvas = new Canvas(tempBitmap);
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;除此之外，&lt;code&gt;PokeBallView&lt;/code&gt;能根据当前的进展改变其颜色。由&lt;code&gt;ArgbEvaluator&lt;/code&gt;完成：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;private void updateCircleColor() {
    float colorProgress = (float) Utils.clamp(outerCircleRadiusProgress, 0.5, 1);
    colorProgress = (float) Utils.mapValueFromRangeToRange(colorProgress, 0.5f, 1f, 0f, 1f);
    this.paintTopBall.setColor((Integer) argbEvaluator.evaluate(colorProgress, 0, TOP_HALF_BALL_COLOR));
    this.paintBottomBall.setColor((Integer) argbEvaluator.evaluate(colorProgress, 0, BOTTOM_HALF_BALL_COLOR));
    this.paintBlack.setColor((Integer) argbEvaluator.evaluate(colorProgress, 0, BLACK_COLOR));
}&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;TypeView&lt;/h5&gt;

&lt;p&gt;这个类是呈现与精灵属性相应的点缀效果图的抽象类，和&lt;code&gt;PokeBallView&lt;/code&gt;类似，在&lt;code&gt;onDraw()&lt;/code&gt;中：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Override
protected void onDraw(Canvas canvas) {
    drawOuterTypesFrame(canvas);
    drawInnerTypesFrame(canvas);
}

private void drawOuterTypesFrame(Canvas canvas) {
    for (int i = 0; i &lt; TYPES_COUNT; i++) {
        int cX = (int) (centerX + currentRadius1 * Math.cos(i * OUTER_TYPES_POSITION_ANGLE * Math.PI / 180));
        int cY = (int) (centerY + currentRadius1 * Math.sin(i * OUTER_TYPES_POSITION_ANGLE * Math.PI / 180));
        drawType(cX, cY, currentTypeSize1, canvas);
    }
}

private void drawInnerTypesFrame(Canvas canvas) {
    for (int i = 0; i &lt; TYPES_COUNT; i++) {
        int cX = (int) (centerX + currentRadius2 * Math.cos((i * OUTER_TYPES_POSITION_ANGLE - 10) * Math.PI / 180));
        int cY = (int) (centerY + currentRadius2 * Math.sin((i * OUTER_TYPES_POSITION_ANGLE - 10) * Math.PI / 180));
        drawType(cX, cY, currentTypeSize2, canvas);
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;点缀效果是基于当前进度进行绘制的，它们排列在不可见圆上——位置由以下因素决定：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;int cX = (int) (centerX + currentRadius1 * Math.cos(i * OUTER_TYPES_POSITION_ANGLE * Math.PI / 180));
int cY = (int) (centerY + currentRadius1 * Math.sin(i * OUTER_TYPES_POSITION_ANGLE * Math.PI / 180));&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;具体的点缀效果绘制交由继承该类的具体实现来进行。以目前已经实现的四个效果之一的&lt;code&gt;Electric&lt;/code&gt;为例。&lt;code&gt;Electric&lt;/code&gt;需要实现该抽象类的四个抽象方法，分别为&lt;code&gt;init()&lt;/code&gt;——进行绘制所需的初始化，&lt;code&gt;drawType(float cx, float cy, float radius, Canvas canvas)&lt;/code&gt;——实际绘制，&lt;code&gt;updateTypesPaints(float currentProgress)&lt;/code&gt;——根据当前进度调整画笔状态，&lt;code&gt;updateTypesAlpha(int alpha)&lt;/code&gt;——根据当前进度调整画笔透明度。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Electric&lt;/code&gt;的相关实现如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Override
protected void init() {

    paintElectric = new Paint();
    paintElectric.setAntiAlias(true);
    paintElectric.setStyle((Paint.Style.FILL));
}

@Override
protected void drawType(float cx, float cy, float radius, Canvas canvas) {
    pathElectric.reset();

    pathElectric.moveTo(cx - radius/2,cy - radius/4);
    pathElectric.lineTo(cx + radius,cy - 3*radius/4);
    pathElectric.lineTo(cx + radius/2,cy + radius/4);
    pathElectric.lineTo(cx + 3*radius/4,cy + 3*radius/8);
    pathElectric.lineTo(cx - 3*radius/4,cy + 7*radius/8);
    pathElectric.lineTo(cx - radius/4,cy);
    pathElectric.lineTo(cx - radius/2,cy - radius/4);

    canvas.drawPath(pathElectric,paintElectric);
}

@Override
protected void updateTypesPaints(float currentProgress) {
    if (currentProgress &lt; 0.5f) {
        float progress = (float) Utils.mapValueFromRangeToRange(currentProgress, 0f, 0.5f, 0, 1f);
        paintElectric.setColor((Integer) argbEvaluator.evaluate(progress, COLOR_1, COLOR_2));
    } else {
        float progress = (float) Utils.mapValueFromRangeToRange(currentProgress, 0.5f, 1f, 0, 1f);
        paintElectric.setColor((Integer) argbEvaluator.evaluate(progress, COLOR_2, COLOR_3));

    }
}

@Override
protected void updateTypesAlpha(int alpha) {
    paintElectric.setAlpha(alpha);
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;将&lt;code&gt;TypeView&lt;/code&gt;设计为抽象类可以让我们在之后绘制新效果时只用关注于具体图案的绘制，实际上目前由于时间关系我只实现了四个属性效果，分别为&lt;code&gt;Electric&lt;/code&gt;,&lt;code&gt;Fire&lt;/code&gt;,&lt;code&gt;Grass&lt;/code&gt;和&lt;code&gt;Water&lt;/code&gt;，而在&lt;code&gt;PokemonType&lt;/code&gt;中我预设了所有的精灵属性：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public enum  PokemonType {
    NORMAL,
    FIRE,
    FIGHTING,
    WATER,
    FLYING,
    GRASS,
    POSITION,
    ELECTRIC,
    GROUND,
    PSYCHIC,
    ROCK,
    ICE,
    BUG,
    DRAGON,
    GHOST,
    DARK,
    STEEL,
    FAIRY
}&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;PokemonButton&lt;/h5&gt;

&lt;p&gt;最终的&lt;code&gt;ViewGroup&lt;/code&gt;由&lt;code&gt;PokeballView&lt;/code&gt;，&lt;code&gt;ImageView&lt;/code&gt;和动态生成的具体实现的&lt;code&gt;TypeView&lt;/code&gt;组成。&lt;/p&gt;

&lt;p&gt;最终的&lt;code&gt;View&lt;/code&gt;动画由一些较小的动画组成，通过&lt;code&gt;AnimatorSet&lt;/code&gt;一起播放：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;animatorSet = new AnimatorSet();

ObjectAnimator outerCircleAnimator = ObjectAnimator.ofFloat(pokeBallView, pokeBallView.OUTER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
outerCircleAnimator.setDuration(250);
outerCircleAnimator.setInterpolator(DECCELERATE_INTERPOLATOR);

ObjectAnimator innerCircleAnimator = ObjectAnimator.ofFloat(pokeBallView, pokeBallView.INNER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
innerCircleAnimator.setDuration(200);
innerCircleAnimator.setStartDelay(200);
innerCircleAnimator.setInterpolator(DECCELERATE_INTERPOLATOR);

ObjectAnimator starScaleYAnimator = ObjectAnimator.ofFloat(icon, ImageView.SCALE_Y, 0.2f, 1f);
starScaleYAnimator.setDuration(350);
starScaleYAnimator.setStartDelay(250);
starScaleYAnimator.setInterpolator(OVERSHOOT_INTERPOLATOR);

ObjectAnimator starScaleXAnimator = ObjectAnimator.ofFloat(icon, ImageView.SCALE_X, 0.2f, 1f);
starScaleXAnimator.setDuration(350);
starScaleXAnimator.setStartDelay(250);
starScaleXAnimator.setInterpolator(OVERSHOOT_INTERPOLATOR);

ObjectAnimator dotsAnimator = ObjectAnimator.ofFloat(typeView, typeView.TYPES_PROGRESS, 0, 1f);
dotsAnimator.setDuration(900);
dotsAnimator.setStartDelay(50);
dotsAnimator.setInterpolator(ACCELERATE_DECELERATE_INTERPOLATOR);

animatorSet.playTogether(
        outerCircleAnimator,
        innerCircleAnimator,
        starScaleYAnimator,
        starScaleXAnimator,
        dotsAnimator
);

animatorSet.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationCancel(Animator animation) {
        pokeBallView.setInnerCircleRadiusProgress(0);
        pokeBallView.setOuterCircleRadiusProgress(0);
        typeView.setCurrentProgress(0);
        icon.setScaleX(1);
        icon.setScaleY(1);
    }
});

animatorSet.start();&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;当然，&lt;code&gt;PokemonButton&lt;/code&gt;需要相应点击事件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Override
public boolean onTouchEvent(MotionEvent event) {
    if(!isEnabled)
        return true;

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            setPressed(true);
            break;

        case MotionEvent.ACTION_MOVE:
            float x = event.getX();
            float y = event.getY();
            boolean isInside = (x &gt; 0 &amp;&amp; x &lt; getWidth() &amp;&amp; y &gt; 0 &amp;&amp; y &lt; getHeight());
            if (isPressed() != isInside) {
                setPressed(isInside);
            }
            break;

        case MotionEvent.ACTION_UP:
            icon.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).setInterpolator(DECCELERATE_INTERPOLATOR);
            icon.animate().scaleX(1).scaleY(1).setInterpolator(DECCELERATE_INTERPOLATOR);
            if (isPressed()) {
                performClick();
                setPressed(false);
            }
            break;
    }
    return true;
}&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>Effective Java ReadingNotes 4</title>
      <link>http://ggao.me/post/readingnotes/effective-java-readingnotes4/</link>
      <pubDate>Fri, 16 Dec 2016 18:18:24 +0800</pubDate>
      
      <guid>http://ggao.me/post/readingnotes/effective-java-readingnotes4/</guid>
      <description>&lt;p&gt;Effective Java阅读笔记(4)&lt;/p&gt;

&lt;p&gt;Effective Java第5章 泛型&lt;/p&gt;

&lt;h5&gt;23.请不要在新代码中使用原生态类型&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;Set&amp;lt;Object&amp;gt;&lt;/code&gt;是个参数化类型，表示可以包含任何对象类型的一个集合；&lt;code&gt;Set&amp;lt;?&amp;gt;&lt;/code&gt;则是一个通配符类型，表示只能包含某种未知对象类型的一个集合；&lt;code&gt;Set&lt;/code&gt;则是个原生态类型，它脱离了泛型系统。前两种是安全的，最后一种不安全。&lt;/p&gt;

&lt;h5&gt;24.消除非受检警告&lt;/h5&gt;

&lt;p&gt;本处主要介绍比较难以消除的警告的示例。&lt;/p&gt;

&lt;p&gt;如果无法消除警告，同时可以证明引起警告的代码是类型安全的，可以用一个&lt;code&gt;@SuppressWarnings(&amp;quot;unchecked&amp;quot;)&lt;/code&gt;注解来禁止这条警告。&lt;strong&gt;应该始终在尽可能小的范围中使用&lt;code&gt;SuppressWarnings&lt;/code&gt;注解，并且每当使用该注解时都要添加一条注释，说明为什么这么做是安全的。&lt;/strong&gt;&lt;/p&gt;

&lt;h5&gt;25.列表优先于数组&lt;/h5&gt;

&lt;p&gt;数组是协变且可以具体化的；泛型是不可变的且可以被擦除的。因此，数组提供了运行时的类型安全，但是没有编译时的类型安全，反之，对于泛型也一样。一般来说，数组和泛型不能很好地混合使用。如果你发现自己将它们混合起来使用，并且得到了编译时错误或者警告，你的第一反应就应该是用列表代替数组。&lt;/p&gt;

&lt;h5&gt;26.优先考虑泛型&lt;/h5&gt;

&lt;p&gt;看如下一个简单堆栈的实现：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public class Stack {
    pprivate Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITAL_CAPACITY = 16;
    
    public Stack() {
        elements = new Object[DEFAULT_INITAL_CAPACITY];
    }
    
    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }
    
    public Object pop() {
        if(size == 0) {
            throw new EmptyStackException();
        }
        Object result = elements[--size];
        elements[size] = null;
        return result;
    }
    
    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if(elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;将该类泛型化可以提高类型的安全性，方便客户端使用(无需显式强制转换类型)&lt;/p&gt;

&lt;p&gt;用类型参数替换所有的&lt;code&gt;Object&lt;/code&gt;类型：&lt;code&gt;private E[] elements;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;由于不能创建不可具体化类型的数组，所以会报错&lt;code&gt;generic array creation new E[DEFAULT_INITIAL_CAPACITY]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;我们有两种解决办法：&lt;/p&gt;

&lt;p&gt;解决方法：&lt;/p&gt;

&lt;p&gt;1.创建一个&lt;code&gt;Object&lt;/code&gt;数组，并将它转换成泛型数组类型：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];&lt;/code&gt;
错误变成一条警告，因为可以保证类型安全，所以可以用&lt;code&gt;SupressWarning&lt;/code&gt;注释忽略掉该警告。&lt;/p&gt;

&lt;p&gt;2.将&lt;code&gt;elements&lt;/code&gt;域的类型从&lt;code&gt;E[]&lt;/code&gt;改为&lt;code&gt;Object[]&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;private Object[] elements;

E result = (E) elements[--size];&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;产生一条警告，因为可以保证类型安全，所以可以用&lt;code&gt;SupressWarning&lt;/code&gt;注释忽略掉该警告。&lt;/p&gt;

&lt;p&gt;这两种方法，一般来说禁止数组类型的未受检转换比禁止标题类型更危险，所以建议使用第二种方法。但是在比这个例子更实际的泛型中，或许代码中会有多个地方需要从数组中读取元素，因此第一种方法比第二种方法开销更小一些，这也是第一种方法更常用的原因。这个例子看起来违反了&lt;code&gt;25&lt;/code&gt;条(它告诉我们对于泛型列表要优于数组)，实际上因为&lt;code&gt;Java&lt;/code&gt;并不是生来就支持列表，所以有些泛型如&lt;code&gt;ArrayList&lt;/code&gt;必须使用数组来实现，有的时候为了提升性能，也会考虑用数组来实现，比如&lt;code&gt;HashMap&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;总之，使用泛型比使用需要在客户端代码中进行转化的类型来的更安全。也更容易，在设计新类的时候，要确保它们不需要这种转换就可以使用。这通常意味着要把类做成是泛型的。&lt;/p&gt;

&lt;h5&gt;27.优先考虑泛型方法&lt;/h5&gt;

&lt;p&gt;考虑如下的方法，它的作用是返回两个集合的联合：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public static Set union(Set s1, Set s2) {  
    Set result = new HashSet(s1);  
    result.addAll(s2);  
    return result;  
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;编译这段代码会产生警告，为了修正这些警告(在新代码中不应该直接使用原始类型，当前是为了举例子)要将方法声名修改为声明一个类型参数，表示这三个元素类型(两个参数及一个返回值)，并在方法中使用类型参数。声名类型参数的类型参数列表，处在方法的修饰符及其返回类型之间，修改后的代码如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public static &amp;lt;E&gt; Set&amp;lt;E&gt; union(Set&amp;lt;E&gt; s1, Set&amp;lt;E&gt; s2) {  
    Set&amp;lt;E&gt; result = new HashSet&amp;lt;E&gt;(s1);  
    result.addAll(s2);  
    return result;  
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;当前这个版本的&lt;code&gt;union&lt;/code&gt;方法即为一般的泛型方法，但是它有一个限制，要求三个集合的类型(两个输入参数及一个返回值)必须全部相同。利用有限制的通配符类型可以使这个方法变得更回灵活。&lt;/p&gt;

&lt;p&gt;泛型方法的一个显著特征是，无需明确指定类型参数的值，不像调用泛型构造器的时候是必须要指定类型参数的，在为泛型方法的类型会存在一个类型推导的过程。编译器通过检查方法参数的类型来计算类型的值。在调用 泛型构造器的时候，要明确传递类型参数的值可能有点麻烦。类型参数出现在了变量的声明的左右两边，显得冗余：&lt;code&gt;Map&amp;lt;String, List&amp;lt;String&amp;gt;&amp;gt; anagrams = new HashMap&amp;lt;String, List&amp;lt;String&amp;gt;&amp;gt;();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;对于这情况，可以遵照第&lt;code&gt;1&lt;/code&gt;条，提供一个静态工厂方法来简化：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public static &amp;lt;K, V&gt; HashMap&amp;lt;K, V&gt; newHashMap() {  
    return new HashMap&amp;lt;K, V&gt;();  
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;利用上面的静态工厂方法，我们可以把变量声明右侧的参数类型省略掉，当参数类型多而复杂时尤其有效。&lt;/p&gt;

&lt;p&gt;有时会需要创建不可变但是又适合于许多不同类型的对象，由于泛型是通过擦除来实现的，可以给所有的必要的类型参数使用同一个单个对象，但是需要一个静态的工厂方法来给每个必要的类型参数分发对象。这种模式叫做&amp;rdquo;泛型单例工厂&amp;rdquo;，这种模式最常用于函数的对象。假设有一个接口，描述了一个方法，该方法接受和返回某个类型&lt;code&gt;T&lt;/code&gt;的值：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public interface UnaryFunction&amp;lt;T&gt; {  
    T apply(T arg);
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;现在假设要提供一个恒等函数，如果 在每次需要的时候都重新创建一个这样会很浪费，因为它是无状态的。如果泛型被具体化，那个每个类型都必须持有相应类型的恒等函数，但是在运行时擦除类型信息后，它们并没有什么区别，所以在这种情况下，只需要一个泛型单例就够了。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;private static UnaryFunction
 INDENTITY_FUNCTION = new UnaryFunction {
    public Object apply(Object arg) { return arg; }  
}; 

@SuppressWarnings(&#34;unchecked&#34;)
public static  UnaryFunction indentityFunction() {
    return (UnaryFunction)INDENTITY_FUNCTION;
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;递归类型限制最普遍的用途与&lt;code&gt;Comparable&lt;/code&gt;接口有关，它定义类型的自然顺序。许多方法都带有一个实现&lt;code&gt;Comparable&lt;/code&gt;接口的元素列表，为了对列表进行排序，并在其中进行搜索，计算出它的最小值或者最大值等等。要完成这其中的任何一项工作要求列表中的每个元素都能够与列表中的其他元素相比较，一个示例：&lt;code&gt;public static &amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt; T max(List&amp;lt;T&amp;gt; list) {  }&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;上面的声名要求&lt;code&gt;T&lt;/code&gt;是可以与自身同类型对象相比较的类型，&lt;code&gt;extends&lt;/code&gt;可以解释为&amp;rdquo;实现某功能&amp;rdquo;。&lt;/p&gt;

&lt;p&gt;总之，泛型方法就像泛型一样，使用起来比要求客户端转换输入参数并返回值的方法来的更加安全，也更加容易。就像类型一样，你应该确保新的方法可以不用转换就能使用，这通常意味着要将它们泛型化。并且就像类型一样，还应该将现有的方法泛型化，使新用户使用起来更加轻松，且不会破坏现有的客户端。&lt;/p&gt;

&lt;h5&gt;28.利用有限制通配符来提升API的灵活性&lt;/h5&gt;

&lt;p&gt;为了获得最大限度的灵活性，要在表示生产者或者消费者的输入参数上使用通配符类型。&lt;code&gt;PESC&lt;/code&gt;表示&lt;code&gt;producter-extends&lt;/code&gt;, &lt;code&gt;consumer-super&lt;/code&gt;。如果参数化类型表示一个&lt;code&gt;T&lt;/code&gt;生产者，就使用&lt;code&gt;&amp;lt;? extends T&amp;gt;&lt;/code&gt;；如果它表示一个&lt;code&gt;T&lt;/code&gt;消费者，就使用&lt;code&gt;&amp;lt;? super T&amp;gt;&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;如果类型参数只在方法声明中出现一次，就可以用通配符取代它。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public static void swap(List&amp;lt;?&gt; list, int i, int j) {  
    swapHelper(list, i, j);  
}  
  
private static &amp;lt;E&gt; void swapHelper(List&amp;lt;E&gt; list, int i, int j) {  
    list.set(i, list.set(j, list.get(i)));  
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;所有的&lt;code&gt;comparable&lt;/code&gt;和&lt;code&gt;comparator&lt;/code&gt;都是消费者。&lt;/strong&gt;&lt;/p&gt;

&lt;h5&gt;29.优先考虑类型安全的异构容器&lt;/h5&gt;

&lt;p&gt;有时会需要未限定固定数目的类型参数的容器，此时，可以将容器的键进行参数化而不是将容器参数化。然后将参数化的键交给容器来插入或者获得值。用泛型系统来确保值的类型和它的键相符。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public class ManyTypeClass
{
    //一个存放数据的容器,由于键值类型是不确定的，那么值类型就只能是object类型
    private Map&amp;lt;Class&amp;lt;?&gt;, Object&gt; data = new HashMap&amp;lt;Class&amp;lt;?&gt;, Object&gt;();
    
    public &amp;lt;T&gt; void putData(Class&amp;lt;T&gt; type, T instance)
    {
        //吧数据放入进去
        if(type != null)
        {
            //这里需要进行一下类型转换，通过Class的cast方法，因为泛型是基于擦除的，如果不进行验证的话，那么插入的数据到底是不是真的type类型就无法确定的了
            data.put(type, type.cast(instance));
        }
    }
    
    public &amp;lt;T&gt; T getDate(Class&amp;lt;T&gt; type)
    {
        //取出数据,由于数据存放进去是object类型的，那么取出来的时候就需要进行一次类型转换
        return type.cast(data.get(type));
    }
    
    public static void main(String[] args)
    {
        ManyTypeClass mtc = new ManyTypeClass();
        mtc.putData(String.class, &#34;cutter_point&#34;);
        mtc.putData(Integer.class, 0xcab145de);
        mtc.putData(Class.class, ManyTypeClass.class);
        mtc.putData(ManyTypeClass.class, mtc);
        
        String dataString = mtc.getDate(String.class);
        int dataInteger = mtc.getDate(Integer.class);
        Class&amp;lt;?&gt; dataClass = mtc.getDate(Class.class);
        ManyTypeClass mtc1 = mtc.getDate(ManyTypeClass.class);
        
        System.out.printf(&#34;%s %x %s %s%n&#34;, dataString,
                dataInteger, dataClass.getName(), mtc1.getDate(String.class));
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;不能存储&lt;code&gt;List&amp;lt;String&amp;gt;&lt;/code&gt;。因为在运行时他们的类型会被擦除，所以&lt;code&gt;List&amp;lt;String&amp;gt;&lt;/code&gt;与&lt;code&gt;List&amp;lt;Integer&amp;gt;&lt;/code&gt;实际上是共用一个Class。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Effective Java ReadingNotes 3</title>
      <link>http://ggao.me/post/readingnotes/effective-java-readingnotes3/</link>
      <pubDate>Fri, 16 Dec 2016 14:18:24 +0800</pubDate>
      
      <guid>http://ggao.me/post/readingnotes/effective-java-readingnotes3/</guid>
      <description>&lt;p&gt;Effective Java阅读笔记(3)&lt;/p&gt;

&lt;p&gt;Effective Java第4章 类和接口&lt;/p&gt;

&lt;h5&gt;13.使类和成员的可访问性最小化&lt;/h5&gt;

&lt;p&gt;访问控制机制决定了类、接口和成员的可访问性。实体的可访问性是由该实体声明所在的位置，以及该实体声明中所出现的访问修饰符共同决定的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;尽可能地使每个类或者成员不被外界访问。&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;私有的&lt;code&gt;private&lt;/code&gt;——只有在声明该成员的顶层类内部才可以访问这个成员。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;包级私有的&lt;code&gt;package-private/default&lt;/code&gt;——声明该成员的包内部的任何类都可以访问这个成员。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;受保护的&lt;code&gt;protected&lt;/code&gt;——声明该成员的类的子类可以访问这个成员（有一些限制），并且，声明该成员的包内部的任何类也可以访问这个成员。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;公有的&lt;code&gt;public&lt;/code&gt;——在任何地方都可以访问该成员。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;实现了Serializable接口的类的私有域可能会被“泄漏”。&lt;/p&gt;

&lt;p&gt;子类中的访问级别不允许低于超类中的访问级别。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;除了公有静态final域的特殊情形之外，公有类都不应该包含公有域。&lt;/strong&gt;&lt;/p&gt;

&lt;h5&gt;14.在公有类中使用访问方法而非公有域&lt;/h5&gt;

&lt;p&gt;如果类可以在它所在的包的外部进行访问，就提供访问方法。公有类永远都不应该暴露可变的域。但是，有时候会需要用包级私有的或者私有的嵌套类来暴露域，无论这个类是可变还是不可变的。&lt;/p&gt;

&lt;h5&gt;15.使可变性最小化&lt;/h5&gt;

&lt;p&gt;不可变类只是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供，并在对象的整个生命周期内固定不变。不可变类比可变类更加易于设计、实现和使用。它们不容易出错，且更加安全。&lt;/p&gt;

&lt;p&gt;为了使类成为不可变，要遵循下面五条规则：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;不要提供任何会修改对象状态的方法。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;保证类不会被扩展。一般使类成为&lt;code&gt;final&lt;/code&gt;，但后面还会讨论其他做法。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;使所有的域都是&lt;code&gt;final&lt;/code&gt;的。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;使所有的域都成为私有的。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;确保对于任何可变组件的互斥访问。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;不可变对象本质上是线程安全的，它们不要求同步；不仅可以共享不可变对象，甚至也可以共享它们的内部信息；不可以对象为其他对象提供了大量的构件。&lt;strong&gt;不可变类真正唯一的缺点是，对于每个不同的值都需要一个单独的对象。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;让不可变的类变成&lt;code&gt;final&lt;/code&gt;的另一种方法就是，让类的所有构造器都变成私有的或者包级私有的，并添加公有的静态工厂来代替公有的构造器。&lt;/p&gt;

&lt;h5&gt;*16.复合优先于继承&lt;/h5&gt;

&lt;p&gt;继承打破了封装性，子类依赖于其超类中特定功能的实现细节。只有当子类和超类之间确实存在子类型关系时，使用继承才是恰当的。&lt;/p&gt;

&lt;p&gt;为了避免继承导致的脆弱性，我们可以用复合和转发机制来代替继承，尤其是当存在适当的接口可以实现包装类的时候。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public class ForwardingSet&lt;E&gt; implements Set&lt;E&gt; {
    private final Set&lt;E&gt; s;

    public ForwardingSet(Set&lt;E&gt; s) {
        this.s = s;
    }

    public void clear() {
        s.clear();
    }

    public boolean contains(Object o) {
        return s.contains(o);
    }

    public boolean isEmpty() {
        return s.isEmpty();
    }

    public int size() {
        return s.size();
    }

    public Iterator&lt;E&gt; iterator() {
        return s.iterator();
    }

    public boolean add(E e) {
        return s.add(e);
    }

    public boolean remove(Object o) {
        return s.remove(o);
    }

    public boolean containsAll(Collection&lt;?&gt; c) {
        return s.containsAll(c);
    }

    public boolean addAll(Collection&lt;? extends E&gt; c) {
        return s.addAll(c);
    }

    public boolean removeAll(Collection&lt;?&gt; c) {
        return s.removeAll(c);
    }

    public boolean retainAll(Collection&lt;?&gt; c) {
        return s.retainAll(c);
    }

    public Object[] toArray() {
        return s.toArray();
    }

    public &lt;T&gt; T[] toArray(T[] a) {
        return s.toArray(a);
    }

    @Override
    public boolean equals(Object o) {
        return s.equals(o);
    }

    @Override
    public int hashCode() {
        return s.hashCode();
    }

    @Override
    public String toString() {
        return s.toString();
    }
}

public class InstrumentedSet&lt;E&gt; extends ForwardingSet&lt;E&gt; {
    private int addCount = 0;

    public InstrumentedSet(Set&lt;E&gt; s) {
        super(s);
    }

    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection&lt;? extends E&gt; c) {
        addCount += c.size();
        return super.addAll(c);
    }

    public int getAddCount() {
        return addCount;
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;17.要么为继承而设计，并提供文档说明，要么就禁止继承&lt;/h5&gt;

&lt;p&gt;为继承而设计的类必须有文档说明它可覆盖&lt;code&gt;overridable&lt;/code&gt;的方法的自用性，且必须通过某种形式提供适当的钩子&lt;code&gt;hook&lt;/code&gt;，以便能够进入到它的内部工作流程中，这种形式可以使精心选择的受保护&lt;code&gt;protected&lt;/code&gt;的方法，也可以是受保护的域，后者比较少见。该类的唯一的测试方法就是编写子类。&lt;/p&gt;

&lt;p&gt;为了允许继承，类还必须遵守其他一些约束。&lt;strong&gt;构造器决不能调用可被覆盖的方法，无论是直接调用还是间接调用。&lt;/strong&gt;&lt;/p&gt;

&lt;h5&gt;18.接口优于抽象类&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;现有的类可以很容易被更新，以实现新的接口。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;接口是定义&lt;code&gt;mixin(混合类型)&lt;/code&gt;的理想选择。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;接口允许我们构造非层次结构的类型框架。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;抽象类的演变比接口的演变要容易得多。&lt;strong&gt;接口一旦被公开发行，并且已被广泛实现，再想改变这个接口几乎是不可能的。&lt;/strong&gt;&lt;/p&gt;

&lt;h5&gt;19.接口只用于定义类型&lt;/h5&gt;

&lt;p&gt;接口应该只被用来定义类型，不应该被用来导出常量，导出常量可以使用枚举类型和不可实例化的工具类。&lt;/p&gt;

&lt;h5&gt;20.类层次优于标签类&lt;/h5&gt;

&lt;p&gt;标签类过于冗长、容易出错，并且效率低下。标签类很少有适用的时候，当你想要编写一个包含显式标签域的类时，应该考虑一下这个标签是否可以被取消，这个类是否可以使用类层次来代替。&lt;/p&gt;

&lt;p&gt;以下为一个标签类：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;class Figure {  
    enum Shape { RECTANGLE, CIRCLE };  
  
    // Tag field - the shape of this figure  
    final Shape shape;  
  
    // These fields are used only if shape is RECTANGLE  
    double length;  
    double width;  
  
    // This field is used only if shape is CIRCLE  
    double radius;  
  
    // Constructor for circle  
    Figure(double radius) {  
        shape = Shape.CIRCLE;  
        this.radius = radius;  
    }  
  
    // Constructor for rectangle  
    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();  
        }  
    }  
}&lt;/code&gt;&lt;/pre&gt;  

&lt;p&gt;修改后：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;abstract class Figure {  
    abstract double area();  
}  
class Circle extends Figure {  
    final double radius;  
  
    Circle(double radius) { this.radius = radius; }  
  
    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;  
    }  
    double area() { return length * width; }  
}  
class Square extends Rectangle {  
    Square(double side) {  
        super(side, side);  
    }  
}&lt;/code&gt;&lt;/pre&gt;  

&lt;h5&gt;21.用函数对象表示策略&lt;/h5&gt;

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

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;Arrays.sort(StringArray,new Comparator&lt;String&gt;(){
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
});&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public class Host {
    private static class StrLenCmp implements Comparator&lt;String&gt;,Serializable{
 
        @Override
        public int compare(String s1, String s2) {
            return s1.length() - s2.length();
        }
    }
     
    public static final Comparator&lt;String&gt; STRING_LENGTH_COMPARATOR = new StrLenCmp();
     
    public static void main(String[] args) {
        String s1 = &#34;&#34;;
        String s2 = &#34;&#34;;
        int result = Host.STRING_LENGTH_COMPARATOR.compare(s1, s2);
        System.out.println(result);
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;22.优先考虑静态成员类&lt;/h5&gt;

&lt;p&gt;嵌套类是指被定义在另一个类的内部的类。嵌套类存在的目的应该只是为他的外围类提供服务。如果嵌套类将来可能会用于其他的某个环境中，他就应该是顶层类。嵌套类有四种：静态成员类、非静态成员类、匿名类和局部类。除了第一种之外，其他三种都称为内部类。&lt;/p&gt;

&lt;p&gt;从语法上讲，静态成员类和非静态成员类之间的唯一区别是，静态成员类的声明中包含修饰符&lt;code&gt;static&lt;/code&gt;。尽管他们的语法非常相似，但是两种嵌套类有很大的不同。非静态成员类的每个实例都隐含着与外围类的一个外围实例相关联。在非静态成员类的实例方法内部，可以调用外围实例上的方法，或者利用修改过的&lt;code&gt;this&lt;/code&gt;构造获得外围实例的引用。如果嵌套类的实例可以在他外围类的实力之外独立存在，这个嵌套类就必须是静态成员类，在没有外围实例的情况下，要想创建非静态成员类的实例是不可能的。&lt;/p&gt;

&lt;p&gt;当非静态成员类的实例被创建的时候，他和外围之间的关联关系也随之被创立起来；而且，这种关联关系以后不能被修改。通常情况下，当在外围类的某个实例方法的内部调用非静态成员类的构造器时，这种关系被自动建立起来。
使用表达式enclosingInstance.new MemberClass(args)来手工建立这种关联关系也是有可能的，但是很少使用。正如你所预料的那样，这种关联关系需要消耗费静态成员类的实例空间，并且构造的时间开销。&lt;/p&gt;

&lt;p&gt;如果声明成员类不要求访问外围实例，就要始终把&lt;code&gt;static&lt;/code&gt;修饰符放在他的声明中。&lt;/p&gt;

&lt;p&gt;匿名类的三种常见用法：1.动态的创建函数对象，如&lt;code&gt;sort&lt;/code&gt;方法的调用利用了匿名的&lt;code&gt;Comparator&lt;/code&gt;实例；2.创建过程对象，如&lt;code&gt;Runnable&lt;/code&gt;、&lt;code&gt;Thread&lt;/code&gt;或者&lt;code&gt;TimerTask&lt;/code&gt;实例；3.在静态工厂方法的内部使用。&lt;/p&gt;

&lt;p&gt;如果一个嵌套类需要在单个方法之外仍然是可见的，或者他太长了，不适合方法内部，就应该使用成员类。如果成员类的每个实例都需要一个指向其外围实例的引用，就要把成员类做成非静态的；否则就做成静态的。假设这个嵌套类属于一个方法的内部，如果你需要在一个地方创建实例，并且已经有了一个预置的类型可以说明这个类的特征，就把他做成匿名类；否则，就做成局部类。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Effective Java ReadingNotes 2</title>
      <link>http://ggao.me/post/readingnotes/effective-java-readingnotes2/</link>
      <pubDate>Thu, 15 Dec 2016 19:18:24 +0800</pubDate>
      
      <guid>http://ggao.me/post/readingnotes/effective-java-readingnotes2/</guid>
      <description>&lt;p&gt;Effective Java阅读笔记(2)&lt;/p&gt;

&lt;p&gt;Effective Java第3章 对于所有对象都通用的方法&lt;/p&gt;

&lt;h5&gt;8.覆盖equals时请遵守通用约定&lt;/h5&gt;

&lt;p&gt;覆盖&lt;code&gt;Object.equals&lt;/code&gt;的时机：类具有自己特有的“逻辑相等”概念（不同于对象等同的概念），而且超类还没有覆盖&lt;code&gt;equals&lt;/code&gt;以实现期望的行为。这通常属于“值类”的情形，有一种“值类”不需要覆盖&lt;code&gt;equals&lt;/code&gt;方法，即用实例受控确保“每个值至多只存在一个对象”的类，比如枚举类型。&lt;/p&gt;

&lt;p&gt;在覆盖&lt;code&gt;equals&lt;/code&gt;方法的时候，你必须要遵守它的通用约定：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;自反性。对于任何非&lt;code&gt;null&lt;/code&gt;的引用值&lt;code&gt;x&lt;/code&gt;,&lt;code&gt;x.equals(x)&lt;/code&gt;必须返回true。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;对称性。对于任何非&lt;code&gt;null&lt;/code&gt;的引用值&lt;code&gt;x&lt;/code&gt;和&lt;code&gt;y&lt;/code&gt;，当且仅当&lt;code&gt;y.equals(x)&lt;/code&gt;返回&lt;code&gt;true&lt;/code&gt;时，&lt;code&gt;x.equals(y)&lt;/code&gt;必须返回&lt;code&gt;true&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;传递性。对于任何非&lt;code&gt;null&lt;/code&gt;的引用值&lt;code&gt;x、y和z&lt;/code&gt;，如果&lt;code&gt;x.equals(y)&lt;/code&gt;返回&lt;code&gt;true&lt;/code&gt;，并且&lt;code&gt;y.equals(z)&lt;/code&gt;也返回&lt;code&gt;true&lt;/code&gt;，那么&lt;code&gt;x.equals(z)&lt;/code&gt;也必须返回true。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;一致性。对于任何非&lt;code&gt;null&lt;/code&gt;的引用值&lt;code&gt;x和y&lt;/code&gt;，只要&lt;code&gt;equals&lt;/code&gt;的比较操作在对象中所用的信息没有被修改，多次调用&lt;code&gt;x.equals(y)&lt;/code&gt;就会一致地返回&lt;code&gt;true&lt;/code&gt;，或者一致地返回&lt;code&gt;false&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;对于任何非null的引用值&lt;code&gt;x&lt;/code&gt;,&lt;code&gt;x,equals(null)&lt;/code&gt;必须返回false。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;实现高质量&lt;code&gt;equals&lt;/code&gt;方法的诀窍：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;使用&lt;code&gt;==&lt;/code&gt;操作符检查“参数是否为这个对象的引用”。如果是，则返回&lt;code&gt;true&lt;/code&gt;。这只不过是一种性能优化，如果比较操作有可能很昂贵，就值得这么做。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;使用&lt;code&gt;instanceof&lt;/code&gt;操作符检查“参数是否为正确的类型”。如果不是，则返回&lt;code&gt;false&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;把参数转换成正确的类型。因为转换前进行过&lt;code&gt;instanceof&lt;/code&gt;测试，所以确保会成功。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;对于该类中的每个“关键”域，检查参数中的域是否与该对象中对应的域相匹配。如果这些测试全部成功，则返回&lt;code&gt;true&lt;/code&gt;，否则返回&lt;code&gt;false&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;对于既不是&lt;code&gt;float&lt;/code&gt;也不是&lt;code&gt;double&lt;/code&gt;类型的基本类型域，可以使用&lt;code&gt;==&lt;/code&gt;操作符进行比较，对于对象引用域，可以递归地调用&lt;code&gt;equals&lt;/code&gt;方法；对于&lt;code&gt;float&lt;/code&gt;域，可以使用&lt;code&gt;Float.compare&lt;/code&gt;方法；对于&lt;code&gt;double&lt;/code&gt;域，则使用&lt;code&gt;Double.compare&lt;/code&gt;。&lt;/p&gt;

&lt;h5&gt;9.覆盖equals时总要覆盖hashCode&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;Object.hashCode&lt;/code&gt;的通用约定：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;在应用程序的执行期间，只要对象的&lt;code&gt;equals&lt;/code&gt;方法的比较操作所用到的信息没有被修改，那么对这同一个对象调用多次,&lt;code&gt;hashCode&lt;/code&gt;方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中，每次执行所返回的整数可以不一致。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如果两个对象根据&lt;code&gt;equals(Object)&lt;/code&gt;方法比较是相等的，那么调用这两个对象中任意一个对象的&lt;code&gt;hashCode&lt;/code&gt;方法都必须产生同样的整数结果。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如果两个对象根据&lt;code&gt;equals(Object)&lt;/code&gt;方法比较是不相等的，那么调用这两个对象中任意一个对象的&lt;code&gt;hashCode&lt;/code&gt;方法，则不一定要产生不同的整数结果。但是程序员应该知道，给不相等的对象产生截然不同的整数结果，有可能提高散列表的性能。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;10.始终要覆盖toString&lt;/h5&gt;

&lt;p&gt;提供好的&lt;code&gt;toString&lt;/code&gt;实现可以使类用起来更加舒适，在实际应用中，&lt;code&gt;toString&lt;/code&gt;方法应该返回对象中包含的所有值得关注的信息。&lt;/p&gt;

&lt;h5&gt;11.谨慎地覆盖clone&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;Clone&lt;/code&gt;方法的通用约定：
对于任何对象&lt;code&gt;x&lt;/code&gt;，表达式&lt;code&gt;x.clone() != x&lt;/code&gt; 将会是&lt;code&gt;true&lt;/code&gt;，并且，表达式&lt;code&gt;x.clone().getClass() == x.getClass()&lt;/code&gt;将会是&lt;code&gt;true&lt;/code&gt;，但这些都不是绝对的要求。虽然通常情况下，表达式&lt;code&gt;x.clone().equals(x)&lt;/code&gt;将会是&lt;code&gt;true&lt;/code&gt;，但是，这也不是一个绝对的要求。拷贝对象往往会导致创建它的类的一个新实例，但它同时也会要求拷贝内部的数据结构。这个过程中没有调用构造器。&lt;/p&gt;

&lt;p&gt;所有实现了&lt;code&gt;Cloneable&lt;/code&gt;接口的类都应该用一个公有的方法覆盖&lt;code&gt;clone&lt;/code&gt;。此公有方法首先调用&lt;code&gt;super.clone&lt;/code&gt;，然后修正任何需要修正的域。一般情况下，这意味着要拷贝任何包含内部“深层结构”的可变对象，并用指向新对象的引用代替原来指向这些对象的引用。虽然，这些内部拷贝操作往往可以通过递归地调用&lt;code&gt;clone&lt;/code&gt;来完成，但这通常并不是最佳方法。如果该类只包含基本类型的域，或者指向不可变对象的引用，那么多半的情况是没有域需要修正。这条规则也有例外，譬如，代表序列号或其他唯一ID值得域，或者代表对象的创建时间的域，不管这些域是基本类型还是不可变的，它们也都需要被修正。&lt;/p&gt;

&lt;p&gt;另一个实现对象拷贝的好办法是提供一个拷贝构造器或拷贝工厂。拷贝构造器只是一个构造器，它唯一的参数类型是包含该构造器的类。如&lt;code&gt;public Go(Go go);&lt;/code&gt;，拷贝工厂是类似于拷贝构造器的静态工厂&lt;code&gt;public static Go newInstance(Go go);&lt;/code&gt;&lt;/p&gt;

&lt;h5&gt;12.考虑实现Comparable接口&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;compareTo&lt;/code&gt;方法的通用约定：&lt;/p&gt;

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

&lt;p&gt;在下面的说明中，符号&lt;code&gt;sgn&lt;/code&gt;表示数学中的&lt;code&gt;signum&lt;/code&gt;函数，它根据表达式的值为负值、零和正值，分别返回-1、0或1。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;实现者必须确保所有的&lt;code&gt;x&lt;/code&gt;和&lt;code&gt;y&lt;/code&gt;都满足&lt;code&gt;sgn(x.compareTo(y)) == -sgn(y.compareTo(x))&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;实现者还必须确保这个比较关系是可传递的。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;最后，实现者必须确保&lt;code&gt;x.compareTo(y) == 0&lt;/code&gt;暗示着所有的&lt;code&gt;z&lt;/code&gt;都满足&lt;code&gt;sgn(x.compareTo(z)) == sgn(y.compareTo(z))&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;强烈建议&lt;code&gt;(x.compareTo(y) == 0) == (x.equals(y))&lt;/code&gt;，但这并非绝对必要。一般说来，任何实现了Comparable接口的类，若违反了这个条件，都应该明确予以说明。推荐使用这样的说法：“注意：该类具有内在的排序功能，但是与equals不一致。”&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Effective Java ReadingNotes 1</title>
      <link>http://ggao.me/post/readingnotes/effective-java-readingnotes1/</link>
      <pubDate>Thu, 15 Dec 2016 16:16:24 +0800</pubDate>
      
      <guid>http://ggao.me/post/readingnotes/effective-java-readingnotes1/</guid>
      <description>&lt;p&gt;Effective Java阅读笔记(1)&lt;/p&gt;

&lt;p&gt;Effective Java第2章 创建和销毁对象&lt;/p&gt;

&lt;h5&gt;1.考虑用静态工厂方法代替构造器&lt;/h5&gt;

&lt;p&gt;静态工厂方法相较于构造器有四大优势：（1）它们有名称；（2）不必在每次调用它们的时候都创建一个新对象；（3）它们可以返回原返回类型的任何子类型的对象；（4）在创建参数化类型实例时，它们使代码变得更加简洁。两大缺点：（1）类如果不含公有的或受保护的构造器，就不能被子类化；（2）它们与其他的静态方法实际上并没有任何区别。&lt;/p&gt;

&lt;h5&gt;2.遇到多个构造器参数时要考虑用构建器&lt;/h5&gt;

&lt;p&gt;一个很直观的例子：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;

    public static class Builder {
        private final int servingSize;
        private final int servings;

        private int calories = 0;
        private int fat = 0;

        public Builder(int servingSize, int servings){
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val){
            calories = val;
            return this;
        }

        public Builder fat(int val){
            fat = val;
            return this;
        }

        public NutritionFacts build(){
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder){
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;当需要创建一个实例时只需根据自己的需要添加参数&lt;code&gt;NutritionFacts cocaCola = new Builder(240,8).calories(100).build();&lt;/code&gt;。&lt;/p&gt;

&lt;h5&gt;3.用私有构造器或者枚举类型强化Singleton属性&lt;/h5&gt;

&lt;p&gt;除了非常常见的两种单例实现方法外，在Java 1.5之后我们有了一个更好的选择，使用单元素枚举类型：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public enum SingleTon {
    INSTANCE;
    public void orz() {
        //blablabla
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后如此调用&lt;code&gt;SingleTon s1 = SingleTon.INSTANCE;s1.orz();&lt;/code&gt;&lt;/p&gt;

&lt;h5&gt;4.通过私有构造器强化不可实例化的能力&lt;/h5&gt;

&lt;p&gt;对于一些只包含静态方法和静态域的工具类，我们不希望它被实例化，可以通过以下方式进行强化：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public class UtilityClass{
    //Suppress default constructor for moninstantiability
    private UtilityClass(){
        throw new AssertionError();
    }
    ...
}&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;5.避免创建不必要的对象&lt;/h5&gt;

&lt;p&gt;一般来说，最好能重用对象而不是在每次需要的时候就创建一个相同功能的新对象。&lt;/p&gt;

&lt;p&gt;对于可重用的对象可以通过使用一个静态的初始化器&lt;code&gt;static{}&lt;/code&gt;来创建以避免重复创建，要优先使用基本类型而不是装箱基本类型，要当心无意识的自动装箱。维护自己的对象池来避免创建对象并不是一种好的做法，除非池中的对象是非常重量级的，其中一个典型示例为数据库连接池。&lt;/p&gt;

&lt;h5&gt;6.消除过期的对象引用&lt;/h5&gt;

&lt;p&gt;要警惕内存泄漏问题，及时清除过期引用，使用合适的数据结构保存缓存、监听器以及其他回调，例如&lt;code&gt;WeakReference&lt;/code&gt;。&lt;/p&gt;

&lt;h5&gt;7.避免使用终结方法&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;finalizer&lt;/code&gt;通常是不可预测的，也是很危险的，一般情况下是不必要的，并且该方法会导致严重的性能损失。如果类的对象中封装的资源确实需要终止，我们应该提供一个显式的终止方法，并要求该类的客户端在每个实例不再有用的时候调用这个方法。终结方法有两种合法用途：1、充当&amp;rdquo;安全网&amp;rdquo;；2、终止非关键的本地资源。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Android Memory Leak</title>
      <link>http://ggao.me/post/android/android-memory-leak/</link>
      <pubDate>Wed, 14 Dec 2016 18:16:24 +0800</pubDate>
      
      <guid>http://ggao.me/post/android/android-memory-leak/</guid>
      <description>&lt;p&gt;Android内存泄漏总结&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;h4&gt;基础知识&lt;/h4&gt;
&lt;br&gt;
&lt;h5&gt;1)泄露的对象&lt;/h5&gt;&lt;/p&gt;

&lt;p&gt;首先我们来了解程序运行时，所需内存的分配策略：&lt;/p&gt;

&lt;p&gt;按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的，对应的，三种存储策略使用的内存空间主要分别是静态存储区（也称方法区）、堆区和栈区。他们的功能不同，对他们使用方式也就不同。&lt;/p&gt;

&lt;p&gt;静态存储区（方法区）：内存在程序编译的时候就已经分配好，这块内存在程序整个运行期间都存在。它主要存放静态数据、全局&lt;code&gt;static&lt;/code&gt;数据和常量。&lt;/p&gt;

&lt;p&gt;栈区：在执行函数时，函数内局部变量的存储单元都可以在栈上创建，函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中，效率很高，但是分配的内存容量有限。&lt;/p&gt;

&lt;p&gt;堆区：亦称动态内存分配。程序在运行的时候用&lt;code&gt;malloc&lt;/code&gt;或&lt;code&gt;new&lt;/code&gt;申请任意大小的内存，程序员自己负责在适当的时候用&lt;code&gt;free&lt;/code&gt;或&lt;code&gt;delete&lt;/code&gt;释放内存（&lt;code&gt;JAVA&lt;/code&gt;则依赖垃圾回收器）。动态内存的生存期可以由我们决定，如果我们不释放内存，程序将在最后才释放掉动态内存。 但是，良好的编程习惯是：如果某动态内存不再使用，需要将其释放掉。&lt;/p&gt;

&lt;p&gt;接下来我们集中说下堆和栈的区别：&lt;/p&gt;

&lt;p&gt;在函数中（说明是局部变量）定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时，&lt;code&gt;JAVA&lt;/code&gt;就在栈中为这个变量分配内存空间，当超过变量的作用域后，&lt;code&gt;JAVA&lt;/code&gt;会自动释放掉为该变量分配的内存空间，该内存空间可以立刻被另作他用。&lt;/p&gt;

&lt;p&gt;堆内存用于存放所有由&lt;code&gt;new&lt;/code&gt;创建的对象（内容包括该对象其中的所有成员变量）和数组。在堆中分配的内存，由&lt;code&gt;JAVA&lt;/code&gt;虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后，还可以在栈中定义一个特殊的变量，这个变量的取值等于数组或者对象在堆内存中的首地址，在栈中的这个特殊的变量就变成了数组或者对象的引用变量，以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象，引用变量相当于为数组或者对象起的一个别名，或者代号。&lt;/p&gt;

&lt;p&gt;堆是不连续的内存区域（因为系统是用链表来存储空闲内存地址，自然不是连续的），堆大小受限于计算机系统中有效的虚拟内存（&lt;code&gt;32bit&lt;/code&gt;系统理论上是4G），所以堆的空间比较灵活，比较大。栈是一块连续的内存区域，大小是操作系统预定好的，&lt;code&gt;windows&lt;/code&gt;下栈大小是&lt;code&gt;2M&lt;/code&gt;（也有是&lt;code&gt;1M&lt;/code&gt;，在编译时确定，&lt;code&gt;VC&lt;/code&gt;中可设置）。&lt;/p&gt;

&lt;p&gt;对于堆，频繁的&lt;code&gt;new/delete&lt;/code&gt;会造成大量内存碎片，使程序效率降低。对于栈，它是先进后出的队列，进出一一对应，不产生碎片，运行效率稳定高。&lt;/p&gt;

&lt;h5&gt;结论：&lt;/h5&gt;&lt;br&gt;

&lt;strong&gt;局部变量的基本数据类型和引用存储于栈中，引用的对象实体存储于堆中。&lt;br&gt;——因为它们属于方法中的变量，生命周期随方法而结束。&lt;br&gt;成员变量全部存储与堆中（包括基本数据类型，引用和引用的对象实体）&lt;br&gt;——因为它们属于类，类对象终究是要被&lt;code&gt;new&lt;/code&gt;出来使用的。&lt;br&gt;我们这里说的内存泄露，是针对，也只针对堆内存，他们存放的就是引用指向的对象实体。&lt;/strong&gt;&lt;/font&gt;&lt;br&gt;&lt;br&gt;

&lt;h5&gt;2)泄露的原因&lt;/h5&gt;

&lt;p&gt;为了判断&lt;code&gt;JAVA&lt;/code&gt;中是否有内存泄露，我们首先必须了解&lt;code&gt;JAVA&lt;/code&gt;是如何管理（堆）内存的。&lt;code&gt;JAVA&lt;/code&gt;的内存管理就是对象的分配和释放问题。在&lt;code&gt;JAVA&lt;/code&gt;中，内存的分配是由程序完成的，而内存的释放是由垃圾收集器&lt;code&gt;Garbage Collection,GC&lt;/code&gt;完成的，程序员不需要通过调用函数来释放内存，但它只能回收无用并且不再被其它对象引用的那些对象所占用的空间。&lt;/p&gt;

&lt;p&gt;Java的内存垃圾回收机制是从程序的主要运行对象(如静态对象/寄存器/栈上指向的堆内存对象等)开始检查引用链，当遍历一遍后得到上述这些无法回收的对象和他们所引用的对象链，组成无法回收的对象集合，而其他孤立对象（集）就作为垃圾回收。&lt;code&gt;GC&lt;/code&gt;为了能够正确释放对象，必须监控每一个对象的运行状态，包括对象的申请、引用、被引用、赋值等，&lt;code&gt;GC&lt;/code&gt;都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象，而释放对象的根本原则就是该对象不再被引用。&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;JAVA&lt;/code&gt;中，这些无用的对象都由&lt;code&gt;GC&lt;/code&gt;负责回收，因此程序员不需要考虑这部分的内存泄露。虽然，我们有几个函数可以访问&lt;code&gt;GC&lt;/code&gt;，例如运行&lt;code&gt;GC&lt;/code&gt;的函数&lt;code&gt;System.gc()&lt;/code&gt;，但是根据&lt;code&gt;JAVA&lt;/code&gt;语言规范定义，该函数不保证&lt;code&gt;JVM&lt;/code&gt;的垃圾收集器一定会执行。因为不同的&lt;code&gt;JVM&lt;/code&gt;实现者可能使用不同的算法管理&lt;code&gt;GC&lt;/code&gt;。通常&lt;code&gt;GC&lt;/code&gt;的线程的优先级别较低。&lt;code&gt;JVM&lt;/code&gt;调用&lt;code&gt;GC&lt;/code&gt;的策略也有很多种，有的是内存使用到达一定程度时，&lt;code&gt;GC&lt;/code&gt;才开始工作，也有定时执行的，有的是平缓执行&lt;code&gt;GC&lt;/code&gt;，有的是中断式执行&lt;code&gt;GC&lt;/code&gt;。但通常来说，我们不需要关心这些。&lt;/p&gt;

&lt;p&gt;至此，我们来看看&lt;code&gt;JAVA&lt;/code&gt;中需要被回收的垃圾：
&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;｛
Person p1 = new Person();
……
｝&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;引用句柄&lt;code&gt;p1&lt;/code&gt;的作用域是从定义到&lt;code&gt;}&lt;/code&gt;处，执行完这对大括号中的所有代码后，产生的&lt;code&gt;Person&lt;/code&gt;对象就会变成垃圾，因为引用这个对象的句柄&lt;code&gt;p1&lt;/code&gt;已超过其作用域，&lt;code&gt;p1&lt;/code&gt;失效，在栈中被销毁，因此堆上的&lt;code&gt;Person&lt;/code&gt;对象不再被任何句柄引用了。 因此&lt;code&gt;person&lt;/code&gt;变为垃圾，会被回收。&lt;/p&gt;

&lt;p&gt;从上面的例子和解释，可以看到一个很关键的词：引用。&lt;/p&gt;

&lt;p&gt;通俗的讲，通过&lt;code&gt;A&lt;/code&gt;能调用并访问到&lt;code&gt;B&lt;/code&gt;，那就说明&lt;code&gt;A&lt;/code&gt;持有&lt;code&gt;B&lt;/code&gt;的引用，或&lt;code&gt;A&lt;/code&gt;就是&lt;code&gt;B&lt;/code&gt;的引用，&lt;code&gt;B&lt;/code&gt;的引用计数&lt;code&gt;+1&lt;/code&gt;。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;比如&lt;code&gt;Person p1 = new Person();&lt;/code&gt;通过&lt;code&gt;p1&lt;/code&gt;能操作&lt;code&gt;Person&lt;/code&gt;对象，因此&lt;code&gt;p1&lt;/code&gt;是&lt;code&gt;Person&lt;/code&gt;的引用；&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;比如类&lt;code&gt;O&lt;/code&gt;中有一个成员变量是&lt;code&gt;I&lt;/code&gt;类对象，因此我们可以使用&lt;code&gt;o.i&lt;/code&gt;的方式来访问I类对象的成员，因此&lt;code&gt;o&lt;/code&gt;持有一个&lt;code&gt;i&lt;/code&gt;对象的引用。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;GC&lt;/code&gt;过程与对象的引用类型是严重相关的，我们来看看&lt;code&gt;JAVA&lt;/code&gt;对引用的分类&lt;code&gt;Strong reference, SoftReference, WeakReference, PhatomReference&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://ggao.me/post/android/reference_table.png&#34; alt=&#34;强弱软虚引用表&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;Android&lt;/code&gt;应用的开发中，为了防止内存溢出，在处理一些占用内存大而且声明周期较长的对象时候，可以尽量应用软引用和弱引用技术。讲多一步，这里的软引用/弱引用一般是做什么的呢？&lt;/p&gt;

&lt;p&gt;软/弱引用可以和一个引用队列&lt;code&gt;ReferenceQueue&lt;/code&gt;联合使用，如果软引用所引用的对象被垃圾回收器回收，&lt;code&gt;Java&lt;/code&gt;虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列可以得知被回收的软/弱引用的对象列表，从而为缓冲器清除已失效的软/弱引用。&lt;/p&gt;

&lt;p&gt;假设我们的应用会用到大量的默认图片，比如应用中有默认的头像，默认游戏图标等等，这些图片很多地方会用到。如果每次都去读取图片，由于读取文件需要硬件操作，速度较慢，会导致性能较低。所以我们考虑将图片缓存起来，需要的时候直接从内存中读取。但是，由于图片占用内存空间比较大，缓存很多图片需要很多的内存，就可能比较容易发生&lt;code&gt;OutOfMemory&lt;/code&gt;异常。这时，我们可以考虑使用软/弱引用技术来避免这个问题发生。以下就是高速缓冲器的雏形:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public class CacheBySoftRef {
    
    // 首先定义一个HashMap，保存软引用对象。

    private Map&lt;String, SoftReference&lt;Bitmap&gt;&gt;imageCache = new HashMap&lt;String, SoftReference&lt;Bitmap&gt;&gt;();

    // 再来定义一个方法，保存Bitmap的软引用到HashMap。

    public void addBitmapToCache(String path) {

        // 强引用的Bitmap对象

        Bitmap bitmap = BitmapFactory.decodeFile(path);

        // 软引用的Bitmap对象

        SoftReference&lt;Bitmap&gt;softBitmap = new SoftReference&lt;Bitmap&gt;(bitmap);

        // 添加该对象到Map中使其缓存

        imageCache.put(path, softBitmap);

    }

    // 获取的时候，可以通过SoftReference的get()方法得到Bitmap对象。

    public Bitmap getBitmapByPath(String path) {

        // 从缓存中取软引用的Bitmap对象

        SoftReference&lt;Bitmap&gt; softBitmap = imageCache.get(path);

        // 判断是否存在软引用

        if (softBitmap == null) {

            return null;

        }

        // 通过软引用取出Bitmap对象，如果由于内存不足Bitmap被回收，将取得空 ，如果未被回收，则可重复使用，提高速度。

        Bitmap bitmap = softBitmap.get();

        return bitmap;

    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用软引用以后，在&lt;code&gt;OutOfMemory&lt;/code&gt;异常发生之前，这些缓存的图片资源的内存空间可以被释放掉的，从而避免内存达到上限，避免&lt;code&gt;Crash&lt;/code&gt;发生。&lt;/p&gt;

&lt;p&gt;如果只是想避免&lt;code&gt;OutOfMemory&lt;/code&gt;异常的发生，则可以使用软引用。如果对于应用的性能更在意，想尽快回收一些占用内存比较大的对象，则可以使用弱引用。&lt;/p&gt;

&lt;p&gt;另外可以根据对象是否经常使用来判断选择软引用还是弱引用。如果该对象可能会经常使用的，就尽量用软引用。如果该对象不被使用的可能性更大些，就可以用弱引用。&lt;/p&gt;

&lt;p&gt;回到我们的问题，为什么内存会泄露？&lt;br&gt;&lt;br&gt;
&lt;strong&gt;堆内存中的长生命周期的对象持有短生命周期对象的强/软引用，尽管短生命周期对象已经不再需要，但是因为长生命周期对象持有它的引用而导致不能被回收，这就是&lt;code&gt;Java&lt;/code&gt;中内存泄露的根本原因。&lt;/strong&gt;
&lt;hr&gt;&lt;/p&gt;

&lt;p&gt;&lt;h4&gt;常见原因及实践&lt;/h4&gt;&lt;br&gt;
&lt;h5&gt;常见原因&lt;/h5&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;集合类&lt;/p&gt;

&lt;p&gt;集合类如果仅仅有添加元素的方法，而没有相应的删除机制，导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性，全局性的 &lt;code&gt;map&lt;/code&gt; 等即有静态引用或 &lt;code&gt;final&lt;/code&gt; 一直指向它)，那么没有相应的删除机制，很可能导致集合所占用的内存只增不减。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;单例模式&lt;/p&gt;

&lt;p&gt;不正确使用单例模式是引起内存泄露的一个常见问题，单例对象在被初始化后将在 &lt;code&gt;JVM&lt;/code&gt; 的整个生命周期中存在(以静态变量的方式)，如果单例对象持有外部对象的引用，那么这个外部对象将不能被 &lt;code&gt;JVM&lt;/code&gt; 正常回收，导致内存泄露。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;ANDROID组件或特殊集合对象的使用&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BraodcastReceiver，ContentObserver，FileObserver，Cursor，Callback&lt;/code&gt;等在 &lt;code&gt;Activity onDestroy&lt;/code&gt; 或者某类生命周期结束之后一定要 &lt;code&gt;unregister&lt;/code&gt; 或者 &lt;code&gt;close&lt;/code&gt; 掉，否则这个 &lt;code&gt;Activity&lt;/code&gt; 类会被 &lt;code&gt;system&lt;/code&gt; 强引用，不会被内存回收。&lt;/p&gt;

&lt;p&gt;不要直接对 &lt;code&gt;Activity&lt;/code&gt; 进行直接引用作为成员变量，如果不得不这么做，请用 &lt;code&gt;private WeakReference mActivity&lt;/code&gt; 来做，相同的，对于&lt;code&gt;Service&lt;/code&gt; 等其他有自己声明周期的对象来说，直接引用都需要谨慎考虑是否会存在内存泄露的可能。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;HANDLER&lt;/p&gt;

&lt;p&gt;要知道，只要 &lt;code&gt;Handler&lt;/code&gt; 发送的 &lt;code&gt;Message&lt;/code&gt; 尚未被处理，则该 &lt;code&gt;Message&lt;/code&gt; 及发送它的 &lt;code&gt;Handler&lt;/code&gt; 对象将被线程 &lt;code&gt;MessageQueue&lt;/code&gt; 一直持有。由于 &lt;code&gt;Handler&lt;/code&gt; 属于 &lt;code&gt;TLS(Thread Local Storage)&lt;/code&gt; 变量, 生命周期和 &lt;code&gt;Activity&lt;/code&gt; 是不一致的。因此这种实现方式一般很难保证跟 &lt;code&gt;View&lt;/code&gt; 或者 &lt;code&gt;Activity&lt;/code&gt; 的生命周期保持一致，故很容易导致无法正确释放。如上所述，&lt;code&gt;Handler&lt;/code&gt; 的使用要尤为小心，否则将很容易导致内存泄露的发生。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;THREAD内存泄露&lt;/p&gt;

&lt;p&gt;线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。比如线程是 &lt;code&gt;Activity&lt;/code&gt; 的内部类，则线程对象中保存了 &lt;code&gt;Activity&lt;/code&gt; 的一个引用，当线程的 &lt;code&gt;run&lt;/code&gt; 函数耗时较长没有结束时，线程对象是不会被销毁的，因此它所引用的老的 &lt;code&gt;Activity&lt;/code&gt; 也不会被销毁，因此就出现了内存泄露的问题。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;一些不良代码造成的内存压力&lt;/p&gt;

&lt;p&gt;有些代码并不造成内存泄露，但是它们，或是对没使用的内存没进行有效及时的释放，或是没有有效的利用已有的对象而是频繁的申请新内存。&lt;/p&gt;

&lt;p&gt;6.1 Bitmap 没调用 recycle()。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Bitmap&lt;/code&gt;对象在不使用时,我们应该先调用 &lt;code&gt;recycle()&lt;/code&gt; 释放内存，然后才它设置为 &lt;code&gt;null&lt;/code&gt;。因为加载 &lt;code&gt;Bitmap&lt;/code&gt; 对象的内存空间，一部分是 &lt;code&gt;java&lt;/code&gt; 的，一部分 &lt;code&gt;C&lt;/code&gt; 的（因为 &lt;code&gt;Bitmap&lt;/code&gt; 分配的底层是通过 &lt;code&gt;JNI&lt;/code&gt; 调用的 )。 而这个 &lt;code&gt;recycle()&lt;/code&gt; 就是针对 &lt;code&gt;C&lt;/code&gt; 部分的内存释放。&lt;/p&gt;

&lt;p&gt;6.2 构造 Adapter 时，没有使用缓存的 convertView。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;优秀实践&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;对&lt;code&gt;activity&lt;/code&gt;等组件的引用应该控制在&lt;code&gt;activity&lt;/code&gt;的生命周期之内；如果不能就考虑使用&lt;code&gt;getApplicationContext&lt;/code&gt;或者&lt;code&gt;getApplication&lt;/code&gt;，以避免&lt;code&gt;activity&lt;/code&gt;被外部长生命周期的对象引用而泄露。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在代码复审的时候关注长生命周期对象：全局性的集合、单例模式的使用、类的&lt;code&gt;static&lt;/code&gt;变量等等。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;尽量不要在静态变量或者静态内部类中使用非静态外部成员变量（包括&lt;code&gt;context&lt;/code&gt;），即使要使用，也要考虑适时把外部成员变量置空；也可以在内部类中使用弱引用来引用外部类的变量。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;Handler&lt;/code&gt;的持有的引用对象最好使用弱引用，资源释放时也可以清空&lt;code&gt;Handler&lt;/code&gt;里面的消息。比如在&lt;code&gt;Activity onStop&lt;/code&gt;或者&lt;code&gt;onDestroy&lt;/code&gt;的时候，取消掉该&lt;code&gt;Handler&lt;/code&gt;对象的&lt;code&gt;Message&lt;/code&gt;和&lt;code&gt;Runnable&lt;/code&gt;，&lt;code&gt;removeCallbacks(Runnable r)&lt;/code&gt;或&lt;code&gt;removeMessages(int what)&lt;/code&gt;或&lt;code&gt;removeCallbacksAndMessages（null）&lt;/code&gt;等。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;线程&lt;code&gt;Runnable&lt;/code&gt;执行耗时操作，注意在页面返回时及时取消或者把&lt;code&gt;Runnable&lt;/code&gt;写成静态类:
a) 如果线程类是内部类，改为静态内部类。
b) 线程内如果需要引用外部类对象如&lt;code&gt;context&lt;/code&gt;，需要使用弱引用。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在&lt;code&gt;Java&lt;/code&gt;的实现过程中，也要考虑其对象释放，最好的方法是在不使用某对象时，显式地将此对象赋空，如清空对图片等资源有直接引用或者间接引用的数组（使用&lt;code&gt;array.clear();array = null&lt;/code&gt;），最好遵循谁创建谁释放的原则。
&lt;hr&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;h4&gt;排查方法以及工具&lt;/h4&gt;&lt;br&gt;
&lt;h5&gt;MAT内存分析&lt;/h5&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;需要的软件&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Android Studio&lt;/code&gt;；&lt;br&gt;
&lt;code&gt;Memory Analyzer&lt;/code&gt;，下载地址：&lt;a href=&#34;http://www.eclipse.org/mat/downloads.php&#34;&gt;http://www.eclipse.org/mat/downloads.php&lt;/a&gt;，下载完成后进行解压，双击&lt;code&gt;MemoryAnalyzer&lt;/code&gt;运行。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如何使用&lt;/p&gt;

&lt;p&gt;2.1 打开&lt;code&gt;Android Studio&lt;/code&gt;，&lt;code&gt;Run/Debug&lt;/code&gt;要进行分析的工程。&lt;/p&gt;

&lt;p&gt;2.2 启动&lt;code&gt;Android Studio&lt;/code&gt;工具栏上的&lt;code&gt;Android Device Monitor&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;2.3 在&lt;code&gt;Android Device Monitor&lt;/code&gt;中选中我们要进行分析的应用，然后点击&lt;code&gt;update heap&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;2.4 在应用内进行操作后，点击&lt;code&gt;DUMP HPROF file&lt;/code&gt;来保存&lt;code&gt;hprof&lt;/code&gt;文件。（建议保存到&lt;code&gt;sdk\platform-tools&lt;/code&gt;目录下，后面转换比较方便）&lt;/p&gt;

&lt;p&gt;2.5 进入到&lt;code&gt;sdk\platform-tools&lt;/code&gt;目录下，启动命令行，输入如下：&lt;code&gt;./hprof-conv inputName.hprof outputName.hprof&lt;/code&gt;。（由于都在一个目录下，输入输出文件可以不用指定位置）&lt;/p&gt;

&lt;p&gt;2.6 启动&lt;code&gt;Memory Analyzer&lt;/code&gt;，选择&lt;code&gt;File&amp;gt;open heap dump&lt;/code&gt;，打开刚才转换好的文件。&lt;/p&gt;

&lt;p&gt;2.7 点击&lt;code&gt;Leak Suspects&lt;/code&gt;会显示几个内存泄露疑点，在其中怀疑的地方，点击 &lt;code&gt;Details&lt;/code&gt; 就可以看到具体的内存使用情况。&lt;/p&gt;

&lt;p&gt;2.8 点击&lt;code&gt;Histogram&lt;/code&gt;，在出现的菜单中选中一项右键选择 &lt;code&gt;list objects&amp;gt;with incoming references&lt;/code&gt; 将列出该类的实例，如果想查看某个实例没被释放的原因，可以右健 &lt;code&gt;Path to GC Roots&amp;gt;exclude all phantom/weak/soft etc. reference&lt;/code&gt; ，这样可以去掉所有的虚/弱/软引用，以便我们快速查看某个对象的 &lt;code&gt;GC Root&lt;/code&gt;。（表头可以进行排序，在表的第一行可以输入正则表达式来匹配结果）&lt;/p&gt;

&lt;p&gt;2.9 有时候为查找内存泄漏，我们通常需要两个&lt;code&gt;Dump&lt;/code&gt;结果作对比，这时我们可以打开&lt;code&gt;Windows&amp;gt;Navigator History&lt;/code&gt;面板，将两个表的&lt;code&gt;Histogram&lt;/code&gt;结果都添加到&lt;code&gt;Compare Basket&lt;/code&gt;中去 ，然后点击面板右上角的红色叹号，得到对比结果，对比条件也是可设置的。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;一个例子&lt;/p&gt;

&lt;p&gt;一个典型的分析内存泄漏的过程：&lt;/p&gt;

&lt;p&gt;3.1 使用&lt;code&gt;Heap&lt;/code&gt;查看当前堆大小为&lt;code&gt;23.00M&lt;/code&gt;；&lt;/p&gt;

&lt;p&gt;3.2 添加一个页后堆大小变为&lt;code&gt;23.40M&lt;/code&gt;；&lt;/p&gt;

&lt;p&gt;3.3 将添加的一个页删除，堆大小为&lt;code&gt;23.40M&lt;/code&gt;；&lt;/p&gt;

&lt;p&gt;3.4 多次操作，结果仍相似，说明添加/删除页存在内存泄漏 (也应注意排除其它因素的影响)；&lt;/p&gt;

&lt;p&gt;3.5 &lt;code&gt;Dump&lt;/code&gt;出操作前后的&lt;code&gt;hprof&lt;/code&gt;文件 &lt;code&gt;1.hprof,2.hprof&lt;/code&gt;，用&lt;code&gt;mat&lt;/code&gt;打开,并得到&lt;code&gt;histgram&lt;/code&gt;结果；&lt;/p&gt;

&lt;p&gt;3.6 使用&lt;code&gt;HomePage&lt;/code&gt;字段过滤&lt;code&gt;histgram&lt;/code&gt;结果，并列出该类的对象实例列表，看到两个表中的对象集合大小不同，操作后比操作前多出一个&lt;code&gt;HomePage&lt;/code&gt;，说明确实存在泄漏；&lt;/p&gt;

&lt;p&gt;3.7 将两个列表进行对比，找出多出的一个对象，用查找&lt;code&gt;GC Root&lt;/code&gt;的方法找出是谁串起了这条引用线路，定位结束。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;LeakCanary内存分析&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;开始使用&lt;/p&gt;

&lt;p&gt;在 &lt;code&gt;build.gradle&lt;/code&gt; 中加入引用：&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;dependencies {
    debugCompile &amp;lsquo;com.squareup.leakcanary:leakcanary-android:1.4-beta2&amp;rsquo;
    releaseCompile &amp;lsquo;com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2&amp;rsquo;
    testCompile &amp;lsquo;com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2&amp;rsquo;
}&lt;/code&gt;&lt;/pre&gt;
在&lt;code&gt;Application&lt;/code&gt;中：&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public class VideoApplication extends Application {
    @Override
    public void onCreate() {
            super.onCreate();
            LeakCanary.install(this);
    }
}&lt;/code&gt;&lt;/pre&gt;
在 &lt;code&gt;debug build&lt;/code&gt; 中，如果检测到某个 &lt;code&gt;activity&lt;/code&gt; 有内存泄露，&lt;code&gt;LeakCanary&lt;/code&gt; 就是自动地显示一个通知。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如何使用&lt;/p&gt;

&lt;p&gt;使用 &lt;code&gt;RefWatcher&lt;/code&gt; 监控那些本该被回收的对象。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;RefWatcher refWatcher = {...};
// 监控
refWatcher.watch(schrodingerCat);
LeakCanary.install() 
//会返回一个预定义的 RefWatcher，同时也会启用一个 ActivityRefWatcher，用于自动监控调用 Activity.onDestroy() 之后泄露的 activity。
public class VideoApplication extends Application {

public static RefWatcher getRefWatcher(Context context) {
    VideoApplication application = (VideoApplication) context.getApplicationContext();
    return application.refWatcher;
}

private RefWatcher refWatcher;

@Override
public void onCreate() {
        super.onCreate();
        refWatcher = LeakCanary.install(this);
}
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用 &lt;code&gt;RefWatcher&lt;/code&gt; 监控 &lt;code&gt;Fragment&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Override
public void onDestroy() {
    super.onDestroy();
    RefWatcher refWatcher = VideoApplication.getRefWatcher(getActivity());
    refWatcher.watch(this);
}&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;工作机制&lt;/p&gt;

&lt;p&gt;3.1 &lt;code&gt;watch()&lt;/code&gt; 创建一个&lt;code&gt;KeyedWeakReference&lt;/code&gt; 到要被监控的对象。&lt;/p&gt;

&lt;p&gt;3.2 然后在后台线程检查引用是否被清除，如果没有，调用&lt;code&gt;GC&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;3.3 如果引用还是未被清除，把 &lt;code&gt;heap&lt;/code&gt; 内存 &lt;code&gt;dump&lt;/code&gt; 到 &lt;code&gt;APP&lt;/code&gt; 对应的文件系统中的一个&lt;code&gt;.hprof&lt;/code&gt; 文件中。&lt;/p&gt;

&lt;p&gt;3.4 在另外一个进程中的&lt;code&gt;HeapAnalyzerService&lt;/code&gt; 有一个 &lt;code&gt;HeapAnalyzer&lt;/code&gt; 使用&lt;code&gt;HAHA&lt;/code&gt; 解析这个文件。&lt;/p&gt;

&lt;p&gt;3.5 得益于唯一的 &lt;code&gt;reference key&lt;/code&gt;, &lt;code&gt;HeapAnalyzer&lt;/code&gt; 找到&lt;code&gt;KeyedWeakReference&lt;/code&gt;，定位内存泄露。&lt;/p&gt;

&lt;p&gt;3.6 &lt;code&gt;HeapAnalyzer&lt;/code&gt; 计算到 &lt;code&gt;GC roots&lt;/code&gt; 的最短强引用路径，并确定是否是泄露。如果是的话，建立导致泄露的引用链。&lt;/p&gt;

&lt;p&gt;3.7 引用链传递到 &lt;code&gt;APP&lt;/code&gt; 进程中的&lt;code&gt;DisplayLeakService&lt;/code&gt;， 并以通知的形式展示出来。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如何复制LEAK TRACE&lt;/p&gt;

&lt;p&gt;在 &lt;code&gt;Logcat&lt;/code&gt; 中，你可以看到类似这样的 &lt;code&gt;leak trace&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;In com.example.leakcanary:1.0:1 com.example.leakcanary.MainActivity has leaked:
\* GC ROOT thread java.lang.Thread.&lt;Java Local&gt; (named &#39;AsyncTask #1&#39;)
\* references com.example.leakcanary.MainActivity$3.this$0 (anonymous class extends android.os.AsyncTask)
\* leaks com.example.leakcanary.MainActivity instance
\* Reference Key: e71f3bf5-d786-4145-8539-584afaecad1d
\* Device: Genymotion generic Google Nexus 6 - 5.1.0 - API 22 - 1440x2560 vbox86p
\* Android Version: 5.1 API: 22
\* Durations: watch=5086ms, gc=110ms, heap dump=435ms, analysis=2086ms&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;LEAK TRACE之外&lt;/p&gt;

&lt;p&gt;有时，&lt;code&gt;leak trace&lt;/code&gt; 不够，你需要通过 &lt;code&gt;MAT&lt;/code&gt; 或者 &lt;code&gt;YourKit&lt;/code&gt; 深挖 &lt;code&gt;dump&lt;/code&gt; 文件。&lt;/p&gt;

&lt;p&gt;通过以下方法，你能找到问题所在：&lt;/p&gt;

&lt;p&gt;5.1 查找所有的&lt;code&gt;squareup.leakcanary.KeyedWeakReference&lt;/code&gt; 实例。&lt;/p&gt;

&lt;p&gt;5.2 检查&lt;code&gt;key&lt;/code&gt; 字段。&lt;/p&gt;

&lt;p&gt;5.3 &lt;code&gt;Find the KeyedWeakReference that has a key field equal to the reference key reported by LeakCanary&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;5.4 找到 &lt;code&gt;key&lt;/code&gt; 和 和 &lt;code&gt;logcat&lt;/code&gt; 输出的 &lt;code&gt;key&lt;/code&gt; 值一样的&lt;code&gt;KeyedWeakReference&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;5.5 &lt;code&gt;referent&lt;/code&gt; 字段对应的就是泄露的对象。&lt;/p&gt;

&lt;p&gt;5.6 剩下的，就是动手修复了。最好是检查到 &lt;code&gt;GC root&lt;/code&gt; 的最短强引用路径开始。
&lt;hr&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;h4&gt;参考资料&lt;/h4&gt;&lt;br&gt;
&lt;a href=&#34;https://segmentfault.com/a/1190000003984512&#34;&gt;内存泄露从入门到精通三部曲之基础知识篇&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://segmentfault.com/a/1190000004018386&#34;&gt;内存泄露从入门到精通三部曲之常见原因与用户实践&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://segmentfault.com/a/1190000003991636&#34;&gt;内存泄露从入门到精通三部曲之排查方法篇&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/&#34;&gt;LeakCanary 中文使用说明&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>