前言
本文整理了 Java 基础核心高频知识点,包括面向对象、数据类型、String、异常、反射注解等,都是面试和日常开发最常用内容,适合 Java 初学者和准备面试的同学复习使用。
Java基础
面向对象
(什么是面向对象,要求有抽象的能力,把实际开发中的各种业务抽象成具体的接口 抽象类 类)
面向对象的三大特征是什么
继承:
问:
子类是否可以继承父类的private修饰的成员属性和方法;///子类能不能继承父类的私有方法/私有属性
答:可以,但是只能继承不能使用,如果继承父类,父类所有的一切包括私有但是调用不了
拓展:编码的设计原则:里氏替换原则
继承可以用,但子类不能 “篡改” 父类的核心约定
多态:实现接口 继承抽象类
可以使用父类去接受具体的子类实现 可以灵活的接受不同类型的实现类
封装:类的封装 方法的封装 模块的封装
面向对象的封装指的是类的封装;类的封装就是私有成员变量公开成员方法
方法的封装指的是一个方法被多次调用可以抽取出来,然后独立定义减少代码的重复度
模块的封装:如果一个功能被多个项目 被多个模块/微服务模块进行调用,可以把这个功能进行抽取封装成一个独立的模块 然后在其他微服务/模块中进行依赖引入
Java中重载(Overload)和重写(Override)的区别
重载指的是相同的方法名称 相同的方法返回值 不同的参数
重写:子类(抽象类 普通类 接口实现类)重写父类的方法实现 方法名称返回值 形参都保持一致,如果不一致就重载了
抽象类和接口的区别
抽象类:如果一个类中至少包含一个abstract 修饰的方法那么这个类就是抽象类
接口:都是抽象方法(默认情况)
区别:抽象类(abstract) 抽象类是 单继承 其中有抽象方法/非抽象方法
抽象类中可以有各种变量 抽象类中有构造方法
接口(interface)接口时多实现 Jdk8 之前只能是抽象方法,jdk8之后可以有默认方法和静态方法
接口中只能是public static final 修饰的常量 接口中不能有构造方法
Java8接口的默认方法和静态方法
默认方法就是有方法体default void method A(){方法体}
静态方法static void method B(){方法体}
抽象方法void abstract C();
数据类型与包装类
Java基本数据类型有哪些?占多少字节?
| 基本数据类型 | 字节 | 取值范围 |
|---|---|---|
| Byte | 1 | -128~127 |
| Short | 2 | -32768 ~ 32767 |
| Int | 4 | -2³¹ ~ 2³¹-1 |
| Long | 8 | -2⁶³ ~ 2⁶³-1 |
| Float | 4 | 约 ±3.40282347E+38(有效小数位 6~7 位) |
| Double | 8 | 约 ±1.79769313486231570E+308(有效小数位 15~16 位) |
| Char | 2 | 0 ~ 65535(即 \u0000 ~ \uffff) |
| Boolean | 不确定 | true / false |
自动装箱和拆箱的原理
integer i=10;//装箱(封装成一个包装类);底层用的是Integer.valueOf(10);Int j =i ; //拆箱();底层是i.intValue();
注意事项:频繁装箱和拆箱会影响性能,如果使用的是包装类可以为 null ,使用时 注意空指针异常 (NullPointerException,简称 NPE)。
Integer缓存机制(-128到127)
Integer a = 127; Integer b = 127; sout(a == b); //true ; Integer c = 128; Integer d = 128; sout(c == d); //false ;Integer的valueOf(int i)在这个方法中会进行判断i的值 如果是在-128到127之间 Integer会进行缓存IntegerCache .cache();
其他包装类缓存:
Byte Short Long (缓存都是):-128到127
Character:0到127
Boolean:true和false两个对象
面试问:你在开发中有没有遇到过一些印象比较深刻的Bug,你是怎么解决的?
我之前在开发的时候 有一个用户发放优惠券的业务,当时的业务要求每个用户只能领取一张优惠券,所以我使用了锁机制,但是我没有对整个优惠券的表进行加锁,这样的并发太低了,我当时使用的是用户id进行加锁也就是同一时间一个用户只能抢一张优惠券 不同的用户可以同时抢,并发会更高,但是还是出现了一个用户抢多张的情况,当时我们的用户id是Long类型的 ,这时候我的用户id的值超过了128那么Long缓存失效,然后同一个用户的id会被判断为不同的锁对象;导致锁失效
= =与equals的区别
= =比较的是什么:
- 基本数据类型:比较的是值是否相等
- 引用类型:比较的是内存地址是否相同(是否是同一个对象)
equals比较的是什么:
- Object类中默认的实现是==
- String Integer等重写
equals()比较的是内容是否相等
String相关
String、StringBuilder、StringBuffer的区别?
String是不可变的;线程是安全的;性能低(会频繁创建新对象)少量字符串操作的时候
StringBuilder是可以变的;线程是不安全的;性能高;单线程大量字符串操作(局部)
StringBuffer是可以变的;线程是安全的;性能较高(有锁开销);多线程大量字符串操作(成员)
String为什么设计成不可变的?
- 安全性
字符串常量池的需要:相同内容的字符串共享同一个对象
防止被篡改:String常用于网络传输 文件路径等以一些操作 通过不可变来保证安全性 - 线程安全
不可变对象天然就是线程安全的 多环境下无需考虑线程安全问题 - 支持哈希
String经常会作为HashMap的Key使用 不可变保证了Hash值不变 - 性能优化
字符串常量池可以复用对象 节省内存
为什么是不可变的/怎么保证不可变????
String类是被final修饰的 也就是不可以被继承 也就意味着不可以重写父类方法
字符数组也是final修饰的 不能修改引用
String类本身并没有提供修改value的方法
String字符串常量池的理解
字符串常量池是JVM为了提升性能和减少内存开销,专门为String类设计的一块内存区域
存储的位置:
JDK7之前是方法区
JDK7之后是堆内存
面试题:
String s = new String ("hello");
创建了几个对象??
如果“hello”在常量池中没有:2个(常量池中1个堆内存里面1个)
如果“hello”在常量池中没有:1个(堆内存里面1个)
intern()方法的作用
intern()方法用于将字符串对象放入到字符串常量池中
原理:
如果常量池中已存在该字符串返回池中的引用
如果常量池中的不存在,将该字符串放入到常量池中,然后再返回引用
异常处理
Java异常体系结构
Throwable
- Error
- OOM
- StackOverflowError栈溢出
- Exception
- RuntimeException
- NPE 空指针异常
- 数组角标越界
- 类型转换异常
- 编译异常
- IOException
- SQLException
Checked异常和Unchecked异常的区别(运行时异常和编译异常的区别)
编译异常:在编译时期就必须要处理的异常(try-catch和throw)
运行时异常:编译时期不强制处理(自己可能意识不到有异常)如果是web项目 通常使用全局异常处理器去拦截这些运行时异常然后统一处理
异常表是什么
定义
异常表是存储在Java字节码中的一个数据结构,它存在于方法的Code属性中,用于指导JVM如何处理方法执行过程中可能发生的异常,然后统一处理。
异常表是在方法级别创建的,而不是类级别或项目级别。只有包含try-catch块的方法才会有异常表。每个包含异常处理逻辑的方法都会有自己的异常表。
异常表是在编译时由Java编译器生成的,作为字节码的一部分。它不是在运行时动态创建的。在Java的.class文件中,异常表是方法属性的一部分。
一个类可能包含多个方法,每个方法可能由也可能没有异常表。一个方法中如果有多个try-catch块也只会存在一个异常表,这个异常表中会有多个条目,具体来说每个catch块都会对应一个条目,如果代码在运行时发生异常但是方法中没有try-catch块也不会自动创建异常表,每个方法的异常表是相互独立的。一个方法的异常处理不会影响其他方法的异常表。
public void methodWithMultipleTryCatch(); Code: //...bytecode... //一个方法中的异常表 //一个try对应两个catch的情况,每个catch对应一个条目 Exception Table: from to target type 0 8 11 Class java/lang/ArithmeticException 14 22 25 Class java/lang/NullPointerException 14 22 38 Class java/lang/Exception //方法中没有try-catch也就没有异常表 public void methodWithoutTryCatch(); Code: //...bytecode... //No exception table for this method结构
异常表的每个条目通常包含以下信息
- from:try块开始的字节码偏移量
- to:try块结束的字节码偏移量
- target:异常处理程序(catch块)的起始位置
- type:要捕获的异常类型
作用
当方法执行过程中抛出异常时,JVM会查询异常表,JVM根据异常表的信息确定是否有匹配的catch块,以及如何处理异常。
工作原理
当异常发生时,JVM检查当前指令的位置是否在任何try块的范围内。如果在try块内,JVM会查找匹配的异常类型。如果找到匹配,执行会跳转到相应的catch块。
优点
效率:异常表提供了一种高效的方式来定位异常处理程序。
灵活性:允许对不同类型的异常进行不同的处理。
查看异常表
可以使用javap工具查看编译后类的字节码,包括异常表。命令:javap -c -verbose ClassName
与finally块的关系
finally块的内容通常会被复制到每个可能的执行路径中,包括正常执行路径和异常处理路径。
性能考虑
异常表的存在不会影响正常执行的性能。只有在实际抛出异常时,才会查询异常表。
嵌套try-catch
对于嵌套的try-catch块,会生成多个异常表条目。
try-catch-finally的执行顺序
- 执行try
- 如果有异常执行对应的catch块
- 无论是否有异常都会执行finally
面试题
try{ return 1; }catch(Exception e){ return 2; }finally{ return 3; } //最终返回3 public static int test(){ int x = 1; try{ x ++; return x; //返回2 先把整个2保存起来 }finally{ x ++; //x = 3; } } //最终返回2finally块一定会执行吗?
通常情况下都会执行
(不会执行)
- 断电
- JVM退出(
System.exit(0);)
public static int test(){ int x = 1; try{ System.exit(0); //退出虚拟机不会执行finally x ++; return x; //返回2 先把整个2保存起来 }finally{ x ++; //x = 3; } }- finally块在守护线程中 主线程结束掉了
- 在try方法块中如果有无限循环或者死锁(finally也不会被执行到)
- 线程被外界直接杀死
try-with-resources的使用
try(FileInputStream fis = new FileInputStream("file.txt")){ //使用fis }catch(IOException e){ e.printStackTrace(); } //自动调用close资源类必须要实现AutoCloseable接口
可以在Try里面同时管理多个资源
反射与注解
什么是反射?反射的应用场景
反射就是在运行过程中,能够动态拿到一个类的所有信息(属性、方法、构造器),甚至能够修改私有属性的值。
核心类:
Class Field Method Constructor
应用场景:
- Spring容器 反射创建Bean
- AOP的实现 反射执行目标方法
- 运行时注解的解析 发射拿注解上的值
- 驱动加载
反射获取Class对象的三种方式
//方式1:通过类名.class //需要导入这个类 或者是你自己创建类 Class<String> clazz1 = String.class; //方式2:通过对象.getClass() //需要创建这个类的实例对象 String str = "hello"; Class<?> clazz2 = str.getClass(); //方式3:通过Class.forName("权限定类名"); //运行时加载 最灵活 通常框架都是使用这种方式 Class<?> clazz3 = Class.forName("java.lang.String"); //三种方式获取的是同一个Class对象 System.out.println(clazz1 == clazz2);//true System.out.println(clazz2 == clazz3);//true反射的优缺点
优点:
- 灵活性:运行时动态操作类,不需要在编译时确定 (判断方法的名称是否是我想要的 以XXX开头)
- 解耦:降低模块间的耦合度
- 可扩展性:支持插件式开发
缺点:
- 性能开销:比直接调用慢10-20倍
- 安全性:可以访问私有成员,破环封装
- 代码可读性差:难以理解和维护
- 编译时无法检查:类型安全问题延迟到运行时
优化建议:
- 缓存Class、Method、Field对象
- 使用setAccessible(true) 减少安全检查开销
- 非必要不使用反射
注解的作用和自定义注解
注解其实就是一个标记没有任何的业务逻辑
作用就是方便有一个核心类然后去扫描带有特定标记的类或方法 然后做业务处理
怎么自定义注解@Interface可以定义属性每个属性都可以有默认值 也可以没有默认值
如果某个属性有默认值在使用这个注解的时候就可以不用再指定值
如果某个属性没有默认值 则在使用这个注解的时候就必须要指定属性的值 否则报错
元注解有哪些?
元注解就是修饰注解的注解。
@Target:约束注解能写在哪(类上、方法上、字段上)。
@Target({ElementType.TYPE,ElementType.METHOD}) public @interface MyAnnotation{}@Retention:约束注解活到什么时候。注解默认的生命周期市编译后就不在了- SOURCE(源码可见,编译丢弃)。
- CLASS(字节码可见,运行丢弃)。
- RUNTIME(运行期可见,反射必须用这个)
@Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation{}- @Documented:注解是否包含在JavaDoc中
- @Inherited:注解是否可被继承
总结
本文整理了 Java 基础高频面试点,适合日常复习和面试突击。基础扎实才能更好学框架、分布式、高并发,建议多敲代码、多总结。
后续会继续更新:集合、多线程、JVM、Spring、MySQL 等系列文章,欢迎关注交流。