final修饰符--Java学习笔记


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;
	}
	
	/*
	下面这个ch不合法
	既没有指定默认值,将来也没有在初始化块、构造器中指定初始值
	*/
	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(){}
        /* 
        'test()' cannot override 'test()' in FinalTest.FinalMethod';
        overridden method is final
        */
    }

但是可以通过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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章