【JAVA八股文】持续更新中...

                     【-------------数据类型------------】                              

一、基本类型、包装类型

基本类型:整型(byte short int long )浮点型(float double)布尔型(boolean) 字符型(char)

包装类型:Byte Short Integer Long Float Double Boolean Character

1.基本类型的转换方式

自动类型转换(小–>大): byte --> short --> int --> long --> float --> double

强制类型转换(大–>小): 小类型 变量名 = (大类型) 值

2.为什么需要包装类型

Java是面向对象的,很多地方要求使用对象而不是基本数据类型,比如集合类中,无法存放基本数据类型

3.基本类型和包装类型的区别

(1)默认值:基本类型默认值为0,包装类型默认值为null

(2)初始化:基本类型不需要new,包装类型需要

(3)存储:基本类型存储在栈上,占用固定的内存;包装类型存储在堆上,占用更多的内存

4.自动拆装箱:基包转换

装箱:基本类型——>包装类型;拆箱:包装类型——>基本类型

装箱其实就是调用了包装类的 valueOf() 方法(Integer.valueOf()),拆箱其实就是调用了 xxxValue() 方法(Integer.intValue())

大小比较:包装类先拆箱成基本类型,然后再进行比较

二、引用类型(类、数组、接口)

1.String

String是一个类,所以是引用数据类型,因为String被final修饰,所以不能被继承。

(1)String/StringBuffer/StringBuilder的区别

【可变性】String内部的value值是final修饰的,所以是一个不可变的类,每一次修改String值时,都会产生一个新的对象;StringBuffer和StringBuilder是可变类,字符串的变更不会产生新的对象。

【线程安全】String是一个不可变的类,所以线程安全,StringBuffer也是线程安全的,因为它的每个操作方法中都用了synchronized同步关键字,StringBuilder不是线程安全的,所以在多线程环境下对字符串操作时应该使用StringBuffer。

【性能】String是性能最低的,因为在字符串拼接或者修改时,需要创建新的对象,StringBuilder性能更高,因为StringBuffer加了同步锁。

【存储】String存储在常量池中,后两者存储在堆内存空间中。

(2)String为什么是不可变的

1.保存字符串的数组被final修饰且为私有的,并且string类没有提供/暴露修改这个字符串的方法。

2.String类被final修饰导致其不能被继承,进而避免了子类破坏String的不可变。

(3)String创建字符串的对象数量

String str = new String ("hello");创建了几个对象?

创建的对象数是1个或2个。new String 会先去常量池中判断有没有此字符串,如果有则只在堆上创建一个字符串并且指向常量池中的字符串,如果常量池中没有此字符串,则会创建 2 个对象,先在常量池中新建此字符串,然后把此引用返回给堆上的对象。

因此推荐使用直接量的方式创建字符串:

String str = "hello";

JVM会检查常量池中是否有hello,没有的话存入常量池,有的话将其引用赋值给变量str

(4)字符串拼接如何实现

+运算符:如果拼接的都是字符串直接量,编译器会直接优化为一个完整的字符串;如果拼接的包含变量,编译时创建StringBuilder实例并调用append()方法

StringBuilder/StringBuffer:拼接的字符串中包含变量,都有字符串缓冲区(默认16),如果超过容量,会有扩容机制。

String的concat():先创建一个数组,先后将两个字符串拼接到数组中,最后将数组转化为字符串

2.接口和抽象类的区别

抽象类属于模板设计。抽象类可以包含抽象方法和具体方法的定义,用于作为其他类的父类。

//动物类可以作为抽象类,因为动物不是具体的,需要通过子类比如猫类、狗类继承
abstract class Animal {
    //成员变量:可以拥有各种访问修饰符
    static String name;
    //构造函数
    public Animal() {
    }
    //抽象方法:以分号结尾,无方法体,必须在子类中实现重写
    abstract void eat();
    //具体方法:可以拥有各种访问修饰符
    public void play() {
        System.out.println("玩耍");
    }
}

接口是一组抽象方法的集合,体现的是一种规范。接口定义了对象应该具备的行为,类可以实现接口来达到多继承的效果。

public interface Demo{
    //常量:必须初始化
    public static final String s = "我是接口";
    //接口属于实现,不是继承,所以没有构造方法
    //抽象方法:必须实现,默认修饰 public abstract
    public abstract void play();
    //静态方法:可以使用接口名调用静态方法
    static void test(){
       system.out.println("我是静态方法");
    }
    //默认方法:为了扩展性和复用性
    default void test2(){
       system.out.println("我是默认方法");
    }
}
(1)定义方式

(1)抽象类:使用关键字“abstract”来定义

(2)接口:使用关键字“interface”来定义

(2)继承和实现

(1)抽象类:使用关键字“extends”来继承,一个类只能继承一个抽象类

(2)接口:使用关键字“implements”来实现,一个类可以实现多个接口

(3)成员变量和构造函数

(1)抽象类:拥有成员变量和构造方法(不用于创建对象,而是初始化抽象类),可以拥有各种访问修饰符

(2)接口:不能拥有成员变量(只能包含静态常量,默认public static final修饰)和构造方法

(4)方法实现

(1)抽象类:包含抽象方法具体方法

(2)接口:包含抽象方法静态方法(静态方法是属于接口本身的方法,它可以直接通过接口名调用,而不需要创建接口的实例)和默认方法(默认方法是指在接口中定义具体实现的方法。当接口的实现类没有实现该方法时,就会使用接口中默认的方法实现)、私有方法

(5)相同点

都不能被实例化不能使用 new 关键字创建实例

都可以包含抽象方法,实现或继承的时候都必须实现这些抽象方法

3.类之间的关系

4.内部类

内部类是定义在另一个类中的类,通常被称为嵌套类

public class OuterClass {
    private String outerField = "外部字段";

    public class InnerClass {
        public void printOuterField() {
            System.out.println(outerField);
        }
    }
}

                     【-------------面向对象------------】                              

一、面向对象和面向过程的区别

面向对象:如果需要实现某个功能,需要先创建对应的对象,然后让对象执行行为。特点是降低系统的耦合,更容易扩展和维护。适用于大规模问题

面向过程:如果需要实现某个功能,只需要编写对应的函数。特点是性能高,但不易拓展和维护。适用于小规模问题

二、面向对象的三大特征

1.封装:将属性和方法写到抽象的类中,外界使用类创建对象,隐藏实现细节,提供公共的访问方式,提高代码安全性和复用性

2.继承:子类默认继承父类的所有属性和方法,子类可以重写(override)父类的属性和方法,子类可以拥有多个父类

3.多态:不同的子类对象调用相同的父类方法,产生不同的执行结果,是子类不同的实现,增加代码的灵活度

// 父类Parent里有say()方法,两个子类重写了say()
// p1和p2都是父类的实例,但是调用的say()是不同的实现
Parent p1 = new Son();
Parent p2 = new Daughter();
p1.say();
p2.say();

三、重写、重载的区别

是Java多态性的不同表现,重写是父类与子类之间多态性的表现,重载是一个类中多态性的表现

1.重写(override):子类和父类的方法名称、参数列表都相同,子类对方法体进行修改

【运行时多态】在程序运行时,根据实际对象的类型来确定调用的方法。

2.重载(overload):在一个类中可以有多个同名方法,但必须有不同的参数列表(参数类型、个数或顺序不同)

【编译时多态】在编译时,根据调用时提供的参数类型和数量来确定具体调用的方法。

四、构造方法为不能被重写、能被重载

作用:在创建对象时初始化对象的数据成员,在new对象时被自动调用,构造函数是没有返回值的

与类名相同,没有返回类型,可以有参数列表

如果没有构造方法,Java编译器会自动提供一个默认的构造方法,不带任何参数也不执行任何操作

构造方法不可以被继承,因此不能重写,但能重载:

重写是子类方法重写父类的方法,重写的方法名不变,而类的构造方法名必须与类名一致,假设父类的构造方法如果能够被子类重写则子类类名必须与父类类名一致才行;而重载是针对同一个方法名的,所以Java构造方法可以被重载。

五、深拷贝和浅拷贝的区别

1.深拷贝:复制并创建一个一模一样的对象,不共享内存,修改新对象,旧对象保持不变

2.浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存

六、JAVA对象的创建过程

  1. 检查类是否加载(非必然步骤,如果没有就执行类的加载);
  2. 分配内存;
  3. 初始化零值;
  4. 设置头对象;
  5. 执行<init>方法(该方法由实例成员变量声明、实例初始化块和构造方法组成)。

七、JAVA创建对象的几种方式

1.使用new关键字

通过调用类的构造函数来创建对象

public class MyClass {
    int x;

    MyClass(int x) {
        this.x = x;
    }

    public static void main(String[] args) {
        MyClass obj = new MyClass(10); // 使用new关键字创建对象
        System.out.println(obj.x); // 输出: 10
    }
}

2.使用反射

Java的反射API允许在运行时创建对象。使用Class类的newInstance方法

public class MyClass {
    // ... 省略其他代码 ...

    public static void main(String[] args) throws Exception {
        Class<?> clazz = MyClass.class;
        MyClass obj = (MyClass) clazz.getDeclaredConstructor(int.class).newInstance(20); // 使用反射创建对象
        System.out.println(obj.x); // 输出: 20
    }
}

3.使用克隆

public class MyClass implements Cloneable {
    int x;

    // ... 省略其他代码 ...

    @Override
    protected MyClass clone() throws CloneNotSupportedException {
        return (MyClass) super.clone(); // 使用clone方法创建对象的副本
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        MyClass original = new MyClass(40);
        MyClass copy = original.clone(); // 使用clone方法创建对象的副本
        System.out.println(copy.x); // 输出: 40
    }
}

 4.使用反序列化

import java.io.*;

public class MyClass implements Serializable {
    // ... 省略其他代码和序列化ID ...

    public static void main(String[] args) throws Exception {
        // 假设我们有一个包含MyClass对象的文件
        FileInputStream fis = new FileInputStream("myfile.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        MyClass obj = (MyClass) ois.readObject(); // 通过反序列化创建对象
        ois.close();
        fis.close();
        // 现在你可以使用obj了
    }
}

八、反射机制

1.理解

JAVA中的对象有两种类型:编译时类型和运行时类型。Person P=new Student();P变量编译时的类型为Person,运行时的类型为Student。

程序运行时接收一个对象,编译时类型为Object,但又需要调用运行时类型的方法,需要利用反射:

(1)可以获取任意一个类的Class对象,并通过对象查看类的信息

(2)可以创建任意一个类的实例,并访问该实例的成员

(3)可以生成一个类的动态代理类或对象

2.应用场景

Spring/Spring BootMyBatis 等等框架中都⼤量使⽤了反射机制。这些框架中也⼤量使⽤了动态代理,⽽动态代理的实现也依赖反射。

九、JAVA的四种引用方式

1.强引用

程序创建一个对象,并赋给一个引用变量,通过引用变量来操作实践的对象,不能被垃圾回收

2.软引用

当一个对象只有软引用时,内存空间不足时有可能被垃圾回收机制回收

3.弱引用

垃圾回收机制运行时总会被回收

4.虚引用

主要用于跟踪对象被垃圾回收的状态,必须和引用队列联合使用

                       【-------------关键字------------】                                

一、Java访问权限

1.public:可以被任意类访问,可修饰类、成员变量、方法、内部类

2.protected:对同一包内的类和所有子类可见

3.default:对同一包内的类可见

4.private:只能在本类中访问

二、static、final区别

1.static

JAVA中包含成员变量、方法、构造器、初始化块、内部类(包括接口、枚举等)5种成员,static除构造器外都可以修饰,修饰之后的为静态成员/类成员。类成员属于类,而不属于对象。

类成员不能访问实例成员,因为类成员的作用域更大

(1)类变量:随类存储在方法区中,类变量可以通过类名来访问

(2)类方法:类变量可以通过类名来访问

(3)静态块:只在类加载的时候被隐式调用一次

(4)静态内部类:不能访问外部类的实例成员,只能访问外部类的静态成员

2.final

final 用于修饰变量、方法或类时,表示它是不可变的。

(1)变量:⼀旦赋值后就不能再修改

(2)方法:不能被子类重写

(3)类:不能被继承

三、final/finally/finalize区别

四、super、this区别

1.super:子类调用父类的方法、属性、构造方法时,放在程序首行

2.this:调用本类中的方法、属性、构造方法时,放在程序首行

this 可以作为当前对象的引用,但是 super 却不可以作为父类对象的引用

 五、==、equals()、hashCode()

1.==equals()区别

都是用来判断两个数据是否相等

(1)来源不同

==是运算符;equals来自于Object类定义的一个方法

(2)使用范围不同

==可以用于基本数据类型(比较值本身)和引用类型(比较对象的内存地址,是否指向了同一个对象);equals只能用于引用类型(重写之前,等价于==比较内存地址,重写之后,比较对象的内容)

2.hashCode()equals()的关系

3.为什么要重写hashCode()equals()

六、&和&&区别

1.&&运算符在运算时当第一个表达式的值为false时,就不会再计算第二个表达式;而&运算符则不管第一个表达式的值是否为真都会执行两个表达式。

2.&运算符可以用作位运算符,而&&运算符不可以。

                         【-------------异常------------】                                  

一、什么是异常

异常是JAVA中一种表示程序出现问题或错误的事件,通常在程序执行期间发生,并且可能会导致程序终止或产生意外结果

比如试图访问一个不存在的数组索引,会抛出ArrayIndexOutOfBoundsException异常

二、异常体系/接口

通过异常类的对象来表示异常,Throwable所有异常类的父类,直接子类有Error、Exception,Error是系统内部错误,和虚拟机相关(如动态链接失败),Exception又分为:

(1)Checked异常:在编译时强制要求处理的异常,否则编译器会报错,比如IOException

(2)Runtime异常:由运行时错误引起的,无需显式声明抛出,比如NullPointerException。所有的RuntimeException类及其子类的实例被称为Runtime异常。

三、异常处理的方法

(1)try捕获异常:在try块中,放置可能引发异常的代码

(2)catch处理异常:需要先记录日志,比如输出错误信息或进行补救措施

(3)finally回收资源:包含的代码无论是否发生异常都会被执行,用于释放资源(数据库连接、打开磁盘文件等)或进行清理操作。不要在此块使用return、throw等导致方法终止的语句,会导致try、catch中的return、throw失效

try {
    // 尝试执行可能抛出异常的代码
    int result = 10 / 0; // 这里将抛出ArithmeticException异常
} catch (ArithmeticException e) {
    // 捕获异常并处理
    System.out.println("发生算术异常: " + e.getMessage());
} finally {
    // 可选的finally块,无论是否捕获到异常都会执行
    System.out.println("这是finally块,无论是否发生异常都会执行。");
}

(4)throw/throws关键字:通过throw主动抛出异常;throws将异常传递给调用者处理,用于在方法签名中声明可能抛出的异常

public void checkValue(int value) {
    if (value < 0) {
        throw new IllegalArgumentException("值不能为负数");
    }
}

public void readFile(String fileName) throws IOException {
    // 假设这里是读取文件的代码,可能会抛出IOException
    if (fileName == null) {
        throw new IOException("文件名不能为空");
    }
    // 文件读取逻辑
}

四、项目中处理异常的最佳实践

使用try-catch-finally来捕获和处理异常,保证程序的稳定和可靠

自定义异常类来更好的反应业务逻辑,避免过多的嵌套异常,简化调试过程

                  【-------------集合类/容器------------】                             

一、集合框架

Java 的集合框架是由一组接口和类组成的,这些接口和类之间形成了一个层次结构。

Java集合类由Collection接口和Map接口派生而出,所有的集合类都是四个接口的实现类。

1.最上层的接口

前三个由Collection接口派生而出,之所以定义多个接口,是为了以不同的方式操作集合对象。Collection⼀次存⼀个元素,是单列集合,Map⼀次存⼀对元素,是双列集合。

  1. List(列表):有序的集合,可重复

  2. Set(集):无序的集合,不重复

  3. Queue(队列):按照FIFO(先进先出)原则存储数据,可重复

  4. Map(映射):存储键/值对的集合,键必须唯一

2.中间的抽象类

在这里实现大多数的接口方法,继承类只需要根据自身特性重写部分方法或者实现接口方法即可

3.最后的实现类

Java集合类,都是上述接口的实现类

import java.util.ArrayList;
import java.util.HashSet;
import java.util.HashMap;
import java.util.LinkedList;
 
public class CollectionExample {
    public static void main(String[] args) {
        // List示例
        ArrayList<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");
 
        // Set示例
        HashSet<String> set = new HashSet<>();
        set.add("Apple");
        set.add("Banana");
        set.add("Cherry"); // 这里会去重
 
        // Map示例
        HashMap<String, Integer> map = new HashMap<>();
        map.put("Apple", 1);
        map.put("Banana", 2);
        map.put("Cherry", 3);
 
        // Queue示例
        LinkedList<String> queue = new LinkedList<>();
        queue.offer("Apple");
        queue.offer("Banana");
        queue.offer("Cherry");
    }
}

二、ArrayList和LinkedList区别

ArrayList基于数组实现,适合查询,不适合增删,需要扩容

LinkedList基于双向链表实现,适合增删,不适合查询,不需要扩容

三、泛型/泛型擦除/泛型转换

1.泛型

集合存在⼀个缺点,即当将对象添加到集合中后,集合会忘记该对象的具体数据类型,导致在取出对象时,变异类型变为 Object 类型。这是因为集合的设计者在创建时⽆法确定集合将被⽤来存储哪种类型的对象,因此选择设计成能够存储任何类型的对象,以保持通⽤性。缺点如下:

1.将不同类型的对象添加到集合中,引发异常

2.取出对象后通常需要进行强制类型转换

泛型允许程序在创建集合时指定集合元素的类型。可以传递多种类型的数据,但是当类型确定时不能传递其他类型的数据。例如List<T>可以为任意类型,List<String> 表示该列表只能保存字符串类型的对象。有泛型类、泛型接口和泛型方法。

2.泛型擦除

为了兼容早期没有泛型的版本,JAVA编译器在编译时会移除所有的泛型信息

当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,尖括号间的类型信息都会被扔掉

3.泛型转换

List<String>是List的子类,但是可以直接把一个List对象赋给一个List<String>对象

4.List<?super T>和List<?extends T>的区别

?是类型通配符

前者?代表未知类型,但它必须是T的父类型

后者?代表未知类型,但它必须是T的子类型

                          【-------------IO------------】                                    

一、分类

1.按流向分类:I/O 流分为输入流(只能读取数据,不能写入数据)和输出流(只能写入数据,不能读取数据)

2.按单位分类:I/O 流分为字节流和字符流。字节流操作的数据单元是8位的字符,字符流操作的数据单元是16位的字符

3.按处理功能:I/O流分为节点流(低级流,直接对特定的I/O设备读写数据)和处理流(堆节点流的连接或封装)

                      【-------------多线程------------】                                 

一、 线程和进程

线程是程序执行的最小单位,是进程中的一个执行流程

进程是正在运行的程序的实例,包含了程序代码、数据和资源的集合

二、创建线程的方式

1.继承Thread类

重写的是run()方法,而不是start()方法,但是占用了继承的名额,Java中的类是单继承的

2.实现Runnable接口

实现Runnable接口,实现run()方法,使用依然要用到Thread,这种方式更常用

3.实现Callable接口

实现Callable接口,实现call()方法,得使用Thread+ FutureTask配合,这种方式支持拿到异步执行任务的结果

4.使用线程池

实现Callable接口或者Runnable接口都可以,由ExecutorService来创建线程

                        【-------------JVM------------】                                  

一、JVM、JDK和JRE的关系

JDK是JAVA语言的软件开发工具包,提供了JAVA的开发环境和运行环境

JRE是JAVA的运行环境,是可以在其上运行、测试和传输应用程序的JAVA平台

JVM是JAVA虚拟机,是JAVA跨平台的关键

简单来说,JDK=JRE+各种开发工具;JRE=JVM+各种类库

二、堆(Heap)和栈(Stack)的区别

1.概念

栈是一个用于存储方法调用和局部变量的地方。管理方式是后进先出(先分配的内存后释放)。栈的空间相对较,分配和释放由系统自动管理,因此效率很高,但生命周期也短,方法执行结束后,栈中的数据会被自动清除。

堆用于动态分配对象的内存空间,主要用于存储通过new关键字创建的对象和数组。对象的生命周期由垃圾回收器负责管理,堆的分配速度相对较慢,可以存储较大的对象,对象的生命周期可以很长。

2.使用场景

变量的生命周期局限在函数内部,就是局部变量,占据的内存区域就是栈区

变量的生命周期希望自己控制,占据的内存区域就是堆区

3.性能

在栈区的性能更高,本质是指针的移动

进程中的所有线程共享一个堆区,因此堆区内存分配器必须处理好线程安全问题

4.内存大小

栈区的大小是固定的,并且容量有限,否则会出现栈溢出

堆区容量更大,必须确保内存可以释放掉,否则会出现内存泄漏

三、垃圾回收机制

1.为什么需要垃圾回收

当程序员手动进行内存管理时,会存在一些问题:内存泄露(忘记释放内存空间)、悬垂指针(忘记初始化指向已经回收的内存地址的指针)、错误释放引发 BUG

2.如何找到垃圾

JVM对内存自动化回收,采用垃圾回收器技术(GC)。采用根可达性算法判定对象是否存活:

(1)枚举一系列对象作为GC Roots,为垃圾回收器提供一个初始的扫描位置

(2)从GC Roots出发,搜索引用链,某个对象不可达时,该对象判定为不可能再被使用(死亡)

3.垃圾回收算法

垃圾回收算法:垃圾回收的策略,用于确定哪些对象应该被回收

(1)标记清除:标记阶段是把所有活动对象都做上标记的阶段。清除阶段是把那些没有标记的对象,也就是非活动对象回收的阶段。优点是实现简单、速度快,缺点是会造成内存碎片(没有一块连续的内存可以存放大的对象)

(2)标记复制:标记阶段是把所有活动对象都做上标记的阶段。把活动对象复制到另一半空间,把原空间里的所有对象都回收掉,变成空闲区域。优点是效率高、不会产生内存碎片,缺点是会有一半的内存被浪费掉

(3)标记压缩:标记阶段是把所有活动对象都做上标记的阶段。将打上标记的活动对象复制到堆的开头。压缩阶段并不会改变对象的排列顺序,只是缩小了它们之间的空隙, 把它们聚集到了堆的一端。缺点是时间消耗长

4.垃圾回收器

在以上算法的基础上,构建了十种垃圾回收器。垃圾回收器负责实际回收内存

Serial、CMS用了标记清除算法;ParNew用了标记复制算法;CMS、G1用了标记压缩算法

5.堆内存逻辑分区

新生代,存储新的对象;老年代:存储经过多次回收但回收不掉的对象

先存到eden区中,垃圾回收之后,将活的对象复制到survivor区中,清空eden区;如果再次添加新对象到eden区,此时垃圾回收要考虑survivor区,将两个区活的对象复制到第二个survivor区中,清空eden和第一个survivor区;如果再次添加新对象到eden区,此时垃圾回收要考虑第二个survivor区,将两个区活的对象复制到第一个survivor区中,清空eden和第二个survivor区。

四、类加载机制

1.类加载

把描述类的数据.class文件从磁盘加载到内存,并对字节码中的数据进行校验、转换解析和初始化,最终形成可以被虚拟机JVM直接使用的类型。

2.类的生命周期/加载阶段

2.类加载器

在类加载流程第⼀步加载阶段,通过⼀个类的全限定名来获取描述该类的二进制字节流,实现这⼀功能的代码叫做类加载器。

有启动类加载器、扩展类加载器、应用类加载器、自定义类加载器

3.双亲委派模型

                      【-------------新特性------------】                                 

一、Lambda 表达式 

允许将一个函数作为另一个函数的参数,也就是代码的传递,不能独立执行,必须实现函数式接口,并且返回一个函数式接口的对象。

函数式接口:仅包含一个抽象方法的接口

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值