Java基础:String不可变性和final修饰

转载请注明出处: jiq•钦's technical Blog - 季义钦


String的不可变性

Java规定String是不可变的(immutable),其实这个不可变具备两层含义:


1 内容不可变

任何看起来修改它们的操作,实际上都是重新new出一个对象。

String s = new String("111");
String newS = s;
newS = "dsfsd";
System.out.println(s);  //111

如果不能做到内容不可变,在两个引用同时指向一个内存的时候,修改其中一个也会影响到另外一个。


2 实现方式不可变

使用String类型变量时,其不能表现出其它行为,这就是为什么String类需要用final关键字来进行修饰的原因,设想一下String不用final修饰,则其可以被继承并重写方法:

/**
 * 模拟非final的String
 * @author jiyiqin
 *
 */
class MyString{
    public char value[] = new char[10];
    MyStringtoLowerCase(MyString s)
    {
       System.out.println("将s转换为小写,但不改变原有s");
       char newValue[] = new char[s.value.length];
       for(int i = 0; i < s.value.length; i++)
           newValue[i]= LowerUtils.Lower(s.value[i]);
       MyStringnewString = newMyString();
       newString.value = newValue;
       return newString;
    }
}
 
/**
 * 继承MyString,将其toLowerCase重写
 * @author jiyiqin
 *
 */
class OverrideString extends MyString{
    MyStringtoLowerCase(MyString s)
    {
       System.out.println("直接修改传递进来的s的内存,将s转换为小写");
       for(int i = 0; i < s.value.length; i++)
           s.value[i] = LowerUtils.Lower(s.value[i]);      
       return s;
    }
}
 
public classFinalStringTest { 
    /**
     * 测试函数,java设计String是不可变的
     * 所以必须将String修饰为final,否则其
     * 一旦被继承,类似下面这种调用时,若传递
     * 进来的参数是其被继承的子类,调用的就是
     * 被重写的方法,这个重写的方法可能会破坏
     * String的不可变特性。
     * @param s
     * @return
     */
    static MyStringLowerCusString(MyString s)//多态
    {     
       return s.toLowerCase(s);
    }
   
    public static void main(String[] args) {
       OverrideStringss = newOverrideString();
       LowerCusString(ss);//传入重写后的字符串类    
       LowerUtils.testFinalClass();
    }
}

可以看到LowerCusString方法想要的是MyString对象,但是其子类可以传递进来使用,调用之后发现传递进来的对象s的内容被修改了,表现出和父类不同的行为!!!

同样地,应用程序可以编写新的String类,修改hasCode方法,让内容相同的String对象返回的hashCode不同,这样HashMap在使用String类型变量作为Key的时候,发现相同内容的Key竟然哈希到不同的位置。

 

除了实现方法不可修改这个原因,将String修饰为final还有一个好处就是效率。

 

 

String其他特性

除了上面讲的不可变性,String还其具备以下特性:

特性1:编译时和运行时区别

u  编译时能确定的String对象都放入常量池

u  运行时才能确定或者new出的String对象都放入堆


特性2:hasCode唯一性

两个内容相同的String对象hashCode返回值一定相同

 

下面通过一个例子总结一下这两个特性:

final String tmp = "ji"; //常量池、编译时确定
String tmp2 = "ji"; //常量池、编译时确定  
String s1 = "jiyiqin"; //常量池、编译时确定
      
String s2 = "jiyiqin"; //常量池、编译时确定
String s3 = "ji" + "yiqin"; //常量池、编译时确定
String s4 = tmp + "yiqin";  //常量池、编译时确定(final一旦初始化就不可变)  
String s5 = tmp2 + "yiqin";//堆、运行时确定
String s6 = new String("jiyiqin");//堆、运行时确定
      
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
System.out.println(s4.hashCode());
System.out.println(s5.hashCode());
System.out.println(s6.hashCode()); //全部相同
      
System.out.println(s1 == s2); //true,指向常量池中相同内存
System.out.println(s1 == s3); //true,指向常量池中相同内存
System.out.println(s1 == s4); //true,指向常量池中相同内存
System.out.println(s1 == s5); //false,一个指向堆一个指向常量池
System.out.println(s1 == s6); //false,一个指向堆一个指向常量池
System.out.println(s5 == s6); //false,指向堆中不同内存区域

Double、Long、Integer

对于String的不可变性(包括内容不可变和实现方式不可变),以及hashCode唯一性,Double、Long、Integer也是一样的。不同的是它们没有运行时和编译时的区别,都是在堆上分配内存。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值