当前位置:   article > 正文

【Java基础】final的详细用法解读_final修饰引用数据类型

final修饰引用数据类型

一. final修饰变量

1. final修饰基本数据类型变量和引用数据类型变量

  • 基本知识:被final修饰的变量,称为常量。常量的值是不可修改的。一般和static一起使用,称为静态常量。常量命名:字母全部大写

  • 数据在内存中的存储:java中的内存分为:栈内存和堆内存。特点:栈的存取速度比较快,栈的内存要小于堆内存。基本类型存储在栈空间中,引用类型 栈中的引用指向堆空间

  • 基本类型存储在栈空间中的示意图
    基本类型存储在栈空间中

  • 引用类型 栈中的引用指向堆空间示意图
    引用类型  栈中的引用指向堆空间示意图

  • 当final修饰的是一个基本数据类型时,这个数据的值再初始化后不能被改变。当final修饰的是一个引用类型数据时,也就是修饰一个对象时,引用再初始化后将永远指向一个内存地址,不可修改,但是该内存地址中保存的对象信息,是可以进行修改的。

  • final修饰基本数据类型时的内存示意图
    final修饰基本数据类型时的内存示意图

  • 在上图中,变量a在初始化后将永远指向003这块内存,而这块内存在初始化后将永远保存数值100

  • final修饰引用数据类型时的内存示意图
    final修饰引用数据类型时的内存示意图

  • 在上图中,变量p指向了0003这块内存,0003内存中保存的是对象p的句柄(存放对象p数据的内存地址),这个句柄值是不能被修改的,也就是变量p永远指向p对象,但是p对象的数据是可以修改的。

public class Test_final {
    private int n1 = 2020;  //普通变量
    private static final int n2 = 2020; //final修饰的变量
    private int age = 4;    //普通变量
    private static final double PI = 3.1415926;     //final修饰的变量
    private static final String PASSWORD = "123456";//final修饰的变量

    public static void main(String[] args) {
        Test_final t = new Test_final();
        t.test();
    }

    public void test(){
//        PI = 3; //编译错误,出现红色波浪线,不可以修改
        age = 9;
        System.out.println(age);    //9    4的值被9覆盖
        System.out.println(PI);     //3.1415926

        final User u = new User("tom",12,1001); //final修饰的引用数据类型
        u.setName("alice");
        System.out.println(u.getName());    //alice User类对象u里的名字name变量值被修改

        User u2 = new User("jack",13,1002);
//        u = u2;     //编译错误,出现红色波浪线,因为u经final修饰永远指向上面定义的u对象,不能指向u2对象
        System.out.println(u);  //Error:(22, 9) java: 无法为最终变量u分配值  这是系统提示报错的信息

        //不变性
        String a = "tom";   //字符常量,放在常量池
        a = "jack";         //常量池中有两个String对象
        String b = "tom";   //创建流程:先判断常量池是否存在tom,如果有,就直接指向
        System.out.println(a);  //jack  当重新赋值,会在内存中再次分配一块新的空间

        String s = "20200202";  //字符常量,放在常量池
        String s1 = n1 + "0202";
        String s2 = n2 + "0202";
        System.out.println(s1);     //20200202
        System.out.println(s2);     //20200202
        System.out.println(s == s1);//false
        System.out.println(s == s2);//true
        //问题1
        String s3 = "2020";
        String s4 = s3 + "0202";    //不是字符常量
        System.out.println(s == s4);//false
        //问题2
        String s5 = "2020" + "0202";//是字符常量,在编译时已经确定值
        System.out.println(s == s5);//true
        //问题3
        String s6 = "2020";
        String s7 = "0202";
        String s8 = s6 + s7;    //不是字符常量  s8不是通过双引号直接创建,通过运算得到的
        System.out.println(s == s8);//false
    }
}

class Animal{
    public final void show(){

    }
}

final class Dog extends Animal{
    //不能被重写
//    @Override
//    public void show(){
//
//    }
    public void sum(final int a, int b){
//        a = 9;  //出现红色波浪线,编译错误,不能修改
        b = 6;
        int sum = a + b;
    }
}

//Dog类不能有子类
//class YellowDog extends Dog{
//
//}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 上述代码中,是下面所有测试的代码,有一个User类,在上一篇博客中有,这里就不粘贴呢,还有一个Test_final类,一共两个类,可以自己粘贴代码测试。
  • 下面的代码是截取上面代码的一段
//        PI = 3; //编译错误,出现红色波浪线,不可以修改
        age = 9;
        System.out.println(age);    //9    4的值被9覆盖
        System.out.println(PI);     //3.1415926

        final User u = new User("tom",12,1001); //final修饰的引用数据类型
        u.setName("alice");
        System.out.println(u.getName());    //alice User类对象u里的名字name变量值被修改

        User u2 = new User("jack",13,1002);
//        u = u2;     //编译错误,出现红色波浪线,因为u经final修饰永远指向上面定义的u对象,不能指向u2对象
        System.out.println(u);  //Error:(22, 9) java: 无法为最终变量u分配值  这是系统提示报错的信息
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 不难看出final修饰变量的本质:final修饰的变量会指向一块固定的内存,这块内存中的值不能改变
  • 引用类型变量所指向的对象之所以可以修改,是因为引用变量不是直接指向对象的数据,而是指向对象的引用的,所以被final修饰的引用类型变量将永远指向一个固定的对象,不能被修改。对象的数据值可以被修改。

2.被final修饰的常量在编译阶段会被放入常量池中

  • final是用于定义常量的,定义常量的好处是:不需要重复的创建相同的变量。由final修饰的变量会在编译阶段放入到调用类的常量池中。
        //不变性
        String a = "tom";   //字符常量,放在常量池
        a = "jack";         //常量池中有两个String对象
        String b = "tom";   //创建流程:先判断常量池是否存在tom,如果有,就直接指向
        System.out.println(a);  //jack  当重新赋值,会在内存中再次分配一块新的空间
  • 1
  • 2
  • 3
  • 4
  • 5
  • 在上述代码中有一个字符串的不变性代码的测试:这里解读一下,字符串的值一旦确定,则不可修改,不可修改指的是指内存中的值不可修改
  • 字符串不变性示意图:
    字符串不变性示意图
  • 什么是常量池:
    String 常量: 使用双引号直接创建的字符串,称为字符常量
    1.字符常量放在内存中的常量池
    2.JDK1.8常量池和堆空间是相对独立
    3.常量池中的值不会被gc回收,垃圾回收机制不会清理该区域的内容
    4.多次出现相同字符常量,只会在常量池中创建一个String对象
		String s = "20200202";  //字符常量,放在常量池
        String s1 = n1 + "0202";
        String s2 = n2 + "0202";
        System.out.println(s1);     //20200202
        System.out.println(s2);     //20200202
        System.out.println(s == s1);//false
        System.out.println(s == s2);//true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 上诉代码我是在最上面的代码中截取的一段,这一段的代码运作过程是这样的。
  • 首先根据final修饰的常量会在编译期放到常量池的原则,n2会在编译期间放到常量池中
  • 然后s变量所对应的“20200202”字符串会放入到字符串常量池中,并对外提供一个引用返回给s变量
  • 这时候 拼接字符串s1,由于n1对应的数据没有放入到字符串常量池中,所以s1暂时无法拼接,需要等程序加载运行时才能确定s1对应的值
  • 但在拼接s2的时候,由于n2已经存在于常量池中,所以可以直接与“0202”拼接,拼接出的结果是“20200202”,这时系统会查看字符串常量池,发现已经存在字符串20200202,所以直接返回20200202的引用,所以s2和s指向的是同一个引用,这个引用指向的是字符串常量池中的20200202.
  • 当程序执行时,n1变量才有具体的指向
  • 当拼接s1的时候,会创建一个新的String类型对象,也就是说字符串常量池中的20200202会对外提供一个新的引用
  • 所以当s1与s用“==”判断时,由于对应的引用不同,会返回false。而s2和s指向同一个引用,返回true。

总结:这个例子想说明的是,由于被final修饰的常量会在编译期进入常量池,如果有涉及到该常量的操作,很有可能在编译期就已经完成。

		String s = "20200202";  //字符常量,放在常量池
		//问题1
        String s3 = "2020";
        String s4 = s3 + "0202";    //不是字符常量
        System.out.println(s == s4);//false
        //问题2
        String s5 = "2020" + "0202";//是字符常量,在编译时已经确定值
        System.out.println(s == s5);//true
        //问题3
        String s6 = "2020";
        String s7 = "0202";
        String s8 = s6 + s7;    //不是字符常量  s8不是通过双引号直接创建,通过运算得到的
        System.out.println(s == s8);//false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 上述代码,又举了几个例子,加深一下理解

二.final修饰方法

  • 被final修饰的方法,不能被重写,不让任何继承类对其进行修改
class Animal{
    public final void show(){

    }
}

final class Dog extends Animal{
    //不能被重写
//    @Override
//    public void show(){
//
//    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

三.final修饰类

  • 被final修饰的类,不能被继承,不能有子类
  • 当程序中有永远不会被继承的类时,或者自己写的类不想被别人继承,可以使用final关键字修饰
  • 被final修饰的类所有成员方法都将被隐式修饰为final方法
final class Dog extends Animal{
    //不能被重写
//    @Override
//    public void show(){
//
//    }
}

//Dog类不能有子类.不能被继承
class YellowDog extends Dog{

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

四.final修饰参数

  • 被final修饰的参数,不能修改
final class Dog extends Animal{
    //不能被重写
//    @Override
//    public void show(){
//
//    }
    public void sum(final int a, int b){
//        a = 9;  //出现红色波浪线,编译错误,不能修改
        b = 6;
        int sum = a + b;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如果对你有帮助,点个赞吧0.0
若有不正之处,请多多谅解并欢迎批评指正,不甚感激

参考资料
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/574291
推荐阅读
相关标签
  

闽ICP备14008679号