【java】常量与变量

简介: 在Java语言中,主要是利用final关键字(在Java类中灵活使用Static关键字)来定义常量。当常量被设定后,一般情况下就不允许再进行更改。如可以利用如下的形式来定义一个常量:final double PI=3.1315。在定义这个常量时,需要注意如下内容:

java常量与变量

一,java常量

在Java语言中,主要是利用final关键字(在Java类中灵活使用Static关键字)来定义常量。

当常量被设定后,一般情况下就不允许再进行更改。如可以利用如下的形式来定义一个常量:final double PI=3.1315。

在定义这个常量时,需要注意如下内容:

  一是常量在定义的时候,就需要对常量进行初始化。也就是说,必须要在常量声明时对其进行初始化。这跟局部变量或者成员变量不同。当在常量定义的时候初始化过后,在应用程序中就无法再次对这个常量进行赋值。如果强行赋值的话,数据库会跳出错误信息,并拒绝接受这一个新的值。(接口中定义的常量的访问方法)

  二是final关键字使用的范围。这个final关键字不仅可以用来修饰基本数据类型的常量,还可以用来修饰对象的引用或者方法。如数组就是一个对象引用。为此可以使用final关键字来定义一个常量的数组。这就是Java语言中一个很大的特色。一旦一个数组对象被final关键字设置为常量数组之后,它只能够恒定的指向一个数组对象,无法将其改变指向另外一个对象,也无法更改数组(有序数组的插入方法可使用的二分查找算法)中的值。

  三是需要注意常量的命名规则。不同的语言,在定义变量或者常量的时候,都有自己一套编码规则。这主要是为了提高代码的共享程度与提高代码的易读性。在Java语言中,定义常量的时候,也有自己的一套规则。如在给常量取名的时候,一般都用大写字符。在Java语言中,大小写字符是敏感的。之所以采用大写字符,主要是跟变量进行区分。虽然说给常量取名时采用小写字符,也不会有语法上的错误。但是,为了在编写代码时能够一目了然的判断变量与常量,最好还是能够将常量设置为大写字符。另外,在常量中,往往通过下划线来分隔不同的字符。而不想对象名或者类名那样,通过首字符大写的方式来进行分隔。这些规则虽然不是强制性的规则,但是为了提高代码友好性,方便开发团队中的其他成员阅读,这些规则还是需要遵守的。没有规矩,不成方圆。

  总之,Java开发人员需要注意,被定义为final的常量需要采用大写字母命名,并且中间最好使用下划线作为分隔符来进行连接多个单词。在定义final的数据不论是常量、对象引用还是数组,在主函数中都不可以改变。否则的话,会被编辑器拒绝并提示错误信息。 

二、Final关键字与static关键字同时使用。  由于Java是面向对象的语言,所以在定义常量的时候还有与其它编程语言不同的地方。如一段程序代码从编辑到最后执行,即使需要经过两个过程,分别为代码的装载与对象的建立。不同的过程对于常量的影响是不同的。现在假设有如下的代码:

  Private static Random rd1=new Random(); //实例化一个随机数生成对象。

  Private final int int1=rd1.nestInt(10); //生成随机数并赋值给常量int1

  Private static final int int2=rd1.nestInt(10); //生成随机数并赋值给常量int2

这上面的语句的大致含义是,通过Java语言提供的随机数类对象,生成随机数。并把生成的随机数赋值给常量int1与int2。细心的读者会发现,虽然同样是赋值语句,但是以上两个语句中有一个细小的差别,即在第二条语句中多了一个关键字static。关于关键字的用途,笔者在以前的文章中也有谈到过。这个是一个静态的概念。即当利用这个关键字来修饰一个变量的时候,在创建对象之前就会为这个变量在内存中创建一个存储空间。以后创建对对象如果需要用到这个静态变量,那么就会共享这一个变量的存储空间。也就是说,在创建对象的时候,如果用到这个变量,那么系统不会为其再分配一个存储空间,而只是将这个内存存储空间的地址赋值给他。如此做的好处就是可以让多个对象采用相同的初始变量。当需要改变多个对象中变量值的时候,只需要改变一次即可。从这个特性上来说,其跟常量的作用比较类似。不过其并不能够取代常量的作用。

那么以上两条语句有什么差别吗?我们首先来看 Private final int int1=rd1.nestInt(10)这条语句。虽然int1也是一个常量,但是其是在对象建立的时候初始化的。如现在需要创建两个对象,那么需要对这个变量初始化两次。而在两次对象初始化的过程中,由于生成的随机数不同,所以常量初始化的值也不同。最后导致的结果就是,虽然int1是常量,但是在不同对象中,其值有可能是不同的。可见,定义为final的常量并不是恒定不变的。因为默认情况下,定义的常量是在对象建立的时候被初始化。如果在建立常量时,直接赋一个固定的值,而不是通过其他对象或者函数来赋值,那么这个常量的值就是恒定不变的,即在多个对象中值也使相同的。但是如果在给常量赋值的时候,采用的是一些函数或者对象(如生成随机数的Random对象),那么每次建立对象时其给常量的初始化值就有可能不同。这往往是程序开发人员不愿意看到的。有时候程序开发人员希望建立再多的对象,其在多个对象中引用常量的值都是相同的。

  要实现这个需求的话,有两个方法。一是在给常量赋值的时候,直接赋予一个固定的值,如abcd等等。而不是一个会根据环境变化的函数或者对象。像生成随机数的对象,每次运行时其结果都有可能不能。利用这个对象来对常量进行初始化的时候,那么结果可能每次创建对象时这个结果都有可能不同。最后这个常量只能够做到在一个对象内是恒定不变的,而无法做到在一个应用程序内是恒定不变的。另外一个方法就是将关键字static与关键字final同时使用。一个被定义为final的对象引用或者常量只能够指向唯一的一个对象,不可以将他再指向其他对象。但是,正如上面举的一个随机数的例子,对象本身的内容的值是可以改变的。为了做到一个常量在一个应用程序内真的不被更改,就需要将常量声明为staitc final的常量。这是什么意思呢?正如上面笔者所说的,当执行一个应用程序的时候,可以分为两个步骤,分别为代码装载与对象创建。为了确保在所有情况下(即创建多个对象情况下)应用程序还能够得到一个相同值的常量,那么就最好告诉编译器,在代码装载的时候就初始化常量的值。然后在后续创建对象的时候,只引用这个常量对象的地址,而不对其再进行再次初始化。就如同Private static final int int2=rd1.nestInt(10)这种形式来定义常量。如此,在后续多次创建对象后,这个常量int2的值都是相同的。因为在创建对象时,只是引用这个常量,而不会对这个常量再次进行初始化。

  由于加上这个static关键字之后,相当于改变了常量的作用范围。为此程序开发人员需要了解自己的需求,然后选择是否需要使用这个关键字。在初始化常量的时候,如果采用函数(如系统当前时间)或者对象(如生成随机数的对象)来初始化常量,可以预见到在每次初始化这个常量时可能得到不同的值,就需要考虑是否要采用这个static关键字。一般情况下,如果只需要保证在对象内部采用这个常量的话,那么这个关键字就可有可无的。但是反过来,如果需要在多个对象中引用这个常量,并且需要其值相同,那么就必须要采用static这个关键字了。以确保不同对象中都只有一个常量的值。或者说,不同对象中引用的常量其实指向的是内存中的同一块区域。

二,Java 变量

Java数据类型图:

网络异常,图片无法展示
|

1.基本数据类型

  基本数据类型,也称内置类型,是可以在栈直接分配内存的,Java保留基本数据类型最大的原因也在此:性能。关于这一点可以参考:Java为什么需要保留基本数据类型  另外,要注意,Java是基于JVM的,所以,其所占字节固定,与机器平台无关,所有地方统一占用内存大小(除了boolean,以及byte/short/boolean数组的时候每个单元所占的内存是由各个JVM自己实现的)。  总共有四类八种基本数据类型(注1):1).整型:全部是有符号类型。1.byte:1字节(8bit),高位为符号位,其余7位为数据位,范围:-2的7次方~2的7次方-1(1111,1111~0111,1111),即-128~127(下面的计算方式相同);

注意:byte类型虽然在语义(逻辑)上是占用1字节,但实际上,JVM中是将其当做int看

的,也就是事实上是占用了32位,4字节的,所以其运算效率和int没区别,short也一样。

之所以要有byte/short类型,一是因为某些地方要明确使用这些范围类型,二是,

在byte[]数组中,JVM存储的则是真的1字节,short[]2字节。(但也有的JVM其byte[]

数组也是4字节1位)

2.short:2字节(16bit),高位为符号位,其余15位为数据位,范围:-2的15次方~2的15次方-1,即-32768~32767;

3.int:4字节(32bit),范围-2的31次方~2的31次方-1;Java默认的整型类型,即:

long l = 0xfffffffffff;//0x表示这个数是16进制数,0表示8进制。

//编译器报错,因为右边默认是int,但其超出了范围(没超出int范围的话

//编译器会隐式将int转为long),故报错(同样的错误也会出现在float)。

同样的还有:

short s = 123;//(这个123也是int类型,这里,= 操作编译器能隐式转换)

s = s + 123;//编译器报错,那是因为s+1是int类型(编译器先将s转化为int,再+1),

//这里,+ 操作编译器不能隐式转换(会提示失真,即精度可能会受损),正确的做法:

s = (short)(s + 123)//注意,不是(short)s + 123。

类型转化详见:Java 数据类型转化4.long:8字节(64bit),范围:-2的63次方~2的63次方-1;声明大的long方法:

long l = 0xfffffffffffL;//即在后面加上L或l。

//(强制转化:long l = (long)0xfffffffffff也没用)

2).浮点型5.float:4字节(32bit),单精度,数据范围:(-2^128)~(-2^(-23-126))-(0)-(2^-149)~2^128。浮点数,通俗来说就是小数,但是,这是有精度要求的,即在这区间float可不是能表达任意小数的,而是在一定精度下,比如float有效位7~8位(包括整数位和小数位,有效小数位是6~7位,这里为什么是7~8(6~7),参考:Java中float/double取值范围与精度),即0.123456789后面的9JVM是不认识的(8能认识,整数位为0则不算是有效位,例如12.1234567后面的7也不认识,只有6位有效小数位(注意,看的是有效位,不是有效小数位,float有7~8位有效位)),即:

if(0.123456781f == 0.123456789f){//注意后面加f/F,否则就是double

   System.out.println("true");

}else{

   System.out.println("false");

}

//打印结果:true

//事实上,浮点数值的比较是不能直接用==判断的,这里原因就要追究到浮点数的内存结构

//浮点数比较可以用一个差值,但这种情况只是近似的比较

//如果想要精确,可以使用BigDecimal

System.out.println(Float.MIN_VALUE);//1.4E-45 = 2^-149

//这里的“最小值”意味float能表示的最小小数,实际上float最小值等于最大值取负

System.out.println(Float.MAX_VALUE);//3.4028235E38 = 2^128

6.double:8字节(64bit),双精度,范围:-2^1024~(-2^(-1022-52))-0-(2^-1074)~2^1024,Java默认的浮点类型,即若后面不加f/F,默认是double类型,即:

float f = 1.23;//编译报错,因为

float f = 1.23f;//或float f = 1.23F;

//默认是double,1.23(double)转成float,做隐式转换,但是double转成float是

//取值范围大的转成取值范围小的会损失精度,因此不能转换(详见Java数据类型转换)

//那为什么,int可以转换成byte、short,int范围更大不是?

//前面已经说过了,byte、short实际在JVM上就是int,因此编译器是不会认为会损失精度的

//但是int是不能转换成boolean,虽然boolean也是4字节(一般JVM),但在JVM认为这

//两者完全是两个东西,当然不能转换(强制也不行,你不能把猫强制转换成鸟,完全两个物种),而byte、short都是整型,同int是一个类型

3).字符型7.char:2字节(16bit),表示一个字符(可以是汉字),字符编码采用Unicode(说的更准确点,字符集(charset)采用UCS-2,编码(encoding)采用UTF-16),实际上就是一个16位的无符号整型,但是,要注意的是,因为随着发展,char所能代表的字符个数(UCS-2字符集)被限定死了,所以并不推荐使用。(更多内容,以及关于Unicode、UTF8/16参考:Unicode、UTF8以及Java char。)

char c = 3+5;//正确,char是无符号整型,但不能这样

int a1 = 3;int a2 = 5;char c0 = a1+a2;//这里需要强制转换才行

char c1 = -3;//编译错误,char不能表示负数,即使

char c2 = (char)-3;//编译正确,但无意义(乱码)

char c3 = '3';//正确,输出字符3

char c4 = "3";//编译错误,双引号,表示的是字符串

char c5 = '65';//编译错误,这里65是两个字符

4).布尔型8.boolean:逻辑上:1bit,但是实际上,boolean并没有具体规定,完全是看各个JVM实现,不过《Java虚拟机规范》给出了4个字节(同byte解释)和boolean数组一个字节的定义。

注1:(1).这种分法是一种比较流行的分法,事实上应该为两种:数值类型与布尔型。数值类型分为整型和浮点型。整型包括:byte、short、int、long、char;浮点型:float、double;布尔型boolean。之所以将char认为是整型是因为char在JVM就是以无符号整型存在的。(2).事实上Java中除去这8种以及对象类型,还有一种比较特殊的类型存在,那就是Void。java.lang.Void,是一个占位符类,不可实例化,保存着Java关键字void的Class对象。为什么说它特殊呢?明明是一个类,难道不是对象类型?那是因为void.class.isPrimitive()(这个方法是用来判断一个Class对象是否是基本类型的)返回的是true,所以Void也算是基本类型的一个了(错了),只不过它比较特殊,不能算是一种数据,只是一种象征。20160921 改:上面弄错了,把Void和void两个混为一体了,事实上,可以简单的把这两者的关系看成类似包装类和基本类型的关系,像Integer和int的关系,java.lang.Void是一个不可实例化的占位符类来保存一个引用代表了Java关键字void的Class对象:

public static final Class<Void> TYPE = Class.getPrimitiveClass("void");

而Integer也有类似的语句:

public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");

区别只是,Void仅仅是为void服务,即所谓的占位符类,不做他用。所以Void类只是一个普通类,而void则可以认作为如同int一样的基本类型。

2.引用数据类型

  也称对象变量类型,复合数据类型,包含类、接口、数组(除了基本类型外,就是引用类型)。引用类型与基本类型最大的区别在于:

int a = 5;//这里的a是对象(严格来说不算是对象,只是个符号标识),5是数值

Integer a = 5;//这里的a是一个引用,5才是一个对象,更形象常见的是:

Object o = new Object();//o是引用(栈中),new Object()是对象(堆中)

//第二行代码中,5被自动包装成Integer对象

  这里的引用有点像C/C ++中的指针,但是同指针不同的是,你不能通过改变它的值从而去改变它所指向的值。即

ClassA p = new ClassA();//C++中,这个时候是可以这样操作的:

p = p + 1;//向前移动一个单元,Java则不能

//这种操作,其实是对内存直接的操作,很显然,Java是不允许程序员做这种操作的

  其实质就是,Java的引用不支持对内存直接操作,而指针则可以,所以,Java用起来更安全,但不够灵活,而指针,自由度大,但同时,要更加小心因为指针操作不当而引起的各种内存问题。在Java中,任何对象都需要通过引用才能访问到,没有引用指向的对象被视为垃圾对象,将会被回收。  引用,其实质同指针一样(可以理解为受限制的指针),存放的是一个地址,至于是实例对象的地址,还是一个指向句柄池的地址(这里可以参考:(3) Java内存结构),完全是看各个JVM的实现了。  Java中的枚举类型,都是Enum类的子类,算是类中的一种,也是引用类型。  引用类型又称为对象变量类型,是相对于基本数据类型来说的(基本数据类型不是对象),而又被称为复合数据类型,可以这样理解,引用类型的数据最终都是由基本数据类型构成的。而像接口,接口是不能实例化的,最终的实现还是由类实现的;数组在JVM中的实现也是通过类实现的,每个类型的一维数组,二维数组……都是一个类,只是这是一个特殊的类,它的对象头有别于一般对象的对象头(最主要的就是,数组对象头有对象长度)

3.变量的作用域

规定了变量所能使用的范围,*只有在作用域范围内变量才能被使用*。根据变量*声明地点的不同*,变量的作用域也不同。根据*作用域的不同*,一般将变量分为不同的类型:*类变量、局部变量、方法参数变量及异常处理参数变量*。下面对这几种变量进行详细说明。

4.变量类型

类变量

类变量也称为成员变量,声明在类中,不属于任何一个方法,作用域是整个类。

例 1:假设在一个类中声明了 3 个变量,下面编写一个测试类输出引起变量的值改变的示例代码。变量声明,实现代码如下所示:

  1. public class DataClass
  2. {
  3. int price=100; //定义类变量 price
  4. price String name; //定义类变量 name
  5. name int num; //定义类变量 num
  6. }

测试类代码如下所示:

1.publicclassTest

2. {

3.publicstaticvoidmain(String[] args)

4. {

5.DataClassdc=newDataCLass();

6.System.out.println("name="+dc.name);

7.System.out.println("num="+dc.num);

8.System.out.println("price="+dc.price);

9. }

10. }

运行结果如下:

name=null

num=0

price=100

在第一段代码中3 个成员变量,并对其中第一个变量 price 进行了初始化,而第二个 name 变量和第三个变量 num 没有进行初始化。由输出结果可以看出,第一个变量的值为显示初始化的值,第二个和第三个变量的值则为系统默认初始化的值。

局部变量

局部变量是指在方法或者方法代码块中定义的变量,其作用域是其所在的代码块。

例 2:声明两个局部变量并输出其值,实现代码如下:

1.publicclassTest2

2. {

3.publicstaticvoidmain(String[] args)

4. {

5.inta=7;

6.if(5>3)

7. {

8.ints=3; //声明一个int类型的局部变量

9.System.out.println("s="+s);

10.System.out.println("a="+a);

11. }

12.System.out.println("a="+a);

13. }

14. }

  上述实例中定义了 a 和 s 两个局部变量,其中 int 类型的 a 的作用域是整个 main() 方法,而 int 类型的变量 s 的作用域是 if 语句的代码块内,运行结果如下:

s=3

a=7

a=7

方法参数变量

*作为方法参数声明*的变量的作用域是整个方法。

例 3:声明一个方法参数变量,实现代码如下:

1.publicclassTest3

2. {

3.publicstaticvoidtestFun(intn)

4. {

5.System.out.println("n="+n);

6. }

7.publicstaticvoidmain(String[] args)

8. {

9.testFun(B);

10. }

11. }

在上例中定义了一个

  1. testFun() 方法,该方法中包含一个 int 类型的参数变量 n,其作用域是 testFun() 方法体内。当调用方法时传递进了一个参数 3,因此其输出控制台的 n 值是 3。

异常处理参数变量

异常处理参数变量的作用域是*在异常处理块中,该变量是将异常处理参数传递给异常处理块,与方法参数变量类似*

例 4:声明一个异常处理语句,实现代码如下:

1.publicclassTest4

2. {

3.publicstaticvoidtest()

4. {

5.try

6. {

7.System.out.println("Hello!Exception!");

8. }

9.catch(Exceptione)

10. { //异常处理块,参数为 Exception 类型

11.e.printStackTrace();

12. }

13. }

14.publicstaticvoidmain(String[] args)

15. {

16.test();

17. }

18. }

在上例中定义了异常处理语句,异常处理块 catch 的参数为 Exception 类型的变量 e,作用域是整个 catch 块。

相关文章
|
1月前
|
存储 缓存 安全
除了变量,final还能修饰哪些Java元素
在Java中,final关键字不仅可以修饰变量,还可以用于修饰类、方法和参数。修饰类时,该类不能被继承;修饰方法时,方法不能被重写;修饰参数时,参数在方法体内不能被修改。
28 2
|
2月前
|
Java 编译器
java“变量 x 可能未被初始化”解决
在Java中,如果编译器检测到变量可能在使用前未被初始化,会报“变量 x 可能未被初始化”的错误。解决方法包括:1. 在声明变量时直接初始化;2. 确保所有可能的执行路径都能对变量进行初始化。
276 2
|
3月前
|
存储 Java
java基础(7)变量以及变量的分类
Java变量是内存中存储数据的基本单元,包含数据类型、名称和字面值。变量的数据类型决定了分配的内存空间大小。变量声明格式为“数据类型 变量名;”,变量名应符合标识符命名规范。变量可以重新赋值,但数据类型需一致。变量可以一行声明多个,作用域决定了变量的可用范围。变量分为局部变量和成员变量,局部变量定义在方法体内,成员变量定义在方法体外、类体内。
49 2
|
28天前
|
Java
java小工具util系列4:基础工具代码(Msg、PageResult、Response、常量、枚举)
java小工具util系列4:基础工具代码(Msg、PageResult、Response、常量、枚举)
50 24
|
1月前
|
Java 编译器
Java重复定义变量详解
这段对话讨论了Java中变量作用域和重复定义的问题。学生提问为何不能重复定义变量导致编译错误,老师通过多个示例解释了编译器如何区分不同作用域内的变量,包括局部变量、成员变量和静态变量,并说明了使用`this`关键字和类名来区分变量的方法。最终,学生理解了编译器在逻辑层面检查变量定义的问题。
Java重复定义变量详解
|
1月前
|
Java 程序员 容器
Java中的变量和常量:数据的‘小盒子’和‘铁盒子’有啥不一样?
在Java中,变量是一个可以随时改变的数据容器,类似于一个可以反复打开的小盒子。定义变量时需指定数据类型和名称。例如:`int age = 25;` 表示定义一个整数类型的变量 `age`,初始值为25。 常量则是不可改变的数据容器,类似于一个锁死的铁盒子,定义时使用 `final` 关键字。例如:`final int MAX_SPEED = 120;` 表示定义一个名为 `MAX_SPEED` 的常量,值为120,且不能修改。 变量和常量的主要区别在于变量的数据可以随时修改,而常量的数据一旦确定就不能改变。常量主要用于防止意外修改、提高代码可读性和便于维护。
|
2月前
|
Java
通过Java代码解释成员变量(实例变量)和局部变量的区别
本文通过一个Java示例,详细解释了成员变量(实例变量)和局部变量的区别。成员变量属于类的一部分,每个对象有独立的副本;局部变量则在方法或代码块内部声明,作用范围仅限于此。示例代码展示了如何在类中声明和使用这两种变量。
|
2月前
|
安全 Java
java BigDecimal 的赋值一个常量
在 Java 中,`BigDecimal` 是一个用于精确计算的类,特别适合处理需要高精度和小数点运算的场景。如果你需要给 `BigDecimal` 赋值一个常量,可以使用其静态方法 `valueOf` 或者直接通过字符串构造函数。 以下是几种常见的方法来给 `BigDecimal` 赋值一个常量: ### 使用 `BigDecimal.valueOf` 这是推荐的方式,因为它可以避免潜在的精度问题。 ```java import java.math.BigDecimal; public class BigDecimalExample { public static void
|
2月前
|
分布式计算 资源调度 Hadoop
大数据-01-基础环境搭建 超详细 Hadoop Java 环境变量 3节点云服务器 2C4G XML 集群配置 HDFS Yarn MapRedece
大数据-01-基础环境搭建 超详细 Hadoop Java 环境变量 3节点云服务器 2C4G XML 集群配置 HDFS Yarn MapRedece
93 4
|
2月前
|
Java
java中父类方法return this.对象还是变量,子类去调用this.这个方法的问题
本文探讨了在Java中,当父类的方法返回`this`对象或变量时,子类调用该方法的行为,以及`this`关键字在不同类中调用方法时的指向问题。
19 0
java中父类方法return this.对象还是变量,子类去调用this.这个方法的问题