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