Java

JavaSE

环境

JDK/JRE/JVM

JDK:Java Development Kit,Java语言软件开发工具包

JRE:Java Runtime Environment,Java运行环境

JVM:Java Virtual Machine,Java虚拟机

JDK = JRE + Java开发工具集

JRE = JVM + JavaEE标准类库

包含关系:JDK { JRE (JVM,JavaEE ) }

开发工具集

javac.exe、java.exe、javadoc.exe

运行过程:

  • .java文件 (源文件)→ javac.exe (编译)→ .class文件(字节码文件)→ java.exe(运行)→ 结果
  • 编译完源程序后,生成一个或多个字节码文件,然后使用JVM中的类的家在其和解释器对生成的字节码文件进行解释运行,需要将字节码文件对应类加载到内存中,涉及到内存解析

字节码文件中不包含注释内容

path变量

JAVA_HOME = bin的上一层目录

path = %JAVA_HOME%\bin

基础

文档注释:

  • /**
    */
  • 注释内容可以被javadoc解析,生成网页形式的说明文档

API:application programming interface

  • 应用程序编程接口(Java提供的一系列类库)

文件:一个源文件中可以声明多个class,只能有一个类声明为public(与文件名同名的类)

程序的入口:main()方法,格式是固定的

  • public static void main( )
  • 有多少类,编译后就生成多少个字节码文件。字节码文件名与类名一一对应

应用程序 = 算法 + 数据结构

编码/解码

编码:将字符串转换为字节数据

解码:编码的逆过程

常见编码表:

  • ASCII:美国标准信息交换码
    • 用一个字节的7位可以表示
  • ISO8859-1:拉丁码表/欧洲码表
    • 用一个字节的8位表示
  • GB2312:中国的中文编码表
    • 最多两个字节编码所有字符
  • GBK:中国的中文编码表升级,融合了更多的中文文字符号
    • 最多两个字节编码
  • Unicode:国际标准码,融合了目前人类使用的所有字符
    • 为每个字符分配唯一的字符码
    • 所有的文字都用两个字节来表示
  • UTF-8:变长的编码方式
    • 可用1-4个字节来表示一个字符

命名规范

包名:多单词组成时,所有字母都小写

  • xxxyyyzzz

类名,接口名:多单词组成时,首字母都大写

  • XxxYyyZzz

变量名,方法名:多单词组成时,第一个单词首字母小写,其余单词首字母大写

  • xxxYyyZzz

常量名:所有首字母都大写,多单词组成时,每个单词用下划线连接

  • XXX_YYY_ZZZ

可用特殊符号 _ $

Java采用unicode字符集,标识符可以用中文

变量使用时先得声明和赋值

取消转译

\ ,eg:\n,\“

变量分类

按数据类型

基本数据类型(primitive type):

  • 数值型:
    • 整数类型:
      • byte:1字节,-128-127(2^8~2^8-1)补码
      • short:2字节
      • int:4字节(21亿),常用
      • long:8字节
        • 赋值时以“L”或“l”结尾(否则会识别为int型)
    • 浮点数型:
      • float:单精度,4字节,表述范围比long大
        • 赋值时以“F”或“f”结尾
      • double:双精度,8字节,常用
  • 字符型(char):一个字符(1~2字节),声明时需要一对单引号
    • 识别转译字符
    • 可使用unicode编码,eg:\u0123
  • 布尔型(boolean):只有“true”或“false”

引用数据类型(reference type):

  • 类(class)(字符串是类类型的)
  • 接口(interface)
  • 数组([array])

基本数据类型运算规则

自动类型提升:

  • byte / short / char➡️int➡️long➡️float➡️double
  • 表示数范围小的向大的靠拢

强制类型转换:

  • 自动类型提升运算的逆运算
  • 需要强转符( )

整型常量默认为int型,浮点型常量默认为double型

进制

二进制以 0b 或 0B开头

八进制以 0 或 0开头

十六进制以 0x或 0X开头

计算机底层都以补码的方式存储数据

保留字

goto

const

标识符命名规则

特殊符_ &

数字不可以开头

不可以使用关键字和保留字(可以包含)

严格区分大小写,长度无限制

标识符不能包含空格

运算符

%取模,eg:7 % 5 ➡️ 2

运算结果的正负与被模数相同

++X,先运算后取值,eg:a = 2; b = ++a ➡️ a = 3; b = 3

X++,先取值后运算,eg:a = 2; b = a++ ➡️ a = 3; b = 2

1
2
3
4
5
6
7
//先运算后赋值
double result2 = int12 / int5 = 2.0
//括号里先算,变成浮点数
double result2 = int12 / (int5 + 0.0) = 2.0
//强制转换
double result2 = int12 / (double)int5 = 2.0
//X++ 自增1不会改变原有数据类型

赋值运算符

+=,-=,*=,/=,&=不会改变原有数值类型

比较运算符的结果都为boolean型

< < >= <= 只能使用在数值类型的数据之间

== != 可以使用在其他引用类型变量之间

对于引用数据类型来说,比较的是它们的地址值是否相同

1
2
3
4
5
6
7
i1 = 10;
i1 += (i1++) + (++i1);
//i1 = i1 + (i1++) + (++i1);
//10 + 10 + (++i1); i1=11
//10 + 10 + 12
System.out.println("i1:" + i1);
//32

逻辑运算符

&逻辑与

&&短路与a&&b时,a为false,b不进行运算

|逻辑或

||短路或a&&b时,a为true,b不进行运算

!逻辑非

^逻辑异或a与b不一样为true

x++==2先比较后运算

位运算符

<<左移:

  • 3<<2 = 12 ➡️ 011<<2 = 01100 = 12

‘>>’右移:

  • 3>>1 = 1 ➡️ 011>>1 = 01 = 1
  • 整型变量,左移1位乘2,右移1位除2

‘>>>>’无符号右移:

  • 被移位二进制最高位空缺都拿0补
  • &与运算eg: 6 & 3 = 2 ➡️ 110 & 011 = 010 = 2
  • |或运算eg: 6|3 = 7 ➡️ 110 | 011 = 111 = 7
  • ^异或运算eg: 6 ^ 3 = 5 ➡️ 110 ^ 011 = 101 = 5
  • ~取反运算eg: ~6 = -7 ➡️ ~0110 = 1001 = -0111 = -7
1
2
3
4
5
6
7
8
m = k ^ n = (m ^ n) ^ n
eg:
m = 13; n = 5
00001101
m^n00000101
k=00001000k = 8
k^n00000101
m=00001101

三元运算符

结果为boolean

类似if

(条件表达式)? 表达式1 : 表达式2

表达式1和表达式2的要求是一致的(数值,字符串。。。),如果不一致会自动类型提升

都可改写成if else(if else 不一定能改写成三元运算符)

效率比if else高

随机数

int xxx = (int)(Math.random() [0.0, 1.0)

公式:[a, b] : (int)(Math.random() * (b - a + 1) + a

流程控制

分支结构

if-else:语句只有一行时可省略大括号

switch-case:根据switch表达式中的值

  • 依次匹配各个case中的常量
  • 匹配成功则进入相应case结构执行语句
  • 如没遇到break;则仍然向下执行其他case至末尾结束为止
  • 表达式只short/int/枚举类型/String
  • case后只能跟常量
  • break是可选的
  • default是可选的且位置不固定
  • case的执行语句相同可以合并
  • 优先使用

循环结构

for:循环条件是boolean类型的

  • 增强for循环:for (数组/集合元素类型 局部变量 : 数组/集合对象) {}

while:while和for能互相转换

无限循环for(;;)或while(true)

do while:至少会执行一次循环体

  • break结束当前循环
  • 可以使用标识跳出指定循环
  • continue结束当次循环
  • 可以使用标识跳出指定循环的当次循环

时间

System.currentTimeMillis();

获取当前时间到1970/01/01-00:00:00的长度(ms)

数组

有序可重复的

多个相同类型数据按一定顺序排列的集合

属于引用型数据变量

创建数组对象会在内存中开辟一整块连续的空间

长度一旦确定无法更改

一旦定义好,元素的类型也就确定了

角标从0开始(SQL表从1开始)

对于添加/删除/插入等操作效率低

一维数组

静态初始化:new一次就新建一个数组

  • 数组初始化和数组元素赋值操作同时进行

  • int[] ids = new int[]{1001,1002,1003,1004};
    
    1
    2
    3
    4
    5

    动态初始化:数组初始化和数组元素赋值操作分开进行

    ```java
    String[] names = new String[5];

字符索引:charAt()

获取字符串长度:length

默认初始化值:

  • 整型:0
  • 浮点型:0.0
  • char型:0(不是’0’)
  • boolean型:false(false:0,true:1)
  • 引用数据类型:null(不是“null”)
  • (引用类型存放数据要么是null,要么是地址值)

array1 = array2;

  • 赋值array1变量为array2
  • 它们使用一个数组
  • 不是复制数组

二维数组

由数组构成的数组(第一个是行,第二个是列)

静态初始化:

1
int[][] array = new int[][] {{1,2,3},{4,5},{6,7,8}}

动态初始化1:

1
String[][] arr2 = new String[3][2]

动态初始化2:

1
2
String[][] arr2 = new String[3][];arr3[1] = new String[4];
//添加列数组

字符串长度:

1
arr2.length = 3arr2[1].length = 2

默认初始化值:

1
arr2[0] = 地址值

常见异常

数据角标越界异常:ArrayIndexOutOfBoundsExcetion

空指针异常:NullPointerException

数组为Null

排序算法

算法优劣:

  • 时间复杂度
  • 空间复杂度

稳定性:若A,B相等,排序后A,B的先后次序保持不变,则是稳定的

算法分类:

  • 内部排序:不用借助外部存储器,所有排序操作都在内存中完成
  • 外部排序:需要借助外部存储器,由多次内部排序组成

十大内部排序算法:

  • 选择排序:
    • 直接选择排序
    • 堆排序
  • 交换排序:
  • 冒泡排序:时间复杂度:O(n^2)
  • 快速排序:时间复杂度:O(nlogn)
    • (平均时间最快)
  • 插入排序:
    • 直接插入排序
    • 折半插入排序
    • Shell(希尔)排序
    • 归并排序
    • 桶式排序
    • 基数排序

内存简化结构

虚拟机栈:VM Stack

  • 局部变量
  • 一个线程一份

堆:Heap

  • new出来的结构:
    • 对象,数组
    • 对象的(非static的)属性
  • 一个进程一份
  • 分为:
    • 新生区
    • 养老区
    • 永久储存区(方法区)

方法区:Method Area

  • 类的加载信息
  • 常量池:
  • 静态域:
  • 一个进程一份

程序计数器:Program Counter Register

  • 一个线程一份

本地方法栈:Native Method Stack

面向对象

对象:

  • 实际存在在该类事物的每个个体

  • 也称为实例(instance)

对象的三要素:

  • 属性(对象是什么)

  • 方法(对象能做什么)

  • 事件(对象如何响应)

面向对象的重点:类的设计(也就是类的成员的设计)

面向对象结构:

  • 属性(成员变量,field,域,字段)

  • 方法(成员方法,函数,method)

面向过程与面向对象:

  • 面向过程:

    • 强调功能行为
    • 以函数为最小单位,考虑怎么做
  • 面向对象:

    • 强调具备了功能的对象
    • 以类/对象为最小单位,考虑谁来做

类及类的成员:属性,方法,构造器,代码块,内部类

面向对象的三大特征:封装,继承,多态

其他关键字:this,super,abstract,interface,static,final,package,import

创建类的对象 = 类的实例化

万事万物皆对象:在Java中,都将功能结构等封装到类中,通过类的事计划来调用具体的功能结

构:Scanner,String等

文件:File

网络资源:URL

Java与前后端交互时,前后端的结构在Java层面交互时都体现为类,对象

类和对象的使用:

  1. 创建类,设计类的成员

  2. 创建类的对象

  3. 通过”对象.属性“或”对象.方法“调用对象的结构

如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的(非static的)属性

如果修改一个对象属性a,则不影响另外一个对象属性a的值

类/成员

对一类事物的表描述

抽象的,概念上的定义

类也是对象

String

String字符串属于引用数据类型,声明时使用“”

String可以和8种基本基本数据类型变量做运算(包括boolean),只能是连接运算+

String不能用 == 比较,要用 xxx.equals() 比较

String转换char:

1
ccc = sss.charAt(0);
Scanner

键盘获取不同类型的变量:

  1. 导包:import java.util.Scanner;(写在类前面)

  2. Scanner实例化Scanner scan = new Scanner(System.in);

  3. 调用Scanner类的相关方法来获取制定类型的变量

(next() / nextXxx())除了String都是后者

没提供char型

输入数据类型与要求不匹配时会报异常InputMismatchException

Arrays
1
2
3
4
5
6
7
8
9
10
//判断两个数是否相等
boolean equals(int[],int[] b);
//输出数组信息
String toString(int[] a);
//将指定填充到数组之中
void fill(int[] a,int val);
//对数组进行排序
void sort(int[] a);
//对排序后对数组进行二分法检索制定的值
int binarySearch(int[] a,int key);
Object

Java的根父类

类没有声明其父类,默认父类为java.lang.Object类

Object所有功能具有通用性

Tread
包装类

使基本数据类型变量具有类的特征

父类Number:

  • byte → Byte
  • short → Short
  • int → Integer
    • 内部定义IntegerCache结构,其中定义Integer[]数组
    • 保存-128~127范围的整数
  • long → Long
  • float → Float
  • double → Double
  • boolean → Boolean
    • 空值为null
    • true(忽略大小写)为true
    • 其他为false
  • char → Character

基本数据类型与包装类的转换:

  • 基本数据类型 → 包装类:调用包装类的构造器
  • 包装类 → 基本数据类型:.intValue()
  • 自动装箱/拆箱:基本数据类型与包装类自动转换
    • Integer内部定义IntegerCache结构,其中定义Integer[]数组用来保存-128~127范围的整数。如果使用自动装箱的方式且赋值的范围在[-128,127]中时,可以直接使用数组中的元素,不用new/销毁,提高效率,此时两个范围在[-128,127]且相等的数比较运算为true,其他为false

基本数据类型/包装类与String类型的转换:

  • 基本数据类型/包装类 → String类型:连接运算(+ “”)
    • 调用String类的方法:valueOf(Xxx xxx)
  • String类型 → 基本数据类型/包装类:
    • 调用包装类的方法:Xxx.parseXxx(String string)
内部类

将一个类A声明在另一个类B中:

  • 类A:“内部类”
  • 类B:”外部类“

分类:

  • 成员内部类:
    • 放入类中
    • 分为:
      • 静态内部类:
        • 创建实例:ClassA.ClassAa xxx = new ClassA.ClassAa();
      • 非静态内部类:
      • 创建实例:
        • ClassA xxx = new ClassA();
        • ClassA.ClassAa yyy = xxx.new ClassAa();
    • 调用外部类的方法:ClassA.this.method();
    • 字节码文件:外部类名$内部类名.class
  • 局部内部类:可放入方法,代码块,构造器内
    • 字节码文件:外部类名$数字内部类名.class
    • 在局部内部类的方法中调用局部内部类所声明的方法中的局部变量时,此局部变量需声明为final的

属性

按是否使用static修饰分为:

  • 静态属性(静态变量):
    • 创建类的多个对象,多个对象共享同一个静态变量
    • 当通过某个对象修改静态变量时,会导致其他对象调用此静态变量时时修改过的
  • 非静态属性(实例变量):
    • 创建类的多个对象,每个对象都独立的拥有一套类中的非静态属性
    • 当修改其中一个对象的非静态属性时,不会导致其他对象中同样的属性值的修改

属性与局部变量的相同点:

  • 格式相同
  • 先声明后使用
  • 有其对应作用域

属性与局部变量的不同点:

  • 在类中的声明位置不同:
    • 属性直接在类的一对{}内
    • 局部变量声明在:方法内,方法形参,代码块内,构造器形参,构造器内部的变量
  • 权限修饰符不同:
    • 常用的权限修饰符:private,public,缺省,protected
    • 属性:可以在声明属性时,指明权限,使用权限修饰符
    • 局部变量:不可以使用权限修饰符
  • 默认出初始化值(隐式赋值)不同:类的属性都有初始化值
    • 整型:byte/short/int/lang:0
    • 浮点型:float/double:0.0
    • 布尔型:boolean:false
    • 字符型:char:0 或 ‘\u0000’
    • 引用数据类型:String:null

局部变量没有初始化值

形参在调用时赋值即可

加载的位置不一样:

  • 非static属性加载到堆空间中
  • 局部变量加载到栈空间中
赋值先后顺序
  1. 默认初始化
  2. 显式初始化
  3. 构造器初始化
  4. 通过“对象.方法”/“对象.属性”赋值

变量

成员变量:

  • 实例变量:不以static修饰
    • 只可以被对象调用
  • 类变量:以static修饰
    • 可以被类和对象调用
  • 局部变量:
    • 形参(方法构造器中定义的变量)
    • 方法局部变量(在方法内定义)
    • 代码块局部变量(在代码块内定义)

方法

描述类应该具有的功能

方法的声明:

1
2
3
4
权限修饰符 返回值类型 方法名(形参列表){
方法体
}
//Void:不需要返回值

权限修饰符:private,public,缺省,protected

返回值类型:

  • 有返回值:
    • 在方法声明时必须指定返回值类型
    • 需要return返回制定类型的变量或常量
  • 没返回值:
    • 返回值类型为void
    • 不需要使用return或只能return;
  • return:
    • 结束方法
    • 返回制定数据
    • return关键字后面不可声明执行语句

方法名:属于标识符

形参列表:可以声明0个至多个

  • 格式:数据类型1 形参1,数据类型2 形参2,……

方法的使用:可以调用当前类的属性,方法

递归方法:

  • 方法A中调用方法A
  • 方法中不可以定义方法
方法重载

在同一个类中允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可

通过对象调用方法时,确定方法名和参数列表来指定方法

“早绑定”

方法重写

override/overwrite

子类可以对父类同名同参的方法,进行覆盖操作

执行程序时子类的方法会覆盖父类的方法

子类中的叫重写的方法,父类中的叫被重写的方法

子类重写修饰符不小于父类被重写的方法的修饰符

子类不能重写父类private方法(不构成重写)

“晚绑定”

返回值类型:

  • 父类被重写方法返回值为void时,子类重写方法返回值只能是void

  • 父类被重写方法返回值为A类时,子类重写方法返回值可为A类或A类的子类

  • 父类被重写方法返回值为基本数据类型时,子类重写方法返回值只能是相同的基本数据类型

  • 子类重写方法抛出的异常类型不大于父类被重写方法抛出的异常类型

  • (形参) throws 异常的类型 {}
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    子父类中同名同参的方法要么都声明为非static的(考虑重写)

    要么都声明为static的(静态方法不可以重写)

    ##### 可变形参

    传入的参数个数可以是0个至多个

    可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载

    可变个数形参的方法与本类中方法名相同,形参相同的数组二者不能共存

    可变个数形参在方法的形参中只能声明在末尾(最多只能声明一个)

    相当于数组:

    ```java
    public void show(String ... strs) {
    for (int num = 0;num < strs.length;num++) {
    System.out.println(strs[num]);
    }
    }

jdk5.0新增内容:

  • 格式:数据类型 … 变量名

    • public void show(String ... strs) 
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70

      - 方法参数的传递机制:值传递

      - 变量赋值:

      - 基本数据类型:赋值变量保存的数据值
      - 引用数据类型:赋值变量保存数据的地址值(含变量的数据类型)

      - 形参:方法定义时,声明的小括号内的参数

      - 实参:方法调用时,实际传递给形参的数据

      - 传递机制:

      - 如果参数时基本数据类型,实参赋给形参的是实参真实存储的数据值
      - 如果参数时引用数据类型,实参赋给形参的是实参存储数据的地址值

      - 递归方法:包含了一种隐式的循环

      ##### main

      程序的入口
      普通的静态方法
      形参可以与控制台交互

      ##### equals

      ==与equals()的区别:

      - ==:运算符
      - 可以使用在基本数据类型变量和引用数据类型中
      - 基本数据类型变量:比较两个变量数据是否相等(有自动类型提升)
      - 引用数据类型变量:比较两个对象的地址值是否相同
      - equals():是方法而非运算符
      - 只能用在引用数据类型中
      - 在Object中,定义与==相同
      - 在String,Date,File,包装类等都重写次方法
      - 比较两个对象的实体内容是否相同

      ##### toString

      输出一个对象的引用时,就是调用当前对象的toString()

      在String,Date,File,包装类等都重写次方法来返回实体内容信息

      ##### wait

      ##### notify

      ##### notifyAll

      #### 构造器

      构造方法,constructor

      作用:创建对象

      - 创建类的对象:new + 构造器
      - Person p = new Person();

      如果没有显示的定义类的构造器,则系统默认提供一个空参的构造器

      定义构造器的格式:修饰权限符 类名(型参列表){}

      初始化对象的属性:

      ```java
      public Person1(int n) {
      age = n;
      }

构造器也可以重载

一旦显示定义类的构造器,系统就不再提供默认的空参构造器

一个类中至少会有一个构造器

代码块

又称初始化块

用来初始化类,对象

如果要修饰的话只能用static修饰

静态代码块:

  • 随着类的加载而执行
  • 可以在创建类时对类的属性等进行初始化

非静态代码块:

  • 随着对象的创建而执行
  • 每创建一个对象就执行一次
  • 可以在创建对象时对对象的属性等进行初始化

如果一个类中定义了多个代码块,则按照声明的先后顺序执行

静态代码块的执行优先于非静态代码块

先后顺序:由父及子,静态先行

  • 默认初始化 → 显示初始化/代码块中赋值 → 构造器中初始化 → 对象.属性/方法赋值

特性

封装和隐藏

高内聚,低耦合

把该隐藏的隐藏起来,该暴露的暴露出来

赋值操作收到属性的数据类型和存储范围制约

实际问题中往往给属性赋值加入额外的限制条件

这个条件不能在属性声明中体现,只能通过方法进行条件的添加,同时需要避免用户在用“对象.属性”的方式对属性进行赋值

(将属性声明为私有的)此时针对属性就体现了封装性

封装性的体现:

  • 将类的属性xxx私有化private
  • 同时提供公共public的方法来获取getXxx和设置setXxx次属性的值
  • 属性,方法,类,构造器私有化 (单例模式将构造器私有化)

权限修饰符:

  • Java提供了4种权限修饰来修饰类及类的内部结构
  • 体现类及类的内部结构在被调用时的可见性的大小:
    • public > protected > 缺省 > private

4种权限都可以修饰类的内部结构:属性,方法,构造器,内部类

修饰类只能用缺省和public

修饰符 内部类 同一个包 不同包的子类 同一个工程
private T
缺省 T T
protected T T T
public T T T T

继承性

extends:延展/扩展

减少代码的冗余,提高代码的复用性,便于功能的扩展

为多态性的使用提供了前提

一旦子类A继承父类B以后,子类A中就获取了父类B中所有的声明结构:属性/方法

子类不可直接调用父类声明为私有的属性/方法

子类可以定义自己的属性/方法:实现功能的扩展

子类和父类的关系不同于子集和集合的关系

格式:class A extends B {}

  • A:子类/派生类/subclass
  • B:父类/超类/基类/superclass
  • 子类→父类(实线箭头)

规定:

  • 就近原则
  • 一个父类可以有多个子类
  • Java类的单继承性:一个类只能有一个父类
  • 类可以多层继承:直接父类,间接父类
  • 所有类都直接/间接继承于java.lang.Object类(除java.lang.Object)
子类实例化

子类继承父类后就获取类父类中声明的属性或方法(继承性)

创建子类的对象,在堆空间中就会加载所有父类中声明的属性

通过子类构造器创建子类对象时,一定会直接/间接调用其父类的构造器

直到调用了java.lang.Object类中空参的构造器

虽然创建子类对象调用了父类的构造器,但始终只创建过new的一个子类对象

多态性

一个事物的多种形态,实现代码的通用性

对象的多态性:父类的引用指向子类的对象

调用子父类同名同参的方法时执行的是子类重写方法(虚拟方法调用)

虚拟方法调用:

  • 编译期,只能调用父类中父类中声明的方法
  • 运行期,实际执行子类重写的方法
  • 编译看左边,运行看右边
  • 只适用于方法,不适用于属性
  • “晚绑定”

内存中实际加载了子类特有的属性和方法

变量声明为父类类型,编译时只能调用父类声明的属性方法

子类特有的属性方法不能调用

可使用强制类型转换符向下转型

使用强转时可能出现ClassCastException的异常

向上/向下转型

向上转型:多态

向下转型:用向下转型解决声明为父类类型的变量无法使用子类特用属性和方法的问题

关键字

package


更好的实现项目中类的管理
使用package声明类或接口所属的包,声明在源文件首行
属于标识符,需遵循标识符的命名规范(全小写),见名知意
每点一次代表一层文件目录
同一个包下不能命名同一个接口或类

import

导入

在源文件中显示的导入指定包下的类,接口

声明在包和类的中间

可以使用xxx.*的方式导入xxx包下的所有结构(不包括其子包下的结构)

使用java.lang包和本包里的类和接口可以省略定义

源文中使用了不同包下的同名的类,则至少有一类需要以全类名的方式显示

import static 导入指定类或接口中的静态结构:属性或方法

this

可以用来修饰属性,方法;构造器

一般省略

除非方法形参和类的属性重名时,使用this.xxx表名此变量不是形参

this:当前对象/当前正在创建的对象

this.xxx:当前对象的xxx属性

this.xxx():当前对象的xxx方法

在类的构造器中可以显示使用this.(形参列表)调用本类中其他构造器

this.(形参列表)必须在当前构造器的首行(最多声明一个)

就近原则

super

理解为:父类的

可以用来调用属性,方法,构造器

可以在子类的方法/构造器中使用super.属性/方法的方式显式调用父类的

通常省略,当同名时必须显式使用

可以在子类构造器中显式调用父类指定的构造器:super(形参列表);

super(形参列表);必须声明在子类构造器的首行(this,super二选一)

默认super();

instanceof

为了避免向下转型时出现的异常

a instanceof A:判断对象a是否是类A的实例,是为true错为false

a instanceof A为true,a instanceof B也为true,则B为A的父类

接口

interface

和类是并列关系

不能实例化

通过让类去实现(implements)的方式来使用,如果实现类实现接口中所有抽象方法则可实例化

类可实现多个接口,弥补了Java单继承性的局限性

格式:class ClassA extends ClassB implements InterfaceA, InterfaceB

接口与接口间可以多继承

具体使用体现多态性

可以看作是一种规范

定义

JDK7

全局常量:public static final(书写时可省略)

  • 若子类继承父类和实现接口声明了同名的属性会报错,必须显式区分

抽象方法:public abstract(书写时可省略)

JDK8

静态方法:public static void method(){}(书写时可省略)

  • 只能通过接口调用

默认方法:public default void method(){}(书写时可省略)

  • 通过实现类的对象可以调用接口中的默认方法
  • 可以被重写/实现
  • 若子类继承父类和实现接口声明了同名同参且没被重写的方法,默认调用父类的方法(类优先原则)
  • 若实现类实现了多个接口,且多个接口都定义了同名同参且的默认方法,实现类没有重写次方法的情况下会报错(接口冲突),则必
  • 须重写此方法
  • 调用接口中的默认方法:InterfaceX.super.method()
JDK9

方法访问权限修饰符可声明为private

修饰符

static

静态的

可修饰属性、方法、代码块、内部类:

  • 修饰属性:
    • 静态变量(静态属性,类变量)
    • 随着类的加载而加载(早于对象的创建)
    • 由于类只会加载一次,则静态变量在内存中也只会存在一份:方法区的静态域中
    • 类中的常量也常被声明为静态的
  • 修饰方法:
    • 静态方法,只能调静态结构
    • 随着类的加载而加载,可以通过类.方法的方式进行调用
    • 静态方法只能调用静态的方法或属性,非静态方法则否可以用
    • 静态方法内不能使用this、super关键字
    • 操作静态属性的方法通常设置为静态
    • 工具类中的方法习惯上声明为静态方法

final

最终的

可修饰类,方法,变量:

  • 修饰类:此类不能被继承
    • eg:string
  • 修饰方法:此方法不能被重写
    • eg:getclass()
  • 修饰变量:变量成为常量
    • 可以显示初始化,代码块中初始化,构造器中初始化属性

static final:

  • 修饰属性:全局常量
  • 修饰方法

abstract

“抽象的”

可修饰类/方法:

  • 修饰类:”抽象类“
    • 此类不能实例化
    • 让子类对象实例化完成相关操作
  • 修饰方法:“抽象方法”
    • 只有方法声明没有方法体:{}
    • 包含抽象方法的类一定是抽象类
    • 子类重写父类所有抽象方法后才可实例化,否则子类仍为抽象类

native

匿名

匿名对象

创建的对象没有显示的赋给一个变量名

匿名对象只能调用一次

匿名类

匿名子类的非匿名对象:

1
2
3
4
5
6
7
8
SuperClass className = new SuperClass() {
@Override
method() {
}
}

method(className) {
}

匿名子类的匿名对象:

1
2
3
4
5
method(new SuperClass() {
@Override
method() {
}
});

JavaBean

一种Java语言写成的可重用组件

指符合如下标准的Java类:

  • 类是公共的
  • 有一个无参的公共的构造器
  • 有属性,且有对应的get,set方法

UML

+:public类型

-:private类型

#:protected类型

方法的写法:方法类型 方法名(参数名:参数类型):返回值类型

JUnit单元测试

要求:

  • 公共类
  • 有公共的无参构造器

声明的单元测试方法:

  • 权限是public
  • 没有返回值
  • 没有形参
  • 方法声明上需要声明注解@Test
  • import org.junit.Test;
  • 声明完成后就可移在方法体内测试相关的代码

设计模式

MVC

将整个程序分为三个层次:

  • 数据模型层model:主要处理数据
  • 视图模型层view:显示数据
  • 控制器层controller:处理业务逻辑

类中声明另外一个类为关联关系

单例模式

“singleton”

需要手写

某个类只能存在一个对象实例

减少系统性能开销

构造器为私有的,只用调用该类的某个静态方法创建对象

饿汉式:

  • 私有化类的构造器
  • 内部创建类的静态对象
  • 提供公共的静态方法返回类的对象
  • 优点:线程安全的
  • 缺点:对象加载时间过长

懒汉式:

  • 私有化类的构造器
  • 声明当前类的静态对象,没有初始化
  • 声明公共静态的返回当前类对象的方法
  • 优点:延迟对象的的创建
  • 缺点:目前的写法导致线程不安全

模版方法

“template”

适用于整体步骤固定通用,某些部分易变的情况

代理模式

“proxy”

安全代理,远程代理等

用到接口

工厂模式

“factory”

实现了创建者与调用的分类

用到接口

异常处理

体系结构

java.lang.Throwable

Error:java.lang.Error

  • Java虚拟机无法解决的严重问题
  • 一般不编写针对性的代码进行处理
  • eg:
    • StackOverflowError
    • OOM

Exception:java.lang.Exception

  • 其他因编程错误或偶然的外在因素导致的一般性问题
  • 可以使用针对性的代码进行处理
  • 分为:
    • 编译时异常(受检异常):
      • IOException
        • FileNotFoundException
        • ……
      • ClassNotFoundException
      • ……
    • 运行时异常(非受检异常):
    • NullPointerException
    • ArrayIndexOutOfBoundsException
    • ClassCastException
    • NumberFormatException
    • InputMismatchException
    • ArithmeticException
    • ……

处理机制

异常对象的产生:系统自动生成

手动生成并抛出:Throw,表示抛出一个异常类的对象,生成异常对象的过程

抓抛模型:

  • 过程1:“抛”
    • 程序正常执行过程中一旦出现异常就会在异常处生成一个对应异常类的对象并将其抛出
    • 一旦抛出对象后剩余代码将不再执行
  • 过程2:“抓”
    • 异常的处理方式:
      • try-catch-finally
      • throws
try-catch-finally

真正的处理异常

1
2
3
4
5
6
7
8
9
10
try {
//可能出现异常的代码
} catch (异常类型1 变量名1) {
//处理异常的方式1
} catch (异常类型n 变量名n) {
//处理异常的方式n
}
finally {
//一定会执行的代码
}

可以被嵌套

子异常类必须声明在父异常类上

Try结构中的变量出了结构后不能再被调用

一旦try中的异常对象匹配到某个catch时,就进入其中进行处理,完成后跳出try-catch结构

finally是可选的

即使catch中出现异常/try-catch中有return,finally中语句仍然执行

数据库连接/输入输出流/网络编程Socket等资源,JVM不能自动回收,需要手动进行资源释放,就需要声明在finally中

主要用来处理编译时异常,使其不报错,但运行时仍可能报错(将编译时出现的异常延迟到运行时可能出现)

常用方法:

  • String getMessage();
  • printStackTrace();

Java8:可实现资源的自动关闭,但是要求执行后必须关闭的所有资源必须在try子句中初始化,否则编译不通过

1
2
3
4
try(InputStreamReader reader = new InputStreamReader(System.in)){
//读取数据细节省略
}catch (IOException e){ e.printStackTrace();
}

Java9:可在try子句中使用已经初始化过的资源,此时的资源是final的

1
2
3
4
5
6
7
8
InputStreamReader reader = new InputStreamReader(System.in);
OutputStreamWriter writer = new OutputStreamWriter(System.out);
try (reader; writer) {
//reader是final的,不可再被赋值
//reader = null;
//具体读写操作省略
} catch (IOException e) { e.printStackTrace();
}
throws

只将异常抛给方法调用者

属于异常处理的一种方式

throws + 异常类型1,异常类型n

卸载方法声明处,指明执行此方法时可能会抛出的异常类型

一旦执行的方法出现的异常满足throws的异常类型时就会被抛出,后续代码不被执行

子类重写方法抛出的异常类型不大于父类被重写方法抛出的异常类型

使用情景

若父类被重写的方法没有throws方式处理异常,则子类重写方法也不能使用throws,只能用try-catch-finally

若执行方法a中递进的调用其他方法执行,则可以使用throws方法,执行的方法a可以使用try-catch-finally方法

自定义异常类

继承于现有的异常结构:

  • RuntimeException
  • Exception

提供全局常量:serialVersionUID

提供重载的构造器

线程

程序:program

  • 一段静态的代码

进程:process

  • 正在运行的程序
  • 作为资源分配的单位

线程:thread

  • 程序内部的一条执行路径
  • 作为调度和执行的单位
  • 每个线程拥有独立的运行栈和程序计数器
  • 分类:
    • 守护线程
    • 用户线程

并行:“多人做多事”
并发:“一人做多事”

多线程创建方式

start():

  • 启动当前线程
  • 调用当前线程的run()

常用方法:

  • start()
  • run()
  • currentTread():静态方法,返回执行当前代码的线程
  • getName()
  • setName()
  • yield():释放当前CPU的执行权,不释放锁
  • join():在线程a中调用线程b的join(),此时线程a进入阻塞状态直到线程b完全执行完后才恢复
  • stop()过时:强制结束当前线程
  • sleep(long milliTime):阻塞milliTime毫秒,不释放锁
  • isAlive():判断当前线程是否存活

优先级:

  • MAX_PRIORITY:10
  • MIN_PRIORITY:1
  • NORM_PRIORITY:5
  • getPriority():获取当前线程优先级
  • setPriority(int i):设置当前线程优先级
  • 高优先级的线程只是有较高概率情况下会被执行
继承Thread类
  1. 继承Thread类
  2. 重写run()
  3. 创建子类的对象
  4. 通过此对象调用start()

若要再启动一个线程,必须重新创建一个Thread子类对象

实现Runnabe接口
  1. 创建一个实现了Runnable接口的类
  2. 实现类实现抽象方法run()
  3. 创建实现类的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  5. 通过此Thread类的对象调用start()
实现Callable接口
  1. 创建Callable的实现类
  2. 实现call方法
  3. 创建Callable实现类的对象
  4. 将此对象作为参数传递到FutureTask的构造器中,创建FutureTask的对象
  5. 将FutureTask的对象传递到Thread的构造器中,创建Thread对象并start()
  6. 可选获取call返回值

JDK5

功能比Runnable更强大:

  • 有返回值
  • 可抛出异常
  • 支持泛型
  • 需要借助FutureTask类
使用线程池
  1. 提供制定线程数量的线程池

  2. 执行指定线程的操作,需要提供实现Runnable/Callable接口实现类的对象:

    • execute()适用于Runnable

    • callable()适用于Callable

  3. 关闭连接池

JDK5.0

提高响应速度

降低资源消耗

便于线程管理

方式比较

优先选择实现Runnable接口方式:

  • 没有单继承性局限
  • 更适合出了多线程共享数据的情况

Thread类也实现Runnable接口

线程状态

新建:new

就绪:

  • start()
  • yield()
  • 失去CPU执行权
  • sleep()时间到
  • join()结束
  • 获取同步锁
  • notify()/notifyAll()
  • resume()[过时]

运行:获得CPU执行权

阻塞:

  • sleep()
  • join()
  • 等待同步锁
  • wait()
  • suspend()过时

死亡:

  • 执行完run()
  • stop()
  • 出现异常且没处理

线程同步

解决线程安全问题

操作同步代码时相当于单线程过程,效率较低

优先使用顺序:

  1. Lock
  2. 同步代码块
  3. 同步方法
synchronized方法

自动释放同步

同步代码块方式:

1
2
3
synchronized (同步监视器(锁)) {
//需要被同步的代码(操作共享数据的代码)
}
  • 共享数据:多个线程共同操作的变量, 不能多不能少
  • 任何一个类的对象都可充当锁,要求多个线程必须共用同一把锁
    • 继承类方法可用ClassX.class,慎用this
    • 实现接口方法可用this,ClassX.class

同步方法方式:

  • 若操作共享数据的代码完整的声明在一个方法中,可将此方法声明为同步的
  • 同步监视器:
    • 不需要显示的声明
    • 继承类方法:把需要同步的方法声明为静态的
Lock方法

手动启动(lock)/释放同步(unlock)

1
2
3
4
5
6
7
private ReentrantLock lock = new ReentrantLock([true]);
try {
lock.lock();
//需要被同步的代码
} finally {
lock.unlock();
}

线程死锁

不同线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源

线程通信

synchronized方法

wait():使调用此方法的线程进入阻塞状态,释放锁

notify():唤醒一个被wait的线程,若多个线程被wait则唤醒优先级高的

notifyAll():唤醒所有被wait的线程

被定义在Object类中

都得在同步方法/同步代码块中

调用者必须是当前的同步监视器

常用类

字符串类

String

声明为fianl,不可被继承

实现Serializable接口,支持序列化

实现Comparable接口,可以比较大小

不可变的字符序列:

  • 通过字面量方式给字符串赋值,字符串值的声明在字符串常量池中
  • 字符串常量池中不会存储相同内容的字符串
  • 对字符串重新赋值是需要重写指定内存区域复制,不能使用原有的value进行赋值

通过new + 构造器的方式赋值时,数据存在堆空间中

常量与常量的拼接在常量池中

变量与常量的拼接在堆中

转换:

  • 与基本数据类型:
    • String → int:Integer.parseInt()
    • int → String:String.valueOf()
  • 与char[]:
    • String → char[]:toCharArray()
    • char[] → String:String(array)
  • 与byte[]:
    • String → byte[]:getBytes()
    • byte[] → String:String(array)
    • 编码:字符串 → 字节
StringBuffer

可变的字符序列

线程安全的(效率低)

底层创建了一个长度是 str.length() + 16 的数组

若增加字符超出数组长度,则创建一个新的容量为原来容量2倍+2的数组(value.length << 1 + 2)并复制到新的数组中

为避免多次扩容,建议使用StringBuffer(int capacity)构造器

StringBuilder

可变的字符序列

线程不安全的(效率高)

其它同StringBuffer

执行效率:StringBuilder > StringBuffer > String

时间日期类JDK1

Date

JDK1.0

toString():显示当前的年月日时分秒

getTime():获取当前对象应对的时间戳

util.Date

  • sql.Date(util.Date的子类)
SimpleDateFormat

对日期Date类的格式化和解析

实例化:new + 构造器

  • 方式之一:SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd GGG hh:mm:ss aaa”)

格式化:日期 → 字符串

  • sdf.format(date)

解析:格式化的逆过程

  • sdf.parse(“yyyy-MM-dd GGG hh:mm:ss aaa”)
  • 字符串必须符合SimpleDateFormat识别的格式(通过构造器参数体现),否则会抛异常
Calendar

JDK1.1 - 一定程度上替换Date类

日历抽象类

具有可变性

实例化:

  • 方法1:创建其子类GregorianCalendar类的对象
  • 方法2:调用其静态方法getInstance()

时间日期类JDK8

LocalDate/LocalTime/LocalDateTime

分别表示使用ISO-8601日历系统的日期/时间/日期和时间

类似于Calendar

使用.of()构造器创建对象时没有偏移量

有不可变性

Instant

时间线上的瞬时点

自1970/01/01-00:00:00(UTC开始的毫秒数)

类似于Date

DateTimeFormatter

格式化解析日期/时间

类似于SimpleDateFormat

常用自定义格式.ofPattern(“yyyy-MM-dd hh:mm:ss”)构建对象

比较器类

Java中的对象只能比较==或!=,若想比较<或>需要使用Comparable/Comparator接口

Comparable

自然排序

一旦指定,实现类的对象在任何都可比较大小

String/包装类等实现了Comparable接口,重写了compareTo()方法后进行从小到大的排列

重写compareTo(obj)的规则:

  • 当前对象this大于形参对象obj,返回正整数
  • 当前对象this小于形参对象obj,返回负整数
  • 当前对象this大于形参对象obj,返回零

自定义类若需要排序,可以实现Comparable接口,重写compareTo()方法,在compareTo()方法指明如何排序

Comparator

定制排序

临时性的比较(常用作匿名实例化)

适用类型:

  • 元素的类型没实现Comparable接口且不方便修改代码
  • 实现了Comparable接口排序规则不适合当前的操作

重写compareTo(obj1, obj2)的规则:

  • 当前对象obj1大于形参对象obj2,返回正整数
  • 当前对象obj1小于形参对象obj2,返回负整数
  • 当前对象obj1大于形参对象obj2,返回零

其它类

System

“系统的工具类”

方法:

currentTimeMilis():获取当前时间到1970/01/01-00:00:00的长度(ms)

  • 称为时间戳

void exit(int status):在图形界面编程中实现程序的退出功能等

  • status为0代表正常退出
Math

数学工作类

BigInteger/BigDecimal

数据长度过长/数据精度要求高时使用

BigInteger可以表示不可变的任意精度的整数

BigDecimal可以表示不可变的任意精度的浮点数

枚举类

JDK5

类的对象只有有限个且确定的

当需要定义一组常量时强烈建议使用枚举类

若枚举类中只有一个对象则是单例模式的一种实现方式

定义

自定义枚举类:

  • JDK5前
  • 私有化类的构造器
  • 使用private final修饰对象属性
  • 提供多个public static final的对象

enum关键字定义:

  • JDK5后
  • 默认继承于java.lang.Enum
  • 一开始就需要提供当前枚举类的对象,用逗号隔开,末尾分号结束,后面步骤同上
1
SPRING("Spring", "春天"),

常用方法

values():返回枚举类型的对象数组,可以很方便地遍历所有枚举值

valueOf(String str):把字符串转换为对应的枚举类对象,字符串必须是枚举类对象的名称(区分大小写)

  • 若没有str的枚举类对象则抛异常IllegalArgumentException

toString():返回当前枚举类对象常量的名称

实现接口

使用关键字定义的枚举类实现接口的情况

情况1:所有枚举类实现统一方法

  • 实现接口,在enum类中实现抽象方法

情况2:每个枚举类的对象分别实现接口中的抽象方法

  • 在枚举类的对象后面加上{}并在其中分别实现抽象方法

注解

Annotation

JDK5

代码里的特殊标记

没有成员定义的注解称为标记

若注解有成员,使用时需要指明成员的值
可修饰包,类,构造器,方法,成员变量,参数,局部变量的声明

框架 = 注解 + 反射 + 设计模式

使用示例

生成文档相关的注解

编译时进行格式检查(JDK内置的三个基本注解):

  • @Override:限定重写父类方法,只能用于方法
  • @Deprecated:表示修饰的元素已过时
  • @SuppressWarnings:抑制编译器警告

跟踪代码依赖性,实现替代配置文件功能

自定义注解

参照@SuppressWarnings

声明为@interface

内部定义成员,通常使用value表示

可以使用default定义成员的默认值

若自定义注解没有成员表明是一个标识作用

自定义注解必须配上注解的信息处理流程(使用反射)才有意义

常指明两个元注解:@Retention,@Target

元注解

用于修饰其它注解

@Retention:指定所修饰注解的生命周期

  • SOURCE:不保留在字节码文件中
  • CLASS(默认):保留在字节码文件中,运行时不加载到内存中
  • RUNTIME(可通过反射获取):运行时加载到内存中

@Target:指定所修饰注解能用于修饰哪些程序元素

@Documented:指定所修饰注解将被javadoc工具提取成文档

  • 默认情况下javadoc不包括注解

@Inherited:指定所修饰注解会有继承性

  • 若某类使用了被@Inherited修饰的注解,则子类将自动具有该注解

@Repeatable:

  • JDK8
  • 可重复注解
  • 在MyAnnotation上声明@Repeatable,成员值为MyAnnotation.class
  • MyAnnotation的@Target/@Inherited/@Retention等元注解与MyAnnotations相同

通过反射获取注解

JDK8新特性

可重复注解

类型注解:

  • 作为参数声明在@target中
  • TYPE_PARAMETER:该注解能写在类型变量的声明语句中(如泛型声明)
  • TYPE_USE:该注解能写在使用类型的任何语句中

集合

集合/数组都是对多个数据进行存储操作的结构,简称Java容器

内存层面的存储,不涉及到持久化的存储

解决数组存储数据方面的弊端

Collection接口

单列数据,用于存储一个一个的对象

添加数据obj时,所在类需要重写equals()

方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
add(Object o):将元素o添加到集合中
size():获取添加元素的个数
addAll(Collection c):将c集合中的元素添加到当前集合中
isEmpty():判断当前集合是否为空
clear():清空集合元素
contains(Object o):使用equals()方法判断当前集合中是否包含o
containsAll(Collection c):判断集合c的所有元素是否都存在于当前集合中
remove(Object o):先使用equals()方法判断当前集合中是否包含o,然后再移除o
removeAll(Collection c):从当前集合移除集合c的所有元素
retainAll(Collection c):求交集并修改当前集合
equals(Object o):判断两个集合元素是否相同
hashCode():返回当前对象的哈希值
toArray():集合转换成数组
Arrays.asList(Object[] o):数组转换成集合(识别包装类)
返回的集合为只读集合
与数组转换

集合 → 数组:toArray()

数组 → 集合:Arrays.asList()

迭代器

迭代器Iterator接口

每次调用iterator()方法都会得到全新的迭代器对象

迭代器模式就是为容器而生

返回Iterator接口的实例用于遍历集合元素:

  • iterator.next():指针下移后返回当前元素
  • iterator.hasNext():判断指针后是否还有数据元素
  • iterator.remove():遍历的时候删除集合中的元素
    • 若还未调用next()/已经调用了remove()方法,再调用remove()会报illegalStateExcetion
List接口

”动态数组“

Collection的子接口

元素有序,可重复的集合

主要实现类:

  • ArrayList:List的主要实现类

    • 线程不安全(效率高)

    • 使用Object[]数组存储

    • 建议使用带参构造器ArrayList (int capacity)

    • 常用方法:

      1
      2
      3
      4
      5
      6
      7
      8
      void add(int index, object element)
      boolean addAll(int index, Collection elements)
      Object get(int index)
      int indexOf(Object obj):返回obj在集合中首次出现的位置
      int lastIndexOf(Object obj):返回obj在集合中末次出现的位置
      Object remove(int index):移除指定index位置的元素并返回
      Object set(int index, Object element)
      List sublist(int fromIndex, int toIndex):返回从fromIndex到toIndex位置左闭又开的子合集
    • 源码分析:

      • 创建数组:
        • JDK7:创建长度为10的Object[]数组
          • 类似于“饿汉式”
      • JDK8:底层Object[]数组初始化为{}
        • 第一次调用add()时,底层才创建长度为10的数组
        • 将数据添加进数组
        • 类似于“懒汉式”
        • 此方法节省内存空间
    • 若添加导致数组容量不够则扩容:

      1. 默认情况扩容为原来容量的1.5倍
      2. 将原数组内的数据复制到新数组中
  • LinkedList:对于频繁插入/删除操作,效率比ArrayList高

    • 使用双向链表存储
    • 源码分析:内部声明Node类型的first和last属性
      • 其中Node定义:
        • Node类型的next
        • Node类型的prev
  • Vector:List的古老实现类(基本不用)

    • 线程安全(效率低)
    • 使用Object[]存储
    • 创建时底层创建长度为10的数组
    • 扩容时默认扩容为原来数组长度的2倍
Set接口

Collection的子接口

没有定义额外的新方法

向Set中添加的属性的所属类一定要重写hashCode()和equals()且这两个方法经可能保持一致性:

  • 对象中用作equals()方法比较的Field都应该用来计算hashCode

特性:

  • 无序性:
    • 不等于随机性
    • 存储的数据在底层数组中并非按照数组索引的顺序添加
    • 根据数据的哈希值进行添加
  • 不可重复:
    • 添加的元素按照equals()判断是不能返回true(相同的元素只能添加一个)
    • 确定性
    • 互异性

添加元素的过程:以HashSet为例,在其中添加元素a

  1. 调用a所在类的hashCode()方法来计算a的哈希值
  2. 使用此哈希算出在HashSet底层数组中存放的位置(索引位置)
  3. 判断此位置上又没有其他元素:
    • 无其它元素:添加成功
    • 有其它元素b/链表形式存在的多个元素:依次比较a与b/多个元素的哈希值
      • 不同:添加成功,与已经存在指定索引位置上数据以链表方式存储
      • 相同:调用a所在类的equals()方法
        • 返回true:添加失败
        • 返回false:添加成功,与已经存在指定索引位置上数据以链表方式存储
          • JDK7:a放入数组,指向原元素
          • JDK8:原元素指向a
          • (七上八下)

主要实现类:

  • HashSet:Set接口的主要实现类
    • 线程不安全的
    • 可以存储null值
  • LinkedHashList:HashSet的子类
    • 添加数据时还有两个引用来记录数据的前后数据
    • 遍历内部数据时可以按照添加的顺序遍历
    • 若要频繁的遍历操作,LinkedHashSet效率高于HashSet
  • TreeSet:保存的所有元素是相同类的对象
    • 底层使用二叉红黑树存储
    • 可以按照添加对象的指定属性进行排序(默认从小到达排列)
    • 比较两个对象是否相同的标准:
    • 自然排序:compareTo()返回0
    • 定制排序:compare()返回0

Map接口

双列数据,用于存储一对一对(键值对)的数据(key - value):

  • Entry:存储一对key和value两个属性
    • 无序不可重复的
    • 使用Set存储
  • key:
    • 无序不可重复的
    • key所在的类需重写equals()/hashCode()(以hashMap为例)
    • 使用Set存储
  • value:
    • 无序可重复的
    • 使用Collection存储
    • 类似于y = f(x)
重要常量
1
2
3
4
5
DEFAULT_INITIAL_CAPACITY:HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子,决定数据密度,0.75
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树,8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量,64
threshold:扩容的临界值,= 容量 * 填充因子,16 * 0.75 => 12
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
Object put(Object key,Object value):将指定key-value添加/修改到当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
实现类

HashMap:Map的主要实现类(类似于HashSet)

  • 线程不安全(效率高)
  • 可存储null的key和value
  • 底层结构:
    • JDK7:数组 + 链表
    • JDK8:数组 + 链表 + 红黑树
      • 当数组索引位置上以链表形式存在的元素数据 > 8 && 数组长度 > 64时,此索引位置上所有数据改为红黑树存储
  • 底层实现原理:
    • 实例化:
      • JDK7:底层创建长度为16的一维数组Entry[] table
      • JDK8:底层未创建长度为16的一维数组Node[] table,直至首次调用put()时才创建
    • 添加键值对:
      • 调用键值对key值所在类的hashCode()计算keyX哈希值
      • 通过此哈希值算出在Entry数组中的存放位置
        • 位置为空:添加成功
        • 位置非空(该位置上存在一或多个数据(以链表存储)):此键值对与目标键值对key值的哈希值逐一比较
          • 不同:添加成功,与已经存在指定索引位置上数据以链表方式存储
          • 相同:调用此键值对key所在类的equals()比较
            • 不同:添加成功,与已经存在指定索引位置上数据以链表方式存储
            • 相同:使用键值对value值替换相同key的value值(修改功能)
    • 扩容:超出临界值且此索引位置上有数据时,默认扩容为2倍原容量并将原有数据复制到新数组

LinkedHashMap:HashMap的子类

  • 内部提供Entry替换HashMap中的Node
  • 可按照添加顺序进行遍历Map元素
  • 适用于频繁的便利操作

Hashtable:Map的古老实现类

  • 线程安全(效率高)
  • 不可存储null的key和value

Properties:Hashtable的子类

  • 常用来处理配置文件
  • key和value都是String类型

SortedMap:Map的子接口

TreeMap:实现SortedMap接口

  • 底层使用红黑树
  • 按照添加的key-value进行排序/实现遍历(按key排序)
  • Key必须时同一个类创建的对象
Java9方法

快速创建只读集合

使用of()方法创建

使用Map.ofEntries(Map.entry()…)方法创建

Java10方法

快速创建只读集合

copyOf():先判断来源集合是不是AbstractImmutableList类型的

  • 是:直接返回
  • 否:调用of创建一个新的集合

Collections工具类

操作Collection/Map

1
2
3
4
5
6
7
8
9
10
11
12
13
reverse(List):反转List中元素的顺序
shuffle(List):对List集合元素进行随机排序
sort(List):根据元素的自然顺序对指定List集合元素按升序排序
sort(List, Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
swap(List, int, int):将指定list集合中的i处元素和j处元素进行交换
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection, Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection, Comparator)
int frequency(Collection, Object):返回指定集合中指定元素的出现次数
void copy(List dest, List src):将src中的内容复制到dest中
boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List对象的所有旧值
synchronizedXxx():可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时使线程安全

泛型

Generic

把元素的类型设计成一个参数,这个参数称为泛型

允许在定义类/接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型,这个类型参数将在使用时确定

泛型类型必须是类

默认为Object

集合与泛型:

  • 集合接口/集合类在JDK5都带有泛型结构
  • 实例化集合类时可以指明泛型类型
  • 在集合类/接口中凡是使用到类的泛型位置都指定为实例化的泛型类型

自定义

泛型类/接口:

  • 声明构造器时不需要加<>
  • 泛型不同引用不能互相赋值
  • 静态方法中不能用类的泛型
  • 异常类不能声明为泛型类
    不能直接new T[]:使用(T[]) new Object[int]
  • 若自定义类/接口中带有泛型,建议实例化时指明泛型
  • 子类继承有泛型的父类:
    • 若指明类型:子类实例化时不再需要指明泛型
    • 若不指明类型:子类仍然是泛型类

泛型方法:

  • 方法中出现的泛型结构与类的泛型参数没有任何关系
  • 泛型方法与所属的类是否泛型无关
  • 可以声明为静态:泛型参数在调用方法时才确定
1
2
public <T> T getInstance(Class<T> aClass, String sql, Object... args)
public <T> List<T> getForList(Class<T> aClass, String sql, Object... args)

继承

类A为类B的父类

通配符

G与G共同的父类为G<?>

list<?>不能向其添加除null之外的数据

读取的数据类型为Object

限制条件的通配符
1
2
3
<? extends Number>:(无穷小 , Number]只允许泛型为Number及Number子类的引用调用
<? super Number>:[Number , 无穷大)只允许泛型为Number及Number父类的引用调用
<? extends Comparable>:只允许泛型为实现Comparable接口的实现类的引用调用

IO流

Java对于数据的输入/输出操作以“流(stream)”的方式进行

分类:

  • 按数据单位:
    • 字节流(8bit):主要处理非文本数据
    • 字符流(16bit):主要处理文本数据
  • 按流向:
    • 输入流:数据 → 程序
    • 输出流:程序 → 数据
  • 按角色:
    • 节点流:直接与特定的目标相连
    • 处理流:套接在已有流的基础上

抽象基类

字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer

常用类

文件流

节点流

流程:

  • 实例化File类的对象,指明要操作的文件
  • 提供具体的流
  • 数据读入/写出
  • 关闭流
  • 为了保证流资源一定可以执行关闭操作,需要用try-catch-finally处理
  • 读入的文件一定要存在,否则会报FileNotFoundException

FileInputStream:

FileOutputStream:

FileReader:

  • read():返回读入的一个字符,若达到文件末尾返回-1
  • close():关闭流

FileWriter:

  • 输出操作对应的File:
    • 不存在:会自动创建此文件
    • 存在:以构造器为准
      • FileWriter(file ,):覆盖原文件
      • FileWriter(file ,true):原文件后面添加
缓冲流

处理流

内部提供缓冲区,默认大小为8Kb

提高流的读取/写入速度

先关外层流,再关内层流:

  • 关闭外层流的同时也会关闭内层流,内层流的关闭可以省略
  • flush():刷新缓冲区

BufferedInputStream:

BufferedOutputStream:

BufferedReader:

  • String readLine():一次读一行
  • newLine():新一行(回车)

BufferedWriter:

转换流

字符流/处理流

提供字节流和字符流之间的转换

InputStreamReader:将InputStream转换为Reader(解码)

  • 构造器参数2指明字符集(取决于保存源文件时使用的字符集)

OutputStreamWriter:将Writer转换为OutputStream(编码)

标准IO流

处理流

System.in:标准输入流,默认从键盘输入

  • 字节流

System.out:标准输出流,默认从控制台输出

可使用System类的setIn(InputStream is)/setOut(PrintStream ps)重新指定输入/输出流

打印流

处理流/输出流

提供一系列重载的print()/println()方法

System.out返回PrintStream的实例

PrintStream

PrintWriter

数据流

处理流

用于读取/写出基本数据类型的变量/字符串

DataInputStream

DataOutputStream

对象流

处理流/字节流

用于存取/读取基本数据类型数据/对象

不能序列化static/transient修饰的成员变量

序列化:将内存中的Java对象保存到磁盘中/通过网络传输

ObjectInputStream:读取(反序列化)

ObjectOutputStream:保存(序列化)

对象可序列化的要求:

  • 实现两个接口之一:
    • Serializable:
      • 对象所在类提供一个全局常量(序列版本号):serialVersionUID
      • 对象所在类的其它内部属性均可序列化(默认情况基本数据类型都可序列化)
    • Externalizable:
随机(任意)存取文件流

RandomAccessFile:

  • 声明在java.io
  • 直接继承java.lang.Object
  • 同时实现DataInput/DataOutput接口(输入/输出流)
  • 作为输出流时:
    • 写出文件不存在:自动创建
    • 写出文件存在:对原有文件内容进行覆盖(默认情况从头覆盖)
  • 创建实例需要指定一个mode参数来指定此对象的访问模式:
    • r:以只读方式打开
    • rw:打开以便读取和写入
    • rwd:打开以便读取和写入,同步文件内容的更新
    • Rws:打开以便读取和写入,同步文件内容和元数据的更新
  • long getFilePointer():获取文件记录指针的当前位置
  • void seek(long pos):将文件记录指针定位到pos位置

NIO

“New IO”/“Non-Blocking IO”

面向缓冲区,基于通道的IO操作

更高效的方式进行文件读写操作

NIO.2

JDK7

增强对文件处理/文件系统特性的支持

Path:替换原有File类

Paths:

Files:操作文件/文件目录的工具类

File类

一个对象代表一个文件/文件目录

声明在java.io包下

若要读取/写入文件内容,需要使用IO流

File类对象常会作为参数传递到流的构造器中指明读取写入的“终点”

JUnit单元测试法的相对路径在当前Module下

main方法的相对路径在当前Project下

构造器
1
2
3
File(String pathname):以pathname为路径创建File对象,可以是绝对路径或者相对路径
File(String parent,String child):以parent为父路径,child为子路径创建File对象
File(File parent,String child):根据一个父File对象和子文件路径创建File对象
方法

常量:

1
static final String separator:根据操作系统动态提供分隔符

获取:

1
2
3
4
5
6
7
8
String getAbsolutePath():获取绝对路径
String getPath():获取路径
String getName():获取名称
String getParent():获取上层文件目录路径。若无,返回null
long length():获取文件长度(即:字节数),不能获取目录的长度
long lastModified():获取最后一次的修改时间,毫秒值
String[] list():获取指定目录下的所有文件或者文件目录的名称数组
File[] listFiles():获取指定目录下的所有文件或者文件目录的File数组

重命名:

1
2
boolean renameTo(File dest):把文件重命名为指定的文件路径
//file1需存在且dest不存在

判断:

1
2
3
4
5
6
boolean isDirectory():判断是否是文件目录
boolean isFile():判断是否是文件
boolean exists():判断是否存在
boolean canRead():判断是否可读
boolean canWrite():判断是否可写
boolean isHidden():判断是否隐藏

创建:

1
2
3
4
5
6
7
boolean createNewFile():创建文件
//若文件存在,则不创建,返回false
boolean mkdir():创建文件目录
//如果此文件目录存在,不创建
//如果此文件目录的上层目录不存在,不创建
boolean mkdirs():创建文件目录。如果上层文件目录不存在,一并创建
//创建文件或者文件目录没有写盘符路径,默认在项目路径下

删除:

1
2
3
boolean delete():删除文件或者文件夹
//Java中删除不走回收站
//删除一个文件目录,该文件目录内不能包含文件或者文件目录

网络编程

InetAddress:此类的对象代表一个IP地址

  • 构造器私有化
  • 实例化方式:
    • getByName()
    • getLocalHost()

TCP

Socket

ServerSocket

客户端:

  1. 创建Socket对象,指明服务器IP及其端口号
  2. 获取输出流用于输出数据
  3. 写出数据
  4. 资源关闭

服务器:

  1. 创建ServerSocket对象,指明自己的端口号
  2. 调用accept()表示自己接收来自客户端的socket
  3. 获取输入流
  4. 读取数据
  5. 资源关闭

read()为阻塞式的方法,传完数据需要socket.shutdownOutput()指明数据发送完毕

UDP

DatagramSocket

DatagramPacket

反射

Reflection

通过反射可以调用类的私有化结构

Class实例对应加载到内存中的一个运行时类

特征:动态性

正常方式:

引入需要的“包类”名称

通过new实例化

取得实例化对象

反射方式:

实例化对象

getClass()方法

得到完整的包名

主要API

1
2
3
4
java.lang.Class:代表一个类
java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器

Class对象类型

Class:外部类/成员(成员内部类,静态内部类)/局部内部类/匿名内部类

interface:接口

[]:数组

enum:枚举

annotation:注解@interface

primitive type:基本数据类型

void

只要元素类型与维度一样,就是同一个Class

加载过程

  1. 程序经过javac.exe命令后会生成一个/多个字节码文件(.class结尾)
  2. 使用java.exe命令对某个字节码文件进行解释运行(将此字节码文件加载到内存中,此过程称为类的加载)
  3. 加载到内存中的类称为“运行时类”,次运行时类就作为Class的一个实例(类本身也是Class的对象/Class的实例对应着一个运行时类)
  4. 加载到内存中的运行时类会缓存一定的时间,在此时间之内可以通过不同的方式来获取此运行时类

获取Class实例

调用运行时类的属性
1
2
.class
Class<Person> class1 = Person.class;
通过运行时类的对象
1
2
Person p = new Person();
Class<? extends Person> class2 = p.getClass();
调用Class静态方法

(常用)

1
2
forName(String classPath)
Class<?> class3 = Class.forName(“cc.mousse.Person”);
使用类的加载器

(了解)

ClassLoader

1
2
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class<?> class4 = classLoader.loadClass(“cc.mousse.Person”);

读取配置文件

使用ClassLoader

方式1:

1
2
3
FileInputStream fileInputStream = new FileInputStream("x.properties");
properties.load(fileInputStream);
//此时文件默认在当前module下

方式2:

1
2
3
4
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("x.properties");
properties.load(inputStream);
此时文件默认在当前module的src下

创建运行时类对象

newInstance()

创建对应的运行时类的对象

内部调用了运行时类的空参构造器

运行时必须提供空参构造器且权限满足使用需求

获取结构

获取属性结构:

  • Field[] getFields():获取当前运行时类及其父类中声明为public的所有属性
  • Field[] getDeclaredFields():获取当前运行时类声明的所有属性(不包含父类)
    • int getModifiers():获取权限修饰符
      • 使用Modifier.toString(modifier)转为String型
    • Class getType():获取数据类型
    • String getName():获取变量名

获取方法结构:

  • Method[] getMethods():获取当前运行时类及其父类中声明为public的所有方法
  • Method[] getDeclaredMethods():获取当前运行时类声明的所有方法(不包含父类)
    • Annotation[] getAnnotations():获取注解(Runtime)
    • int getModifiers():获取权限修饰符
      • 使用Modifier.toString(modifier)转为String型
    • Class getReturnType():获取返回值类型
    • String getName():获取方法名
    • Class[] getParameterTypes():获取形参类型
    • Class[] getExceptionTypes():获取异常

获取构造器结构:

  • Constructor[] getConstructors():获取当前运行时类声明为public的所有构造器
  • Constructor[] getDeclaredConstructors():获取当前运行时类声明的所有构造器

获取父类:

  • Class getSuperclass()
  • 获取带泛型的父类:
    • Type getGenericSuperclass()
      • 获取带泛型的父类的泛型:
        • 使用(ParameterizedType)强转
        • Type[] getActualTypeArguments
1
2
3
4
5
Class class = Xxx.class;
Typer genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
String name = ((Class) actualTypeArguments[i]).getName();

获取接口:

  • Class[] getInterfaces()
  • Class[] getSuperclass().getInterfaces():获取其父类实现的接口

获取所在的包:Package getPackage()

获取注解:Annotation[] getAnnotations()

调用运行时类指定结构

属性

getField方法:(不常用)

  1. 创建运行时类的对象:aClass.newInstance()
  2. 获取指定的(public)属性:clazz.getField(String fieldName)
  3. 设置当前属性的值:set()
    • 参数1:设置哪个对象的属性(对象)
    • 参数2:设置此属性的值
  4. 获取当前属性的值:get()
    • 参数1:获取哪个对象的属性

getDeclaredField方法:(常用)

  1. 创建运行时类的对象:aClass.newInstance()
  2. 获取指定的属性:aClass.getDeclaredField(String fieldName)
  3. 设置当前属性是可访问的:fieldName.setAccessible(true)
  4. 获取/设置当前属性的值
方法

创建运行时类的对象:aClass.newInstance()

获取指定的某个方法:aClass.getDeclaredMethod()

  • 参数1:指明获取方法的名称
  • 参数2:指明获取方法的形参列表

设置当前属性是可访问的:method.setAccessible(true)

调用指定方法:invoke()

  • 参数1:方法调用者
  • 参数2:为形参赋值

返回值即为对应类中调用方法的返回值(没有返回值则为null)

构造器

获取指定构造器:aClass.getDeclaredConstructor()

  • 参数:指明构造器的形参列表

设置当前构造器是可访问的:constructor.setAccessible(true)

调用此构造器创建运行时类的对象:constructor.newInstance()

动态代理

代理模式:

  • 用一个代理将对象包装起来,用该代理对象取代原始对象
  • 任何对原始对象的调用都要通过代理
  • 代理对象决定是否/何时将方法调用转到原始对象上

静态代理:代理类和被代理类在编译期间就确定下来,不利于程序的扩展

动态代理:客户通过代理类调用其他对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象

  • 通过Proxy.newProxyInstance()实现根据加载到内存中的被代理类动态创建一个代理类及其对象
  • 通过InvocationHandler接口的实现类及其方法invoke()实现通过代理类的对象调用方法a时动态调用被代理类的同名方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class DynamicProxyTest {

public static void main(String[] args) {
//instance:代理类的对象
Human instance = (Human) ProxyFactory.getProxyInstance(new SuperMan());
//通过代理类对象调用方法时会自动调用被代理类中同名的方法
System.out.println(instance.getBelief());
instance.eat("pizza");
}

}

interface Human {

String getBelief();

void eat(String food);

}

//被代理类
class SuperMan implements Human {

@Override
public String getBelief() {
return "I believe i can fly";
}

@Override
public void eat(String food) {
System.out.println("Eat " + food);
}

}

//代理类工厂
class ProxyFactory {

/**
* @param o 被代理类的对象
* @return 代理类的对象
*/
public static Object getProxyInstance(Object o) {
Class<?> oClass = o.getClass();
InvocationHandler handler = new MyInvocationHandler(o);
//参数2:代理类和被代理类实现同一接口
return Proxy.newProxyInstance(oClass.getClassLoader(), oClass.getInterfaces(), handler);
}

static class MyInvocationHandler implements InvocationHandler {

//需要使用被代理类的对象进行赋值
private final Object o;

public MyInvocationHandler(Object o) {
this.o = o;
}

//当通过代理类的对象被调用方法时就会使用如下方法
//将被代理类的要执行的方法声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method即为代理类对象调用的方法,此方法也作为被代理对象要调用的方法
//此方法返回值即为当前类中的invoke()的返回值
return method.invoke(o, args);
}

}

}

Java8

Lambda表达式

匿名函数

一段可以传递的代码

Lambda作为函数式接口的实例

->:Lambda操作符/箭头操作符

  • ->左边:Lambda形参列表

    • 数据类型可以省略,由编译器推断得出(类型推断”)

      1
      Comparator<Integer> c2 = (o1, o2) -> Integer.compare(o1, o2);
    • 若只有一个参数,参数的小括号可以省略

      1
      Runnable r2 = () -> System.out.println("Runnable2");
  • ->右边:Lambda体

    • 若只有一条语句,return/大括号都可省略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class LambdaTest {

//无参无返回值
@Test
public void test1() {
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Runnable1");
}
};
r1.run();

Runnable r2 = () -> System.out.println("Runnable2");
r2.run();
}

//单参无返回值
@Test
public void test2() {
Consumer<String> c1 = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
c1.accept("a");

//类型推断:数据类型可省略,由编译器推断得出
Consumer<String> c2 = (s) -> {
System.out.println(s);
};
c2.accept("b");

//单参小括号可省
Consumer<String> c3 = s -> {
System.out.println(s);
};
c3.accept("c");


//Lambda体只用一条语句时,return和大括号都可省略
Consumer<String> c4 = s -> System.out.println(s);
c4.accept("c");

Consumer<String> c5 = System.out::println;
c5.accept("d");
}

//多参有返回值
@Test
public void test3() {
Comparator<Integer> c1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return Integer.compare(o1, o2);
}
};
System.out.println(c1.compare(1, 2));

Comparator<Integer> c2 = (o1, o2) -> {
System.out.println(o1);
System.out.println(o2);
return Integer.compare(o1, o2);
};
System.out.println(c2.compare(1, 2));

//Lambda体只用一条语句时,return和大括号都可省略
Comparator<Integer> c3 = (o1, o2) -> Integer.compare(o1, o2);
System.out.println(c3.compare(1, 2));

//方法引用
Comparator<Integer> c4 = Integer::compare;
System.out.println(c4.compare(1, 2));
}

}

函数式接口

“FunctionalInterface”

只声明了一个抽象方法

可在一个接口上使用@FunctionalInterface注解,这样可以检查它是否是一个函数式接口

匿名实现类都可以用Lambda表达式来写

实际开发中若需要定义函数式接口,可先查看JDK是否提供满足需求的函数式接口

四大核心函数式接口

Consumer:消费型接口

  • 对类型为T的对象应用操作:void accept(T t)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public void consumerTest() {
    happyTime(500.0, new Consumer<Double>() {
    @Override
    public void accept(Double aDouble) {
    System.out.println("money: " + aDouble);
    }
    });

    happyTime(600.0, money -> System.out.println("money: " + money));
    }

    private void happyTime(Double money, Consumer<Double> consumer) {
    consumer.accept(money);
    }

Supplier:供给型接口

  • 返回类型为T的对象:T get()

Function<T, R>:函数型接口

  • 对类型为T的对象应用操作并返回结果,结果是R类型的对象:R apply(T t)

Predicate:断定型接口

  • 确定类型为T的对象是否满足某约束并返回boolean值:boolean test(T t)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public void predicateTest() {
    List<String> list = Arrays.asList("a", "b", "c", "a");
    System.out.println(filterString(list, new Predicate<String>() {
    @Override
    public boolean test(String s) {
    return "a".equals(s);
    }
    }));

    System.out.println(filterString(list, s -> "a".equals(s)));

    System.out.println(filterString(list, "a"::equals));
    }

    //根据给定规则过滤集合中的字符串,规则由Predicate方法决定
    private List<String> filterString(List<String> list, Predicate<String> predicate) {
    List<String> filterList = new ArrayList<>();
    for (String s : list) {
    if (predicate.test(s)) {
    filterList.add(s);
    }
    }
    return filterList;
    }

引用

方法引用

当要传递给Lambda体的操作已经有实现的方法了,可以使用方法引用

方法引用与接口中的抽象方法的形参列表、返回值类型相同

方法引用就是Lambda表达式,Lambda表达式作为函数式接口的实例,所以方法引用也是函数式接口的实例

三种主要使用情况:

  • 对象::非静态方法名:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致

    1
    2
    3
    4
    5
    6
    Employee e = new Employee(1001, "Tom", 23, 5600);
    Supplier<String> s1 = () -> e.getName();
    System.out.println(s1.get());

    Supplier<String> s2 = e::getName;
    System.out.println(s2.get());
  • 类::静态方法名:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致

    1
    2
    3
    4
    5
    Comparator<Integer> c1 = (i1, i2) -> i1.compareTo(i2);
    System.out.println(c1.compare(1, 2));

    Comparator<Integer> c2 = Integer::compareTo;
    System.out.println(c2.compare(1, 2));
  • 类::非静态方法名:当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数) 时可用

  • // Comparator中的int compare(T t1,T t2)
    // String中的int t1.compareTo(t2)
    Comparator<String> comparator = String::compareTo;
    // Function中的R apply(T t)
    // Employee中的String getName();
    Function<Employee, String> function = Employee::getName;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    #### 构造器引用

    函数式接口的抽象方法的形参列表和构造器的形参列表一致,抽象方法的返回值类型即为构造器所属的类的类型

    类名::new

    ```java
    //Supplier中的T get()
    Supplier<Employee> supplier = Employee::new;
    //Function中的R apply(T t)
    Function<Integer, Employee> function = Employee::new;

数组引用

1
可以把数组看做是一个特殊的类,写法与构造器引用一致

数组类型[]::new

1
2
//Function中的R apply(T t)
Function<Integer, String[]> function = String[]::new;

StreamAPI

类似于使用SQL执行的数据库查询

Collection是一种静态的内存数据结构,Stream是有关计算的(与CPU打交道)

特点:

  • Stream自己不会存储元素
  • Stream不会改变源对象,会返回一个持有结果的新Stream
  • Stream操作是延迟执行的,会等到需要结果的时候才执行

流程

创建操作

一个数据源(集合、数组等),获取一个流

通过集合获取:

  • default Stream stream():返回一个顺序流
  • default Stream parallelStream():返回一个并行流
1
2
3
List<Employee> employees = EmployeeData.getEmployees();
Stream<Employee> stream = employees.stream();
Stream<Employee> parallelStream = employees.parallelStream();

通过数组获取:

  • 调用Arrays类的静态方法
  • static Stream stream(T[] array):返回一个流
1
2
3
4
5
6
int[] array = {1, 2, 3};
IntStream stream = Arrays.stream(array);
Employee employee1 = new Employee(1001, "Tom");
Employee employee2 = new Employee(1002, "Jerry");
Employee[] employees = {employee1, employee2};
Stream<Employee> empStream = Arrays.stream(employees);

通过Stream的of():public static Stream of(T… values):返回一个流

1
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

创建无限流:(了解)

  • 迭代:public static Stream iterate(final T seed, final UnaryOperator f)

    1
    Stream.iterate(0, i -> i + 2).limit(10).forEach(System.out::println);
  • 生成:public static Stream generate(Supplier s)

    1
    Stream.generate(Math::random).limit(10).forEach(System.out::println);
中间操作

一个中间操作链,对数据源的数据进行处理

筛选与切片:

  • filter(Predicate p):接收Lambda,从流中排除某些元素

    1
    employees.stream().filter(e -> e.getSalary() > 7000).forEach(System.out::println);
  • distinct():筛选,通过流所生成元素的hashCode()和equals()去除重复元素

    1
    employees.stream().distinct().forEach(System.out::println);
  • limit(long maxSize):截断流,使其元素不超过给定数量

    1
    employees.stream().limit(3).forEach(System.out::println);
  • skip(long n):跳过元素,返回一个扔掉了前n个元素的流

    • 若流中元素不足n个则返回一个空流
    • 与limit(n)互补
    1
    employees.stream().skip(3).forEach(System.out::println);

映射:

  • map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上并将其映射成一个新元素

    1
    2
    List<String> list = Arrays.asList("AA", "BB", "CC", "DD");
    list.stream().map(s -> s.toLowerCase(Locale.ROOT)).forEach(System.out::println);
  • flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

    • map(Function f)相当于list1.add(list2)
    • flatMap(Function f)相当于list1.addAll(list2)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     public void mapTest() {
    Stream<Stream<Character>> streamStream = list.stream().map(IntermediateTest::fromStringToStream);
    streamStream.forEach(s -> s.forEach(System.out::print));
    Stream<Character> characterStream = list.stream().flatMap(IntermediateTest::fromStringToStream);
    characterStream.forEach(System.out::print);
    }

    public static Stream<Character> fromStringToStream(String s) {
    List<Character> list = new ArrayList<>();
    for (Character c : s.toCharArray()) {
    list.add(c);
    }
    return list.stream();
    }

排序:

  • sorted():产生一个新流,按自然顺序排序
  • sorted(Comparator com):产生一个新流,按比较器顺序排序
1
2
3
4
5
6
7
8
9
10
11
employees.stream().sorted((e1, e2) -> e1.getName().compareTo(e2.getName())).forEach(System.out::println);
employees.stream().sorted(Comparator.comparing(Employee::getName)).forEach(System.out::println);

employees.stream().sorted((e1, e2) -> {
int compare = Integer.compare(e1.getAge(), e2.getAge());
if (compare != 0) {
return compare;
}
return Double.compare(e1.getSalary(), e2.getSalary());
}).forEach(System.out::println);
employees.stream().sorted(Comparator.comparingInt(Employee::getAge).thenComparingDouble(Employee::getSalary)).forEach(System.out::println);
终止操作

一旦执行终止操作就执行中间操作链并产生结果,之后不会再被使用

匹配与查找:

1
2
3
4
5
6
7
8
9
10
11
allMatch(Predicate p):检查是否匹配所有元素
anyMatch(Predicate p):检查是否至少匹配一个元素
noneMatch(Predicate p):检查是否没有匹配所有元素
findFirst():返回第一个元素
findAny():返回当前流中的任意元素
count():返回流中元素总数
max(Comparator c):返回流中最大值
min(Comparator c):返回流中最小值
forEach(Consumer c):内部迭代
//Collection接口需要用户去做迭代,称为外部迭代
//StreamAPI使用内部迭代(它帮你把迭代做了)

归约:

1
2
reduce(T identity, BinaryOperator b):将流中元素反复结合起来得到一个值,返回T
reduce(BinaryOperator b):将流中元素反复结合起来得到一个值,返回Optional<T>

收集:

1
2
collect(Collector c):将流转换为其他形式
//接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class TerminatingTest {

List<Employee> employees = EmployeeData.getEmployees();

@Test
public void matchTest() {
//allMatch(Predicate p):检查是否匹配所有元素
boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
System.out.println("allMatch: " + allMatch);

//anyMatch(Predicate p):检查是否至少匹配一个元素
boolean anyMatch = employees.stream().anyMatch(e -> e.getAge() > 18);
System.out.println("anyMatch: " + anyMatch);

//noneMatch(Predicate p):检查是否没有匹配所有元素
boolean noneMatch = employees.stream().noneMatch(e -> e.getAge() > 18);
System.out.println("noneMatch: " + noneMatch);

//findFirst():返回第一个元素
Optional<Employee> first = employees.stream().filter(e -> e.getAge() > 18).findFirst();
System.out.println("findFirst: " + first);

//findAny():返回当前流中的任意元素
Optional<Employee> any = employees.stream().findAny();
System.out.println("findAny: " + any);

//count():返回流中元素总数
long count = employees.stream().count();
System.out.println("count: " + count);

//max(Comparator c):返回流中最大值
Optional<Employee> max = employees.stream().max(Comparator.comparingInt(Employee::getAge));
System.out.println("max: " + max);

//min(Comparator c):返回流中最小值
Optional<Employee> min = employees.stream().min(Comparator.comparingInt(Employee::getAge));
System.out.println("min: " + min);

//forEach(Consumer c):内部迭代
}

@Test
public void reduceTest() {
//reduce(T identity, BinaryOperator b):将流中元素反复结合起来得到一个值,返回T
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(list.stream().reduce(0, Integer::sum));

//reduce(BinaryOperator b):将流中元素反复结合起来得到一个值,返回Optional<T>
System.out.println(EmployeeData.getEmployees().stream().map(Employee::getSalary).reduce(Double::sum));
}

@Test
public void collectTest() {
List<Employee> collect = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
collect.forEach(System.out::println);
}

}

Java9方法

1
2
3
4
5
6
7
8
takeWhile():返回从开头开始的按照指定规则尽量多的元素
dropWhile():返回剩余的元素(与takeWhile相反)
ofNullable():允许创建一个单元素Stream,可包含一个非空元素,也可以创建一个空Stream
//Java8中Stream不能完全为null
of()允许有多个null
ofNullable()只允许一个null
iterate()重载:可提供一个判断条件来指定什么时候结束迭代
Stream.iterate(1, i -> i < 100, i -> i + 1).forEach(System.out::println);

Optional类

避免空指针

创建Optional类对象的方法:

  • Optional.of(T t):创建一个Optional实例,t必须非空
  • Optional.empty():创建一个空的Optional实例
  • Optional.ofNullable(T t):t可以为null

判断Optional容器中是否包含对象:

  • boolean isPresent():判断是否包含对象
  • void ifPresent(Consumer<? super T> consumer):若有值,执行Consumer接口的实现代码并且该值会作为参数传给它

获取Optional容器的对象:

  • T get():如果调用对象包含值,返回该值,否则抛异常
    • 可与of()搭配使用
  • T orElse(T other):如果有值则将其返回,否则返回指定的other对象
    • 可与ofNullable()搭配使用
  • T orElseGet(Supplier<? extends T> other):如果有值则将其返回,否则返回由Supplier接口实现提供的对象
  • T orElseThrow(Supplier<? extends X> exceptionSupplier):如果有值则将其返回,否则抛出由Supplier接口实现提供的异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class OptionalTest {

private String getGirlName(Boy boy) {
//会出现空指针
return boy.getGirl().getName();
}

//优化后的方法
public String getGirlName(Boy boy) {
if (boy != null) {
if (boy.getGirl() != null) {
return boy.getGirl().getName();
}
}
return null;
}

//使用Optional类的方法
public String getGirlName(Boy b) {
Optional<Boy> boyOptional = Optional.ofNullable(b);
//boy一定非空
Boy boy = boyOptional.orElse(new Boy(new Girl("girl")));
Optional<Girl> girlOptional = Optional.ofNullable(boy.getGirl());
//girl一定非空
Girl girl = girlOptional.orElse(new Girl("girl"));
return girl.getName();
}

}

后续方法

boolean isEmpty():判断value是否为空

  • JDK 11

ifPresentOrElse(Consumer<?super T> action, Runnable emptyAction):

  • Value非空,执行参数1功能
  • value为空,执行参数2功能
  • JDK 9

Optional or(Supplier<?extends Optional<? extends T>> supplier):

  • value非空,返回对应的Optional;
  • value为空,返回形参封装的Optional
  • JDK 9

Stream stream():

  • value非空,返回仅包含此value的Stream
  • 否则返回一个空的Stream
  • JDK 9

T orElseThrow():

  • value非空,返回value
  • 否则抛异常NoSuchElementException
  • JDK 10

Java9

模块化系统

Jigsaw/Modularity

指定哪些部分可以暴露,哪些部分隐藏

在src中使用module-info.java定义:

  • exports:控制着哪些包可以被其它模块访问到
    • 所有不被导出的包默认都被封装在模块里面
  • requires:指明对其它模块的依赖

REPL工具

REPL:read-evaluate-print-loop

即写即得、快速运行

jShell命令

在JShell环境下语句末尾的“;”可选,推荐还加上提高代码可读性

变量/方法可覆盖

Tab可补全代码

钻石操作符改进

能够与匿名实现类共同使用钻石操作符

String存储结构变更

String不再使用char[]存储,改成byte[]加上编码标记,节约一些空间

StringBuffer/StringBuilder同理

InputStream加强

可使用transferTo()将数据直接传输到OutputStream

1
2
3
4
5
6
ClassLoader cl = this.getClass().getClassLoader();
try (InputStream is = cl.getResourceAsStream("hello.txt");
OutputStream os = new FileOutputStream(“src\\hello1.txt”) {
is.transferTo(os); // 把输入流中的所有数据直接自动地复制到输出流中
} catch (IOException e) { e.printStackTrace();
}

改进Nashorn

为Java提供轻量级的Javascript运行时

JDK9包含一个用来解析Nashorn的ECMAScript语法树的API

Java11被设为“过时的”

Java10

局部变量类型推断

局部变量的显示类型声明,常常被认为是不必须的

减少啰嗦/形式的代码,避免信息冗余,对齐变量名,更容易阅读

处理var时,编译器先是查看表达式右边部分,并根据右边变量值的类型进行推断,作为左边变量的类型,然后将该类型写入字节码当中

Var不是关键字,除了不能用它作为类名,其他的都可以

1
2
3
4
5
var num = 10;
var list = new ArrayList<>();
for (var v : list) {
System.out.println(v);
}

不能使用:

  • 初始值为null
  • Lambda表达式
  • 方法引用
  • 数组静态初始化
  • 方法的返回类型
  • 方法的参数类型
  • 构造器的参数类型
  • 属性
  • catch块

Java11

String新增方法

判断字符串是否为空白:” “.isBlank(); // true
去除首尾空白:” Javastack “.strip(); // “Javastack”
去除尾部空格:” Javastack “.stripTrailing(); // “ Javastack”
去除首部空格:” Javastack “.stripLeading(); // “Javastack “
复制字符串:”Java”.repeat(3);// “JavaJavaJava”
行数统计:”A\nB\nC”.lines().count(); // 3

局部变量类型推断升级

错误的形式:

1
2
//必须要有类型,可以加上var
Consumer<String> con1 = (@Deprecated t) -> System.out.println(t.toUpperCase());

正确的形式:

1
2
//使用var的好处是在使用lambda表达式时给参数加上注解
Consumer<String> con2 = (@Deprecated var t) -> System.out.println(t.toUpperCase());

HTTP客户端API

使用HttpClient替换HttpURLConnection

更简化的编译运行程序

通过java命令直接编译运行文件

执行源文件中的第一个类, 第一个类必须包含主方法

不可以使用其它源文件中的自定义类

废弃Nashorn引擎

废除Nashorn javascript引擎,有需要的可以考虑使用GraalVM

ZGC

实验性的

优势:

  • GC暂停时间不会超过10ms
  • 既能处理几百兆的小堆,也能处理几个T的大堆(OMG)
  • 和G1相比,应用吞吐能力不会下降超过15%
  • 为未来的GC功能和利用colord指针以及Load barriers优化奠定基础
  • 初始只支持64位系统

其它新特性

Unicode 10

Deprecate the Pack200 Tools and API

新的Epsilon垃圾收集器

完全支持Linux容器(包括Docker)

支持G1上的并行完全垃圾收集

最新的HTTPS安全协议TLS 1.3

Java Flight Recorder(相当于黑匣子)


Java
http://docs.mousse.cc/JavaSE/
作者
Mocha Mousse
发布于
2025年5月26日
许可协议