面试-基础
Kiml Lv5
  • 前言
    ❗表示必掌握,❔表示基本不会问

  • 更新

1
24.06.03 初始记录

❗Java 的基本数据类型

byte,short,int,long,float,double,char,boolean

面向对象的三大特性

继承、多态、封装

JDK 和 JRE 的区别

JRE:Java 运⾏时环境。它是运⾏已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,Java 命令和其他的⼀些基础构件。但是,它不能⽤于创建新程序。

JDK(Java Development Kit):它是功能⻬全的 Java SDK。它拥有 JRE 所拥有的⼀切,还有编译器(javac)和⼯具(如 javadoc 和 jdb)。它能够创建和编译程序。

简单来说,JDK 包含 JRE,JRE 包含 JVM。

Java 支持多继承和多实现吗

不支持多继承,支持多实现。

构造函数和一般函数有什么区别

  1. 定义格式不同

  2. 构造函数是在对象创建时,就被调用,用于初始化,而且初始化动作只执行一次。 一般函数,是对象创建后,需要调用才执行,可以被调用多次。

Java 怎么创建对象

  1. 使用 new 关键字

  2. 使用 newInstance 方法

Java 反射中有一个 newInstance() 方法,可以创建对象,步骤如下:获取要创建的类的 Class 对象。如果只需要调用这个类的访问权限为 public 无参构造器,直接使用 Class 类的实例方法 newInstance()。获取 Class 对象的构造器对象,通过调用 Class 类的实例方法 getDeclaredConstractors() 来获取构造器对象的数组。(获取所有构造器,无视访问权限的限制,数组顺序按照代码中的顺序决定) 如果调用的构造器是 private 的,需要调用 Constractor 类的父类 AccessibleObject 类的实例方法 setAccessible(true) 来打破访问限制。使用 Constractor 类的实例方法 newInstance()。获取 Class 对象的方法有 3 个,此处不多赘述。获取 Constractor 对象的方法有 4 个,此处不多赘述。

  1. 使用 clone() 方法

Object 类是所有类的直接或间接父类,Object 类中提供了 实例方法 native(),在给定对象的基础上,创建一个完全相同的对象。步骤如下:想要使用 clone() 方法创建对象的类,实现 Cloneable 接口。在类的内部,重写 Object 类的 clone() 方法。备注:没有实现 Cloneable 接口,会抛出 CloneNotSupportedException 异常。Object 类提供的 clone() 方法,访问权限是 protected,所以如果不重写 clone() 方法,是没有权限调用的。Object 类的 clone() 方法,是 native 方法。

  1. ❔使用反序列化的 readObject() 方法

这个方法一共分两步:将对象序列化,存储到一个文件中。从文件中反序列化,得到类对象。

序列化:想要序列化对象的类,实现 Serializable 接口。使用文件输出流 FileOutputStream 创建存储序列化之后对象的文件。使用对象输出流 ObjectOutputStream 的实例方法 writeObject(obj)。判断类中是否存在,名为 writeReplace(),返回类型为 Object 的方法。若有,写入这个方法的返回值;否则,写入 obj 对象。

反序列化:使用文件输入流 FileInputStream 找到存储序列化对象的文件。使用对象输入流 ObjectInputStream 的实例方法 readObject()。判断类中是否存在,名为 readResolve(),返回类型为 Object 的方法,若有读取这个对象;否则,反序列化文件中的对象流。

备注:在类中,writeReplace() 和 readResoleve() 是两个非常特殊的方法,其特征签名需要严格限制:方法名限定,参数个数限定为 0,返回类型必须是 Object,不能为 Object 的子类,但是可以抛出不同的异常。访问修饰符没有限制,但一般推荐为 private,防止误操作。其特殊的地方还在于将其设为 private 方法,没有其他方法调用的情况下,编译器不会发出警告。

Java 中的异常

1
2
3
4
5
6
7
Throwable ->Exception -> ClassNotFoundException
-> IOException
-> RuntimeException -> NullPointerException
-> NumberFormatException
-> IndexOutOfBoundsException
......
->Error

❗抽象类和接口的区别

  1. 定义:接口定义:interface;抽象类:abstrat class;

  2. 抽象类可以定义构造方法供子类调用,接口不可以;

  3. 接口的所有方法都是抽象方法,所有属性都是常量:static final 类型;而抽象类中可以有不是抽象的方法和不是常量的属性;

  4. 抽象类只能单继承,而接口之间可以多继承,接口之间的继承用 extexds,类实现接口用 implements.

== 和 equals()

  • ==

  1. 如果比较的对象是基本数据类型,则比较的是其存储的值是否相等;

  2. 如果比较的是引用数据类型,则比较的是所指向对象的地址值是否相等(是否是同一个对象)。

  • equals

  1. 如果没有对 equals 方法进行重写,则相当于 ==,比较的是引用类型的变量所指向的对象的地址值。

  2. 一般情况下,类会重写 equals 方法用来比较两个对象的内容是否相等。比如 String 类中的 equals() 是被重写了,比较的是对象的值。

为什么重写 equals 方法,还必须要重写 hashcode 方法

  1. 保证是同一个对象,如果重写了 equals 方法,而没有重写 hashcode 方法,会出现 equals 相等的对象,但是 hashcode 不相等的情况,重写 hashcode 方法就是为了避免这种情况的出现。

  2. 使用 hashcode 方法提前校验,可以避免每一次比对都调用 equals 方法,提高效率

重写后:

  1. equals() 相等的两个对象,hashcode() 一定相等;

  2. hashcode() 不等,一定能推出 equals() 也不等;

  3. hashcode() 相等,equals() 可能相等,也可能不等。

  4. 所以先进行 hashcode() 判断,不等就不用 equals() 方法了。

  5. 但 equels 是是根据对象的特征进行重写的,有时候特征相同,但 hash 值不同,也不是一个对象。 所以两个都重写才能保障是同一个对象。

深拷贝和浅拷贝

浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址。

深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。

浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。

深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。

Java 中都有哪些引用类型?

强引用:发生 gc 的时候不会被回收。

软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。

弱引用:有用但不是必须的对象,在下一次 GC 时会被回收。

虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。

SPI 介绍

​SPI 全称 Service Provider Interface,是 Java 提供的一套用来被第三方实现或者扩展的 API,它可以用来启用框架扩展和替换组件。

Java 的 SPI 机制就是将一些==类信息写在约定的文件中==,然后由特定的类加载器 ServiceLoader 加载解析文件获取资源。

Java SPI 基于 “接口编程+策略模式+配置文件(约定)”组合实现的动态加载机制。它提供了一种服务发现机制,允许在程序外部动态指定具体实现。

一般情况下是调用方调用实现好的接口,但是当接口存在于调用方这边时,就是 SPI ,由接口调用方确定接口规则,然后由不同的厂商去根据这个规则对这个接口进行实现,从而提供服务。

​以下是 SPI 的一些运用场景:

场景 说明
数据库驱动 数据库驱动加载接口实现类的加载 JDBC 加载不同类型数据库的驱动
日志门面 SLF4J 接口实现类加载 SLF4J 加载不同提供商的日志实现类
Spring Spring 中大量使用了 SPI,比如:对 servlet3.0 规范对 ServletContainerInitializer 的实现、自动类型转换 Type Conversion SPI(Converter SPI、Formatter SPI) 等
Dubbo Dubbo 中也大量使用 SPI 的方式实现框架的扩展,不过它对 Java 提供的原生 SPI 做了封装,允许用户扩展实现 Filter 接口
SpringBoot SpringBoot 基于 SPI 思想实现自动装配
插件扩展 开源框架,想使用别人的插件,扩展某个功能。

ServiceLoader:点击链接跳转链接到 JavaGuide。

面试大白话

Java 中的 SPI 就是你提供一个接口,然后让别人提供实现类。然后将这个实现放在 resources/META-INF/services/ 中。这样 Java 启动的时候就会去扫描这个下面的 jar 包,并把他加载进来。一般来说用在开源框架中的插件扩展,然后比较常见的思想提现就是 Java 的 JDBC 实现,可以根据自己使用的数据库,将对应的 jar 包引用进来。

(Dubbo 中可能问到的问题)Dubbo 中也使用了这个思想: 比如说有一行代码是:

1
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

然后在使用 Protocol 接口时,dubbo 他会去找一个你配置的 Protocol,他就会将你配置的 Protocol 实现类,加载到 jvm 中来,然后实例化对象,就用你的那个 Protocol 实现类就可以了。这个 Protocol 就是用来配置他的网络协议的,然后在源码中可以看到,默认使用的是 dubbo 协议,就是 DubboProtocol。dubbo 里面提供了大量的类似上面的扩展点,就是说,你如果要扩展一个东西,只要自己写个 jar,让你的 consumer 或者是 provider 工程,依赖你的那个 jar,在你的 jar 里指定目录下配置好接口名称对应的文件,里面通过 key=实现类(这些都可以在官网的文档里找到)。

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
访客数 访问量