一,初识Java:

1,java程序的编写-编译-运行的过程

编写:编写的java代码将保存在以".java"结尾的源文件中。

编译:程序运行前将会使用javac.exe编译.java源文件,编译完成后会生成一个或多个字节码文件,字节码文件以".class"结尾,文件名与".java"源文件中的类名相同。

​ 手动编译格式:javac 源文件名.java

运行:使用java.exe命令解释运行字节码文件。

​ 手动运行格式:java 类名

2,

在一个java源文件中可以声明多个class。但是,只能有一个类可以声明为public。

而且要求声明public的类的类名必须和源文件名相同。

3,

程序的入口是main()方法。格式是固定的

4,

输出语句:

system.out.println();#先输出数据,然后换行

system.out.print();#只进行输出数据

5,

每一条语句后都以**";"**结束

二,基础语法

1,关键字和保留字

  • 定义:被java语言赋予特殊含义,用做专门用途的字符串。
  • 特点:关键字中所有的字母都小写。

2,标识符

  • 定义:java中对各种变量,方法和类等要素命名时使用的字符序列称为标识符。
  • 简记:只要是可以自己起名字的地方都叫标识符。
  • 标识符命名规则:
  1. 由26个英文字母大小写,0-9,—和$组成。

  2. 数字不可以开头。

  3. 不可以使用关键字和保留字,但能包含关键字和保留字。

  4. java中严格区分大小写,长度无限制。

  5. 标识符不允许包含空格。

  • java中的名词命名规范:
  1. 包名:多单词组成时所有字母都小写:xxxyyyzzz

  2. 类名,接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz

  3. 变量名,方法名:多单词组成时,第一个单词的首字母小写,

    ​ 第二个单词开始首字母大写:xxxYyyZzz

  4. 常量名:所有字母都大写。多单词时用下划线来连接:XXX_YYY_ZZZ

3,变量

基本数据类型


整型:byte(1字节=8bit,范围:-128-127)

​ 声明long型变量,必须以"l"或"L"结尾

浮点型:float:单精度,尾数只能精确到7位有效数字。声明时后加'f'或'F'

​ double:双精度,精度是float的两倍,通常使用此类型

字符型:char:定义char型变量,通常使用一对' ',内部只能写一个字符

​ 作用:1,声明一个字符 char a = 'A'

​ 2,转义字符 char a = '\n' \\换行符

​ 3,直接使用Unicode值来表示字符型常量 char a = '\u0043'

布尔型:只有两个值:true,false

​ 常常在条件判断,循环结构中使用

变量使用的注意点

  1. 变量使用都要先声明,后使用。
  2. 变量都定义在其作用域内。在作用域内,它是有效的,出作用域即失效。
  3. 在同一个作用域内,不可以声明两个同名的变量。

基本数据类型变量间转换

基本数据类型之间的运算规则:\\只考虑七种数据类型变量间的运算,不包含boolean类型。

1,自动类型提升:

当容量小的数据类型的变量与容量大的数据类型的变量进行运算时,结果自动提升为容量大的数据类型。byte,char,short进行运算时自动提升至int类型。

byte,char,short --> int --> long --> float --> double

2,强制类型转换:自动类型提升的逆运算。

(1)需要使用强转符:转后类型 转后变量 = (转后类型)转前变量;

(2)注意点:强制类型转换,可能导致精度损失。

基本数据类型与String间转换

String类型变量的使用:

  1. String属于引用数据类型

  2. 声明String类型变量时,使用一对" "

  3. String可以和其他基本数据类型进行运算,但只能为连接运算。

  4. 运算结束后的类型还是String类型

    //练习*	*
    System.out.println("*   *");//*	*
    System.out.println('*' + '\t' + '*');//93
    System.out.println('*' + "\t" + '*');//*	*
    System.out.println('*' + '\t' + "*");//51*
    System.out.println('*' + ('\t' + "*"));//*	*
    

进制与进制间的转换

  • 二进制:0,1,满2进1,以0b或0B开头
  • 十进制:0-9,满10进1
  • 八进制:0-7,满8进1,以数字0开头表示
  • 十六进制:0-9及A-F,满16进1,以0x或0X开头表示,a-f不区分大小写

进制转换的思想:

4,运算符

算数运算符

**注意:**自增自减不会改变原来的数据类型

赋值运算符

符号:=

1,当”=“两侧数据类型不一样时,可以通过自动类型转换或强制类型转换原则进行处理。

2,支持连续赋值

扩展赋值运算符:

	`+=,-=,*=,/=,%=`

**注意:**扩展运算符不会改变原来的数据类型。

比较运算符(关系运算符)

注意:==运算符运用在应用数据类型时,比较的是地址值。

逻辑运算符

注意: 1,逻辑运算符只对boolean类型进行运算。

​ 2,在使用短路与进行运算时,只要前者为false,则后者不进行运算。

​ 3,在使用短路或进行运算时,只要前者为true,则后者不进行运算。

​ 4,实际开发中,推荐使用 &&||

位运算符

<<:在一定范围内,每向左移一位,相当于 * 2

>>:在一定范围内,每向右移一位,相当于 / 2

三元运算符

**注意:**三元运算符可以嵌套使用

运算符的优先级

5,程序流程控制

条件语句

if...else

if (Grades >= 100){
            System.out.println("奖励一辆BMW");
        } else if(Grades >= 80){
            System.out.println("奖励一台iPhone xs max");
        } else if(Grades >= 60){
            System.out.println("奖励一台ipad");
        } else {
            System.out.println("奖励一个大嘴巴子!");
        }

**注意:**if...else可以嵌套使用。

​ if...else满足条件调用执行语句结束则自动跳出。

switch...case

int num = 1;
        switch (num){
            case 0:
                System.out.println("zero");
            case 1:
                System.out.println("one");//在此开始
            case 2:
                System.out.println("two");
            case 3:
                System.out.println("three");//在此结束
                break;
            default:
                System.out.println("other");

注意:

  1. 根据 switch 表达式中的值,依次匹配各个 case 中的常量。一旦匹配成功,则进入相应的case结构中,调用其执行语句。

  2. 调用完执行语句后,仍然向下执行其他case结构中的执行语句,直到遇见 break 或switch...case结构末尾结束

  3. switch 结构中的表达式只能是如下6种数据类型之一:byte、short、char、int、枚举类型、String类型。

  4. case 后只能声明常量,不能声明范围。

  5. break关键字是可选的。

  6. default:相当于if...else中的else.

    default结构是可选的,而且位置是灵活的。

  7. 如果多个执行语句一样,则可以考虑将case合并

总结:

  1. 凡是使用switch-case的结构,都可以转换为if-else。反之,不成立。
  2. switch-case的执行效率稍高。所以在switch-case表达式取值不多的情况下,优先使用switch-case。

循环结构

循环结构的四个要素

  1. 初始化条件
  2. 循环条件 ----->是boolean类型
  3. 循环体
  4. 迭代条件

执行过程:1 -- 2 -- 3 -- 4 -- 2 -- 3 -- 4 -- .......

for循环

//基本语法
for (1;2;4){
    3
}
//实例:将hello,world!输出10次
for(int x = 1;x <= 10;x++){
            System.out.println("hello,world!");
        }
//重复循环语法:
for(;;)

while循环

//基本语法
1
while(2){
    3;
    4;
}
//实例:将hello,world!输出10次
int i = 1;
while (i<=10){
	System.out.println("hello,world");
	i++;
}
//重复循环语法:
while(true)

说明:

  • 写while循环时,注意迭代条件,一旦丢失,则容易造成死循环。

  • for循环和while循环是可以相互转换的。

    ​ 区别:for循环和while循环的初始化条件部分的作用范围不同。

do-while循环结构

//基本语法
1
do{
    3;
    4;
}while(2);
//执行过程:1 - 3 - 4 - 2 - 3 - 4 - 2 - ...
//实例:将hello,world!输出10次
int i = 1;
do {
	System.out.println("hello,world!");
	i++;
}while (i <= 10);

说明:

  • do-while循环至少会执行一次循环体。
  • 如果进行多次循环,则do-while循环和while循环结果一样。

嵌套循环

嵌套循环的使用:

  1. 将一个循环结构A声明在另一个循环结构B的循环体中,就构成了嵌套循环。

  2. 外层循环:循环结构B

    内层循环:循环结构A

  3. 内层循环结构遍历一遍,只相当于外层循环循环体执行了一次。

  4. 假设外层循环需要执行m次,内层循环需要执行n次,此时内层循环体一共执行了m * n次。

技巧:

外层循环控制行数

内层循环控制列数

break和continue关键字的使用

			使用范围				循环中使用的作用
break:		switch-case				结束当前循环
			循环结构中

continue	循环结构中				 结束当次循环
a:for (int i = 1;i <= 4;i++){
            for (int j = 1;j <= 10;j++){
                if (j % 4 ==0){
                  //break; 默认结束离次关键字最近的循环
                    break a;//结束指定标签的一层循环
                }
                System.out.print(j);
            }
            System.out.println();
        }

三,数组

数组的概述

数组(Array), 是多个相同类型数据一定顺序排列的集合,并使用一个名字命名,并通过编号的方式对这些数据进行统一管理。

数组的常见概念:

  • 数组名
  • 下标(索引)
  • 元素
  • 数组的长度

数组的特点:

  1. 数组是有序排列的
  2. 数组属于引用数据类型的变量。数组的元素,既可以是基本数据类型,也可以是引用数据类型
  3. 创建数组对象会在内存中开辟一整块连续的空间
  4. 数组的长度一旦确定, 就不能修改

数组的分类:

  1. 按照维数: -维数组、二维数组、。。
  2. 按照数组元素的类型:基本数据类型元素的数组、引用数据类型元素的数组

一维数组的使用

  • 一维数组的声明和初始化

    int[] ids;//声明
    //静态初始化:数组的初始化和数组元素的赋值操作同时进行
    ids = new int[]{1001,1002,1003,1004};
    //动态初始化:数组的初始化和数组元素的赋值操作分开进行
    String[] names = new String[5];
    
  • 如何调用数组的指定位置的元素

names[0] = "A";//使用角标进行调用
names[1] = "B";//角标从0开始
names[2] = "C";
names[3] = "D";
names[4] = "E";
  • 如何获取数组的长度
//属性:length
System.out.println(names.length);
  • 如何遍历数组
for(int i = 0;i < names.length;i++){
	System.out.println(names[i]);
}
  • 数组元素的默认初始化值

    1. ​ 数组元素是整形:0
    2. ​ 数组元素是浮点型:0.0
    3. ​ 数组元素是char型:0或'\u0000',而非'0'
    4. ​ 数组元素是boolean型:false
    5. ​ 数组元素是引用数据类型:null
  • 数组的内存解析

二维数组的使用

  • 二维数组的声明和初始化

    //静态初始化
    int [][] arr = new int [][] {{1,2,3},{4,5},{6,7}};
    //动态初始化
    String [][] arr2 = new String [3][];
    
  • 如何调用数组的指定位置的元素

    System.out.println(arr[0][1]);//2
    System.out.println(arr2[1][1]);//null
    
  • 如何获取数组的长度

    System.out.println(arr.length);//3
    System.out.println(arr[1].length);//2
    
  • 如何遍历数组

    for (int i = 0;i < arr.length;i++){
    	for (int j = 0;j < arr[i].length;j++){
    		System.out.print(arr[i][j] + "  ");
    		}
    	System.out.println();
    }
    
  • 数组元素的默认初始化值

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

    ​ 外层元素的初始化值为:地址值

    ​ 内层元素的初始化值为:与一维数组初始化情况相同

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

    ​ 外层元素的初始化值为:null

    ​ 内层元素的初始化值为:不能调用,否则报错

  • 数组的内存解析

算法

数组元素的赋值(杨辉三角、回形数等)

//声明并初始化数组
int[][] yangHui = new int[10][];
for (int i = 0;i < yangHui.length;i++){
	//给数组的元素赋值
    yangHui[i] =new int [i + 1];
    //给每行的首尾元素赋值
    yangHui[i][0] = yangHui[i][i] = 1;
    	//给非首尾元素赋值
        for (int j = 1;j < yangHui[i].length-1;j++){
        	yangHui[i][j] = yangHui[i-1][j-1] + yangHui[i-1][j];
        }
}
//遍历二维数组
for (int i = 0;i < yangHui.length;i++) {
	for (int j = 0; j < yangHui[i].length; j++) {
		System.out.print(yangHui[i][j] + " ");
    }
	System.out.println();
}

数组元素的查找(线性查找、二分法查找)

//线性查找
String[] array = new String[]{"AA","BB","CC","DD","EE"};
String dest = "BB";
boolean isflog = true;
for (int i = 0;i < array.length;i++){
	if (dest.equals(array[i])){
		System.out.println("找到了,位置为" + i);
		isflog = false;
		break;
	}
	if (!isflog){
		System.out.println("很抱歉,没有找到");
	}
}
//二分法查找
//前提:所要查找的数组必须有序
		Scanner scan = new Scanner(System.in);
        int[] array1 = new int[]{-444,-67,-43,-23,-7,0,3,23,43,54,77,554};
        int head = 0;//初始的首索引
        int end = array1.length - 1;//初始的末索引
        System.out.println("请输入需要查找的数:");
        int dest1 = scan.nextInt();
        boolean isflog = true;
        int middle = (head + end)/2;
        if (array1[middle] == dest1) {
            System.out.println("找到了指定的元素,位置为" + middle);


        }else if(array1[middle] > dest1) {
            for (int i = middle; i >= 0; i--){
                if (array1[i] == dest1) {
                    System.out.println("找到了指定的元素,位置为" + i);
                    isflog = true;
                    break;
                }else {
                    isflog = false;
                }
            }
            if (!isflog){
                System.out.println("很抱歉,没有找到指定的元素");
            }


        }else if(array1[middle] < dest1) {
            for (int i = middle; i <= end; i++) {
                if (array1[i] == dest1) {
                    System.out.println("找到了指定的元素,位置为" + i);
                    isflog = true;
                    break;
                }else {
                    isflog = false;
                }
            }if (!isflog){
                System.out.println("很抱歉,没有找到指定的元素");
            }
        }

排序算法

冒泡排序:

概念:

每一 趟进行的过程:从第一个元素开始,比较两个相邻的元素。若相邻的
元素的相对位置不正确,则进行交换;否则继续比较下面两个相邻的元素。
结束条件:在任何一趟进行过程中,未出现交换。

//冒泡排序具体实现
		int[] array = new int[]{-36,32,-27,21,-16,77,-7,0,4,-14,24,-45,66};
        for (int i = 0;i < array.length-1;i++){
            for (int j = 0;j < array.length - 1 - i;j++){
                if (array[j] > array[j+1]){
                    int temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp;
                }
            }
        }

四,面向对象(上)

面向对象的基础概念

面向对象思想编程内容的三条主线分别是什么:

​ ① 类及类的成员:属性、方法、构造器;代码块、内部类。

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

​ ③ 其他关键字:this,super,abstract,interface,static,final,package,import。

面向对象与面向过程的区别:

  • 面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做。
  • 面向对象:强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。

面向对象的两个要素:

  • 类:对一类事物的描述,是抽象的、概念上的定义

  • 对象:是实际存在的该类事物的每个个体,因而也称为实例(instance)

    >面向对象程序设计的重点是类的设计

    >设计类,就是设计类的成员

    属性 = 成员变量 = field = 域、字段

    方法 = 成员方法 = 函数 = method

    创建类的对象 = 类的实例化 = 实例化类

理解“万事万物皆对象”

  • 在Java语言范畴中,我们都将功能、结构等封装在类中,通过类的实例化,来调用具体的功能结构

​ >Scanner,String等

​ >文件:File

​ >网络资源:URL

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

类与对象

类和对象的使用(面向对象思想落地的实现):

  1. 创建类,设计类的成员
  2. 创建类的对象
  3. 通过"对象.属性"或"对象.方法"调用对象的结构

如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。

​ 意味着:如果我们修改一个对象的属性a,则不影响另一个对象属性a的值。

对象的内存解析:

类的结构之一:属性

类中属性的使用:

属性(成员变量) vs 局部变量

​ 1,相同点:

​ 定义变量的格式:数据类型 变量名 = 变量值

​ 先声明,后使用

​ 变量都有其对应的作用域

​ 2,不同点:

在类中声明的位置不同:

​ 属性:直接定义在类的一对{}内

​ 局部变量:声明在方法内、方法形参、代码块内、构造器内部的变量

关于权限修饰符的不同:

​ 属性:可以在声明其属性时,指明其权限,使用权限修饰符

​ 常用的权限修饰符:private、public、缺省、protected --->封装性

​ 局部变量:不允许使用权限修饰符

默认初始化的情况:

​ 属性:类的属性,根据其类型,都有默认初始化值

​ 局部变量:没有默认初始化值

​ 特别的:形参在调用时赋值即可

在内存中加载的位置:

​ 属性:加载到堆空间中(非static)

​ 局部变量:加载到栈空间

对属性可以赋值的位置:

  1. 默认初始化

  2. 显式初始化

  3. 构造器中初始化

  4. 有了对象以后,可以通过“对象.属性”或“对象.方法”的方式,进行赋值

  5. 在代码块中进行赋值

    属性赋值执行的先后顺序:① -- ② / ⑤ -- ③ -- ④

类的结构之二:方法

类中方法的声明和使用:

​ **方法:**描述类应该具有的功能

​ **方法的声明:**权限修饰符 返回值类型 方法名(形参列表){

​ 方法体

​ }

​ 例:public void eat(){}

public String getNation(String nation){}

说明:

​ **关于权限修饰符:**默认方法的权限修饰符都先使用public

​ Java规定的4种权限修饰值:private、public、缺省、protected --->封装性细讲

​ **关于返回值类型:**有返回值 vs 无返回值

​ 如果方法有返回值,则必须在方法声明时,指定返回值的类型。同时,方法中,

​ 需要使用return关键字来返回指定类型的变量或常量。

​ 如果方法没有返回值,则方法声明时,使用void来表示。通常,没有返回值的方法中

​ 就不使用return,但是,如果使用的话,只能"return;"表示结束此方法的意思。

​ **关于方法名:**属于标识符,遵循标识符的规则和规范,“见名知意”

​ **形参列表:**方法可以声明0个、1个,或多个形参

​ 格式:数据类型1 形参1,数据类型2 形参2,···

return关键字的使用:

​ 作用范围:使用在方法体中

​ 作用:① 结束方法

​ ② 针对有返回值类型的方法,使用"return 数据"方法返回所要的数据

​ 注意点:return关键字后不可以声明执行语句

**注意:**方法的使用中,可以调用当前类中的属性或方法

​ 特殊的:方法A中又调用了方法A:递归方法

​ 方法中,不可以再定义方法

方法的重载(overload)

定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可

​ ”两同一不同“:同一个类、同一个方法名

​ 参数列表不同:参数个数不同,参数类型不同

判断是否重载:

​ 跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系。严格按照定义判断

在通过对象调用方法时,如何确定某一个指定的方法:

​ 方法名 - - - > 参数列表

可变个数的形参:

  1. 可变个数形参的格式:数据类型 ... 变量名
  2. 当调用可变个数的形参的方法时,传入的参数个数可以是:0个,1个,2个
  3. 可变个数形参的方法与本类中方法名相同,形参不同的方法构成重载
  4. 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载,意味着二者不能共存
  5. 可变个数形参在方法的形参中,必须声明在末尾
  6. 可变个数形参在方法的形参中,最多只能声明一个可变形参

方法参数的值传递机制:

  • 关于变量的赋值:

    如果变量是基本数据类型,此时赋值的是变量所保存的数据值。

    如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。

  • 方法形参的值传递机制:

    1,形参:方法定义时,声明的小括号内的参数

    ​ 实参:方法调用时,实际传递给形参的数据

    2,值传递机制

    ​ 如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值。

    ​ 如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值。

递归方法的使用:

1,递归方法:一个方法体内调用它本身

2,方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无需循环控制

​ 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。

//计算1-n之间所有自然数的和
public int getSum(int n){
	if(n == 1){
		reture 1;
	}else {
		reture n + getSum(n - 1);
	}
}

类的结构之三:构造器(constructor)

一、构造器的作用

  1. 创建对象

  2. 初始化对象的信息

二、说明

  1. 如果没有显式的定义构造器的话,则系统会默认提供一个空参的构造器

  2. 定义构造器的格式:权限修饰符 类名(形参列表){}

  3. 一个类中定义的多个构造器,彼此构成重载

  4. 一旦显式的定义了类的构造器以后,系统就不再提供默认的空参构造器

  5. 一个类中,至少有一个构造器

面向对象的特征一:封装性

问题的引入:
当我们创建一个类的对象以后, 我们可以通过"对象.属性"的方式,对对象的属性进行赋值。

这里,赋值操作要受属性的数据类型和存储范围的制约。除此之外,没有其他制约条件。但是,在实际问题中,我们往往需要给属性赋加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如: setLegs())
同时,我们需要避免用户再使用"对象.属性"的方式对属性进行赋值。则需要将属性声明为私有的(private)
-->此时,针对于属性就体现了封装性。

封装性的体现:

我们将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)

​ 拓展:封装性的体现:①:如上 ②:不对外暴露的私有的方法 ③:单例模式 · · ·

封装性的体现,需要权限修饰符来配合

1,Java规定的四种权限(从小到大排序):private,缺省(==不写),protected,public

2,四种权限修饰符可以用来修饰类及类的内部结构: 属性、方法、构造器、内部类

总结封装性:Java提供了4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的 大小

this关键字的使用

1,this可以用来修饰或调用:属性、方法、构造器

2,this修饰属性和方法:

​ this理解为:当前对象 或 当前正在创建的对象

​ 2.1 在类的方法中,我们可以使用“this.属性”或"this.方法"的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略"this."。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。

​ 2.2 在类的构造器中,我们可以使用“this.属性”或"this.方法"的方式,调用正在创建的对象属性当前对象属性或方法。但是,通常情况下,我们都选择省略"this."。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。

3,this调用构造器

​ ① 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器

​ ② 构造器中不能通过"this(形参列表)"方式调用自己

​ ③ 如果一个类中有n个构造器,则最多有n - 1构造器使用了"this(形参列表)"

​ ④ 规定:"this(形参列表)"必须声明在当前构造器的首行

​ ⑤ 构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器

package关键字的使用

1,package关键字的使用

  • 为了更好的实现项目中类的管理,提供包的概念

  • 使用package声明类或接口所属的包,声明在源文件的首行

  • 包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)、“见名识意”

  • "."一次,代表一层文件目录

  • 同一个包下,不能命名同名的接口、类

    不同的接口下,可以命名同名的接口、类

MVC设计模式

import关键字的使用

  • 在源文件中显式的使用import结构导入指定包下的类、接口
  • 声明在包的声明和类的声明之间
  • 如果需要导入多个结构,则并列写出即可
  • 可以使用"xxx.*"的方式,表示可以导入xxx包下的所有结构
  • 如果使用的类或接口是java.lang包下定义的,则可以省略import
  • 如果使用的类或接口是本包下定义的,则可以省略import结构
  • 如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式显示
  • 使用"xxx.*"方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显式的导包
  • import static:导入指定类或接口中的静态结构:属性或方法

五,面向对象(中)

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

一,继承性的格式:class A extends B{ }

​ A:子类、派生类、subclass

​ B:父类、超类、基类、superclass

​ **2.1 体现:**一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。

​ 特别的,父类中声明为private的属性或方法,子类继承父类后,仍然认为获取了父类中私有的结构。

​ 只是因为封装性的影响,使得子类不能直接调用父类的结构而已。

2.2 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。

​ 子类和父类的关系,不同于子集和集合的关系。

二,继承性的好处

  1. 减少代码的冗余,提高代码的复用性
  2. 便于功能的扩展
  3. 为之后多态性的使用,提供了前提

三,Java中关于继承性的规定

  1. 一个类可以被多个子类继承
  2. Java中类的单继承性:一个类只能有一个父类
  3. 子父类是相对的概念
  4. 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
  5. 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法

四,补充说明

  1. 如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
  2. 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
  3. 意味着,所有的java类具有java.lang.Object类声明的功能

五,方法的重写(override / overwrite)

  1. 重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作

  2. 应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中同名同参数的方法时,实际执行的是子类重写父类的方法。

  3. 重写的规定:

    ​ 方法的声明:权限修饰符 返回值类型 方法名(形参列表){

    ​ //方法体

    ​ }

    ​ 约定俗称:子类中的叫重写方法,父类中的叫被重写方法

    1. 子类重写的方法名和形参列表与父类被重写的方法名和形参列表相同

    2. 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符

      ​ >特殊情况:子类不能重写父类中声明为private权限的方法

    3. 返回值类型

      ​ >父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void

      ​ >父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类

    4. 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型

  4. static方法不能进行重写

super关键字的使用

  1. super理解为:父类的
  2. super可以用来调用:属性、方法、构造器
  3. super的使用:调用属性和方法
    1. 我们可以在子类的方法或构造器中。通过使用“super.属性”或“super.方法”的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略“super.”
    2. 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性。
    3. 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法。
  4. super调用构造器
    1. 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
    2. "super(形参列表)"的使用,必须声明在子类构造器的首行!
    3. 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,不能同时出现。
    4. 在构造器的首行,没有显式的声明"this(形参列表)"或"super(形参列表),则默认调用的是父类中空参的构造器:super( )
    5. 在类的多个构造器中,至少有一个类的构造器中使用了"super(形参列表)",调用父类中的构造器

子类对象实例化的全过程

  1. 从结果上看:(继承性)

    ​ 子类继承父类以后,就获取了父类中声明的属性或方法

    ​ 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。

  2. 从过程上看:

    当我们通过子类的构造器创建子对象时,我们一定回直接或间接的调用其父类的构造器,进而调用父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存从有父类中的结构,子类对象才可以考虑进行调用。

    明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象

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

  1. 理解多态性:可以理解为一个事物的多种形态

  2. 何为多态性:

    对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)

  3. 多态的使用:虚拟方法调用

    有了对象的多态性以后会,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。

    总结:编译:看左边;运行:看右边。

  4. 多态性的使用前提:

    1. 类的继承关系
    2. 方法的重写
  5. 对象的多态性,只适用于方法,不适用于属性。

对多态的理解:

  1. 实现代码的通用性。

  2. object类中定义的public boolean equals(0bject obj){ }

    JDBC:使用java程序操作(获取数据库连接、CRUD)数据库(MySQL、Oracle、 DB2、 SQL Server)

  3. 抽象类、接口的使用肯定体现了多态性。(抽象类、 接口不能实例化)

虚拟方法调用

向下转型

**使用前提:**有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于能量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。为了解决这个问题,所以需要使用向下转型。

**如何实现:**使用()进行强制类型转换

Person p2 = new Person();
Man m1 = (Man)p2;
m1.earnMoney();//调用Man类中的earnMoney方法

instanceof关键字的使用

a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。

使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型;如果返回false,则不进行向下转型。

如果a instanceof A返回true,则a instanceof B也返回true。其中,类B是类A的父类。

向下转型注意错误

//问题一:编译时通过,运行时不通过
//举例一:
Person p3 = new Woman();
Man m3 = (Man)p3;
//举例二:Person p4 = new Person();
Man m4 = (Man)p4;

//问题二:编译通过,运行时也通过(实际开发中可用)
Object obj = new Woman();
Person p = (Person)obj;

//问题三:编译不通过
Man m5 = new Woman();

String str = new Date();

Java.lang.Object类

  1. Object类是所有Java类的根父类

  2. 如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类

  3. Object类中的功能(属性、方法)具有通用性。

    属性:无

    方法:equals() / toString() / getClass / hashCode() / clone() / finalize() / wait() 、notify() 、notifyAll()

    1. equals()方法的使用:

      1. 是一个方法,而非运算符

      2. 只能适用于应用数据类型

      3. Object类中equals()的定义:

        public boolean equals(Object obj){
        	return(this == obj)
        }
        //说明Object类中定义的equals()和==的作用是相同的:
        //比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
        
      4. 像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址值是否相同,而是比较两个对象的"实体内容"是否相同。

      5. 通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容是否相同"。那么,我们就需要对Object类中的equals()进行重写。

        重写的原则:比较两个对象的实体内容是否相同。

    2. Object类中toString()的使用:

      1. 当我们输出一个对象的引用时,实际上就是调用当前对象的toString()

      2. Object类中toString()的定义:

        public String toString() {
            return getClass().getName() + "@" + 					Integer.toHexString(hashCode());
        }
        
      3. 像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回"实体内容"信息。

      4. 自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容"

  4. Object类只声明了一个空参的构造器。

JUnit单元测试

  1. java类要求:
    1. 此类是public的
    2. 此类提供无参构造器
  2. 此类中声明单元测试方法要求:
    1. 方法权限是public
    2. 没有返回值
    3. 没有形参
  3. 此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;

包装类(Wrapper)的使用

  • 针对八种基本数据类型定义相应的引用类型—包装类(封装类),使得基本数据类型的变量具有类的特征

基本数据类型、包装类和String类间转化

基本数据类型、包装类和String类之间转化

基本数据类型和包装类之间相互转换(自动装箱与自动拆箱):

//手动装箱
//基本数据类型 - - > 包装类:调用包装类的构造器
//手动拆箱
//包装类 - - >基本数据类型:调用包装类的xxxValue()
//自动装箱:基本数据类型 - - > 包装类
int num1 = 10;
Integer in1 = num1; //自动装箱

boolean b1 = true;
Boolean b2 = b1;//自动装箱

//自动拆箱:包装类 - - > 基本数据类型
System.out.print(in1.toString());
int num2 = in1;//自动拆箱

//补充:Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[]
//保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在
//-128~127范围内时,可以直接使用数组中的元素,不用去new了
//目的:提高效率
Integer m = 1;
Interge n = 1;
System.out.println(m == n);//true	在范围内所以直接引用了同地址值数组的内容
Integer x = 128;
Integer y = 128;
System.out.println(m == n);//false	不在范围内,重新new了一个对象

基本数据类型、包装类和String类型相互转化:

//基本数据类型、包装类 - - - > String类型:调用String重载的valueOf(Xxx xxx)
//方式一:连接运算
int num1 = 10;
String str1 = num1 + "";
//方式二:调用String的valueOf(Xxx xxx)
float f1 = 12.3f;
String str2 = String.valueOf(f1);//12.3
//包装类 - - > String类型
Double d1 = new Double(12.4);
String str3 = String.valueOf(d1);//12.4

//string类型 - - > 基本数据类型、包装类:调用包装类的parseXxx(String str)
String str1 = "123";
int num1 = Integer.parseInt(str1);//123

String str2 = "true";
boolean b1 = Boolean.parseBoolean(str2);

六,面向对象(下)

static关键字的使用

  1. static:静态的

  2. static可以用来修饰:属性、方法、代码块、内部类

  3. 使用static修饰属性:静态变量(类变量)

    1. 属性,按是否使用了static修饰,分为:静态属性 vs 非静态属性(实例变量)

      实例变量:我们创建了类的多个对象,每个对象都独立的的拥有一套类中的非静态属性。当修改其中一个非静态属性时,不会导致其他对象中同样的属性值的修改。

      静态变量:我们创建类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过的。

    2. static修饰属性的补充说明:

      1. 静态变量随着类的加载而加载
      2. 静态变量的加载要早于对象的创建
      3. 由于类只会加载一次,则静态变量在内存中也只会存在一次:存在方法区的静态域中。
      4. ​ 类变量 实例变量 (调用情况)
        ​ 类 yes no
        ​ 对象 yes yes
    3. 静态属性举例:System.out;Math.PI

  4. 使用static修饰方法:静态方法

    1. 随着类的加载而加载,可以通过"类.静态方法"的方式进行调用

    2. ​ 静态方法 非静态方法 (调用情况)
      ​ 类 yes no
      ​ 对象 yes yes

    3. 静态方法中,只能调用静态的方法或属性

      非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性

  5. static注意点:

    1. 在静态的方法内,不能使用this关键字、super关键字
    2. 关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。
  6. 开发中,如何确定一个属性是否要声明为static

    1. 属性是可以被多个对象所共享的,不会随着对象的不同而不同的
  7. 开发中,如何确定一个方法是否要声明为static

    1. 操作静态属性的方法,通常设置为static
    2. 工具类中的方法,习惯上声明为static的。比如:Math、Arrays、Collections

单例(Singleton)设计模式

  1. 所谓类的单例设计模式,就是采取一定的方法保证在整个软件系统里,对某个类只能存在一个对象实例。

  2. 如何实现

    饿汉式 vs 懒汉式

    具体实现:查看IDEA中的singleton包

  3. 区分饿汉式和懒汉式

    1. 饿汉式:

      坏处:对象加载时间过长

      好处:饿汉式是线程安全的

    2. 懒汉式:

      坏处:以目前的写法来说,线程不安全 - - -> 在多线程时讲如何修改

      好处:延迟对象的创建

  4. 单例设计模式 --- 应用场景

模板方法设计模式(TemplateMethod)

理解main方法

main()方法的使用说明:

  1. main()方法作为程序的入口
  2. main()方法也可以是一个普通的静态方法
  3. main()方法可以作为我们与控制台交互的方式。(如之前:使用Scanner)

类的结构之四:代码块

  1. 代码块的作用:用来初始化类、对象
  2. 代码块只能使用static进行修饰
  3. 分类:静态代码块 vs 非静态代码块
  4. 静态代码块
    1. 内部可以有输出语句
    2. 随着类的加载而执行,而且仅执行一次
    3. 作用:初始化类的信息
    4. 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
    5. 静态代码块的执行要优先于非静态代码块的执行
    6. 静态代码块内只能调用静态的属性、方法
  5. 非静态代码块
    1. 内部可以有输出语句
    2. 随着对象的创建而执行
    3. 每创建一个对象,就执行一次非静态代码块
    4. 作用:可以在创建对象时,对对象的属性等进行初始化
    5. 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
    6. 非静态代码块内可以调用静态的属性、方法,或非静态的属性、方法

final关键字的使用

final:最终的

  1. final可以用来修饰的的结构:类、方法、变量

  2. final 用来修饰一个类:此类不能被其他类所继承

    ​ 比如:String类、System类、StringBuffer类

  3. final 用来修饰方法:表明此方法不能被重写

    ​ 比如:Object类中getClass();

  4. final 用来修饰变量:此时的"变量"就被称为一个常量

    1. final 修饰属性:可以考虑赋值的位置有:显式初始化、代码块初始化、构造器初始化

    2. final 修饰局部变量

      尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦以后,就只能在方法体内使用此形参,但不能进行重新赋值

  5. static final 用来修饰属性:全局常量

抽象类与抽象方法的使用

abstract关键字的使用

  1. abstract:抽象的
  2. abstract可以用来修饰的结构:类、方法
  3. abstract修饰类:抽象类
    1. 此类不能实例化
    2. 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
    3. 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
  4. abstract修饰方法:抽象方法
    1. 抽象方法只有方法的声明,没有方法体
    2. 包含抽象方法的类一定是一个抽象类。反之,抽象类中可以没有抽象方法
    3. 若子类重写了父类中的所有抽象方法后,此子类方可实例化
    4. 若子类没有重写父类中所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
  5. abstract使用上的注意点:
    1. abstract不能用来修饰:属性、构造器等结构
    2. abstract不能用来修饰私有方法、静态方法、final的方法、final的类

接口(interface)

接口的使用

  1. 接口使用interface来定义

  2. Java中,接口和类是并列的两个结构

  3. 如何定义接口:定义接口中的成员

    1. JDK7及以前:只能定义全局常量和抽象方法

      1. 全局常量:public static final的。但是书写时可以省略不写
      2. 抽象方法:public abstract的
    2. JKD8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法

      1. 接口中定义的静态方法,只能通过接口来调用

      2. 通过实现类的对象,可以调用接口中的默认方法;接口本身想要调用则需要使用:

        接口名.super.默认方法名

        如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法

      3. 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法

        那么子类在没有重写此方法的情况下默认调用的是父类中同名同参数的方法 - - ->类优先原则

      4. 如果实现类实现了多个接口,而多个接口中定义了同名同参数的默认方法,那么实现类在没有重写此方法的情况下,报错 - - - > 接口冲突

        意味着我们必须在实现类中重写此方法

  4. 接口中不能定义构造器:意味着接口不能实例化

  5. Java开发中,接口通过让类去实现(implements)的方式去实现

    如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化

    如果实现类没有覆盖接口中所有抽象方法,则此实现类仍为一个抽象类

  6. Java类可以实现多个接口 - - - >弥补了Java只有单继承的局限性

    格式: calss AA extends BB implements CC,DD,EE

  7. 接口与接口之间可继承,且可以进行多继承

  8. 接口的具体使用:体现多态性

  9. 接口,实际上可以看成一种规范

类的结构之五:内部类

  1. Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类

  2. 内部类的分类:成员内部类(静态、非静态 vs 局部内部类(方法内,代码块内,构造器内

  3. 成员内部类:
    1. 一方面,作为外部类的成员:
    1. 调用外部类的结构
    2. 可以被static修饰
    3. 可以被四种不同的权限修饰
    2. 另一方面,作为一个类:
    1. 类内可以定义属性、方法、构造器等
    2. 可以被final修饰,表示此类不能被继承。言外之意:不使用final,就可以被继承
    3. 可以被abstract修饰

  4. 使用中关注以下三个问题:

    1. 如何实例化成员内部类的对象

      //创建静态成员内部类的实例
      外部类.内部类 对象名 = new 外部类.内部类();
      //创建非静态成员内部类的实例
      先实例化外部类
      外部类.内部类 内部类对象名 = 外部类对象名.new 内部类();
      
    2. 如何在成员内部类中区分调用外部类的结构

    3. 开发中局部内部类的使用

七,异常处理

异常概述与异常体系结构

异常概述

异常体系结构

java.lang.Throwable

  • java.lang.Error:一般不编写针对性代码进行处理
  • java.lang.Exception:可以进行异常的处理
    • 编译时异常(checked)
      • IOException
        • FileNotFoundException
      • ClassNotFoundException
    • 运行时异常(unchecked)
      • NullPointerException 空指针异常
      • ArrayIndexOutOfBoundsException 角标越界
      • ClassCastException 类型转化异常
      • NumberFormatException 数值转化异常
      • InputMismatchException 输入类型不匹配
      • ArithmaticException 算数异常

异常处理方式

一、异常的处理:抓抛模型

过程一:“抛”:

  1. 程序再正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。并将此对象抛出。
  2. 一旦抛出对象以后,其后的代码就不在执行。
  3. 关于异常对象的产生:
    1. 系统自动生成的异常对象
    2. 手动的生成一个异常对象,并抛出(throw)

过程二:“抓”:

  1. 可以理解为异常的处理方式:① try-catch-finally ② throws

二、异常处理的方式一:try-catch-finally

try{
	//可能出现异常的代码
}catch(异常类型1 变量名1){
	//处理异常的方式1
}catch(异常类型2 变量名2){
	//处理异常的方式2
}
......//可以写多个catch
finally{
	//一定会执行的代码
}

说明:

  1. finally是可选的

  2. 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配

  3. 一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况)。继续执行其后的代码

  4. catch中的异常类型如果没有子父类关系,则谁声明在上谁声明在下无所谓

    catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错

  5. 常用的异常对象处理的方式:

    1. String getMessage() //获取异常的信息
    2. printStackTrace() //打印整个错误信息
  6. 在try结构中声明的变量,再出了try结构后,就不能再被调用

体会:

  1. 使用try-catch-finally处理编译时异常,是让程序在编译时就不再报错,但是在运行时仍可能报错。相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现
  2. 开发中,由于运行时异常比较常见,所以我们就不针对运行时异常编写try-catch-finally了。针对编译时异常,我们说一定要考虑异常的处理。

try-catch-finally中finally的使用:

  1. finally是可选的
  2. finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try中有return语句,catch中有return语句等情况。

异常处理的方式二:throws + 异常类型

  1. "throws + 异常类型"写在是方法的声明处。指明此方法执行时,可能会抛出的异常类型。一旦方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。异常代码后续的代码,就不再执行!

  2. 体会:try-catch-finally:真正将异常处理掉了。

    ​ throws的方式只是将异常抛给了方法的调用者。并没有真正的将异常处理掉。

  3. 开发中如何选择使用try-catch-finally 还是使用throws?

    1. 如果父类被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理
    2. 执行的方法a中,先后调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a中可以考虑使用try-catch-finally的方式进行处理。

如何自定义异常类

  1. 继承现有的异常结构:RuntimeException、Exception
  2. 提供全局常量:serialVersionUID
  3. 提供重载的构造器

八,IO流

IO流原理

流的分类

IO流体系

九,多线程

多线程的基本概念

Thread类的常用方法

多线程的创建

方式一:继承Thread类

  1. 创建一个继承与Thread类的子类
  2. 重写Thread类的run() - - -> 将此线程执行的操作声明在run()中
  3. 创建Thread类的子类的对象
  4. 通过此对象调用start():① 启动当前线程 ② 调用当前线程的run()
//例子:用两个线程同时遍历100以内的偶数
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100;i++){
            if (i % 2 == 0){
                System.out.println(i);
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        for (int i = 0; i < 100;i++){
            if (i % 2 == 0){
                System.out.println(i + "*****");
            }
        }
        
        //创建Thread类的匿名子类
        new Thread(){
            @Override
            public void run() {
                super.run();
            }
        }.start();

    }
}

方式二:实现Runnable接口

  1. 创建一个实现了Runnable接口
  2. 实现类去实现Runnable中的抽象方法:run()
  3. 创建实现类的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  5. 通过Thread类的对象调用start()
/**
 * 例子:创建三个窗口卖票,总票数为100
 */

class Thread1 implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + ":" + ticket);
                ticket--;
            }else{
            	break;
            }
        }
    }
}

public class ThreadTest1 {
    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();

        Thread t1 = new Thread(thread1, "窗口一");
        Thread t2 = new Thread(thread1, "窗口二");
        Thread t3 = new Thread(thread1, "窗口三");

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

    }
}

方式三:实现Callable接口

/**
 * 创建多线程的方式三:实现Callable接口
 *
 * 如何理解实现Callable接口的方式比实现Runnable接口的方式更强大
 * 1.call()是有返回值的
 * 2.call()可以抛出异常,被外面的操作捕获,获取异常的信息
 * 3.Callable是支持泛型的
 *
 */
//创建一个实现Callable接口的实现类
class NumThread implements Callable {

    //实现call(),将此线程需要执行的操作声明在call()当中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}


public class ThreadNew {
    public static void main(String[] args) {
        //创建callable实现类的对象
        NumThread numThread = new NumThread();
        //将此callable实现类的对象作为参数传递在FutureTask构造器中,创建FutureTask对象
        FutureTask futureTask = new FutureTask(numThread);
        //将FutureTask对象传递到Thread构造器中,创建Thread对象,并调用start()启动线程
        new Thread(futureTask).start();
        try {
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
            Object num = futureTask.get();
            System.out.println(num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

方式四:使用线程池

class NumberThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

class NumberThread1 implements Callable {

    @Override
    public Object call() throws Exception {
        for (int i = 0; i < 100; i++) {
            if (i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
        return null;
    }
}


public class ThreadPool {
    public static void main(String[] args) {
        //创建一个可重用固定10线程数的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //ThreadPoolExecutor类是ExecutorService接口的实现类,接口本身并没有方法,所以需要强转为接口的实现类对象
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //利用实现类对象可以操作设置线程池的方法
        //service1.setMaximumPoolSize(20);

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

    }
}

线程的生命周期

进程的同步

解决线程安全问题方式一、二:synchronized(同步监视器)

/**
 * 例子:创建三个窗口卖票,总票数为100
 * <p>
 * 问题:
 * 1.卖票过程中出现了重票,错票 - - -> 出现了线程安全问题
 * 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程也参与进来操作车票
 * 3.如何解决:当一个线程a在操作ticket时,其他线程不能参与进来,直到线程a完成操作,这种情况即使线程a出现阻塞,也无法改变
 * 4.在java中,我们通过同步机制,来解决线程的安全问题
 * 5.同步的方式解决了线程的安全问题。 ---- 好处
 *  操作代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低  ---  局限性
 * 解决线程安全问题方式一:同步代码块
 *      synchronized(同步监视器){
 *          需要被同步的代码
 *      }
 * 说明:1.操作共享数据的代码,即为需要被同步的代码
 *      2.共享数据:多个线程共同操作的对象
 *      3.同步监视器,俗称:锁:任何一个一个类的对象,都可以充当锁
 *          要求:多个线程必须共同一把锁
 * 补充:1.在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
 *      2.在继承Thread类创建多线程的方式中,我们可以考虑使用当前类对象充当同步监视器
 *
 * 解决线程安全问题方式二:同步方法
 *  如果操作共享数据的代码完整声明在一个方法中,我们可以将此方法声明为同步方法
 *
 * 关于同步方法的总结:
 * 1.同步方法仍涉及到同步监视器,只是不需要我们显式的声明
 * 2.非静态的同步方法,同步监视器默认为:this
 *    静态的同步方法,同步监视器默认为:当前类本身
 *
 */

解决线程安全问题方式三:Lock锁

class Window1 implements Runnable {

    private int ticket = 100;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock(true);//fair:公平,按顺序进入

    @Override
    public void run() {
        while (true) {
            try {
                //调用lock()
                lock.lock();

                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + ":" + ticket);
                    ticket--;
                } else {
                    break;
                }
            } finally {
                //调用解锁方法:unlock()
                lock.unlock();
            }
        }

    }
}

public class LockTest {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w, "窗口一");
        Thread t2 = new Thread(w, "窗口二");
        Thread t3 = new Thread(w, "窗口三");

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

synchronized和Lock的对比

线程的通信

/**
 * 线程通信:
 * 例子:使用两个线程交替打印1-100
 * 涉及的三个方法:
 * wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
 * notify():一旦执行此方法,就会唤醒wait的一个线程,如果有多个线程被wait,则唤醒优先级高的那个
 * notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
 *
 * 说明:
 * 1.wait()、notify()、notifyAll()这三个方法必须使用在同步代码块或同步方法中
 * 2.wait()、notify()、notifyAll()这三个方法的调用者必须为同步监视器对象
 *      两者不一致会报错
 * 3.wait()、notify()、notifyAll()这三个方法定义在java.lang.Object类中
 */
 
 
 class Number implements Runnable{
    private int number = 1;

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

                //调用notify()释放一个阻塞线程
                notify();

                if (number <= 100){
                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;
                }else {
                    break;
                }
                //使调用wait()的线程进入阻塞状态
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

public class CommunicationTest {
    public static void main(String[] args) {
        Number num = new Number();
        Thread t1 = new Thread(num,"A");
        Thread t2 = new Thread(num,"B");

        t1.start();
        t2.start();

    }
}

Q.E.D.


你笑的时候一晃便是经年