1. Final修饰的变量值可以改变吗?
刚才在看设计模式,看到中介者模式的时候,看到【中介者具体实现】的对象的时候,看到这么一段代码:
private final List<IUser> list =new ArrayList<IUser>();
本来在我脑海里,java的final修饰变量时,就表明变量一经定义是不能在改变的,但是在原文的意思里,这个用户列表是可以动态添加用户的,是不是就意味着变量list改变了?那不是和final有冲突吗?
然后我简单的验证了一下:
public class MyTest { private static final List<String> list =new ArrayList<String>(); public static void main(String[] args) { list.add("1"); list.add("2"); System.out.println(list.size()); } }
输出结果是:2。
完全没有问题。
这让我有点奇怪,难道是我对final的认识有问题吗?然后我定义了一个简单类型的变量试了一下,发现是不可以的,在编译的时候就会出问题,所以我想我我理解的应该也没错,可能是和地址引用有关系。然后我简单地查了一下,final修饰成员变量时:
当final修饰一个原生数据类型时,表示该原生数据类型的值不能发生变化;如果final修饰一个引用类型时,表示该引用类型不能再指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。
2. Java中Final的用法
看了很多的解释,尝试着把所有关于Final的用法总结了一下。
(1) final修饰类
final修饰的类不允许被继承。
当一个类被修饰为final时,它的含义很明确,就是不允许该类被继承,任何继承它的操作都会以编译错误告终。
示例:
public final class A { } // 编译错误!A是final类型,不可被继承! //!public class B extends A{ //!}
一个类不能既是final的,又是abstract的。因为abstract的主要目的是定义一种约定,让子类去实现这种约定,而final表示该类不能被继承,两者矛盾。
(2) final修饰方法
final修饰方法,表示该方法不能被子类中的方法覆写Override。
首先,修饰方法的final含义不是“不可修改”,而是指该方法不可被继承成员重新定义。(注意,这里所说的不能被重新定义,并不是指子类一定不能定义同名方法,如果父类的方法是私有类型,子类是允许定义该方法的,这里指的不能重新定义是指不能通过改写方法来使得方法重写的多态性得以实现,如不希望A a = new B(); a.f();这样的重写方法情况出现)示例:
public class A { // final方法f public final void f() { System.out.println("类A中的final方法f被调用了"); } } public class B extends A { // 编译错误!父类的f方法是final类型,不可重写! // ! public void f() { // ! System.out.println("类B中的方法f被调用了"); // ! } }
此外,当一个方法被修饰为final方法时,意味着编译器可能将该方法用内联(inline)方式载入,所谓内联方式,是指编译器不用像平常调用函数那样的方式来调用方法,而是直接将方法内的代码通过一定的修改后copy到原代码中(将方法主体直接插入到调用处,而不是进行方法调用)。这样可以让代码执行的更快(因为省略了调用函数的开销),比如在int[] arr = new int[3]调用arr.length()等。
另一方面,私有方法也被编译器隐式修饰为final,这意味着private final void f()和private void f()并无区别。
(3) final修饰变量
在java中因为变量类型包含基本类型和引用类型,所以final修饰这两种类型的数据的时候,是不一样的。
final成员变量表示常量,只能被赋值一次,赋值后值不再改变:
当final修饰一个原生数据类型时,表示该原生数据类型的值不能发生变化;
如果final修饰一个引用类型时,表示该引用类型不能再指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。
有一个注意点就是,final修饰一个成员变量(属性),必须要显示初始化。
这里有两种初始化方式:
一种是在变量声明的时候初始化;
第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在类的构造函数中对这个变量赋初值。
(4) final 修饰方法参数
在方法的引用类型参数上添加final修饰符,是为了防止参数的引用地址被无意的修改。对于final修饰的引用参数,如果在方法体中试图修改参数的引用地址是会出编译错误的。示例:
final class MyTest { public static void main(String[] args) { aaa(new ArrayList<String>()); } public static void aaa(final List<String> aa) { aa =new ArrayList<String>(); } }
虽然引用地址不可以修改,但是引用类型参数的内容是可以修改的。