java基础八股(上)
前述:Ⅰ.⭐️代表面试高频,不要错过。Ⅱ.❌代表可不看。Ⅲ.没有符号标注即为常规基础
1.为什么说Java 是编译与解释共存的语言 。
因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(.class
文件),然后由 Java 解释器来解释执行。
2.java和c++的区别
Java 不提供指针来直接访问内存,程序内存更加安全
Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存。
C++同时支持方法重载和操作符重载,但是 Java 只支持方法重载
3.基本类型和包装类型的区别?
存储方式:基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 static
修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。
占用空间:相比于包装类型(对象类型), 基本数据类型占用的空间往往非常小。
默认值:成员变量包装类型不赋值就是 null
,而基本类型有默认值且不是 null
。
比较方式:对于基本数据类型来说,==
比较的是值。对于包装数据类型来说,==
比较的是对象的内存地址。所有整型包装类对象之间值的比较,全部使用 equals()
方法。
4.为什么浮点数运算的时候会有精度丢失的风险?
计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失
BigDecimal常见方法
- 加减乘除 add、subtract、multiply、divide
- 大小比较 compareTo
- 保留几位小数 setScale
5.面向对象和面向过程的区别
- 面向过程编程(POP):面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。
- 面向对象编程(OOP):面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。
6.获取class对象的四种方式
- 调用具体类的class方法
- class.forName()传入类的全路径
- 对象实例的getClass()获取
- 类加载器的loadClass()方法传入类的路径
7.Comparable 和 Comparator 的区别
Comparable
接口实际上是出自java.lang
包 它有一个 compareTo(Object obj)
方法用来排序
Comparator
接口实际上是出自 java.util
包它有一个compare(Object obj1, Object obj2)
方法用来排序
8.⾯向对象特性:封装,多态(动态绑定,向上转型),继承
封装:封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。
继承:继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能
多态:顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。
动态绑定:就是在运行时根据对象的实际类型来确定调用的方法
向上转型:是指将一个子类对象转换为父类对象的过程。通过向上转型,可以使用父类类型的引用来引用子类对象,从而实现多态性。
9.泛型,类型擦除
泛型把类型明确的工作推迟到创建对象或调用方法时的一种特殊的类型。编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。一般的使用方式就是泛型类、泛型接口、泛型方法。使用泛型可以增强代码的可读性以及稳定性。
类型擦除:类型擦除会将泛型类型的实际参数用其边界或者Object类型替换,从而使得泛型类、泛型接口、泛型方法在编译后的字节码中不在包含泛型信息。比如说:一个泛型类List<T>在编译后会变成List<Object>,而在运行时,所有的T都会被擦除为Object类型。
10.反射,原理,优缺点,应用场景
是什么:反射赋予我们在运行时分析类以及获取类中方法的能力。通过反射,我们可以获取任意一个类的所有属性和方法,还可以调用这些属性和方法。
作用:
- 动态性在运行时获取类的信息、创建对象、调用方法、访问和修改属性
- 为很多框架提供开箱即用的功能提供支持
- 用于动态代理
优点:反射可以让我们的代码更加灵活,为各种框架提供开箱即用的功能提供了支持。
缺点:增加了安全问题,比如可以无视泛型参数的安全检查。并且反射的性能也要稍差一些。
使用:
// 获取类的 Class 对象 Class<?> clazz = Class.forName("MyClass"); // 创建类的实例 Object instance = clazz.getDeclaredConstructor().newInstance(); // 获取类的所有方法 Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { System.out.println("Method: " + method.getName()); } // 获取特定的方法并调用它 Method method = clazz.getDeclaredMethod("sayHello", String.class); method.invoke(instance, "World"); // 获取类的所有字段 Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { System.out.println("Field: " + field.getName()); } // 访问和修改私有字段 Field field = clazz.getDeclaredField("privateField"); field.setAccessible(true); // 绕过私有访问权的限制 field.set(instance, "New Value"); // 获取所有构造函数 Constructor<?>[] constructors = clazz.getDeclaredConstructors(); for (Constructor<?> constructor : constructors) { System.out.println("Constructor: " + constructor); } // 使用特定构造函数创建对象 Constructor<?> constructor = clazz.getDeclaredConstructor(int.class); Object newInstance = constructor.newInstance(42);
应用场景:
(1)使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序
(2)Spring通过xml配置模式装载Bean的过程:1)将程序中所有xml或者properties配置文件加载入内存;2)java类里面解析xml或properties里面的内容,得到实体类的字节码字符串以及相关的属性信息;3)使用反射机制,根据这个字符串获得某个类的Class实例;4)动态配置实例的属性
11.static , final 关键字⭐️
final关键字,意思是最终的、不可修改的,用来修饰类、方法和变量。final修饰的类不能被继承,final修饰的方法不能被重写,final修饰的变量是常量,如果是基本数据类型的变量,其数值一旦初始化后就不能更改;如果是引用类型的变量,对其初始化后就不能再指向另一个对象。
static关键字是静态的意思,可以用来修饰成员变量、成员方法、代码块、内部类等。被static修饰的成员属于类,而不属于类的某个对象。静态成员变量存放在java内存区域的方法区中。静态代码块定义在类中方法外,静态代码块在非静态代码块之前执行,并且只执行一次。静态内部类和非静态内部类的最大区别就是非静态内部类在编译完成后会隐含地保存着一个引用,该引用指向创建它的外围类,但是静态内部类没有。
12.String , StringBuffer , StringBuilder 底层区别
StringBuilder和StringBuffer都继承自AbstractStringBuilder类。底层使用字符数组保存字符串,不过没有使用final和private关键字修,所以是可变的。而StringBuffer中的方法都加了synchronized关键字,保证了线程安全。而StringBuilder中则没有加这个关键字。
13. io多路复用⭐️⭐️⭐️
ps:超级高频,他要敢问io多路相关,你就从select到poll到epoll给他全讲一遍。
select的执行原理:1.将当前进程的所有文件描述符,一次性的从用户态拷贝到内核态2.在内核中快速的无差别遍历每个文件描述符fd,判断是否有数据到达3.将所有文件描述符fd状态,从内核态拷贝到用户态,并返回已就绪fd的个数4.用户态遍历判断具体哪个fd已就绪,然后进行相应的事件处理
select的不足:1.文件描述符表为bitmap结构,有长度为1024的限制2.fdset无法做到重用,每次循环必须重新创建3.频繁的用户态和内核态拷贝,开销比较大4.需要对文件描述符表进行遍历,需要o(n)的轮询时间复杂度
poll模型是对select模型的改进版,他的执行原理和select类似,只是poll使用了pollfd结构数组,解决了select的1024个文件描述符的限制。但仍然存在一些不足,比如仍然需要频繁的用户态和内核态拷贝,开销比较大,需要对文件描述符表进行遍历,需要o(n)的轮询时间复杂度
epoll原理:当调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来。而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法,它会将发生的事件添加到rdlist双链表中。当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。
epoll的优势:epoll模型基于红黑树+双链表存储事件,没有最大连接数的限制。在epoll_ctl()函数中,为每个文件描述符都指定了回调函数,基于回调函数把就绪事件放到就绪队列中,因此时间复杂度从o(n)将为了o(1)。并且只需要在epoll_ctl()时传递一次文件描述符,epoll_wait()不需要再次传递文件描述符。
epoll的触发机制:LT和ET;LT模式下,只要一个句柄上的事件一次没有处理完,会在以后调用epoll_wait时次次返回这个句柄,而ET模式仅在第一次返回。
14.Object 类的⽅法
getClass,hashcode,equals,toString,wait,notify,notifyAll,finalize等。
15.⾃动拆箱和⾃动装箱
装箱就是把基本类型用对应的包装类型包装起来拆箱就是把包装类型转换为基本类型
从字节码中,我们可以发现自动装箱其实就是调用了包装类的valueOf()方法,拆箱其实是调用了xxxValue()方法。需要注意的就是如果频繁拆装箱会严重影响系统的性能。
16.String类的常用方法
17.spi是什么
SPI 即 Service Provider Interface ,字面意思就是:“服务提供者的接口”,专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,修改或者替换服务实现并不需要修改调用方。优点:大大地提高接口设计的灵活性。缺点:需要遍历加载所有的实现类,不能做到按需加载
17.1 spi破坏了双亲委派机制吗
⭐️
SPI机制在一定程度上“破坏”了双亲委派机制。这是因为SPI机制允许服务提供商的jar包中的类由自己的类加载器加载,而不是由启动类加载器或扩展类加载器加载。这种做法使得同一个接口的不同实现可以由不同的类加载器加载,从而绕过了双亲委派模型。
17.2 SPI机制的ServiceLoader是如何进行加载的?
⭐️‼️
查找服务提供者的配置文件;读取配置文件获取实现类的全限定名;通过类加载器加载实现类;反射创建实例;然后迭代器遍历所有的服务提供者实例。
- 查找服务提供者配置文件:当调用
ServiceLoader.load()
方法时,ServiceLoader
会在META-INF/services
目录下查找与全限定服务接口名称相对应的文件。 - 读取配置文件:配置文件包含一个或多个实现类的全限定名,每行一个。
ServiceLoader
会读取这些名称。 - 加载实现类:对于配置文件中的每个实现类名称,
ServiceLoader
将尝试通过其类加载器来加载这个类。 - 创建服务实例:加载实现类后,
ServiceLoader
会通过反射创建这些类的实例。 - 提供迭代器:
ServiceLoader
实现了Iterable
接口,因此可以通过迭代器或for-each
循环来遍历所有可用的服务提供者实例。
18.java nio的核心组件❌
channel,buffer,selector
channel类似于流,每个channel对应一个buffer缓冲区。channel会注册到selector。
buffer和channel都是可读可写的。
selector会根据channel上发生的读写事件,将请求交由某个空闲的线程处理。selector对应一个或者多个线程。
19.JDK VS JRE⭐️
JDk是Java开发工具的集合,包含了JRE和开发所需要的工具(编译器javac,反编译器javap),适合java开发者使用
JRE是运行java程序所必需的环境,包含了JVM和java平台核心类库
20.JIT和AOT❌
JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用
AOT属于静态编译避免了 JIT 预热等各方面的开销,可以提高 Java 程序的启动速度,避免预热时间长。并且,AOT 还能减少内存占用和增强 Java 程序的安全性,特别适合云原生场景。
20.1 字节码如何翻译成机器码,是全部一次性翻译吗⭐️‼️
- 字节码加载: 当 Java 程序运行时,Java 虚拟机(JVM)会将 .class 文件中的字节码加载到内存中。
- 解释执行: JVM 初始会通过解释器逐行解释执行字节码。这种方式启动快,但执行速度较慢。
- 热点代码检测: JVM 会监控哪些方法或代码块被频繁执行(称为热点代码)。当某段代码被执行多次后,JVM 会将其标记为热点代码。
- 即时编译(JIT): 对于热点代码,JIT 编译器会将其字节码编译成机器码。这个过程是在运行时进行的,编译后的机器码会被缓存起来,以便下次直接执行,提高执行效率。
- 优化: JIT 编译器不仅仅是简单地将字节码翻译成机器码,还会进行各种优化,例如内联展开、循环优化等,以进一步提高性能。
附:本文内容来自javaguide、小林coding、牛客面经、模型生成、个人总结等。
#实习##春招##秋招##八股#博主博主,网上八股那么多,不知道看哪个怎么办,有没有什么重点的八股拿来学习一下的? 有的,兄弟有的! 会陆续发布一些本牛在实习和秋招过程中总结的八股。