java基础语法


基础

快捷键

1
2
3
4
5
6
7
8
9
10
Ctrl+d    //复制当前行到下一行
Ctrl+Alt+I //自动对齐
Ctrl+左键 //查看文档
Shift+点第一个和点最后一个 //多选
Alt+Insert //生成构造器
Ctrl+H //继承关系
Ctrl+Alt+T //将代码包在一个块中,例如try/catch
Ctrl+B //看方法的源码
Alt+7 //打开一个窗口,可以看到类的所有信息
Alt //多选

注释

1
2
3
4
5
6
7
8
9
10
11
//单行注释

/*
多行注释
*/

快捷键:Ctrl+/

/**
*文档注释
*/

标识符和关键字

关键字:

abstract assert boolean break byte
case catch char class const
continue default do double else
enum extends final finally float
for goto if implements import
instanceof int interface long native
new package private protected public
return strictfp short static super
switch synchronized this throw throws
transient try void volatile while

数据类型

强类型语言:要求变量的使用要严格符合规定,所有变量都必须先定义后才能使用

java的数据类型分为两大类:

  1. 基本数据类型:整数类型、浮点类型、字符类型、boolean类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //八大基本数据类型

    //整数
    int num1 = 10;
    byte num2 = 20;
    short num3 = 30;
    long num4 = 30L;//Long类型要在数字后面加个L

    //小数:浮点数
    float num5 = 50.1F; //float类型要在数字后面加个F
    double num6 = 3.1415926;

    //字符
    char name = '常';
    //字符串,String不是关键字,是类

    //布尔值:是非
    boolean flag = true;
  2. 引用数据类型:类、接口、数组

类型转换

1
2
3
4
低 -------------------------------------> 高
byte,short,char-> int-> long-> float-> double

运算中,不同数据类型的数据先转化为同一类型,然后进行运算
1
2
3
4
5
6
//操作比较大的数的时候,注意溢出问题
//JDK7新特性,数字之间可以用下划线分割
int money = 10_0000_0000;
int years = 20;
int tota1 = money*years;//-1474836480,计算的时候溢出了
int total2 = money*(long)years;//20000000000

变量、常量、作用域

变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Hello {
//类变量 static
static double salary = 2500;

//实例变量;从属于对象;
String name;
int age;

public static void main(String[] args) {
//局部变量;必须声明和初始化值
int i = 10;
System.out.println(i);

//变量类型 变量名字 = new Hello();
Hello h = new Hello();
System.out.println(h.name);
System.out.println(h.age);

//类变量 static
System.out.println(salary);
}
}

常量:用final来修饰

1
2
3
static final double PI = 3.14;
//常量名一般用大写字母和下划线:MAX_VALUE
//类名一般首字母大写+驼峰

运算符

  • 算数运算符:+,_,*,/,%,++,–
  • 赋值运算符:=
  • 关系运算符:>,<,>=,<=,==,!=,instanceof(判断是否是一个类的实例)
  • 逻辑运算符:&&,||,!
  • 位运算符:&,|,~,>>,<<,>>>
  • 条件运算符:?:
  • 扩展赋值运算符:+=,-=,*=,/=
1
2
幂运算
double pow = Math.pow(2,3);

包机制

包语句的语法格式为:

1
package pkg1[.pk2[.pkg3...]];

一般利用公司域名倒置作为包名

导包用import语句

javaDoc生成文档

jaovDoc命令用来生成自己的API文档

参数信息

  • @author 作者名
  • @version 版本号
  • @since 指明需要最早使用的jdk版本
  • @param 参数
  • @return 返回值情况
  • @throws 异常抛光情况

命令行生成文档:

1
javadoc -encoding UTF-8 -charset UTF-8 Doc.java

idea生成JavaDoc文档:

1
Tools->Generate JavaDoc

流程控制

用户交互Scanner

next():

  • 以空格为结束符
  • 一定要读取到有效字符后才可以结束输入
  • next()不能得到带有空格的字符串

nextLine():

  • 以Enter为结束符
  • 可以获得空白
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//创建一个扫面器对象,用于接收键盘数据
Scanner scanner = new Scanner(System.in);

System.out.println("使用next方式接收:");

//判断用户有没有输入字符串
if(scanner.hasNext()){
//使用next方式接收
String str = scanner.next();
System.out.println("输出的内容为:"+str);
}

System.out.println("使用nextLine方式接收:");

//判断用户有没有输入字符串
if(scanner.hasNextLine()){
//使用next方式接收
String str = scanner.nextLine();
System.out.println("输出的内容为:"+str);
}


//凡是属于IO流的类如果不关闭会一直占用资源,用完就关
scanner.close();

判断输入的是否是整数:scanner.has.NextInt()

判断输入的是否是小数:scanner.has.NextFloat()

顺序结构

JAVA的基本结构就是顺序结构,除非特别指明,否则就按照顺序一句一句执行

它是任何一个算法都离不开的一种基本算法结构

选择结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if(布尔表达式1){

}else if(布尔表达式2){

}else if(布尔表达式3){

}

switch(expression){

case value:
//语句
break;
case value:
//语句
break;
default:
//语句
}

循环结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
while(布尔表达式){
//循环内容
}

do{
//代码语句
}while(布尔表达式)

for(初始化;布尔表达式;更新){
//代码语句
}

int[] number = {10,20,30,40,50};

for(int x:number){
System.out.println(x);
}

break & continue

  • break在任何循环语句的主题部分,均可用break控制循环的流程。break用于强制退出循环

  • continue用于终止某次循环过程

方法

Java方法是语句的集合,它们在一起执行一个功能

  • 方法是解决一类问题的步骤的有序组合
  • 方法包含于类或对象中
  • 方法在程序中被创建,在其他地方被引用

设计方法的原则:最好保持方法的原子行,就是一个方法只完成1个功能,这样利于我们后期的扩展

定义及调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
修饰符 返回值类型 方法名(参数类型 参数名){

...

方法名

...

return 返回值;

}

public static void max(int num1,int num2){

}

方法重载

重载就是在一个类中,有相同的函数名称,但形参不同的函数

规则:

  • 方法名称必须相同
  • 参数列表必须不同(个数、类型、参数排列顺序等)
  • 方法的返回类型可以相同也可以不相同
  • 仅仅返回类型不同不足以成为方法的重载

理论:方法名称相同时,编译器会根据调用方法的参数个数、参数类型等去逐个匹配,以选择对应的方法, 如果匹配失败,则编译器保持。

可变参数

JDK1.5开始,jav支持传递同类型的可变参数给一个方法

在方法声明中,在指定参数类型后加一个省略号(…)

一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。

递归

递归:自己调用自己

递归结构:

  • 递归头:什么时候不调用自身方法。如果没有头,将陷入死循环
  • 递归体:什么时候调用自身方法

数组

  • 数组是相同类型数据的有序集合
  • 数组描述的是相同类型的若干数据,按照一定的先后次序排列组合而成
  • 每一个数据称作一个数组元素,用下标来访问

数组声明创建

1
2
dateType[] arrayRefVar;
dateType[] arrayRefvar = new dataType[arraySize];
1
2
3
4
5
6
7
8
//静态初始化
int[] a = {1,2,3};
Man[] mans = {new Man(),new Man()};

//动态初始化
int [] a = new int[2];
a[0]=1;
a[1]=2;

数组使用

  • 普通的For循环
  • For-Each循环
  • 数组作方法入参
  • 数组作返回值
1
2
3
for(int array : arrays){
System.out.println(array);
}

多维数组

多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组

1
int[][] a = {{},{}};

Arrays类

数组的工具类java.util.Arrays

  • 给数组赋值:fill
  • 对数组排序:sort
  • 比较数组:equals
  • 查找数组元素:binarySearch

稀疏数组

当一个数组中大部分元素为0,或者为同一值的数组时,可以使用稀疏数组来保存该数组

处理方式:

  • 记录数组一共有几行几列,有多少个不同的值
  • 把具有不同值的元素和行列及值记录在一个小规模的数组中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int[][] array1 = new int[11][11];
array1[1][2] = 1;
array1[2][3] = 2;

int sum = 0;
for(int i = 0; i < 11; i++)
for(int j = 0; j < 11; j++)
if(array1[i][j] != 0)
sum++;

int[][] array2 = new int[sum+1][3];

array2[0][0] = 11;
array2[0][1] = 11;
array2[0][2] = sum;

int count = 0;
for(int i = 0; i < array1.length; i++)
for(int j = 0; j < array1.length; j++)
if(array1[i][j] != 0){
count++;
array2[count][0] = i;
array2[count][1] = j;
array2[count][2] = array1[i][j];
}

面向对象

面向对象思想:

  • 物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后才对某个分类下的细节进行面向过程的思索
  • 面向对象适合处理复杂的问题,适合处理需要多人协作的问题

面向对象编程(Object-Oriented Programming,OOP)

面向对象编程的本质:以类的方式组织代码,以对象的组织(封装)数据

抽象

三大特性:

  • 封装
  • 继承
  • 多态

构造器

  1. 和类名相同
  2. 没有返回值

作用:

  1. new本质在调用构造方法
  2. 初始化对象的值

注意点:

  1. 定义有参构造后,如果想使用无参构造,显示的定义一个无参的构造

封装

  • 我们程序设计要追求”高内聚,低耦合“。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合就是仅暴露少量的方法给外部使用
  • 封装(数据的隐藏):通常应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问
  • 属性私有,get/set

好处:

  • 提高程序的安全性,保护数据
  • 隐藏代码的实现细节
  • 统一接口
  • 系统可维护性增加

继承

  • 继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模
  • extends的意思是”扩展“。子类是父类的扩展
  • JAVA中类只有单继承,没有多继承
  • 继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖。组合、聚合等
  • 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示
  • 在JAVA中,所有的类都默认直接或间接继承Object
  • 私有的东西无法被继承

super

super注意点

  • super调用父类的构造方法,必须在构造方法的第一个
  • super必须只能出现在子类的方法或者构造方法中
  • super和this不能同时调用构造方法

Vs this:

  • 代表的对象不同

    • this:本身调用者这个对象
    • super:代表父类对象的应用
  • 前提

    • this:没有继承也可以使用
    • super:只能在继承条件才可以使用
  • 构造方法

    • this():本来的构造
    • super():父类的构造

方法重写

重写都是方法的重写,和属性无关

需要有继承关系,子类重写父类的方法

子类的方法和子类必须一致,方法体不同

  • 方法名必须相同
  • 参数列表必须相同
  • 修饰符:范围可以扩大不能缩小 public>Protect>Default>private
  • 抛出的异常:范围,可以被缩小,不能扩大:ClassNotFoundException –> Exception(大)

为什么需要重写:

  • 父类的功能,子类不一定需要,或不一定满足

多态

  • 多态即同一方法可以根据发送对象的不同而采用多种不同的行为方式
  • 一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多(父类,有关系的类)
  • 多态存在的条件
    • 有继承关系
    • 子类重写父类方法
    • 父类引用指向子类对象
  • 注意:多态是方法的多态,属性没有多态性
  • 把子类转换为父类,向上转型
  • 把父类转换为子类,向下转型 强制转换
  • 方便方法的调用,减少重复的代码 简洁

instanceof 判断一个对象是什么类型

1
2
Student s = new Student();
System.out.println(s instanceof Student); //true

static

抽象类

abstract,抽象方法,只要方法名字,没有方法实现

  • 不能new这个抽象类,只能靠子类去实现它;约束!
  • 抽象类中可以写普通的方法
  • 抽象方法必须在抽象类中
  • 抽象的抽象:约束

接口

  • 普通类:只有具体实现
  • 抽象类:具体实现和规范(抽象方法)都有
  • 接口:只有规范

OO的精髓,是对对象的抽象,最能体现这一点的就是接口。

声明接口的关键字是interface

实现接口用implements

接口不能被实例化,接口中没有构造方法

内部类

在一个类的内部定义一个类

  • 成员内部类
  • 静态内部类
  • 局部内部类
  • 匿名内部类

异常

  • 异常(Exception)指程序运行中出现的不期而至的各种状况,如:文件找不到,网络连接错误,非法参数等

  • 异常发生在程序运行期间,它影响了正常的程序执行流程

  • 检查性移仓:最具代表的检查性异常是用户错误或问题引起的异常

  • 运行时异常:是可能被程序员避免的异常

  • 错误:错误不是异常,而是脱离程序员控制的问题

异常体系结构

  • java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类
  • 在Java API中已经定义了许多异常类,常分为两大类,错误Error和异常Exception

Error

  • Error类对象由Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关
  • Java虚拟机运行错误,当JVM不再有继续执行操作所需的内存资源是,将出现OutOfMemoryError。这些异常发生时,JVM一般会选择线程终止
  • 还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError),连接错误(LinkageError)。

Exception

  • 在Exception分支中有一个重要的子类RuntimeException(运行时异常)
    • ArrayIndexOutOfBoundsException(数组小标越界)
    • NullPointerException(空指针异常)
    • ArithmeticException(算术异常)
    • MissingResourceException(丢失资源)
    • ClassNotFoundException(找不到类)
  • 这些异常一般时由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生
  • Error和Exception的区别:Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,JVM一般会选择终止线程。Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。

捕获、抛出异常

  • 抛出异常
  • 捕获异常
  • 异常处理五个关键字:try、catch、finally、throw、throws
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class text {
public static void main(String[] args) {
int a = 1;
int b = 0;

//捕获多个异常:从小到大
try {//try监控区域
System.out.println(a / b);
}catch(Error e){//catch(想要捕获的异常类型!)捕获异常
System.out.println("Error");
}catch (Exception e) {
System.out.println("Exception");
}catch(Throwable e){
System.out.println("Throwable");
} finally{
System.out.println("finally");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class text {
public static void main(String[] args) {
try {
new text().test(1,0);
} catch (ArithmeticException e) {
e.printStackTrace();
}
}
public void test(int a,int b) throws ArithmeticException{
if(b==0){
throw new ArithmeticException();//主动抛出异常,一般在方法中使用
}
}
}

自定义异常

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
public class Test {
static void test(int a)throws MyException{
System.out.println("传递的参数为:"+a);

if(a>10){

throw new MyException(a);
}

System.out.println("OK");
}

public static void main(String[] args) {
try {
test(11);
} catch (MyException e) {
e.printStackTrace();
}
}
}

public class MyException extends Exception{

//传递数字>10
private int detail;

public MyException(int x) {
this.detail=x;
}

@Override
public String toString() {
return "MyException{" +
"detail=" + detail +
'}';
}
}

  • 处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
  • 在多重catch快后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
  • 对应不确定的代码,也可以加上try-catch,处理潜在的异常
  • 尽量去处理异常,切忌只是简单地调用PrintStackTrace()去打印输出
  • 具体如何处理异常,要根据不同的业务需求和异常类型去决定
  • 尽量添加finally语句块去释放占用的资源

常用类

常用类

常用类

包装类

  • 针对八种基本数据类型相应的引用类型—-包装类
  • 有了类的特点,就可以调用类中的方法
基本数据类型 包装类 父类
boolean Boolean Object
char Character Object
byte Byte Number
short Short Number
int Integer Number
long Long Number
float Float Number
double Double Number
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
//jak5之前,手动装箱和手动拆箱
int n1 = 100;
Integer integer = new Integer(n1);
Integer integer1 = Integer.valueOf(n1);

int i = integer.intValue();

//jdk5后,就可以自动装箱和自动拆箱
int n2 = 200;
//自动装箱 int->Integer
Integer integer2 = n2;
//自动拆箱 Integer->int
int n3 = integer2;
}

包装类与String互转

1
2
3
4
5
6
7
8
9
10
11
12
13
//包装类(Integer)->String
Integer i = 100;
//方式1
String str1 = i + "";
//方式2
String str2 = i.toString();
//方式3
String str3 = String.valueOf(i);

//String -> 包装类(Integer)
String str4 = "123456";
Integer i2 = Integer.parseInt(str4);
Integer i3 = new Integer(str4);

String

  • 字符串不可变,他们的值在创建后不能被更改
  • String的值不可变,但是它们可以被共享

构造方法:

1
2
3
4
5
6
7
8
9
String s1 = new String();    //初始化新创建的 String对象,使其表示空字符序列。

char[] chs = {'a','b','c'};
String s2 = new String(chs); //根据字符数组的内容来创建字符串对象

byte[] bys = {97,98,99};
String s3 = new String(bys); //根据字节数组的内容来创建字符串对象

String s4 = "abc"; //直接赋值的方式创建字符串对象

字符串比较

使用==做比较

  • 基本类型:比较的是数据值是否相同
  • 引用类型:比较的是地址值是否相同

字符串是对象,它比较内容是否相同,是用equals()比较多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class StringTest {
public static void main(String[] args) {
String name = "xingchuwu";
String password = "123456";
Scanner scanner = new Scanner(System.in);
String name1;
String password1;
for(int i=0;i<3;i++){

System.out.print("请输入用户名:");
name1 = scanner.nextLine();
System.out.print("请输入密码:");
password1 = scanner.nextLine();
if(name1.equals(name) && password1.equals(password)){
System.out.println("登录成功!");
break;
}else if(i==2) {
System.out.println("您的账号已被锁定!");
}else {
System.out.println("登录失败!你还有" + (2-i) + "次机会");
}
}
}
}

字符串遍历

1
2
3
4
5
6
7
8
9
public class StringTest {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String line = sc.nextLine();
for(int i=0;i<line.length();i++){
System.out.println(line.charAt(i));
}
}
}

常用方法

indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。

toCharArray():把字符串转变成字符数组

replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。

StringBuffer

可变长字符串,JDK1.0提供,运行效率慢、线程安全

StringBuilder

可变长字符串,JDK5.0提供,运行效率快、线程不安全

如果对字符串进行拼接操作,每次拼接,都会构建一个新的String对象,即耗时又浪费内存空间。

StringBuilder是一个可变的字符串类,可以把它看成是一个容器

构造方法

1
2
3
4
5
6
7
8
9
10
//创建一个空白可变字符串对象,不含任何内容
StringBuilder sb = new StringBuilder();
System.out.println("sb:"+sb);
System.out.println("sb.length():"+sb.length());

//根据字符串的内容,来创建可变字符串对象
StringBuilder sb2 = new StringBuilder("hello");
System.out.println("sb2:"+sb2);
System.out.println("sb2.length():"+sb2.length());

添加和反转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class StringTest {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();

//public StringBuilder append(任意类型); 添加数据,返回对象本身
// sb.append("hello");
// sb.append(" ");
// sb.append("world");

//链式编程
sb.append("hello").append(" ").append("world");
System.out.println(sb);

//public StringBuilder reverse(); 返回相反的字符序列
sb.reverse();
System.out.println(sb);
}
}

StringBuilder和String相互转换

  • StringBuilder –> String:通过toString()
  • String –> tringBuilder:通过构造方法
1
2
3
4
5
StringBuilder sb = new StringBuilder("hello")

String s = sb.toString;

StringBuilder sb1 = new StringBuilder(s);

Date

构造方法

1
2
3
4
5
6
7
8
//public Date();分配一个Date对象,并初始化,以便它代表它被分配的时间,精确的毫秒
Date d1 = new Date();
System.out.println(d1); //Sun Dec 26 23:28:48 CST 2021

//public Date(long date); 分配一个Date对象,并将其初始化为便是从标准基准时间起指定的毫秒数
long date = 1000*60*60;
Date d2 = new Date(date);
System.out.println(d2); //Thu Jan 01 09:00:00 CST 1970

常用方法

1
2
3
4
5
6
7
8
Date d = new Date();
//public long getTime();获取的是日期对象从1970年1月1日 00:00:00到现在的毫秒值。
System.out.println(d.getTime());

//public void setTime(long time);设置时间,给的时毫秒值。
long time = System.currentTimeMillis();//当前时间
d.setTime(time);
System.out.println(d);

SimpleDateFormat

SimpleDateFormat是一个具体的类,用于以区域设置敏感的方式格式化和解析日期

日期和时间格式由日期和时间模式字符串指定,其中,从’A’到’Z’以及从’a’到’z’引号的字母被解释为表示日期或时间字符串的组件的模式字母

常用的模式字母及对应关系如下:

  • y 年
  • M 月
  • d 日
  • H 时
  • m 秒
  • s 分

方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//格式化,从Date到String
//public final String format(Date date);将日期格式化成日期/时间字符串
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat();
String s = sdf.format(d);
System.out.println(s);//21-12-27 上午12:53

SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String s1 = sdf1.format(d);
System.out.println(s1);//2021年12月27日 00:53:06

//解析:从String到Date
//public Date parse(String source);从给定的字符串开始解析文本以生成日期
String s2 = "2016-12-13 05:02:01";
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d2 = sdf2.parse(s2);
System.out.println(d2);//Tue Dec 13 05:02:01 CST 2016

Calendar

Calendar为某一时刻和一组日历字段之间的转换提供了一些方法,并为操作日历字段提供了一些方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//Calendar提供了一个类方法getInstance用于获取Calendar对象,其日历字段已实验当前日期和时间初始化
Calendar c = Calendar.getInstance();
//public int get(int field);返回给定日历字段的值
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH) + 1;
int date = c.get(Calendar.DATE);
System.out.println(year + "年" + month + "月" + date + "日");

//public abstract void add(int field,int amount);根据日历的规则,将指定的时间量添加或减去给定的日历字段
//10年前的5天后
c.add(Calendar.YEAR,-10);
c.add(Calendar.DATE,5);
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH) + 1;
int date = c.get(Calendar.DATE);
System.out.println(year + "年" + month + "月" + date + "日");

//public final void set(int year,int month,int date);设置当前日历的年月日
Calendar c1 = Calendar.getInstance();
c1.set(2016,11,13);
int year1 = c1.get(Calendar.YEAR);
int month1 = c1.get(Calendar.MONTH)+1;
int date1 = c1.get(Calendar.DATE);
System.out.println(year1 + "年" + month1 + "月" + date1 + "日");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
      Scanner sc = new Scanner(System.in);
System.out.println("请输入年份:");
int year = sc.nextInt();

Calendar c = Calendar.getInstance();
c.set(year,2,1);
c.add(Calendar.DATE,-1);
int date = c.get(Calendar.DATE);
System.out.println(year + "年的2月份有" + date + "天");


//请输入年份:
//2016
//2016年的2月份有29天

集合

集合类的特点:提供一种存储空间可变的存储模型,存储的数据容量可以发生改变

集合体系

集合

集合

ArrayList

ArrayList:

  • 可调整大小的数组实现
  • :是一种特殊的数据类型,泛型

构造和添加方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ArrayListDemo01 {
public static void main(String[] args) {
//public ArrayList();创建一个空的集合对象
ArrayList<String> array= new ArrayList<String>();

//public boolean add(E e);将指定的元素追加到此集合的末尾
array.add("hello");
array.add("world");
array.add("java");

//public void add(int index,E element);在此集合中的指定位置插入指定的元素

array.add(1,"javase");
System.out.println("arrat:" + array);
}
}

常用方法

public boolean remove(Object o);删除指定的元素,返回删除是否成功

public E remove(int index);删除指定索引处的元素,返回被删除的元素

public E set(int index,E element);修改指定索引处的元素,返回被修该的元素

public E get(int index);返回指定索引处的元素

public int size();返回集合中的元素的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ArrayListDemo02 {
public static void main(String[] args) {
//创建一个空的集合对象
ArrayList<String> array= new ArrayList<String>();

//添加元素
array.add("hello");
array.add("world");
array.add("java");

//public boolean remove(Object o);删除指定的元素,返回删除是否成功
// System.out.println(array.remove("world"));
// System.out.println(array.remove("world"));

//public E remove(int index);删除指定索引处的元素,返回被删除的元素
System.out.println(array.remove(1));

//public E set(int index,E element);修改指定索引处的元素,返回被修该的元素
System.out.println(array.set(1,"javaee"));

//public E get(int index);返回指定索引处的元素
System.out.println(array.get(0));
System.out.println(array.get(1));
// System.out.println(array.get(2));

//public int size();返回集合中的元素的个数
System.out.println(array.size());

//输出集合
System.out.println("arrat:" + array);
}
}

Collection

集合概述:

  • 是单例集合的顶层接口,它表示一组对象,这些对象也被称为Collection的元素
  • JDK不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现

创建Collection集合的对象

  • 多态的方式
  • 具体的实现类ArrayList

构造方法

1
2
3
4
5
6
7
8
9
//创建Collection集合的对象,通过多态的方式,ArrayList()
Collection<String> c = new ArrayList<String>();

//添加元素:boolean add (E e)
c.add("hello");
c.add("hello1");
c.add("hello2");

System.out.println(c);

常用方法

  • boolean add(E e) //添加元素
  • boolean remove(Object o) //从该集合中删除指定元素
  • void clear() //清空集合中的元素
  • boolean contains(Object o) //判断集合中是否存在指定的元素
  • boolean isEmpty() //判断集合是否为空
  • int size() //集合的长度,也就是集合中元素的个数

集合的遍历

Iterator:迭代器,集合的专用遍历方式

  • Iterator iterator(); 返回此集合中元素的迭代器,通过集合的iterator()方法得到
  • 迭代器是通过集合的iterator()方法得到的,所以我们说它是依赖于集合而存在的

Iterator中的常用方法:

  • E next(); 返回迭代中的下一个元素
  • boolean hasNext(); 如果迭代具有更多元素,则返回true
1
2
3
4
5
6
7
8
9
10
11
Collection<String> c = new ArrayList<String>();

c.add("hello1");
c.add("hello2");
c.add("hello3");

Iterator<String> it = c.iterator();

while(it.hasNext()){
System.out.println(it.next());
}

List

List集合概述

  • 有序集合(也称为序列 )。 该界面的用户可以精确控制列表中每个元素的插入位置。 用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素。
  • 与Set集合不同,列表通常允许重复的元素。

List集合特点

  • 有序:存储和取出的元素顺序一致
  • 可重复:存储的元素可以重复

构造方法

1
2
3
4
5
6
7
8
9
10
11
List<String> list = new ArrayList<>();

list.add("hello");
list.add("hello");
list.add("hello");
list.add("hello");

Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}

常用方法

  • void add(int index,E element) 在此集合中的指定位置插入指定的元素
  • E remove(int index) 删除指定索引处的元素,返回被删除的元素
  • E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
  • E get(int index) 返回指定索引处的元素

并发修改异常

  • 并发修改异常(ConcurrentModificationException)

  • 原因:迭代器遍历过程中,通过集合对象修改了集合中元素的长度,导致了迭代器获取元素中判断预期修改值和实际修改值不一致

  • 解决方案:用for循环遍历,然后用集合对象做对应的操作即可

ListIterator

ListItertor:列表迭代器

  • 通过List集合的listIterator()方法得到,所以说它是List集合特意的迭代器
  • 用于允许程序员沿任一方向遍历列表的列表迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位置

ListIterator中的常用方法:

  • E next():返回迭代中的下一个元素
  • boolean hasNext():如果迭代具有更多元素,则返回true
  • E previous():返回列表中的上一元素
  • boolean hasPrevious():如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回true
  • void add(E e):将指定的元素插入列表

增强for循环

增强for:简化数组和Collection集合的遍历

  • 实现Iterable接口的类允许其对象成为增强型语句的目标
  • 它时JDK5之后出现的,其内部原理是一个Iterrator迭代器

增强for的格式

  • 格式:
1
2
3
for(元素数据类型 变量名 : 数组或者Collection集合){
//在此处使用变量即可,该变量就是元素
}
  • 范例
1
2
3
4
5
6
7
8
9
10
11
12
13
int[] arr = {1,2,3,4};
for(int i : arr){
System.out.println(i);
}

List<String> list = new ArrayList<String>();
list.add("hello");
list.add("hello2");
list.add("hello3");

for(String s : list){
System.out.println(s);
}

List集合子类

List集合常用子类:ArrayList、LinkedList

  • ArrayList:底层数据结构是数组,查询快,增删慢
  • LinkedList:底层数据结构是链表,查询慢,增删快

LinkedList集合的特有功能

  • public void addFirst(E e); 在该列表开头插入指定的元素
  • public void addLast(E e); 将指定的元素追加到此列表的末尾
  • public E getFirst(); 返回此列表中的第一个元素
  • public E getLast(); 返回此列表中的最后一个元素
  • public E removeFirst(); 从此列表中欧给删除并返回第一个元素
  • public E removeLast(); 从此列表中欧给删除并返回最后一个元素

Set

set集合特点

  • 不包含重复元素的集合
  • 没有带索引的方法,所以不能使用普通for循环遍历
1
2
3
4
5
6
7
Set<String> set = new HashSet<String>();
set.add("hello1");
set.add("hello2");
set.add("hello3");
for(String s : set){
System.out.println(s);
}

哈希值

哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值

Object类中有一个方法可以获取对象的哈希值

  • Public int hashCode(); 返回对象的哈希码值

对象的哈希值特点

  • 同一个对象多次调用hashCode()方法返回的哈希值是相同的
  • 默认情况下,不同对象的哈希值是不同的,而重写hasCode()方法,可以使其相同

HashSet

集合特点

  • 底层数据结构使哈希表
  • 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
  • 没有带索引的方法,所以不能使用普通for循环遍历
  • 由于是Set集合,所以不包含重复元素

LinkedHashSet

LinkedHashSet集合概述和特点

  • 哈希表和链表实现的Set接口,具有可预测的迭代次序
  • 由链表保证元素有序,也就是说元素的存储和取出顺序一致
  • 由哈希表保证元素唯一,也就是说没有重复的元素

TreeSet

TreeSet集合特点

  • 元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方法取决于构造方法
    • TreeSet(); 根据其元素的自然排序进行排序
    • TreeSet(Comparator comparator); 根据指定的比较器进行排序
  • 没有带索引的方法,所以不能使用普通的for循环遍历
  • 由于是Set集合,所以不包含重复元素的集合

Comparable

  • 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
  • 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
  • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
1
2
3
4
5
public int compareTo(Student s){
int num = this.age - s.age;
int num2 = num == 0?this.name.compareTo(s.name):num;
return num;
}

Comparator

1
2
3
4
5
6
7
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>(){
public int compare(Student s1,Student s2){
int num = s1.getAge() - s2.getAge();
int num2 = num == 0?s1.getName().compareTo(s2.getName()):num;
return num2;
}
})

泛型

泛型:是JDK5中引入的特性,它提供了编译时类型安全检测机制,该极致允许在编译时检测到非法的类型

它的本质时参数化类型,也就是说所操作的数据类型被指定为一个参数

泛型定义格式:

  • <类型>:指定一种类型的格式。这里的类型可以看成是形参
  • <类型1,类型2…>:指定多种类型的格式,多种类型之间用逗号隔开。这里的类型可以看成事形参
  • 将来具体调用时给定的类型可以看成事实参,并且实参的类型只能是引用数据类型

泛型的好处:

  • 把运行时期的问题提前到了编译期间
  • 避免了强制类型转换

泛型类

泛型类的定义格式:

  • 格式:修饰符 class 类名 <类型>{}
  • 范例:public class Generic{}
    • 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
1
2
3
4
5
6
7
8
9
10
11
public class Generic<T> {
private T t;

public T getT() {
return t;
}

public void setT(T t) {
this.t = t;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class GenericDemo {
public static void main(String[] args) {
Generic<String> g1 = new Generic<>();
g1.setT("xingchuwu");
System.out.println(g1.getT());

Generic<Integer> g2 = new Generic<>();
g2.setT(100);
System.out.println(g2.getT());
}
}

泛型方法

泛型方法的定义格式:

  • 格式:修饰符 <类型> 返回值类型 方法名(类型 变量名){}
  • 范例:public void show(T t){}
1
2
3
4
5
public class Generic{
public <T> void show(T t){
System.out.println(t);
}
}
1
2
3
4
5
6
7
public class GenericDemo {
public static void main(String[] args) {
Generic g = new Generic();
g.show("xingchuwu");
g.show(100);
}
}

泛型接口

泛型接口的定义格式:

  • 格式:修饰符 interface 接口名<类型>{}
  • 范例:public interface Generic{}
1
2
3
public interface Generic1<T> {
void show(T t);
}
1
2
3
4
5
6
public class GenericImpl<T> implements Generic1<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
1
2
3
4
5
6
7
8
9
public class GenericDemo1 {
public static void main(String[] args) {
GenericImpl<String> g = new GenericImpl<>();
g.show("xingchuwu");

GenericImpl<Integer> g1 = new GenericImpl<>();
g1.show(100);
}
}

类型通配符

为了表示各种泛型List的父类,可以使用类型通配符

  • 类型通配符:<?>
  • List<?>:表示元素类型未知的List,它的元素可以匹配任何的类型
  • 这种带通配符的List仅代表它是各种泛型List的父类,并不能把元素添加到其中

如果说我们不希望List<?>是任何泛型List的父类,只希望它代表某一类泛型List的父类,可以使用类型通配符的上限

  • 类型通配符上限:<? extends 类型>
  • List<? extends Number>:它表示的类型是Number或者其子类型

除了可以指定类型通配符的上限,我们也可以指定其下限

  • 类型通配符下限:<? super 类型>
  • List<? super Number>:它表示的类型是Number或者其父类型
1
2
3
4
5
6
7
8
9
List<?> list1 = new ArrayList<Object>();
List<?> list2 = new ArrayList<Number>();
List<?> list3 = new ArrayList<Integer>();

List<? extends Number> list4 = new ArrayList<Number>();
List<? extends Number> list5 = new ArrayList<Integer>();

List<? super Number> list6 = new ArrayList<Object>();
List<? super Number> list7 = new ArrayList<Number>();

可变参数

可变参数又称参数个数可变,用作方法的形参出现,那么方法的参数个数就是可变的了

  • 格式:修饰符 返回值类型 方法名(数据类型…变量名){}
  • 范例:public static int sum(int…a){}

注意事项:

  • 这里的变量其实是一个数组
  • 如果一个方法有多个参数,包含可变参数,可变参数要放在最后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ArgsDemo {
public static void main(String[] args) {
System.out.println(sum(10,20));
System.out.println(sum(10,20,30));
System.out.println(sum(10,20,30,40));
}

public static int sum (int...a){
int sum = 0;
for(int i : a){
sum += i;
}
return sum;
}
}

可变参数的使用

Arrays工具类中有一个静态方法:

  • public static List asList(T…a); 返回由指定数组支持的固定大小的列表
  • 返回的集合不能做增删操作,可以做修改操作

List接口中有一个静态方法:

  • public static List of(E…elements); 返回包含任意数量元素的不可变列表
  • 返回的集合不能做增删改操作

Set接口中有一个静态方法:

  • public static Set of(E…elements); 返回一个包含任意数量元素的不可变集合
  • 返回的集合不能做增删操作,没有修改的方法

Map

Map集合概述:

  • Interface Map<K,V> K:健的类型;V:值的类型
  • 将健映射到值的对象;不能包含重复的健;每个健可以映射到最多一个值
  • 举例:学生的学号和姓名

创建Map集合的对象:

  • 多态的方式
  • 具体的实现类HashMap
1
2
3
4
5
6
7
8
9
10
11
public class MapDemo1 {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();

map.put("xingchuwu","qianxiyun");
map.put("52","1213");
map.put("52","121399");

System.out.println(map);
}
}

常用方法

  • V put(K key,V value); 添加元素
  • V remove(Object key); 根据健删除键值对元素
  • void clear(); 移除所有的键值对元素
  • boolean containsKey(Object key); 判断集合中是否包含指定的健
  • boolean containsValue(Object value); 判断集合中是否包含指定的值
  • boolean isEmpty(); 判断集合是否为空
  • int size(); 集合长度,也就是集合中键值对的个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MapDemo2 {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();

map.put("xingchuwu","qianxiyun");
map.put("52","1213");


// System.out.println(map.remove("52"));

// map.clear();

System.out.println(map.containsKey("xingchuwu"));
System.out.println(map.containsValue("qianxiyun"));
System.out.println(map.size());
System.out.println(map.isEmpty());


}
}

Map集合的获取功能

  • V get(Object key); 根据健获取值
  • Set keySet(); 获取所有健的集合
  • Collection values(); 获取所有值的集合
  • Set<Map.Entry<K,V>> entrySet(); 获取所有键值对对象的集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MapDemo2 {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();

map.put("xingchuwu","qianxiyun");
map.put("52","1213");

Set<Map.Entry<String,String>> entrySet = map.entrySet();
for(Map.Entry<String,String> me : entrySet){
String key = me.getKey();
String value = me.getValue();
System.out.println(key + ',' + value);
}

}
}

HashMap案例

ArrayList集合存储HashMap元素并遍历:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MapDemo3 {
public static void main(String[] args) {
ArrayList<HashMap> arrayList = new ArrayList<>();
HashMap<String,String> hm1 = new HashMap<>();

hm1.put("hello1","world1");
hm1.put("hello2","world2");
hm1.put("hello3","world3");

HashMap<String,String> hm2 = new HashMap<>();
hm2.put("hello4","world4");
hm2.put("hello5","world5");
hm2.put("hello6","world6");

arrayList.add(hm1);
arrayList.add(hm2);

for(HashMap<String,String> hm : arrayList){
Set<String> keySet = hm.keySet();
for(String key : keySet){
String value = hm.get(key);
System.out.println(key + ',' + value);
}
}
}
}

HashMap集合存储ArrayList元素并遍历:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MapDemo4 {
public static void main(String[] args) {
HashMap<String, ArrayList<String>> hm =new HashMap<>();

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

arrayList1.add("hello1");
arrayList1.add("hello2");
arrayList1.add("hello3");

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

arrayList2.add("hello4");
arrayList2.add("hello5");
arrayList2.add("hello6");

hm.put("hh",arrayList1);
hm.put("hhh",arrayList2);

Set<String> keySet = hm.keySet();
for(String key : keySet){
ArrayList<String> value = hm.get(key);
for(String s : value)
System.out.println(key + ' ' + s);
}
}
}

统计字符串中每个字符出现的次数:

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
public class MapDemo5 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str = sc.nextLine();

HashMap<Character,Integer> hm = new HashMap<>();


for(int i=0;i<str.length();i++){
char key = str.charAt(i);

if(!hm.containsKey(key)){
hm.put(key,1);
}else{
int num = hm.get(key);
hm.put(key,num+1);
}
}

// Set<Character> set = hm.keySet();
// for(Character c : set){
// Integer value = hm.get(c);
// System.out.print(c);
// System.out.print("(");
// System.out.print(value);
// System.out.print(")");
// }

StringBuilder sb = new StringBuilder();

Set<Character> keySet = hm.keySet();
for(Character key : keySet){
Integer value = hm.get(key);
sb.append(key).append("(").append(value).append(")");
}
System.out.println(sb);
}
}

Collctions

Collections类是针对集合操作的工具类

常用方法

  • public static <T extends Comparable<? super T>> void sort(List list); 将指定的列表按升序排序
  • public static void reverse(List<?> list); 反转指定列表中元素的顺序
  • public static void shuffle(List<?> list); 使用默认的随机源随机排序指定列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CollectionsDemo11 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();

list.add(30);
list.add(50);
list.add(20);
list.add(40);
list.add(10);

System.out.println(list);

Collections.sort(list);
System.out.println(list);

Collections.reverse(list);
System.out.println(list);

Collections.shuffle(list);
System.out.println(list);
}
}

案例

ArrayList存储学生对象并排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class CollectionsDemo2 {
public static void main(String[] args) {
ArrayList<Student> arrayList = new ArrayList<>();

Student s1 = new Student("xingchuwu",20);
Student s2 = new Student("qianxiyunu",19);
Student s3 = new Student("xxx",35);
Student s4 = new Student("aaa",20);

arrayList.add(s1);
arrayList.add(s2);
arrayList.add(s3);
arrayList.add(s4);

Collections.sort(arrayList, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num = s1.getAge() -s2.getAge();
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
return num2;
}
});

for(Student s : arrayList){
System.out.println(s.getName() + ' ' + s.getAge());
}
}
}

模拟斗地主:

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
public class CollectionsDemo3 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();

String[] colors = {"♦","♠","♥","♣"};
String[] numbers = {"2","3","4","5","6","7","8","9","10","J","Q","K","A"};
for(String color : colors){
for(String number : numbers)
arrayList.add(color+number);
}
arrayList.add("大王");
arrayList.add("小王");

Collections.shuffle(arrayList);

ArrayList<String> dzArray = new ArrayList<>();
ArrayList<String> nmArray1 = new ArrayList<>();
ArrayList<String> nmArray2 = new ArrayList<>();
ArrayList<String> dpArray = new ArrayList<>();

for(int i=0; i<arrayList.size();i++){
String poker = arrayList.get(i);

if(i >= arrayList.size()-3){
dpArray.add(poker);
}else if(i%3==0){
dzArray.add(poker);
}else if(i%3==1){
nmArray1.add(poker);
}else if(i%3==2){
nmArray2.add(poker);
}
}

lookPoker("地主",dzArray);
lookPoker("农民1",nmArray1);
lookPoker("农民2",nmArray2);
lookPoker("地牌",dpArray);
}

//看牌方法
public static void lookPoker(String name,ArrayList<String> arrayList){
System.out.println(name + "的牌是");
for(String poker : arrayList){
System.out.print(poker + " ");
}
System.out.println();
}
}

模拟斗地主升级版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class CollectionsDemo4 {
public static void main(String[] args) {

HashMap<Integer,String> hm = new HashMap<>();

String[] colors = {"♦","♠","♥","♣"};
String[] numbers = {"2","3","4","5","6","7","8","9","10","J","Q","K","A"};
hm.put(0,"大王");
hm.put(1,"小王");
int num = 2;
for(String number : numbers){
for(String color : colors){
hm.put(num,color+number);
num++;
}
}

Set<Integer> keySet = hm.keySet();
for(Integer key : keySet){
String value = hm.get(key);
StringBuilder sb = new StringBuilder();
sb.append(key).append(":").append(value);
System.out.println(sb);

}
ArrayList<Integer> arrayList = new ArrayList<>();
for(int i=0;i<52;i++)
arrayList.add(i);
Collections.shuffle(arrayList);

TreeSet<Integer> dzArray = new TreeSet<>();
TreeSet<Integer> nmArray1 = new TreeSet<>();
TreeSet<Integer> nmArray2 = new TreeSet<>();
TreeSet<Integer> dpArray = new TreeSet<>();

for(int i=0; i<arrayList.size();i++){
Integer poker = arrayList.get(i);

if(i >= arrayList.size()-3){
dpArray.add(poker);
}else if(i%3==0){
dzArray.add(poker);
}else if(i%3==1){
nmArray1.add(poker);
}else if(i%3==2){
nmArray2.add(poker);
}
}

lookPoker("地主",dzArray,hm);
lookPoker("农民1",nmArray1,hm);
lookPoker("农民2",nmArray2,hm);
lookPoker("地牌",dpArray,hm);
}

public static void lookPoker(String name,TreeSet<Integer> arrayList,HashMap<Integer,String> hm){
System.out.println(name + "的牌是");
for(Integer poker : arrayList){
System.out.print(hm.get(poker) + " ");
}
System.out.println();
}
}

常用API

Math

math包含执行基本数字运算的方法

静态类,通过类名可以直接调用

工具类的设计思想:

  • 构造方法用private修饰
  • 成员用public static修饰

常用方法

方法名 说明
public static int abs(int a) 返回参数的绝对值
public static double ceil(double a) 返回大于或等于参数的最小double值,等于一个整数
public static double floor(double a) 返回小于或等于参数的最大double值,等于一个整数
public static int round(float a) 按照四舍五入返回最接近参数的int
public static int max(int a,int b) 返回两个int值中的较大值
public static int min(int a,int b) 返回两个int值中的较小值
public static double pow(double a,double b) 返回a的b次幂的值
public static double random() 返回值为double的正值,[0.0,1.0)

System

System包含几个有用的类字段和方法,它不能被实例化

常用方法

方法名 说明
public static void exit(int status) 终止当前运行的java虚拟机,非零表示异常终止
public static long currentTimeMillis() 返回当前的时间(以毫秒为单位)
1
2
3
4
5
6
long start = System.currentTimeMillis();
for(int i = 0; i < 10000; i++){
System.out.println(i);
}
long end = System.currentTimeMillis();
System.out.println("共耗时:" + (end - start) + "毫秒");

Object

Object是类层次结构的根,每个类都可以将Object作为超类,所有类都直接或间接的继承自该类

构造方法:public Object();

常用方法

  • public String toString();//返回对象的字符串表示形式,建议所有子类重写此方法,自动生成即可
  • public boolean equals(Object obj);//比较对象是否相等。默认比较地址,重写可以比较内容,自动生成

Arrays

Arrays类包含用于操作数组的各种方法

常用方法

  • public static String toString(int[] a);返回指定数组的内容的字符串表示形式
  • public static void sort(int[] a);按照数字顺序排列指定的数组

IO

IO

IO

IO流概述:

  • IO:输入/输出(Input/Output)
  • 流:是一种抽象概念,是对数据传输的总称。也就是说数据在设备间的传输称为流,流的本质是数据传输
  • IO流就是用来处理设备间数据传输问题的,如:文件复制、文件上传、文件下载

分类:

  • 按照数据的流向:

    • 输入流:读数据
    • 输出流:写数据
  • 按照数据类型:

    • 字节流:字节输入流、字节输出流
    • 字符流:字符输入流、字符输出流
  • 如果数据通过记事本软件打开,可以读懂,用字符流

  • 否则用字节流,一般默认字节流

File

File:它是文件和目录路径名的抽象表示

  • 文件和目录是可以通过File封装成对象的
  • 对应File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是存在的,也可以是不存在的。将来是要通过具体的操作把这个路径的内容转换为具体存在的

构造方法

  • File(String pathname); 通过将给定的路径名字符串转换为抽象路径名来创建新的File实例
  • File(String parent,String child); 从父路径名字符串和子路径名字符串创建新的File实例
  • File(File parent,String child); 从父抽象路径名和子路径名字符串创建新的File实例
1
2
3
4
5
6
7
8
9
10
11
12
13
public class FileDemo1 {
public static void main(String[] args) {
File f1 = new File("E:\\javadProgram\\javaIOTest\\java.txt");
System.out.println(f1);

File f2 = new File("E:\\javadProgram\\javaIOTest","java.txt");
System.out.println(f2);

File f3 = new File("E:\\javadProgram\\javaIOTest");
File f4 = new File(f3,"java.txt");
System.out.println(f4);
}
}

创建方法

  • public boolean createNewFile(); 当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新空文件
  • public boolean mkdir(); 创建由此抽象路径名命名的目录
  • public boolean mkdirs(); 创建由此抽象路径名命名的目录,包括任何必须但不存在的父目录
1
2
3
4
5
6
7
8
9
10
11
12
13
public class FileDemo2 {
public static void main(String[] args) throws IOException {
File f1 = new File("E:\\javadProgram\\javaIOTest\\java.txt");
System.out.println(f1.createNewFile());

File f2 = new File("E:\\javadProgram\\javaIOTest\\javaSE");
System.out.println(f2.mkdir());

File f3 = new File("E:\\javadProgram\\javaIOTest\\javaWeb\\html");
System.out.println(f3.mkdirs());
}
}

判断和获取功能

  • public boolean isDirectory(); 测试此抽象路径名表示的File是否为目录
  • public boolean isFile(); 测试此抽象路径名表示的File是否为文件
  • public boolean exists(); 测试此抽象路径名表示的File是否为存在
  • public String getAbsolutePath(); 返回此抽象路径名的绝对路径名字符串
  • public String getPath(); 将此抽象路径名转换为路径名字符串
  • public String getName(); 返回此抽象路径名表示的文件或目录的名称
  • public String[] list(); 返回此抽象路径名表示的目录中的文件和目录的名称字符串数组
  • public File[] listFiles(); 返回此抽象路径名表示的目录中的文件和目录的File对象数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class FileDemo3 {
public static void main(String[] args) {
File f1 = new File("javaStudy.iml");

System.out.println(f1.isDirectory());
System.out.println(f1.isFile());
System.out.println(f1.exists());

System.out.println(f1.getAbsoluteFile());
System.out.println(f1.getPath());
System.out.println(f1.getName());

File f2 = new File("E:\\javadProgram\\javaIOTest");
String[] strArrays = f2.list();
for(String str : strArrays)
System.out.println(str);

File[] fileArrays = f2.listFiles();
for(File file : fileArrays){
if(file.isFile())
System.out.println(file.getName());
}
}
}

删除功能

  • public boolean delete(); 删除由此抽象路径名表示的文件或目录

注意事项

  • 如果一个目录中有内容(目录,文件),不能直接删除。应该先删除目录中的内容,最后才能删除目录

案例

遍历目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class FileDemo4 {
public static void main(String[] args) {
File f = new File("E:\\javadProgram\\javaIOTest");
if(f.exists())
getAllFilePath(f);
}
public static void getAllFilePath(File f){
File[] fileArray = f.listFiles();
if(fileArray != null){
for(File file : fileArray){
if(file.isDirectory()){
// System.out.print(file.getPath());
// System.out.println(":");
getAllFilePath(file);
}else if(file.isFile()){
System.out.println(file.getAbsoluteFile());
}
}
}
}
}

字节流

字节流抽象基类:

  • InputStream:这个抽象类是表示字节输入流的所有类的超类
  • OutputStream:这个抽象类是表示字节输出流的所有类的超类
  • 子类名特点:子类名称都是以其父类名作为子类名的后缀

FileOutputStream

FileOutputStream:文件输出流用于将数据写入File

  • FileOutputStream(String name):创建文件输出流以指定的名称写入文件

使用字节输出流写数据的步骤:

  • 创建字节输出流对象(调用系统功能创建了文件,创建字节输出流对象,让字节输出流对象指向文件)
  • 调用字节输出流对象的写数据方法
  • 释放资源
1
2
3
4
5
6
7
8
9
public class FileOutputStreamDemo1 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("E:\\javadProgram\\javaStudy\\src\\com\\chang\\base\\myIO\\ByteStreamDemo\\fos.txt");

fos.write(97);

fos.close();
}
}

写数据方法

  • void write(int b):将指定的字节写入此文件输出流,一次写一个字节数据
  • void write(byte[] b):将b.length字节从指定的字节数组写入此文件输出流,一次写一个字节数组数据
  • void write(byte[] b,int off,int len):将len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流,一次写一个字节数组的部分数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class FileOutputStreamDemo2 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("E:\\javadProgram\\javaStudy\\src\\com\\chang\\base\\myIO\\ByteStreamDemo\\fos.txt");

fos.write(97);
byte[] bys = "abcde".getBytes();
fos.write(bys);

fos.write(bys,1,2);

fos.close();
}
}

换行和追加写入

换行:

  • 写完数据后,加换行符
    • windows:\r\n
    • linux:\n
    • mac:\r

追加写入:

  • public FielOutputStream(String name,boolean append)
  • 创建文件输出流以指定的名称写入文件。如果第二个参数为true,则字节将写入文件的末尾而不是开头

异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class FileOutputStreamDemo3 {
public static void main(String[] args) {
FileOutputStream fos = null;

try {
fos = new FileOutputStream("fos.txt");
fos.write("hello".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

FileInputStream

FileInputStream:从文件系统中的文件获取输入字节

  • FileInputStream(String name):通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名

使用字节输入流写数据的步骤:

  • 创建字节输入流对象
  • 调用字节输入流对象的读数据方法
  • 释放资源
1
2
3
4
5
6
7
8
9
10
11
12
public class FileInputStreamDemo1 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("fos.txt");

int by;
while((by = fis.read()) != -1){
System.out.print((char)by);
}

fis.close();
}
}

读字节数组数据

1
2
3
4
5
6
7
8
9
10
11
12
public class FileInputStreamDemo3 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("fos.txt");

byte[] bys = new byte[1024]; //1024及其倍数
int len;
while((len = fis.read(bys)) != -1){
System.out.println(new String(bys,0,len));
}
fis.close();
}
}

案例

字节流复制文本文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class FileInputStreamDemo2 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("fos.txt");
FileOutputStream fos = new FileOutputStream("E:\\javadProgram\\javaIOTest\\java.txt");

int by;
while((by = fis.read()) != -1){
fos.write(by);
}

fis.close();
fos.close();

}
}

复制图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class FileInputStreamDemo4 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("E:\\UpupooWallpaper\\1800011075\\previewFix.jpg");
FileOutputStream fos = new FileOutputStream("E:\\javadProgram\\javaIOTest\\test.jpg");

int by;
while((by = fis.read()) != -1){
fos.write(by);
}

fis.close();
fos.close();
}
}

复制视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class CopyAviDemo {
public static void main(String[] args) throws IOException {
long startTime = System.currentTimeMillis();


// method1();
// method2();
// method3();
method4();

long endTime = System.currentTimeMillis();
System.out.println(endTime-startTime);
}
//基本字节流一次读写一个字节 107610ms
public static void method1() throws IOException{
FileOutputStream fos = new FileOutputStream("E:\\javadProgram\\javaIOTest\\1111.avi");
FileInputStream fis = new FileInputStream("C:\\Users\\10923\\Videos\\Captures\\1111.avi");

int by;
while((by = fis.read()) != -1){
fos.write(by);
}

fos.close();
fis.close();
}

//基本字节流一次读写一个字节数组 143ms
public static void method2() throws IOException{
FileOutputStream fos = new FileOutputStream("E:\\javadProgram\\javaIOTest\\1111.avi");
FileInputStream fis = new FileInputStream("C:\\Users\\10923\\Videos\\Captures\\11112.avi");

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

fos.close();
fis.close();
}

//字节缓冲流一次读写一个字节 189ms
public static void method3() throws IOException{

BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\Users\\10923\\Videos\\Captures\\1111.avi"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\javadProgram\\javaIOTest\\11113.avi"));

int by;
while((by = bis.read()) != -1){
bos.write(by);
}

bos.close();
bis.close();
}

//字节缓冲流一次读写一个字节数组 35ms
public static void method4() throws IOException{
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\Users\\10923\\Videos\\Captures\\1111.avi"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\javadProgram\\javaIOTest\\11114.avi"));

byte[] bys = new byte[1024];
int len;
while((len = bis.read(bys)) != -1){
bos.write(bys,0,len);
}

bos.close();
bis.close();
}

}

字节缓冲流

字节缓冲流:

  • BufferedOutputStream:该类实现缓冲输出流。通过设置这样的输出流,应用程序可以想底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用
  • BufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组。当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节

构造方法:

  • 字节缓冲输出流:BufferedOutputStream(OutputSteam out)
  • 字节缓冲输入流:BufferedInputStream(InputStream in)

字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本得字节流对象进行操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class BufferStreamDemo {
public static void main(String[] args) throws IOException {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("fos.txt"));

bos.write("hello\r\n".getBytes());
bos.write("world\r\n".getBytes());

bos.close();

BufferedInputStream bis = new BufferedInputStream(new FileInputStream("fos.txt"));

byte[] bys = new byte[1024];
int len;
while((len = bis.read(bys)) != -1){
System.out.println(new String(bys,0,len));
}
}
}

字符流

由于字节流操作中文不是特别方便,所有java就提供了字符流

  • 字符流 = 字节流 + 编码表

用字节流复制文本文件时,最终底层操作会自动进行字节拼接成中文

  • 汉字在存储时,无论选择哪种编码存储,第一个字节都是负数

编码表

  • Ascll

  • GBK

  • UTF-8

编码:

  • byte[] getBytes(),使用平台的默认字符集将String编码为一系列字节,将结果存储到新的字节数组中
  • byte[] getBytes(String charsetName),使用指定得字符集将String编码为一系列字节,将结果存储到新的字节数组中

解码:

  • String(byte[] bytes),通过使用平台的默认字符集解码指定的字节数组来构造新的String
  • String(byte[] bytes, String charsetName),通过指定的字符集解码指定的字节数组来构造新的String

字符流的编码解码

字符流抽象基类:

  • Reader:字符输入流的抽象类
  • Writer:字符输出流的抽象类

字符流中和编码解码问题相关的两个类:

  • InputStreamReader
  • OutputStreamWriter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ConversionStreamDemo1 {
public static void main(String[] args) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("fos.txt"),"GBK");

osw.write("行初雾");
osw.close();

InputStreamReader isr = new InputStreamReader(new FileInputStream("fos.txt"),"GBK");
int ch;
while((ch = isr.read()) != -1){
System.out.println((char)ch);
}
}
}

写数据方法

  • void write(int c): 写一个字符
  • void write(char[] cbuf): 写入一个字符数组
  • void write(char[] cbuf,int off,int len): 写入字符数组的一部分
  • void write(String str): 写一个字符串
  • void write(String str,int off,int len): 写一个字符串的一部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("fos.txt"));

osw.write(97);
osw.flush();

char[] chs = {'a','b','c','d','e'};
osw.write(chs);
osw.flush();

osw.write(chs,1,3);
osw.flush();

osw.write("hello");
osw.flush();

osw.write("hello",1,3);

osw.close();
}
}

读数据方法

  • int read(): 一次读一个字符数据
  • int read(char[] cbuf): 一次读一个字符数组数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("fos.txt"));

// int ch;
// while((ch = isr.read()) != -1){
// System.out.println((char)ch);
// }

char[] chs = new char[1024];
int len;
while((len = isr.read(chs)) != -1){
System.out.println(new String(chs,0,len));
}

isr.close();
}
}

案例

复制java文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CopyJavaDemo01 {
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\javadProgram\\javaStudy\\src\\com\\chang\\base\\myIO\\ConversionStreamDemo\\ConversionStreamDemo1.java"));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\javadProgram\\javaIOTest\\copy.java"));

// int ch;
// while((ch = isr.read()) != -1){
// osw.write(ch);
// }

char[] chs = new char[1024];
int len;
while((len = isr.read(chs)) != -1){
osw.write(chs,0,len);
}
isr.close();
osw.close();
}
}

复制java文件(改进版)

为了简化书写,转换流提供了对应的子类

  • FileReader:用于读取字符文件的便捷类
    • FileReader(String fileName)
  • FileWriter:用于写入字符文件的便捷类
    • FileWriter(String fileName)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CopyJavaDemo02 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("E:\\\\javadProgram\\\\javaStudy\\\\src\\\\com\\\\chang\\\\base\\\\myIO\\\\ConversionStreamDemo\\\\ConversionStreamDemo1.java");
FileWriter fw = new FileWriter("E:\\\\javadProgram\\\\javaIOTest\\\\copy.java");

// int ch;
// while((ch = fr.read()) != -1){
// fw.write(ch);
// }

char[] chs =new char[1024];
int len;
while((len = fr.read(chs)) != -1){
fw.write(chs,0,len);
}

fr.close();
fw.close();

}
}

复制java文件(字符缓冲流改进版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CopyJavaDemo3 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("E:\\javadProgram\\javaStudy\\src\\com\\chang\\base\\myIO\\ConversionStreamDemo\\ConversionStreamDemo1.java"));
BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\javadProgram\\javaIOTest\\copy3.java"));

// int ch;
// while((ch = br.read()) != -1){
// bw.write(ch);
// }

char[] chs = new char[1024];
int len ;
while((len = br.read(chs)) != -1){
bw.write(chs,0,len);
}

br.close();
bw.close();
}
}

字符缓冲流

  • BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
  • BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。默认值足够大,可用于大多数用途
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class BufferedStreamDemo {
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("fos.txt"));

bw.write("hello\r\n");
bw.write("hello\r\n");
bw.close();

BufferedReader br = new BufferedReader(new FileReader("fos.txt"));

int ch;
while((ch = br.read()) != -1){
System.out.print((char)ch);
}
br.close();
}
}

特有功能

  • BufferedWriter:
    • void newLine():写一行行分隔符,行分割符字符串由系统属性定义
  • BufferedReader:
    • public String readLine():读一行文字,结果包含行的的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class BufferedStreamDemo2 {
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("fos.txt"));
for(int i=0;i<10;i++){
bw.write("hello"+i);
bw.newLine();
bw.flush();
}

bw.close();

BufferedReader br = new BufferedReader(new FileReader("fos.txt"));
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
br.close();
}
}

案例

集合到文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ArrayListToTxtDemo {
public static void main(String[] args) throws IOException {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("hello1");
arrayList.add("hello2");
arrayList.add("hello3");

BufferedWriter bw = new BufferedWriter(new FileWriter("fos.txt"));

for(String array : arrayList){
bw.write(array);
bw.newLine();
bw.flush();
}
bw.close();
}
}

文件到集合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TxtToArrayListDemo {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("fos.txt"));

ArrayList<String> arrayList = new ArrayList<>();
String line;
while((line = br.readLine()) != null){
arrayList.add(line);
}
br.close();

for(String s : arrayList){
System.out.println(s);
}
}
}

复制单级文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class CopyFolderDemo {
public static void main(String[] args) throws IOException {
File srcFolder = new File("E:\\javadProgram\\javaIOTest\\javaSE");

String srcFolderName = srcFolder.getName();

File destFolder = new File("E:\\javadProgram\\javaIOTest\\javaWeb",srcFolderName);

if(!destFolder.exists()){
destFolder.mkdir();
}

File[] files = srcFolder.listFiles();
for(File srcFile : files){
String srcFileName = srcFile.getName();
File destFile = new File(destFolder,srcFileName);
copyFile(srcFile,destFile);
}
}
private static void copyFile(File srcFile,File destFile) throws IOException{
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));

byte[] bys = new byte[1024];
int len;
while((len = bis.read(bys)) != -1){
bos.write(bys,0,len);
}
bis.close();
bos.close();
}
}

复制多级文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class CopyFoldersDemo {
public static void main(String[] args) throws IOException {
File srcFile = new File("E:\\javadProgram\\javaIOTest");

File destFile = new File("E:\\javadProgram\\javaIOTest2");

copyFolder(srcFile,destFile);

}

private static void copyFolder(File srcFile,File destFile) throws IOException {
if(srcFile.isDirectory()){
String srcFileName = srcFile.getName();
File destFolder = new File(destFile,srcFileName);
if(!destFolder.exists()){
destFolder.mkdir();
}
File[] files = srcFile.listFiles();
for(File file : files){
copyFolder(file,destFolder);
}
}else if(srcFile.isFile()){
File newFile = new File(destFile,srcFile.getName());
copyFile(srcFile,newFile);
}
}

private static void copyFile(File srcFile, File destFile) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));

byte[] bys = new byte[1024];
int len;
while((len = bis.read(bys)) != -1){
bos.write(bys,0,len);
}
bis.close();
bos.close();
}
}

特殊操作流

标准输入输出流

System类中有两个静态的成员变量:

  • public static final InputStream in:标准输入流。通常该流对应于键盘输入或由主机环境或用户指定的另一个输入源
  • public static final PrintStream out:标准输出流。通常该流对应于显示输出或由主机环境或用户指定的另一个输入目标

自己实现键盘录入数据

  • BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

Java提供了一个类实现键盘录入

  • Scanner sc = new Scanner(System.in);

输出语句的本质:是一个标准的输出流

  • PrintStream ps = System.out;
  • PrintStream类有的方法,System.out都可以使用

打印流

打印流分类:

  • 字节打印流:PrintStream
  • 字符打印流:PrintWriter

打印流的特点:

  • 只负责输出数据,不负责读取数据
  • 有自己的特有方法

字节打印流:

  • PrintStream(String fileName):使用指定的文件名创建新的打印流
  • 使用继承父类的方法(write())写数据,查看时会转码;使用自己特有的方法(print())写数据,查看的数据原样输出

字符打印流:

  • PrintWriter(String fileName) 使用指定的文件名创建一个新的PrintWriter,而不需要自动执行刷新
  • PrintWriter(Writer out,boolean autoFlush) 创建一个新的PrintWriter,out:字符输出流;autoFlush:若为真,则Println,printf,format方法将刷新输出缓冲区

对象序列化流

对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象

对象序列化流:ObjectOutputStream

对象反序列化流

对象反序列化流:ObjectInputStream

多线程

多线程

多线程

多线程

线程简介

  • 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
  • 进程是执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位
  • 一个进程中可以包含若干个线程。线程是CPU调度和执行的单位

核心概念:

  • 线程就是独立的执行路径
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程
  • main()称之为主线程,为系统的入口,用于执行整个程序
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的
  • 堆同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
  • 线程会带来额外的开销,如cpu调度时间,并发控制开销
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

线程实现(重点)

创建线程的三种方式:

  • 继承Thread类
    • 子类继承Thread类具有多线程能力
    • 启动线程:子类对象.start()
    • 不建议使用:避免OOP单继承局限性
  • 实现Runnable接口
    • 实现接口Runnable具有多线程能力
    • 启动线程:传入目标对象+Thread对象.start()
    • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
  • 实现Callable接口

Thread

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//总结:注意,线程开启不一定立即执行,由CPU调度执行
public class ThreadDemo1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("run------------" + i);
}
}

public static void main(String[] args) {
ThreadDemo1 threadDemo1 = new ThreadDemo1();
threadDemo1.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main-" + i);
}
}
}

案例:下载网图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class ThreadDemo2 extends Thread{
private String url; //网络图片地址
private String name; //保存的文件名

public ThreadDemo2(String url,String name){
this.name = name;
this.url = url;
}

@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载了文件:" + name);
}

public static void main(String[] args) {
ThreadDemo2 t1 = new ThreadDemo2("https://bkimg.cdn.bcebos.com/pic/730e0cf3d7ca7bcb3700f79abe096b63f624a80f?x-bce-process=image/resize,m_lfit,w_220,limit_1/format,f_auto","E:\\javadProgram\\javaIOTest\\1.jpg");
ThreadDemo2 t2 = new ThreadDemo2("https://bkimg.cdn.bcebos.com/pic/730e0cf3d7ca7bcb3700f79abe096b63f624a80f?x-bce-process=image/resize,m_lfit,w_220,limit_1/format,f_auto","2.jpg");
ThreadDemo2 t3 = new ThreadDemo2("https://bkimg.cdn.bcebos.com/pic/730e0cf3d7ca7bcb3700f79abe096b63f624a80f?x-bce-process=image/resize,m_lfit,w_220,limit_1/format,f_auto","3.jpg");

t1.start();
t2.start();
t3.start();
}
}

//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}

Runnable

  • 自定义类实现Runnable接口
  • 实现run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ThreadDemo3 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("run----------" + i);
}
}

public static void main(String[] args) {
ThreadDemo3 threadDemo3 = new ThreadDemo3();

new Thread(threadDemo3).start();

for (int i = 0; i < 1000; i++) {
System.out.println("main--" + i);
}
}
}

案例

并发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ThreadDemo4 implements Runnable{
//票数
private int ticketNums = 10;

@Override
public void run() {
while(true){
if(ticketNums <=0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-> 拿到了第" + ticketNums-- + "张票");
}
}

public static void main(String[] args) {
ThreadDemo4 ticket = new ThreadDemo4();

new Thread(ticket,"A").start();
new Thread(ticket,"B").start();
new Thread(ticket,"C").start();
}
}

龟兔赛跑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class ThreadDemo5 implements Runnable{

private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {

//模拟兔子休息
if(Thread.currentThread().getName() == "兔子" && i%10 ==5){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

boolean flag = gameOver(i);
if(flag){
break;
}
System.out.println(Thread.currentThread().getName() + "-->跑了"+ i + "步");
}
}



//判断是否完成比赛
private boolean gameOver(int steps){
if(winner!=null){
return true;
}else{
if(steps>=100){
winner = Thread.currentThread().getName();
System.out.println("winner is " + winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
ThreadDemo5 threadDemo5 = new ThreadDemo5();
new Thread(threadDemo5,"兔子").start();
new Thread(threadDemo5,"乌龟").start();
}
}

Callable

  • 实现Callable接口,需要返回值类型
  • 重写call方法,需要抛出异常
  • 创建目标对象
  • 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(3);
  • 提交执行:Future r1 = ser.submit(t1);
  • 获取结果:boolean rs1 = r1.get();
  • 关闭服务:ser.shutdownNow();
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
public class ThreadDemo6 implements Callable<Boolean> {
private String url; //网络图片地址
private String name; //保存的文件名

public ThreadDemo6(String url,String name){
this.name = name;
this.url = url; }
@Override
public Boolean call(){
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载了文件:" + name);
return true;
}

public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadDemo6 t1 = new ThreadDemo6("https://bkimg.cdn.bcebos.com/pic/730e0cf3d7ca7bcb3700f79abe096b63f624a80f?x-bce-process=image/resize,m_lfit,w_220,limit_1/format,f_auto","E:\\javadProgram\\javaIOTest\\1.jpg");
ThreadDemo6 t2 = new ThreadDemo6("https://bkimg.cdn.bcebos.com/pic/730e0cf3d7ca7bcb3700f79abe096b63f624a80f?x-bce-process=image/resize,m_lfit,w_220,limit_1/format,f_auto","E:\\javadProgram\\javaIOTest\\2.jpg");
ThreadDemo6 t3 = new ThreadDemo6("https://bkimg.cdn.bcebos.com/pic/730e0cf3d7ca7bcb3700f79abe096b63f624a80f?x-bce-process=image/resize,m_lfit,w_220,limit_1/format,f_auto","E:\\javadProgram\\javaIOTest\\3.jpg");

//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);

//提交执行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
Future<Boolean> r3 = ser.submit(t3);

//获取结果
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r2.get();

//关闭服务
ser.shutdownNow();

}
}

Lambda

  • 避免匿名内部类定义过多
  • 其实质属于函数式编程得概念
  • 理解Functional Interface(函数式接口)是学习Java8 lambda表达式得关键所在
  • 函数式接口得定义:
    • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
    • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class LambdaDemo1 {

//3.静态内部类
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda2");
}
}

public static void main(String[] args) {
ILike like = new Like();
like.lambda();

like = new Like2();
like.lambda();

//4.局部内部类
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda3");
}
}
like = new Like3();
like.lambda();

//5.匿名内部类
like = new ILike() {
@Override
public void lambda() {
System.out.println("i like lambda4");
}
};
like.lambda();

//6.用lambda简化
like = ()-> {
System.out.println("i like lambda5");
};
like.lambda();
}
}
//1.定义一个函数式接口
interface ILike{
void lambda();
}

//2.实现类
class Like implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class LambdaDemo2 {
public static void main(String[] args) {
ILove love = null;
//简化1.参数类型
love = (a)->{
System.out.println("i love you ->" + a);
};
//简化2.简化括号
love = a -> {
System.out.println("i love you ->" + a);
};
//简化3.去掉花括号
love = a -> System.out.println("i love you ->" + a);
love.love(520);
}
}
interface ILove{
void love(int a);
}

总结:

  • lambda表达式只能在有一行代码的情况下才能简化成为一行,如果有多好,那么就用代码块包裹
  • 前提是接口为函数式接口
  • 多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号

静态代理模式

  • 真实对象和代理对象都要实现同一个接口

  • 代理对象要代理真实角色

  • 好处:

    • 代理对象可以做很多真实对象做不了得事情
    • 真实对象专注做自己得事情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class StaticProxy {
public static void main(String[] args) {
// WeddingCompany weddingCompany = new WeddingCompany(new You());
// weddingCompany.HappyMarry();
You you = new You();

new Thread( ()-> System.out.println("aaa")).start();

new WeddingCompany(you).HappyMarry();
}
}

interface Marry{
void HappyMarry();
}
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("HAPPYMARRY");
}
}
class WeddingCompany implements Marry{
private Marry target;

public WeddingCompany(Marry target) {
this.target = target;
}

@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}

线程状态

线程状态

线程方法

  • setPriority(int newPriority) 更改线程优先级
  • static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
  • void join() 等待该线程终止
  • static void yield() 暂停当前正在执行的线程对象,并执行其他线程
  • void interrupt() 中断线程,别用这个方式
  • boolean isAlive() 测试线程是否处于活动状态

线程停止:

  • 建议线程正常停止—>利用次数,不建议死循环
  • 建议使用标志位–>设置一个标志位
  • 不要使用stop或者destroy等过时或JDK不建议使用的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class TestStop implements Runnable{
//1.设置一个标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run.......Thread" + i++);
}
}

//2.设置一个公开的方法停止线程,转换标志位
public void stop(){
this.flag = false;
}

public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main"+i);
if(i==900){
testStop.stop();
System.out.println("线程该停止了");
}
}
}
}

线程休眠

  • sleep(时间)指定当前线程阻塞的毫秒数
  • sleep存在异常InterruptedException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TestSleep2 {

public static void main(String[] args) throws InterruptedException {
Date startTime = new Date(System.currentTimeMillis());
while(true){
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis());
}
}
public static void tenDown() throws InterruptedException {
int num = 10;
while(true){
Thread.sleep(1000);
System.out.println(num--);
if(num<=0)
break;
}
}
}

线程礼让

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让cpu重新调度,礼让不一定成功!看CPU心情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();

new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
}

线程强制执行

  • Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
  • 可以想象成插队
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("线程vip来了"+i);
}
}

public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();

for (int i = 0; i < 500; i++) {
if(i==200){
thread.join();
}
System.out.println("main"+i);
}
}
}

线程状态观测

Thread.State:线程状态。线程可以处于以下状态之一

  • NEW
  • RUNNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread t= new Thread( ()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("/////");
});
Thread.State state = t.getState();
System.out.println(state); //NEW

t.start();
state = t.getState();
System.out.println(state); //RUNNABLE

while(state!=Thread.State.TERMINATED){
Thread.sleep(200);
state = t.getState();
System.out.println(state);
}
}
}

线程优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度那个线程来执行
  • 现成的优先级用数字表示,范围从1~10
    • Thread.MIN_PRIORITY = 1
    • Thread.MAX_PRIORITY = 10
    • Thread.NORM_PRIORITY = 5
  • 使用一些方式改变或获取优先级
    • getPriority()
    • setPriority(int x)
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
public class TestPriority {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());

MyPriority myPriority = new MyPriority();

Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5 = new Thread(myPriority);
Thread t6 = new Thread(myPriority);

t1.start();

t2.setPriority(7);
t2.start();

t3.setPriority(4);
t3.start();

t4.setPriority(Thread.MAX_PRIORITY);
t4.start();

// t5.setPriority(-1);
// t5.start();
//
// t6.setPriority(11);
// t6.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {

System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}

收获线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如,后台记录操作日志,监控内存,垃圾回收等待
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class TextDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();

Thread thread = new Thread(god);
thread.setDaemon(true);

thread.start();

new Thread(you).start();
}
}

//上帝
class God implements Runnable{
@Override
public void run() {
while(true)
System.out.println("上帝保佑着你");
}
}

//你
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("你一生都开心的活着");
}
System.out.println("byebye");
}
}

线程同步(重点)

  • 线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
  • 形成条件:队列+锁
  • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
    • 一个线程持有锁会导致其他所有需要此锁的线程挂起
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

线程不安全的集合:

1
2
3
4
5
6
7
8
9
10
11
12
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());

}
}

同步方法

  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以只需要针对方法提出一套机制,也就是synchronized关键字,它包括两种用法:synchronized方法和synchronzied块
    • 同步方法:public synchronized void method(int args){}
  • synchronzied方法控制对对象的访问,每个对象对应一把锁,每个synchronzied方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
    • 缺陷:若将一个大的方法申明为synchronzied将会影响效率
    • 弊端:方法里需要修改的内容才需要锁,锁的太多,浪费资源

同步块

  • 同步块:synchronzied(Obj){}
  • Obj称之为同步监视器
    • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
  • 同步监视器的执行过程
    • 第一个线程访问,锁定同步监视器,执行其中代码
    • 第二个线程访问,发现同步监视器被锁定,无法访问
    • 第一个线程访问完毕,解锁同步监视器
    • 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class ThreadDemo4 {

public static void main(String[] args) {
BuyTicket ticket = new BuyTicket();

new Thread(ticket,"A").start();
new Thread(ticket,"B").start();
new Thread(ticket,"C").start();
}

}

class BuyTicket implements Runnable{
//票数
private int ticketNums = 10;
boolean flag = true;

@Override
public void run() {
System.out.println(Thread.currentThread().getName());

while(flag){

try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private synchronized void buy() throws InterruptedException {
if(ticketNums<=0){
flag = false;
return;
}

System.out.println(Thread.currentThread().getName() + "-> 拿到了第" + ticketNums-- + "张票");
}
}

锁的对象是变化的量,需要增删改的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {

new Thread(()->{

synchronized (list){
list.add(Thread.currentThread().getName());
}

}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());

}
}

死锁

  • 多个线程各自占有一些共有资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象的锁”时,就可能发生死锁问题

产生死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0,"灰姑娘");
Makeup g2 = new Makeup(1,"白雪公主");

g1.start();
g2.start();
}
}
//口红
class Lipatick{

}
//镜子
class Mirror{

}

class Makeup extends Thread {
//需要的资源只有一份,用static来保证只有一份
static Lipatick lipatick = new Lipatick();
static Mirror mirror = new Mirror();

int choice;//选择
String girlName;//使用化妆品的人

public Makeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}

@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

//化妆,互相持有对方的锁,就是需要拿到对方的资源
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipatick) {
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(1000);
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
}
}
/*
//原本是拿着镜子用口红,现在是拿镜子,放镜子,那口红
synchronized (lipatick) {
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(1000);
}
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
}
*/
} else {
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(1000);
synchronized (lipatick) {
System.out.println(this.girlName + "获得口红的锁");
}

}

}
}
}

Lock(锁)

  • 从JDK5.0开始,Java提供了更强大的线程同步机制—-通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock类实现了Lock,它拥有与synchronized相同的并分性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁
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
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();

new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}

class TestLock2 implements Runnable{
int ticketNums = 10;

//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){

try {
lock.lock();//加锁
if(ticketNums>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
}else{
break;
}
}finally {
lock.unlock();
}

}
}
}

synchronized与Lock对比

  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,除了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:
    • Lock > 同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

线程通信

  • 应用场景:生产者和消费者问题
    • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
    • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生成并等待,直到仓库中的产品被消费者取走为止
    • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
  • 这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件
  • 在此问题中,仅有synchronized是不够的
    • synchronized可阻止并发更新同一个共享资源,实现了同步
    • synchronized不能用来实现不同线程之间的消息传递(通信)

方法

  • wait() 表示线程一直等待,知道其他线程通知,与sleep不同,会释放锁
  • wait(long timeout) 指定等待的毫秒数
  • notify() 唤醒一个处于等待状态的线程
  • notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度
  • 均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常

管程法

  • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
  • 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
  • 生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
public class TestPC {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();

new Productor(synContainer).start();
new Consumer(synContainer).start();
}
}

//生产者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container){
this.container = container;
}

//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
container.push(new Chicken(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产了"+ i + "只鸡");
}
}
}

//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container = container;
}

//消费

@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
System.out.println("消费了-->" + container.pop().id +"只鸡");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

//产品
class Chicken{
int id;

public Chicken(int id) {
this.id = id;
}
}

//缓冲区
class SynContainer{
//容器大小
Chicken[] chickens = new Chicken[10];
//计数器
int count = 0;

//生产者放入产品
public synchronized void push(Chicken chicken) throws InterruptedException {
//如果容器满了,就需要等待消费者消费
if(count==chickens.length){
this.wait();
}

//如果没满,生产者丢入产品
chickens[count] = chicken;
count++;

//通知消费者消费
this.notifyAll();
}

//消费者消费产品
public synchronized Chicken pop() throws InterruptedException {
//判断能否消费
if(count == 0){
//等待生产者生成
this.wait();
}

count--;
Chicken chicken = chickens[count];

this.notifyAll();
return chicken;

}
}

信号灯法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}

//生产者--> 演员
class Player extends Thread{
TV tv;
public Player(TV tv){
this.tv=tv;
}

@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i%2==0)
this.tv.play("快乐大本营播放中");
else
this.tv.play("抖音:记录美好生活");
}
}
}
//消费者--> 观众
class Watcher extends Thread{
TV tv;
public Watcher(TV tv){
this.tv=tv;
}

@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}

//产品--> 节目
class TV{
//演员表演,观众等待 T
//观众观看,演员等待 F
String voice; //表演的节目
boolean flag = true;

//表演
public synchronized void play(String voice){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println("演员表演了:"+voice);

this.notify();
this.voice=voice;
this.flag = !this.flag;
}

//观看
public synchronized void watch(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了:"+voice);
this.notify();
this.flag = !this.flag;
}
}

线程池

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
  • 思路:提前创建好多个线程,放入线程池中,使用是直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具
  • 好处:
    • 提高响应时间(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理(…)
  • corePoolSize:核心池的大小
  • maximumPoolSize:最大线程数
  • keepAliveTime:线程没有任务时最多保持多长时间后终止
  • JDK5.0起提供了线程池相关API:ExecutorService和Executors
  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
    • void execute(Runnable command): 执行任务/命令,没有返回值,一般用来执行Runnable
    • Future submit(Callable task): 执行任务,有返回值,一般用来执行Callable
    • void shutdown(): 关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TestPool {
public static void main(String[] args) {
//1.创建服务,创建线程池
//newFixedThreadPool 参数为:线程池大小
ExecutorService executorService = Executors.newFixedThreadPool(10);

executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.execute(new MyThread());

executorService.shutdown();

}
}

class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().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
44
public class ThreadNew {
public static void main(String[] args) {
new MyThread1().start();

new Thread(new MyThread2()).start();

FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
new Thread(futureTask).start();

try {
Integer integer = futureTask.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}

//1.继承Thread类
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("MyThread1");
}
}

//2.实现Runnable接口
class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("MyThread2");
}
}

//3.实现Callable接口
class MyThread3 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("MyThread3");
return 100;
}
}

注解和反射

注解和反射

注解

什么是注解

Annotation是从JDK5.0开始引入的新技术

Annotation的作用:

  • 不是程序本身,可以对程序作出解释。(这一点和注释(comment)没什么区别)
  • 可以被其他程序(如:编译器等)读取

Annotation的格式:

  • 注解是以”@注释名”在代码中存在的,还可以添加一些参数值,如:@SuppressWarnings(value=:unchecked”)

Annotation在那里使用?

  • 可以附加在package,class,method,field等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问

内置注解

  • @Override:定义在java.lang.Override中,此注释只适用于修斯方法,表示一个方法声明打算重写超类中的另一个方法声明
  • @Deprecated:定义在java.lang.Deprecated中,此注释可以用于修斯方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择
  • @SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息
    • 与前两个注释不同,需要添加一个参数才能正确使用
    • @SuppressWarnings(“all”)
    • SuppressWarnings(“unchecked”)
    • SuppressWarnings(value={“unchecked”,”deprecation”})
    • 等等……

元注解

元注解的作用就是负责注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明

  • @Target:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
  • @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期
    • SOURCE < CLASS < RUNTIME
  • @Document:说明该注解将被包含在javadoc中
  • @Inherited:说明子类可以继承父类中的该注解

自定义注解

使用 @interface自定义注解时,自动继承了java.lang.annotation.Annotation接口

  • @interface用来声明一个注解,格式:public @ interface 注解名 {定义内容}
  • 其中的每一个方法实际上是声明了一个配置参数
  • 方法的名称就是参数的名称
  • 返回值类型就是参数的类型(返回值只能是基本类型,Class,String,Enum)
  • 可以通过default来声明参数的默认值
  • 如果只有一个参数成员,一般参数名为value
  • 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Demo1 {

//注解可以显示复制,如果没有默认值,则必须给注解赋值
@MyAnnotation(age = 10,name = "xingchuwu")
public void test(){}

@MyAnnotation2("xingchuwu")
public void test2(){}
}

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
//注解的参数 : 参数类型 + 参数名 ();
String name() default "";
int age() ;
int id() default -1;//如果默认值为-1,代表不存在

String[] schools() default {"aaa","bbb"};
}

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
//当注解只有一个参数时,推荐使用value,此时赋值时可不写value
String value();
}

反射

  • 动态语言:是一类在运行时可以改变其结果的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或时其他结果上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构

    • 主要动态语言:Object-C、C#、JavaScript、PHP、Python等
  • 静态语言:运行时结构不可变的语言。

    • 如Java、C、C++
  • java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性

什么是反射

  • Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
  • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象,这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过镜子可以看到类的结构,姑称之为:反射

功能:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时处理注解
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时获取泛型信息
  • 生成动态代理
  • 。。。。。。

优点:

  • 可以实现动态创建对象和编译,体现出很大的灵活性

缺点:

  • 对性能有影响。

Class类

在Object类中定义了以下方法,此方法将被所有子类继承

  • public final Class getClass()

Class类的常用方法

  • static Class forName(String name) 返回指定类名name的Class对象
  • Object newInstance() 调用缺省构造函数,返回Class对象的一个实例
  • getName() 返回此Class对象所表示的实体(类,接口,数组类或void)的名称
  • Class getSuperClass() 返回当前Class对象的父类的Class对象
  • Class[] getInterfaces() 获取当前Class对象的接口
  • ClassLoader getClassLoader 返回该类的类加载器
  • Constructor[] getConstructors() 返回一个包含某些Constructor对象的数组
  • Method getMothod(String name,Class.. T) 返回一个Method对象,此对象的形参类型为paramType
  • Field[] getDeclaredFields() 返回Field对象的一个数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class Deni2 {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println("这个人是:" + person.name);

//方式一:通过对象获得
Class c1 = person.getClass();
System.out.println(c1.hashCode());

//方法二:forname获得
Class c2 = Class.forName("com.chang.base.myReflection.Student");
System.out.println(c2.hashCode());

//方式三:通过类名.class获得
Class c3 = Student.class;
System.out.println(c3.hashCode());

//方式四:基本内置类型的包装类都有一个Type属性
Class c4 = Integer.TYPE;
System.out.println(c4.hashCode());

//获得父类类型
Class c5 = c1.getSuperclass();
System.out.println(c5);
}

}

class Person{
String name;

public Person() {
}

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

public String getName() {
return name;
}

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

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

class Student extends Person{
public Student() {
this.name = "Student";
}
}

class Teacher extends Person{
public Teacher(){
this.name = "Teacher";
}
}

那些类型有Class对象

  • class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
  • interface:接口
  • []:数组
  • enum:枚举
  • annotation:注解@interface
  • primitive type:基本数据类型
  • void
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
public class Demo3 {
public static void main(String[] args) {
Class c1 = Object.class; //类
Class c2 = Comparable.class; //接口
Class c3 = String[].class; //一维数组
Class c4 = int[][].class; //二维数组
Class c5 = Override.class; //注解
Class c6 = ElementType.class; //枚举
Class c7 = Integer.class; //基本数据类型
Class c8 = void.class; //void
Class c9 = Class.class; //Class

System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);

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

java内存分析

java内容:

    • 存放new的对象和数据
    • 可以被所有的线程共享,不会存放别的对象引用
    • 存放基本变量类型(会包含这个基本类型的具体数值)
    • 引用对象的变量(会存放这个引用在堆里面的具体地址)
  • 方法区
    • 可以被所有的线程共享
    • 包含了所有的class和static变量

类的加载与ClassLoader的理解

  • 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象
  • 链接:将Java类的二进制代码合并到 JVM的运行状态之中的过程
    • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
    • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
    • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
  • 初始化:
    • 执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
    • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
    • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Demo4 {
public static void main(String[] args) {
A a = new A();
System.out.println(a.m);

/*
1.加载到内存,会产生一个类对应Class对象
2.链接,链接结束后 m = 0
3.初始化{
<clinit>(){
System.out.println("A类静态代码块初始化");
m = 300;
m = 100;
}

m = 100;
}
*/

}
}

class A{
static {
System.out.println("A类静态代码块初始化");
m = 300;
}
/*
m = 300;
m = 100;
*/

static int m = 100;

public A(){
System.out.println("A类的无参构造初始化");
}
}

类初始化时间

  • 类的主动引用(一定会发生类的初始化)
    • 当虚拟机启动,先初始化main方法所在的类
    • new一个类的对象
    • 调用类的静态成员(除了final常量)和静态方法
    • 使用java.lang.reflect包的方法对类进行反射调用
    • 当初始化一个类,如果其父类没有被初始化,则会先初始化它的父类
  • 类的被动引用(不会发生类的初始化)
    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
    • 通过数组定义类引用,不会触发此类的初始化
    • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
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
public class Demo5 {

static {
System.out.println("Main类被加载");
}

public static void main(String[] args) throws ClassNotFoundException {
//1.主动引用
// Son son = new Son();

//反射也会产生主动引用
// Class.forName("com.chang.base.myReflection.Son");

//不会产生类的引用的方法
// System.out.println(Son.b);

// Son[] arrays = new Son[5];

System.out.println(Son.M);
}
}

class Father{

static int b = 2;

static {
System.out.println("父类被加载");
}
}

class Son extends Father{
static {
System.out.println("子类被加载");
m = 300;
}

static int m = 100;
static final int M = 1;
}

类加载器的作用

  • 类加载器的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后再堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
  • 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象

类加载器作用是用来把类(class)装载进内存的。JVM规范定义了如下类型的类的加载器:

  • 引导类加载器:用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取
  • 扩展类加载器:负责jre/lib/ext目录下的jar包或—D java.ext.dirs指定目录下的jar包装入工作库
  • 系统类加载器:负责java -classpath 或—D java.class.path所指目录下的类与jar包装入工作,是最常用的加载器
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
public class Demo6 {
public static void main(String[] args) throws ClassNotFoundException {

//获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);

//获取系统类加载器的父类加载器-->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);

//获取扩展类加载器的父类加载器 --> 根加载器(C/c++)
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);

//测试当前类是哪个加载器加载的
ClassLoader classLoader = Class.forName("com.chang.base.myReflection.Demo6").getClassLoader();
System.out.println(classLoader);

//测试JDK内置的类是谁加载的
ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader1);

//获得系统加载器可以加载的路径
String property = System.getProperty("java.class.path");
System.out.println(property);

//双亲委派机制

/*
E:\Java\jdk1.8\jre\lib\charsets.jar;
E:\Java\jdk1.8\jre\lib\deploy.jar;
E:\Java\jdk1.8\jre\lib\ext\access-bridge-64.jar;
E:\Java\jdk1.8\jre\lib\ext\cldrdata.jar;
E:\Java\jdk1.8\jre\lib\ext\dnsns.jar;
E:\Java\jdk1.8\jre\lib\ext\jaccess.jar;
E:\Java\jdk1.8\jre\lib\ext\jfxrt.jar;
E:\Java\jdk1.8\jre\lib\ext\localedata.jar;
E:\Java\jdk1.8\jre\lib\ext\nashorn.jar;
E:\Java\jdk1.8\jre\lib\ext\sunec.jar;
E:\Java\jdk1.8\jre\lib\ext\sunjce_provider.jar;
E:\Java\jdk1.8\jre\lib\ext\sunmscapi.jar;
E:\Java\jdk1.8\jre\lib\ext\sunpkcs11.jar;
E:\Java\jdk1.8\jre\lib\ext\zipfs.jar;
E:\Java\jdk1.8\jre\lib\javaws.jar;
E:\Java\jdk1.8\jre\lib\jce.jar;
E:\Java\jdk1.8\jre\lib\jfr.jar;
E:\Java\jdk1.8\jre\lib\jfxswt.jar;
E:\Java\jdk1.8\jre\lib\jsse.jar;
E:\Java\jdk1.8\jre\lib\management-agent.jar;
E:\Java\jdk1.8\jre\lib\plugin.jar;
E:\Java\jdk1.8\jre\lib\resources.jar;
E:\Java\jdk1.8\jre\lib\rt.jar;
E:\javadProgram\javaStudy\out\production\javaStudy;
E:\IntelliJ IDEA\lib\idea_rt.jar

*/
}
}

获取运行时类的完整结构

通过反射获取运行时类的完整结构:

Field、Method、Constructor、Superclass、Interface、Annot

  • 实现的全部接口
  • 所继承的父类
  • 全部的构造器
  • 全部的方法
  • 全部的Field
  • 注解
  • 。。。
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
public class Demo7 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {

Class c1 = Class.forName("com.chang.base.myReflection.User");

//获取类的名字
System.out.println(c1.getName()); //获得包名+类名
System.out.println(c1.getSimpleName()); //获得类名
System.out.println("=======================");
//获得类的属性
Field[] fields = c1.getFields(); //只能找到public属性
for(Field field : fields){
System.out.println(field);
}
System.out.println("------------------");
fields = c1.getDeclaredFields(); //找到全部的属性
for(Field field : fields){
System.out.println(field);
}
System.out.println("------------------");
//获取指定属性的值
Field name = c1.getDeclaredField("name");
System.out.println(name);
System.out.println("=======================");
//获得类的方法
Method[] methods = c1.getMethods(); //获得本类及其父类的全部public方法
for(Method method : methods){
System.out.println("getMethods" + method);
}
System.out.println("------------------");
methods = c1.getDeclaredMethods(); //获得本类的所有方法
for(Method method : methods){
System.out.println("getDeclaredMethods:" + method);
}
System.out.println("------------------");
//获得指定方法
//重载
Method getName = c1.getMethod("getName",null);
Method setName = c1.getMethod("setName", String.class);
System.out.println(getName);
System.out.println(setName);
System.out.println("=======================");
//获得构造器
Constructor[] constructors = c1.getConstructors(); //获得public
for(Constructor constructor : constructors){
System.out.println(constructor);
}
constructors = c1.getDeclaredConstructors(); //获得全部
for(Constructor constructor : constructors){
System.out.println(constructor);
}
System.out.println("------------------");
//获取指定构造器
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
System.out.println("指定:" + declaredConstructor);
}
}

动态创建对象执行方法

创建类的对象:调用Class对象的newInstance()方法

  • 类必须有一个无参数的构造器
  • 类的构造器的访问权限需要足够

没有无参数的构造器:

  • 通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器
  • 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
  • 通过Constructor实例化对象

调用指定的方法:

  • 通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型
  • 之后使用Object invoke(Object obj,Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息

setAccessible

  • Method和Field、Constructor对象都有setAccessible方法
  • setAccessible作用时启动和禁用访问安全检查的开关
  • 参数值为true则知指示反射的对象在使用时应该取消java语言访问检查
    • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true
    • 使得原本无法访问的私有成员也可以访问
  • 参数值为false则指示反射的对象应该实施java语言访问检查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Demo8 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class c1 = Class.forName("com.chang.base.myReflection.User");

//构造一个对象
User user = (User)c1.newInstance(); //本质是调用了类的无参构造器
System.out.println(user);

//通过构造器创建对象
Constructor constructor = c1.getDeclaredConstructor(String.class,int.class,int.class);
User user2 = (User)constructor.newInstance("xingchuwu",100,10);
System.out.println(user2);

//通过反射调用普通方法
User user3 = (User)c1.newInstance();
//通过反射获取一个方法
Method setName = c1.getDeclaredMethod("setName", String.class);

//invoke : 激活的意思
// (对象,”方法的值“)
setName.invoke(user3,"xingchuwu1");
System.out.println(user3);

//通过反射操作属性
User user4 = (User)c1.newInstance();
Field name = c1.getDeclaredField("name");

//不能直接操作私有属性,需要关闭程序的安全检测
name.setAccessible(true);
name.set(user4,"hello");
System.out.println(user4.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
44
public class Demo9 {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
test1();
test2();
test3();
}

//普通方式调用 3ms
public static void test1(){
User user = new User();
long startTime = System.currentTimeMillis();
for(int i=0; i<1000000000; i++){
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式执行10亿次:" + (endTime - startTime) + "ms");

}
//反射方式调用 1642ms
public static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c1 = user.getClass();
Method method = c1.getDeclaredMethod("getName",null);
long startTime = System.currentTimeMillis();
for(int i=0; i<1000000000; i++){
method.invoke(user,null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射方式执行10亿次:" + (endTime - startTime) + "ms");
}
//反射方式调用 关闭检测 1046ms
public static void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c1 = user.getClass();
Method method = c1.getDeclaredMethod("getName",null);
method.setAccessible(true);
long startTime = System.currentTimeMillis();
for(int i=0; i<1000000000; i++){
method.invoke(user,null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射方式执行10亿次:" + (endTime - startTime) + "ms");
}
}

反射操作泛型(了解即可)

  • Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除
  • 为了通过反射操作这些类型,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型
  • ParameterizedType:表示一种参数化类型,如Collection
  • GenericArrayType:表示一种元素类型时参数化类型或者类型变量的数组类型
  • TypeVariable:是各种类型变量的公共父接口
  • WildcardType:代表一种通配符类型表达式

反射操作注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public class Demo10 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("com.chang.base.myReflection.Student2");

//通过反射获得注解
Annotation[] annotations = c1.getAnnotations();
for(Annotation annotation : annotations){
System.out.println(annotation);
}

//获得注解的value的值
ClassAnnotation classAnnotation = (ClassAnnotation) c1.getAnnotation(ClassAnnotation.class);
String value = classAnnotation.value();
System.out.println(value);

//获得类指定的注解
Field f = c1.getDeclaredField("name");
FieldAnnotation fieldAnnotation = f.getAnnotation(FieldAnnotation.class);
System.out.println(fieldAnnotation.columnName());
System.out.println(fieldAnnotation.type());
System.out.println(fieldAnnotation.length());
}
}

@ClassAnnotation("db_student")
class Student2{
@FieldAnnotation(columnName = "db_age",type = "int",length = 10)
private int age;
@FieldAnnotation(columnName = "db_id",type = "int",length = 10)
private int id;
@FieldAnnotation(columnName = "db_name",type = "varchar",length = 10)
private String name;

public Student2() {
}

public Student2(int age, int id, String name) {
this.age = age;
this.id = id;
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

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

@Override
public String toString() {
return "Student2{" +
"age=" + age +
", id=" + id +
", name='" + name + '\'' +
'}';
}
}

//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface ClassAnnotation{
String value();
}

//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldAnnotation{
String columnName();
String type();
int length();
}

JDK8新特性

Lambda表达式

Lambda和匿名内部类在使用上的区别:

  • 所需的类型不一样:
    • 匿名内部类:需要的类型可以是类,抽象类,接口
    • Lambda表达式,需要的类型必须是接口
  • 抽象方法的数量不一样
    • 匿名内部类所需的接口中抽象方法的数量随意
    • Lambda表达式所需的接口只能有一个抽象方法
  • 实现原理不同
    • 匿名内部类是在编译后会形成class
    • Lambda表达式是在程序运行的时候动态生成class

小结:当接口中只有一个抽象方法时,建议使用Lambda表达式,其他情况还是需要使用匿名内部类

集合之Stream流式操作

接口的增强

JDK8接口新增两个方法:

  • 默认方法
  • 静态方法

JDK8以前的接口:

1
2
3
4
interface 接口名{
静态常量;
抽象方法;
}

JDK8的接口

1
2
3
4
5
6
interface 接口名{
静态常量;
抽象方法;
默认方法;
静态方法;
}

默认方法

在JDK 8以前接口中只能有抽象方法。存在以下问题:

如果给接口新增抽象方法,所有实现类都必须重写这个抽象方法。不利于接口的扩展

因此,在JDK 8时为接口新增了默认方法,格式如下:

1
2
3
4
5
interface 接口名{
修饰符 default 返回值类型 方法名(){
代码;
}
}
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
public class Demo1 {
public static void main(String[] args) {
B b = new B();
b.test1(); //实现方法一:直接调用接口的默认方法
C c = new C();
c.test1(); //实现方法二:重写接口的默认方法
}
}

interface A{
public default void test1(){
System.out.println("我是接口A的默认方法");
}
}

class B implements A{

}

class C implements A{
@Override
public void test1() {
System.out.println("我是C类重写的默认方法");
}
}

静态方法

为了方便接口扩展,JDK 8为接口新增了静态方法

静态方法不能重写

格式如下:

1
2
3
4
5
interface 接口名{
修饰符 static 返回值类型 方法名(){
代码;
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Demo2 {
public static void main(String[] args) {
A1.test1();
}
}

interface A1{
public static void test1(){
System.out.println("我是接口A1的静态方法");
}
}

默认方法和静态方法的区别

  • 默认方法通过实例调用,静态方法通过接口名调用
  • 默认方法可以被继承,实现类可以直接使用接口默认方法,也可以重写接口默认方法
  • 静态方法不能被继承,实现类不能重写接口静态方法,只能使用接口名调用

小结:

如果这个方法需要被实现类继承或重写,使用默认方法,否则使用静态方法

并行数组排序

Optional中避免Null检查

新的时间和日期API

可重复注解