标签 程序员 下的文章

Effective Java阅读笔记-item12

Dreamhead把他用大把银子买来的“Effective Java”借给我阅读,我真是很感动亚,我只能用行动来感谢Dreamhead了。^_^

从item12~item18讨论的都是单个类或接口的设计技术。

12、使类和成员的可访问能力最小化
作者提出“你应尽可能的是每一个类或成员不被外界访问”

* 顶层类或接口的访问级别
  public — 意味着你的类是提供给客户API的一部分,你有义务永远支持它,维护它。
  package-private — 如果一个类或接口做成packge-private,那么意味着它实际上已成为包的实现的一个部分,而不是包提供给客户(使用者)的API的一部分。在以后包的维护过程中你对该类进行修改、替换和删除对客户并无影响。

* 类or接口内成员的访问级别

在几乎每一本java教科书中都有,我这里就不浪费文字了。不过书中有几个观点还是值得我们注意的:
- 如果一个方法改写了超类的一个方法,那么子类中该方法的访问级别低于超类中的访问级别是不允许的。这样可确保子类的实例可被用在任何超类实例使用的场合。

- 公有类不应该包含public field,例外情况通过公有的静态final field来暴露类中的常量是可以的。
- 如果类内的一个final field包含一个指向可变对象的引用,那么它具有非final field的一切缺点,虽然引用本身是final的不能被修改,但是它引用的对象却可以被修改,这将是一个很危险的安全漏洞。如果你非要这么做的话,请确保被 public static final field所引用的对象是不可变的。

Considering the following code :

public class TestItem12 {
public static final Integer[] intArray = { 1 , 4 , 5 , 6};//可以看出我们提供该final Integer的意图是其////不被修改,但实际上是可以被修改的。
    public static void main(String[] args) {
        System.out.println("Before modifying :");
        //这是我们的意图,我们要使用static final field的值。
        for (Integer i : TestItem12.intArray ){
            System.out.println(i);
        }

//这可不是我们的意图,但是这样做编译器不会告诉你你的final引用的对象被修改了,you //are in danger
        TestItem12.intArray[1] = 11;
        TestItem12.intArray[2] = 10;
        System.out.println("After modifying :");
        for (Integer i : TestItem12.intArray ){
            System.out.println(i);
        }
    }
}
//output:
Before modifying :
1
4
5
6
After modifying :
1
11
10
6
可以看到引用本身并未发生变化但是所引用对象的值发生了变化。我们改变一下:

public class TestItem12 {
    public static final Integer[] valuesOfArray(){
        return (Integer[])intArray.clone();
    }
    private static final Integer[] intArray = { 1 , 4 , 5 , 6};
    public static void main(String[] args) {
        Integer[] ia = TestItem12.valuesOfArray();
        for (Integer i : ia ){
            System.out.println(i);
        }
        a[2] = 123;
        for (Integer i : TestItem12.valuesOfArray() ){
            System.out.println(i);
        }            
    }
}
//output:
1
4
5
6
1
4
5
6

当然这样会牺牲一些性能。^_^在这个过程中顺便谈谈clone()这个方法吧,很有意思的,呵呵,看代码吧。

public class TestItem12 {
    public static final Integer[] valuesOfArray(){
        return (Integer[])intArray; // remove the clone() method
    }
        
    private static final Integer[] intArray = { 1 , 4 , 5 , 6};
    public static void main(String[] args) {
        Integer[] ia = TestItem12.valuesOfArray();
        for (Integer i : ia ){
            System.out.println(i);
        }
        a[2] = 123;
        for (Integer i : TestItem12.valuesOfArray() ){
            System.out.println(i);
        }
}
//output:
1
4
5
6
1
4
123
6

看到上面代码,remove the clone method后,输出了123,也就是说final reference所引用的对象的值被修改了。这样做是十分危险的,因为它直接暴露了类内部的成员。Clone的作用是它重新new了一块内存,并用intArray对其进行了初始化。这样实际上外部的引用就不会和内部的private引用指向同一块memory了。Simple吧.^_^

Effective Java阅读笔记-item4、6

Dreamhead把他用大把银子买来的“Effective Java”借给我阅读,我真是很感动亚,我只能用行动来感谢Dreamhead了。^_^

4、避免创建重复对象
我们知道构造函数每次被调用的时候都会创建一个新的对象,在有些情况下这样会很浪费。那我们能不能重复使用一个对象(这个对象应该是immutable的),而不是在每次需要的时候都要创建一个新的对象呢?这就是这个item4所要讲述的事情。

重用immutable对象
在item1时我们说过使用static factory method方法可以控制对象的创建,在这里我们将用到此种方法。

举个Boolean的例子,Boolean只有两个值true or false,而且Boolean是immutable的。Considering the following code:

private static final Boolean TRUE = new Boolean(true);
private static final Boolean FALSE = new Boolean(false);

public static final Boolean valueOf(final boolean b)
{
  if( b )
    return TRUE;
  else
    return FALSE;
}
可以看出immutable classes可以使用static factory method来避免产生重复对象。

重用已知不会被修改的可变对象

看看下面的例子:(改编自书中的例子,大部分摘取)
import java.util.*;

public class Person1 {
    private final Date birthDate;
    
    Person1(Date birthDate) {
        this.birthDate = birthDate;
    }

    public boolean isEighties(){//判断是否是80年代的人
       Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1980, Calendar.JANUARY, 1, 0, 0, 0);
        Date EIGHTIES_START = gmtCal.getTime();
        gmtCal.set(1990, Calendar.JANUARY, 1, 0, 0, 0);
        Date EIGHTIES_END = gmtCal.getTime();
 
        return birthDate.compareTo(EIGHTIES_START) >= 0 &&
               birthDate.compareTo(EIGHTIES_END)   <  0;
    }

    public static void main(String[] args) {
        Person1 p = new Person1(new Date());
        long startTime = System.currentTimeMillis();
        for (int i=0; i<1000000; i++){
            p.isEighties();
        }
        long endTime = System.currentTimeMillis();
        long time = endTime – startTime;
        System.out.println(time+" ms.");
    }
}
//output:
9250ms

可以看出上述代码每次调用isEighties函数时,Calendar,Date,TimeZone都要被实例化一次,这样的代价适合昂贵的。我们改进一下,将不变的Date对象的实例化的工作放在一个static初始化中,看看效果如何。

import java.util.*;

public class Person {
    private final Date birthDate;
    
    Person(Date birthDate) {
        this.birthDate = birthDate;
    }
    
    private static final Date EIGHTIES_START;
    private static final Date EIGHTIES_END;

    static {
        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1980, Calendar.JANUARY, 1, 0, 0, 0);
        EIGHTIES_START = gmtCal.getTime();
        gmtCal.set(1990, Calendar.JANUARY, 1, 0, 0, 0);
        EIGHTIES_END = gmtCal.getTime();
    }

    public boolean isEighties(){
        return birthDate.compareTo(EIGHTIES_START) >= 0 &&
               birthDate.compareTo(EIGHTIES_END)   <  0;
    }

    public static void main(String[] args) {
        Person p = new Person(new Date());

        long startTime = System.currentTimeMillis();
        for (int i=0; i<1000000; i++){
            p.isEighties();
        }
        long endTime = System.currentTimeMillis();
        long time = endTime – startTime;
        System.out.println(time+" ms.");
    }
}
//output:
62ms

哇! 9250 vs 62 ,速度得到飞快提升。

6、避免使用终结函数
在这一节中作者也写了很多,不过我最感兴趣的是“finalizer chaining”终结函数链。
其主要的内容就是“如果一个类(除了Object类)拥有一个finalize函数,并且它的子类override了finalize函数(java中所有类的member func都是virtual的,可以被override的),那么你应该在子类的finalize函数中手动调用父类的finalize函数”。

protected void finalize() throws Throwable {
    try {
        //finalize subclass state
        …
    }finally{
        super.finalze();
}
}

如果你不手动调用父类的finalize函数,父类的finalize函数将永远不会被调用。那么父类的finalize函数中的关键资源将不能够被释放。但是程序员总是有些“粗心大意”的,我们不能指望程序员都能记住要手动调用父类的finalize,那么我们就要想办法了。书中提到了“finalizer guardian”的概念。书中的论点是:“对于所有带有finalize函数的非final的public class,都应该考虑finalizer guardian技术”这里举例说一下我的理解,以书中的例子为例:

从原来的
public class Foo {
    protected void finalize() throws Throwable {
        //finalize the class Foo object
            …..
        }
    };
    //…..
}

进化为
public class Foo {
    private final Object finalizerGuardian = new Object() {
        protected void finalize() throws Throwable {
            //finalize the outer class Foo object
            …..
        }
    };
    //…..
    //注意:该Foo类中并无finalize函数,所有关键资源的释放都由inner class override的finalize负责释放。
}

我们首先看看进化后类中的角色:

Foo类:拥有关键资源需要释放且可能作为父类被继承。
Inner class: 继承自Object类,override了Object的finalize方法,并在finalize方法中帮助释放其outer class Foo的关键性资源。

关键性资源释放的流程:由于inner class object和Foo object同生命周期,它们可以同时启动终结过程,inner class在终结过程中就会帮助Foo class释放关键性资源,保证关键性资源不会泄露。这样即使Foo类的子类在finalize中没有手动调用(如果父类使用了finalizer guardian技术,子类就无需手动调用父类的finalize了)父类的finalize,父类的关键资源也会被释放掉。That’s all!^_^

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats