精华内容
下载资源
问答
  • [面试题]-String为什么不可变

    千次阅读 2019-04-28 16:42:09
    String类被final修饰,不可被继承。 String的成员变量char数组value被final修饰,初始化后不可更改引用。 String的成员变量value访问修饰符private,不对外界提供修改value数组值的方法。 String类源码 public ...
    • String类被final修饰,不可被继承。
    • String的成员变量char数组value被final修饰,初始化后不可更改引用。
    • String的成员变量value访问修饰符为private,不对外界提供修改value数组值的方法。

    String类源码

    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        private final char value[];
    
        /** Cache the hash code for the string */
        private int hash; // Default to 0
    
    展开全文
  • String不可变以及不可变类总结

    千次阅读 2017-10-01 20:11:38
    String类在java中是immutable(不可变),因为它被关键字final修饰。... 要了解String类创建的实例为什么不可变,首先要知道final关键字的作用:final的意思是“最终,最后”。final关键字可以修饰类、方法...

          String类在java中是immutable(不可变),因为它被关键字final修饰。当String实例创建时就会被初始化,并且以后无法修改实例信息。String类是工程师精心设计的艺术品。

    一、String为什么不可变?

           要了解String类创建的实例为什么不可变,首先要知道final关键字的作用:final的意思是“最终,最后”。final关键字可以修饰类、方法、字段。修饰类时,这个类不可以被继承;修饰方法时,这个方法就不可以被覆盖(重写),在JVM中也就只有一个版本的方法--实方法;修饰字段时,这个字段就是一个常量。

            查看java.lang.String方法时,可以看到:

    /**
     * The String class represents character strings. All string literals in Java programs, such as "abc",
     * are implemented as instances of this class.
    */
    public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        private final char value[];
       ...
    }

    "The String class represents character strings"意思是String类表示字符串;String类被关键字final修饰,可以说明它不可被继承;从"private final char value[]"可知,String本质上是一个char数组

            String类的成员字段value是一个char[]数组,而且也是用final关键字修饰,被final关键字修饰的字段在创建后其值是不可变的,但也只是value这个引用地址不可变,可是Array数组的值却是可变的,Array数组数据结构如下图所示:

    从图中可以看出,Array变量只是栈上(stack)的一个引用,数组中的数据存储在堆上(heap)。String类里的value是用final修饰,只能说在栈上(stack)这个value的引用地址不可变,可没说堆里的Array本身数据不可变。看下面这个例子:

    final int[] value = {1,2,3,4,5};
    int otherValue = {6,7,8,9,10};
    value = otherValue;//编译报错

    value是被final关键字修饰的,编译器不允许把value指向堆另外一个地址;但如果直接对数组元素进行赋值,就允许;如下面这个例子:

    final int[] value  = {1,2,3,4,5};
    value[0] = 0;

            所以说String是不可变,在后面所有的String方法里没有去动Array中的元素,也没有暴露内部成员字段。private  final char value[],private的私有访问权限的作用都比final大。所以String是不可变的关键都是在底层实现的,而不是一个简单的final关键字。

    二、String类实例不可变的内存结构图

            String类实例不可变很简单,如下图所示是String类实例不可变的内存结构图:

     

    有一个字符串s的值为"abcd",它再次被赋值为"abcdef",不是在原堆的地址上修改数据,而是重新指向一个新的对象,新的地址。

    三、字符串常量池

            字符串常量池是方法区(Method Area)中一个特殊的存储区域。当一个字符串被创建时,如果这个字符串的值已经存在String pool中,就返回这个已经存在的字符串引用,而不是创建一个新的对象。下面的代码只会在堆中创建一个对象:

    String name="abcd";
    String userName="abcd";

    这样在大量使用字符串的情况下,可以节省内存空间,提高效率。但之所以能实现这个特性,String的不可变性是最基础的一个必要条件。

    四、String类不可变有什么好处?

        最简单的就是为了安全和效率。从安全上讲,因为不可变的对象不能被改变,他们可以在多个线程之间进行自由共享,这消除了进行同步的要求;从效率上讲,设计成final,JVM才不用对相关方法在虚函数表中查询,而是直接定位到String类的相关方法上,提高执行效率;总之,由于效率和安全问题,String被设计成不可变的,这也是一般情况下,不可变的类是首选的原因。

    五、不可变类

            不可变类只是它的实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例时就提供,并在对象 的整个生命周期内固定不变。String、基本类型的包装类、BigInteger和BigDecimal就是不可变得类。

            为了使类成为不可变,必须遵循以下5条规则:①不要提供任何会修改对象状态的方法②保证类不会被扩展③使所有的域都是final④使所有的域都成为私有的⑤确保 对于任何可变组件的互斥访问。如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用。

    六、不可变类的优点和缺点

            不可变类实例不可变性,具有很多优点。①不可变类对象比较简单。不可变对象可以只有一种状态,即被创建时的状态。②不可变对象本质上是线程安全的,它们不要求同步。当多个线程并发访问这样的对象时,它们不会遭到破坏。实际上,没有任何线程会注意到其他线程对于不可变对象的影响。所以,不可变对象可以被自由地分配。“不可变对象可以被自由地分配”导致的结果是:永远不需要进行保护性拷贝。③不仅可以共享不可变对象,甚至也可以共享它们的内部信息。④不可变对象为其他对象提供了大量的构件。如果知道一个复杂对象内部的组件不会改变,要维护它的不变性约束是比较容易的。

            不可变类真正唯一的缺点是,对于每个不同的值都需要一个单独的对象。创建这种对象的代价很高。

    七、如何构建不可变类?

            构建不可变类有两种方式:用关键字final修饰类让类的所有构造器都变成私有的或者包级私有的,并添加公有的静态工厂来替代公有的构造器

            为了具体说明用静态工厂方法来替代公有的构造器,下面以Complex为例:

    //复数类
    public class Complex{
        //实数部分
        private final double re;
        //虚数部分
        private final double im;
        //私有构造器
        private Complex(double re,double im){
            this.re = re;
            this.im = im;
        }
        //静态工厂方法,返回对象唯一实例
        public static Complex valueOf(double re,double im){
            return new Complex(re,im);
        }
        ...
    }

          不可变的类提供一些静态工厂方法,它们把频繁请求的实例缓存起来,从而当现在实例符合请求的时候,就不必创建新的实例。使用这样的静态工厂方法使得客户端之间可以共享现有的实例,而不是创建新的实例,从而减低内存占用和垃圾回收的成本。

           总之,使类的可变性最小化。不要为每个get方法编写一个相对应的set方法,除非有很好的理由要让类成为可变的类,否则就应该是不可变的。如果有些类不能被做成是不可变的,仍然应该尽可能地限制它的可变性。不可变的类有很多优点,但唯一的缺点就是在特定的情况下存在潜在的性能问题。

     

    PS:静态工厂方法是什么?

    静态工厂方法只是一个返回类的实例的静态方法,如下面是一个Boolean的简单实例。这个方法将boolean基本类型值转换成一个Boolean对象引用。

    public static Boolean valueOf(boolean b){
        return b?Boolean.TRUE?Boolean.FALSE;
    }

    静态工厂方法相对于构造器来说,具有很多优势:

     

    创建的方法有名字

    不必在每次调用它们的时候都创建一个新的对象

    可以返回原返回类型的任何子类的对象。这样我们在选择返回对象的类时就有更大的灵活性,这种灵活性的一种应用是API可以返回对象,同时又不会使对象的类变成公有的。以这种方式隐藏实现类会使API变得非常简洁,这项技术适用于基于接口的框架。

    在创建参数化类型实例时,它们使代码变得更加简洁。编译器可以替你找到类型参数,这被称为类型推导。如下面这个例子

    public static<k,v> HashMap<k,v> newInstance(){
        return new HashMap<k,v>();
    }

    静态工厂方法也有缺点:

    类如果不含公有的或者受保护的构造器,就不能被子类化。对于公有的静态工厂方法所返回的非公有类也同样如此。

    它们与静态方法实际上没有什么区别

    简而言之,静态工厂方法和公有构造器都各有用处,我们需要理解它们各自的长处。结合实际情况,再做选择。

     

     

    参考文章:Why String is immutable in Java?

                      在java中String类为什么要设计成final?程序员--知乎

    展开全文
  • Java中的String为什么不可变的? -- String源码分析

    万次阅读 多人点赞 2014-01-15 22:07:12
    众所周知, 在Java中, String类是不可变的。那么到底什么不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象...

    什么是不可变对象?

    众所周知, 在Java中, String类是不可变的。那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。


    区分对象和对象的引用

    对于Java初学者, 对于String是不可变对象总是存有疑惑。看下面代码:
    		
    		String s = "ABCabc";
    		System.out.println("s = " + s);
    		
    		s = "123456";
    		System.out.println("s = " + s);
    		

    打印结果为:
    s = ABCabc
    s = 123456

    首先创建一个String对象s,然后让s的值为“ABCabc”, 然后又让s的值为“123456”。 从打印结果可以看出,s的值确实改变了。那么怎么还说String对象是不可变的呢? 其实这里存在一个误区: s只是一个String对象的引用,并不是对象本身。对象在内存中是一块内存区,成员变量越多,这块内存区占的空间越大。引用只是一个4字节的数据,里面存放了它所指向的对象的地址,通过这个地址可以访问对象。
    也就是说,s只是一个引用,它指向了一个具体的对象,当s=“123456”; 这句代码执行过之后,又创建了一个新的对象“123456”, 而引用s重新指向了这个心的对象,原来的对象“ABCabc”还在内存中存在,并没有改变。内存结构如下图所示:



    Java和C++的一个不同点是, 在Java中不可能直接操作对象本身,所有的对象都由一个引用指向,必须通过这个引用才能访问对象本身,包括获取成员变量的值,改变对象的成员变量,调用对象的方法等。而在C++中存在引用,对象和指针三个东西,这三个东西都可以访问对象。其实,Java中的引用和C++中的指针在概念上是相似的,他们都是存放的对象在内存中的地址值,只是在Java中,引用丧失了部分灵活性,比如Java中的引用不能像C++中的指针那样进行加减运算。

    为什么String对象是不可变的?

    要理解String的不可变性,首先看一下String类中都有哪些成员变量。 在JDK1.6中,String的成员变量有以下几个:
    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence
    {
        /** The value is used for character storage. */
        private final char value[];
    
        /** The offset is the first index of the storage that is used. */
        private final int offset;
    
        /** The count is the number of characters in the String. */
        private final int count;
    
        /** Cache the hash code for the string */
        private int hash; // Default to 0

    在JDK1.7中,String类做了一些改动,主要是改变了substring方法执行时的行为,这和本文的主题不相关。JDK1.7中String类的主要成员变量就剩下了两个:
    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        private final char value[];
    
        /** Cache the hash code for the string */
        private int hash; // Default to 0


    由以上的代码可以看出, 在Java中String类其实就是对字符数组的封装。JDK6中, value是String封装的数组,offset是String在这个value数组中的起始位置,count是String所占的字符的个数。在JDK7中,只有一个value变量,也就是value中的所有字符都是属于String这个对象的。这个改变不影响本文的讨论。 除此之外还有一个hash成员变量,是该String对象的哈希值的缓存,这个成员变量也和本文的讨论无关。在Java中,数组也是对象(可以参考我之前的文章 java中数组的特性)。 所以value也只是一个引用,它指向一个真正的数组对象。其实执行了String s = “ABCabc ”; 这句代码之后,真正的内存布局应该是这样的:

    value,offset和count这三个变量都是private的,并且没有提供setValue, setOffset和setCount等公共方法来修改这些值,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改, 并且在String类的外部不能访问这三个成员。此外,value,offset和count这三个变量都是final的, 也就是说在String类内部,一旦这三个值初始化了, 也不能被改变。所以可以认为String对象是不可变的了。

    那么在String中,明明存在一些方法,调用他们可以得到改变后的值。这些方法包括substring, replace, replaceAll, toLowerCase等。例如如下代码:
    		
    		String a = "ABCabc";
    		System.out.println("a = " + a);
    		a = a.replace('A', 'a');
    		System.out.println("a = " + a);
    		

    打印结果为:
    a = ABCabc
    a = aBCabc

    那么a的值看似改变了,其实也是同样的误区。再次说明, a只是一个引用, 不是真正的字符串对象,在调用a.replace('A', 'a')时, 方法内部创建了一个新的String对象,并把这个心的对象重新赋给了引用a。String中replace方法的源码可以说明问题:

    读者可以自己查看其他方法,都是在方法内部重新创建新的String对象,并且返回这个新的对象,原来的对象是不会被改变的。这也是为什么像replace, substring,toLowerCase等方法都存在返回值的原因。也是为什么像下面这样调用不会改变对象的值:

    		String ss = "123456";
    		
    		System.out.println("ss = " + ss);
    		
    		ss.replace('1', '0');
    		
    		System.out.println("ss = " + ss);

    打印结果:
    ss = 123456
    ss = 123456


    String对象真的不可变吗?

    从上文可知String的成员变量是private final 的,也就是初始化之后不可改变。那么在这几个成员中, value比较特殊,因为他是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗? 比如将数组中的某个位置上的字符变为下划线“_”。 至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组。
    那么用什么方式可以访问私有成员呢? 没错,用反射, 可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。下面是实例代码:

    	public static void testReflection() throws Exception {
    		
    		//创建字符串"Hello World", 并赋给引用s
    		String s = "Hello World"; 
    		
    		System.out.println("s = " + s);	//Hello World
    		
    		//获取String类中的value字段
    		Field valueFieldOfString = String.class.getDeclaredField("value");
    		
    		//改变value属性的访问权限
    		valueFieldOfString.setAccessible(true);
    		
    		//获取s对象上的value属性的值
    		char[] value = (char[]) valueFieldOfString.get(s);
    		
    		//改变value所引用的数组中的第5个字符
    		value[5] = '_';
    		
    		System.out.println("s = " + s);  //Hello_World
    	}

    打印结果为:
    s = Hello World
    s = Hello_World

    在这个过程中,s始终引用的同一个String对象,但是再反射前后,这个String对象发生了变化, 也就是说,通过反射是可以修改所谓的“不可变”对象的。但是一般我们不这么做。这个反射的实例还可以说明一个问题:如果一个对象,他组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。








    展开全文
  • Java中String为什么不可变的?

    千次阅读 多人点赞 2018-10-29 23:11:53
    Java中String为什么不可变的?

    一、引言

    在我们的日常开发中,肯定必用 java.lang 包下的 String类。我们每天都使用,以至于都忽略的String是怎么实现的。甚至有的人或许把它当做基本数据类型来使用,其实不然。下面本文从Java内存模型展开,结合 JDK 中 String类的源码进行深入分析,力求能够对String类的原理和使用,作一个全面和准确的介绍。

    二、Java 内存模型 和 变量

    1、Java的内存模型

    对于Java的内存模型,在https://blog.csdn.net/zxd1435513775/article/details/80996034文中介绍过,此处就不在做过多介绍,可以参考上文。

    2、常量和变量

    注意:在理解常量和变量之前,我们要特别理解Java内存模型中的:栈和堆的概念。

    (1)、变量:一般把内存地址不变,值可以改变的东西称为变量。换句话说,在内存地址不变的前提下,内存的内容是可变的,例如:
    public class StringExample { 
    
        public static void example(){  
            Student s1 = new Student(1,11);  
            Student s2 = s1; 
            System.out.printfln("s1: " +  s1.toString());   
            System.out.printfln("s2: " + s2.toString());   
    
            s1.d = 3;  
            s1.age = 32;  
            System.out.printfln("s1: " +  s1.toString());   
            System.out.printfln("s2: " + s2.toString());   
    
            System.out.println( s1 == s2 );  // true : 引用值不变,即对象内存地址不变,但内容改变
        }
    }  
    
    (2)、常量:一般把内存地址不变,则值也不可以改变的东西称为常量。典型的 String 就是不可变的,所以称之为常量(constant)。此外,我们可以通过final关键字来定义常量,但严格来说,只有基本类型被其修饰后才是常量(对基本类型来说是其值不可变,而对于对象变量来说其引用不可再变,即在栈中的引用不能改变)。

    三、String 定义

    1、定义(JDK1.6中)

    在这里插入图片描述

    2、说明:
    (1)、上面是 String 类中定义的前面几行,也是它的成员变量;
    
    (2)、String 的底层实现是基于 char[] 数组来实现的;
    
    (3)、由 JDK 中关于String的声明可以知道:
    
    	不同字符串可能共享同一个底层char数组。例如字符串 String s="abc" 与 s.substring(1) 就共享同一个char数组:
    	char[] c={‘a’,’b’,’c’}。其中,前者的offset 和 count 的值分别为0和3,后者的offset 和 count 的值分别为1和2;
    	 	
    	offset 和 count 两个成员变量不是多余的,比如,在执行substring操作时;
    
    (4)、也就是说,如果在String第一次赋值后,当char[]数字已经创建完成,则String的长度是不可变的,这是在创建数组时,
    	由数组决定的;
    
    (5)、String 不属于八种基本数据类型,String 的实例是一个对象。因为对象的默认值是null,所以String的默认值也是null;
    	但它又是一种特殊的对象,有其它对象没有的一些特性(String 的不可变性导致其像八种基本类型一样,
    	比如,作为方法参数时,像基本类型的传值效果一样)。例如:
    	
    (6)、new String() 和 new String("")都是声明一个新的空字符串,是空串不是null;
    
    	public class StringTest {
    
    	    public static void changeStr(String str) {
    	        String s = str;
    	        str += "welcome";
    	        System.out.println(s);
    	    }
    	
    	    public static void main(String[] args) {
    	        String str = "1234";
    	        changeStr(str);
    	        System.out.println(str);
    	    }
    	}
    
    	/* Output: 
    	        1234
    	        1234 
    	*///:~ 
    

    四、String 的不可变性

    1、什么是不可变对象?

    在 Java 中,String 类是不可变类的典型代表 (基本类型的包装类都是不可改变的),也是Immutable设计模式的典型应用。String变量一旦初始化后就不能更改(指的是内容不能改变,由char[]数组决定,而char[]数组长度不可变,由于数组的性质决定),禁止改变对象的状态,从而增加共享对象的坚固性、减少对象访问的错误,同时还避免了在多线程共享时进行同步的需要。那么,到底什么是不可变的对象呢?

    可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态指的是不能改变对象内的成员变量,包括:

    (1)、基本数据类型的值不能改变;
    
    (2)、引用类型的变量不能指向其他的对象;(Java内存模型中,栈中的值不能改变)
    
    (3)、引用类型指向的对象的状态也不能改变;(Java内存模型中,堆中定义好的对象的值不能改变)
    
    (4)、除了构造函数之外,不应该有其它任何函数(至少是任何public函数)修改任何成员变量;
    
    (5)、任何使成员变量获得新值的函数都应该将新的值保存在新的对象中,而保持原来的对象不被修改。
    
    2、区分引用和对象

    对于Java初学者, 对于String是不可变对象总是存有疑惑。看下面代码:

    	String s = "ABCabc";
    	System.out.println("s = " + s);    // s = ABCabc
    	
    	s = "123456";
    	System.out.println("s = " + s);    // s = 123456
    

    上述代码中,首先会创建一个 String 对象 s (栈中),然后让 s 的值为“ABCabc”(堆内存中分配空间), 然后又让 s 的值为“123456”(堆内存)。 从打印结果可以看出,s 的值确实改变了。那么怎么还说String对象是不可变的呢? 其实这里存在一个误区:

    s 只是一个 String 对象的引用,并不是对象本身。对象在内存中(指堆内存)是一块内存区,成员变量越多,这块内存区占的空间越大。引用只是一个 4 字节的数据(在栈中,代表指向堆内存的地址),里面存放了它所指向的对象的地址,通过这个地址可以访问对象。 也就是说,s 只是一个引用,它指向了一个具体的对象,当 s=“123456”; 这句代码执行过之后,又创建了一个新的对象“123456”, 而引用 s 重新指向了这个心的对象,原来的对象“ABCabc”还在内存中存在,并没有改变。内存结构如下图所示:
    在这里插入图片描述

    Java 和 C++ 的一个不同点是,在 Java 中,引用是访问、操纵对象的唯一方式: 我们不可能直接操作对象本身,所有的对象都由一个引用指向,必须通过这个引用才能访问对象本身,包括获取成员变量的值,改变对象的成员变量,调用对象的方法等。而在C++中存在引用,对象和指针三个东西,这三个东西都可以访问对象。其实,Java中的引用和C++中的指针在概念上是相似的,他们都是存放的对象在内存中的地址值,只是在Java中,引用丧失了部分灵活性,比如Java中的引用不能像C++中的指针那样进行加减运算。

    注意:引用代表的是地址

    3、为什么String对象是不可变的?

    要理解 String 的不可变性,首先看一下String类中都有哪些成员变量。 在JDK1.6中,String 的成员变量有以下几个:

    	public final class String implements java.io.Serializable, Comparable<string>, CharSequence{
    
    	    /** The value is used for character storage. */
    	    private final char value[];
    	
    	    /** The offset is the first index of the storage that is used. */
    	    private final int offset;
    	
    	    /** The count is the number of characters in the String. */
    	    private final int count;
    	
    	    /** Cache the hash code for the string */
    	    private int hash; // Default to 0</string>
    

    在JDK1.7中,String 类做了一些改动,主要是改变了 substring() 方法执行时的行为,这和本文的主题不相关。JDK1.7中String类的主要成员变量就剩下了两个:

    	public final class String implements java.io.Serializable, Comparable<string>, CharSequence {
    
    	    /** The value is used for character storage. */
    	    private final char value[];
    	
    	    /** Cache the hash code for the string */
    	    private int hash; // Default to 0</string>
    

    由以上的代码可以看出, 在 Java 中,String 类其实就是对字符数组char []的封装。JDK6中, value 是String封装的数组,offset 是 String 在这个 value 数组中的起始位置,count 是 String 所占的字符的个数。在JDK7中,只有一个 value 变量,也就是 value 中的所有字符都是属于 String 这个对象的。这个改变不影响本文的讨论。 除此之外还有一个 hash 成员变量,是该 String 对象的哈希值的缓存,这个成员变量也和本文的讨论无关。在Java中,数组也是对象。 所以 value 也只是一个引用,它指向一个真正的数组对象。其实执行了String s = “ABCabc”; 这句代码之后,真正的内存布局应该是这样的:

    在这里插入图片描述

    value,offset 和 count 这三个变量都是 private 的,并且没有提供 setValue,setOffset 和 setCount等公共方法来修改这些值,所以在 String 类的外部无法修改 String。也就是说一旦初始化就不能修改, 并且在 String类 的外部不能访问这三个成员。此外,value,offset 和 count 这三个变量都是final的, 也就是说在 String 类内部,一旦这三个值初始化了, 也不能被改变。所以,可以认为 String 对象是不可变的了。

    那么在 String 中,明明存在一些方法,调用他们可以得到改变后的值。这些方法包括substring(), replace(), replaceAll(), toLowerCase()等。例如如下代码:

    	String a = "ABCabc";
    	System.out.println("a = " + a);    // a = ABCabc
    	
    	a = a.replace('A', 'a');
    	System.out.println("a = " + a);    //a = aBCabc
    

    那么 a 的值看似改变了,其实也是同样的误区。再次说明, a 只是一个引用, 不是真正的字符串对象,在调用 a.replace(‘A’, ‘a’) 时, 方法内部创建了一个新的 String对象 ,并把这个心的对象重新赋给了引用 a 。String中replace方法的源码可以说明问题:

    在这里插入图片描述

    我们可以自己查看其他方法,都是在方法内部重新创建新的 String 对象,并且返回这个新的对象,原来的对象是不会被改变的。这也是为什么像replace(), substring(),toLowerCase() 等方法都存在返回值的原因。也是为什么像下面这样调用不会改变对象的值:

    	String ss = "123456";
    	System.out.println("ss = " + ss);     // ss = 123456
    	
    	ss.replace('1', '0');
    	System.out.println("ss = " + ss);     //ss = 123456
    
    4、String对象真的不可变吗?

    从上文可知,String 的成员变量是 private final 的,也就是初始化之后不可改变。那么在这几个成员中, value 比较特殊,因为它是一个引用变量,而不是真正的对象。value 是 final 修饰的,也就是说 final 不能再指向其他数组对象(地址不能改变),那么我能改变 value 指向的数组中元素的值吗? 比如,将数组中的某个位置上的字符变为下划线“_”。 至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组,那么,用什么方式可以访问私有成员呢? 没错,用反射,可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。下面是实例代码:

    	public static void testReflection() throws Exception {
    
    	    //创建字符串"Hello World", 并赋给引用s
    	    String s = "Hello World"; 
    	
    	    System.out.println("s = " + s); //Hello World
    	
    	    //获取String类中的value字段
    	    Field valueFieldOfString = String.class.getDeclaredField("value");
    	
    	    //改变value属性的访问权限
    	    valueFieldOfString.setAccessible(true);
    	
    	    //获取s对象上的value属性的值
    	    char[] value = (char[]) valueFieldOfString.get(s);
    	
    	    //改变value所引用的数组中的第5个字符
    	    value[5] = '_';
    	
    	    System.out.println("s = " + s);  //Hello_World
    	}
    

    在这个过程中,s 始终引用的同一个 String 对象,但是在反射前后,这个 String 对象发生了变化, 也就是说,通过反射是可以修改所谓的“不可变”对象的。但是一般我们不这么做。这个反射的实例还可以说明一个问题:如果一个对象,它组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。

    五、String 对象创建方式

    1、字面值形式:
    JVM虚拟机会自动根据字符串常量池中字符串的实际情况来决定是否创建新对象 (要么不创建,要么创建一个对象,关键要看常量池中有没有)
    

    JDK 中明确指出:

    String s = "abc";
    

    等价于:

    char data[] = {'a', 'b', 'c'};
    String str = new String(data);
    

    该种方式先在栈中创建一个对String类的对象引用变量s,然后去查找 “abc”是否被保存在字符串常量池中。若 ”abc” 已经被保存在字符串常量池中,则在字符串常量池中找到值为”abc”的对象,然后将 s 指向这个对象; 否则,在堆中创建char数组 data,然后在堆中创建一个 String 对象 object,它由 data 数组支持,紧接着这个String对象 object 被存放进字符串常量池,最后将 s 指向这个对象。例如:

        private static void test01(){  
    	    String s0 = "kvill";        // 1
    	    String s1 = "kvill";        // 2
    	    String s2 = "kv" + "ill";     // 3
    	
    	    System.out.println(s0 == s1);       // true  
    	    System.out.println(s0 == s2);       // true  
    	}
    

    执行第 1 行代码时,“kvill” 入常量池并被 s0 指向;执行第 2 行代码时,s1 从常量池查询到” kvill” 对象并直接指向它;所以,s0 和 s1 指向同一对象。 由于 ”kv” 和 ”ill” 都是字符串字面值,所以 s2 在编译期由编译器直接解析为 “kvill”,所以 s2 也是常量池中”kvill”的一个引用。 所以,我们得出 s0 == s1 == s2;

    2、通过 new 创建字符串对象 :

    一律在堆中创建新对象,无论字符串字面值是否相等 (要么创建一个,要么创建两个对象,关键要看常量池中有没有)

    String s = new String("abc");  
    

    等价于:

    String original = "abc"; 
    String s = new String(original);
    

    所以,通过 new 操作产生一个字符串(“abc”)时,会先去常量池中查找是否有“abc”对象,如果没有,则创建一个此字符串对象并放入常量池中。然后,在堆中再创建“abc”对象,并返回该对象的地址。所以,对于 String str = new String(“abc”):如果常量池中原来没有”abc”,则会产生两个对象(一个在常量池中,一个在堆中);否则,产生一个对象。
     
    用 new String() 创建的字符串对象位于堆中,而不是常量池中。它们有自己独立的地址空间,例如,

        private static void test02(){  
    	    String s0 = "kvill";  
    	    String s1 = new String("kvill");  
    	    String s2 = "kv" + new String("ill");  
    	
    	    String s = "ill";
    	    String s3 = "kv" + s;    
    	    
    	    System.out.println(s0 == s1);       // false  
    	    System.out.println(s0 == s2);       // false  
    	    System.out.println(s1 == s2);       // false  
    	    System.out.println(s0 == s3);       // false  
    	    System.out.println(s1 == s3);       // false  
    	    System.out.println(s2 == s3);       // false  
    	}  
    

    例子中,s0 还是常量池中”kvill”的引用,s1 指向运行时创建的新对象”kvill”,二者指向不同的对象。对于s2,因为后半部分是 new String(“ill”),所以无法在编译期确定,在运行期会 new 一个 StringBuilder 对象, 并由 StringBuilder 的 append 方法连接并调用其 toString 方法返回一个新的 “kvill” 对象。此外,s3 的情形与 s2 一样,均含有编译期无法确定的元素。因此,以上四个 “kvill” 对象互不相同。StringBuilder 的 toString 为:

    	public String toString() {
        	return new String(value, 0, count);   // new 的方式创建字符串
        }
    

    构造函数 String(String original) 的源码为:

    	/**
         * 根据源字符串的底层数组长度与该字符串本身长度是否相等决定是否共用支撑数组
         */
        public String(String original) {
            int size = original.count;
            char[] originalValue = original.value;
            char[] v;
            if (originalValue.length > size) {
                // The array representing the String is bigger than the new
                // String itself. Perhaps this constructor is being called
                // in order to trim the baggage, so make a copy of the array.
                int off = original.offset;
                v = Arrays.copyOfRange(originalValue, off, off + size);  // 创建新数组并赋给 v
            } else {
                // The array representing the String is the same
                // size as the String, so no point in making a copy.
                v = originalValue;
            }
    
            this.offset = 0;
            this.count = size;
            this.value = v;
        }
    
    展开全文
  • Java中的String为什么不可变的?

    万次阅读 多人点赞 2018-09-03 21:27:55
    与其问String为什么不可变的,还不如问String类是如何实现其对象不可变的。 什么是不可变对象?  如果一个对象它被创建后,状态不能改变,则这个对象被认为是不可变的。 String是如何实现其对象不可变?  ...
  • Java中的String为什么不可变

    千次阅读 2018-07-23 15:00:42
    String不可变如下图: 假设给字符串s赋值abcd,第二次重新赋值abcdef,这时候并不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。 2、String毛不可变? 看源码的前3行你就懂了。 1、首先...
  • 为什么Java里面String类是不可变

    千次阅读 2019-01-06 18:39:28
    在Java里面String类型是不可变对象,这一点毫无疑问,那么为什么Java语言的设计者要把String类型设计成不可变对象呢?这是一个值得思考的问题 Java语言的创建者James Gosling,曾经在一次采访中被人问到:什么时候...
  • 为什么String类型是不可变的?

    千次阅读 2013-11-22 01:18:45
    这篇文章将对字符串的驻留做详细的解释和剖析。 引自:...原文:这是一个很久以前就备受瞩目的问题,有很多原因导致String在Java中是不可变的量,首先要解决这个问题,取决于你对内存布局,同步问题,
  • String不可变很简单,如下图,给一个已有字符串“abcd”第二次赋值成"abced",不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。 2.String为什么不可变? 翻开JDK源码,java.lang.String类起手前三行...
  • 为什么String要设计成不可变的?

    万次阅读 多人点赞 2013-11-18 20:30:03
    翻译人员: 铁锚 翻译日期: 2013年11月18日 原文链接: Why ... 在Java中将String设计成不可变的是综合考虑到各种因素的结果,想要理解这个问题,需要综合内存,同步,数据结构以及安全等方面的考虑.
  • 熟悉Java的都知道String类是不可变,但String为什么设计成不可变类,如何做到不可变没有仔细想过。 String为什么设计成不可变类? 1.符合Java字符串池的设计方式。[java] view plain copy String s1=”abc”; ...
  • 字符串在Java中是不可变的,因为String对象缓存在String池中。由于缓存的字符串在多个客户之间共享,因此始终存在风险,其中一个客户的操作会影响所有其他客户。例如,如果一段代码将String “Test”的值更改...
  • String为什么不可变的? String、StringBuffer与StringBuffer区别 String是字符串常量 StringBuffer和StringBuilder是字符串变量 有时候大家会很疑惑,为什么String会是常量,如下代码: 1. String s = &...
  • String a = "hello"; System.out.println(a); a = " world!!!"; System.out.println(a); 输出一个是 hello 一个world!!! 为什么String不可变的。
  • 众所周知, 在Java中, String类是不可变的。那么到底什么不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象...
  • 0. 前言 如果一个对象,在它创建完成之后不能再改变它的状态,包括对象内的成员变量... String为什么不可变的 首先看一下String类的源码中://JDK1.6 public final class String implements java.io.Serializab
  • Java中的String字符串为什么不可变

    千次阅读 2016-05-02 20:38:10
    众所周知, 在Java中, String类是不可变的。那么到底什么不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象...
  • 前几天,有个实习生问我,String为什么被设计成不可变的,说他看见网上的都说可变。我看了看他,说:那篇博客你没看完吧!他挠了挠头回答说:没有!我又问他:知道string类有个变量value么?他说知道,我又问这个...
  • 1.String对象不可变、StringBuffer对象可变的含义: 举个例子:String str = "aa"; str = "aa"+"bb"; 此时str的值"aabb",但是"aabb"不是在开始的字符串"aa"后面直接连接的"bb",而是又新生成了字符串"aabb",字符...
  • String为什么不可改变?

    千次阅读 2018-07-17 20:40:21
    众所周知, 在Java中, String类是不可变的。那么到底什么不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象...
  • String变量为什么不可变的?

    千次阅读 2016-09-21 23:11:58
    String
  • Java基础整理-为什么String不可变

    千次阅读 2018-03-22 15:41:18
    为什么String不可变String所有属性都被final修饰、私有的并且没有提供修改方法。 (主要字段是char数组,虽然被final修饰但数组是可变的,私有保证了不被修改但还是可以通过反射来改变String为什么String设计...
  • 为什么String被设计为不可变

    千次阅读 2015-06-05 17:58:59
    对象不可变定义 不可变对象是指对象的状态在被初始化以后,在整个对象的生命周期内,不可改变。 如何不可变 通常情况下,在java中通过以下步骤实现不可变 对于属性不提供设值方法 所有的属性定义private final ...
  • java中String类设计成不可变的原因

    千次阅读 2018-07-22 00:55:08
     String不可变很简单,如下图,给一个已有字符串“abcd”第二次赋值成"abced",不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。 2.String为什么不可变?从原理上分析。  翻开JDK源码...
  • 为什么String要设计成不可变

    千次阅读 2018-08-16 22:11:22
    在Java中将String设计成不可变的是综合考虑到各种因素的结果,想要理解这个问题,需要综合内存,同步,数据结构以及安全等方面的考虑. 在下文中,我将各种原因做一个小结。 1. 字符串常量池的需要 字符串常量池...
  • 简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,...
  • JAVA String为什么是Final不可变

    千次阅读 多人点赞 2020-12-09 19:58:16
    为什么为什么? 原因: 1、因为要实现字符串池 什么是字符串池:java中的字符串池是存储在Java堆内存中的字符串池 ...3、为了实现String可以创建HashCode不可变性 我们先来看一下String的源码 可以...
  •  相信大家在开发中,对于final修饰符都比较常见,它会将修饰的变量固定不可重复赋值。但是我们是否思考过被它所修饰属性变量或类存储在哪里呢?下面已我将用比较有趣的问答形式,让大家对final有一个全面的认识。在...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 638,481
精华内容 255,392
关键字:

string为什么不可变