一、final关键字介绍
1、介绍:
直译为最终的,可以修饰在类上(内部类、外部类,此两者是相对而言的)、方法上(实例方法、静态方法)、变量上(局部变量、成员变量)。
2、修饰对象的特点:
在修饰类时,修饰的类不能够被继承。例如:String, StringBuilder
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
。。。。
}
在修饰方法时,该方法不能够被重写。例如:Object的 getClass() 方法
public final native Class<?> getClass();
在修饰变量(局部变量、成员变量),该变量表示一个常量,只能够被赋值一次,且需要在声明时或在代码块或构造方法中中显式赋值。
//常量,用static与final修饰,可以使用静态代码块初始化,使用代码块、构造方法报错。
class A{
private static final int i;
static {
i = 1;
}
}
//不可变量,使用final修饰,使用代码块、构造方法初始化,使用静态代码块报错。
class A{
private final int i;
{
i = 1;
}
//或,二者选其一
public A(int i1){
i = i1;
}
}
class A{
public int addOne(final int x) {
return ++x; // return x + 1是可以的;
}
}
由于由fianl修饰的变量只能够被赋值一次,因此x的值不能够被改变。
二、不可变对象
1、介绍:
即一旦被创建,属性与状态在其生命周期内不可被改变。
例如:String、包装类。
2、理解不可变类
让并发编程更加简单:
大多数情况下,对于资源互斥的访问,通过加锁的方式来实现对资源的串行访问,来保证线程安全,比如:Synchonized,Lock,这种方案的问题在于:加锁和解锁的需要十分慎重,如果加速和解锁的时机不对,就会引发重大问题,这个问题编译器无法发现。
对于引发线程安全问题的根本原因:多个线程需要同时访问同一个共享资源
假如没有共享资源,那么多线程安全问题就自然解决了,Java中提供的ThreadLocal机制就是采取的这种思想
但大多数时候需要共享资源来实现相互通信,如果这个资源在创建之后完全不再变更,
如同一个常量,而多个线程间并发读取该共享资源是不会存在线上安全问题的,因为所有线程无论何时读取该共享资源,总是能获取到一致的、完整的资源状态。
消除副作用:
例如:在调用某个方法时改变了属性的值,从而导致了外部指向的对象也发生了改变,如果这个对象是不可变的,那么就不会发生这种副作用
减少容器使用过程出错的概率:
当我们存储一个对象到容器中,一旦我们对这个对象发生改变,那么在检测时,这个对象可能就会出现不存在这个容器的现象。
public class Person {
private int age;
private String identityCardID;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getIdentityCardID() {
return identityCardID;
}
public void setIdentityCardID(String identityCardID) {
this.identityCardID = identityCardID;
}
@Override
public boolean equals(Object obj) {
Object otherObject = obj;
if(otherObject == this) return true;
if(otherObject == null) return false;
if(otherObject instanceof Parent){
Person other = (Person) otherObject;
return Objects.equals(this.getAge(),other.getAge());
}
return false;
}
@Override
public int hashCode() {
int hash = 0;
return 31 * hash + age;
}
}
public class Test{
public static void main(String[] args){
Person jack = new Person();
jack.setAge(10);
jack.setIdentityCardID("421182200903152XXX");
Set<Person>personSet=newHashSet<>();
personSet.add(jack);
jack.setAge(11);//修改值,此时hashCode的值也随之而改变
System.out.println(personSet.contains(jack));//false
}
}
此时的hashCode值改变,找不到所真实的对应的桶了。
3、创建不可变对象
1、类:
最好不允许类被继承(非必须)。
2、成员变量:
变量声明为private;最好用final修饰(非必须)。
3、方法:
不提供改变原有对象的方法:
1.通常采用不提供set方法。
2.如果提供修改方法,创建一个新的对象,并对新创建的对象上进行修改。
4、初始化:
通过构造器初始化所有成员变量,引用类型的成员变量进行深拷贝。
5、get方法:
不能对外暴露this引用和成员变量的引用
public final class People implements Cloneable{
private final String name;
private final int age;
private final Friend friend;
public People(String name, int age, Friend friend){
this.name = name; //String本身就是不可变类
this.age = age;
this.friend = (Friend) friend.clone();
}
public People getPeople(){
return (People) this.clone();
}
@Override
public Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
class Friend implements Cloneable{
@Override
public Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
class Test{
public static void main(String[] args) {
People people = new People("小智", 21, new Friend());
People people1 = people.getPeople();
System.out.println(people);//XXXX.People@6e8cf4c6
System.out.println(people1);//XXXX.People@12edcd21
}
}
4、不可变类是否真的不可变 ?
不可变对象虽然具备了不可变性,但不可变对象并非是不可修改的,可以通过反射手段改变不可变对象的状态,不可变对象的意义分析能看出来对象的不可变性只是用来辅助帮助大家更简单地去编写代码,减少程序编写过程中出错的概率,这是不可变对象的初衷。如果真要靠通过反射来改变一个对象的状态,此时编写代码的人也应该会意识到此类在设计的时候就不希望其状态被更改,从而引起编写代码的人的注意。