文章目录
1. 特点
- 简单易学;
- 面向对象(封装,继承,多态);
- 平台无关性( Java 虚拟机实现平台无关性);
- 可靠性;
- 安全性;
- 支持多线程;
- 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便);
- 编译与解释并存。
2. 三大特性: 封装 继承 多态
2.1 封装
封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
2.2 继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。
注意
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
2.3 多态
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
在 Java 中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
3. 基本数据类型
数据类型 | 字节数 | 默认值 | 取值范围 | 包装器类型 |
---|---|---|---|---|
byte(位) | 1字节(8位) | 0 | -27 ~ 27 - 1 | Byte |
short(短整数) | 2字节(16位) | 0 | -215 ~ 215 - 1 | Short |
int(整数) | 4字节(32位) | 0 | -231 ~ 231 - 1 | Integer |
long(长整数) | 8字节(64位) | 0 | -263 ~ 263 - 1 | Long |
float(单精度) | 4字节(32位) | 0.0f | -231 ~ 231 - 1 | Float |
double(双精度) | 8字节(64位) | 0.0d | -263 ~ 263 - 1 | Double |
char(字符) | 2字节(16位) | 空 | 0 ~ 216 - 1 | Character |
boolean(布尔值) | 1字节(8位) | false | true、false | Boolean |
4. Integer 类的缓存机制
Integer 内部有个静态类 IntegerCache,它在初始化时,会缓存默认数值为 -128~127 的 Integer 实例。当使用 valueOf() 方法时,如果创建值在 IntegerCache 的数组区间中,则直接返回该缓存值,否则创建新的 Integer 实例。使用 new 创建的对象是不会复用缓存实例。
实际上不仅仅 Integer 具有缓存机制,Byte、Short、Long、Character都具有缓存机制,这些类都有缓存的范围,其中Byte、Short、Integer、Long 为 -128 到 127,Character 范围为 0 到 127。除了 Integer 可以通过参数 -XX:AutoBoxCacheMax=size
改变范围外,其它的都不行。但 Integer 的缓存上界取指定值与127的最大值并且不超过 Integer 表示范围,而下界不能指定,只能为 -128。
5. 值传递
Java 程序设计语言总是采用按值调用。方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。
注意
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型);
- 一个方法可以改变一个对象参数的状态;
- 一个方法不能让对象参数引用一个新的对象。
6. 自动装箱与自动拆箱
- 自动装箱:将基本类型用它们对应的引用类型包装起来;
- 自动拆箱:将包装类型转换为基本数据类型。
7. 浅拷贝与深拷贝
- 浅拷贝:在堆中创建一个新对象,属性值和原对象相同。基本数据类型拷贝的是值,引用类型进行引用拷贝。
- 深拷贝:在堆中创建一个新对象,并且复制原对象的成员变量。包括基本数据类型和引用类型。
引用拷贝
指向堆内存中同一个对象
浅拷贝
深拷贝
7.1 实现方式
浅拷贝
- 拷贝构造方法。
构造函数参数为该本类。 - 重写Clone()方法。
首先实现Cloneable接口,重写Object类的clone()方法;再调用Object类的clone()方法,返回一个Object实例,即super.clone();
。
深拷贝
- 重写Clone()方法。
首先实现Cloneable接口,重写Object类的clone()方法;重点是为每一层的每一个对象都实现Cloneable接口并重写clone()方法,最后在最顶层的类的重写的clone方法中调用所有的clone()方法即可实现深拷贝。简单的说就是:每一层的每个对象都进行浅拷贝=深拷贝。 - 对象序列化。
将对象序列化为字节序列后,默认会将该对象的整个对象进行序列化,再通过反序列即可实现深拷贝。
8. 接口与抽象类
区别点 | 接口 | 抽象类 |
---|---|---|
方法修饰符 | 默认是 public | 抽象方法可以有 public、protected 和 default (为了被重写不能使用 private 修饰) |
成员变量 | 只能有static、final 变量 | 没限制 |
成员方法 | 所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现) | 可以有非抽象的方法 |
实现方式 | 一个类可以实现多个接口,接口自己本身可以通过 extends 关键字扩展多个接口 | 一个类只能实现一个抽象类 |
设计层面 | 接口是对行为的抽象,是一种行为的规范 | 抽象是对类的抽象,是一种模板设计 |
JDK 7 ~ 9 中 Java 接口概念的变化:
- 在 JDK 7 或更早版本中,接口里面只能有常量变量和抽象方法,这些接口方法必须由选择实现接口的类实现。
- JDK 8 的时候接口可以有默认方法和静态方法功能。
- JDK 9 在接口中引入了私有方法和私有静态方法。
9. 成员变量与局部变量
区别点 | 成员变量 | 局部变量 |
---|---|---|
作用域 | 属于类 | 在方法中定义的变量,或方法的参数 |
修饰符 | public、protected、private、static、final 等修饰符 | 除了final,其他都不能修饰 |
存储方式 | 1. 没有使用 static 修饰,则属于实例的,存于堆中 2. 使用 static 修饰,则属于类,存于元空间(方法区)中 3. 使用 final 修饰,表示常量,存于元空间(方法区)中 | 1. 为基本数据类型,存于虚拟机栈中的栈帧中的局部变量表中 2. 为引用数据类型,虚拟机栈中的栈帧中的局部变量表中存放的是指向堆内存对象的引用或者是指向常量池中的地址 |
生命周期 | 对象的一部分,随着对象的创建而存在 | 随着方法的调用而自动消失 |
自动赋值 | 自动以类型的默认值而赋值(被 final 修饰的成员变量也必须显式地赋值) | 不会 |
10. final 与 static
final
修饰位置 | 特点 |
---|---|
变量 | 1. 基本数据类型,其数值一旦在初始化之后便不能更改 2. 引用类型,则在对其初始化之后便不能再让其指向另一个对象 |
类 | 这个类不能被继承,final 类中的所有成员方法都会被隐式地指定为 final 方法 |
方法 | 1. 把方法锁定,以防任何继承类修改它的含义 2. 效率。在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的 Java 版本已经不需要使用 final 方法进行这些优化了)。 3. 类中所有的 private 方法都隐式地指定为 final。 |
static
修饰位置 | 特点 |
---|---|
成员变量/方法 | 1. 被 static 修饰的成员属于类,被类中所有对象共享,并且可以通过类名直接调用。 2. 被 static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的元空间(方法区)。 3. 该类不管创建多少对象,静态代码块只执行一次。 |
内部类 | 静态内部类与非静态内部类之间存在一个最大的区别:非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类;但是静态内部类却没有。 没有这个引用就意味着: 1. 它的创建是不需要依赖外围类的创建。 2. 它不能使用任何外围类的非static成员变量和方法。 |
导包(用来导入类中的静态资源,1.5之后的新特性) | 格式为:import static 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。 |
11. this 与 super
this
用于引用类的当前实例。此关键字是可选的,这意味着在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。
super
用于从子类访问父类的变量和方法。
注意
- 在构造器中使用 super() 调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。
- this,super不能用在static方法中。被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以,this 和 super 是属于对象范畴的东西,而静态方法是属于类范畴的东西。
12. == 与 equals()
==
判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。基本数据类型比较的是值,引用数据类型比较的是内存地址。
equals()
判断两个对象是否相等。
- 类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过
==
比较这两个对象。 - 类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true。即,认为这两个对象相等。
13. hashCode() 与 equals()
hashCode()
作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
为什么要有 hashCode() ?
以“HashSet 如何检查重复”为例子: 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashCode 值来判断对象加入的位置,同时也会与该位置其他已经加入的对象的 hashCode 值作比较,如果没有相符的 hashCode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashCode 值的对象,这时会调用 equals() 方法来检查 hashCode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals() 的次数,相应就大大提高了执行速度。
hashcode() 与 equals() 的相关规定:
- 如果两个对象相等,则 hashCode() 一定也是相同的;
- 两个对象相等,对两个对象分别调用 equals() 方法都返回 true;
- 两个对象有相同的 hashCode() 值,它们也不一定是相等的。
因此,equals() 方法被覆盖过,则 hashCode() 方法也必须被覆盖。hashCode() 的默认行为是对堆上的对象产生独特值,如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
14. 重载与重写
重载
它发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。
重写
发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。
- 返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;
- 如果父类方法访问修饰符为 private、final 或 static ,则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明;
- 构造方法无法被重写。
重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变 。
区别点 | 重载 | 重写 |
---|---|---|
发生范围 | 同一个类 | 子类中 |
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可修改 | 一定不能修改 |
异常 | 可修改 | 可以减少或删除,一定不能抛出新的或范围更广的异常 |
访问修饰符 | 可修改 | 一定不能做更严格的限制,可降低限制 |
发生阶段 | 编译器 | 运行期 |
15. 内部类
位于类的内部,有静态内部类和非静态内部类(成员内部类,方法内部类,匿名内部类)。
静态内部类
可以定义静态或非静态成员,从技术上来讲,是两个类。非静态内部类保存着指向外围类的引用,静态内部类没有。
成员内部类
定义在类的(非构造器,方法,块)中,可访问外围类的所有成员属性和方法。不能有 staic 成员和方法,需要先创建外围类才能再创建成员内部类。
局部内部类
定义在类的构造器,方法,块中,只能访问类中的 final 成员。
匿名内部类
没有名称,直接用 new 一个类实例。常用在回调时定义。
16. 异常
在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 有两个重要的子类:Error(错误)和 Exception(异常) ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。
Error(错误)
程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java 虚拟机运行错误(VirtualMachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如 Java 虚拟机运行错误(VirtualMachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java 中,错误通过 Error 的子类描述。
Exception(异常)
程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。RuntimeException 异常由 Java 虚拟机抛出。NullPointerException(要访问的变量没有引用任何对象时,抛出该异常)、ArithmeticException(算术运算异常,一个整数除以 0 时,抛出该异常)和 ArrayIndexOutOfBoundsException (下标越界异常)。
注意:异常能被程序本身处理,错误是无法处理。
17. 异常处理
- try 块: 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
- catch 块: 用于处理 try 捕获到的异常。
- finally 块: 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。
在以下 4 种特殊情况下,finally 块不会被执行:
- 在 finally 语句块第一行发生了异常。 因为在其他行,finally 块还是会得到执行;
- 在前面的代码中用了 System.exit(int) 已退出程序。 exit 是带参函数;若该语句在异常语句之后,finally 会执行;
- 程序所在的线程死亡;
- 关闭 CPU。
注意: 当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将会覆盖原始的返回值。
18. 内存溢出与内存泄漏
18.1 内存溢出(Out Of Memory)
程序在申请内存时,没有足够的内存空间供其使用,出现内存溢出
原因
- 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
- 集合类中有对对象的引用,使用完后未清空,使得 JVM 不能回收;
- 代码中存在死循环或循环产生过多重复的对象实体;
- 启动参数内存值设定的过小。
18.2 内存泄露(memory leak)
指程序在申请内存后,无法释放已申请的内存空间,无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光,最终导致内存溢出。
原因
- 静态集合类引起内存泄漏:像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。当集合里面的对象属性被修改后,再调用 remove() 方法时不起作用。
- 监听器:在释放对象的时候却没有去删除这些监听器,增加了内存泄漏的机会。
- 各种连接:比如数据库连接,网络连接和IO连接,除非其显式的调用了其 close() 方法将其连接关闭,否则是不会自动被GC 回收的。
- 内部类和外部模块的引用:内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。
- 单例模式:不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在 JVM 的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏。
19. IO
Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的:
- InputStream/Reader:所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流。
19.1 IO 流分类
- 流的流向:分为输入流和输出流;
- 操作方式:分为字节流和字符流;
- 操作对象:分为节点流和处理流。
操作方式分类
操作对象分类
既然有了字节流,为什么还要有字符流?
问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
19.2 BIO 与 NIO 与 AIO
- BIO (Blocking I/O):同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。
- NIO (Non-blocking/New I/O):NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
- AIO (Asynchronous I/O):AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO 操作本身是同步的。
19.3 NIO
NIO由三个核心部分组成:
- Buffer(缓冲区):缓存数据;
- Channel(管道):传输 Buffer 中的数据;
- Selector(选择器):通过检查一个或多个Channel的状态,判断是否处于可读、可写的状态,来实现单线程管理多个Channel。
19.3.1 Buffer
Buffer类维护了4个核心变量属性来提供关于其所包含的数组的信息:
其中 0 <= mark <= position <= limit <= capacity
- capacity(容量):Buffer能够容纳的数据元素的最大数量,底层是数组,在Buffer创建时被设定;
- position(位置):初始为0。当写数据时,表示当前位置写的位置,最大可为capacity-1;当读数据时,需要将Buffer从写模式切换成读模式,重置为0;
- limit(上界):缓冲区里的数据的总数。当写数据时,表示能容纳多少数据,limit等于capacity;当读数据时,需要将Buffer从写模式切换成读模式,表示能读取多少数据,limit等于写数据时的position;
- mark(标记):一个备忘位置。用于记录上一次读写的位置,包含pos,limit 和 cap 的值。
写模式
capacity和limit值相等,指向最大数量。通过改变position的值,记录写入位置。
读模式
通过 filp() 切换成读模式。limit指向position的位置,position指向头部,读取buffer中的值。
19.3.2 Channel
Channel类似流,但又有些不同:
- 可以从一个Channel中读取数据,再写入到另一个Channel中。而流的读写通常是单向的;
- Channel可以异步地读写;
- Channel中的数据总是要先读到一个Buffer,或者写入一个Buffer。
Channel负责传输数据,不直接操作数据的。操作数据都是通过Buffer来进行操作。
操作:从Channel读取数据后写入到Buffer,再从Buffer读取数据后写入到Channel。
非直接与直接缓冲区
非直接缓冲区需要经过一个 copy 的阶段的(从内核空间copy到用户空间);直接缓冲区不需要经过 copy 阶段,可理解成内存映射文件。
分散读取与聚集写入
- 分散读取(scatter):将一个通道中的数据分散读取并写入到多个缓冲区中;
- 聚集写入(gather):将多个缓冲区中的数据集中写入到一个通道中。
19.3.2 Selector
Selector 是选择器,可理解成多路复用器,能够仅用单线程来处理多个Channel。它通过检查一个或多个Channel的状态,判断是否处于可读、可写的状态,来实现单线程管理多个Channel,也就是可以管理多个网络连接。
20. String,StringBuffer 与 StringBuilder
可变性
String 类中使用 final 关键字修饰字符数组来保存字符串,private final char value[]
,所以 String 对象是不可变的。
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串char[]value
,但是没有用 final 关键字修饰,所以这两种对象都是可变的。
线程安全性
String 中的对象是不可变的,也就可以理解为常量,线程安全。
AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
性能
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
总结
- 操作少量的数据:适用 String
- 单线程操作字符串缓冲区下操作大量数据:适用 StringBuilder
- 多线程操作字符串缓冲区下操作大量数据:适用 StringBuffer
21. 反射
在运行状态中,动态获取信息以及动态调用对象方法。对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性。
21.1 动态代理
代理类在程序运行时创建的代理方式。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。JDK 代理是利用反射实现的。
21.2 SPI
SPI(Service Provider Interface,服务提供者接口)是一种扩展机制,在相应配置文件中定义好某个接口的实现类,然后再根据这个接口去这个配置文件中反射加载这个实例类并实例化。
JDBC驱动加载案例:利用Java的SPI机制,我们可以根据不同的数据库厂商来引入不同的JDBC驱动包。
22. 注解
@Target 定义Annotation能够被应用于源码的哪些位置:
- ElementType.TYPE:类或接口
- ElementType.FIELD:字段
- ElementType.METHOD:方法
- ElementType.CONSTRUCTOR:构造方法
- ElementType.PARAMETER:方法参数
@Retention 定义了Annotation的生命周期:
- RetentionPolicy.SOURCE:仅编译期
- RetentionPolicy.CLASS:仅class文件
- RetentionPolicy.RUNTIME:运行期,可以在运行期通过反射读取RUNTIME类型的注解,否则运行期无法读取到该注解。
@Target({
ElementType.TYPE,
ElementType.CONSTRUCTOR,
ElementType.METHOD,
ElementType.FIELD,
ElementType.PARAMETER
})
@Retention(RetentionPolicy.RUNTIME)
public @interface Alias {
String name() default "";
String value() default "";
String comment() default "";
}
23. JDK8 变化
- JVM 内存模型变化,去掉方法区改为元空间,方法区中运行时常量池改为放在堆中;
- HashMap 数据结构 数组+链表 -> 数据+链表/红黑树;
- HashMap 头插改尾插;
- ConcurrentHashMap 数据结构 segment+hashentry -> 数据+链表/红黑树 synchronized +cas;
- 接口可以有默认方法和静态方法功能
- Lambda 表达式;
- Stream API ;
- Date Time API:LocalDate/LocalTime 和 LocalDateTime等。
参考:
Java基础
应届生/社招面试最爱问的几道Java基础问题
Integer类的缓存机制
什么是Java深浅拷贝?
JDK 7 ~ 9 中 Java 接口概念的变化
final,static,this,super 关键字总结
Java hashCode() 和 equals()的若干问题解答
java内存泄漏与内存溢出
Java NIO Tutorial
Java NIO Tutorial 翻译
如何学习Java的NIO?
Java NIO之Selector(选择器)