夯实基础-java学习-final关键词

目录

  • final修饰符
  • final修饰变量
    • final修饰成员变量
    • final修饰局部变量
    • final修饰形参
    • final修饰基本类型和引用类型之间的区别
    • 可执行“宏替换”的final变量
  • final方法
  • final类
  • 不可变类

final修饰符

final 关键词可用于修饰类、变量和方法,final关键字用于表示它修饰的类、方法和变量不可改变。

final 修饰的变量如果获得了初始值就不可被改变,final就可以修饰成员变量也可以修饰局部变量、形参。

final修饰变量

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变量来说,不管他是类变量、实例变量还是局部变量,只要该变量满足三个条件,这个final变量就不再是一个变量,而是相当于一个直接量。

  • 使用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修饰的方法不可被重写,如果出于某些原因不愿意被此方法被子类重修,可是使用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类

final修饰的类不能拥有子类,例如java.lang.Math类就是一个final类,它不能拥有子类。

不可变类

不可变类(immuable)是指创建该类的实例之后,该实例的实例变量是不可改变的。Java的八个包装类和string类都是不可变类,当创建他们的实例之后,其实例变量不可改变。

如若想要自定义不可变类需要遵循以下几点:

  • 使用private和final修饰词来修饰该类的成员变量
  • 提供带参数的构造器用于初始化该实例的成员变量。
  • 可以定义成员变量的get方法,但是无需定义set方法。
  • 如果需要可以重修该类的equals方法和hashCode方法。

以下代码就定义了一个不可变类

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());
    }

你可能感兴趣的:(夯实基础-java学习-final关键词)