《Effective java》笔记(第二版) --第二章(4-7)

来源:互联网 发布:网站模板源码免费下载 编辑:程序博客网 时间:2024/06/05 03:53

个人笔记

第二章 ——


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

考虑一个LogUtil日志打印类,这种工具类的方法都是static调用的,也就是不需要实例来调用就可以了,那么这样的东西,就不该被实例化,把构造函数私有化是个不错的选择,加上一条注释告诉别人为什么不该被实例化
class LogUtil{    private LogUtil(){}//私有化,工具类    public static void debug(){...}}
但是这样写将不能再新建LogUtil的子类,常识



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

看一段简单的代码

public class newString{    public static void main(String[] args){        String strA = "hello";        String strB = new String("hello");    }}

拿到class文件
拿到class

接着javap -verbose查看class文件,主要指令列出

ldc
push a constant #index from a constant pool (String, int or float) onto the stack

astore_1
store a reference into local variable 1

new
create new object of type identified by class reference in constant pool index (indexbyte1 << 8 + indexbyte2)

dup
duplicate the value on top of the stack

Constant pool:   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V   #2 = String             #16            // hello   #3 = Class              #17            // java/lang/String   #4 = Methodref          #3.#18         // java/lang/String."<init>":(Ljava/lang/String;)V   #5 = Class              #19            // newString   #6 = Class              #20            // java/lang/Object   #7 = Utf8               <init>   #8 = Utf8               ()V   #9 = Utf8               Code  #10 = Utf8               LineNumberTable  #11 = Utf8               main  #12 = Utf8               ([Ljava/lang/String;)V  #13 = Utf8               SourceFile  #14 = Utf8               newString.java  #15 = NameAndType        #7:#8          // "<init>":()V  #16 = Utf8               hello  #17 = Utf8               java/lang/String  #18 = NameAndType        #7:#21         // "<init>":(Ljava/lang/String;)V  #19 = Utf8               newString  #20 = Utf8               java/lang/Object  #21 = Utf8               (Ljava/lang/String;)V{  public newString();    descriptor: ()V    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."<init>":()V         4: return      LineNumberTable:        line 1: 0  public static void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=3, locals=3, args_size=1         0: ldc           #2                  // String hello         2: astore_1         3: new           #3                  // class java/lang/String         6: dup         7: ldc           #2                  //String hello         9: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V        12: astore_2        13: return      LineNumberTable:        line 3: 0        line 4: 3        line 5: 13}

可见,”hello”的常量最先被ldc进常量池中,hello字符串的位置在
#2 = String #16 // hello
第二个位置
astore_1 拿下标为1,也就是hello,装载了之后
调用
new #3 // class java/lang/String
拿到常量池中的
#3 = Class #17 // java/lang/String
String class文件,来创建一个String
上面的部分对应

String strA = "hello"; //除去加载hello,只花了astore_1,new两条指令

dup 复制一份先不管
7: ldc #2 //String hello
可以看到 ldc再次装载hello字符串,下标还是#2
验证这本书原话

对于所有在同一台虚拟机中运行的代码,只要它们包含相同的字符串常量,,该对象就会被重用
然后就是

      9: invokespecial #4                        // Methodjava/lang/String."<init>":(Ljava/lang/String;)V        12: astore_2

invokespecial 调用 String的构造方法,吧hello丢进去,
对应

String strB = new String("hello");

那么问题来了,这两句谁更高效?
new String(“hello”);会丢一个hello,还要调用构造
而 = “hello”; 也是丢一个hello,但是
一个 new 指令和 invokespecial 调用一个方法谁更高效?当然是new指令,结局很明了,没必要花哨的东西,那么不要做花哨的事情
除非你有非这样做不可的理由

记住static 能在类加载的时候只加载一次,也就是减少了初始化
考虑一个问题,如果一个妹子的萌用String类型来表示

最开始版本

public class Girl {    public void check(String[] girls) {        for (String girl : girls) {            if (girl.equals("beautiful"))                System.out.println("wow, i'd like");            if (girl.equals("soso"))                System.out.println("wow");        }    }}

改良版

public class Girl {    private static String beautiful = "beautiful";    private static String soso = "soso";    public void check(String[] girls) {        for (String girl : girls) {            if (girl.equals(beautiful))                System.out.println("wow, i'd like");            if (girl.equals(soso))                System.out.println("wow");        }    }}

之前已经知道”“字符串常量会被丢到池子中,
已经知道了static仅仅加载一次
那么

如果我用new Girl().check(new String[]{“huyuanyuan”});
当数组中只有一个的时候,两者没有差别,
但是如果new Girl().check(一个1000个妹子的数组);
那么,后者会比前者,用了static来减少初始化的,将会少
次数-1 的String的构造成本,这种数据没必要测试,将它谨记在心,不再写浪费时间的代码

书中还有讲到一个
考虑适配器的情形
Map的KeySet方法总是返回Map的Set集合视图,这种需要重复创建这种集合么,想一下,假设HashMap(Map实现)的KeySet里面
是一种遍历掉所有键,然后new一个Set来return,是不是好蠢
那进去源码看看

    public Set<K> keySet() {        Set<K> ks;        return (ks = keySet) == null ? (keySet = new KeySet()) : ks;    }

keySet是何方神圣,为什么HashMap中没有?,试试去AbstractMap中找一找,233,可以看到这样的东西

    transient volatile Set<K>        keySet = null;

两关键字用法不是笔记内容,跳过
是不是很清晰了
HashMap里面有KetSet的final class

    final class KeySet extends AbstractSet<K> ...

你看别人都不会做多余的事情,那这种适配器的,或者说视图的情况,也要多多致敬

第5条补充,还有一种java 1.5支持的特性,就是基本类型的包装类的自动装箱和自动拆箱,记得多依赖度娘,简单说就是需要基本类型的时候,包装类就会摇身一变成基本类型,基本类型也是如此
具体看java.lang包中Integer,Double,Float,哈哈,大写嘛

那么数据库链接怎么办呢,这种有人叫做线程池,好高级,其实和c++的智能指针一样,里面存着,外面用着,仅次而已,但是你真的需要那么多余的戏法么,除非里面的财宝真的值得这样做再考虑吧
~


第六条: 消除过期的对象引用

我用java也要做这种c++的苦活么,我非得做这种辣自己手的事情么?恩,一般是不用,但是考虑以下情况

import java.util.Arrays;import java.util.EmptyStackException;public class Stack {    private Object[] elements;    private int size = 0;    private static final int DEFAULT_INITIAL_CAPACITY = 16;    public Stack() {        elements = new Object[DEFAULT_INITIAL_CAPACITY];    }    public void push(Object e) {        ensureCapacity();        elements[size++] = e;    }    public Object pop() {        if (size == 0) {            throw new EmptyStackException();        }        return elements[--size];    }    //如果不够容量,就给现容量x2 + 1    private void ensureCapacity() {        if (elements.length == size) {            elements = Arrays.copyOf(elements, 2 * size + 1);        }    }}

这个pop是不是看起来很奇怪,对,没有删除其中的元素,本不该存在的元素还被elements所引用着,假设JVM是引用计数的话,只要elements没有返回或者置null,那么就不该回收这个元素,这种没法控制的元素,管不了的东西,就称作内存泄露了吧哈哈,
所以你要手工置null,并且记得及时缩减你这个数组没必要的长度

    public Object pop() {        if (size == 0) {            throw new EmptyStackException();        }        Object object = elements[--size];        elements[size] = null;        return object;    }

总结几条规律:
只要类自己管理内存,程序员就应该警惕内存泄露,
内存泄露的另一个常见来源是缓存
出现在各种各样的引用之间,需要使用java.lang.ref包中的来使用,避免因为没有手动而导致内存溢出,详细参见LinkedHashMap等类的使用

第七条: 避免使用终结方法

Object中有个
protected void finalize()throws Throwable
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。子类重写 finalize 方法,以配置系统资源或执行其他清除。

这个让我想起来c++的析构,但是finalize是由JVM的gc主动回收才会触发的,上代码

先定义一个类

class Finaz {    @Override    protected void finalize() throws Throwable {        System.out.println(this.getClass() + " finalize");        super.finalize();    }}

置为null

public class IsFinaz {    public static void main(String[] args) {        Finaz finaz = new Finaz();        finaz = null;//置为null告诉JVM可以回收了    }}

运行结果:
结果

什么都没有,那么修改为

public class IsFinaz {    public static void main(String[] args) {        Finaz finaz = new Finaz();        finaz = null;        System.gc();    }}

结果

但是在代码中使用gc显然不明智,不仅破坏了原有的结构,还多了和业务没关系的代码,
finalize最好与try catch组合使用,保证了,这个类的使用者忘记了手动调用还能在异常抛出的情况下进行finalize,调用的时刻就只能在抛出异常的时候被调用,这点要权衡

finalize如果子类实现了,那么就必须调用父类的finalize函数,但是还是有人会忘记这点,那么考虑把这个执行finalize的对象作为一个final的内部成员,并且给一个Object的匿名实现

final Object object = new Object(){    @Override protected void finalize() throws Throwable{    ...//对外围资源进行finalize,因为它可以访问外围嘛}} 

除了这两种的情况,都不要轻易使用finalize去操作

0 0