概述
final关键字算是与static都比较常用的一个关键字,它可以作用于属性、方法、参数和类。
声明后,
属性 -> 不可变;
方法 -> 不可覆盖;
形参 -> 不可变;
类 -> 不可继承。
Final属性(常量)
在概述中已经提到,被final关键字修饰的属性是不可变的。
在这个例子中我重新对str赋值,然后报了个语法错误,他说str不能被分配,如果想改变它的值的话,让我移除掉fianl关键字。
这时候,str在编译的时候值就已经被确定了,值也不能被修改。
Final属性(构造器、代码块、方法初始化)
上面说的只能确定一个值,怎么样的使用才能让这个final的属性的赋值更加灵活呢?
构造器
这种不在定义的时候初始化,而在构造函数中初始化的,也叫做空白Final变量。
public class FinalNoun {
private final String str;
public FinalNoun(String str) {
this.str = str;
}
public void print() {
System.out.println(str);
}
}
在这个例子中,我先把被final修饰的str不让它赋值,值在这个类的构造方法中进行赋值。需要了解构造方法可以戳这里。
public class Test {
public static void main(String[] args) {
new FinalNoun("TEST").print();;
}
}
去new这个类的时候,构造方法被调用,值也确实被赋上了。
TEST
代码块
既然前面提到构造方法是可以的,那么代码块肯定是也可以的,只不过构造代码块要比构造方法要执行的早一点。
public class FinalNoun {
private final String str;
{
this.str = "TEST";
}
public void print() {
System.out.println(str);
}
}
输出同样也是
TEST
但是代码块只能使用普通的,而不能使用静态的,因为静态代码块执行要早。要想了解静态代码块的话,戳这里。
方法
比如有一个方法是生成任意整数,它只有在执行的时候才可以知道这个方法返回的最终的值。
public class FinalNoun {
private final int str = new Random().nextInt();
public void print() {
System.out.println(str);
}
}
首先要拿到这个任意整数之后才可以赋值给str,还算比较好理解。
Final属性(引用数据类型)
先来看一个例子:
public class Monkey {
private String name = "";
public Monkey(String value){
this.name = value;
}
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
}
这只是一个普普通通的类,对象里有一个name属性,还有普普通通的get、set方法。
而就在我修改一个被final修饰的引用的时候,编辑器却提示该操作与final关键字是冲突的。
原来,final关键字修饰的是引用,而不是对象。所以,被final修饰过后,只有引用指向堆中的地址不变,而地址上的对象怎么变都可以。
为了验证对象的属性是可以被改变的,我把对象的name给它重新赋值。
public class Test {
public final Monkey mMonkey = new Monkey("Huang");
public void change() {
mMonkey.setName("Zhang");
System.out.println(mMonkey.getName());
}
public static void main(String[] args) {
new Test().change();
}
}
看到打印,对象的name属性确实是被改变了。
Zhang
Final形参
先来一个基本数据类型,int吧。
当一个方法的形参被final修饰的时候,这个参数在该方法内不可以被修改。
那么,我们来试试引用数据类型的。
public class Test {
public void change(final Monkey monkey) {
//不可修改引用
//monkey = new Monkey("Zhang");
monkey.setName("Zhang");
System.out.println(monkey.getName());
}
public static void main(String[] args) {
new Test().change(new Monkey("Huang"));
}
}
当写到注释的地方,没让我们失望,还是报错了,可见,与上面一样,也是无法修改引用所指向的地址。
但这里的对象依然是可以被改变的。
Zhang
Final方法
假如我有一个类中有一个被fianl修饰了的方法:
public class Father {
public final void face() {
System.out.println("face");
}
public void face(String str) {
System.out.println(str);
}
public static void main(String[] args) {
new Father().face();
}
}
好像没什么不一样的,该调用调用,该重载重载。
那我再写一个子类去继承它:
public class Son extends Father {
@Override
public void face(String str) {
super.face(str);
}
}
发现我根本无法重写face()方法,只能重写face(String str)方法。
public class Test {
public static void main(String[] args) {
new Son().face();
}
}
在调用子类的face()方法的时候,也可以正常调用。
face
Final类
被final修饰的类不能被继承,但可以改变类内部的属性。
假如我把父类给Final掉,再用子类去继承它:
好像这也没什么用嘛!只是不能被继承而已。
其实不然呀,因为它是只读的,所以它在多线程的情况下是绝对安全的,也不需要用锁之类的去做线程同步,像String.java类就是final类。
补充
在匿名类中所有变量都必须是final变量;
接口中声明的所有变量本身是final的;
被 final static 共同修饰的,称为全局变量;
所有的private方法都隐式的指定为final的,为private方法添加final无任何意义;
final方法在编译阶段绑定,称为静态绑定;
没有在声明时初始化final变量的称为空白final变量。
参考文献
https://blog.csdn.net/qq_31655965/article/details/54800523
https://www.cnblogs.com/xiaoxiaoyihan/p/4974273.html#autoid-2-0-0