Fork me on GitHub

Java-summary1

自用Java小结(Ⅰ)回顾一下之前学的

Java路线

CodeSheep来告诉你编程培训班到底培训什么内容和具体学习路线!

摘自CodeSheep的B站视频

第一阶段入门进阶

  • JAVA基本语法
  • OO的编程思想
  • 集合
  • IO
  • 异常
  • 泛型
  • 反射
  • 多线程
  • 函数式.

第二阶段Web基础和工具

  • 前段基础三件套(html/css/js),
  • jquery,ajax,jsp,cookie,session,
  • http基础
  • servlet基础,
  • Git,SVN等代码管理工具

第三阶段企业级应用框架培训

  • maven/gradle项目管理工具
  • Spring全家桶:Spring/Spring MVC/ SpringBoot(比较先进的培训有SpringBoot,而SSH基本不用报)
  • 关系型数据库 MySQL,jdbc,MyBatis,Hibernate.
  • 非关系数据库 Redis缓存 (也是区别重点)
  • 模板技术: thymeleaf,freemarker

第四阶段高级应用框架培训

  • 搜索引擎 elastic search
  • RPC框架/微服务框架: Dubbo,Spring Cloud(区别重点)
  • 中间件技术 RabbitMQ,RocketMQ,ActiveMQ,Kafka等.
  • 虚拟化技术:Docker容器,k8s容器编排技术等

第五阶段高级话题

  • JVM优化和排错
  • GC分析
  • 数据库高级优化等话题

基本概念

Java语言的特点

  1. 面向对象
  2. 健壮性
  3. 跨平台性(JVM)

Java两种核心机制

  • Java虚拟机(Java Virtual Machine)

可以理解为一个以字节码为机器指令的CPU,是解释型

  • 垃圾回收机制(Garbage collection)

名词解释

  1. JDK(J2SDK) & JRE

    SDK(Software Development kit 软件开发包)

    Java Runtime Environment (Java运行环境)

    ps: 开发需要JDK,用户只需JRE。

    • JDK = JRE + 开发工具集(例如Javac编译工具)
    • JRE = JVM + Java SE标准类库
  2. Java技术体系平台

    Java SE(Java Standard Edition) 标准版

    Java EE(Java Enterprise Edition)企业版

    Java ME(Java Micro Edition)小型版

    Java Card

  3. IDE (Integrated Development Environment) 集成开发环境

基本语法

注释类型Comment

  1. 单行注释 //
  2. 多行注释 / /
  3. 文档注释 (Java特有)
1
2
3
4
/**
@author 指定Java程序的作者
@version 指定源文件的版本
*/

PS:文档注释的内容可以被javadoc所解析,生成一套以网页文件形式体现的该程序的说明文档。

1
2
> javadoc -d myXXXdoc -author -version XXX.java
>

Java API 文档

API (Application Programming Interface, 应用程序编程接口)

  1. 一个Java源文件只能有一个类声明public,该类名必须与文件名一致
  2. 程序入口是main()
  3. 输出语句:System.out.println() 输出数据再换行 / System.out.print()
  4. 编译会生成(多个)与源文件中类名相同的字节码文件。

关键字与保留字

Java保留字: goto 、const

标识符

各种变量、方法和类(凡是自己起名字的)

  • 特点:字母,0-9,_ 或$组成,数字不开头

  • 规范:(PS:Java 采用unicode字符集)

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

    类名、接口名:多单词所有单词首字母大写:XxxYyyZzz

    变量名、方法名:第一个首字母小写其余首字母大写:xxxYyyZzz

    常量名:所有字母大写,多单词下划线连接:XXX_YYY_ZZZ

变量

是程序中最基本的储存单元。包括变量类型、变量名和储存的值。先声明后使用

数据类型:

  • 基本数据类型(primitive type)
    • 整数类型(byte 1, short 2, int 4, long 8)
    • 浮点类型(float 4数值范围大必须末尾加f, double 8双精度)
    • 字符类型(char 2 ‘ ‘)
    • 布尔型(boolean)
  • 引用数据类型(reference type)
    • 类(class)
    • 接口(interface)
    • 数组(string[])

基本数据类型转换(不包含boolean):

  • 自动类型提升

    byte、char、short互相 –> int –> long –> float –> double

  • 强制类型转换

    自动类型提升的逆运算,强制转换符()

1
2
3
4
5
6
7
8
9
10
11
// 特殊情况1
long l = 123123;//int
//long ll = 123123123123123123;
//float f = 12.3;//double false

// 特殊情况2
//整型常量默认为int,浮点型常量默认为double
byte b = 12;
byte b1 = b + 1;//int false

//char c = '';//false

引用数据类型string

string可以和8种基本数据类型进行运算,且只能进行连接运算: +

1
2
3
4
5
6
//String str1 = 123;//false
String str2 = 123 + "";

//int num = str1;//false
//int num = (int)str1;//false
int num = Integer.parseInt(str1);

进制

  • 二进制(binary):以0B或0b开头,原码 补码(反码+1) 反码(符号位不变)计算机底层都以补码存储数据
  • 十进制(decimal)
  • 八进制(octal):以数字0开头
  • 十六进制(hex):以0x或0X开头

运算符

  • 算术运算符

    (%结果与被模数符号相同)(++不改变本身数据 类型)

  • 赋值运算符

    (+=不改变本身数据 类型,带隐形制性转换)

  • 比较运算符

    (ps: instanceof是否是string)

  • 逻辑运算符

    (逻辑 & | ! ^ 短路 && ||)

    ps: & 与 && (| 与 ||类似)开发中推荐使用双

    • 相同点:运算结果相同,当左边为true时都执行
    • 不同点:当左边为false时,&继续执行&&短路不执行
  • 位运算符

    (<< (最高效2*8) >> (最高位0补0,1补1) >>>无符号右移(都用0补) & | ^(俩数交换) ~取反(补码各位取反))取决于数据类型

  • 三元运算符

    (? : )

    ps:可以嵌套使用,凡是?:(运算效率高)都可以改写成if-else,反之不成立

[a:b] -> (int)(Math.random() * (b - a + 1) + a)

程序流程控制

基本流程结构:

  • 顺序结构
  • 分支结构
    • if-else (万能)
    • switch (执行效率更高,都可以转换成if-else,反之不成立)
  • 循环结构
    • while
    • do-while
    • for
    • foreace

引用数据类型变量:数组

  • 初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 静态初始化
    int[] ids = new int[]{1,3,5,6};
    int[] ids = {1,3,5,6}; //类型判断

    int[][] ids1 = new int[][]{{3.4},{1,2,3},{5,6}};

    // 动态初始化
    int[] ids = new int[4];
    int[] ods = new int[3][4];

    String[][] arr = new String[1][2];
    String[] arr[] = new String[1][2];
    String[][] arr = new String[2][];
    //错误String[][] arr = new String[][2];
    // 数组一旦初始化完成,在内存中占有一连串地址,并且长度无法改变。

    PS:默认初始化值:整型为0,浮点型0.0,char为0或‘\u000’非这个‘0’,boolean型false,引用类型为null 非“null”,多维数组外层为地址值,内层地址未指定时为null。

  • 获取数组的长度:length属性

    1
    System.out.println(name.length);
  • 数组的遍历:for

    1
    2
    3
    4
    5
    6
    7
    8
    9
    for (int i = 0; i < name.length; i++) {
    System.out.println(name[i]);
    }

    for (int i = 0; i < arr.length; i++) {
    for (int j = 0; j < arr[i].length; j++) {
    System.out.println(arr[i][j]);
    }
    }
  • 内存结构解析

    栈stack:局部变量

    (基本数据类型的变量,一个对象的引用,函数调用的现场保留)

    堆heap:new出来的结构:对象、数组(构造器创建的对象)

    方法区

    ​ 常量池

    ​ 静态域 (直接的100,“hello“,常量)

    1
    2
    3
    4
    String str = new String("hello");
    //str -> 栈
    //new出来的对象 -> 堆
    //"hello"字面量 -> 静态区s
  • 数组中涉及的常见算法

    1.数组元素的赋值(杨辉三角、回形数等)
    2.求数值型数组中元素的最大值、最小值、平均数、总和等
    3.数组的复制(需new)、反转、查找(线性查找、二分法查找(必须有序))
    4.数组元素的排序算法(加粗手写,斜体原理)

    ●选择排序:直接选择排序、堆排序
    ●交换排序:冒泡排序O(n^2)、快速排序O(nlog2n)
    ●插入排序:直接插入排序、折半插入排序、Shell排序(希尔)
    归并排序
    ●桶式排序
    ●基数排序

  • Array工具类(操作数组的工具类Arrays.XXX)定义在java.util下

    • boolean equals(int[] a, int[] b) 判断两个数组是否相等
    • String toString(int[] a) 输出数组信息
    • void fill(int[] a, int val) 将指定值填充到数组之中
    • void sort(int[] a) 对数组进行排序
    • int binarySearch(int[] a, int key) 对排序后的数组进行二分法检索特定的值(注意是排序后的)
  • 数组中常见异常

    角标越界异常:ArrayIndexOutOfBoundsExcetion

    空指针异常:NullPointerException

*补充:eclipse快捷键

  • 1.补全代码的声明:alt + /
  • 2.快速修复: ctrl + 1
  • 3.批量导包:ctrl + shift + o
  • 4.使用单行注释:ctrl + /
  • 5.使用多行注释: ctrl + shift + /
  • 6.取消多行注释:ctrl + shift + \
  • 7.复制指定行的代码:ctrl + alt + down 或 ctrl + alt + up
  • 8.删除指定行的代码:ctrl + d
  • 9.上下移动代码:alt + up 或 alt + down
  • 10.切换到下一行代码空位:shift + enter
  • 11.切换到上一行代码空位:ctrl + shift + enter
  • 12.如何查看源码:ctrl + 选中指定的结构 或 ctrl + shift + t
  • 13.退回到前一个编辑的页面:alt + left
  • 14.进入到下一个编辑的页面(针对于上面那条来说的):alt + right
  • 15.光标选中指定的类,查看继承树结构:ctrl + t
  • 16.复制代码: ctrl + c
  • 17.撤销: ctrl + z
  • 18.反撤销: ctrl + y
  • 19.剪切:ctrl + x
  • 20.粘贴:ctrl + v
  • 21.保存: ctrl + s
  • 22.全选:ctrl + a
  • 23.格式化代码: ctrl + shift + f
  • 24.选中数行,整体往后移动:tab
  • 25.选中数行,整体往前移动:shift + tab
  • 26.在当前类中,显示类结构,并支持搜索指定的方法、属性等:ctrl + o
  • 27.批量修改指定的变量名、方法名、类名等:alt + shift + r
  • 28.选中的结构的大小写的切换:变成大写: ctrl + shift + x
  • 29.选中的结构的大小写的切换:变成小写:ctrl + shift + y
  • 30.调出生成 getter/setter/构造器等结构: alt + shift + s
  • 31.显示当前选择资源(工程 or 文件)的属性:alt + enter
  • 32.快速查找:参照选中的 Word 快速定位到下一个 :ctrl + k
  • 33.关闭当前窗口:ctrl + w
  • 34.关闭所有的窗口:ctrl + shift + w
  • 35.查看指定的结构使用过的地方:ctrl + alt + g
  • 36.查找与替换:ctrl + f
  • 37.最大化当前的 View:ctrl + m
  • 38.直接定位到当前行的首位:home
  • 39.直接定位到当前行的末位:end

面向对象

Java类及类的成员:

  • 属性、方法、构造器;代码块、内部类

类与对象

面向对象程序设计的重点是类的设计,设计类就是设计类的成员

类的成员

  • 属性:Field = 域、字段 = 成员变量
  • 行为:Method =(成员)方法 = 函数

类和对象的使用

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

调用对象的结构(属性、方法)

  • 属性: object.field
  • 方法: object.method()

对象的内存解析

  • 堆(Heap)存放对象实例

  • 栈(Stack)存储局部变量

  • 方法区(Method Area)存储已被虚拟机加载的类信息、变量、静态变量、即时编译器编译后的代码等

理解万事万物皆对象

1
2
1. 在Java语言范畴中,我们将功能,结构等封装到类中,通过类的实例化,来调用具体的功能结构。
2. 涉及到Java与前端Html、后端的数据库交互时,前后端的结构在Java层面交互时,都体现为类、对象。

PS:引用类型的变量,只可能储存两类值,null 或 地址值(含变量的类型)

匿名对象的使用,只能调用一次

属性

属性(成员变量)VS 局部变量

  • 相同点

    定义变量格式相同,先声明后使用,且都有对应的作用域

  • 不同点

    1. 声明的位置不同

      属性:直接定义在类的{}中

      局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量

    2. 权限修饰符的不同

      属性:可以在声明属性时,使用权限修饰符指明其权限

      局部变量:不可以使用

    3. 默认初始化值的情况

      属性:类的属性根据类型都有默认初始化值

      局部变量:无初始化值

    4. 在内存中加载的位置

      属性:堆空间(非static)

      局部变量:栈空间

方法

方法重载(overload)

定义:在同一个类中允许存在一个以上的同名方法,只要他们的参数个数或者类型不同即可。(跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系)

“两同一不同”:同一个类,同一个方法名。参数列表不同。

可变个数的形参

格式:数据类型 … 变量名, 必须申明在末尾,可传入参数个数为0个及以上

1
2
3
4
5
6
public void show(String[] strs){}
public void show(String ... strs){
for(int i = 0; i < strs.length; i++) {
System.out.println(strs[i]);
}
}

方法参数的值传递机制

变量赋值:

  • 基本数据类型:赋值的是变量所保存的数据值
  • 引用数据类型:赋值变量所保存数据的地址值

类的成员之三:构造器(构造方法constructor)

1
2
Person p = new Person();
//构建类的对象:new + 构造器

作用:创建对象;给对象进行初始化

格式:权限修饰符 + 类名(形参列表){}

ps:构造器可重载,且一旦显示定义了类的构造器之后,系统不再提供默认空参构造器。(必须自己加,一个类中至少有一个构造器)

类的成员之四:代码块(初始化块)

就是一对大括号,用来初始化类、对象,若有修饰,只能是static

  • 静态代码块
    • 内部可以有输出语句,并随着类的加载而执行(不只是加载,最先甚至先于main方法),且只执行一次
    • 若多个静态代码块,按声明顺序依次执行,总优先于非静态代码块
    • 静态代码块内只能静态的属性、方法,不能调用非静态的结构
    • 作用:初始化类的信息我一行写不下,应用场景如连接数据池)
  • 非静态代码块

    • 内部可以有输出语句,随着每次对象的创建而执行

    • 非静态代码块内既能静态的属性、方法,也能调用非静态的结构

调用顺序:

代码块的执行先于构造器,甚至先于main方法(由父及子,静态先行)
Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:“”
Leaf的普通初始化块
Leaf的构造器

属性赋值的相关问题:

可以对属性进行赋值的位置:

  1. 默认初始化
  2. 显式初始化
  3. 构造器中初始化
  4. 有对象后,通过“对象.属性”或“对象.方法”的方法进行赋值
  5. 在代码块中进行赋值

属性赋值的先后顺序:1 -> 2 / 5 (看谁后写)-> 3 -> 4

类的成员之五:内部类

类A声明在类B中,A为内部类

分类:成员内部类 (静态、非静态) VS 局部内部类(方法、代码块、构造器内)

成员内部类:

  • 作为外部类的成员:调用外部类的结构、可以被static修饰、可以被四种权限修饰符修饰
  • 作为一个类:内可以定义属性、方法、构造器等,可以被final修饰,可以被abstract修饰
  • 相关使用细节:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//如何实例化成员内部类的对象:
//创建静态成员内部类对象
Person.Dog dog = new Person.Dog();
dog.show();
//创建非静态成员内部类对象
Person p = new Person();
Person.Bird bird = p.new Person.Bird();
bird.show();

//如何在成员内部类中区分调用外部类的结构:
//方法的形参
name
//内部类的形参
this.name
//外部类的形参
Person.this.name

局部内部类 的方法中,如果调用局部内部类所声明的方法中的局部变量, 要求此 局部变量 声明为 final 的(JDK8以后可以省略)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//开发中局部内部类的使用:
//eg:返回一个实现了XXX接口的类的对象
public Comparable getComparable(){
//创建一个实现了Comparable接口的类:局部内部类
//方式一:
// class MyComparable implements Comparable{
// @Override
// public int compareTo(Object o) {
// return 0;
// }
// }
// return new MyComparable();

//方式二:
return new Comparable(){
@Override
public int compareTo(Object o) {
return 0;
}
};
}

面向对象的三个特征

封装性、继承性、多态性、(抽象性)

封装与隐藏

体现:

  • 将类的属性XXX私有化private,提供公有化public方法来获取getXXX和设置属性setXXX的值。
  • 不对外暴露的私有的方法
  • 单例模式(将构造器私有化)
  • 如果不希望 类 在包外被使用可以设置成缺省

目标:高内聚,低耦合

PS:封装性的体现需要权限修饰符来体现。修饰 类 只能public与缺省

权限修饰符(从小到大)

修饰符 类内部 同一个包 不同包的子类 同一个工程
private
(缺省)
protect
public

继承性inheritance

好处:

  • 减少了代码的多余,提高代码的复用性
  • 便于功能的扩展
  • 为之后多态性的使用,提供了前提

格式:

1
class A extends B{}
  • A:子类、派生类、subclass
  • B:父类、超类、superclass

体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。(private的属性也继承到了,只是因为封装性的影响不能直接调用)

ps:Java只支持单继承和多继承,不允许多重继承,一个子类只能有一个父类。如果没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类,所有Java类都直接或间接继承了java.lang.Object类。

方法的重写(override/overwrite)

定义:在子类中根据需要对父类中的方法进行改造。

应用:重写以后,当创建子类对象,调用同名方法时调用的是重写的方法。

规定:(建议:开发中直接从父类粘贴过来

  1. 子类重写的方法名与形参列表与被重写一样

  2. 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符

    ps:特殊情况,子类中不能重写父类中声明为private的方法。

  3. 返回类型:

    • 父类被重写方法的返回类型为void,子类重写的方法只能返回void;
    • 父类被重写方法的返回类型为基本类型,子类重写的方法只能返回相同的基本类型;
    • 父类被重写方法的返回类型为A类型,子类重写的方法可以返回A类型或A的子类;
  4. 异常类型:子类重写的方法的异常类型不大于父类被重写的方法抛出的异常类型

特别注意:子类和父类的同名同参数的方法要么声明非static(考虑重写),要么都声明为static(不是重写,静态方法不能被覆盖)

区分重载和重写:
①二者的概念:
②重载和重写的具体规则
③重敢:不表现为多态性重写:表现为多态性

多态性polymorphism

定义:一个事物的多种形态

在Java中的体现:对象的多态性,父类的引用指向子类的对象

1
2
3
Person p1 = new Man();
Person p2 = new Woman();
//子类的对象赋给父类的引用

多态的使用:

在编译期,只能调用父类中声明的方法,在运行期,实际执行的是子类中的重写的方法。

虚拟方法调用(Virtual Method Invocation)

当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法

Java引用变量有两种类型:编译时类型 和 运行时类型。

调用方法时,编译看左边,执行看右边–动态绑定

多态是运行时行为。重载是编译是就已经确定了,“早绑定”,”静态绑定”。

Bruce Eckel:”不要犯傻,如果它不是晚绑定,就不是多态。”

多态性的使用前提:

  • 类的继承关系
  • 方法的重写

ps: 对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边) 1001 VS 1002

向下转型(使用强制类型转换符)

编译时只能调用父类声明的属性和方法,如何调用子类特有的属性和方法? 使用强制类型转换。

使用强制转换时,可能出现ClassCastException的异常。

使用instanceof进行检测:

1
2
3
if(a instanceof A) {

} //判断对象是否是A的实例,如是返回true。

1.若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。
2.对于实例变量,则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量。

其他关键字:

this、super、static、final、abstract、interface、package、import与相关补充知识

this:

理解为 当前对象,通常可以省略。可以调用:属性(用以区分局部变量和属性)、方法和构造器。

调用本类中指定的构造器 this(形参列表) 减少代码重复,且必须放在构造器内第一行,最多只能声明一个来调用其他构造器。

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

super:

理解为 父类的,可以调用:属性、方法和构造器。

通常可以省略,当子类和父类中定义同名的属性时,需要显式的super

特殊情况, 当子类重写父类的方法时,需要调用父类中的方法时,需要显式的super

super调用父类构造器:在子类构造器中显式的“super(参数列表)”,且必须声明在子类构造器中的首行

在类的构造器中,针对“super(参数列表)”和“this(参数列表)”只能二选一,构造器中没有,默认super(空参)

在类的多个构造器中,至少有一个类的构造器使用了super(参数列表),调用父类的构造器。

虽然创建子类时调用了父类的构造器,只是创建了一个对象。

static:

某些特定的数据在内存中只有一份。eg:每个中国人都共享中国这个国籍。

修饰:属性、方法、代码块、内部类(注意构造器不行)

修饰属性:静态变量(类变量)

  • 属性按是否有static修饰又分为:静态属性 VS 非静态属性(实例变量)
  • 实例变量:当创建类的多个对象,每个对象都独立拥有一套类中的非静态属性,当修改某一个对象的静态属性时,不会导致其他对象中同样的属性值的修改。
  • 静态属性:创建类的多个对象,每个对象都共享同一个静态属性。当修改某一个对象的静态属性时,会导致其他对象调用此静态变量时,属性值是修改的。
  • PS:静态变量(类变量)随类的加载而加载,且早于对象的加载。因此可以通过”类.静态变量“进行调用。由于类只会加载一次 ,则静态变量在内存中也只会存在一份

举例:System.out / Math.PI

修饰方法:静态方法

  • 可以通过”类.静态方法“进行调用
  • 静态方法只能调用静态的方法或属性,非静态则都可以。
  • 在静态方法中,不能使用this,super关键字

static应用场景:

  • 属性是可以被多个对象共享,不会随着对象的不同而改变
  • 操作静态属性的方法,设置成static;工具类中的方法,习惯上声明为static

final:

可以修饰的结构:类、方法、变量

  • 修饰类:此类不能被其他类继承。eg:String类、System类、StringBuffer类。
  • 修饰方法:此方法不能被重写。eg:Object类中的getClass()。
  • 修饰变量:此“变量”被称为一个常量。
    • 修饰属性,之后(必须初始化)可以考虑的赋值位置有:显式初始化、代码块中初始化、构造器中初始化、不可以在方法中赋值(因为未调用)。
    • 修饰局部变量:常量不可修改,尤其修饰形参时,表明此形参是常量,方法内不能重新赋值。
  • static final 修饰属性:全局常量

abstract:抽象类与抽象方法

修饰的结构:类、方法

  • 修饰类:抽象类,不可实例化类中一定要构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)开发中,都会提供抽象类的子类。让子类对象实例化,完成相关的。
  • 修饰方法:抽象方法只有方法的声明,没有方法体。包含抽象方法的类一定是一个抽象类,抽象类中可以没有抽象方法。若子类重写了父类的所有的抽象方法后,此子类方可实例化。若未重写,则该子类也是一个抽象类,需要abstract修饰。(子类必须重写后才可实例化)
  • PS:abstract不可以用来修饰属性、构造器等。abstract不能修饰私有方法、静态方法、final的类与方法。

抽象类的匿名子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Worker worker = new Worker();
method1(worker);//非匿名的类非匿名对象

method1(new Worker());//非匿名的类匿名的对象

Person p = new Person(){
@override
public void eat() {
}
@override
public void breath() {
}
}//匿名子类的对象:p

method1(new Person(){
@override
public void eat() {
}
@override
public void breath() {
}
});//匿名子类的匿名对象 --- 省事

接口(interface)

  • 与类并列的结构,一定程度解决类的单继承性的缺陷。本质是契约、规范、标准。(JDK7及以前)接口是一种特殊的抽象类,这种抽象类只包含常量和方法的定义。

  • 继承是一个“是不是”的关系,而接口实现的是“能不能”的关系。(例如学生和运动员都能学习)

  • 接口中的成员:

    • JDK1.7及以前,只能定义全局变量和抽象方法(默认缺省也是)
      • 全局变量public static final
      • 抽象方法public abstract
    • JDK8,除了定义以上之外,还可以定义静态static方法、默认default方法。
  • 接口中不能定义构造器!意味着接口不能实例化

  • Java开发中,接口通过让类去实现(implement)的方法来使用(面向接口编程),若实现类覆盖了接口的所有的抽象方法后,此实现类方可实例化。若未重写,则该实现类也是一个抽象类。

  • Java类可以实现多个接口(多实现),弥补类的单继承性的局限性

    1
    2
    //先写extends再写implements
    class AA extends BB implements CC,DD,EE
  • 接口与接口之间可以继承,而且可以多继承

  • 接口的具体使用,体现了多态性、

Java8中关于接口的改进

  1. 接口中定义的静态方法,只能通过接口来调用(像工具类)

  2. 通过实现类的对象,可以调用接口中的默认方法

  3. 类优先原则:若子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法(属性bushi),那么子类在没有重写此方法的情况下,默认调用的是父类的同名同参数的方法。

  4. 接口冲突:若实现类实现了多个接口,而多个接口都定义了同名同参数的默认方法,在实现类没有重写的情况下,报错。因此实现类必须重写此方法。

  5. 如何在子类(实现类)的方法中调用父类、接口中被重写的(默认)方法:

    1
    2
    3
    4
    method();//调用自己重写的方法
    super.method();//调用父类中声明的
    Interface1.super.method();//调用接口中的默认方法
    Interface1.method();//直接调用接口中的静态方法

接口的应用:

  1. 代理模式(Proxy)

    代理模式

    为其他对象提供一种代理以控制对这个对象的访问另一个博文(中介,歌手经纪人)应用场景:

    • 安全代理

    • 远程代理

    • 延迟加载

分类:

  • 静态代理(静态定义代理类)
  • 动态代理(动态生存代理类)
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 NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
// server.browse();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse();
}
}

interface NetWork{
public void browse();
}

//被代理类
class Server implements NetWork{
@Override
public void browse() {
System.out.println("真实的服务器访问网络");
}

}
//代理类
class ProxyServer implements NetWork{
private NetWork work;
public ProxyServer(NetWork work){
this.work = work;
}
public void check(){
System.out.println("联网之前的检查工作");
}
@Override
public void browse() {
check();
work.browse();
}
}
  1. 工厂模式 Factory

创建者和调用者分离

补充

JavaBean

是一种Java语言写成的可重用组件,符合以下标准的Java类:

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

Debug调试

设置断点

操作 作用
step into 跳入(f5) 进入当前行所调用的方法中
step over 跳过(f6) 执行完当前行的语句,进入下一行
step return 跳回(f7) 执行完当前行所在的方法,进入下一行
drop to frame 回到当前行所在方法的第一行
resume恢复 执行完当前行所在断点的所有代码,进入 下一个断点,如果没有就结束
Terminate 终止 停止 JVM, 后面的程序不会再执行

JUnit单元测试方法

  • 选中当前工程 - 右键选择:build path - add libraries - JUnit 4 - 下一步

  • 创建Java类

    (要求:①此类是public ②此类提供公共空参构造器)进行单元测试

  • 在此类中声明单元测试方法

    要求权限为 public,没有返回值 且 没有形参

  • PS:需要声明注释@Test,并导入包import org.junit.Test; 左键双击 单元测试方法名,右键:run as - JUnit Test

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import org.junit.Test;

    public class JunitTest {

    @Test
    public void test1() {

    }
    }
  • 若执行结果无异常为绿色,异常为

Object类:

只定义了一个空参构造器

方法: clone() / getClass() / finalize() /hashCode() / wait() / notify() / notifyAll() / equals() / toString()

== VS equals()

  • == 是运算符,可以用于 基本数据 与 引用类型变量,前者比较保存的数据是否相同(不一定要类型相同,但必须一致,否则编译不通过),后者比较地址值是否相同,是否引用指向同一个对象

  • equals() 是一个方法只能用于 引用数据类型变量 的比较*object类中定义的equals() 和 == 的作用是相同的*(未重写)。像String、Date、File、包装类等都重写了Object类中的equals()方法,重写以后比较的是两个对象的实体内容是否相同。若自己定义的类也要有这样的功能,比较对象的实体内容,应该重写equals()方法。(equals()建议反着写,比如:”反着来”.equals(str) ,这样可以避免str可能是空指针的情况!)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //重写的原则,比较两个对象的实体内容是否相同(举例:name和age)
    @Override
    public boolean equals(Object obj) {
    if(this == obj) {
    return true;
    }

    if(obj instanceof XXX) {
    XXX xxx = (XXX)obj;
    return this.age == xxx.age && this.name.equals(xxx.name);
    } else {
    return false;
    }
    }

    //实际使用自动生成 和setter、getter一样

toString()方法

  • 输出一个 引用变量 时,实际上输出的是对象的toString()
  • 像String、Date、File、包装类等都重写了Object类中的toString()方法,返回“实体内容”信息
  • 自定义类也可以重写该方法。

main()方法

  • 作为程序的入口
  • 也是一个普通的静态方法(只能调用静态属性方法,不然造类:通过实例化类对象调用普通属性与方法
  • 可以作为与控制台交互的方式(java xxxDemo “str”)因为有参数String[] args

包装类(Wrapper)的使用

  • 针对八种基本数据类型定义相应的引用类型-包装类(封装类),具备类的特征,就可以调用类中的方法,实现真正的面向对象。

    | 基本数据类型 | 包装类 |
    | ———— | ————- |
    | byte | Byte |
    | short | Short |
    | int | Integer |
    | long | Long |
    | float | Float |
    | double | Double |
    | boolean | Boolean |
    | char | Character |

  • Byte Short Integer Long Float Double 父类为Number

  • 基本数据类型、包装类、String三者的相互转换

    1. 包装类 -> 基本数据类型:调用包装类的xxxValue()

    2. 基本数据类型 -> 包装类:调用包装类的构造器new()

    3. 基本数据类型、包装类 -> String类型:调用String重载的valueOf(Xxx xxx)

      1
      2
      3
      4
      5
      int num1 = 10;
      //方式一:连接运算
      String str1 = num1 + "";
      //方式二:调用String重载的valueOf(Xxx xxx)
      String str2 = String.valueOf(num1);
    4. String类型 -> 基本数据类型、包装类:调用包装类的parseXxx()

      1
      2
      String str3 = "1234";
      int num3 = Integer.parseInt(str3);
  • 自动装箱与拆箱(JDK5.0以后)

    1
    2
    3
    4
    int num1 = 10;
    Integer in1 = num1; //自动装箱

    int num3 = in1;//自动拆箱
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
// 关于包装类的比较迷惑的问题
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1); //1.0 提升了!!
Object o2;
if (true) {
o2 = new Integer(1);
} else {
new Double(2.0);
}
System.out.println(o1); //1


Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); //false

Integer m = 1;
Integer n = 1;
System.out.println(m == n); //true
// Integer内部定义了IntegerCache结构,其中定义了Integer[]
//保存了-128~127,如果使用自动装箱时,直接调用。
//128~127范围内时,可以直接使用数组中的元素,不用再去new了。目的,提高效率

Integer x = 128;
Integer y = 128;
System.out.println(x == y); //false

设计模式

是在大量实践中总结和理论化后优选的代码结构、编程风格以及解决问题的思考方式。“套路

单例(Singleton)设计模式

只能存在一个对象实例,好处减少了系统性能的开销(注意:学习时由static延申

实现一:饿汉式 (线程安全)

  1. 私有化类的构造器

  2. 内部创建类的对象(private static)

  3. 提供公共的方法(public static),返回类的对象

    1
    2
    3
    4
    5
    6
    7
    private Bank(){

    }
    private static Bank instance = new Bank();
    public static Bank getInstance() {
    return instance;
    }

实现二:懒汉式 (延迟对象的创建)

  1. 私有化类的构造器

  2. 声明当前类对象(private static),没有初始化null

  3. 声明public、static的返回当前类对象的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    private Order(){

    }
    private static Order instance = null;
    public static Order getInstance() {
    if(instance == null) {
    instance = new Order();
    }
    return instance;
    }

饿汉式 VS 懒汉式

区别:懒汉式好处延迟对象的创建饿汉式坏处,对象加载时间太长但其是线程安全的

使用场景:

  • 网站的计数器
  • 应用程序的日志应用
  • 数据库连接池
  • 读取配置文件的类
  • Application也是单例的典型应用
  • Windows的Task Manager(任务管理器)
  • Windows中的Recycle Bin(回收站)

模板方法设计模式(TemplateMethod)

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行拓展、改造,但子类总体上会保留抽象类的行为方式。

解决的问题:

  • 当功能内部一部分实现是确定的,一部分实现是不确定的。这是可以把不确定部分暴露出来,让子类去实现。
  • 在软件开发中实现一个算法时,整体步骤很确定、通用,这些步骤在父类中写好,但部分易变,可以将该部分抽象出来,供不同子类去实现。

MVC设计模式

模型层 model:主要处理数据

  • 数据对象封装: model.bean/domain
  • 数据库操作类: model.dao
  • 数据库: model.db

视图层 view: 显示数据

  • 相关工具类: view.utils
  • 自定义view: view.ui

控制层 controller: 处理业务逻辑

  • 应用界面相关: controller.activity
  • 存放fragment: controller.fragment
  • 显示列表的适配器: controller.adapter
  • 服务相关的: controller.service
  • 抽取的基类: controller.base

异常处理

概述与体系结构

开发过程中的 语法错误逻辑错误 不是异常

执行过程中出现的异常分为两类:

  • Error:Java虚拟机无法解决的严重问题。eg:StackOverflowError 栈溢出 和 OutOfMemoryError 堆溢出。一般不编写针对性的代码进行处理。
  • Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。又分为编译时异常(受检checked异常) VS 运行时异常(非受检unchecked异常,RuntimeException)

异常处理机制

抓抛模型:

  1. “抛”:程序在正常执行中,一旦出现异常就会在代码处生成一个对应异常类的对象,并抛出。之后的代码不再执行。

    关于异常对象的产生:

    • 系统自动生成的异常对象

    • 手动的生成一个异常对象,并抛出(throw

      1
      2
      throw new RuntimeException("nnn");
      //自身并不输出nnn,被catch后.getMessage才有
  2. ‘’抓“:可以理解为异常的处理方式,如下两种方式:

① try-catch-finally

1
2
3
4
5
6
7
8
9
10
11
12
try{
//可能会出现异常的代码
} catch(异常类型1 变量名1) {
//处理异常的方法1
} catch(异常类型2 变量名2) {
//处理异常的方法2
}
...
finally{
//一定会执行的代码
}
// 类似直接上药
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test1() {
String str = "123";
str = "abc";
try {
int num = Integer.parseInt(str);
System.out.println("hello---1"); //未运行
} catch(NumberFormatException e) {
System.out.println("数值转换异常啦。");
System.out.println(e.getMessage());
//e.printStackTrace();
}
System.out.println(str + "hello");
}
//output:
// 数值转换异常啦。
// For input string: "abc"
// abchello

常用异常对象处理的方式
①String getMessage()
②printStackTrace()

PS:

  • catch中的异常类型 若无子类父类关系 ,无需考虑声明的先后顺序;若有,子类必须声明在父类的上面,否则报错(类似if和switch break)。
  • try中声明的变量在大括号外不能调用。try-catch-finally可以嵌套
  • finally是可选的,其中声明的是一定会执行的代码,即便catch中又出现了异常,try或catch中有return语句等情况。
  • finally重要应用:像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,需要收到编写代码进行资源的释放,此时代码就需要编写在finally中。

体会:

  1. 使用try-catch-finally处理编译时异常,是使得程序在编译时不再报错,但在运行时仍可能报错“延迟”到运行时
  2. 开发中,由于运行时异常比较常见,所以我们通常不针对运行时异常编写try-catch-finally,针对编译时异常,一定要考虑异常的处理。

② throws + 异常类型

喊人通报,未解决)声明在方法的声明处,指明此方法执行时,可能回抛出的异常类型,一旦方法体执行时出现异常,仍会在异常处生成一个异常类的对象。此对象满足throws后的异常类型时,就会被抛出,之后的代码就不再执行。

如何选择

  • 若父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着若子类重写的方法有异常必须使用try-catch-finally
  • 执行的方法a中,先后调用了另外几个方法,这几个方法是递进关系执行的。建议这几个方法使用throws的方式处理(层层向上报),而执行的方法a可以考虑使用try-catch-finally

自定义异常类

  1. 继承现有的异常结构:RuntimeException、Exception

  2. 提供全局常量:serialVersionUID

  3. 提供重载的构造器

    (记得要搭配throw手动抛出)

1
2
3
4
5
6
7
8
9
10
11
12
public class MyException extends RuntimeException{

static final long serialVersionUID = -70348971766939L;

public MyException() {
super();
}

public MyException(String message) {
super(message);
}
}

throw VS throws

  • throw表示抛出一个异常类对象,生成异常对象的过程声明在方法体内
  • throws属于异常处理的一种方式,声明在方法的声明处

程序、进程、线程

基本概念

  • 程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象
  • 进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
    PS:程序是静态的,进程是动态的
    进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。(方法区和堆)
  • 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
    线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。一个进程中的多个线程共享相同的内存单元/内存地址空间。它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患

何时需要多线程

  • 程序需要同时执行两个或多个任务。
  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
  • 需要一些后台运行的程序时。

多线程的创建

方法一:继承于Thread类

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
/**
*多线程的创建,方法一:继承于Thread类
* 1.创建一个继承于Thread类的子类
* 2.重写Thread类的run() -> 将此线程执行的操作声明在run()中
* 3.创建Thread类的子类对象
* 4.通过此对象调用start(): ①启动当前线程;②调用当前线程的run()
*
* PS: 1.不能直接调用run()启动线程
* 2.要运行多个线程需要造多个对象(不可以让已经start()的线程去执行,会报IllegalThread)
*
* @author goodwell
* @create 2019-09-25-9:39
*/
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}

public class ThreadTest{
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}

}

创建Thread类的匿名子类的方法

1
2
3
4
5
6
7
8
9
10
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();

Thread类中的常用方法:

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
/**
* 测试Thread中的常用方法
* 1. start() 启动线程;调用当前线程的run()
* 2. run() 通常需要重写Thread类中的方法,将创建的线程要执行的操作声明在此方法
* 3. currentThread() 静态方法,返回执行当前代码的线程
* 4. getName() 获取当前线程的名字
* 5. setName() 设置当前线程的名字
* 6. yield() 释放当前线程cpu的执行权
* 7. join() 在线程a中调用了线程b的join(),此时线程a进入阻塞状态,直到b完全执行完,a才结束阻塞状态。
* 8. sleep(long millitime) 让当前线程“睡眠”millitime 单位为ms,此时为阻塞状态
* 9. isAlive() 判断当前线程是否存活
*
* @author goodwell
* @create 2021-03-09 19:15
*/

class HelloThread extends Thread{

@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + i);
}
// if (i % 20 == 0) {
// yield();
// }
}
}

//直接构造器命名
public HelloThread(String name) {
super(name);
}
}

public class ThreadMethodTest {
public static void main(String[] args) {
HelloThread hiThread =new HelloThread("Thread:1");
// hiThread.setName("线程一");
hiThread.start();

// 主线程命名
Thread.currentThread().setName("主线程");
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
if (i == 0) {
try {
hiThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

方法二:实现Runnable接口

  1. 创建一个实现了Runnable接口的类
  2. 实现类 去 实现Runnable中的抽象方法:run()
  3. 创建 实现类 的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  5. 通过Thread类的对象调用start()
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
/**
* 创建多线程方法二
*
* @author goodwell
* @create 2019-09-25-16:13
*/
class MThread implements Runnable{

@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}

public class ThreadTest1 {
public static void main(String[] args) {
MThread mThread = new MThread();
Thread t1 = new Thread(mThread);
t1.start();
}
}

两种创建方式的对比

开发中,优先选择实现Runnable接口的方式

  • 原因:

    1. 实现的方式没有类的单继承的局限性
    2. 实现的方式更适合处理多个线程有共享数据的情况
  • 联系:

    1
    public class Thread implements Runnable
  • 相同点:都需要重写run(),将线程执行的逻辑声明在run()中

方法三:实现Callable接口

  1. 创建一个Callable的实现类
  2. 实现call方法,将线程需要执行的操作声明在call()中
  3. 创建callable实现类的对象
  4. 将此对象传递到FutureTask构造器中,创建FutureTask的对象
  5. 将FutureTask的对象作为参数传给Thread的构造器,创建Thread对象,并调用start()
  6. 获取Callable中call方法的返回值

使用Runnable VS Callable

如何理解与使用Runnable相比, Callable功能更强大些

  • 相比run()方法,可以有返回值
  • 方法可以抛出异常,被外面操作捕获,得到异常信息
  • 支持泛型的返回值
  • 需要借助FutureTask类,比如获取返回结果

    Future接口

  • 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。

  • FutrueTask是Futrue接口的唯一的实现类
  • FutureTask 同时实现了 Runnable, Future 接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
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
// 1. 创建一个Callable的实现类
class NumThread implements Callable {
//2. 实现call方法,将线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception{
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}

public class CallTest {
public static void main(String[] args) {
// 3. 创建callable实现类的对象
NumThread numThread = new NumThread();
// 4. 将此对象传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
// 5. 将FutureTask的对象作为参数传给Thread的构造器,创建Thread对象,并调用start()
new Thread(futureTask).start();

Object sum = null;
try {
// 6. get()获取Callable中call方法的返回值
sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}

方法四:使用线程池

背景: 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路:提前 创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

步骤:

  1. 提供指定线程数量的线程池
  2. 执行指定的线程操作,需要提供实现Runnable、Callable接口实现类的对象
  3. 关闭连接池

好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会终止
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
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}

public class ThreadPool {

public static void main(String[] args) {
// 1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//↑是接口,↓是类 System.out.println(service.getClass());
// ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
// 设置线程池的属性
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();

// 2. 执行指定的线程操作,需要提供实现Runnable、Callable接口实现类的对象
service.execute(new NumberThread()); // 适用于Runnable
// service.submit(Callable callable); // 适用于Callable
// 3. 关闭连接池
service.shutdown();
}
}

线程的生命周期

线程的生命周期

线程的同步

举例问题:卖票过程中,出现了重票、错票 –> 出现了线程的安全问题,通过同步机制来解决

方式一:同步代码块

1
2
3
synchronized(同步监视器){
//需要同步的代码
}

说明:操作共享数据的代码,及需要被同步的代码;共享数据,多个线程共同操作的变量;同步监视器,俗称:锁,任何一个类的对象都可以当锁,runnable实现类可以用this,或者用类(Xxxx.class)。(要求:多个线程必须要共同的一把锁

补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充同步监视器;在继承Thread类创建多线程的方式中,慎用this充当同步监视器考虑使用当前类作为锁Xxxx.class

同步的优缺点:

  • 好处:解决了线程的安全问题
  • 局限性:操作同步代码时,只能有一个线程参加,其他线程等等待,效率较低。

方法二:同步方法

如操作共享数据的代码完整的声明在一个方法中。

总结:

  1. 同步方法仍然涉及到同步监视器,不需要显式的声明
  2. 非静态的同步方法,同步监视器是:this;静态的同步方法,同步监视器是:当前类本身。

线程安全的饿汉式单例模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Bank{
private Bank(){};
private static Bank instance = null;
private static Bank getInstance(){
//方式一:效率较差
// synchronized (Bank.class) {
// if (instance == null) {
// instance = new Bank();
// }
// return instance;
// }
//方式二:效率更高
if (instance == null) {
synchronized (Bank.class) {
instance = new Bank();
}
}
return instance;
}
}

线程的死锁问题

死锁的理解:

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

说明:

  • 出现死锁后,不会出现异常、提示,只是所有的线程都处于阻塞状态,无法继续
  • 使用同步的时候,要避免出现死锁。

方式三:lock锁 — JDK5.0新增

  1. 实例化ReentrantLock
  2. 调用lock
  3. 调用解锁方法:unlock
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
class Window implements Runnable{

private int ticket = 100;
//1.实例化Reentrantlock
private ReentrantLock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
try {
//2.调用lock
lock.lock();

if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println(Thread.currentThread().getName() + ":" + i);
ticket--;
} else {
break;
}
} finally {
//3.调用解锁方法:unlock
lock.unlock();
}
}
}
}

synchronized VS Lock 异同

  • 相同:二者都可以解决线程安全问题
  • 不同:synchronized机制在执行完相应的同步代码后,自动释放同步监视器;Lock需要手动的启动同步(Lock()),同时结束同步也需要手动的实现(unlock())

优先使用顺序:

Lock -> 同步代码块(已经进入了方法体,分配了相应资源) -> 同步方法(在方法体之外)

线程的通信

相关方法:

wait():一旦执行,当前线程就进入阻塞状态,并释放同步监视器

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

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

说明:

  1. 此三个方法必须使用在同步代码块
  2. 调用者必须是 同步代码块中 的同步监视器,否则会出现IllegalMonitorStateException异常
  3. 都是定义在java.lang.Object类中

sleep() VS wait() 异同:

  • 同:一旦执行,都可以是当前的线程进入阻塞状态
  • 异:
    1. 方法声明的位置不同:Thread类中声明sleep,Object类中声明wait
    2. 调用的要求不同:sleep可以在任何场景下调用,wait必须使用在同步代码块
    3. 是否释放同步监视器:若都使用在同步代码块或同步方法中,sleep不会释放锁wait会释放锁

字符串相关的类

String

字符串是常量,用双引号引起来,他们的值在创建后就不能改变。

String对象的 字符串内容 是 存储在一个 字符数组final char[] value中。

特点

  • 实现了 Serializable 接口,表示字符串支持序列化的;

  • 实现了 Comparable 接口,表示String可以比较大小

  • String是一个final类,不可被继承,其代表不可变的字符序列。(不可变性

    体现:

    1. 当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value进行赋值;
    2. 当对现有的字符串进行拼接时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值;
    3. 当调用String的replace()修改字符串,也需要重新指定内存区域赋值。
1
2
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence

通过字面量的方式(区别于new方式)给一个字符串赋值,此时的 字符串值 声明在字符串常量池中,字符串常量池 不会重复储存相同的 字符串

实例化的方式

  1. 通过字面量定义
  2. 通过new + 构造器

面试题:

1
2
3
4
5
String s = new String("abc");//此方式创建对象,在内存中创建了几个对象? 俩:一个堆空间中new结构,另一个是char[]对应的常量池中的数据“abc”
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2); //false
// 先造了对象在堆中,对象的value指向常量池
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
public class StringTest {
@Test
public void testString(){
String s1 = "hello";
String s2 = "goodwell";

String s3 = "hellogoodwell";
String s4 = "hello" + "goodwell";
String s5 = s1 + "goodwell";
String s6 = "hello" + s2;
String s7 = s1 + s2;
//1.
System.out.println(s3 == s4);//true
//2.
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s6 == s7);//false

String s8 = s5.intern();
//返回值得到的s8使用的常量值已经存在的“hellogoodwell”
//3.
System.out.println(s3 == s8);//true

final String s9 = "hello";
String s10 = s9 + "goodwell";
//4.
System.out.println(s3 == s10);//true
}

结论:

  • 常量与常量的 拼接结果 在常量池。且常量池中 不会存在相同内容的常量。
  • 只要拼接的其中有一个是变量,结果就在堆中(类似new)
  • 如果拼接的结果 调用intern()方法,返回值就在常量池中
  • final String(也在常量池中)和字面量连接,结果在常量池中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class StringTest {
String str = new String("good");
char[] ch = {'t','e','s','t'};

public void change(String str, char ch[]) {
str = "test ok";
ch[0] = 'b';
}

public static void main(String[] args) {
StringTest ex = new StringTest();
ex.change(ex.str, ex.ch);
System.out.println(ex.str);//good
//不可变性
System.out.println(ex.ch);//best
}
}

常用方法

  • int length() :返回字符串的长度: return value.length
  • char charAt(int index): : 返回某索引处的字符return value[index]
  • boolean isEmpty() :判断是否是空字符串:return value.length == 0
  • String toLowerCase() :使用默认语言环境,将 String 中的所有字符转换为小写
  • String toUpperCase() :使用默认语言环境,将 String 中的所有字符转换为大写
  • String trim(): :返回字符串的副本,忽略前导空白和尾部空白
  • boolean equals(Object obj): :比较字符串的内容是否相同
  • boolean equalsIgnoreCase(String anotherString) :与equals方法类似,忽略大小写
  • String concat(String str) :将指定字符串连接到此字符串的结尾。 等价于用“+”
  • int compareTo(String anotherString): :比较两个字符串的大小
  • String substring(int beginIndex): :返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
  • String substring(int beginIndex, int endIndex) : :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
  • boolean contains(CharSequence s) :当且仅当此字符串包含指定的 char 值序列时,返回 true
  • int indexOf(String str): :返回指定子字符串在此字符串中第一次出现处的索引
  • int indexOf(String str, int fromIndex): :返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
  • int lastIndexOf(String str): :返回指定子字符串在此字符串中最右边出现处的索引
  • int lastIndexOf(String str, int fromIndex): :返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
    注:indexOf和lastIndexOf方法如果未找到都是返回-1
  • boolean endsWith(String suffix): :测试此字符串是否以指定的后缀结束
  • boolean startsWith(String prefix): :测试此字符串是否以指定的前缀开始
  • boolean startsWith(String prefix, int toffset): :测试此字符串从指定索引开始的子字符串是否以指定前缀开始
  • 替换
  • String replace(char oldChar, char newChar): :返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
  • String replace(CharSequence target, CharSequence replacement): :使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
  • String replaceAll(String regex, String replacement) : : 使 用 给 定 的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
  • String replaceFirst(String regex, String replacement) : : 使 用 给 定 的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
  • 匹配
  • boolean matches(String regex): :告知此字符串是否匹配给定的正则表达式。
  • 切片
  • String[] split(String regex): :根据给定正则表达式的匹配拆分此字符串。
  • String[] split(String regex, int limit): :根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

类型转换

String 与 char[] 之间的转换:

  • String –> char[] 调用String的toCharArray()

    1
    char[] charArray = str1.toCharArray();
  • char[] –> String 调用String的构造器

    1
    String str2 = new String(arr);

String 与 byte[] 之间的转换:

  • String –> byte[] 调用String的getBytes()

    1
    byte[] bytes = str1.getBytes(); //使用默认的字符集进行编码
  • byte[] –> String 调用String的构造器

    1
    2
    String str2 = new String(bytes);//使用默认的字符集进行解码
    //说明:解码时,要求解码使用的字符集必须和编码时使用的字符集一致,否则出现乱码

StringBuffer类

java.lang.StringBuffer代表 可变的字符序列,JDK1.0中声明,可以对字符串内容进行增删,此时不会产生新的对象。很多方法与String相同。作为参数传递时,方法内部可以改变值。

StringBuffer类不同于String,其对象必须使用构造器生成。有三个构造器:

  • StringBuffer() :初始为 容量为16 的字符串缓冲区
  • StringBuffer(int size) :构造 指定容量的字符串缓冲区
  • StringBuffer(String str) :将内容初始化为指定字符串内容

StringBuffer 类的常用方法

  • StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接(啥都变成字符串,例如“null”)
  • StringBuffer delete(int start,int end):删除指定位置的内容
  • StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
  • StringBuffer insert(int offset, xxx):在指定位置插入xxx
  • StringBuffer reverse() :把当前字符序列逆转

  • public int indexOf(String str)

  • public String substring(int start,int end)
  • public int length()
  • public char charAt(int n )
  • public void setCharAt(int n ,char ch)

## StringBuilder类

StringBuilder和StringBuffer 非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样

对比String 、StringBuffer 、StringBuilder

  • String(JDK1.0):不可变字符序列

  • StringBuffer(JDK1.0):可变字符序列、效率低、线程安全

  • StringBuilder(JDK 5.0):可变字符序列、效率高、线程不安全,底层都是使用char[] 存储

    注意:作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值。

1
2
3
4
5
6
7
8
String str = null;
//System.out.println(str.length());//NullPointerException
StringBuffer sb = new StringBuffer();
sb.append(str);
System.out.println(sb.length());//4
System.out.println(sb);//"null"
//StringBuffer sb1 = new StringBuffer(str);
//System.out.println(sb1);//NullPointerException

源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
String str = new String();// char[] value = new char[0];
String str1 = new String("abn");// char[] value = new char[]{'a','b','n'};

StringBuffer sb1 = new StringBuffer();// char[] value = new char[16];
sb1.append('a');// value[0] = 'a';
sb1.append('b');// value[1] = 'b';

StringBuffer sb2 = new StringBuffer("abc");// char[] value = new char["abc".length + 16]{'a','b','c's};

//问题一:
System.out.println(sb2.length());// 3
//问题二:
//扩容问题:若添加的数据底层数组装不下,那就需要扩容底层的数组;默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中。

指导建议:

​ 开发中使用,StringBuffer(int capacity) 或 StringBuilder(int capacity) 指定容量的

对比三者效率:

​ StringBuilder > StringBuffer > String

总结:

  • 增:append(xxx)
  • 删:delete(int start, int end)
  • 改:setCharAt(int n, char ch) / replace(int start, int end, String str)
  • 查:charAt(int n)
  • 插:insert(int offset, xxx)
  • 长度:length()
  • *遍历:for() + charAt() / toString()

时间相关的类

JDk8之前的日期和时间的API

①. java.lang.System类

System.currentTimeMillis(): 返回当前时间与1970年1月1日0时0分0秒之间 以毫秒为单位的时间差,也称为时间戳

②. java.util.Date (java.sql.Date继承前者)

构造器:

  • Date() 创建一个对应当前时间的Date对象
  • //Date(int year, int month, int day) 创建一个对应时间的Date对象
  • Date(long) 创建指定毫秒数的Date对象

方法:

  • tiString() 显示当前年月日
  • getTime() 获取当前Date对象对应的时间戳

java.sql.Date 对应着数据库中的日期类型的变量

  • 将java.util.Date对象转换成java.sql.Date对象

    1
    java.sql.Date dateSql = new java.sql.Date(dateUtil.getTime());
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
public class TimeTest {
//1.System类中的currentTimeMillis()
@Test
public void test11(){
long time = System.currentTimeMillis();
System.out.println(time);
//1569513273409返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差

}
//2.java.util.Date类(java.sql.Date类)
// ①两个构造器的使用
// > Date() 创建一个当前时间的Date对象
// > Date(long) 创建指定毫秒数的Date对象
// ②两个方法的使用
// > toString() 显示当前的年、月、日、时、分、秒
// > getTime() 获取当前Date对象对应的毫秒数(时间戳)
// ③java.sql.Date类对应着数据库中的日期类型的变量
// > 如何实例化
// > util.Date --getTime()-> sql.Date对象
// >< sql.Date --> util.Date对象(不需要转,学生本身就是人)
@Test
public void test22(){
//构造器一:Date() 创建一个当前时间的Date对象
Date date1 = new Date();
System.out.println(date1.toString());//Fri Sep 27 00:50:46 CST 2019
System.out.println(date1.getTime());//1569516806737
//构造器二:Date(long) 创建指定毫秒数的Date对象
Date date2 = new Date(1569516806737L);
System.out.println(date2.toString());//Fri Sep 27 00:53:26 CST 2019
//创建java.sql.Date对象
java.sql.Date date3 = new java.sql.Date(1569516806737L);
System.out.println(date3);//2019-09-27有无.toString()一样
//sql.Date --> util.Date对象
Date date8 = date3;
System.out.println(date8.toString());
//util.Date --getTime()-> sql.Date对象
//情况1
Date date4 = new java.sql.Date(1569516806737L);
java.sql.Date date5 = (java.sql.Date) date4;
//情况2
Date date6 = new Date();
java.sql.Date date7 = new java.sql.Date(date6.getTime());
}
}

③. SimpleDateFormat类

java.text.SimpleDateFormat() 不与语言环境有关的方式来对Date类的格式化和解析的具体类

构造器:

  • SimpleDateFormat() 默认的模式和语言环境创建对象
  • SimpleDateFormat(String Pattern) 用参数pattern指定的格式创建一个对象。

该对象可以

  • 格式化: 日期 -> 文本 String format(Date date)
  • 解析:文本 -> 日期 Date parse(String source)
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
public class DateTimeTesr {
/*
SimpleDateFormat的使用:对日期Date类的格式化和解析
1.两个操作
- 格式化: 日期 ---> 字符串
- 解析: 字符串 ---> 日期
2.SimpleDateFormat的实例化
*/
@Test
public void testSimpleDateFormat(){
//实例化SimpleDateFormat:使用默认构造器
SimpleDateFormat sdf = new SimpleDateFormat();

//格式化: 日期 ---> 字符串
Date date = new Date();
System.out.println(date);

String format = sdf.format(date);
System.out.println(format);

//解析: 字符串 ---> 日期
String str = "19-9-27 下午8:12";
Date date1 = null;
try {
date1 = sdf.parse(str);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(date1);

// 按照指定的方式格式化和解析,调用带参数的构造器
// SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyy.MMMMM.dd GGG hh:mm aaa");
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//格式化
String format1 = sdf1.format(date);
System.out.println(format1);//2019-09-27 08:22:08
//解析
Date date2 = null;
try {
date2 = sdf1.parse("2019-09-27 08:22:08");
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(date2);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void test12() throws ParseException {
/*
练习一: 字符串“2020-09-08”抓换成java.sql.Date

练习二: “三天打鱼两天晒网” 1990-01-01 xxxx-xx-xx 打鱼?晒网?
*/
String birth = "2020-09-08";

SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf1.parse(birth);
// System.out.println(date);
java.sql.Date birthDate = new java.sql.Date(date.getTime());
System.out.println(birthDate);
//思路:总天数%5==?
//方式一:( date2.getTime() - date1.getTime() ) / (1000 * 60 * 60 * 24) + 1
//方式二:时间分段整数年加闰年
}

④. Calendar类

是一个抽象基类,主要用于完成日期之间相互操作的功能。

1.实例化
方式一:创建其子类(GregorianCalendar)的对象
方式二:调用其静态方法getInstance()

2.常用方法

  • get()
  • set() 可变性
  • add()
  • getTime() 日历类 –> Date
  • setTime() Date –> 日历类

注意:

  • 获取月份时:一月是0 … 十二月是11
  • 获取星期时:周日是1 … 周六是7
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
/*
Calendar类(抽象类)的使用
*/
@Test
public void testCalendar(){
// 1.实例化
// 方式一:创建其子类(GregorianCalendar)的对象
// 方式二:调用其静态方法getInstance()
Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getClass());

// 2.常用方法
// get()
int days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);//28
System.out.println(calendar.get(Calendar.DAY_OF_YEAR));//271

// set() 可变性
calendar.set(Calendar.DAY_OF_MONTH,22);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);//22

// add()
calendar.add(Calendar.DAY_OF_MONTH,22);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);//14

// getTime() 日历类 --> Date
Date date = calendar.getTime();
System.out.println(date);

// setTime() Date --> 日历类
Date date1 = new Date();
calendar.setTime(date1);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);//28
}

JDK 8中新日期时间API

java.time

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
/*
LocalDate LocalTime LocalDateTime 的使用
类似于Calendar()
*/
@Test
public void testTime(){
//1. now() 获取当前时间日期
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();

System.out.println(localDate);//2019-09-28
System.out.println(localTime);//09:19:28.307
System.out.println(localDateTime);//2019-09-28T09:19:28.307

//2. of() 设置指定的年月日时分秒,没有偏移量
LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 10, 0, 0, 0);
System.out.println(localDateTime1);//2020-10-10T00:00

//getXxx() 获取相关属性
System.out.println(localDateTime.getDayOfMonth());//28
System.out.println(localDateTime.getDayOfWeek());//SATURDAY
System.out.println(localDateTime.getDayOfYear());//271
System.out.println(localDateTime.getHour());//9
System.out.println(localDateTime.getMonthValue());//9

//withXxx() 修改、设置 不同于Calendar() 体现不可变性
LocalDate localDate1 = localDate.withDayOfMonth(22);
System.out.println(localDate);//2019-09-28
System.out.println(localDate1);//2019-09-22

//plusXxx() 增加
LocalDateTime localDateTime2 = localDateTime.plusMonths(4);
System.out.println(localDateTime);//2019-09-28T09:29:43.638
System.out.println(localDateTime2);//2020-01-28T09:29:43.638

//minusXxx() 减去
LocalDateTime localDateTime3 = localDateTime.minusDays(3);
System.out.println(localDateTime);//2019-09-28T09:32:06.256
System.out.println(localDateTime3);//2019-09-25T09:32:06.256
}

Instant

瞬时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
Instant 的使用
类似于Date
*/
@Test
public void testInstant(){
//now() 获取本初子午线对应的标准时间
Instant instant = Instant.now();
System.out.println(instant); //2019-09-28T01:42:23.739Z

//添加时间的偏移量
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime); //2019-09-28T09:42:23.739+08:00

//toEpochMilli() 获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数 --> Date类的getTime()
long milli = instant.toEpochMilli();
System.out.println(milli);//1569638022547

//ofEpochMilli() 通过给定的毫秒数,获取Instant实例 --> Date(long millis)
Instant instant1 = Instant.ofEpochMilli(11111111L);
System.out.println(instant1);//1970-01-01T03:05:11.111Z
}

java.time.format.DateTimeFormatter

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
/*
DateTimeFormatter 格式化或解析日期、时间
类似于SimpleDateFormat
*/
@Test
public void test3(){
//方式1:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
//格式化: 日期 --> 字符串
LocalDateTime localDateTime = LocalDateTime.now();
String str = formatter.format(localDateTime);
System.out.println(localDateTime);//2019-09-28T14:49:57.682
System.out.println(str);//2019-09-28T14:49:57.682
//解析: 字符串 --> 日期
TemporalAccessor parse = formatter.parse("2019-09-28T14:49:57.682");
System.out.println(parse);//{},ISO resolved to 2019-09-28T14:49:57.682

//方式2:本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)
//FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT ↑
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
//格式化
String str1 = formatter1.format(localDateTime);
System.out.println(localDateTime);//2019-09-28T15:33:41.802
System.out.println(str1);//2019年9月28日 下午03时33分41秒
//ofLocalizedDate()
DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
//格式化
String str2 = formatter2.format(LocalDate.now());
System.out.println(str2);//2019年9月28日 星期六

//方式3:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//格式化
String str3 = formatter3.format(localDateTime);
System.out.println(localDateTime);//2019-09-28T15:38:38.889
System.out.println(str3);//2019-09-28 03:38:38

//解析
TemporalAccessor parse1 = formatter3.parse("2019-09-28 03:38:38");
System.out.println(parse1);//{SecondOfMinute=38, HourOfAmPm=3, MicroOfSecond=0, NanoOfSecond=0, MinuteOfHour=38
}

Java比较器

对象数组的排序问题,涉及对象之间的比较。Java对象正常情况下,只能进行比较: == 或 != ,不能使用 > 或 < 。Java实现对象排序的方式有两种:

自然排序:java.lang.Comparable

Comparable接口的使用举例 自然排序

  1. 像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个大小的方式

  2. 像String、包装类等重写了compareTo()方法以后,进行了从小到大的排序

  3. 重写compareTo(obj)的规则:

    如果当前对象this大于形参对象obj,则返回正整数;

    如果当前对象this小于形参对象obj,则返回负整数;

    如果当前对象this等于形参对象obj,则返回零。

  4. 对于自定义类,若需要排序,可让自定义类实现Comparable接口,重写compareTo(obj)方法指明如何排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test1(){
String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}

//Eg:商品价格从低到高:按价格排序
class Goods implements Comparable {
}
@Override
public int compareTo(Objact o) {
if(o instanceod Goods) {
Goods goods = (Goods)o;
return Double.compare(this.price,goods.price)
}
throw new RuntimeException("传入的数据类型不一致");
}

定制排序:java.util.Comparator

  1. 背景:

    当元素的类型没有实现Comparable接口 而又不方便修改代码;或者实现了Comparable接口的排序规则不适合当前的操作,那么可以考虑使用Comparator的对象来进行排序。

  2. 重写compare(Object o1, Object o2)方法,比较o1和o2的大小:

    若方法返回正整数,表示o1大于o2;

    返回0,表示相等;

    返回负数,表示o1小于o2。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test2(){
String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};
Arrays.sort(arr,new Comparator(){
//按照字符串大到小
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof String && o2 instanceof String){
String s1 = (String) o1;
String s2 = (String) o2;
return -s1.compareTo(s2);
}
// return 0;
throw new RuntimeException("输入的数据类型不一致");
}
});
System.out.println(Arrays.toString(arr));
}

Comparable接口与Comparator的使用的对比:

  • 前者一旦指定,保证Comparable接口实现类的对象在任何位置都可以比较大小

  • 后者属于临时性的比较。Arrays.sort(arr,new Comparator(){…..}

其他类

System类

  • System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包。
  • 由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便的进行调用。
  • 成员变量
    System类内部包含 in、out和err 三个成员变量,分别代表 标准输入流(键盘输入),标准输出流(显示器)和 标准错误输出流(显示器)。
  • 成员方法

    • native long currentTimeMillis():
      该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。

    • void exit(int status):
      该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。 使用该方法可以在图形界面编程中实现程序的退出功能等。

    • void gc():
      该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。

    • String getProperty(String key):
      该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下表所示:

      |属性名 |属性说明|
      | —- | —- |
      |java. version |Java运行时环境版本|
      |java. home |java安装目录操作系统的名称|
      |os.version |操作系统的版本|
      |user.nane |用户的账户名称|
      |user.home |用户的主目录|
      |user.dir |用户的当前工作目录|

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
@Test
public void test3(){
String javaVersion = System.getProperty("java.version");
System.out.println("java的version:" + javaVersion);
String javaHome = System.getProperty("java.home");
System.out.println("java的home:" + javaHome);
String osName = System.getProperty("os.name");
System.out.println("os的name:" + osName);
String osVersion = System.getProperty("os.version");
System.out.println("os的version:" + osVersion);
String userName = System.getProperty("user.name");
System.out.println("user的name:" + userName);
String userHome = System.getProperty("user.home");
System.out.println("user的home:" + userHome);
String userDir = System.getProperty("user.dir");
System.out.println("user的dir:" + userDir);
}
out:
java的version:1.8.0_191
java的home:C:\Program Files\Java\jdk1.8.0_191\jre
os的name:Windows 10
os的version:10.0
user的name:goodwell
user的home:C:\Users\goodwell
user的dir:D:\Codes\IdeaProjects\JavaSenior\Day

Math类

java.lang.Math 提供了一系列静态方法用于 科学 计算。其 方法的参数和返回值类型一般为double 型

  • abs 绝对值
  • acos,asin,atan,cos,sin,tan 三角函数
  • sqrt 平方根
  • pow(double a,doble b) a 的b 次幂
  • log 自然对数
  • exp e 为底指数
  • max(double a,double b)
  • min(double a,double b)
  • random() 返回0.0 到1.0 的随机数
  • long round(double a) double 型数据a 转换为long 型(四舍五入)
  • toDegrees(double angrad) 弧度—> 角度
  • toRadians(double angdeg) 角度—>弧度

BigInteger与BigDecimal

BigInteger类

java.math包的 BigInteger 可以表示不可变的任意精度的整数。提供所有 Java 的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法。
另外,BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、位操作以及一些其他操作。

  • 构造器
     BigInteger(String val):根据字符串构建BigInteger对象

  • 常用 方法
     public BigInteger abs()

    返回此 BigInteger 的绝对值的 BigInteger。
     BigInteger add(BigInteger val) :

    返回其值为 (this + val) 的 BigInteger
     BigInteger subtract(BigInteger val) :

    返回其值为 (this - val) 的 BigInteger
     BigInteger multiply(BigInteger val) :

    返回其值为 (this * val) 的 BigInteger
     BigInteger divide(BigInteger val) :

    返回其值为 (this / val) 的 BigInteger。整数相除只保留整数部分。
     BigInteger remainder(BigInteger val) :

    返回其值为 (this % val) 的 BigInteger。
     BigInteger[] divideAndRemainder(BigInteger val):返回包含 (this / val) 后跟(this % val) 的两个BigInteger 的数组。
     BigInteger pow(int exponent) :

    返回其值为 (this exponent ) 的 BigInteger。

### BigDecimal类

一般的Float类和Double类可以用来做科学计算或工程计算,但在 商业计算中,到 要求数字精度比较高,故用到java.math.BigDecimal类 。
BigDecimal类支持不可变的、任意精度的有符号十进制定点数。

  • 构造器
     public BigDecimal(double val)
     public BigDecimal(String val)
  • 常用方法
     public BigDecimal add(BigDecimal augend)
     public BigDecimal subtract(BigDecimal subtrahend)
     public BigDecimal multiply(BigDecimal multiplicand)
     public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
1
2
3
4
5
6
7
8
9
public void testBigInteger() {
BigInteger bi = new BigInteger("12433241123");
BigDecimal bd = new BigDecimal("12435.351");
BigDecimal bd2 = new BigDecimal("11");
System.out.println(bi);
// System.out.println(bd.divide(bd2));
System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));
System.out.println(bd.divide(bd2, 15, BigDecimal.ROUND_HALF_UP));
}

枚举类的使用

含义: 类的对象只有有限个确定的。(当需要定义一组常量时,强烈建议使用枚举类。若枚举类只有一个对象,则可作为单例模式的实现方式。)

如何定义:

  • 方式一:jdk5.0之前,自定义枚举类
  • 方式二:jdk5.0时,可使用enum关键字定义枚举类
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
public class enumTest {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);//Season{seasonName='春天', seasonDesc='春暖花开'}
}
}

//自定义枚举类
class Season {
//1.声明season对象的属性: private final修饰
private final String seasonName;
private final String seasonDesc;

//2.私有化类的构造器,并给对象属性赋值
private Season(String seasonName, String seasonDesc) {
this.seasonDesc = seasonDesc;
this.seasonName = seasonName;
}

//3.提供当前枚举类的多个对象: public static final修饰
public static final Season SPRING = new Season("春天", "春暖花开");
public static final Season SUMMER = new Season("夏天", "夏日炎炎");
public static final Season AUTUMN = new Season("秋天", "秋高气爽");
public static final Season WINTER = new Season("冬天", "冬天雪地");

//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}

public String getSeasonDesc() {
return seasonDesc;
}
//4.其他诉求2:提供toString()
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
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
public class enumTest1 {
public static void main(String[] args) {
Season1 summer = Season1.SUMMER;
System.out.println(summer);//SUMMER

System.out.println(Season1.class.getSuperclass());//class java.lang.Enum
}
}

//使用enum关键字定义枚举类
//说明:定义的枚举类默认继承于class java.lang.Enum
enum Season1 {
//1.提供当前枚举类的多个对象,多个对象之间用","隔开,末尾对象";"结束
SPRING("春天", "春暖花开"),
SUMMER("夏天", "夏日炎炎"),
AUTUMN("秋天", "秋高气爽"),
WINTER("冬天", "冬天雪地");

//2.声明season对象的属性: private final修饰
private final String seasonName;
private final String seasonDesc;

//2.私有化类的构造器,并给对象属性赋值
private Season1(String seasonName, String seasonDesc) {
this.seasonDesc = seasonDesc;
this.seasonName = seasonName;
}

//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}

public String getSeasonDesc() {
return seasonDesc;
}
// //4.其他诉求2:提供toString()
//
// @Override
// public String toString() {
// return "Season1{" +
// "seasonName='" + seasonName + '\'' +
// ", seasonDesc='" + seasonDesc + '\'' +
// '}';
// }
}

Enum类的主要方法

  • values() 方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
  • valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
  • toString():返回当前枚举类对象常量的名称
1
2
3
4
5
6
7
8
9
//values():
Season1[] values = Season1.values();
for (int i = 0; i < values.length; i++) {
System.out.println(values[i]);
}//SPRING SUMMER AUTUMN WINTER

//valueOf(String objName):返回枚举类中对象名是objName的对象
Season1 winter = Season1.valueOf("WINTER");
System.out.println(winter);//WINTER

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

  • 情况一:实现接口,在enum类中实现抽象方法;
  • 情况二:让枚举类的对象分别实现接口中的抽象方法(每个都不一样)
1
2
3
4
5
6
SPRING("春天", "春暖花开"){
@Override
public void show(){
System.out.println("春天在哪里");
}
}

注解(Annotation)的使用

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

理解Annotation:

  • jdk 5.0 新增的功能
  • Annotation 其实就是代码里的 特殊标记, 这些标记可以在编译, 类加
    载, 运行时被读取, 并执行相应的处理。通过使用Annotation, 程序员
    可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
  • 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,
    忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如
    用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗
    代码和XML配置等。

Annotation的使用示例

  • 示例一:生成文档相关的注解

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

    • @Override: 限定重写父类方法, 该注解只能用于方法
    • @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为
      所修饰的结构危险或存在更好的选择
    • @SuppressWarnings: 抑制编译器警告
  • 示例三: 跟踪 代码依赖性,实现替代配置文件功能

如何自定义注解

  1. 注解声明为:publi @interface XXX
  2. 内部定义成员,通常使用value表示
  3. 可以指定成员的默认值,使用default定义(String[] value default “hello”)
  4. 若自定义注解没有成员,表明是一个标识作用

PS:

  • 若注解有成员,在使用注解时,需要指明成员的值
  • 自定义注解必须配上注解的信息处理流程(使用反射)才有意义
  • 自定义注解通常都会指明两个元注解:Retention、Target

jdk 提供的4种元注解

元注解:对现有的注解进行解释说明的 注解

  • Retention:指定所修饰的 Annotation 的生命周期:SOURCE \ CLASS(默认行为)\ RUNTIME只有声明为RUNTIME生命周期的注解,才能通过反射获得
  • Target:用于指定被修饰的 Annotation 能用于修饰那些程序元素
  • Documented:表示所修饰的注解在被javadoc解析时,保留下来
  • Inherited:被它修饰的 Annotation 将具有继承性

JDK8中 注解的新特性可重复注解 、 类型注解

  • 可重复注解

    ① 在MyAnnotation 上声明@Repeatable,成员值为 MyAnnotation.class

    ② MyAnnotationd Taget 和 Reten等元注解与MyAnnotation相同。

  • 类型注解

    ELementType. TYPE PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)
    ELementType.TYPE_USE 表示该注解能写在使用类型的任何语句中

集合

概述

  • 集合、数组都是对 多个数据 进行 存储操作 的结构,简称Java容器
  • 说明:此时的存储,主要指的是内存层面的存储,不涉及持久化的存储(.txt,.jpg,.avi,数据库中)

数组在存储多个数据方面的特点(缺点*):

  • (*)一旦初始化后,长度就确定了
  • 一旦定义好,元素的类型也确定了
  • (*)提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高
  • (*)获取数组中实际元素的个数的需求,没有现成的属性或方法可用
  • (*)存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足

Java集合 可以分为CollectionMap两种体系(接口)

  • Collection接口:单列数据,定义了存取一些对象的方法的集合
    • List:元素有序、可重复的集合,“动态数组”
    • Set:元素无序、不可重复的集合,“数学上的集合”
  • Map接口:双列数据,保存具有映射关系”key-value对”的集合,“数学上的函数映射”

集合框架

1
2
3
4
5
6
7
8
9
|----Collection接口:单列集合,用来存储一个一个的对象
|----List接口:存储有序的、可重复的数据 --> “动态”数组
|----ArrayList、LinkedList、Vector

|----Set接口:存储无序的、不可重复的数据 --> 数学中的“集合”
|----HashSet、LinkedHashSet、TreeSet

|----Map接口:双列接口,用来存储一对(key-value)数据 --> y = f(x)
|----HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

Collection常用方法(通用)

  • add(Object e): 将元素e添加到集合coll中
  • addAll(Collection coll1): 将coll1集合中的元素添加到当前的集合中
  • size(): 获取添加的元素的个数
  • clear(): 清空集合元素
  • isEmpty(): 判断当前集合是否为空
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
@Test
public void test1(){
Collection coll = new ArrayList();

//add(Object e): 将元素e添加到集合coll中、
coll.add("AA");
coll.add("BB");
coll.add(123); //自动装箱
coll.add(new Date());

//size(): 获取添加的元素的个数
System.out.println(coll.size());//4

//addAll(Collection coll1): 将coll1集合中的元素添加到当前的集合中
Collection coll1 = new ArrayList();
coll1.add(456);
coll1.add("cc");
coll.addAll(coll1);

System.out.println(coll.size());//6
System.out.println(coll);//[AA, BB, 123, Thu Oct 31 12:03:02 CST 2019, 456, cc]

//clear(): 清空集合元素
coll.clear();

//isEmpty(): 判断当前集合是否为空
System.out.println(coll.isEmpty());//true
}
  • contains(Object obj): 判断当前集合中是否包含obj

PS:向Collection接口的实现类的对象中 添加数据obj时,要求obj所在类要重写equals()。不是判断地址,判断内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test2(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
Person p = new Person("Jerry1", 20);
coll.add(p);
coll.add(new Person("Jerry", 20));
coll.add(new String("Tom"));
coll.add(false);

//1.contains(Object obj): 判断当前集合中是否包含obj
//我们在判断时会调用obj对象所在类的equals()
boolean contains = coll.contains(123);
System.out.println(contains);//true
System.out.println(coll.contains(new String("Tom")));//true

System.out.println(coll.contains(new Person("Jerry", 20)));//false
System.out.println(coll.contains(p));//true
//PS:向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()。
}
  • containsAll(Collection coll1): 判断形参coll1中的所有元素都存在于当前集合中。
1
2
3
//2.containsAll(Collection coll1): 判断形参coll1中的所有元素都存在于当前集合中。
Collection coll1 = Arrays.asList(123,4567);
System.out.println(coll.containsAll(coll1));//false
  • remove(Object obj): 从当前集合中移除obj元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test3(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry", 20));
coll.add(new String("Tom"));
coll.add(false);

//3.remove(Object obj): 从当前集合中移除obj元素
coll.remove(123);
System.out.println(coll);
//[456, com.good.java.Person@4ee285c6, Tom, false]
coll.remove(new Person("Jerry", 20));
System.out.println(coll);
//[456, com.good.java.Person@4ee285c6, Tom, false]
}
  • removeAll(Collection coll1): 差集,从当前集合中移除coll1中所有的元素
1
2
3
4
5
//4.removeAll(Collection coll1): 差集,从当前集合中移除coll1中所有的元素
Collection coll1 = Arrays.asList(123,456);
coll.removeAll(coll1);
System.out.println(coll);
//[com.good.java.Person@4ee285c6, Tom, false]
  • retainAll(Collection coll1): 交集,获取当前集合和coll1集合的交集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test4() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry", 20));
coll.add(new String("Tom"));
coll.add(false);

//5.retainAll(Collection coll1): 交集,获取当前集合和coll1集合的交集
Collection coll1 = Arrays.asList(123,456,789);
coll.retainAll(coll1);
System.out.println(coll);
//[123, 456]
}
  • equals(Object obj)
1
2
3
4
5
6
7
8
9
    Collection coll1 = new ArrayList();
coll1.add(123);
coll1.add(456);
// coll1.add(new Person("Jerry", 20));
coll1.add(new String("Tom"));
coll1.add(false);

//6.equals(Object obj)
System.out.println(coll.equals(coll1));
  • hashCode(): 返回当前对象的哈希值
1
2
3
4
5
6
7
8
9
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry", 20));
coll.add(new String("Tom"));
coll.add(false);

//7.hashCode(): 返回当前对象的哈希值
System.out.println(coll.hashCode());//701070075
  • 集合 —> 数组:toArray()

    /拓展:数组 —> 集合: 调用Arrays类的静态方法asList()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Object[] arr = coll.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}

//拓展:数组 ---> 集合: 调用Arrays类的静态方法asList()
List<String> list = Arrays.asList(new String[]{"aa","bb","cc"});
System.out.println(list);//[aa, bb, cc]

List arr1 = Arrays.asList(new int[]{123,456});
System.out.println(arr1.size());//1

List arr2 = Arrays.asList(new Integer[]{123,456});
System.out.println(arr2.size());//2
  • iterator() 返回Iterator接口的实例,用于遍历集合元素,注意是一次性的

集合元素的遍历(迭代器接口Iterator)

  • next() 判断是否还有下有一个元素
  • hasNext() ①指针下移 ②将下移以后集合位置上的元素返回
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
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add("aa");
coll.add("BB");
coll.add(123); //自动装箱
coll.add(new Date());
Iterator iterator = coll.iterator();

//方式一:
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
//报异常NoSuchElementException
// System.out.println(iterator.next());

//方式二: 不推荐
// for (int i = 0; i < coll.size(); i++) {
// System.out.println(iterator.next());
// }

//方式三: 推荐
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
  • remove() 删除集合中某数据(调用前需要先next())
1
2
3
4
5
6
while (iterator.hasNext()) {
Object obj = iterator.next();
if ("BB".equals(obj)) {
iterator.remove();
}
}

foreach 循环遍历集合

JDK5 新增了foreach 用于遍历数组和集合(内部任然调用迭代器)

  • for(集合元素类型 局部变量:集合对象)
1
2
3
4
for (Object obj : coll) {
System.out.println(obj);
}
// 注意是局部变量,不会改变

List接口

替代数组,元素有序,且可重复

具体实现类:ArrayList、LinkedList、Vector

三者异同:

  • 同:三个类都实现了List接口,存储数据的特点相同,元素有序,且可重复

  • 异:

    ArrayList 作为List接口的主要实现类 ,线程不安全,效率高,底层用Object[]存储

    LinkedList 底层用双向链表存储,对于频繁插入、删除操作,此类效率高

    Vector 作为List的古老实现类,线程安全,效率低,底层用Object[]存储

Arraylist,的源码分析:JDK7情况下
ArrayList list= new ArrayList(/(底层创建了长度是10850bc数 HelementData List. add (123); //eLementData【0】= new Integer(123); List.0(1):/0果此次的添加导致底层 eLementDat数组容量不够,则扩容默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中结论:建议开发中使用带参的构造器:ArrayList List= new Arraylist( unt capacity)

JDK8中 ArrayList的变化:
ArrayList list= new ArrayList()/)底层0 bject】 elementdata初始化为},并没有创建List.0d(123);//第次调用d()时,底层才创建了长度10的数组,并将数据123添加到 elemen后续的添加和扩容操作与jR7无异

总结:JDK7中的 Arraylist的对象的创建类似于单例的汉式,而8中的ryst的对象的创建类似于单例的像汉式,延迟了数组的创建,节省内存

List中的常用方法

  • void add (int index, Object ele):在 index位置插入eLle元素
  • boolean addAll (int index, Collection eles):从 index位置开始特eles中的所有元素添加进来
  • Object get ( int index):获取指定 index位置的元素
  • int indexOf (Object obj):返园obj在集合中首次出现的位置
  • int lastIndexOf (Object obj):返bj在当前集台中末次出现的位置
  • Object remove ( int index):移除指定inex位置的元素,并返回此元素。(区别于Collection的,eg:List. remove(2); list.remove(new Integer (2);)
  • Object set ( int index, Object ele):设置指定 index位置的元素为ele
  • List subList ( int fromIndex, int toIndex):返从 fromIndex到 toIndex位置的子集合

总结:常用方法

  • 增:add(Object obj)
  • 删:remove(int index) / remove(object obj)
  • 改: set(int index, Object ele)
  • 查:get(int index)
  • 插:add(int index, Object ele)
  • 长度:size()
  • 遍历:①Iterate送代器 ②增强for循环 ③普通的循环

Set接口

存储无序的、不可重复的数据 –> 数学中的“集合”
|—-HashSet、LinkedHashSet、TreeSet

  • 无序性:不等于随机性,添加的位置不同
  • 不可重复性:保证添加的元素按照equals()判断时,不能返回true

PS:Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。

要求:

  • 向Set中添加的数据,其所在类一定要重写hashCode() 和 equals()
  • 重写的方法必须保持一致性,相同的对象必须具有相等的散列码。(技巧:对象中用作equals方法比较的Field,都应该用来计算hashCode值)

HashSet: 作为set接口的主要实现类;线程不安全的;可以存储null值

LinkedHashSet: 作为HashSet的子类,遍历内部数据时,可以按照添加的顺序遍历(原因:在添加数据的同时,每个数据还维护了俩应用,记录此数据前一个数据和后一个数据的地址)优点:对于频繁的遍历操作效率更高。

TreeSet可以按照添加对象的指定属性,进行排序

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
@Test
public void test2(){
//HashSet
Set set = new HashSet();
set.add(123);
set.add("AA");
set.add(false);

Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// AA
// false
// 123
//LinkedHashSet
LinkedHashSet linkedHashSet = new LinkedHashSet();
linkedHashSet.add(123);
linkedHashSet.add("AA");
linkedHashSet.add(false);

Iterator iterator1 = linkedHashSet.iterator();
while (iterator1.hasNext()) {
System.out.println(iterator1.next());
}
// 123
// AA
// false
}

TreeSet:向其中添加数据,要求是相同的对象。比较是否添加的对象相同,此处不使用equals,可以分别实现Comparable和Comparator实现自然和定制排序。

  • 自然排序使用的是compareTo返回0,必须重写compareTo
  • 定制排序使用的是compare返回0,必须重写compare
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test2(){
TreeSet set = new TreeSet();
set.add(123);
set.add(34);
set.add(577);

Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}

Map接口

双列接口,用来存储一对(key-value)数据 –> y = f(x)

|—-HashMap :作为Map的主要实现类;线程不安全,效率高,能存储null 的key 和value,底层:数组+链表(JDK7)+红黑树(JDK8)
|—-LinkedHashMap :保证在遍历map元素时,可以按照添加的顺序实现遍历,对于频繁的遍历操作,此类执行效率高于HashMap
|—-TreeMap : 保证按照添加的 key-value对 进行排序,实现排序遍历,按照key自然排序或定制排序,底层使用红黑树。
|—-Hashtable : 作为古老的实现类,线程安全,效率低,不能存储null 的key 和value
|—-Properties : 常用来处理配置文件,key和value都是String类型

Map结构的理解:

  • Map中的 key:无序的、不可重复的,使用 Set 存储所有的key —–> key所在的类要重写equals和 hashCode
  • Map中的 value:无序的、可重复的,使用 Collection存储所有的 value —–> value所在的类要重写equals
  • 一个键值对:key- value构成了一个 Entry对象
  • Map中的 entry:无序的、不可重复的,使用 Set 存所有entry

HashMap的底层实现原理

HashMap的底层实现原理?d7为例说明HashMap map new HashMap():
在实例化以后,底层创建了长度是16的一维数组 Entry【 table
..可能已经执行过多次put map. put(key1, vaLue1)
首先,调用key1所在类的 hashcode()计算key1哈希值,此哈希信经过某种算法计算以后,得到在 Entry数组中的存放位置。
如果此位置上的数据为空,此时的ey1- value1添加成功情况如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在),比较key1和已经存在的一个或多个数据的哈希值:
如果Rey1的哈希值与已经存在的数据的哈希值都不相同,此的key1- value1添加成功。—)况2如果Rey1的哈希值和已经存在的某一个数据(Rey2-vaue2)的哈希值相同,继续比较:调用key1所在类的 equals(key2如果 equals()返aLse:此的key1-vae1添加成功。—情况3如果 equaLs()返回true:使用vLue1营换 value2补充:关于情况2和情况3:此的key1- value1和原来的数据以链表的方式存储

在不断的添加过程中,会涉及到扩容问题,默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来

jdk8相较于jdk7在底层实现方面的不同1. new Hash№p():底层没有创建一个长度为6的数组
2.j如k8底层的数组是:Mode【】,非 Entry【
3.营次调用put()方法的,底层创建长度为16的数组4.jk7底居结构只有:数组+链表。dR8中底居结构:数组+链表+红黑树当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64的此时此索引位置上的所有数据改为体用红黑树存储。

Map接口的常用方法

添加、删除、修改操作

  • 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集台

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
@Test 
public void test5(){
Map map = new HashMap();
map.put("AA"123);
map.put(45,123);
map.put("BB"56);
//遍历所有的key集:keySet()
Set set = map.keySet();
Iterator iterator = set.iterator();
While(iterator.hasNext()) {
System.out.println(iterator,next());
}
//遍历所有的value集:values()
Collection values = map.values();
for(object obj : values) {
System.out.println(obj);
}
//遍历所有的key-value
//方式一 entrySet()
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
While(iterator1.hasNext()) {
Object obj = iterator1.next();
//entrySet:集合中的元素都是entry
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "----" + entry.getValue());
}
//方式二:
Set keySet = map.keySet();
Iterator iterator2 = keySet.iterator();
While(iterator2.hasNext()){
object key = iterator2.next();
Object value = map.get(key);
System.out.println(key + "=====" + value);
}
}

总结:常用方法:
添加:put(Object key, Object value)
删除:remove(Object key)
修改:put(Object key, Object value)
查询:get(Object key)
长度:size()
遍历:keySet() / values() / entrySet()

向 TreeMap 中添加key- value,要求 key必须是由同一个类创建的对象,因为要按照key进行排序:自然排序、定制排序

1
2
3
4
5
6
7
8
9
// Properties:常用来处理配置文件。key和value都是 String类型
public static void main(String[] args) throws Exception {
Properties pros = new Properties();
FileInputstream fis = new FileInputstream(jdbc.properties);
pros.load(fis); //加载流对应的文件
String name = pros.getProperty("name");
String password = pros.getProperty("password1");
System.out.println("name =" + name + "password =" password);
}

Collections工具类

操作Set、List和Map等集合的工具类

● Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。

排序操作:(均为 static方法)

  • 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对象的所有旧值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test2{
List list = new ArrayList();
list.add(123);
list.add(43);
list.add(765);
list.add(-97);
// 报异常:IndexOutOfBounds Exception(" Source does not fit in dest")
// List dest = new ArrayList;
// Collections.copy(dest, List);
// 正确的:
List dest = Arrays.asList(new Object(list.size());
System.out.println(dest.size());
// List.sizeof
Collections.copy(dest, list);
System.out.println(dest);
}

Collections类中提供了多个synchronizedXxx()方法,该方法可使将指定集合包装成 线程同步 的集合,从而可以解决多线程并发访问集合时的线程安全。

-------------本文结束goodwell感谢您的阅读-------------
小二,上酒~
undefined