1、基本数据类型和包装类型
1.1、区别
-
基本数据类型就是普通的变量类型;包装类型是类类型,本质是对象。
-
包装类型需要使用new关键字来创建
-
包装类型使用new关键字创建之后默认值为null;int默认值为 0 ;boolean的默认值为false。
-
包装类型存储 在堆中;基本数据类型存储在栈中。
-
容器中存放的是包装类型,不能存放基本数据类型。
1.2、自动拆箱和自动装箱的区别
基本数据类型转换为包装类型叫装箱;包装类型转换为基本数据类型叫拆箱。
1.3、笔试题
// 值比较
int a = 100;
Integer b = 100;
System.out.println(a == b); // 输出 true
//此时 b 会先自动拆箱然后再和 a 进行比较
//引用地址的比较-1
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // 输出true
//引用地址的比较-2
Integer a = 200;
Integer b = 200;
System.out.println(a == b); // 输出false
//引用地址的比较-3
Integer a = new Integer(10);
Integer b = new Integer(10);
System.out.println(a == b); //输出false
注意:
-
自动装箱时,如果数据在 -128 ~ 127 之间,则不会创建新的对象,而是直接使用缓存中已经创建好的对象,直接进行自动装箱。
-
数据不在该范围之内则会创建新的对象再进行自动装箱。
2、Java 异常体系
3、Java虚拟机的内存分配
Java虚拟机在执行Java程序的过程中会把它管理的内存空间划分为若干个不同的数据区域;Java的运行时数据区主要包括:堆区, 栈区, 方法区, 程序计数器。
3.1、栈:存放局部变量
3.1.1、虚拟机栈
虚拟机栈是线程私有的,生命周期与程序计数器相同。
-
每创建一个线程,Java虚拟机就会为这个线程创建一个虚拟机栈
-
虚拟机栈表示Java方法执行的内存模型,每调用一个方法,就会生成一个栈帧用于存储方法的本地变量表、操作栈、方法出口等信息,当方法执行完毕之后就会弹出相应的栈帧。栈帧的入栈到出栈的过程就对应方法调用到执行完毕的过程。
如果请求的栈深度过大,虚拟机栈可能会抛出StackOverflowError(栈溢出)的异常,如果虚拟机的实现允许虚拟机栈动态扩展,当内存不足以扩展栈的时候,会抛出OutOfMemoryError(内存溢出)的异常。
3.1.1.1、栈帧
栈帧分为三部分:局部变量区、操作数栈和帧数据区。
-
局部变量区:局部变量区被组织为一个一个从0开始的字数组, byte、short、char在存储前被转换为int, boolean也被转换为int, 0表示false, 非0表示true,long和double占据两个字长。
-
操作数栈:操作数栈也被组织为一个字数组,但不同于局部变量区,它不是通过数组下标访问的,而是通过栈的Push和Pop操作。
-
帧数据区:作用
-
常量池中的数据解析
-
方法执行后处理返回,恢复调用现场
-
方法执行过程中抛出异常时的异常处理,存储有一个异常表,当出现异常时虚拟机查找相应的异常表看是否有对应的Catch语句,如果没有就抛出异常终止这个方法的调用。
-
3.1.2、本地方法栈:
与虚拟机栈类似,只是执行本地方法的时候使用,本地方法栈则为 JVM 使用到 Native 方法服务。
3.2、堆区:
-
用于存放new关键字创建的对象和容器等,
-
堆区是垃圾回收的主要区域(垃圾回收的了另一个区域是方法区)。
-
堆区是Java虚拟机所管理的最大的一块内存空间
-
堆区被划分为两个不同的区域:新生代(Young)、老年代(Old)
-
Java的堆只要求逻辑上是连续的,在物理上可以不连续
3.2.1、新生代
新生代主要用来存放新生的对象
3.2.2、老生代
-
老生代主要存放应用程序中生命周期长的内存对象。
-
在新生代中记过多次垃圾回收仍然存活的对象,会被存放到老生代中,老生代通过标记/清理算法来清理无用内存。
3.3、方法区:
静态变量 + 常量 + 类信息(版本、方法、字段等) + 运行时常量池存放在方法区中
方法区是线程之间共享的,当两个线程需要加载同一个类型的时候,只有一个线程会请求ClassLoader加载,另一个线程会等待。
3.3.1、常量池的分类
-
静态常量池存在于class文件中
-
运行时常量池就是在class文件被加载到内存之后,常量池保存在了方法区中
3.4、程序计数器
程序计数器是线程私有的,可以视为当前线程的指示器。
程序计数器的作用:一个程序运行之后,Java虚拟机都会为该程序创建一个线程。对于一个单核的处理器,在某一时刻只会执行一条线程中的指令,为了保证线程切换时可以恢复到之前执行的位置,所以每个线程都需要一个独立的程序计数器来记录线程切换前的运行状态。
4、Java 的垃圾回收机制
5、如何捕获处理空指针异常
预防空指针异常定义变量的时候进行初始化
// "", 和null的区别,
String str = ""; //表示一个String类型的对象引用了一个字符串,已经分配了内存空间;
String str = null; //表示一个空对象,而不是字符串,未分配内存;
使用对象之前先进行控制判断
if(obj != null){
obj.hashcode();
}
启用Java 14以后的空指针处理机制
从java 14 开始,如果出现空指针异常, JVM 就可以详细的展示出详细的异常详细,指定出具体为空的对象可以在NullPointerException
的详细信息中看到类似... because "<local1>.address.city" is null
,意思是city
字段为null
,这样我们就能快速定位问题所在。
这种增强的NullPointerException
详细信息是Java 14新增的功能,但默认是关闭的,我们可以给JVM添加一个-XX:+ShowCodeDetailsInExceptionMessages
参数启用它:
java -XX:+ShowCodeDetailsInExceptionMessages Main.java
使用instanceof关键字
instanceof是用来测试对象是不是类的实例。
public static void main(String[] args) {
String str = null;
//判断str是不是String的实例对象
if(str instanceof String){
str.hashCode();
}
else{
System.out.println("可能存在空指针异常...");
}
}
使用Optional类
Java 8引入了Optional类,它可以更加优雅地处理空指针异常。通过使用Optional类,可以在对象为空的情况下返回一个默认值或执行其他操作,而无需显式地进行空值检查。
使用 try-catch 语句捕获空指针异常进行处理
使用断言
在开发和测试阶段,可以使用断言(assertion)来检查对象是否为空,从而及早地发现空指针异常。
assert myObject != null : "myObject must not be null";
断言
断言(assertion)是一种在程序中用于检查特定条件的机制,它用于在代码中插入一些检查点,以确保程序的状态符合预期。在Java中,断言通常用于调试和测试阶段,以便在发现错误时及早地停止程序的执行,并给出相应的错误信息。
在Java中,断言使用assert关键字来声明,通常采用以下格式:
assert <boolean_expression> : <error_message>;
其中,<boolean_expression>
是一个布尔表达式,用于描述一个期望的条件,<error_message>
是一个可选的错误信息,用于在断言失败时显示。
断言的使用:
-
检查程序的内部状态是否符合预期,以帮助发现潜在的错误。
-
在开发和测试阶段,用于验证假设和约定是否得到满足。
-
作为一种文档形式,用于描述程序中的一些不变条件或者假设。
6、sleep和wait的区别
7、“==” 和 hashcode( ) 和 equals( ) 的区别
-
“==” 比较的是两个对象时,比较对象引用地址是否相同
-
“==” 比较的是基本数据类型时, 比较的是值是否相等
-
Object 类中的 equals 方法比较的是两个对象的地址是否相等
-
String ,Double, Integer等类中equals方法比较的是对象的内容是否相同
-
hashcode方法是Object类中的一个本地方法
-
String,Double,Integer等包装类中都重写了hashcode方法
在哈希表(Hashmap)中,为了保证存储元素的不重复,在插入元素时需要判断集合中是否已经存在该元素。如果使用常规的equals方法判断的话比较次数较多,效率很低。
所以HashMap重写了hashcode方法,在插入元素前先使用hashcode方法求出哈希映射值,如果映射值已经存在则再接着用equals方法比较,如果equals判断为真,则表示元素已经存在。
如果映射值不存在则直接插入。
映射值存在但equals方法判断为假也直接插入元素。