当前位置:   article > 正文

JAVA入门到放弃_java 从入门到放弃

java 从入门到放弃

学习了有一段时间的java了,感谢我的恩师——李钦坤。那么今天来梳理下java的相关知识吧。


注意:手机app访问不支持目录跳转

主要是自己用来复习,有需要的可以收藏。

文章目录

前言

  JAVA的应用前景十分可观,java可以做网站,可以做游戏,可以做android应用,也能做电脑软件。

  网站:Java可以用来编写网站,现在很多大型网站都用Jsp写的,JSP全名Java Server Pages。BAI它是一种动态网页技bai术,比如我们熟悉的163,一些政府网站都是采用JSP编写的。所以学习Java的同学可以找开发网站方面的工作,而且现在找这方面的岗位比较多。比如:网站开发,当然就是JSP+Servlet+JavaBean,一直以来都相当流行。

  Android:Android是一种基于Linux的自由及开放源代码的操作系统,其源代码是Java。所以市场上见到的手机系统例如MIUI ,阿里云,乐蛙等,都是修改源代码再发行的。Java做安卓不单单是指系统,还有APP对于更多的开发人员来说,他们更多的时间是花在开发APP上面

  游戏:或者你不知道,在以前诺基亚还很流行的时候,你玩的手机游戏有90%以上都是Java开发的。当然现在已经很少人再去开发了,都转到安卓上去了。电脑上也有Java开发的游戏,最经典的是:《我的世界》(minecraft ,简称MC),当今世界最具人气和影响力的网络游戏之一《英雄联盟》(简称lol)。

  软件:一般编程语言都可以做软件的,Java 也不例外,例如Eclipse,MyEclipse等知名Java开发工具。有关开发软件组件,可以了解Java Swing编程 或者 awt 相关知识。
  比如:企业级应用开发,这里是JAVA的天地,大到全国联网的系统,小到中小企业的应用解决方案,Java都占有极为重要的地位。另外还有移动领域,典型的应用是手机游戏(国内主要是这方面),这里是J2ME的天地,其实应用范围是很广的。

  总结:Java已成为当今市面上最受欢迎的编程软件,对于处于信息高速发达的今天,Java技术已经无处不在,手机软件、手机JAVA游戏、电脑软件等等

正文开始

一、框架简述

  我们该如何学习java,简单的叙述一下我的学习思路。首先是入门,学习java环境的搭建,window电脑在cmd命令窗输入java和javac验证是否环境搭建成功,成功之后用记事本写最基础的java程序。然后是软件的使用,初学者可以用idea(收费)或者eclipse(开源)软件进行开发。接着开始介绍基础语法,包括java基础,数组,异常,线程,泛型,正则表达式等。

二、JAVASE环境的搭建

  java现有发行版包括javaSE(基础部分 可以做出类似QQ咋样的软件),javaEE(企业级应用上,如网站开发等,可以做个网站),javaME(主要用在嵌入式开发上,比如手机等),关于java的环境搭建网上有很多,有直接安装就完成环境搭建的(新手不推荐),最好下载jdk的压缩版,自己完成搭建。
  由于操作系统的不同,直接附上几篇大佬的文章。
  链接:

java的环境搭建

Alt

三、java基础知识

  java基础知识是基础中的基础,如果想要进阶学习,该阶段的内容必须掌握。

1.java概述

a.人机交互

  • 命令提示符: Windows用的是dos命令,linux中使用的是shell命令
  • 图形界面:方便使用,实质是黑盒
dos命令功能
cd在同一盘符下进行目录的任意切换
.代表当前目录 当在linux中执行一些当前目录的脚本时需要使用 ./xxx
代表上一级目录
md创建一个文件夹
rd删除对应的文件夹
echo将一段脚本或者文字输出到当前DOS窗口的命令提示行中
>代表将管道进行重定向
cls清屏
exit退出DOS命令
help可以单独使用也可以和其他命令结合使用 表示查看当前命令或者默认命令的使用方式
ping测试网络通畅
ipconfig查看当前电脑的所有网卡的状态及内容
path查看当前环境变量的主配置
其他命令具体看使用的软件决定


b.java的代码结构
代码如下(示例):

public class 类名{
    public static void main(String[] args){
        //main方法当前程序的入口
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5


c.注释
  在Java代码中 经常用来描述某个属性(变量)的意义 或者某个方法的作用 或者定义一些关键描述 都可以使用注释来解决

用法如下:

// 单行注释
/*
	多行注释  写在此处的注释可以换行
*/

/**

文本注释  主要用来生成javadoc文档  (API)
 @param
 @return 
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  单行注释和多行注释在java程序编译时就自动忽略了
  文本注释 是具有一定功能性的注释 可以使用javadoc工具来生成指定格式的API文档

d.Java运行原理
   首先写好的程序代码会通过java的编译器JVM编译为字节码class文件,然后再交给执行器去执行。
f.Java标识符
  在Java中的包名、类名、属性名、方法名称 都可以称之为标识符(需要自己起名字的地方)
  标识符只能用 数字 字母 _ $ 构成 且不能以数字开头 不能是关键字 和保留字 大小写敏感 长度无限制

g.关键字
  在语法创建时含有特殊意义的字符 可以称之为关键字 具体的关键字 后期会讲解 不需要专门去记忆
h.保留字
  goto,const
i.常量
  概念:不会随程序的执行而发生改变的量成为常量。
  类型:整型常量,浮点类型,布尔类型,字符类型,null

j.转义字符
  概念:在程序中 有时会用到一些含有特殊语义的符号 将之当做普通字符 或者 通过一些特殊符号来转移成固定的格式 转译符号 \ 每个转译符号 只能转译紧接着他的一个字符
符号翻译
\n换行
\r将光标移动至当前行的第一个字符位置
\t制表符Tab
\"将“变成普通字符
\’将’变成普通字符
\将\变成普通字符

k.变量
   概念:随着程序的执行,会改变其中值的储存空间(数学中的未知数)
   语法定义:
数据类型  变量名;//变量的声明
变量名 = 数值;//变量的赋值

//在初始化时 赋值
数据类型  变量名  = 数值;
  • 1
  • 2
  • 3
  • 4
  • 5

注意:在同一范围内,变量类型不可发生变化,变量名不可重复,但可以重复使用。


l.命名规范
   起名字要做到见文知意
  满 足标识符的定义
  包名:全小写 使用域名或项目或公司名称的倒置 来定义包名 eg. com.baidu.utils
  类名:采用大驼峰式命名 所有单词的首字母全部大写
  变量名/方法名: 小驼峰式命名 除了首个单词的首字母小写外 其余多有单词首字母大写
  常量: 全部单词大写 如果遇到单词分隔的情况 使用_来进行分隔



m.数据类型

在Java中的数据类型分为两大类:

  • 基础数据类型
    • 整型:
      • byte(1字节)
      • short(2字节)
      • int(4字节,默认整型是int类型)
      • long(8字节,使用long时,需要在数字后面添加L(推荐)或者l)
    • 浮点类型:
      • float( 4字节 单精度 使用float类型 需要在数字结尾添加 f 或者 F)
      • double(8字节 双精度 默认所有的浮点数类型都是double类)
    • 字符型:char( 2个字节 底层其实还是使用数字来进行存储的(编码) 使用一对 ‘’ 包裹起来的一个字符)
    • 布尔类型:boolean 布尔类型 只有两个值 true真 false假
  • 引用数据类型
    • 所有的非基本数据类型都是引用数据类型


n.基本数据类型转换
两种转换方式
  自动转化:JVM在底层编码时就帮助进行转换 该过程是JVM自动完成 无需程序员手动处理
  eg:将一个小范围的类型数据 转换成一个大类型范围的数据 会自动类型转换
byte num1 = 12;
int num2 = num1;//自动类型转换
  • 1
  • 2

   强制类型转化:当需要将大范围的数据转换至下范围的类型时 很有可能发生溢出的情况 此时JVM出于安全性考虑 不会自动类型转换 需要程序员 根据实际情况 进行手动强制类型转换
eg:
 int num1 = 127;
 byte num2 = (byte)num1;//强制类型转换  但是有数据溢出的风险 谨慎使用

//语法
小类型 变量  = (小类型) 大类型值;
  • 1
  • 2
  • 3
  • 4
  • 5

注意:
1.byte,short,int --> long --> float --> double 按照从小到大的返回 会出现自动类型转换 都是数字类型
2.byte,short,int不会互相转换,它们三者在计算时会转换成int类型



o.运算符
  • 算数运算符
  • 赋值运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 字符串拼接符
  • 三目运算符

算术运算符

+ - * / % ++ –

加减乘除都是正常计算,但除法/ 当符号两边都是整型时 结果是整数类型 ,如果任意一边是浮点类型 ,则结果是小数类型

++、-- 使用时要注意

int a = 10;
a++;//a++  --->  a=a+1;
System.out.println(a++);//11  先执行后运算
System.out.println(++a);//12  先运算后执行
  • 1
  • 2
  • 3
  • 4

赋值运算符

= += -= *= /= %=

= 指的是普通的赋值运算 会先计算= 右侧的内容 之后再将值赋值给左侧的变量 不能进行类型的自动提升

复合型赋值运算符

+= -= *= /= %= 在计算时 就是将左侧变量与右侧值进行运算符相关运算操作后 将值再赋值给左侧变量 计算过程中会出现自动类型提升,小心使用

关系运算符

> < >= <= == !=

所有的关系运算符的结果都是boolean类型

逻辑运算符

& | ! ^ && ||

符号描述
&当运算符两侧的值都为真 结果才为真 一个为假即为假
|当运算符两侧都为假 结果才是假 一个为真 结果为真
!放置在变量前面 用于改变当前变量的值 真的变假的 假的变真的
^运算符两侧值相同时为假 不同时为真
&&短路与 判断依据和&相同 只是当第一个表达式能够确定整个运算结果时 则不再执行后面的表达式 可以提升运算效率 建议使用
||短路或 判断依据和|相同 只是当第一个表达式能够确定整个运算结果时 则不再执行后面的表达式 可以提升运算效率 建议使用

字符串拼接符
+ 当做字符串拼接符 区别于算数运算符中的+
当+的任意一侧的数据类型为字符串类型时 该符号即为 字符串拼接符 用于拼接字符串
  • 任何类型的数据 和字符串进行拼接后 全部会转型成字符串类型 之后进行字符串拼接
  • 可以使用该语法来快速转化字符串类型 eg. a+"";

位运算符
& | ^ ~ >> << >>>
所有位运算 都是转化成二进制后 才开始按位运算
  • 十进制和二进制转换
  • 十进制和八进制之间的转换
  • 十进制和十六进制之间的转换
  • 二进制和八进制之间的转换
  • 二进制和十六进制之间的转换




    三目运算符

    当需要通过一个条件来决定两个结果中最终使用哪一个 可以采用三目符来实现
//三目运算符 语法
//(条件表达式)?表达式1:表达式2 当条件表达式为真时  执行表达式1 反之执行表达式2
int age = 25;
System.out.println((age>=18)?"已成年":"未成年");
  • 1
  • 2
  • 3
  • 4

表达式:

在Java中 满足语法的前提下 将运算符和变量按照一定顺序排列的序列 可以称之为表达式

在表达式中 存在两个概念 表达式的值 表达式类型

  • 表达式的值 :当前表达式最终的结果即为该表达式的值
  • 表达式的类型 : 当前表达式最终结果所属的数据类型 即为该表达式的数据类型

优先级:

所有的运算符都有优先级问题, 无需特殊记忆 ,如果需要优先级 则可以使用()来自定义优先级顺序

  • 最高优先级 ()
  • 最低优先级 =
  • 当优先级相同时 是从左至右依次执行
Separator. ( ) { } ; ,
L to R* / %
L to R+ -
L to R<< >> >>>
L to R< > <= >= instanceof
L to R== !=
L to R&
L to R^
L to R|
L to R&&
L to R||
R to L++ – ~ ! (data type)
R to L? :
R to L= *= /= %= += -= <<= >>= >>>= &= ^= |=


四、流程控制

1.流程的类型


   在Java代码中,根据具体的业务流程需要分为三种不同的流程,流程分为三种不同的控制流程,流程控制在java中是很重要的内容,必须掌握。
  • 控制流程
  • 选择流程
  • 循环流程


    顺序流程

    程序默认情况下 会按照从上至下的顺序 逐行执行以完成的代码内容 该过程叫做顺序流程

    选择流程

    当遇到一些需要逻辑判断的执行内容时 选择流程就起到作用 具体根据条件表达致的值来决定具体的执行内容
    选择流程有两种语句:


    if语句:
/*语法

当满足条件时 执行  不满足时跳过:
if(条件表达式){
	条件执行代码
}
*/
if(3<5){System.out.println("这是真的");}

/*

只有两种结果的分支选择:
if(条件表达式){
	条件符合时执行的代码
}else{
	条件不符合时执行的diamante
}
*/
int age = 18;
if(age >= 18){
    System.out.println("已成年");
}else{
     System.out.println("未成年");
}

/*
	在同一逻辑内部有多种结果分支  则使用一下方式:
	if(条件表达式1){
	条件符合1时执行的代码
    }else if(条件表达式2){
        条件符合2时执行的diamante
    }else if(条件表达式3){
        条件符合3时执行的diamante
    }...
    else{
    	以上条件都不满足  执行该区域代码
    }

*/
	int score = 70;
	if(score >= 0 && score < 60){
			System.out.println("不及格,需要继续努力哦~~加油~~我看好你!!!");
		}else if(score >= 60 && score <70){
			System.out.println("刚及格,打了个擦边球,一不小心就挂了");
		}else if(score >= 70 && score <80){
			System.out.println("良好,还可以,莫骄傲,继续前进吧");
		}else if(score >= 80 && score <90){
			System.out.println("优秀,秀儿,你回来了");
		}else if(score >= 90 && score <100){
			System.out.println("666,打6就完事了");
		}else if(score == 100){
			System.out.println("完美,你本来就很美");
		}else{
			System.out.println("成绩都输不对,你个憨憨,赶紧核对成绩");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

switch语句:
/*
	switch 语句 适用于 条件比较固定 且范围较小 而且是等值匹配的情况  
	优点 : 效率比if高
	缺点 : 逻辑不灵活 比较死板
	
	语法:
	switch(n){
		case v1 : 执行语句1;[break;]
		case v2 : 执行语句2;[break;]
		case v3 : 执行语句3;[break;]
		...
		default:  执行语句n;[break;]
	}
	
	执行过程 当n  和下属case后面的某个值匹配时  会直接执行该case后面的执行语句 如果有break 则执行完跳出 如果没有break 则继续向下执行  直到遇到break或者程序块执行完毕 
	switch中 case以及default都没有先后顺序  但是建议按照一定的逻辑  排列case以及default
    n变量  在java中只能是 byte short  char  int 在JDK7之后 提供了 String  枚举类型
	
*/

int num = 7;
		switch(num){
			case 1:System.out.println("今天是星期一,一周的开始,不高兴");break;
			case 2:System.out.println("今天是星期二,才过了一天,好慢呀");break;
			
			case 3:System.out.println("今天是星期三,好不容易到中间了,在坚持一下");break;
			
			case 4:System.out.println("今天是星期四,还有一天,小激动");break;
			case 5:System.out.println("今天是星期五,明天就放假了,好高兴");break;
			case 6:System.out.println("今天是星期六,终于放假了,想干啥干啥");break;
			case 7:System.out.println("今天是星期日,明天又上班了,太快了");break;
			default: System.out.println("快醒醒,你要活在当下");break;
		}
  • 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


循环流程
   在日常的业务逻辑中,经常会出现重复性执行的内容,此时需要循环流程来解决该问题
在Java中有三个主要的循环语句


while语句:
  先进行判断 之后在执行循环体内容

语法:


while(条件表达式){
循环内容
}

//循环输出1-100
int i = 1;
while(i<=100){
    System.out.println("当前输出的数字为:"+i++);
}
  • 1
  • 2
  • 3
  • 4
  • 5


do-while:
  无论如何 都会先执行一次循环体内容 之后再去判定条件表达式 如满足则继续执行循环 否则直接跳出

语法:


do{

循环内容

}while(条件表达式);

//计算5-70 的和
int i = 5;
int sum = 0;
do{
    sum += i++;
}while(i<=70);
System.out.println("5-70的和为:"+sum);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在条件满足的情况下 while和 do-while 执行方式和内容完全一致 只有当第> 一次条件不满足时 才会有执行区分 根据业务需求进行选择
一定注意 在使用while和do-while的语句时 不要写成死循环 一定要进行出> 口达式的描述
除非遇到死循环需求



for循环语句
  for语句的语法结构较为复杂 但是其灵活性很好 适合大多数的业务 所以经常使用

语法:


for(初始化表达式 1; 条件表达式2 ; 出口表达式3 ){

循环内容4
}
for循环一共可分为4个部分 执行顺序为


1 —》 2 ----》 4 —》 3 ----》 2 —》4 —》3 …… —》2 不满足条件 退出程序

/*
	循环输出 1-100 的和
*/
int sum = 0 ;
for(int i = 1; i<=100 ;i++){
    sum += i;
}
System.out.println("1-100的和为:"+sum);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8


2.流程控制说明


   在实际情况下,业务需求是及其庞杂的,经常需要将上述的流程控制语句组合使用,例如 : 循环嵌套 条件嵌套 循环嵌套条件 条件嵌套循环 根据业务需求灵活使用 ---- 多写多练

3.打断流程的关键字


   经常需要在循环中,依据特殊的条件进行循环跳出的功能,此时需要特定的关键字来完成
  常用的两个关键字:

  break:
  使用break可以直接跳出当前所在循环
  而且break之后的所有内容不再执行 注意逻辑顺序问题 一般将跳出的时要执行的内容放置在break前面
//输入一个1-10 之内的数  之后从0开始循环至当前数字
		int n = 6;
		for(int i = 0 ; i <=10; i++){
			System.out.println("当前数字为:"+i);
			if( i == n){
					System.out.println("到"+n+"了");
					break;
			}
			
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

   continue
   使用continue可以直接跳当前所在循环
  在执行continue之后 后面的代码就不在执行 所以一般将跳过的内容放置在continue之后 注意逻辑执行顺序
	//当循环执行到 n 时  忽略该次循环  
		for(int i = 0 ; i<= 10 ; i++){
			System.out.println("当前数字为:"+i);
			if( i == n){
					System.out.println("到"+n+"了");
					continue;
			}
			
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

break 和 continue的使用 尤其是在嵌套循环中 出现的循环层级不同 结果
不同 每个关键字 只控制当前所在的那个循环层次

例如:

for(int i = 0 ; i < 5;i++){
    if(i==3){
        continue;
    }

    for(int j = 0 ; j < 5 ; j++ ){

        if(j == 2){
            continue;
        }
        System.out.println("当前是第"+(i+1)+"次循环,目前是该循环的第"+(j+1)+"次");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

五、JAVA的方法

1.概念

在其他语言中叫做函数,在Java中称之为方法。

在程序的开发过程中,希望将重复性的功能进行封装,打包成一个个的方法,每次可以重复使用

[访问修饰符1 访问修饰符2] 返回值类型 方法名称(形参列表){
方法主体
[return 返回值;]
}

2.注意事项

考虑封装一个方法时,主要考虑两个方面:

  • 当前方法需要返回什么内容 ----- 返回值类型

    如果一个方法有返回值,则必须声明当前返回值的类型,否则必须在返回值类型处声明void关键字

  • 当前方法在执行时需要哪些未知数(原料) ---- 形参列表

    参数:形式参数:方法声明时 用于同时调用者 当前需要哪种类型的参数 也称之为形参

    ​ 实际参数:在使用者调用方法时 实际传入的参数 称之为实参 真正参与方法执行的参数


   当参数声明完成时,并不会被执行。只有调用者主动调用时,才会进入到方法内部执行内容。
  如果方法没有返回值 则调用者不可以接受当前函数的结果
  如果方法有返回值 则调用者可以选择性接收方法返回的结果 可以忽略 也可以接收处理

3.return关键字


   如果方法没有返回值 可以省略不写 写上不会报错 但一般不建议再写 除非需求
  return会终止当前所在范围(代码块)内的所有代码 所以在同一范围内不要在return后面再写任何代码
  如果方法有返回值 则必须使用return关键字进行返回

注意 方法内部不能再定义任何的方法 方法不能嵌套定义



4.递归算法

定义:在当前方法内部调用方法自身

用法:

//典型的递归函数
public void test(){
    test();
}
  • 1
  • 2
  • 3
  • 4

   递归函数在执行时,会等待期内部的函数执行完毕后 再返回当前方法执行 所以会在内存中开辟很多的空间用来存储当前的临时节点,如果说递归的深度过于大时 程序会产生异常 StackOverflow(栈溢出)
   注意:
  1. 在递归定义时,一定要指定方法的出口
  2. 建议不要用递归来实现复杂的算法问题 可以用来解决业务问题
  3. 一般情况下 在封装递归时 考虑两个事:
    通用规律是什么(公式)
    当前出口是什么(定理)


六、 Array数组

1. 一维数组

概念:将具有相同类型的一组数据放置在一个组里,这种数据结构,叫做数组

注意:
1. 是引用数据类型。
2. 数组中可以放置任何类型的数据 但是一但确定数据类型 就要保证内部的数据类型完全一致

//数组的声明
数据类型[]  变量名;//在Java中建议使用此种方式
数据类型 变量名[];

int[] a;

/*
	数组的创建  需要使用关键字 new 
*/
数据类型[] 变量名 = new 数据类型[数组长度];
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

注意:
数组初始化时,必须要确定当前的数组长度,且一旦确定了 就无法更改。
使用数组的好处,底层存储快,效率相对高,在能够确认总长度的需求中,建议使用数组


数组的初始化

静态初始化

数据类型[] 变量名 = {v1,v2,v3,…,vn};


   在创建对象的同时 进行初始化数组内容
//静态初始化
int[] x = {2,4,6,8,9};//--> 完整形式 int[] x = new x[]{2,4,6,8,9};
  • 1
  • 2

   静态初始化完成时,该数组的长度就已定且确定下来且不能更改
动态初始化

数据类型[] 变量名 = new 数据类型[长度];
根据下标(索引) 去依次初始化


注意:
1.默认初始化 :
当一个数组被创建但未进行初始化赋值时,JVM会自动给当前数组按照类型进行默认初始化。
2.数组长度:
在java中,对数组提供了内置属性 length ,可以获取当前数组中的元素个数。
3.下标,索引
数组中,每个元素都会对应一个下标(索引)--Index,索引从左至右排序 ,以0开始。

数组结构:

   数组是引用数据类型 所以其变量存储在栈中 值存储在堆中

在这里插入图片描述


可变参数:

在JDK5(含)之后,出现了可变参数,用来接收未知个数的参数

数据类型… 变量名

eg:(可变参数必须要放置在当前参数列表的最后一个参数位置)

public void add(String name,int... nums){
    System.out.println("操作员:"+name);
    //可变参数在操作时  按照数组方式操作
    System.out.println("当前接受到的可变参数个数:"+nums.length);
    int sum = 0;
    for(int i = 0 ; i < nums.length;i++){
        sum += nums[i];
    }
     System.out.println("所有可变参数的和为:"+sum);
    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

命令行参数
   在Java的入口方法中存在参数 String[] args,该参数通过命令行方式 使用jar工具可以在调用该入口方法时 传递参数, 这个参数叫做命令行参数 一般用来进行项目的设置或者初始化数据

ForEach
JDK5(含)之后可以使用ForEach方式来遍历数组或者集合

for(元素类型 变量名 : 待遍历数组/集合){
​> 操作变量名
}

public void forEach(int[] x ){
    for(int n : x){
        System.out.println(n);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
注意:forEach只能遍历 不能改变原数组内容

2.二维数组

概述:


在Java中并没有在底层实现二维数组的存储结构,只不过是在一维数组内部的元素仍是数组 二维数组

创建数组方式:

eg:

//创建二维数组
数据类型[][] 变量名  = new 数据类型[l1][l2];//l1是必须要写的长度  l2 可写可不写
//动态初始化
int[][] x = new int[3][3];
int[0][0] = 1;
int[0][2] = 3;
int[2][2] = 6;
//静态初始化
int[][] x = {{1,2,3},{3,4},{3}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注意:

二维数组中 可以改变一维数组中的元素数组 但必须使用new关键字创建的数组才可以进行替换

int[][] x = {{2,3,4},{0}}
//error
x[1] = {1,2,3}

//正常
x[1] = new int[]{1,2,3}
//或者
x[1] = new int[3];
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

二维数组内存结构

在这里插入图片描述

七、JAVA的基础算法

1.求最值

核心思想:求最值都需要将当前所有要比较的元素全部过滤一遍 只需要一个循环将所有数据进行比较即可

示例:

publc int max(int... x){
    int temp = 0 ;
    int i = 1;
    for(int n : x){
        if(i == 1){
            temp = n;
            i++;
            continue;
        }
        if(temp < x){//最小值 就是改变判断符号 将小于改成大于即可
            temp = x;
        }
    }
    return temp;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

2.排序算法

冒泡排序

核心思想:每次比较相邻两个元素的值,之后将较大的放置在右侧(位置交换)

如果有n个待排序的元素 则一共需要比较n-1轮 每一轮需要比较 n-当前的轮数


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

public void BubbleSort(int... args ){
    for(int i = 0 ;i < args.length-1;i++){
        //一共比较多少次
        for(int k = 0 ; k< args.length-1-i;k++){
            //每轮比较的次数
            //如果右侧的小于左侧的 就交换两个元素的位置
            if(args[k]<args[k+1]){
                int t = args[k];
                args[k] = args[k+1];
                args[k+1] = t;
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

选择排序
由于冒泡排序,每轮比较两个元素,并可能需要交转元素位置,交换频率较高,性能空间使用相对较弱, 可以采用选择排序来提升其性能。

核心思想:每轮比较,都只存储当前轮数中的最小下标,当此轮比较结束时,替换当前轮数最左侧和最小下标元素。每轮最多只需要交换一次。

如果有n个元素 比较的轮数n-1轮 每轮比较次数 n-轮数

执行过程:

在这里插入图片描述


示例代码:

//选择排序
public void selectSort(int[] x){
    for(int i = 0 ; i < x.length -1;i++){
        //控制比较轮数
        int temp = i;//每轮先将最左侧元素下标当做最小下标
        for(int j = i+1 ; j < x.length;j++){
            //每轮比较多少次 
            if(x[temp] > x[j]){
                //当前元素小于temp中的元素 需要更新temp中的最小下标值
                temp = j;
            }
        }
        
        //如果当前temp大于i的值 表示最小小标更新 需要交换两个元素的位置
        if(temp > i){
            int t = x[temp];
            x[temp] = x[i];
            x[i] = t;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

快速排序 在排序算法中,时间复杂度和空间复杂度较均衡且效率高的排序算法就是快速排序算法,同时也是JDK内置排序算法

核心思想:
  1. 找位置 指定一个基准数据 最终定位该基准数据的最终位置
  2. 分而治之 将基准数组最终位置左右两侧的未排序数组 分别再次实现找位置 每个数字找到自己的位置 整个序列就排好顺序

  一般将最左侧元素当做基准数据看待,后期可以针对快速排序进行优化,例如:随机选择基准数,取中选择基本书,直接找到大小两个元素之后交换位置。

在这里插入图片描述


核心算法:

//快速排序

/**
	采用分治思想实现最终排序
*/
public void fastSort(int[] x, int l, int h){
    
    //使用迭代来进行分治
    if(l < h){
        int index = getIndex(x,l,h);
        fastSort(x,l,index-1);
        fastSort(x,index+1,h);
    }
    
    
}

/**
	获取当前基准数据的最终位置
*/
pubic int getIndex(int[] x, int l , int h){
    
    int base = x[l];//每次获取最左侧数据当做基准数据
    int low = l;//最低位置
    int height = h;//最高位置
    
    while(low < height){
        //未找到基准数据的最终位置
        //先从右至左开始查找小于基准数据的值 如果没有则height--
        while(low < height && base < x[height]){
            height--;
        }
        //找到小于基准数据的值后 进行赋值操作
        if(low < height){
            x[low] = x[height];
            low++}
        //再从左至右开始查找大于基准数据的值  如果没有则low++
        while(low < height && base > x[low]){
            low++;
        }
        if(low < height){
            x[height] = x[low];
            height--;
        }
    }
    //循环执行完毕后 产生low=height的情况 此时基准位置确定 进行赋值操作
    x[low] = base;
    return low;//返回最终基准位置
    
}
  • 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


3.查询算法

两种查询算法:

1.遍历查询
2.二分查找




遍历查询

当前数组从左至右依次查找该元素 如果找到返回该元素下标,否则返回-1
缺点:数据量大时 查找成本高
示例代码:

//遍历查询
public int find(int[] x , int n){
    for(int i = 0 ; i < x.length;i++){
        if(x[i] == n){
            //表示找到了
            return i;
        }
    }
    return -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

二分查找

也叫做折半查找 ,执行该操作前必须保证当前数组是已排序

  思想:每次获取中间位置的数 和带查找数据比较 小于的在左侧 大于的在右侧 之后重复之前操作 直到找到位置 返回下标 否则返回-1

  优点:查找成本降低
//二分查找
public int halfFind(int[] x, int n ){
    int low = 0;//最低位下标
    int height = x.length-1;//最高位下标
    
    while(low < height){
        int middle = (low+height)/2;
        //判断当前查询数据和中间数据的关系
        if(x[middle] < n){
            //在右侧继续查找
            low = middle + 1;
        }else if(x[middle] > n){
            //在左侧继续查找
            height = middle - 1;
        }else {
            return middle;
        }        
    }
    
    return -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

4.算法的优劣性

考量一个算法的优劣主要通过两个维度来评判:

  • 时间复杂度

    不是真实的执行时间 使用O()来表示渐进时间表达式
  • 空间复杂度

    不是真实的存储空间 计算时临时开辟的计算空间(内存) 取决于变量的创建规模 S()



八、JAVA面向对象

  • 面向过程:在编程过程中,以具体的流程为主进行编程就是面向过程编程,更加贴近底层。
  • 面向对象:在编程过程中,以具体事务为主就是面向对象编程,更偏向业务。

1. 面向对象的概念

  1.在Java中“万事万物皆对象”
  2.面向对象更贴近人的正常思维 更易于编程逻辑的设置
  3.设计面向对象的程序:我可以通过哪些工具来帮我完成当前的业务 并指挥所有业务之间的关系
  4.面向对象的编程:通过当前可用的工具来进行业务开发,如果没有可用工具,则需要自己创建一个。

2.面向对象的三大特征

  • 封装(四大特征——抽象)
  • 继承
  • 多态

3.类和对象

面向对象编程过程中,最底层,最核心的概念:类和对象。

  • 类:可以想象成一个设计图,模板
  • 对象:根据设计图制作出来的具体产品,后期程序只能使用对象来进行编程

类和对象相辅相成:

可以将所有的事物抽象成一个类,在使用时再实例化成一个对象。
在抽象成类的过程中,也需要参考很多对象,之后再根据已有对象,按经验进行封装。

在这里插入图片描述
生活中描述事物无非就是描述事物的属性行为
在一个类中只需要考虑两个方面:

  • 属性(成员变量)
  • 行为(成员方法)

4.变量分类

局部变量

范围:在当前代码块或者语句中

特点:

  • 局部变量必须手动初始化 才能使用
  • 局部变量存储在栈中
  • 出了当前代码块或语句后 立即释放


成员变量

范围::在当前所在类的范围内部,可以任意使用。

特点:

  • 成员变量可以默认初始化,不需要手动初始化也可以使用
  • 储存在堆中
  • 使用完毕后等待JVM的自动垃圾处理

当在成员变量和局部变量名称冲突时 局部变量会在代码块内部范围内覆盖成员变量

5.默认初始化问题

在引用数据类型中,所有的成员属性都可以被默认初始化,具体初始化的值和数据类型有关

成员变量类型默认值
byte0
short0
int0
long0L
char‘\u0000’
float0.0F
double0.0D
booleanfalse
所有的引用类型null

6.封装

概念

就是隐藏对象的属性和实现细节,仅对外提供公共访问方式。

优点

将变化隔离,便于使用,提高重用性,提高安全性。

封装原则:

将不需要对外提供的内容都隐藏起来。
把属性都隐藏,提供公共方法对其访问。


7.抽象

将客观事物的特征用java的语言描述出来,抽象只关注与当前主题有关的方面,忽略与目标无关的方面

8.类的语法

一般类里面包含两类,成员属性和成员方法

访问修饰符 class 类名{
成员属性
成员方法
}

9.构造方法

概念:在Java中 所有的类都需要通过new关键字抵用构造方法来创建出一个个的对象

构造方法是在类里面创建的

构造器就是用来创建对象的 帮助初始化成员属性

语法

访问修饰符 方法名(){
初始化成员属性
}

要求
一般要访问修饰符为public,并且方法名与类名完全相同。


构造方法分类

  • 无参构造
    a.在java中,如果没有手动声明任何构造器,则JVM在编译过程中会自动添加一个无参构造,以便程序员快速新建对象。

    b.如果程序员手动创建了一个构造器,则JVN不再管理任何构造器,后期只能根据已有的构造器进行对象的创建。

  • 含参构造
    需要根据需求 在构造器中对成员属性进行初始化定义 需要在构造方法的参数列表中 添加所有要初始化属性的参数 之后在构造器中去进行初始化过程

建议:如果手动创建了含参构造 一定要在去手动添加一个无参构造

10.方法的调用

当我们写好一个方法之后,该如何去调用呢?

如果对象是

  • 成员方法:可以使用对象名(变量名).方法 使用. 成员方法一般是为了解决某个功能而封装的方法

  • 构造方法:必须使用new 关键字来进行调用 构造方法只是用来创建对象使用

什么是成员方法和构造方法?
成员方法指的是本类新建的方法,而构造方法是在另一个类新建的,另一个
类可以是写在同一个文件里面,也可以写在不同文件里面。

11.引用对象的内存分析

引用数据类型的变量指针索引储存在栈中,而真实的值储存在堆中。

而基本数据类型的值直接储存在栈中。

栈与队的区别
栈结构:后进先出(弹夹模型),执行完毕后自动回收垃圾。

堆结构:先进先出(手枪模型),很大的可共享空间,执行完毕后会等待JVM的垃圾回收。
举例

public class Student{
   ……
   public static void main(String[] args){
       Student stu = new Student(“张三”, 18, ‘男’, “精英班”, 10);
       stu.displayInfo();
       Student stu2 = new Student(“李四”, 23, ‘女’, “预热班”, 18);
       stu2.displayInfo();
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

数据在内存的位置如图:
在这里插入图片描述

12.方法的重载

概念:指同一个类中可以有方法名相同名字的多个方法,调用时会根据参数不同选择相应的方法。

好处:方便使用者调用,优化了程序的设计。

构成语法重载的条件:

  • 在同一个类
  • 方法名相同
  • 参数列表不同
    • 参数个数不同
    • 参数类型不同
    • 参数顺序不同
  • 返回值无关

在一个类中 普通方法和构造方法均可以重载

13.this关键字

每个类的每个非静态方法(没有被static修饰)都会隐含一个this引用名称,它指向调用这个方法的对象

当在方法中使用本类的属性时,都会隐含地使用this名称,当然也可以明确指定。

this可以看作是一个变量,它的值就是当前对象的引用


举例

//Persion类的构造方法
public Person(String n, boolean s, int a){
    this.name = n;
    this.sex = s;
    this.age = a;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在一般情况下,当前类的属性和方法可以不用写this关键字来调用,底层也会添加this

必须使用this关键字的俩种情况:
   1.当类中某个非静态方法的参数名跟类的某个成员变量名相同时,为了避免参数的作用范围覆盖了成员变量的作用范围,必须显式地使用this关键字来指定成员变量
public class Employee {
    private String name;       //姓名
    private int age;           //年龄
    private double salary;     //薪水

    public Employee(String name, int age, double salary){    //构造方法
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

   2.如果某个构造方法的第一条语句具有形式this(...),那么这个构造方法将调用本类中的其他构造方法。
public class Employee {
    private String name;        //姓名
    private int age;                //年龄
    private double salary;     //薪水

    public Employee(String name, int age, double salary){    //构造方法1
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public Employee(){    //构造方法2
        this(“无名”, 18, 800.0);    //调用到了构造方法1
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

14.

作用:

为了便于管理大型软件系统中数目众多的类,解决类命名冲突的问题


使用

在package语句中,用"."来指明包(目录)的层次。包对应着文件系统的目录层次结构。
如:package com.liqk; -->编译后对应的类文件位于com\liqk目录下。

导入某个包中的所有类使用:包名.*

  • 如:import com.liqk.*;

注意

  • 同一包中的类之间直接引用,无需import语句

  • 无名包中的类,无法在其它带包的类中使用

  • 建议:自定义类都要放置在包中。

JDK常用包介绍

  • java.lang -包含一些Java语言的核心类,如:Object、String、Math、Integer、System和Thread,提供常用功能。
  • 此包非常常用,所以在任何类中不用导入就可能直接使用。
  • java.util -包含一些实用工具类,如定义系统特性、日期时间、日历、集合类等。
  • java.io -包含能提供多种输入输出的流类。
  • java.net -包含执行网络相关的操作的类。
  • java.sql -java操作数据库的一些API。
  • java.text-包含一些用来处理文本、日期、数字和消息的类和接口
  • java.awt -包含了构成抽象窗口工具集的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
  • javax.swing -包含了构成“轻量级”窗口的组件。

15.继承

java三大特性之一 继承 在逻辑上具有 is-a 关系的类 可以声明成继承关系
继承好处:代码的复用性 扩展性
使用关键字 extends 来表示当前父类是谁


在Java中只有单一继承 :一个子类只能有一个父类 而一个父类 可以派生出多个子类




但继承关系可以向下传递 父 —> 儿 -->孙 -->…–>n

示例:

class Employee {
    private String name;                     //姓名
    private double salary = 2000.0;     //薪水
    public Employee(String name, double salary){
        this.name = name;
        this.salary = salary;
    }
    public Employee(){}
    public double getSalary(){        return salary;    }
}
class Manager extends Employee{
    private double bonus;    //奖金
    public void setBonus(double bonus){        this.bonus = bonus;    }
}
public class InheritanceTest {
    public static void main(String [] args){
        Manager manager = new Manager();
        manager.getSalary();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

解释
父类中除了构造器之外其他的所有信息都能被子类继承


子类在继承父类的信息后 可以在原有基础上进行扩展 扩展的内容只能子类自己使用 父类无法访问


在创建子类的过程中 一定会先创建父类 如果没有显示声明 则会默认掉用父类的无参构造来进行创建


如果父类没有无参构造 则必须手动显示声明调用父类的已有构造器


16.super关键字

在Java中 除了this关键字 之外 还有一个super关键字

super 只能出现在继承关系的子类中 表示某个参数或者某个方法 是父类的属性或方法
super关键字 在一般情况下可写可不写 一般不写


必须要声明super关键字的两种情况

  • 子类方法 重写父类方法的同时 还希望调用父类原有方法体内容 此时必须用super关键字来声明哪个是父类方法 否则会出现无出口递归 造成栈溢出异常
    示例
class Employee {public void displayInfo(){        System.out.println(“name=+ name);   }
}

class Manager extends Employee{
    private double bonus;    //奖金
    private String position;   //职位
    
    public Manager(String name, double salary, String position){
        super(name, salary);
        this.position = position;
   super.displayInfo();
    }

    public void setBonus(double bonus){        this.bonus = bonus;    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 在父类只有有参构造时 子类必须使用super关键字 并在当前构造器的第一行进行 父类构造的调用
    示例
class Employee {
    private String name;                     //姓名
    private double salary = 2000.0;     //薪水
    public Employee(String name, double salary){
        this.name = name;
        this.salary = salary;
    }
    public Employee(){}
    public double getSalary(){        return salary;    }
    public void displayInfo(){        System.out.println(“name=+ name);   }
}
class Manager extends Employee{
    private double bonus;    //奖金
    private String position;   //职位
    public Manager(String name, double salary, String position){
        super(name, salary);
        this.position = position;
    }
    public void setBonus(double bonus){        this.bonus = bonus;    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

17.访问修饰符

所有的类、属性、方法都是有使用范围的 由访问修饰符类决定

范围

位置private默认protectedpublic
同一个类
同一个包内的类
不同包内的子类
不同包并且不是子类

18.方法的重写

当子类不满足父类提供的方法时 需要重写父类方法 该过程叫做方法的重写 override

在重写中 Java专门定义了一个注解 @Override 用来在编译过程中校验当前方法是否构成重写


构成重写的语法条件

  • 发生在继承关系中
  • 方法名相同
  • 参数列表相同
  • 返回值类型兼容
  • 访问范围不能被缩小
  • 异常范围不能被扩大
  • 静态方法只能重写父类的静态方法。

示例

class Employee {
    private String name;                     //姓名
    private double salary = 2000.0;     //薪水
    public Employee(String name, double salary){
        this.name = name;
        this.salary = salary;
    }
    public double getSalary(){        return salary;    }
}

class Manager extends Employee{
    private double bonus;    //奖金
    private String position;   //职位
    public Manager(String name, double salary, String position){
        super(name, salary);
        this.position = position;
    }
    public void setBonus(double bonus){        this.bonus = bonus;    }
    public double getSalary(){
         return super.getSalary() + bonus;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

19.static关键字

对于希望数据共享的属性或方法 可以使用static关键字进行修饰 其内容会随着类的加载而初始化 整个程序只有一份static数据,

static既可以修饰属性 该属性可被共享

static也可修饰方法 该方法只能调用static修饰的成员 而不能调用非static修饰的内容

static修饰的内容 可以称之为类下属的属性和方法

两种调用方式

  • 通过类名直接调用 (建议)
  • 通过实例名进行调用(不建议)


对于static修饰的属性 提供了静态代码块 可以进行静态属性初始化

public class Dog{
    private static int count = 100;
    private int id;
    private String name;

    public Dog(String name){
        this.name = name;
        id = count++;
    }
    public void displayInfo(){
        System.out.print(“名字是:+ name + 编号是" + id);
    }
    public static void currentCount(){
        System.out.println("当前编号是" + count);
    }

    public static void main(String[] args){
        Dog.currentCount();
        Dog dog = new Dog("小黑");
        dog.displayInfo();

        Dog dog2 = new Dog(“旺财”);
        dog2.displayInfo();
        Dog.currentCount();
    }
}
  • 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

注意:
static 修饰的方法中 不能使用this和super关键字

static代码块 每次类加载时 只执行一次 而普通代码块 随着对象的创建 每次都会执行

执行顺序

静态代码块 --> 普通代码块 -->构造方法 --> 普通代码块 --> 构造方法 …

示例

public class Student{
    private static String country;
    private String name;
    static {
        country = "中国";
        System.out.println("Student类已经加载");
    }
    public Student(String name){
        this.name = name;
    }
    public void displayInfo(){
        System.out.println("我的名字是:" + name + ",国家是:" + country);
    }
    public static void main(String[] args){
        Student stu = new Student("张三");
        stu.displayInfo();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

20.final关键字

final修饰的变量(成员变量或局部变量)的值不能改变,即等同于常量,在定义时就必须要进行初始化,之后就再也不改变它的值了。

在后期版本中可以将声明和初始化分开执行 但是每个步骤都不能省略 且一旦赋值完成后 值不能修改

final修饰方法的参数叫最终参数。调用这个方法时给最终参数赋值之后,在方法体内再也不能改变它的值。

注意:

  • final修饰的方法不能被子类重写。
  • final修饰的类不能被继承

示例:

public class TestFinal {
    public static void main(String[] args) {
        T t = new T();
        //t.i = 100;
    }
}
final class T {
    final int i = 10;
    public final void m() {    //j = 11;    }
}
class TT extends T {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

21.单例模式

在项目中经常会遇到一些功能性的类,那么可以将该类制作成一个单例模式的类,全项目只适用这一个类来提供服务,节省内存开销。

常见的写法

  • 饿汉式
  • 懒汉式
  • 静态内部类
  • 线程锁
  • 双重锁
  • 枚举方式

示例 :

饿汉式:

//饿汉式  --- 简单易用  建议使用
public class  Singleton01{
    //私有化构造器
    private Singleton01(){}
    //提供私有静态变量 用来接收单例对象
    private  static Singleton01 s1 = new Singleton01();
    //提供静态公共访问方式
    public static Singleton01 getInstance(){
        return s1;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

懒汉式:
//懒汉式  对于饿汉式的加载时创建对象  懒汉式提出使用时创建对象  但存在线程安全问题
public class Singleton02{
    private Singleton02(){}
    private static Singleton02 s2 ;
    public static Singleton02 getInstance(){
        if(null == s2){
            s2 = new Singleton02();
        }
        return s2;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

22.多态

Java三大特性之一

多态: 同一个事物具有不同形态

对象的多态性

  • 父类的变量指向子类的对象 或 子类的对象赋值给父类的变量
  • 子类对象和父类对象之间的相互转换 称之为类型转换
  • 子类转父类 无需手动处理 该过程称之为向上转型
  • 但是父类转子类 必须强制类型转换 该过程称之为向下转型 且需要判定当前父类是否是由该子类伪装而成

判定一个父类是否指向某个具体的子类 需要使用关键字 instanceof 返回值为boolean

语法:对象变量名 instanceof 类名(或接口名)

class Animal {
  private String name;
  Animal(String name) {
      this.name = name;
  }
  public String getName(){
    return name;
  }
}
class Cat extends Animal {
  private String eyesColor;
  Cat(String n,String c) {
      super(n); eyesColor = c;
   }
  public String getEyesColor(){
    return eyesColor;
  }
}
class Dog extends Animal {
  private String furColor;
  Dog(String n,String c) {
      super(n); furColor = c;
  } 
  public String getFurColor() {
    return furColor;
  }
}

public class CastingTest{
    public static void main(String args[]){
      Animal a = new Animal("动物");
        Cat c = new Cat("猫","black");
        Dog d = new Dog("狗","yellow");

        System.out.println(a  instanceof  Animal);
        System.out.println(c  instanceof  Animal);
        System.out.println(d  instanceof  Animal);
      System.out.println(a  instanceof  Cat);

       //向上转型
       Animal an = new Dog("旺财","yellow");
      System.out.println(an.getName());   
      System.out.println(an.getFurColor());  //error!
      System.out.println(an  instanceof  Animal);   //true
      System.out.println(an  instanceof  Dog);  //true

      //向下转型,要加强制转换符
        //-- 为了安全起见,要加 instanceof 判断
      Dog d2 = (Dog)an;
        //Cat c2 = (Cat)an;
      System.out.println(d2.getFurColor());
    }
}
  • 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

多态的好处:
   使用多态 能够大大提升当前项目的扩展性 并提升代码的复用性
方法的动态绑定:
   随着程序的执行 父类的方法会获取当前真实指向的子类 并动态绑定至该子类的同名重写方法
动态绑定的要求:
  • 需要继承关系
  • 子类必须重写父类方法
  • 必须有多态性 – 父类变量指向子类对象
  • 父类变量 调用子类重写父类的方法

示例

class Animal {
  private String name;
  public Animal(String name) {this.name = name;
  }
  public void enjoy(){
    System.out.println("叫声......");
  }
}
class Cat extends Animal {
  private String eyesColor;
  public Cat(String n) {super(n);}

  public void enjoy(){
    System.out.println("喵~喵~");
  }
}
class Dog extends Animal {
  private String furColor;
  public Dog(String n) {super(n);}

  public void enjoy(){
    System.out.println("汪~汪~");
  }
}


class Lady{
  private String name;
  private Animal pet;
  public Lady(String name, Animal pet) {
    this.name = name; 
    this.pet = pet;
  }
  public void myPetEnjoy(){
      pet.enjoy();
  }
}

public class DynamicBindingTest {
    public static void main(String args[]){
      Cat c = new Cat("catname","blue");
        Lady l = new Lady(“张女士”, c);
        l.myPetEnjoy();
    }
}
  • 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

23.抽象类

当一个类中有某些方法不知道如何实现时,可以将该方法定义成抽象方法,只需使用abstract修饰符进行修饰

抽象方法形式:(没有方法体)

[访问修饰符] abstract 返回类型 方法名(参数列表);

在一个类中如果出现了抽象方法 则该类 必须是抽象类

声明抽象类语法

[访问修饰符] abstract class 类名{…… }

抽象类特征

  1. 有构造方法 但是无法调用
  2. 可以有以实现的方法
  3. 可以定义自己的成员属性
  4. 抽象方法的访问修饰符不能是private

用到抽象类其实就是面向父类编程的思想

抽象类的使用

无法直接通过new来创建对象 需要定义一个类
继承自抽象类 之后重写抽象类中所有未实现方法
如果有任意一个 抽象方法未实现 则当前类也必须定义为抽象类

示例:

abstract class Shape {  //形状类
    protected double length;    //长
    protected double width;    //宽
    public Shape(double length, double width){
        this.length = length;  this.width = width;
    }
    public abstract double area();    //计算面积
}
class Rectangle extends Shape {   //矩形
    Rectangle(final double num, final double num1) {        super(num, num1);    }
    public double area() {        return length * width;    }
}
class Triangle extends Shape{    //三角形
    Triangle(final double num, final double num1) {        super(num, num1);    }
    public double area() {        return length * width/2;    }
}
public class TestAbstract{
    public static void main(String[] args){}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

思考:

抽象类中可不可以没有抽象方法?
有抽象方法的类可以可以不是抽象类?

24.接口

接口就是某个事物对外提供的一些功能的申明 。相当于一个说明书

可以利用接口实现多态,同时也弥补Java单一继承的弱点

使用interface关键字定义接口。

JDK8之前,在接口中所有的方法必须都是抽象方法 且修饰符必须是 public abstract

所有的属性 都必须是静态常量 修饰符为 public static final

如果不定义修饰符 则JVM会在编译过程中自动添加 但是不能出现和语法修饰符相悖的修饰符

接口没有构造方法,所以不能被实例化(不能用来创建对象)。

//方法接口
public interface Runner{
    public void run();
}

//定义常量的接口
public interface Constants{
  public static final int COLOR_RED = 1;
  public static final int COLOR_GREEN = 2;
  public static final int COLOR_BLUE = 3;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

使用接口可以提升扩展性 降低程序的耦合度
使用接口 也可以认为是面向接口编程
接口的使用 用关键字 **implements** 实现接口。如:
>class Car implements Runner

每个类只能有一个父类,但可以实现多个接口。如果实现多个接口,则用逗号隔开接口名称,如下所示:

class Car implements Runner, Constants

一个类实现了一个接口,它必须实现接口中定义的所有方法,否则该类必须声明为抽象类。

接口可以继承自其它的接口,并添加新的常量和方法。接口支持多重继承

示例:

class Car implements Runner,Constants{  //实现两个接口
    public void run(){
        System.out.println("车颜色是:" + COLOR_RED);
        System.out.println("用四个轮子跑...");
    }
}

interface Animal extends Runner{   //接口的继承
    void breathe(); //呼吸
}

class Fish implements Animal{
    public void run(){
        System.out.println("颜色是:" + COLOR_BLUE);
        System.out.println(“游啊游...");
    }
    public void breathe(){
        System.out.println("冒气泡来呼吸");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

25.嵌套类

声明在类的内部的类称之为嵌套类(nested class)

//语法定义
[public] class OuterClass{
    ...
    [public|proteceted|private] [static] class NestedClass{
        ...
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

嵌套类主要可以分为两大类
  • 静态嵌套类:用static修饰的嵌套类
  • 非静态嵌套类:内部类

嵌套类在编译后会生成OuterClass$NestedClass.class类文件

内部类:

内部类作为外部类的一个成员存在,与外部类的成员变量、成员方法并列

示例:

class Outer {
    private int outer_i = 100;
    private int j = 123;
    public void test() {        System.out.println("Outer:test()");    }
    public void accessInner(){
        Inner inner = new Inner();//外部类中使用内部类也需要创建出它的对象
        inner.display();
    }
    public class Inner {
        private int inner_i = 100;
        private int j = 789;     //与外部类某个属性同名
        public void display() {
            //内部类中可直接访问外部类的属性
            System.out.println("Inner:outer_i=" + outer_i);  
            test();      //内部类中可直接访问外部类的方法
            //内部类可以用this来访问自己的成员
            System.out.println("Inner:inner_i=" + this.inner_i); 
            System.out.println(j);  //访问的是内部类的同名成员
            //通过“外部类.this.成员名”来访问外部类的同名成员
            System.out.println(Outer.this.j); 
        }
    }
}

//调用测试
public class MemberInnerClassTest {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.test();
        outer.accessInner();
        //在外部类以外的地方创建内部类的对象
        Outer.Inner inner = outer.new Inner();
        inner.display();
    }
}
  • 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

静态嵌套类:
静态嵌套类中可声明static成员或非静态成员,但只能访问外部类中的静态成员
[public ] class OuterClass{
    ...
    static class StaticNestedClass { //静态嵌套类   
        ...
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

方法类

  • 方法类(局部内部类):在方法体内声明的类

可访问它所在方法中的final参数和final局部变量

由于线程与并发的原因,局部内部类仅能用方法中的final参数和final局部变量。

public class Outer{
      public void test(final int y){
          final int x = 9;//可以访问final修饰的变量
            class FunctionInner{//方法类(局部内部类)
                public void show()
                {    
                    System.out.println(x+" "+y);  
                 }
            }
      FunctionInner f = new FunctionInner();//创建方法类对象
      f.show();//调用方法类中的方法
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

匿名内部类
没有声明名称的内部类
匿名类与方法类相同,仅能访问方法中final的局部变量。
public class AnnonymoseInnerClassTest {
    public static void main(String[] args) {
        (new AClass("redhacker") {
            public void print() {  //对父类的print方法进行覆盖
                System.out.println("the anonymose class print");
                super.print();  //调用父类中的print方法
            }
        }).print();  //调用覆盖后的print方法
    }
}

class AClass {
    private String name;
    AClass(String name) {        this.name = name;}
    public void print() { 
        System.out.println("SuperClass:The name = " + name);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

说明
方法类和匿名内部类最常见的用处就是实现一个接口,而这个接口通常都只会使用一次,所以不用专门用一个类去实现它。比起方法类,匿名类更加简洁。但当类体较长时,这时候就不适合使用匿名类了,而是应该使用方法类。

九、JAVA的异常处理

简介:异常是程序在运行期发生的不正常的事件,它会打断指令的正常执行流程

设计良好的程序应该在异常发生时提供处理这些不正常事件的方法,使程序不会因为异常的发生而阻断或产生不可预见的结果。

1. JVM底层异常处理机制

  • 抛出
    当程序中出现异常时 JVM会将当前异常自动生成一个异常对象 并将该对象抛出(throw)给JVM
  • 捕获(处理)
    JVM接收到一个异常对象时 会先寻找是否有能够处理该异常对象的处理类 如果有就就将该异常交给处理类来处理 该过程叫做捕获(catch)
    如果JVM没有找到异常的处理类 则该异常由JVM自行处理 结果程序异常中断退出

2. 异常分类

在程序中顶级的异常类型 叫做Throwable 所有的异常都是通过该类衍生而来 所以也称之为 万恶之源

分类如图

异常分类图
按照严重性来分

  • 错误(Error)
    JVM系统内部错误或资源耗尽等严重情况-属于JVM需要负担的责任
    这一类异常事件无法恢复或不可能捕获,将导致应用程序中断。
  • 异常(Exception)
    其它因为编程失误或偶然的外在因素导致的一般性问题。
    这类异常得到恰当的处理时,程序有机会恢复至正常运行状况。

JVM只能处理Exception,而对Error无能为力。

从编程角度分

  • 非受检(Unchecked)异常:编译器不要求强制处理的异常。
    一般是指编程时的逻辑失误。是程序员应该积极避免其出现的异常
    java.lang.RuntimeException及它的子类都是非受检异常:
    1. 错误的类型转换:java.lang.ClassCastException
    2. 数组下标越界:java.lang.ArrayIndexOutOfBoundsException
    3. 空指针访问:java.lang.NullPointerException
    4. 算术异常(除0溢出):java.lang.ArithmeticException
  • 受检(checked)异常:编译器要求必须处理的异常。
    指的是程序在运行时由于外界因素造成的一般性异常。
    所有的受检异常都来自Exception
    1. 没有找到指定名称的类:java.lang.ClassNotFoundException
    2. 访问不存在的文件:java.io.FileNotFoundException
    3. 操作文件时发生的异常:java.io.IOException
    4. 操作数据库时发生的异常:java.sql.SQLException
    5. 网络操作时发生的异常:java.net.SocketException

最直观判定受检非受检 看程序是否强制进行异常捕获


3. 异常的捕获语法

异常的抛出默认情况下是遇到错误时自动抛出给JVM

捕获操作需要程序员手动处理,语法构成:

try{

	......	//可能产生异常的代码

}catch(ExceptionName1 e ){

	......	//异常的处理代码

}catch(ExceptionName2 e ){

    ...... //异常的处理代码

}finally{

    ......//无论如何都会执行的语句

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

try块中的内容越少越好 因为try中的代码执行开销较大

对于一个try块 可以同时捕获多个异常类型 即 使用多个catch进行捕获

示例:

try{
    String a = null;
    a.split(",");//空指针异常
    int[] x = {2,4,6,7}
    x[4] = 10;//数组下标越界
}catch(NullPointException e){
    //获取异常的信息
    System.out.println(e.getMessage());
    //打印异常的堆栈信息
    e.printStackTrace();
    //输出自定义信息
    System.out.println("不能为空");
}catch(ArrayIndexOutOfBoundException e){
    e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

注意:
1. catch 中的异常类型 如果没有继承关系 则和顺序无关
2. 如果有继承关系 则必须先声明小的异常类型 再声明大的异常类型 否则编译报错
3. 在try块中一旦产生一个异常 则从该异常开始到try结束的代码均不会执行 而是直接跳转至该异常的捕获处理代码块
4. 如果没有异常产生,所有的catch段的代码都会被忽略不执行
5. 受检异常必须try和catch 否则编译错误
6. finally段的代码无论是否发生异常都执行
示例
public class TestException2{
    public int calculate(int num1, int num2)  {
        int result = num1 / num2; 
        return result;
    }
    public static void main(String[] args){
    	TestException2 test = new TestException2();
    	try{
    		int i = test.calculate(100, 10);
    		System.out.println(i);
    	}catch(Exception e){
    		System.out.println("父类异常..");
    	}catch(ArithmeticException e){
    		// 错误–不能到达的代码
    		Sysetm.out.println("出异常啦");
    		e.printStackTrace();
    	}finally{
           System.out.println(finally语句块是始终要执行的");
      }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

4. 抛出异常的语法

throw关键字用在方法代码中主动抛出一个异常

如果方法代码中自行抛出的异常是受检异常,则这个方法要用throws关键字声明这个异常。

throws用来声明一个方法可能会抛出的所有异常。跟在方法签名的后面。

如果一个方法声明的是受检异常,则在调用这个方法之处必须处置这个异常(谁调用谁处理),继续用throws向上声明。

注:重写一个方法时,它所声明的异常范围不能被扩大

import java.io.*;
import java.sql.*;
class E {
    public String[] createArray(int length) {    //error!!
        if (length < 0) {        throw new Exception("数组长度小于0,不合法");
        } else {            return new String[length];        }
    }
    public void test() {        createArray(10);    }
    public void readFile() throws IOException,SQLException { }
}
/*class EE extends E {
    public void readFile() throws Exception { }
}*/
public class TestException3 {
    public static void main(String[] args) {}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

5. 自定义异常

创建自定义异常

  • 继承自Exception 或其子类。
  • 继承自RuntimeException或其子类。
    示例
public class MyException extends Exception {
    public MyException() {        super();    }
    public MyException(String msg) {        super(msg);    }
    public MyException(Throwable cause) {       super(cause);    }
    public MyException(String msg, Throwable cause) {     super(msg, cause); }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

使用自定义异常
示例:
public String[] createArray(int length) throws MyException {
    if (length < 0) {    
        throw new MyException("数组长度小于0,不合法");
    } 
    return new String[length]; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

6. 异常实践

观察抛出的异常的名字和行号很重要。

调用内置类库中某个类的方法前,阅读其API文档了解它可能会抛出的异常。然后再据此决定自己是应该处理这些异常还是将其加入throws列表。

应捕获和处理那些已知如何处理的异常,而传递那些不知如何处理的异常。

尽量减小try语句块的体积。

在处理异常时,应该打印出该异常的堆栈信息以方便调试。

十、多线程

1. 进程和线程的区分

进程(Process):每个独立执行的程序称为进程。

​多进程: 在操作系统中能同时运行多个任务(程序)

线程(Thread):程序内部的一条执行路径。

​多线程: 在同一应用程序中多条执行路径同时执行

线程和进程的区别

  • 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换开销大
  • 同一进程内的多个线程共享相同的代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小

2. 线程的类型及创建方式

在Java中有三种线程类型

继承Thread类方式、并重写run方法

  • Thread类本身是实现了Runnable接口的一个类,内部提供了很多对线程的操作方法

示例:

public class MyThread extends Thread{
    //重写run方法
    @Override
    public void run(){
        //多线程执行代码
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

实现Runnable接口方式、实现run方法
示例:
public class MyRunnable implements Runnable{
    @Override
    public void run(){
        //多线程执行代码
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

实现Callable接口,重写call方法 并使用Future来处理返回值
public class Mycallable<T> implements Callable<T>{
    @Override
    public T call() throws Exception{
        //执行多线程内容
        return T;//返回最终值
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3. Thread类中常用的方法

  • public void start() //启动该线程 注意千万不要调用run方法启动
  • public static Thread currentThread() //返回对当前线程对象的引用
  • public ClassLoader getContextClassLoader() //返回该线程的上下文ClassLoader
  • public final boolean isAlive() //线程是否处于活动状态
  • public Thread.State getState() //返回该线程的状态
  • public final String getName() //返回该线程的名称
  • public final void setName(String name) //改变线程名称
  • public final void setDaemon(boolean on) //将该线程标记为守护线程或用户线程

线程的启动

线程可以通过Thread类来进行启动,如果线程较多可以交给线程池来管理(后面再说)。

通过Thread来开启线程

public class StartThread{
    public void static main(String[] args){
        //参照上面描述的三种线程来分别启动
        //1、继承Thread
        MyThread mth = new MyThread();
        mth.start();
        
        //2、实现Runnable接口
        MyRunnable mrun = new MyRunnable();
        Thread rth = new Thread(mrun);
        rth.start();
        
        //3、实现Callable接口
        Mycallable<Integer> mcall = new Mycallable<>();
        FutureTask<Integer> ft = new FutureTask<>(mcall);
        Thread cth = new Thread(ft);
        cth.start();
        
        //3中的取值问题
        Integer num = ft.get();//Future的get方法会阻塞线程 直到接收到值
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

4. 用户线程和守护线程

用户线程:默认创建的都是用户线程 ,用于完成指定任务的一个线程。

守护线程:在没有其它用户线程在运行时会自动退出

  • 用于服务用户线程的。
  • ​通过Thread类的setDaemon(true)来完成,在开启次线程之前设置。
  • 无论守护线程中的内容是否执行完毕,只要用户进程全部结束,则守护线程也会结束
  • main线程不能设置为Daemon线程
  • Java垃圾回收线程就是一个典型的守护线程

5.线程的状态

对于线程来说,有6中状态,在Java中专门对6中状态定义了一个枚举类型 Thread.State
从生命周期来说

  • NEW 线程被创建(new)出来,但并未调用start方法开启
  • RUNNABLE 线程被start方法唤醒,准备或者正在执行
  • TERMINATED 线程销毁状态 处于改状态的线程已经死亡

从执行或阻塞的角度来说

  • BLOCKED 阻塞状态 ,通过线程同步实现(加锁)阻塞,一般当锁住的内容执行完毕后会自动恢复到RUNNABLE状态
  • WAITING 等待阻塞状态,通过线程调用wait() join()等方法来实现的 当其他线程进行唤醒是notify() notifyAll() 方法时 会恢复到RUNNABLE状态
  • TIMED_WAITING 延时等待阻塞 当前线程会根据指定的时间来阻塞线程 当指定时间到达时 线程自动恢复到RUNNABLE状态

示意图

线程状态图

6.线程调度器

程调度器,根据系统分为两种:

  • 抢占式模型:根据多个线程的优先级、饥饿时间(已经多长时间没有被执行了),计算出每个线程的总优先级,线程调度器就会让总优先级最高的这个线程来执行。

  • 分时间片模型:所有线程排成一个队列。线程调度器按照它们的顺序,给每个线程分配一段时间(毫秒级)。

    • 如果在时间片结束时线程还没有执行完,则它的执行权会被调度器剥夺并分配给另一个线程;

    • 如果线程在时间片结束前阻塞或结束,则调度器立即进行切换。


7. 线程优先级

为了表示不同线程对操作系统和用户的重要性,Java定义了10个等级的优先级。
•用1-10之间的数字表示,数字越大表明线程的级别越高
•三个静态成员变量:MIN_PRIORITY、MAX_PRIORITY和NORMAL_PRIORITY(默认值)

优先级的操作
void setPriority(int newPriority); //要在start()前调用

注意:不要依赖优先级来控制线程的执行顺序。


8. 控制线程的命令

线程睡眠

  • Thread.sleep(long millis) throws InterruptedException 方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。

线程让步:

  • Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给别的线程。

示例:

public class ThreadYieldTest {
    public static void main(String[] args) {
        System.out.println("主线程:" + Thread.currentThread().getName());
        Thread thread1 = new Thread(new MyThread2());
        thread1.start();
        Thread thread2 = new Thread(new MyThread2());
        thread2.start();
    }
}
class MyThread2 implements Runnable{
    public void run() {
        for(int i = 0; i < 100; i++){
            System.out.println(Thread.currentThread().getName()+ ":" + i);
            if(i % 10 == 0){        Thread.yield(); //线程让步            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

线程加入:

  • void join() throws InterruptedException方法在当前线程中调用另一个线程的join()方法,则当前线程转入WAITING状态,直到另一个线程运行结束,当前线程再由阻塞转为就绪状态。

示例:

public class ThreadJoinTest {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyThread3());
        thread1.start();
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            if (i == 50) {
                try {    thread1.join();    //线程合并   } catch (InterruptedException e) {    e.printStackTrace();    }
            }
        }
    }
}
class MyThread3 implements Runnable{
    public void run() {
        for (int i = 1; i <= 50; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {    Thread.sleep(100);  } catch (InterruptedException e) {     e.printStackTrace();    }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

9. 线程的停止

线程停止指定是让长睡眠或者死循环的线程终止退出

通过改变逻辑标识可以结束死循环的线程

public class ThreadEndTest {
    public static void main(String[] args) {
        SomeThread some = new SomeThread();
        Thread thread1 = new Thread(some);
        thread1.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {        e.printStackTrace();    }
        some.terimate();
    }
}
class SomeThread implements Runnable {
    private boolean flag = true;
    public void terimate() {        this.flag = false;    }
    public void run() {
        while (flag) {
            System.out.println("...run...");
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

通过interrupt()方法来终止长睡眠 终止睡眠的方法通过异常来实现
public class ThreadEnd2Test {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new SomeThread2());
        thread1.start();
        try {        Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
        thread1.interrupt();
    }
}
class SomeThread2 implements Runnable{
    public void run(){
        System.out.println("sleep.........");
        try {
            Thread.sleep(9999);
        } catch (InterruptedException e) {
            System.out.println(" interrupted...");
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

如果程序因为输入/输出的等待而阻塞,基本上必须等待输入/输出的动作完成才能离开阻塞状态。无法用interrupt()方法来使得线程离开run()方法,要想离开,只能通过引发一个异常。


10. 线程同步

原理:
多个线程需要共享对同一个数据的访问。如果每个线程都会调用一个修改共享数据状态的方法,那么,这些线程将会互相影响对方的运行。

在Java语言中,引入对象互斥锁的概念,保证共享数据操作的完整性。

每个对象都对应一个可称为“互斥锁”的标记,这个标记保证在任一时刻,只能有一个线程访问对象。

多线程同步的实现

synchronized同步代码块
当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。

synchronize可以用来同步方法 :

public synchronized void sale(){}
  • 1
  • 2
  • 3

也可以用来修饰一个代码块
synchronized(obj){
    需要同步的代码;
}
  • 1
  • 2
  • 3

synchronized在同步时需要指定一个唯一的对象,此时才能实现当前的线程同步操作,如果锁的对象不唯一,则不构成互斥锁。建议使用当前类的class对象

说明: synchronized修饰方法时 其锁的对象时 this 当锁this时一定要注意当前是否只有一个Runnable对象

//使用synchronized加锁
class MyThread implements Runnable{

    private int num = 1000;
    @Override
    public void run() {

        while(true){
            synchronized (MyThread.class){
                if(num > 0){
                    System.out.println(Thread.currentThread().getName()+"--当前数字减一,结果为:"+(--num));
                }
            }
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

​使用Lock来同步代码
显式加锁:JDK5.0。锁的粒度越细越好

Lock是JDK5推出的一个新锁 使用灵活 轻量级锁 常用其实现类ReentrantLock来获取实例

示例:

private static final Lock lock = new ReentrantLock();  //创建Lock实例
lock.lock();  //获取锁
...
lock.unlock();  //释放锁
  • 1
  • 2
  • 3
  • 4

上面synchronized的锁可以用Lock替换为如下代码
//使用Lock加锁
class MyThread implements Runnable{
    private int num = 1000;
    private Lock lock = new ReentrantLock();//创建出一个锁对象

    @Override
    public void run() {
        while(true){

                lock.lock();
                if(num > 0){
                    System.out.println(Thread.currentThread().getName()+"--当前数字减一,结果为:"+(--num));
                }else{
                    break;
                }
                lock.unlock();

            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

多线程安全问题
当run()方法体内的代码操作到了成员变量(共享数据)时,就可能会出现多线程安全问题(线程不同步问题)。

编程技巧:在方法中尽量少操作成员变量,多使用局部变量


11. 线程交互

线程等待

Object类中的wait() throws InterruptedException 方法,导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()唤醒方法。

线程唤醒

Object 类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的。
Object类中的notifyAll()方法,唤醒在此对象监视器上等待的所有线程。

这三个方法只能在被同步化(synchronized)的方法或代码块中调用
示例:生产者-消费者模式


12. 使用Java Timer调度任务

java.util.Timer类提供了基本的调度功能。这个类允许你调度一个任务(通过java.util.TimerTask子类定义)按任意周期运行。

java.util.Timer类:代表一个计时器

  • public void schedule(TimerTask task, long delay, long period)
    • 重复的以固定的延迟时间去执行一个任务
  • public void scheduleAtFixedRate(TimerTask task, long delay, long period)
    • 重复的以固定的频率去执行一个任务
  • public void cancel()
    • 终止此计时器,丢弃所有当前已安排的任务

java.util.TimerTask抽象类:表示一个计时器任务

  • public abstract void run()
    • 此计时器任务要执行的操作
  • public boolean cancel()
    • 取消此计时器任务

示例

public class TimmerTest {


    public static void main(String[] args) {
        Timer t = new Timer();//将Timer比作Thread  需要在Timer中运行TimeTask(Runnable)
        //当前任务延时多久后第一次执行 第二个参数代表当前任务每隔多久执行一次
        Date d = new Date();
        long l = d.getTime() + 5000;
        Date dd = new Date(l);
//        t.schedule(new MyTime(),dd,2000);
        t.scheduleAtFixedRate(new MyTime(),dd,2000);
        System.out.println("主线程执行至此");
    }
}

class MyTime extends TimerTask{

    @Override
    public void run() {
        System.out.println("我被打印了");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

13. 线程池

Thread创建线程的缺点

  1. 每次new Thread新建对象性能差。
  2. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
  3. 缺乏更多功能,如定时执行、定期执行、线程中断。

线程池的作用
  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池结构
从JDK5开始,Java内置支持线程池,其继承架构

在这里插入图片描述
一些重要的类

ExecutorService:真正的线程池接口。
ScheduledExecutorService能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutorExecutorService的默认实现。
ScheduledThreadPoolExecutor继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

内置线程池
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池,都是调用了TreadPoolExecutor构造器创建的
线程工厂描述
newSingleThreadExecutor创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照指定顺序(FIFO, LIFO,优先级)执行
newFixedThreadPool创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
newCachedThreadPool创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程, 当任务数增加时,此线程池又可以智能的添加新线程来处理任务。 此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
newScheduledThreadPool创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求

ThreadPoolExecutor常用方法
方法名描述
execute()这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行
submit()这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,最终利用了Future来获取任务执行结果
shutdown()不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow()立即尝试终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务 – 最终目的打断线程的长睡眠

线程池的执行流程
当任务提交给ThreadPoolExecutor 线程池中,先检查核心线程数是否已经全部使用
如果没有交由核心线程去执行任务
如果核心线程数已经全部占用,则将任务添加到队列里面
如果队列已经占满,比较当前线程池的中线程的数量是不是超过maximumPoolSize
如果没有超过则创建线程去执行,也就是说线程池最多可以接受多少任务呢?就是maximumPoolSize+队列的大小。
当线程池中的线程的数量大于corePoolSize数量, 有空闲线程则执行回收,回收时间是keepAliveTime,单位是unit,都是初始化的时候设置的。

十一、泛型

泛型:就是在定义类、接口、方法、方法参数或成员变量时,指定它的操作对象的数据类型为一个参数。

在具体使用类、接口、方法、方法参数或成员变量时,将这个参数用具体的某一数据类型来代替。

泛型的好处:它在编译时进行类型安全检查,并且在运行时所有的转换都是强制的、隐式的。提高了代码的重用率。


1. 泛型的分类

  • 类(接口)泛型

  • 方法泛型

类(接口)泛型定义语法

class 类名<类型参数的名称> { …}

interface 接口名<类型参数的名称> { …}

说明:类型参数的名称建议使用单个大写字母。如常用的名有:

  • E:表示集合中的元素类型。
  • K:表示“键值对”中的键的类型。
  • V:表示“键值对”中的值的类型。
  • T:表示其它所有的类型。

当某个类的父类是泛型类时:这个子类要把类型参数传递给父类;也可以把子类定义成特定于指定类型的,这个子类就不再是泛型类了。

class SuperClass<T>{
    private T o;
    public SuperClass(T o){        this.o = o;    }
    public String toString(){        return "T:" + o;    }
}
/** 泛型类的继承 */
class SubClass <T> extends SuperClass<T>{
    public SubClass(T o){        super(o);    }
}
class SpecialSubClass extends SuperClass<String>{
    public SpecialSubClass(String o){        super(o);    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

具体子类要把类型参数传递给所实现的接口;也可以把具体子类定义成特定于指定类型的,示例:
interface MyGenericTypeInterface<T>{  }
class MyImplement<T> implements MyGenericTypeInterface<T>{}
class IntegerImplement  implements MyGenericTypeInterface<Integer>{ }
  • 1
  • 2
  • 3

方法泛型定义
泛型类中的任何实例方法本质上都是泛型方法。只有**静态方法**需要显式定义成泛型方法,定义语法:

访问控制符 [修饰符] <类型参数列表> 返回值类型 方法名(参数列表)

示例

public static <T extends Number> void max(T... args){
        T temp = (T)Integer.valueOf(0);
        for(T t : args){
            if(t.doubleValue() > temp.doubleValue()){
                temp = t;
            }
        }
        System.out.println(temp);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2. 泛型对边界的定义

普通泛型(默认):无界泛型

有界泛型(手动设置):
泛型只支持上界泛型 使用关键字 extends 表示当前的类型只能是某个类或器子类类型,示例:

class Statistics<T extends Number> {
    private T[] arrs;
    public Statistics(T[] arrs) {
        this.arrs = arrs;
    }
    public double count() {  //计算数组中元素数值的总和
        double sum = 0.0;
        for (int i = 0; i < arrs.length; ++i) {
           sum += arrs[i].doubleValue();    
        }
        return sum;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

3. 通配符

当使用泛型类或接口声明属性、局部变量、参数类型或返回值类型时,可使用通配符来代替类型参数

通配符和泛型的区别

泛型:通常只在某个是个的固定类型
通配符: 在当前不能确定该类型是什么


通配符语法:<?>

通配符的界限

默认通配符为无界通配符

可手动设置边界:

  • 通配符上界:
    • <? extends Number>表示这个类型参数必须是Number类或其子类的实例。
  • 通配符下界:
    • <? super Integer>表示这个类型参数必须是Integer类或其父类的实例。

4. 擦除

JDK1.5以前的版本中没有泛型,为了保证对以前版本的兼容,Java采用了被称为擦除的方式来处理泛型。

当Java代码被编译成字节码时,泛型类型的定义信息会被删除(擦除),而使用界限类型或Object来代替泛型参数。

在使用泛型类型时,也会用相应的强制转换(由类型参数来决定)以维持与类型参数的类型兼容。


5. 泛型的局限

  1. 不能使用基本类型的类型参数

因为,在擦除时基本类型无法用Object类来代替。可以使用基本类型的包装类来代替它们。

  1. 静态成员无法使用类型参数

因为静态成员独立于任何对象,是在对象创建之前就已经存在了,此时,编译器根本还无法知道它使用的是哪一个具体的类型。

  1. 不能使用泛型类异常

Java代码中不能抛出也不能捕获泛型类的异常。

  1. 不能使用泛型数组:这是Java语法的规定。

Generic arr[] = new Generic[10];

  1. 不能实例化参数类型对象:也就是说,不能直接使用泛型的参数类型来构造一个对象。

public class A{ T a = new T(); //编译报错}


十二、集合(Collection)

集合:能够动态存放多个数据的存储结构,Java提供的一个公共API,存在于java.util包中

整个集合的架构如图:
在这里插入图片描述
主要有两个分支:

  • Collection 每次只能存入一个值 具体的存储性质和不同的子接口有关

    • Set 存入的元素无序且不重复 常用实现类有

      1. HashSet 无序不重复的集合
      2. LinkedHashSet 有序不重复的集合 有序是通过链表方式实现
    • List 存入的元素有序且可以重复 常用实现类有

      1. ArrayList 底层以数组方式实现 特点 读取效率高 但修改的效率较低 经常使用ArrayList
      2. LinkedList 底层以双向链表实现实现 特点 修改的效率高 但读取效率较低
  • Map 每次需要存入一个键值对

    • 常用实现类 HashMap

1. Collection

Collection是所有单个存储元素的一个顶级类 常用方法:

方法名解释
int size();返回此collection中的元素数。
boolean isEmpty()判断此collection中是否包含元素。
boolean contains(Object obj);判断此collection是否包含指定的元素。
boolean contains(Collection<?> c);判断此collection是否包含指定collection中的所有元素。
boolean add(E element);向此collection中添加元素
boolean addAll(Collection<? extends E> c);将指定collection中的所有元素添加到此collection中
boolean remove(Object element);从此collection中移除指定的元素。
boolean removeAll(Collection<?> c);移除此collection中那些也包含在指定collection中的所有元素。
void clear();移除些collection中所有的元素。
boolean retainAll(Collection<?> c);仅保留此collection中那些也包含在指定collection的元素。
Iterator iterator();返回在此collection的元素上进行迭代的迭代器。
T[] toArray(T[] arr);把此collection转成数组。

Iterator
Iterator 是Collection的父级接口 内部提供了对于Collection类型的集合的遍历方式
常用方法
  • hasNext() 查看当前指针的下一个位置是否有元素 如果有返回true 否则返回false
  • next() 移动指针到下一个元素 并获取该元素 返回给调用者
  • remove() 移除当前遍历的指针

自从JDK5开始,所有Iterator方式实现的变量都可以使用 forEach方式来进行遍历


Set接口
   Set接口是Collection的一个子接口类型,它的特点是无序且不重复
  Set集合与数学中“集合”的概念相对应
判定Set的元素是否重复的方法:
  1. 判定该对象的hashCode()方法返回的值是否相等 如果相等即判定当前两个对象相等 直接跳过该对象的存储步骤 如果不等则直接存入元表中
  2. 当hashCode() 返回的值相等时 再利用对象的equals()方法来判定当前两个对象是否相等 如果相等则跳过该存储不走 如果不等 则会将该存储的模块转换为链表形式进行存储 在JDK8后如果链表的长度达到8 则会将链表转换成一个红黑树结构进行存储 但此种方式很少会触发

根据对象的哈希码值计算出它的存储索引,在散列表的相应位置(表元)上的元素间进行少量的比较操作就可以找出它。

Set要注意的事项

  1. Set系的集合存、取、删对象都有很高的效率。
  2. 对于要存放到Set集合中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法以实现对象相等规

Set常用子接口

  • HashSet 不保存元素的存入顺序
    • 底层是使用Hash表的形式进行存储数据的,所以读取效率较高,直接根据hash值读取对应的数据
  • LinkedHashSet 底层是链表 保证存入顺序 但依然不能重复
    • 需要有序且不重复时使用 但效率略低

List接口

实现List接口的集合类中的元素是有序的,且允许重复

List集合中的元素都对应一个整数型的序号记载其在集合中的位置,可以根据序号存取集合中的元素。

JDK API所提供的List集合类常用的有:

  • ArrayList

  • LinkedList

常用方法

public Object get(int index) 返回列表中的元素数

public Object add(int index, Object element); 在列表的指定位置插入指定元素.将当前处于该位置的元素(如果有的话)和所有后续元素向右移动

public Object set(int index, Object element) ; 用指定元素替换列表中指定位置的元素

public Object remove(int index) 移除列表中指定位置的元素

public ListIterator listIterator() 返回此列表元素的列表迭代器

remove(Object obj) 如果obj存在则直接移除

remove(int index) 移除指定位置处的元素 并将元素返回给调用者

ArrayList

使用数组结构实现的List集合

在这里插入图片描述
优点:

  • 对于使用索引取出元素有较好的效率

  • 它使用索引来快速定位对象

缺点:

  • 元素做删除或插入速度较慢

因为使用了数组,需要移动后面的元素以调整索引顺序


LinkedList
LinkedList是使用双向链表实现的集合。
LinkedList新增了一些插入、删除的方法。

如图:

在这里插入图片描述
优点:

  • 对频繁的插入或删除元素有较高的效率

  • 适合实现栈(Stack)和队列(Queue)

缺点:

  • 读取数据只能某一方向依次遍历查找 效率较低

2. Map

在集合中,Map和Collection是一个并列的关系,它内部只能存储键值对(key-value)。且key的值不能重复且是无序的,value无要求。

常用实现类

  • HashMap 内部使用哈希表对 “键-值”映射对 进行散列存放。

    • 使用频率最高的一个集合。
  • LinkedHashMap 是HashMap的子类

    • 使用哈希表存放映射对

    • 用链表记录映射对的插入顺序。

Map实现类中存储的“键-值”映射对是通过键来唯一标识,Map底层的“键”是用Set来存放的。


所以,存入Map中的映射对的“键”对应的类必须重写hashcode()和equals()方法。常用String作为Map的“键”。


常用方法
  • Object put(Object key, Object value); //将指定的“键-值”对存入Map中
  • Object get(Object key);  //返回指定键所映射的值
  • Object remove(Object key); //根据指定的键把此“键-值”对从Map中移除。
  • boolean containsKey(Object key); //判断此Map是否包含指定键的“键-值”对。
  • boolean containsValue(Object value); //判断是否包含指定值的“键-值”对。
  • boolean isEmpty();  //判断此Map中是否有元素。
  • int size();  //获得些Map中“键-值”对的数量。
  • void clear();  //清空Map中的所有“键-值”对。
  • Set keySet();   //返回此Map中包含的键的Set集。
  • Collection values();  //返回此Map中包含的值的Collection集。
  • Set<Map.Entry<K,V>> entrySet(); //返回此Map中包含的“键-值”对的Set集。

3. 遗留类

Vector

旧版的ArrayList,它大多数操作跟ArrayList相同,区别之处在于Vector是线程同步的。
它有一枚举方式可以类似Iterator进行遍历访问,也可以使用forEach或者Enumcation(JDK1.0用来遍历集合,后面不推荐使用可以用Iterator替代)


Stack

  • Stack类是Vector的子类,它是以后进先出(LIFO)方式存储元素的栈。线程同步的。

Stack类中增加了5个方法对Vector类进行了扩展

方法名方法介绍
public E push(E item)把元素压入堆栈顶部
public E pop()移除堆栈顶部的对象,并作为此方法的值返回该对象
public E peek()查看堆栈顶部的对象,但不从堆栈中移除它。
public boolean empty()测试堆栈是否为空。
public int search(Object o)返回对象在堆栈中的位置,以1为基数。

Hashtable

旧版的HashMap,JDK1.0中创建的,在JDK2.0中使用HashMap替换Hashtable,本身具有线程同步的功能。

使用方式和HashMap大致一样。

Properties

Hashtable的一个子类,用于获取当前项目中的配置文件中的信息,该类没有泛型 所有的key-value均为String类型

Properties类表示了一个持久的属性集。Properties可保存在流中或从流中加载。属性集中每个键及其对应值都是一个字符串。

不建议使用 put 和 putAll 这类存放元素方法,应该使用 setProperty(String key, String value)方法,因为存放的“键-值”对都是字符串。类似取值也应该使用getProperty(String key)。

示例:

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class PropertiesTest {
    public static void main(String[] args) {
        InputStream is = Thread.currentThread()
			    .getContextClassLoader()
                                                .getResourceAsStream("config.properties");
        Properties prop = new Properties();
        try {
            prop.load(is);
        } catch (IOException e) {        e.printStackTrace();    }
        String name = prop.getProperty("name");
        String pwd = prop.getProperty("pwd");
        System.out.println(name + ", " + pwd);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

4. 排序接口

TreeSet & TreeMap

在集合中存在一个自动排序的存储集合,底层使用红黑树进行数据存储。

只要存入该集合中的数据都会按照排序规则进行默认排列

对于数据类型的包装类 以及 String类型 默认都实现了排序接口 会按照字典顺序进行数据排列

如果希望元素放置在TreeSet或者TreeMap中 则该对象必须实现比较接口Comparable 否则在存放时会抛出异常


Compare
比较接口,所有需要实现排序规则的对象都应该实现该接口,表示该类时一个可比较类。
实现Comparable时需要实现其CompareTo(Object o) 方法
如果按照升序进行规则判定:
  • a.age > b.age 需返回 正整数
  • a.age < b.age 需返回 负整数
  • a.age == b.age 需返回 0

按照降序排列则只需将正整数和负整数的位置交换即可

优缺点

优点:无序另外设置排序规则,在可排序的方法中可以直接进行排

缺点:在一个项目中如果实现Comparable 则该对象默认只能有一种排序方式

public class Student  implements Comparable{
    private int id;         //编号
    private String name;   //姓名
    private double score;  //考试得分
    public Student(){}
    public Student(int id, String name, double score) {
        this.id = id;        this.name = name;        this.score = score;
    }
    //省略所有属性的getter和setter方法

    //实现compareTo方法,按成绩升序排序
    public int compareTo(Object o) {
        Student other = (Student)o;
        if(this.score > other.score){            return 1;
        }else if(this.score < other.score){            return -1;
        }else{            return 0;        }
    }
    public boolean equals(Object obj) {}
    public int hashCode() {}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

Comparator
   比较器接口,可以自定义一个类来实现Comparator接口,并实现其compare(Object o1,Object o2)方法

比较规则和Comparable中的规则一致。


优缺点
  • 优点:在一个项目中可以按照不同的需求定义不同的比较器,在具体需求出使用精准的比较规则来进行数据排序
  • 缺点:在使用时需要手动告知排序接口排序规则(设置参数),如果仅使用一次则见识使用匿名内部类或Lambda表达式来实现

当一个对象即实现了Comparable接口 用有Comparator类型的排序类 则如果显示调用Comparator的排序类 会覆盖Comparable的实现规则 否则直接使用Comparable的默认排序

示例:

/** 学生考试得分比较器 */
class StudentScoreComparator implements Comparator<Student>{
    public int compare(Student o1, Student o2) {
        if(o1.getScore() > o2.getScore()){            return 1;
        }else if(o1.getScore() < o2.getScore()){            return -1;
        }else{            return 0;        }
    }
}
/** 学生姓名比较器 */
class StudentNameComparator implements Comparator<Student>{
    public int compare(Student o1, Student o2) {
        return o1.getName().compareTo(o2.getName());
    }
}
 //使用不同比较器的排序集合
Set<Student> set = new TreeSet<Student>(new StudentScoreComparator());
Set<Student> set2 = new TreeSet<Student>(new StudentNameComparator());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

5. Collections

针对于集合专门设计的工具类,提供了集合常用的功能,方便开发者提升开发效率。

常用方法

  • void sort(List list) 根据元素的自然顺序 对指定List列表按升序进行排序。List列表中的所有元素都必须实现 Comparable 接口。

  • void shuffle(List list) 对List列表内的元素进行随机排列

  • void reverse(List list) 对List列表内的元素进行反转

  • void copy(List dest, List src) 将src列表内的元素复制到dest列表中

  • List synchronizedList(List list) 返回指定列表支持的同步(线程安全的)列表


6. 集合类的线程安全

集合类大多数默认都没有考虑线程安全问题,程序必须自行实现同步以确保共享数据在多线程下存取不会出错:

  • synchronized(list){ list.add(…); }
    • 用java.uitl.Collections的synchronizedXxx()方法来返回一个同步化的容器对象
  • List list = Collections.synchronizedList(new ArrayList());
    • 这种方式在迭代时仍要用synchronized修饰
      示例:

List list = Collections.synchronizedList(new ArrayList());
...
synchronized(list) {
        Iterator i = list.iterator();         while (i.hasNext()) {
            foo(i.next());
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

从JDK5.0开始,提供了一个concurrent包,专门提供了一些在并发操作中好用的对象。跟集合相关的常用对象有:
  • ConcurrentHashMap

  • CopyOnWriteArrayList

  • CopyOnWriteArraySet

以上的对象在同步性和效率性上进行了平衡,只在修改元素时加锁,在读取元素时不上锁,提高读取效率,适合使用于并发性强且不长修改的数据。

十三、字符集

1. 字符乱码问题

使用字符流读取不同编码类型的字符时,会出现乱码问题

  • 解码:字节->字符
  • 编码:字符->字节

JVM默认的字符集:跟你的源文件的编码有关。

  • Charset cs = Charset.defaultCharset(); 获取项目默认字符集

2. 常用字符集概述

字符集(charset):是一套文字符号及其编码的集合。

  1. ASCII 美式字符集
    单字节7位编码。共可表示127个字符 aà 97

    大小写英文字母、阿拉伯数字和标点符号以及33个控制符号
  2. ISO-8859-1(Latin1) 西欧字符集
    单字节8位编码。共可表示255个字符 aà97

    与ASCII编码兼容
  3. GB2312
    双字节编码

    包括对简体中文字符的编码,一共收录了7445个字符,包括6763个汉字和682个其他符号,未收录繁体中文汉字和一些生僻字。

    与ISO-8859-1字符编码兼容
    早期中文标准
  4. GBK
    双字节编码

    完全兼容GB2312,收录了21886个字符,包括繁体和简体中文字符

    GB2312的扩展。使用广泛
  5. GB18030
    2或4字节编码

    向下兼容 GBK 和 GB2312 标准。收录了27484个汉字

    中文新标准。目前使用还不广泛

3. Unicode字符集

Unicode字符集包括全世界所有语言的文字和符号。
它为每种语言中的每个字符设定了统一并且唯一的二进制编码



Unicode的常用编码实现方案:

  • UTF-16:

    用固定4字节存储一个Unicode字符。

    Java和windows xp内使用
  • UTF-8:
    用1~6个字节存储一个Unicode字符。

    互联网和UNIX/Linux广泛支持, MySQL Server内部也使用

    英文字母、数字和符号用1个字节存储–>兼容ISO-8859-1
    中文用3个字节存储–>不兼容任何GBXXX

十四、IO流

1. File对象

File类代表文件(文件和目录)

存储介质中的文件和目录在Java程序中都是用File类的实例来表示

常用构造方法:

public File(String pathname):以pathname为路径创建File对象

pathname为一个具体路径:

  • 绝对路径 完整的路径 含有其实盘符到最终的目标文件/目录
    • 优点:在当前系统中任何地方都可以直接定位到目标
    • 缺点:不利于移植
  • 相对路径 相对一个参照路径(基准路径) 使用 . 或者 进行路径的映射
    • 优点: 利于移植
    • 缺点: 基准路径不固定

建议:

  1. 在项目中建议固定基准路径之后可以使用相对路径进行定位
  2. File类的一个常用属性
  3. public static final String separator存储了当前系统的路径分隔符
  4. 在 UNIX 系统上,此字段的值为 ‘/’;在 Windows 系统上为 '\'
  5. 为了程序的跨平台特性,文件的路径应该用这个属性值来代表。或则都使用’/‘进行分隔 因为windows对’/'有兼容性

常用方法

1.访问File对象的属性

•public boolean canRead()
•public boolean canWrite()
•public boolean exists()
•public boolean isDirectory()
•public boolean isFile()
•public boolean isHidden()
•public long lastModified() //毫秒值
•public long length() //以字节为单位
•public String getName() //获取文件名
•public String getPath() //路径名
•public String getAbsolutePath() //返回此File对象的绝对路径名
•public File getAbsoluteFile()
•public String getCanonicalPath() //返回此File对象的规范路径名字符串
•public File getCanonicalFile() //返回此File对象的规范形式
•public String getParent() //返回父目录的路径名字符串
•public URI toURI() //返回此文件的统一资源标识符名

示例

import java.io.File;
public  class FileTest{
    public static void main(String[] args){
        File file = new File(args[0]);
        System.out.println("文件或目录是否存在:" +  file.exists());
        System.out.println("是文件吗:" +  file.isFile());
        System.out.println("是目录吗:" +  file.isDirectory());
        System.out.println("名称:" +  file .getName());
        System.out.println("路径: " + file.getPath());
        System.out.println("绝对路径: " + file.getAbsolutePath());
        System.out.println("最后修改时间:" + file.lastModified());    
        System.out.println(“文件大小:+ file.length()+ “ 字节”); 	 	
        ……
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

2. 对File对象进行操作
  • 浏览目录中的子文件和子目录

•public String[] list() //返回此目录下的文件名和目录名的数组
•public File[] listFiles()//返回此目录下的文件和目录File实例数组
•public File[] listFiles(FilenameFilter filter) //返回此目录中满足指定过滤器的文件和目录
•java.io.FilenameFilter接口:实现此接口的类实例可用于过滤文件名

  • 对文件的操作:

•public boolean createNewFile() //不存在时创建此文件对象所代表的空文件
•public boolean delete() //删除文件或目录。目录必须是空才能删除
•public boolean mkdir() //创建此抽象路径名指定的目录
•public boolean mkdirs() //创建此抽象路径名指定的目录,包括所有必需但不存在的父目录
•public boolean renameTo(File dest) //重命名

示例

用递归算法列出指定目录下的所有子孙文件和目录

package file;

   /*
    * 将某个目录所有下属目录或文件 全部遍历出来
    * */

import java.io.File;

public class LookDir {

    public static final String SYMBOL = "|--";

    public static void main(String[] args) {
        String path = "D:/iotest";
        lookSome(new File(path),1);
    }

    public static void lookSome(File file,int level){
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < level ; i++) {
            sb.append(SYMBOL);
        }
        System.out.println(sb.toString()+file.getName());
        //判断当前是否是目录
        if(file.isDirectory()){
            //如果是目录 再进行遍历
            File[] fs = file.listFiles();//获取当前目录的所有子元素
            for(File f : fs){
                //遍历当前子元素
                if(f.isFile()){
                    System.out.println(sb.toString()+f.getName());//出口
                }else{
                    lookSome(f,++level);//使用递归来进行文件结构的查看
                }
            }
        }else{
            //如果是文件 则直接输出文件名
            System.out.println(sb.toString()+file.getName());//出口
        }


    }
}
  • 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

列出指定目录下的jpg类型图片

import java.io.File;
import java.io.FileFilter;

public class FileFilterTest {

    public static void main(String[] args) {
        /*
        File 对象可以获取当前目录下的指定文件 --- 可以进行文件过滤
         */
        File  f = new File("D:/iotest");
        File[] fs = f.listFiles();
        for(File f1 : fs){
            System.out.println(f1.getName());
        }
        //需求只希望看到当前目录下的所有.jpg文件
        File[] fs1 = f.listFiles(new FileFilter() {
            @Override
            public boolean accept(File ff) {
                if (ff.getName().endsWith(".jpg")) {
                    return true;
                } else {
                    return false;
                }
            }
        });
        System.out.println("~~~~~~~~~~~~~~~~~");
        for(File f1 : fs1){
            System.out.println(f1.getName());
        }

    }
}
  • 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 file;

import java.io.File;

/*
    删除指定目录/文件
    核心思想
    文件直接删除  如果是目录 必须进行判定 是空目录才能删除
    否则需要先删除其内部的所有文件 之后再删除该目录
 */
public class DeleteDir {

    public static void main(String[] args) {
        String  path = "D:/iotest";
        deleteDir(new File(path));
    }

    public static void deleteDir(File file){
        if(null == file ){
            System.out.println("目标文件不存在,请检查");
        }else{
            if(file.isDirectory()){
                //清空当前目录下的所有元素
                File[] fs = file.listFiles();
                for(File f : fs){
                    if(f.isFile()){
                        //下属元素是文件 直接删除
                        f.delete();
                    }else{
                        //如果是目录 使用递归方式将该目录当做参数 传递给删除方法
                        deleteDir(f);
                    }
                }
            }
            file.delete();//删除当前目标元素
        }
    }
}
  • 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

2. 输入/输出的基本原理

数据流(Stream)是指数据通信的通道。

java程序中对数据的输入、输出操作是以“流”方式进行的。JDK中提供了各式的“流”类来获取不同种类的数据。

示意图:

在这里插入图片描述

3. 流的概述

  • 按照方向区分

    • 输入流 程序可以从中读取数据的流
    • 输出流 程序能向其中写出数据的流

输入和输出方向的参照物是内存

  • 按照传输单位区分

    • 字节流 底层以字节方式传输 可以传输任何类型的文件
    • v字符流 底层以字符方式传输 只能传输纯文本类型的文件
  • 按照功能区分

    • 节点流 源空间到目标空间的底层数据通道
    • 过滤流(处理流) 是对一个已存在的流的连接和封装,通过对数据的处理为程序提供更为强大、灵活的读写功能

4. Java中对于流的处理类

在Java中按照方向和传输单位两个维度 提供了4大抽象类

字节字符
输入InputStreamReader
输出OutPutStreamWriter

InputStream抽象类
  • 继承自InputStream的流都是用于向程序中输入数据的,且数据的单位为字节(8位)。(粉色为节点流,蓝色为过滤流)

示意图

在这里插入图片描述
InputStream的常用方法

  • public abstract int read() throws IOException
    从输入流中读取数据的下一个字节, 返回读到的字节值。若遇到流的末尾,返回-1
  • public int read(byte[] b) throws IOException
    从输入流中读取b.length个字节的数据并存储到缓冲区数组b中。返回的是实际读到的字节总数。若遇到流的末尾,返回-1
  • public int read(byte[] b, int off, int len) throws IOException
    读取 len 个字节的数据,并从数组b的off位置开始写入到这个数组中
  • public void close() throws IOException
    关闭此输入流并释放与此流关联的所有系统资源
  • public int available() throws IOException
    返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数
  • public skip(long n) throws IOException
    跳过和丢弃此输入流中数据的 n 个字节,返回实现路过的字节数。

OutputStream抽象类
  • 继承自OutputStream的流是程序用于向外输出数据的,且数据的单位为字节(8位)。

示意图:
在这里插入图片描述
OutPutStream基础方法

  • public abstract void write(int b) throws IOException
    将指定的字节写入此输出流
  • public void write(byte[] b) throws IOException
    将 b.length 个字节从指定的 byte 数组写出到此输出流
  • public void write(byte[] b, int off, int len) throws IOException
    将指定 byte 数组中从偏移量 off 开始的 len 个字节写出此输出流
  • public void flush() throws IOException
    新此输出流并强制写出所有缓冲的输出字节
  • pulbic void close() throws IOException
    关闭此输出流并释放与此流有关的所有系统资源

Reader抽象类

继承自Reader的流都是用于向程序中输入数据的,且数据的单位为字符(16位)。

示意图:


在这里插入图片描述
Reader常用方法

  • public int read() throws IOException
    读取单个字符,返回作为整数读取的字符,如果已到达流的末尾返回-1
  • public int read(char[] cbuf) throws IOException
    将字符读入数组,返回读取的字符数;否则返回-1
  • public abstract int read(char[] cbuf, int off, int len) throws IOException
    读取len个字符,并从数组cbuf的off位置开始写入到这个数组中
  • public abstract void close() throws IOException
    关闭该流并释放与之关联的所有资源
  • public long skip(long n) throws IOException
    跳过n个字符。

Writer抽象类

  • 继承自Writer的流是程序用于向外输出数据的,且数据的单位为字符(16位)。

示意图:
在这里插入图片描述


Writer的基础方法

  • public void write(int c) throws IOException
    写入单个字符
  • public void write(char[] cbuf) throws IOException
    写入字符数组
  • public abstract void write(char[] cbuf, int off, int len) throws IOException
    写入字符数组的某一部分
  • public void write(String str) throws IOException
    写入字符串
  • public void write(String str, int off, int len) throws IOException
    写字符串的某一部分
  • public abstract void flush() throws IOException
    刷新该流的缓冲,将缓冲的数据全写到目的地
  • public abstract void close() throws IOException
    关闭此流,但要先刷新它

5. 文件流

主要用来操控文件的读写,可以用来复制文件或者剪切。

专门用于操作文件的流。Java SE API中提供了4种:

  • FileInputStream继承自InputStream
  • FileOutputStream继承自OutputStream
  • FileReader继承自Reader
  • FileWriter继承自Writer

二进制文件(字节文件):图片、音频、视频等。 需要使用字节流来操作
文本文件(字符文件):.txt、.properties、.xml、.html 字符或字节都可以操作 但建议适应字符操作 很简便

注:文件续写问题 在文件写出时 如果该输出流的构造器第二个参数为true 则会自动续写 如果为false或者不写第二个参数 则直接覆盖原文件所有内容

示意 :

/*
使用字节流完成文件复制的功能
*/
int b = 0;
FileInputStream in = null;
FileOutputStream out = null;
try {
    in = new FileInputStream("d:/IOTest/soruce.jpg");
    out = new FileOutputStream("d:/IOTest/dest.jpg");
    while ((b = in.read()) != -1) {   //有没有效率更高的方式?
        out.write(b);
    }
    System.out.println("文件复制成功");
} catch (FileNotFoundException e) {
    System.out.println("找不到指定文件");
    e.printStackTrace();
} catch (IOException e) {
    System.out.println("文件复制错误");
    e.printStackTrace();
}finally{}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

6. 缓冲流——过滤流

缓冲流是建立在相应的节点流之上,对读写的数据提供了缓冲的功能,提高了读写的效率,还增加了一些新的方法。

Java SE API提供四种缓冲流:

  • BufferedInputStream 可以对任何的InputStream流进行包装(套接)
  • BufferedOutputStream 可以对任何的OutputStream流进行包装(套接)
  • BufferedReader 可以对任何的Reader流进行包装
  • 新增了readLine()方法用于一次读取一行字符串(’\r’或’\n’作为行结束)
  • BufferedWriter 可以对任何的Writer流进行包装
  • 新增了newLine()方法用于写入一个行分隔符。

对于缓冲输出流,写出的数据会先缓存在内存缓冲区中,关闭此流前要用flush()方法将缓存区的数据立刻写出。
关闭过滤流时,会自动关闭过滤流所包装(套接)的所有底层流。

示例:

BufferedInputStream bis = null;
BufferedOutputStream bos = null;
byte[]  buf = new byte[1024];
try{
  bis = new BufferedInputStream(new FileInputStream(src));
  bos = new BufferedOutputStream(new FileOutputStream(dest));
  for(int len = 0; (len = bis.read(buf)) != -1;){
    bos.write(buf, 0, len);
  }
  bos.flush();
}catch(IOException e){
  e.printStackTrace();
}finally{
  if(bos != null){
    try {bos.close();} catch (IOException e) {e.printStackTrace();}
  }
  if(bis != null){
    try {bis.close();} catch (IOException e) {e.printStackTrace();}
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

7. 转化流 ——过滤流

转换流用于在字节流和字符流之间转换。

JaveSE API提供了两种转换流:

  • InputStreamReader需要和InputStream“套接”,它可以将字节流中读入的字节解码成字符

  • OutputStreamWriter需要和OutputStream“套接”,它可以将要写入字节流的字符编码成字节

常用于:

  • 解决乱码问题

  • 网络编程中,只提供字节流。

示例 :

System.out.println("请输入信息(退出输入e):");
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = null;
try {
    while ((s = br.readLine()) != null) {  //阻塞程序
        if (s.equals("e")) {
            System.out.println("安全退出!!");
            break;
        }
        System.out.println("-->:"+s.toUpperCase());
        System.out.println("继续输入信息");
    }
} catch (IOException e) {
    e.printStackTrace();
} finally { 
    try {        if (null != br) {    br.close();  }    } catch (IOException e) {        e.printStackTrace();    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

8. 对象流

用于存储和读取基本类型数据或对象的过滤流

  • ObjectOutputStream保存基本类型数据或对象:序列化
  • ObjectInputStream读取基本类型数据或对象:反序列化

能被序列化的对象所对应的类必须实现java.io.Serializable这个标识性接口
示例:

package serialized;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SeriaillzedTest {

    public static void main(String[] args) {
        /*
        在java中有一个流 ObjectStream 对象流  能够将对象序列化
        序列化 就是将程序中的对象或者信息 持久化(本地存储)
        可以通过序列化 和反序列化将一个对象 转换成一个文件 之后再从文件中读取还原成一个对象
        底层就可以通过文件来传输该对象的信息
        示例:将一个学生类 序列化成一个文件
         */
        Student stu = new Student("王源", 20);

        xlh(stu);
        fxlh();
    }

    public static void xlh(Student stu) {
        File f = new File("D:/iotest/stu.dat");
        ObjectOutputStream oo = null;
        try {
            oo = new ObjectOutputStream(new FileOutputStream(f));
            oo.writeObject(stu);
            oo.writeBoolean(true);
            oo.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oo != null) {
                try {
                    oo.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    public static void fxlh() {
        File f = new File("D:/iotest/stu.dat");
        ObjectInput oi = null;
        try {
            oi = new ObjectInputStream(new FileInputStream(f));
            Student stu = (Student) oi.readObject();
            System.out.println(stu);
            boolean b = oi.readBoolean();
            System.out.println("booelan=" + b);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

序列化的相关问题
  • transient关键字修饰成员变量时,表示这个成员变量是不需要序列化的。
  • static修饰的成员变量也不会被序列化。
  • 实现了Serializable接口的类都应该生成一个private static final long *
  • serialVersionUID 序列化版本ID作为标识。

9. 其他流

  • 数据流

    • DataInputStream和DataOutputStream
  • 打印流

    • PrintStream和PrintWriter都属于打印流 它们的输出操作永远也不会抛出IOException
  • 随机访问文件

    • RandomAccessFile类,自身具备读写的方法

void seek(long pos); //文件指针移动到指定的指针偏移量
​ long getFilePointer(); //获取当前文件指针偏移量
​ int read(byte[] b); //读数据
​ void write(byte[] b, int off, int len); //写数据

10. try-with-resources语句

JDK7中提供的,它可以自动关闭相关的资源的语法:

try (资源声明1; 资源声明2; …) {

}catch(…){
}


资 源 是指实现了java.lang.**AutoCloseable**或java.io.**Closeable**的类的对象。如:InputStream、OutputStream、Reader、Writer、Connection、ResultSet等。

它会确保在本语句结束时关闭try语句中声明的所有资源。资源被关闭的顺序与它们被创建的顺序相反。

示例

public static void copy(File src, File dest){
  byte[]  buf = new byte[1024];
  try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
  BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(dest))){
     for(int len = 0; (len = bis.read(buf)) != -1;){
        bos.write(buf, 0, len);
     }
     bos.flush();
  }catch (Exception e) {
     e.printStackTrace();
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

十五、网络编程

1. 计算机网络基础概念

计算机网络

把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息,共享硬件、软件、数据信息等资源。

网络通信协议

要使计算机连成的网络能够互通信息,需要对数据传输速率、传输代码、代码结构、传输控制步骤、出错控制等制定一组标准,这一组共同遵守的通信标准就是网络通信协议,不同的计算机之间必须使用相同的通讯协议才能进行通信。

在Internet中TCP/IP协议是使用最为广泛的通讯协议。TCP/IP是英文Transmission Control Protocol/Internet Protocol的缩写,意思是“传输控制协议/网际协议”。

网络参考模型

OSI参考模型:开放系统互连参考模型。由国际标准化组织ISO提出的一个网络系统互连模型。

OSI模型目前主要用于教学理解。实际使用中,网络硬件设备基本都是参考TCP/IP模型

示意图:

在这里插入图片描述

2. TCP/IP

•针对TCP/IP模型的各层,都有对应数据传输格式,这个数据传输的格式就是常说的协议

•TCP/IP包括上百个各种功能的协议,如:超文本传输协议(HTTP)、远程登录(TeLnet)、文件传输(FTP)和电子邮件(SMTP、POP3、IMAP4)等。

3. IP地址

IP地址:网络中每台设备的标识号

是一个逻辑地址:IPV4、IPV6 区别在于可用范围

  • IP地址不便记忆,可用主机名

  • 本地回环地址:127.0.0.1 主机名:localhost

  • 端口号:用于标识具有网络功能的进程的逻辑地址(标识号)

  • 有效端口:0~65535,其中0~1024系统使用或保留端口。

  • 端口与协议有关:TCP和UDP的端口互不相干,TCP和UPD的端口号可以重复

传输协议:通讯的规则 常用协议:TCP、UDP


4. java.net.InetAddress 类

表示互联网地址(IP地址),它封装了IP地址和域名相关的操作方法

//使用域名创建InetAddress对象
InetAddress inet1 = InetAddress.getByName("www.baidu.com");
System.out.println(inet1);
//使用IP创建InetAddress对象
InetAddress inet2 = InetAddress.getByName("117.79.93.222");
System.out.println(inet2);
InetAddress inet3 = InetAddress.getLocalHost(); //获得本机InetAddress对象
System.out.println(inet3);
String host = inet3.getHostName();//获得InetAddress对象中存储的域名
System.out.println("本机名:" + host);
String ip = inet3.getHostAddress(); //获得InetAddress对象中存储的IP
System.out.println("本机IP:" + ip);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

5. 两个重要的传输协议

  • TCP(Transmission Control Protocol)传输控制协议
    • 通过三次握手建立连接,形成传输数据的通道。
    • 断开链接需要四次挥手加一次等待
    • 在连接中通过流来进行字节数据传输
    • 基于连接,是可靠协议
    • 面向连接、数据传输可靠、效率稍低
  • UDP(User Datagram Protocol)用户数据报协议
    • 将数据及源和目的封装成数据包中,不需要建立连接
    • 每个数据报的大小在限制在64k内
    • 因无连接,是不可靠协议
    • 面向非连接、数据传输不可靠、效率高

6. Socket(套接字)

两个应用程序可以通过一个双向的网络通信连接实现数据交换,这个双向链路的一端称为一个Socket。

通信的两端都有Socket。

网络通信其实就是Socket间的通信。

数据在两个Socket间通过IO传输。

创建Socket的四个基本步骤

  1. 创建socket;
  2. 打开连接到socket的输入/输出流;
  3. 按照一定的协议对socket进行读/写操作;
  4. 关闭socket;

7. TCP编程

java.net包中定义了两个类:SocketServerSocket,分别用来实现TCP的client和server端

示意图:

在这里插入图片描述
基于TCP 方式的Socket数据传输示例

  • 服务端
package socket.demo;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class Service {

    /*
        Socket使用场景  一般用在两个程序之间进行数据通信
        ServerSocket 和Socket 两个对象  一个是服务端用来接收客户请求并进行制定响应
        Socket就是一个客户端 用来和服务端之间进行数据传递
     */
    public static void main(String[] args) {
        int port = 8888;
        startServer(port);

    }

    public static void startServer(int port){
        System.out.println("服务端开启....");
        //服务端 只用执行端口号来监听客户端访问
        ServerSocket ss = null;
        BufferedReader br = null;
        try {
            ss = new ServerSocket(port);
            Socket s = ss.accept();

            //使用Socket的流来读取数据即可
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            String str = br.readLine();
            System.out.println(str);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(br != null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(ss != null){
                try {
                    ss.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }


    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

  • 客户端
package socket.demo;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

public class Client {

    /*
    模拟客户端访问服务端Socket并发送数据
     */
    public static void main(String[] args) throws UnknownHostException {

        System.out.println("客户端准备中.....");
        String ip = "127.0.0.1";
//        String host = "localhost";//hostname
        int port = 8888;
        String msg = "你好,我是SocketClient,How do you do!";
        sendMsg(InetAddress.getByName(ip),port,msg);

    }

    public static void sendMsg(InetAddress ip ,int prot,String msg){
        Socket s = null;
        PrintWriter pw = null;
        try {
            s = new Socket(ip,prot);

            pw = new PrintWriter(new OutputStreamWriter(s.getOutputStream()));
            pw.println(msg);
            pw.flush();

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null != s){
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

基于TCP方式的聊天程序

  • 服务端
//Socket服务端 先等待接收 之后再想客户端发送信息
/**
模拟小明和小红聊天
小红为服务端
小明为客户端
 */
public class XiaoHong {

    public static void main(String[] args) {
        ServerSocket ss = null;
        PrintWriter pw = null;
        BufferedReader br = null;
        BufferedReader sc = null;
        Socket xm = null;
        System.out.println("欢迎小红登录,请开始聊天...");
        try {
            ss = new ServerSocket(6666);
            xm = ss.accept();
            pw = new PrintWriter( new OutputStreamWriter(xm.getOutputStream()));
            br = new BufferedReader(new InputStreamReader(xm.getInputStream()));
            sc = new BufferedReader(new InputStreamReader(System.in));
            String xmMsg = null;
            while(!(xmMsg = br.readLine()).equalsIgnoreCase("exit")){
                System.out.println("小明:"+xmMsg);
                String xhMsg = sc.readLine();
                pw.println(xhMsg);
                pw.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null != sc){
                try {
                    sc.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(null != br){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(null != xm){
                try {
                    xm.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(null != ss){
                try {
                    ss.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 客户端
/*
客户端先向服务端发起链接 之后发送数据 再等待服务端返回的消息
 */
public class XiaoMing {


    public static void main(String[] args) {
        Socket xiaoming = null;
        PrintWriter pw = null;
        BufferedReader br = null;
        BufferedReader sc = null;
        System.out.println("欢迎小明登录,请开始聊天...");
        try {
            xiaoming = new Socket("localhost", 6666);
            /*
            先向服务端发起信息
            之后再接收服务端的信息
             */
            OutputStream os = xiaoming.getOutputStream();//输出流 发送信息
            InputStream is = xiaoming.getInputStream();//输入流 接收信息
            pw = new PrintWriter(new OutputStreamWriter(os));
            br = new BufferedReader(new InputStreamReader(is));
            sc = new BufferedReader(new InputStreamReader(System.in));//从标准输入流获取传输信息
            while(true){
                String str = sc.readLine();
                if(str.equalsIgnoreCase("exit")){
                    break;
                }else{
                     pw.println(str);
                     pw.flush();
                    String xhMsg = br.readLine();
                    System.out.println("小红:"+xhMsg);
                }
            }

        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null != sc){
                try {
                    sc.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(null != br){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(null != xiaoming){
                try {
                    xiaoming.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }


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

8. UDP编程

UDP编程所需要的类DatagramSocket

  • 用来发送和接收数据报包的套接字

    • void send(DatagramPacket p)
    • void receive(DatagramPacket p)
  • DatagramPacket 数据报包

    • DatagramPacket(byte[] buf, int length) 用来接收长度为 length 的数据包。
    • DatagramPacket(byte[] buf, int length, InetAddress address, int port)用来将长度为 length 的包发送到指定主机上的指定端口号

•buf - 包数据
•length - 包长度
•address - 目的地址
•port - 目的端口号
•int getLength() 返回将要发送或接收到的数据的长度。


基于UDP的数据传输 全部要通过数据报包的方式进行传递 以下为UDP方式的传输示例
  • 发送端
package udp.socket;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

/*
基于UDP协议的 数据报包的发送  发送至接收端
 */
public class SendMsg {

    public static void main(String[] args) {
        String msg = "重大消息,xxxxxx开业了,所有商品一律6折,请各位奔走相告!";
        byte[] msgb = msg.getBytes();
        DatagramSocket ds = null;
        try {
            ds = new DatagramSocket("9999");//定义发包的端口号,该数据从此端口向外发送,如不写则系统默认选取一个闲置端口使用
            DatagramPacket dp = new DatagramPacket(msgb,msgb.length, InetAddress.getByName("localhost"),6666);//数据报包拼接完毕 需指定发送至何处(接收端的ip和端口号)
            //开始发送
            ds.send(dp);
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null != ds){
                ds.close();
            }
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 接收端
package udp.socket;


import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

/*
接收数据报包
 */
public class ReciveMsg {

    public static void main(String[] args) {
        DatagramSocket ds = null;
        byte[] b = new byte[64*1024];
        try {
            ds = new DatagramSocket(6666);//该接口为接听端的端口 发送时需和该端口一致
            DatagramPacket dp = new DatagramPacket(b,b.length);
				//接收方法会产生阻塞 拿到包后继续执行
                ds.receive(dp);
                //接收到数据报包之后 开始解析
            byte[] data = dp.getData();
            String str = new String(data,0,dp.getLength());
            System.out.println("接收到的信息为:"+str);
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

9. 基于URL的网络通信

URL代表一个统一资源定位符,它是指向互联网“资源”的指针。

网络中的资源都有唯一的URL地址。

  • 构造方法:
    public URL(String spec) throws MalformedURLException
    根据指定的字符串URL地址创建URL对象
  • 常用方法:
    public URLConnection openConnection() throws IOException
    public final InputStream openStream() throws IOException
    打开到此URL的连接并返回一个用于从该连接读入的InputStream
//获取百度的HTML内容
import java.io.*;
import java.net.URL;
public class TestURL {
    public static void main(String[] args) {
        String strUrl = "http://www.baidu.com";
        BufferedReader br = null;
        try {
            URL url = new URL(strUrl);
            br = new BufferedReader(new InputStreamReader(url.openStream()));
            String str = "";
            while((str = br.readLine()) != null){
                System.out.println(str);
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{   
            if(null != br){   
                try {    
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace(); 
                }
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

10. URLConnection

代表应用程序和URL之间的通信链接。此类的实例可用于读取和写入此URL引用的资源。

常用它的子类HttpURLConnection,用于支持HTTP的通信链接

创建一个到URL的连接的步骤:

  • 通过在URL上调用openConnection()方法创建连接对象。
  • 可在真正连接之前(调用connect()方法),处理设置参数和一般请求属性
  • 使用connect()方法建立到远程对象的实际连接。
  • 远程对象变为可用。远程对象的头字段和内容变为可访问。

//使用Get方式请求数据
public static String sendGet(String url) {
  String result = ""; BufferedReader ir;
  try {
    URL u = new URL(url);
    HttpURLConnection connection = (HttpURLConnection)u.openConnection();//如确认是HTTP协议传输,则此处可强转成HTTPURLConnection
    connection.connect(); 
    ir = new BufferedReader(new InputStreamReader(connection.getInputStream()));
    for(String line = null; (line = ir.readLine()) != null;) {
        result += "\n" + line;
    }
  } catch (Exception e) {
     System.out.println("没有结果!" + e);
  } finally{ ir.close(); }
  return result;
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

十六、注解和反射

1. 注解

a. 元数据

元数据(metadata):就是关于数据的数据。

示例:

  • 表格中呈现的是数据,而表格还会有额外的数据来说明表格的作用,这个就是表格的元数据。
  • 数据库中的表存放了数据,但表还需要有表的定义、字段的定义等,这个就是数据库表的元数据。
  • XML文件可以存放数据。但xml文件的每个标签还需要有相应的描述,这些描述就是XML标签的元数据。
  • 元数据可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查

b. Annotation(注解)

在JDK5版本开始 ,提出了注解的概念,Java核心语法。

注解是可以添加到代码中的修饰符,对程序代码做出一些说明和解释。可以用于包声明、类声明、构造方法、方法、字段、参数和变量

  • 这样就将程序的元素和元数据联系起来。编译器就可以将元数据存储在Class文件中。之后虚拟机和其它对象可以根据这些元数据来决定如何使用这些程序元素或改变它们的行为。
  • DK5.0包含了内置注解,还支持编写定制注解

注解语法

  • 注解采用“@”标记形式 ,后面跟注解类型名称。通过(name=value)向注解提供参数数据。每次使用这类表示法时,就是在生成注解。

注解类型和注解的区别:注解类型类似于类,注解类似于该类的实例


常用内置注解
  • Override:只能放在方法上 用来在编译时检查 当前的方法是否构成重写语法 如果不符合会直接在编译过程中提示错误

  • Deprecated:可以放在类中的任意地方 其对应的内容 会被提示为已过时的内容 在编程中不建议时间器修饰的对象来进行编程开发 在IDE工具中如果一个对象被标为过时 则会出现中划线

  • SuppressWarnings :可以放在任何位置 用来进行告警抑制功能 可以对当前IDE工具中提示的告警信息进行抑制 但其在使用时必须要提供参数。

    • 它有一个必需属性:value 是String[]类型的,指定取消显示的警告集。警告类型有

      •unused 未被使用的警告

      •deprecation 使用了不赞成使用的类或方法时的警告

      •unchecked 执行了未检查的转换时的警告

      •rawtypes 没有用泛型 (Generics) 的警告

      •fallthrough 当 Switch 程序块直接通往下一种情况而没有 Break 时的警告。

      •path 在类路径、源文件路径等中有不存在的路径时的警告。

      •serial 当在可序列化的类上缺少 serialVersionUID 定义时的警告。

      •finally 任何 finally 子句不能正常完成时的警告。

      •all 关于以上所有情况的警告。

注意:注解如果仅接收一个参数且名称是“value”,则可省略”value=“,否则必须显示声明所有的属性值


c. 自定义注解

语法:

[访问修饰符] @interface 注解类型名称{
//如果有参数
变量类型 变量名() [default 默认值];

}

  • 默认自定义注解没有任何的限制,建议根据具体的需求来限制当前自定义注解的一个使用范围或生命周期
  • 注解需要配合反射才能正常使用

元注解
修饰注解的注解 为注解类型提供某种元数据,以便编译器保证按照预期的目的使用注解
使用系统预定义的元注解可以对你的注解进行注解
  • @Target 指定此注解的适用时机 使用ElementType枚举类定义当前注解的使用范围

  • @Retention 告知编译器如何处理此注解 使用Retention枚举来描述声明周期

  • @Documented 要求此注解成为API文件的一部分

  • @Inherited 指定子类是否继承父类的注解


2. 反射

a. 反射概述

反射是Java语言的特征之一。它允许在运行时动态加载类、获取类信息、生成对象、操作对象的属性或方法等。主要提供了以下功能:

  • 在运行时判断任意一个对象所属的类;

  • 在运行时构造任意一个类的对象;

  • 在运行时判断任意一个类所具有的成员变量和方法;

  • 在运行时调用任意一个对象的方法。甚至是private方法。

  • 生成动态代理

b. Class类

Class类是Java反射的起源。

运行中的类或接口在JVM中都会有一个对应的Class对象存在,它保存了对应类或接口的类型信息。

包括:基本数据类型、void、数组、enum、annotation

JVM为每种类型管理着一个独一无二的Class对象


获取Class对象
有三种方式可以获取每一个对象对应的Class对象:

  • 使用Object类中的getClass()方法。//返回的Class<? exstends S>

  • 使用Class类的静态方法forName(String className);//会抛出ClassNotFoundException 最终返回Class<?>

  • 使用class常量。格式: 类名.class;// 返回一个Class

示例

public class ClassDemo {
    public static void main(String[] args) {
        String str = "java技术";
        Class stringClass = str.getClass();
        //Class stringClass = Class.forName("java.lang.String");
        //Class stringClass = String.class;
        System.out.println("类名:" + stringClass.getName());
        System.out.println("是否为接口:" + stringClass.isInterface());
        System.out.println("是否为基本类型:" + stringClass.isPrimitive());
        System.out.println("父类名称:" +  stringClass.getSuperclass().getName());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

class常用方法
  • 获取类的包名: Package getPackage()
  • 获取类的修饰符: int getModifiers() 参见Modifier类
  • 获取类的全限定名:String getName()
  • 获取类的父类:Class<? super T> getSuperclass()
  • 获取类实现的接口:Class<?>[] getInterfaces()
  • 获取全限定名 getName()
  • 获取简单名称(类名) getSimpleName()

c. 其他反射中要用的类型

关于Class其内部可以认为就是对于一个类的完全描述 所以一个类中所有的构成都可以看做一个个单独的类型:

  • 类中的属性 — Field
  • 类中的构造器 — Constructor
  • 类中的方法 — Method
  • 类中的注解 — Annotation
  • 类中的泛型 — ParameterizedType

使用Class类可以获取以上每个类型的对象 但是在使用时 如果是私有类型的 则必须要开启可访问模式 setAccessible(true)

使用反射来创建对象

Class.newInstance();//默认调用当前类型的无参公共构造来创建对象 所以一般声明含参构造时 一定要声明无参构造 以方便框架的反射机制来创建对象


十七、正则表达式

正则表达式(regular expression)描述了一种字符串匹配的模式。

正则表达式作为一个模板,与所搜索的字符串进行匹配。

常用于:字符串的匹配、查找、替换、分割等

1. 正则相关类

java.util.regex包中提供了相关类

  • Pattern:模式类。正则表达式的编译表示形式

  • Matcher:匹配器。用于匹配字符序列与正则表达式模式的类

  • PatternSyntaxException:正则表达式模式中的语法错误

String类中与正则相关的方法

  • public boolean matches(String regex); 告知此字符串是否匹配给定的正则表达式

  • String[] split(String regex); 根据给定正则表达式的匹配拆分此字符串

  • String replaceAll(String regex, String replacement);使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串

2. Pattern常用方法

  • public static Pattern compile(String regex) 将给定的正则表达式编译到模式中

  • public static Pattern compile(String regex, int flags) 将给定的正则表达式编译到具有给定标志的模式中

    • flags参数表示匹配时的选项,常用的flags参数值有:
    1. CASE_INSENSITIVE:启用不区分大小写的匹配。
    2. COMMENTS:模式中允许空白和注释。
    3. MULTILINE:启用多行模式。
  • public Matcher matcher(CharSequence input) 生成一个给定命名的Matcher对象

  • static boolean matches(String regex, CharSequence input) 直接判断字符序列input是否匹配正则表达式regex。该方法适合于该正则表达式只会使用一次的情况

3. Matcher常用方法

public boolean matches() 尝试将整个输入序列与该模式匹配。 (开头到结尾)

public boolean find() 扫描输入序列以查找与该模式匹配的下一个子序列

public String replaceAll(String replacement) 替换模式与给定替换字符串相匹配的输入序列的每个子序列

public String replaceFirst(String replacement) 替换模式与给定替换字符串匹配的输入序列的第一个子序列

public String group() 返回由以前匹配操作所匹配的输入子序列

public String group(int group) 返回在以前匹配操作期间由给定组捕获的输入子序列


4. 正则语法

正则表达式的模式串:是由普通字符(如字符a到z)以及一些特殊字符(称为元字符)组成

元字符从功能上分为:限定符、选择匹配符、特殊字符、字符匹配符、定位符、分组组合符、反向引用符。

a. 限定符

在正则中使用限定符来限制其修饰的前一个/一组元素的出现次数

字符说明
*****匹配前面的字符或子表达式零次或多次。 例如:"ja*“匹配"j"和"jaaaaaaaa”。
+匹配前面的字符或子表达式一次或多次。 例如:"ja+"与"ja"和"jaaaaa"匹配,但与"j"不匹配。
?匹配前面的字符或子表达式零次或一次。 例如:“core?”匹配“cor”或“core”。不能匹配”coreeeee”
{n}正好匹配n次。n是一个非负整数。 例如:"o{2}"与"Bob"中的"o"不匹配,与"food"中的两个"o"匹配。
{n,}至少匹配n次。 例如:"o{2,}"与"Bob"中的"o"不匹配,与"foooood"中的所有"o"匹配。
{n,m}最少匹配n次,且最多匹配m次。m和n均为非负整数,其中n<=m。 例如:"o{1,3}"匹配"fooooood"中的头三个 o。注意:不能将空格插入逗号和数字之间。
贪婪匹配&非贪婪匹配

限定符默认都是贪婪匹配,即尽可能多的去匹配字符。

当“?”紧跟在任何一个其他限定符(*,+,?,{n},{n,},{n,m})后面时,就是非贪婪匹配模式。

String str = "abccccc";
Pattern p = Pattern.complie("abc?");//默认贪婪匹配 能够匹配到abccccc
Pattern p = Pattern.complie("abc*?");//使用?后变为非贪婪 能够匹配到ab
  • 1
  • 2
  • 3

b. 选择匹配符

  • 选择匹配符:"|",它用于选择匹配两个选项之中的任意一个。

例如:“j|qava” 匹配“j”或“qava” ,"(j|q)ava"匹配"java"或"qava"。

c. 特殊字符

普通字符可以直接用来表示它们本身,也可以用它们的ASCII码或Unicode代码来代表。

  • ASCII码:两位的十六进制值,前面加"\x"
    字符b的ASCII码为98(十六进制是62)。所以表示b字符可用"\x62"

  • Unicode码:四位十六进制值,前面加"\u"
    匹配字符b,可以用它的Unicode代码"\u0062"

  • 最常用情况是用来表示中文字符的范围:"\u4E00-\u9FA5"

  • 元字符中用到的特别字符,当作普通字符使用时需要用""进行转义:

    • "\“匹配”"字符
    • "?"匹配问号字符
    • "\n"匹配换行符
    • "\r"匹配回车符
    • "\t"匹配制表符
    • \v 匹配垂直制表符
    • "\f"匹配换页符

d. 字符匹配符

使用[]来进行一位内容的匹配

注意 使用[]包裹的所有字符都会变为普通字符

字符说明
[…]字符集。匹配指定字符集合包含的任意一个字符。 例如:"[abc]“可以与"a”、“b”、"c"三个字符中的任何一个匹配。
[ ^… ]反向字符集。匹配指定字符集合未包含的任意一个字符。 例如:"[ ^abc]“匹配"a”、“b”、"c"三个字符以外的任意一个字符。
[a-z]字符范围。匹配指定范围内的任意一个字符。 例如:"[a-z]“匹配"a"到"z"范围内的任何一个小写字母。”[0-9]"匹配"0"到"9"范围内容的任何一个数字。
[ ^a-z ]反向字符范围。匹配不在指定范围内的任何一个字符。 例如:"[ ^a-z]“匹配任何不在"a"到"z"范围内的任何一个字符。”[ ^0-9]"匹配任何不在"0"到"9"范围内容的任何一个字符。
.匹配除"\n"之外的任何单个字符。若要匹配包括"\n"在内的任意字符,使用如"[\s\S]"之类的模式。
\d数字字符匹配。等效于[0-9]。
\D非数字字符匹配。等效于[ ^0-9]。
\w匹配任何单词字符,包括下划线。与"[A-Za-z0-9_]"等效。
\W与任何非单词字符匹配。与"[ ^A-Za-z0-9_]"等效。
\s匹配任何空白字符,包括空格、制表符、换页符等。与"[\f\n\r\t\v]"等效。
\S匹配任何非空白字符。与"[ ^\f\n\r\t\v]"等效。

e. 定位符

用于匹配字符出现的位置

字符说明
^匹配输入字符串的开始位置。^必须出现在正则模式文本的最前才起定位作用。
$匹配输入字符串的结尾位置。$必须出现在正则模式文本的最面才起定位作用。
\b匹配一个单词边界。 例如:“er\b"匹配"never love"中的"er”,但不匹配"verb"中的"er"。
\B非单词边界匹配。 例如:“er\B"匹配"verb"中的"er”,但不匹配"never"中的"er"。

f. 分组符

用()将正则表达式中的某一部分定义为"组",并且将匹配这个组的字符保存到一个临时区域,该方式叫做捕获性分组。

字符说明
(pattern)捕获性分组。将圆括号中的pattern部分组合成一个组合项当作子匹配,每个捕获的子匹配项按照它们在正则模式中从左到右出现的顺序存储在缓冲区中,编号从1开始,可供以后使用。 例如:“(dog)\1"可以匹配"dogdogdog"中的"dogdog"。
(?:pattern)非捕获性分组。即把pattern部分组合成一个组合项,但不能捕获供以后使用。
(?=pattern)正向预测匹配分组。 例如:“Windows(?=95|98|NT|2000)“能匹配"Windows2000"中的"Windows”,但不能匹配"Windows3.1"中的"Windows”。
(?!pattern)正向否定预测匹配分组
(?<=pattern)负向预测匹配分组。例如:"(?<=95|98|NT|2000)Windows"
(?<!pattern)负向否定预测匹配分组

反向引用符:用于对捕获分组后的组进行引用的符号。格式为"\组编号" 能够在模式串中根据组编号来引用当前某个组的内容

例如:要匹配"goodgood study, dayday up!“中所有连续重复的单词部分,可使用”\b([a-z]+)\1\b"来匹配。

"$分组编号"可用来引用分组匹配到的结果字符串,在匹配器执行过程中可以通过该方式来获取对应分组内容

String str = "我我我要要学java";
Matcher m = Pattern.compile("(.)\\1+").matcher(str);
str = m.replaceAll("$1");
System.out.println(str);

  • 1
  • 2
  • 3
  • 4
  • 5

十八、JavaSE–API

1. API

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

Java SE API:JDK中提供的各种功能的java类和接口

JDk_API
JDK6.0——API文档百度网盘下载,密码: 6666

java.lang包中提供的是利用Java编程语言进行程序设计的基础类。

java.lang包中的类由编译器自动导入

主要常用类
  • Object类
  • 基本数据类型的包装类
  • 枚举类型
  • 数学函数类
  • System类
  • Runtime类
  • 字符串相关类

2. Object类

所有Java类的根类。如果在类的声明中未明确使用extends关键字指定父类,则默认为继承自Object类

查询Java SE API 中Object类的方法说明

  • String toString():返回代表该对象值的字符串。

    Object类中返回的串是 “类的全限定名@对象哈希码的十六进制整数值"
    建议在自定义类中重写 重写成可识别的对信息描述

  • boolean equals(Object obj):测试其它某个对象是否与此对象"相等"

    equals()方法内部默认使用 == 来进行比较 比较的是地址是否相同
    建议在自定义类中重写 重写成比较两个对象的属性是否相等

  • int hashCode():返回该对象的哈希码值

    在重写equals()方法时,建议同时重写hashCode()方法,因为在某些场合需要比较两个对象是否为相同的对象时,会调用到这两个方法来判断

public class TestObject{

    public static void main(String[] args){
        TestObject obj = new TestObject();
        System.out.println(obj.toString());
        System.out.println(obj.hashCode());

        TestObject obj2 = new TestObject();
         
        System.out.println(obj.equals(obj2)); 
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3. 包装类

所有基本数据类型都提供了对应的包装类

基本数据类型包装类
byte(字节)java.lang.Byte
char(字符)java.lang.Character
short(短整型)java.lang.Short
int(整型)java.lang.Integer
long(长整型)java.lang.Long
float(浮点型)java.lang.Float
double(双精度)java.lang.Double
boolean(布尔)java.lang.Boolean

所有的包装类可以和基本数据类型之间无缝转换

public class NumberWrap {
    public static void main(String[] args) {
        String str = args[0];
        //------------- String转换成Ingeter
        Integer integer = new Integer(str);     // 方式一
        // Integer integer = Integer.valueOf(str); //方式二
        //------------- Ingeter转换成String
        String str2 = integer.toString();
        //------------- 把Ingeter转换成int
        int i = integer.intValue();
        //------------- 把int转换成Integer
        Integer integer2 = new Integer(i);//
        //Integer integer2 = Integer.valueOf(i);
        //------------- String转换成int
        int i2 = Integer.parseInt(str);
        //------------- 把int转换成String
        String str3 = String.valueOf(i2);  // 方式一
        String str4 = i2 + "";   // 方式二
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

JDK5.0提出自动拆装箱
  • 从包装类到基本数据类型 自动拆箱
  • 从基本数据类型到包装类 自动装箱

java编译器在编译时期会根据源代码的语法来决定是否进行装箱或拆箱

4. 枚举类型

Java SE 5.0以后版本中引入的一个新的引用类型。

枚举类型是指由一组固定的常量组成合法值的类型,例如:一年的四季、一个星期的七天、红绿蓝三基色、状态的可用和不可用等。

f
使用enum关键字来定义,如:

public enum Year{ SPRING, SUMMER, AUTUMN, WINTER}
  • 1

所有枚举类型对应的类是java.lang.Enum类的子类。在枚举类型定义中可以添加属性、构造器和方法,并可以实现任意的接口。

每个枚举类型 其实底层会编译为一个final修饰的类 且该类需要继承自Enum

枚举中的每个选项都是 public static final修饰的当前类类型

enum Status{
    ACTIVE("可用"),INACTIVE("不可用");  //枚举值列表
    private final String name;//final成员
    Status(String name){//构造器。只能在内部定义枚举值时使用
        this.value = value;
    }
    public String getName(){   //提供get方法访问Name属性的值
        return name;
    }
}
public class StatusEnumTest {
    public static void main(String[] args) {
        Status status = Status.ACTIVE;
        System.out.println("文章状态:" + status);
        System.out.println("此状态对应的名称:" + status.getName());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

5. Math数学函数类

提供了方便数学计算的方法,Math类中所有的属性和方法都是static,可以直接使用

  • 静态常量

    • E :自然对象底数的double值
    • PI :圆周率的double值
  • 静态方法

    double sin(double a)计算角 a的正弦值
    double cos(double a)计算角 a的余弦值
    double pow(double a, double b)计算 a 的 b 次方
    double sqrt(double a)计算给定值的平方根
    int abs(int a)计算int类型值a的绝对值.也提供long/float/double的
    double ceil(double a)返回大于等于 a的最小整数的double值
    double floor(double a)返回小于等于 a的最大整数的double值
    int max(int a, int b)返回int型值a和b中的较大值.也提供long/float/double的
    int min(int a, int b)返回 a 和 b 中的较小值。也提供long/float/double的
    int round(float a);四舍五入返回整数
    double random()返回带正号的double值,该值大于等于0.0且小于1.0
public class MathTest {
    public static void main(String[] args) {
        int num = 38;
        float num1 = -65.7f;
        System.out.println(Math.ceil(num));
        System.out.println(Math.ceil(num1));
        System.out.println(Math.floor(num));
        System.out.println(Math.floor(num1));
        System.out.println(Math.round(num));
        System.out.println(Math.round(num1));
        System.out.println((int)(Math.random()*10 + 1));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

使用小技巧:

Math.random() 可以返回一个随机的[0,1)范围的数

如果希望返回的是[min,max]范围的数据,使用以下方式:

M a t h . f l o o r ( M a t h . r a n d o m ( ) ∗ ( m a x − m i n + 1 ) ) + m i n Math.floor(Math.random()*(max-min+1))+min Math.floor(Math.random()(maxmin+1))+min

6. 静态导入

在JDK5.0以上版本 当一个类中频繁的使用某个静态类的属性和方法时 ,可以采用静态导入的方式来进行简化

import static 包名.类名.*;

不建议使用:

  • 一个类中很少碰到使用一个静态类的多个属性和方法
  • 如果一个类中即使用A静态导入 又使用B静态导入 在后期维护时 很难分辨当前静态属性或者方法的从属关系
import static java.lang.Math.*;
public class StaticImportTest {
    public static void main(String[] args) {
        int num = 38;
        System.out.println(PI);
        System.out.println(ceil(num));
        System.out.println(floor(num));
        System.out.println(round(num));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

7. System

System类代表与操作系统平台进行沟通的类。

它和Math类有类似之处,也定义成final的,构造器也定义成了私有的,所有的属性和方法都是static的。

常用方式

  • 获取标准输入、输出和错误输出流:in/out/err属性
  • 获取当前时间值:currentTimeMillis()/nanoTime()
  • 获取或设置属性:getProperties()/getProperty()/setProperty()
  • 获取操作系统的环境变量:getenv(String name)

集合总是包含以下键的值:

相关值的描述
java.version Java运行时环境版本
java.vendor Java运行时环境供应商
java.vendor.url Java供应商的 URL
java.home Java安装目录
java.vm.specification.version Java虚拟机规范版本
java.vm.specification.vendor Java虚拟机规范供应商
java.vm.specification.name Java虚拟机规范名称
java.vm.version Java虚拟机实现版本
java.vm.vendor Java虚拟机实现供应商
java.vm.name Java虚拟机实现名称
java.specification.version Java运行时环境规范版本
java.specification.vendor Java运行时环境规范供应商
java.specification.name Java运行时环境规范名称
java.class.version Java类格式版本号
java.class.path Java类路径
java.library.path加载库时搜索的路径列表
java.io.tmpdir默认的临时文件路径
java.compiler要使用的 JIT 编译器的名称
java.ext.dirs一个或多个扩展目录的路径
os.name操作系统的名称
os.arch操作系统的架构
os.version操作系统的版本
file.separator文件分隔符(在 UNIX 系统中是“/”)
path.separator路径分隔符(在 UNIX 系统中是“:”)
line.separator行分隔符(在 UNIX 系统中是“/n”)
user.name用户的账户名称
user.home用户的主目录
user.dir用户的当前工作目录

8. Runtime

Runtime类提供的方法用于本应用程序与其运行的环境进行信息的交互。

这个类使用单例模式构建,即应用程序只能通过它提供的**getRuntime()**静态方法来获取唯一的实例。

常用

  • 获取运行时的内存情况:maxMemory()/totalMemory()/freeMemory()

执行指定的命令

Process proc = Runtime.getRuntime().exec(cmd); //执行系统的一些命令
 if (proc.waitFor() != 0) {  //等待子进程结束,判断出口值
 }

  • 1
  • 2
  • 3
  • 4

9. JVM垃圾回收机制

在Java中所有创建出来的对象都可以自动的由JVM进行垃圾回收

判定一个对象是否为垃圾 主要的思想:当前对象是否有引用关系 如果没有则判断为垃圾

堆中的空间 主要分为两个部分

  • 新生代

    • 空间占据整个对存储的2/3 (可设置)
    • 内部分为3个区域 比例为8:1:1
    • Eden Survive1 Survive2
    • 所有新创建的普通对象都会进入到Eden区 直到该区域空间不足时 触发YGC 将存活的对象移动至Survive1区 清空Eden区内容 并将存活对象年龄+1
    • 后续每次触发YGC 都会将当前Eden区和Survive区中存活的数据一致另一个Survive区 并清空其他空间 且所有存活对象年龄+1
  • 老年代

    • 如果创建的对象时一个大对象或者大数组类型的对象时 会直接将该对象放置到老年代空间
    • 当新生代中的对象到达指定年龄(可设置) 就会将该对象移入到老年代
    • 当老年代空间不足时 会触发FullGC 该GC会造成SWT现象 所以尽量减少并控制SWT的周期和时间时后期优化的重点
    • 使用System.gc()或者 Runtime.getRuntime().gc()均可触发FullGC
    • 老年代空间占比为1/3 (可设置)

在JDK1.7版本中还有一个区域 永久代(方法区)

在JDK1.8版本及以后新推出元空间替代原有永久代(整个内存空间,可设置大小)。

10. String 类

java.lang.String代表不可变的字符序列。是final类

创建字符串实例的方式:(分析内存)

String a = “hello”;
String b = new String(“hello”);

  • Java中允许使用符号"+"把两个字符串连接起来组合成一个新字符串:String str = “你好” + “世界”;

  • "+"号也能将字符串与其它的数据类型相连成一个新的字符串。String str = “abc” + 12;

String类中有length()方法可以获得此字符串的长度。
注意跟数组的length属性区分开来。

  String str = "abc你好吗";  str.length();
  
  • 1
  • 2

==号用来比较两个字符串是否存储在同一内存位置
String类的equals()方法用来比较两字符串的内容是否相等

public class TestStringSign {
    public static void main(String[] args) {
        String s1 = "abc中文";
        String s2 = "abc中文";
        String s3 = "abc";
        System.out.println(s1 == s2); // true 
        System.out.println(s1 == s3); // false 
        System.out.println(s1.equals(s2)); // true 
        System.out.println(s1.equals(s3)); // false
    }
}

public class TestStringEquals {
    public static void main(String[] args) {
        String ss1 = new String("abc中文");
        String ss2 = new String("abc中文");
        String ss3 = new String("abc");
        System.out.println(ss1 == ss2); // false
        System.out.println(ss1 == ss3); // false
        System.out.println(ss1.equals(ss2)); // true
      System.out.println(ss1.equals(ss3)); // false
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

常用方法

方法说明
boolean equalsIgnoreCase(String val)此方法比较两个字符串,忽略大小写形式
int compareTo(String value)按字典顺序比较两个字符串。 如果两个字符串相等,则返回 0; 如果字符串在参数值之前,则返回值小于 0; 如果字符串在参数值之后,则返回值大于 0
int compareToIgnoreCase(String val)按字典顺序比较两个字符串,不考虑大小写
boolean startsWith(String value)检查一个字符串是否以参数字符串开始。
boolean endsWith(String value)检查一个字符串是否以参数个字符串结束。
public int indexOf(int ch)返回指定字符ch在此字符串中第一次出现处的索引值;如果未出现该字符,则返回 -1。
public int indexOf(String str)返回第一次出现的指定子字符串str在此字符串中的索引值;如果未出现该字符串,则返回 -1。
public int lastIndexOf(int ch)返回最后一次出现的指定字符在此字符串中的索引值;如果未出现该字符,则返回 -1。
public int lastIndexOf(String str)返回最后一次出现的指定子字符串str在此字符串中的索引值;如果未出现该字符串,则返回 -1。
public char charAt(int index)从指定索引index处提取单个字符,索引中的值必须为非负
public class SearchString {
    public static void main(String[] args) {
        String name = "test@126.com";
        System.out.println("我的Email是: " + name);

        System.out.println("@ 的索引是:" + name.indexOf('@'));
        System.out.println(".com 的索引是:" + name.indexOf(".com"));

        if (name.indexOf('@') < name.indexOf(".com")) {
            System.out.println("该电子邮件地址有效");
        } else {
            System.out.println("该电子邮件地址无效");
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
方法说明
public String substring(int index)提取从位置索引index开始直到此字符串末尾的这部分子字符串
public String substring(int beginIndex, int endIndex)提取从 beginindex开始直到 endindex(不包括此位置)之间的这部分字符串
public String concat(String str)将指定字符str串联接到此字符串的结尾成为一个新字符串返回。
public String replace(char oc, char nc)返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 而生成的。
public String replace(CharSequence target, CharSequence replacement)使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串
public String trim()返回字符串的副本,忽略前导空白和尾部空白
public String toUpperCase();将此字符串中的所有字符都转换为大写
public String toLowerCase();将此字符串中的所有字符都转换为小写
 public class StringMethods {
    public static void main(String [] args) {
        String s1 = "Hello world";
        String s2 = "Hello";
        String s3 = " Hello world ";
        System.out.println(s1.charAt(7));
        System.out.println(s1.substring(3, 8));
        System.out.println(s2.concat("World"));
        System.out.println(s2.replace('l', 'w'));
        System.out.println(s3.trim());
    }
}

public class StringCase {
    public static void main(String [] args) {
        String str = "Hello World";
        System.out.println(str.toUpperCase());
        System.out.println(str.toLowerCase());
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

在String类中定义了一些静态的重载方法

public static String **valueOf(…)**可以将基本类型数据、Object类型转换为字符串。如:

public static String valueOf(double d)double类型数据转成字符串
public static String valueOf(Object obj) 调用obj的toString()方法得到它的字符串表示形式。
int I = 100;
**String s = I + “”;**
  • 1
  • 2
  • 3
  • 4

String中和正则相关的方法

方法说明
public boolean matches(String regex)此字符串是否匹配给定的正则表达式
public String[] split(String regex)根据给定正则表达式的匹配拆分此字符串
public String replaceAll(String regex, String replacement)使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串
字符串的不变性
一个String对象的长度是固定的,不能改变它的内容,或者是附加新的字符到String对象中。
您也许会使用+来串联字符串以达到附加新字符或字符串的目的,但+会产生一个新的String对象。
String s = "6667";
String s1 = "666"+"7";//s == s1
String t = "7";
String s2 = "666"+t;//s2 != s
/*字符串拼接符两端都是常量 则JVM会自动帮你转换成一个拼接好的字符串 最终放置在字符串常量池中
如果字符串中有一个是变量 则会创建n个字符串对象 放置在常量池中
故在程序中不建议使用+ 用来拼接大量的含变量的数据  eg."你好"+nane+"欢迎您第"+n+“次访问”+osName+"系统,当前时间为:"+date+",请开心使用,如遇问题请致电"+phone
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

如果程序对这种附加字符串的需求很频繁,并不建议使用+来进行字符串的串联。应该使用java.lang.StringBuilder 类或者java.lang.StringBuffer类。

11. StringBuild类 & StringBuffer类

java.lang.StringBuilder/StringBuffer代表可变的字符序列。它们提供了相同的操作方法

  • StringBuilder类的方法不保证线程同步,在非线程的情况下使用会有较好的效率
  • StringBuffer类的方法保证线程同步

StringBuilder类的常用构造方法:

  • StringBuilder()
    构造一个其中不带字符的字符串缓冲区,初始容量为 16 个字符

  • StringBuilder(String str)
    构造一个字符串缓冲区,并将其内容初始化为指定字符串内容

方法说明
StringBuilder append(String str);将指定的字符串追加到此字符序列
StringBuilder insert(int offset, String str)将字符串str插入此字符序列指定位置中
int length( )确定 StringBuffer 对象的长度
void setCharAt(int pos, char ch)使用 ch 指定的新值设置 pos 指定的位置上的字符
String toString( )转换为字符串形式
StringBuilder reverse()反转字符串
StringBuilder delete(int start, int end)此方法将删除调用对象中从 start 位置开始直到 end 指定的索引 – 1 位置的字符序列
StringBuilder deleteCharAt(int pos)此方法将删除 pos 指定的索引处的字符
StringBuilder replace(int start, int end, String s)此方法使用一组字符替换另一组字符。将用替换字符串从 start 指定的位置开始替换,直到 end 指定的位置结束

StringBuild和String之间的互相转换

public class StringBuilderTest {
    public static void main(String []args) {

        StringBuilder sb = new StringBuilder("Java");
        sb.append(" action "); 
        sb.append(1.0);

        sb.insert(5, "in ");                               
                               
        String s = sb.toString();   //转换为字符串

        System.out.println(s);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

12. java.util 包

包含collection框架、事件模型、日期和时间设施、国际化和各种实用工具类。

常用类

  • Random类
  • Arrays类
  • 日期和时间相关类

13. Random类

此类用于生成随机数(伪随机)。

两种构造方法

  • Random(); 创建一个新的随机数生成器(使用当前时间纳秒值为种子)
  • Random(long seed); 使用单个long种子创建一个新随机数生成器

如果用相同的种子创建两个 Random 实例,则对每个实例进行相同的方法调用序列,它们将生成并返回相同的数字序列

Random类的方法:

  • nextInt(int n); 返回一个0到n(不包括n)的随机整数值。
  • nextInt()
  • nextFloat()
  • nextDouble() 在 0.0 和 1.0 之间均匀分布的 double 值
  • nextBoolean()
import java.util.Random;

public class TestRandom {
    public static void main(String[] args) {
        Random random = new Random();
        for(int i = 0; i < 5; i++){
            System.out.println(random.nextInt(11));
        }
        Random random2 = new Random(123);
        System.out.println(random2.nextDouble());
        Random random3 = new Random(123);
        System.out.println(random3.nextDouble());
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

注:如果需要获取[min,max]之间的一个随机数 则使用公式
n e x t I n t ( m a x − m i n + 1 ) + m i n nextInt(max-min+1)+min nextInt(maxmin+1)+min

14. Arrays类

常用方法:

  • static String toString(int[] a); 返回数组内容的字符串表示形式

  • static int[] sort(int[] a); 使用快速排序法对指定的int型数组按数字升序进行排序

  • static int binarySearch(int[] a, int key); 使用二分搜索法搜索指定的int型数组,获得指定值所在的索引值

  • static int[] copyOf(int[] original, int newLength);复制指定的数组

15. 日期时间相关类

世界时间标准
  • UTC(Coordinated Universal Time):v世界协调时间、世界统一时间或世界标准时间
  • GMT(Greenwich Mean Time):格林威治标准时间或格林威治平均时间

严格来讲,UTC比GMT更加精确,不过它们的差值不会超过0.9秒

16. java.util.Date 类

java.util.Date类表示特定的瞬间,精确到毫秒

常用构造方法

  • Date() 
    使用系统当前的时间创建 一个Date实例
    内部就是使用System. currentTimeMillis()获取系统当前时间的毫秒数来创建Date对象

  • Date(long dt) 
    使用自1970年1月1日00:00:00 GMT以来的指定毫秒数创建 一个Date实例

常用方法

  • getTime()
    返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
  • toString()
    把此 Date 对象转换为以下形式的 String:
  • dow mon dd hh:mm:ss zzz yyyy :
    星期 月 日 时:分:秒 时区 年

Date类中的API方法不易于实现国际化,大部分被废弃了*。

17. java.util.Calendar 类

Calendar类(日历)是一个抽象基类,主要用于完成日历字段之间相互操作的功能。即可以设置和获取日历数据的特定部分。

使用Calendar.getInstance(); 调用它的子类GregorianCalendar的构造方法 来获取Calendar类的实例

常用方法:

  • 获取指定日历字段值
    public int get(int field)

  • 更改指定日历字段值

    • void set(int field, int value) //设置指定时间单位的值 不会重新计算日历的时间值

    • void add(int field, int amount) //强迫日历系统立即按照增/减时间 重新计算日历的毫秒数和所有字段

//Calendar的使用方式
import java.util.Calendar;
import java.util.GregorianCalendar;
public class CalendarTest {
    public static void main(String[] args) {
        // Calendar cal = Calendar.getInstance();
        Calendar cal = new GregorianCalendar();
        System.out.println("Date 和 Time 的各个组成部分: ");
        System.out.println("年: " + cal.get(Calendar.YEAR));
        // 一年中的第一个月是JANUARY,值为0
        System.out.println("月: " + (cal.get(Calendar.MONTH)));
        System.out.println("日: " + cal.get(Calendar.DATE));
        // Calendar的星期常数从星期日Calendar.SUNDAY是1,星期六Calendar.SATURDAY是7
        System.out.println("星期: " + (cal.get(Calendar.DAY_OF_WEEK)));
        System.out.println("小时: " + cal.get(Calendar.HOUR_OF_DAY));
        System.out.println("分钟: " + cal.get(Calendar.MINUTE));
        System.out.println("秒: " + cal.get(Calendar.SECOND));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
//add 和 set方法示例
import java.util.Calendar;
import java.util.Date;
public class CalendarChangeFieldTest {
    public static void main(String[] args) {
        Calendar calendar = Calendar.getInstance();
        Date date = calendar.getTime(); // 从一个 Calendar 对象中获取 Date 对象
        calendar.setTime(date); //使用给定的 Date 设置此 Calendar 的时间

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
//Calendar与Date的转换
public class Calendar2DateTest {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        //Calendar转换成Date
        Date date = cal.getTime();

        //Date转换成Calendar
        Date date2 = new Date();
        Calendar cal2 = Calendar.getInstance();
        cal2.setTime(date2);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

18. 国际化和格式化相关

国际化(I18N):是指某个软件应用程序运行时,在不改变它们程序逻辑的前提下支持各种语言和区域。

本地化(简称L10N):是指某个软件应用程序在运行时,能支持特定地区。

格式化是指对一些语言和区域敏感的数据按照特定的要求进行特定输出。

java.util.Locale类

代表特定的地理、政治和文化地区

  • 国际标准语言代码是小写的两个字母。中文zh、英文en

  • 区域代码是大写的两个字母。大陆CN、台湾TW、美国US。

java.util.ResourceBundle

用于加载一个资源包

常用方法:

  • static ResourceBundle getBundle(String baseName, Locale lo)

  • String getString(String key)

java.text.MessageFormat类

提供了与语言无关的生成连接消息的方式。格式化字符串

v常用方法:

  • public static String format(String pattern, Object… args)
    pattern用来指定消息模式串,可用"{索引}"来预设占位符。
    args是为消息模式中的指定位符传值
/*
classpath下的资源文件
msgs.properties
msgs_zh_CN.properties 
需要通过native2ascii.exe把本地字符转换成Unicode码
*/
public class I18NHelloWorldTest {
    public static void main(String[] args) {
        Locale myLocale = Locale.getDefault(); //取得系统默认的国家/语言环境
        //根据指定的国家/语言环境加载资源包
        ResourceBundle bundle = ResourceBundle.getBundle("msgs", myLocale);
        //从资源包中取得key所对应的消息
        String msg = bundle.getString("hello");
        //为带占位符的字符串传入参数
        String s = MessageFormat.format(msg, new Object[]{"张三", new Date()});
        System.out.println(s);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

19. java.text.DateFormat类

DateFormat用来格式化(日期->文本)、解析(文本->日期)日期/时间。

  • 获取实例:static getInstance()
  • 也可通过SimpleDateFormat类的构造方法指定模式字符串来创建
  • 格式化:String format(Date d);
  • 解析:Date parse(String s);
public class TestDateFormat {
    public static void main(String[] args) {
        Date date = new Date();
        DateFormat formater = new SimpleDateFormat();
        System.out.println(formater.format(date));
        DateFormat f2 = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        System.out.println(f2.format(date));

        try {
            Date date2 = f2.parse("2008年08月08日 08:08:08");
            System.out.println(date2.toString());
        } catch (ParseException e) {
	e.printStackTrace();
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
时间格式化占位符

占位符

20. java.text.NumberFormat 类

NumberFormat类用来格式化和解析数值。

获取实例:static getInstance()

也可通过DecimalFormat类的构造方法指定模式字符串来创建

  • 格式化:
    String format(long n)
    String format(double d)

  • 解析:
    Number parse(String s);

数字格式化占位符

占位符

public class DecimalFormatTest {
    public static void main(String[] args) {
        System.out.println(formatDecimal("#,###.##", 12345.6));
        System.out.println(formatDecimal("0,000.00", 12345.678));
        System.out.println(formatDecimal("#0.00%", 0.123456789));
        System.out.println(formatDecimal("¤#,##0.00", 12345.6789));
    }
    public static String formatDecimal(String pattern, double num){
        DecimalFormat df = new DecimalFormat(pattern);
        return df.format(num);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

21. 大数字操作

当程序中出现大数字类型并需要进一步计算时:

666666666666666666666666666 * 88888888888888 = ?

Java语言支持大数字的操作。在java.math包中提供了两个专门的类:

  • BigInteger和BigDecimal类

java.math.BigInteger类

BigInteger类代表任意精度的整数

  • public BigInteger add(BigInteger val):加运算。
  • public BigInteger subtract(BigInteger val):减运算。
  • public BigInteger multiply(BigInteger val):乘运算。
  • public BigInteger divide(BigInteger val):除运算。
  • public BigInteger remainder(BigInteger val):模运算。
  • public BigInteger[] divideAndRemainder(BigInteger val):除运算
java.math.BigDecimal

BigDecimal类代表任意精度的浮点数。

  • public BigDecimal add(BigDecimal val):加运算。
  • public BigDecimal subtract(BigDecimal val):减运算。
  • public BigDecimal multiply(BigDecimal val):乘运算。
  • public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode):除运算
    • 标度指的是要保留的小数位位数。
    • BigDecimal.ROUND_HALF_UP 就是我们常用的四舍五入
public class BigDecimalTest {
public static void main(String[] args) {
        BigDecimal bd = new BigDecimal("66666666666.666666666");
        BigDecimal bd2 = new BigDecimal("1234567890.123456789");

        System.out.println("和:"+ bd.add(bd2));
        System.out.println("差:"+ bd.subtract(bd2));
        System.out.println("积:"+ bd.multiply(bd2));
        System.out.println("商:"+bd.divide(bd2, 10, BigDecimal.ROUND_HALF_UP));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

十九、JDK1.8新特性

JDK8版本开始较之前的版本变化较大、核心添加了流式编程、并增加了G1垃圾回收机制

新特性:

1. 接口变化

在JDK1.8中允许接口中有已实现的方法,但该方法必须使用static或者default来修饰。

interface Formula {    
	double calculate(int a);    
	default double sqrt(int a) {return Math.sqrt(a); }//接口中定义默认方法
}

Formula formula = new Formula() {    @Override    public double calculate(int a) {        return sqrt(a * 100);//实现接口方法中调用接口中的默认方法    }};
formula.calculate(100);     // 100.0formula.sqrt(16);           // 4.0

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2. 方法/匿名内部类

在JD1.8中允许方法/匿名内部类中使用非常量的值,即可以使用非final修饰的变量,但是该变量只能赋值一次,如果多次赋值则也不能在其内部使用。

public class Outer{
      public void test(){
          final int x = 9;//可以访问final修饰的变量
          int y = 10;//jdk8.0后 可以访问只赋值过一次的变量
          //y = 12;//非final修饰的变量,如果改变它的值,则不能被方法类访问
            
	class FunctionInner{//方法类(局部内部类)
		public void show(){	System.out.println(x+" "+y);  }
}
      FunctionInner f = new FunctionInner();//创建方法类对象
      f.show();//调用方法类中的方法
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3. 日期时间

在JDK1.8中专门提供了一个java.time包,该包内部提供了操作日期和时间的类,首先这些类大部分是不可变的,类似于String的不可变性。

所以在使用时线程安全,针对于之前的Date或者Calender类来说,当前的时间日期类更加符合日常规则,使用灵活方便。

常用的类:

  • LocalDate
  • LocalTime
  • LocalDateTime
  • Instant
  • DateTimeFormatter
  • ZoneID

时间基础知识

  • 时间的基本知识
关于时间的相关名词含义
GMTGreenwich Mean Time 格林尼治标准时间,十七世纪,格林威治皇家天文台为了海上霸权的扩张计画而进行天体观测。到了1884年决定以通过格林威治的子午线作为划分地球东西两半球的经度零度,并以地球由西向东每24小时自转一周360°,订定每隔经度15°,时差1小时。而每15°的经线则称为该时区的中央经线,将全球划分为24个时区,其中包含23个整时区及180°经线左右两侧的2个半时区,东区的时间要早于西区,例如北京是东八区,东京在东九区,北京现在是下午14:00,那东京就是下午15:00
UTCCoordinated Universal Time 世界协调时间,比GMT更加精准,但功能和精度基本差不多
ISOISO 8601是日期和时间的表示方法,即用字母和符号表示日期和时间
时间戳从1970-01-01 00:00:00与现在所差的秒数,这里的差值表示的伦敦时间的差值,换算到本地时,会加上本地的偏移量的秒数

LocalXXX

在JDK8中 常用的三个时间相关类

  • LocalDate(日期,最小单位到天)
  • LocalTime(时间,最大单位是小时,最小单位到纳秒)
  • LcoalDateTime(日期时间类,最大单位是年,最小单位是纳秒)

以上的类全部都实现了Temporal接口 而其父接口为TemporalAccess
这三个类分别表示使用 ISO-8601日历系统的 日期、时间、日期和时间

常用方法
实例化方法
  • 通过静态方法 :now()(获取的时间是系统当前的时间
  • 通过静态方法:of()(方法参数可以指定时间
/* 通过静态方法 now() 返回该类的实例 */ 
LocalDateTime now = LocalDateTime.now(); //获取当前的日期时分秒 
System.out.println(now); 
LocalDate now1 = LocalDate.now(); //获取当前的日期 
System.out.println(now1); 
LocalTime now2 = LocalTime.now(); //获取当前的时分秒 
System.out.println(now2); 
/* 静态方法 of() 返回该类的实例 */ 
LocalDateTime localDateTime = LocalDateTime.of(2048, 11, 25, 12, 00, 30); //指定日期时分秒 
System.out.println(localDateTime); 
LocalDate date = LocalDate.of(2020, 12, 12); //指定日期 
System.out.println(date); 
LocalTime time = LocalTime.of(14, 20, 30); //指定时分秒 
System.out.println(time);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
获取某个指定时间单位的值

常用get方法

•getYear():获取年

•getHour():获取小时

•getMinute():获取分钟

•getSecond():获取秒值

•getDayOfMonth():获得月份天数(1-31)

•getDayOfYear():获得年份天数(1-366)

•getDayOfWeek():获得星期几(返回一个 DayOfWeek枚举值)

•getMonth():获得月份(返回一个 Month 枚举值)

•getMonthValue():获得月份(1-12)

LocalDateTime now = LocalDateTime.now(); 
//获取年份 
int year = now.getYear(); System.out.println(year); 
//获取月份枚举 
//Month 枚举类,定义了十二个月份 
Month month = now.getMonth(); System.out.println(month); 
//获取月份的数值 
int monthValue = now.getMonthValue(); System.out.println(monthValue); 
//获取当天在本月的第几天
 int dayOfMonth = now.getDayOfMonth(); System.out.println(dayOfMonth); 
//获取小时
 int hour = now.getHour(); System.out.println(hour); 
//获取分钟 
int minute = now.getMinute(); System.out.println(minute); 
//获取秒值 int second = now.getSecond(); System.out.println(second);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
时期时间转换

LocalDateTime可以通过toLocalDate()转换成LocalDate

通过toLocalTime()转成LocalTime


反之也可以

LocalDateTime now = LocalDateTime.now(); 
System.out.println(now); 
//将目标LocalDateTime转换为相应的LocalDate对象 
LocalDate localDate = now.toLocalDate(); System.out.println(localDate); 
//将目标LocalDateTime转换为相应的LocalTime对象
LocalTime localTime = now.toLocalTime(); System.out.println(localTime);

//将LocalDate和LocalTime转换成LocalDateTime
LocalDateTime.0f(localDate,localTime);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
日期时间判定

时间判定方法

  • isAfter():判断一个日期是否在指定日期之后
  • sBefore():v判断一个日期是否在指定日期之前
  • isEqual():v判断两个日期是否相同
  • isLeapYear():判断是否是闰年(注意是LocalDate****类 和 LocalDateTime****类特有的方法)

注意:以上方法只能在相同类型之间进行判定

//获取当前的日期 
LocalDate now = LocalDate.now(); 
//指定的日期 
LocalDate of = LocalDate.of(2015, 12, 12); 
//判断一个日期是否在另一个日期之前 
boolean before = of.isBefore(now); System.out.println(before); 
//判断一个日期是否在另一个日期之后 
boolean after = of.isAfter(now); System.out.println(after); 
//判断这两个日期是否相等 
boolean after1 = now.equals(of); System.out.println(after1); 
//判断闰年 
boolean leapYear = of.isLeapYear(); System.out.println(leapYear);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
对时间进行计算

按时间单位增减方法(plus/minus系列的方法)如果传入的值为负数 则其功能反转

  • plusYears(int offset):增加指定年份
  • plusMonths(int offset):增加指定月份
  • plusWeeks(int offset):增加指定周
  • plusDates(int offset):增加指定日
  • plusHours(int offset):增加指定时
  • plusMinuets(int offset):增加指定分
  • plusSeconds(int offset):增加指定秒
  • plusNanos(int offset):增加指定纳秒
  • minusYears(int offset):减少指定年
  • minusMonths(int offset):减少指定月
  • minusWeeks(int offset):减少指定周
  • minusDates(int offset):减少指定日
  • minusHours(int offset):减少指定时
  • minusMinuets(int offset):减少指定分
  • minusSeconds(int offset):减少指定秒
  • minusNanos(int offset):减少指定纳秒
//增加时间量的方法 plusXXX系类的方法 返回的是一个新的日期对象 
LocalDateTime now = LocalDateTime.now(); 
//可以给当前的日期增加时间量 
LocalDateTime newDate= now.plusYears(1); 
int year = newDate.getYear(); 
System.out.println(year); 
//减去时间量的方法minusXXX 系列的方法 返回的是一个新的日期对象
LocalDate now1 = LocalDate.now(); 
LocalDate newDate2 = now1.minusDays(10);
int dayOfMonth = newDate2.getDayOfMonth(); 
System.out.println(dayOfMonth);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
设置时间

指定年月日时分秒的方法(均不改变原有时间而是返回一个新的时间副本)

  • LocalDate方法

    • with(TemporalAdjuster adjuster):指定特殊时间
    • withYear(int year):指定年
    • withDayOfYear(int dayOfYear):指定日
    • withMonth(int month):指定月
    • withDayOfMonth(int dayOfMonth):指定日
  • LocalTime方法

    • withHour(int hour) :指定小时
    • withMinute(int minute) :指定分钟
    • LocalTime withNano(int nanoOfSecond) :执行纳秒变化值。
    • LocalTime withSecond(int second) :指定秒值

以上方法LocalDateTime 均可用

//设置日期
LocalDate now2 = LocalDate.now();
 LocalDate localDate = now2.withYear(2014); 
System.out.println(localDate);
//设置时间
LocalTime now3 = LocalTime.now();
LocalTime lt = now3.withMinute(33);
System.out.println(lt);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
TemporalAdjuster

TemporalAdjuster接口—— 时间校正器

提供了校正时间的具体执行方案,一般来说常用一个TemporalAdjusters类中的静态方法来获取一些指定好的常用规则,具体参照API文档

我们主要运用它的几个静态方法next()/previous()方法来指定日期
或者采用
自定义的方式
指定日期with(TemporalAdjuster adjuster):指定特殊时间

LocalDate now = LocalDate.now(); 
//指定日期
 //对于一些特殊的日期,可以通过一个工具类TemporalAdjusters 来指定 
TemporalAdjuster temporalAdjuster = TemporalAdjusters.firstDayOfMonth(); //见名知意,本月第一天 LocalDate with = now.with(temporalAdjuster); 
System.out.println(with); 
TemporalAdjuster next = TemporalAdjusters.next(DayOfWeek.SUNDAY); //下周周末
LocalDate with1 = now.with(next); 
System.out.println(with1);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

当TemporalAdjusters中的方法不能满足需求是,可以自定义TemporalAdjuster实现方法,例如获取下一个工作日

LocalDate now1 = LocalDate.now(); 
//自定义日期 —— 下一个工作日 
LocalDate with2 = now1.with(new TemporalAdjuster() { 
@Override 
//参数 nowDate 当前的日期对象 
public Temporal adjustInto(Temporal nowDate) {
 //向下转型 
LocalDate date= (LocalDate) nowDate; 
if(date.getDayOfWeek().equals(DayOfWeek.FRIDAY)){ 
LocalDate localDate = date.plusDays(3); 
return localDate; }
else if(date.getDayOfWeek().equals(DayOfWeek.SATURDAY)){ 
LocalDate localDate = date.plusDays(2); return localDate;
 }else{ LocalDate localDate = date.plusDays(1); return localDate; } } }); 
System.out.println("下一个工作日是:" + with2);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
计算时间相隔

主要用until方法来获取两个时间按照指定单位计算的相隔时间

Period until(ChronoLocalDate endDateExclusive)

将此日期和其他日期之间的期间计算为 Period 。 此方法只有LocalDate可用

long until(Temporal endExclusive, TemporalUnit unit)
根据指定的单位计算直到另一个日期的时间量。 一般使用此方法

//设置日期
LocalDateTime now1 = LocalDateTime .of(2020,11,12,10,20,23);
LocalDateTime now2 = LocalDateTime . of(2020,12,30,12,22,25);
//ChronoUnit是TemporalUntil的实现枚举,具体参考API文档
long days = now1.until(now2, ChronoUnit.DAYS);
long weeks = now1.until(now2, ChronoUnit. WEEKS );
long hours= now1.until(now2, ChronoUnit. HOURS );
System.out.println(“相差天数:”+days);
System.out.println(“相差星期:”+ weeks );
System.out.println(“相差小时:”+hours);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
DateTimeFormatter

DateTimeFormatter类 解析和格式化日期或时间的类

常用API

  • 静态方法ofPattern(“yyyy-MM-dd”):通过给定格式获取对象
  • format():把一个日期对象的默认格式 格式化成指定的格式字符串
  • parse(): 把一个日期字符串转换成日期对象
//指定格式 静态方法 ofPattern() 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // DateTimeFormatter 自带的格式方法 
LocalDateTime now = LocalDateTime.now(); 
//把日期对象,格式化成字符串 
String format = formatter.format(now); 
//刚才的方式是使用的日期自带的格式化方法 
String format1 = now.format(formatter); 
System.out.println(format); System.out.println(format1);
//把字符串转日期对象
LocalDateTime lt1 = formatter. parse(2020-12-25 00:05:36,LocalDateTime::from);//不常用
LocalDateTime lt2= LocalDateTime.parse (2020-12-25 00:05:36, formatter);//常用
System.out.println(lt1 ); System.out.println(lt2);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Instant 瞬时类

在时间线上的瞬间点。 该类在时间线上建立单个瞬时点。 这可能用于在应用程序中记录事件时间戳

Instant.now():获取当前时刻,注意默认获取的是美国时间,而中国是东8区,所以要设置时区

Instant now = Instant.now().plusMillis(TimeUnit.HOURS.toMillis(8));//设置为东8区时间
  • 1
  • getEpochSecond():获取秒为单位的时间戳
  • toEpochMilli():获取毫秒为单位的时间戳
  • Instant和Date时间类的转换
//互相转化 
Date date = Date.from(localDateTime2.atZone(ZoneId.systemDefault()).toInstant()); 
LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(),ZoneId.systemDefault());

  • 1
  • 2
  • 3
  • 4

Duration类

用于计算两个“时间”间隔的类

常用API:

  • 静态方法 between():计算两个时间的间隔,默认是
  • toDays(): 将时间转换为以天为单位的
  • toHours(): 将时间转换为以时为单位的
  • toMinutes(): 将时间转换为以分钟为单位的
  • toMillis():将时间转换为以毫秒为单位的
  • toNanos(): 将时间转换为以纳秒为单位的
  • 这些方法会将上一级时间单位的间隔累计入当前时间单位

注意:不适合LocalDate使用

Instant start = Instant.now(); 
for (int i = 0; i < 15; i++) { System.out.println(i); } 
Instant end = Instant.now(); 
Duration duration = Duration.between(start, end); 
long l = duration.toNanos(); //间隔的时间 
System.out.println("循环耗时:"+l+"纳秒");

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Period类

用于计算两个“日期”间隔的类

常用API:

  • 静态方法 between():计算两个日期的间隔
  • getYears():获取年份
  • getMonths():获取月份
  • getDays():获取天数
  • 这些方法不会计算上一级时间单位的累计

注意:不适合LocalTime使用

//计算两个日期的间隔 
LocalDate birthday = LocalDate.of(2012, 12, 12); 
LocalDate now = LocalDate.now(); 
//模拟从出生到现在,有多少岁,零几个月,几天  计算两个日期的间隔 
Period between = Period.between(birthday, now); 
int years = between.getYears(); 
int months = between.getMonths(); 
int days = between.getDays(); 
System.out.println(“出生了"+years+""+months+""+days+"天了...");

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4. 函数式接口

JDK8.0新增注解类型@FunctionalInterface

在一个接口上方添加@FunctionalInterface,代表这个接口为函数式接口,函数式接口只允许有一个抽象方法。编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。

@FunctionalInterface
//这个接口专门用于类型转换
interface Converter<F, T> {
	T convert(F from);//只有一个抽象方法
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

5. 方法的引用符号 ::

:: 符号

Java 8 可以使用 ::关键字来传递方法或者构造函数引用,只适用于函数式接口,且两个::中间不能加任何符号。

  • ::引用静态方法(为了方便,围绕上面的示例Converter函数式接口来说明)

使用匿名内部类方式实现接口:

//正常情况使用匿名内部类来实现接口中的抽象方法,功能说明:将字符串类型转化为int类型
Convert<Integer, String> convert =
 new Convert<Integer, String>() {
	@Override
	public Integer convert(String from) {
	return Integer.valueOf(from);
	}
};

int temp = convert1.convert("123");
System.out.println(temp);//输出123

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

使用Lambda表达式实现接口

//由于上面功能的实现核心在于Intege.valueOf(from)方法,所以可以直接将该方法的引用交给Convert接口,来实现Convert接口中的抽象方法
//使用::关键字,将Integer的静态方法引用赋值给convert
//注意,引用的方法不能带() 
Convert<Integer, String> convert = Integer::valueOf;

int temp = convert1.convert("123");
System.out.println(temp);//输出123

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

::关键字还可以用来引用对象的方法。

public class TestA {
       //获取字符串的第一个字符
        public  String startWith(String a){
       		 return a.substring(0, 1);
        }
}
public class Test{
       public static void main(String[] args) {
            TestA ta = new TestA();//创建TestA的对象ta
            //将ta的startWith方法的引用赋值给converter 接口,
            //相当于用satrtWith方法中的内容来实现了Convert接口中的抽象方法
            Convert<String, String> converter = ta::startWith;
            String converted = converter.convert("Java");
            System.out.println(converted); //输出”J”
       }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

::关键字可以用来引用构造函数

//首先我们定义一个包含多个构造函数的简单类
class Person {
    String firstName;
    String lastName; 
    Person() {}
    Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

//接下来我们指定一个用来创建Person对象的对象工厂接口
interface PersonFactory<P extends Person> {
    P create(String firstName, String lastName);
}

//这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

/* 说明:
只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会
自动根据PersonFactory.create方法的签名来选择合适的构造函数。
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

6. λ(Lambda) 表达式

简单介绍 λ(Lambda) 表达式

在Java8之前,想实现一个只用一次的接口可以使用匿名内部类

但在Java 8 中你就没必要使用这种传统的匿名内部类的方式了,Java 8提供了更简洁的语法,lambda表达式

适用范围:只能适用于函数(型)接口(functional interface),接口中只有一个抽象方法。

语法:

(形参列表) -> { 要实现对应抽象方法的代码块}

示例:

//定义一个函数接口
@FunctionalInterface
public interface MathUtils{
int add(int a,int b);//返回两个数字相加的和
}
//匿名内部类方法实现
MathUtils mUtils = new MathUtils(){
	@Override
	public int add(int a,int b) {
	        return a+b;
	}
};
System.out.println(mUtils.add(5,3));//输出8

//用λ(Lambda) 表达式方式实现
MathUtils mUtils = (int a, int b) -> {return a+b;};
System.out.println(mUtils.add(5,3));//输出8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

对于完整的语法,可以根据不同的情况简化该结构

  • 参数列表部分:

    • 多个参数时,可以从(int i,int j)->{…} 简化为 (i,j)->{…};
    //以上面的MathUtils接口为例,可简化参数列表
    MathUtils mUtils = (a, b) -> {return a+b;};
    
    System.out.println(mUtils.add(5,3));//输出8
    
    • 1
    • 2
    • 3
    • 4
    • 当只有一个参数时,可以将(i)->{…}简化为 i->{…};
    @FunctionalInterface
    public interface StringUtils {
            //获取str的第一个字符
            String getFirstChar(String str);
    }
    
    
    //可简化参数列表
    StringUtils strUtil = (s)->{ return s.substring(0, 1);};
    
    System.out.println(strUtil.getFirstChar(“Java”));//输出“J”
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 当没有参数时, 写成 ()-> 形式 注意:不能省略()
    @FunctionalInterface
    public interface StringUtils {
            //任意返回一个字符串
            String getString();
    }
    
    //可简化参数列表为
    StringUtils strUtil = ()->{ return “Hello World”;};
    System.out.println(strUtil.getString());//输出“Hello World”
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • {代码块}部分

    • 代码块中需要有多行语句时或者接口返回类型为void时,必须将方法体写在{}中,也就是正常的语法。()->{…}
    • 当方法体中仅有返回值的单一语句时,可以直接写出返回表达式。
    //示例1
    @FunctionalInterface
    public interface StringUtils {
            //任意返回一个字符串
            String getString();
    }
    //可将代码块部分简化为
    StringUtils strUtil = ()-> “Hello World”;
    System.out.println(strUtil.getString());//输出“Hello World”
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    //示例2
    @FunctionalInterface
    public interface MathUtils{
    int add(int a,int b);//返回两个数字相加的和
    }
    
    //可将代码块部分简化为
    MathUtils mUtils = (a,b) -> a+b;
    System.out.println(mUtils.add(5,3));//输出8
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

7. Optional类

JDK8.0针对于流式编程中频繁出现的NullPointException,提供了一个解决方案。Optional是一个容器对象,将非空检测标准化

Optional的常用方法:

方法描述
empty放回一个值为空的Optional实例
filter如果值存在并且满足提供的谓词,就返回包含该Optional对象;否则返回一个空的Optional对象
flatMap如果值存在,就对该值执行提供的mapping函数,将mapping函数返回值直接返回,但要求其返回值类型必须为Optional,如果不是,需要手动将返回值封装成Optional对象,否则就返回一个空的Optional对象 一般用在返回值类型本身就是Optional类型
get如果值存在就返回该Optional对象,否则就抛出一个 NoSuchElementException异常
ifPresent如果值存在就对该值执行传入的方法,否则就什么也不做
isPresent如果值存在就返回true,否则就返回false
map如果值存在,就对该值执行提供的mapping函数调用,并自动将mapping函数返回值用Optional封装并返回,所有返回值都会自动被Optional进行封装 常用在返回值类型是非Optional类型的情况
of如果传入的值存在,就返回包含该值的Optional对象,否则就抛出NullPointerException异常
ofNullable如果传入的值存在,就返回包含该值的Optional对象,否则返回一个空的Optional对象
orElse如果值存在就将其值返回,否则返回传入的默认值 注:orElse中的内容无论如何都会执行
orElseGet如果值存在就将其值返回,否则返回一个由指定的Supplier接口生成的值 注:只有当值为空时才调用
orElseThrow如果值存在就将其值返回,否则返回一个由指定的Supplier接口生成的异常

Optional的使用示例:

//当前有3个类 有嵌套关系 注:为了节省空间 省略所有的get/set方法

class Person{
    private Address addr;
    private String name;
}

class Address{
    private Content content;
    private String no;
}

class Content{
    private String street;
    private String doorNo;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

使用普通范式来避免NPE问题,并通过person获取street的值

boolean f = false;
        if(null != person){
            Address addr = person.getAddr();
            if(null != addr){
                Content content = addr.getContent();
                if(null != content){
                    String street = content.getStreet();
                    System.out.println(street);
                }else{
                    f = true;
                }
            }else{
                f = true;
            }
        }else{
            f = true;
        }
    if(f){
        System.out.println("获取值的过程出现NPE问题");
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

使用Optional来皮面NPE问题,并通过person获取street的值

String street =  Optional.ofNullable(person)
               .map(p->p.getAddr())
               .map(Address::getContent)
               .map(Content::getStreet).orElse("出现NPE问题");
        System.out.println(street);
  • 1
  • 2
  • 3
  • 4
  • 5


2021年2月23号完结

笔者:娄黔子

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/数据灵魂2/article/detail/62177
推荐阅读
相关标签
  

闽ICP备14008679号