全网八股文面试高频题目--JAVA基础

发布时间 2023-06-09 16:17:17作者: 风吹屁屁6666

八股文--JAVA基础

目录

1.JDK、JRE、JVM有什么区别

JDK 的全称(Java Development Kit Java 开发工具包)

JRE(Java Runtime Environment Java 运行环境)

JVM:Java虚拟机用于运行Java字节码文件,跨平台的核心.

JVM只认识字节码,它能够解释到系统的API调用,针对不同的系统有不同的JVM实现,但是这条代码被编译成字节码以后,他们都是一样的。所以说JVM之所以能够跨平台,它的核心就是在于通过JVM隐藏了操作系统。我们Java工程师只需要面对JVM层面进行开发,再由JVM转移到其他系统的对应的API,完成对应的工作就可以了。

  1. JDK = JRE + 开发工具集(例如 Javac,java 编译工具等)
  2. JRE = JVM + Java SE 标准类库(java 核心类库)

1.1Java为什么被称为平台无关性语言?

因为它的字节码可以在任何支持Java虚拟机的平台上运行。在编写Java程序时,程序员并不需要关心底层的硬件和操作系统,因为Java虚拟机会负责将字节码转换成平台特定的机器语言并执行。这种机制使得Java程序可以在各种不同的操作系统和硬件平台上运行,而不需要进行任何修改。因此,Java被称为“一次编写,到处运行”的语言。

2.常用数字类型的区别

名称 取值范围 存储空间
字节byte -27-27-1(-128-127) 一个字节
短整数short -215-215-1(-32768-32767) 二个字节
整数int -231-231-1(-2147483648-2147483647) 四个字节
长整数long -263-263-1 八个字节
单精度float -2149-2128-1 四个字节
双精度double -21074-21024-1 八个字节

3.Float在JVM的表达方式及使用陷阱

float类型在内存中的存储形式为科学计数法,表达式为:4.2343242E7,保留小数点后七位,

所以即使d1=433432423f,d2=d1+1;由于精度原因结果d1==d2。

4.面向对象三个特性是什么

封装:通常认为封装是把数据和操作数据的方法封装起来,对数据的访问只能通过已定义的接口。实现专业的分工、减少代码耦合、可以自由修改类的内部结构。

继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类/基类),得到继承信息的被称为子类(派生类)Java中类不支持多继承,接口可以,一个接口可以继承多个其他接口。

多态:多态是建立在封装和继承基础之上的,同一个行为具有多个不同表现形式或形态的能力;同一个接口,使用不同的实例而执行不同操作。多态:分为编译时多态(方法重载)和运行时多态(方法重写)。要实现多态需要做两件事:一是子类继承父类并重写父类中的方法,二是用父类型引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为。

补充

​ 1)子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。因为在一个子类被创建的时候,首先会在内存中创建一个父类对象,然后在父类对象外部放上子类独有的属性,两者合起来形成一个子类的对象;
(2)子类可以拥有自己属性和方法;
(3)子类可以用自己的方式实现父类的方法。(重写)

4.1重载和重写的区别?

(1)重载:编译时多态、同一个类中同名的方法具有不同的参数列表、不能根据返回类型进行区分【因为:函数调用时不能指定类型信息,编译器不知道你要调哪个函数】;
(2)重写(又名覆盖):运行时多态、子类与父类之间、子类重写父类的方法具有相同的返回类型、更好的访问权限。

4.2Java 中是否可以重写一个 private 或者 static 方法?

Java 中 static 方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。static 方法跟类的任何实例都不相关,所以概念上不适用。

Java 中也不可以覆盖 private 的方法,因为 private 修饰的变量和方法只能在当前类中使用, 如果是其他的类继承当前类是不能访问到 private 变量或方法的,当然也不能覆盖。

4.3构造方法有哪些特性?

(1)名字与类名相同;

(2)没有返回值,但不能用 void 声明构造函数;

(3)成类的对象时自动执行,无需调用。

4.4在 Java 中定义一个不做事且没有参数的构造方法有什么作用?

Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。

因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是:在父类里加上一个不做事且没有参数的构造方法。

4.5接口与抽象类的:异同

相同点 不同点
都是上层的抽象 抽象类可包含方法的实现,接口则只能包含方法的声明
不能被实例化 继承类只能继承一个抽象类,实现类可以实现多个接口
都可以包含抽象方法 抽象级别:接口>抽象类>实现类
作用不同:接口用于约束程序行为,继承则用于代码复用
抽象类中可以定义构造函数,接口不能定义构造函数
抽象类中可以包含静态方法,而接口中不可以包含静态方法

注意:JDK8以上版本,接口可以有default方法,包含方法实现

4.6抽象方法能不能加final

抽象方法不能加final关键字。final关键字用于修饰变量、方法和类,修饰的目的是为了防止被修改或继承。而抽象方法是一种没有方法体的方法,只能被子类实现,不允许直接调用,也就没有必要对其使用final关键字。如果一个抽象方法被final关键字修饰,那么子类就不能继承并实现该方法,与抽象方法的作用相违背。因此,抽象方法不能加final关键字。

5.静态变量与实例变量的区别

语法区别:静态变量前要加static关键字,实例变量不用;

隶属区别:实例变量属于某个对象的属性,而静态变量属于类;

运行区别:静态变量是在JVM加载类自己创建,实例变量是在实例化对象时创建。静态变量存放在方法区,实例变量存放在堆。

6.类的执行顺序

1.静态优先2.父类优先3.非静态块优先于构造函数。

存在继承的情况下,初始化顺序为:

  1. 父类(静态变量、静态语句块)
  2. 子类(静态变量、静态语句块)
  3. 父类(实例变量、普通语句块)
  4. 父类(构造函数)
  5. 子类(实例变量、普通语句块)
  6. 子类(构造函数)

7.String与字符串常量池

Strings2="abc"

String s3="abc"+"def"

String s4="abddef"

String s5=s2+"def"

System.out.println(s3==s4) ture 内存地址一样

System.out.println(s4==s5) false s2算引用类型,产生新的内存地址。

7.1String aa = new String(“aa”) 创建了几个字符串?

创建了两个字符串对象,一个是在常量池中的字符串常量"aa",另一个是在堆中的字符串对象,它所代表的字符串内容也是"aa"。常量池中的字符串常量是在编译期就确定的,而堆中的字符串对象是在运行时动态创建的。

8.String、StringBuilder与StringBuffer的区别

String属于不可变类 StringBuffer属于可变类 StringBuilder属于可变类
执行速度 最差 其次 最高
线程安全 线程安全 线程安全 线程不安全
使用场景 少量字符串操作 多线程环境下的大量操作 单线程环境下的大量操作

8.1.String 为什么要设计为不可变类?

(1)字符串常量池的需要:字符串常量池是 Java 堆内存中一个特殊的存储区域, 当创建一个 String 对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象;

(2)允许 String 对象缓存 HashCode:Java 中 String 对象的哈希码被频繁地使用, 比如在 HashMap 等容器中。字符串不变性保证了 hash 码的唯一性,因此可以放心地进行缓存。这也是一种性能优化手段,意味着不必每次都去计算新的哈希码;

(3)String 被许多的 Java 类(库)用来当做参数,例如:网络连接地址 URL、文件路径 path、还有反射机制所需要的 String 参数等, 假若 String 不是固定不变的,将会引起各种安全隐患。

8.2String 字符串修改实现的原理?

当用 String 类型来对字符串进行修改时,其实现方法是首先创建一个 StringBuilder,其次调用 StringBuilder 的 append() 方法,最后调用 StringBuilder 的 toString() 方法把结果返回。

8.3String 类的常用方法都有那些?

indexOf():返回指定字符的索引。

charAt():返回指定索引处的字符。

replace():字符串替换。

trim():去除字符串两端空白。

split():分割字符串,返回一个分割后的字符串数组。

getBytes():返回字符串的 byte 类型数组。

length():返回字符串长度。

toLowerCase():将字符串转成小写字母。

toUpperCase():将字符串转成大写字符。

substring():截取字符串。

equals():字符串比较。

8.4final 修饰 StringBuffer/ StringBuilder后还可以 append 吗?

可以。final关键字表示该变量的引用不能改变,但是可以改变该对象的状态。在Java中,StringBuffer类是可变的,因此使用final修饰的StringBuffer对象仍然可以调用其append()等方法,只是不能改变该对象的引用。即final修饰的变量引用指向的对象是不可变的,但是对象本身的状态是可变的。

9.hashCode与equals的联系与区别

equals()方法用来判断两个对象是否“相同” ;考虑更多,速度慢

hashCode()返回一个int,代表“将对象的内部地址” 速度快,不过可能有相同的

两个对象如果equals()成立,hashCode()一定成立;

如果equals()不成立,hashCode()可能成立;

如果hashCode()成立,equals()不一定成立;

hashCode()不相等,equals()一定不成立;

因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖;

hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

9.1== 和 equals 的区别?

= =:如果比较的对象是基本数据类型,则比较的是数值是否相等;如果比较的是引用数据类型,则比较的是对象的地址值是否相等。

equals 方法:用来比较两个对象的内容是否相等。注意:equals 方法不能用于比较基本数据类型的变量。如果没有对 equals 方法进行重写,则比较的是引用类型的变量所指向的对象的地址(很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等)。

10.Java IO中有几种类型的流

输入流 输出流
字节流 InputStream\FileInputStream\BufferedInputStream OutputStream\FileOutputStream\BufferedOutputStream
字符流 Reader\FileReader\InputStreamReader\BufferedReader Writer\FileWriter\OutputStreamWriter\BufferedWriter

10.1字节流和字符流有什么区别?

字节流按 8 位传输,以字节为单位输入输出数据,字符流按 16 位传输,以字符为单位输入输出数据。

但是不管文件读写还是网络发送接收,信息的最小存储单元都是字节。

11.利用IO实现文件复制

public static void main(String[] args) {
    File source = new File("d:/demo/a/qwr.txt");
    File target = new File("d:/demo/target");
    InputStream input = null;
    OutputStream output= null;
    try {
        input = new FileInputStream(source);
        output = new FileOutputStream(target);
        byte [] buf=new byte[1024];
        int byteRead;
        while ((byteRead=input.read(buf))!=-1){
            output.write(buf,0,byteRead);
        }
    } catch (NullPointerException |IOException e) {
        e.printStackTrace();
    }finally {
        try {
            input.close();
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

2.FileChannel实现文件复制

3.Commons IO组件实现文件复制

12.请实现对象浅复制与深复制(深克隆与浅克隆)

如何实现对象的克隆?

(1)实现 Cloneable 接口并重写 Object 类中的 clone() 方法;

(2)实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆。

浅复制:只对对象及变量值进行复制,引用对象地址不变。

public class Dancer implements Cloneable ,Serializable{
    private String name;
    private Dancer partner;
    public Dancer deepClone() throws IOException, ClassNotFoundException {
        //序列化,将内存中的对象序列化为字节数组
        ByteArrayOutputStream bos=new ByteArrayOutputStream();//创建一个字节数组输出流,将对象序列化后写入该流。
        ObjectOutputStream oos=new ObjectOutputStream(bos);//创建一个对象输出流,将对象序列化后写入字节数组输出流bos中。
        oos.writeObject(this);//将当前对象写入对象输出流中,实现对象序列化。
        //反序列化,将字节数组转为对象,同时完成深复制的任务
        ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois=new ObjectInputStream(bis);// 创建一个对象输入流,从字节数组输入流中读取序列化后的对象。
       return (Dancer) ois.readObject();//将读取到的对象转换成Dancer类型并返回。
    }
    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
        Dancer d1 = new Dancer();
        d1.setName("lium");
        Dancer d2 = new Dancer();
        d2.setName("mxx");
        d1.setPartner(d2);
        System.out.println(d1.hashCode());
        System.out.println("partner" + d2.hashCode());
        //浅复制
        Dancer shallow = (Dancer) d1.clone();
        System.out.println("浅复制" + shallow.getPartner().hashCode());
        //深复制
        Dancer deep=(Dancer) d1.deepClone();
        System.out.println("深复制"+deep.getPartner().hashCode());}
    public String getName() { return name;}
    public void setName(String name) { this.name = name;}
    public Dancer getPartner() { return partner;}
    public void setPartner(Dancer partner) {this.partner = partner;}
}
控制台:
    partner1956725890
浅复制1956725890
深复制764977973

因为浅复制只复制了对象的引用,即d1和浅复制对象shallow持有的partners引用指向的是同一个对象d2,所以它们的hashCode值相等。

深复制:不仅对象及变量值进行复制,引用对象也进行复制。

13.Object类有哪些方法?详细说明

Object类是Java中所有类的父类,它提供了一些通用的方法,如下所示:

  1. equals(Object obj):比较当前对象和指定对象是否相等。所有的Java类都继承了equals()方法,该方法默认实现是比较对象引用是否相等,如果需要比较对象的内容,需要重写该方法。
  2. hashCode():返回当前对象的哈希码。哈希码是一个整数,用来表示对象的内存地址,可以用来进行快速查找。
  3. toString():返回当前对象的字符串表示。该方法返回一个字符串,用来描述对象的状态。
  4. getClass():返回当前对象的类。该方法返回一个Class对象,表示当前对象所属的类。
  5. notify():唤醒一个正在等待该对象锁的线程。该方法用于多线程编程中,在同步代码块中调用notify()方法可以唤醒一个正在等待该对象锁的线程。
  6. notifyAll():唤醒所有正在等待该对象锁的线程。该方法用于多线程编程中,在同步代码块中调用notifyAll()方法可以唤醒所有正在等待该对象锁的线程。
  7. wait():让当前线程等待,直到其他线程唤醒它。该方法用于多线程编程中,调用wait()方法可以使当前线程等待,直到其他线程调用notify()或notifyAll()方法唤醒它。
  8. clone():创建并返回当前对象的副本。该方法将当前对象复制一份,并返回复制后的对象。
  9. finalize():当垃圾回收器决定回收该对象时,会调用该方法进行清理处理。该方法可以被子类重写以实现自己的清理逻辑。

14.解释下什么是面向对象?面向对象和面向过程的区别?

面向对象OOP是一种基于面向过程POP的编程思想,是向现实世界模型的自然延伸,这是一种“万物皆对象”的编程思想。由执行者变为指挥者,在现实生活中的任何物体都可以归为一类事物,而每一个个体都是一类事物的实例。面向对象的编程是以对象为中心,以消息为驱动。

区别:
(1)编程思路不同:面向过程以实现功能的函数开发为主,而面向对象要首先抽象出类、属性及其方法,然后通过实例化类、执行方法来完成功能。

(2)封装性:都具有封装性,但是面向过程是封装的是功能,而面向对象封装的是数据和功能。

(3)面向对象具有继承性和多态性,而面向过程没有继承性和多态性,所以面向对象优势很明显

15.Java 中创建对象的几种方式?

1、使用 new 关键字;

2、使用 Class 类的 newInstance 方法,该方法调用无参的构造器创建对象(反射):Class.forName.newInstance();

3、使用 clone() 方法;

4、反序列化,比如调用 ObjectInputStream 类的 readObject() 方法。

16.Integer 和 int 的比较

1、由于 Integer 变量实际上是对一个 Integer 对象的引用,所以两个通过 new 生成的 Integer 变量永远是不相等的,因为其内存地址是不同的;

2、Integer 变量和 int 变量比较时,只要两个变量的值是相等的,则结果为 true。因为包装类 Integer 和基本数据类型 int 类型进行比较时,Java 会自动拆包装类为 int,然后进行比较,实际上就是两个 int 型变量在进行比较;

3、非 new 生成的 Integer 变量和 new Integer() 生成的变量进行比较时,结果为 false。因为非 new 生成的 Integer 变量指向的是 Java 常量池中的对象,而 new Integer() 生成的变量指向堆中新建的对象,两者在内存中的地址不同;

4、对于两个非 new 生成的 Integer 对象进行比较时,如果两个变量的值在区间 [-128, 127] 之间,则比较结果为 true,否则为 false。

  • 包装类的缓存:

Boolean:全部缓存

Byte:全部缓存(Byte 的数值范围是 -128~127)

Character:<= 127 缓存

Short:-128 — 127 缓存

Long:-128 — 127 缓存

Integer:-128 — 127 缓存

Float:没有缓存

Doulbe:没有缓存

17.switch 语句能否作用在 byte 上,能否作用在 long 上,能否作用在 String 上?

由于,byte、short、char 都可以隐式转换为 int,所以,这些类型以及这些类型的包装类型也都是可以的。而 long 和 String 类型都不符合 switch 的语法规定,并且不能被隐式的转换为 int 类型,所以,它们不能作用于 switch 语句中。不过,需要注意的是在 JDK1.7 版本之后 switch 就可以作用在 String 上了。

18、final、finally、finalize 的区别

final:用于声明属性、方法和类,分别表示属性不可变、方法不可覆盖、被其修饰的类不可继承;

finally:异常处理语句结构的一部分,表示总是执行;

finallize:Object类的一个方法,在垃圾回收时会调用被回收对象的finalize

19.& 和 && 的区别?

&&:有短路功能,当第一个表达式的值为 false 的时候,则不再计算第二个表达式;

&:不管第一个表达式结果是否为 true,第二个都会执行。除此之外,& 还可以用作位运算符:当 & 两边的表达式不是 Boolean 类型的时候,& 表示按位操作。

20.Java 中的参数传递时传值呢?还是传引用?

Java 的参数是以值传递的形式传入方法中,而不是引用传递。

当传递方法参数类型为基本数据类型(数字以及布尔值)时,一个方法是不可能修改一个基本数据类型的参数。

当传递方法参数类型为引用数据类型时,一个方法将修改一个引用数据类型的参数所指向对象的值。即使 Java 函数在传递引用数据类型时,也只是拷贝了引用的值罢了,之所以能修改引用数据是因为它们同时指向了一个对象,但这仍然是按值调用而不是引用调用。

21.什么是 Java 的序列化,如何实现 Java 的序列化?

对象序列化是一个用于将对象状态转换为字节流的过程,可以将其保存到磁盘文件中或通过网络发送到任何其他程序。从字节流创建对象的相反的过程称为反序列化。而创建的字节流是与平台无关的,在一个平台上序列化的对象可以在不同的平台上反序列化。序列化是为了解决在对象流进行读写操作时所引发的问题。

序列化的实现:将需要被序列化的类实现 Serializable 接口,该接口没有需要实现的方法,只是用于标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream 对象,接着使用 ObjectOutputStream 对象的 writeObject(Object obj) 方法可以将参数为 obj 的对象写出,要恢复的话则使用输入流。

21.1什么情况下需要序列化?

(1)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;

(2)当你想用套接字在网络上传送对象的时候;

(3)当你想通过 RMI 传输对象的时候。

RMI(Remote Method Invocation)是Java中的一种远程过程调用机制,它允许一个Java虚拟机中的对象调用另一个Java虚拟机中的对象,并且像本地方法一样进行调用。

RMI通过Java的序列化机制实现了远程对象之间的通信,它支持将Java对象序列化并传输到另一个Java虚拟机中,并在另一个虚拟机中重建对象以实现远程方法调用(Remote Method Invocation)。RMI还提供了一组可扩展的机制,使得程序员可以自定义通信协议和远程对象管理。

在RMI中,客户端可以像调用本地对象一样调用远程对象,远程对象不必关心调用的是本地还是远程方法,因为程序员可以使用相同的接口定义本地和远程对象。RMI使得在分布式系统中使用Java程序变得更加容易和方便。

22.Java 的泛型是如何工作的 ? 什么是类型擦除 ?

泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如:List<String> 在运行时仅用一个 List 来表示。这样做的目的,是确保能和 Java 5 之前的版本开发二进制类库进行兼容。

类型擦除:泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 < T > 则会被转译成普通的 Object 类型,如果指定了上限如 < T extends String > 则类型参数就被替换成类型上限。

22.1什么是泛型中的限定通配符和非限定通配符 ?

限定通配符对类型进行了限制。有两种限定通配符,一种是< ? extends T > 它通过确保类型必须是 T 的子类来设定类型的上界,另一种是< ? super T >它通过确保类型必须是 T 的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。

另一方面 < ? > 表示了非限定通配符,因为 < ? > 可以用任意类型来替代。

23.Java 中的反射是什么意思?有哪些应用场景?

每个类都有一个 Class 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。类加载相当于 Class 对象的加载,类在第一次使用时才动态加载到 JVM 中。也可以使用 Class.forName(“com.mysql.jdbc.Driver”) 这种方式来控制类的加载,该方法会返回一个 Class 对象。

反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类:

(1)Field :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;

(2)Method :可以使用 invoke() 方法调用与 Method 对象关联的方法;

(3)Constructor :可以用 Constructor 创建新的对象。

应用举例:工厂模式,使用反射机制,根据全限定类名获得某个类的 Class 实例。

24.反射的优缺点?

优点:

运行期类型的判断,class.forName() 动态加载类,提高代码的灵活度;

缺点:

尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。在我们使用反射技术时,下面几条内容应该牢记于心。

(1)性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。

(2)安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。

(3)内部暴露:由于反射允许代码执行一些在正常情况下不被允许的操作(比如:访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

25.Java 中的动态代理是什么?有哪些应用?

动态代理:当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新功能。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强。

动态代理的应用:Spring 的 AOP 、加事务、加权限、加日志。

25.1怎么实现动态代理?

Java 中,实现动态代理有两种方式:
1、JDK 动态代理:java.lang.reflect 包中的 Proxy 类和 InvocationHandler 接口提供了生成动态代理类的能力。
2、Cglib 动态代理:Cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。

JDK 动态代理和 Cglib 动态代理的区别:
1、JDK 的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。
2、cglib 代理的对象则无需实现接口,达到代理类无侵入。(如果想代理没有实现接口的类,就可以使用 CGLIB实现。)

26.static 关键字的作用?

(1)静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份;

(2)静态方法:静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字;

(3)静态语句块:静态语句块在类初始化时运行一次;

(4)静态内部类:非静态内部类依赖于外部类的实例,而静态内部类不需要。静态内部类不能访问外部类的非静态的变量和方法;

(5)初始化顺序:静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。

27.super 关键字的作用?

(1)访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。

(2)访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。

(3)this() 和 super() 不能同时出现在一个构造函数里面,因为 this 必然会调用其它的构造函数,其它的构造函数必然也会有 super 语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。

transient 关键字的作用?

对于不想进行序列化的变量,使用 transient 关键字修饰。

transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化。当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。

28.BIO、NIO、AIO 有什么区别?

BIO:Block IO,同步阻塞式 IO(传统 IO),服务实现模式为一个连接对应一个线程,即客户端发送一个连接,服务端要有一个线程来处理。如果连接多了,线程数量不够,就只能等待,即会发生阻塞。它的特点是模式简单使用方便,并发处理能力低。
NIO:New IO,同步非阻塞 IO(传统 IO 的升级),客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。一个线程可以处理多个连接,即客户端发送的连接都会注册到多路复用器上,然后进行轮询连接,有I/O请求就处理。
AIO:Asynchronous IO,异步非阻塞IO(又升级),基于事件和回调机制。引入了异步通道,采用的是proactor模式,特点是:有效的请求才启动线程,先有操作系统完成再通知服务端。

应用场景:
BIO:适用连接数目比较小且固定的架构,对服务器要求比较高,并发局限于应用中
NIO:适用连接数目多且连接比较短的架构,如:聊天服务器,弹幕系统等,编程比较复杂
AIO:适用连接数目多且连接长的架构,如相册服务器

29.异常:异常分类 + 记住几个常见异常

Exception的分类:
1.Checked Exception(非Runtime Exception)
2.Runtime Exception

常见异常类型:
IOException:当发生某种I/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类。

IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数。

NullPointerException:当应用程序试图访问空对象时,则抛出该异常。

SQLException:提供关于数据库访问错误或其他错误信息的异常。

IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。

NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。

FileNotFoundException:当试图打开指定路径名表示的文件失败时,抛出此异常。

ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。

ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常。

ArithmeticException:当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。

NoSuchMethodException:无法找到某一特定方法时,抛出该异常。

SecurityException:由安全管理器抛出的异常,指示存在安全侵犯。

RuntimeExceptionRuntimeException:是那些可能在Java虚拟机正常运行期间抛出的异常的超类。

30.java的4种引用

  1. 强引用(Strong Reference):指向对象的引用,只要存在强引用,对象就不会被回收。
  2. 软引用(Soft Reference):用于描述一些有用但非必需的对象,当系统内存充足时,软引用对象不会被回收,当系统内存不足时,软引用对象可能会被回收。
  3. 弱引用(Weak Reference):比软引用还要弱,当系统内存不足时,弱引用对象会被立即回收。
  4. 虚引用(Phantom Reference):最弱的一种引用,无法通过虚引用访问对象,其唯一作用就是在对象被回收时,发出一个通知。为了使用虚引用,必须与引用队列(ReferenceQueue)一起使用,当虚引用对象被回收后,Java虚拟机会把它插入到与之关联的引用队列中。

31.sleep()和wait()的区别?

sleep()和wait()方法虽然都可以暂停线程的执行,但是它们的作用和用法是有所不同的:

  1. sleep()方法属于Thread类中的方法,它在指定的时间内暂停线程的执行,但不会释放对象锁。
  2. wait()方法属于Object类中的方法,它暂停当前线程的执行,并释放对象锁,使得其他线程可以获取该对象锁并执行相应的同步代码块。
  3. sleep()方法必须指定暂停的时间长度,而wait()方法可以指定暂停的时间,也可以不指定,在不指定时间的情况下,需要使用notify()或notifyAll()方法来唤醒等待的线程。
  4. sleep()方法可以在任何地方使用,wait()方法必须在同步的代码块中使用。

总之,sleep()方法主要用于控制线程的执行顺序,wait()方法主要用于多线程之间的通信和协作,它们都是实现线程控制的重要方法。

对象锁是一种线程同步机制,主要用于控制多个线程对同一对象的访问。在Java中,每一个对象都有一个内置锁(也称为监视器锁),线程在执行同步方法或同步代码块时,会获取该对象的锁,并在执行结束后释放锁。如果一个线程想要访问对象的同步方法或同步代码块,但持有锁的线程还未释放锁,则该线程就会被阻塞,直到获取到锁才能继续执行。

对象锁的作用是保证多个线程访问共享资源时的同步和互斥。通过使用对象锁,可以避免多个线程同时访问共享资源而导致的并发问题,保证程序的正确性和稳定性。同时,由于对象锁是基于对象的,因此可以精细地控制不同对象的访问权限,提高程序的灵活性和可控性。

32.throw和throws的区别

  1. throw用于抛出一个异常对象,它通常用于方法中发现了某种错误或异常情况并且无法处理时,将该异常对象抛出给调用方法的代码段处理。
  2. throws通常用于方法的声明处,用于指定该方法可能会抛出的异常类型,以便调用者在调用该方法时进行相应的异常处理或转抛。

例如,一个方法可能会抛出多种异常,可以使用throws关键字声明:

public void readFromFile() throws FileNotFoundException, IOException {// …}

而如果在方法内部发现了某种异常情况,可以使用throw关键字抛出一个异常对象,例如:

if (file.exists()) {// …} else {throw new FileNotFoundException(“File not found”);}

因此,throw和throws都是Java中用于异常处理的关键字,它们的使用可以提高程序的健壮性和可维护性。需要注意的是,在使用它们时,需要仔细处理异常的类型、范围和抛出的条件等,从而保证程序的正确性和稳定性。

33.try catch会影响性能吗?为什么抛出异常的时候会影响性能?

try catch语句可以影响程序的性能,因为它会增加程序的额外开销。当程序执行到try catch语句时,虚拟机需要保存当前的现场,然后跳转到相应的catch块处理异常,处理完成后再恢复现场继续执行。这一过程需要消耗额外的时间和资源,因此会影响程序的性能。

抛出异常也会影响性能,因为抛出异常操作也需要消耗额外的时间和资源。当Java程序抛出异常时,它需要将异常的类型和相关信息封装成一个异常对象,并将该对象传递给调用者,这个过程也需要消耗时间和资源。因此,在编写程序时需要注意异常的使用和处理方式,避免不必要的性能损耗和影响。通常情况下,只有在必要的情况下才应该抛出异常,而对于预计可能会出现的异常情况,应该使用条件语句等方式进行处理。