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

个人笔记

第二章 ——


第四条: 通过私有构造器强化不可实例化的能力
考虑一个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/la
ng/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去操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值