001、基本数据类型有几种?
- byte:长 1 字节,8 比特,表示范围
-2^7 ~ 2^7-1
- char:长 2 字节,16 比特
- short:长 2 字节,16 比特,表示范围
-2^15 ~ 2^15-1
- int:长 4 字节,32 比特,表示范围
-2^31 ~ 2^31-1
- float:长 4 字节,32 比特
- long:长 8 字节,64 比特,表示范围
-2^63 ~ 2^63-1
- double:长 8 字节,64 比特
- boolean:boolean 只有两个值 true 和 false,可以使用 1 bit 来存储,但没明确规定具体大小。JVM 会在编译时将 boolean 类型的数据转换为 int 类型,使用 1 来表示 true,0 表示 false,此时占 4 个字节;但对于 boolean 类型的数组 JVM 会将其编译成 byte 数组,每个 boolean 类型元素占 1 个字节。
002、基本数据类型的包装类?
每种基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。
所有包装类都被声明为 final,都不可被继承。
Integer a = 66; // 自动装箱 调用了 Integer.valueOf(66)
int b = a; // 自动拆箱 调用了 a.intValue()
003、包装类的缓存池?
new Integer(123) 与 Integer.valueOf(123) 的区别在于:
- new Integer(123) 每次都会新建一个对象
- Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用
Integer a1 = new Integer(123);
Integer a2 = new Integer(123);
System.out.println(a1 == a2); // false
System.out.println(a1.equals(a2)); // true
Integer a3 = 123; // 相当于调用 Integer.valueOf(123)
Integer a4 = 123; // 相当于调用 Integer.valueOf(123)
System.out.println(a3 == a4); // true
System.out.println(a3.equals(a4)); // true
valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
在 Java 8 中,Integer 缓存池的大小默认为 -128 ~ 127。
Integer a5 = 1239;
Integer a6 = 1239;
System.out.println(a5 == a6); // false
System.out.println(a5.equals(a6)); // true
基本类型对应的缓冲池如下:
包装类 | 范围 |
---|---|
Boolean | true 和 false |
Byte | -128 ~ 127,byte 类型所有数据 |
Short | -128 ~ 127 |
Integer | -128 ~ 127 |
Long | -128 ~ 127 |
Character | \u0000 ~ \u007F,所有字符 |
Float | 无 |
Double | 无 |
004、谈谈你对 String 字符串的理解?
String
类被声明为 final,因此不可被继承。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
}
在 Java8 中,String
内部使用被 final 修饰的 char 类型数组 value 来存储数据,这意味着 value 数组初始化之后不能再引用其他数组,同时 String 内部没有提供改变 value 数组的方法,因此可以保证 String 不可变。
005、String 类型变量不可变的好处?
-
可以缓存 hash 值。因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
-
String Pool 的需要。如果一个 String 对象已经被创建过了,那么就会从字符串常量池中取得引用。如果字符串是可变的,则使用一个引用更改字符串将导致其他引用的值错误,只有字符串是不可变的,才可能使用 String Pool。
-
线程安全。String 的不可变性使其天生就是线程安全的,可以在多个线程中安全地使用。
006、String, StringBuffer and StringBuilder 对比?
1. 可变性
- String 不可变
- StringBuffer 和 StringBuilder 可变
2. 线程安全
- String 不可变,因此是线程安全的
- StringBuilder 不是线程安全的
- StringBuffer 内部使用 synchronized 进行同步,是线程安全的
3. 使用场景
- 在读多写少的场景下,推荐使用 String
- 在读少写多且不需要考虑线程安全的情况下,推荐使用 StringBuilder
- 在读少写多且需要考虑线程安全的情况下,必须使用 StringBuffer
007、字符串常量池?
设计思想
- 字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能
- JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
- 为字符串开辟一个字符串常量池,类似于缓存区
- 创建字符串常量时,首先坚持字符串常量池是否存在该字符串
- 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中
- 实现的基础
- 实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享
- 运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收
具体解析
- 直接使用双引号声明出来的
String
对象会直接存储在字符串常量池中。 - 如果使用
new String("abc")
方式创建字符串对象,首先会判断字符串常量池中是否存在该字符串,若不存在就会先在字符串常量池中创建当前字符串对象,然后在堆中 new 一个 String(“abc”) 对象。 - intern() 会直接返回该字符串在字符串常量池中对应字符串对象的引用。
String str1 = "abc";
String str2 = "abc";
String str3 = "abc";
System.out.println(str1 == str2); // true
String str4 = new String("abc");
String str5 = new String("abc");
String str6 = new String("abc");
System.out.println(str4 == str5); // false
System.out.println(str1 == str4); // false
str4 = str4.intern();
System.out.println(str1 == str4); // true
在 Java7 之前,String Pool 被放在运行时常量池中,它属于永久代(方法区的一种实现);而在 Java7 之后,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
String str = new String(“abc”);这句话创建了几个字符串对象?
创建 1 或 2 个字符串对象。如果字符串常量池中已存在字符串常量“abc”,则只会在堆空间创建一个字符串常量“abc”。如果字符串常量池中没有字符串常量“abc”,那么它将首先在字符串常量池中创建,然后再在堆空间中创建,因此将创建总共 2 个字符串对象。
在堆中创建的对象到底有没有引用常量池中的常量?
不确定,我认为是引用了的。
008、final 的作用?
-
修饰变量
如果变量是基本类型,final 使其数值不可变;如果变量是引用类型,final 使其引用不可变,也就是说该引用不能再指向其他对象,但是该应用所指向的对象本身是可以改变的。
-
修饰方法
声明该方法不可以被子类重写。
private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法名相同,此时子类的方法不是重写父类方法,而是在子类中定义了一个新的方法。
-
修饰类
声明当前类不可以被继承。
009、static 的作用?
-
修饰变量
被 static 修饰的变量叫做静态变量,又称为类变量,也就是说这个变量属于类,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。
-
修饰方法
被 static 修饰的方法叫做静态方法,和静态变量一样,静态方法也属于类,可以通过类名直接访问,但静态方法不能是抽象的,并且只能访问所属类的静态字段和静态方法,静态方法中不能有 this 和 super 两个关键字(因为这两个关键字都与具体对象关联)。
-
修饰代码块
被 static 修饰的代码块叫做静态代码块,静态代码块只在类初始化时执行一次。
-
修饰内部类
被 static 修饰的内部类叫做静态内部类,静态内部类与非静态内部类之间最大的区别是:非静态内部类在编译完成之后会隐含地保存着一个引用,该引用指向创建它的外围类;而静态内部类却没有,没有这个引用就意味着它的创建不需要依赖外围类的创建,并且它不能使用任何外围类的非 static 成员变量和方法。
public class OuterClass { class InnerClass { } static class StaticInnerClass { } public static void main(String[] args) { // InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context OuterClass outerClass = new OuterClass(); InnerClass innerClass = outerClass.new InnerClass(); StaticInnerClass staticInnerClass = new StaticInnerClass(); } }
-
静态导包
静态导包格式为:
import static
。这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。举个例子,没使用静态导包前代码如下:
public class Demo { public static void main(String[] args) { int max = Math.max(6, 9); System.out.println(max); } }
使用静态导包后代码如下:
import static java.lang.Math.*; public class Demo { public static void main(String[] args) { int max = max(6, 9); System.out.println(max); } }
010、静态代码块、普通代码块、构造方法初始化顺序?
静态代码块和静态变量优先于普通代码块和实例变量初始化,静态代码块和静态变量的初始化顺序取决于它们在代码中的顺序,最后才是构造方法的初始化,初始化顺序如下:
- 父类(静态代码块、静态变量)
- 子类(静态代码块、静态变量)
- 父类(普通代码块、实例变量)
- 父类(构造方法)
- 子类(普通代码块、实例变量)
- 子类(构造方法)