《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文件
接着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去操作
- 《Effective java》笔记(第二版) --第二章(4-7)
- 《Effective java》笔记(第二版) --第二章(1-3)
- Effective Java笔记(第二章)
- 《Effective Java 学习笔记 第二章》
- 《Effective java》笔记(第二版) --第三章(8-12)
- 《Effective java》笔记(第二版) --第四章(13-16)
- 《Effective java》笔记(第二版) --第四章(17-19)
- 《Effective java》笔记(第二版) --第四章(20-22)
- Effective Java 第二版读书笔记
- Effective Java 中文第二版
- Effective Java 中文第二版
- Effective java第二版读书笔记
- effective java 第二章读书笔记
- Effective Java中文版(第二版)学习笔记(一)
- Effective Java 学习笔记——第二章
- effective C++ 第二章复习笔记
- Effective Java(第二版)-复合优于继承
- 好书推荐:《Effective Java》(中文第二版)
- 记录一美炸天程序媛进入新公司后的心酸历程
- 整个社会总嫌自己不够“快”,为啥?
- Js调用Java方法并互相传参
- 【C#链接数据库】用command和Connection实现系统登录界面
- Material Design 定制状态栏
- 《Effective java》笔记(第二版) --第二章(4-7)
- jsoup解析网站资源。Android
- 支付宝集成最新常见问题详解
- jenkins构建
- 如何在Android Studio中使用LeakCanary检测内存泄露
- Servlet生命周期,工作原理,eclipse第一个Servlet,Servlet线程安全
- Android View 布局 过程
- 外部系统调用AWS ML服务的接口 - Python
- Android Studio导入项目一直卡在Building gradle project info最快速解决方案