一. final修饰变量
1. final修饰基本数据类型变量和引用数据类型变量
-
基本知识:被final修饰的变量,称为常量。常量的值是不可修改的。一般和static一起使用,称为静态常量。常量命名:字母全部大写
-
数据在内存中的存储:java中的内存分为:栈内存和堆内存。特点:栈的存取速度比较快,栈的内存要小于堆内存。基本类型存储在栈空间中,引用类型 栈中的引用指向堆空间
-
基本类型存储在栈空间中的示意图
-
引用类型 栈中的引用指向堆空间示意图
-
当final修饰的是一个基本数据类型时,这个数据的值再初始化后不能被改变。当final修饰的是一个引用类型数据时,也就是修饰一个对象时,引用再初始化后将永远指向一个内存地址,不可修改,但是该内存地址中保存的对象信息,是可以进行修改的。
-
final修饰基本数据类型时的内存示意图
-
在上图中,变量a在初始化后将永远指向003这块内存,而这块内存在初始化后将永远保存数值100
-
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{
//
//}
- 上述代码中,是下面所有测试的代码,有一个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分配值 这是系统提示报错的信息
- 不难看出final修饰变量的本质:final修饰的变量会指向一块固定的内存,这块内存中的值不能改变
- 引用类型变量所指向的对象之所以可以修改,是因为引用变量不是直接指向对象的数据,而是指向对象的引用的,所以被final修饰的引用类型变量将永远指向一个固定的对象,不能被修改。对象的数据值可以被修改。
2.被final修饰的常量在编译阶段会被放入常量池中
- final是用于定义常量的,定义常量的好处是:不需要重复的创建相同的变量。由final修饰的变量会在编译阶段放入到调用类的常量池中。
//不变性
String a = "tom"; //字符常量,放在常量池
a = "jack"; //常量池中有两个String对象
String b = "tom"; //创建流程:先判断常量池是否存在tom,如果有,就直接指向
System.out.println(a); //jack 当重新赋值,会在内存中再次分配一块新的空间
- 在上述代码中有一个字符串的不变性代码的测试:这里解读一下,字符串的值一旦确定,则不可修改,不可修改指的是指内存中的值不可修改
- 字符串不变性示意图:
- 什么是常量池:
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
- 上诉代码我是在最上面的代码中截取的一段,这一段的代码运作过程是这样的。
- 首先根据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
- 上述代码,又举了几个例子,加深一下理解
二.final修饰方法
- 被final修饰的方法,不能被重写,不让任何继承类对其进行修改
class Animal{
public final void show(){
}
}
final class Dog extends Animal{
//不能被重写
// @Override
// public void show(){
//
// }
}
三.final修饰类
- 被final修饰的类,不能被继承,不能有子类
- 当程序中有永远不会被继承的类时,或者自己写的类不想被别人继承,可以使用final关键字修饰
- 被final修饰的类所有成员方法都将被隐式修饰为final方法
final class Dog extends Animal{
//不能被重写
// @Override
// public void show(){
//
// }
}
//Dog类不能有子类.不能被继承
class YellowDog extends Dog{
}
四.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;
}
}
如果对你有帮助,点个赞吧0.0
若有不正之处,请多多谅解并欢迎批评指正,不甚感激