final修饰符

final可以用来修饰类,成员变量和方法,用于表示他修饰的类,成员变量和方法是不可改变的。

用final修饰,并不是不能赋值,而是赋值后不能改变或者说,只能赋值一次。

修饰成员变量

final修饰的成员变量必须由程序员显示的指定初始值。

原因:当类初始化的时候,系统会为该类的类变量分配内存,并设置默认值;当创建对象的时候,系统会为对象的实例变量分配内存,并设置默认值。对于final修饰的成员变量而言,一旦有了初始值,就不能被重新赋值,如果既没有在定义的地方指定初始值,也没有在初始化块,构造器中指定初始值,那么这些成员变量的值,将一直是系统默认分配的0,‘\u0000’,false或null,这些成员变量就完全失去了存在的意义。所以java规定:final修饰的成员变量必须由程序员显示的指定初始值。

final修饰的成员变量可以指定初始值的地方:

  • 类变量:声明的时候或者是静态初始化块中,只能是二选一。
  • 实例变量:声明的时候,普通初始化块或者构造器,只能是三选一。

静态初始化块不能对实例变量赋值,因为静态成员不能访问非静态成员。类变量不能在普通初始化块中赋值,因为类变量已经在静态代码块中被赋值了,所以不能重新赋值。

在final修饰的成员变量被初始化之前,不要访问成员变量,会报错。

修饰局部变量

系统不会对局部变量进行显示的初始化,局部变量必须由程序员显示的进行初始化。因此使用final修饰局部变量的时候,既可以在定义的时候指定默认值,也可以不指定默认值。

如果final修饰的局部变量在定义的时候没有指定初始值,则可以在后面的代码中对该final变量赋初始值,但只能一次,不能重赋值;如果final修饰的局部变量在定义的时候已经指定了初始值,则后面的代码中不能再赋值。

1
2
3
4
5
6
7
8
9
10
11
public void testOne(final int a){
a=5;//不能对final修饰的形参赋值
}

public void testTwo(){
final Stirng str = "hello";
str = "";//出错
final double d;
d = 0.5;
d = 0.6;//出错
}

修饰基本类型变量和引用类型变量的区别

修饰基本类型的变量的时候,不能对基本类型的变量重新赋值,此基本类型的变量不会发生改变。当修饰引用类型的变量的时候,这个引用变量的值不可以改变,但是这个引用变量所引用的对像是可能会发生改变的。

1
2
3
4
5
6
7
8
9
10
11
12
class Person{
private int age;
public Person(){}
public Person(int age){this.age=age;}
....getter和setter方法....
}

public static void main(String[] args){
final Person p = new Person(28);
p.setAge(29);//合法
p=null;//报错
}

宏替换

final修饰的变量,只要满足三个条件,就不再是变量,而是相当于一个直接量。

  • 使用final修饰符
  • 在定义final变量的时候,指定了初始值
  • 该初始值在编译的时候就可以确定下来

如果一个变量满足了上面的三个条件,这个变量就相当于一个“宏变量”,编译器会把程序中所有用到该变量的地方直接替换成这个变量的值。

1
2
final int a = 10;
System.out.print(a);

以上代码实际上相当于变量a不存在,System.out.print(a)相当于System.out.print(5);

如果赋值表达式只是基本的算数表达式或者字符串连接运算,没有访问普通变量,调用方法,java编译器同样会把这种变量当成“宏变量”处理。

1
2
3
4
5
6
7
final int a = 10+10;//“宏变量”
final int b = 10/10;//“宏变量”
final String strA = "ha"+"ha";//“宏变量”
final String strB = "haha"+"10";//“宏变量”
final String strC = "haha"+String.valueof(10);//非“宏变量”
System.out.print(strB == "haha10");//true
System.out.print(strC =="haha10");//false

深入

1
2
3
4
5
6
7
String s1= "我擦嘞";
String s2 = "我擦"+"嘞";
String strA = "我擦";
String strB = "嘞";
String s3 = strA+strB;
System.out.print(s1 == s2);//true
System.out.print(s1 == s3);//false

strA和strB只是两个普通的变量,编译的时不会执行宏替换,编译的时候无法确定s3的值,s3无法指向字符串常量池中的“我擦嘞”。

如果想要输出true,只要让strA和strB执行宏替换即可,即将它们声明为final类型即可。

修饰方法

final方法不可以被重写

修饰类

final类不能被继承

不可变类

创建实例后,实例变量不可以改变的类,称为不可变类。

Java中的不可变类:8个包装类和String类。

自定义不可变类,遵循如下规则:

  • 使用final和private修饰该类的成员变量
  • 提供带参数的构造器,用于根据传入的参数来初始化类里的成员变量
  • 仅为该类的成员变量提供getter方法
  • 如果有必要,重写该类的hashCode方法和equals方法。equals方法根据关键字成员变量来作为两个对象是否相等的标准,除此以外还应该保证两个用equals方法判断相等的对象,hashCode值也相等

特殊情况,不可变类中的成员变量是一个引用类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Name{
private String xing;
private String ming;
public Name(){}
public Name(String xing,String ming){
this.xing = xing ;
this.ming = ming;
}
....省略getter和seter方法
}

public class Person{
private final Name name;
public Person(){}
public Person(Name name){
this.name = new Name(name.getXing(),name.getMing());
}
pulic Name getName(){
return new Name(name.getXing(),name.getMing());
}
}

缓存实例的不可变类

不可变类的实例状态不可以改变,可以很方便的被多个对象所共享。如果程序经常需要使用相同的不可变类实例,则应该考虑缓存这种不可变类的实例。毕竟重复创建相同的对象没有太大的意义,而且加大系统的开销。如果可能,应该将已经创建的不可变类的实例进行缓存。

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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修饰符来隐藏构造器,是外部无法通过new
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;
}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();
}
}
public class CacheImmutaleTest{
public static void main(String []args){
CacheImmutale c1=CacheImmutale.valueof("hello");
CacheImmutale c2=CacheImmutale.valueof("hello");
//true
System.out.println(c1==c2);
}
}

Java中的Integer类就采用了与CacheImmutale类相同的处理策略,如果采用new构造器来创建对象,则每次返回都是新的;使用valueof()方法创建对象则会缓存该方法创建的对象

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器