6.4 final修饰符
一些情况下,我们是不想让子类重写父类的方法的。比如在父类中表达了一个意思是希望不被更改,但我们知道,如果子类出现了同名变量,就会出现重写。但我们又想让别人用,不想让子类去覆盖父类的功能。这个时候我们引入一个新的关键字:final。
final关键字可用于修饰类、变量和方法,用于表示它修饰的类、方法和变量不可改变。final修饰变量时,表示该变量一旦获得了初始值,就不可被改变,final既可以修饰成员变量,也可以修饰局部变量、形参。
6.4.1 final成员变量
成员变量是随着类初始化或者对象的初始化而初始化的。那么成员变量的初始值可以在定义变量时指定默认的初始值也可以在初始化块或者构造器中指定初始值。
对于final修饰的成员变量而言,一旦有了初始值,就不能被重新赋值。如果没有在定义成员变量时指定初始值,也没有在初始化块、构造器中指定初始值,那么这些成员变量也就失去了意义。因此Java语法规定:final修饰的成员变量必须由开发人员显示的指定默认值,系统不会对final成员变量进行隐式赋值。
使用final修饰的成员变量初始化归纳如下:
类Field:必须在静态初始化块中或声明该Field时指定初始值。
实例Field:必须在非静态初始化块、声明该Field或构造器中指定初始值。
举例1:
package com.langsin.test;
public class Test {
public Test(){
s3 = "abc"; //变量s3在构造器中初始化
}
public static final int a; //类变量a定义时没有初始化,但在静态初始化块中进行初始化
public static final int b = 2; //类变量b在定义时进行了初始化
static{
a = 1; //在静态初始化块中对a进行了初始化
}
public final String s1 = "abc"; //普通成员变量在定义时进行了初始化
public final String s2; //普通成员变量在s2在初始化块中进行了初始化。
{
s2 = "123";
}
public final String s3; //普通成员变量s3在定义时没有初始化,但是在构造器中进行了初始化。
}
在继承中查看,父类中一个普通的成员变量,一个final修饰的成员变量,如果用在子类中重新为变量赋值会出现一种什么情况呢?
6.4.2 final局部变量
系统不会对局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用final修饰局部变量时,既可以在定义时指定默认值,也可以不指定默认值。
如果final修饰的局部变量在定义时没有指定默认值,则可以在后面代码中对该final变量赋初始值,但只能赋值一次,不能重复赋值。
举例1:
public class Test {
public void run(){
final int a = 1;
for(int i=0;i<5;i++){
a = i; //程序编译时报错,提示去掉final
}
System.out.println(a);
}
}
由此发现,用final定义的变量无法重新赋值,其实这个变量是一个常量:自定义常量。常量分为字面值常量和自定义常量。字面值常量:1,2,‘a’。自定义常量:final int a = 1;
6.4.3 final修饰基本类型和引用类型变量的区别
在讲之前先看一看下面这个代码。
Eg:
(1)public class Student{
int age = 18;
}
(2)public class Test{
public static void main(String[] args){
Student st = new Student();
System.out.println(st.age);
st.age = 20;
System.out.println(st.age);
final Student s1 = new Student();
System.out.println(st1.age);
s1.age = 20;
System.out.println(s1.age);
}
}
使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final只能保证这个引用类型变量所引用的地址不会改变,即一直指向这个对象,但这个对象内的内容完全可以改变。
举例1:
package com.langsin.test;
public class Test {
private final Student st = new Student();
public void run(){
st.setAge(36);
st.setName("王二");
System.out.println(st.getAge());
System.out.println(st.getName());
}
public static void main(String[] args) {
Test test = new Test();
test.run();
}
}
class Student{
private int age = 25;
private String name = "张三";
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
6.4.4 final方法
final修饰的方法不可被重写,出于某些原因,不希望子类重写父类的某些方法,则可以使用final修饰该方法。
Java提供的Object类里中提供的getClass()方法就是使用final进行修饰的,因为Java不希望任何类重写这个方法,所以使用final把这个方法封闭起来。但对于该类提供的toString()和equals()方法,都允许子类重写,因此没有使用final修饰他们。
举例1:
package com.langsin.test;
public class Bird {
public final void fly(){
System.out.println("小鸟可以在天空中自由飞翔。。。");
}
}
package com.langsin.test;
public class Ostrich extends Bird {
public void fly(){
System.out.println("鱼儿在水中游来游去。。。");
}
}
子类Ostrich在重写fly方法时编译报错。
注意:对于使用private修饰的方法,表示私有方法,只能是对象本身调用,那么子类在继承时是无法访问该方法的,所以子类无法重写该方法。如果子类中定义一个与父类private方法相同方法名、相同返回值类型、相同参数列表的方法,也不是重写,而是定义了一个新的方法。因此即使使用final修饰一个使用private访问权限的的方法,依然可以在其子类中定义一个与该方法一模一样的方法。
package com.langsin.test;
public class Bird {
private final void fly(){
System.out.println("小鸟可以在天空中自由飞翔。。。");
}
}
package com.langsin.test;
public class Ostrich extends Bird {
public void fly(){
System.out.println("鱼儿在水中游来游去。。。");
}
}
此时,此类中定义的方法将不会编译报错。
修饰基本类型和引用类型变量的区别。修饰变量时无法被重新赋值。修饰方法无法被重写。修饰类无法被继承。
6.4.5 final类
final修饰的类不可以有子类,例如:java.lang.Math类就是一个final类,它不可以有子类。
当子类继承父类时,将可以访问到父类内部数据,并可通过重写父类的方法来改变父类方法的实现细节,这可能会导致一些不安全的因素。为了确保某个类不被继承,则可以使用final类修饰这个类。
6.4.6 不可变类
不可变类的意思是创建该类的实例后,该实例的Field是不可改变的。Java提供的8个基本数据类型的包装类和java.lang.String类都是不可变类,当创建他们的实例后,其实例的Field不可改变。
举例:
Double d = new Double("12.5");
String str = new String("abc");
创建两个对象,并传入了两个字符串作为参数,那么Double类和String类肯定需要提供实例成员变量来存放这个两个参数,但程序无法修改这两个实例成员的值。
因此,如果需要创建自定义的不可变类,可遵循如下规则:
1.使用private和final修饰符来修饰该类的Field
2.提供参数构造器,用于根据传入参数来初始化类里的Field
3.仅为该类的Field提供getter方法。