final修饰符--java学习笔记
- final修饰成员变量
- final修饰局部变量
- final在修饰基本类型与引用类型的区别
- final与“宏替换”
- final方法
- final类
final
关键字可表示它修饰的类、方法、变量不可改变
final修饰成员变量
成员变量随着类/对象的初始化而初始化,此时系统为该变量分配内存以及默认值。可以在定义时就指定初始值,或者在初始化块、构造器中指定初始值。
但final
修饰的成员变量必须显式指定初始值。一旦有了初始值,就不能被重新赋值。具体来说:1. 若果是类变量,必须在静态初始化块中指定初始值或者声明类变量时指定初始值;2. 如果是实例变量,在非静态初始化块、声明该实例变量、构造器三者之一中指定初始值。
普通方法不为final
修饰的成员变量赋值,也不在普通方法中为final
成员变量指定初始值。
Java允许通过方法来访问final
成员变量,但这可能违背了final
成员变量设计初衷:对于final
成员变量,我们总希望能访问到固定的、显式初始化的值。
下面的程序显示final
修饰成员变量的效果。
public class FinalVariableTest{
// 定义成员变量就指定初始值,或在构造器或初始化块中分配初始值
final int a = 6;
final String str;
final int c;
final static double d;
{
str = "Hello";
a = 5;// cannot assign a value to final variable 'a'
}
static{
d = 1.1;
}
final char ch;
d = 1.2; // cannot assign a value to final variable 'd'
ch = 'a'; // 错误
}
下面代码展示了通过方法访问未初始化的final
变量。(实际不会这样做)
public class FinalErrorTest{
final int age;
// variable 'age' might not have been initialized
System.out.print(age);
printAge(); // 这类方法是合法的,将输出0
public void printAge(){
System.out.println(age);
}
public static void main(String[] args){
new FinalErrorTest();
}
}
final修饰局部变量
系统不会对局部变量进行初始化,因此在定义使用了final
修饰的局部变量时是否指定默认值是可选的。下面的程序示范了final
修饰局部变量、形参的场景。
public class FinalLocalVariableTest{
public void test(final int a){
a = 5; // cannot assign a value to final variable 'a'
}
public static void main(String[] args) {
final String str = "hello";
str="World";//cannot assign a value to final variable 'str'
final int b;
b = 2;
b = 4;// variable 'b' might already have been assigned to
}
}
final在修饰基本类型与引用类型的区别
根据final
修饰的变量不可改变不难得知,当final
修饰基本类型变量时不可改变,但修饰引用类型变量时,引用的对象可以改变,引用的地址不变。
下面的代码将展示这一情形:final
分别修饰数组和Person
对象的情形。
class Person{
private int age;
public Person() {
}
public Person(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class FinalTest{
public static void main(String[] args) {
final int[] arr = {1,2,3,4};
System.out.println(Arrays.toString(arr));
Arrays.sort(arr);
arr[2] = 100;
arr = null;
// cannot assign a value to final variable 'arr'
final Person person = new Person(34);
person.setAge(12);
System.out.println(person.getAge());
person = null;
// cannot assign a value to final variable 'arr'
}
}
final与“宏替换”
- 只要一个
final
变量满足以下三个条件,那么该变量就是一个直接量(“宏变量”):- 使用
final
修饰 - 在定义时指定了初始值
- 该初始值在编译时就被确定下来
- 使用
如为final
变量赋值时使用基本的算术表达式或字符串连接运算,没有访问普通变量,调用方法,Java将将这种final
变量当作"宏变量",编译器会把程序中所有用到该变量的地方直接替换成相对应的值。
示例1:
public class FinalTest{
public static void main(String[] args) {
final int a = 1 + 1;
final String str = "Hello" + "World";
final String str2 = "HelloWorld" + 100;
final String str3 = "Hello World" + String.valueOf(100);
// 调用了valueOf方法
System.out.println(str2 == "HelloWorld100");// true
System.out.println(str3 == "HelloWorld100");// false
}
}
示例2:
public class FinalTest{
public static void main(String[] args) {
String helloWorld = "HelloWorld";
String s = "Hello" + "World";
System.out.println(helloWorld==s);// true
String s1 = helloWorld + s;
System.out.println(s==s1);// false
}
}
输出为 false 是因为编译器无法在编译时确定 s1 ,
不会让 s1 指向先前常量池中的 "HelloWorld" .
若要第二个输出为 true
只需要将 helloWorld 和 s 变成 final "宏变量"即可
具体关于字符串的比较见博客:String深入理解
final方法
如果不希望子类方法重写父类的某个方法,则可以使用final
修饰该方法。
例如Java提供的Object
类里有个final
方法:getClass()
@HotSpotIntrinsicCandidate
public final native Class<?> getClass();
而对于这个类提供的toString()
和 equals()
方法,就没有使用final
修饰,允许我们重写:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public boolean equals(Object obj) {
return (this == obj);
}
如果试图重写final
方法,将会引发编译错误:
public class FinalMethod{
public final void test(){}
}
class Sub extends FinalMethod{
public void test(){}
}
但是可以通过private
修饰该方法,那么其字类无法访问该方法,即便在字类拥有一个与父类private
方法中同名同参的方法,也只是重新定义了一个新方法:
public class FinalMethod{
private final void test(){}
}
class Sub extends FinalMethod{
public void test(){}
}
然而可以重载父类同名方法,不会出现任何问题.
final类
同样地,final修饰的类不可被继承,如果出现以下用法将报错:
public final class FinalClass{}
class Sub extends FinalClass{}
// cannot inherit from final 'FinalTest.FinalClass'
参考资料:疯狂Java