码迷,mamicode.com
首页 > 编程语言 > 详细

java 不可变类

时间:2015-10-27 19:43:00      阅读:318      评论:0      收藏:0      [点我收藏+]

标签:

0.final修饰的类就是不可变类吗?

答:不是.final修饰的类叫不可继承类.两者并无关系.也就是说不可变类的类名可以用final修饰也可以不用.

 

1.不可变类的特点是什么?即什么是不可变类?

答:特点是一旦创建了类的实例,实例内容(状态)不可被修改.典型类就是java中的String.

 

2.既然String是不可变类,为何可以对它重新赋值而改变了其内容?

技术分享

答:String确实被设计成了不可变类,但重新赋值并不是对String a对象的改变;可以看到第7句本来想将BOOK改为小写,但最后未成功.java中的引用类型都是引用变量指向对象这种结构来管理的.重新赋值只是将对象的引用重新改变了位置(由上面的hashCode可知).

 

3.String的new和" "和+三种用法的区别;

答:new会在堆中开辟内容," "会缓存在字符串常量池. +会隐式调用toString方法. 拼接相同字符串并不会重新开辟内存

技术分享

只要记得比较字符串用equals就行了.但是深入理解这个问题将对我们优化程序大有裨益.注意String重写了equals方法,该equals比较对象内容相同就返回true.

== 更严格些,它要求对象引用不变.

 

右上图可知(1) " "的内容相同指同一个对象,这是由于java的String pool的缓存作用.jvm会将" "内的内容作为常量存在pool内,除非显示new一个,否则找pool中有没有,有就指向,如上例的 a == b;没有就在pool里再创建一个.

(2)new出来的String,就算内容相同也是不同的对象. 由c == d可知.

(3)new出来的对象和" "就算内容相同,也不是同一个对象.//很明显这句话可能有误区,""的内容有的书上称为常量池,常量池是编译时就确定了的,常量池里的内容不光是字符串类型,

也就是 说 常量池里并未保存的是对象而是常量. 本质上对象和常量在内存中都是一样的,

(4)由a == e , c== e可知只要new了就不同了.字符串拼接也是先从pool中找.

 

4.final修饰基本类型和引用类型的区别?

答:final修饰基本类型变量不可改变;修饰引用类型,由于其只是保存了一个引用,final只能保证这个引用的地址不会改变,即一直引用同一个对象,对象内容改变与否无关,

注意final修饰引用类型变量和不变类的区别.

一个是引用不变,一个是引用的对象不变.

 

5.那么如何实现不可变类?

答:参考String.

使用private final 修饰该类的field//注意该field如果是基本类型还好,如果是引用类型要保证其不可变,否则将可能导致创建不变类失败.

                                          //filed要为基本类型或不可变类的引用类型

提供带参构造器,用于根据传入的参数来初始化该field

仅仅为该类提供getter方法,不提供setter方法

如有必要重写hashCode和equals方法

该程序来自<<疯狂java>>

class Name {
    private String firstName;
    private String lastName;
    public Name() {}
    public Name(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getFirstName() {
        return this.firstName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public String getLastName() {
        return this.lastName;
    }
}

public class Person {
    private final Name name;
    public Person(Name name) {
        this.name = name;//(1)   this.name = new Name(name.getFirstName(), name.getLastName());
    }
    public Name getName() {
        return name; //(2)          return new Name(name.getFirstName(), name.getLastName());
}
public static void main(String[] args) { Name n = new Name("wukong", "sun"); Person p = new Person(n); System.out.println(p.getName().getFirstName());//wukong n.setFirstName("bajie"); System.out.println(p.getName().getFirstName()); //bajie } }

可以看到Person类的filed是引用类型的,且在Name类里可以改变其状态.所以Person类并未形成一个不可变类.

书上提供了一种方法将Person类变为不可变类

即把(1)(2)这两句都用匿名对象来处理.

原版本经过Name类来改变Person类里的field,然后从Personl类的构造器返回为改变后的对象. 因此该filed指向的对象容易受到Name类的影响而改变

而改为匿名对象后,从Person类的构造器返回就是临时创建的Name对象,无论你是改变firstName还是lastName,他都只是改变了n指向的firstName.

读者可以在n.setFirstName前一行和后一行分别加上

System.out.println(n.getFirstName());

会发现前后是不一样的.

这就说明n指向的Name对象确实被改变了.

而p的name没改变.

注意:n的filed是两个String类型的;p的filed是一个Name类型的.//类也是一种对象

 

下面定义一个不可变类Address----来自疯狂java

/**
private final 基本数据类型 或 不可变类的引用类型
提供带参构造器,用于根据传入的参数来初始化该field
仅仅为该类提供getter方法,不提供setter方法
如有必要重写hashCode和equals方法
*/
public class Address {
    private final String detail;
    private final String postCode;
    
    public Address() { //如果类编写了有参构造器通常建议提供一个无参构造器
        this.detail = "";
        this.postCode = "";
    }
    
    public Address(String detail, String postCode) {
        this.detail = detail;
        this.postCode = postCode;
    }
    
    public String getPostCode() {
        return this.postCode;
    }
    public String getDetail() {
        return this.detail;
    }
    
    public boolean equals(Object obj) {
        if(this == obj) {
            return true;
        }
        if (obj != null && obj.getClass() == Address.class) {
            Address ad = (Address)obj;
            if (this.getDetail().equals(ad.getDetail()) 
                && this.getPostCode().equals(ad.getPostCode())) {
                    return true;
                }
        }
        return false;
    }
    
    public int hashCode() {
        return detail.hashCode() + postCode.hashCode() * 31;
    }
}

由于没提供方法setter方法,所以无法修改Address类的field;

那么难道只要不提供setter方法就可以构成不可变类吗?
不,private final可以保证不可变类创建的实例的field不被改变不被外界访问到.

 

6.不可变类的实例状态不会变化,这样的实例可以安全地被其他与之关联的对象共享,还可以安全地被多个线程共享,为了节约内存空间,应该尽可能重用不可变类的实例,避免重复创建具有相同属性值的实例.那么该如何实现缓存技术呢?缓存技术有很多种,下面用数组作为缓存池,来实现一个缓存实例的不可变类.

 

/**
* 此程序用数组模拟缓存池
* CachePerson类能控制生成的CachePerson对象个数,只能通过valueof()获取该实例
* 如果某个对象只使用一次,那么没必要使用缓存;
* 如果某个对象频繁使用,缓存就很必要.
* java.lang.Integer采用了相同的策略,如果采用new对象,则每次都是全新的;如果采用valueof则缓存
*/

class CachePerson {
    private static int MAX_SIZE = 10;
    //使用数组缓存
    private static CachePerson[] cache = new CachePerson[MAX_SIZE];
    //记录缓存实例在缓存中的位置:cache[pos-1]是新缓存的实例
    private static int pos = 0;
    private final String name;
    
    //private构造器,外类无法构造对象
    private CachePerson(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
//一旦封装了构造器,一定有public方法来创建对象
//static是必要的,因创建对象只能是类方法
    public static CachePerson valueof(String name) {
        //遍历已缓存的实例
        for (int i = 0;i < MAX_SIZE; i++) {
            如有相同实例则直接返回改实例
            if (cache[i] != null && cache[i].getName().equals(name)) {
                return cache[i];
            }
        }
        //缓存已满
        if (pos == MAX_SIZE) {
            //把缓存的第一个实例覆盖,即把刚生成的对象放在缓存池的开始位置
            cache[0] = new CachePerson(name);
            pos = 1;
        } else {
            //新创建的实例缓存起来,pos+1
            cache[pos++] = new CachePerson(name);
        }
        return cache[pos - 1];
    }
    /*此处equals方法很常用最好记下来:判断两个对象是否相等
     == 对于基本数据类型只要值相等就行;对于引用变量,只有指向同一个对象时才true;
     == 不可比较没有继承关系的对象
    默认的String类的equals方法只要对象的字符序列相等即认为相等;
    String类的equals方法是重写了Object类的equals方法,该方法与==区别不大;
    一般情况下,我们要求像String类那样只要值相同则认为其相同 @see java.lang.String#964
    */
    public boolean equals(Object obj) {
//判断两个对象是否是同一个对象       
       if (this == obj) {
            return true;
        }
        //当obj不为null且是CachePerson的实例时
        if (obj != null && obj.getClass() == CachePerson.class) {
            CachePerson ci = (CachePerson)obj;
            return name.equals(ci.getName());
        }
        return false;
    }
    public int hashCode() {
        return name.hashCode();
    }
}

public class CachePersonTest {
    public static void main(String[] args) {
        CachePerson c1 = CachePerson.valueof("hello");
        CachePerson c2 = CachePerson.valueof("hello");
        //output:true
        System.out.println(c1 == c2);
    }

 

题外话:说实话,关于java中对于String,不可变类,只能懂60%.暂时就写到这了.等以后悟性高了再回头看吧.

java 不可变类

标签:

原文地址:http://www.cnblogs.com/xzqhextt/p/4914798.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!