数据类型
1. Java有哪些数据类型
-
Java基本数据类型
这八种基本类型都有对应的包装类分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean 。 -
引用数据类型
引用数据类型非常多,大致包括:类、 接口类型、 数组类型、 枚举类型、 注解类型、 字符串型
例如,String 类型就是引用类型、八种基本类型的包装类。简单来说,所有的非基本数据类型都是引用数据类型。
2. 字符型常量和字符串常量的区别?
- 形式 : 字符常量是单引号引起的一个字符,字符串常量是双引号引起的 0 个或若干个字符。
- 含义 : 字符常量相当于一个整型值(
ASCII
值(American Standard Code for Information Interchange,美国信息交换标准代码)),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)。 - 占内存大小 : 字符常量只占 2 个字节; 字符串常量占若干个字节。
(注意: char 在 Java 中占两个字节)
3. float f = 3.4 是否正确
不正确。3.4是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f = (float)3.4;
或者写成float f = 3.4F;
。
4. 基本类型和包装类型的区别?
- 成员变量包装类型不赋值就是 null ,而基本类型有默认值且不是 null。
- 包装类型可用于泛型,而基本类型不可以。
- 基本数据类型的
局部变量存放在 Java 虚拟机栈中的局部变量表
中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java 虚拟机的堆
中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。 - 相比于包装类型, 基本数据类型占用的空间非常小。
为什么说是几乎所有对象实例呢?
这是因为 HotSpot 虚拟机引入了 JIT 优化之后,会对对象进行逃逸分析
,如果发现某一个对象并没有逃逸到方法外部,那么就可能通过标量替换
来实现栈上分配,而避免堆上分配内存
逃逸分析
是指在编译期间分析程序中对象的作用域,以确定对象的生命周期和是否会被其他对象引用。如果对象没有逃逸出方法外部,则可以将其分配在栈上,而不是在堆上分配内存,以提高程序的性能和减少内存的占用。
标量替换
是指将一个对象拆分成其属性或成员变量,然后将这些属性或成员变量分别存储在栈上或寄存器中,而不是在堆上存储整个对象。这样可以避免在堆上分配内存,并且提高程序的运行效率。
5. 包装类型的缓存机制了解么?
Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能,其中Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True or False。
Integer 缓存源码:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static {
// high value may be configured by property
int h = 127;
...
}
.......
}
Character 缓存源码:
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
private static class CharacterCache {
private CharacterCache(){}
static final Character cache[] = new Character[127 + 1];
static {
for (int i = 0; i < cache.length; i++)
cache[i] = new Character((char)i);
}
}
Boolean 缓存源码:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
注意: 如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。
下面我们来看一下问题。下面的代码的输出结果是 true 还是 false 呢?
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1 == i2);
解释: Integer i1=40 这一行代码会发生装箱,也就是说这行代码等价于 Integer i1=Integer.valueOf(40) 。因此,i1 直接使用的是缓存中的对象。而Integer i2 = new Integer(40) 会直接创建新的对象。因此,答案是 false 。你答对了吗?
注意:
所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
6. 自动装箱与拆箱了解吗?原理是什么?
什么是自动拆装箱?
- 装箱:将基本类型用它们对应的引用类型包装起来;
- 拆箱:将包装类型转换为基本数据类型;
Integer i = 10; //装箱
int n = i; //拆箱
7. 为什么浮点数运算的时候会有精度丢失的风险?
浮点数运算精度丢失代码演示:
float a = 2.0f - 1.9f;
float b = 1.8f - 1.7f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999905
System.out.println(a == b);// false
为什么会出现这个问题呢?
这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示。
8. 如何解决浮点数运算的精度丢失问题?
BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal 来做的。
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
System.out.println(x); /* 0.1 */
System.out.println(y); /* 0.1 */
System.out.println(Objects.equals(x, y)); /* true */
BigDecimal 详解
9. 超过 long 整型的数据应该如何表示?
基本数值类型都有一个表达范围,如果超过这个范围就会有数值溢出的风险。
在 Java 中,64 位 long 整型是最大的整数类型。
long l = Long.MAX_VALUE;
System.out.println(l + 1); // -9223372036854775808
System.out.println(l + 1 == Long.MIN_VALUE); // true
BigInteger 内部使用 int[] 数组来存储任意大小的整形数据。
相对于常规整数类型的运算来说,BigInteger 运算的效率会相对较低。