Java基础知识

1.jvm概述

1.1 程序计数器

占用内存比较小,记录程序当前的线程所执行的字节码(jvm指令)行号指示器,jvm通过改变计数器的志来选取下一条要执行的指令,

多线程之间的程序计数器相互独立,互不影响,保证每个线程回复之后都能回到中断的位置,进而继续执行。

1.2 java堆

就是指的是Java堆,用来存放Java对象的,每一个Java对象都是存放在堆中的。

面向对象和面向过程。

面向对象关注程序能抽象出多少个数据模型,不需要关注具体的步骤。

Java堆是被线程共享的。gc是垃圾收集器的主要的管理区域。用各种的算法去处理。

Java堆分为年轻代和老年代,永久代。

  • 年轻代:

    eden(伊甸园)区和survivor(幸存者)

eden(伊甸园)区:创造的新的对象存放的地方

survivor: gc 回收的时候,将Eden不需要回收(存活)的对象存入survivor from 区,在下一次回收的时候,将from区中不需要回收的对象存入到to区,然后清理from区,在下下一次回收的时候,将to区中不需要回收的对象存入到from区,然后再进行循环。

每次回收之后,存活下来的对象的年龄都会+1,年龄增加到一定的程度,移动到老年代中。

  • 老年代

存放生命周期较长的对象

  • 永久代

jdk 1.7之前将类的信息存放在永久代之中,jdk1.8之后就去掉了,改成了元空间。

Java对象都是在堆中的,堆中不只是只有对象。

元空间和永久代都是方法区的实现,方法区只是一种规范。

1.7之前字符串常量池存放于永久代中的,1.8去掉了永久代,1.8之后的字符串的常量池放在了堆中。为什么要这样处理?

因为永久代的空间是有限,创建字符串的话,我们要调用的是inter方法。

元空间使用的是本地内存,永久代使用的jvm的内存。

本地内存有多大,那么元空间就是有多大,不再受限于jvm的内存了。开辟出更多的区域可以使用,效率更高。

1.3 虚拟机栈

Java方法中的执行是在虚拟机栈中完成的,方法的执行是入栈和出栈的过程。

栈帧包括:

1.局部变量表 (Java方法中用到的变量)

2.操作数栈(数据是要入栈和出栈的,包含的运算符栈)

3.动态链接(方法的返回地址以及方法的出口)

每一个方法的执行,jvm都会创建一个栈帧,并将栈帧压入到Java的栈中,方法的执行,该栈帧出栈。

IDE集成开发平台:Eclipse .IDEA (主流的开发软件)

JDK+IDEA

插件 提升开发的效率

2.编码规范

2.1 强制编码规范

“呵呵哈哈哈”.sout

10.var

1.java 程序的文件名是必须和类名是一致的。

2.必须要有main方法

2.2 建议编码规范

1.代码分行去写,不要只写在一行。

2.还需要注意代码的缩进,提高代码的可读性。

3.变量

3.1 什么是变量?

变量是计算机语言中一个概念,可以表示某个具体的数值,并且这个可以修改,如果不能修改的值,就叫做常量。

3.2 为什么要使用变量?

程序需要将各种数据先储存,再运算,计算机储存数据的地方是内存,JVM内存,JVM内存不等于本地内存,元空间直接使用本地内存。

int num = 100;

double num2 = 0.03;

从内存中取出数据,先通过内存地址找到具体的内存区域,进而取出里面存放的数据。

问题:内存地址是十六进制的数据,并且没有规律,是随机生成的。例如:

变量的作用是为了解决内现地址不方便记忆的问题。

是程序储存数据的基本单元,变量有三要素:

1.数据类型

2.变量值

3.变量

int num1 = 100;
double num2 = 0.03;


3.3 变量名的命名规范

变量名可以包含数字,字母,下划线,$,不能包含空格,运算符,关键字

不能以数字开头。

标识符由数字(0~9)和字母(A~Z 和 a~z)、美元符号($)、下划线(_)以及 Unicode 字符集中符号大于 0xC0 的所有符号组合构成(各符号之间没有空格)。
	
	标识符的第一个符号为字母、下划线和美元符号,后面可以是任何字母、数字、美元符号或下划线。

变量的命名不能随便起名字,应该见到见名知义,遵循驼峰式命名法(第一个单词手首字母小写,后续的读单词的首字母大写)

比如studentName,userId.

4.Java中的数据类型

java中有无数种数据类型。

分为两大类:

1.基本数据类型8种

2.引用数据类型,无数种,除了jdk提供的类库之外,开发者可以自定义数据类型。

  • 整数类型:byte(1个字节),short,int,long

k:1024 1KB = 1024 byte

​ 1MB = 1024 KB

​ 1GB = 1024MB

​ 1TB = 1024GB

一个字节是一个8位的二进数

short (2个字节) 16位的二进数

int(4个子节),32位的二进数

long(8个子节),64位二进数

  • 浮点型:double,float

float 4个子节,单精度浮点型

double 8个子节,双精度浮点型

精度是描述数据的精准度,因为小数在计算机中的存储可能会涉及数据的丢失。

除不尽的小数储存的时候会存在精度损失,使用double存放会比float更加精准一点。

  • boolean 描述逻辑运算的结果

只有TRUE和Flase 两个值

Boolean的大小是多少个子节?

bit 一位二进制数

byte 一个子节 = 8 bit

Boolean 是一个1 bit ,1/8个byte

用1表示TRUE,用0来表示Flase

  • char 描述字符

只能描述单个字符,大小是2个byte

实际开发中主要使用的基本类型包括:int double Boolean

4.1数据类型的转换

4.1.1 自动类型的转换

Java程序根据变量的数据类型,自动的对数值进行类型转换,以匹配变量的数据类型。

转换的规则:只能从低字节到高字节来进行转换,反之则不行。

小屋换大屋,大屋换小屋不行。

4.1.2 强制类型的转换

在前面加上括号

强制类型一般不推荐使用,能不用尽量不用,存在数据安全的问题,产生数据问题的丢失。

int num = int10.9999

5.运算符

5.1 基本算数运算符

= + - ++ –

++ 在前面和在后面的输出的值不一样

5.2 复合算数运算符

+ = 、 − = 、 ∗ = 、 / = 、 +=、-=、*=、/=、%= +===/=

变量A +=变量B:先求出变量A+变量B的结果,再把这个结果赋值给变量A

复合运算符包含了强转

i += j;
i = i + j ;

5.3 关系运算符

==、!=、<、>、>=、<=

<、>、>=、<=(只能用作基本数据类型的变量,引用类型的变量不能使用)

关系运算符是用来判断两个变量的关系,返回值得类型是Boolean类型。

5.4 逻辑运算符

判断逻辑是否成立,一般在使用多个表达式的情况下

& | !

A&B :A和B都为TRUE,结果为TRUE

A|B:A或者B为TRUE,结果为TRUE

!:取反 A= TRUE !A= Flase

&& || 

&& 短路与

|| 短路或

&& 和 & 预算结果完全一致,但是效率更高

||和|预算结果完全一致,但是效率更高

5.5 条件运算符

条件运算符也叫三元运算符/三元运算符/三目运算符,根据条件给变量赋值

基本语法:变量 = 条件?值1:值2

条件成立的时候赋值1;条件不成立的时候赋值2

int num1 = 10;
int num2 = 11;
String str = num1 > num2 ? "A" : "B"

5.6 位运算符

二进制转十进制:从目标数的最右端算起,本位的数字乘以本位的权重,权重就是2的第N位的N-1次方,第一位的权重就是1,第2位的权重就是2,第3位的权重就是4

把每一位的乘积(数字*权重)相加得到的就是十进制。

十进制转二进制:目标数除以二,如果能除尽则该位记0,如果除不尽,该位记1,以此类推,直到商为0;

位运算符:&(按位与),|(按位或),^(按位异或),<<(左移),>>(右移)

位运算符的特点就是把所有的操作数全部转换为二进制再进行运算;

A&B:将AB的每一位数字进行对比,如果都为1,则该位记作1,否则为0

A|B: 将AB每一位数字来进行对比,只要有1个是1,则该位记作1,否则为0

A^B: 将AB每一位数字来进行对比,如果相同,则该位记作0,不同则记为1

A << B :A 乘以2的B次方,2<<3 16

A >> B: A除以2的B次方,2>>3 0

计算 10 & 5、10 | 5、10 ^ 3、2 << 3、2 >> 3

10 --> 1010

5 --> 0101

3 --> 11

1 0 1 0

0 0 1 1

1001

1+0+0+8=9

& |:如何区分逻辑运算和位运算?

如果操作数是数值,则是位运算符,如果操作数是表达式(boolean)则是逻辑运算符

运算符的优先级:!> 算术运算符 > 关系运算符 > 逻辑运算符(&>|)

6.流程控制

if-else

switch-case

流程控制是对程序的走向进行处理,比如登录成功就进入首页,登录失败就回到登录页面,那么登录的判断就是一个流程控制。

基本语法:

if(条件){

​ //成功逻辑

}else{

​ //失败逻辑

}

答题得分大于 60,同时积分 >= 500

答题得分大于 80,同时积分 >= 300

import com.southwind.entity.User;

public class Test {
    public static void main(String[] arg) {
        int score = 70;
        int num = 600;
        if(((score > 60) && (num >= 500)) || ((score > 80) && (num >= 300))){
            System.out.println("得奖");
        }
    }
}

多重 if

173 以下 M,173~178 L,178 以上 XL

import com.southwind.entity.User;

public class Test {
    public static void main(String[] arg) {
        int height = 176;
//        if(height > 178){
//            System.out.println("XL");
//        }else{
//            if(height >= 173){
//                System.out.println("L");
//            }else{
//                System.out.println("M");
//            }
//        }

//        if(height > 178){
//            System.out.println("XL");
//        }
//        if(height >= 173 && height <= 178){
//            System.out.println("L");
//        }
//        if(height < 173){
//            System.out.println("M");
//        }

        if(height>178){
            System.out.println("XL");
        } else if(height>=173){
            System.out.println("L");
        }else{
            System.out.println("M");
        }
    }
}

if 嵌套

答题得分 > 80,并且积分 > 500

public class Test {
    public static void main(String[] arg) {
        int score = 90;
        int num = 100;
        if(score > 80){
            if(num > 200){
                System.out.println("得奖");
            }else{

            }
        }else{
            
        }
    }
}

switch-case:

switch-case 也可以完成流程控制,但是与 if-else 不同,只能完成等值判断,不能进行关系判断。

switch (placing){
    case 1:
        System.out.println("奖励2000元");
        break;
    case 2:
        System.out.println("奖励1000元");
        break;
    case 3:
        System.out.println("奖励500元");
        break;
    default:
        System.out.println("没有奖励");
        break;
}

7.循环

for、while、do-while、foreach

循环四要素:

1、初始化循环变量

2、循环条件

3、循环体

4、更新循环变量

public class Test2 {
    public static void main(String[] args) {
//        for (int i = 0;i < 100;i++) {
//            System.out.println("Hello World");
//        }

//        int i = 0;
//        while(i < 100){
//            System.out.println("Hello World");
//            i++;
//        }

        int i = 0;
        do{
            System.out.println("Hello World");
            i++;
        }while(i < 100);
    }
}

while 和 do-while 的区别?

do-while 先执行一次循环体,再判断循环条件

while 先判断循环条件,再执行循环体

do-while 至少要执行一次,while 有可能一次都不执行

接收用户输入的信息

for 和 while/do-while 的区别?

for 循环是次数确定的循环,while/do-while 的次数不确定

7.1 双重循环

两个循环的嵌套

public class Test3 {
    public static void main(String[] args) {

        for (int j = 0; j < 6; j++) {
            for (int i = 0; i < 6; i++) {
                System.out.print(i+1);
            }
            System.out.println();
        }

    }
}

8.数组

8.1 什么是数组?

数组就是一种可以存放大量数据的数据结构,数组中存储的数据类型必须一致,是一个具有相同数据类型的数据集合。

使用数组也需要在内存中开辟一块空间来存储数据,同时数组的空间是连续的。

一个数组是由四个基本元素构成:

  • 数组名称,数组也是一个变量,所以需要有变量名
  • 数组元素,1,2,3,存入数组的数值
  • 元素下标,从 0 开始
  • 数据类型,int,存入数组的数据类型必须一致

数组的下标为什么是从 0 开始?而不是从 1 开始?

这样设计的主要原因是为了提升数组的查询效率,数组的特性是内存空间是连续的,这种设计可以提高查询效率。

数组的查询是通过寻址公式来完成的,我们拿到的是数组的首地址(第一个元素的内存地址)

寻址公式就是通过下标快速计算对应内存地址的一个公式,因为数组的数据类型是一致的,所以数组中的每一块区域长度都是一样的。

n 从 1 开始,寻址公式:1000+(n-1)*4

n 从 0 开始,寻址公式:1000 + n*4

n 从 0 开始比 n 从 1 开始,少了一步减法运算,所以运算效率更高,这就是数组下标从 0 开始的原因,就是为了少一步运算,从而极致的提升运算效率。

[I@10f87f48

Java 程序中任何一个引用类型变量输出的内容,都是在调用 toString 方法。

toString,Object 类中提供的一个方法,Object 类是 Java 中所有数据类型的根节点。

只有引用类型才是 Object 类的衍生品,引用类型就是对象,基本数据类型不属于这个范畴,基本数据类型和 Object 没有任何关系。

[I:[ 表示数组,I 是数据类型,int

@

10f87f48:hashCode,简单理解为内存地址

遍历数组:

int[] array2 = {1,2,3}

int[] array = new int[3]
array[0] = 1 ;
array[1] = 2 ;
array[2] = 13;

byte[] a1,a2[]

byte[] a1;

byte[] a2[];

byte[][] a2;
byte a2[][];
byte[]a2[];

static 修饰的方法叫做静态方法,在静态方法中,只能访问静态的属性

非静态内部类(没有添加 static 修饰的)的创建必须依赖于外部类对象。

静态内部类(用 static 修饰的)的创建不能依赖于外部类对象,而是直接依赖于外部类。

8.2 二维数组

多个一维数组嵌套在一起的数组,一个一维数组中存储的不是具体的数据,而是另一个一维数组,这样的数组就是二维数组。

int[] array  = {1,2,3}
int[][] array2  = {{1,23},{1,2,4},{5,3,2}}
//先创建
int[][] array = new int [3][3];
array[0][0] = 1;
//	第二个[]长度的必须给

查询用户和订单的时候,用二维数组。

Java 变量的内存模型

简化来看,JVM 内存模型分为两部分:栈内存 + 堆内存

所有的变量都保持在栈内存中(基本数据类型,引用类型)

int num = 1;

int[] array = {1,2,3};

array[0]

num

基本数据类型的变量和堆没有关系,变量开辟在栈中,数值直接存储在开辟的空间中。

8.3 二维数组的遍历

public class Test {
    public static void main(String[] args){
     //先创建

     int[][] array = {{1,2,3},{1,2},{2,3,4}};
        for (int i = 0; i < array.length; i++) {
            int[] ints = array[i];
            System.out.println(("开始遍历第" + (i + 1) + "个内层数组"));
            for (int j = 0; j < ints.length; j++) {
                System.out.println(ints[j]);

            }
        }
    }
}

for do-while while

int i = 0;
while(i<array.length){
    int[] ints = array[i];
    int j = 0;
    while(j < ints.length){
        System.out.println(ints[j]);
        j++;
    }
    i++;
}
int i = 0;
do{
    int[] ints = array[i];
    int j = 0;
    do{
        System.out.println(ints[j]);
        j++;
    }while(j < ints.length);
    i++;
}while(i<array.length);

9.面向对象

Java 中的变量有两种

1、基本数据类型

2、引用类型

基本数据类型和引用类型的区别?

  • 基本数据类型的类型范围是固定的,8 种
  • 引用类型的类型不是固定的,可以无限种可能(JDK、框架、开发者自定义)
  • 基本数据类型只需要用到栈内存,不需要用到堆内存,具体的数值直接存储到栈内存中
  • 引用类型需要同时用到栈内存和堆内存,真正的对象数据存储在堆中,堆的内存地址存入到栈中,栈中是通过引用的方式指向堆中真正的数据。

引用类型的数据都是对象,基本数据类型的数据不是对象。

Java 是面向对象的编程语言,如何实现面向对象

9.1 类和对象的关系

每个对象都应该有自己的特征,如何描述这些特征?

1、属性:用来描述静态特征

2、方法:用来描述动态特征

对象是用来描述客观存在的一个实体,改实体由一组属性和方法构成。

与对象紧密结合的另外一个概念是类

1、一个 Java 文件就是一个类(也可以包含多个类,少数情况)

2、类是对象的模板,通过一个类可以创建出 N 个对象

对象是动态存在的,类是静态的

程序员写出来的东西就是类,通过 JVM 让这个类创建对象,程序结束之后对象就全部销毁,但是类依然存在。

类是抽象的概念,是一种描述,仅仅是一个模板

对象才是具体的真实存在的物体

9.2 定义类

狗 Dog

关于内部类可以拥有自己的成员变量和方法。

静态方法只能访问静态的

猫 Cat

public class 类名{
	//属性
	//方法
}

9.3 面向对象三大特征

封装、继承、多态

封装

封装是为了解决对象的数据安全问题,将属性进行私有化(外部无法直接访问),用 private 修饰属性即可。

步骤:

1、将属性全部私有化 private。

2、提供 public 的方法对属性进行访问(赋值、取值)。

3、在 setter 方法中添加对数据进行保护的逻辑代码。

this 关键字,指的是当前对象,而不是当前类。

package com.southwind.test;

public class Dog {
    //属性
    private int age;
    private String name;
    private double weight;

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

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

    //方法
    public void run(){
        System.out.println(name + "跑起来了");
    }
}
package com.southwind.test;

public class Test3 {
    public static void main(String[] args) {
        //创建一个Dog对象
        Dog dog1 = new Dog();
        dog1.setName("旺财");
        dog1.setAge(1);
        dog1.setWeight(33.5);
        System.out.println(dog1);
        dog1.run();
        Dog dog2 = new Dog();
        dog2.setName("大黄");
        dog2.setAge(2);
        dog2.setWeight(66.6);
        System.out.println(dog2);
        dog2.run();
    }
}

9.4 继承

创建一个公共的父类,来完成相同代码的编写,然后再让 Student、Teacher(子类) 复用父类中的属性和方法,这就继承的原理,子类拥有父类的非私有信息。

父类中的 private 信息(属性+方法),子类无法继承。

派生类()

每个子类只有一个直接父类

父类的构造函数子类无法继承

构造函数/构造方法/构造器(Constructor)

是用来创建对象的

Student student = new Student();

每个类至少有一个构造方法,即使你一个方法都不写,也会自动生成一个默认的构造方法(无参构造),编译的时候自动加进去的。

如果手动编写一个有参构造,那么默认的无参构造就会被覆盖掉。

有参构造主要是用来赋值的。

如何判断一个类中有没有构造器?

反射机制 耦合度比较低的

对象的创建

在创建任何一个对象的时候,都会先创建它的父类对象(自动),一直追踪到 Object 类。

子类通过无参构造创建对象的时候,父类也会通过无参构造创建对象(默认)。

子类通过有参构造创建对象的时候,父类还是会通过无参构造创建对象(默认)。

this 和 super 的关系

this 指当前对象,super 指父类对象。

Lombok 插件:自动生成 getter/setter、toString 方法

1、安装插件

2、导入 Lombok 依赖,第三方 jar 包

static 修饰的方法叫做静态方法,在静态方法中,只能访问静态的属性

非静态内部类(没有添加 static 修饰的)的创建必须依赖于外部类对象。

静态内部类(用 static 修饰的)的创建不能依赖于外部类对象,而是直接依赖于外部类。

9.5 static关键字

实际开发中,哪里会经常用到自定义工具类中

static表示静态或者全局,用来修饰成员变量(属性)和成员方法和代码块和类

常规的方式:方法的调用需要依赖于对象,必须先有对象才能调方法。

添加static关键字之后,方法的调用就不再依赖于对象了,可以直接通过类来调用,极大提升了开发效率。

使用 static 修饰的成员独立于该类的任何一个实例化对象,被该类的所有对象所共享的资源。

被 static 修饰之后

静态成员变量/类变量 依赖于类,凌驾于所有对象之上的全局变量

静态成员方法/类方法 依赖于类,凌驾于所有对象之上的全局方法

没有被 static 修饰

实例成员变量,依赖于对象的变量

实例成员方法,依赖于对象的方法

普通成员在内存中多份,创建多少个对象,就会有多少份。

静态成员在内存中只有一份,无论创建多少个对象,都只有一份。

用static之后就不能使用this了

9.6类中信息的加载顺序

成员变量、成员方法、构造方法、代码块

1、先执行静态代码块

package com.southwind.test3;

public class Test {

static {
    System.out.println("静态代码块执行...");
}

public static void main(String[] args) {
    System.out.println("main方法执行...");
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IFK7kSYV-1647864057402)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210514204907298.png)]

2、类中定义静态成员变量对象,会先创建对象

public class Test {
    
    static {
        System.out.println("静态代码块执行...");
    }

    static User user = new User();

    public static void main(String[] args) {
        System.out.println("main方法执行...");
    }

}

3、非静态代码块和成员变量,不执行

public class Test {
{
    System.out.println("代码块执行...");
}

User user = new User();

public static void main(String[] args) {
    System.out.println("main方法执行...");
}

}

4、同时存在非静态代码块和静态代码块,非静态成员变量和成员变量,先执行静态的信息,并且只执行一次,再执行非静态的信息(创建对象),创建多少个对象就执行多少次。

public class Test {

    {
        System.out.println("代码块执行...");
    }

    User user = new User();

    static{
        System.out.println("静态代码块执行...");
    }

    static User user2 = new User();

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Test();
        }
    }

}

4、同时存在非静态代码块和静态代码块,非静态成员变量和成员变量,先执行静态的信息,并且只执行一次,再执行非静态的信息(创建对象),创建多少个对象就执行多少次。


public class Test {

    {
        System.out.println("代码块执行...");
    }

    User user = new User();

    static{
        System.out.println("静态代码块执行...");
    }

    static User user2 = new User();

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Test();
        }
    }

}

4、同时存在非静态代码块和静态代码块,非静态成员变量和成员变量,先执行静态的信息,并且只执行一次,再执行非静态的信息(创建对象),创建多少个对象就执行多少次。


public class Test {

    {
        System.out.println("代码块执行...");
    }

    User user = new User();

    static{
        System.out.println("静态代码块执行...");
    }

    static User user2 = new User();

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Test();
        }
    }

}

4、同时存在非静态代码块和静态代码块,非静态成员变量和成员变量,先执行静态的信息,并且只执行一次,再执行非静态的信息(创建对象),创建多少个对象就执行多少次。

public class Test {
{
    System.out.println("代码块执行...");
}

User user = new User();

static{
    System.out.println("静态代码块执行...");
}

static User user2 = new User();

public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        new Test();
    }
}
}

创建子类就会创建出一个父类

9.7 多态

一种对象在不同的情况下有多种不同的表现形式,

收银员内部的会员对象符合多态的特征

当顾客为普通会员的时候,会员对象就变成普通会员

当顾客为超级会员的时候,会员对象就变成超级会员

如何实现多态?

多态是建立在继承的基础上。子类赋予父类,可以直接引用。

//父类引用指向子类对象,向上转型,可以自动完成,不需要进行强制转换。
//子类引用指向父类对象,向下转型,不能自动完成,需要进行强制转换。(将一个大范围变成一个小范围)

范围扩大可以自动转换。

9.8 抽象类

9.8.1 抽象方法

只有方法的声明,没有方向的实现,没有方法体。

一旦某个类中存在了抽象方法,那么这个类就必须声明为抽象类。

加上abstract

因为多态的场景下,父类的方法会被子类所重写,所以父类的方法就没有实现的必要了,以就不需要实现,那么没有方法实现的方法就是抽象方法,一旦类中出现了抽象方法,那么该类就需要声明为抽象类。

public abstract class Member {

    public abstract void buy();

}

抽象类和普通类的区别?

抽象类中一定会包含至少一个抽象方法,

抽象类中可以没有任何一个抽象方法,正确的

普通类中不能存在抽象方法,正确的

抽象类中可以全部是抽象方法,也可以全部是普通方法,也可以普通方法和抽象方法同时存在。

普通类中不能出现抽象方法,必须全部是普通方法。

普通类可以创建实例化对象,抽象类不能创建实例化对象。

抽象类允许存在构造方法,但是不能调用。

抽象类不能实例化,如何使用?

创建抽象类的子类,让子类来实现抽象的方法,具体化。

9.8.2 抽象类的使用

1、定义一个抽象类,定义 N 个抽象方法。

2、创建子类,实现抽象方法。

3、除了创建子类,还可以使用匿名内部类的方式进行实现。

4、创建子类对象,用父类的引用指向它,进行操作。

目的还是为了实现多态。

匿名内部类。

接口相对于用的更多一点。

9.9 Object 类

9.9.1 什么是 Object 类

Java 通过类来构建代码的结构,类分为两种:

1、Java 提供的工具类,不需要开发者自定义的类,可以直接调用。

2、开发者自定义的类。

Java 程序 = JDK 类库+开发者自定义类+第三方框架

Object 就是 JDK 提供的一个类,并且它很特殊,它是所有类的根节点,Java 中的所有类。包括 JDK 提供,以及开发者自定义的,第三方框架的所有内容都属于 Object 的派生类。

hashCode:每一个 Java 对象都有自己的 hashCode,什么是 hashCode。在 JVM 内存中将对象存入到一个哈希列表中,返回一个数字类型的映射值,就是该对象的 hashCode。

hashcode的作用帮助程序判断两个对象是否相等,比较简单,直接比较两个对象的hashcode是否相等。

1、如果两个对象的 hashCode 不相等,则两个对象一定不是同一个对象。

2、但是如果两个对象的 hashCode 相等,可能是同一个对象,也可能不是同一个对象。

利用散列算法将对象的内存地址+内部信息融合到一起

面试题

hashCode 和 equals 的区别是什么?如何使用?

在不重写的情况下,equals 比较的就是内存地址

hashCode 是返回对象的哈希值

Set 集合的特点,就是不能存入重复的数据

Set 判断对象是否相等的时候,将 hashCode 和 equals 结合起来使用的

1、先用 hashCode 判断两个对象是否相等,如果 hashCode 不相等,则这两个对象肯定不是同一个对象,直接得到结论。

2、如果 hashCode 相等,此时还不能断定两个对象相等,这个就需要再使用 equals 来判断了。

9.9.2 可能被重写的常用方法

toString:默认返回的是类的信息+hashCode,实际开发中更希望看到的是对象的属性值。

原生的 toString 方法

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

重写根据类的信息由 IDEA 自动生成一个方法,将属性拼接起来展示。

hashCode:

特殊业务的中,可能需要重写,比如 Student ,根据学生编号来决定它的 hashCode

public class Student {
    private int id;
    private String name;

    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 int hashCode() {
        return 10*id;
    }
}

equals:

string类中的重写,JDK自己完成重写

String str1 = new String("Hello");
String str2 = new String("Hello");
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String aString = (String)anObject;
        if (!COMPACT_STRINGS || this.coder == aString.coder) {
            return StringLatin1.equals(value, aString.value);
        }
    }
    return false;
}
public static boolean equals(byte[] value, byte[] other) {
    if (value.length == other.length) {
        for (int i = 0; i < value.length; i++) {
            if (value[i] != other[i]) {
                return false;
            }
        }
        return true;
    }
    return false;
}

自定义User类重写equals方法

public class User {
    private int id;
    private String name;

    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 "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if(this == o) return true;
        if(o instanceof User){
            User user = (User) o;
            if(this.id == user.id){
                return true;
            }
        }
        return false;
    }

}

9.10 包装类

9.10.1 什么是包装类?

把基本数据类型封装成对象,所用到的类就是包装类。

为什么要将基本数据类型封装成对象?

基本数据类型不能赋 null

数据库中会存储业务数据,保不齐字段会有 null 值。

需要将数据库中的数据读出来,映射成一个对象,根据数据表生成一个对应的类用来存储数据。

一共有8种

Byte(byte)、Short(short)、Integer(int)、Long(long)

Float(float)、Double(double)、Character(char)、Boolean(boolean)

都是存放于 java.lang 包中,体系结构。

null和“ ”的区别:一个有引用,一个没有

null 压根没有对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RqaOqRjm-1647864057404)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210521125814353.png)]

“ ”有对象,但是值为空。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e5ozYiNz-1647864057404)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210521125940553.png)]

包装类之间还有number

9.10.2装箱和拆箱

装箱是指将基本数据类型转为对应的包装类

拆箱是指将包装类类型转为对应的基本数据类型

装箱的方法

1、使用构造器 不推荐使用

Integer integer1 = new Integer(1);

2、构造器,参数为 String 类型

Integer integer1 = new Integer("100");

3、valueOf 方法

public class Test2 {
    public static void main(String[] args) {
        Integer integer1 = Integer.valueOf(100);
        Byte byte1 = Byte.valueOf((byte)1);
        Short short1 = Short.valueOf((short)1);
        Long long1 = Long.valueOf(1);
        Double double1 = Double.valueOf(10.5);
        Float float1 = Float.valueOf((float) 10.6);
        Character character1 = Character.valueOf('a');
        Boolean boolean1 = Boolean.valueOf("hello");
        System.out.println(boolean1);
    }
}

Boolean 的 String 参数方法,如果传入的是 “true” 则结果为 true,否则都为 false。

9.10.2.1 拆箱

1、*Value()

每个保证类都有一个 *Value() 方法, * 就是对应的基本数据类型

Integer integer1 = Integer.valueOf(100);
int i = integer1.intValue();

2、static parse*(String value)

每个包装类都有一个将 String 转为基本数据类型的方法,除了 Character。

int i = Integer.parseInt("1900");
System.out.println(i);
boolean flag = Boolean.parseBoolean("abc");
System.out.println(flag);

实际开发中,使用较多的方法是 parse* 方法,装箱方法用的很少。

9.11 接口

9.11.1 概念描述

面向接口编程

接口是由抽象类衍生出来的一个概念

类、继承、多态、抽象类、接口

面向接口编程就是将程序的业务逻辑进行分离,以接口的形式区对接不同的业务模块,用来实现

接口只负责串联而不实现,具体的实现交给实现类来完成

优点:

1、最大限度地解耦合,降低程序的耦合度(低耦合,高内聚)

2、有利于程序的扩展

3、有利于后期的维护

9.11.2 具体使用

定义接口和定义类相似,因为接口本身就是一种特殊的类,他是一种机制的抽象类。

什么是抽象类,一个抽象类如果存在一个抽象方法,那么他就是抽象类。

抽象类中 100 个方法,1 个抽象方法,99 个普通方法,0 个抽象方法,100 个普通方法。

如果一个抽象类中所有的方法全部都是抽象方法,那么他就是个接口。

接口就是抽象到极致的/极度抽象的抽象类,100%的抽象方法

接口中不允许存在非抽象的方法,这句话对吗?

错误,要看 JDK 版本,1.8 之前这句话绝对正确,1.8 之后这句话就是错的

1.8 增加了新特性,默认方法,用 default 修饰方法,这种方法就可以直接实现。

public interface 接口名{
       抽象方法
       默认方法
}
public interface MyInterface {
    //抽象方法
    public void test();
    //默认方法
    default public void show(){
        System.out.println("接口");
    }
}

接口中是否允许存在成员变量

1、不能定义 private 和 protected 修饰的成员变量,只能定义 public 和默认访问权限修饰符定义的成员变量。

2、接口中的成员变量在定义时必须被初始化

3、接口中的成员变量都是静态常量,可以直接通过接口访问,并且值不能修改。(非静态只能通过对象去访问,静态的话直接用类去访问)。

public interface MyInterface {

    public int id = 10;

}
public class Test {

    private int num;

    public static void main(String[] args) {
        System.out.println(MyInterface.id);
    }
}

因为接口不能实例化对象,所以成员变量必须定义成静态方可访问,否则就无法访问。

接口中定义了一个抽象的方法,仅仅只是一个虚的概念性的描述,不能够具体化。

真正的具体化的操作不是由接口来完成的,而是接口的实现类来完成的,

非抽象子类就是对抽象类抽象方法的具体的实现,实现类就是对接口抽象方法的具体实现。

实现类本质上就是接口的非抽象子类。

接口不能实现接口,但是可以继承接口。

继承存在一个缺陷,只能是单继承,一个类只能有一个直接父类,接口就可以解决这个问题。

接口可以多实现,一个实现类可以同时实现多个接口。

public class Animal {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public interface MyInterface1 {
    public void fly();
}
public interface MyInterface2{
    public void run();
}
public class Bird extends Animal implements MyInterface1,MyInterface2 {
    @Override
    public void fly() {
        System.out.println("fly...");
    }

    @Override
    public void run() {
        System.out.println("run...");
    }
}
public class Test {
    public static void main(String[] args) {
        Bird bird = new Bird();
        bird.setName("鸟");
        bird.fly();
        bird.run();
    }
}

实际开发中,使用接口的一种思想,将接口定义成一种功能,将功能用接口来表示,比如 fly,run 都定义成接口(功能)

如果你的目标类需要具备某种功能,只需要实现对应的接口即可,而且是可以追加的

提高程序的扩展性

接口有多个功能,但是只想实现一个功能,怎么办?

1、default

public interface MyInterface {
    default public int test1(){
        return 0;
    }
    public int test2();
    
    default public int test3(){
        return 1;
    }
}

2、将目标类定义抽象类

public interface MyInterface {
    public int test1();
    public int test2();
    public int test3();
}
public abstract class MyClass implements MyInterface {

    @Override
    public int test2() {
        return 0;
    }

}

3、先用实现,再用继承

public interface MyInterface {
    public int test1();
    public int test2();
    public int test3();
}
public class MyTarget implements MyInterface {
    @Override
    public int test1() {
        return 0;
    }

    @Override
    public int test2() {
        return 0;
    }

    @Override
    public int test3() {
        return 0;
    }
}
public class MyClass extends MyTarget {
    @Override
    public int test2() {
        return super.test2();
    }

    @Override
    public int test3() {
        return super.test3();
    }
}

9.11.3面向接口编程的实际应用

工厂生成产品,产品A需要使用设备A来生产,产品B需要使用设备B来生产以此类推。

要求生产产品B

package com.southwind.test4;

public interface Equimpment {
    public void work();
}
package com.southwind.test4;

public class EquimpmentA implements Equimpment {
    public void work(){
        System.out.println("设备A运行,生产产品A...");
    }
}
package com.southwind.test4;

public class EquimpmentB implements Equimpment {
    public void work(){
        System.out.println("设备B运行,生产产品B...");
    }
}
package com.southwind.test4;

public class EquimpmentB implements Equimpment {
    public void work(){
        System.out.println("设备B运行,生产产品B...");
    }
}
package com.southwind.test4;

public class EquimpmentC implements Equimpment {
    public void work(){
        System.out.println("设备C运行,生产产品...");
    }
}
package com.southwind.test4;

public class Factory {

    private Equimpment equimpment;

    public Equimpment getEquimpment() {
        return equimpment;
    }

    public void setEquimpment(Equimpment equimpment) {
        this.equimpment = equimpment;
    }

    public void work(){
        System.out.println("开始生产...");
        this.equimpment.work();
    }
}
public class Test {    
    public static void main(String[] args) {        
        EquimpmentA equimpmentA = new EquimpmentA();        
        EquimpmentB equimpmentB = new EquimpmentB();           EquimpmentC equimpmentC = new EquimpmentC();        
        Factory factory = new Factory();                 
        factory.setEquimpment(equimpmentC);        
        factory.work();    }
}

面向接口也是多态的一种体现。

10.异常

10.1 什么是异常?

Java 中的错误大致分为两类:一类是编译时错误,一般是语法错误,很容易发现。

另一类是运行时错误,可以通过编译,语法正确,但是逻辑是错的。

Java可以把错误(运行时的错误)抽象成一个对象,以面向对象的方式来处理错误,Java中有一组专门来描述各种不同的错误的类,叫做异常处理。

Java 结合异常类提供了处理错误的机制,具体步骤是当程序出现错误的时候,Java 会自动创建一个对象,用来描述该错误信息,并且将该对象提交给系统,由系统将该对象交给能够处理这个错误的代码进行处理。

10.2 异常的分类

异常分为两类,Error 和 Exception

Error 是指系统错误,程序无法对它进行处理

Exception 是指程序运行期间出现的错误,程序可以对它进行处理。

10.3 异常的使用

try-catch

try 监听可能会抛出异常的代码,一旦出现异常,就会把异常抛出,通过 catch 对异常进行捕获,进而进行下一步的处理。

try{
	可能会发送异常的代码
} catch(异常对象){
	处理异常
}

默认情况是对异常的调用进行打印,帮助开发者排除错误。

除了使用 try-catch 之外,还会使用 finally ,finally 是追加在 catch 后面的代码块,表示无论代码是否抛出异常,finally 中的代码一定会执行。

try{
	可能会发送异常的代码
} catch(异常对象){
	处理异常
} finally{
	一定会执行的代码
}
public class Test {
    public static void main(String[] args) {
        System.out.println(test());
    }

    public static int test(){
        try {
            System.out.println("try");
            return 10;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("finally...");
            return 20;
        }
    }
}

10.4 异常类

树状结构,最顶点的类是 Throwable,它有两个子类 Exception 和 Error,都存放于 java.lang 中。

10.5 throw和throws

throw 和 throws 都是用来抛出异常的,但是使用方式和表示的含义完全不同。

Java 一共有 3 种抛出异常的方式

第一种 try-catch 可以理解为一种防范机制

第二种 throw 开发者主动抛出异常

package com.southwind.test;

public class Test {
    public static void main(String[] args) throws Exception {
        RuntimeException runtimeException = new RuntimeException();
        throw runtimeException;
    }
}

第三种 throws 描述一个方法有可能抛出某种异常

public static void test(int num) throws Exception{
    System.out.println(10 / num);
}

描述 test 方法内部可能会出现异常,在调用 test 方法的时候,就需要手动进行捕获

10.6 自定义异常

用来描述一些特殊的异常信息,JDK 提供的异常不足以描述,创建一个类,继承 Exception 或者 RuntimeException

定义一个方法,对传入的参数进行 ++ 操作并返回,同时要求参数必须是整数类型,如果传入的参数不是整数则抛出自定义异常。

package com.southwind.exception;

public class MyNumberException extends Exception {
    public MyNumberException(String message) {
        super(message);
    }
}
public static void main(String[] args) {
    Test3 test3 = new Test3();
    try {
        test3.add("hello");
    } catch (MyNumberException e) {
        e.printStackTrace();
    }
}

public int add(Object object) throws MyNumberException {
    //如何判断object是否为Integer
    if(!(object instanceof Integer)){
        String message = "传入的参数不是整数类型";
        throw new MyNumberException(message);
    }else{
        int num = (int)object;
        return num++;
    }
}

在代码中 throw 自定义异常的时候,必须在方法的定义处通过 throws 来描述要抛出的异常。

当自定义异常直接继承 Exception 的时候,需要上述处理

如果自定义异常继承 RuntimeException,则不需要上述处理

package com.southwind.exception;

public class MyNumberException extends RuntimeException {
    public MyNumberException(String message) {
        super(message);
    }
}
public static void main(String[] args) {
    Test3 test3 = new Test3();
    test3.add("hello");
}

public int add(Object object) {
    //如何判断object是否为Integer
    if(!(object instanceof Integer)){
        String message = "传入的参数不是整数类型";
        throw new MyNumberException(message);
    }else{
        int num = (int)object;
        return num++;
    }
}

Exception 分为 checked Exception 和 runtime Exception,checked Exception 表示需要强制去处理的异常(直接继承 Exception)要么立即 try-catch 进行处理,要么继续往出抛,谁接到谁处理。

runtime Exception 没有这个限制,throw 之后可以不处理。

直接继承 Exception 的类就是 checked Exception,直接继承 RuntimeException 的类就是 runtime Exception。

11 多线程

并发指的是同一时间有很多人使用。

并发量的能力,就是来评测服务器的能力。

11.1 基础

提升程序性能非常重要的一种方式,使用多线程可以让程序充分利用cpu的资源,提高CPU的使用率。

多线程的优点:

1、CPU 资源得到更合理的利用

2、程序设计更加简洁

3、程序响应更快,运行效率更高

多线程的缺点:

1、需要更多的内存空间来支持多线程

2、多线程并发访问可能会影响数据的准确性

3、数据被多线程共享可能会出现死锁

11.2 进程和线程

什么是进程?计算机正在运行的一个独立的应用程序。

什么是线程?线程是组成进程的基本单位,可以完成特定的功能,一个进程是由一个或多个线程组成。

进程和线程都是应用程序在执行过程中的概念,动态的,如果应用程序没有运行起来,那么就不存在进程或线程的概念。

进程和线程的区别?

进程在运行的时候,会有自己独立的内存空间,每个进程所占用的内存都是独立的,互不影响。

多个线程是共享内存空间的,但是线程的执行是相互独立的,线程必须依赖于进程才能执行,单独的线程是无法执行的,由进程来控制多个线程的执行。

11.3 什么是多线程

在同一个进程中,多个线程同时执行,这里的同时执行是一个假象,并不是真正的同时执行。

系统会为每个线程分配 CPU 资源,在某个具体的时间段内 CPU 资源会被某个线程所占用,在下一个时间段内被另外一个线程所占用,多个线程交替占用 CPU 资源,因为线程运行速度很快,所以看起来是同时在执行的。

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println("++++++++++main");
        }

        Test2 test2 = new Test2();
        test2.test();

    }
}

Java 程序的 main 方法就是一个主线程,无论在 main 方法中创建多个对象,调用多少个方法,它们都是单线程,有主线程来依次排队完成多个任务的执行。

程序中写的业务代码可以看作是一个个任务,这些任务是需要通过线程来执行的。

new Thread(()->{}).start()
package com.southwind.test;

public class Test {
    public static void main(String[] args) {

        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println("thread------------------");
··            }
        }).start();

        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println("===============OK================");
            }
        }).start();

    }
}

取快递

package com.southwind.test;

public class Express {
    private Integer id;

    public Express(Integer id) {
        this.id = id;
    }

    public void get() {
        System.out.println("开始取快递...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("完成取快递...");
    }
}
package com.southwind.test;

public class Test3 {
    public static void main(String[] args) {
        //100个快递,单线程
        //100S全部取完
        for (int i = 0; i < 100; i++) {
            Express express = new Express(i);
            express.get();
        }
//        for (int i = 0; i < 100; i++) {
//            Express express = new Express(i);
//            new Thread(()->{
//                express.get();
//            }).start();
//        }
    }
}

11.4 Java中线程的使用

Java 中创建线程有几种方式?

  • 继承 Thread
  • 实现 Runnable
  • 实现 Callable

继承 Thread

Thread 类是 JDK 提供的专门用来创建线程对象的类。

创建一个自定义的线程类,继承 Thread

线程和任务

任务就是具体的事情,线程是完成这件事情的具体的对象

张三去取快递

取快递就是任务

张三就是线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jgVc3AlY-1647864057404)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210603214319025.png)]

Java 中的 Thread 类如何绑定任务?

把当前这个线程要执行的任务定义到 run 方法中即可。

package com.southwind.test2;

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        //启动
        myThread.start();

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

注意:创建子线程对象之后,一定要调用 start 方法,才能让线程启动,去争夺 CPU 资源,才是多线程,如果调用 run 方法,那么线程根本没有启动,不会去争夺 CPU 资源,所以仍然是单线程。

11.5 java中创建线程的方式

11.5.1 继承 Thread 类

package com.southwind.test2;

public class MyThread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("MyThread.........");
        }
    }
}
package com.southwind.test2;

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

11.5.2 实现 Runable 接口

package com.southwind.test3;

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}
package com.southwind.test3;

public class Test {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }
}

1、自定义一个类,实现 Runnable 接口。

2、创建一个 Thread,将第一步创建的 Runnable 对象注入到 Thread 对象中,启动 Thread 对象。

Java中线程的使用需要两个参与角色,一个是线程,一个是任务。

任务是描述要做的事情,线程是具体执行任务的对象。

张三取快递

张三就是线程 Thread

取快递就是任务 Runnable

独立的线程和任务都是没有任何的意义的。

11.5.3 继承Thread 和 Runnable 接口的区别

无论那种形式,最终都要任何和线程进行集成。

继承Thread的方式在定义类的时候已经将线程和任务整合在一起

实现Runnable接口的方式将任务和线程分开的,使用的时候再整合到一起。

继承

实现接口

实际开发中推荐使用实现接口的方式进行开发,可以做到解耦合。 ·

11.5.4 实现Runnable 接口的优化

1、单独定义一个类实现接口

package com.southwind.test4;

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "--" + i);
        }
    }
}
package com.southwind.test4;

public class Test {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }
}

2、不用单独定义一个类,而使用内部类

package com.southwind.test4;

public class Test2 {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }

    static class MyRunnable implements Runnable{

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

3、匿名内部类

package com.southwind.test4;

public class Test3 {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + "--" + i);
                }
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
    }
}

4、lambda 表达式

package com.southwind.test4;

public class Test4 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "--" + i);
            }
        });
        thread.start();
    }
}

lambda表达式就是方法的实现作为参数来进行传递

Mytset mytest = new Mytest(() - >{

​ System.out.println(“test”)

})

某个类中,需要使用到接口,构造器中需要传入接口来创建对象

接口只能有一个抽象方法,因为如果有多个方法,那么 Lambda 传入的方法实现就无法确定到底是接口中的哪个方法了。

11.6 java 中线程的状态

线程一共有 5 种状态,线程可以在不同的状态之间进行切换。

  • 创建状态:实例化一个新的线程对象,还未启动。

new Thread () ;

  • 就绪状态:启动线程对象,调用 start() 方法,进入线程池等待抢占 CPU 资源。

new Thread().start()

  • 运行状态:线程对象获取到了 CPU 资源,在某个时间段内执行任务。

  • 阻塞状态:正在运行的线程暂停执行任务,释放所占用的 CPU 资源,并且在解除阻塞状态之后也不能直接回到运行状态,而是重新回到就绪状态,等待获取 CPU 资源。

  • 终止状态:线程运行完毕或者因为异常导致线程中断。 66

package com.southwind.test6;

public class Test {
    public static void main(String[] args) {
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "-------"+i);
            }
        },"张三").start();

        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println(i + "================" + Thread.currentThread().getName());
            }
        },"李四").start();
    }
}

11.7 线程调度

11.7.1 线程休眠

让线程暂停执行,从运行状态进入阻塞状态,让出 CPU 资源给其他线程。

调用 sleep 方法实现线程休眠。

sleep(long mills)

package com.southwind.test6;

public class Test2 {
    public static void main(String[] args) {
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println(i);
                if(i == 50){
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}
package com.southwind.test6;

public class Test2 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println(i);
            }
        });
        try {
            thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.start();

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

    }
}

上述代码表示,先等待 2 s ,然后主线程的循环和子线程的循环交替执行。

package com.southwind.test6;

public class Test2 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(i);
            }
        });

        thread.start();

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

    }
}

直接运行主线程的循环,再每隔 2s 执行一次子线程的循环。

package com.southwind.test6;

public class Test2 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 100; i++) {
                System.out.println(i);
            }
        });

//        try {
//            Thread.sleep(2000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

        thread.start();

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

    }
}

直接运行主线程的循环,间隔 2s ,运行子线程的循环。

sleep 方法在哪调用,就是让当前线程休眠,无论通过哪种形式调用

主线程做了两件事

1、启动子线程

2、循环

11.7.2 线程合并

将指定的线程加入到当前的线程中,合并为一个线程,由原本的两个线程交替执行变成一个线程中的两个任务顺序执行。

join()

线程甲和线程乙,线程甲在执行到某个时间节点的时候,调用线程乙的 join 方法,从当前时间节点开始,CPU 资源全部交给线程乙,被它独占,线程甲就进入阻塞状态,直到乙执行完毕,甲进入就绪状态,继续等待争夺 CPU 资源。

package com.southwind.test;

public class Test {
    public static void main(String[] args) {
        JoinRunnbale joinRunnbale = new JoinRunnbale();
        Thread thread = new Thread(joinRunnbale);
        thread.start();
        for (int i = 0; i < 100; i++) {

            if(i == 20){
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println(i + "+++++++++++++++main");
        }
    }
}

/**
 * 任务类
 */
class JoinRunnbale implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i + "-----------------JoinRunnable");
        }
    }
}

join() 存在重载:join(long millis),某个时间节点调用了线程乙的 join(long millis),从这一时刻起,CPU 资源被乙独占,线程甲进入阻塞状态,持续时间为 millis,到点之后,无论乙是否执行完毕,它不再独占资源,而是恢复两个线程争夺资源。

package com.southwind.test;

public class Test {
    public static void main(String[] args) {
        JoinRunnbale joinRunnbale = new JoinRunnbale();
        Thread thread = new Thread(joinRunnbale);
        thread.start();
        for (int i = 0; i < 100; i++) {
            if(i == 20){
                try {
                    thread.join(8000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(i + "+++++++++++++++main");
        }
    }
}

/**
 * 任务类
 */
class JoinRunnbale implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i + "-----------------JoinRunnable");
        }
    }
}

11.7.3 线程礼让

在某个时间点,让线程暂停抢占CPU资源的行为,将CPU资源让给其他线程使用。

礼让就是一下,在这个瞬间过去之后就继续开始争夺。

只是某个时间点(瞬间)礼让一次,过了这个时间点,又开始参与争夺 CPU 资源。

yield

package com.southwind.test;

public class Test2 {
    public static void main(String[] args) {
        YieldThread1 thread1 = new YieldThread1();
        thread1.setName("线程甲");
        YieldThread2 thread2 = new YieldThread2();
        thread2.setName("线程乙");
        thread1.start();
        thread2.start();
    }
}

class YieldThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i == 5){
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName() + "-----------" + i);
        }
    }
}

class YieldThread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "-----------" + i);
        }
    }
}

11.8 线程同步

11.8.1 线程同步的实现

异步和同步

有两个任务 A 和 B

异步指 A 和 B 同时进行,互不影响。

同步指 A 和 B 顺序执行,同一时间内只能执行一个任务,排队。

线程同步存在的意义是什么?

当多个线程同时访问一个共享数据的时候,可能会造成数据的不安全。

同步的话,即使是多个线程也要看成一个线程来看,就是进行顺序的执行。

异步的话,就是多线程的之间的关系。

package com.southwind.test2;

public class Test {
    public static void main(String[] args) {
        Account account = new Account();
        Thread thread1 = new Thread(account,"张三");
        Thread thread2 = new Thread(account,"李四");
        thread1.start();
        thread2.start();
    }
}

class Account implements Runnable{

    private static int num;

    @Override
    public void run() {
        num++;
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "是当前的第" + num + "位访客");
  

同时输出两个2是因为两个人是异步的。

上述就是多个线程同时访问共享数据造成的数据不安全,如何解决?

只需要让多个线程排队(同步)即可。

最简单的同步方式就是使用 synchronized 关键字来实现。

public synchronized void run ( )

相当于给资源上锁,要访问资源就必须拿到锁,如果锁被其他线程拿走了,那么当前线程就只能处于阻塞状态,等待锁的释放。

package com.southwind.test2;

public class Test {
    public static void main(String[] args) {
        Account account = new Account();
        Thread thread1 = new Thread(account,"张三");
        Thread thread2 = new Thread(account,"李四");
        thread1.start();
        thread2.start();
    }
}

class Account implements Runnable{

    private static int num;

    @Override
    public synchronized void run() {
        num++;
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "是当前的第" + num + "位访客");
    }
}

synchronized 可以修饰实例方法(非 static),也可以修饰静态方法,两者有区别。

synchronized 除了可以锁定方法之外,还可以锁定代码块。当他来修饰静态方法的时候,就是锁的所有的方法,但是如果修饰的是实例方法的话,每一个实例方法创造出来的都是一个新的实例方法。所以锁的话,就是会很麻烦。

package com.southwind.test3;

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(()->{
//                Test.test();
                Test test = new Test();
                test.test();
            });
            thread.start();
        }
    }

    public void test(){
        synchronized (this){
            System.out.println("start...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}
package com.southwind.test3;

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(()->{
                Test.test();
            });
            thread.start();
        }
    }

    public static void test(){
        synchronized (Test.class){
            System.out.println("start...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}

synchronized 关键字是用来实现线程同步的,可以锁方法,也可以锁代码块,无论锁谁,底层的原理都是一样的,就是看锁定的东西在内存中是一个还是多个,如果是一个,就会实现同步,5 个人 1 个厕所。

如果锁定的东西内存中是多个,就不会同步,5 个人 5 个厕所,同时进行。

11.9 线程安全的单例模式

一个类只能有一个实例化对象,由多个线程共享该对象资源。

如果是多线程模式的话,就是AB同时进入,就会创建两个对象。如果说是不用两个来进入的话,就是使用synchronized关键字就可以了。

package com.southwind.test;

public class SingletonDemo {

    private static SingletonDemo instance;

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

    private SingletonDemo(){

    }


}
package com.southwind.test;

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(SingletonDemo.getInstance());
        }
    }
}

非静态方法的话只能通过对象来进行调用。

string str 直接赋值和构造器是不一样的。

11.20 字符串常量池

内存中的一块特定的区域,专门用来存放字符串对象,为了节省空间。

String str = new String(“abc”);

String str = new String(“abc”);

通过构造器的方式创建字符串对象,直接将创建好的对象在堆内存中存放,不考虑是否有重复,而使用字符串常量池,获取一个对象的时候,首先在字符串常量池中查找是否存在,如果存在,直接返回,否则再创建新的对象。

直接赋值就是在字符串常量池中查找对象

使用构造器就不会考虑字符串常量池,而是直接在堆中创建。

11.21 包装类常量池

跟字符串常量池类似,使用包装类对象的时候也可以使用常量池来节约资源。

除了通过构造器,其他创建对象的方式都会使用常量池。

包装类使用常量池有取值区间的限制,-128到127之间会使用常量池,一旦超过这个范围,不会使用常量池,而是使用堆。

对象才有常量池这个概念。

11.22 死锁

几个人围着一张桌子吃饭,每人发一根筷子,要求每个人必须凑够两根筷子才能吃饭,每个人都希望得到别人的筷子,同时都不愿意把自己的筷子让出来,就形成一个互斥的状态,导致程序无法执行,卡住。

package com.southwind.test2;

public class Data {
}
package com.southwind.test2;

public class DeadLockRunnable implements Runnable {
    public int num;
    private static Data data1 = new Data();
    private static Data data2 = new Data();
    @Override
    public void run() {
        if(num == 1){
            System.out.println(Thread.currentThread().getName() + "获取到了data1,等待获取data2");
            //获取data1
            synchronized (data1){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //获取data2
                synchronized (data2){
                    System.out.println(Thread.currentThread().getName() + "用餐完毕");
                }
            }
        }
        if(num == 2){
            System.out.println(Thread.currentThread().getName() + "获取到了data2,等待获取data1");
            synchronized (data2){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (data1){
                    System.out.println(Thread.currentThread().getName() + "用餐完毕");
                }
            }
        }
    }
}
package com.southwind.test2;

public class Test {
    public static void main(String[] args) {
        DeadLockRunnable runnable1 = new DeadLockRunnable();
        runnable1.num = 1;
        DeadLockRunnable runnable2 = new DeadLockRunnable();
        runnable2.num = 2;
        new Thread(runnable1,"张三").start();
        new Thread(runnable2,"李四").start();
    }
}

我们要去了解如何去解决死锁的问题。解决死锁的话还是不要让线程同时进行。

package com.southwind.test2;

public class Test {
    public static void main(String[] args) {
        DeadLockRunnable runnable1 = new DeadLockRunnable();
        runnable1.num = 1;
        DeadLockRunnable runnable2 = new DeadLockRunnable();
        runnable2.num = 2;
        new Thread(runnable1,"张三").start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(runnable2,"李四").start();
    }
}

11.23 重入锁

ReentrantLock 是对 synchronized 的升级,synchronized 是通过 JVM 实现,ReentrantLock 是通过 JDK 实现,让开发者可以用面向对象的思想来操作同步锁。

重入锁是指可以多次上锁,上锁和解锁方式和 synchronized 不同

synchronized 是自动上锁,自动解锁,只需要添加关键字,不需要开发者手动操作上锁和解锁。自动的不专业。

ReentrantLock 需要开发者手动上锁、手动解锁。

package com.southwind.test3;

import java.util.concurrent.locks.ReentrantLock;

public class Account implements Runnable {

    private static int num;
    private ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {
        //上锁
        reentrantLock.lock();
        num++;
        System.out.println(Thread.currentThread().getName() + "是当前的第" + num + "位访客");
        //解锁
        reentrantLock.unlock();
    }
}
package com.southwind.test3;

public class Test {
    public static void main(String[] args) {
        Account account = new Account();
        new Thread(account).start();
        new Thread(account).start();
    }
}

使用 ReentrantLock 可以让锁定的粒度更细,不需要上锁的代码就可以分离出来。

ReentrantLock 除了在使用上更加灵活之外,还具备限时性的特点,是指在等待某个资源的时候可以设置一个等待时间,一旦超出这个时间还没有拿到锁,则中断线程,不会一直等下去,而 sychronized 就会一直等下去。

tryLock(long time,TimeUnit unit) 使用的时间值的看法。

package com.southwind.test3;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class TimeLock implements Runnable {
private ReentrantLock reentrantLock = new ReentrantLock();

@Override
public void run() {
    try {
        if(reentrantLock.tryLock(3, TimeUnit.SECONDS)){
            System.out.println(Thread.currentThread().getName() + "获取到了锁");
            Thread.sleep(5000);
        }else{
            System.out.println(Thread.currentThread().getName() + "没有拿到锁");
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        if(reentrantLock.isHeldByCurrentThread()){
            reentrantLock.unlock();
        }
    }
}
package com.southwind.test3;

public class Test {
    public static void main(String[] args) {
        TimeLock timeLock = new TimeLock();
        new Thread(timeLock,"张三").start();
        new Thread(timeLock,"李四").start();
    }

只需要让时间超过三秒就可以了去解决这问题了

锁的话还是主要的去锁资源,就是共享的资源。

11.24 多线程实现买票功能

public class TicketRunnable implements Runnable {
   //剩余球票
    private int count1 = 15;
    //已经卖出的球票
    private int count2 = 0;
    @Override
    public void run() {
       while(count1 > 0){
           try{
               Thread.sleep(500);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           if(count1 == 0){
               return;
           }
           synchronized (TicketRunnable.class){
               count1--;
               count2++;
               if(count1 == 0){
                   System.out.println((Thread.currentThread().getName() + "售出第" + count2 + "张票,球票已经售空"));
               }else {
                   System.out.println(Thread.currentThread().getName() + "售出第" + count2 + "张票,剩余" + count1 + "张票");
               }
           }
       }
    }
}
public class Test {
    public static void main(String[] args) {
        TicketRunnable runnable = new TicketRunnable();
        new Thread(runnable,"窗口A").start();
        new Thread(runnable,"窗口B").start();
        new Thread(runnable,"窗口C").start();
    }
}

12 JUC并发编程

juc 指的是Java的并发编程的工具包

Java.util.concurrent JDK一个包,包下全部都是Java并发编程相关的类/接口

为什么公司如此看重并发编程的能力,并发编程的目的就是充分利用计算机的资源,把计算机的性能发挥到极致。

12.1 什么是高并发

并行和并发的关系

并发:指的是多线程操作同一个资源,但不是同时操作,而是交替操作,单核cpu

并行:才是真正的多个线程同时执行,多核cpu,每个线程使用一个cpu的资源来运行。

我们所说的并发是一种程序的设计标准,在单核cpu下,开发出来的代码应该更加充分的利用资源,有效提升程序的效率

编写出来的代码具备处理多个任务在同一时间段内执行的能力

高并发是互联网分布式系统架构中心中必须要考虑的因素之一,他是指的通过设计保证系统在互联网海量的用户请求的情况下,能够确保系统的正常运行。

互联网架构中,如何实现高并发设计?

垂直扩展和水平扩展。

垂直扩展:提升单机的处理的能力

1.增强的单机的硬件性能,cpu核数,提升内存,硬盘扩容,网卡升级

2.提升软件架构性能,使用缓存来提高查询的效率,使用异步请求来提升服务的吞吐量,使的NoSQL提升的数据访问能力,使用的是并发框架

垂直扩展有上限,他会一定到某个瓶颈无法再优化

水平扩展:

水平扩展理论是没有上线的,所有互联网的分布式架构设计的最终方案还是水平扩展。

集群和分布式的区别?

集群:每台服务器完成的工作是一样,增加服务器的数量来提高并发能力

分布式:指的是将系统拆分成不同的模块,交给不同服务器来完成

12.2 Java中实现多线程的第三种方式

callable 和 runnable

实现 Callable 接口,与 Runnable 的区别在于 Callable 方法有返回值。

public class Test {
    public static void main(String[] args) {
        //任务
        MyCallable myCallable = new MyCallable();
        //线程
        FutureTask<String> futureTask = new FutureTask<>(myCallable);
        new Thread(futureTask).start();
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("callable");
        return "success";
    }
}

Callable 和 Runnable 的区别

  • Callable 可以在任务结束后提供一个返回值,Runnable 没有这个功能。
  • Callable 中的 call 方法可以抛出异常,Runnable 的 run 不能抛异常。

12.3 sleep 和 wait 方法的区别

让线程暂停执行任务

来自不同的类

sleep 是 Thread 类中的方法

wait 是 Object 类中的方法

sleep 是直接让当前线程暂停执行,进入阻塞状态,暂停不是看用哪个线程对象调用,而是在哪调用,在哪调就是让当前线程修眠

让主线程休眠

package com.southwind.test;

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println("A----------------");
            }
        });

        thread.start();
        try {
            //让主线程休眠
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        for (int i = 0; i < 100; i++) {
            System.out.println("=====================B");
        }
    }
}

让子线程休眠

package com.southwind.test;

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            try {
                //让子线程休眠
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 100; i++) {
                System.out.println("A----------------");
            }
        });

        thread.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("=====================B");
        }
    }
}

wait 也是让线程休眠,但是不直接作用于线程对象,而是作用于线程对象访问的资源,Thread --》data,调用 data.wait,此时 Thread 就会休眠。

wait 方法有一个前提,当前线程对象必须拥有 data 对象,所以 wait 方法只能在同步方法或者同步代码块中调用,否则抛出异常。

package com.southwind.test;

import java.util.concurrent.TimeUnit;

public class Test2 {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.test(i);
            }
        }).start();
    }
}

class Data {
    public synchronized void test(int i){
        if(i == 5){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i + "--------------------------");
    }
}

wait 让线程对象休眠,没有阻塞时间,如果不加处理,线程会永远阻塞下去。

如何解除阻塞?

1、指定 wait 时间,wait(long millis)

if(i == 5){
    try {
        this.wait(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

2、通过 notify 方法唤醒线程

package com.southwind.test;

import java.util.concurrent.TimeUnit;

public class Test2 {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.test(i);
            }
        }).start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(8);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            data.test2();
        }).start();
    }
}

class Data {
    //在i==5的时候让线程休眠
    public synchronized void test(int i){
        if(i == 5){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i + "--------------------------");
    }

    //唤醒线程
    public synchronized void test2(){
        this.notify();
    }
}

是否释放锁

wait 会释放锁,sleep 不会释放锁

最终完成的目的是一样的。

12.4 synchronized 锁定的是谁?

如果 synchronized 修饰非静态方法,则锁定的就是方法调用者

package com.southwind.test2;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            data.func1();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            data.func2();
        },"B").start();
    }
}

class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

    public synchronized void func2(){
        System.out.println("2...");
    }
}

synchronized 修饰的是 func1,func2,所以谁调用 func1、func2 就锁谁

对代码进行修改

public class Test {
    public static void main(String[] args) {
        Data data1 = new Data();
        Data data2 = new Data();
        new Thread(()->{
            data1.func1();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            data2.func2();
        },"B").start();
    }
}

class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

    public synchronized void func2(){
        System.out.println("2...");
    }
}

不会排队,继续修改

public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            data.func1();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            data.func3();
        },"B").start();
    }
}

class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

    public synchronized void func2(){
        System.out.println("2...");
    }

    public void func3(){
        System.out.println("3...");
    }
}

不会排队,因为 func3 的调用不需要锁定资源,无需等待 func3 的执行完毕即可执行。

如果方法添加了 synchronized ,不是说不能调,只是调用的是需要锁定当前对象,如果没有添加 synchronized,不需要考虑任何锁定的问题,直接调。

如果 synchronized 修饰静态方法,则锁定的就是类,无论多少个对象调用,都会同步

public class Test2 {
    public static void main(String[] args) {
        Data2 data2 = new Data2();
        Data2 data1 = new Data2();
        new Thread(()->{
            data1.func1();
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            data2.func2();
        }).start();
    }
}

class Data2 {
    public synchronized static void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

    public synchronized static void func2(){
        System.out.println("2...");
    }
}

如果 synchronized 静态方法和 synchronized 非静态方法同时存在,静态方法锁类,实例方法锁对象

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Data3 data = new Data3();
        Data3 data2 = new Data3();
        new Thread(()->{
            data.func1();
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            data2.func2();
        }).start();
    }
}

class Data3{
    public synchronized static void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

    public synchronized void func2(){
        System.out.println("2...");
    }
}

如果 synchronized 修饰的是代码块,则锁定的是传入的对象

能否实现同步,就看锁定的对象是否位同一个对象

public class Test {
    public static void main(String[] args) {
        Data4 data = new Data4();
        for (int i = 0; i < 5; i++) {
            Integer num = Integer.valueOf(1);
            new Thread(()->{
                data.func(num);
            }).start();
        }
    }
}

class Data4{
    public void func(Integer num){
        synchronized (num){
            System.out.println("start...");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}

此时会同步,因为 num = 1 在内存中只有一份,5 个线程争夺同一个资源。

public class Test {
    public static void main(String[] args) {
        Data4 data = new Data4();
        for (int i = 0; i < 5; i++) {
            Integer num = Integer.valueOf(i);
            new Thread(()->{
                data.func(num);
            }).start();
        }
    }
}

class Data4{
    public void func(Integer num){
        synchronized (num){
            System.out.println("start...");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}

不会同步,因为 5 个线程有 5 个不同的 num,不会产生争夺,而是各用各的。

public class Test {
    public static void main(String[] args) {
        Account account = new Account();
        new Thread(()->{
            account.count();
        },"A").start();
        new Thread(()->{
            account.count();
        },"B").start();
    }
}
class Account{
    private Integer num = 0;
    private Integer id = 0;
    public void count(){
        synchronized (id){
            num++;
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "是第" + num + "位访客");
        }
    }
}

12.5 JUC

ConcurrentModificationException

并发修改异常,集合 List Set Map

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }
    }
}

当多个线程同时对集合对象进行读写操作的时候,就会抛出 ConcurrentModificationException 异常。

ArrayList 是线程不安全的

如何解决?使用线程安全的类来完成数据的装载。

1、直接将 ArrayList 替换为 Vector

public class Test {
    public static void main(String[] args) {
        List<String> list = new Vector<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }
    }
}

2、Collections.synchronizedList

public class Test {
    public static void main(String[] args) {
        List<String> list = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }
    }
}

3、JUC CopyOnWriteArrayList

public class Test {
    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }
    }
}

CopyOnWrite 写时复制,当我们往一个容器中添加元素的时候,不是直接操作这个容器,而是将原来的容器先复制一份,往复制出来的新容器中添加元素,添加完毕,再将原容器的引用指向新容器,以此来解决并发修改异常,实际上就是实现了读写分离。

12.6 JUC 工具类

CountDownLatch

减法计数器

Java Memory Model Java 内存模型 JMM

工作内存,当一个线程对数据进行操作的时候,不会直接操作主内存,而是会将

主内存中的数据进行复制,复制到工作内存中,线程操作的是工作内存中的数据,操作

完成之后,再将工作内存中的数据同步到主内存中。

public class Test2 {
    public static void main(String[] args) {

        CountDownLatch countDownLatch = new CountDownLatch(100);

        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println("+++++++++++++++++++++++++Thread");
                countDownLatch.countDown();
            }
        }).start();

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

countDown():计数器减一

await():计数器停止,唤醒其他线程

CyclicBarrier

加法计数器

public class CyclicBarrierTest {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(30, ()->{
            System.out.println("放行");
        });

        for (int i = 0; i < 90; i++) {
            final int temp = i;
            new Thread(()->{
                //lambda表达式中只能访问final修饰的变量
                System.out.println("-->" + temp);
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

Semaphore

计数信号量,实现限流操作,限制可以访问某些资源的线程数量。

Semaphore 只有 3 个操作

  • 初始化
  • 获得许可
  • 释放
public class SemaphoreTest {
    public static void main(String[] args) {
        //初始化
        Semaphore semaphore = new Semaphore(5);
        for (int i = 0; i < 15; i++) {
            new Thread(()->{
                try {
                    //获得许可
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "进店购物");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + "出店");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //释放
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

12.7 读写锁

ReadWriteLock 接口,实现类 ReentrantReadWriteLock,可以多线程同时读,但是同一时间只能有一个线程进行写入。

读锁是一个共享锁,写锁是一个独占锁。

public class ReadWriteLockTest {
    public static void main(String[] args) {
        Cache2 cache = new Cache2();
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                cache.write(temp,String.valueOf(temp));
            }).start();
        }

        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                cache.read(temp);
            }).start();
        }
    }
}

class Cache2{

    private Map<Integer,String> map = new HashMap<>();
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    /**
     * 写操作
     * @param key
     * @param value
     */
    public void write(Integer key,String value){
        readWriteLock.writeLock().lock();
        System.out.println(key + "开始写入");
        map.put(key, value);
        System.out.println(key + "写入完毕");
        readWriteLock.writeLock().unlock();
    }

    /**
     * 读操作
     * @param key
     */
    public void read(Integer key){
        readWriteLock.readLock().lock();
        System.out.println(key + "开始读取");
        map.get(key);
        System.out.println(key + "读取完毕");
        readWriteLock.readLock().unlock();
    }

}

12.8 线程池

存放线程对象的缓冲池,为了节约资源。

预先创建一定数量的线程对象,存入缓冲池中,需要用的时候直接从缓冲池中取出,用完不销毁,重新放回到缓冲池中,供下一次请求使用。

优势:

  • 提高线程的利用率
  • 提高响应速度
  • 便于统一管理线程对象
  • 可控制最大并发数

7 大核心参数:

1、corePoolSize 核心池的大小,目前上班的柜台数

2、maximumPoolSize 线程池最大线程数,所有的柜台数(包括上班和不上班的)

3、keepAliveTime:空闲线程的存活时间

4、unit:keepAliveTime 时间单位

5、workQueue:阻塞队列,等候区

6、threadFactory:线程工厂,生成线程对象

7、handler:拒绝任务策略

public class Test2 {
    public static void main(String[] args) {
        //线程池对象
        ExecutorService executorService = new ThreadPoolExecutor(
                2,
                3,
                1L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        for (int i = 0; i < 6; i++) {
            executorService.execute(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "===>办理业务");
            });
        }

        executorService.shutdown();
    }
}

Executors 提供了三种快速创建线程池的方式

newSingleThreadExecutor():线程池中只有一个线程对象

newFixedThreadPool(5):线程池中有 5 个线程对象

newCachedThreadPool():线程池中线程对象随机分配,由电脑配置决定

public class Test3 {
    public static void main(String[] args) {
        //单例线程池,只有一个线程对象
//        ExecutorService executorService = Executors.newSingleThreadExecutor();
//        ExecutorService executorService = Executors.newFixedThreadPool(5);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10000; i++) {
            final int temp = i;
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName() + ":" + temp);
            });
        }
        executorService.shutdown();
    }
}

AbortPolicy 直接抛出异常
DiscardPolicy 放弃任务,不抛出异常
DiscardOldestPolicy 尝试与阻塞队列最前面的任务去争夺,不抛出异常
CallerRunsPolicy 谁调用谁处理

13 集合

需要创建多个对象,但是数量和类型不确定。

集合是 Java 提供的一种类型,功能和数组类似,但是长度和数据类型都是动态。

集合框架(包括很多类和接口)

可以分为 3 层,最上层是接口,继而是接口的实现类,接下来是对集合进行操作的各种工具类。

常用的接口

接口描述
ListCollection的子接口,存储一组有序,不唯一的数据
SetCollection的子接口,存储一组无序,唯一的数据
Collection集合框架最基础的接口
Map与 Collection 同级的接口,存储一组键值对象,无序,key 值唯一,value 可以不唯一
Iterator输出集合元素的接口,一般适用于无序集合,遍历集合中的数据

Collection 接口常用方法

方法描述
int size()获取集合长度
boolean isEmpty()判断集合是否为空
boolean contains(Object e)判断集合是否包含某个元素
Itreator iterator()获取迭代器(遍历集合)
Object[] toArray()集合转数组
boolean add(E e)向集合中添加元素
boolean remove(Object e)删除集合中的元素
boolean containsAll(Collection c)判断当前集合是否包含另外一个集合
boolean addAll(Collectino c)将集合添加到另外一个集合中
boolean removeAll(Collection c)从目标集合中删除子集合
void clear()清除集合中的所有元素
boolean equals(Object o)比较两个集合是否相等
int hashCode()获取集合的哈希值

子接口:

  • List
  • Set
  • Queue

13.1 List 接口

List 接口是 Collection 的子接口,常用的实现类有 ArrayList、LinkedList

ArrayList

ArrayList 实现了长度可变的数组,可以在内存中分配连续的空间,底层是基于索引的数据结构,所以查询效率很高,缺点是添加或删除数据效率较低,需要完成元素的移动。

重写 toString 方法,拼接数据

public class Test {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add("Hello");
        arrayList.add("World");
        arrayList.add("JavaSE");
        arrayList.add("JavaME");
        arrayList.add("JavaEE");
        System.out.println(arrayList.toString());
        System.out.println("集合长度:"+arrayList.size());
        System.out.println("集合是否包含Hello:" + arrayList.contains("Hello"));
        Iterator iterator = arrayList.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
        for (int i = 0; i < arrayList.size(); i++) {
            System.out.println(arrayList.get(i));
        }
        System.out.println("删除之前的集合是:" + arrayList);
        arrayList.remove(2);
        System.out.println("*****************************************");
        System.out.println("删除之后的集合是:" + arrayList);
        System.out.println("*****************************************");
        arrayList.remove("World");
        System.out.println("删除之后的集合是:" + arrayList);
        arrayList.add(2, "OK");
        System.out.println("添加之后的集合是:" + arrayList);
        arrayList.set(2, "TEST");
        System.out.println(arrayList);
        System.out.println(arrayList.indexOf("TEST2"));
    }
}

Vector 是一个早期的 List 实现类,用法基本和 ArrayList 一致。


public class Test2 {
    public static void main(String[] args) {
        Vector arrayList = new Vector();
        arrayList.add("Hello");
        arrayList.add("World");
        arrayList.add("JavaSE");
        arrayList.add("JavaME");
        arrayList.add("JavaEE");
        System.out.println(arrayList.toString());
        System.out.println("集合长度:"+arrayList.size());
        System.out.println("集合是否包含Hello:" + arrayList.contains("Hello"));
        Iterator iterator = arrayList.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
        for (int i = 0; i < arrayList.size(); i++) {
            System.out.println(arrayList.get(i));
        }
        System.out.println("删除之前的集合是:" + arrayList);
        arrayList.remove(2);
        System.out.println("*****************************************");
        System.out.println("删除之后的集合是:" + arrayList);
        System.out.println("*****************************************");
        arrayList.remove("World");
        System.out.println("删除之后的集合是:" + arrayList);
        arrayList.add(2, "OK");
        System.out.println("添加之后的集合是:" + arrayList);
        arrayList.set(2, "TEST");
        System.out.println(arrayList);
        System.out.println(arrayList.indexOf("TEST2"));
    }
}

ArrayList 和 Vector 的区别是什么?

Vector 是线程安全的,ArrayList 是线程不安全的

Stack 是 Vector 的子类,实现了栈的数据结构,先进后出、后进先出

public class Test3 {
    public static void main(String[] args) {
        Stack stack = new Stack();
        stack.push("Hello");
        stack.push("JavaSE");
        stack.push("JavaME");
        stack.push("JavaEE");
        System.out.println(stack);
        //将栈顶元素的值取出,但是栈顶元素不会删除
        System.out.println(stack.peek());
        //直接弹出栈顶元素
        System.out.println(stack.pop());
        System.out.println(stack.pop());
        System.out.println(stack.pop());
    }
}

LinkedList 实现了链表的数据结构,“先进先出”,元素的存储空间是不连续的,随机分散在内存中的,元素和元素之间通过存储彼此的位置信息来形成连接关系,通过位置信息找到前后节点的关系。

优势是增删效率高,缺点是查询效率低,与 ArrayList 形成对比,它们的特性都是由于底层的存储结构决定的。

public class Test4 {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        linkedList.add("Hello");
        linkedList.add("World");
        linkedList.add(1,"OK");
        System.out.println(linkedList);
        linkedList.addFirst("TEST");
        linkedList.addLast("Success");
        System.out.println(linkedList);
        System.out.println("**********************************");
        System.out.println(linkedList.peekFirst());
        System.out.println(linkedList.peekLast());
        System.out.println(linkedList.peek());
        System.out.println(linkedList.poll());
        System.out.println(linkedList.pollFirst());
        System.out.println(linkedList.pollLast());
    }
}

13.2 Set 接口

和 List 一样,也是 Collection 的子接口,Set 中的元素没有顺序,但是不能重复

List 存入有序,可重复的元素。

Set 常用实现类包括 HashSet、LinkedHashSet、TreeSet。

HashSet 底层是 HashMap 实现的

HashSet

存储一组无序且唯一的元素

public class HashSetTest {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("Hello");
        hashSet.add("World");
        hashSet.add("Java");
        hashSet.add("Hello");
        System.out.println(hashSet.size());
        System.out.println(hashSet);
        System.out.println("***************************************");
        Iterator iterator = hashSet.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
        hashSet.remove("Hello");
        System.out.println(hashSet);
    }
}

LinkedHashSet

存储一组有序且唯一的元素

有序和 List 的有序不是一回事

List 的有序是指存入集合的元素是有下标的,可以通过下标访问任意元素。

LinkedHashSet 的有序并不是说元素有下标,是指元素的存储顺序和遍历顺序是一致的。

public class LinkedHashSetTest {
    public static void main(String[] args) {
        LinkedHashSet linkedHashSet = new LinkedHashSet();
//        linkedHashSet.add("Hello");
//        linkedHashSet.add("World");
//        linkedHashSet.add("Java");
//        linkedHashSet.add("Hello");
        for (int i = 0; i < 100; i++) {
            linkedHashSet.add(i);
        }
        System.out.println(linkedHashSet);
        Iterator iterator = linkedHashSet.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

equals 和 hashCode 一般是配合起来使用,来共同决定两个对象是否相等。

1、在比较的时候,首先比例两个对象的 hashCode,如果不相等,则直接判断两个对象不是同一个对象。

2、如果相等,此时不能决定两个对象是否相等,需要再次利用 equals 方法来判断,如果 equals 返回 true,则认为两个对象相等,否则认为两个对象不相等。

public class LinkedHashSetTest {
    public static void main(String[] args) {
        LinkedHashSet linkedHashSet = new LinkedHashSet();
        linkedHashSet.add(new A(1));
        linkedHashSet.add(new A(2));
        System.out.println(linkedHashSet);
//        linkedHashSet.add("Hello");
//        linkedHashSet.add("World");
//        linkedHashSet.add("Java");
//        linkedHashSet.add("Hello");
//        for (int i = 0; i < 100; i++) {
//            linkedHashSet.add(i);
//        }
//        System.out.println(linkedHashSet);
//        Iterator iterator = linkedHashSet.iterator();
//        while (iterator.hasNext()){
//            System.out.println(iterator.next());
//        }

    }
}

class A{
    private int num;

    public A(int num) {
        this.num = num;
    }

    @Override
    public String toString() {
        return "A{" +
                "num=" + num +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        return false;
    }

    @Override
    public int hashCode() {
        if(num == 1) return 1;
        if(num == 2) return 1;
        return 0;
    }
}

TreeSet

TreeSet 存储一组有序,唯一的元素,这里的有序和 List、LinkedHashSet 都不同

TreeSet 的有序是指集合会自动对存入 TreeSet 中的元素按照升序进行排列。

public class TreeSetTest {
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet();
        treeSet.add(1);
        treeSet.add(3);
        treeSet.add(6);
        treeSet.add(2);
        treeSet.add(5);
        treeSet.add(4);
        treeSet.add(1);
        System.out.println(treeSet);
    }
}

输出结果:[1, 2, 3, 4, 5, 6]

TreeSet 内部会自动按照升序对元素进行排列,所以添加到 TreeSet 集合中的元素必须具备排序的功能。

public class TreeSetTest {
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet();
        treeSet.add(new B(1));
        treeSet.add(new B(3));
        treeSet.add(new B(6));
        treeSet.add(new B(2));
        treeSet.add(new B(5));
        treeSet.add(new B(4));
        treeSet.add(new B(1));
        System.out.println(treeSet);
    }
}

class B implements Comparable{
    private int num;

    public B(int num) {
        this.num = num;
    }

    @Override
    public int compareTo(Object o) {
        /**
         * A.compareTo(B)
         * 1表示A大于B
         * 0表示A等于B
         * -1表示A小于B
         */
        B b = (B) o;
        if(this.num > b.num) return 1;
        if(this.num == b.num) return 0;
        if(this.num < b.num) return -1;
        return 0;
    }

    @Override
    public String toString() {
        return "B{" +
                "num=" + num +
                '}';
    }
}

13.3 Map

Map 和 Collection 没有关系,是独立于 Collection 的另外一个体系

Set、List、Collection 只能操作单个元素,但是 Map 操作的是一组元素

Map 中存储的是键值对形式的数据,key-value 的映射关系。

Map 中常用的方法

方法描述
int size()获取集合长度
boolean isEmpty()判断集合是否为空
boolean containsKey(Object key)判断集合中是否存在某个key值
boolean containsValue(Object value)判断集合中是否存在某个value值
V get(Object key)通过key取value
V put(K key,V value)存入一组数据
V remove(Object key)通过可以删除value
void clear()清空集合
Set keySet()取出集合中的所有key,返回一个Set
Collection values()取出集中的所有value,返回一个Collection
Set enrtySet()将Map集合转换为Set集合
int hashCode()获取集合的哈希值
boolean equals()判断两个集合是否相等

13.3.1 Map 的实现类

HashMap:存储一组无序,key不可以重复,value可以重复的数据

Hashtable:存储一组无序,key不可以重复,value可以重复的数据

TreeMap:存储一组有效,key不可以重复,value可以重复的数据,按照key进行排序

public class HashMapTest {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        hashMap.put("h", "Hello");
        hashMap.put("w", "World");
        hashMap.put("j", "Java");
        hashMap.put("s", "JavaSE");
        hashMap.put("m", "JavaME");
        hashMap.put("e", "JavaEE");
        hashMap.put("j", "Jack");
        System.out.println(hashMap);
        System.out.println("***********************************");
        Set set = hashMap.entrySet();
        Iterator<Map.Entry> iterator = set.iterator();
        while (iterator.hasNext()) {
            Map.Entry next = iterator.next();
            System.out.println(next.getKey());
            System.out.println(next.getValue());
        }
    }
}

13.3.2 HashMap

HashMap key 是不能重复的,value 可以重复

所以使用了一个可以防重复的set集合,然后放一个重复的collection集合

底层结构 key-value 进行存储,key-value 存入到 Set 中,再将 Set 装载到 HashMap

public class HashMapTest {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        hashMap.put("h", "Hello");
        hashMap.put("w", "World");
        hashMap.put("j", "Java");
        System.out.println(hashMap);
        System.out.println("***********************************");
        //map有3种遍历方式
        //1、获取Set(包含kv)
        Set set = hashMap.entrySet();
        Iterator<Map.Entry> iterator = set.iterator();
        while (iterator.hasNext()) {
            Map.Entry next = iterator.next();
            System.out.println(next.getKey()+"------------"+next.getValue());
        }
        System.out.println("****************************************");
        //2、获取key所在的set集合
        Set keySet = hashMap.keySet();
        Iterator iterator1 = keySet.iterator();
        while (iterator1.hasNext()) {
            System.out.println(iterator1.next());
        }
        System.out.println("****************************************");
        //3、获取value所在的Collection集合
        Collection values = hashMap.values();
        Iterator iterator2 = values.iterator();
        while (iterator2.hasNext()) {
            System.out.println(iterator2.next());
        }
    }
}

13.3.3 Hashtable

用法和 HashMap 一致,区别在于 Hashtable 是较早推出的一个类,是线程安全的,效率低。可以来给他上锁。

HashMap 是线程不安全的,效率高。

13.3.4TreeMap

TreeMap 中存储有序的元素,存入的元素会自动进行排序,按照 key 值进行升序排列。

在java里面单引号是字符,双引号是字符串。

字符串了,类型都是可以进行排序。

接口就是用来完成功能的。

public class TreeMapTest {
    public static void main(String[] args) {
        TreeMap treeMap = new TreeMap();
        treeMap.put(3, "Java");
        treeMap.put(5, "JavaME");
        treeMap.put(1,"Hello");
        treeMap.put(6, "JavaEE");
        treeMap.put(2, "World");
        treeMap.put(4, "JavaSE");
        Set keySet = treeMap.keySet();
        Iterator iterator = keySet.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println(next + "---" + treeMap.get(next));
        }
    }
}

public class TreeMapTest {
    public static void main(String[] args) {
        TreeMap treeMap = new TreeMap();
        treeMap.put(new C(1), "Java");
        treeMap.put(new C(2), "JavaME");
        treeMap.put(new C(3),"Hello");
        treeMap.put(new C(4), "JavaEE");
        treeMap.put(new C(5), "World");
        treeMap.put(new C(6), "Big");
        treeMap.put(new C(7), "JavaSE");
        Set keySet = treeMap.keySet();
        Iterator iterator = keySet.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println(next + "---" + treeMap.get(next));
        }
    }
}

class C implements Comparable{
    private int num;

    public C(int num) {
        this.num = num;
    }

    /**
     * A.compareTo(B)
     * 1 A>B
     * 0 A=B
     * -1 A<B
     * @param o
     * @return
     */
    @Override
    public int compareTo(Object o) {
        C c = (C) o;
        if(this.num > c.num) return -1;
        if(this.num == c.num) return 0;
        if(this.num < c.num) return 1;
        return 0;
    }

    @Override
    public String toString() {
        return "C{" +
                "num=" + num +
                '}';
    }
}

13.3.5 HashMap 底层结构

HashMap 底层其实是一个数组,称为哈希桶,JDK 1.7 和 JDK 1.8 底层结构是不同的。

JDK 1.7:数组+链表

JDK 1.8:数组+链表+红黑树

13.3.6 什么是红黑树?

红黑树是一个平衡二叉树,保证了一个查询效率

二叉树 : 数字一个一个往里面填,左小右大。提高我们的效率。

https:// s.usfca.edu/~galles/visualization/Algorithms.html

​ 红黑树

1、结点是红色或者黑色

2、根节点是黑色的

3、每个叶子节点都是空节点

4、每个红色节点的两个子节点都是黑色的

5、从任一节点到它的每个叶子节点的所有路径都包含相同数目的黑色节点

哈希冲突:key 所映射的 hash 一样

HashMap 源码

从构造器入手来看

并没有创建数组,而是给加载因子赋值 = 0.75,加载因子是用来完成数组扩容的。

只有在添加数据的时候才会创建数组,HashMap 是懒加载的形式。

DEFAULT_INITIAL_CAPACITY 就是数组的初始化长度,16,哈希桶默认的长度为 16

数组长度为 16,存入多少个元素的时候,需要进行数组扩容呢?

16 * 0.75 = 12,超过12的时候,每次扩容是给数组长度乘以 2,16 -》32

1<<4 1x2的4次方

13.3.7 HasMap 底层实现

JDK 1.8

HashMap 底层设计涉及到三种不同的数据结构,分别是数组、链表、红黑树。

1、基本的存储是数组,根据 key 值求出一个数组下标,将元素(key-value)存入到下标对应的位置。

尽量的扩容数组。就是超过四分之三的时候。

2、存入的数据过多的话,key 所求出下标就有可能重复,叫做哈希冲突,怎么解决?引入链表,在数组的具体位置发展出一条链表,来存储多个下标一致的元素。

3、如果链表很长的话,会影响到元素的查询速度,为了提高查询速度,引入了红黑树(平衡二叉树),当链表长度大于 8 的时候,自动转换成红黑树。

13.3.8 HashMap 加载顺序

创建 HashMap 并不会创建数组,而是定义加载因子(数组扩容使用)

当给 HashMap 添加元素的时候,再来创建数组,懒加载机制,数组长度为 16。

(h = key.hashCode()) ^ (h >>> 16)

如果 key 不为 null,则返回 key 的 hashCode 和自己的高 16 位进行异或(相同为0,不同为1)运算得出的结果。

h >>> 16,无符号右移,取出 h 的高 16 位
0000 0100 1011 0011 1101 1111 1110 0001

>>> 16

0000 0000 0000 0000 0000 0100 1011 0011
高16位的意思就是将高位的16位移到了低16位;。

问题:既然已经拿到了 key 的 hashCode,为什么不直接返回?而要进行复杂的运算呢?

int 的取值范围是 -21 亿- 21 亿,40 亿种可能,而数组的默认长度为 16,远远小于 hash 可取值的范围,所以很有可能多次求出的下标根本不能用。

所以需要 hashCode 与自己的高 16 位进行异或运算,目的是混合原始 hash 码的高位和低位,以此来加大低位的随机性,尽量让值变小。

尽管如此,取到的值仍然很可能超过数组下标,所以为了保证绝对的安全性,拿到 hash 值之后,再与数组的最大索引(15)进行按位与运算(都为1返回1,否则返回0)得到索引。

就是为了让数字防止过大。

最后

hashCode:1111 1111 1111 1111 1111 0000 1110 1010

h:			1111 1111 1111 1111 1111 0000 1110 1010
h >>> 16:	0000 0000 0000 0000 1111 1111 1111 1111

hash:h^h>>>16:   1111 1111 1111 1111 0000 1111 0001 0101

15 & hash:		  0000 0000 0000 0000 0000 0000 0000 1111
				  1111 1111 1111 1111 0000 1111 0001 0101
				  
				  0000 0000 0000 0000 0000 0000 0000 0101 = 5

HashMap 的存值过程

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    //创建一个 Node 数组 tab,就是哈希桶
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //判断数组是否为空,如果为空就创建数组
    //HashMap的数组是延迟创建,这也是一种优化机制
    if ((tab = table) == null || (n = tab.length) == 0)
        //resize是创建数组和扩容数组的方法,这里是创建了长度为16的数组,用n记录它的长度,n=16
        n = (tab = resize()).length;
    //通过索引取出数组元素,判断是否为null
    if ((p = tab[i = (n - 1) & hash]) == null)
        //如果为null,直接创建一个 Node 存入
        tab[i] = newNode(hash, key, value, null);
    else {
        //如果不为null,判断 key 是否相同
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            //如果相同,直接覆盖原来的值
            e = p;
        //如果不相同,需要在原 Node 的基础上那个添加新的 Node
        //首先判断该位置是链表还是红黑树
        else if (p instanceof TreeNode)
            //如果是红黑树,将 Node 存入红黑树
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            //如果是链表,就遍历链表
            for (int binCount = 0; ; ++binCount) {
                //找到最后一位
                if ((e = p.next) == null) {
                    //将 Node 添加到链表的最后一位
                    p.next = newNode(hash, key, value, null);
                    //如果长度超过 8,需要将链表转成红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    //进入 treeifyBin 方法
                    <!-- start -->
                        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
                    <!-- end -->
                    //当数组的长度小于 64 的时候,并不会转换成红黑树,而是对数组进行扩容,能用数组就尽量用数组,因为数组查询效率最高
                    break;
                }
                //如果链表中存值重复的 key 值,直接把当前元素赋给 p 进行替换
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            //返回被覆盖的 value
            return oldValue;
        }
    }
    ++modCount;
    //Node 个数++ 
    //threshold=capacity* loadFactor=16*0.75=12
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    //如果 key 不重复,则返回 null
    return null;
}

什么是 Node?

Node 是 Map.Entry 的实现类,就将 key 和 value 封装成一个对象。

1、根据 key 计算 hash 值。
2、在 put 的时候判断数组是否存在,如果不存在用 resize 方法创建长度为 16 的数组。
3、确定要存入的 node 在数组中的位置,根据 hash 与数组最大索引进行按位与运算得到下标。
4、判断该位置是否有元素,如果没有直接创建一个 Node 存入。
5、如果有元素,判断 key 是否相同,如果相同,则覆盖并返回。
6、如果不同,需要在原 Node 基础上添加新的 Node,首先判断该位置是链表还是红黑树。
7、如果是红黑树,将 Node 存入红黑树。
8、如果是链表,遍历链表,找到最后一位将 Node 存入。
9、将 Node 存入之后,需要判断此时链表的长度是否超过 8,如果超过 8 需要将链表转换为红黑树,这里还有一个条件,如果数组的长度小于 64,会再进行一次数组扩容,当数组长度大于 64 的时候才会进行转换。
10、添加完成之后,再判断数组是否需要扩容。

13.4 泛型

Generics 是指在定义类的时候不指定类中某个信息(属性/方法返回值)的具体数据类型,而是用一个标识符来替代,当外部实例化对象的时候再来指定具体的数据类型。

集合和数组相比较,优势在于长度可以随时改变,集合中存储的数据类型是灵活的,不固定。

13.4.1为什么要使用泛型?

使用泛型可以确保集合中数据的统一性,同时它又兼具很好的灵活性

public class Test2 {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList();
        list.add(1);
        list.add(2);
        for (int i = 0; i <list.size() ; i++) {
            int num = list.get(i);
            System.out.println(num);
        }

    }
}

13.4.2 泛型的应用

在自定义类中添加泛型,基本语法

public class 类名<泛型标识1,泛型标识2...>{
	private 泛型标识1 id;
	
	public 泛型标识2 test(){
		return (泛型标识2new object();
	}
}

自定义一个表示时间的类 Time

public class Time<T> {
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}
public class Test4 {
    public static void main(String[] args) {
        Time<Integer> time1 = new Time<>();
        time1.setValue(10);
        System.out.println("现在的时间是" + time1.getValue());
        Time<String> time2 = new Time<>();
        time2.setValue("十点");
        System.out.println("现在的时间是" + time2.getValue());
    }
}
public class Test5 {
    public static void main(String[] args) {
        Time2<String,Integer,Float> time = new Time2<>();
        time.setHour("十点");
        time.setMinute(10);
        time.setSeconde(10.0f);
        System.out.println("现在的时间是" + time.getHour() + ":" + time.getMinute() + ":" + time.getSeconde());
    }
}
public class Time2<H,M,S> {

    private H hour;
    private M minute;
    private S seconde;

    public H getHour() {
        return hour;
    }

    public void setHour(H hour) {
        this.hour = hour;
    }

    public M getMinute() {
        return minute;
    }

    public void setMinute(M minute) {
        this.minute = minute;
    }

    public S getSeconde() {
        return seconde;
    }

    public void setSeconde(S seconde) {
        this.seconde = seconde;
    }
}

静态成员不能用泛型来修饰,非静态成员变量可以用泛型。

静态成员变量在类加载的时候就存入到内存中了,没有类型,所以无法存入,就不能定义为静态的。

静态方法可以使用泛型

private static Object hour;
public static<H> H getHour() {
    return (H)hour;
}

13.4.3 泛型通配符

String 和 Integer 在泛型引用中不能转换为 Object,多态在泛型引用中不生效

public class Test {

    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<>();
        test(list1);
        ArrayList<Integer> list2 = new ArrayList<>();
        test(list2);
    }

    /**
     * 既可以传入String类型的集合
     * 也可以传入Integer类型的集合
     */
    public static void test(ArrayList<?> list){
        
    }

}

13.4.4 泛型上限和下限

使用泛型的时候可以通过泛型上限和下限对数据类型的范围进行扩充

泛型上限

表示实例化时的具体数据类型,可以是上限类型的子类或者上限类型本身,用 extends 关键字来修饰。

泛型下限

表示实例化时的具体数据类型,可以是下限类型的父类或者下限类型本身,用 super 关键字来修饰。

基本语法:

泛型上限:类名<泛型标识符 extends 上限类名>
泛型下限:类名<泛型标识符 super 下限类名>
public class Time<T> {
    public static void main(String[] args) {
        test(new Time<Number>());
        test(new Time<Byte>());
        test(new Time<Integer>());
        test2(new Time<String>());
        test2(new Time<Object>());
    }

    /**
     * 参数的泛型只能是Number或者它的子类,Number、Byte、short、Integer...
     */
    public static void test(Time<? extends Number> time){

    }

    /**
     * 参数的泛型只能是String或者它的父类,String和Object
     */
    public static void test2(Time<? super String> time){

    }
}

13.4.5 泛型接口

我们在定义类的时候可以使用泛型,定义接口的时候同样可以使用泛型。

public interface MyInterface<T>{    
    public T getValue();
}

实现泛型接口有两种方式,一种是实现类在定义时继续使用泛型标识,另一种时实现类在定义时直接给出具体的数据类型。

1、实现类继续使用泛型

public class MyImplement<T> implements MyInterface<T> {

    private T obj;

    public T getObj() {
        return obj;
    }

    public void setObj(T obj) {
        this.obj = obj;
    }

    @Override
    public T getValue() {
        return null;
    }
}

2、实现类给出具体的数据类型

public class MyImplement2 implements MyInterface<String> {
    
    private String obj;

    public String getObj() {
        return obj;
    }

    public void setObj(String obj) {
        this.obj = obj;
    }

    @Override
    public String getValue() {
        return null;
    }
}

实现类继续使用泛型,在具体实例化的时候给出具体的数据类型

实现类给出具体的数据类型,在具体实例化的时候不需要给出具体的数据类型。

public class Test {
    public static void main(String[] args) {
        MyImplement<String> myInterface = new MyImplement<>();
        MyImplement2 myInterface1 = new MyImplement2();
    }

14 java实用类

14.1 枚举 Enum

Enum 是一种有确定取值区间的数据类型,本质上就是一个类,简洁、安全、方便。

枚举的值被约束到一个特定的范围,只能从这个范围内取值。

public enum WeekEnum {
    MONDAY,TUESDAY,WEDENSDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY;
}
package com.southwind.test3;
public enum  ResponseEnum {
    SUCCESS(0,"成功"),
    ERROR(-1,"失败");

    private Integer code;
    private String msg;

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    ResponseEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}
public class Test2 {
    public static void main(String[] args) {
        System.out.println(ResponseEnum.SUCCESS.getCode());
        System.out.println(ResponseEnum.ERROR.getMsg());
    }
}

14.2 数学 Math

提供一系列数学方法,同时包含两个静态常量 E(自然对数的底数)PI(圆周率)

public class Test {
    public static void main(String[] args) {
        //自然对数的底数
        System.out.println(Math.E);
        //圆周率
        System.out.println(Math.PI);
        //9开平方
        System.out.println(Math.sqrt(9));
        //8开立方
        System.out.println(Math.cbrt(8));
        //2的3次方
        System.out.println(Math.pow(2, 3));
        //比较大小
        System.out.println(Math.max(6, 3));
        System.out.println(Math.min(6, 3));
        //求绝对值
        System.out.println(Math.abs(-10));
        //向下取整
        System.out.println(Math.floor(10.999));
        //向上取整
        System.out.println(Math.ceil(10.001));
        //四舍五入
        System.out.println(Math.round(5.6f));
        //取随机数
        System.out.println(Math.random());
    }
}

14.3 String

String 的实例化

有两种方式,第一种是直接赋值,第二种是通过构造器创建。

String str1 = "Hello";
String str2 = new String("Hello");

str1 存储在字符串常量池中,str2 存储在堆内存中。

判断两个字符串对象是否相等,通过 equals 方法进行判断,== 比较的是内存地址,所以并不能非常准确的描述两个字符串对象的相等关系。

String 类对 equals 方法进行了重写

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String aString = (String)anObject;
        if (!COMPACT_STRINGS || this.coder == aString.coder) {
            return StringLatin1.equals(value, aString.value);
        }
    }
    return false;
}
public static boolean equals(byte[] value, byte[] other) {
    if (value.length == other.length) {
        for (int i = 0; i < value.length; i++) {
            if (value[i] != other[i]) {
                return false;
            }
        }
        return true;
    }
    return false;
}

String 常用方法

package com.sputhwind.test;

public class Test3 {
    public static void main(String[] args) {
        char[] array = {'J','a','v','a',',','H','e','l','l','o',',','W','o','r','l','d'};
        String str = new String(array);
        String str1 = "Java,Hello,World";
        //intern方法是将字符串对象复制到字符串常量池中,并返回常量池的引用
        String intern = str.intern();
        System.out.println(intern == str1);
        System.out.println(intern);
        System.out.println(str);
        //获取字符串的长度
        System.out.println(str.length());
        //判断字符串是否为空
        System.out.println(str.isEmpty());
        //下标为2的字符
        System.out.println(str.charAt(2));
        //H的下标
        System.out.println(str.indexOf("H"));
        String str2 = "Hello";
        String str3 = "HELLO";
        //忽略大小写判断字符串是否相等
        System.out.println(str2.equalsIgnoreCase(str3));
        //是否以Java开头
        System.out.println(str.startsWith("Java"));
        //是否以Java结尾
        System.out.println(str.endsWith("Java"));
        //从2开始截取到结尾
        System.out.println(str.substring(2));
        //从2开始截取到6,左开右闭
        System.out.println(str.substring(2, 6));
        //World替换成Java
        System.out.println(str.replaceAll("World", "Java"));
        //用逗号分割字符串
        String[] split = str.split(",");
        System.out.println(split[0]);
        System.out.println(split[1]);
        System.out.println(split[2]);
        //转为字符数组
        char[] chars = str.toCharArray();
        System.out.println(chars[3]);
        //转大写
        System.out.println(str2.toUpperCase());
        //转小写
        System.out.println(str3.toLowerCase());
    }
}

14.3 StringBuffer

String 对象一旦创建,值不能修改。

如果要修改,会重新开辟内存空间来存储修改之后的对象,即修改了 String 的引用。

因为 String 的底层是用数组来存储的,数组的长度不能改变。

如果频繁修改字符串的值,使用 String 就不合适了,因为会造成内存空间的极大浪费。

如何解决?使用 StringBuffer 来解决。

StringBuffer 和 String 类似,底层也是用一个数组来存储字符串的值,并且数组的默认长度为 16,即一个空的 StringBuffer 对象,长度为 16。

使用无参构造创建一个 StringBuffer 对象,数组长度为 16,如果使用有参构造创建一个 StringBuffer 对象,数组长度为参数的长度 + 16。

所以一个 StringBuffer 创建完成之后,有 16 个字节的空间可以修。

如果修改的值长度超过了 16 个字节,需要对 StringBuffer 数组进行扩容,调用 ensureCapacityInterval 方法进行扩容,保持引用不变。

StringBuffer 一上来就预留了 16 个长度的空间允许修改,如果不够,对数组进行扩容。

package com.sputhwind.test;

public class Test5 {
    public static void main(String[] args) {
        StringBuffer stringBuffer = new StringBuffer();
        System.out.println(stringBuffer);
        System.out.println(stringBuffer.length());
        stringBuffer = new StringBuffer("Hello World");
        System.out.println(stringBuffer);
        //下标为2的字符
        System.out.println(stringBuffer.charAt(2));
        //追加内容
        stringBuffer.append("Java");
        System.out.println(stringBuffer);
        //删除指定区间的内容
        stringBuffer.delete(3, 6);
        System.out.println(stringBuffer);
        //删除指定下标的字符
        stringBuffer.deleteCharAt(3);
        System.out.println(stringBuffer);
        //替换指定区间的内容
        stringBuffer.replace(2, 3, "StringBuffer");
        System.out.println(stringBuffer);
        //截取
        String str = stringBuffer.substring(3);
        System.out.println(str);
        //截取区间
        String substring = stringBuffer.substring(3, 6);
        System.out.println(substring);
        //在指定下标插入字符
        stringBuffer.insert(6, "six");
        System.out.println(stringBuffer);
        //获取某个字符的下标
        int r = stringBuffer.indexOf("r", 6);
        System.out.println(r);
        //字符反转
        stringBuffer.reverse();
        System.out.println(stringBuffer);
    }
}

14.4 日期类

Date 类

package com.sputhwind.test;

import java.text.SimpleDateFormat;
import java.util.Date;

public class Test6 {
    public static void main(String[] args) {
        Date date = new Date();
        System.out.println(date);
        //格式化
        //2021-01-01 20:00:00
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format = simpleDateFormat.format(date);
        System.out.println(format);
    }
}

15 IO流

input output

输入:将文件以数据流的形式读取到Java程序中,上传

输出:通过Java程序

15.1 File 类

Java 程序是如何操作文件的?

java.io.File 类专门用来创建文件对象的,表示某个文件。

方法描述
public File(String path)根据路径创建文件对象
public String getName()获取文件名
public String getParent()获取文件所在的目录
public File getParentFile()获取文件所在的目录对应的 File 对象
public String getPath()获取文件路径
public boolean exists()判断文件对象是否存在
public boolean isDirectory()判断对象是否为目录
public boolean isFile()判断对象是否为文件
public long length()获取文件的大小(byte)
pubilc boolean createNewFile()根据当前对象创建文件
public boolean delete()删除文件
public boolean mkdir()根据当前对象创建目录
public boolean renameTo(File file)为已存在的对象重命名
package com.southwind.test;

import java.io.File;

public class Test {
    public static void main(String[] args) throws Exception {
        File file = new File("D:\\java\\test.txt");
        if (file.exists()) {
            System.out.println(file + "存在");
            System.out.println("文件名:"+file.getName());
            System.out.println("文件长度:" + file.length());
            System.out.println("文件路径:" + file.getPath());
            System.out.println("文件的parent:" + file.getParent());
            System.out.println("文件的parentFile" + file.getParentFile());
            File parentFile = file.getParentFile();
            if(parentFile.isDirectory()){
                System.out.println("parentFile是一个文件夹");
            }
        }
        System.out.println("*********************************************");
        File file1 = new File("D:\\java\\test2.txt");
        File file2 = new File("D:\\java\\test3.txt");
        System.out.println(file1.renameTo(file2));
        System.out.println(file2.delete());
    }
}

15.2 字节流

Java 中的流有很多种分类

按照方向,可以分为输入流和输出流。

按照单位,可以分为字节流和字符流。

按照功能,可以分为节点流和处理流。

字节流又可以按照方向分为输入字节流(InputStream)和输出字节流(OutputStream)

15.2.1 InputStream

方法描述
int read()以字节为单位读取数据
int read(byte b[])将数据存入byte数组,返回数据长度
int read(byte b[],int off,int len)将数据存入byte数组的指定区间
byte[] readAllBytes()将数据转换成一个byte数组
int available()当前数据流中未读取的数据个数
void close()关闭数据流
package com.southwind.test;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class Test2 {
    public static void main(String[] args) throws Exception {
        //获取文件对象
        File file = new File("D:\\java\\test.txt");
        //获取输入流
        InputStream inputStream = new FileInputStream(file);
        for (int i = 0; i < file.length(); i++) {
            int read = inputStream.read();
            System.out.println(read);
        }
        System.out.println("***************************");
        inputStream = new FileInputStream(file);
//        System.out.println(inputStream.read());
//        System.out.println(inputStream.read());
//        System.out.println(inputStream.read());
        int temp = 0;
        while ((temp = inputStream.read()) != -1){
            System.out.println("当前未读取的数据个数是:" + inputStream.available());
            System.out.println(temp);
        }
    }
}
package com.southwind.test;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Arrays;

public class Test2 {
    public static void main(String[] args) throws Exception {
        //获取文件对象
        File file = new File("D:\\java\\test.txt");
        //获取输入流
        InputStream inputStream = new FileInputStream(file);
        byte[] bytes = new byte[1024];
        System.out.println(inputStream.read(bytes,10,5));
        System.out.println(Arrays.toString(bytes));
    }
}
package com.southwind.test;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Arrays;

public class Test2 {
    public static void main(String[] args) throws Exception {
        //获取文件对象
        File file = new File("D:\\java\\test.txt");
        //获取输入流
        InputStream inputStream = new FileInputStream(file);
        byte[] bytes = inputStream.readAllBytes();
        System.out.println(Arrays.toString(bytes));
    }
}

15.2.2 OutputStream

方法描述
void write(int b)以字节为单位写数据
void write(byte b[])将byte数组中的数据写出
void write(byte b[],int off,in len)将byte数组中指定区间的数据写出
void flush()强制将缓冲区的数据同步到输出流中
void close()关闭数据流
package com.southwind.test;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;

public class Test3 {
    public static void main(String[] args) throws Exception {
        //创建文件对象
        File file = new File("D:\\java\\copy.txt");
        if(file.exists()){
            //创建输出流
            OutputStream outputStream = new FileOutputStream(file);
            //开始写数据
            byte[] bytes = {12,16,33,72,90,100,108,109};
            outputStream.write(bytes,3,3);
            //关闭数据流
            outputStream.close();
        }else{
            System.out.println("文件不存在");
        }
    }
}

文件复制

package com.southwind.test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class Test4 {
    public static void main(String[] args) throws Exception {
        //文件复制
        //java/test.txt 复制到 java2/test.txt
        //InputStream
        InputStream inputStream = new FileInputStream("D:\\java\\test.txt");
        //OutputStream
        OutputStream outputStream = new FileOutputStream("D:\\java2\\test.txt");
        int temp = 0;
        //读数据
        while ((temp = inputStream.read())!=-1){
            //写数据
            outputStream.write(temp);
        }
        outputStream.close();
        inputStream.close();
    }
}

15.3 字符流

字符流是有别于字节流的另一种数据流,两者的区别在于每次处理的数据单位不同,一个是以字节为单位进行处理,另一个是以字符为单位进行处理,相当于用两根不同粗细的管子抽水。

跟字节流类似,字符流也可以按照方向分为输入字符流(Reader)和输出字符流(Writer)。

Reader 和 Writer 都是抽象类,使用的时候需要使用它们的非抽象子类。

15.3.1 Reader

常用方法

方法描述
int read()以字符为单位读取数据
int read(char chars[])将数据读入 char 数组,并返回数据长度
int read(char chars[],int off,int len)将数据读入 char 数组的指定区间,并返回数据长度
void close()关闭数据流
transferTo(Writer out)将数据直接读入字符输出流
package com.southwind.test;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class Test {
    public static void main(String[] args) {
        Reader reader = null;
        try {
            //获取Reader
            reader = new FileReader("D:\\java\\test.txt");
            //开始读取数据
            int temp = 0;
            while ((temp = reader.read())!=-1){
                System.out.println(temp);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e){
            e.printStackTrace();
        } finally {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

英文字母、数字、标点符号,一个字符就是一个字节,使用字节流和字符流其实没有区别。

在 UTF-8 编码的情况下,一个汉字等于三个字节

package com.southwind.test;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class Test {
    public static void main(String[] args) {
        Reader reader = null;
        try {
            //获取Reader
            reader = new FileReader("D:\\java\\test.txt");
            //开始读取数据
            char[] chars = new char[1024];
            int read = reader.read(chars,2,7);
            System.out.println(read);
            for (char aChar : chars) {
                System.out.println(aChar);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e){
            e.printStackTrace();
        } finally {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

15.3.2 Writer

方法描述
void write(int c)以字符为单位写数据
void write(char chars[])将 char 数组中的数据写出
void write(char chars[],int off,int len)将 char 数组中指定区间的数据写出
void write(String str)将 String 数据写出
void write(String str,int off,int len)将 String 指定区间的数据写出
void flush()强制将缓冲区的数据同步到输出流中
void close()关闭数据流
package com.southwind.test;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class Test3 {
    public static void main(String[] args) {
        Writer writer = null;
        try {
            writer = new FileWriter("D:\\java2\\test.txt");
            writer.write(230);
            writer.write(123456);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
package com.southwind.test;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class Test3 {
    public static void main(String[] args) {
        Writer writer = null;
        try {
            writer = new FileWriter("D:\\java2\\test.txt");
            char[] chars = {'好','好','学','习'};
            writer.write(chars,2,2);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
package com.southwind.test;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class Test3 {
    public static void main(String[] args) {
        Writer writer = null;
        try {
            writer = new FileWriter("D:\\java2\\test.txt");
            String str = "好好学习";
            writer.write(str,2,2);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

15.4 缓冲流

缓冲流属于处理流,不能直接访问文件。

如何区分节点流和处理流?

能直接访问文件的就是节点流(字节流、字符流)

不能直接访问文件的就是处理流(缓冲流),必须基于节点流,构造处理流的时候必须将节点流作为参数传入到处理流中

15.4.1 字节缓冲流

​ 字节输入缓冲流 字节输出缓冲流

15.4.2 字符缓冲流

​ 字符输入缓冲流 字符输出缓冲流

字节输入缓冲流 BufferedInputStream

package com.southwind.test;

import java.io.*;

public class Test5 {
    public static void main(String[] args) {
        BufferedInputStream bufferedInputStream = null;
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream("D:\\java\\test.txt");
            bufferedInputStream = new BufferedInputStream(inputStream);
            int temp = 0;
            while ((temp = bufferedInputStream.read())!=-1){
                System.out.println(temp);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
                bufferedInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

字符输入缓冲流 BufferedReader

package com.southwind.test;

import java.io.*;

public class Test6 {
    public static void main(String[] args) {
        BufferedReader bufferedReader = null;
        Reader reader = null;
        try {
            reader = new FileReader("D:\\java\\test.txt");
            bufferedReader = new BufferedReader(reader);
            String str = "";
            while ((str = bufferedReader.readLine())!=null){
                System.out.println(str);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                reader.close();
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

字节输出缓冲流 BufferedOutputStream

package com.southwind.test;

import java.io.*;

public class Test7 {
    public static void main(String[] args) {
        OutputStream outputStream = null;
        BufferedOutputStream bufferedOutputStream = null;
        try {
            outputStream = new FileOutputStream("D:\\java2\\test.txt");
            bufferedOutputStream = new BufferedOutputStream(outputStream);
            String str = "由于C++所具有的优势,该项目组的研究人员首先考虑采用C++来编写程序。但对于硬件资源极其匮乏的单片式系统来说,C++程序过于复杂和庞大。";
            bufferedOutputStream.write(str.getBytes());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bufferedOutputStream.flush();
                bufferedOutputStream.close();
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

字符输出缓冲流 BufferedWriter

package com.southwind.test;import java.io.BufferedWriter;import java.io.FileWriter;import java.io.IOException;import java.io.Writer;public class Test8 {    public static void main(String[] args) {        Writer writer = null;        BufferedWriter bufferedWriter = null;        try {            writer = new FileWriter("D:\\java2\\test.txt");            bufferedWriter = new BufferedWriter(writer);            String str = "由于C++所具有的优势,该项目组的研究人员首先考虑采用C++来编写程序。但对于硬件资源极其匮乏的单片式系统来说,C++程序过于复杂和庞大。";            bufferedWriter.write(str);        } catch (IOException e) {            e.printStackTrace();        } finally {            try {                bufferedWriter.flush();                bufferedWriter.close();                writer.close();            } catch (IOException e) {                e.printStackTrace();            }        }    }}

15.5 文件复制

IO 流实现,文本类型可以使用字符流,除了文本类型之外的其他类型,不能使用字符流,只能使用字节流去处理,因为使用字符流会破坏其内部构成,导致文件无法使用。

package com.southwind.test;

import java.io.*;

public class Test10 {
    public static void main(String[] args) {
        InputStream inputStream = null;
        BufferedInputStream bufferedInputStream = null;
        OutputStream outputStream = null;
        BufferedOutputStream bufferedOutputStream = null;
        try {
            inputStream = new FileInputStream("D:\\java\\123.png");
            bufferedInputStream = new BufferedInputStream(inputStream);
            outputStream = new FileOutputStream("D:\\java2\\123.png");
            bufferedOutputStream = new BufferedOutputStream(outputStream);
            int temp = 0;
            while ((temp = bufferedInputStream.read())!=-1){
                bufferedOutputStream.write(temp);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bufferedOutputStream.flush();
                bufferedOutputStream.close();
                outputStream.close();
                bufferedInputStream.close();
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

16 反射

反射是 Java 成为动态语言的关键,非常重要。

invoke(调用)就是调用Method类代表的方法。可以实现动态调用,例如可以动态的传人参数,可以把方法参数化。

16.1 什么是反射?

反转,具体事物的特征。

程序中的反射是通过对象映射到类,在程序运行期间就可以获取到类的信息(结构)。

类-》对象

对象-》类

在编写程序的时候,不知道要创建的对象是谁,直到程序运行的时候,才知道你要创建哪个对象。

Class 类是专门用来描述其他类的类,Class 的对象就是另外一个类结构的抽象对象。

bean就是一个对象

bean=com.southwind.entity.Fish
package com.southwind.test;

import com.southwind.entity.Dog;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Properties;

public class Test {

    private static Properties properties;

    static {
        try {
            properties = new Properties();
            properties.load(new FileInputStream(System.getProperty("user.dir")+"/src/bean.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        //获取目标类名
        String bean = properties.getProperty("bean");
        //根据目标类名获取目标类所对应的Class对象(描述结构)
        Class clazz = Class.forName(bean);
        //获取构造器
        Constructor constructor = clazz.getConstructor(null);
        //通过构造器创建对象
        System.out.println(constructor.newInstance(null));
    }
}

16.2 如何使用反射

反射的源头是 Class 类,java.lang.Class

Class 类是专门用来描述其他类的类,Class 的每一个实例化对象都是对目标类的具体描述。

动态的创建某个对象。

Class 有 3 种获取方式

//1、通过类名获取
Class clazz1 = Class.forName("com.southwind.entity.Fish");
//2、通过类字面量获取
Class clazz2 = Fish.class;
//3、通过对象获取
Fish fish = new Fish();
Class clazz3 = fish.getClass();

每个类的 Class 对象只有一个,因为每个类在内存中只加载一次,加载到内存中类叫做运行时类,因为只加载一次,所以每个类的运行时类只有一个。

Class 就是描述运行时类的,每个 Class 对象就是目标类的运行时类。

获取到了反射源头之后,就可以进行操作,获取类的各种信息

类的信息包括属性、方法、构造器、父类、实现的接口、所在的包、访问权限

16.2.1 构造器

无参构造

Constructor 是专门用来描述构造器的类,Constructor 的对象就是目标类的构造器。

构造器就是用来创造对象的。

Fish fish = new Fish();
Class clazz3 = fish.getClass();
//获取无参构造
Constructor constructor = clazz3.getConstructor(String.class,Integer.class);
System.out.println(constructor);
Constructor[] constructors = clazz3.getConstructors();
for (Constructor constructor1 : constructors) {
    System.out.println(constructor1);
}
Constructor<Fish> constructor = clazz3.getConstructor(null);
Fish fish1 = constructor.newInstance();
System.out.println(fish1);

有参构造

Constructor<Fish> constructor = clazz3.getConstructor(String.class);
Fish fish1 = constructor.newInstance("小鱼");
System.out.println(fish1);

16.2.2 属性

package com.southwind.test;

import com.southwind.entity.Cat;

import java.lang.reflect.Field;

public class FieldTest {
    public static void main(String[] args) throws NoSuchFieldException {
        Class clazz = Cat.class;
        //获取公有属性数组,无法获取私有属性
        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
        System.out.println("***********************************");
        //获取属性数组(对访问权限修饰符无要求),就是没有要求,也可以认为是私有属性
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }
        System.out.println("***********************************");
        //根据属性名获取公有属性
        Field field = clazz.getField("id");
        System.out.println(field);
        //根据属性名获取属性
        Field id = clazz.getDeclaredField("id");
        System.out.println(id);
    }
}

获取属性的目的就是给对象赋值

首先通过构造器创建对象,接下来通过属性给对象赋值。

暴力反射。即使是私有也能赋值。

16.2.3 方法

package com.southwind.test;

import com.southwind.entity.Cat;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MethodTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz = Cat.class;
        //获取公有方法(自身以及从父类继承的)
//        Method[] methods = clazz.getMethods();
//        for (Method method : methods) {
//            System.out.println(method);
//        }
//        System.out.println("******************************");
//        Method setId = clazz.getMethod("setId", Integer.class);
//        System.out.println(setId);
        Object o = clazz.getConstructor(null).newInstance(null);
        Method setId = clazz.getMethod("setId", Integer.class);
        Method setName = clazz.getMethod("setName", String.class);
        setId.invoke(o, 300);
        setName.invoke(o, "喵喵");
        System.out.println(o);
    }
}

就是我们可以把父类的一些东西都可以拿出来,父类里面有公有属性。

16.3 反射的应用

1、在程序运行过程中动态创建对象。

第一步,在配置文件中定义类的信息。

bean=com.southwind.entity.Fish

第二步,动态读取配置文件创建对象。

package com.southwind.test;

import com.southwind.entity.Dog;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Properties;

public class Test {

    private static Properties properties;

    static {
        try {
            properties = new Properties();
            properties.load(new FileInputStream(System.getProperty("user.dir")+"/src/bean.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        //获取目标类名
        String bean = properties.getProperty("bean");
        //根据目标类名获取目标类所对应的Class对象(描述结构)
        Class clazz = Class.forName(bean);
        //获取构造器
        Constructor constructor = clazz.getConstructor(null);
        //通过构造器创建对象
        System.out.println(constructor.newInstance(null));
    }
}

2、动态代理。

Java 中的动态代理是反射非常重要的一个应用。

代理模式

代理模式是一种常用的 Java 设计模式,代理模式是指在处理一个业务的时候,通过代理的方式来完成。

委托方和代理方,委托方委托代理帮助他完成某些工作。

在所有的代理模式中,委托方和代理方都有一个共性:双方都具备完成需求的能力。

Java 中将对象所具备的能力封装成接口,Java 中代理模式的原则就是委托类和代理类都实现了同样的接口。

委托类和代理类

动态代理

代理类和委托类之间通过依赖注入进行关联,在设计程序时需要将委托类定义为代理类的成员变量。

代理类本身并不会去执行业务逻辑,而是通过调用委托类的方法来完成,真正的业务逻辑仍然是委托类来完成,代理类只是完成一些核心业务以外的逻辑,这样来实现解耦合。

一个完整的业务包含:核心业务模块 + 辅助性的业务(打印日志、消息过滤、类型转换)

委托类只需要负责核心业务,不需要参杂任何与核心业务无关的代码。

辅助性的业务由代理类来完成,将委托类注入到代理类中,分别完成核心和辅助。

程序员只需要调用代理对象即可完成整套业务。

16.4 静态代理

静态代理是指代理类是提前写好的,动态代理是指代理类是动态生成的。

卖手机

package com.southwind.entity;

public interface Phone {
    public String salePhone();
}
package com.southwind.entity;

public class Huawei implements Phone {
    @Override
    public String salePhone() {
        return "销售华为手机";
    }
}
package com.southwind.entity;

public class Xiaomi implements Phone {
    @Override
    public String salePhone() {
        return "销售小米手机";
    }
}
package com.southwind.staticproxy;

import com.southwind.entity.Phone;

public class PhoneProxy implements Phone {
    //定义一个成员变量,即可以指向华为,也可以指向小米
    private Phone phone;

    public PhoneProxy(Phone phone) {
        this.phone = phone;
    }


    @Override
    public String salePhone() {
        System.out.println("启动代理模式销售手机");
        return this.phone.salePhone();
    }
}
package com.southwind.test;

import com.southwind.entity.Huawei;
import com.southwind.entity.Phone;
import com.southwind.entity.Xiaomi;
import com.southwind.staticproxy.PhoneProxy;

public class Test2 {
    public static void main(String[] args) {
        //创建委托对象
        Phone phone = new Huawei();
        //创建代理对象
        PhoneProxy phoneProxy = new PhoneProxy(phone);
        //代理对象开始工作
        System.out.println(phoneProxy.salePhone());
        phone = new Xiaomi();
        phoneProxy = new PhoneProxy(phone);
        System.out.println(phoneProxy.salePhone());
    }
}

卖汽车

package com.southwind.entity;

public interface Car {
    public String saleCar();
}
package com.southwind.entity;

public class BMW implements Car {
    @Override
    public String saleCar() {
        return "销售宝马汽车";
    }
}
package com.southwind.entity;

public class Benz implements Car {
    @Override
    public String saleCar() {
        return "销售奔驰汽车";
    }
}
package com.southwind.staticproxy;

import com.southwind.entity.Car;

public class CarProxy implements Car {

    private Car car;

    public CarProxy(Car car) {
        this.car = car;
    }

    @Override
    public String saleCar() {
        System.out.println("启动代理模式销售汽车");
        return this.car.saleCar();
    }
}
package com.southwind.test;

import com.southwind.entity.BMW;
import com.southwind.entity.Benz;
import com.southwind.entity.Car;
import com.southwind.staticproxy.CarProxy;

public class Test3 {
    public static void main(String[] args) {
        //创建委托对象
        Car car1 = new BMW();
        CarProxy carProxy = new CarProxy(car1);
        System.out.println(carProxy.saleCar());
        car1 = new Benz();
        carProxy = new CarProxy(car1);
        System.out.println(carProxy.saleCar());
    }
}

16.5 动态代理

创建一个厂商,既可以销售手机,也可以销售汽车,使用静态代理就不合适了。

代理类并不是提前写好的,而是在程序运行过程中动态生成的。

如何实现这一过程呢?代理类是动态生成的,但是我们需要告诉代理类它的生成策略。

如何指定代理类的生成策略?通过 java.lang.reflect.InvocationHandler 接口,可以在程序运行期间动态生成代理类,即生成策略通过该接口来完成。

1、实现 InvocationHandler 接口

Proxy.newProxyInstance() 三个参数:

1、类加载器 ClassLoader

2、委托类的接口

3、当前 InvocationHandler 实例对象

动态生成一个类,代理类

代理类的特点?

委托类和代理类都实现了同样的接口,代理类实现委托类的接口,必须要获取委托类的接口,第 2 个参数的作用。

动态创建类之后,类需要加载到 Java 内存中才能使用,创建对象,需要用到类加载器 ClassLoader,将动态生成的类加载到 Java 内存中,第 1 个参数的作用。

上述的过程需要一个对象来完成,就是当前的实例对象,第 3 个参数的作用。

package com.southwind.dymic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyInvocationHandler implements InvocationHandler {
    //注入委托对象
    private Object obj = null;

    //生成代理对象
    public Object bind(Object obj){
        this.obj = obj;
        return Proxy.newProxyInstance(MyInvocationHandler.class.getClassLoader(),obj.getClass().getInterfaces(),this);
    }

    //实现核心业务代码和辅助代码整合的
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //核心业务代码委托对象来完成,方法由委托对象来调用
        System.out.println("启动动态代理模式~~~");
        Object invoke = method.invoke(this.obj, args);
        return invoke;
    }
}
package com.southwind.test;

import com.southwind.dymic.MyInvocationHandler;
import com.southwind.entity.*;

public class Test4 {
    public static void main(String[] args) {
        MyInvocationHandler handler = new MyInvocationHandler();
        //定义委托对象
        Phone phone = new Xiaomi();
        //获取动态代理对象
        Phone proxy = (Phone) handler.bind(phone);
        System.out.println(proxy.salePhone());
        Car car = new BMW();
        Car carProxy = (Car) handler.bind(car);
        System.out.println(carProxy.saleCar());

        //委托对象
        House house = new BigHouse();
        //代理对象
        House houseProxy = (House) handler.bind(house);
        System.out.println(houseProxy.saleHouse());
    }
}

17 Java 网络编程

17.1 计算机网络

计算机网络就是通过硬件设视、传输媒介把各个不同物理地址上的计算机进行连接,形成一个资源共享和数据传输的网络系统。

两台终端通过网络进行连接交互的时候,需要遵守一定的规则,这个规则就是网络协议。

  • 语法:数据信息的结构
  • 语义:描述动作和响应
  • 同步:动作的实现顺序

常用的网络通信协议 TCP/IP 协议,分层的协议。

TCP/IP 协议分为 4 层:应用层、传输层、网络层、网络接口层

  • 应用层:整个体系结构中的顶层,通过应用程序之间的数据交互完成网络应用。

  • 传输层:为两台终端中应用程序之间的数据交互提供了数据传输的服务。

  • 网络层:IP 层,负责为网络中不同的终端提供通信服务。

  • 网络接口层:包括数据链路层和物理层,数据链路层的作用是为两台终端的数据传输提供链路协议;物理层是指光纤、电缆或者无线信号等真实存在的物理媒介,可以传送网络信号。

17.2 IP 和端口

17.2.1IP

就是电脑在网络中的地址,通过该地址就可以找到对应的电脑。

IP 地址 = 网络地址 + 主机地址

IP 值:127.0.0.1/localhost 都是指本机 IP

Java 中如何描述 IP,InetAddress

package com.southwind.test;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class Test {
    public static void main(String[] args) {
        try {
            InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
            System.out.println(inetAddress);
            InetAddress inetAddress1 = InetAddress.getByName("localhost");
            System.out.println(inetAddress1);
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }
}

17.2.2 端口

IP 就相当于一栋大厦的地址,端口就是不同房间的门牌号,IP 地址需要和端口结合起来使用,才能准确的找到一台电脑上的一个具体的应用程序。

不同的应用就是利用各种各样的端口加IP。

Tomcat:localhost:8080

MySQL:localhost:3306

Redis:localhost:6379

MQ:localhost:9876

17.3 URL

URL 统一资源定位符,通过 URL 可以找到网络中的一个资源,每一个网络资源都会有一个对应的 URL,用户可以通过这个 URL 找到资源。

https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic1.nipic.com%2F2008-12-22%2F20081222121824191_2.jpg&refer=http%3A%2F%2Fpic1.nipic.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628167787&t=b42ecf2726dd21c415fc6779a3dae2f8

简单的下载功能,给出一个具体的功能。

package com.southwind.test;

import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;

public class Test2 {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic1.nipic.com%2F2008-12-22%2F20081222121824191_2.jpg&refer=http%3A%2F%2Fpic1.nipic.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628167787&t=b42ecf2726dd21c415fc6779a3dae2f8");
        InputStream inputStream = url.openStream();
        OutputStream outputStream = new FileOutputStream("D:\\java2\\test.png");
        int temp = 0;
        while ((temp = inputStream.read())!=-1){
            outputStream.write(temp);
        }
        inputStream.close();
        outputStream.close();
    }
}

17.4 TCP 协议

TCP 协议的优点是非常可靠,安全性较高,通过 TCP 传输的数据,不会出现丢失的情况,并且数据是按照先后顺序依次到达。

缺点就是速度慢,效率低。

Java 实现 TCP 协议,通过 Socket 完成 TCP 程序的开发。

package com.southwind.test;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        Socket socket = null;
        OutputStream outputStream = null;
        InputStream inputStream = null;
        DataInputStream dataInputStream = null;
        DataOutputStream dataOutputStream = null;
        try {
            serverSocket = new ServerSocket(8080);
            System.out.println("------服务端------");
            System.out.println("已启动,等待接收客户端请求");
            while (true){
                socket = serverSocket.accept();
                //接收消息
                inputStream = socket.getInputStream();
                dataInputStream = new DataInputStream(inputStream);
                String request = dataInputStream.readUTF();
                System.out.println("接收到了客户端请求:"+request);
                //回复消息
                outputStream = socket.getOutputStream();
                dataOutputStream = new DataOutputStream(outputStream);
                String reponse = "Hello World";
                dataOutputStream.writeUTF(reponse);
            }
`        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
                outputStream.close();
                dataInputStream.close();
                dataOutputStream.close();
                socket.close();
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
package com.southwind.test;

import java.io.*;
import java.net.Socket;

public class Client {
    public static void main(String[] args) {
        //请求Server
        Socket socket = null;
        OutputStream outputStream = null;
        InputStream inputStream = null;
        DataOutputStream dataOutputStream = null;
        DataInputStream dataInputStream = null;
        try {
            socket = new Socket("127.0.0.1", 8080);
            System.out.println("------客户端------");
            String request = "你好!";
            System.out.println("客户端说:" + request);
            //发送消息
            outputStream = socket.getOutputStream();
            dataOutputStream = new DataOutputStream(outputStream);
            dataOutputStream.writeUTF(request);
            //接收消息
            inputStream = socket.getInputStream();
            dataInputStream = new DataInputStream(inputStream);
            String response = dataInputStream.readUTF();
            System.out.println("服务端响应:" + response);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
                outputStream.close();
                dataInputStream.close();
                dataOutputStream.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

17.5 UDP 协议

UDP 优势是速度快,效率高,缺点是安全性较差。

类似于微信发消息,他不用建立起链接。

package com.southwind.test;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;

public class TerminalA {
    public static void main(String[] args) throws Exception {
        //接收数据
        byte[] buff = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(buff, buff.length);
        DatagramSocket datagramSocket = new DatagramSocket(8181);
        datagramSocket.receive(datagramPacket);
        String mess = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
        System.out.println("我是TerminalA,接收到了" + datagramPacket.getPort() + "传来的数据:" + mess);
        //发送数据
        String reply = "我是TerminalA,已接收到你发来的数据";
        SocketAddress socketAddress = datagramPacket.getSocketAddress();
        DatagramPacket datagramPacket1 = new DatagramPacket(reply.getBytes(), reply.getBytes().length,socketAddress);
        datagramSocket.send(datagramPacket1);
    }
}
package com.southwind.test;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketAddress;

public class TerminalB {
    public static void main(String[] args) throws Exception {
        //发送数据
        String reply = "我是TerminalB,你好";
        InetAddress inetAddress = InetAddress.getByName("localhost");
        DatagramPacket datagramPacket = new DatagramPacket(reply.getBytes(), reply.getBytes().length,inetAddress,8181);
        DatagramSocket datagramSocket = new DatagramSocket(8080);
        datagramSocket.send(datagramPacket);
        //接收数据
        byte[] buff = new byte[1024];
        DatagramPacket datagramPacket2 = new DatagramPacket(buff, buff.length);
        datagramSocket.receive(datagramPacket2);
        String mess = new String(datagramPacket2.getData(), 0, datagramPacket2.getLength());
        System.out.println("我是TerminalB,接收到了" + datagramPacket2.getPort() + "返回的数据:" + mess);
    }
}

18 聊天软件的小想法

package com.southwind.test;

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

public class Server {

    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        Socket socket = null;
        try {
            serverSocket = new ServerSocket(8080);
            System.out.println("服务器已启动...");
            while (true){
                socket = serverSocket.accept();
                //聊天的业务,多线程
                new Thread(new SocketThread(socket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
    }

}

package com.southwind.test;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class SocketThread implements Runnable {
    private Socket socket;

    public SocketThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        //聊天,接收客户端发来的数据,给客户端发数据
        InputStream inputStream = null;
        DataInputStream dataInputStream = null;
        OutputStream outputStream = null;
        DataOutputStream dataOutputStream = null;
        String message = null;
        Scanner scanner = new Scanner(System.in);
        try {
            while (true){
                //读
                inputStream = socket.getInputStream();
                dataInputStream = new DataInputStream(inputStream);
                message = dataInputStream.readUTF();
                System.out.println("客户端:" + message);
                //写
                System.out.print("服务器:");
                message = scanner.next();
                outputStream = socket.getOutputStream();
                dataOutputStream = new DataOutputStream(outputStream);
                dataOutputStream.writeUTF(message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
    }
}
package com.southwind.test;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) {
        Socket socket = null;
        InputStream inputStream = null;
        DataInputStream dataInputStream = null;
        OutputStream outputStream = null;
        DataOutputStream dataOutputStream = null;
        Scanner scanner = new Scanner(System.in);
        try {
            System.out.println("客户端已启动...");
            socket = new Socket("127.0.0.1", 8080);
            String message = null;
            while (true){
                //写
                System.out.print("客户端:");
                message = scanner.next();
                outputStream = socket.getOutputStream();
                dataOutputStream = new DataOutputStream(outputStream);
                dataOutputStream.writeUTF(message);
                //读
                inputStream = socket.getInputStream();
                dataInputStream = new DataInputStream(inputStream);
                message = dataInputStream.readUTF();
                System.out.println("服务器:" + message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
    }
}

聊天的东西,使用的是TCP协议

就只是一个单机版本的聊天软件。就是使用了一个服务器来进行改变链接。

Web 应用的本质,客户端通过某种网络协议(TCP/IP)与服务器建立起网络连接(张三给李四打通了电话)

客户端和服务器通过 IO 流进行数据的传输(张三李四开始聊天)

构建联系来进行开始聊天。
nputStream inputStream = null;
DataOutputStream dataOutputStream = null;
DataInputStream dataInputStream = null;
try {
socket = new Socket(“127.0.0.1”, 8080);
System.out.println("------客户端------");
String request = “你好!”;
System.out.println(“客户端说:” + request);
//发送消息
outputStream = socket.getOutputStream();
dataOutputStream = new DataOutputStream(outputStream);
dataOutputStream.writeUTF(request);
//接收消息
inputStream = socket.getInputStream();
dataInputStream = new DataInputStream(inputStream);
String response = dataInputStream.readUTF();
System.out.println(“服务端响应:” + response);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
outputStream.close();
dataInputStream.close();
dataOutputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}


## 17.5 UDP 协议

UDP 优势是速度快,效率高,缺点是安全性较差。

类似于微信发消息,他不用建立起链接。

```java
package com.southwind.test;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;

public class TerminalA {
    public static void main(String[] args) throws Exception {
        //接收数据
        byte[] buff = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(buff, buff.length);
        DatagramSocket datagramSocket = new DatagramSocket(8181);
        datagramSocket.receive(datagramPacket);
        String mess = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
        System.out.println("我是TerminalA,接收到了" + datagramPacket.getPort() + "传来的数据:" + mess);
        //发送数据
        String reply = "我是TerminalA,已接收到你发来的数据";
        SocketAddress socketAddress = datagramPacket.getSocketAddress();
        DatagramPacket datagramPacket1 = new DatagramPacket(reply.getBytes(), reply.getBytes().length,socketAddress);
        datagramSocket.send(datagramPacket1);
    }
}
package com.southwind.test;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketAddress;

public class TerminalB {
    public static void main(String[] args) throws Exception {
        //发送数据
        String reply = "我是TerminalB,你好";
        InetAddress inetAddress = InetAddress.getByName("localhost");
        DatagramPacket datagramPacket = new DatagramPacket(reply.getBytes(), reply.getBytes().length,inetAddress,8181);
        DatagramSocket datagramSocket = new DatagramSocket(8080);
        datagramSocket.send(datagramPacket);
        //接收数据
        byte[] buff = new byte[1024];
        DatagramPacket datagramPacket2 = new DatagramPacket(buff, buff.length);
        datagramSocket.receive(datagramPacket2);
        String mess = new String(datagramPacket2.getData(), 0, datagramPacket2.getLength());
        System.out.println("我是TerminalB,接收到了" + datagramPacket2.getPort() + "返回的数据:" + mess);
    }
}

18 聊天软件的小想法

package com.southwind.test;

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

public class Server {

    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        Socket socket = null;
        try {
            serverSocket = new ServerSocket(8080);
            System.out.println("服务器已启动...");
            while (true){
                socket = serverSocket.accept();
                //聊天的业务,多线程
                new Thread(new SocketThread(socket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
    }

}

package com.southwind.test;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class SocketThread implements Runnable {
    private Socket socket;

    public SocketThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        //聊天,接收客户端发来的数据,给客户端发数据
        InputStream inputStream = null;
        DataInputStream dataInputStream = null;
        OutputStream outputStream = null;
        DataOutputStream dataOutputStream = null;
        String message = null;
        Scanner scanner = new Scanner(System.in);
        try {
            while (true){
                //读
                inputStream = socket.getInputStream();
                dataInputStream = new DataInputStream(inputStream);
                message = dataInputStream.readUTF();
                System.out.println("客户端:" + message);
                //写
                System.out.print("服务器:");
                message = scanner.next();
                outputStream = socket.getOutputStream();
                dataOutputStream = new DataOutputStream(outputStream);
                dataOutputStream.writeUTF(message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
    }
}
package com.southwind.test;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) {
        Socket socket = null;
        InputStream inputStream = null;
        DataInputStream dataInputStream = null;
        OutputStream outputStream = null;
        DataOutputStream dataOutputStream = null;
        Scanner scanner = new Scanner(System.in);
        try {
            System.out.println("客户端已启动...");
            socket = new Socket("127.0.0.1", 8080);
            String message = null;
            while (true){
                //写
                System.out.print("客户端:");
                message = scanner.next();
                outputStream = socket.getOutputStream();
                dataOutputStream = new DataOutputStream(outputStream);
                dataOutputStream.writeUTF(message);
                //读
                inputStream = socket.getInputStream();
                dataInputStream = new DataInputStream(inputStream);
                message = dataInputStream.readUTF();
                System.out.println("服务器:" + message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
    }
}

聊天的东西,使用的是TCP协议

就只是一个单机版本的聊天软件。就是使用了一个服务器来进行改变链接。

Web 应用的本质,客户端通过某种网络协议(TCP/IP)与服务器建立起网络连接(张三给李四打通了电话)

客户端和服务器通过 IO 流进行数据的传输(张三李四开始聊天)

构建联系来进行开始聊天。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值