Java基础

本文最后更新于:2023年2月28日 下午

全局介绍

JVM/JRE/JDK

image-20220316094437686

命名

  • .xml文件

    • groupId: 域名(顶级域名.公司.组织.个人.功能板块)

    • artifactId: 功能模块

  • 命名规范

    • 包:域名(顶级域名.公司.组织.个人.功能板块)

    • 类:大驼峰

    • 变量:小驼峰

    • 常量:大写下划线连接

机制

  • 引用机制

​ 基本数据类型:值传递(不与其它变量共享值)

​ 变量:引用传递(传递存储地址)

  • 垃圾回收机制

​ JRE自动销毁对象释放内存空间

基本语法

关键字f/s/a/d/t

关键字 意义 补充
final 声明对象构建之后,值不可变 可用于声明不被继承的类/不被重写的方法/引用不被更改的变量
static 声明静态域、静态方法 静态域(静态变量/静态常量):所有对象共享,内存中占一份
静态方法:不需要实例化,可直接调用,与对象无关
静态导入:如import static java.lang.System -> System.out.println = out.println直接使用类的静态方法和静态域
abstract 抽象类/方法 抽象类:含有一个或多个抽象方法的类,不能创造实例对象,可以被子类继承并重写其抽象方法
default 默认
this 当前对象的引用 可以通过this,在实例方法或构造函数中引用当前对象的成员变量

关于抽象类的代码示例:

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
80
81
82
83
/* Person抽象类中存在 getDescription抽象方法
Person抽象类被Employee、Student子类继承 */

public class PersonTest
{
public static void main(String[] args)
{
Person[] people = new Person[2]; //抽象类可创建实例数组,数组中的元素需为非抽象子类实例

people[0] = new Employee("Harry Hacker", 50000198910, 1);
people[1] = new Student("Maria Morris",computer science' );

for (Person p : people)
System.out.println(p.getNameO + ", " + p.getDescription());
// 若省略抽象类Person中的getDescription抽象方法,则无法调用p对象的getDescription方法
}
}

public abstract class Person
{
public abstract String getDescription();
private String name;

public Person(String name)
{
this.name = name;
}

public String getName()
{
return name;
}
}

public class Employee extends Person
{
private double salary;
private Loca1Date hireDay;

public Employee(String name, double salary, int year, int month, int day)
{
super(name);
this.salary = salary;
hireDay = LocalDate.of(year, month, day);
}

public double getSalary()
{
return salary;
}

public LocalDate getHireDay()
{
return hireDay;
}

public String getDescription()
{
return String. format("an employee with a salary of $%.2f", salary);
}

public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
}

public class Student extends Person
{
private String major;

public Student(String name, String major)
{
super(name);
this.major = major;
}

public String getDescription()
{
return "a student majoring in " + major;
}
}

访问限制修饰符

Access level modifiers

关键字 意义 补充
public 所有类可见 top level,即独立的源文件,只有类/接口/枚举/注解,支持创建为顶级的独立源文件,不存在顶级的方法/变量/常量等
private 本类可见 member level,类的私有静态变量,类的每一个对象内部均可访问,但外部无法访问
protected 本包/所有子类可见 member level
package-private 无修饰符的默认声明,仅本包内可访问 top level/ member level,Package级允许源文件名与类型名称不同(但是禁止使用),Public级别则不允许

声明

  • 包声明 package

  • 类声明 class

  • 方法声明

    image-20220318105228328

    方法签名:方法名称/参数列表(参数个数/参数类型)组成,其不同保证编译成功

    方法重载:同名不同参方法

    1. 访问限制修饰符

    2. 返回值类型

    无返回值:void

    1. 方法名

    2. 参数列表

      可变长度参数: <数据类型>…

      image-20220318112205842

    3. {<代码块>}

数据类型

Java的变量类型分为2种。基本类型(原始类型)和引用类型。

Java提供了8种基本类型。6种数字类型(4个整数型,2个浮点型),1种字符类型,还有1种布尔型。

  • 十进制,表示指数 10^2 = e2
  • 十六进制,前缀0x,表示指数 0.125 = 2^-3 = 0x1.0p-3
  • 支持下划线 ‘_’ 分隔提高代码可读性
Type Def Remark Cate
byte 8位整数 4个整数型
short 16位整数 4个整数型
int 32位整数 4个整数型
long 64位整数 后缀L 4个整数型
float 单精度浮点 后缀f/F 2个浮点型
double 双精度浮点 后缀d/D,可省略 2个浮点型
boolean 布尔 仅具有true/false,无法与0/非0整型替换 1种布尔型
char 字符 单字符,单引号声明 1种字符类型
String 字符串 多字符,双引号声明 非基本数据类型,封装类,字符串类型
enum 枚举 自定义,包含有限个命名的值 非基本数据类型

关于常量(不可变对象)与变量

1
2
3
4
5
6
7
8
9
10
// 1. String
String str="abc";
System.out.println(str);
str=str+"de";
System.out.println(str);

// 2. StringBuilder
StringBuilder stringBuilder=new StringBuilder().append("abc").append("de");
System.out.println(str);
System.out.println(stringBuilder.toString());

以上代码的运行逻辑为:

如果运行这段代码会发现先输出“abc”,然后又输出“abcde”,好像是str这个对象被更改了,其实,这只是一种假象罢了,JVM对于这几行代码是这样处理的,首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。

String的可变数据类型
StringBuilder 不支持并发操作,线程不安全,单线程下速度快
StringBuffer 支持并发操作,线程安全,适用于多线程

如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全

类型转换

  • 小向大转换,可直接转换 (例如int to double)
  • 大向小转换,必须声明强制转换 (例如double to int)

image-20220423164942004

  • 当操作数的类型不同时,自动向高精度类型转换

数组

两种创建方式

  • 创建指定长度的空数组(new)

    image-20220316101920000

  • 创建确定元素的数组

image-20220316102006991

多维数组创建

image-20220415134242148

image-20220415134256623

多维数组元素访问

image-20220415134345359

运算符

operations mean
++i i +=1; return i
i++ return i; i +=1
&&
||
^ 异或
?: 三目运算符(条件判断?真:假)

控制流

statement use mean
for for(初始条件; 终止条件; 循环步长){} 循环
forEach for(变量 : 数组){} 增强型循环
while while(终止条件){} 循环
do/while do{}while(终止条件) 循环
if/then/else ;if/else

面向对象

关键字

关键字 意义
extends 子类 extends 超类
implements 类 implements 接口
super 代表当前类的超类,指示编译器调用超类方法/构造器 不能理解为是指向父类的引用,虽然实例化时会调用超类构造函数,但并不会创建超类对象。因此,不存在超类对象。
image-20220416160610090

构造器

1
JAVA使用构造器(consturctor)这一特殊方法构造实例、初始化对象

构造器的创建

  • 编译器自动为没有显式声明构造函数的类,创建一个无参构造函数(no-argument constructor)
    但,当类显式声明了有参构造函数,编译器将不再自动创建无参构造函数
  • 一个类可以声明多个构造函数基于不同构造函数创建对象并初始化属性值
  • 在构造函数中,通过super调用超类构造函数语句必须置于构造函数第一行;父类无无参构造函数必须显示调用父类有参构造函数

构造器的使用

  • 构造器名字与类名相同,使用时在构造器前面加上 new 操作符构造一个对象

    image-20220415140850631

  • 为了构造的对象多次使用,通过变量引用此对象。new操作符为对象分配内存,并返回该对象的内存地址给变量

    image-20220415141021653

  • 数组初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /* 整型 */
    int[] a; //只声明数组a,没有初始化
    int[] a = new int[5]; //初始化为默认值,int/byte/short/long型为0
    int[] a = {1,2,3,4,5}; //初始化为给定值
    int[] a = new int[] {1,2,3,4,5}; //初始化为给定值
    int[] a = new int[5] {1,2,3,4,5}; //错误,如果提供了数组初始化操作,则不能定义维表达式

    /* 布尔型 */
    boolean[] boolArray=new boolean[5]; // 初始化为默认值,bool型为false

    /* 浮点型 */
    double[] doubles = new double[5]; // 初始化为默认值,float/double型为0.0

实例化

类加载实例化过程(Order of Initialization)

  1. 首先,基于类的加载顺序(先加载子类的父类),完成类的加载

    类的加载:执行静态相关操作。即,按顺序初始化static变量(置于堆空间的静态区共享使用),执行static代码块。

  2. 其次,基于类的加载顺序(先加载子类的父类),执行类中初始化属性操作

    初始化属性:按顺序执行属性声明及构造函数。

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
//示例
public class Father {
// 父类构造函数
public Father() {
System.out.println("Father: Constructor.");
}

// 父类静态变量
private static String S = create();
private static String create() {
System.out.println("Father: Static method create().");
return ""
}

// 父类静态方法
public static void getS() {
System.out.println("Father: Static method getS().")
}
}

public class Son extends Father {
// 子类构造函数
public Son() {
System.out.println("Son: Constructor.");
}

// 子类静态代码块
static {
System.out.println("Son: Static block.")
}

// 子类静态方法
public static void getS() {
System.out.println("Son: Static method getS().")
}

public static void getSon() {
System.out.println("Son: Static method getSon().")
}
}

public class PropTest {
private Father father; // 仅声明属性,初始化该类时不会加载被声明类
private Son son = new Son(); // 声明属性被初始化时,初始化该类会依次进行声明类静态相关操作和初始化操作

public Son() {
System.out.println("PropTest: Constructor.");
}

public static void getS() {
System.out.println("PropTest: Static method getS().")
}
}

public class Test {
public static void main(String[] args) {
new Father(); // 实例化父类对象,先执行父类static相关操作,再执行构造函数
new Son(); // 实例化子类对象,先执行父类然后子类static相关操作,再执行父类然后子类的构造函数
Father.getS(); // 调用父类静态方法,执行父类static相关操作
Son.getSon(); // 调用子类静态方法,执行父类然后子类static相关操作
}
}

访问器与更改器

由于对类属性的封装,其数据改变需要编写对外的访问器方法。

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
//示例
private int speed;
private bool moving;

//getter 获取属性
public int getSpeed(){
return speed
}
/*
1.返回类型与原类型保持一致
2.命名规范 get
*/

//setter 改变属性
public void setSpeed(int speed){
return speed
}
public void isMoving(bool moving){
this.moving = moving
}
/*
1.参数类型与原类型保持一致
2.命名规范 get/is(bool)
3.this指代对象
*/

继承

  • 每个类只能拓展于一个子类
  • 子类必须满足超类特性。因此,逻辑上,能够构造初始化子类的前提,是必须能够构造初始化超类

image-20220424113333057

  • overriding/hiding:

    支持在子类中声明一个与超类中方法签名相同的,新实例方法,从而overriding覆盖超类方法(方法的重写),重写方法的访问范围,必须大于等于超类声明的范围

    支持在子类中声明一个与超类中方法签名相同的,新的静态方法,从而hiding隐藏超类静态方法

多态

–作业–

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
//此题声明的接口/类,为以下所有选择题提供支持
public interface I {
void getI();
}

//F 实现 I 接口
public class F implements I {
public void getF() {
System.out.println("F getF");
}
@Override
public void getI() {
System.out.println("F getI");
}
}

//S 继承 F 父类
public class S extends F {
public void getS() {
System.out.println("S getS");
}

@Override
public void getF() {
System.out.println("S getF");
}

@Override
public void getI() {
System.out.println("S getI");
}
}
//没有结果,无法编译/执行的为,错误

向上转型

向上转型(upcasting),(隐式转换,implicit casting)

  • 隐式的上转型,子类一定具有超类的特性,因此编译器可自动实现类型转换
  • 接口不能直接实例化,但是可以通过向上转型将其实现类实例化。
  • 向上转型是将子类对象赋给父类引用,限制了子类对象的行为类型,只能够调用子类重写的方法
1
2
3
4
5
6
7
/*
接口不能直接实例化
*/

I i = new I();
i.getI();
//错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
向上转型 upcasting,
把子类对象赋给父类引用,
即赋值左边为父类 右边为子类。
*/

I i = new F();
i.getI();
//F getI
//声明时即向上转型

F f = new F();
I i = f;
i.getI();
//F getI
//先声明,后向上转型
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
/*
向上转型只能够调用子类重写的方法,
不能调用子类独有方法。
*/

I i = new F();
i.getF();
//错误

F f = new F();
I i = f;
i.getF();
//错误

S s = new S();
F f = s;
f.getS();
//错误

I i = new S();
i.getI();
//S getI

S s = new S();
F f =s;
I i = f;
i.getI();
//S getI

S s = new S();
F f = s;
f.getF();
//S getF

向下转型

向下转型(downcasting),(显式转换,explicit casting)

  • 显式的下转型,超类未必一定是子类,因此需显式声明强制转换,且只有运行时才能知道错误
  • 即父类强转子类,是将父类引用的子类对象重新赋给子类引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
向下转型
父类对象不能直接赋给子类引用
只有父类引用的子类对象可以重新赋给子类引用
*/

F f = new F();
S s = (S) f;
s.getS();
//错误

I i = new S(); // 子类S的对象赋给父类I的引用
F f = (F) i; // 子类S对象的父类I引用赋给子类F的引用
S s = (S) f; // 子类S对象的父类I引用的子类F的引用赋给子类S的引用
s.getI(); // 本质一直都是子类S的对象
//S getI

I i = new F();
F f = (F) i;
S s = (S) f;
s.getI();
//错误
//本质是子类F的对象 对于F的子类S来说是父类对象 不能向下转型

接口

  • 一个接口可以扩展自任意数量的接口,一个类可以实现多接口

  • 仅可包含方法(抽象方法,默认方法和静态方法)与常量(公有常量)

    • 方法与常量默认声明为(无需重复编写):public static final,可使用default声明默认方法,支持package修饰符(顶级)
  • 由普通的类来实现接口,必须将接口所有抽象方法重写

    由抽象类来实现接口,则接口的方法可根据需要选择是否重写。

    继承实现接口的抽象类时,子类必须重写抽象类未实现的方法。

    抽象类的作用即是:
    将接口中不需要使用抽象方法交给抽象类完成,这样接口实现类只需要对接口需要方法进行重写,以降低接口实现类对接口实现过程难度

    graph LR;
    	Servt实现类 --extends---> HttpServlet抽象类 --extends---> GenericServlet抽象类 --extends---> Servlet接口
    
  • 定义了一个接口,就是定义了一个可以引用的类型,像类一样,在任何需要的地方作为类型使用

  • 接口不能直接实例化,但是可以通过向上转型将其实现类实例化。

抽象类

抽象类是含有一个或多个抽象方法的类。

  • 抽象类里可以有抽象方法也可以有普通方法

  • 抽象类中可以有构造器,但是不能创造实例对象

  • 抽象类的子类都必须重写父类中全部的抽象方法,而不必需重写其普通方法

  • 抽象方法abstract不能与哪些关键字共存

    privateprivate修饰的方法只能在本类中使用,而抽象方法要求必须让子类实现,两者相互矛盾
    finalfinal修饰的方法不能被重写,而抽象方法必须要被重写,
    staticstatic修饰的方法可以通过类名直接调用,而抽象方法是没有方法体的

匿名内部类

匿名内部类,没有名字、不被复用的内部类,通常用来简化代码编写。能够同时的一步到位的,声明和创建一个实现/继承的接口/抽象类的实现类和对象。

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
// 实例1:抽象类的匿名内部类
abstract class Person {
public abstract void eat();
}

public class Demo {
public static void main(String[] args) {
Person p = new Person() {
public void eat() {
System.out.println("eat something");
}
};
p.eat();
}
}

// 实例2:接口的匿名内部类
interface Person {
public void eat();
}

public class Demo {
public static void main(String[] args) {
Person p = new Person() {
public void eat() {
System.out.println("eat something");
}
};
p.eat();
}
}

函数式编程

Lambda函数

匿名函数组成

简写的函数

image-20220430205825565

匿名函数特点

  • 匿名。省略修饰符/返回类型/名称。
  • 函数。不属于任何类,有函数体/(参数列表/返回值)。
  • 传递。匿名函数可作为参数传递给方法,或作为变量的值

方法引用

双冒号(::)操作符是 Java 中的方法引用。 使用一个方法的引用时,目标引用放在 :: 之前,目标引用提供的方法名称放在 :: 之后,即 目标引用(类)::方法(类中的方法)

1
2
// e.g. 引用Person类中的getAge()方法
Person::getAge;

函数式接口

函数式接口,能且只能包含1个抽象方法的接口。用@FunctionalInterface注解标记(可以省略)。

可以使用Lambda表达式创建一个函数式接口的对象,此对象可以用于传参/赋值,如 Stream/Optional 中的一些方法以函数为参数都是基于函数式接口实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 自定义函数式接口
@FunctionalInterface
public interface MyFunction {
int calcInt(int x);
}

// 函数式接口应用
public class MyList() {
private List<Integar> list;
public void forEach(MyFunction f) {
for (Intefar i : list) {
int res = f.calcInt(i);
System.out.println(res);
}
}
}

// 应用
MyList ls = new MyList(List.of(4,6,7))
// 1.声明实现函数逻辑的对象
MyFunction f = x -> return x*x;
ls.forEach(f);
// 2. 直接传入lambda函数
ls.forEach(x -> return x*x)

集合

集合是存储元素对象的容器。

集合框架

集合框架,用于表示和操作集合的体系结构,包含

  • 接口(Interfaces):表示集合的抽象数据类型
  • 实现(Implementations):集合接口的具体实现类,由不同的数据结构实现
  • 算法(Algorithms):对集合执行搜索/排序等操作

即,Java提供了一套包含,多种集合类型,多种数据结构实现,以及操作处理算法的集合框架,供开发人员直接使用

image-20220430113343704

LIST

graph LR;
0[java.util.List/接口] --基本实现类--- java.util.ArrayList/类
0 --基本实现类--- java.util.LinkedList/类
接口/类 特点 方法
java.util.List 有序,允许重复元素
集合类型常量不可改变集合结构,如增/删集合元素将引发异常;可以替换元素,元素对象属性值可以改变
集合为逻辑上的容器,容器中仅保存元素对象的引用地址;操作集合中的元素时,实际操作的是元素引用的对象
of():静态方法,返回空列表对象
of(elements…):静态方法,基于指定元素创建集合对象
add(int index, E element) : 将元素插入到指定索引处
set(int index, E element) :将指定索引处元素替换
get(int index):获取指定索引处元素
getName(): 获取元素名
remove(int index): 移除指定索引处元素
toArray(T[] a):列表转换为数组。创建0长度数组实例即可,方法内部仅将自动调整
java.util.ArrayList 基于对象数组数据结构的实现
java.util.LinkedList 基于双向链表数据结构的实现 为每个元素创建2个节点,保存前/后元素的地址

声明与创建

image-20220430114718891

SET

Set集合,不包含重复元素

graph LR;
	0[java.util.Map/接口] --基本实现类--- java.util.HashSet/类;
	0 --基本实现类--- java.util.LinkedHashSet/类;
	0 --基本实现类--- java.util.TreeSet/类
接口/类 特点 方法
java.util.Set 不包含重复元素,可实现迭代器
添加重复元素不改变集合也不报错
均无基于索引的操作方法
Set.of() 返回空集合对象/集合不可变
Set.of(elements…) 基于指定元素创建集合对象
add(E element))
java.util.HashSet 元素无序 getName() 无序获取
java.util.LinkedHashSet 元素有序
java.util.TreeSet 元素有序

ITERATOR

Iterator接口,迭代器,帮助集合完成遍历与移除,通过移动游标进行操作。

方法 作用
image-20220430200156100
获取集合对象的迭代器
next() 向后移动游标后,返回游标指向的元素
hasNext() Iterator中是否有下一个元素
remove() 从集合移除当前游标指向的元素

示例

image-20220501150853634

MAP

Map不是集合;不继承于Iterable接口,不支持forEach遍历;不支持基于索引的操作

image-20220430190455855

graph LR;
	0[java.util.Map/接口] --基本实现类--- java.util.HashMap/类
	0 --基本实现类--- java.util.TreeMap/类
方法 作用
image-20220430191452490 声明与创建,key必须是唯一的,且每个key只能对应一个value。添加key-value时,如果key已经存在,则后一个覆盖前一个
put(K key, V value) 保存键值对
get(K key) 基于key获取对应的value,如果value不存在,返回null
of(K,V,K,V...) 静态方法,基于指定元素创建键值对对象

泛型

泛型,即**“参数化类型”**,是以类或接口为参数的类型参数,用<>声明,只接受引用类型,不接受基本类型。

泛型类与泛型方法

泛型类

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
public class GenericsAccept<T> {

// 类中,T为泛型形参
private T t;

public void set(T t) {
this.t = t;
}

public T get() {
return t;
}

public static void main(String[] args) {

// 实例化时,传入泛型的类型实参 Integer/String
GenericsAccept<Integer> integerG = new Box<Integer>();
GenericsAccept<String> stringG = new Box<String>();

integerG.add(new Integer(10));
stringG.add(new String("菜鸟教程"));

System.out.printf("整型值为 :%d\n\n", integerG.get());
System.out.printf("字符串为 :%s\n", stringG.get());
}
}

/* 输出
整型值为 :10

字符串为 :菜鸟教程
*/

泛型方法

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
public class GenericMethod {
// 泛型方法 printArray,<E>为集合类型形参,传入方法的参数由参数类型E[]和参数形参变量名构成
public static <E> void printArray (E[] inputArray) {
// 输出数组元素
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}

public static void main(String args[]) {
// 创建不同类型数组: Integer, DoubleCharacter
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };

System.out.println( "整型数组元素为:" );
printArray( intArray ); // 传递一个整型数组

System.out.println( "\n双精度型数组元素为:" );
printArray( doubleArray ); // 传递一个双精度型数组

System.out.println( "\n字符型数组元素为:" );
printArray( charArray ); // 传递一个字符型数组
}
}

/* 输出
整型数组元素为:
1 2 3 4 5

双精度型数组元素为:
1.1 2.2 3.3 4.4

字符型数组元素为:
H E L L O
*/

继承和子类型

graph LR;
	0[子类型关系] --- 子类关系;
	0 --- 元素间拥有继承关系的数组

赋值支持子类型关系,即等号右侧必须是左侧的子类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Animal {}
class Dog extends Animal {}

public class Test {
public static void main(String[] args) {
//编译成功,数组的元素存在继承关系,可通过赋值实现隐式类型转换。
Dog dogs1 = new Dog[5];
animals1 = new Animal[5];
animals1 = dogs1;

//编译错误,List<Dog>和List<Animal>平级,均继承自Object类,无子类型关系不得赋值。
List<Dog> dogs2 = new ArrayList<>();
List<Animal> animals2 = new ArrayList<>();
animals2 = dogs2;
}
}

通配符

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
// 为实现 List<Animal> list = new ArrayList<Dog>(),引入通配符

/* 上界通配符 < ? extends E>
用 extends 声明,表示参数化的类型是指定类型E的子类 */

List<? extends Animal> list = new ArrayList<Dog>()

/* 下界通配符 < ? super E>
用 super 声明,表示参数化的类型是指定类型E的父类型,直至 Object */

public class ShiftTest {
private <T> void test(List<? super T> fatherList, List<T> sonList){
for (T t : sonList) {
fatherList.add(t);
}
}
}

public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = new ArrayList<>();
new ShiftTest().test(animals,dogs);
}


通配符/标记符(常用于标记泛型形参) 释义
E Element (在集合中使用,因为集合中存放的是元素)
T Type(类)
K Key(键)
V Value(值)
N Number(数值类型)
? 不确定类型
不能保证参数一致性,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。

集合流

集合流不是存储元素的数据结构,而是操作集合元素的管道。仅会对集合中的元素进行操作,不影响源集合结构。

STREAM

方法 作用 操作类型
image-20220430210353334
获取集合对象的集合流 Initial Operations
初始化操作
collect() 聚合操作结果 Terminal Operations
终止操作
image-20220501113958190forEach() 迭代集合流的每个元素 Terminal Operations
终止操作
image-20220501115049997filter() 基于参数选择集合流中的元素,过滤
参数表达式结果必须为布尔值,为真置于新集合流,为假过滤掉
Intermediate Operations
中间操作
image-20220501115812867image-20220501120015607map() 基于条件将集合流中的元素映射为新类型元素,映射 Intermediate Operations
中间操作
sorted() 排序集合流中的元素,排序 Intermediate Operations
中间操作
count() 获取集合流中元素个数,计数 Intermediate Operations
中间操作
findFirst() 从流中取第一个符合条件元素,封装到Optional
java.lang.Comparator
Comparator类image-20220501144047875image-20220501144115869
比较器
comparing(),基于参数表达式排序
reversed(),倒序
结合sorted()
java.util.stream.Collectors
Collectors类
image-20220501144727820
用于操作聚合结果的工具类
toList()/toSet(),转换聚合结果为指定数据引用类型
groupingBy(),按传入的表达式参数以MAP形式分组
toMap(K,V),基于给定键值以MAP形式分组
结合collect()

OPTIONAL

java.util.Optional<T>,为解决空引用异常引入的,用于封装单值元素的容器(single-value container)。

方法 作用 操作类型
Optional<T> Optional.of(T value) 基于必不为空对象,创建optional容器,注入为空元素将抛出NullPointerException异常 Initial Operations
初始化操作
image-20220501173204128Optional<T> Optional.ofNullable(T value) 基于可能为空的对象,创建optional容器 Initial Operations
初始化操作
ifPresent(action) 当optional容器不为空时,执行指定函数;为空忽略执行image-20220501173614108仅检测指定元素对象,但嵌套对象为空时依然异常 Intermediate Operations
中间操作
ifPresentOrElse(action, emptyAction) 当容器不为空时执行第一个函数;为空执行第二个函数image-20220501173719316 Intermediate Operations
中间操作
.filter()
.map()
与Stream中一致 Intermediate Operations
中间操作
.orElse() 如果Optional实例有值则将其返回,否则返回orElse方法传入的参数 Terminal Operations
终止操作

操作链

中间操作执行后,将结果置入一个新集合流,从而允许基于新集合流实现后续操作,形成集合流的操作链。

1
2
3
4
5
6
7
8
9
10
// Optional操作链
Optional<USB> = Optional.ofNullable(usb)
.map(USB::getVersion)
.ifPresent ( System. out: : println);

// Stream操作链
List<Apple> = apples.stream()
.filter(a -> a.getWeight() >= 50)
.map(a::getColor())
.collect(collectors.toList());

异常

异常,是发生在程序执行过程中的,扰乱了程序正常流程的事件。当方法执行中发生错误时,该方法创建一个对象并将其传递给运行时系统(runtime system),该对象称为异常对象(exception object)。创建一个异常对象,并将其交给运行时系统,称为抛出异常(throwing an exception)

异常类型

相关类

java.lang.Throwable类

方法:

  • String getMessage():返回异常信息字符串
  • printStackTrace():打印异常栈信息

类型

graph LR;
	0[错误/异常类型] --- 1[受检异常Checked Exceptions];
	1 -.- 1.1[程序与程序控制之外的外部资源互交时产生的错误];
	1 -.- 1.2[如文件无法读取/数据库无法访问/网络无法连接等]
	0 --- 2[非受检异常Unchecked Exceptions];
	2 -.- 2.1[程序中的错误逻辑/代码];
	2 -.- 2.2[如空指针异常/数组集合索引越界等];
	0 --- 3[程序错误Error];
	3 -.- 3.1[程序外部在特殊条件下产生];
	3 -.- 3.2[如系统内存溢出/磁盘损坏]

异常处理

异常处理流程

  • 调用栈(Call Stack):开始于主函数,终止于发生错误的方法的方法列表

  • 异常处理程序(exception handler):处理异常的代码块。在调用栈中反序检索得到 (如catch{})

  • 捕获异常(catch the exception):系统在调用栈中找到适当的异常处理程序 (若未找到程序终止)

    image-20220602202640394

显示处理

可能抛出**受检异常(CheckedException)**的方法,必须显式捕获/抛出异常,否则无法编译。

非受检异常(UncheckedException)无需显式捕获处理,将终止程序,直接抛出异常并打印异常栈信息。

  1. try catch 显示捕获处理异常

    每个catch块都是一个异常处理程序,负责处理指定的异常。

    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
    // try-catch 模块
    try {
    // 可能抛出受检异常的代码块
    } catch (ExceptionType name | ExceptionType name) {
    // 出现异常类型1执行的操作
    } catch (ExceptionType name) {
    // 出现异常类型2执行的操作
    }

    // 示例
    try {
    Files.readString(Path.of(path))
    } catch (IOException e) {
    System.out.println(e.getMessage()); // 返回异常信息字符串
    e.printTraceStack; // 打印异常栈信息
    }

    // finally 模块
    /* 无异常时,执行try块代码 -> 执行finally块代码 -> tryreturn语句 / 继续执行块外代码
    引发异常时,从try块异常处 -> catch块代码 -> 执行finally块代码 -> catch内return语句 /继续执行块外代码

    finally块可用于避免由于方法返回、方法执行异常而意外绕过关闭资源对象代码
    仅用于释放资源,禁止参与业务逻辑的处理,块内禁止使用return语句 */

    public static String getTryCatchFinally() {
    try {
    // 可能抛出受检异常的代码块
    System.out.println("try块内异常前");
    Files.readString(Path.of(path);
    System.out.println("try块内异常后");
    return "try中结果”;
    } catch (IOException e) {
    // 出现异常类型执行的操作,不出现异常将被忽略
    System.out.println ("catch块内");
    } finally {
    //不出现异常,在执行return前,程序将跳转finally模块执行,无return语句时将顺序执行之后的代码
    System.out.println("finally块内”);
    }
    System.out.println("finally块后”);
    return "finally后结果”;
    }
  2. throws/throw 抛出异常

    可以在 构造函数/静态方法/实例方法/方法内的代码块/Lambda表达式 等中声明抛出异常

    throws子句,使当前方法抛出异常,交由调用者处理异常。

    throw声明,抛出一个异常类的异常实例,跳出当前流程

    使用throw抛出受检/自定义异常E,则其所在的函数必须使用throws关键字抛出E或E的父类异常。

    构造函数/静态/实例方法/方法内的代码块/Lambda表达式等中可声明抛出异常。其中抽象方法抛出异常时。重写抽象方法的实例方法才可抛出异常。

    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
    // throws子句 ; throw声明
    methodname throws Exception1,Exception2,..,ExceptionN { }

    // 示例1
    public static void getThrows() throws IOException{
    Files.readString(Path.of(path);
    }

    public static void main(String[] args) {
    try {
    getThrows();
    } catch(IOException e) {
    System.out.println(e.getMessage());
    }
    }

    // 结合自定义异常
    // 自定义异常:通过继承相关异常类实现
    /* 继承RuntimeException异常类
    实现自定义非受检异常
    调用RuntimeException构造函数
    实现对自定义异常的相关操作 */
    public class MyException extends RuntimeException{
    public MyException (String message) {
    super(message);
    }
    public MyException (String message, Throwable cause) {
    super(message,cause);
    }
    }

    // 示例2: 检测异常转非检测异常
    public static String shiftException() {
    try {
    Files.readString(Path.of(path);
    } catch (IOException e) {
    throw new MyException("文件读取错误", e)
    }
    }

    // 示例3:
    private static double divide(double divisor, double dividend) {
    if (dividend == 0) {
    throw new MyException("被除数为空");
    return divisor / dividend;
    }

多线程

进程 (Processes): 操作系统进行资源分配的最小单元,应用程序的执行实例。

线程 (Threads): 进程中的运行实体,操作系统调度的最小任务单位。当程序中所有线程终止,进程随之终止。

线程的创建

  1. 实现Runnable接口,创建Thread实例,以Runnable实现类实例为构造函数参数

    实现线程和任务解耦

    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
    // 定义一个实现Runnable接口的任务类
    public class HelloRunnable implements Runnable {
    private String name;

    public HelloRunnable(String name) {
    this.name = name;
    }

    // 根据该线程待执行任务实现run()方法
    @Override
    public void run() {
    for (int i = 0; i < 5; i++) {
    System.out.println(name + "线程运行: " + i);
    try {
    Thread.sleep((int Math.random() * 10);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }

    private static void getHelloRunnable() {
    // 将Runnable对象基于构造函数传递给Thread对象
    Thread hr1=new Thread(new HelloRunnable("ThreadA"));
    Thread hr2=new Thread(new HelloRunnable("ThreadB"));
    hr1.start();
    hr2.start();
    }
  2. 继承Thread类,重写run()方法

    线程和任务未解耦

    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
    // 继承Thread类
    public class HelloThread extends Thread {
    private String name;

    public HelloThread(String name) {
    this.name = name;
    }

    @Override
    public void run() {
    for (int i = 0; i < 5; i++) {
    System.out.println(name + "线程运行: " + i);
    try {
    sleep((int) Math.random() * 10);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }

    private static void getHelloThread() {
    // 创建继承Thread类的类的对象
    HelloThread ht1=new HelloThread("ThreadA");
    HelloThread ht2=new HelloThread("ThreadB");
    ht1.start();
    ht2.start();
    }

java.lang.Runnable接口

作用:定义预执行的任务

方法:

  • run() : 唯一的抽象方法,需要实现类重写方法时在方法中实现需要线程执行的任务

线程的状态

image-20220503164327678

java.lang.Thread类

作用:定义预执行的线程

方法:

  • run():实现Runnable接口,但重写方法为空,需要继承类时再重写方法,在方法中实现需要线程执行的任务执行完毕后,自动停止线程

  • start():启动线程

  • sleep():静态方法,挂起当前线程,使线程进入非可执行状态,可以通过中断(Interrupt)来终止

  • interrupt():设置中断标识,使指定线程中断,抛出InterruptedException异常 (抛出异常后中断标识清除)

  • boolean interrupted():静态方法,检查当前线程是否中断,返回boolean值,清除中断状态

  • join():当前线程暂停执行,直到指定线程终止,可以通过中断(Interrupt)来终止

java.util.concurrent.CountDownLatch类

作用:允许多个线程等待的同步工具

方法:

  • countDown():减少锁计数器的计数,计数达到零则释放所有等待线程
  • await():当前线程暂停执行直到锁计数器递减到image-20220604124336371

线程同步

synchronized关键字

作用对象:

  • 可以作用于类、方法 (包括静态方法)、代码块

  • 不得作用于抽象方法、构造函数

作用:修饰部分仅允许一个线程独占访问资源

volatile关键字

作用对象:变量

作用:保证变量的可见性,即各线程不在本地缓存变量副本,每次使用即时从主内存加载变量

java.util.concurrent.locks.Lock接口

作用:控制多线程访问共享资源的工具,提供比同步操作更广泛、更灵活的锁定操作,提供对共享资源的独占访问权限 (一次只有一个线 程可以获取该锁)

方法:

  • lock() :锁定任意代码区域 try代码块
  • unlock():操作结束后释放锁 finally代码块

实现类:ReentrantLock(重入锁)/ReadLock/WriteLock

image-20220604124016767

java.util.concurrent.ExecutorService接口

作用:继承于Executor,提供了基于指定线程池策略,创建Executor对象的工厂方法

方法:

  • newSingleThreadExecutor():创建只有1个工作线程的线程池,串行执行提交的任务

  • newFixedThreadPool(int nThreads):创建固定大小的线程池,每提交一个任务创建一个线程,直到达到指定数量

  • execute(Runnable r):添加执行任务到Executor线程池

  • shutdown():禁止添加新任务,等待当前执行中以及任务队列中的所有任务执行完成,关闭线程池

  • List<Runnable> shutdownNow():取消任务队列中需执行的任务,返回未执行任务列表。通过向线程池线程发送 interrupt中断信号,由其中执行的任务捕获处理异常。执行中的任务结束后关闭线程池。

java.util.concurrent.atomic包

作用:实现对特定变量类型的单个变量进行原子操作

包含类:如标量类:AtomicBoolean,AtomicInteger;数组类:AtomicIntegerArray等

方法:get()/set()/increment():类似在volatile变量上读写

java.lang.ThreadLocal<T>类

作用:提供线程局部变量,如充当web容器,持有每个请求线程的变量副本

IO流

I/O

IO流,数据源/数据目标、输入/输出的抽象表示

文件的数据形式

数据文件是基于byte(字节)而非bit(位)保存以及传输

1个byte(字节) == 8个二进制bit(位) == 1个255以内的十进制整数

1个char(字符)占的字节与编码有关。英文字符占1个字节;中文字符,在UTF8(Windows 10)中占3个字节,在GBK (Windows 7)中占2个字节

java.io.InputStream抽象类

输入流

实现子类:FileInputStream,ByteArrayInputStream等

image-20220601210226621

方法:

  • 构造函数:抛出IOException

  • int read() throws IOException:抽象方法,由具体子类实现。基于单字节操作,返回流中下一字节的十进制整数形式;到达流末无可读字节,返回-1

    int read(byte[] b):字节数组缓冲区,将流中字节读取到字节数组中,第1个字节置入数组[0]…,直到读取到数组长度的字节位置为止;返回读取的字节长度;如果没有可读字节,返回-1

  • close() throws IOException:关闭资源

  • long transferTo(out) throws IOExecption:直接将输入流中字节转移至输出流,返回总字节长度

  • byte[] readAllBytes() throws IOException:将输入流中所有字节读出到字节数组

java.io.OutputStream抽象类

输出流

实现子类同输入流

方法

  • 构造函数:抛出IOException

  • void write(int b) throws IOException :抽象方法,基于单字节操作,将十进制整数按字节写入输出

  • void write(byte[] b, int off, int len),从字节数组[off]开始读取,至数组[len]结束

  • close() throws IOException:关闭资源

自动关闭

完整IO代码

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
private static void implementIO() {
// try代码块中进行具体操作前,要将IO流声明为全局变量
FileInputStream in = null;
FileOutputStream out = null;

try {
// 实例化IO流(FileIn/OutputStream从文件系统的文件中读取输入字节)
in = FileInputStream("D:/input.txt")
out = FileOutputStream("D:/output.txt")

// 声明存储返回流下一数据的变量、字节缓冲区
byte[] buffer = new byte[4];
int len = 0;

// 从输入流中循环读入长度为4的字节数组覆盖字节缓冲区并写入输出流
while((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 资源对象必须被关闭
if (in != null) {
// close方法抛出io异常
try {
in.close()
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close()
} catch (IOException e) {
e.printStackTrace();
}
}
}

}

java.lang.AutoCloseable接口

任何实现了java.lang.AutoCloseable接口的类型,均是支持自动关闭的资源类型,java.io.Closeable接口继承AutoCloseable接口

try-with-resources语句

try()语句中声明需要被关闭的资源,资源将在try{}结束后自动关闭,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 在try()语句中创建资源实例
try(FileInputStream in = new FileInputStream("D:/input.txt");
FileOutputStream out = new FileOutputStream("D:/output.txt")) {
byte[] buffer = new byte[4];
int len = 0;
while((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}

// 在try()语句外创建资源实例,在语句中声明
FileInputStream in = new FileInputStream("D:/input.txt");
FileOutputStream out = new FileOutputStream("D:/output.txt");
try(in; out) {
byte[] buffer = new byte[4];
int len = 0;
while((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}

文件系统

NIO2 (java8) 将文件路径与文件操作分离,支持异步非阻塞

java.nio.file.Path接口

作用:不依赖于系统的描述文件路径 (即运行在不同操作系统下具体实现不同)

方法

  1. 创建:

    • java.nio.file.Paths类

      1
      Path p = Paths.get("/tmp/foo");
    • Path.of()静态方法

      1
      Path p = Path.of("/tmp/foo");
  2. 获取:

    1
    // 如 对于路径"D:/test/input.txt"
    • Path getFileName():返回文件名或名称元素序列的最后一个元素

      "input.txt"

    • Path getParent():返回父目录的路径

      D:\test

    • Path getRoot():返回路径的根

      D:\

  3. 拼接:

    • Path resolve(Path other):拼接各级路径

      1
      2
      3
      Path dir = Path.of("D:/test");
      Path file = Path.of("input.txt");
      Path p = dir.resolve(file);

java.nio.file.Files类

作用:通过丰富的静态方法,读取/写入/操作文件与目录

方法(以下方法均为静态方法):

  1. 检查

    • boolean exists(Path path) / notExists(Path path):检查路径是否存在
    • boolean isDirectory(Path path):检查是否为目录
  2. 创建

    • Path createDirectory(Path dir) throws IOException:创建单级目录,目录路径为多级或已存在则异常

    • Path createDirectories(Path dir) throws IOException:创建多级目录

    • Path createFile(path) throws IOException:基于指定路径创建文件,文件已存在则异常

      1
      2
      3
      4
      5
      private static void createFile throws IOException {
      Path file = Path.of("D:/test/input.txt");
      Files.createDirectories(file.getParent());
      Files.createFile(file);
      }
  3. 复制

    • Path copy(Path source, Path target, CopyOption... options) throws IOException:将文件复制到目标文件,默认如果文件已经存在则异常

      java.nio.file.StandardCopyOption枚举:实现了CopyOption接口,提供复制选项CopyOption

      • ATOMIC_MOVE:将文件作为原子文件操作移动 (多线程操作)
      • COPY_ATTRIBUTES:将属性复制到新文件
      • REPLACE_TXISTING:如果文件已存在则替换现有文件
      1
      2
      3
      4
      5
      6
      private static void createFile throws IOException {
      Path dir = Path.of("D:/test");
      Path source = dir.resolve("input.txt");
      Path target = dir.resolve("output.txt");
      Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
      }
  4. 移动

    • Path move(Path source, Path target, CopyOption... options) throws IOException:将文件移动或重命名 (相同目录下) 为目标文件,默认目标文件存在则异常
  5. 删除

    • delete(Path path) throws IOException:基于指定路径删除文件/目录,路径不存在/删除目录不为空则异常
    • boolean deleteIfExists(Path path) throws IOException:路径不存在时不删除,返回是否删除成功,删除目录不为空则异常
  6. 遍历

参考资料


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!