final可以修饰类、方法、变量,用来表示这些东西都不可再变
1、修饰成员变量:必须程序员显式地指定初始值
- 类变量:声明该变量时指定初始值、静态初始化块中指定初始值(二者选其一)
- 实例变量:声明该变量时指定初始值、非静态初始化块中指定初始值、构造器中指定初始值(三者选其一)
宏变量效果:对于final修饰的实例变量来说,如果在定义该变量时就指定初始值,那么这个变量就有了“宏变量”的效果。
public class Test{
//实例变量
final int a=1; //声明变量时指定初始值
final String str; //普通初始化块中指定初始值
final int c; //构造器中指定
//类变量
final static double d=3.234; //声明变量时指定初始值
final static String op;
{
str="hello world"; // 普通初始化块中指定没有赋值的实例变量初始值
//a=9; //错误,a已经指定值了,此处不可再指定
}
static {
op="hello China"; 普通初始化块中指定没有赋值的类变量初始值
}
public Test() {
c=6; //构造器中,指定实例变量的初始值
}
public static void main(String[] args) {
System.out.println(Test.d);
System.out.println(Test.op);
Test t=new Test();
System.out.println(t.a);
System.out.println(t.str);
System.out.println(t.c);
}
}
2、修饰局部变量:只能赋值一次
public class Test{
public static void main(String[] args) {
final String str;
final int age;
str="hello"; //第一次赋值,成功
age=29; //第一次赋值,成功
str="hi"; //第二次赋值,报错
}
}
3、修饰形参:不可被赋值(由于形参是在调用方法时,根据系统传入的参数来完成初始化的)
public class Test{
public void test(final String str) {
str="hello"; //报错,形参不可以被赋值
}
public static void main(String[] args) {
final int age=37;
}
}
4、修饰引用类型变量:引用的对象永远不会改变
对于基本类型来说,final修饰后不可再改变其值,但是引用类型不同,此时final修饰的变量保存的只是堆内存中的地址,即一直引用的是同一个对象,但是这个对象内的值会不会改变,跟final没有关系。
class Person{
private String name;
private int age;
public Person() {} //无参构造器
//提供有参构造器
public Person(String name,int age) {
this.setName(name);
this.setAge(age);
}
//name变量的setter和getter方法
public void setName(String name) {
if(name.length()<2||name.length()>6) {
System.out.println("输入的名字不符合要求");
}
else {
this.name=name;
}
}
public String getName() {
return this.name;
}
//age变量的setter和getter方法
public void setAge(int age) {
if(age<0||age>100) {
System.out.println("输入的年龄不符合要求");
}
else {
this.age=age;
}
}
public int getAge() {
return this.age;
}
//重写toString方法
public String toString() {
return "Person[name="+getName()+",age="+getAge()+"]";
}
}
public class Test {
public static void main(String[] args) {
final Person p=new Person("张三",42); //创建一个不可变的引用变量p
System.out.println(p);
p.setAge(28); //修改该引用中的实例变量,正确
System.out.println(p);
//p=null; 重新给p赋值,错误
}
}
实际中的内存如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ewL3Kjs1-1589868967982)(C:\Users\24973\AppData\Roaming\Typora\typora-user-images\image-20200519094127109.png)]
虽然p引用中的值改变了,但是p这个引用没有变,因此不存在错误,但是重新给p赋值为null,表示将p当前这个引用删除,这改变了p,所以出错
同理下面这个例子相似。
public class Test {
public static void main(String[] args) {
final int[] arr=new int[] {1,2,3,4};
System.out.println(Arrays.toString(arr)); //输出:[1,2,3,4]
arr[2]=8; //正确
System.out.println(Arrays.toString(arr)); //输出:[1,2,8,4]
arr=null; //错误
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6D1BALJL-1589868967986)(C:\Users\24973\AppData\Roaming\Typora\typora-user-images\image-20200519095224674.png)]
5、修饰方法:final修饰的方法不可被重写
如果不希望子类重写父类的某个方法,可以将该类用final修饰。
class Person{
final void test() {
//该类不可被重写
}
}
public class Test extends Person{
//编译错误:该类不可以被重写
public void test() {
}
}
如果将final改为private呢?
class Person{
private void test() {}
private final void show(){}
}
public class Test extends Person{
//编译正常
public void test() {
}
public void show(){}
}
这并不是方法重写,因为父类的test方法是private修饰的,子类无法访问,而子类只是重新定义了一个新方法,恰好这个方法名也为test,但是这并不是方法重写或者方法重载。同理方法show也是,即使采用了private、final等修饰了,并不代表不能再子类中新建一个和父类同名字的方法。
final修饰的方法不可以被重写,但是可以被重载。(方法重写与重载的知识详见《Java方法重载与方法重写》)
public class Test extends Person{
public final void test() {}
public final void test(String str) {} //编译正确:方法重载
}
6、修饰类:final修饰的类不能被继承,即它不可以有子类
final class Person{
}
//编译错误:Person类不可以被继承
public class Test extends Person{
public final void test(String str) {}
}