zoukankan      html  css  js  c++  java
  • JAVA的可变类与不可变类

    可变类和不可变类(Mutable and Immutable Objects)

    1. 可变类和不可变类(Mutable and Immutable Objects)的初步定义:
    可变类:当你获得这个类的一个实例引用时,你可以改变这个实例的内容。
    不可变类:当你获得这个类的一个实例引用时,你不可以改变这个实例的内容。不可变类的实例一但创建,其内在成员变量的值就不能被修改。 
    举个例子:String和StringBuilder,String是immutable的,每次对于String对象的修改都将产生一个新的String对象,而原来的对象保持不变,而StringBuilder是mutable,因为每次对于它的对象的修改都作用于该对象本身,并没有产生新的对象。

    2. 如何创建一个自己的不可变类:
    .所有成员都是private final
    .不提供对成员的改变方法,例如:setXXXX 提供带参数的构造器,用于根据传入的参数来初始化属性。
    .确保所有的方法不会被重载。手段有两种:使用final Class(强不可变类),或者将所有类方法加上final(弱不可变类)。
    .如果某一个类成员不是原始变量(primitive)或者不可变类,必须通过在成员初始化(in)或者get方法(out)时通过深度clone方法,来确保类的不可变。
    .如果有必要,重写hashCode和equals方法,同时应保证两个用equals方法判断为相等的对象,其hashCode也应相等。
    示例:
    1. public class Address {  
    2.     private final String detail;  
    3.   
    4.     public Address() {  
    5.         this.detail = "";  
    6.     }  
    7.   
    8.     public Address(String detail) {  
    9.         this.detail = detail;  
    10.     }  
    11.   
    12.     public String getDetail() {  
    13.         return detail;  
    14.     }  
    15.   
    16.     @Override  
    17.     public int hashCode() {  
    18.         return detail.hashCode();  
    19.     }  
    20.   
    21.     @Override  
    22.     public boolean equals(Object obj) {  
    23.         if (obj instanceof Address) {  
    24.             Address address = (Address) obj;  
    25.             if (this.getDetail().equals(address.getDetail())) {  
    26.                 return true;  
    27.             }  
    28.         }  
    29.         return false;  
    30.     }  
    31.   

    要写出这样的类,需要遵循以下几个原则:

    1)immutable对象的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象。

    2)Immutable类的所有的属性都应该是final的。

    3)对象必须被正确的创建,比如:对象引用在对象创建过程中不能泄露(leak)。

    4)对象应该是final的,以此来限制子类继承父类,以避免子类改变了父类的immutable特性。

    5)如果类中包含mutable类对象,那么返回给客户端的时候,返回该对象的一个拷贝,而不是该对象本身(该条可以归为第一条中的一个特例)


    有时候你要实现的immutable类中可能包含mutable的类,比如java.util.Date,尽管你将其设置成了final的,但是它的值还是可以被修改的,为了避免这个问题,我们建议返回给用户该对象的一个拷贝,这也是Java的最佳实践之一。下面是一个创建包含mutable类对象的immutable类的例子:

    public final class ImmutableReminder{
        private final Date remindingDate;
       
        public ImmutableReminder (Date remindingDate) {
            if(remindingDate.getTime() < System.currentTimeMillis()){
                throw new IllegalArgumentException("Can not set reminder” +
                            “ for past time: " + remindingDate);
            }
            this.remindingDate = new Date(remindingDate.getTime());
        }
       
        public Date getRemindingDate() {
            return (Date) remindingDate.clone();
        }
    }

    上面的getRemindingDate()方法可以看到,返回给用户的是类中的remindingDate属性的一个拷贝,这样的话如果别人通过getRemindingDate()方法获得了一个Date对象,然后修改了这个Date对象的值,那么这个值的修改将不会导致ImmutableReminder类对象中remindingDate值的修改。

    使用Immutable类的好处:
    1)Immutable对象是线程安全的,可以不用被synchronize就在并发环境中共享

    2)Immutable对象简化了程序开发,因为它无需使用额外的锁机制就可以在线程间共享

    3)Immutable对象提高了程序的性能,因为它减少了synchroinzed的使用

    4)Immutable对象是可以被重复使用的,你可以将它们缓存起来重复使用,就像字符串字面量和整型数字一样。你可以使用静态工厂方法来提供类似于valueOf()这样的方法,它可以从缓存中返回一个已经存在的Immutable对象,而不是重新创建一个。

    immutable也有一个缺点就是会制造大量垃圾,由于他们不能被重用而且对于它们的使用就是”用“然后”扔“,字符串就是一个典型的例子,它会创造很多的垃圾,给垃圾收集带来很大的麻烦。当然这只是个极端的例子,合理的使用immutable对象会创造很大的价值。

    Java的string类为什么是不可变的

    什么是不可变对象(immutable object),不可变对象有什么好处,在什么情况下应该用,或者更具体一些,Java的String类为什么要设成immutable类型?
    不可变对象,顾名思义就是创建后不可以改变的对象,典型的例子就是Java中的String类。

    1. String s = "ABC";    
    2. s.toLowerCase();  
    如上s.toLowerCase()并没有改变“ABC“的值,而是创建了一个新的String类“abc”,然后将新的实例的指向变量s。
    字符串常量池(String pool, String intern pool, String保留池) 是Java堆内存中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。
    如下面的代码所示,将会在堆内存中只创建一个实际String对象.
    1. String s1 = "abcd";    
    2. String s2 = "abcd";    

    相对于可变对象,不可变对象有很多优势:
    1).不可变对象可以提高String Pool的效率和安全性。如果你知道一个对象是不可变的,那么需要拷贝这个对象的内容时,就不用复制它的本身而只是复制它的地址,复制地址(通常一个指针的大小)需要很小的内存效率也很高。对于同时引用这个“ABC”的其他变量也不会造成影响。
    Java中String对象的哈希码被频繁地使用, 比如在hashMap 等容器中。
    字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码. 在String类的定义中有如下代码:
    1. private int hash;//用来缓存HashCode    


    2).不可变对象对于多线程是安全的,因为在多线程同时进行的情况下,一个可变对象的值很可能被其他进程改变,这样会造成不可预期的结果,而使用不可变对象就可以避免这种情况。

    3)安全性
    如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
    假如有如下的代码:

    1. boolean connect(String s) {  
    2.     if (!isSecure(s)) {  
    3.         throw new SecurityException();  
    4.     }  
    5.     causeProblem(s);  
    6. }  
    如果在其他地方可以修改String,那么此处就会引起各种预料不到的问题/错误 
    当然也有其他方面原因,但是Java把String设成immutable最大的原因应该是效率和安全。


    密码应该存放在字符数组中而不是String中
    但有的时候String的immutable特性也会引起安全问题,这就是密码应该存放在字符数组中而不是String中的原因!
    1) Since Strings are immutable in Java if you store password as plain text it will be available in memory until Garbage collector clears it and since String are used in String pool for reusability there is pretty high chance that it will be remain in memory for long duration, which pose a security threat. Since any one who has access to memory dump can find the password in clear text and that's another reason you should always used an encrypted password than plain text. Since Strings are immutable there is no way contents of Strings can be changed because any change will produce new String, while if you char[] you can still set all his element as blank or zero. So Storing password in character array clearly mitigates security risk of stealing password.

    1)由于String在Java中是不可变的,如果你将密码以明文的形式保存成字符串,那么它将一直留在内存中,直到垃圾收集器把它清除。而由于字符串被放在字符串缓冲池中以方便重复使用,所以它就可能在内存中被保留很长时间,而这将导致安全隐患,因为任何能够访问内存(memory dump内存转储)的人都能清晰的看到文本中的密码,这也是为什么你应该总是使用加密的形式而不是明文来保存密码。由于字符串是不可变的,所以没有任何方式可以修改字符串的值,因为每次修改都将产生新的字符串,然而如果你使用char[]来保存密码,你仍然可以将其中所有的元素都设置为空或者零。所以将密码保存到字符数组中很明显的降低了密码被窃取的风险。


    2) Java itself recommends using getPassword() method of JPasswordField which returns a char[] and deprecated getText() method which returns password in clear text stating security reason. Its good to follow advice from Java team and adhering to standard rather than going against it.

    2)Java本身也推荐使用JPasswordField组件的getPassword()方法,该方法将返回一个字符数组,而放弃了原来的getText()方法,这个方法把密码以明文的形式返回而可能会引起安全问题。所以,最好能听从来自Java团队的建议并且坚持标准,而不是去反对它。


    3) With String there is always a risk of printing plain text in log file or console but if use Array you won't print contents of array instead its memory location get printed. though not a real reason but still make sense.

    3)使用字符串,在将文本输出到日志文件或者控制台的时候会存在风险。但是使用数组你不会把数组的内容打印出来,相反,打印出来的是数组在内存中的位置。尽管这算不上一个真正的原因,但这仍然很有意义。

    That's all on why character array is better choice than String for storing passwords in Java. Though using char[] is not just enough you need to erase content to be more secure. I also suggest working with hash'd or encrypted password instead of plain text and clearing it from memory as soon as authentication is completed.

    这就是全部的关于为什么使用字符数组存储密码比字符串更好。只使用字符数组也是不够的,为了更安全你需要将数组内容进行转化。我也建议使用哈希的或者是加密过的密码而不是明文,然后一旦完成验证,就将它从内存中清除掉。

    转载文档

    1) http://shixm.iteye.com/blog/297906

    2)http://www.jb51.NET/article/49092.htm

    3)http://my.oschina.Net/jasonultimate/blog/166968

    4)http://www.cnblogs.com/gdjdsjh/p/5111083.html

  • 相关阅读:
    RocketMQ延迟消息的代码实战及原理分析
    如何做技术选型?Sentinel 还是 Hystrix?
    什么是服务熔断?
    降级-熔断-限流-傻傻分不清楚
    java-分布式-降级 熔断 限流
    java-分布式-分布式事务
    常用限流算法的应用场景和实现原理
    使用Redis作为分布式锁的一些注意点
    ansible {{}}引用变量,变量中嵌套变量如何表示
    shell获得java进程号跟进程对应的线程号
  • 原文地址:https://www.cnblogs.com/esther-qing/p/6484965.html
Copyright © 2011-2022 走看看