Java 中的变量类型、拆箱装箱及相互间的转换

2022-10-25 15:35:16 浏览数 (2)

Java 中的变量类型、拆箱装箱及相互间的转换

  • 一、Java 中变量类型
    • 1.1 以数据类型划分
      • 1.1.1 基本数据类型
        • 浮点数的题外话
      • 1.1.2 引用数据类
    • 1.2 以声明的位置为依据划分
    • 1.2.1 成员变量
    • 1.2.2 局部变量
  • 二、拆箱与装箱机制
    • 一个 String 的例子
  • 三、相互间的转换

一、Java 中变量类型

1.1 以数据类型划分

1.1.1 基本数据类型

  • 整数型变量

变量名

说明

byte

1字节,包装类为Byte

short

2字节,包装类为Short

int

4字节,包装类为Integer

long

8字节,赋值常量后面加L,包装类为Long

注意:1字节是8byte,以int为例,范围是[-2^31, 2^31-1],因为存在负数,故指数位要-1,正整数部分存在0,故要-1。0开头表示八进制,0x开头表示十六进制。

  • 浮点型 表示形式:十进制数形式(3.14)、科学记数法形式(314E-2)。 特点:
    1. 不精确,不能用于比较;(除非使用java.math包中的BigDecimal类)
    2. Java默认double为浮点数默认类型。

变量名

说明

float

4字节,赋值时加后缀F,包装类为Float

double

8字节,包装类为Double

浮点数的题外话
  1. 浮点数的内存存储方式
代码语言:javascript复制
IEEE规定浮点数num = (-1)^S * M * 2^E,下面以32位的float类型为例:(double类型为1 11 52,精度为15-16位小数)

    |-|--------|-----------------------|
S(1bit) E(8bit)         M(23bit)

首先需要说明一下,任何数的科学计数法都可以表示为1.xxx*2^n(计算机中均以二进制存储),由于尾数部分第一位均以1开头,故在M中省略,即23位的尾数部分可以表示24bit精度,也就是小数点后6-7位,绝对保证的精度为6位。
S:符号位,0表示正数,1表示负数
E:指数位,可正可负,故第一位为符号位,实际范围为-128~127。采用偏移位存储,元数据为127(0111 1111表示0)
M:尾数位,实际数值为1.M,后面不足23为加0,注意小数转化为二进制的方法

举例:6.2的存储结构
整数部分:6的二进制为110
小数部分:0.2的二进制为0011 0011 0011 0011 0011…
	    这里的计算方法可以为:0.2 * 2 = 0.4 …… 0
	    				  0.4 * 2 = 0.8 …… 0
	    				  0.8 * 2 = 1.6 …… 1
	    				  0.6 * 2 = 1.2 …… 1
	    				  ……
	    从这个计算中可以看出,计算机在存储浮点数的时候是不精确的,0.2是一个无限循环小数。
规格化:110.00110011… = 1.1000110011 * 2^2
填充:S = 0
	 E = 2   127 = 129 = 10000001
	 M = 10001100
故6.2的float存储结果为:|0|1000 0001|100 0110 0110 0110 0110 0110|
  1. BigDecimal的使用 构造 BigDecimal 对象的方法:
代码语言:javascript复制
BigDecimal bigDecimal = new BigDecimal(long a);  //不常用,不精确会变大
BigDecimal bigDecimal = new BigDecimal(String s);//常用
bigDecimal.valueOf(long a);                      //常用,是long转BigDecimal的方法

建议在使用 BigDecimal 的构造函数时最好采用基于整数或 String 的构造函数。 此外,BigDecimal 类型不能用使用一般的运算符号(±*/),需要使用对象的相应运算方法(如add())。

  1. Double中的两个特殊值 上面说到了浮点数的存储是不精确的,在 Double 类中就存在这样的两个数据:Double.NaN 和 Double.POSITIVE_INFINITY,Float 类同理。 3.1 i == i 1 无穷大加1还是无穷大,在 Java 中如果你计算1/0结果会抛出 ArithmeticException,但是计算1.0/0结果会得到 Infinity,这是标准类库提供的常量。浮点数在计算时不会抛出异常。 3.2 i != i IEEE 754 浮点算术保留了一个特殊的值用来表示一个不是数字的数量:NaN(Not a Number),用于表示没有良好的数字定义的浮点计算,如0.0/0。任何浮点操作,只有它的一个或多个操作数为 NaN,其结果必然是 NaN,显然 NaNcy 与任何数比较结果均返回 false。但是如果使用 Double.compare() 函数来比较两个 NaN,则结果返回0(相等)。 这里有个神奇的现象,对于 0.0 与 -0.0,compare() 函数认为正0要比负0大。所以对于0的比较还是用 == 比较好。
  • 字符型 特点:使用Unicode字符集(’uxxx’)。

变量名

说明

char

2字节,包装类为Charac

  • 逻辑型

变量名

说明

boolean

1字节,包装类为Boolean

1.1.2 引用数据类

  • 接口
  • 数组

1.2 以声明的位置为依据划分

1.2.1 成员变量

类中定义的变量,但是在方法、构造方法和语句块之外

  • 实例变量:不以static修饰
  • 类变量:以static修饰

1.2.2 局部变量

方法、构造方法和语句块中定义的变量

  • 形参:方法签名中定义
  • 方法局部变量:方法体内定义
  • 代码块局部变量:代码块中定义

二、拆箱与装箱机制

Java 中一切皆对象,为了方便编程引入了基本数据类型,但是每个类型都引入了对应的包装类型,Java 5 开始引入了自动装箱/拆箱机制,使得二者可以互相转换。但是注意 Ingeter 初值为 null,而 int 初值为 0。

注意:所有的包装类都是final类,即不可变类。虽然在代码A处看起来是改变了counter的值,但实际上是创建了另一个对象,并将方法内的counter参数的引用指向了这个新创建的对象,由于是不同的引用,所以不会对方法外的引用有任何的影响。

装箱:

代码语言:javascript复制
//二者等价
Integer a = 123;
Integer a = Integer.valueOf(123);

拆箱:

代码语言:javascript复制
Integer a = new Integer(123);
int b = a;
//等价于 b = a.intValue();

深入理解:

代码语言:javascript复制
Integer a = new Integer(123);
Integer b = new Integer(123);
Integer c = 123;
Integer d = 123;
Integer e = 128;
Integer f = 128;
//逻辑表达式(a == b)为false,因为栈中的对象a、b指向不同的堆中对象
//逻辑表达式(a == c)为true,因为自动拆箱的原因,实际比较的是两个int型数值
//逻辑表达式(c == d)为true,因为自动装箱时IntegerCache类在初始化时,生成了一个-128-127的Integer类型的常量池,如果值在此范围内则不会再生成新的对象
//逻辑表达式(e == f)为falsed,理由同上

一个 String 的例子

这样就不难理解 String 不是基本数据类型,而是一个对象,但是需要注意的是 String 可以直接赋值创建,而 StringBuilder 必须通过 new 来创建。 为了更进一步了解 Java 中的 String 请先看以下代码:

代码语言:javascript复制
final String c1  = "a";
String c2  = "a";

String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = new String("abc");
String s5 = c1   "bc";
String s6 = c2   "bc";

在JVM里,考虑到垃圾回收(Garbage Collection)的方便,将heap(堆)划分为三部分:young generation(新生代)、tenured generation (old generation)(旧生代)、permanent generation(永生代)。 字符串为了解决字符串重复问题,生命周期长,存于pergmen中。

  • 逻辑表达式s1 == s2为 true 因为String s1 = "abc"可能创建一个或不创建对象,如果 “abc” 这个字符串在 Java String 池中不存在,则会在 JVM 的字符串池中创建一个 String 对象 “abc”,然后将 s1 指向这个内存地址,以后在创建值为 “abc” 的字符串对象,始终只有一个内存地址被分配,其余的都是 String 的拷贝。 所以这里比较的是两个变量名实际指向的 String 对象地址。

Java 中成为“字符串驻留”:所有的字符串常量都会在编译之后自动地驻留。

  • 逻辑表达式s3 == s4为 false 因为String s3 = new String("abc")创建一个或两个对象,由于 new 关键字的存在,会在堆中创建一个 String 类型的 s3 对象,它的值为 “abc”(创建与否同上)。 所以这里比较的是堆中两个 String 对象的地址。如果想要比较值,应使用s3.equals(s4),内部逐项进行比较。
  • 逻辑表达式s1 == s5为 true,逻辑表达式s1 == s6为 false 因为将一个字符串连接表达式赋给字符串变量时,如果这个字符串连接表达式的值可以在编译时就确定下来,那么 JVM 会在编译时确定字符串变量的值,并让它指向字符串池中对应的字符串。这里 final 关键字在编译时对 c1 进行了宏替换,在编译时可以确定 s5 的值。

建议:在使用字符串、基本数据类型包装实例时,进行使用直接复制,而不是通过 new、包装类实例化,这样可以保证更好的性能。

三、相互间的转换

代码语言:javascript复制
/* int与String的互转
 */
int i = 123;
String s1 = String.valueOf(i);  //方法1
String s2 = Integer.toString(i);//方法2

String s = "123";
int i1 = Integer.parseInt(s);//方法1,返回的是int类型,常用(性能和Integer常量池范围限制问题)
int i2 = Integer.valueOf(s); //方法2,返回的是Integer类型,调用了parseInt的方法,如果要变为int型,还要进行一次装箱操作intValue()


/* StringBuilder与String的互转
 */
String s = "abc";
StringBuilder sb1 = new StringBuilder(s);//方法1
StringBuilder sb2 = new StringBuilder(); //方法2
sb2.append(s);

String s1 = sb.toString();//方法1
String s2 = ""   sb;      //方法2

/* char[]与String的互转
 */
String str = "abc";
char[] ca = str.toCharArry();

char[] ca = {'a','b','c'};
String s1 = String.valueOf(ca); //方法1,这里的valueOf函数可以有三个参数,截取字符数组范围
String s2 = Arrays.toString(ca);//方法2,但是输出格式为[, , ,]

//char[]转StringBuilder只能利用循环硬转

0 人点赞