interview changed.

This commit is contained in:
zeroornull
2025-03-11 22:53:29 +08:00
parent e880fde93a
commit bd4afda4ed
4 changed files with 477 additions and 11 deletions

View File

@@ -4,6 +4,13 @@ export default navbar([
"/",
"/portfolio",
"/demo/",
{
text: "面试",
link: "/interview/x-interview.md",
icon: "lightbulb",
// 仅在 `/interview/` 激活
activeMatch: "^/interview/$",
},
{
text: "指南",
icon: "lightbulb",

View File

@@ -4,16 +4,16 @@ import navbar from "./navbar.js";
import sidebar from "./sidebar.js";
export default hopeTheme({
hostname: "https://vuepress-theme-hope-docs-demo.netlify.app",
hostname: "https://docs.tuotuo.de",
author: {
name: "Mr.Hope",
url: "https://mister-hope.com",
name: "pax",
url: "https://blog.tuotuo.de",
},
logo: "https://theme-hope-assets.vuejs.press/logo.svg",
repo: "vuepress-theme-hope/vuepress-theme-hope",
repo: "zeroornull/my-docs",
docsDir: "src",
@@ -120,13 +120,13 @@ export default hopeTheme({
// 在这里配置主题提供的插件
plugins: {
// 注意: 仅用于测试! 你必须自行生成并在生产环境中使用自己的评论服务
comment: {
provider: "Giscus",
repo: "vuepress-theme-hope/giscus-discussions",
repoId: "R_kgDOG_Pt2A",
category: "Announcements",
categoryId: "DIC_kwDOG_Pt2M4COD69",
},
// comment: {
// provider: "Giscus",
// repo: "vuepress-theme-hope/giscus-discussions",
// repoId: "R_kgDOG_Pt2A",
// category: "Announcements",
// categoryId: "DIC_kwDOG_Pt2M4COD69",
// },
components: {
components: ["Badge", "VPCard"],

6
src/interview/README.md Normal file
View File

@@ -0,0 +1,6 @@
---
title: 面试
index: false
icon: laptop-code
sidebar: false
---

View File

@@ -0,0 +1,453 @@
---
title: Java全栈面试
index: false
icon: laptop-code
sidebar: false
toc: true
editLink: false
headerDepth: 5
---
## Java基础
```plain
Java基础部分包括语法基础泛型注解异常反射和其它如SPI机制等
```
### 1.1 语法基础
#### 面向对象特性?
- **封装**
利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。
优点:
- 减少耦合: 可以独立地开发、测试、优化、使用、理解和修改
- 减轻维护的负担: 可以更容易被程序员理解,并且在调试的时候可以不影响其他模块
- 有效地调节性能: 可以通过剖析确定哪些模块影响了系统的性能
- 提高软件的可重用性
- 降低了构建大型系统的风险: 即使整个系统不可用,但是这些独立的模块却有可能是可用的
以下 Person 类封装 name、gender、age 等属性,外界只能通过 get() 方法获取一个 Person 对象的 name 属性和 gender 属性,而无法获取 age 属性,但是 age 属性可以供 work() 方法使用。
注意到 gender 属性使用 int 数据类型进行存储,封装使得用户注意不到这种实现细节。并且在需要修改 gender 属性使用的数据类型时,也可以在不影响客户端代码的情况下进行。
```java
public class Person {
private String name;
private int gender;
private int age;
public String getName() {
return name;
}
public String getGender() {
return gender == 0 ? "man" : "woman";
}
public void work() {
if (18 <= age && age <= 50) {
System.out.println(name + " is working very hard!");
} else {
System.out.println(name + " can't work any more!");
}
}
}
```
- **继承**
继承实现了 **IS-A** 关系,例如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal从而获得 Animal 非 private 的属性和方法。
继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。
Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 **向上转型**
```java
Animal animal = new Cat();
```
- **多态**
多态分为编译时多态和运行时多态:
- 编译时多态主要指方法的重载
- 运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定
运行时多态有三个条件:
- 继承
- 覆盖(重写)
- 向上转型
下面的代码中,乐器类(Instrument)有两个子类: Wind 和 Percussion它们都覆盖了父类的 play() 方法,并且在 main() 方法中使用父类 Instrument 来引用 Wind 和 Percussion 对象。在 Instrument 引用调用 play() 方法时,会执行实际引用对象所在类的 play() 方法,而不是 Instrument 类的方法。
```java
public class Instrument {
public void play() {
System.out.println("Instrument is playing...");
}
}
public class Wind extends Instrument {
public void play() {
System.out.println("Wind is playing...");
}
}
public class Percussion extends Instrument {
public void play() {
System.out.println("Percussion is playing...");
}
}
public class Music {
public static void main(String[] args) {
List<Instrument> instruments = new ArrayList<>();
instruments.add(new Wind());
instruments.add(new Percussion());
for(Instrument instrument : instruments) {
instrument.play();
}
}
}
```
#### a = a + b 与 a += b 的区别
+= 隐式的将加操作的结果类型强制转换为持有结果的类型。如果两个整型相加,如 byte、short 或者 int首先会将它们提升到 int 类型,然后在执行加法操作。
```java
byte a = 127;
byte b = 127;
b = a + b; // error : cannot convert from int to byte
b += a; // ok
```
(因为 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错)
#### 3*0.1 == 0.3 将会返回什么? true 还是 false?
false因为有些浮点数不能完全精确的表示出来。
#### 能在 Switch 中使用 String 吗?
从 Java 7 开始,我们可以在 switch case 中使用字符串,但这仅仅是一个语法糖。内部实现在 switch 中使用字符串的 hash code。
#### 对equals()和hashCode()的理解?
- **为什么在重写 equals 方法的时候需要重写 hashCode 方法**?
因为有强制的规范指定需要同时重写 hashcode 与 equals 是方法,许多容器类,如 HashMap、HashSet 都依赖于 hashcode 与 equals 的规定。
- **有没有可能两个不相等的对象有相同的 hashcode**?
有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。相等 hashcode 值的规定只是说如果两个对象相等必须有相同的hashcode 值,但是没有关于不相等对象的任何规定。
- **两个相同的对象会有不同的 hash code 吗**?
不能,根据 hash code 的规定,这是不可能的。
#### final、finalize 和 finally 的不同之处?
final 是一个修饰符,可以修饰变量、方法和类。如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。
Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,但是什么时候调用 finalize 没有保证。
finally 是一个关键字,与 try 和 catch 一起用于异常的处理。finally 块一定会被执行,无论在 try 块中是否有发生异常。
#### String、StringBuffer与StringBuilder的区别
第一点: 可变和适用范围。String对象是不可变的而StringBuffer和StringBuilder是可变字符序列。每次对String的操作相当于生成一个新的String对象而对StringBuffer和StringBuilder的操作是对对象本身的操作而不会生成新的对象所以对于频繁改变内容的字符串避免使用String因为频繁的生成对象将会对系统性能产生影响。
第二点: 线程安全。String由于有final修饰是immutable的安全性是简单而纯粹的。StringBuilder和StringBuffer的区别在于StringBuilder不保证同步也就是说如果需要线程安全需要使用StringBuffer不需要同步的StringBuilder效率更高。
#### 接口与抽象类的区别?
- 一个子类只能继承一个抽象类, 但能实现多个接口
- 抽象类可以有构造方法, 接口没有构造方法
- 抽象类可以有普通成员变量, 接口没有普通成员变量
- 抽象类和接口都可有静态成员变量, 抽象类中静态成员变量访问类型任意接口只能public static final(默认)
- 抽象类可以没有抽象方法, 抽象类可以有普通方法接口在JDK8之前都是抽象方法在JDK8可以有default方法在JDK9中允许有私有普通方法
- 抽象类可以有静态方法接口在JDK8之前不能有静态方法在JDK8中可以有静态方法且只能被接口类直接调用不能被实现类的对象调用
- 抽象类中的方法可以是public、protected; 接口方法在JDK8之前只有public abstract在JDK8可以有default方法在JDK9中允许有private方法
#### this() & super()在构造方法中的区别?
- 调用super()必须写在子类构造方法的第一行, 否则编译不通过
- super从子类调用父类构造, this在同一类中调用其他构造均需要放在第一行
- 尽管可以用this调用一个构造器, 却不能调用2个
- this和super不能出现在同一个构造器中, 否则编译不通过
- this()、super()都指的对象,不可以在static环境中使用
- 本质this指向本对象的指针。super是一个关键字
#### Java移位运算符
java中有三种移位运算符
- `<<` :左移运算符,`x << 1`,相当于x乘以2(不溢出的情况下),低位补0
- `>>` :带符号右移,`x >> 1`,相当于x除以2,正数高位补0,负数高位补1
- `>>>` :无符号右移,忽略符号位,空位都以0补齐
### 1.2 泛型
#### 为什么需要泛型?
1. **适用于多种数据类型执行相同的代码**
```java
private static int add(int a, int b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static float add(float a, float b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static double add(double a, double b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
```
如果没有泛型要实现不同类型的加法每种类型都需要重载一个add方法通过泛型我们可以复用为一个方法
```java
private static <T extends Number> double add(T a, T b) {
System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
return a.doubleValue() + b.doubleValue();
}
```
- **泛型中的类型在使用时指定,不需要强制类型转换****类型安全**,编译器会**检查类型**
看下这个例子:
```java
List list = new ArrayList();
list.add("xxString");
list.add(100d);
list.add(new Person());
```
我们在使用上述list中list中的元素都是Object类型无法约束其中的类型所以在取出集合元素时需要人为的强制类型转化到具体的目标类型且很容易出现`java.lang.ClassCastException`异常。
引入泛型,它将提供类型的约束,提供编译前的检查:
```java
List<String> list = new ArrayList<String>();
// list中只能放String, 不能放其它类型的元素
```
#### 泛型类如何定义使用?
- 从一个简单的泛型类看起:
```java
class Point<T>{ // 此处可以随便写标识符号T是type的简称
private T var ; // var的类型由T指定由外部指定
public T getVar(){ // 返回值的类型由外部决定
return var ;
}
public void setVar(T var){ // 设置的类型也由外部决定
this.var = var ;
}
}
public class GenericsDemo06{
public static void main(String args[]){
Point<String> p = new Point<String>() ; // 里面的var类型为String类型
p.setVar("it") ; // 设置字符串
System.out.println(p.getVar().length()) ; // 取得字符串的长度
}
}
```
- 多元泛型
```java
class Notepad<K,V>{ // 此处指定了两个泛型类型
private K key ; // 此变量的类型由外部决定
private V value ; // 此变量的类型由外部决定
public K getKey(){
return this.key ;
}
public V getValue(){
return this.value ;
}
public void setKey(K key){
this.key = key ;
}
public void setValue(V value){
this.value = value ;
}
}
public class GenericsDemo09{
public static void main(String args[]){
Notepad<String,Integer> t = null ; // 定义两个泛型类型的对象
t = new Notepad<String,Integer>() ; // 里面的key为Stringvalue为Integer
t.setKey("汤姆") ; // 设置第一个内容
t.setValue(20) ; // 设置第二个内容
System.out.print("姓名;" + t.getKey()) ; // 取得信息
System.out.print(",年龄;" + t.getValue()) ; // 取得信息
}
}
```
#### 泛型接口如何定义使用?
- 简单的泛型接口
```java
interface Info<T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
}
class InfoImpl<T> implements Info<T>{ // 定义泛型接口的子类
private T var ; // 定义属性
public InfoImpl(T var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
}
public class GenericsDemo24{
public static void main(String arsg[]){
Info<String> i = null; // 声明接口对象
i = new InfoImpl<String>("汤姆") ; // 通过子类实例化对象
System.out.println("内容:" + i.getVar()) ;
}
}
```
#### 泛型方法如何定义使用?
泛型方法,是在调用方法的时候指明泛型的具体类型。
- 定义泛型方法语法格式
![img](https://b2files.173114.xyz/blogimg/2025/03/4eebe47eb4f2f9d0833c0d687d44b940.png)
- 调用泛型方法语法格式
![img](https://b2files.173114.xyz/blogimg/2025/03/2e383a7c2bbbf60c5bb35c95f128edf9.png)
说明一下,定义泛型方法时,必须在返回值前边加一个`<T>`,来声明这是一个泛型方法,持有一个泛型`T`然后才可以用泛型T作为方法的返回值。
`Class<T>`的作用就是指明泛型的具体类型,而`Class<T>`类型的变量c可以用来创建泛型类的对象。
为什么要用变量c来创建对象呢既然是泛型方法就代表着我们不知道具体的类型是什么也不知道构造方法如何因此没有办法去new一个对象但可以利用变量c的newInstance方法去创建对象也就是利用反射创建对象。
泛型方法要求的参数是`Class<T>`类型,而`Class.forName()`方法的返回值也是`Class<T>`,因此可以用`Class.forName()`作为参数。其中,`forName()`方法中的参数是何种类型,返回的`Class<T>`就是何种类型。在本例中,`forName()`方法中传入的是User类的完整路径因此返回的是`Class<User>`类型的对象因此调用泛型方法时变量c的类型就是`Class<User>`因此泛型方法中的泛型T就被指明为User因此变量obj的类型为User。
当然,泛型方法不是仅仅可以有一个参数`Class<T>`,可以根据需要添加其他参数。
**为什么要使用泛型方法呢**因为泛型类要在实例化的时候就指明类型如果想换一种类型不得不重新new一次可能不够灵活而泛型方法可以在调用的时候指明类型更加灵活。
#### 泛型的上限和下限?
在使用泛型的时候,我们可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。
上限
```java
class Info<T extends Number>{ // 此处泛型只能是数字类型
private T var ; // 定义泛型变量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){ // 直接打印
return this.var.toString() ;
}
}
public class demo1{
public static void main(String args[]){
Info<Integer> i1 = new Info<Integer>() ; // 声明Integer的泛型对象
}
}
```
下限
```java
class Info<T>{
private T var ; // 定义泛型变量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){ // 直接打印
return this.var.toString() ;
}
}
public class GenericsDemo21{
public static void main(String args[]){
Info<String> i1 = new Info<String>() ; // 声明String的泛型对象
Info<Object> i2 = new Info<Object>() ; // 声明Object的泛型对象
i1.setVar("hello") ;
i2.setVar(new Object()) ;
fun(i1) ;
fun(i2) ;
}
public static void fun(Info<? super String> temp){ // 只能接收String或Object类型的泛型String类的父类只有Object类
System.out.print(temp + ", ") ;
}
}
```
#### 如何理解Java中的泛型是伪泛型
泛型中类型擦除 Java泛型这个特性是从JDK 1.5才开始加入的因此为了兼容之前的版本Java泛型的实现采取了“伪泛型”的策略即Java在语法上支持泛型但是在编译阶段会进行所谓的“类型擦除”Type Erasure将所有的泛型表示尖括号中的内容都替换为具体的类型其对应的原生态类型就像完全没有泛型一样。
### 1.3 注解
#### 注解的作用?
注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。它主要的作用有以下四方面:
- 生成文档通过代码里标识的元数据生成javadoc文档。
- 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
- 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
- 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。
#### 注解的常见分类?
- **Java自带的标准注解**,包括`@Override``@Deprecated``@SuppressWarnings`,分别用于标明重写某个方法、标明某个类或方法过时、标明要忽略的警告,用这些注解标明后编译器就会进行检查。
- **元注解**,元注解是用于定义注解的注解,包括`@Retention``@Target``@Inherited``@Documented`
- `@Retention`用于标明注解被保留的阶段
- `@Target`用于标明注解使用的范围
- `@Documented`用于标明是否生成javadoc文档
- **自定义注解**,可以根据自己的需求定义注解,并可用元注解对自定义注解进行注解。
### 1.4 异常
#### Java异常类层次结构?
- **Throwable** 是 Java 语言中所有错误与异常的超类。
- **Error** 类及其子类:程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
- **Exception** 程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。
![img](https://b2files.173114.xyz/blogimg/2025/03/5b8dd265a5f94619125f81a2829c14c1.png)