-
前言
❗表示必掌握,❔表示基本不会问 -
更新
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 支持多继承和多实现吗
不支持多继承,支持多实现。
构造函数和一般函数有什么区别
-
定义格式不同
-
构造函数是在对象创建时,就被调用,用于初始化,而且初始化动作只执行一次。 一般函数,是对象创建后,需要调用才执行,可以被调用多次。
Java 怎么创建对象
-
使用 new 关键字
-
使用 newInstance 方法
Java 反射中有一个 newInstance() 方法,可以创建对象,步骤如下:获取要创建的类的 Class 对象。如果只需要调用这个类的访问权限为 public 无参构造器,直接使用 Class 类的实例方法 newInstance()。获取 Class 对象的构造器对象,通过调用 Class 类的实例方法 getDeclaredConstractors() 来获取构造器对象的数组。(获取所有构造器,无视访问权限的限制,数组顺序按照代码中的顺序决定) 如果调用的构造器是 private 的,需要调用 Constractor 类的父类 AccessibleObject 类的实例方法 setAccessible(true) 来打破访问限制。使用 Constractor 类的实例方法 newInstance()。获取 Class 对象的方法有 3 个,此处不多赘述。获取 Constractor 对象的方法有 4 个,此处不多赘述。
-
使用 clone() 方法
Object 类是所有类的直接或间接父类,Object 类中提供了 实例方法 native(),在给定对象的基础上,创建一个完全相同的对象。步骤如下:想要使用 clone() 方法创建对象的类,实现 Cloneable 接口。在类的内部,重写 Object 类的 clone() 方法。备注:没有实现 Cloneable 接口,会抛出 CloneNotSupportedException 异常。Object 类提供的 clone() 方法,访问权限是 protected,所以如果不重写 clone() 方法,是没有权限调用的。Object 类的 clone() 方法,是 native 方法。
-
❔使用反序列化的 readObject() 方法
这个方法一共分两步:将对象序列化,存储到一个文件中。从文件中反序列化,得到类对象。
序列化:想要序列化对象的类,实现 Serializable 接口。使用文件输出流 FileOutputStream 创建存储序列化之后对象的文件。使用对象输出流 ObjectOutputStream 的实例方法 writeObject(obj)。判断类中是否存在,名为 writeReplace(),返回类型为 Object 的方法。若有,写入这个方法的返回值;否则,写入 obj 对象。
反序列化:使用文件输入流 FileInputStream 找到存储序列化对象的文件。使用对象输入流 ObjectInputStream 的实例方法 readObject()。判断类中是否存在,名为 readResolve(),返回类型为 Object 的方法,若有读取这个对象;否则,反序列化文件中的对象流。
备注:在类中,writeReplace() 和 readResoleve() 是两个非常特殊的方法,其特征签名需要严格限制:方法名限定,参数个数限定为 0,返回类型必须是 Object,不能为 Object 的子类,但是可以抛出不同的异常。访问修饰符没有限制,但一般推荐为 private,防止误操作。其特殊的地方还在于将其设为 private 方法,没有其他方法调用的情况下,编译器不会发出警告。
Java 中的异常
1 | Throwable ->Exception -> ClassNotFoundException |
❗抽象类和接口的区别
-
定义:接口定义:interface;抽象类:abstrat class;
-
抽象类可以定义构造方法供子类调用,接口不可以;
-
接口的所有方法都是抽象方法,所有属性都是常量:static final 类型;而抽象类中可以有不是抽象的方法和不是常量的属性;
-
抽象类只能单继承,而接口之间可以多继承,接口之间的继承用 extexds,类实现接口用 implements.
== 和 equals()
-
==
:
-
如果比较的对象是基本数据类型,则比较的是其存储的值是否相等;
-
如果比较的是引用数据类型,则比较的是所指向对象的地址值是否相等(是否是同一个对象)。
-
equals
:
-
如果没有对 equals 方法进行重写,则相当于
==
,比较的是引用类型的变量所指向的对象的地址值。 -
一般情况下,类会重写 equals 方法用来比较两个对象的内容是否相等。比如 String 类中的 equals() 是被重写了,比较的是对象的值。
为什么重写 equals 方法,还必须要重写 hashcode 方法
-
保证是同一个对象,如果重写了 equals 方法,而没有重写 hashcode 方法,会出现 equals 相等的对象,但是 hashcode 不相等的情况,重写 hashcode 方法就是为了避免这种情况的出现。
-
使用 hashcode 方法提前校验,可以避免每一次比对都调用 equals 方法,提高效率
重写后:
-
equals() 相等的两个对象,hashcode() 一定相等;
-
hashcode() 不等,一定能推出 equals() 也不等;
-
hashcode() 相等,equals() 可能相等,也可能不等。
-
所以先进行 hashcode() 判断,不等就不用 equals() 方法了。
-
但 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=实现类(这些都可以在官网的文档里找到)。