Fork me on GitHub

Java-Basics

自用 Java基础 笔记

Java路线

第一阶段入门进阶

  • 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()方法,该方法可使将指定集合包装成 线程同步 的集合,从而可以解决多线程并发访问集合时的线程安全。

泛型Generic

标签

概念

集合容器类在 设计阶段/声明阶段 不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为 Object, JDK1.5之后使用泛型来解决。 因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。 Collection,List, ArrayList这个就是类型参数,即泛型。(不能是基础类型)

所谓泛型,就是允许在定义类、接口时通过一个标识表示类中 某个属性的类型 或者是 某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。

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
//在集合中使用泛型之前的情况
@Test
public void test1{
ArrayList list = new ArrayList();
//需求:存放学生的成绩
list.add(78);
list.add(76);
//问题一:类型不安全
List.add("Tom");
for(object score : list){
//问题二:强转时,可能出现 CLassCastException
int stuScore = (Integer) score;
System.out.println(stuScore);
}
//在集合中使用泛型的情况
public void test2{
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(78);
list.add(76);
//编译时,就会进行类型检查,保证数据的安全
List.add("Tom");
for(Integer score : list) {
//避免了强转操作
int stuScore = score;
System.out.println(stuScore);
}

//方式二:
Iterator<Integer> iterator = list.iterator();
While(iterator.hasNext()) {
int stuScore = iterator.next();
System.out.println(stuScore);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//在集合中使用泛型的情况:以 HashMap 为例
public void test3(){
Map<String,Integer> map = new HashMap<String, Integer>();
map.put("Jerry", 87);
map.put("Jack", 67);
// map.put(123,"ABC");报错
//泛型的放套
Set<Map Entry<string, Integer>> entry = map.entryset();
Iterator<Map Entry<String, Integer>> iterator = entry.iterator();
While(iterator.hasNext()){
Map Entry<String, Integer> e = iterator.next();
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key + "----" + value);
}

在集合中使用泛型总结

  • ① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。

  • ② 在实例化 集合类 时,可以指明具体的泛型类型

  • ③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型

    比如:add(E e) —> 实例化以后:add(Integer e)

  • ④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换

  • ⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。

自定义泛型结构

泛型类、泛型接口

1
2
3
4
5
//子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再要指明泛型
public class SubOrder1<T> extends order<T>
// Suborder1<T>:仍然是泛型类
public class subOrder extends order<Integer>
// SubOrder:不是泛型类

PS:

  • 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。

    比如 <E1, E2, E3>

  • 泛型类的 构造器 如下: public GenericClass(){}
    而下面是错误的:public Generic Class(){}

  • 实例化后,操作原来泛型位置的结构 必须与指定的泛型类型一致

  • 泛型不同的引用不能相互赋值

    尽管在编译时 ArrayList和 ArrayList是两种类型,但是,在运行时只有一个 ArrayList被加载到JVM中

  • 泛型如果不指定,将被搽除,泛型对应的类型均按照Object处理,但不等价于Object。

    经验:泛型要使用一路都用。要不用,一路都不要用。

  • 如果泛型类是一个接口或抽象类,则不可创建泛型类的对象。

  • jdk1.7,泛型的简化操作:ArrayList fist = new Array List<>();;

  • 泛型的指定中不能使用基本数据类型,可以使用包装类替换。

  • 在 类/接口 上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。

  • 异常类不能是泛型的

  • 不能使用new E。但是可以:E elements = (E) new Object()
    参考:ArrayList 源码中声明:Object[] elementData,而非泛型参数类型数组。

  • 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:

    子类 不保留 父类的泛型:按需实现没有类型 擦除 具体类型

    子类 保留 父类的泛型:泛型子类 全部保留 / 部分保留

    结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型

泛型方法

在方法中出现了泛型的结构,泛型参数 与 类的泛型参数没有任何关系
换句话说,泛型方法所属的类是不是泛型类都没有关系

泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的,并非在实例化类时确定。

1
2
3
4
5
6
7
public static <E> List<E> copyFromArrayToList(E[] arr) {
ArrayList<E> list = new ArrayList<E>();
for(E e : arr) {
list.add(e);
}
return list
}

泛型在继承方面的体现

虽然类A是类B的父类,但是G 和 G 二者不具备子父类关系,二者是并列关系。

补充:类A是类B的父类,A 是B 的父类

通配符: ?

类A是类B的父类,G 和G 是没有关系的,二者共同的父类是:G<?>

  • 对于G<?> 就不能向其内部添加数据,除了添加null之外
  • 允许读取数据,读取的数据类型为Object。

有限制条件的通配符

  • ? extends A:
  • ? super A:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*有限制条件的通配符的使用。
? extends A:
上限<= G<? extends A> 可以作为G<A>和G<B>的父类,其中B是A的子类
? super A:
下限>= G<? super A> 可以作为G<A>和G<B>的父类,其中B是A的父类
*/
@Test
public void test4(){
List<? extends Person> list1 = null;
List<? super Person> list2 = null;

List<Student> list3 = new ArrayList<Student>();
List<Person> list4 = new ArrayList<Person>();
List<Object> list5 = new ArrayList<Object>();
list1 = list3;
list1 = list4;
// list1 = list5;
// list2 = list3;
list2 = list4;
list2 = list5;

//读取数据:
list1 = list3;
Person p = list1.get(0);
//编译不通过
//Student s = list1.get(0);
list2 = list4;
Object obj = list2.get(0);
////编译不通过
// Person obj = list2.get(0);

//写入数据:
//编译不通过
// list1.add(new Student());
//编译通过
list2.add(new Person());
list2.add(new Student());
}
}

IO

File类

java.io.File类,文件和文件目录路径的抽象表示形式,与平台无关

  • File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
  • File类声明在java.io包下
  • File类中涉及到关于文件或文件目录创建、删除、重命名、修改时间、文件大小等方法,
  • 并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
  • 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的”终点”.

创建File类的实例

  • File(String filePath)
  • File(String parentPath,String childPath)
  • File(File parentFile,String childPath)

PS:

  • 相对路径:相较于某个路径下,指明的路径。
    绝对路径:包含盘符在内的文件或文件目录的路径
  • .路径分隔符
    windows:\
    unix:/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test1(){
//构造器1
File file1 = new File("hello.txt");//相对于当前module
File file2 = new File("D:\\workspace_idea1\\JavaSenior\\day08\\he.txt");
System.out.println(file1);
System.out.println(file2);

//构造器2:
File file3 = new File("D:\\workspace_idea1","JavaSenior");
System.out.println(file3);

//构造器3:
File file4 = new File(file3,"hi.txt");
System.out.println(file4);
}

常用方法

  • public String getAbsolutePath():获取绝对路径
  • public String getPath() :获取路径
  • public String getName() :获取名称
  • public String getParent():获取上层文件目录路径。若无,返回null
  • public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
  • public long lastModified() :获取最后一次的修改时间,毫秒值

如下的两个方法适用于文件目录

  • public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
  • public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
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
@Test
public void test2(){
File file1 = new File("hello.txt");
File file2 = new File("d:\\io\\hi.txt");
System.out.println(file1.getAbsolutePath());
System.out.println(file1.getPath());
System.out.println(file1.getName());
System.out.println(file1.getParent());
System.out.println(file1.length());
System.out.println(new Date(file1.lastModified()));

System.out.println();
System.out.println(file2.getAbsolutePath());
System.out.println(file2.getPath());
System.out.println(file2.getName());
System.out.println(file2.getParent());
System.out.println(file2.length());
System.out.println(file2.lastModified());
}
@Test
public void test3(){
File file = new File("D:\\workspace_idea1\\JavaSenior");

String[] list = file.list();
for(String s : list){
System.out.println(s);
}
System.out.println();

File[] files = file.listFiles();
for(File f : files){
System.out.println(f);
}
}
  • public boolean renameTo(File dest):把文件重命名为指定的文件路径
    比如:file1.renameTo(file2)为例:要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在
1
2
3
4
5
6
7
8
@Test
public void test4(){
File file1 = new File("hello.txt");
File file2 = new File("D:\\io\\hi.txt");

boolean renameTo = file2.renameTo(file1);
System.out.println(renameTo);
}
  • public boolean isDirectory():判断是否是文件目录
  • public boolean isFile() :判断是否是文件
  • public boolean exists() :判断是否存在
  • public boolean canRead() :判断是否可读
  • public boolean canWrite() :判断是否可写
  • public boolean isHidden() :判断是否隐藏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void test5(){
File file1 = new File("hello.txt");
file1 = new File("hello1.txt");

System.out.println(file1.isDirectory());
System.out.println(file1.isFile());
System.out.println(file1.exists());
System.out.println(file1.canRead());
System.out.println(file1.canWrite());
System.out.println(file1.isHidden());

System.out.println();

File file2 = new File("d:\\io");
file2 = new File("d:\\io1");
System.out.println(file2.isDirectory());
System.out.println(file2.isFile());
System.out.println(file2.exists());
System.out.println(file2.canRead());
System.out.println(file2.canWrite());
System.out.println(file2.isHidden());
}

创建

  • public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
  • public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
  • public boolean mkdirs() :创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建

删除

  • public boolean delete():删除文件或者文件夹
    删除注意事项:**Java中的删除不走回收站**。
    
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
@Test
public void test6() throws IOException {
File file1 = new File("hi.txt");
if(!file1.exists()){
//文件的创建
file1.createNewFile();
System.out.println("创建成功");
}else{//文件存在
file1.delete();
System.out.println("删除成功");
}
}
@Test
public void test7(){
//文件目录的创建
File file1 = new File("d:\\io\\io1\\io3");

boolean mkdir = file1.mkdir();
if(mkdir){
System.out.println("创建成功1");
}

File file2 = new File("d:\\io\\io1\\io4");

boolean mkdir1 = file2.mkdirs();
if(mkdir1){
System.out.println("创建成功2");
}
//要想删除成功,io4文件目录下不能有子目录或文件
File file3 = new File("D:\\io\\io1\\io4");
file3 = new File("D:\\io\\io1");
System.out.println(file3.delete()); //false
}

IO流

处理设备之间的数据传输,对于数据的输入输出操作以“流Stream”的方式进行。

流的分类

  • 按操作数据单位不同分为:字节流(8bit),字符流(16bit)
  • 按数据流的流向不同分为:输入流,输出流
  • 按流的角色的不同分为:节点流,处理流
抽象基类 字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer

PS:

  • Java的O流共涉及40多个类,实际上非常规则,都是从如上4个抽象基类派生的
  • 由这四个类派生出来的子类名称都是以其父类名作为子类名后缀

流的体系结构

抽象基类 节点流(或文件流) 缓冲流(处理流的一种)
InputStream FileInputStream (read(byte[] buffer)) BufferedInputStream (read(byte[] buffer))
OutputStream FileOutputStream (write(byte[] buffer,0,len) BufferedOutputStream (write(byte[] buffer,0,len) / flush()
Reader FileReader (read(char[] cbuf)) BufferedReader (read(char[] cbuf) / readLine())
Writer FileWriter (write(char[] cbuf,0,len) BufferedWriter (write(char[] cbuf,0,len) / flush()

节点流

字符流

1.read() 从内存中写出数据到硬盘的文件里。

  • File类的实例化
  • FileReader流的实例化
  • 读入的操作
  • 资源的关闭

说明点:

  • read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
  • 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
  • 读入的文件一定要存在,否则就会报FileNotFoundException
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
@Test
public void testFileReader(){
FileReader fr = null;
try {
//1.实例化File类的对象,指明要操作的文件
File file = new File("hello.txt");//相较于当前Module
//2.提供具体的流
fr = new FileReader(file);

//3.数据的读入
//read():返回读入的一个字符。如果达到文件末尾,返回-1
//方式一:
// int data = fr.read();
// while(data != -1){
// System.out.print((char)data);
// data = fr.read();
// }
//方式二:语法上针对于方式一的修改
int data;
while((data = fr.read()) != -1){
System.out.print((char)data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.流的关闭操作
// try {
// if(fr != null)
// fr.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
//或
if(fr != null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

//对read()操作升级:使用read的重载方法

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
@Test
public void testFileReader1() {
FileReader fr = null;
try {
//1.File类的实例化
File file = new File("hello.txt");

//2.FileReader流的实例化
fr = new FileReader(file);

//3.读入的操作
//read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1
char[] cbuf = new char[5];
int len;
while((len = fr.read(cbuf)) != -1){
//方式一:
//错误的写法
// for(int i = 0;i < cbuf.length;i++){
// System.out.print(cbuf[i]);
// } //heloworld123ld
//正确的写法
// for(int i = 0;i < len;i++){
// System.out.print(cbuf[i]);
// }
//方式二:
//错误的写法,对应着方式一的错误的写法
// String str = new String(cbuf);
// System.out.print(str);
//正确的写法
String str = new String(cbuf,0,len);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fr != null){
//4.资源的关闭
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

2.write() 从内存中写出数据到硬盘的文件里。

说明:

  • 输出操作,对应的File可以不存在的。并不会报异常
  • File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
    File对应的硬盘中的文件如果存在:
    如果流使用的构造器是:**FileWriter(file,false)** / **FileWriter(file)**:**对原有文件的覆盖**
    如果流使用的构造器是:**FileWriter(file,true)**:**不会对原有文件覆盖,而是在原有文件基础上追加内容**
    
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 testFileWriter() {
FileWriter fw = null;
try {
//1.提供File类的对象,指明写出到的文件
File file = new File("hello1.txt");

//2.提供FileWriter的对象,用于数据的写出
fw = new FileWriter(file,false);

//3.写出的操作
fw.write("I have a dream!\n");
fw.write("you need to have a dream!");
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.流资源的关闭
if(fw != null){

try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
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
@Test
public void testFileReaderFileWriter() {
FileReader fr = null;
FileWriter fw = null;
try {
//1.创建File类的对象,指明读入和写出的文件
File srcFile = new File("hello.txt");
File destFile = new File("hello2.txt");

//不能使用字符流来处理图片等字节数据
// File srcFile = new File("爱情与友情.jpg");
// File destFile = new File("爱情与友情1.jpg");
//2.创建输入流和输出流的对象
fr = new FileReader(srcFile);
fw = new FileWriter(destFile);
//3.数据的读入和写出操作
char[] cbuf = new char[5];
int len;//记录每次读入到cbuf数组中的字符的个数
while((len = fr.read(cbuf)) != -1){
//每次写出len个字符
fw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭流资源
//方式一:
// try {
// if(fw != null)
// fw.close();
// } catch (IOException e) {
// e.printStackTrace();
// }finally{
// try {
// if(fr != null)
// fr.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
//方式二:
try {
if(fw != null)
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

字节流

结论:

  • 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
  • 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,…),使用字节流处理
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
//使用字节流FileInputStream处理文本文件,可能出现乱码。
@Test
public void testFileInputStream() {
FileInputStream fis = null;
try {
//1. 造文件
File file = new File("hello.txt");

//2.造流
fis = new FileInputStream(file);

//3.读数据
byte[] buffer = new byte[5];
int len;//记录每次读取的字节的个数
while((len = fis.read(buffer)) != -1){
String str = new String(buffer,0,len);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
//4.关闭资源
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

实现对图片的复制操作

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
84
85
86
87
@Test
public void testFileInputOutputStream() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
File srcFile = new File("爱情与友情.jpg");
File destFile = new File("爱情与友情2.jpg");
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);

//复制的过程
byte[] buffer = new byte[5];
int len;
while((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

//指定路径下文件的复制
public void copyFile(String srcPath,String destPath){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
File srcFile = new File(srcPath);
File destFile = new File(destPath);
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//复制的过程
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

@Test
public void testCopyFile(){
long start = System.currentTimeMillis();

String srcPath = "C:\\Users\\Administrator\\Desktop\\01-视频.avi";
String destPath = "C:\\Users\\Administrator\\Desktop\\02-视频.avi";

// String srcPath = "hello.txt";
// String destPath = "hello3.txt";

copyFile(srcPath,destPath);

long end = System.currentTimeMillis();

System.out.println("复制操作花费的时间为:" + (end - start));//618
}

缓冲流

处理流,就是“套接”在已有的流的基础上。处理流之一:缓冲流

  • BufferedInputStream
  • BufferedOutputStream
  • BufferedReader
  • BufferedWriter

作用:提高流的读取、写入的速度(内部提供了一个缓冲区)

实现非文本文件的复制

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
@Test
public void BufferedStreamTest() throws FileNotFoundException {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//1.造文件
File srcFile = new File("爱情与友情.jpg");
File destFile = new File("爱情与友情3.jpg");
//2.造流
//2.1 造节点流
FileInputStream fis = new FileInputStream((srcFile));
FileOutputStream fos = new FileOutputStream(destFile);
//2.2 造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);

//3.复制的细节:读取、写入
byte[] buffer = new byte[10];
int len;
while((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
// bos.flush();//刷新缓冲区
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源关闭
//要求:先关闭外层的流,再关闭内层的流
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
// fos.close();
// fis.close();
}
}

实现文本文件的复制

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
@Test
public void testBufferedReaderBufferedWriter(){
BufferedReader br = null;
BufferedWriter bw = null;
try {
//创建文件和相应的流
br = new BufferedReader(new FileReader(new File("dbcp.txt")));
bw = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));

//读写操作
//方式一:使用char[]数组
// char[] cbuf = new char[1024];
// int len;
// while((len = br.read(cbuf)) != -1){
// bw.write(cbuf,0,len);
// // bw.flush();
// }
//方式二:使用String
String data;
while((data = br.readLine()) != null){
//方法一:
// bw.write(data + "\n");//data中不包含换行符
//方法二:
bw.write(data);//data中不包含换行符
bw.newLine();//提供换行的操作

}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
if(bw != null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

实现文件复制的方法

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
public void copyFileWithBuffered(String srcPath,String destPath){
BufferedInputStream bis = null;
BufferedOutputStream bos = null;

try {
//1.造文件
File srcFile = new File(srcPath);
File destFile = new File(destPath);
//2.造流
//2.1 造节点流
FileInputStream fis = new FileInputStream((srcFile));
FileOutputStream fos = new FileOutputStream(destFile);
//2.2 造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);

//3.复制的细节:读取、写入
byte[] buffer = new byte[1024];
int len;
while((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源关闭
//要求:先关闭外层的流,再关闭内层的流
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}

}
if(bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}

}
//说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
// fos.close();
// fis.close();
}
}

@Test
public void testCopyFileWithBuffered(){
long start = System.currentTimeMillis();

String srcPath = "C:\\Users\\Administrator\\Desktop\\01-视频.avi";
String destPath = "C:\\Users\\Administrator\\Desktop\\03-视频.avi";


copyFileWithBuffered(srcPath,destPath);


long end = System.currentTimeMillis();

System.out.println("复制操作花费的时间为:" + (end - start));//618 - 176
}

转换流

处理流之二:转换流

  • 转换流:属于字符流

    InputStreamReader:将一个字节的输入流转换为字符的输入流

    OutputStreamWriter:将一个字符的输出流转换为字节的输出流

  • 作用:提供字节流与字符流之间的转换

  • 解码:字节、字节数组 —>字符数组、字符串

    编码:字符数组、字符串 —> 字节、字节数组

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

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
/*
此时处理异常的话,仍然应该使用try-catch-finally
InputStreamReader的使用,实现字节的输入流到字符的输入流的转换
*/
@Test
public void test1() throws IOException {

FileInputStream fis = new FileInputStream("dbcp.txt");
// InputStreamReader isr = new InputStreamReader(fis);//使用系统默认的字符集
//参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt保存时使用的字符集
InputStreamReader isr = new InputStreamReader(fis,"UTF-8");//使用系统默认的字符集

char[] cbuf = new char[20];
int len;
while((len = isr.read(cbuf)) != -1){
String str = new String(cbuf,0,len);
System.out.print(str);
}
isr.close();
}
/*
此时处理异常的话,仍然应该使用try-catch-finally
综合使用InputStreamReader和OutputStreamWriter
*/
@Test
public void test2() throws Exception {
//1.造文件、造流
File file1 = new File("dbcp.txt");
File file2 = new File("dbcp_gbk.txt");

FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);

InputStreamReader isr = new InputStreamReader(fis,"utf-8");
OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");

//2.读写过程
char[] cbuf = new char[20];
int len;
while((len = isr.read(cbuf)) != -1){
osw.write(cbuf,0,len);
}
//3.关闭资源
isr.close();
osw.close();
}

其他流

  • 标准的输入、输出流
  • 打印流
  • 数据流

标准的输入、输出流

  • System.in:标准的输入流,默认从键盘输入
  • System.out:标准的输出流,默认从控制台输出

System类的 setIn(InputStream is) / setOut(PrintStream ps) 方式重新指定输入和输出的流。

练习:
从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,
直至当输入“e”或者“exit”时,退出程序。

方法一:使用Scanner实现,调用next()返回一个字符串
方法二:使用System.in实现。System.in  --->  转换流 ---> BufferedReader的readLine()
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
public static void main(String[] args) {
BufferedReader br = null;
try {
InputStreamReader isr = new InputStreamReader(System.in);
br = new BufferedReader(isr);

while (true) {
System.out.println("请输入字符串:");
String data = br.readLine();
if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
System.out.println("程序结束");
break;
}
String upperCase = data.toUpperCase();
System.out.println(upperCase);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

打印流

PrintStreamPrintWriter

实现将基本数据类型的 数据格式 转化 为字符串输出,提供了一系列重载的print() 和 println()
练习:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void test2() {
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(new File("D:\\IO\\text.txt"));
// 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
ps = new PrintStream(fos, true);
if (ps != null) {// 把标准输出流(控制台输出)改成文件
System.setOut(ps);
}
for (int i = 0; i <= 255; i++) { // 输出ASCII字符
System.out.print((char) i);
if (i % 50 == 0) { // 每50个数据一行
System.out.println(); // 换行
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (ps != null) {
ps.close();
}
}
}

数据流

DataInputStreamDataOutputStream
作用:用于读取或写出基本数据类型的变量或字符串

练习:将内存中的字符串、基本数据类型的变量写出到文件中。

注意:处理异常的话,仍然应该使用try-catch-finally.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test3() throws IOException {
//1.
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
//2.
dos.writeUTF("刘建辰");
dos.flush();//刷新操作,将内存中的数据写入文件
dos.writeInt(23);
dos.flush();
dos.writeBoolean(true);
dos.flush();
//3.
dos.close();
}

将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中。

注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test4() throws IOException {
//1.
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
//2.
String name = dis.readUTF();
int age = dis.readInt();
boolean isMale = dis.readBoolean();
System.out.println("name = " + name);
System.out.println("age = " + age);
System.out.println("isMale = " + isMale);
//3.
dis.close();
}

对象流

ObjectInputStreamObjectOutputStream

  • 作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

  • 要想一个java对象是可序列化的,需要满足相应的要求。见Person.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * Person需要满足如下的要求,方可序列化
    * 1.需要实现接口:Serializable
    * 2.当前类提供一个全局常量:serialVersionUID
    * 3.除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)
    *
    * 补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
    */
    public class Person implements Serializable{
    public static final long serialVersionUID = 475463534532L;
    ...
  • 序列化机制

    对象序列化机制允许把内存中的Java对象 转换成 平台无关的 二进制流,从而允许把这种 二进制流 持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。

    当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。

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
/*
序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去
使用ObjectOutputStream实现
*/
@Test
public void testObjectOutputStream(){
ObjectOutputStream oos = null;

try {
//1.
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
//2.
oos.writeObject(new String("我爱北京天安门"));
oos.flush();//刷新操作

oos.writeObject(new Person("王铭",23));
oos.flush();

oos.writeObject(new Person("张学良",23,1001,new Account(5000)));
oos.flush();

} catch (IOException e) {
e.printStackTrace();
} finally {
if(oos != null){
//3.
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/*
反序列化:将磁盘文件中的对象还原为内存中的一个java对象
使用ObjectInputStream来实现
*/
@Test
public void testObjectInputStream(){
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("object.dat"));

Object obj = ois.readObject();
String str = (String) obj;

Person p = (Person) ois.readObject();
Person p1 = (Person) ois.readObject();

System.out.println(str);
System.out.println(p);
System.out.println(p1);

} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if(ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

随机存取文件流

RandomAccessFile

  • 直接继承于java.lang.Object类,实现了DataInputDataOutput接口
  • 既可以作为一个输入流,又可以作为一个输出流
  • 作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。
  • 如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖,不是覆盖文件)
  • 可以通过相关的操作,实现RandomAccessFile “插入”数据的效果
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
@Test
public void test1() {
RandomAccessFile raf1 = null;
RandomAccessFile raf2 = null;
try {
//1.
raf1 = new RandomAccessFile(new File("爱情与友情.jpg"),"r");
raf2 = new RandomAccessFile(new File("爱情与友情1.jpg"),"rw");
//2.
byte[] buffer = new byte[1024];
int len;
while((len = raf1.read(buffer)) != -1){
raf2.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//3.
if(raf1 != null){
try {
raf1.close();
} catch (IOException e) {
e.printStackTrace();
}

}
if(raf2 != null){
try {
raf2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void test2() throws IOException {

RandomAccessFile raf1 = new RandomAccessFile("hello.txt","rw");

raf1.seek(3);//将指针调到角标为3的位置
raf1.write("xyz".getBytes());//

raf1.close();
}

使用RandomAccessFile实现数据的插入效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void test3() throws IOException {
RandomAccessFile raf1 = new RandomAccessFile("hello.txt","rw");

raf1.seek(3);//将指针调到角标为3的位置
//保存指针3后面的所有数据到StringBuilder中
StringBuilder builder = new StringBuilder((int) new File("hello.txt").length());
byte[] buffer = new byte[20];
int len;
while((len = raf1.read(buffer)) != -1){
builder.append(new String(buffer,0,len)) ;
}
//调回指针,写入“xyz”
raf1.seek(3);
raf1.write("xyz".getBytes());

//将StringBuilder中的数据写入到文件中
raf1.write(builder.toString().getBytes());

raf1.close();
//思考:将StringBuilder替换为ByteArrayOutputStream
}

网络编程

概述

网络编程中有两个主要的问题:

  1. 如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
  2. 找到主机后如何可靠高效地进行数据传输

网络编程中的两个要素:

  1. 对应问题一:IP和端口号
  2. 对应问题二:提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)

通信要素一:

IP和端口号

  • IP:唯一的标识 Internet 上的计算机(通信实体)
  • 在Java中使用InetAddress类代表IP
  • IP分类:IPv4 和 IPv6 ; 万维网 和 局域网
  • 域名: www.baidu.com www.mi.com www.sina.com www.jd.com
  • 本地回路地址:127.0.0.1 对应着:localhost
  • 如何实例化InetAddress:两个方法:getByName(String host) 、 getLocalHost()
  • 两个常用方法:getHostName() / getHostAddress()
  • 端口号:正在计算机上运行的进程。
  • 要求:不同的进程有不同的端口号
  • 范围:被规定为一个 16 位的整数 0~65535。
  • 端口号与IP地址的组合得出一个网络套接字:Socket
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
public static void main(String[] args) {
try {
//File file = new File("hello.txt");
InetAddress inet1 = InetAddress.getByName("192.168.10.14");

System.out.println(inet1);

InetAddress inet2 = InetAddress.getByName("www.atguigu.com");
System.out.println(inet2);

InetAddress inet3 = InetAddress.getByName("127.0.0.1");
System.out.println(inet3);

//获取本地ip
InetAddress inet4 = InetAddress.getLocalHost();
System.out.println(inet4);

//getHostName()
System.out.println(inet2.getHostName());
//getHostAddress()
System.out.println(inet2.getHostAddress());

} catch (UnknownHostException e) {
e.printStackTrace();
}
}

实现TCP的网络编程

  • 例题3:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。
  • 并关闭相应的连接。
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
@Test
public void client() throws IOException {
//1.
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
//2.
OutputStream os = socket.getOutputStream();
//3.
FileInputStream fis = new FileInputStream(new File("beauty.jpg"));
//4.
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){
os.write(buffer,0,len);
}
//关闭数据的输出
socket.shutdownOutput();

//5.接收来自于服务器端的数据,并显示到控制台上
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bufferr = new byte[20];
int len1;
while((len1 = is.read(buffer)) != -1){
baos.write(buffer,0,len1);
}

System.out.println(baos.toString());

//6.
fis.close();
os.close();
socket.close();
baos.close();
}

/*
这里涉及到的异常,应该使用try-catch-finally处理
*/
@Test
public void server() throws IOException {
//1.
ServerSocket ss = new ServerSocket(9090);
//2.
Socket socket = ss.accept();
//3.
InputStream is = socket.getInputStream();
//4.
FileOutputStream fos = new FileOutputStream(new File("beauty2.jpg"));
//5.
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}

System.out.println("图片传输完成");

//6.服务器端给予客户端反馈
OutputStream os = socket.getOutputStream();
os.write("你好,美女,照片我已收到,非常漂亮!".getBytes());

//7.
fos.close();
is.close();
socket.close();
ss.close();
os.close();

}

UDPd协议的网络编程

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
//发送端
@Test
public void sender() throws IOException {

DatagramSocket socket = new DatagramSocket();



String str = "我是UDP方式发送的导弹";
byte[] data = str.getBytes();
InetAddress inet = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);

socket.send(packet);

socket.close();

}
//接收端
@Test
public void receiver() throws IOException {

DatagramSocket socket = new DatagramSocket(9090);

byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);

socket.receive(packet);

System.out.println(new String(packet.getData(),0,packet.getLength()));

socket.close();
}

URL网络编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
try {
URL url = new URL("http://localhost:8080/examples/beauty.jpg?username=Tom");
// public String getProtocol( ) 获取该URL的协议名
System.out.println(url.getProtocol());
// public String getHost( ) 获取该URL的主机名
System.out.println(url.getHost());
// public String getPort( ) 获取该URL的端口号
System.out.println(url.getPort());
// public String getPath( ) 获取该URL的文件路径
System.out.println(url.getPath());
// public String getFile( ) 获取该URL的文件名
System.out.println(url.getFile());
// public String getQuery( ) 获取该URL的查询名
System.out.println(url.getQuery());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
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
public static void main(String[] args) {

HttpURLConnection urlConnection = null;
InputStream is = null;
FileOutputStream fos = null;
try {
URL url = new URL("http://localhost:8080/examples/beauty.jpg");

urlConnection = (HttpURLConnection) url.openConnection();

urlConnection.connect();

is = urlConnection.getInputStream();
fos = new FileOutputStream("day10\\beauty3.jpg");

byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}

System.out.println("下载完成");
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(urlConnection != null){
urlConnection.disconnect();
}
}
}

*idea快捷键:

  1. 重构一切:Ctrl+Shift+Alt+T
  2. 智能补全:Ctrl+Shift+Space
  3. 选你所想:Ctrl+W
  4. 自我修复:Alt+Enter
  5. 自动完成:Ctrl+Shift+Enter
  6. 可以新建类、方法等任何东西、get/set、toString方法: alt+insert
  7. 自动new完整对象: Ctrl+Alt+V,可以引入变量
  8. 自动选中模块代码:Ctrl+W
  9. 移动到前/后方法:Alt+Forward/Backward
  10. 删除行:Ctrl+Y、复制:Ctrl+D
  11. 切换vim模式:Ctrl+;
  12. 高亮错误或警告快速定位:F2或shift+F2
  13. 打开类或资源:Ctrl+N/Ctrl+Shift+N
  14. 弹出框中搜索任何东西,包括类、资源、配置项、方法:Shift+Shift
  15. 查看当前类的所有方法:Ctrl+F12
  16. 找到类或方法使用的地方:,Alt+F7
  17. 格式化import列表:Ctrl+Alt+O,格式化代码:Ctrl+Alt+L
  18. 查看项目结构选中类:Alt+1,查看搜索窗口:Alt+3,查看运行调试Alt+4/5
  19. 打开最近打开或编辑过的文件列表:Ctrl+E
  20. 运行程序:Alt+Shift+F10,启动调试:Shift+F9,停止:Ctrl+F2。
  21. 调试:F7/F8/F9分别对应Step into,Step over,Continue
  22. 上/下移一行:Alt+Shift+Up/Down

反射

概述

反射的特征:动态性

疑问1:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用那个?
建议:直接new的方式。
什么时候会使用:反射的方式。
疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?
不矛盾。

java.lang.Class类的理解

  • 类的加载过程
    程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
    接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件
    加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此
    运行时类,就作为Class的一个实例。
    
  • 换句话说,Class的实例就对应着一个运行时类
  • 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。

获取Class的实例的方式

  1. 调用运行时类的属性:.class
  2. 通过运行时类的对象,调用getClass()
  3. 调用Class的静态方法:forName(String classPath)
  4. 使用类的加载器:ClassLoader (了解)
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
//(前三种方式需要掌握)
@Test
public void test3() throws ClassNotFoundException {
//方式一:调用运行时类的属性:.class
Class clazz1 = Person.class;
System.out.println(clazz1);
//方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);

//方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("com.atguigu.java.Person");
// clazz3 = Class.forName("java.lang.String");
System.out.println(clazz3);

System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);

//方式四:使用类的加载器:ClassLoader (了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
System.out.println(clazz4);

System.out.println(clazz1 == clazz4);
}

Class实例可以是哪些结构的说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//万事万物皆对象?对象.xxx,File,URL,反射,前端、数据库操作
@Test
public void test4(){
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;

int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要数组的元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11);
}

了解类的加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test1(){
//对于自定义类,使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//调用系统类加载器的getParent():获取扩展类加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
//调用扩展类加载器的getParent():无法获取引导类加载器
//引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2);

ClassLoader classLoader3 = String.class.getClassLoader();
System.out.println(classLoader3);
}

使用 ClassLoader加载配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//Properties:用来读取配置文件。
@Test
public void test2() throws Exception {

Properties pros = new Properties();
//此时的文件默认在当前的module下。
//读取配置文件的方式一:
// FileInputStream fis = new FileInputStream("jdbc.properties");
// FileInputStream fis = new FileInputStream("src\\jdbc1.properties");
// pros.load(fis);

//读取配置文件的方式二:使用ClassLoader
//配置文件默认识别为:当前module的src下
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);

String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user = " + user + ",password = " + password);
}

通过反射

创建运行时类的对象

  • newInstance(): 调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
    要想此方法正常的创建运行时类的对象,要求:
    1. 运行时类必须提供空参的构造器
    2. 空参的构造器的访问权限得够。通常,设置为public。

在javabean中要求提供一个public的空参构造器。原因:
1.便于通过反射,创建运行时类的对象
2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

1
2
3
4
5
6
7
@Test
public void test1() throws IllegalAccessException, InstantiationException {
Class<Person> clazz = Person.class;

Person obj = clazz.newInstance();
System.out.println(obj);
}

体会反射的动态性

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
@Test
public void test2(){

for(int i = 0;i < 100;i++){
int num = new Random().nextInt(3);//0,1,2
String classPath = "";
switch(num){
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "com.atguigu.java.Person";
break;
}
try {
Object obj = getInstance(classPath);
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*
创建一个指定类的对象。
classPath:指定类的全类名
*/
public Object getInstance(String classPath) throws Exception {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}

获取运行时类的完整结构

属性结构

  • getFields(): 获取当前运行时类及其父类中声明为public访问权限的属性
  • getDeclaredFields(): 获取当前运行时类中声明的所有属性。(不包含父类中声明的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test1(){
Class clazz = Person.class;
//获取属性结构
//getFields():获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = clazz.getFields();
for(Field f : fields){
System.out.println(f);
}
System.out.println();
//getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
Field[] declaredFields = clazz.getDeclaredFields();
for(Field f : declaredFields){
System.out.println(f);
}
}

权限修饰符 数据类型 变量名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test2(){
Class clazz = Person.class;
Field[] declaredFields = clazz.getDeclaredFields();
for(Field f : declaredFields){
//1.权限修饰符
int modifier = f.getModifiers();
System.out.print(Modifier.toString(modifier) + "\t");
//2.数据类型
Class type = f.getType();
System.out.print(type.getName() + "\t");
//3.变量名
String fName = f.getName();
System.out.print(fName);
System.out.println();
}
}

方法结构

  • getMethods(): 获取当前运行时类及其所有父类中声明为public权限的方法
  • getDeclaredMethods(): 获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test1(){
Class clazz = Person.class;
//getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
Method[] methods = clazz.getMethods();
for(Method m : methods){
System.out.println(m);
}
System.out.println();
//getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m : declaredMethods){
System.out.println(m);
}
}

权限修饰符 返回值类型 方法名(参数类型1 形参名1,…) throws XxxException{}

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
@Test
public void test2(){
Class clazz = Person.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m : declaredMethods){
//1.获取方法声明的注解
Annotation[] annos = m.getAnnotations();
for(Annotation a : annos){
System.out.println(a);
}

//2.权限修饰符
System.out.print(Modifier.toString(m.getModifiers()) + "\t");

//3.返回值类型
System.out.print(m.getReturnType().getName() + "\t");

//4.方法名
System.out.print(m.getName());
System.out.print("(");
//5.形参列表
Class[] parameterTypes = m.getParameterTypes();
if(!(parameterTypes == null && parameterTypes.length == 0)){
for(int i = 0;i < parameterTypes.length;i++){
if(i == parameterTypes.length - 1){
System.out.print(parameterTypes[i].getName() + " args_" + i);
break;
}
System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
}
}
System.out.print(")");

//6.抛出的异常
Class[] exceptionTypes = m.getExceptionTypes();
if(exceptionTypes.length > 0){
System.out.print("throws ");
for(int i = 0;i < exceptionTypes.length;i++){
if(i == exceptionTypes.length - 1){
System.out.print(exceptionTypes[i].getName());
break;
}
System.out.print(exceptionTypes[i].getName() + ",");
}
}
System.out.println();
}
}

构造器结构

  • getConstructors(): 获取当前运行时类中声明为public的构造器
  • getDeclaredConstructors(): 获取当前运行时类中声明的所有的构造器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test1(){

Class clazz = Person.class;
//getConstructors():获取当前运行时类中声明为public的构造器
Constructor[] constructors = clazz.getConstructors();
for(Constructor c : constructors){
System.out.println(c);
}

System.out.println();
//getDeclaredConstructors():获取当前运行时类中声明的所有的构造器
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for(Constructor c : declaredConstructors){
System.out.println(c);
}
}

其他结构

获取运行时类的父类

1
2
3
4
5
6
7
@Test
public void test2(){
Class clazz = Person.class;

Class superclass = clazz.getSuperclass();
System.out.println(superclass);
}

获取运行时类的带泛型的父类

1
2
3
4
5
6
7
@Test
public void test3(){
Class clazz = Person.class;

Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
}

获取运行时类的带泛型的父类的泛型

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test4(){
Class clazz = Person.class;

Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
//获取泛型类型
Type[] actualTypeArguments = paramType.getActualTypeArguments();
// System.out.println(actualTypeArguments[0].getTypeName());
System.out.println(((Class)actualTypeArguments[0]).getName());
}

获取运行时类实现的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test5(){
Class clazz = Person.class;

Class[] interfaces = clazz.getInterfaces();
for(Class c : interfaces){
System.out.println(c);
}
System.out.println();
//获取运行时类的父类实现的接口
Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
for(Class c : interfaces1){
System.out.println(c);
}
}

获取运行时类所在的包

1
2
3
4
5
6
7
@Test
public void test6(){
Class clazz = Person.class;

Package pack = clazz.getPackage();
System.out.println(pack);
}

获取运行时类声明的注解

1
2
3
4
5
6
7
8
9
@Test
public void test7(){
Class clazz = Person.class;

Annotation[] annotations = clazz.getAnnotations();
for(Annotation annos : annotations){
System.out.println(annos);
}
}

调用运行时类的指定结构

属性、方法、构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 不需要掌握
@Test
public void testField() throws Exception {
Class clazz = Person.class;

//创建运行时类的对象
Person p = (Person) clazz.newInstance();
//获取指定的属性:要求运行时类中属性声明为public
//通常不采用此方法
Field id = clazz.getField("id");
/*
设置当前属性的值
set():参数1:指明设置哪个对象的属性 参数2:将此属性值设置为多少
*/
id.set(p,1001);
/*
获取当前属性的值
get():参数1:获取哪个对象的当前属性值
*/
int pId = (int) id.get(p);
System.out.println(pId);
}

操作指定的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
如何操作运行时类中的指定的属性 -- 需要掌握
*/
@Test
public void testField1() throws Exception {
Class clazz = Person.class;

//创建运行时类的对象
Person p = (Person) clazz.newInstance();

//1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
Field name = clazz.getDeclaredField("name");

//2.保证当前属性是可访问的
name.setAccessible(true);
//3.获取、设置指定对象的此属性值
name.set(p,"Tom");

System.out.println(name.get(p));
}

指定的方法

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
/*
如何操作运行时类中的指定的方法 -- 需要掌握
*/
@Test
public void testMethod() throws Exception {

Class clazz = Person.class;
//创建运行时类的对象
Person p = (Person) clazz.newInstance();
/*
1.获取指定的某个方法
getDeclaredMethod():参数1 :指明获取的方法的名称 参数2:指明获取的方法的形参列表
*/
Method show = clazz.getDeclaredMethod("show", String.class);
//2.保证当前方法是可访问的
show.setAccessible(true);
/*
3. 调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参
invoke()的返回值即为对应类中调用的方法的返回值。
*/
Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN");
System.out.println(returnValue);

System.out.println("*************如何调用静态方法*****************");
// private static void showDesc()

Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
//如果调用的运行时类中的方法没有返回值,则此invoke()返回null
// Object returnVal = showDesc.invoke(null);
Object returnVal = showDesc.invoke(Person.class);
System.out.println(returnVal);//null
}

指定的构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
如何调用运行时类中的指定的构造器
*/
@Test
public void testConstructor() throws Exception {
Class clazz = Person.class;

//private Person(String name)
/*
1.获取指定的构造器
getDeclaredConstructor():参数:指明构造器的参数列表
*/
Constructor constructor = clazz.getDeclaredConstructor(String.class);

//2.保证此构造器是可访问的
constructor.setAccessible(true);

//3.调用此构造器创建运行时类的对象
Person per = (Person) constructor.newInstance("Tom");
System.out.println(per);
}

应用:动态代理

静态代理举例

特点:代理类和被代理类在编译期间,就确定下来了。

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
interface ClothFactory{
void produceCloth();
}

//代理类
class ProxyClothFactory implements ClothFactory{

private ClothFactory factory;//用被代理类对象进行实例化

public ProxyClothFactory(ClothFactory factory){
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println("代理工厂做一些准备工作");

factory.produceCloth();

System.out.println("代理工厂做一些后续的收尾工作");
}
}

//被代理类
class NikeClothFactory implements ClothFactory{

@Override
public void produceCloth() {
System.out.println("Nike工厂生产一批运动服");
}
}

public class StaticProxyTest {
public static void main(String[] args) {
//创建被代理类的对象
ClothFactory nike = new NikeClothFactory();
//创建代理类的对象
ClothFactory proxyClothFactory = new ProxyClothFactory(nike);
proxyClothFactory.produceCloth();
}
}

动态代理的举例

要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly!";
}

@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}

class HumanUtil{
public void method1(){
System.out.println("====================通用方法一====================");
}
public void method2(){
System.out.println("====================通用方法二====================");
}
}

/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
*/
class ProxyFactory{
//调用此方法,返回一个代理类的对象。解决问题一
public static Object getProxyInstance(Object obj){//obj:被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}

class MyInvocationHandler implements InvocationHandler{

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

public void bind(Object obj){
this.obj = obj;
}
//当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
//将被代理类要执行的方法a的功能就声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

HumanUtil util = new HumanUtil();
util.method1();
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//obj:被代理类的对象
Object returnValue = method.invoke(obj,args);

util.method2();
//上述方法的返回值就作为当前类中的invoke()的返回值。
return returnValue;
}
}

public class ProxyTest {

public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("四川麻辣烫"); System.out.println("*****************************");
NikeClothFactory nikeClothFactory = new NikeClothFactory();
ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
proxyClothFactory.produceCloth();
}
}

新特性

Java8其他新特性

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
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
* Lambda表达式的使用
*
* 1.举例: (o1,o2) -> Integer.compare(o1,o2);
* 2.格式:
* -> :lambda操作符 或 箭头操作符
* ->左边:lambda形参列表 (其实就是接口中的抽象方法的形参列表)
* ->右边:lambda体 (其实就是重写的抽象方法的方法体)
*
* 3. Lambda表达式的使用:(分为6种情况介绍)
*
* 总结:
* ->左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只有一个参数,其一对()也可以省略
* ->右边:lambda体应该使用一对{}包裹;如果lambda体只有一条执行语句(可能是return语句),省略这一对{}和return关键字
*
* 4.Lambda表达式的本质:作为函数式接口的实例
*
* 5. 如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。我们可以在一个接口上使用 @FunctionalInterface 注解,
* 这样做可以检查它是否是一个函数式接口。
*
* 6. 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
*
* @author shkstart
* @create 2019 上午 11:40
*/
public class LambdaTest1 {
//语法格式一:无参,无返回值
@Test
public void test1(){
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我爱北京天安门");
}
};

r1.run();

System.out.println("***********************");

Runnable r2 = () -> {
System.out.println("我爱北京故宫");
};

r2.run();
}
//语法格式二:Lambda 需要一个参数,但是没有返回值。
@Test
public void test2(){

Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("谎言和誓言的区别是什么?");

System.out.println("*******************");

Consumer<String> con1 = (String s) -> {
System.out.println(s);
};
con1.accept("一个是听得人当真了,一个是说的人当真了");

}

//语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
@Test
public void test3(){

Consumer<String> con1 = (String s) -> {
System.out.println(s);
};
con1.accept("一个是听得人当真了,一个是说的人当真了");

System.out.println("*******************");

Consumer<String> con2 = (s) -> {
System.out.println(s);
};
con2.accept("一个是听得人当真了,一个是说的人当真了");

}

@Test
public void test4(){

ArrayList<String> list = new ArrayList<>();//类型推断

int[] arr = {1,2,3};//类型推断

}

//语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略
@Test
public void test5(){
Consumer<String> con1 = (s) -> {
System.out.println(s);
};
con1.accept("一个是听得人当真了,一个是说的人当真了");

System.out.println("*******************");

Consumer<String> con2 = s -> {
System.out.println(s);
};
con2.accept("一个是听得人当真了,一个是说的人当真了");


}

//语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值
@Test
public void test6(){

Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};

System.out.println(com1.compare(12,21));

System.out.println("*****************************");
Comparator<Integer> com2 = (o1,o2) -> {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
};

System.out.println(com2.compare(12,6));


}

//语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
@Test
public void test7(){

Comparator<Integer> com1 = (o1,o2) -> {
return o1.compareTo(o2);
};

System.out.println(com1.compare(12,6));

System.out.println("*****************************");

Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2);

System.out.println(com2.compare(12,21));

}

@Test
public void test8(){
Consumer<String> con1 = s -> {
System.out.println(s);
};
con1.accept("一个是听得人当真了,一个是说的人当真了");

System.out.println("*****************************");

Consumer<String> con2 = s -> System.out.println(s);

con2.accept("一个是听得人当真了,一个是说的人当真了");

}

}

函数式 Functional接囗

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
/**
* java内置的4大核心函数式接口
*
* 消费型接口 Consumer<T> void accept(T t)
* 供给型接口 Supplier<T> T get()
* 函数型接口 Function<T,R> R apply(T t)
* 断定型接口 Predicate<T> boolean test(T t)
*
*
* @author shkstart
* @create 2019 下午 2:29
*/
public class LambdaTest2 {

@Test
public void test1(){

happyTime(500, new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println("学习太累了,去天上人间买了瓶矿泉水,价格为:" + aDouble);
}
});

System.out.println("********************");

happyTime(400,money -> System.out.println("学习太累了,去天上人间喝了口水,价格为:" + money));
}

public void happyTime(double money, Consumer<Double> con){
con.accept(money);
}


@Test
public void test2(){
List<String> list = Arrays.asList("北京","南京","天津","东京","西京","普京");

List<String> filterStrs = filterString(list, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("京");
}
});

System.out.println(filterStrs);


List<String> filterStrs1 = filterString(list,s -> s.contains("京"));
System.out.println(filterStrs1);
}

//根据给定的规则,过滤集合中的字符串。此规则由Predicate的方法决定
public List<String> filterString(List<String> list, Predicate<String> pre){

ArrayList<String> filterList = new ArrayList<>();

for(String s : list){
if(pre.test(s)){
filterList.add(s);
}
}

return filterList;

}

}

方法引用与构造器引用

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/**
* 方法引用的使用
*
* 1.使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
*
* 2.方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以
* 方法引用,也是函数式接口的实例。
*
* 3. 使用格式: 类(或对象) :: 方法名
*
* 4. 具体分为如下的三种情况:
* 情况1 对象 :: 非静态方法
* 情况2 类 :: 静态方法
*
* 情况3 类 :: 非静态方法
*
* 5. 方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的
* 形参列表和返回值类型相同!(针对于情况1和情况2)
*
* Created by shkstart.
*/
public class MethodRefTest {

// 情况一:对象 :: 实例方法
//Consumer中的void accept(T t)
//PrintStream中的void println(T t)
@Test
public void test1() {
Consumer<String> con1 = str -> System.out.println(str);
con1.accept("北京");

System.out.println("*******************");
PrintStream ps = System.out;
Consumer<String> con2 = ps::println;
con2.accept("beijing");
}

//Supplier中的T get()
//Employee中的String getName()
@Test
public void test2() {
Employee emp = new Employee(1001,"Tom",23,5600);

Supplier<String> sup1 = () -> emp.getName();
System.out.println(sup1.get());

System.out.println("*******************");
Supplier<String> sup2 = emp::getName;
System.out.println(sup2.get());

}

// 情况二:类 :: 静态方法
//Comparator中的int compare(T t1,T t2)
//Integer中的int compare(T t1,T t2)
@Test
public void test3() {
Comparator<Integer> com1 = (t1,t2) -> Integer.compare(t1,t2);
System.out.println(com1.compare(12,21));

System.out.println("*******************");

Comparator<Integer> com2 = Integer::compare;
System.out.println(com2.compare(12,3));

}

//Function中的R apply(T t)
//Math中的Long round(Double d)
@Test
public void test4() {
Function<Double,Long> func = new Function<Double, Long>() {
@Override
public Long apply(Double d) {
return Math.round(d);
}
};

System.out.println("*******************");

Function<Double,Long> func1 = d -> Math.round(d);
System.out.println(func1.apply(12.3));

System.out.println("*******************");

Function<Double,Long> func2 = Math::round;
System.out.println(func2.apply(12.6));
}

// 情况三:类 :: 实例方法 (有难度)
// Comparator中的int comapre(T t1,T t2)
// String中的int t1.compareTo(t2)
@Test
public void test5() {
Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
System.out.println(com1.compare("abc","abd"));

System.out.println("*******************");

Comparator<String> com2 = String :: compareTo;
System.out.println(com2.compare("abd","abm"));
}

//BiPredicate中的boolean test(T t1, T t2);
//String中的boolean t1.equals(t2)
@Test
public void test6() {
BiPredicate<String,String> pre1 = (s1,s2) -> s1.equals(s2);
System.out.println(pre1.test("abc","abc"));

System.out.println("*******************");
BiPredicate<String,String> pre2 = String :: equals;
System.out.println(pre2.test("abc","abd"));
}

// Function中的R apply(T t)
// Employee中的String getName();
@Test
public void test7() {
Employee employee = new Employee(1001, "Jerry", 23, 6000);


Function<Employee,String> func1 = e -> e.getName();
System.out.println(func1.apply(employee));

System.out.println("*******************");


Function<Employee,String> func2 = Employee::getName;
System.out.println(func2.apply(employee));


}

}
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
/**
* 一、构造器引用
* 和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。
* 抽象方法的返回值类型即为构造器所属的类的类型
*
* 二、数组引用
* 大家可以把数组看做是一个特殊的类,则写法与构造器引用一致。
*
* Created by shkstart
*/
public class ConstructorRefTest {
//构造器引用
//Supplier中的T get()
//Employee的空参构造器:Employee()
@Test
public void test1(){

Supplier<Employee> sup = new Supplier<Employee>() {
@Override
public Employee get() {
return new Employee();
}
};
System.out.println("*******************");

Supplier<Employee> sup1 = () -> new Employee();
System.out.println(sup1.get());

System.out.println("*******************");

Supplier<Employee> sup2 = Employee :: new;
System.out.println(sup2.get());
}

//Function中的R apply(T t)
@Test
public void test2(){
Function<Integer,Employee> func1 = id -> new Employee(id);
Employee employee = func1.apply(1001);
System.out.println(employee);

System.out.println("*******************");

Function<Integer,Employee> func2 = Employee :: new;
Employee employee1 = func2.apply(1002);
System.out.println(employee1);

}

//BiFunction中的R apply(T t,U u)
@Test
public void test3(){
BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
System.out.println(func1.apply(1001,"Tom"));

System.out.println("*******************");

BiFunction<Integer,String,Employee> func2 = Employee :: new;
System.out.println(func2.apply(1002,"Tom"));

}

//数组引用
//Function中的R apply(T t)
@Test
public void test4(){
Function<Integer,String[]> func1 = length -> new String[length];
String[] arr1 = func1.apply(5);
System.out.println(Arrays.toString(arr1));

System.out.println("*******************");

Function<Integer,String[]> func2 = String[] :: new;
String[] arr2 = func2.apply(10);
System.out.println(Arrays.toString(arr2));

}
}

强大的 Stream API

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
/**
* 1. Stream关注的是对数据的运算,与CPU打交道
* 集合关注的是数据的存储,与内存打交道
*
* 2.
* ①Stream 自己不会存储元素。
* ②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
* ③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
*
* 3.Stream 执行流程
* ① Stream的实例化
* ② 一系列的中间操作(过滤、映射、...)
* ③ 终止操作
*
* 4.说明:
* 4.1 一个中间操作链,对数据源的数据进行处理
* 4.2 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
*
*
* 测试Stream的实例化
*
* @author shkstart
* @create 2019 下午 4:25
*/
public class StreamAPITest {

//创建 Stream方式一:通过集合
@Test
public void test1(){
List<Employee> employees = EmployeeData.getEmployees();

// default Stream<E> stream() : 返回一个顺序流
Stream<Employee> stream = employees.stream();

// default Stream<E> parallelStream() : 返回一个并行流
Stream<Employee> parallelStream = employees.parallelStream();

}

//创建 Stream方式二:通过数组
@Test
public void test2(){
int[] arr = new int[]{1,2,3,4,5,6};
//调用Arrays类的static <T> Stream<T> stream(T[] array): 返回一个流
IntStream stream = Arrays.stream(arr);

Employee e1 = new Employee(1001,"Tom");
Employee e2 = new Employee(1002,"Jerry");
Employee[] arr1 = new Employee[]{e1,e2};
Stream<Employee> stream1 = Arrays.stream(arr1);

}
//创建 Stream方式三:通过Stream的of()
@Test
public void test3(){

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

}

//创建 Stream方式四:创建无限流
@Test
public void test4(){

// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
//遍历前10个偶数
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);


// 生成
// public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random).limit(10).forEach(System.out::println);

}

}
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/**
* 测试Stream的中间操作
*
* @author shkstart
* @create 2019 下午 4:42
*/
public class StreamAPITest1 {

//1-筛选与切片
@Test
public void test1(){
List<Employee> list = EmployeeData.getEmployees();
// filter(Predicate p)——接收 Lambda , 从流中排除某些元素。
Stream<Employee> stream = list.stream();
//练习:查询员工表中薪资大于7000的员工信息
stream.filter(e -> e.getSalary() > 7000).forEach(System.out::println);

System.out.println();
// limit(n)——截断流,使其元素不超过给定数量。
list.stream().limit(3).forEach(System.out::println);
System.out.println();

// skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
list.stream().skip(3).forEach(System.out::println);

System.out.println();
// distinct()——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素

list.add(new Employee(1010,"刘强东",40,8000));
list.add(new Employee(1010,"刘强东",41,8000));
list.add(new Employee(1010,"刘强东",40,8000));
list.add(new Employee(1010,"刘强东",40,8000));
list.add(new Employee(1010,"刘强东",40,8000));

// System.out.println(list);

list.stream().distinct().forEach(System.out::println);
}

//映射
@Test
public void test2(){
// map(Function f)——接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。
List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);

// 练习1:获取员工姓名长度大于3的员工的姓名。
List<Employee> employees = EmployeeData.getEmployees();
Stream<String> namesStream = employees.stream().map(Employee::getName);
namesStream.filter(name -> name.length() > 3).forEach(System.out::println);
System.out.println();
//练习2:
Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest1::fromStringToStream);
streamStream.forEach(s ->{
s.forEach(System.out::println);
});
System.out.println();
// flatMap(Function f)——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
Stream<Character> characterStream = list.stream().flatMap(StreamAPITest1::fromStringToStream);
characterStream.forEach(System.out::println);

}

//将字符串中的多个字符构成的集合转换为对应的Stream的实例
public static Stream<Character> fromStringToStream(String str){//aa
ArrayList<Character> list = new ArrayList<>();
for(Character c : str.toCharArray()){
list.add(c);
}
return list.stream();

}



@Test
public void test3(){
ArrayList list1 = new ArrayList();
list1.add(1);
list1.add(2);
list1.add(3);

ArrayList list2 = new ArrayList();
list2.add(4);
list2.add(5);
list2.add(6);

// list1.add(list2);
list1.addAll(list2);
System.out.println(list1);

}

//3-排序
@Test
public void test4(){
// sorted()——自然排序
List<Integer> list = Arrays.asList(12, 43, 65, 34, 87, 0, -98, 7);
list.stream().sorted().forEach(System.out::println);
//抛异常,原因:Employee没有实现Comparable接口
// List<Employee> employees = EmployeeData.getEmployees();
// employees.stream().sorted().forEach(System.out::println);


// sorted(Comparator com)——定制排序

List<Employee> employees = EmployeeData.getEmployees();
employees.stream().sorted( (e1,e2) -> {

int ageValue = Integer.compare(e1.getAge(),e2.getAge());
if(ageValue != 0){
return ageValue;
}else{
return -Double.compare(e1.getSalary(),e2.getSalary());
}

}).forEach(System.out::println);
}
}
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
84
85
86
87
88
89
90
91
92
93
94
95
/**
* 测试Stream的终止操作
*
* @author shkstart
* @create 2019 下午 6:37
*/
public class StreamAPITest2 {

//1-匹配与查找
@Test
public void test1(){
List<Employee> employees = EmployeeData.getEmployees();

// allMatch(Predicate p)——检查是否匹配所有元素。
// 练习:是否所有的员工的年龄都大于18
boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
System.out.println(allMatch);

// anyMatch(Predicate p)——检查是否至少匹配一个元素。
// 练习:是否存在员工的工资大于 10000
boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
System.out.println(anyMatch);

// noneMatch(Predicate p)——检查是否没有匹配的元素。
// 练习:是否存在员工姓“雷”
boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
System.out.println(noneMatch);
// findFirst——返回第一个元素
Optional<Employee> employee = employees.stream().findFirst();
System.out.println(employee);
// findAny——返回当前流中的任意元素
Optional<Employee> employee1 = employees.parallelStream().findAny();
System.out.println(employee1);

}

@Test
public void test2(){
List<Employee> employees = EmployeeData.getEmployees();
// count——返回流中元素的总个数
long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
System.out.println(count);
// max(Comparator c)——返回流中最大值
// 练习:返回最高的工资:
Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
Optional<Double> maxSalary = salaryStream.max(Double::compare);
System.out.println(maxSalary);
// min(Comparator c)——返回流中最小值
// 练习:返回最低工资的员工
Optional<Employee> employee = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(employee);
System.out.println();
// forEach(Consumer c)——内部迭代
employees.stream().forEach(System.out::println);

//使用集合的遍历操作
employees.forEach(System.out::println);
}

//2-归约
@Test
public void test3(){
// reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值。返回 T
// 练习1:计算1-10的自然数的和
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream().reduce(0, Integer::sum);
System.out.println(sum);


// reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
// 练习2:计算公司所有员工工资的总和
List<Employee> employees = EmployeeData.getEmployees();
Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
// Optional<Double> sumMoney = salaryStream.reduce(Double::sum);
Optional<Double> sumMoney = salaryStream.reduce((d1,d2) -> d1 + d2);
System.out.println(sumMoney.get());

}

//3-收集
@Test
public void test4(){
// collect(Collector c)——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
// 练习1:查找工资大于6000的员工,结果返回为一个List或Set

List<Employee> employees = EmployeeData.getEmployees();
List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());

employeeList.forEach(System.out::println);
System.out.println();
Set<Employee> employeeSet = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());

employeeSet.forEach(System.out::println);
}
}

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
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* Optional类:为了在程序中避免出现空指针异常而创建的。
*
* 常用的方法:ofNullable(T t)
* orElse(T t)
*
* @author shkstart
* @create 2019 下午 7:24
*/
public class OptionalTest {

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

*/
@Test
public void test1(){
Girl girl = new Girl();
// girl = null;
//of(T t):保证t是非空的
Optional<Girl> optionalGirl = Optional.of(girl);

}

@Test
public void test2(){
Girl girl = new Girl();
// girl = null;
//ofNullable(T t):t可以为null
Optional<Girl> optionalGirl = Optional.ofNullable(girl);
System.out.println(optionalGirl);
//orElse(T t1):如果单前的Optional内部封装的t是非空的,则返回内部的t.
//如果内部的t是空的,则返回orElse()方法中的参数t1.
Girl girl1 = optionalGirl.orElse(new Girl("赵丽颖"));
System.out.println(girl1);

}


public String getGirlName(Boy boy){
return boy.getGirl().getName();
}

@Test
public void test3(){
Boy boy = new Boy();
boy = null;
String girlName = getGirlName(boy);
System.out.println(girlName);

}
//优化以后的getGirlName():
public String getGirlName1(Boy boy){
if(boy != null){
Girl girl = boy.getGirl();
if(girl != null){
return girl.getName();
}
}

return null;

}

@Test
public void test4(){
Boy boy = new Boy();
boy = null;
String girlName = getGirlName1(boy);
System.out.println(girlName);

}

//使用Optional类的getGirlName():
public String getGirlName2(Boy boy){

Optional<Boy> boyOptional = Optional.ofNullable(boy);
//此时的boy1一定非空
Boy boy1 = boyOptional.orElse(new Boy(new Girl("迪丽热巴")));

Girl girl = boy1.getGirl();

Optional<Girl> girlOptional = Optional.ofNullable(girl);
//girl1一定非空
Girl girl1 = girlOptional.orElse(new Girl("古力娜扎"));

return girl1.getName();
}

@Test
public void test5(){
Boy boy = null;
boy = new Boy();
boy = new Boy(new Girl("苍老师"));
String girlName = getGirlName2(boy);
System.out.println(girlName);
}
}
-------------本文结束goodwell感谢您的阅读-------------
小二,上酒~
undefined