转载请注明原文地址:http://www.javashuo.com/article/p-mozkuidp-hm.htmlhtml
Java中的对象按照建立后,对象的内容是否能够被修改,分为 mutable object 和 immutable object。【注意:是对象的内容不可变,而不是指向该对象的引用变量内容不可变。】java
咱们常见的不可变对象是几个基本数据类型的包装类——Integer、Double、String等。【想一想为何?——Tips:出于节省内存开销,避免重复建立。】c++
不可变类有5大基本原则:正则表达式
1)类定义时,添加final修饰符,保证类不被继承【即:不容许在子类中被修改】数组
2)类定义时,其成员变量一律使用 final private 修饰,保证变量私有的同时不容许修改缓存
3)不提供能够修改为员变量的方法,包括setter安全
4)在构造函数中采用deep copy的形式将参数值拷贝给成员变量,而不是直接将参数值赋给成员变量【由于引用类型的参数只是传了一个地址,这样在外部改变该地址的内容会致使不可变对象的成员变量改变】多线程
5)在成员变量的getter方法中,不能直接返回成员变量自己,而是返回成员变量的copy对象【这也是为了防止引用类型的成员变量被外部获取后,改变引用指向的对象值引发不可变对象的内容变化】app
1)字符串常量池的须要——避免每次使用相同的字符串常量都从新建立相同的对象、节省存储空间jvm
2)线程安全:当一个String对象被多个线程共享时,无需担忧线程安全问题
3)支持hash和缓存:因为字符串对象是不可变的而且hashcode就缓存在对象中【在下文讲解】了,不须要从新计算,所以很适合做为Map中的键,由于字符串键的哈希处理速度要快过其它的键对象【这就是HashMap中的键每每都使用字符串的缘由】
4)使用类加载器时,要用字符串来传递加载的类名,而字符串的不可变性提供了安全性,以保证正确的类被加载。
5) 缺点:当对String变量有从新赋值、修改等操做时,会不断建立大量的String对象。【当修改后的值未出如今字符串常量池的前提下】————【延伸:所以,在代码中涉及大量字符串操做时,使用StringBuilder或StringBuffer来进行】
三、“不可变对象”的很是规手段修改
对于不可变对象,能够经过反射机制的手段改变其值——获取类的字段定义->改变该字段的可见性和可修改性->修改对象的变量值。
例如:
//建立字符串"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
咱们先来看一下程序:
String s = "my.test.txt"; System.out.println(s.replace(".", "#")); System.out.println(s.replaceAll(".", "#")); System.out.println(s.replaceFirst(".", "#"));
它们的结果会同样吗?——No。
my#test#txt
###########
#y.test.txt
这三个函数中,replaceFirst、replaceAll在替换时使用了正则表达式,所以上面三个函数的参数含义是不一样的。
replace(src,des):将字符串中的src子串[也是一段字符串]替换成des。
replaceAll(reg,des):将字符串中符合reg模式的内容替换成des。
replaceFirst(reg,des):将字符串中,第一个匹配reg模式的内容替换成des。
因此上面示例代码中,replace函数是将字符串中的“.”字符换成“#”,而replaceAll则是将全部字符[“.”是正则表达式的通配符]替换成“#”,replaceFirst则是将第一个字符替换成“#”。
java不支持运算符重载,由于java的语法比较繁杂,会致使使用类对象 像基本数据类型那样 用运算符进行操做时,没法作到像c++同样流畅。所以,Java中针对类对象的运算操做通常都是经过方法来定义,而不是运算符重载。
惟一例外的是String类,它的拼接运算(+) 通过了重载,这个重载是经过jvm编译实现的,具体原理能够 手写一个字符串相+的java类文件并编译,而后经过 javap -c 文件.class 查看具体的过程。
其原理是:String的+会被转化为StringBuilder的append方法,并生成一个新的String对象返回。
Java中有如下五种方法处理字符串拼接:
1. 加号 “+”:适用于小数据量的操做,代码简洁方便。
2. String contact() 方法:适用于小数据量的操做,代码简洁方便。
3. StringUtils.join() 方法:适用于将ArrayList转换成字符串的情景,能够省掉用for循环读取ArrayList手动拼接的过程。
4. StringBuffer append() 方法:继承自AbstractStringBuilder,效率高,大批量的数据处理的好选择,该方法线程安全,因为加了线程锁,速度会比下面第5中慢一点。
5. StringBuilder append() 方法:继承自AbstractStringBuilder,效率最高,大批量的数据处理的好选择,该方法线程不安全,所以速度最快,若是不涉及多线程操做,优先使用此方法。
同:都返回参数的字符串表示形式。
异: 对空值的调用结果不一样:
java.lang.Object类里已有public方法.toString(),因此对任何严格意义上的java对象均可以调用此方法。但在使用时要注意,必须保证object不是null值,不然将抛出NullPointerException异常。
而valueOf(Object obj)对null值进行了处理,不会报任何异常。但当object为null 时,String.valueOf(object)的值是字符串”null”,而不是null。
Java1.7以前,switch只能局限于int 、short 、byte 、char四类作判断条件,由于在JVM内部实际大部分字节码指令只有int类型的值。
在使用switch的时候,若是是非int型,会先转为int型,再进行条件判断。
可是在Java1.7中,switch增长了对String做为判断条件的支持,可String并不能直接转为int型,这是怎么作到的呢?
原理:switch比较的是字符串常量的哈希值(缓存的int类型值,前文提到过),可是hash值可能会有冲突,因此还须要再调用equals方法将 switch(param)的param 与 case str的str 进行二次比较,两者综合之下达到惟一匹配的目的。
全局字符串常量池(string pool):全局常量池在每一个JVM中只有一份,存放的是字符串常量值。
string pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(也就是咱们常说的用双引号括起来的),
字符串在第一次出现时被建立而且把引用放到stringtable中,在后面再出现时就不会重复建立而是直接从stringtable中找到字符串字面值的地址,返回给字符串引用变量。
Class常量池:class常量池是在编译的时候每一个class都有的,用于编译阶段,存放的是class文件中的字面量(常量)和符号引用。
字面量就是咱们所说的常量,如文本字符串、八种基本类型的值、被声明为final的常量值等。
符号引用是一组符号,用来描述所引用的目标,符号能够是任何形式的字面量,只要使用时能无歧义地定位到目标便可。
直接引用是指向方法区的本地指针,相对偏移量或是一个能间接定位到目标的句柄。
运行时常量池:运行时常量池是在类加载完成以后,将每一个class常量池中的符号引用值转存到运行时常量池中,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。
运行时常量池更具动态性,在运行期间也能够将新的变量放入常量池中,而不是必定要在编译时肯定的常量才能放入。最主要的运用即是String类的intern()方法
str.intern()函数:把字符串对象str加入常量池中,若是常量池中已有该字符串字面值,则返回stringtable中的引用值。(这样作主要是为了不在堆中不断地建立新的字符串对象)
常量池咱们都知道他是存在于方法区的,他是方法区的一部分,而方法区是线程共享的,因此常量池也就是线程共享的,可是他并非线程不安全的,他实际上是线程安全的,由于它让有相同值的引用指向同一个位置。若是引用值变化了,可是常量池中没有新的值,那么就会新开辟一个常量结果来交给新的引用。
详细原理请看:http://www.javashuo.com/article/p-ghvihton-ko.html
咱们以String s1=new String("abc");为例 首先当咱们的类Class在被ClassLoader加载时,"abc"被做为常量读入,在String Pool(字符串常量池)建立了一个"abc"的实例。 而后,调用到new String("abc")的时候,会在Heap里面复制一个相同的对象。
(1)类加载对一个类只会进行一次。"abc"在类加载时就已经建立并驻留了(若是该类被加载以前已经有"abc"字符串被驻留过则不须要重复建立用于驻留的"abc"实例)。驻留的字符串引用是放在全局共享的字符串常量池中的。[加载时建立一次]
(2)在这段代码后续被运行的时候,"abc"字面量对应的String实例已经固定了,不会再被重复建立。因此这段代码将常量池中的对象实例复制一份放到heap中,而且把heap中的这个对象的引用交给s1 持有。[运行时复制一次]
所以,这条语句建立了2个对象。【这两个引用,它们的对象实例是不一样的。】
String类中定义了一个私有成员变量——hash,它是一个整数,保存String对象的哈希值,也就是说String类型的对象的哈希值不会重复计算,计算过一次后就保存起来了,之后在被hash时直接取该值便可,无需从新计算。
这个值在String对象第一次被调用时进行初始化(懒加载,不是在String对象建立时初始化)。