final修饰符
final关键字可以用于修饰类、变量和方法,被修饰的类、变量和方法初始化后就不可再改变
final成员变量
-
成员变量是随着类初始化或对象初始化而初始化的;对于final修饰的成员变量而言,如果既没有在定义成员变量时指定初始值,也没有在初始化块、构造方法中为成员变量指定初始值,那么这些成员变量的值将一直是系统默认分配的0、’\u0000’、false或null,这些成员变量也就失去存在的意义。因此Java语法规定:final修饰的成员变量必须由程序员显式地指定初始值
-
final修饰的类变量、实例变量能指定初始值的地方如下
- 类变量:必须在静态初始化块中指定初始值或生命该类变量时指定初始值,而且只能在两个地方之一指定
- 实例变量:必须在非静态初始化块、声明该实例变量或构造方法中指定初始值,而且只能在三个地方的其中之一指定
final局部变量
- 系统不会对局部变量进行初始化,局部变量必须由程序员显式初始化,因此使用final修饰局部变量时,既可以在定义时指定默认值,也可以不指定默认值
- 如果final修饰的局部变量在定义时没有指定默认值,则可以在后面代码中对该final变量赋初值,但只能一次,不能重复赋值
final修饰基本类型变量和引用类型变量的区别
- 当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变
可执行“宏替换”的final变量
-
final修饰符的一重要用途就是定义“宏变量”。对于一个任意final变量来说,只要该变量满足三个条件,这个final变量就不再是一个变量,而是相当于一个直接量,这个直接量本质是就是一种“宏变量”,编译器会把程序中所有用到该变量的地方直接替换成该变量的值
- 使用final修饰符修饰
- 在定义该final变量时指定了初始值
- 该初始值可以在编译时被确定下来
-
除了为final变量赋值时赋直接量的情况外,如果被赋的表达式只是基本的算数表达式或字符串连接运算,没有访问普通变量,调用方法,Java编译器通用会将这种final变量当初“宏变量”处理。
- 例如:
final int a=5+2; final double b=1.2/3; final String str="我是"+"一个演示"; final String book="33+66="+99;
- 例如:
-
宏变量与普通变量直接使用”==”运算符比较时,会返回false
final方法
- final修饰的方法不可被覆写,如果处于某些原因,不希望子类覆写父类的某个方法,可以使用final修饰符修饰该方法
- 如果用final修饰本身已经用private修饰的方法,那么因为这个方法对于子类是不可以见的,所以子类可以重新定义一个同名同参的方法
- final修饰的方法可以被重载
final类
- final修饰的类不可以有子类,俗称“太监类”
不可变(immutable)类
-
不可变类的意思是创建该类的实例后,该实例的实例变量是不可改变的,例如Java提供的8个包装类和String类就是不可变类
-
如果需要创建自定义的不可变类,可遵守如下规则
- 使用private和final修饰符来修饰该类的成员变量
- 提供带参数构造方法,用于根据传入参数来初始化类的成员变量
- 仅为该类的成员变量提供getter方法,不要为该类的成员变量提供setter方法
- 如果有必要,重写Object类的hashCode()和equals()方法。equals()方法根据关键成员变量来作为两个对象是否相等的标准,除此之外,还应该保证两个用equals()方法判断为相等的对象的hashCode()也相等
-
而如果使用final修饰引用变量时,其指向的对象依然可以改变,这就产生了一个问题:当创建不可变类时,如果它包含成员变量的类型是可变的,那么其对象的成员变量的值依然是可变的——这个不可变类其实是失败的
-
例如:
public class Test { public static void main(String[] args) { Name n = new Name("梓臻", "欧阳"); Person p = new Person(n); System.out.println(p.getName().getFirstName()); n.setFirstName("峰"); System.out.println(p.getName().getFirstName()); } } class Name { private String firstName, lastName; public Name() { } public Name(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } } class Person { private final Name name; public Person(Name name) { this.name = name; } public Name getName() { return name; } }
-
上面的程序中修改了Name类对象n的firstName的值,导致了Person类对象p的name的firstName改变,破坏了设计Person类的初衷,final修饰符失去了它的意义。为了保护Person类对象p的不可变性,必须让程序无法访问到Person对象的name成员变量,Person类的代码可以改下为如下:
class Person { private final Name name; public Person(Name name) { this.name = new Name(name.getFirstName(), name.getLastName()); } public Name getName() { return new Name(name.getFirstName(), name.getLastName()); } }
-
-
-
上面的程序中,Person的构造方法创建Person对象时并不是直接利用已有的Name对象,而是重新创建了一个Name对象来赋给Person对象的name实例变量。当Person对象返回name变量时,它并没有直接把name实例变量返回,直接返回那么实例变量的值也可能导致它所引用的Name对象被修改
-
缓存实例的不可变类
-
不可变类的实例状态不可改变,可以很方便地被多个对象所共享。如果程序经常使用相同的不可变类实例,则应该考虑缓存这种不可变类的实例
-
缓存是软件设计中一个非常有用的模式,缓存的实现方式有很多种,不同的实现方法可能存在较大的性能差别
-
例如
-
public class Test { public static void main(String[] args) { CacheImmutale c1=CacheImmutale.valueOf("Hello"); CacheImmutale c2=CacheImmutale.valueOf("Hello"); System.out.println(c1==c2+""+c1.getName()); } } class CacheImmutale { private static int MAX_SIZE = 10; // 使用数组来缓存已有的实例 private static CacheImmutale[] cache = new CacheImmutale[MAX_SIZE]; // 记录缓存实例在缓存中的位置,cache[pos-1]是最新缓存的实例 private static int pos = 0; private final String name; private CacheImmutale(String name) { this.name = name; } public String getName() { return name; } public static CacheImmutale valueOf(String name) { // 遍历已缓存的对象 for (int i = 0; i < MAX_SIZE; i++) { // 如果已有相同实例,则直接返回该缓存的实例 if (cache[i] != null && cache[i].getName().equals(name)) { return cache[i]; } } // 如果缓存池已满 if (pos == MAX_SIZE) { // 把缓存的第一个对象覆盖,把刚刚生成的对象放在缓存池的最开始位置 cache[0] = new CacheImmutale(name); // 把pos设为1 pos = 1; } else { // 把新创建的对象缓存起来,pos+1 cache[pos++] = new CacheImmutale(name); } return cache[pos - 1]; } public boolean equals(Object obj) { if (this == obj) { return true; } if (obj != null && obj.getClass() == CacheImmutale.class) { CacheImmutale ci = (CacheImmutale) obj; return name.equals(ci.getName()); } return false; } public int hashCode() { return name.hashCode(); } }
- 上面程序中,CacheImmutable类使用一个数组来缓存该类的对象,这个数组长度为MAX_SIZE。当缓存池已满时,则按照“先进先出”原则来决定哪个对象将被移出缓存池。CacheImmutable类能控制系统生成CacheImmutable对象的个数,且只能使用该类的valueOf()方法来得到去对象
- Integer类就采用了与CacheImmutable类相同的处理策略,如果采用new构造器来创建Integer对象,则每次返回全新的Integer对象;如果采用valueOf()方法来创建Integer对象(仅限-128-127之间的整数),则会缓存该方法创建的对象
-
加油
赶紧来学习一下,膜拜大神