java中 什么是不可变对象,为什么说Java的String对象是不可变的
各位老铁们好,相信很多人对java中 什么是不可变对象都不是特别的了解,因此呢,今天就来为大家分享下关于java中 什么是不可变对象以及为什么说Java的String对象是不可变的的问题知识,还望可以帮助大家,解决大家的一些困惑,下面一起来看看吧!
Java什么只能查看不能修改
在Java中,可以使用关键字 final来定义一些只读变量或只读常量,这些变量或常量一旦被初始化后就不能再被修改。所以,Java中的 final只能用于声明常量和只读变量(也称为不可变对象),一旦被赋值就不能再进行修改。
对于基本数据类型(如 int、char等),final关键字可以用来定义常量,常量值一旦在声明时被赋值后就不能再被修改。例如:
final int MAX_SIZE= 100;
对于引用类型变量,final关键字可以用来定义只读变量,即该变量所引用的对象内容不可被修改,但该变量所引用的对象本身是可以被修改的。例如:
final List<String> myList= new ArrayList<>();
myList.add("hello");
上述代码中, myList变量被定义为 final类型,表示变量 myList只能指向一个 List对象,一旦指定对象后就不能再指向其他 List对象。但是,由于 myList指向的对象是 List类型,因此该对象的内容是可以被修改的,例如可以通过调用 add()方法向该 List对象中添加元素。
综上所述,Java中的 final关键字只能用于定义常量和只读变量,一旦被赋值之后就不能再进行修改。
java字符串为什么是不可变对象
从代码层面,看String类的源码你会发现,一个字符串对象,其元素是一个final修饰的byte数组,那么就意味着这个字符串对象初始化之后,其元素(byte数组)就无法重新赋值了,而且String类中并没有getValue获取这个数组元素的方法,所以就无法从数组内更改字符串对象的值,另外,字符串是放在字符串常量池的,既然是常量,自然应该是无法更改的.
通常代码中对 String的重新赋值,都是更改对象引用,就是说重新赋值之后的字符串对象,指向了另外一个字符串的内存地址,而原本字符串初始值的内存地址,并没有发生改变,值也没有发生改变,改变的是对象的引用地址.
为什么说Java的String对象是不可变的
今天晚上部门领导开会的时候,又突然想起来这个问题,这里记录下来自己的理解,期待以后会有更深刻的收获。
根据JDK中java.lang.String的源码进行分析,从中可以得出String类型的对象不可变的原因,大致上有如下两个:
1、java.lang.String类型在实现时,其内部成员变量全部使用final来修饰,保证成员变量的引用值只能通过构造函数来修改;
2、java.lang.String类型在实现时,在外部可能修改其内部存储值的函数实现中,返回时一律构造新的String对象或者新的byte数组或者char数组;
第2的重要性在于,假如通过String类型的toCharArray方法可以直接访问String类型内部定义的char数组,那么即便String类型内部的char数组使用了final来修饰,也仅仅保证这个成员变量的引用不可变,而无法保证引用指向的内存区域不可变。由上述两点,保证外部不可能修改java.lang.String类型对象的内部属性,从而保证String对象是不可变的。
提到String,就不得不提一下JDK中存在另外两个常用来表示字符串的类,StringBuffer和StringBuilder。根据注释,StringBuffer可谓老资格了,从JDK1.0时即伴随Java征战世界,而StringBuilder直到JDK1.5时才出现。
面试时,StringBuffer和StringBuilder的区别也是常问的话题,有些没有开发经验,对多线程编码不了解、对synchronized的使用不熟悉的兄弟,很容易在这个问题上吃亏。
StringBuffer和StringBuilder的共同点:
1、都是可变对象,对象内的字符缓存会随着拼接操作而动态扩展;
2、用来完成字符串拼接操作;
3、构造时传入内部缓存大小时,可以降低缓存扩展的次数,明显提升字符串拼接操作的效率;
StringBuffer和StringBuilder的区别:
1、StringBuilder的方法都是线程不安全的,从另外一个角度讲,StringBuilder类型的对象在做字符串拼接操作时,由于少了线程同步的操作,执行效率上有很大提升;
2、StringBuffer的方法都加上了synchronized关键字,因而在一定的场景下,StringBuffer类型的对象都是线程安全的,但在执行效率上,由于多了线程同步的操作,因而会有少许的损失;
在大多数场景下,字符串拼接操作都是不需要考虑多线程环境下对结果的影响的,因而使用StringBuilder类型可以提升代码的执行效率。
在多个线程的代码中共享同一个StringBuffer类型的对象时,需要关注synchronized关键字对最终结果的影响。由于StringBuffer类的实现中,仅仅对每个方法使用了synchronized修饰,这只能保证在多线程场景下,访问StringBuffer对象的同一个方法时可以保证最终结果的一致性,假如一个线程访问A方法,另外一个线程方法B方法,则由于加锁对象的不同,可能会出现不一致的现象,这是需要程序员特别要注意的地方。类似的,可以参考Vector的实现和应用场景。
如何创建不可变的Java类或对象
不可变对象是指一个对象的状态在对象被创建之后就不再变化。不可变对象对于缓存是非常好的选择,因为你不需要担心它的值会被更改。
创建一个不可变类:
将类声明为final,所以它不能被继承;
将所有的成员声明为私有的,这样就不允许直接访问这些成员;
对变量不要提供setter方法;
将所有可变的成员声明为final,这样只能对它们赋值一次;
通过构造器初始化所有成员,进行深拷贝(deep copy);
在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝;
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public final class Immutable{
private final int id;
private final String name;
private final HashMap map;
public int getId(){
return id;
}
public String getName(){
return name;
}
/**
*可变对象的访问方法
*/
public HashMap getMap(){
return(HashMap) testMap.clone();
}
/**
*实现深拷贝的构造器*/
public Immutable(int i, String n, HashMap hm){this.id=i;
this.name=n;
HashMap tempMap=new HashMap();
String key;
Iterator it= hm.keySet().iterator();
while(it.hasNext()){
key=it.next();
tempMap.put(key, hm.get(key));
}
this.map= tempMap;
}
}
不变模式有两种形式:弱不变模式和强不变模式。
弱不变模式,指类实例的状态是不可改变,但是这个类的子类的实例具有可能会变化的状态,要实现弱不变模式,一个类必须满足下面条件:
对象没有任何会修改对象状态的方法,这样一来当对象的构造函数将对象的状态初始化之后,对象的状态便不再改变;
属性都是私有的,以防客户端对象直接修改内部状态;
这个对象所引用的其他对象如果是可变的话,必须设法限制外界对这些可变对象的访问,以防止外界修改这些对象,尽量在不可变对象内部初始化这些被引用的对象,而不要在客户端初始化再传入到不可变对象内部来,如果某个可变对象必须在客户端初始化,然后再传入到不变对象里的话,就应当考虑在不可变对象初始化的时候,将这个可变对象进行拷贝。
好了,关于java中 什么是不可变对象和为什么说Java的String对象是不可变的的问题到这里结束啦,希望可以解决您的问题哈!