final 关键词可用于修饰类、变量和方法,final关键字用于表示它修饰的类、方法和变量不可改变。
final 修饰的变量如果获得了初始值就不可被改变,final就可以修饰成员变量也可以修饰局部变量、形参。
final变量必须由程序员显示的指定初始值。归纳起来final修饰的成员变量和实例变量可以指定初始值的位置有以下位置:
系统不会对局部变量初始化,只能由程序员显式的初始化。因此使用final定义的局部变量时,既可以指定默认值,也可以不指定默认值。
因为形参是由系统传递进来的初始化值,因此使用final修饰的形参不能被赋值。
正如上文所说final修饰的所有变量都是不可变的,但是与基本数据类型不同的是,java中引用类型保存的只是实际变量的地址。因此,final修饰的引用变量所指向的地址是不可以改变的,但是变量是可以改变的。
以下代码演示了final修饰的引用变量变化的示例:
public class App {
public static void main(String[] args) throws Exception {
final FinalDemo finalDemo= new FinalDemo();
finalDemo.name= "test1";
finalDemo.getName();
finalDemo.name="test2";
finalDemo.getName();
}
}
class FinalDemo{
public String name;
public void getName(){
System.out.println("the name of the class is "+ name);
}
}
输出结果如下:
the name of the class is test1
the name of the class is test2
对于一个final变量来说,不管他是类变量、实例变量还是局部变量,只要该变量满足三个条件,这个final变量就不再是一个变量,而是相当于一个直接量。
public class test
{
public static void main(String[] args){
final var i = 5;
System.out.println(i);
}
}
上述程序中的代码定义了一个final局部变量,并在定义该final变量时指定初始值为5.对这个程序来说,变量i根本不存在,当程序执行 println方法时,直接转化为System.out.println(5)。
final修饰符的一个重要用途就是定义“宏变量”。当定义final变量时就为该变量指定了初始值,而该初始值可以在编译时就被确定下来,那么这个final变量本质上就是一个“宏变量”,编译器会把程序当中所有用到该变量的地方直接替换为该变量的值。
除了上面那种直接指定初始值的方法,如果被赋的表达式只是基本的算术表达式或者字符串连接运算,没有访问普通变量,调用方法,java编译器同样会将这种final变量当作“宏变量”处理。
final修饰的方法不可被重写,如果出于某些原因不愿意被此方法被子类重修,可是使用final关键词定义该方法。
public class App {
public static void main(String[] args) throws Exception {
final FinalDemo finalDemo= new FinalDemo();
finalDemo.name= "test1";
finalDemo.getName();
finalDemo.name="test2";
finalDemo.getName();
}
}
class FinalDemo{
public String name;
final public void getName(){
System.out.println("the name of the class is "+ name);
}
}
class FinalDemoOverride extends FinalDemo{
//以下代码在编译时会报错
@Override
public void getName(){
}
}
对于一个private方法,因为它仅在当前类中可见,其子类无法访问该方法,所以子类无法重写该方法——如果子类定义了一个重名方法也只是定义了一个新方法。因此即使使用了final修饰了private方法,子类依旧可以定义一个重名方法。
class FinalDemo{
public String name;
final private void getName(){
System.out.println("the name of the class is "+ name);
}
}
class FinalDemoOverride extends FinalDemo{
//以下代码在编译时不会报错
private void getName(){
}
}
final修饰的方法只是不能被重写,并非不能被重载
以下写法没有任何问题。
class FinalDemo{
public String name;
final private void getName(){
System.out.println("the name of the class is "+ name);
}
private void getName(int a){
System.out.println("the name of the class is "+ name);
}
}
final修饰的类不能拥有子类,例如java.lang.Math类就是一个final类,它不能拥有子类。
不可变类(immuable)是指创建该类的实例之后,该实例的实例变量是不可改变的。Java的八个包装类和string类都是不可变类,当创建他们的实例之后,其实例变量不可改变。
如若想要自定义不可变类需要遵循以下几点:
以下代码就定义了一个不可变类
public class ImmuableClass{
private final int a;
private final int b;
public ImmuableClass(int x , int y){
this.a = x;
this.b = y;
}
}
与不可变类相对应的时可变类,可变类的含义时该类的实例变量时可变的。大部分时候创建的类都是可变类,特别时javaBean,因为总是为其实例变量提供了set和get方法。
与可变类相比,不可变类的实例在整个生命周期中永远处于初始化状态,它的实例变量的值不可改变,因此对不可变类的实例控制将更加简单。
当使用final修饰引用变量时,仅表示这个引用类型的变量不可被重新赋值,但是引用类型所指向的对象依旧可以改变。这就产生了一个问题:当创建不可变类时,如果它包含成员变量的类型时可变的,那么其对象的成员变量值依然是可改变的——这个不可变类其实时失败的。
这种情况的示例代码如下
public class Person {
private final name name;
public Person(name name){
this.name=name;
}
public name getName(){
return this.name;
}
public static void main(String[] args){
name name = new name("孙", "悟空");
Person person = new Person(name);
System.out.println(person.getName().
getFirstName()+" "+person.getName().getLastName());
name.setFirstName("猪");
name.setLastName("八戒");
System.out.println(person.getName().
getFirstName()+" "+person.getName().getLastName());
}
}
class name{
private String firstName;
private String lastName;
public name(String s1 , String s2){
this.firstName = s1;
this.lastName = s2;
}
public void setFirstName(String string){
this.firstName = string;
}
public void setLastName(String string){
this.lastName = string;
}
public String getLastName(){
return this.lastName;
}
public String getFirstName(){
return this.firstName;
}
}
以上代码输出结果如下
孙 悟空
猪 八戒
为了保持类的不可变性,必须保护好Person对象引用类型的成员变量:name,让程序无法访问到Person对象的name变量。也就无法利用name变量的可变性来改变Person对象了。
因此将Person代码修改一部分。
public Person(name name){
//设置name实例变量为临时创建的name对象,该对象的firstName与lastName
//与传入的name参数的成员变量相同
this.name=new name(name.getFirstName() , name.getLastName());
}
public name getName(){
//返回一个匿名对象,该对象的firstName与lastName
//与该对象的name参数的成员变量相同
return new name(name.getFirstName() , name.getLastName());
}