final修饰的变量就是常量?
概念
什么是常量?
对于这个问题,可能很多人都可以脱口而出 : 用final修饰的变量是常量 ,或者是在编译时期定义好的字符串。(字符串常量)
但是这种说法是不严谨的,因为准确来说 : 常量是用final修饰的成员变量!常量在类编译时期载入类的常量池中。
即final修饰的成员变量(实例变量)和静态变量(静态变量也只能是用static修饰的成员变量),那么用final修饰的局部变量(方法内)我们也可以称之为不可变变量。(存储在栈中)
常量池
Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
静态常量池 : *.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。(编译时期)
运行时常量池 : jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。(运行时期)
补充 : 运行时常量池中的常量,基本来源于各个class文件中的常量池。(即每个class文件都有对应的常量池)
常量池的好处
常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
(2)节省运行时间:比较字符串时,比equals()快。对于两个引用变量,只用判断引用是否相等,也就可以判断实际值是否相等
双等号==的含义
基本数据类型之间应用双等号,比较的是他们的数值。
复合数据类型(类)之间应用双等号,比较的是他们在内存中的存放地址。(引用地址)
String hello="helloMoto"; String hello2="helloMoto";
例如我们定义hello和hello2,并且字符串常量池中没有存在”helloMoto”这个字符串常量。
那么首先会在字符串常量池中创建”helloMoto”字符串对象,hello指向字符串常量池中”helloMoto”字符串对象。
第一行代码,hello2首先会去常量池中寻找是否有”helloMoto”,发现已经存在,就直接指向该字符串常量池中”helloMoto”字符串对象。(String对象探索)
Class类文件中的常量池
魔数 : 每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。很多文件存储标准中都使用魔数来表示身份识别。使用魔数而不是扩展名来进行识别主要是基于安全方面的考虑,因为文件扩展名可以随意的改动。Class文件的魔数有很浪漫的气息,值为0x CAFEBABE这也是java是咖啡图标和商标名的原因之一。
版本号 : 紧接着4个魔数字节后面存储的是Class文件的版本号:第5和6个字节是次版本号,第7和第8个字节是主版本号。
常量池 : 接着主次版本号之后的是常量池入口,常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一。(Class类文件中的常量池在类未加载到内存中可以称为静态常量池) 。入口处用2个字节标识常量池常量数量。
我们使用十六进制编辑器WinHex打开Class文件
public class Test2 {
public static void main(String[] args) {String hello="helloMoto";
}
}
这里写图片描述
常量池中存放了各种类型的常量,他们都有自己的类型,并且都有自己的存储规范,本文只关注字符串常量,字符串常量以01开头(1个字节),接着用2个字节记录字符串长度,然后就是字符串实际内容。
这里写图片描述
常量池
常量池主要用于存放两大类常量: 字面量和符号引用量
字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值(成员变量)等。
符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:
类和接口的全限定名
字段名称和描述符
方法名称和描述符
运行时常量池
在Class类文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用如不过不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。 这部分内容将在类加载后进入方法区的运行时常量池中存放。
运行时常量池相对于CLass文件常量池(静态常量池)的另外一个重要特征是 具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。
包装类常量池(对象池)
java中基本类型的包装类的大部分都实现了常量池技术,
即Byte,Short,Integer,Long,Character,Boolean;Float,Double
Integer i1 = 127;Integer i2 = 127;System.out.println(i1==i2);//trueInteger i3 = 128;Integer i4 = 128;System.out.println(i3==i4);//false
对于上面2段代码不同结果我们可以追溯Integer源码
//Integer
public static Integer valueOf(int i) {if (i >= -128 && i <= 127)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);
}
可以看到如果值位于[-128,127]区域中,会使用IntegerCache类缓存数据,类似于字符串常量池。
所以如果赋的值超出这个区域, 便会创建一个新的Integer对象。(好处是平时如果频繁的使用Integer,并且数值在[-128,127]中,便不会重复创建新的Integer对象)
但是Double和Float这两个基本数据类型的包装类就没有对应常量池(对象池)的实现。
//Double
public static Double valueOf(double d) {return new Double(d);
}
Java中装箱和拆箱
基本数据类型 包装类
int(4字节) Integer
byte(1字节) Byte
short(2字节) Short
long(8字节)) Long
float(4字节) Float
double(8字节) Double
char(2字节) Character
boolean(1字节) Boolean
赋值时
装箱
如果要生成一个数值为10的Integer对象,只需要这样:
Integer i = 10;
这个过程中会自动根据数值创建对应的 Integer对象,这就是装箱。
拆箱
Integer i = 10; //装箱
int n = i; //拆箱
简单一点说,装箱就是 自动将基本数据类型转换为包装器类型;拆箱就是 自动将包装器类型转换为基本数据类型。
方法调用时
public class Test2 {
public static void main(String[] args) {int result = print(5);//int值 5 转换成对应的Integer对象(装箱)
}private static int print(Integer a) {//接收Integer对象作为参数System.out.println("a==" + a);return a;//返回int 类型,Integer自动拆箱转为int类型。
}
}
//a==5
方法运算时
public class Test2 {
public static void main(String[] args) {Integer sum = 0;for (int i = 1000; i < 5000; i++) {//自动拆箱为int类型才能运算//运算结果再自动装箱为Integer类型sum += i;}
}
}
上面的代码sum+=i可以看成sum = sum + i,但是+这个操作符不适用于Integer对象,首先sum进行自动拆箱操作,进行数值相加操作,最后发生自动装箱操作转换成Integer对象。其内部变化如下
int result = sum.intValue() + i;
Integer sum = new Integer(result);
由于我们这里声明的sum为Integer类型,在上面的循环中会创建将近4000个无用的Integer对象,在这样庞大的循环中,会降低程序的性能并且加重了垃圾回收的工作量。因此在我们编程时,需要注意到这一点,正确地声明变量类型,避免因为自动装箱引起的性能问题。
final修饰的变量就是常量?相关推荐
- final修饰的变量就是常量?final修饰局部变量在栈还是堆还是常量池中?
概念 常量池 常量池的好处 Class类文件中的常量池 常量池 运行时常量池 包装类常量池对象池 Java中装箱和拆箱 赋值时 方法调用时 方法运算时 参考 概念 什么是常量? 对于这个问题,可能很多 ...
- final修饰的变量是引用不能改变还是引用的对象不能改变
我们都知道final修饰变量时 会变为常量,但是使 用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变? 下面让我们来看这段代码: [java] view plain copy /* ...
- Java中被final修饰的变量的几种赋值方式
关于final final 表示"最后的.最终的"含义,变量一旦赋值后,不能被重新赋值.被 final 修饰的实例变量必须显式指定初始值. final 修饰符通常和 static ...
- final修饰的变量必须初始化吗?
final关键字的用法大家应该都知道. 修饰的类不能被继承. 修饰的方法子类可以使用,但是不能进行重写. 修饰的变量只能被赋值一次,引用不可变. 引用不可变的变量,只能被赋值一次.但是如果变量是对象, ...
- 关于final修饰的变量赋值的问题
一.赋值的方式 1.可以定义变量时直接赋值 2.可以在代码块中进行赋值(静态代码块和构造代码块) 3.可以再构造方法中进行赋值. 总结起来,就是要在对象创建之前完成赋值的过程. 二.值能不能改变的问题 ...
- final修饰的变量
被final修饰的实例变量必须显式指定初始值,而且只能在如下3个位置指定初始值. 1.定义final实例变量时指定初始值; 2.在非静态初始化块中为final实例变量指定初始值; 3.在构造器中为fi ...
- 被final修饰的变量到底能不能被修改
final:可以修饰类,方法,变量: 对类的修饰:表示类不可以被继承: 对方法修饰:表示该方法不能被子类重写: 对变量修饰:表示该变量不能被修改: 不知道大家有没有遇到一些很奇怪的现象,就是被fina ...
- java 中final修饰的变量_java中final修饰符的使用方法
本文为大家分享了java中final修饰符的使用,供大家参考,具体内容如下 1.final修饰符的用法: final可以修饰变量,被final修饰的变量被赋初始值之后,不能对它重新赋值. final可 ...
- 被final修饰的变量在哪存储_final,static,this,super 关键字总结,一点课堂(多岸学院)...
final 关键字 final关键字主要用在三个地方:变量.方法.类. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改:如果是引用类型的变量,则在对其初始化之后便 ...
最新文章
- .classpath 和.project文件含义
- 数据库之关系模型的组成,特点以及完整性约束
- 2014年4月的北京大学微电子考研复试题-什么是小信号?
- mysql 选择插入语句_带有last_insert_id()的Mysql多行插入 – 选择语句
- Visual Studio:error MSB8020
- (ECC)椭圆曲线加密算法原理和C++实现源码
- python模拟抛硬币_python实现简单随机模拟——抛呀抛硬币
- SylixOS armv8 原子操作
- Windows10 phpStudyV8.1的基本使用
- php创建文件目录,及删除目录和文件
- SpringApplication#run⽅法的第6步,创建ApplicationContext(五)
- 《自卑与超越》读书笔记优秀范文3200字
- 寒假刷刷算法题(13)
- 面向物联网的可重构流式深度卷积神经网络加速器
- 敏捷开发scrum详解 敏捷项目管理流程
- 2023山东大学计算机考研信息汇总
- 揭秘李佳琪直播带货绝招,学会它让你直播带货效益翻倍。
- Tomcat跨域配置
- 计算机学报在线阅读,含指针程序的单子切片方法-计算机学报.pdf
- 配置同时使用内网和外网
热门文章
- android 严振杰权限管理,MyAndroidFrameWork
- 俄语计算机考研,研友分享:俄语考研91分的经验体会
- python解决数学建模发电商机组调度问题
- C++保存中间结果到文件
- Stegsolve查看隐水印(暗水印)java jar包工具
- 回顾 | OpenAI 入门(一)- OpenAI 基础
- EasyRecovery最新中文Win/Mac全版本下载安装激活数据恢复软件
- 不动点迭代以及其收敛性
- 【10】AccessibilityService实现探探app的自动化喜欢和不喜欢+ [as 3.0如何打开层级调用uiautomatorviewer]
- 【下载器篇】IDM下载器个性化设置