语言概述和jdk安装与配置

1-1 软件开发介绍

  • 软件开发
    软件,即一系列按照特定顺序组织的计算机数据和指令的集合。有系统软件和应用软件之分。
  • 人机交互方式
    图形化界面(Graphical User Interface GUI):这种方式简单直观,使用者易于接受,容易上手操作。
    命令行方式(Command Line Interface CLI):需要有一个控制台,输入特定的指令,让计算机完成一些操作。较为麻烦,需要记录住一些命令。
  • 常用的DOS命令
    dir : 列出当前目录下的文件以及文件夹
    md : 创建目录
    rd : 删除目录
    cd : 进入指定目录
    cd… : 退回到上一级目录
    cd: 退回到根目录
    del : 删除文件
    exit : 退出 dos 命令行
    echo javase>1.doc:创建文件1.doc并写入javase
  • 常用快捷键
    ← →:移动光标
    ↑ ↓:调阅历史操作命令
    Delete和Backspace:删除字符

1-2 计算机编程语言介绍

  • 什么是计算机语言
    语言:是人与人之间用于沟通的一种方式。例如:中国人与中国人用普通话沟通。而中国人要和英国人交流,就要学习英语。
    计算机语言:人与计算机交流的方式。
    如果人要与计算机交流,那么就要学习计算机语言。
    计算机语言有很多种。如:C ,C++ ,Java ,PHP , Kotlin,Python,Scala等。
  • 第一代语言:机器语言。
    指令以二进制代码形式存在。
  • 第二代语言:汇编语言。
    使用助记符表示一条机器指令。
  • 第三代语言:高级语言
    C、Pascal、Fortran面向过程的语言
    C++面向过程/面向对象
    Java跨平台的纯面向对象的语言
    .NET跨语言的平台
    Python、Scala…

1-3 Java语言概述

  • 是SUN(Stanford University Network,斯坦福大学网络公司 ) 1995年推出的一门高级编程语言。
  • 是一种面向Internet的编程语言。Java一开始富有吸引力是因为Java程序可以在Web浏览器中运行。这些Java程序被称为Java小程序(applet)。applet使用现代的图形用户界面与Web用户进行交互。 applet内嵌在HTML代码中。
  • 随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。
    后台开发:Java、PHP、Python、Go、Node.js

1.3.1 Java简史
1991年 Green项目,开发语言最初命名为Oak (橡树)
1994年,开发组意识到Oak 非常适合于互联网
1996年,发布JDK 1.0,约8.3万个网页应用Java技术来制作
1997年,发布JDK 1.1,JavaOne会议召开,创当时全球同类会议规模之最
1998年,发布JDK 1.2,同年发布企业平台J2EE
1999年,Java分成J2SE、J2EE和J2ME,JSP/Servlet技术诞生
2004年,发布里程碑式版本:JDK 1.5,为突出此版本的重要性,更名为JDK 5.0
2005年,J2SE -> JavaSE,J2EE -> JavaEE,J2ME -> JavaME
2009年,Oracle公司收购SUN,交易价格74亿美元
2011年,发布JDK 7.0
2014年,发布JDK 8.0,是继JDK 5.0以来变化最大的版本
2017年,发布JDK 9.0,最大限度实现模块化
2018年3月,发布JDK 10.0,版本号也称为18.3
2018年9月,发布JDK 11.0,版本号也称为18.9

1.3.2 Java技术体系平台

  • Java SE(Java Standard Edition)标准版
    支持面向桌面级应用(如Windows下的应用程序)的Java平台,提供了完整的Java核 心API,此版本以前称为J2SE。
  • Java EE(Java Enterprise Edition)企业版
    是为开发企业环境下的应用程序提供的一套解决方案。该技术体系中包含的技术如:Servlet 、Jsp等,主要针对于Web应用程序开发。版本以前称为J2EE。
  • Java ME(Java Micro Edition)小型版
    支持Java程序运行在移动终端(手机、PDA)上的平台,对Java API有所精简,并加入了针对移动终端的支持,此版本以前称为J2ME。
  • Java Card
    支持一些Java小程序(Applets)运行在小内存设备(如智能卡)上的平台。

1.3.3 Java在各领域的应用

  • 企业级应用:主要指复杂的大企业的软件系统、各种类型的网站。Java的安全机制以及它的跨平台的优势,使它在分布式系统领域开发中有广泛应用。应用领域包括金融、电信、交通、电子商务等。
  • Android平台应用:Android应用程序使用Java语言编写。Android开发水平的高低很大程度上取决于Java语言核心能力是否扎实。
  • 大数据平台开发:各类框架有Hadoop,spark,storm,flink等,就这类技术生态圈来讲,还有各种中间件如flume,kafka,sqoop等等 ,这些框架以及工具大多数是用Java编写而成,但提供诸如Java,scala,Python,R等各种语言API供编程。
  • 移动领域应用:主要表现在消费和嵌入式领域,是指在各种小型设备上的应用,包括手机、PDA、机顶盒、汽车通信设备等。

1.3.4 Java语言的诞生
java之父James Gosling团队在开发”Green”项目时,发现C缺少垃圾回收系统,还有可移植的安全性、分布程序设计和多线程功能。最后,他们想要一种易于移植到各种设备上的平台,于是Java语言诞生了。

1.3.4 Java语言的主要特性

  • Java语言是易学的。Java语言的语法与C语言和C++语言很接近,使得大多数程序员很容易学习和使用Java。
  • Java语言是强制面向对象的。Java语言提供类、接口和继承等原语,为了简单起见,只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制(关键字为implements)。
  • Java语言是分布式的。Java语言支持Internet应用的开发,在基本的Java应用编程接口中有一个网络应用编程接口(java net),它提供了用于网络应用编程的类库,包括URL、URLConnection、Socket、ServerSocket等。Java的RMI(远程 方法激活)机制也是开发分布式应用的重要手段。
  • Java语言是健壮的。Java的强类型机制、异常处理、垃圾的自动收集等是Java程序 健壮性的重要保证。对指针的丢弃是Java的明智选择。
  • Java语言是安全的。Java通常被用在网络环境中,为此,Java提供了一个安全机制以防恶意代码的攻击。如:安全防范机制(类ClassLoader),如分配不同的名字空间以防替代本地的同名类、字节代码检查。
  • Java语言是体系结构中立的。Java程序(后缀为java的文件)在Java平台上被编译为体系结构中立的字节码格式(后缀为class的文件),然后可以在实现这个Java平台的任何系统中运行。
  • Java语言是解释型的。如前所述,Java程序在Java平台上被编译为字节码格式,然后可以在实现这个Java平台的任何系统的解释器中运行。
  • Java是性能略高的。与那些解释型的高级脚本语言相比,Java的性能还是较优的。
  • Java语言是原生支持多线程的。在Java语言中,线程是一种特殊的对象,它必须由Thread类或其子(孙)类来创建。

1-4 Java程序运行机制及运行过程

1.4.1 Java语言的特点

  • 特点一:面向对象
    两个基本概念:类、对象
    三大特性:封装、继承、多态
  • 特点二:健壮性
    吸收了C/C++语言的优点,但去掉了其影响程序健壮性的部分(如指针、内存的申请与释放等),提供了一个相对安全的内存管理和访问机制
  • 特点三:跨平台性
    跨平台性:通过Java语言编写的应用程序在不同的系统平台上都可以运行。“Write once , Run Anywhere”
    原理:只要在需要运行 java 应用程序的操作系统上,先安装一个Java虚拟机 (JVM Java Virtual Machine) 即可。由JVM来负责Java程序在该系统中的运行。

1.4.2 Java两种核心机制

  • Java虚拟机 (Java Virtal Machine)
  • 垃圾收集机制 (Garbage Collection)

①核心机制—Java虚拟机

  • JVM是一个虚拟的计算机,具有指令集并使用不同的存储区域。负责执行指令,管理数据、内存、寄存器。
  • 对于不同的平台,有不同的虚拟机。
  • 只有某平台提供了对应的java虚拟机,java程序才可在此平台运行
  • Java虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,到处运行”。
    ②核心机制—垃圾回收
  • 不再使用的内存空间应回收—— 垃圾回收。
    在C/C++等语言中,由程序员负责回收无用内存。
    Java 语言消除了程序员回收无用内存空间的责任:它提供一种系统级线程跟踪存储空间的分配情况。并在JVM空闲时,检查并释放那些可被释放的存储空间。
  • 垃圾回收在Java程序运行过程中自动进行,程序员无法精确控制和干预。
  • Java程序还会出现内存泄漏和内存溢出问题吗?Yes!

1-5 Java语言的环境搭建

1.5.1 什么是JDK,JRE?

  • JDK(Java Development Kit Java开发工具包)
    JDK是提供给Java开发人员使用的,其中包含了java的开发工具,也包括了JRE。所以安装了JDK,就不用在单独安装JRE了。
    其中的开发工具:编译工具(javac.exe) 打包工具(jar.exe)等
  • JRE(Java Runtime Environment Java运行环境)
    包括Java虚拟机(JVM Java Virtual Machine)和Java程序所需的核心类库等,如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。

1.5.2 JDK、JRE、JVM关系

  • JDK = JRE + 开发工具集(例如Javac编译工具等)
  • JRE = JVM + Java SE标准类库

下载并安装JDK请参考:Java中JDK的下载安装与配置

1-6 开发体验— HelloWorld

步骤:

  1. 将 Java 代码编写到扩展名为 .java 的文件中。
1
2
3
4
5
6
public class Test{
public static void main(String[] args) {
System.out.println(“Hello World!”);
}
}
12345
  • 通过 javac 命令对该 java 文件进行编译。
    在这里插入图片描述
    有了java源文件,通过编译器将其编译成JVM可以识别的字节码文件。在该源文件目录下,通过javac编译工具对Test.java文件进行编译。如果程序没有错误,没有任何提示,但在当前目录下会出现一个est.class文件,该文件称为字节码文件,也是可以执行的java的程序。
  • 通过 java 命令对生成的 class 文件进行运行。
1
2
java Test
1

1-7 常见问题及解决方法

在这里插入图片描述
原因:
1.源文件名不存在或者写错
2.当前路径错误
3.后缀名隐藏问题
在这里插入图片描述
原因:
1.类文件名写错,尤其文件名与类名不一致时,要小心
2.类文件不在当前路径下,或者不在classpath指定路径下
在这里插入图片描述
原因:声明为public的类应与文件名一致,否知编译失败
在这里插入图片描述
原因:编译失败,注意错误出现的行数,再到源代码中指定位置改错
总结:
学习编程最容易犯的错是语法错误。Java要求你必须按照语法规则编写代码。如果你的程序违反了语法规则,例如:忘记了分号、大括号、引号,或者拼错了单词,java编译器都会报语法错误。尝试着去看懂编译器会报告的错误信息。

1-8 注释

  • 用于注解说明解释程序的文字就是注释。
  • Java中的注释类型:
    单行注释: //注释文字
    多行注释:/* 注释文字 */
    文档注释 (java特有) :
1
2
3
4
5
/**
@author 指定java程序的作者
@version 指定源文件的版本
*/
1234
  • 提高了代码的阅读性;调试程序的重要方法。
  • 注释是一个程序员必须要具有的良好编程习惯。
  • 将自己的思想通过注释先整理出来,再用代码去体现
  • 注:
    ①对于单行和多行注释,被注释的文字,不会被JVM(java虚拟机)解释执行。
    ②多行注释里面不允许有多行注释嵌套。
    ③文档注释内容可以被JDK提供的工具 javadoc 所解析,生成一套以网页文件形式体现的该程序的说明文档。

1-9 Java API文档

  • API (Application Programming Interface,应用程序编程接口)是 Java 提供的基本编程接口。
  • Java语言提供了大量的基础类,因此 Oracle
    也为这些基础类提供了相应的API文档,用于告诉开发者如何使用这些类,以及这些类里包含的方法。
  • 下载API:
    Java API文档下载

1-10 良好的编程风格

  • 正确的注释和注释风格
    ①使用文档注释来注释整个类或整个方法。
    ②如果注释方法中的某一个步骤,使用单行或多行注释。
  • 正确的缩进和空白
    ①使用一次tab操作,实现缩进
    ②运算符两边习惯性各加一个空格。比如:2 + 4 * 5。
  • 块的风格
    Java API 源代码选择了行尾风格
1
2
3
4
5
6
public class Test {
public static void main(String[] args){
System.out.println("Block Style!");
}
}
12345

推荐阅读:阿里巴巴编码规范

1-11 常用的Java开发工具

1.文本编辑工具:

  • 记事本
  • UltraEdit
  • EditPlus
  • TextPad
  • NotePad

2.Java集成开发环境(IDE):

  • JBuilder
  • NetBeans
  • Eclipse
  • MyEclipse
  • STS
  • IntelliJ IDEA

java基本语法

1、关键字与标识符

关键字

1.定义:被Java语言赋予了特殊含义,用做专门用途的字符串(单词)
2.特点:关键字中所有字母都为小写
3.官方地址:https://docs.oracle.com/[javase](https://so.csdn.net/so/search?q=javase&spm=1001.2101.3001.7020)/tutorial/java/nutsandbolts/_keywords.html

用于定义数据类型的关键字
class interface enum byte short
int long float double char
boolean void
用于定义流程控制的关键字
if else switch case default
while do for continue continue
return
用于定义访问权限修饰符的关键字
private protected public
用于定义类,函数,变量修饰符的关键字
abstract final static synchronized
用于定义类与类之间关系的关键字
extends implements
用于定义建立实例及引用实例,判断实例的关键字
new this super instanceof
用于异常处理的关键字
try catch finally throw throws
用于包的关键字
package import
其他修饰符关键字
native strictfp transient volatile assert
用于定义数据类型值的字面值
true false null

保留字

Java保留字:现有Java版本尚未使用,但以后版本可能会作为关键字使用。自己命名标识符时要避免使用这些保留字(goto 、const)。

标识符

1.标识符:
①Java 对各种变量、方法和类等要素命名时使用的字符序列称为标识符
②技巧:凡是自己可以起名字的地方都叫标识符。
2.定义合法标识符规则:
①由26个英文字母大小写,0-9 ,_或 $ 组成
②数字不可以开头。
③不可以使用关键字和保留字,但能包含关键字和保留字。
④Java中严格区分大小写,长度无限制。
⑤标识符不能包含空格。
2.Java中的名称命名规范:
①包名:多单词组成时所有字母都小写:xxxyyyzzz
②类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz
③变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写:xxxYyyZzz
④常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ

  • 注意1:在起名字时,为了提高阅读性,要尽量有意义,“见名知意”。
  • 注意2:java采用unicode字符集,因此标识符也可以使用汉字声明,但是不建议使用。

推荐阅读:阿里巴巴编码规范代码整洁之道

2、变量

变量的分类

1.1 按数据类型分类
在这里插入图片描述

详细说明:

  1. 整型:byte(1字节=8bit) \ short(2字节) \ int(4字节) \ long(8字节)
    ① byte范围:-128 ~ 127
    ② 声明long型变量,必须以”l”或”L”结尾
    ③ 通常,定义整型变量时,使用int型。
    ④整型的常量,默认类型是:int型
  2. 浮点型:float(4字节) \ double(8字节)
    ① 浮点型,表示带小数点的数值
    ② float表示数值的范围比long还大
    ③ 定义float类型变量时,变量要以”f”或”F”结尾
    ④ 通常,定义浮点型变量时,使用double型。
    ⑤ 浮点型的常量,默认类型为:double
  3. 字符型:char (1字符=2字节)
    ① 定义char型变量,通常使用一对’’,内部只能写一个字符
    ② 表示方式:1.声明一个字符 2.转义字符 3.直接使用 Unicode 值 来表示字符型常量
  4. 布尔型:boolean
    ① 只能取两个值之一:true 、 false
    ② 常常在条件判断、循环结构中使用

1.2 按声明的位置分类

在这里插入图片描述

定义变量的格式:

数据类型 变量名 = 变量值;

数据类型 变量名;
变量名 = 变量值;

变量使用的注意点:

① 变量必须先声明,后使用
② 变量都定义在其作用域内。在作用域内,它是有效的。换句话说,出了作用域,就失效了
③ 同一个作用域内,不可以声明两个同名的变量

基本数据类型变量间运算规则

4.1. 涉及到的基本数据类型:除了boolean之外的其他7种

4.2. 自动类型转换(只涉及7种基本数据类型)
结论:当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果自动提升为容量大的数据类型。
byte 、char 、short –> int –> long –> float –> double
特别的:当byte、char、short三种类型的变量做运算时,结果为int型
说明:此时的容量大小指的是,表示数的范围的大和小。比如:float容量要大于long的容量

4.3. 强制类型转换(只涉及7种基本数据类型):自动类型提升运算的逆运算。
1.需要使用强转符:()
2.注意点:强制类型转换,可能导致精度损失。

4.4. String与8种基本数据类型间的运算

  1. String属于引用数据类型,翻译为:字符串
  2. 声明String类型变量时,使用一对””
  3. String可以和8种基本数据类型变量做运算,且运算只能是连接运算:+
  4. 运算的结果仍然是String类型
    避免:
    String s = 123;//编译错误
    String s1 = “123”;//正确
    int i = (int)s1;//编译错误

3、进制

关于进制

  • 所有数字在计算机底层都以二进制形式存在。
  • 对于整数,有四种表示方式:
    ①二进制(binary):0,1 ,满2进1.以0b或0B开头。
    ②十进制(decimal):0-9 ,满10进1。
    ③八进制(octal):0-7 ,满8进1. 以数字0开头表示。
    ④十六进制(hex):0-9及A-F,满16进1. 以0x或0X开头表示。此处的A-F不区分大小写。如:0x21AF +1= 0X21B0

二进制

  • java整数常量默认是int类型,当用二进制定义整数时,其第32位是符号位;当是long类型时,二进制默认占64位,第64位是符号位.
  • 二进制的整数有如下三种形式:
    ①原码:直接将一个数值换成二进制数。最高位是符号位
    ②负数的反码:是对原码按位取反,只是最高位(符号位)确定为1。
    ③负数的补码:其反码加1。
  • 计算机以二进制补码的形式保存所有的整数。
    ①正数的原码、反码、补码都相同
    ②负数的补码是其反码+1

为什么要使用原码、反码、补码表示形式呢?
计算机辨别“符号位”显然会让计算机的基础电路设计变得十分复杂! 于是人们想出了将符号位也参与运算的方法. 我们知道, 根据运算法则减去一个正数等于加上一个负数, 即: 1-1 = 1 + (-1) = 0 , 所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了。
在这里插入图片描述
二进制->十进制
在这里插入图片描述
计算机底层都以补码的方式来存储数据!
在这里插入图片描述
在这里插入图片描述
对于正数来讲:原码、反码、补码是相同的:三码合一。
计算机底层都是使用二进制表示的数值
计算机底层都是使用的数值的补码保存数据的。

进制间的转化

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4、运算符

算术运算符

在这里插入图片描述
算术运算符的注意问题
①如果对负数取模,可以把模数负号忽略不记,如:5%-2=1。 但被模数是负数则不可忽略。此外,取模运算的结果不一定总是整数。
②对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。 例如:int x=3510;x=x/1000*1000; //3000
③“+”除字符串相加功能外,还能把非字符串转换成字符串.例如:System.out.println(“5+5=”+5+5); //打印结果是:5+5=55

赋值运算符

赋值运算符:= += -= *= /= %=

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
		int i2,j2;
//连续赋值
i2 = j2 = 10;
int i3 = 10,j3 = 20;
int num1 = 10;
num1 += 2;//num1 = num1 + 2;
System.out.println(num1);//12

int num2 = 12;
num2 %= 5;//num2 = num2 % 5;
System.out.println(num2);

short s1 = 10;
//s1 = s1 + 2;//编译失败
s1 += 2;//结论:不会改变变量本身的数据类型
System.out.println(s1);
12345678910111213141516

说明:
1.运算的结果不会改变变量本身的数据类型

1
2
3
4
5
6
7
8
9
		//开发中,如果希望变量实现+2的操作,有几种方法?(前提:int num = 10;)
//方式一:num = num + 2;
//方式二:num += 2; (推荐)

//开发中,如果希望变量实现+1的操作,有几种方法?(前提:int num = 10;)
//方式一:num = num + 1;
//方式二:num += 1;
//方式三:num++; (推荐)
12345678

比较运算符

在这里插入图片描述
①比较运算符的结果都是boolean型,也就是要么是true,要么是false。
②比较运算符“==”不能误写成“=” 。
【典型代码】

1
2
3
4
5
6
7
8
9
10
11
		int i = 10;
int j = 20;

System.out.println(i == j);//false
System.out.println(i = j);//20

boolean b1 = true;
boolean b2 = false;
System.out.println(b2 == b1);//false
System.out.println(b2 = b1);//true
12345678910

【特别说明的】
1.比较运算符的结果是boolean类型
2.> < >= <= :只能使用在数值类型的数据之间。
\3. == 和 !=: 不仅可以使用在数值类型数据之间,还可以使用在其他引用类型变量之间。
Account acct1 = new Account(1000);
Account acct2 = new Account(1000);
boolean b1 = (acct1 == acct2);//比较两个Account是否是同一个账户。
boolean b2 = (acct1 != acct2);

逻辑运算符

在这里插入图片描述
【特别说明的】
1.逻辑运算符操作的都是boolean类型的变量。而且结果也是boolean类型
【典型代码】

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
		//区分& 与 &&
//相同点1:& 与 && 的运算结果相同
//相同点2:当符号左边是true时,二者都会执行符号右边的运算
//不同点:当符号左边是false时,&继续执行符号右边的运算。&&不再执行符号右边的运算。
//开发中,推荐使用&&
boolean b1 = true;
b1 = false;
int num1 = 10;
if(b1 & (num1++ > 0)){
System.out.println("我现在在北京");
}else{
System.out.println("我现在在南京");
}
System.out.println("num1 = " + num1);

boolean b2 = true;
b2 = false;
int num2 = 10;
if(b2 && (num2++ > 0)){
System.out.println("我现在在北京");
}else{
System.out.println("我现在在南京");
}
System.out.println("num2 = " + num2);

// 区分:| 与 ||
//相同点1:| 与 || 的运算结果相同
//相同点2:当符号左边是false时,二者都会执行符号右边的运算
//不同点3:当符号左边是true时,|继续执行符号右边的运算,而||不再执行符号右边的运算
//开发中,推荐使用||
boolean b3 = false;
b3 = true;
int num3 = 10;
if(b3 | (num3++ > 0)){
System.out.println("我现在在北京");
}else{
System.out.println("我现在在南京");
}
System.out.println("num3 = " + num3);


boolean b4 = false;
b4 = true;
int num4 = 10;
if(b4 || (num4++ > 0)){
System.out.println("我现在在北京");
}else{
System.out.println("我现在在南京");
}
System.out.println("num4 = " + num4);
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950

位运算符

在这里插入图片描述
位运算是直接对整数的二进制进行的运算
在这里插入图片描述
【典型代码】

1
2
3
4
5
6
7
8
9
10
11
12
		int i = 21;
i = -21;
System.out.println("i << 2 :" + (i << 2));
System.out.println("i << 3 :" + (i << 3));
System.out.println("i << 27 :" + (i << 27));

int m = 12;
int n = 5;
System.out.println("m & n :" + (m & n));
System.out.println("m | n :" + (m | n));
System.out.println("m ^ n :" + (m ^ n));
1234567891011

【特别说明的】
① 位运算符操作的都是整型的数据
② >> :在一定范围内,每向右移1位,相当于 / 2;
<< :在一定范围内,每向左移1位,相当于 * 2。

三元运算符

在这里插入图片描述
三元运算符:(条件表达式)? 表达式1 : 表达式2
【典型代码】
①获取两个整数的较大值
②获取三个数的最大值
【特别说明的】

  1. 说明
    ① 条件表达式的结果为boolean类型
    ② 根据条件表达式真或假,决定执行表达式1,还是表达式2.
    如果表达式为true,则执行表达式1。
    如果表达式为false,则执行表达式2。
    ③ 表达式1 和表达式2要求是一致的。
    ④ 三元运算符可以嵌套使用
  2. 凡是可以使用三元运算符的地方,都可以改写为if-else,反之,不成立。
  3. 如果程序既可以使用三元运算符,又可以使用if-else结构,那么优先选择三元运算符。原因:简洁、执行效率高。

5、流程控制

顺序结构

在这里插入图片描述

分支结构

1.if-else条件判断结构

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
结构一:
if(条件表达式){
执行表达式
}

结构二:二选一
if(条件表达式){
执行表达式1
}else{
执行表达式2
}

结构三:n选一
if(条件表达式){
执行表达式1
}else if(条件表达式){
执行表达式2
}else if(条件表达式){
执行表达式3
}
...
else{
执行表达式n
}
123456789101112131415161718192021222324

说明:
① else 结构是可选的。
② 针对于条件表达式:

如果多个条件表达式之间是“互斥”关系(或没有交集的关系),哪个判断和执行语句声明在上面还是下面,无所谓。
如果多个条件表达式之间有交集的关系,需要根据实际情况,考虑清楚应该将哪个结构声明在上面。
如果多个条件表达式之间有包含的关系,通常情况下,需要将范围小的声明在范围大的上面。否则,范围小的就没机会执行了。

③if-else结构是可以相互嵌套的。
④ 如果if-else结构中的执行语句只有一行时,对应的一对{}可以省略的。但是,不建议大家省略。

2.switch-case选择结构

1
2
3
4
5
6
7
8
9
10
11
12
13
switch(表达式){
case 常量1:
执行语句1;
//break;
case 常量2:
执行语句2;
//break;
...
default:
执行语句n;
//break;
}
123456789101112

说明:
① 根据switch表达式中的值,依次匹配各个case中的常量。一旦匹配成功,则进入相应case结构中,调用其执行语句。当调用完执行语句以后,则仍然继续向下执行其他case结构中的执行语句,直到遇到break关键字或此switch-case结构 末尾结束为止。
② break,可以使用在switch-case结构中,表示一旦执行到此关键字,就跳出switch-case结构
③ switch结构中的表达式,只能是如下的6种数据类型之一:
byte 、short、char、int、枚举类型(JDK5.0新增)、String类型(JDK7.0新增)
④ case 之后只能声明常量。不能声明范围。
⑤ break关键字是可选的。
⑥ default:相当于if-else结构中的else.
default结构是可选的,而且位置是灵活的。
3.如果switch-case结构中的多个case的执行语句相同,则可以考虑进行合并。
4.break在switch-case中是可选的

循环结构

1.循环结构的四要素
① 初始化条件
② 循环条件 —>是boolean类型
③ 循环体
④ 迭代条件
说明:通常情况下,循环结束都是因为②中循环条件返回false了。

2.三种循环结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//2.1 for循环结构
for(①;②;④){

}
//执行过程:① - ② - ③ - ④ - ② - ③ - ④ - ... - ②

//2.2 while循环结构

while(②){
③;
④;
}
//执行过程:① - ② - ③ - ④ - ② - ③ - ④ - ... - ②

//2.3 do-while循环结构

do{
③;
④;
}while(②);
//执行过程:① - ③ - ④ - ② - ③ - ④ - ... - ②
/*说明:
写while循环千万小心不要丢了迭代条件。一旦丢了,就可能导致死循环!
for和while循环总结:
1. 开发中,基本上我们都会从for、while中进行选择,实现循环结构。
2. for循环和while循环是可以相互转换的!
区别:for循环和while循环的初始化条件部分的作用范围不同。
3. 我们写程序,要避免出现死循环。
4. do-while循环至少会执行一次循环体!
5.开发中,使用for和while更多一些。较少使用do-while*/
123456789101112131415161718192021222324252627282930

3.“无限循环”结构: while(true) 或 for( ; ; )
总结:如何结束一个循环结构?
方式一:当循环条件是false时
方式二:在循环体中,执行break

4.嵌套循环
4.1.嵌套循环:将一个循环结构A声明在另一个循环结构B的循环体中,就构成了嵌套循环
内层循环:循环结构A
外层循环:循环结构B
4.2.说明:
① 内层循环结构遍历一遍,只相当于外层循环循环体执行了一次
② 假设外层循环需要执行m次,内层循环需要执行n次。此时内层循环的循环体一共执行了m * n次
③ 外层循环控制行数,内层循环控制列数
【典型练习】

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
		//练习一:
/*
******
******
******
******
*/
for(int j = 1;j <= 4;j++ ){
for(int i = 1;i <= 6;i++){
System.out.print('*');
}
System.out.println();
}
//练习二:
/* i(行号) j(*的个数)
* 1 1
** 2 2
*** 3 3
**** 4 4
***** 5 5
*/

for(int i = 1;i <= 5;i++){//控制行数
for(int j = 1;j <= i;j++){//控制列数
System.out.print("*");

}
System.out.println();
}
//练习三:九九乘法表
//练习四:100以内的质数
12345678910111213141516171819202122232425262728293031

衡量一个功能代码的优劣:
1.正确性
2.可读性
3.健壮性
4.高效率与低存储:时间复杂度 、空间复杂度 (衡量算法的好坏)

4、break和continue关键字的使用

使用范围 循环中使用的作用(不同点) 相同点
break switch-case 、循环结构中 结束当前循环 关键字后面不能声明执行语句
continue 循环结构中 结束当次循环 关键字后面不能声明执行语句

6、计算机字符编码

有关编码的基础知识

1、位 bit 最小的单元
字节 byte 机器语言的单位 1byte=8bits
1KB=1024byte
1MB=1024KB
1GB=1024MB
2、二进制 binary
八进制 octal
十进制 decimal
十六进制 hex
3、字符:是各种文字和符号的总称,包括各个国家的文字,标点符号,图形符号,数字等。
字符集: 字符集是多个符号的集合,每个字符集包含的字符个数不同。
字符编码: 字符集只是规定了有哪些字符,而最终决定采用哪些字符,每一个字符用多少字节表示等问题,则是由编码来决定的。计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。

常见字符编码的介绍

首先来看一下常用的编码有哪些,截图自 Notepad++。其中 ANSI 在中国大陆即为 GBK(以前是 GB2312),最常用的是 GBK 和 UTF8 无 BOM 编码格式。后面三个都是有 BOM 头的文本格式,UCS-2 即为人们常说的 Unicode 编码,又分为大端、小端。
在这里插入图片描述
所谓 BOM 头(Byte Order Mark)就是文本文件中开始的几个并不表示任何字符的字节,用二进制编辑器(如 bz.exe)就能看到了。
UTF8 的 BOM 头为 0xEF 0xBB 0xBF
Unicode 大端模式为 0xFE 0xFF
Unicode 小端模式为 0xFF 0xFE
在这里插入图片描述
说明:需要添加插件 HEX-Editor

ASCII 码

计算机一开始发明的时候是用来解决数字计算的问题,后来人们发现,计算机还可以做更多的事,例如文本处理。但由于计算机只识“数”,因此人们必须告诉计算机哪个数字来代表哪个特定字符,例如 65 代表字母‘A’,66 代表字母‘B’,以此类推。但是计算机之间字符-数字的对应关系必须得一致,否则就会造成同一段数字在不同计算机上显示出来的字符不一样。因此美国国家标准协会 ANSI 制定了一个标准,规定了常用字符的集合以及每个字符对应的编号,这就是 ASCII 字符集Character Set),也称 ASCII 码。
那时候的字符编解码系统非常简单,就是简单的查表过程。例如将字符序列编码为二进制流写入存储设备,只需要在 ASCII 字符集中依次找到字符对应的字节,然后直接将该字节写入存储设备即可。解码二进制流的过程也是类似。
其中:

  • 0~31 及 127(共 33
    个)是控制字符或通信专用字符(其余为可显示字符),如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)
  • 32~126(共 95 个)是字符(32 是空格),其中 48~57 为 0 到 9 十个阿拉伯数字。
  • 65~90 为 26 个大写英文字母,97~122 号为 26 个小写英文字母,其余为一些标点符号、运算符号等。
  • 后 128 个称为扩展 ASCII 码。许多基于 x86 的系统都支持使用扩展(或“高”)ASCII。扩展ASCII 码允许将每个字符的第
    8 位用于确定附加的 128 个特殊符号字符、外来语字母和图形符号。

OEM 字符集的衍生

当计算机开始发展起来的时候,人们逐渐发现,ASCII 字符集里那可怜的 128 个字符已经不能再满足他们的需求了。人们就在想,一个字节能够表示的数字(编号)有 256 个,而 ASCII 字符只用到了 x000x7F,也就是占用了前 128 个,后面 128 个数字不用白不用,因此很多人打起了后面这 128 个数字的主意。可是问题在于,很多人同时有这样的想法,但是大家对于0x80-0xFF 这后面的 128 个数字分别对应什么样的字符,却有各自的想法。这就导致了当时销往世界各地的机器上出现了大量各式各样的 OEM 字符集。
大家对于 0x00
0x7F 这个范围的解释基本是相同的,而对于后半部分 0x80~0xFF 的解释却不一定相同。甚至有时候同样的字符在不同 OEM 字符集中对应的字节也是不同的。
不同的 OEM 字符集导致人们无法跨机器交流各种文档。例如职员甲发了一封简历 résumés 给职员乙,结果职员乙看到的却是 r?sum?s,因为é字符在职员甲机器上的 OEM 字符集中对应的字节是 0x82,而在职员乙的机器上,由于使用的 OEM 字符集不同,对 0x82 字节解码后得到的字符却是?。

多字节字符集(MBCS)和中文字符集

上面我们提到的字符集都是基于单字节编码,也就是说,一个字节翻译成一个字符。这对于拉丁语系国家来说可能没有什么问题,因为他们通过扩展第8个比特,就可以得到256个字符了, 足够用了。但是对于亚洲国家来说,256 个字符是远远不够用的。因此这些国家的人为了用上电脑,又要保持和 ASCII 字符集的兼容,就发明了多字节编码方式,相应的字符集就称为多字节字符集(Muilti-Bytes Charecter Set)。例如中国使用的就是双字节字符集编码。
例如目前最常用的中文字符集 GB2312,涵盖了所有简体字符以及一部分其他字符;GBK(K 代表扩展的意思)则在 GB2312 的基础上加入了对繁体字符等其他非简体字符。这两个字符集的字符都是使用 1-2 个字节来表示。Windows 系统采用 936 代码页来实现对 GBK 字符集的编解码。在解析字节流的时候,如果遇到字节的最高位是 0 的话,那么就使用 936 代码页中的第 1 张码表进行解码,这就和单字节字符集的编解码方式一致了。如果遇到字节的最高位是 1 的话,那么就表示需要两个字节值才能对应一个字符。
假如你使用 GB2312 写了这么一句话:
我叫 ABC
它的二进制编码是这样的:
11001110 11010010 10111101 11010000 01000001 01000002 01000003
全角?
全角是一种电脑字符,且每个全角字符占用两个标准字符(或半角字符)位置。通常的英文字母、数字键、符号键都是半角的,半角的显示内码都是一个字节。为了排列整齐,英文和其它拉丁文的字符和标点也提供了全角格式。在中文输入法中,切换全角和半角格式的快捷键为SHIFT+空格。

ANSI 标准、国家标准、ISO 标准

不同 ASCII 衍生字符集的出现,让文档交流变得非常困难,因此各种组织都陆续进行了标准化流程。例如美国 ANSI 组织制定了 ANSI 标准字符编码(注意,我们现在通常说到 ANSI 编码,通常指的是平台的默认编码,例如英文操作系统中是 ISO-8859-1,中文系统是 GBK),ISO 组织制定的各种 ISO 标准字符编码,还有各国也会制定一些国家标准字符集,例如中国的 GBK,GB2312 和 GB18030。
操作系统在发布的时候,通常会往机器里预装这些标准的字符集还有平台专用的字符集,这样只要你的文档是使用标准字符集编写的,通用性就比较高了。例如你用 GB2312 字符集编写的文档,在中国大陆内的任何机器上都能正确显示。同时,我们也可以在一台机器上阅读多个国家不同语言的文档了,前提是本机必须安装该文档使用的字符集。

Unicode 的出现

虽然通过使用不同字符集,我们可以在一台机器上查阅不同语言的文档,但是我们仍然无法解决一个问题:如果一份文档中含有不同国家的不同语言的字符,那么无法在一份文档中显示所有字符。为了解决这个问题,我们需要一个全人类达成共识的巨大的字符集,这就是 Unicode字符集。Unicode 字符集涵盖了目前人类使用的所有字符,并为每个字符进行统一编号,分配唯一的字符码(Code Point)。Unicode 字符集将所有字符按照使用上的频繁度划分为 17 个层面(Plane),每个层面上有 216=65536 个字符码空间。
其中第 0 个层面 BMP,基本涵盖了当今世界用到的所有字符。其他的层面要么是用来表示一些远古时期的文字,要么是留作扩展。我们平常用到的 Unicode 字符,一般都是位于 BMP 层面上的。目前 Unicode 字符集中尚有大量字符空间未使用。

编码系统的变化

在 Unicode 出现之前,所有的字符集都是和具体编码方案绑定在一起的(即字符集≈编码方式),都是直接将字符和最终字节流绑定死了,例如 ASCII 编码系统规定使用 7 比特来编码 ASCII 字符集;GB2312 以及 GBK 字符集,限定了使用最多 2 个字节来编码所有字符,并且规定了字节序。这样的编码系统通常用简单的查表,也就是通过代码页就可以直接将字符映射为存储设备上的字节流了。例如下面这个例子:
在这里插入图片描述
Unicode 同样也不完美,这里就有三个问题,一个是,我们已经知道,英文字母只用一个字节表示就够了,第二个问题是如何才能区别 Unicode 和 ASCII?计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号呢?第三个,如果和 GBK 等双字节编码方式一样,用最高位是 1 或 0 表示两个字节和一个字节,就少了很多值无法用于表示字符,不够表示所有字符。Unicode 在很长一段时间内无法推广,直到互联网的出现,为解决 Unicode 如何在网络上传输的问题,于是面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF-8 就是每次 8 个位传输数据,而 UTF-16 就是每次 16 个位。UTF-8 就是在互联网上使用最广的一种Unicode 的实现方式,这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。
UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用 1~4 个字节表示一个符号。从 unicode 到 uft-8 并不是直接的对应,而是要过一些算法和规则来转换(即 Uncidoe 字符集≠UTF-8 编码方式)。
Unicode 符号范围 | UTF-8 编码方式
(十六进制) | (二进制)
—————————————————————–
0000 0000-0000 007F | 0xxxxxxx(兼容原来的 ASCII)
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
因此,Unicode 只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的 Unicode 编码是 UTF-16 和UTF-8。
早期字符编码、字符集和代码页等概念都是表达同一个意思。例如 GB2312 字符集、GB2312编码,936 代码页,实际上说的是同个东西。
但是对于 Unicode 则不同,Unicode 字符集只是定义了字符的集合和唯一编号,Unicode 编码,则是对 UTF-8、UCS-2/UTF-16 等具体编码方案的统称而已,并不是具体的编码方案。所以当需要用到字符编码的时候,你可以写 gb2312,codepage936,utf-8,utf-16,但请不要写 Unicode。
造成乱码的原因就是因为使用了错误的字符编码去解码字节流,因此当我们在思考任何跟文本显示有关的问题时,请时刻保持清醒:当前使用的字符编码是什么。只有这样,我们才能正确分析和处理乱码问题。
常见 CharSet 有:GBK、GB2312、US-ASCII、ISO-8859-1、UTF-8、UTF-16BE、UTF-16LE、UTF-16
在这里插入图片描述

java中数组

一、数组的概述

1、数组(Array),是多个相同类型数据按一定顺序排列的集合,并使用一个名字命名,并通过编号的方式对这些数据进行统一管理。
2、数组的常见概念
① 数组名
②下标(或索引)
③ 元素
④数组的长度
3、 数组本身是引用数据类型,而数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型。
4、创建数组对象会在内存中开辟一整块连续的空间,而数组名中引用的是这块连续空间的首地址。
5、数组的长度一旦确定,就不能修改。
6、 我们可以直接通过下标(或索引)的方式调用指定位置的元素,速度很快。
7、数组的分类:
①按照维度:一维数组、二维数组、三维数组、…
②按照元素的数据类型分:基本数据类型元素的数组、引用数据类型元素的数组(即对象数组)

二、一维数组

1、一维数组的声明
①一维数组的声明方式:
type var[] 或 type[] var;
例如:
int a[];
int[] a1;
double b[];
String[] c; //引用类型变量数组
② Java语言中声明数组时不能指定其长度(数组中元素的数), 例如: int a[5]; //非法

2、一维数组的初始化

  • 动态初始化:数组声明且为数组元素分配空间与赋值的操作分开进行
1
2
3
4
5
6
7
8
9
10
11
int[] arr = new int[3];
arr[0] = 3;
arr[1] = 9;
arr[2] = 8;

String names[];
names = new String[3];
names[0] = “钱学森”;
names[1] = “邓稼先”;
names[2] = “袁隆平”;
12345678910
  • 静态初始化:在定义数组的同时就为数组元素分配空间并赋值。
1
2
3
4
int arr[] = new int[]{ 3, 9, 8};

int[] arr = {3,9,8};
123

3、一维数组元素的引用

①定义并用运算符new为之分配空间后,才可以引用数组中的每个元素;
②数组元素的引用方式:数组名[数组元素下标]

  • 数组元素下标可以是整型常量或整型表达式。如a[3] , b[i] , c[6*i];
  • 数组元素下标从0开始;长度为n的数组合法下标取值范围: 0 —>n-1;如int a[]=new int[3]; 可引用的数组元素为a[0]、a[1]、a[2]

③每个数组都有一个属性length指明它的长度,例如:a.length 指明数组a的长度(元素个数)

  • 数组一旦初始化,其长度是不可变的

4、一维数组数组元素的默认初始化值

数组是引用类型,它的元素相当于类的成员变量,因此数组一经分配空间,其中的每个元素也被按照成员变量同样的方式被隐式初始化。例如:

1
2
3
4
5
6
7
public class Test {
public static void main(String argv[]){
int a[]= new int[5];
System.out.println(a[3]); //a[3]的默认值为0
}
}
123456
  • 对于基本数据类型而言,默认初始化值各有不同
  • 对于引用数据类型而言,默认初始化值为null(注意与0不同!)
    在这里插入图片描述
    5、一维数组的创建
    Java中使用关键字new来创建数组,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test{
public static void main(String args[]){
int[] s;
s = new int[10];
//int[] s=new int[10];
//基本数据类型数组在显式赋值之前,
//Java会自动给他们赋默认值。
for ( int i=0; i<10; i++ ) {
s[i] =2*i+1;
System.out.println(s[i]);
}
}
}
12345678910111213

6、一维数组的内存解析

在这里插入图片描述

三、二维数组

1、如何理解二维数组?
①数组属于引用数据类型
②数组的元素也可以是引用数据类型
③一个一维数组A的元素如果还是一个一维数组类型的,则,此数组A称为二维数组。

2、二维数组的声明与初始化

1
2
3
4
5
6
7
8
9
10
11
int[] arr = new int[]{1,2,3};//一维数组
//静态初始化
int[][] arr1 = new int[][]{{1,2,3},{4,5},{6,7,8}};
//动态初始化1
String[][] arr2 = new String[3][2];
//动态初始化2
String[][] arr3 = new String[3][];
//也是正确的写法:
int[] arr4[] = new int[][]{{1,2,3},{4,5,9,10},{6,7,8}};
int[] arr5[] = {{1,2,3},{4,5},{6,7,8}};//类型推断
12345678910

3、如何调用二维数组元素?

1
2
3
4
5
6
7
System.out.println(arr1[0][1]);//2
System.out.println(arr2[1][1]);//null

arr3[1] = new String[4];
System.out.println(arr3[1][0]);
System.out.println(arr3[0]);
123456

4、二维数组的属性:

1
2
3
4
System.out.println(arr4.length);//3
System.out.println(arr4[0].length);//3
System.out.println(arr4[1].length);//4
123

5、遍历二维数组元素

1
2
3
4
5
6
7
	for(int i = 0;i < arr4.length;i++){			
for(int j = 0;j < arr4[i].length;j++){
System.out.print(arr4[i][j] + " ");
}
System.out.println();
}
123456

6、二维数组元素的默认初始化值

规定:二维数组分为外层数组的元素,内层数组的元素

  •  int[][] arr = new int[4][3];
    1
    
    1
    2
    3
    4

    - ```
    外层元素:arr[0],arr[1]等
    1
  •  内层元素:arr[0][0],arr[1][2]等
    1
    
    1
    2
    3
    4
    5
    6

    针对于初始化方式一:比如:int[][] arr = new int[4][3];

    - ```
    外层元素的初始化值为:地址值
    1
  •  内层元素的初始化值为:与一维数组初始化情况相同
    1
    
    1
    2
    3
    4
    5
    6

    针对于初始化方式二:比如:int[][] arr = new int[4][];

    - ```
    外层元素的初始化值为:null
    1
  •  内层元素的初始化值为:不能调用,否则报错。
    1
    
    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

    **7、二维数组的内存解析**
    ![在这里插入图片描述](../UpPictures/javasePictures/20200411105346908.png)

    ## 四、Arrays工具类的使用

    **1.理解:**
    ① 定义在java.util包下。
    ② Arrays:提供了很多操作数组的方法。

    **2.使用:**

    ```java
    //1.boolean equals(int[] a,int[] b):判断两个数组是否相等。
    int[] arr1 = new int[]{1,2,3,4};
    int[] arr2 = new int[]{1,3,2,4};
    boolean isEquals = Arrays.equals(arr1, arr2);
    System.out.println(isEquals);

    //2.String toString(int[] a):输出数组信息。
    System.out.println(Arrays.toString(arr1));

    //3.void fill(int[] a,int val):将指定值填充到数组之中。
    Arrays.fill(arr1,10);
    System.out.println(Arrays.toString(arr1));

    //4.void sort(int[] a):对数组进行排序。
    Arrays.sort(arr2);
    System.out.println(Arrays.toString(arr2));

    //5.int binarySearch(int[] a,int key)
    int[] arr3 = new int[]{-98,-34,2,34,54,66,79,105,210,333};
    int index = Arrays.binarySearch(arr3, 210);
    if(index >= 0){
    System.out.println(index);
    }else{
    System.out.println("未找到");
    }
    12345678910111213141516171819202122232425

五、数组中常见的异常

1.数组角标越界异常:ArrayIndexOutOfBoundsException

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

for(int i = 0;i <= arr.length;i++){
System.out.println(arr[i]);
}
System.out.println(arr[-2]);//报异常,后面的语句不会执行
System.out.println("hello");
1234567

2.空指针异常:NullPointerException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
		//情况一:
int[] arr1 = new int[]{1,2,3};
arr1 = null;
System.out.println(arr1[0]);

//情况二:
int[][] arr2 = new int[4][];
System.out.println(arr2[0][0]);

//情况三:
String[] arr3 = new String[]{"AA","BB","CC"};
arr3[0] = null;
System.out.println(arr3[0].toString());

1234567891011121314

一旦程序出现异常,未处理时,就终止执行。

面向对象编程

面向对象之类与对象

面向过程与面向对象

1、面向过程(POP) 与 面向对象(OOP)
①二者都是一种思想,面向对象是相对于面向过程而言的。面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做。面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
② 面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。
2、面向对象的三大特征
①封装 (Encapsulation)
②继承 (Inheritance)
③多态 (Polymorphism)
3、面向对象的思想概述
①程序员从面向过程的执行者转化成了面向对象的指挥者
②面向对象分析方法分析问题的思路和步骤:

  • 根据问题需要,选择问题所针对的现实世界中的实体。
  • 从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类。
  • 把抽象的实体用计算机语言进行描述,形成计算机世界中类的定义。即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构。
  • 将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具。

Java语言的基本元素:类和对象

1、类:对一类事物的描述,是抽象的、概念上的定义
2、对象:是实际存在的该类事物的每个个体,因而也称为实例(instance)

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

3、 类的语法格式
在这里插入图片描述

  • 属 性:对应类中的成员变量
  • 行 为:对应类中的成员方法
1
2
3
4
5
6
7
public class Person{
private int age ; //声明私有变量 age
public void showAge(int i) { //声明方法showAge( )
age = i;
}
}
123456

在这里插入图片描述
说明:

  • 属性 = 成员变量 = field = 域、字段
  • 方法 = 成员方法 = 函数 = method
  • 创建类的对象 = 类的实例化 = 实例化类

4、创建Java自定义类
步骤:
① 定义类(考虑修饰符、类名)
② 编写类的属性(考虑修饰符、属性类型、属性名、初始化值)
③编写类的方法(考虑修饰符、返回值类型、方法名、形参等)

对象的创建和使用

1、创建对象语法: 类名 对象名 = new 类名();
2、使用“对象名.对象成员”的方式访问对象成员(包括属性和方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Animal {
public int legs;
public void eat(){
System.out.println(“Eating.”);
}
public viod move(){
System.out.println(“Move.”);
}
}
// 测试
public class Zoo{
public static void main(String args[]){
//创建对象
Animal xb=new Animal();
xb.legs=4;//访问属性
System.out.println(xb.legs);
xb.eat();//访问方法
xb.move();//访问方法
}
}
1234567891011121314151617181920

3、类的访问机制:
①在一个类中的访问机制:类中的方法可以直接访问类中的成员变量。(例外:static方法访问非static,编译不通过。)
②在不同类中的访问机制:先创建要访问类的对象,再用对象访问类中定义的成员。

4、对象的创建

1
2
3
4
5
//典型代码:
Person p1 = new Person();
Person p2 = new Person();
Person p3 = p1;//没有新创建一个对象,共用一个堆空间中的对象实体。
1234

说明:
如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的)
意味着:如果我们修改一个对象的属性a,则不影响另外一个对象属性a的值。
5、对象的内存解析
在这里插入图片描述
在这里插入图片描述
6、匿名对象
匿名对象:我们创建的对象,没显式的赋给一个变量名。即为匿名对象。
特点:匿名对象只能调用一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
		new Phone().sendEmail();
new Phone().playGame();

new Phone().price = 1999;
new Phone().showPrice();//0.0
12345
PhoneMall mall = new PhoneMall();

//匿名对象的使用
mall.show(new Phone());

class PhoneMall{
public void show(Phone phone){
phone.sendEmail();
phone.playGame();
}

}
123456789101112

8、理解”万事万物皆对象”
①在Java语言范畴中,我们都将功能、结构等封装到类中,通过类的实例化,来调用具体的功能结构

  • Scanner,String等
  • 文件:File
  • 网络资源:URL

②涉及到Java语言与前端Html、后端的数据库交互时,前后端的结构在Java层面交互时,都体现为类、对象。

类的三大结构:属性、方法、构造器

类的成员之一:属性(field)

1、语法格式:

修饰符 数据类型 属性名 = 初始化值 ;

说明1: 修饰符

  • 常用的权限修饰符有:private、缺省、protected、public
  • 其他修饰符:static、final (暂不考虑)

说明2:数据类型

  • 任何基本数据类型(如int、Boolean) 或 任何引用数据类型。

说明3:属性名

  • 属于标识符,符合命名规则和规范即可。
1
2
3
4
5
public class Person{
private int age; //声明private变量 age
public String name = “Lila”; //声明public变量 name
}
1234

2、变量的分类:成员变量与局部变量

  • 在方法体外,类体内声明的变量称为成员变量。
  • 在方法体内部声明的变量称为局部变量。
    在这里插入图片描述
    注意:二者在初始化值方面的异同:
    同:都有生命周期
    异:局部变量除形参外,均需显式初始化。

3、成员变量(属性)和局部变量的区别?

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person{//人类
//属性
String name;//姓名
int age = 1;//年龄
boolean isMale;//是否是男性
public void show(String nation){
//nation:局部变量
String color;//color:局部变量
color = "yellow";
}
}
//测试类
class PersonTest{
public static void main(String[] args){
Person p = new Person();
p.show(“USA”);
}
}
123456789101112131415161718

在这里插入图片描述

4、对象属性的默认初始化赋值

在这里插入图片描述

类的成员之二:方法(method)

1、什么是方法(method、函数):

  • 方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程。
  • 将功能封装为方法的目的是,可以实现代码重用,简化代码
  • Java里的方法不能独立存在,所有的方法必须定义在类里。
1
2
3
4
5
6
7
8
9
10
public class Person{
private int age;
public int getAge() { //声明方法getAge()
return age;
}
public void setAge(int i) { //声明方法setAge
age = i; //将参数i的值赋给类的成员变量age
}
}
123456789

2、方法的声明格式:

修饰符 返回值类型 方法名(参数类型 形参1, 参数类型 形参2, ….){
方法体程序代码
return 返回值;

其中:
修饰符:public,缺省,private, protected等
返回值类型:

  • 没有返回值:void。
  • 有返回值,声明出返回值的类型。与方法体中“return 返回值”搭配使用

方法名:属于标识符,命名时遵循标识符命名规则和规范,“见名知意”
形参列表:可以包含零个,一个或多个参数。多个参数时,中间用“,”隔开
返回值:方法在执行完毕后返还给调用它的程序的数据。

3、方法的分类:按照是否有形参及返回值

在这里插入图片描述

4、方法的调用

  • 方法通过方法名被调用,且只有被调用才会执行。
  • 方法调用的过程分析
    在这里插入图片描述
    注 意:
  • 方法被调用一次,就会执行一次
  • 没有具体返回值的情况,返回值类型用关键字void表示,那么方法体中可以不必使用return语句。如果使用,仅用来结束方法。
  • 定义方法时,方法的结果应该返回给调用者,交由调用者处理。
  • 方法中只能调用方法或属性,不可以在方法内部定义方法。

5、对象数组的内存解析

在这里插入图片描述

6、方法的重载

①重载的概念
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
②重载的特点:
与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//返回两个整数的和
int add(int x,int y){return x+y;}
//返回三个整数的和
int add(int x,int y,int z){return x+y+z;}
//返回两个小数的和
double add(double x,double y){return x+y;}
123456
public class PrintStream {
public static void print(int i) {……}
public static void print(float f) {……}
public static void print(String s) {……}
public static void main(String[] args) {
print(3);
print(1.2f);
print("hello!");
}
}
12345678910
  • 使用重载方法,可以为编程带来方便。
  • 例如,System.out.println()方法就是典型的重载方法,其内部的声明形式如下:
1
2
3
4
5
6
7
8
9
10
11
public void println(byte x)
public void println(short x)
public void println(int x)
public void println(long x)
public void println(float x)
public void println(double x)
public void println(char x)
public void println(double x)
public void println()
……
12345678910

7、可变个数的形参

JavaSE 5.0 中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。

1
2
3
4
5
//JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a ,String[] books);
//JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a ,String…books);
1234

说明:
① 声明格式:方法名(参数的类型名 …参数名)
② 可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个
③ 可变个数形参的方法与同名的方法之间,彼此构成重载
④ 可变参数方法的使用与方法参数部分使用数组是一致的
⑤ 方法的参数部分有可变形参,需要放在形参声明的最后
⑥ 在一个方法的形参位置,最多只能声明一个可变个数形参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void test(String[] msg){
System.out.println(“含字符串数组参数的test方法 ");
}
public void test1(String book){
System.out.println(“****与可变形参方法构成重载的test1方法****");
}
public void test1(String ... books){
System.out.println("****形参长度可变的test1方法****");
}
public static void main(String[] args){
TestOverload to = new TestOverload();
//下面两次调用将执行第二个test方法
to.test1();
to.test1("aa" , "bb");
//下面将执行第一个test方法
to.test(new String[]{"aa"});
}
1234567891011121314151617

8、方法参数的值传递机制

① 方法,必须由其所在类或对象调用才有意义。若方法含有参数:

  • 形参:方法声明时的参数
  • 实参:方法调用时实际传给形参的参数值

② Java的实参值如何传入方法呢?
Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。

  • 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
  • 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参

基本数据类型的参数传递
在这里插入图片描述
引用数据类型的参数传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
Person obj = new Person();
obj.age = 5;
System.out.println("修改之前age = " + obj.age);// 5
// x是实参
change(obj);
System.out.println("修改之后age = " + obj.age);// 3
}
public static void change(Person obj) {
System.out.println("change:修改之前age = " + obj.age);
obj.age = 3;
System.out.println("change:修改之后age = " + obj.age);
}
//其中Person类定义为:
class Person{
int age;
}
1234567891011121314151617

在这里插入图片描述
经典题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TransferTest3 {
public static void main(String args[]) {
TransferTest3 test = new TransferTest3();
test.first();
}
public void first() {
int i = 5;
Value v = new Value();
v.i = 25;
second(v, i);
System.out.println(v.i);
}
public void second(Value v, int i) {
i = 0;
v.i = 20;
Value val = new Value();
v = val;
System.out.println(v.i + " " + i);
}
}
class Value {
int i = 15;
}
1234567891011121314151617181920212223

内存解析:
在这里插入图片描述
打印结果:

15 0
20

9、递归(recursion)方法

  • 递归方法:一个方法体内调用它自身。
  • 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
  • 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
1
2
3
4
5
6
7
8
9
//计算1-100之间所有自然数的和
public int sum(int num){
if(num == 1){
return 1;
}else{
return num + sum(num - 1);
}
}
12345678

斐波那契数列、不死神兔、n的阶乘等问题等可以用递归方法解决!

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

1、构造器的特征

  • 它具有与类相同的名称
  • 它不声明返回值类型。(与声明为void不同)
  • 不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值

2、构造器的作用

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

  • 如:Order o = new Order(); Person p = new Person(“Peter”,15);

3、语法格式

修饰符 类名 (参数列表) {
初始化语句;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Animal {
private int legs;
// 构造器
public Animal() {
legs = 4;
}
public void setLegs(int i) {
legs = i;
}
public int getLegs() {
return legs;
}
}
12345678910111213

创建Animal类的实例:Animal a = new Animal();
调用构造器,将legs初始化为4。

4、分类

根据参数不同,构造器可以分为如下两类:

  • 隐式无参构造器(系统默认提供)
  • 显式定义一个或多个构造器(无参、有参)
    注 意:
  • Java语言中,每个类都至少有一个构造器
  • 默认构造器的修饰符与所属类的修饰符一致
  • 一旦显式定义了构造器,则系统不再提供默认构造器
  • 一个类可以创建多个重载的构造器
  • 父类的构造器不可被子类继承

5、构造器重载

  • 构造器一般用来创建对象的同时初始化对象
1
2
3
4
5
6
7
8
class Person{
String name;
int age;
public Person(String n , int a){
name=n; age=a;
}
}
1234567
  • 构造器重载使得对象的创建更加灵活,方便创建各种不同的对象。
1
2
3
4
5
6
7
public class Person{
public Person(String name, int age, Date d) {this(name,age);…}
public Person(String name, int age) {…}
public Person(String name, Date d) {…}
public Person(){…}
}
123456
  • 构造器重载,参数列表必须不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Person {
private String name;
private int age;
private Date birthDate;
public Person(String n, int a, Date d) {
name = n;
age = a;
birthDate = d;
}
public Person(String n, int a) {
name = n;
age = a;
}
public Person(String n, Date d) {
name = n;
birthDate = d;
}
public Person(String n) {
name = n;
age = 30;
}
}
12345678910111213141516171819202122

6、JavaBean

①JavaBean是一种Java语言写成的可重用组件。
②所谓javaBean,是指符合如下标准的Java类:

  • 类是公共的
  • 有一个无参的公共的构造器
  • 有属性,且有对应的get、set方法
  • 用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
    举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class JavaBean {
private String name; // 属性一般定义为private
private int age;
public JavaBean() {
}
public int getAge() {
return age;
}
public void setAge(int a) {
age = a;
}
public String getName() {
return name;
}
public void setName(String n) {
name = n;
}
}
123456789101112131415161718

7、UML类图

在这里插入图片描述

总结

1、 赋值的位置:
① 默认初始化
② 显式初始化
③ 构造器中初始化
④ 通过“对象.属性“或“对象.方法”的方式赋值
2、 赋值的先后顺序:
① - ② - ③ - ④

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

面向对象特征之一:封装和隐藏

1、使用者对类内部定义的属性(对象的成员变量)的直接操作会导致数据的错误、混乱或安全性问题。
在这里插入图片描述
2、信息的封装和隐藏
Java中通过将数据声明为私有的(private),再提供公共的(public)方法:getXxx()和setXxx()实现对该属性的操作,以实现下述目的:

  • 隐藏一个类中不需要对外提供的实现细节;
  • 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作;
  • 便于修改,增强代码的可维护性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Animal {
private int legs;// 将属性legs定义为private,只能被Animal类内部访问
public void setLegs(int i) { // 在这里定义方法 eat() 和 move()
if (i != 0 && i != 2 && i != 4) {
System.out.println("Wrong number of legs!");
return;
}
legs = i;
}
public int getLegs() {
return legs;
}
}
public class Zoo {
public static void main(String args[]) {
Animal xb = new Animal();
xb.setLegs(4); // xb.setLegs(-1000);
//xb.legs = -1000; // 非法
System.out.println(xb.getLegs());
}
}
123456789101112131415161718192021

3、四种访问权限修饰符
在这里插入图片描述

面向对象特征之二:继承性

1、为什么要有继承?

  • 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。

2、 此处的多个类称为子类(派生类),单独的这个类称为父类(基类 或超类)。可以理解为:“子类 is a 父类”
3、类继承语法规则:

class Subclass extends SuperClass{ }

①为描述和处理个人信息,定义类Person:
在这里插入图片描述
②为描述和处理学生信息,定义类Student:
在这里插入图片描述
③通过继承,简化Student类的定义:
在这里插入图片描述

  • Student类继承了父类Person的所有属性和方法,并增加了一个属性school。Person中的属性和方法,Student都可以使用。
    在这里插入图片描述

4、作用:

  • 继承的出现减少了代码冗余,提高了代码的复用性。
  • 继承的出现,更有利于功能的扩展。
  • 继承的出现让类与类之间产生了关系,提供了多态的前提。

注意:

  • 不要仅为了获取其他类中某个功能而去继承
  • 子类继承了父类,就继承了父类的方法和属性。
  • 在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
  • 在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”。

5、关于继承的规则:

  • 子类不能直接访问父类中私有的(private)的成员变量和方法。

6、 Java只支持单继承和多层继承,不允许多重继承

  • 一个子类只能有一个父类
  • 一个父类可以派生出多个子类
    在这里插入图片描述

方法的重写

1、定义:在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
2、要求:
①子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
②子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
③ 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限

  • 子类不能重写父类中声明为private权限的方法

④子类方法抛出的异常不能大于父类被重写方法的异常
注意:
子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Person {
public String name;
public int age;
public String getInfo() {
return "Name: "+ name + "\n" +"age: "+ age;
}
}
public class Student extends Person {
public String school;
public String getInfo() { //重写方法
return "Name: "+ name + "\nage: "+ age
+ "\nschool: "+ school;
}
public static void main(String args[]){
Student s1=new Student();
s1.name="Bob";
s1.age=20;
s1.school="school2";
System.out.println(s1.getInfo()); //Name:Bob age:20 school:school2
Person p1=new Person();
//调用Person类的getInfo()方法
p1.getInfo();
//调用Student类的getInfo()方法
s1.getInfo();
//这是一种“多态性”:同名的方法,用不同的对象来区分调用的是哪一个方法。
}
}
123456789101112131415161718192021222324252627

面向对象特征之三:多态性

1、多态性,是面向对象中最重要的概念,在Java中的体现:对象的多态性:父类的引用指向子类的对象

  • 可以直接应用在抽象类和接口上

2、Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。

  • 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
  • 多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法) “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)

3、对象的多态 —在Java中,子类的对象可以替代父类的对象使用

  • 一个变量只能有一种确定的数据类型
  • 一个引用类型变量可能指向(引用)多种不同类型的对象

Person p = new Student();
Object o = new Person();//Object类型的变量o,指向Person类型的对象
o = new Student(); //Object类型的变量o,指向Student类型的对象

4、子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。
5、一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法

Student m = new Student();
m.school = “pku”; //合法,Student类有school成员变量
Person e = new Student();
e.school = “pku”; //非法,Person类没有school成员变量

属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编译错误。
6、方法声明的形参类型为父类类型,可以使用子类的对象作为实参调用该方法
7、虚拟方法调用(多态情况下) 子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。

Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法
编译时类型和运行时类型:编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。——动态绑定

方法的重载与重写小结
从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不
同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了
不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类
和子类的,即子类可以重载父类的同名不同参数的方法。
所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”; 而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”
多态小结
① 多态作用:

  • 提高了代码的通用性,常称作接口重用

②前提:

  • 需要存在继承或者实现关系
  • 有方法的重写

③ 成员方法:

  • 编译时:要查看引用变量所声明的类中是否有所调用的方法。
  • 运行时:调用实际new的对象所属的类中的重写方法。

④ 成员变量:

  • 不具备多态性,只看引用变量所声明的类。

类与类之间的关系

依赖关系(Dependency)

对象之间最弱的一种关联方式,是临时性的关联。代码中一般指由局部变量、函数参数、返回值建立的对于其他对象的调用关系。

1
2
3
4
5
6
7
8
9
10
class A{
public B method(C c,D d){
E e = new E();
...
B b = new B();
...
return b;
}
}
123456789

这个代码结构中,表示 A 类依赖了 B,C,D,E 类
在这里插入图片描述

关联关系(Association)

对象之间一种引用关系,比如客户类与订单类之间的关系。这种关系通常使用类的属性表达。关联可以有方向,即导航。一般不作说明的时候,导航是双向的,不需要在线上标出箭头。
大部分情况下导航是单向的,可以加一个箭头表示。

1
2
3
4
5
6
7
8
9
class Employee{
private int eid;//员工编号
private String name;//员工姓名
private Computer coumputer;//员工所使用的电脑
//....
}
class Computer{
}
12345678

Employee——>Computer

1
2
3
4
5
6
7
class Husband{
private Wife wife;
}
class Wife{
private Husband husband;
}
123456

Husband——Wife

关联表示类之间的“持久”关系,这种关系一般表示一种重要的业务之间的关系,需要保存的,或者说需要“持久化”的,或者说需要保存到数据库中的。另外,依赖表示类之间的是
一种“临时、短暂”关系,这种关系是不需要保存的。

聚合(Aggregation)

聚合(关联关系的一种):表示 has-a 的关系。与关联关系一样,聚合关系也是通过实例变量来实现这样关系的。关联关系和聚合关系来语法上是没办法区分的,从语义上才能更好的区分两者的区别。
如汽车类与引挚类,轮胎类之间的关系就是整体与个体的关系。
与关联关系一样,聚合关系也是通过实例变量来实现的。

1
2
3
4
5
class Car{
private Engine engine;//引擎
private Tyre[] tyres;//轮胎
}
1234

在这里插入图片描述
关联和聚集(聚合)的区别:
关联关系所涉及的两个对象是处在同一个层次上的。比如人和自行车就是一种关联关系,而不是聚合关系,因为人不是由自行车组成的。
聚合关系涉及的两个对象处于不平等的层次上,一个代表整体,一个代表部分。比如电脑和它的显示器、键盘、主板以及内存就是聚集关系,因为主板是电脑的组成部分。

组合(Composite)

对象 A 包含对象 B,对象 B 离开对象 A 没有实际意义。是一种更强的关联关系。人包含手,手离开人的躯体就失去了它应有的作用。
组合:表示 contains-a 的关系,是一种强烈的包含关系。组合类负责被组合类的生命周期。也使用属性表达组合关系,是关联关系的一种,是比聚合关系强的关系。

1
2
3
4
5
6
class Window{
private Menu menu;//菜单
private Slider slider;//滑动条
private Panel panel;//工作区
}
12345

在这里插入图片描述

继承(Generalization)

又称为泛化,is-a 的关系
类与类的继承关系,类与接口的实现关系。
场景:父与子、动物与人、植物与树
在这里插入图片描述

抽象类与接口

抽象类与抽象方法

随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
1、用abstract关键字来修饰一个类,这个类叫做抽象类。
2、用abstract来修饰一个方法,该方法叫做抽象方法。

  • 抽象方法:只有方法的声明,没有方法的实现。以分号结束,比如:public abstract void talk();

3、 含有抽象方法的类必须被声明为抽象类。
4、 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
5、 不能用abstract修饰变量、代码块、构造器;
6、 不能用abstract修饰私有方法、静态方法、final的方法、final的类。
在这里插入图片描述
在这里插入图片描述
注意:抽象类不能实例化 new Vihicle()是非法的
7、多态的应用:模板方法设计模式(TemplateMethod)
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:

  • 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
  • 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。

设计模式:模板方法 (TemplateMethod)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class Template {
public final void getTime() {
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
System.out.println("执行时间是:" + (end - start));
}
public abstract void code();
}
class SubTemplate extends Template {
public void code() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
}
}
12345678910111213141516

模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:

  • 数据库访问的封装
  • Junit单元测试
  • JavaWeb的Servlet中关于doGet/doPost方法调用
  • Hibernate中模板程序
  • Spring中JDBCTemlate、HibernateTemplate等

接口(interface)

1、概述

  • 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
  • 另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。
  • 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个”是不是”的关系,而接口实现则是 “能不能”的关系。
  • 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
    2、 接口(interface)是抽象方法和常量值定义的集合。
  • 接口的特点:
    ①用interface来定义。
    ②接口中的所有成员变量都默认是由public static final修饰的。
    ③接口中的所有抽象方法都默认是由public abstract修饰的。
    ④接口中没有构造器。
    ⑤接口采用多继承机制。
1
2
3
4
5
6
7
public interface Runner {
int ID = 1;
void start();
public void run();
void stop();
}
123456

上述例子相当于:

1
2
3
4
5
6
7
public interface Runner {
public static final int ID = 1;
public abstract void start();
public abstract void run();
public abstract void stop();
}
123456
  • 定义Java类的语法格式:先写extends,后写implements
  • class SubClass extends SuperClass implements InterfaceA{ }
  • 一个类可以实现多个接口,接口也可以继承其它接口。
  • 实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
  • 接口的主要用途就是被实现类实现。(面向接口编程)
  • 与继承关系类似,接口与实现类之间存在多态性
  • 接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义
    (JDK7.0及之前),而没有变量和方法的实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface Runner {
public void start();
public void run();
public void stop();
}
class Person implements Runner {
public void start() {
// 准备工作:弯腰、蹬腿、咬牙、瞪眼
// 开跑
}
public void run() {
// 摆动手臂
// 维持直线方向
}
public void stop() {
// 减速直至停止、喝水。
}
}
123456789101112131415161718

3、代理模式(Proxy)
概述:代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
在这里插入图片描述
①应用场景:

  • 安全代理:屏蔽对真实角色的直接访问。
  • 远程代理:通过代理类处理远程方法调用(RMI)
  • 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象,比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。

②分类

  • 静态代理(静态定义代理类)
  • 动态代理(动态生成代理类)

抽象类与接口的异同

在这里插入图片描述
面试题:排错
在这里插入图片描述
解析:子类继承B实现A中都有变量x,此时子类打印x时不知道到底是打印A的还是B的,所以应指定:

修改为:
System.out.println(A.x);
或者
System.out.println(super.x);

Java 8中关于接口的改进

Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
1、静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。
2、默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。
比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface AA {
double PI = 3.14;
public default void method() {
System.out.println("北京");
}
default String method1() {
return "上海";
}
public static void method2() {
System.out.println(“hello lambda!");
}
}
123456789101112

3、接口中的默认方法
①若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接
口时,会出现:接口冲突。

  • 解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突。
    ②若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则。接口中具有
    相同名称和参数的默认方法会被忽略。
    在这里插入图片描述

java中的结构和关键字

关键字:package、import

1、package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。它的格式为:
package 顶层包名.子包名 ;
举例:

1
2
3
4
5
6
7
8
//pack1\pack2\PackageTest.java
package pack1.pack2; //指定类PackageTest属于包pack1.pack2
public class PackageTest{
public void display(){
System.out.println("in method display()");
}
}
1234567

2、包对应于文件系统的目录,package语句中,用 “.” 来指明包(目录)的层次;
3、包通常用小写单词标识。通常使用所在公司域名的倒置:com.baidu.xxx
4、包的作用:

  • 包帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式
  • 包可以包含类和子包,划分项目层次,便于管理
  • 解决类命名冲突的问题
  • 控制访问权限

5、MVC设计模式
在这里插入图片描述
6、JDK中主要的包介绍
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

关键字:final

1、
在这里插入图片描述
①1. final修饰类

1
2
3
4
5
final class A{
}
class B extends A{ //错误,不能被继承。
}
1234

②final修饰方法

1
2
3
4
5
6
7
8
9
10
11
class A {
public final void print() {
System.out.println("A");
}
}
class B extends A {
public void print() { // 错误,不能被重写。
System.out.println("面向对象编程");
}
}
12345678910

③ final修饰变量——常量

1
2
3
4
5
6
7
8
class A {
private final String INFO = "java"; //声明常量
public void print() {
//The final field A.INFO cannot be assigned
//INFO = "mysql";
}
}
1234567

常量名要大写,内容不可修改。
static final:全局常量
2、举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final class Test {
public static int totalNumber = 5;
public final int ID;
public Test() {
ID = ++totalNumber; // 可在构造器中给final修饰的“变量”赋值
}
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.ID);
final int I = 10;
final int J; J = 20;
J = 30; // 非法
}
}
1234567891011121314

Object类的使用

1、在这里插入图片描述
2、Object类中的主要结构
在这里插入图片描述
3、==操作符与equals方法
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
4、toString() 方法
在这里插入图片描述

包装类的使用

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
总结:基本类型、包装类与String类间的转换
在这里插入图片描述
举例:
在这里插入图片描述
在这里插入图片描述

main()方法的使用

1、理解main方法的语法
① 由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public,又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须
是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
② 又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子中多次碰到。
在这里插入图片描述

类的成员之四:代码块

在这里插入图片描述
在这里插入图片描述
静态初始化块举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
public static int total;
static {
total = 100;
System.out.println("in static block!");
}
}
public class PersonTest {
public static void main(String[] args) {
System.out.println("total = " + Person.total);
System.out.println("total = " + Person.total);
}
}
12345678910111213

输出:
in static block
total=100
total=100

在这里插入图片描述

类的内部成员之五:内部类

在这里插入图片描述
在这里插入图片描述
举例:

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
class Outer {
private int s;
public class Inner {
public void mb() {
s = 100;
System.out.println("在内部类Inner中s=" + s);
}
}
public void ma() {
Inner i = new Inner();
i.mb();
}
}
public class InnerTest {
public static void main(String args[]) {
Outer o = new Outer();
o.ma();
}
}
12345678910111213141516171819
public class Outer {
private int s = 111;
public class Inner {
private int s = 222;
public void mb(int s) {
System.out.println(s); // 局部变量s
System.out.println(this.s); // 内部类对象的属性s
System.out.println(Outer.this.s); // 外部类对象属性s }
}
public static void main(String args[]) {
Outer a = new Outer();
Outer.Inner b = a.new Inner();
b.mb(333);
}
}
123456789101112131415

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

native 关键字的理解

使用 native 关键字说明这个方法是原生函数,也就是这个方法是用 C/C++等非Java 语言实现的,并且被编译成了 DLL,由 java 去调用。

1、为什么要用 native 方法?
java 使用起来非常方便,然而有些层次的任务用 java 实现起来不容易,或者我们对程序的效率很在意时,问题就来了。例如:有时 java 应用需要与 java 外面的环境交互。这是本地方法存在的主要原因,你可以想想 java 需要与一些底层系统如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解 java 应用之外的繁琐的细节。

2、native 声明的方法,对于调用者,可以当做和其他 Java 方法一样使用
一个 native method 方法可以返回任何 java 类型,包括非基本类型,而且同样可以进行异常控制。

native method 的存在并不会对其他类调用这些本地方法产生任何影响,实际上调用这些方法的其他类甚至不知道它所调用的是一个本地方法。JVM 将控制调用本地方法的所有细节。

如果一个含有本地方法的类被继承,子类会继承这个本地方法并且可以用 java语言重写这个方法(如果需要的话)。

java中异常

一、异常概述与异常体系结构

在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式,读取文件是否存在,网络是否始终保持通畅等等。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、常见异常

在这里插入图片描述
常见异常:ArrayIndexOutOfBoundsException
在这里插入图片描述
常见异常:NullPointerException
在这里插入图片描述
常见异常:ArithmeticException
在这里插入图片描述
常见异常:ClassCastException
在这里插入图片描述

三、异常处理机制一:try-catch-finally

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class IndexOutExp {
public static void main(String[] args) {
String friends[] = { "lisa", "bily", "kessy" };
try {
for (int i = 0; i < 5; i++) {
System.out.println(friends[i]);
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("index err");
}
System.out.println("\nthis is the end");
}
}
12345678910111213

程序IndexOutExp.java运行结果:java IndexOutExp
lisa
bily
kessy
index err
this is the end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DivideZero1 {
int x;
public static void main(String[] args) {
int y;
DivideZero1 c = new DivideZero1();
try {
y = 3 / c.x;
} catch (ArithmeticException e) {
System.out.println("divide by zero error!");
}
System.out.println("program ends ok!");
}
}
12345678910111213

程序DivideZero1运行结果:java DivideZero1
divide by zero error!
program ends ok!

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class IOExp {
public static void main(String[] args) {
try {
FileInputStream in = new FileInputStream("atguigushk.txt");
int b; b = in.read();
while (b != -1) {
System.out.print((char) b);
b = in.read();
}
in.close();
} catch (IOException e) {
System.out.println(e);
} finally {
System.out.println(" It’s ok!");
}
}
}
1234567891011121314151617

四、异常处理机制二:throws

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五、手动抛出异常

在这里插入图片描述

六、用户自定义异常类

在这里插入图片描述
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyExpTest {
public void regist(int num) throws MyException {
if (num < 0)
throw new MyException("人数为负值,不合理", 3);
else
System.out.println("登记人数" + num);
}
public void manager() {
try {
regist(100);
} catch (MyException e) {
System.out.print("登记失败,出错种类" + e.getId());
}
System.out.print("本次登记操作结束");
}
public static void main(String args[]) {
MyExpTest t = new MyExpTest();
t.manager();
}
}
1234567891011121314151617181920

在这里插入图片描述

一首小悟结束异常处理

世界上最遥远的距离,是我在if里你在else里,似乎一直相伴又永远分离;
世界上最痴心的等待,是我当case你是switch,或许永远都选不上自己;
世界上最真情的相依,是你在try我在catch。无论你发神马脾气,我都默
默承受,静静处理。到那时,再来期待我们的finally

java中的多线程

一、基本概念:程序、进程、线程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、线程的创建和使用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、线程的生命周期

在这里插入图片描述
在这里插入图片描述

四、线程的同步

 问题的提出
多个线程执行的不确定性引起执行结果的不稳定
多个线程对账本的共享,会造成操作的不完整性,会破坏数据。

模拟火车站售票程序,开启三个窗口售票。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五、线程的通信

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
经典生产者和消费者问题

六、JDK5.0新增线程创建方式

在这里插入图片描述

在这里插入图片描述

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
//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 ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();

try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}

}
123456789101112131415161718192021222324252627282930313233343536373839

在这里插入图片描述
在这里插入图片描述

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
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);
}
}
}
}

class NumberThread1 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);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();


//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable

// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}

java中的常用类

一、字符串相关的类

1、字符串String的介绍

在这里插入图片描述
在这里插入图片描述
String对象的创建:

1
2
3
4
5
6
7
8
9
String str = "hello";
//本质上this.value = new char[0];
String s1 = new String();
//this.value = original.value;
String s2 = new String(String original);
//this.value = Arrays.copyOf(value, value.length);
String s3 = new String(char[] a);
String s4 = new String(char[] a,int startIndex,int count);
12345678

在这里插入图片描述
字符串对象的存储
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、String常用方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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
String str = "12hello34world5java7891mysql456";
//把字符串中的数字替换成,,如果结果中开头和结尾有,的话去掉
String string = str.replaceAll("\\d+", ",").replaceAll("^,|,$", "");
System.out.println(string);
String str = "12345";
//判断str字符串中是否全部有数字组成,即有1-n个数字组成
boolean matches = str.matches("\\d+");
System.out.println(matches);
String tel = "0571-4534289";
//判断这是否是一个杭州的固定电话
boolean result = tel.matches("0571-\\d{7,8}");
System.out.println(result);

String str = "hello|world|java";
String[] strs = str.split("\\|");
for (int i = 0; i < strs.length; i++) {
System.out.println(strs[i]);
}

System.out.println();

String str2 = "hello.world.java";
String[] strs2 = str2.split("\\.");
for (int i = 0; i < strs2.length; i++) {
System.out.println(strs2[i]);
}
1234567891011121314151617181920212223242526

3、String与基本数据类型转换

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1
2
3
4
5
6
7
8
9
String str = "中";
System.out.println(str.getBytes("ISO8859-1").length);// -128~127
System.out.println(str.getBytes("GBK").length);
System.out.println(str.getBytes("UTF-8").length);
System.out.println(new String(str.getBytes("ISO8859-1"),
"ISO8859-1"));// 乱码,表示不了中文
System.out.println(new String(str.getBytes("GBK"), "GBK"));
System.out.println(new String(str.getBytes("UTF-8"), "UTF-8"));
12345678

4、StringBuffer类

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5、StringBuilder类

在这里插入图片描述
【面试题】程序输出:

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

二、JDK8之前日期时间API

在这里插入图片描述

1、java.lang.System类

在这里插入图片描述

2、java.util.Date类

在这里插入图片描述

1
2
3
4
5
6
7
8
Date date = new Date();
System.out.println(date);
System.out.println(System.currentTimeMillis());
System.out.println(date.getTime());
Date date1 = new Date(date.getTime());
System.out.println(date1.getTime());
System.out.println(date1.toString());
1234567

3、java.text.SimpleDateFormat类

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Date date = new Date(); // 产生一个Date实例
// 产生一个formater格式化的实例
SimpleDateFormat formater = new SimpleDateFormat();
System.out.println(formater.format(date));// 打印输出默认的格式
SimpleDateFormat formater2 = new SimpleDateFormat("yyyy年MM月dd日 EEE
HH:mm:ss");
System.out.println(formater2.format(date));
try {
// 实例化一个指定的格式对象
Date date2 = formater2.parse("2008年08月08日 星期一 08:08:08");
// 将指定的日期解析后格式化按指定的格式输出
System.out.println(date2.toString());
} catch (ParseException e) {
e.printStackTrace();
}
123456789101112131415

4、 java.util.Calendar(日历)类

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
Calendar calendar = Calendar.getInstance();
// 从一个 Calendar 对象中获取 Date 对象
Date date = calendar.getTime();
// 使用给定的 Date 设置此 Calendar 的时间
date = new Date(234234235235L);
calendar.setTime(date);
calendar.set(Calendar.DAY_OF_MONTH, 8);
System.out.println("当前时间日设置为8后,时间是:" + calendar.getTime());
calendar.add(Calendar.HOUR, 2);
System.out.println("当前时间加2小时后,时间是:" + calendar.getTime());
calendar.add(Calendar.MONTH, -2);
System.out.println("当前日期减2个月后,时间是:" + calendar.getTime());

一、JDK8中新日期时间API

1、背景

在这里插入图片描述

2、LocalDate、LocalTime、LocalDateTime

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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
/*
LocalDate、LocalTime、LocalDateTime 的使用
说明:
1.LocalDateTime相较于LocalDate、LocalTime,使用频率要高
2.类似于Calendar
*/
@Test
public void test1(){
//now():获取当前的日期、时间、日期+时间
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();

System.out.println(localDate);
System.out.println(localTime);
System.out.println(localDateTime);

//of():设置指定的年、月、日、时、分、秒。没有偏移量
LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);
System.out.println(localDateTime1);


//getXxx():获取相关的属性
System.out.println(localDateTime.getDayOfMonth());
System.out.println(localDateTime.getDayOfWeek());
System.out.println(localDateTime.getMonth());
System.out.println(localDateTime.getMonthValue());
System.out.println(localDateTime.getMinute());

//体现不可变性
//withXxx():设置相关的属性
LocalDate localDate1 = localDate.withDayOfMonth(22);
System.out.println(localDate);
System.out.println(localDate1);


LocalDateTime localDateTime2 = localDateTime.withHour(4);
System.out.println(localDateTime);
System.out.println(localDateTime2);

//不可变性
LocalDateTime localDateTime3 = localDateTime.plusMonths(3);
System.out.println(localDateTime);
System.out.println(localDateTime3);

LocalDateTime localDateTime4 = localDateTime.minusDays(6);
System.out.println(localDateTime);
System.out.println(localDateTime4);
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849

3、 瞬时:Instant

在这里插入图片描述
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 /*
Instant的使用
类似于 java.util.Date类

*/
@Test
public void test2(){
//now():获取本初子午线对应的标准时间
Instant instant = Instant.now();
System.out.println(instant);//2019-02-18T07:29:41.719Z

//添加时间的偏移量
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);//2019-02-18T15:32:50.611+08:00

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

//ofEpochMilli():通过给定的毫秒数,获取Instant实例 -->Date(long millis)
Instant instant1 = Instant.ofEpochMilli(1550475314878L);
System.out.println(instant1);
}
1234567891011121314151617181920212223

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
47
48
49
 /*
DateTimeFormatter:格式化或解析日期、时间
类似于SimpleDateFormat

*/

@Test
public void test3(){
// 方式一:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
//格式化:日期-->字符串
LocalDateTime localDateTime = LocalDateTime.now();
String str1 = formatter.format(localDateTime);
System.out.println(localDateTime);
System.out.println(str1);//2019-02-18T15:42:18.797

//解析:字符串 -->日期
TemporalAccessor parse = formatter.parse("2019-02-18T15:42:18.797");
System.out.println(parse);

// 方式二:
// 本地化相关的格式。如:ofLocalizedDateTime()
// FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
//格式化
String str2 = formatter1.format(localDateTime);
System.out.println(str2);//2019年2月18日 下午03时47分16秒


// 本地化相关的格式。如:ofLocalizedDate()
// FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT : 适用于LocalDate
DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
//格式化
String str3 = formatter2.format(LocalDate.now());
System.out.println(str3);//2019-2-18


// 重点: 方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//格式化
String str4 = formatter3.format(LocalDateTime.now());
System.out.println(str4);//2019-02-18 03:52:09

//解析
TemporalAccessor accessor = formatter3.parse("2019-02-18 03:52:09");
System.out.println(accessor);

}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748

5、其他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
//ZoneId:类中包含了所有的时区信息
// ZoneId的getAvailableZoneIds():获取所有的ZoneId
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
for (String s : zoneIds) {
System.out.println(s);
}
// ZoneId的of():获取指定时区的时间
LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Asia/Tokyo"));
System.out.println(localDateTime);
//ZonedDateTime:带时区的日期时间
// ZonedDateTime的now():获取本时区的ZonedDateTime对象
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);
// ZonedDateTime的now(ZoneId id):获取指定时区的ZonedDateTime对象
ZonedDateTime zonedDateTime1 = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
System.out.println(zonedDateTime1);
12345678910111213141516
//Duration:用于计算两个“时间”间隔,以秒和纳秒为基准
LocalTime localTime = LocalTime.now();
LocalTime localTime1 = LocalTime.of(15, 23, 32);
//between():静态方法,返回Duration对象,表示两个时间的间隔
Duration duration = Duration.between(localTime1, localTime);
System.out.println(duration);
System.out.println(duration.getSeconds());
System.out.println(duration.getNano());
LocalDateTime localDateTime = LocalDateTime.of(2016, 6, 12, 15, 23, 32);
LocalDateTime localDateTime1 = LocalDateTime.of(2017, 6, 12, 15, 23, 32);
Duration duration1 = Duration.between(localDateTime1, localDateTime);
System.out.println(duration1.toDays());
123456789101112
//Period:用于计算两个“日期”间隔,以年、月、日衡量
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = LocalDate.of(2028, 3, 18);
Period period = Period.between(localDate, localDate1);
System.out.println(period);
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println(period.getDays());
Period period1 = period.withYears(2);
System.out.println(period1);
12345678910
// TemporalAdjuster:时间校正器
// 获取当前日期的下一个周日是哪天?
TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.SUNDAY);
LocalDateTime localDateTime = LocalDateTime.now().with(temporalAdjuster);
System.out.println(localDateTime);
// 获取下一个工作日是哪天?
LocalDate localDate = LocalDate.now().with(new TemporalAdjuster() {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate date = (LocalDate) temporal;
if (date.getDayOfWeek().equals(DayOfWeek.FRIDAY)) {
return date.plusDays(3);
} else if (date.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
return date.plusDays(2);
} else {
return date.plusDays(1);
} }
});
System.out.println("下一个工作日是:" + localDate);
12345678910111213141516171819

6、与传统日期处理的转换

在这里插入图片描述

二、Java比较器

在这里插入图片描述

1、方式一:自然排序:java.lang.Comparable

在这里插入图片描述
在这里插入图片描述

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 Goods implements Comparable {
private String name;
private double price;
//按照价格,比较商品的大小
@Override
public int compareTo(Object o) {
if(o instanceof Goods) {
Goods other = (Goods) o;
if (this.price > other.price) {
return 1;
} else if (this.price < other.price) {
return -1;
}
return 0;
}
throw new RuntimeException("输入的数据类型不一致");
}
//构造器、getter、setter、toString()方法略
}

public class ComparableTest{
public static void main(String[] args) {
Goods[] all = new Goods[4];
all[0] = new Goods("《红楼梦》", 100);
all[1] = new Goods("《西游记》", 80);
all[2] = new Goods("《三国演义》", 140);
all[3] = new Goods("《水浒传》", 120);
Arrays.sort(all);
System.out.println(Arrays.toString(all));
}
}
12345678910111213141516171819202122232425262728293031

2、方式二:定制排序:java.util.Comparator

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Goods[] all = new Goods[4];
all[0] = new Goods("War and Peace", 100);
all[1] = new Goods("Childhood", 80);
all[2] = new Goods("Scarlet and Black", 140);
all[3] = new Goods("Notre Dame de Paris", 120);
Arrays.sort(all, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Goods g1 = (Goods) o1;
Goods g2 = (Goods) o2;
return g1.getName().compareTo(g2.getName());
}
});
System.out.println(Arrays.toString(all));
1234567891011121314

三、System类

在这里插入图片描述
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
1234567891011121314

四、 Math类

在这里插入图片描述

五、 BigInteger与BigDecimal

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
@Test
public void test2() {
BigInteger bi = new BigInteger("1243324112234324324325235245346567657653");
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, 25, BigDecimal.ROUND_HALF_UP));
}

java中枚举与注解

一、枚举类的使用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1、 自定义枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
class Season{
private final String SEASONNAME;//季节的名称
private final String SEASONDESC;//季节的描述
private Season(String seasonName,String seasonDesc){
this.SEASONNAME = seasonName;
this.SEASONDESC = seasonDesc;
}
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("冬天", "白雪皑皑");
}
123456789101112

2、使用enum定义枚举类

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public enum SeasonEnum {
SPRING("春天","春风又绿江南岸"),
SUMMER("夏天","映日荷花别样红"),
AUTUMN("秋天","秋水共长天一色"),
WINTER("冬天","窗含西岭千秋雪");
private final String seasonName;
private final String seasonDesc;
private SeasonEnum(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
123456789101112131415161718

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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
public class SeasonTest1 {
public static void main(String[] args) {
Season1 summer = Season1.SUMMER;
//toString():返回枚举类对象的名称
System.out.println(summer.toString());

// System.out.println(Season1.class.getSuperclass());
System.out.println("****************");
//values():返回所有的枚举类对象构成的数组
Season1[] values = Season1.values();
for(int i = 0;i < values.length;i++){
System.out.println(values[i]);
values[i].show();
}
System.out.println("****************");
Thread.State[] values1 = Thread.State.values();
for (int i = 0; i < values1.length; i++) {
System.out.println(values1[i]);
}

//valueOf(String objName):返回枚举类中对象名是objName的对象。
Season1 winter = Season1.valueOf("WINTER");
//如果没有objName的枚举类对象,则抛异常:IllegalArgumentException
// Season1 winter = Season1.valueOf("WINTER1");
System.out.println(winter);
winter.show();
}
}

interface Info{
void show();
}

//使用enum关键字枚举类
enum Season1 implements Info{
//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
SPRING("春天","春暖花开"){
@Override
public void show() {
System.out.println("春天在哪里?");
}
},
SUMMER("夏天","夏日炎炎"){
@Override
public void show() {
System.out.println("宁夏");
}
},
AUTUMN("秋天","秋高气爽"){
@Override
public void show() {
System.out.println("秋天不回来");
}
},
WINTER("冬天","冰天雪地"){
@Override
public void show() {
System.out.println("大约在冬季");
}
};

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

//2.私有化类的构造器,并给对象属性赋值

private Season1(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}

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

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


// @Override
// public void show() {
// System.out.println("这是一个季节");
// }
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596

二、注解(Annotation)

1、概述

在这里插入图片描述
在这里插入图片描述

2、常见的Annotation示例

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.annotation.javadoc;
/**
* @author lyf
* @version 1.0
* @see Math.java
*/
public class JavadocTest {
/**
* 程序的主方法,程序的入口
* @param args String[] 命令行参数
*/
public static void main(String[] args) {
}
/**
* 求圆面积的方法
* @param radius double 半径值
* @return double 圆的面积
*/
public static double getArea(double radius){
return Math.PI * radius * radius;
}
}
12345678910111213141516171819202122

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.annotation.javadoc;
public class AnnotationTest{
public static void main(String[] args) {
@SuppressWarnings("unused")
int a = 10;
}
@Deprecated
public void print(){
System.out.println("过时的方法");
}
@Override
public String toString() {
return "重写的toString方法()";
}
}
123456789101112131415

在这里插入图片描述

1
2
3
4
5
6
7
8
9
<servlet> 
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
12345678

在这里插入图片描述

3、 自定义 Annotation

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@MyAnnotation(value = "java注解")
public class MyAnnotationTest {
public static void main(String[] args) {
Class clazz = MyAnnotationTest.class;
Annotation a = clazz.getAnnotation(MyAnnotation.class);
MyAnnotation m = (MyAnnotation) a;
String info = m.value();
System.out.println(info);
}
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation {
String value() default "java Annotation";
}
12345678910111213141516

4、JDK中的元注解

在这里插入图片描述

①@Retention

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
12345678910

在这里插入图片描述
举例:

1
2
3
4
@Retention(RetentionPolicy.SOURCE)
@interface MyAnnotation1{ }
@Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation2{ }
123

②@Target

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

③@Documented和@Inherited

在这里插入图片描述

5、利用反射获取注解信息

在这里插入图片描述
在这里插入图片描述

6、JDK8中注解的新特性

在这里插入图片描述
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@MyAnnotation
public class AnnotationTest<U> {
@MyAnnotation
private String name;
public static void main(String[] args) {
AnnotationTest<@MyAnnotation String> t = null;
int a = (@MyAnnotation int) 2L;
@MyAnnotation
int b = 10;
}
public static <@MyAnnotation T> void method(T t) { }
public static void test(@MyAnnotation String arg) throws @MyAnnotation Exception { }
}
@Target(ElementType.TYPE_USE)
@interface MyAnnotation { }

集合

一、 Java集合框架概述

在这里插入图片描述
在这里插入图片描述

1、Collection接口继承树

在这里插入图片描述

2、Map接口继承树

在这里插入图片描述

二、Collection接口方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、Iterator迭代器接口

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、Collection子接口之一:List接口

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五、Collection子接口之二:Set接口

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public int hashCode() {
int result;
long temp;
result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + age;
temp = Double.doubleToLongBits(salary);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
1234567891011

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

结果: [Person{id=1002, name=‘BB’}, Person{id=1001, name=‘CC’}]
[Person{id=1002, name=‘BB’}, Person{id=1001, name=‘CC’}, Person{id=1001, name=‘CC’}]
[Person{id=1002, name=‘BB’}, Person{id=1001, name=‘CC’}, Person{id=1001, name=‘CC’}, Person{id=1001, name=‘AA’}]

在这里插入图片描述

六、Map接口

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
常用方法:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
HashMap的底层实现原理

在这里插入图片描述
在这里插入图片描述

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
public class TreeMapTest {

//向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
//因为要按照key进行排序:自然排序 、定制排序
//自然排序:user实现了comparable接口
@Test
public void test1(){
TreeMap map = new TreeMap();
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);

map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);

Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());

}
}

//定制排序
@Test
public void test2(){
TreeMap map = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge());
}
throw new RuntimeException("输入的类型不匹配!");
}
});
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);

map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);

Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());

}
}


}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364

在这里插入图片描述
在这里插入图片描述

七、Collections工具类

在这里插入图片描述
在这里插入图片描述

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
 @Test
public void test2(){
List list = new ArrayList();
list.add(123);
list.add(43);
list.add(765);
list.add(-97);
list.add(0);

//报异常:IndexOutOfBoundsException("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.size();
Collections.copy(dest,list);

System.out.println(dest);


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

*/
//返回的list1即为线程安全的List
List list1 = Collections.synchronizedList(list);


}
12345678910111213141516171819202122232425262728293031

在这里插入图片描述
在这里插入图片描述

hashmap底层实现原理

HashMap的底层实现原理

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
JDK 1.8之前

在这里插入图片描述
在这里插入图片描述
JDK 1.8
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

泛型

一、泛型的理解

在这里插入图片描述

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 test1(){
ArrayList list = new ArrayList();
//需求:存放学生的成绩
list.add(78);
list.add(76);
list.add(89);
list.add(88);
//问题一:类型不安全
// list.add("Tom");

for(Object score : list){
//问题二:强转时,可能出现ClassCastException
int stuScore = (Integer) score;

System.out.println(stuScore);

}

}

123456789101112131415161718192021

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、在集合中使用泛型

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
ArrayList<Integer> list = new ArrayList<>();//类型推断
list.add(78);
list.add(88);
list.add(77);
list.add(66);
//遍历方式一:
//for(Integer i : list){
//不需要强转
//System.out.println(i);
//}
//遍历方式二:
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
123456789101112131415
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("Tom1",34);
map.put("Tom2",44);
map.put("Tom3",33);
map.put("Tom4",32);
//添加失败
//map.put(33, "Tom");
Set<Entry<String,Integer>> entrySet = map.entrySet();
Iterator<Entry<String,Integer>> iterator = entrySet.iterator();
while(iterator.hasNext()){
Entry<String,Integer> entry = iterator.next();
System.out.println(entry.getKey() + "--->" + entry.getValue());
}
12345678910111213

三、自定义泛型结构

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class GenericTest {
public static void main(String[] args) {
// 1、使用时:类似于Object,不等同于Object
ArrayList list = new ArrayList();
// list.add(new Date());//有风险
list.add("hello");
test(list);// 泛型擦除,编译不会类型检查
// ArrayList<Object> list2 = new ArrayList<Object>();
// test(list2);//一旦指定Object,编译会类型检查,必须按照Object处理
}
public static void test(ArrayList<String> list) {
String str = "";
for (String s : list) {
str += s + ",";
}
System.out.println("元素:" + str);
}
}
123456789101112131415161718

在这里插入图片描述

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
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{
}
// 2)具体类型
class Son2 extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {
}
12345678910111213141516
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son<A, B> extends Father{//等价于class Son extends Father<Object,Object>{
}
// 2)具体类型
class Son2<A, B> extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2, A, B> extends Father<Integer, T2> {
}
12345678910111213141516
class Person<T> {
// 使用T类型定义变量
private T info;
// 使用T类型定义一般方法
public T getInfo() {
return info;
}
public void setInfo(T info) {
this.info = info;
}
// 使用T类型定义构造器
public Person() {
}
public Person(T info) {
this.info = info;
}
// static的方法中不能声明泛型
//public static void show(T t) {
//
//}
// 不能在try-catch中使用泛型定义
//public void test() {
//try {
//
//} catch (MyException<T> ex) {
//
//}
//}
}
1234567891011121314151617181920212223242526272829

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、 泛型在继承上的体现

在这里插入图片描述
在这里插入图片描述

五、通配符的使用

在这里插入图片描述
在这里插入图片描述

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
  @Test
public void test3(){
List<Object> list1 = null;
List<String> list2 = null;

List<?> list = null;

list = list1;
list = list2;
//编译通过
// print(list1);
// print(list2);


//
List<String> list3 = new ArrayList<>();
list3.add("AA");
list3.add("BB");
list3.add("CC");
list = list3;
//添加(写入):对于List<?>就不能向其内部添加数据。
//除了添加null之外。
// list.add("DD");
// list.add('?');

list.add(null);

//获取(读取):允许读取数据,读取的数据类型为Object。
Object o = list.get(0);
System.out.println(o);


}

public void print(List<?> list){
Iterator<?> iterator = list.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
}

123456789101112131415161718192021222324252627282930313233343536373839404142

在这里插入图片描述
在这里插入图片描述

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
/*
3.有限制条件的通配符的使用。
? 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类的使用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1
2
3
4
File file1 = new File("d:\\java\\info.txt");
File file2 = new File("d:" + File.separator + "java" + File.separator + "info.txt");
File file3 = new File("d:/java");
123

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
File dir1 = new File("D:/IOTest/dir1");
if (!dir1.exists()) { // 如果D:/IOTest/dir1不存在,就创建为目录
dir1.mkdir();
}
// 创建以dir1为父目录,名为"dir2"的File对象
File dir2 = new File(dir1, "dir2");
if (!dir2.exists()) { // 如果还不存在,就创建为目录
dir2.mkdirs();
}
File dir4 = new File(dir1, "dir3/dir4");
if (!dir4.exists()) {
dir4.mkdirs();
}
// 创建以dir2为父目录,名为"test.txt"的File对象
File file = new File(dir2, "test.txt");
if (!file.exists()) { // 如果还不存在,就创建为文件
file.createNewFile();
}
123456789101112131415161718

二、IO流原理及流的分类

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
public class FileInputOutputStreamTest {

//使用字节流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();
}

}
}

}

/*
实现对图片的复制操作
*/
@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();
}

}
}

}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
public class FileReaderWriterTest {

public static void main(String[] args) {
File file = new File("hello.txt");//相较于当前工程
System.out.println(file.getAbsolutePath());

File file1 = new File("java\\hello.txt");
System.out.println(file1.getAbsolutePath());
}

/*


说明点:
1. read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
2. 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
3. 读入的文件一定要存在,否则就会报FileNotFoundException。

*/
@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的重载方法
@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]);
// }
//正确的写法
// 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();
}

}
}

}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113

三、节点流(或文件流)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、缓冲流

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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
 /*
实现非文本文件的复制
*/
@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();
}



}

//实现文件复制的方法
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();
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108

五、转换流

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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 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();


}
12345678910111213141516171819202122232425

在这里插入图片描述
在这里插入图片描述
详情见:计算机字符编码

六、标准输入、输出流

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public 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();
}

}
}
}
12345678910111213141516171819202122232425262728293031

七、打印流

在这里插入图片描述

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
 @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();
}
}

}
1234567891011121314151617181920212223242526272829

八、数据流

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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();


}
12345678910111213141516

九、对象流

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

十、随机存取文件流

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

十一、NIO.2中Path、Paths、Files类的使用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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
public class PathTest {

//如何使用Paths实例化Path
@Test
public void test1() {
Path path1 = Paths.get("d:\\nio\\hello.txt");//new File(String filepath)

Path path2 = Paths.get("d:\\", "nio\\hello.txt");//new File(String parent,String filename);

System.out.println(path1);
System.out.println(path2);

Path path3 = Paths.get("d:\\", "nio");
System.out.println(path3);
}

//Path中的常用方法
@Test
public void test2() {
Path path1 = Paths.get("d:\\", "nio\\nio1\\nio2\\hello.txt");
Path path2 = Paths.get("hello.txt");

// String toString() : 返回调用 Path 对象的字符串表示形式
System.out.println(path1);

// boolean startsWith(String path) : 判断是否以 path 路径开始
System.out.println(path1.startsWith("d:\\nio"));
// boolean endsWith(String path) : 判断是否以 path 路径结束
System.out.println(path1.endsWith("hello.txt"));
// boolean isAbsolute() : 判断是否是绝对路径
System.out.println(path1.isAbsolute() + "~");
System.out.println(path2.isAbsolute() + "~");
// Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
System.out.println(path1.getParent());
System.out.println(path2.getParent());
// Path getRoot() :返回调用 Path 对象的根路径
System.out.println(path1.getRoot());
System.out.println(path2.getRoot());
// Path getFileName() : 返回与调用 Path 对象关联的文件名
System.out.println(path1.getFileName() + "~");
System.out.println(path2.getFileName() + "~");
// int getNameCount() : 返回Path 根目录后面元素的数量
// Path getName(int idx) : 返回指定索引位置 idx 的路径名称
for (int i = 0; i < path1.getNameCount(); i++) {
System.out.println(path1.getName(i) + "*****");
}

// Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
System.out.println(path1.toAbsolutePath());
System.out.println(path2.toAbsolutePath());
// Path resolve(Path p) :合并两个路径,返回合并后的路径对应的Path对象
Path path3 = Paths.get("d:\\", "nio");
Path path4 = Paths.get("nioo\\hi.txt");
path3 = path3.resolve(path4);
System.out.println(path3);

// File toFile(): 将Path转化为File类的对象
File file = path1.toFile();//Path--->File的转换

Path newPath = file.toPath();//File--->Path的转换

}


}

网络编程

一、网络编程概述

在这里插入图片描述
在这里插入图片描述

二、网络通信要素概述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、通信要素1:IP和端口号

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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) {

try {
//File file = new File("hello.txt");
InetAddress inet1 = InetAddress.getByName("192.168.10.14");

System.out.println(inet1);

InetAddress inet2 = InetAddress.getByName("www.baidu.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();
}
}
123456789101112131415161718192021222324252627

四、通信要素2:网络协议

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五、TCP网络编程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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
    /*
这里涉及到的异常,应该使用try-catch-finally处理
*/
@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();

}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273

六、 UDP网络编程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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 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();
}
123456789101112131415161718192021222324252627282930313233

七、URL编程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1
2
3
4
5
6
7
8
URL url = new URL("http://localhost:8080/examples/myTest.txt");
System.out.println("getProtocol() :"+url.getProtocol());
System.out.println("getHost() :"+url.getHost());
System.out.println("getPort() :"+url.getPort());
System.out.println("getPath() :"+url.getPath());
System.out.println("getFile() :"+url.getFile());
System.out.println("getQuery() :"+url.getQuery());
1234567

在这里插入图片描述
在这里插入图片描述

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 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("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();
}
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445

在这里插入图片描述
在这里插入图片描述

反射

一、Java反射机制概述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、理解Class类并获取Class的实例

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//方式一:调用运行时类的属性:.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.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);
12345678910111213141516171819202122

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
123456789101112131415

三、类的加载与ClassLoader的理解

在这里插入图片描述
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(A.m);
}
}
class A {
static {
m = 300;
}
static int m = 100;
}
//第二步:链接结束后m=0
//第三步:初始化后,m的值由<clinit>()方法执行决定
// 这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并产生,类似于
// <clinit>(){
// m = 300;
// m = 100;
// }
123456789101112131415161718

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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
@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);

}
1234567891011121314151617
//1.获取一个系统类加载器
ClassLoader classloader = ClassLoader.getSystemClassLoader();
System.out.println(classloader);
//2.获取系统类加载器的父类加载器,即扩展类加载器
classloader = classloader.getParent();
System.out.println(classloader);
//3.获取扩展类加载器的父类加载器,即引导类加载器
classloader = classloader.getParent();
System.out.println(classloader);
//4.测试当前类由哪个类加载器进行加载
classloader = Class.forName("exer2.ClassloaderDemo").getClassLoader();
System.out.println(classloader);
//5.测试JDK提供的Object类由哪个类加载器加载
classloader =
Class.forName("java.lang.Object").getClassLoader();
System.out.println(classloader);
//*6.关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流
InputStream in = null;
in = this.getClass().getClassLoader().getResourceAsStream("exer2\\test.properties");
System.out.println(in);
1234567891011121314151617181920
@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);



}
123456789101112131415161718192021222324

四、创建运行时类的对象

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
//1.根据全类名获取对应的Class对象
String name = “atguigu.java.Person";
Class clazz = null;
clazz = Class.forName(name);
//2.调用指定参数结构的构造器,生成Constructor的实例
Constructor con = clazz.getConstructor(String.class,Integer.class);
//3.通过Constructor的实例创建对应类的对象,并初始化类属性
Person p2 = (Person) con.newInstance("Peter",20);
System.out.println(p2);
123456789

五、获取运行时类的完整结构

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
获取运行时类的完整结构示例
在这里插入图片描述

六、调用运行时类的指定结构

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
调用运行时类指定结构示例

七、反射的应用:动态代理

动态代理详解

java8新特性

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、JDK8中新日期时间API

JDK8中新日期时间API

二、Lambda表达式

java中的Lambda表达式

三、强大的Stream API

java8中的StreamAPI

四、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
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
public class Girl {

private String name;

@Override
public String toString() {
return "Girl{" +
"name='" + name + '\'' +
'}';
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Girl() {

}

public Girl(String name) {

this.name = name;
}
}

1234567891011121314151617181920212223242526272829
public class Boy {
private Girl girl;

@Override
public String toString() {
return "Boy{" +
"girl=" + girl +
'}';
}

public Girl getGirl() {
return girl;
}

public void setGirl(Girl girl) {
this.girl = girl;
}

public Boy() {

}

public Boy(Girl girl) {

this.girl = girl;
}
}

12345678910111213141516171819202122232425262728
/**
* Optional类:为了在程序中避免出现空指针异常而创建的。
*
* 常用的方法:ofNullable(T t)
* orElse(T t)
*
*/
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);

}
}

java中jdbc

第1章:JDBC概述

1.1 数据的持久化

  • 持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现过程大多通过各种关系数据库来完成
  • 持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。
  • 在这里插入图片描述

1.2 Java中的数据存储技术

  • 在Java中,数据库存取技术可分为如下几类:
    • JDBC直接访问数据库
    • JDO (Java Data Object )技术
    • 第三方O/R工具,如Hibernate, Mybatis 等
  • JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封装了JDBC。

1.3 JDBC介绍

  • JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用这些类库可以以一种标准的方法、方便地访问数据库资源。
  • JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
  • JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。
  • 如果没有JDBC,那么Java程序访问数据库时是这样的:
  • 在这里插入图片描述
  • 在这里插入图片描述
    在这里插入图片描述

1.4 JDBC体系结构

  • JDBC接口(API)包括两个层次:
    • 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。
    • 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。

JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。

不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。 ————面向接口编程

1.5 JDBC程序编写步骤

在这里插入图片描述

补充:ODBC(Open Database Connectivity,开放式数据库连接),是微软在Windows平台下推出的。使用者在程序中只需要调用ODBC API,由 ODBC 驱动程序将调用转换成为对特定的数据库的调用请求。

第2章:获取数据库连接

2.1 要素一:Driver接口实现类

2.1.1 Driver接口介绍

  • java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。
  • 在程序中不需要直接去访问实现了 Driver 接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现。
    • Oracle的驱动:oracle.jdbc.driver.OracleDriver
    • mySql的驱动: com.mysql.jdbc.Driver
      在这里插入图片描述
      在这里插入图片描述
      将上述jar包拷贝到Java工程的一个目录中,习惯上新建一个lib文件夹。
      在这里插入图片描述
      注意:如果是Dynamic Web Project(动态的web项目)话,则是把驱动jar放到WebContent(有的开发工具叫WebRoot)目录中的WEB-INF目录中的lib目录下即可
      在这里插入图片描述

2.1.2 加载与注册JDBC驱动

  • 加载驱动:加载 JDBC 驱动需调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名
    • Class.forName(“com.mysql.jdbc.Driver”);
  • 注册驱动:DriverManager 类是驱动程序管理器类,负责管理驱动程序
    • 使用DriverManager.registerDriver(com.mysql.jdbc.Driver)来注册驱动
    • 通常不用显式调用 DriverManager 类的 registerDriver() 方法来注册驱动程序类的实例,因为 Driver 接口的驱动程序类包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例。下图是MySQL的Driver实现类的源码:
    • 在这里插入图片描述

2.2 要素二:URL

  • JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。
  • JDBC URL的标准由三部分组成,各部分间用冒号分隔。
    • jdbc:子协议:子名称
    • 协议:JDBC URL中的协议总是jdbc
    • 子协议:子协议用于标识一个数据库驱动程序
    • 子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名
  • 举例:

在这里插入图片描述

  • 几种常用数据库的 JDBC URL
    • MySQL的连接URL编写方式:
      • jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值
      • jdbc:mysql://localhost:3306/test
      • jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8(如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集)
      • jdbc:mysql://localhost:3306/test?user=root&password=123456
    • Oracle 9i的连接URL编写方式:
      • jdbc:oracle:thin:@主机名称:oracle服务端口号:数据库名称
      • jdbc:oracle:thin:@localhost:1521:test
    • SQLServer的连接URL编写方式:
      • jdbc:sqlserver://主机名称:sqlserver服务端口号:DatabaseName=数据库名称
      • jdbc:sqlserver://localhost:1433:DatabaseName=test

2.3 要素三:用户名和密码

  • user,password可以用“属性名=属性值”方式告诉数据库
  • 可以调用 DriverManager 类的 getConnection() 方法建立到数据库的连接

2.4 数据库连接方式举例

2.4.1 连接方式一

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 testConnection1() {
try {
//1.提供java.sql.Driver接口实现类的对象
Driver driver = null;
driver = new com.mysql.jdbc.Driver();

//2.提供url,指明具体操作的数据
String url = "jdbc:mysql://localhost:3306/test";

//3.提供Properties的对象,指明用户名和密码
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "abc123");

//4.调用driver的connect(),获取连接
Connection conn = driver.connect(url, info);
System.out.println(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
12345678910111213141516171819202122

说明:上述代码中显式出现了第三方数据库的API

2.4.2 连接方式二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
	@Test
public void testConnection2() {
try {
//1.实例化Driver
String className = "com.mysql.jdbc.Driver";
Class clazz = Class.forName(className);
Driver driver = (Driver) clazz.newInstance();

//2.提供url,指明具体操作的数据
String url = "jdbc:mysql://localhost:3306/test";

//3.提供Properties的对象,指明用户名和密码
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "abc123");

//4.调用driver的connect(),获取连接
Connection conn = driver.connect(url, info);
System.out.println(conn);

} catch (Exception e) {
e.printStackTrace();
}
}
123456789101112131415161718192021222324

说明:相较于方式一,这里使用反射实例化Driver,不在代码中体现第三方数据库的API。体现了面向接口编程思想。

2.4.3 连接方式三

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 testConnection3() {
try {
//1.数据库连接的4个基本要素:
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "abc123";
String driverName = "com.mysql.jdbc.Driver";

//2.实例化Driver
Class clazz = Class.forName(driverName);
Driver driver = (Driver) clazz.newInstance();
//3.注册驱动
DriverManager.registerDriver(driver);
//4.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
} catch (Exception e) {
e.printStackTrace();
}

}
12345678910111213141516171819202122

说明:使用DriverManager实现数据库的连接。体会获取连接必要的4个基本要素。

2.4.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
	@Test
public void testConnection4() {
try {
//1.数据库连接的4个基本要素:
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "abc123";
String driverName = "com.mysql.jdbc.Driver";

//2.加载驱动 (①实例化Driver ②注册驱动)
Class.forName(driverName);


//Driver driver = (Driver) clazz.newInstance();
//3.注册驱动
//DriverManager.registerDriver(driver);
/*
可以注释掉上述代码的原因,是因为在mysql的Driver类中声明有:
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}

*/


//3.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
} catch (Exception e) {
e.printStackTrace();
}

}
12345678910111213141516171819202122232425262728293031323334353637

说明:不必显式的注册驱动了。因为在DriverManager的源码中已经存在静态代码块,实现了驱动的注册。

2.4.5 连接方式五(最终版)

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 testConnection5() throws Exception {
//1.加载配置文件
InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);

//2.读取配置信息
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");

//3.加载驱动
Class.forName(driverClass);

//4.获取连接
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);

}
123456789101112131415161718192021

其中,配置文件声明在工程的src目录下:【jdbc.properties】

1
2
3
4
5
user=root
password=abc123
url=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.jdbc.Driver
1234

说明:使用配置文件的方式保存配置信息,在代码中加载配置文件

使用配置文件的好处:

①实现了代码和数据的分离,如果需要修改配置信息,直接在配置文件中修改,不需要深入代码
②如果修改了配置信息,省去重新编译的过程。

第3章:使用PreparedStatement实现CRUD操作

3.1 操作和访问数据库

  • 数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。
  • 在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
    • Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
    • PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
    • CallableStatement:用于执行 SQL 存储过程

在这里插入图片描述

3.2 使用Statement操作数据表的弊端

  • 通过调用 Connection 对象的 createStatement() 方法创建该对象。该对象用于执行静态的 SQL 语句,并且返回执行结果。

  • Statement 接口中定义了下列方法用于执行 SQL 语句:

    1
    2
    3
    int excuteUpdate(String sql):执行更新操作INSERTUPDATEDELETE
    ResultSet executeQuery(String sql):执行查询操作SELECT
    12
  • 但是使用Statement操作数据表存在弊端:

    • 问题一:存在拼串操作,繁琐
    • 问题二:存在SQL注入问题
  • SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password FROM user_table WHERE user=‘a’ OR 1 = ’ AND password = ’ OR ‘1’ = ‘1’) ,从而利用系统的 SQL 引擎完成恶意行为的做法。

  • 对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了。

  • 代码演示:

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
public class StatementTest {

// 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题
@Test
public void testLogin() {
Scanner scan = new Scanner(System.in);

System.out.print("用户名:");
String userName = scan.nextLine();
System.out.print("密 码:");
String password = scan.nextLine();

// SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '='1' or '1' = '1';
String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password
+ "'";
User user = get(sql, User.class);
if (user != null) {
System.out.println("登陆成功!");
} else {
System.out.println("用户名或密码错误!");
}
}

// 使用Statement实现对数据表的查询操作
public <T> T get(String sql, Class<T> clazz) {
T t = null;

Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 1.加载配置文件
InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);

// 2.读取配置信息
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");

// 3.加载驱动
Class.forName(driverClass);

// 4.获取连接
conn = DriverManager.getConnection(url, user, password);

st = conn.createStatement();

rs = st.executeQuery(sql);

// 获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();

// 获取结果集的列数
int columnCount = rsmd.getColumnCount();

if (rs.next()) {

t = clazz.newInstance();

for (int i = 0; i < columnCount; i++) {
// //1. 获取列的名称
// String columnName = rsmd.getColumnName(i+1);

// 1. 获取列的别名
String columnName = rsmd.getColumnLabel(i + 1);

// 2. 根据列名获取对应数据表中的数据
Object columnVal = rs.getObject(columnName);

// 3. 将数据表中得到的数据,封装进对象
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnVal);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

return null;
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110

综上:

在这里插入图片描述

3.3 PreparedStatement的使用

3.3.1 PreparedStatement介绍
  • 可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取 PreparedStatement 对象
  • PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句
  • PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值
3.3.2 PreparedStatement vs Statement
  • 代码的可读性和可维护性。
  • PreparedStatement 能最大可能提高性能:
    • DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
    • 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
    • (语法检查,语义检查,翻译成二进制命令,缓存)
  • PreparedStatement 可以防止 SQL 注入
3.3.3 Java与SQL对应数据类型转换表
Java类型 SQL类型
boolean BIT
byte TINYINT
short SMALLINT
int INTEGER
long BIGINT
String CHAR,VARCHAR,LONGVARCHAR
byte array BINARY , VAR BINARY
java.sql.Date DATE
java.sql.Time TIME
java.sql.Timestamp TIMESTAMP
3.3.4 使用PreparedStatement实现增、删、改操作
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
	//通用的增、删、改操作(体现一:增、删、改 ; 体现二:针对于不同的表)
public void update(String sql,Object ... args){
Connection conn = null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
conn = JDBCUtils.getConnection();

//2.获取PreparedStatement的实例 (或:预编译sql语句)
ps = conn.prepareStatement(sql);
//3.填充占位符
for(int i = 0;i < args.length;i++){
ps.setObject(i + 1, args[i]);
}

//4.执行sql语句
ps.execute();
} catch (Exception e) {

e.printStackTrace();
}finally{
//5.关闭资源
JDBCUtils.closeResource(conn, ps);

}
}
1234567891011121314151617181920212223242526
3.3.5 使用PreparedStatement实现查询操作
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
	// 通用的针对于不同表的查询:返回一个对象 (version 1.0)
public <T> T getInstance(Class<T> clazz, String sql, Object... args) {

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1.获取数据库连接
conn = JDBCUtils.getConnection();

// 2.预编译sql语句,得到PreparedStatement对象
ps = conn.prepareStatement(sql);

// 3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}

// 4.执行executeQuery(),得到结果集:ResultSet
rs = ps.executeQuery();

// 5.得到结果集的元数据:ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();

// 6.1通过ResultSetMetaData得到columnCount,columnLabel;通过ResultSet得到列值
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {// 遍历每一个列

// 获取列值
Object columnVal = rs.getObject(i + 1);
// 获取列的别名:列的别名,使用类的属性名充当
String columnLabel = rsmd.getColumnLabel(i + 1);
// 6.2使用反射,给对象的相应属性赋值
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnVal);

}

return t;

}
} catch (Exception e) {

e.printStackTrace();
} finally {
// 7.关闭资源
JDBCUtils.closeResource(conn, ps, rs);
}

return null;

}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455

说明:使用PreparedStatement实现的查询操作可以替换Statement实现的查询操作,解决Statement拼串和SQL注入问题。

3.4 ResultSet与ResultSetMetaData

3.4.1 ResultSet
  • 查询需要调用PreparedStatement 的 executeQuery() 方法,查询结果是一个ResultSet 对象

  • ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商提供实现

  • ResultSet 返回的实际上就是一张数据表。有一个指针指向数据表的第一条记录的前面。

  • ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象的 next() 方法移动到下一行。调用 next()方法检测下一行是否有效。若有效,该方法返回 true,且指针下移。相当于Iterator对象的 hasNext() 和 next() 方法的结合体。

  • 当指针指向一行时, 可以通过调用 getXxx(int index) 或 getXxx(int columnName) 获取每一列的值。

    • 例如: getInt(1), getString(“name”)
    • 注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始。
  • ResultSet 接口的常用方法:

    • boolean next()
    • getString()

    在这里插入图片描述

3.4.2 ResultSetMetaData
  • 可用于获取关于 ResultSet 对象中列的类型和属性信息的对象
  • ResultSetMetaData meta = rs.getMetaData();
    • getColumnName(int column):获取指定列的名称
    • getColumnLabel(int column):获取指定列的别名
    • getColumnCount():返回当前 ResultSet 对象中的列数。
    • getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
    • getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
    • isNullable(int column):指示指定列中的值是否可以为 null。
    • isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。

在这里插入图片描述

问题1:得到结果集后, 如何知道该结果集中有哪些列 ? 列名是什么?

需要使用一个描述 ResultSet 的对象, 即 ResultSetMetaData

问题2:关于ResultSetMetaData

  1. 如何获取 ResultSetMetaData: 调用 ResultSet 的 getMetaData() 方法即可
  2. 获取 ResultSet 中有多少列:调用 ResultSetMetaData 的 getColumnCount() 方法
  3. 获取 ResultSet 每一列的列的别名是什么:调用 ResultSetMetaData 的getColumnLabel() 方法
    在这里插入图片描述

3.5 资源的释放

  • 释放ResultSet, Statement,Connection。
  • 数据库连接(Connection)是非常稀有的资源,用完后必须马上释放,如果Connection不能及时正确的关闭将导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
  • 可以在finally中关闭,保证及时其他代码出现异常,资源也一定能被关闭。

3.6 JDBC API小结

  • 两种思想

    • 面向接口编程的思想
    • ORM思想(object relational mapping)
      • 一个数据表对应一个java类
      • 表中的一条记录对应java类的一个对象
      • 表中的一个字段对应java类的一个属性

    sql是需要结合列名和表的属性名来写。注意起别名。

  • 两种技术

    • JDBC结果集的元数据:ResultSetMetaData
      • 获取列数:getColumnCount()
      • 获取列的别名:getColumnLabel()
    • 通过反射,创建指定类的对象,获取指定的属性并赋值

第4章 操作BLOB类型字段

4.1 MySQL BLOB类型

  • MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。
  • 插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。
  • MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)

在这里插入图片描述

  • 实际使用中根据需要存入的数据大小定义不同的BLOB类型。
  • 需要注意的是:如果存储的文件过大,数据库的性能会下降。
  • 如果在指定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的配置参数: max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重新启动mysql服务。

4.2 向数据表中插入大数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//获取连接
Connection conn = JDBCUtils.getConnection();

String sql = "insert into customers(name,email,birth,photo)values(?,?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);

// 填充占位符
ps.setString(1, "徐海强");
ps.setString(2, "xhq@126.com");
ps.setDate(3, new Date(new java.util.Date().getTime()));
// 操作Blob类型的变量
FileInputStream fis = new FileInputStream("xhq.png");
ps.setBlob(4, fis);
//执行
ps.execute();

fis.close();
JDBCUtils.closeResource(conn, ps);

12345678910111213141516171819

4.3 修改数据表中的Blob类型字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Connection conn = JDBCUtils.getConnection();
String sql = "update customers set photo = ? where id = ?";
PreparedStatement ps = conn.prepareStatement(sql);

// 填充占位符
// 操作Blob类型的变量
FileInputStream fis = new FileInputStream("coffee.png");
ps.setBlob(1, fis);
ps.setInt(2, 25);

ps.execute();

fis.close();
JDBCUtils.closeResource(conn, ps);
1234567891011121314

4.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
String sql = "SELECT id, name, email, birth, photo FROM customer WHERE id = ?";
conn = getConnection();
ps = conn.prepareStatement(sql);
ps.setInt(1, 8);
rs = ps.executeQuery();
if(rs.next()){
Integer id = rs.getInt(1);
String name = rs.getString(2);
String email = rs.getString(3);
Date birth = rs.getDate(4);
Customer cust = new Customer(id, name, email, birth);
System.out.println(cust);
//读取Blob类型的字段
Blob photo = rs.getBlob(5);
InputStream is = photo.getBinaryStream();
OutputStream os = new FileOutputStream("c.jpg");
byte [] buffer = new byte[1024];
int len = 0;
while((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}
JDBCUtils.closeResource(conn, ps, rs);

if(is != null){
is.close();
}

if(os != null){
os.close();
}

}

第5章 批量插入

5.1 批量执行SQL语句

当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率

JDBC的批量处理语句包括下面三个方法:

  • addBatch(String):添加需要批量处理的SQL语句或是参数;
  • executeBatch():执行批量处理语句;
  • clearBatch():清空缓存的数据

通常我们会遇到两种批量执行SQL语句的情况:

  • 多条SQL语句的批量处理;
  • 一个SQL语句的批量传参;

5.2 高效的批量插入

举例:向数据表中插入20000条数据

  • 数据库中提供一个goods表。创建如下:
1
2
3
4
5
CREATE TABLE goods(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);
1234
5.2.1 实现层次一:使用Statement
1
2
3
4
5
6
7
Connection conn = JDBCUtils.getConnection();
Statement st = conn.createStatement();
for(int i = 1;i <= 20000;i++){
String sql = "insert into goods(name) values('name_' + "+ i +")";
st.executeUpdate(sql);
}
123456
5.2.2 实现层次二:使用PreparedStatement
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
long start = System.currentTimeMillis();

Connection conn = JDBCUtils.getConnection();

String sql = "insert into goods(name)values(?)";
PreparedStatement ps = conn.prepareStatement(sql);
for(int i = 1;i <= 20000;i++){
ps.setString(1, "name_" + i);
ps.executeUpdate();
}

long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));//82340


JDBCUtils.closeResource(conn, ps);
12345678910111213141516
5.2.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
/*
* 修改1: 使用 addBatch() / executeBatch() / clearBatch()
* 修改2:mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。
* ?rewriteBatchedStatements=true 写在配置文件的url后面
* 修改3:使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar
*
*/
@Test
public void testInsert1() throws Exception{
long start = System.currentTimeMillis();

Connection conn = JDBCUtils.getConnection();

String sql = "insert into goods(name)values(?)";
PreparedStatement ps = conn.prepareStatement(sql);

for(int i = 1;i <= 1000000;i++){
ps.setString(1, "name_" + i);

//1.“攒”sql
ps.addBatch();
if(i % 500 == 0){
//2.执行
ps.executeBatch();
//3.清空
ps.clearBatch();
}
}

long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));//20000条:625 //1000000条:14733

JDBCUtils.closeResource(conn, ps);
}
12345678910111213141516171819202122232425262728293031323334
5.2.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
/*
* 层次四:在层次三的基础上操作
* 使用Connection 的 setAutoCommit(false) / commit()
*/
@Test
public void testInsert2() throws Exception{
long start = System.currentTimeMillis();

Connection conn = JDBCUtils.getConnection();

//1.设置为不自动提交数据
conn.setAutoCommit(false);

String sql = "insert into goods(name)values(?)";
PreparedStatement ps = conn.prepareStatement(sql);

for(int i = 1;i <= 1000000;i++){
ps.setString(1, "name_" + i);

//1.“攒”sql
ps.addBatch();

if(i % 500 == 0){
//2.执行
ps.executeBatch();
//3.清空
ps.clearBatch();
}
}

//2.提交数据
conn.commit();

long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));//1000000条:4978

JDBCUtils.closeResource(conn, ps);
}

第6章: 数据库事务

6.1 数据库事务介绍

  • 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
  • **事务处理(事务操作):**保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都**被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)**到最初状态。
  • 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。

6.2 JDBC事务处理

  • 数据一旦提交,就不可回滚。

  • 数据什么时候意味着提交?

    • 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
    • 关闭数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下。
  • JDBC程序中为了让多个 SQL 语句作为一个事务执行:

    • 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
    • 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
    • 在出现异常时,调用 rollback(); 方法回滚事务

    若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交状态。

【案例:用户AA向用户BB转账100】

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
public void testJDBCTransaction() {
Connection conn = null;
try {
// 1.获取数据库连接
conn = JDBCUtils.getConnection();
// 2.开启事务
conn.setAutoCommit(false);
// 3.进行数据库操作
String sql1 = "update user_table set balance = balance - 100 where user = ?";
update(conn, sql1, "AA");

// 模拟网络异常
//System.out.println(10 / 0);

String sql2 = "update user_table set balance = balance + 100 where user = ?";
update(conn, sql2, "BB");
// 4.若没有异常,则提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
// 5.若有异常,则回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
try {
//6.恢复每次DML操作的自动提交功能
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
//7.关闭连接
JDBCUtils.closeResource(conn, null, null);
}
}

1234567891011121314151617181920212223242526272829303132333435363738

其中,对数据库操作的方法为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//使用事务以后的通用的增删改操作(version 2.0)
public void update(Connection conn ,String sql, Object... args) {
PreparedStatement ps = null;
try {
// 1.获取PreparedStatement的实例 (或:预编译sql语句)
ps = conn.prepareStatement(sql);
// 2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 3.执行sql语句
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4.关闭资源
JDBCUtils.closeResource(null, ps);

}
}
1234567891011121314151617181920

6.3 事务的ACID属性

  1. 原子性(Atomicity)
    原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  2. 一致性(Consistency)
    事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
  3. 隔离性(Isolation)
    事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  4. 持久性(Durability)
    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
6.3.1 数据库的并发问题
  • 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
    • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
    • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
    • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
  • 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。
  • 一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。
6.3.2 四种隔离级别
  • 数据库提供的4种事务隔离级别:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6m0gPvK8-1592151802471)(尚硅谷_宋红康_JDBC.assets/1555586275271.png)]

  • Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED

  • Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ。

6.3.3 在MySql中设置隔离级别
  • 每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别。

  • 查看当前的隔离级别:

    1
    2
    SELECT @@tx_isolation;
    1
  • 设置当前 mySQL 连接的隔离级别:

    1
    2
    set  transaction isolation level read committed;
    1
  • 设置数据库系统的全局的隔离级别:

    1
    2
    set global transaction isolation level read committed;
    1
  • 补充操作:

    • 创建mysql数据库用户:

      1
      2
      create user tom identified by 'abc123';
      1
    • 授予权限

      1
      2
      3
      4
      5
      #授予通过网络方式登录的tom用户,对所有库所有表的全部权限,密码设为abc123.
      grant all privileges on *.* to tom@'%' identified by 'abc123';

      #给tom用户使用本地命令行方式,授予atguigudb这个库下的所有表的插删改查的权限。
      grant select,insert,delete,update on atguigudb.* to tom@localhost identified by 'abc123';

第7章:DAO及相关实现类

  • DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、Delete),而不包含任何业务相关的信息。有时也称作:BaseDAO
  • 作用:为了实现功能的模块化,更有利于代码的维护和升级。

在这里插入图片描述

  • 层次结构:

在这里插入图片描述

【BaseDAO.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
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
package com.atguigu.bookstore.dao;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;


/**
* 定义一个用来被继承的对数据库进行基本操作的Dao
*
*
* @param <T>
*/
public abstract class BaseDao<T> {
private QueryRunner queryRunner = new QueryRunner();
// 定义一个变量来接收泛型的类型
private Class<T> type;

// 获取T的Class对象,获取泛型的类型,泛型是在被子类继承时才确定
public BaseDao() {
// 获取子类的类型
Class clazz = this.getClass();
// 获取父类的类型
// getGenericSuperclass()用来获取当前类的父类的类型
// ParameterizedType表示的是带泛型的类型
ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
// 获取具体的泛型类型 getActualTypeArguments获取具体的泛型的类型
// 这个方法会返回一个Type的数组
Type[] types = parameterizedType.getActualTypeArguments();
// 获取具体的泛型的类型·
this.type = (Class<T>) types[0];
}

/**
* 通用的增删改操作
*
* @param sql
* @param params
* @return
*/
public int update(Connection conn,String sql, Object... params) {
int count = 0;
try {
count = queryRunner.update(conn, sql, params);
} catch (SQLException e) {
e.printStackTrace();
}
return count;
}

/**
* 获取一个对象
*
* @param sql
* @param params
* @return
*/
public T getBean(Connection conn,String sql, Object... params) {
T t = null;
try {
t = queryRunner.query(conn, sql, new BeanHandler<T>(type), params);
} catch (SQLException e) {
e.printStackTrace();
}
return t;
}

/**
* 获取所有对象
*
* @param sql
* @param params
* @return
*/
public List<T> getBeanList(Connection conn,String sql, Object... params) {
List<T> list = null;
try {
list = queryRunner.query(conn, sql, new BeanListHandler<T>(type), params);
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}

/**
* 获取一个但一值得方法,专门用来执行像 select count(*)...这样的sql语句
*
* @param sql
* @param params
* @return
*/
public Object getValue(Connection conn,String sql, Object... params) {
Object count = null;
try {
// 调用queryRunner的query方法获取一个单一的值
count = queryRunner.query(conn, sql, new ScalarHandler<>(), params);
} catch (SQLException e) {
e.printStackTrace();
}
return count;
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109

【BookDAO.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
package com.atguigu.bookstore.dao;

import java.sql.Connection;
import java.util.List;

import com.atguigu.bookstore.beans.Book;
import com.atguigu.bookstore.beans.Page;

public interface BookDao {

/**
* 从数据库中查询出所有的记录
*
* @return
*/
List<Book> getBooks(Connection conn);

/**
* 向数据库中插入一条记录
*
* @param book
*/
void saveBook(Connection conn,Book book);

/**
* 从数据库中根据图书的id删除一条记录
*
* @param bookId
*/
void deleteBookById(Connection conn,String bookId);

/**
* 根据图书的id从数据库中查询出一条记录
*
* @param bookId
* @return
*/
Book getBookById(Connection conn,String bookId);

/**
* 根据图书的id从数据库中更新一条记录
*
* @param book
*/
void updateBook(Connection conn,Book book);

/**
* 获取带分页的图书信息
*
* @param page:是只包含了用户输入的pageNo属性的page对象
* @return 返回的Page对象是包含了所有属性的Page对象
*/
Page<Book> getPageBooks(Connection conn,Page<Book> page);

/**
* 获取带分页和价格范围的图书信息
*
* @param page:是只包含了用户输入的pageNo属性的page对象
* @return 返回的Page对象是包含了所有属性的Page对象
*/
Page<Book> getPageBooksByPrice(Connection conn,Page<Book> page, double minPrice, double maxPrice);

}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263

【UserDAO.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
package com.atguigu.bookstore.dao;

import java.sql.Connection;

import com.atguigu.bookstore.beans.User;

public interface UserDao {

/**
* 根据User对象中的用户名和密码从数据库中获取一条记录
*
* @param user
* @return User 数据库中有记录 null 数据库中无此记录
*/
User getUser(Connection conn,User user);

/**
* 根据User对象中的用户名从数据库中获取一条记录
*
* @param user
* @return true 数据库中有记录 false 数据库中无此记录
*/
boolean checkUsername(Connection conn,User user);

/**
* 向数据库中插入User对象
*
* @param user
*/
void saveUser(Connection conn,User user);
}
12345678910111213141516171819202122232425262728293031

【BookDaoImpl.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
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
package com.atguigu.bookstore.dao.impl;

import java.sql.Connection;
import java.util.List;

import com.atguigu.bookstore.beans.Book;
import com.atguigu.bookstore.beans.Page;
import com.atguigu.bookstore.dao.BaseDao;
import com.atguigu.bookstore.dao.BookDao;

public class BookDaoImpl extends BaseDao<Book> implements BookDao {

@Override
public List<Book> getBooks(Connection conn) {
// 调用BaseDao中得到一个List的方法
List<Book> beanList = null;
// 写sql语句
String sql = "select id,title,author,price,sales,stock,img_path imgPath from books";
beanList = getBeanList(conn,sql);
return beanList;
}

@Override
public void saveBook(Connection conn,Book book) {
// 写sql语句
String sql = "insert into books(title,author,price,sales,stock,img_path) values(?,?,?,?,?,?)";
// 调用BaseDao中通用的增删改的方法
update(conn,sql, book.getTitle(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(),book.getImgPath());
}

@Override
public void deleteBookById(Connection conn,String bookId) {
// 写sql语句
String sql = "DELETE FROM books WHERE id = ?";
// 调用BaseDao中通用增删改的方法
update(conn,sql, bookId);

}

@Override
public Book getBookById(Connection conn,String bookId) {
// 调用BaseDao中获取一个对象的方法
Book book = null;
// 写sql语句
String sql = "select id,title,author,price,sales,stock,img_path imgPath from books where id = ?";
book = getBean(conn,sql, bookId);
return book;
}

@Override
public void updateBook(Connection conn,Book book) {
// 写sql语句
String sql = "update books set title = ? , author = ? , price = ? , sales = ? , stock = ? where id = ?";
// 调用BaseDao中通用的增删改的方法
update(conn,sql, book.getTitle(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getId());
}

@Override
public Page<Book> getPageBooks(Connection conn,Page<Book> page) {
// 获取数据库中图书的总记录数
String sql = "select count(*) from books";
// 调用BaseDao中获取一个单一值的方法
long totalRecord = (long) getValue(conn,sql);
// 将总记录数设置都page对象中
page.setTotalRecord((int) totalRecord);

// 获取当前页中的记录存放的List
String sql2 = "select id,title,author,price,sales,stock,img_path imgPath from books limit ?,?";
// 调用BaseDao中获取一个集合的方法
List<Book> beanList = getBeanList(conn,sql2, (page.getPageNo() - 1) * Page.PAGE_SIZE, Page.PAGE_SIZE);
// 将这个List设置到page对象中
page.setList(beanList);
return page;
}

@Override
public Page<Book> getPageBooksByPrice(Connection conn,Page<Book> page, double minPrice, double maxPrice) {
// 获取数据库中图书的总记录数
String sql = "select count(*) from books where price between ? and ?";
// 调用BaseDao中获取一个单一值的方法
long totalRecord = (long) getValue(conn,sql,minPrice,maxPrice);
// 将总记录数设置都page对象中
page.setTotalRecord((int) totalRecord);

// 获取当前页中的记录存放的List
String sql2 = "select id,title,author,price,sales,stock,img_path imgPath from books where price between ? and ? limit ?,?";
// 调用BaseDao中获取一个集合的方法
List<Book> beanList = getBeanList(conn,sql2, minPrice , maxPrice , (page.getPageNo() - 1) * Page.PAGE_SIZE, Page.PAGE_SIZE);
// 将这个List设置到page对象中
page.setList(beanList);

return page;
}

}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495

【UserDaoImpl.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
package com.atguigu.bookstore.dao.impl;

import java.sql.Connection;

import com.atguigu.bookstore.beans.User;
import com.atguigu.bookstore.dao.BaseDao;
import com.atguigu.bookstore.dao.UserDao;

public class UserDaoImpl extends BaseDao<User> implements UserDao {

@Override
public User getUser(Connection conn,User user) {
// 调用BaseDao中获取一个对象的方法
User bean = null;
// 写sql语句
String sql = "select id,username,password,email from users where username = ? and password = ?";
bean = getBean(conn,sql, user.getUsername(), user.getPassword());
return bean;
}

@Override
public boolean checkUsername(Connection conn,User user) {
// 调用BaseDao中获取一个对象的方法
User bean = null;
// 写sql语句
String sql = "select id,username,password,email from users where username = ?";
bean = getBean(conn,sql, user.getUsername());
return bean != null;
}

@Override
public void saveUser(Connection conn,User user) {
//写sql语句
String sql = "insert into users(username,password,email) values(?,?,?)";
//调用BaseDao中通用的增删改的方法
update(conn,sql, user.getUsername(),user.getPassword(),user.getEmail());
}

}
123456789101112131415161718192021222324252627282930313233343536373839

【Book.java】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.atguigu.bookstore.beans;
/**
* 图书类
*
*/
public class Book {

private Integer id;
private String title; // 书名
private String author; // 作者
private double price; // 价格
private Integer sales; // 销量
private Integer stock; // 库存
private String imgPath = "static/img/default.jpg"; // 封面图片的路径
//构造器,get(),set(),toString()方法略
}
12345678910111213141516

【Page.java】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.atguigu.bookstore.beans;

import java.util.List;
/**
* 页码类
*
*/
public class Page<T> {

private List<T> list; // 每页查到的记录存放的集合
public static final int PAGE_SIZE = 4; // 每页显示的记录数
private int pageNo; // 当前页
// private int totalPageNo; // 总页数,通过计算得到
private int totalRecord; // 总记录数,通过查询数据库得到

123456789101112131415

【User.java】

1
2
3
4
5
6
7
8
9
10
11
package com.atguigu.bookstore.beans;
/**
* 用户类
*
*/
public class User {

private Integer id;
private String username;
private String password;
private String email;

第8章:数据库连接池

8.1 JDBC数据库连接池的必要性

  • 在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
    • 在主程序(如servlet、beans)中建立数据库连接
    • 进行sql操作
    • 断开数据库连接
  • 这种模式开发,存在的问题:
    • 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
    • 对于每一次数据库连接,使用完后都得断开否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(回忆:何为Java的内存泄漏?)
    • 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。

8.2 数据库连接池技术

  • 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
  • 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
  • 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
  • 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

在这里插入图片描述

  • 工作原理:

在这里插入图片描述

  • 数据库连接池技术的优点

    1. 资源重用

    由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。

    2. 更快的系统反应速度

    数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间

    3. 新的资源分配手段

    对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源

    4. 统一的连接管理,避免数据库连接泄漏

    在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露

8.3 多种开源的数据库连接池

  • JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
    • DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
    • C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。hibernate官方推荐使用
    • Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
    • BoneCP 是一个开源组织提供的数据库连接池,速度快
    • Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快
  • DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
  • DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
  • 特别注意:
    • 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
    • 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
8.3.1 C3P0数据库连接池
  • 获取连接方式一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//使用C3P0数据库连接池的方式,获取数据库的连接:不推荐
public static Connection getConnection1() throws Exception{
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
cpds.setUser("root");
cpds.setPassword("abc123");

// cpds.setMaxPoolSize(100);

Connection conn = cpds.getConnection();
return conn;
}
12345678910111213
  • 获取连接方式二
1
2
3
4
5
6
7
//使用C3P0数据库连接池的配置文件方式,获取数据库的连接:推荐
private static DataSource cpds = new ComboPooledDataSource("helloc3p0");
public static Connection getConnection2() throws SQLException{
Connection conn = cpds.getConnection();
return conn;
}
123456

其中,src下的配置文件为:【c3p0-config.xml】

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
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<named-config name="helloc3p0">
<!-- 获取连接的4个基本信息 -->
<property name="user">root</property>
<property name="password">abc123</property>
<property name="jdbcUrl">jdbc:mysql:///test</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>

<!-- 涉及到数据库连接池的管理的相关属性的设置 -->
<!-- 若数据库中连接数不足时, 一次向数据库服务器申请多少个连接 -->
<property name="acquireIncrement">5</property>
<!-- 初始化数据库连接池时连接的数量 -->
<property name="initialPoolSize">5</property>
<!-- 数据库连接池中的最小的数据库连接数 -->
<property name="minPoolSize">5</property>
<!-- 数据库连接池中的最大的数据库连接数 -->
<property name="maxPoolSize">10</property>
<!-- C3P0 数据库连接池可以维护的 Statement 的个数 -->
<property name="maxStatements">20</property>
<!-- 每个连接同时可以使用的 Statement 对象的个数 -->
<property name="maxStatementsPerConnection">5</property>

</named-config>
</c3p0-config>
12345678910111213141516171819202122232425
8.3.2 DBCP数据库连接池
  • DBCP 是 Apache 软件基金组织下的开源连接池实现,该连接池依赖该组织下的另一个开源系统:Common-pool。如需使用该连接池实现,应在系统中增加如下两个 jar 文件:
    • Commons-dbcp.jar:连接池的实现
    • Commons-pool.jar:连接池实现的依赖库
  • Tomcat 的连接池正是采用该连接池来实现的该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。
  • 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
  • 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但上面的代码并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
  • 配置属性说明
属性 默认值 说明
initialSize 0 连接池启动时创建的初始化连接数量
maxActive 8 连接池中可同时连接的最大的连接数
maxIdle 8 连接池中最大的空闲的连接数,超过的空闲连接将被释放,如果设置为负数表示不限制
minIdle 0 连接池中最小的空闲的连接数,低于这个数量会被创建新的连接。该参数越接近maxIdle,性能越好,因为连接的创建和销毁,都是需要消耗资源的;但是不能太大。
maxWait 无限制 最大等待时间,当没有可用连接时,连接池等待连接释放的最大时间,超过该时间限制会抛出异常,如果设置-1表示无限等待
poolPreparedStatements false 开启池的Statement是否prepared
maxOpenPreparedStatements 无限制 开启池的prepared 后的同时最大连接数
minEvictableIdleTimeMillis 连接池中连接,在时间段内一直空闲, 被逐出连接池的时间
removeAbandonedTimeout 300 超过时间限制,回收没有用(废弃)的连接
removeAbandoned false 超过removeAbandonedTimeout时间后,是否进 行没用连接(废弃)的回收
  • 获取连接方式一:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static Connection getConnection3() throws Exception {
BasicDataSource source = new BasicDataSource();

source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUrl("jdbc:mysql:///test");
source.setUsername("root");
source.setPassword("abc123");

//
source.setInitialSize(10);

Connection conn = source.getConnection();
return conn;
}
1234567891011121314
  • 获取连接方式二:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//使用dbcp数据库连接池的配置文件方式,获取数据库的连接:推荐
private static DataSource source = null;
static{
try {
Properties pros = new Properties();

InputStream is = DBCPTest.class.getClassLoader().getResourceAsStream("dbcp.properties");

pros.load(is);
//根据提供的BasicDataSourceFactory创建对应的DataSource对象
source = BasicDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
e.printStackTrace();
}

}
public static Connection getConnection4() throws Exception {

Connection conn = source.getConnection();

return conn;
}
12345678910111213141516171819202122

其中,src下的配置文件为:【dbcp.properties】

1
2
3
4
5
6
7
8
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true&useServerPrepStmts=false
username=root
password=abc123

initialSize=10
#...
1234567
8.3.3 Druid(德鲁伊)数据库连接池

Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.atguigu.druid;

import java.sql.Connection;
import java.util.Properties;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSourceFactory;

public class TestDruid {
public static void main(String[] args) throws Exception {
Properties pro = new Properties(); pro.load(TestDruid.class.getClassLoader().getResourceAsStream("druid.properties"));
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
Connection conn = ds.getConnection();
System.out.println(conn);
}
}

123456789101112131415161718

其中,src下的配置文件为:【druid.properties】

1
2
3
4
5
6
7
8
9
10
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
username=root
password=123456
driverClassName=com.mysql.jdbc.Driver

initialSize=10
maxActive=20
maxWait=1000
filters=wall
123456789
  • 详细配置参数:
配置 缺省 说明
name 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
url 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username 连接数据库的用户名
password 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter
driverClassName 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive 8 最大连接池数量
maxIdle 8 已经不再使用,配置了也没效果
minIdle 最小连接池数量
maxWait 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements false 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements -1 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrow true 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdle false 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis 有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun 不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls 物理连接初始化的时候执行的sql
exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

第9章:Apache-DBUtils实现CRUD操作

9.1 Apache-DBUtils简介

  • commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。
  • API介绍:
    • org.apache.commons.dbutils.QueryRunner
    • org.apache.commons.dbutils.ResultSetHandler
    • 工具类:org.apache.commons.dbutils.DbUtils
  • API包说明:

在这里插入图片描述
在这里插入图片描述

9.2 主要API的使用

9.2.1 DbUtils
  • DbUtils :提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法如下:
    • public static void close(…) throws java.sql.SQLException: DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet。
    • public static void closeQuietly(…): 这一类方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLEeception。
    • public static void commitAndClose(Connection conn)throws SQLException: 用来提交连接的事务,然后关闭连接
    • public static void commitAndCloseQuietly(Connection conn): 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。
    • public static void rollback(Connection conn)throws SQLException:允许conn为null,因为方法内部做了判断
    • public static void rollbackAndClose(Connection conn)throws SQLException
    • rollbackAndCloseQuietly(Connection)
    • public static boolean loadDriver(java.lang.String driverClassName):这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。
9.2.2 QueryRunner类
  • 该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
  • QueryRunner类提供了两个构造器:
    • 默认的构造器
    • 需要一个 javax.sql.DataSource 来作参数的构造器
  • QueryRunner类的主要方法:
    • 更新
      • public int update(Connection conn, String sql, Object… params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。
    • 插入
      • public T insert(Connection conn,String sql,ResultSetHandler rsh, Object… params) throws SQLException:只支持INSERT语句,其中 rsh - The handler used to create the result object from the ResultSet of auto-generated keys. 返回值: An object generated by the handler.即自动生成的键值
    • 批处理
      • public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT, UPDATE, or DELETE语句
      • public T insertBatch(Connection conn,String sql,ResultSetHandler rsh,Object[][] params)throws SQLException:只支持INSERT语句
    • 查询
      • public Object query(Connection conn, String sql, ResultSetHandler rsh,Object… params) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。
  • 测试
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 testInsert() throws Exception {
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnection3();
String sql = "insert into customers(name,email,birth)values(?,?,?)";
int count = runner.update(conn, sql, "何成飞", "he@qq.com", "1992-09-08");

System.out.println("添加了" + count + "条记录");

JDBCUtils.closeResource(conn, null);

}
12345678910111213
// 测试删除
@Test
public void testDelete() throws Exception {
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnection3();
String sql = "delete from customers where id < ?";
int count = runner.update(conn, sql,3);

System.out.println("删除了" + count + "条记录");

JDBCUtils.closeResource(conn, null);

}
12345678910111213
9.2.3 ResultSetHandler接口及实现类
  • 该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
  • ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。
  • 接口的主要实现类:
    • ArrayHandler:把结果集中的第一行数据转成对象数组。
    • ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
    • BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
    • BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
    • ColumnListHandler:将结果集中某一列的数据存放到List中。
    • KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
    • MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
    • MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
    • ScalarHandler:查询单个值对象
  • 测试
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
/*
* 测试查询:查询一条记录
*
* 使用ResultSetHandler的实现类:BeanHandler
*/
@Test
public void testQueryInstance() throws Exception{
QueryRunner runner = new QueryRunner();

Connection conn = JDBCUtils.getConnection3();

String sql = "select id,name,email,birth from customers where id = ?";

//
BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
Customer customer = runner.query(conn, sql, handler, 23);
System.out.println(customer);
JDBCUtils.closeResource(conn, null);
}
12345678910111213141516171819
/*
* 测试查询:查询多条记录构成的集合
*
* 使用ResultSetHandler的实现类:BeanListHandler
*/
@Test
public void testQueryList() throws Exception{
QueryRunner runner = new QueryRunner();

Connection conn = JDBCUtils.getConnection3();

String sql = "select id,name,email,birth from customers where id < ?";

//
BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
List<Customer> list = runner.query(conn, sql, handler, 23);
list.forEach(System.out::println);

JDBCUtils.closeResource(conn, null);
}
1234567891011121314151617181920
/*
* 自定义ResultSetHandler的实现类
*/
@Test
public void testQueryInstance1() throws Exception{
QueryRunner runner = new QueryRunner();

Connection conn = JDBCUtils.getConnection3();

String sql = "select id,name,email,birth from customers where id = ?";

ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() {

@Override
public Customer handle(ResultSet rs) throws SQLException {
System.out.println("handle");
// return new Customer(1,"Tom","tom@126.com",new Date(123323432L));

if(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
Date birth = rs.getDate("birth");

return new Customer(id, name, email, birth);
}
return null;

}
};

Customer customer = runner.query(conn, sql, handler, 23);

System.out.println(customer);

JDBCUtils.closeResource(conn, null);
}
12345678910111213141516171819202122232425262728293031323334353637
/*
* 如何查询类似于最大的,最小的,平均的,总和,个数相关的数据,
* 使用ScalarHandler
*
*/
@Test
public void testQueryValue() throws Exception{
QueryRunner runner = new QueryRunner();

Connection conn = JDBCUtils.getConnection3();

//测试一:
// String sql = "select count(*) from customers where id < ?";
// ScalarHandler handler = new ScalarHandler();
// long count = (long) runner.query(conn, sql, handler, 20);
// System.out.println(count);

//测试二:
String sql = "select max(birth) from customers";
ScalarHandler handler = new ScalarHandler();
Date birth = (Date) runner.query(conn, sql, handler);
System.out.println(birth);

JDBCUtils.closeResource(conn, null);
}
12345678910111213141516171819202122232425

JDBC总结

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
总结
@Test
public void testUpdateWithTx() {

Connection conn = null;
try {
//1.获取连接的操作(
//① 手写的连接:JDBCUtils.getConnection();
//② 使用数据库连接池:C3P0;DBCP;Druid
//2.对数据表进行一系列CRUD操作
//① 使用PreparedStatement实现通用的增删改、查询操作(version 1.0 \ version 2.0)
//version2.0的增删改public void update(Connection conn,String sql,Object ... args){}
//version2.0的查询 public <T> T getInstance(Connection conn,Class<T> clazz,String sql,Object ... args){}
//② 使用dbutils提供的jar包中提供的QueryRunner类

//提交数据
conn.commit();


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


try {
//回滚数据
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}

}finally{
//3.关闭连接等操作
//① JDBCUtils.closeResource();
//② 使用dbutils提供的jar包中提供的DbUtils类提供了关闭的相关操作

}
}

comparator

使用外部比较器 Comparator 进行排序

当我们需要对集合的元素进行排序的时候,可以使用 java.util.Comparator 创建一个比较器来进行排序。Comparator 接口同样也是一个函数式接口,我们可以把使用 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
package com.common;

import java.util.*;
import java.util.stream.Collectors;

public class ComparatorTest {
public static void main(String[] args) {

Employee e1 = new Employee("John", 25, 3000, 9922001);
Employee e2 = new Employee("Ace", 22, 2000, 5924001);
Employee e3 = new Employee("Keith", 35, 4000, 3924401);

List<Employee> employees = new ArrayList<>();
employees.add(e1);
employees.add(e2);
employees.add(e3);

/**
* @SuppressWarnings({"unchecked", "rawtypes"})
* default void sort(Comparator<? super E> c) {
* Object[] a = this.toArray();
* Arrays.sort(a, (Comparator) c);
* ListIterator<E> i = this.listIterator();
* for (Object e : a) {
* i.next();
* i.set((E) e);
* }
* }
*
* sort 对象接收一个 Comparator 函数式接口,可以传入一个lambda表达式
*/
employees.sort((o1, o2) -> o1.getName().compareTo(o2.getName()));

Collections.sort(employees, (o1, o2) -> o1.getName().compareTo(o2.getName()));

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


/**
* [Employee(name=John, age=25, salary=3000.0, mobile=9922001),
* Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
* Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
*/
class Employee {
String name;
int age;
double salary;
long mobile;

// constructors, getters & setters


public Employee(String name, int age, double salary, long mobile) {
this.name = name;
this.age = age;
this.salary = salary;
this.mobile = mobile;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public double getSalary() {
return salary;
}

public void setSalary(double salary) {
this.salary = salary;
}

public long getMobile() {
return mobile;
}

public void setMobile(long mobile) {
this.mobile = mobile;
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Employee{");
sb.append("name='").append(name).append('\'');
sb.append(", age=").append(age);
sb.append(", salary=").append(salary);
sb.append(", mobile=").append(mobile);
sb.append('}');
return sb.toString();
}
}

使用 Comparator.comparing 进行排序

comparing 方法一

查看 Comparator 类内部实现,还有一个 comparing 方法,实现如下,

1
2
3
4
5
6
7
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

其返回值是 (c1, c2) -> keyExtractor.apply (c1).compareTo (keyExtractor.apply (c2)); 一个 lambda 表达式,也就是一个 Compator 。所以上面那个例子也可以改造成 如下,

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
package com.common;

import java.util.*;

public class ComparatorTest {
public static void main(String[] args) {

Employee e1 = new Employee("John", 25, 3000, 9922001);
Employee e2 = new Employee("Ace", 22, 2000, 5924001);
Employee e3 = new Employee("Keith", 35, 4000, 3924401);

List<Employee> employees = new ArrayList<>();
employees.add(e1);
employees.add(e2);
employees.add(e3);

/**
* @SuppressWarnings({"unchecked", "rawtypes"})
* default void sort(Comparator<? super E> c) {
* Object[] a = this.toArray();
* Arrays.sort(a, (Comparator) c);
* ListIterator<E> i = this.listIterator();
* for (Object e : a) {
* i.next();
* i.set((E) e);
* }
* }
*
* sort 对象接收一个 Comparator 函数式接口,可以传入一个lambda表达式
*/
employees.sort((o1, o2) -> o1.getName().compareTo(o2.getName()));

Collections.sort(employees, (o1, o2) -> o1.getName().compareTo(o2.getName()));

employees.forEach(System.out::println);


/**
* Comparator.comparing 方法的使用
*
* comparing 方法接收一个 Function 函数式接口 ,通过一个 lambda 表达式传入
*
*/
employees.sort(Comparator.comparing(e -> e.getName()));

/**
* 该方法引用 Employee::getName 可以代替 lambda表达式
*/
employees.sort(Comparator.comparing(Employee::getName));

}
}


/**
* [Employee(name=John, age=25, salary=3000.0, mobile=9922001),
* Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
* Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
*/
class Employee {
String name;
int age;
double salary;
long mobile;

// constructors, getters & setters


public Employee(String name, int age, double salary, long mobile) {
this.name = name;
this.age = age;
this.salary = salary;
this.mobile = mobile;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public double getSalary() {
return salary;
}

public void setSalary(double salary) {
this.salary = salary;
}

public long getMobile() {
return mobile;
}

public void setMobile(long mobile) {
this.mobile = mobile;
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Employee{");
sb.append("name='").append(name).append('\'');
sb.append(", age=").append(age);
sb.append(", salary=").append(salary);
sb.append(", mobile=").append(mobile);
sb.append('}');
return sb.toString();
}
}

comparing 方法二

1
2
3
4
5
6
7
8
9
10
public static <T, U> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (Comparator<T> & Serializable)
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
keyExtractor.apply(c2));
}

和 comparing 方法一不同的是 该方法多了一个参数 keyComparator ,keyComparator 是创建一个自定义的比较器。

1
2
3
4
Collections.sort(employees, Comparator.comparing(
Employee::getName, (s1, s2) -> {
return s2.compareTo(s1);
}));

使用 *Comparator.reversed* 进行排序

返回相反的排序规则,

1
2
3
4
5
6
/**
* 相反的排序规则
*/
Collections.sort(employees, Comparator.comparing(Employee::getName).reversed());

employees.forEach(System.out::println);

输出,

1
2
3
Employee{name='Keith', age=35, salary=4000.0, mobile=3924401}
Employee{name='John', age=25, salary=3000.0, mobile=9922001}
Employee{name='Ace', age=22, salary=2000.0, mobile=5924001}

使用 ***Comparator.***nullsFirst 进行排序

当集合中存在 null 元素时,可以使用针对 null 友好的比较器,null 元素排在集合的最前面

1
2
3
4
5
6
7
employees.add(null);  //插入一个null元素
Collections.sort(employees, Comparator.nullsFirst(Comparator.comparing(Employee::getName)));
employees.forEach(System.out::println);


Collections.sort(employees, Comparator.nullsLast(Comparator.comparing(Employee::getName)));
employees.forEach(System.out::println);

使用 Comparator*.*thenComparing 排序

首先使用 name 排序,紧接着在使用 ege 排序,来看下使用效果

1
2
Collections.sort(employees, Comparator.comparing(Employee::getAge).thenComparing(Employee::getName));
employees.forEach(System.out::println);

collections.copy

拷贝一个ArrayList对象到另一个ArrayList对象中,正好发现Collections有一个copy方法。可是不好用啊总是报错。查看api才知道,它的capacity(容纳能力大小)可以指定(最好指定)。而初始化时size的大小永远默认为0,只有在进行add和remove等相关操作 时,size的大小才变化。然而进行copy()时候,首先做的是将desc的size和src的size大小进行比较,只有当desc的 size 大于或者等于src的size时才进行拷贝,否则抛出IndexOutOfBoundsException异常;

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
import java.util.ArrayList;



import java.util.Arrays;



import java.util.Collections;



import java.util.List;







public class test1 {







/**



* Collections.copy()使用的小技巧







* @param args



*/



public static void main(String[] args) {



// TODO Auto-generated method stub



List<String> src = new ArrayList<String>();



src.add("111");



src.add("222");



src.add("333");



src.add("444");



List<String> dic = new ArrayList<String>(Arrays.asList(new String[src



.size()]));



Collections.copy(dic, src);



for (Object s : dic) {



System.out.println(s);



}



}







}

第二种办法:
dest1 = new ArrayList();
Collections.addAll(dest1, new Object[src1.size()]);
Collections.copy(dest1, src1);

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
public static void main(String[] args) {



// TODO Auto-generated method stub



List<String> src = new ArrayList<String>();



src.add("111");



src.add("222");



src.add("333");



src.add("444");



List<String> dest1 = new ArrayList<String>();



Collections.addAll(dest1, new String[src.size()]);



Collections.copy(dest1, src);



for (Object s : dest1) {



System.out.println(s);



}