从零开始的Java编程(一)

一、数据类型

1、基本数据类型:
整数型:byte(1个字节)、short(2个字节)、int(4个字节)、long(8个字节)
浮点型:float(4个字节)、double(8个字节)
字符型:char(2个字节)
布尔型:boolean(1个字节)

2、整数默认是int型,带有小数默认是double型。

3、byte / short / char 这三种类型在运算的时候,会被先提升成为int型,然后再进行计算。
例如:

byte num1 = 40, num2 = 50;
byte num3 = num1 + num2;

第二行会报错,因为左边是byte型,右边是int型,可能发生数据溢出。

作为对比:

short s = 1;
s = s + 1;		//会报错
s += 1;			//不会报错,系统会默认强制转换,等价于 s = (short)(s + 1);

再举个例子:

int b = 20;
b += 5.5;		//相当于=====>    b = (int)(b + 5.5);
System.out.println(b);

输出结果是25。

4、如果运算当中有不同类型的数据,那结果的数据类型将会是数据类型范围大的那种。
例如:int num1 = 10; double num2 = 20;
那num1 + num2 结果的数据类型会是double型。

5、任何数据类型和字符串进行连接的时候,结果都会变成字符串。
例如:

String str = "Java";
System.out.println(str + 9);

输出结果是 Java9。

二、左移与右移

1、a <<= 1与a <<1 的区别
(1)运算符号的含义不同
<<=是左移赋值运算符,a<<=b是把a的二进制数左移b位,然后把结果赋值给a;
<<是左移运算符,a<<b是把a的二进制数左移b位。

(2)运算结果不同
a <<= 1表示把左边运算数a的二进制数进行左移1位运算,然后把计算的结果赋值给a,会改变a的值;a << 1表示把左边运算数a的二进制数进行左移1位运算,不改变a的值。

2、无符号右移 >>> 或 >>>=

无符号右移(>>>)跟右移(>>)运算符不一样。

右移不改变数的正负。

对于一个正数,无符号右移不会变成负数(相当于除以2再取整);但是对于一个负数,无符号右移会将负数变成正数。

int i = -4;		
System.out.printf("%-10d %32s\n", i, Integer.toBinaryString(i));		
i >>>= 1;  // 无符号右移1位		
System.out.printf("%-10d %32s\n", i, Integer.toBinaryString(i));
i >>>= 1;		
System.out.printf("%-10d %32s\n", i, Integer.toBinaryString(i));

输出:

-4            11111111111111111111111111111100       
//负数以补码形式存储,-4的补码表示是11111111111111111111111111111100
2147483646     1111111111111111111111111111110       
//无符号右移1位,-4的补码表示的符号位也右移了1位,导致符号位变成0,成为正数
1073741822      111111111111111111111111111110       //再无符号右移1位
int i = 15;
System.out.printf("%-10d %32s\n", i, Integer.toBinaryString(i));
i >>>= 1;
System.out.printf("%-10d %32s\n", i, Integer.toBinaryString(i));

输出:

15                                     1111
7                                       111

再举个例子:

-2147483648 10000000000000000000000000000000       //最小负数的补码表示
1073741824   1000000000000000000000000000000       //符号位右移一位,变成正数
536870912     100000000000000000000000000000

总结:
(1)无符号右移连同符号位一起右移;
(2)对负数进行无符号右移,符号位也一起右移,将会变成正数;
(3)对正数进行若干次无符号右移,得到的永远都是正数或0。

跟无符号右移运算不同的是,无符号左移和左移是一样的。因此java没有无符号左移运算。(<<<和<<<=将报错)

因为无符号右移运算需要考虑符号位的右移,而符号位只存在于二进制表示的最左边,最右边没有。

三、switch语句

switch语句括号当中只能是下列数据类型:
基本数据类型:byte / short / char / int
引用数据类型:String字符串、enum枚举

四、方法

1、方法重载与方法的返回值类型无关。
例如:

public static int sum(int a , int b){
	return a + b;
}

public static double sum(int a , int b){
	return a + b;
}

这样的方法重载是错误的。

2、对于基本类型当中的 boolean 值,Getter 方法要写成 isXxx 的形式,而setXxx规则不变。

public void setRain(boolean temp1){
	rain = temp;
}
public boolean isRain(){
	return rain;
}
public void setNumber(int temp2){
	number = temp2;
}
public int getNumber(){
	return number;
}

五、数组

在这里插入图片描述

1、数组初始化
(1)动态初始化数组(指定长度):在创建数组的时候,直接指定数组当中的数据元素个数。
动态初始化数组格式:
数据类型[ ] 数组名称 = new 数据类型[数组长度];

int[] array1 = new int[10];

动态初始化数组的时候,数组元素会被默认赋值。规则如下:
如果是整数类型,默认值是0;
如果是浮点类型,默认值是0.0;
如果是字符类型,默认值是‘\u0000’;
如果是布尔类型,默认值是false;
如果是引用类型,默认值是null。

(2)静态初始化数组(指定内容):在创建数组的时候,不直接指定数据个数多少,而是直接指定数据的具体内容。
静态初始化数组标准格式:
数据类型[ ] 数组名称 = new 数据类型[ ] {元素1,元素2,…};

int[] array2 = new int[]{5, 10, 15};

静态初始化数组省略格式:
数据类型[ ] 数组名称 = {元素1,元素2,…};

int[] array3 = {5, 10, 15};

静态初始化其实也有默认赋值的过程,不过系统自动马上将默认值替换成了大括号中的具体数值。

数组的引用:

int[] array3 = array2;

这样,array2 和 array3 指向的是同一块区域,修改一方的值,另一方的值也会相应改变。

获取数组的长度,格式:数组名称.length

六、Java内存

Java的内存划分成为5个部分:
1、栈(Stack):存放的是方法中的局部变量。方法一定要在栈当中运行。
局部变量:方法的参数,或者是方法{ }内部的变量。
作用域:一旦超出作用域,立刻从栈内存当中消失。

2、堆(Heap):凡是new出来的东西,都在堆当中。
堆内存里面的东西都有一个地址值:16进制
堆内存里面的数据,都有默认值。规则:
如果是整数类型,默认值是0;
如果是浮点类型,默认值是0.0;
如果是字符类型,默认值是‘\u0000’;
如果是布尔类型,默认值是false;
如果是引用类型,默认值是null。

3、方法区(Method Area):存储.class相关信息,包含方法的信息。

4、本地方法栈(Native Method Stack):与操作系统相关。

5、寄存器(pc Register):与CPU相关。

七、变量

局部变量和成员变量的区别:
(1)定义的位置不一样
局部变量:在方法的内部
成员变量:在方法的外部,在类当中

(2)作用范围不一样
局部变量:作用域是方法内
成员变量:作用域是整个类中

(3)默认值不一样
局部变量:没有默认值,如果要想使用,必须手动进行赋值
成员变量:如果没有赋值,会有默认值,规则和数组一样

(4)内存的位置不一样
局部变量:位于栈内存
成员变量:位于堆内存

(5)生命周期不一样
局部变量:随着方法进栈而创建,随着方法出栈而回收
成员变量:随着对象创建而创建,随着对象回收而回收

八、类

1、一个标准的类通常要拥有下面四个组成部分:
(1)所有的成员变量都要使用 private 关键字修饰
(2)为每一个成员变量编写一对 Getter/Setter方法
(3)编写一个无参数的构造方法
(4)编写一个全参数的构造方法
这样标准的类也叫做 Java Bean。

2、Random类
Random类用来生成随机数字,使用有三个步骤:
(1)导包

import java.util.Random;

(2)创建

Random r = new Random();

(3)使用
获取一个随机的int数字(范围是int的正负所有范围):int num = r.nextInt()
获取一个随机的int数字(参数代表了范围,左闭右开区间):int num = r.nextInt(3)
实际上代表的含义是:[0,3),也就是0~2。
例如:

public class Demo {
    public static void main(String[] args) {
        Random r = new Random();
        int num1 = r.nextInt();
        int num2 = r.nextInt(3);
    }
}

3、ArrayList类
对于ArrayList来说,有一个尖括号< E >代表泛型。
泛型:也就是集合当中的元素都是统一类型。
注意:泛型只能是引用类型,不能是基本类型。

ArrayList 当中的常用方法有:
public boolean add(E e):向集合当中添加元素,参数的类型和泛型一致。返回值代表添加是否成功。
备注:对于 Arraylist集合来说,add添加动作一定是成功的,所以返回值可用可不用。
但是对于其他集合来说,add添加动作不一定成功。
public E get(int index):从集合当中获取元素,参数是索引编号,返回值就是对应位置的元素。
public E remove(intt index):从集合当中删除元素,参数是索引編号,返回值就是被删除掉的元素。
public int size():获取集合的尺寸长度,返回值是集合中包含的元素个数。
import java.util.ArrayList;

public class DemoArrayList {
    public static void main(String[] args) {
        //创建了一个ArrayList集合,集合的名称是list,里面装的全都是String字符串类型的数据
        //备注:从JDK1.7+开始,右侧的尖括号内部可以不写内容,但是<>本身还是要写
        ArrayList<String> list = new ArrayList<>();
        System.out.println(list);

        //向集合中添加一些数据,需要用到add方法
        list.add("张三");
        System.out.println(list);
    }
}

运行结果:

[]
[张三]

如果希望向集合 ArrayList当中存储基本数据类型,必须使用基本类型对应的“包装类”。

基本类型    包装类(引用类型、包装类都位于java.Lang包下)
byte        Byte
short       Short
int         Integer
float       Float
double      Double
char        Character
boolean     Boolean

例如:

ArrayList<Integer> list = new ArrayList<>();

从JDK1.5+开始,支持自动装箱、自动拆箱。
自动装箱:基本类型–>包装类型
自动拆箱:包装类型–>基本类型

九、字符串

1、字符串的特点:
(1)字符串的内容不可改变。
(2)因为字符串不可改变,所以字符串可以共享使用。
(3)字符串效果上相当于是char[]字符数组,但是底层原理是byte[]字节数组。

2、创建字符串的常见3+1种方式。

三种构造方法:
public String():创建一个空白字符串,不含有任何内容。
public String(char[] array):根据字符数组的内容,来创建对应的字符串。
public String(byte[] array):根据字节数組的内容,来创建对应的字符串。

一种直接创建:
String str = "Hello";	//右边直接用双引号

例如:

public class demoString {
    public static void main(String[] args) {
        String str1 = new String();
        System.out.println("第1个字符串:" + str1);

        char[] charArray = {'A', 'B', 'C'};
        String str2 = new String(charArray);
        System.out.println("第2个字符串:" + str2);

        byte[] byteArray = {97, 98, 99};
        String str3 = new String(byteArray);
        System.out.println("第3个字符串:" + str3);
    }
}

输出结果:

第1个字符串:
第2个字符串:ABC
第3个字符串:abc

3、字符串常量池
在这里插入图片描述
4、字符串的比较方法
==是进行对象的地址值比较,如果需要对字符串内容比较,有两个方法:

(1)public boolean equals(Object obj):参数可以是任何对象,只有参数是一个字符串并且内容相同时才
会返回true,否则返回 false。

注意事项:
a、任何对象都能用object进行接收。
b、equals方法具有对称性,也就是a.equals(b)和b.equals(a)效果一样。
c、如果比较双方一个是常量一个是变量,推荐把常量字符串写在前面。

例如:

public class DemoStringEquals {
    public static void main(String[] args) {
        String str1 = "Hello";
        String str2 = "Hello";

        System.out.println(str1.equals(str2));
        System.out.println(str1.equals("Hello"));
        System.out.println("Hello".equals(str1));
    }
}

输出结果:

true
true
true
推荐:"abc".equals(str)  		不推荐:str.equals("abc")

例如:

public class DemoStringEquals {
    public static void main(String[] args) {
        String str = null;

        System.out.println("abc".equals(str));  //输出false
        System.out.println(str.equals("abc"));  //报错,空指针异常NullPointerException
    }
}
(2)public boolean equalsIgnoreCase(string str):忽略大小写,进行内容比较。

例如:

public class DemoStringEquals {
    public static void main(String[] args) {
        String str1 = "Java";
        String str2 = "java";
        System.out.println(str1.equals(str2));
        System.out.println(str1.equalsIgnoreCase(str2));
    }
}

输出结果:

false
true

5、字符串的相关获取方法

public int length():获取字符串的长度。
public String concat(String str):将当前字符串和参数字符串拼接成新的字符串作为返回值。
public char charAt(int index):获取指定索引位置的单个字符。
public int indexOf(String str):查找参数字符串在本字符串当中首次出现的索引位置,如果没有返回-1。

6、字符串的截取方法

public String substring(int index):从参数位置截取一直到字符串末尾,返回新字符串。
public String substring(int begin, int end):从begin开始截取,一直到end结束,返回新字符串。
备注:范围是[begin,end),左闭右开。

例如:

public class DemoSubstring {
    public static void main(String[] args) {
        String str1 = "HelloWorld";

        String str2 = str1.substring(5);
        System.out.println(str2);

        String str3 = str1.substring(4, 7);
        System.out.println(str3);
    }
}

输出结果:

World
oWo

7、字符串的转换相关方法

public char[] toCharArray():将当前字符串拆分成为字符数组作为返回值。
public byte[] getBytes():获得当前字符串底层的字节数组。
public String replace(CharSequence oldString, CharSequence newString):将出现的所有oldString
替换成为newString,返回替换之后的新字符串。
备注:CharSequence 可以接受字符串类型。

例如:

public class DemoStringConvert {
    public static void main(String[] args) {
        //转换成为字符数组
        char[] chars = "Hello".toCharArray();
        System.out.println(chars[0]);
        System.out.println(chars.length);
        System.out.println("============");

        //转换成为字节数组
        byte[] bytes = "abc".getBytes();
        for (int i = 0; i < bytes.length; i++) {
            System.out.println(bytes[i]);
        }
        System.out.println("============");

        //字符串的内容替换
        String str1 = "How do you do?";
        String str2 = str1.replace("o", "*");
        System.out.println(str1);
        System.out.println(str2);
    }
}

输出结果:

H
5
============
97
98
99
============
How do you do?
H*w d* y*u d*?

8、字符串的分割方法

public String[] split(String regex):按照参数的规则,将字符串切分成为若干部分。

例如:

public class DemoStringSplit {
    public static void main(String[] args) {
        String str="aaa,bbb,ccc";
        String[] array = str.split(",");
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i]);
        }
    }
}

输出结果:

aaa
bbb
ccc

十、静态static关键字

1、静态 static 关键字修饰成员变量
如果使用 static 修饰成员变量,则此变量不再属于对象,而是属于所在类。多个对象共享同一份数据。

2、静态 static 关键字修饰成员方法
成员方法使用 static 修饰就成为了静态方法。静态方法不属于对象,而是属于类。

如果没用 static 修饰,那么首先要创建对象,通过对象才能调用。
如果有 static 修饰,不需要创建对象,直接通过类名称调用。

静态变量和静态方法都推荐使用类名称进行调用。
静态变量:类名称.静态变量
静态方法:类名称.静态方法()

注意事项:
(1)静态不能访问非静态。
原因:在内存当中是先有静态内容,然后才有非静态内容。

(2)静态方法中不能使用this。
原因:this 代表当前对象,而在编译时没有当前对象。

public class demoStaticMethod {
    public static void main(String[] args) {
        //对于静态方法来说,可以通过对象名进行调用,也可以直接通过类名称调用
        //对于本类当中的静态方法,可以省略类名称
        method();   //正确,不推荐,这种写法在编译之后也会被javac翻译成"类名称.静态方法名"
        demoStaticMethod.method();    //正确,推荐
    }
    public static void method(){
        System.out.println("调用静态方法");
    }
}

3、静态 static 的内存图
在这里插入图片描述
4、静态代码块

静态代码块的格式:

public class 类名称 {
    static {
        //静态代码块的内容
    }
}

特点:第一次用到本类时,静态代码块执行唯一的一次。
静态内容总是优先于非静态,所以静态代码块比构造方法先执行。

静态代码块的典型用途:用来一次性地对静态成员变量进行赋值。

例如:
Person.java

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

    public Person() {
        System.out.println("执行构造方法!");
    }
}

DemoStatic.java

public class DemoStatic {
    public static void main(String[] args) {
        Person one = new Person();
        Person two = new Person();
    }
}

输出结果:

执行静态代码块!
执行构造方法!
执行构造方法!

十一、Arrays工具类

java.util.Arrays是一个与数组相关的工具类,里面提供了大量静态方法,用来实现数组常见的操作。

public static String tostring(数组):将参数数组変成字符串(按照默认格式:[元素1,元素2,元素3..])。
public static void sort(数组):按照默认升序(从小到大)对数组的元素进行排序。

备注:
1.如果是数值,sort默认按照从小到大升序排列。
2,如果是字符串,sort默认按照字母ASCII码值升序排序。
3.如果是自定义类型,那么这个自定义的类需要有Comparable或者Comparator接口的支持。

例如:

public class DemoArrays {
    public static void main(String[] args) {
        int[] array1={2,1,3,10,6};
        Arrays.sort(array1);
        System.out.println(Arrays.toString(array1));

        String[] array2={"bbb","aaa","ccc"};
        Arrays.sort(array2);
        System.out.println(Arrays.toString(array2));
    }
}

输出结果:

[1, 2, 3, 6, 10]
[aaa, bbb, ccc]

十二、Math工具类

java.lang.Math类是数学相关的工具类,里面提供了大量的静态方法,完成与数学运算相关的操作。

public static double abs(double num):获取绝对值。有多种重载。
public static double ceil(double num):向上取整。
public static double floor(double num):向下取整。
public static long round(double num):四舍五入。

Math.PI代表近似的圆周率常量(double类型)。

例如:

public class DemoMath {
    public static void main(String[] args) {
        System.out.println(Math.abs(-2.5));

        System.out.println(Math.ceil(3.1));

        System.out.println(Math.floor(30.9));

        System.out.println(Math.round(20.4));
        System.out.println(Math.round(20.5));
    }
}

输出结果:

2.5
4.0
30.0
20
21

十三、继承

1、继承主要解决的问题是:共性抽取

父类:也叫基类、超类。
子类:也叫派生类。

2、继承关系的特点:
(1)子类拥有父类的内容。
(2)子类可以拥有专有的内容。

3、在继承的关系中,“子类就是一个父类”。也就是说,子类可以被当做父类看待。
例如父类是员工,子类是讲师,那么“讲师就是一个员工”。关系:is-a。

4、继承的格式

定义父类的格式:(就是一个普通的类定义)
public class 父类名称{
    //...
}

定义子类的格式:
public class 子类名称 entends 父类名称 {
    //...
}

5、继承中成员变量的访问特点
在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:
(1)直接通过子类对象访问成员变量:等号左边是谁,就优先用谁,没有则向上找父类。
(2)间接通过成员方法访问成员变量:该方法属于谁,就优先用谁,没有则向上找父类。

6、区分子类方法中重名的三种变量
局部变量:直接写成员变量名
本类的成员变量:this.成员变量名
父类的成员变量:super.成员变量名

7、继承中成员方法的访问特点
在父子类的继承关系当中,创建子类对象,访问成员方法的规则:创建的对象是谁,就优先用谁,如果没有则向上找父类。

8、继承中方法的覆盖重写
方法覆盖重写的注意事项:
(1)必须保证父子类之间方法的名称相同,参数列表也相同。
可以在方法前面写上@override来检测是不是有效的正确覆盖重写。

(2)子类方法的返回值必须小于等于父类方法的返回值范围。
提示:java.lang.Object类是所有类的最高父类,java.lang.String就是Object的子类。

(3)子类方法的权限必须大于等于父类方法的权限。
提示:public > protected > ( default) > private
备注:(default)不是关键字default,而是什么都不写,留空。

9、继承中构造方法的访问特点
(1)创建子类对象时,先调用父类构造方法,后调用子类构造方法。
(2)子类构造方法通过super关键字来调用父类的构造方法。没有写则默认是super()。
(3)通过super调用父类构造方法,必须写在子类构造方法的第一个语句。并且子类构造方法只能调用一次父类构造方法。

10、this关键字的三种用法
this关键字访问本类内容有三种情况:
(1)在本类的成员方法中,访问本类的成员变量。
(2)在本类的成员方法中,访问本类的另一个成员方法。
(3)在本类的构造方法中,访问本类的另一个构造方法。
在第三种用法中要注意:
A.this(…)调用必须是构造方法的第一个语句,且只能有一个。
B.不能同时调用super 和 this两种构造方法。

11、super与this关键字图解
在这里插入图片描述
12、继承的三个特点
在这里插入图片描述

十四、抽象

1、抽象的概念在这里插入图片描述
2、抽象方法和抽象类的格式
抽象方法:用 abstract 关键字修饰,没有方法体。抽象方法所在的类必须是抽象类。
抽象类:用 abstract 关键字修饰。

例如:

public abstract classs Animal {
    public abstract void eat();
}

3、抽象方法和抽象类的使用
如何使用抽象类和抽象方法:
(1)不能直接创建抽象类对象。
(2)必须用一个子类来继承抽象父类。
(3)子类必须覆盖重写抽象父类当中所有的抽象方法。
(4)创建子类对象进行使用。

4、抽象方法和抽象类的注意事项
(1)抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
解释:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

(2)抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
解释:子类的构造方法中,有默认的super(),需要访问父类构造方法。

(3)抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
解释:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

(4)抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
解释:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

十五、接口

1、接口概述
接口就是多个类的公共规范标准。接口是一种引用数据类型

2、定义接口的基本格式

public interface 接口名称 {
	//接口内容
}

如果是Java 7,那么接口中可以包含的内容有:
(1)常量
(2)抽象方法

如果是Java8,还包含有:
(3)默认方法
(4)静态方法

如果是Java9,还包含有:
(5)私有方法

3、接口的抽象方法
(1)接口的抽象方法定义
在任何版本的Java中,接口都能定义抽象方法。
格式:

public abstract 返回值类型 方法名称(参数列表);

注意事项:
a. 接口当中的抽象方法,修饰符是固定的关键字: public abstract
b. 这两个关键字修饰符,可以省略。

例如:

public interface MyInterface {
    public abstract void methodAbs1();
    
    abstract void methodAbs2();
    
    public void methodAbs3();
    
    void methodAbs4();
}

(2)接口的抽象方法使用
接口使用步骤:
a. 接口不能直接使用,必须有一个“实现类”来“实现”该接口。
格式:

public class 实现类名称 implements 接口名称 (
	//...
}

b. 接口的实现类必须实现接口中所有的抽象方法。

c. 创建实现类的对象,进行使用。

注意事项:
如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类必须是抽象类。

4、接口的默认方法
(1)定义格式:

public default 返回值类型 方法名称(参数列表) {
	方法体
}

备注:接口当中的默认方法,可以解决接口升级的问题。

(2)方法使用
a. 接口的默认方法,可以通过接口实现类对象直接调用。
b. 接口的默认方法,也可以被接口实现类进行覆盖重写。

例如:
MyInterfaceDefault.java

public interface MyInterfaceDefault {
    //抽象方法
    public abstract void methodAbs();

    //新添加的默认方法
    public default void methodDefault() {
        System.out.println("这是新添加的默认方法");
    }
}

MyInterfaceDefaultA.java

public class MyInterfaceDefaultA implements MyInterfaceDefault {
    @Override
    public void methodAbs() {
        System.out.println("实现了抽象方法,AAA");
    }
}

MyInterfaceDefaultB.java

public class MyInterfaceDefaultB implements MyInterfaceDefault {
    @Override
    public void methodAbs() {
        System.out.println("实现了抽象方法,BBB");
    }
    
    @Override
    public void methodDefault() {
        System.out.println("实现类B覆盖重写了接口的默认方法");
    }
}

DemoInterface.java

public class DemoInterface {
    public static void main(String[] args) {
        //创建实现类对象
        MyInterfaceDefaultA a = new MyInterfaceDefalutA();
        a.methodAbs();  //调用抽象方法

        //调用默认方法,如果实现类当中没有,会向上找接口
        a.methodDefault();  //调用新添加的默认方法
        System.out.println("========");

        MyInterfaceDefaultB b = new MyInterfaceDefaultB();
        b.methodAbs();
        b.methodDefault();
    }
}

输出结果:

实现了抽象方法,AAA
这是新添加的默认方法
========
实现了抽象方法,BBB
实现类B覆盖重写了接口的默认方法

5、接口的静态方法
(1)定义格式

public static 返回值类型 方法名称(参数列表) {
	方法体
}

(2)方法使用
注意事项:不能通过接口实现类的对象来调用接口当中的静态方法。
正确用法:通过接口名称,直接调用其中的静态方法。

格式:接口名称.静态方法名(参数);

6、接口的私有方法
从Java9开始,接口当中允许定义私有方法。私有方法只有接口自己才能调用,不能被实现类或别人使用。
(1)普通私有方法,解决多个默认方法之间重复代码的问题。
格式:

private 返回值类型 方法名称(参数列表) {
	//方法体
}

(2)静态私有方法,解决多个静态方法之间重复代码的问题。
格式:

private static 返回值类型 方法名称(参数列表) {
	//方法体
}

7、接口的常量
定义格式:

public static final 数据类型 常量名称 = 数据值;
//使用final关键字修饰,说明不可改变

注意事项:
(1)接口中常量的定义可以省略public static final 这三个修饰符。
(2)接口中的常量必须进行赋值。
(3)接口中常量的命名规则推荐:使用大写字母,并用下划线进行分隔。

8、注意事项
(1)接口不能有静态代码块和构造方法。
(2)一个类只有一个直接父类,但可以实现多个接口。
格式:

public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
	//覆盖重写所有抽象方法
}

(3)如果实现类所实现的多个接口中存在一样的抽象方法,那么只需要覆盖重写一次即可。
(4)如果实现类没有覆盖重写所有接口中的所有抽象方法,那么实现类必须是抽象类。
(5)如果实现类所实现的多个接口中存在一样的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写。
(6)对于一个类,如果直接父类中的方法和接口中的默认方法产生冲突,优先使用直接父类的方法。
(7)对于一个有多个父接口的接口,如果多个父接口中的抽象方法有重复,不会产生错误;但是如果多个父接口中的默认方法有重复,那么子接口必须进行默认方法的覆盖重写。

十六、多态

1、多态的概述
在这里插入图片描述
2、多态的格式与使用
代码中体现多态性,其实就是一句话:父类引用指向子类对象。

格式:
父类名称 对象名 = new 子类名称();
或者
接口名称 对象名 = new 实现类名称();

例如:
Fu.java

public class Fu {
    public void method() {
        System.out.println("父类方法");
    }
    
    public void methodFu() {
        System.out.println("父类特有方法");
    }
}

Zi.java

public class Zi extends Fu {
    @Override
    public void method() {
        System.out.println("子类方法");
    }
}

DemoMulti.java

public class DemoMulti {
    public static void main(String[] args) {
		Fu obj = new Zi();
		obj.method();
		obj.methodFu();
    }
}

输出结果:

子类方法
父类特有方法

3、多态中成员变量的使用特点
访问成员变量的两种方式:
(1)直接通过对象名称访问成员变量:看等号左边是谁,就优先用谁,没有则向上找。
(2)间接通过成员方法访问成员变量:看该方法属于谁,就优先用谁,没有则向上找。

例如:
Fu.java

public class Fu {
	int num = 10;
    public void showNum() {
        System.out.println(num);
    }
}

Zi.java

public class Zi extends Fu {
	int num = 20;
	int age = 10;
    @Override
    public void showNum() {
        System.out.println(num);
    }
}

DemoMultiField.java

public class DemoMultiField {
    public static void main(String[] args) {
    	//使用多态的写法,父类引用指向子类对象
		Fu obj = new Zi();
		System.out.println(obj.num);	//父:10
		//System.out.println(obj.age);	//错误写法!
		System.out.println("=============");

		//子类没有覆盖重写,输出就是父:10
		//子类如果覆盖重写,输出就是子:20
		obj.showNum();
    }
}

输出结果:

10
=============
20

4、多态中成员方法的使用特点
在多态的代码当中,成员方法的访问规则是:看new的是谁,就优先用谁,没有则向上找。
口诀:编译看左边,运行看右边。

对比一下:
成员变量:编译看左边,运行还看左边。
成员方法:编译看左边,运行看右边。

例如:
Fu.java

public class Fu {
    public void method() {
        System.out.println("父类方法");
    }
    
    public void methodFu() {
        System.out.println("父类特有方法");
    }
}

Zi.java

public class Zi extends Fu {
    @Override
    public void method() {
        System.out.println("子类方法");
    }

    public void methodZi() {
        System.out.println("子类特有方法");
    }
}

DemoMultiMethod.java

public class DemoMultiField {
    public static void main(String[] args) {
		Fu obj = new Zi();	//多态
		obj.method();	//父子都有,优先用子
		obj.methodFu();	//子类没有,向上找,父类有		
		
		//编译看左边,左边是Fu,Fu当中没有methodZi方法,所以编译报错
		//obj.methodZi();	//错误写法!
    }
}

输出结果:

子类方法
父类特有方法

5、使用多态的好处
在这里插入图片描述
6、对象的向上和向下转型
在这里插入图片描述
向上转型一定是安全的,但是也有一个弊端:对象一旦向上转型为父类,那么就无法调用子类原本特有的内容。

解决方案:用对象的向下转型进行【还原】。

例如:
Animal.java

public abstract class Animal {
	public abstract void eat();
}

Cat.java

public class Cat extends Animal {
    @Override	
	public void eat() {
        System.out.println("猫吃鱼");		
	}

	//子类特有方法
	public void catchMouse() {
        System.out.println("猫抓老鼠");		
	}
}

Dog.java

public class Dog extends Animal {
    @Override	
	public void eat() {
        System.out.println("狗吃骨头");		
	}

	//子类特有方法
	public void watchHouse() {
        System.out.println("狗看家");		
	}
}

DemoMultiMethod.java

public class DemoMain {
    public static void main(String[] args) {
    	//对象的向上转型,就是父类引用指向子类对象
		Animal animal = new Cat();	//创建的是一只猫
		animal.eat();	//猫吃鱼
		
		//animal.catchMouse();	//错误写法!
		Cat cat = (Cat) animal;
		cat.catchMouse();	//猫抓老鼠

		//下面是错误的转型
		//本来new的是一只猫,现在要当作狗
		//这种是错误写法,编译虽然不会报错,但是运行会出现异常
		//java.lang.ClassCastException,类转换异常
		Dog dog = (Dog) animal;
    }
}

输出结果:
在这里插入图片描述
7、用instanceof关键字进行类型判断
如何才能知道一个父类引用的对象,本来是什么子类?用 instanceof 关键字可以进行类型判断。
格式:对象 instanceof 类名称
这将会得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实例。

例如:

public class DemoInstanceof {
    public static void main(String[] args) {
		giveMeAPet(new Dog());
    }
    
    public static void giveMeAPet(Animal animal) {
		//如果希望调用子类特有方法,需要向下转型
		//判断一下父类引用animal本来是不是Dog
		if(animal instanceof Dog){
			Dog dog = (Dog) animal;
			dog.watchHouse();
		}
		//判断一下父类引用animal本来是不是Cat		
		if(animal instanceof Cat){
			Cat cat = (Cat) animal;
			Cat.catchMouse();
		}		
    }    
}

8、接口作为方法的参数和返回值

例如:

import java.util.*;

/*
java.util.List正是ArrayList所实现的接口
 */
public class DemoInterface {
    public static void main(String[] args) {
        //左边是接口名称,右边是实现类名称,这就是多态写法
        List<String> list = new ArrayList<>();

        List<String> result = addNames(list);
        for (int i = 0; i < result.size(); i++) {
            System.out.println(result.get(i));
        }
    }

    public static List<String> addNames(List<String> list) {
        list.add("小明");
        list.add("小红");
        return list;
    }
}
小明
小红

十七、final关键字

final关键字代表最终、不可改变的。

常见四种用法:
(1)可以用来修饰一个类
(2)可以用来修饰一个方法
(3)还可以用来修饰一个局部变量
(4)还可以用来修饰一个成员变量

1、final关键字用于修饰类
当 final 关键字用来修饰一个类的时候,格式:

public final class 类名称 {
	//...
}

含义:当前这个类不能有子类。
注意:一个类如果是 final 的,那么其所有的成员方法都无法进行覆盖重写。

2、final关键字用于修饰成员方法
当 final 关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重写。

格式:

修饰符 final 返回值类型 方法名称(参数列表) {
	//方法体
}

注意事项:
对于类、方法来说,abstract关键字和final关键字不能同时使用,因为会产生矛盾。

3、final关键字用于修饰局部变量
使用 final 用来修饰局部变量,那这个变量就不能进行更改,“一次赋值,终生不变”。

对于基本类型来说,不可变说的是变量当中的数据不可改变。
对于引用类型来说,不可变说的是变量当中的地址值不可改变。

例如:
Student.java

public class Student {
    private String name;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

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

DemoFinal

public class DemoFinal {
    public static void main(String[] args) {
        final int num = 10;

//      num = 20;     //错误写法,数据不可改变

        Student stu1 = new Student("小明");
        System.out.println(stu1);
        System.out.println(stu1.getName());

        stu1 = new Student("小红");
        System.out.println(stu1);
        System.out.println(stu1.getName());
        System.out.println("=====================");

        final Student stu2 = new Student("小明明");
//      stu2 = new Student("小红红");	//错误写法!final的引用类型常量,其中的地址不可改变
        System.out.println(stu2.getName());
        stu2.setName("小明明明");	//stu2的地址值不能改变,但是stu2所指向的对象可以改变
        System.out.println(stu2.getName());
    }
}

输出结果:

pratice1.Student@b4c966a
小明
pratice1.Student@2f4d3709
小红
=====================
小明明
小明明明

4、final关键字用于修饰成员变量
如果使用final关键字修饰成员变量,那么这个变量是不可变的。要么使用直接赋值,要么通过构造方法赋值。若通过构造方法赋值,必须保证类当中所有重载的构造方法都会对此成员变量进行赋值。

例如:

public class Person {
    private final String name/* = "小明"*/;

    public Person() {
    	name = "小红";
    }

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

    public String getName() {
        return name;
    }

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

十八、四种权限修饰符

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

十九、内部类

如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类。
例如:身体和心脏的关系。又如:汽车和发动机的关系。

分类:
(1)成员内部类
(2)局部内部类(包含匿名内部类)

一、成员内部类

成员内部类的定义格式:

修饰符 class 外部类名称 {
	修饰符 class内部类名称 {
		//...
	}
	//...
}

注意:内用外,随意访问;外用内,需要内部类对象。

如何使用成员内部类?有两种方式:
1.间接方式:在外部类的方法当中,使用内部类:然后main只是调用外部类的方法。
2.直接方式,公式:类名称 对象名 = new 类名称();
【外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();】

例如:
Body.java

public class Body {  //外部类

    public class Heart {   //成员内部类
        //内部类的方法
        public void beat() {
            System.out.println("心脏跳动");
            System.out.println("我叫:" + name); //正确写法
        }
    }

    //外部类的成员变量
    private String name;

    //外部类的方法
    public void methodBody() {
        System.out.println("外部类的方法");
        new Heart().beat();
    }

    public String getName() {
        return name;
    }

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

DemoInnerClass.java

public class DemoInnerClass {
    public static void main(String[] args) {
        Body body = new Body();   //外部类的对象
        body.methodBody();	//通过外部类的对象,调用外部类的方法,里面间接在使用内部类Heart
        System.out.println("===================");

        //按照公式写:
        Body.Heart heart = new Body().new Heart();
        heart.beat();
    }
}

输出结果:

外部类的方法
心脏跳动
我叫:null
===================
心脏跳动
我叫:null

二、内部类的同名变量访问

如果出现了重名,那么调用外部类变量的格式为:外部类名称.this.外部类成员变量名

例如:
Outer.java

public class Outer {  //外部类
    int num = 10;   //外部类的成员变量
    
    public class Inner {
        int num = 20;   //内部类的成员变量
        
        public void methodInner() {
            int num = 30;   //内部类方法的局部变量
            System.out.println(num);    //局部变量,就近原则
            System.out.println(this.num);   //内部类的成员变量
            System.out.println(Outer.this.num);   //外部类的成员变量
        }
    }
}

DemoInnerClass.java

public class DemoInnerClass {
    public static void main(String[] args) {
		//外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
		Outer.Inner obj = new Outer().new Inner();
		obj.methodInner();
    }
}

三、局部内部类

如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。
“局部”:只有当前所属的方法才能使用它,出了这个方法外面就不能用了。

定义格式:

修饰符 class 外部类名称 {
	修饰符 返回值类型 外部类方法名称(参数列表) {
		class 局部内部类名称{
			//...
		}
	}
}

例如:

public class Outer {
    public void methodOuter(){
        class Inner{    //局部内部类
            int num = 10;
            public void methodInner(){
                System.out.println(num);    //10
            }
        }
        
        Inner inner = new Inner();
        inner.methodInner();
    }
}

小结一下类的权限修饰符:
public > protected > (default) > private
定义一个类的时候,权限修饰符规则:
1.外部类: public / (default)
2.成员内部类: public / protected / (default) / private
3.局部内部类:什么都不能写

四、局部内部类的 final 问题

局部内部类如果要访问所在方法的局部变量,那么这个局部变量必须是【有效 final的】。

备注:从Java8+开始,只要局部变量不変,那么 final 关键字可以省略。

原因:
1.new出来的对象在堆内存当中。
2.局部变量是跟着方法走的,在栈内存当中。
3.方法运行结束之后,立刻出栈,局部变量会立刻消失。
4.但是new出来的对象会在堆当中持续存在,直到垃圾回收。

例如:

public class MyOuter {
    public void methodOuter() {
        int num = 10;   //所在方法的局部变量

        class MyInner {
            public void methodInner() {
                System.out.println(num);
            }
        }
    }
}

五、匿名内部类

如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,
那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】。

匿名内部类的定义格式:

接口名称 对象名 = new 接口名称() {
    //覆盖重写所有抽象方法
}

例如:
MyInterface.java

public interface MyInterface {
    void method();  //抽象方法
}

DemoMain.java

public class DemoMain {
    public static void main(String[] args) {
        //使用匿名内部类
        MyInterface obj = new MyInterface() {
            @Override
            public void method() {
                System.out.println("匿名内部类实现了方法!");
            }
        };
        obj.method();
    }
}

对格式 “new 接口名称() {…}” 进行解析:
1.new代表创建对象的动作
2.接口名称就是匿名内部类需要实现哪个接口
3.{…}这才是匿名内部类的内容

另外还要注意几点问题:
1.匿名内部类,在【创建对象】的时候,只能使用唯一一次。
如果希望多次创建对象,而且类的内容一样的话,那么就必须使用单独定义的实现类了。

2.匿名对象,在【调用方法】的时候,只能调用唯一一次。
如果希望同一个对象,调用多次方法,那么必须给对象起个名字。

3.匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】
强调:匿名内部类和匿名对象不是一回事!!

例如:
MyInterface.java

public interface MyInterface {
    void method1();  //抽象方法

    void method2();
}

DemoMain.java

public class DemoMain {
    public static void main(String[] args) {
        //使用匿名内部类,不是匿名对象,对象名称叫objA
        MyInterface objA = new MyInterface() {
            @Override
            public void method1() {
                System.out.println("匿名内部类实现了方法!111-A");
            }

            @Override
            public void method2() {
                System.out.println("匿名内部类实现了方法!222-A");
            }
        };
        objA.method1();
        objA.method2();
        System.out.println("====================");

        //使用了匿名内部类,而且省略了对象名称,也是匿名对象
        new MyInterface() {
            @Override
            public void method1() {
                System.out.println("匿名内部类实现了方法!111-B");
            }
            @Override
            public void method2() {
                System.out.println("匿名内部类实现了方法!222-B");
            }
        }.method1();
        //因为匿名对象无法调用第二次方法,所以需要再创建一个匿名内部类的匿名对象
        new MyInterface() {
            @Override
            public void method1() {
                System.out.println("匿名内部类实现了方法!111-B");
            }
            @Override
            public void method2() {
                System.out.println("匿名内部类实现了方法!222-B");
            }
        }.method2();
    }
}
匿名内部类实现了方法!111-A
匿名内部类实现了方法!222-A
====================
匿名内部类实现了方法!111-B
匿名内部类实现了方法!222-B

二十、Objects类

JDK7添加了一个Objects工具类,它提供了一些方法来操作对象,它由一些静态的实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的),用于计算对象的hashcode、返回对象的字符串表示形式、比较两个对象。

在比较两个对象的时候,Object的equals方法容易抛出空指针异常,而Objects类中的equals方法就优化了这个问题。方法如下:

public static boolean equals(object a, object b):判断两个对象是否相等。

源码:

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}

二十一、Object类&Objects类总结

一、Object类
1、Object类介绍
Objet类是所有类的父类。一个类都会直接或间接地继承该类。该类中提供了一些非常常用的方法。

2、toString()方法
A:作用:打印对象的信息
B:重写前:打印的是包名类名@地址值
C:重写后:打印的是对象中的属性值

3、equals()方法
A:作用:比较两个对象
B:重写前:比较的是对象的地址值
C:重写后:比较的是对象中的属性值

二、Objects类
1、equals()方法
比较两个对象是否相同,但是加了一些健壮性的判断。

二十二、Date类的构造方法和成员方法

1、Data类的空参数构造方法

Date();	//获取的是当前系统的日期和时间

例如:

Date date = new Date();

2、Data类的带参数构造方法

Date(long date);	//参数是毫秒值,把毫秒值转换为Date日期

例如:

Date date = new Date(158588558L);

3、Data类的成员方法

long getTime()  把日期转换为毫秒(相当于System.currentTimeMillis())
//返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数

例如:

Date date = new Date();
long time = date.getTime();

二十三、DateFormat类&SimpleDateFormat类

一、DateFormat类
java.text.DateFormat:是日期/时间格式化子类的抽象类

作用:格式化(也就是日期 -> 文本)、解析(文本 -> 日期)

成员方法:

String format(Date date)		按照指定的模式,把Date日期格式化为符合模式的字符串
Date parse(String source)		把符合模式的字符串,解析为Date日期

注意,parse原声明为:
	public Date parse(String source) throws ParseException
parse方法声明了一个异常叫ParseException解析异常
如果字符串和构造方法中的模式不一样,那么程序就会抛出此异常
调用了一个抛出异常的方法,就必须处理这个异常,要么throws继续声明抛出这个异常,要么try...catch自己处理这个异常

DateFormat类是一个抽象类,无法直接创建对象使用,可以使用DateFormat的子类

二、SimpleDateFormat类
java.text.SimpleDateFormat extends DateFormat

构造方法:

SimpleDateFormat(String pattern)	用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat
参数:String pattern:传递指定的模式
模式:区分大小写
	y		年
	M		月
	d		日
	H		时
	m		分
	s		秒

写对应的模式,会把模式替换为对应的日期和时间,例如:
“yyyy-MM-dd HH:mm:ss”
“yyyy年MM月dd日 HH时mm分ss秒”

注意:模式中的字母不能改变,连接模式的符号可以改变

DateFormat类的format方法和parse方法举例:

    private static void demo() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日  HH时mm分ss秒");
        Date date = new Date();
        String text = sdf.format(date);
    }
    private static void demo() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日  HH时mm分ss秒");
        Date date = sdf.parse("2020年7月21日  22时22分22秒");
    }

二十四、Calendar类

一、Calendar类介绍
java.util.Calendar类:日历类

Calendar类是一个抽象类,里边提供了很多操作日历字段的方法(YEAR、MONTH、DAY_OF_MONTH、HOUR)

Calendar类无法直接创建对象使用,里边有一个静态方法叫getInstance(),该方法返回了Calendar类的子类对象。

static Calendar getInstance() 使用默认时区和语言环境获得一个日历。

例如:

Calendar c = Calendar.getInstance();	//多态

二、Calendar类的常用成员方法
Calendar类的成员方法:

-public int get(int field):返回给定日历字段的值。
-public void set(int field,int value):将给定的日历字段设置为给定值。
-public abstract void add(int field,int amount):根据日历的规则,为给定的日历字段添加或减去指定的时间量。
-public Date getTime():返回一个表示此Calendar时间值(从历元到现在的毫秒偏移量)的Date对象。

成员方法的参数:

int field:日历类的字段,可以使用Calendar类的静态成员变量获取
public static final int YEAR = 1;年
public static final int MONTH = 2;月
public static final int DATE = 5;月中的某一天
public static final int DAY_OF_MONTH = 5;月中的某一天
public static final int HOUR = 10;时
public static final int MINUTE = 12;分
public static final int SECOND = 13;秒

三、常用成员方法详细说明

public int get(int field):返回给定日历字段的值
参数:传递指定的日历字段(YEAR,MONTH...)
返回值:日历字段代表具体的值

举例:

private static void demo1() {
	//使用getInstance方法获取Calendar对象
	Calendar c = Calendar.getInstance();
	int year = c.get(Calendar.YEAR);
	int month = c.get(Calendar.MONTH);
	//月份是按西方的标准,西方的月份是0-11,东方是1-12
}
public void set(int field,int value):将给定的日历字段设置为给定值。
参数:
int field:传递指定的日历字段(YEAR,MONTH...)
int value:传递的字段设置的具体的值

举例:

private static void demo2() {
	//使用getInstance方法获取Calendar对象
	Calendar c = Calendar.getInstance();

	//设置年为9999
	c.set(Calendar.YEAR,9999);
	//设置月为9
	c.set(Calendar.MONTH,9999);
	//设置日为9	
	c.set(Calendar.DATE,9999);	

	//同时设置年月日,可以使用set的重载方法
	c.set(8888,8,8);
}

二十五、System类 & StringBuilder类

1、System类的常用方法
在这里插入图片描述
在这里插入图片描述

package lianxi;

import java.util.Arrays;

public class lianxi1 {
    public static void main(String[] args) {
        demo1();
        demo2();
    }

    private static void demo1() {
        //程序执行前,获取一次毫秒值
        long s = System.currentTimeMillis();
        //执行for循环
        for (int i = 0; i < 9999; i++) {
            System.out.println(i);
        }
        //程序执行后,再获取一次毫秒值
        long e = System.currentTimeMillis();
        System.out.println("程序共耗时:" + (e - s) + "毫秒");
    }

    private static void demo2() {
        //定义源数组
        int[] src = {1,2,3,4,5};
        //定义目标数组
        int[] dest = {6,7,8,9,10};
        System.out.println("复制前:"+ Arrays.toString(dest));
        //使用System类的方法arraycopy将src数组中前3个元素复制到dest数组的前3个位置上
        System.arraycopy(src,0,dest,0,3);
        System.out.println("复制后:"+ Arrays.toString(dest));
    }
}
程序共耗时:71毫秒
复制前:[6, 7, 8, 9, 10]
复制后:[1, 2, 3, 9, 10]

2、StringBuilder的原理
在这里插入图片描述
3、StringBuilder的构造方法和常用方法
在这里插入图片描述

package lianxi;

import java.util.Arrays;

public class lianxi1 {
    public static void main(String[] args) {
        StringBuilder bu1 = new StringBuilder();
        System.out.println("bu1:" + bu1); //bu1:

        StringBuilder bu2 = new StringBuilder("abc");
        System.out.println("bu2:" + bu2); //bu2:abc
    }
}
bu1:
bu2:abc

在这里插入图片描述

package lianxi;

import java.util.Arrays;

public class lianxi1 {
    public static void main(String[] args) {
        //创建StringBuilder对象
        StringBuilder bu1 = new StringBuilder();
        //使用append方法往StringBuilder中添加数据
        //append方法返回的是this,调用方法的对象bu1
        StringBuilder bu2 = bu1.append("abc");
        System.out.println(bu1);//abc
        System.out.println(bu2);//abc
        System.out.println(bu1 == bu2);//true 两个对象是同一个对象

        //使用append方法无需接收返回值
        bu1.append("abc");
        bu1.append(1);
        bu1.append(true);
        bu1.append(8.8);
        bu1.append('中');
        System.out.println(bu1);//abc1true8.8中

        /*
            链式编程:方法的返回值是一个对象,可以根据对象继续调用方法
         */
        StringBuilder bu3 = new StringBuilder();
        bu3.append("abc").append(1).append(true).append(8.8).append('中');
        System.out.println(bu3);
    }
}
abc
abc
true
abcabc1true8.8中
abc1true8.8中

reverse方法:反转内容

package lianxi;

import java.util.Arrays;

public class lianxi1 {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        sb.append("hello").append("world").append("java");
        System.out.println(sb);
        sb.reverse();
        System.out.println(sb);
    }
}
helloworldjava
avajdlrowolleh
package lianxi;

import java.util.Arrays;

/*
    StringBuilder和String可以相互转换:
        String->StringBuilder:可以使用StringBuilder的构造方法
            StringBuilder(String str) 构造一个字符串生成器,并初始化为指定的字符串内容。
        StringBuilder->String:可以使用StringBuilder中的toString方法
            public String toString():将当前StringBuilder对象转换为String对象。
 */
public class lianxi1 {
    public static void main(String[] args) {
        //String->StringBuilder
        String str = "hello";
        System.out.println("str:" + str);
        StringBuilder bu = new StringBuilder(str);
        //往StringBuilder中添加数据
        bu.append("world");
        System.out.println("bu:" + bu);

        //StringBuilder->String
        String s = bu.toString();
        System.out.println("s:" + s);
    }
}
str:hello
bu:helloworld
s:helloworld

二十六、包装类

1、包装类的概念
在这里插入图片描述
在这里插入图片描述
2、装箱与拆箱
在这里插入图片描述
在这里插入图片描述

package lianxi;

import java.util.Arrays;

public class lianxi1 {
    public static void main(String[] args) {
        //装箱:把基本类型的数据,包装到包装类中(基本类型的数据->包装类)
        //构造方法
        Integer in1 = new Integer(1);//方法上有横线,说明方法过时了
        System.out.println(in1);//1 重写了toString方法

        Integer in2 = new Integer("1");
        System.out.println(in2);//1

        //静态方法
        Integer in3 = Integer.valueOf(1);
        System.out.println(in3);

        //Integer in4 = Integer.valueOf("a");//NumberFormatException数字格式化异常
        Integer in4 = Integer.valueOf("1");
        System.out.println(in4);

        //拆箱:在包装类中取出基本类型的数据(包装类->基本类型的数据)
        int i = in1.intValue();
        System.out.println(i);
    }
}
1
1
1
1
1

3、自动装箱与自动拆箱
在这里插入图片描述

package lianxi;

import java.util.ArrayList;
import java.util.Arrays;

/*
    自动装箱与自动拆箱:
        基本类型的数据和包装类之间可以自动的相互转换
        JDK1.5之后出现的新特性
 */
public class lianxi1 {
    public static void main(String[] args) {
        /*
            自动装箱:直接把int类型的整数赋值给包装类
            Integer in = 1; 就相当于Integer in = new Integer(1);
         */
        Integer in = 1;

        /*
            自动拆箱:in是包装类,无法直接参与运算,可以自动转换为基本类型的数据,再参与计算
            in + 2; 就相当于 in.intValue() + 2 = 3;
            in = in + 2;  就相当于  in = new Integer(3) 自动装箱
         */
        in = in + 2;
        System.out.println(in);

        //ArrayList集合无法直接存储整数,可以存储Integer包装类
        ArrayList<Integer> list = new ArrayList<>();

        list.add(1);//自动装箱  list.add(new Integer(1));

        int a = list.get(0);//自动拆箱  list.get(0).intValue();
    }
}
3

4、基本类型与字符串类型之间的相互转换
在这里插入图片描述
在这里插入图片描述

package lianxi;

/*
    基本类型与字符串之间的转换
    基本类型-->字符串
        1.基本类型数据的值+“” 最简单的方式(工作中常用)
        2.使用包装类中的静态方法
            static String toString(int i)   返回一个表示指定整数的String对象。
        3.使用String类中的静态方法
            static String ValueOf(int  i)   返回int参数的字符串表示形式。
    字符串-->基本类型
        使用包装类的静态方法parseXX("字符串")
            Integer类:   static int parseInt(String  s)
            Double类:    static double parseDouble(String s)
            ...
 */
public class lianxi1 {
    public static void main(String[] args) {
        //基本类型-->字符串
        String s1 = 100+"";
        System.out.println(s1+200);//100200

        String s2 = Integer.toString(100);
        System.out.println(s2+200);//100200

        String s3 = String.valueOf(100);
        System.out.println(s3+200);//100200

        //字符串-->基本类型
        int i = Integer.parseInt("100");
        System.out.println(i=200);

        //int i2 = Integer.parseInt("a");//NumberFormatException: For input string: "a"   数字格式化异常
        //System.out.println(i2);
    }
}
100200
100200
100200
200

二十七、Collection集合

1、集合概述
在这里插入图片描述
2、集合框架
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、Collection常用功能
在这里插入图片描述

package lianxi;

import java.util.ArrayList;
import java.util.Collection;

public class lianxi1 {
    public static void main(String[] args) {
        //创建集合对象,可以使用多态
        Collection<String> coll = new ArrayList<>();
        System.out.println(coll);//[]   重写了toString方法

        /*
            public boolean add(E e):    把给定的对象添加到当前集合中
            返回值是一个boolean值,一般都返回true,所以可以不用接收
         */
        boolean b1 = coll.add("张三");
        System.out.println("b1:" + b1);//b1:true
        System.out.println(coll);//[张三]
        coll.add("李四");
        coll.add("赵六");
        coll.add("田七");
        System.out.println(coll);//[张三, 李四, 赵六, 田七]

        /*
            public boolean remove(E e): 把给定的对象在当前集合中删除
            返回值是一个boolean值,集合中存在元素,删除元素,返回true
                                 集合中不存在元素,删除元素,返回false
         */
        boolean b2 = coll.remove("赵六");
        System.out.println("b2:" + b2);//b2:true

        boolean b3 = coll.remove("赵四");
        System.out.println("b3:" + b3);//b3:false
        System.out.println(coll);//[张三, 李四, 田七]

        /*
            public boolean contains(E e):   判断当前集合中是否包含给定的对象
            包含返回true,不包含返回false
         */
        boolean b4 = coll.contains("李四");
        System.out.println("b4:" + b4);//b4:true

        boolean b5 = coll.contains("赵四");
        System.out.println("b5:" + b5);//b5:false

        //public boolean isEmpty(): 判断当前集合是否为空。集合为空返回true,集合不为空返回false
        boolean b6 = coll.isEmpty();
        System.out.println("b6:" + b6);//b6:false

        //public int size(): 返回集合中元素的个数
        int size = coll.size();
        System.out.println("size:" + size);//size:3

        //public Object[] toArray():  把集合中的元素存储到数组中
        Object[] arr = coll.toArray();
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }

        //public void clear():  清空集合中所有的元素。不会删除集合,集合还存在
        coll.clear();
        System.out.println(coll);//[]
    }
}
[]
b1:true
[张三]
[张三, 李四, 赵六, 田七]
b2:true
b3:false
[张三, 李四, 田七]
b4:true
b5:false
b6:false
size:3
张三
李四
田七
[]

二十八、Iterator迭代器

1、Iterator接口
在这里插入图片描述
2、迭代器的代码实现

package lianxi;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/*
    java.util.Iterator接口:迭代器(对集合进行遍历)
    有两个常用的方法:
        boolean hasNext()  如果仍有元素可以迭代,则返回true。
            判断集合中还有没有下一个元素,有就返回true,没有就返回false
        E next()  返回迭代的下一个元素
            取出集合中的下一个元素
    Iterator迭代器,是一个接口,我们无法直接使用,需要使用Iterator接口的实现类对象,获取实现类的方式比较特殊
    Collection接口中有一个方法叫iterator(),这个方法返回的就是迭代器的实现类对象
        Iterator<E> iterator() 返回在此collection的元素上进行迭代的迭代器

    迭代器的使用步骤(重点):
        1.使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
        2.使用Iterator接口中的方法hasNext判断还有没有下一个元素
        3.使用Iterator接口中的方法next取出集合中的下一个元素
 */
public class lianxi1 {
    public static void main(String[] args) {
        //创建一个集合对象
        Collection<String> coll = new ArrayList<>();
        //往集合中添加元素
        coll.add("科比");
        coll.add("詹姆斯");
        /*
            1.使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
            注意:
                Iterator<E>接口也是有泛型的,迭代器的泛型跟着集合走,集合是什么泛型,迭代器就是什么泛型
         */
        //多态    接口          实现类对象
        Iterator<String> it = coll.iterator();

        //2.使用Iterator接口中的方法hasNext判断还有没有下一个元素
        boolean b = it.hasNext();
        System.out.println(b);//true
        //3.使用Iterator接口中的方法next取出集合中的下一个元素
        String s = it.next();
        System.out.println(s);//科比

        b = it.hasNext();
        System.out.println(b);//true
        s = it.next();
        System.out.println(s);//詹姆斯

        b = it.hasNext();
        System.out.println(b);//没有元素,返回false
        //s = it.next();
        //System.out.println(s);//没有元素,再取出元素会抛出NoSuchElementException异常

        System.out.println("-------------------");
        Iterator<String> it2 = coll.iterator();
        while(it2.hasNext()){
            String e = it2.next();
            System.out.println(e);
        }
    }
}
true
科比
true
詹姆斯
false
-------------------
科比
詹姆斯

3、迭代器的实现原理
在这里插入图片描述
4、增强for循环
在这里插入图片描述

package lianxi;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/*
    增强for循环:底层使用的也是迭代器,使用for循环的格式,简化了迭代器的书写,是JDK1.5之后出现的新特性
    Collection<E> extends Iterable<E>:所有的单列集合都可以使用增强for
    public interface Iterable<T>  实现这个接口允许对象成为 “foreach” 语句的目标

    增强for循环:用来遍历集合和数组

    格式:
        for(集合/数组的数据类型  变量名:集合名/数组名){
            sout(变量名);
        }
 */
public class lianxi1 {
    public static void main(String[] args) {
        demo1();
        demo2();
    }

    //使用增强for循环遍历数组
    private static void demo1() {
        int[] arr = {1, 2, 3, 4, 5};
        for (int i : arr){
            System.out.println(i);
        }
    }

    //使用增强for循环遍历集合
    private static void demo2() {
        ArrayList<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        for (String s : list) {
            System.out.println(s);
        }
    }
}
1
2
3
4
5
aaa
bbb
ccc
ddd

二十九、泛型

1、泛型概述
在这里插入图片描述
2、使用泛型的好处

package lianxi;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class lianxi1 {
    public static void main(String[] args) {
        demo1();
        demo2();
    }

    /*
        创建集合对象,不使用泛型
        好处:
            集合若不使用泛型,那默认的类型就是Object,可以存储任意类型的数据
        弊端:
            不安全,会引发异常
     */
    private static void demo1() {
        ArrayList list = new ArrayList<>();
        list.add("abc");
        list.add(1);

        //使用迭代器遍历list集合
        //获取迭代器
        Iterator it = list.iterator();
        //使用迭代器中的方法hasNext和next遍历集合
        while (it.hasNext()) {
            //取出的元素也是Object类型
            Object obj = it.next();
            System.out.println(obj);

            //想要使用String类特有的方法,length获取字符串的长度;不能使用      多态 Object obj = "abc";
            //需要向下转型
            String s = (String) obj;//会抛出ClassCastException类型转换异常,不能把Integer类型转换为String类型
            System.out.println(s.length());
        }
    }

    /*
        创建集合对象,使用泛型
        好处:
            1、避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型
            2、把运行期异常(代码运行之后会抛出的异常),提升到了编译期(写代码的时候会报错)
        弊端:
            泛型是什么类型,就只能存储什么类型的数据
     */
    private static void demo2() {
        ArrayList<String> list = new ArrayList<>();
        list.add("abc");
        //list.add(1);//add(java.lang.String)in ArrayList cannot be applied to (int)

        //使用迭代器遍历list集合
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String s = it.next();
            System.out.println(s + "->" + s.length());
        }
    }
}

3、泛型的定义与使用
在这里插入图片描述
lianxi1.java

package lianxi;

/*
    定义一个含有泛型的类,模拟ArrayList集合
*/
public class lianxi1<E> {
    private E name;

    public E getName() {
        return name;
    }

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

lianxi2.java

package lianxi;

public class lianxi2 {
    public static void main(String[] args) {
        //不写泛型默认为Object类型
        lianxi1 lx1 = new lianxi1();
        lx1.setName("只能是字符串");
        Object obj = lx1.getName();

        //创建lianxi1对象,泛型使用Integer类型
        lianxi1<Integer> lx2 = new lianxi1<>();
        lx2.setName(1);

        Integer name = lx2.getName();
        System.out.println(name);

        //创建lianxi1对象,泛型使用String类型
        lianxi1<String> lx3 = new lianxi1<>();
        lx3.setName("小明");
        String name1 = lx3.getName();
        System.out.println(name1);
    }
}

在这里插入图片描述
lianxi1.java

package lianxi;

/*
    定义含有泛型的方法:泛型定义在方法的修饰符和返回值类型之间
    格式:
        修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)){
            方法体;
        }
*/
public class lianxi1<E> {
    //定义一个含有泛型的方法
    public <M> void method1(M m){
        System.out.println(m);
    }

    //定义一个含有泛型的静态方法
    public static <S> void method2(S s){
        System.out.println(s);
    }
}

lianxi2.java

package lianxi;

/*
    测试含有泛型的方法
*/
public class lianxi2 {
    public static void main(String[] args) {
        //创建lianxi1对象
        lianxi1 lx = new lianxi1();

        /*
            调用含有泛型的方法method
            传递什么类型,泛型就是什么类型
         */
        lx.method1(10);
        lx.method1("abc");
        lx.method1(8.8);
        lx.method1(true);

        lianxi1.method2("静态方法");
        lianxi1.method2(1);
    }
}

在这里插入图片描述
MyInterface.java

package lianxi;
/*
    定义含有泛型的接口
 */
public interface MyInterface<I> {
    public abstract void method(I i);
}

MyInterfaceImpl1.java

package lianxi;
import java.util.Iterator;
/*
    含有泛型的接口第一种使用方式:定义接口的实现类,实现接口,指定接口的泛型
    例如:
    public interface Iterator<E> {
        E next();
    }
    Scanner类实现了Iterator接口,并指定接口的泛型为String,所以重写的next方法泛型默认就是String
    public final class Scanner implements Iterator<String> {
        public String next() {}
}
 */
public class MyInterfaceImpl1 implements MyInterface<String> {
    @Override
    public void method(String s) {
        System.out.println(s);
    }
}

MyInterfaceImpl2.java

package lianxi;
/*
    含有泛型的接口第二种使用方式:接口使用什么泛型,实现类就使用什么泛型,类跟着接口走
    就相当于定义了一个含有泛型的类,创建对象的时候确定泛型的类型
    例如:
    public interface List<E> {
    boolean add(E e);
    E get(int index);
}
    public class ArrayList<E> implements List<E> {
    public boolean add(E e) {}
    public E get(int index) {}
}
 */
public class MyInterfaceImpl2<I> implements MyInterface<I> {
    @Override
    public void method(I i) {
        System.out.println(i);
    }
}

lianxi1.java

package lianxi;
/*
    测试含有泛型的接口
*/
public class lianxi1<E> {
    public static void main(String[] args) {
        //创建MyInterfaceImpl1对象
        MyInterfaceImpl1 mi1 = new MyInterfaceImpl1();
        mi1.method("字符串");

        //创建MyInterfaceImpl2对象
        MyInterfaceImpl2<Integer> mi2 = new MyInterfaceImpl2<>();
        mi2.method(10);

        MyInterfaceImpl2<Double> mi3 = new MyInterfaceImpl2<>();
        mi3.method(8.8);
    }
}

4、泛型通配符
在这里插入图片描述

package lianxi;
import java.util.ArrayList;
import java.util.Iterator;
/*
    泛型的通配符:
        ?:代表任意的数据类型
    使用方式:
        不能作为创建对象使用
        只能作为方法的参数使用
*/
public class lianxi1<E> {
    public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        list1.add(1);
        list1.add(2);

        ArrayList<String> list2 = new ArrayList<>();
        list2.add("a");
        list2.add("b");

        printArray(list1);
        printArray(list2);

        //ArrayList<?> list3 = new ArrayList<?>();  会报错,不能作为创建对象使用
    }

    /*
        定义一个方法,能遍历所有类型的ArrayList集合
        这时候我们不知道ArrayList集合使用什么数据类型,可以使用泛型通配符?来接收数据类型
        注意:
            泛型没有继承概念
     */
    public static void printArray(ArrayList<?> list) {//这里的类型不能写成Object,因为不会自动向上转型。另外,这里的ArrayList<?> list也可以写成ArrayList list
        //使用迭代器遍历集合
        Iterator<?> it = list.iterator();
        while (it.hasNext()) {
            //it.next方法取出的元素是Object,可以接收任意的数据类型
            Object o = it.next();
            System.out.println(o);
        }
    }
}
1
2
a
b

在这里插入图片描述

package lianxi;
import java.util.ArrayList;
import java.util.Collection;
/*
    泛型的上限限定:? extends E   代表使用的泛型只能是E类型的子类/本身
    泛型的下限限定:? super E     代表使用的泛型只能是E类型的父类/本身
*/
public class lianxi1<E> {
    public static void main(String[] args) {
        Collection<Integer> list1 = new ArrayList<>();
        Collection<String> list2 = new ArrayList<>();
        Collection<Number> list3 = new ArrayList<>();
        Collection<Object> list4 = new ArrayList<>();

        gerElement1(list1);
        //gerElement1(list2);//报错
        gerElement1(list3);
        //gerElement1(list4);//报错

        //gerElement2(list1);//报错
        //gerElement2(list2);//报错
        gerElement2(list3);
        gerElement2(list4);

        /*
            类与类之间的继承关系
            Integer extends Number extends Object
            String extends Object
         */
    }

    //泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
    public static void gerElement1(Collection<? extends Number> coll) {}

    //泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
    public static void gerElement2(Collection<? super Number> coll) {}
}

三十、List集合

package lianxi;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/*
    java.util.List接口 extends Collection接口
    List接口的特点:
        1.有序的集合,存储元素和取出元素的顺序是一致的(存储123 取出123)
        2.有索引,包含了一些带索引的方法
        3.允许存储重复的元素

    List接口中带索引的方法(特有)
        - public void add(int index, E element):将指定的元素,添加到该集合中的指定位置上。
        - public E get(int index):返回集合中指定位置的元素
        - public E remove (int index):移除列表中指定位置的元素,返回的是被移除的元素。
        - public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回更新前的元素
    注意:
        操作索引的时候,一定要防止索引越界异常
        IndexOutOfBoundsException: 索引越界异常,集合会报这个异常
        ArrayIndexOutOfBoundsException: 数组索引越界异常
        StringIndexOutOfBoundsException: 字符串索引越界异常
*/
public class lianxi1<E> {
    public static void main(String[] args) {
        //使用多态创建一个List集合对象
        List<String> list = new ArrayList<>();
        //使用add方法往集合中添加元素
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("a");
        //打印集合
        System.out.println(list);//[a, b, c, d, a]      重写了toString方法,因此输出的不是地址

        //public void add(int index, E element):将指定的元素,添加到该集合中的指定位置上。
        //在c和d之间添加一个itheima
        list.add(3, "itheima");//[a, b, c, itheima, d, a]
        System.out.println(list);

        //public E remove (int index):移除列表中指定位置的元素,返回的是被移除的元素。
        //移除元素
        String removeE = list.remove(2);
        System.out.println("被移除的元素:" + removeE);//被移除的元素:c
        System.out.println(list);//[a, b, itheima, d, a]

        //public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回更新前的元素
        //把最后一个a,替换为A
        String setE = list.set(4, "A");
        System.out.println("被替换的元素:" + setE);//被替换的元素:a
        System.out.println(list);//[a, b, itheima, d, A]

        //List集合遍历有3种方式
        //使用普通的for循环
        for (int i = 0; i < list.size(); i++) {
            //public E get(int index):返回集合中指定位置的元素
            String s = list.get(i);
            System.out.println(s);
        }
        System.out.println("--------------------");
        //使用迭代器
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String s = it.next();
            System.out.println(s);
        }
        System.out.println("--------------------");
        //使用增强for
        for (String s : list) {
            System.out.println(s);
        }
        //String r = list.get(5);//IndexOutOfBoundsException: Index 5 out of bounds for length 5
        //System.out.println(r);
    }
}
[a, b, c, d, a]
[a, b, c, itheima, d, a]
被移除的元素:c
[a, b, itheima, d, a]
被替换的元素:a
[a, b, itheima, d, A]
a
b
itheima
d
A
--------------------
a
b
itheima
d
A
--------------------
a
b
itheima
d
A

三十一、LinkedList集合

LinkedList集合是List集合的子类

package lianxi;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/*
    java.util.LinkedList集合 implements List接口
    LinkedList集合的特点:
        1.底层是一个链表结构:查询慢,增删快
        2.里边包含了大量操作首尾元素的方法
        注意:因为要使用LinkedList集合特有的方法,所以不能使用多态

        - public void addFirst(E e):将指定元素插入此列表的开头。
        - public void addLast(E e):将指定元素添加到此列表的结尾。
        - public void push(E e):将元素推入此列表所表示的堆栈。

        - public E getFirst ():返回此列表的第一个元素。
        - public E getLast ():返回此列表的最后一个元素。

        - public E removeFirst ():移除并返回此列表的第一个元素。
        - public E removeLast ():移除并返回此列表的最后一个元素。
        - public E pop():从此列表所表示的堆栈处弹出一个元素。

        - public boolean isEmpty():如果列表不包含元素,则返回true。
*/
public class lianxi1<E> {
    public static void main(String[] args) {
        show01();
        System.out.println("-------------");
        show02();
        System.out.println("-------------");
        show03();
    }

    /*
        - public void addFirst(E e):将指定元素插入此列表的开头。
        - public void addLast(E e):将指定元素添加到此列表的结尾。
        - public void push(E e):将元素推入此列表所表示的堆栈。此方法等效于 addFirst(E)。
     */
    private static void show01() {
        //创建LinkedList集合对象
        LinkedList<String> linked = new LinkedList<>();
        //使用add方法往集合中添加元素
        linked.add("a");
        linked.add("b");
        linked.add("c");
        System.out.println(linked);//[a, b, c]

        //public void addFirst(E e):将指定元素插入此列表的开头。
        //linked.addFirst("www");
        linked.push("www");
        System.out.println(linked);//[www, a, b, c]

        //public void addLast(E e):将指定元素添加到此列表的结尾。此方法等效于 add()。
        linked.addLast("com");
        System.out.println(linked);//[www, a, b, c, com]
    }

    /*
        - public E getFirst ():返回此列表的第一个元素。
        - public E getLast ():返回此列表的最后一个元素。
     */
    private static void show02() {
        //创建LinkedList集合对象
        LinkedList<String> linked = new LinkedList<>();
        //使用add方法往集合中添加元素
        linked.add("a");
        linked.add("b");
        linked.add("c");
        System.out.println(linked);//[a, b, c]

        //linked.clear();//清空集合中的元素,若再获取集合中的元素会抛出NoSuchElementException异常

        //public boolean isEmpty():如果列表不包含元素,则返回true。
        if (!linked.isEmpty()) {
            String first = linked.getFirst();
            System.out.println(first);//a
            String last = linked.getLast();
            System.out.println(last);//c
        }
    }

    /*
        - public E removeFirst ():移除并返回此列表的第一个元素。
        - public E removeLast ():移除并返回此列表的最后一个元素。
        - public E pop():从此列表所表示的堆栈处弹出一个元素。此方法相当于 removeFirst
     */
    private static void show03() {
        //创建LinkedList集合对象
        LinkedList<String> linked = new LinkedList<>();
        //使用add方法往集合中添加元素
        linked.add("a");
        linked.add("b");
        linked.add("c");
        System.out.println(linked);//[a, b, c]

        //String first = linked.removeFirst();
        String first = linked.pop();
        System.out.println("被移除的第一个元素:" + first);
        String last = linked.removeLast();
        System.out.println("被移除的最后一个元素:" + last);
        System.out.println(linked);
    }
}
[a, b, c]
[www, a, b, c]
[www, a, b, c, com]
-------------
[a, b, c]
a
c
-------------
[a, b, c]
被移除的第一个元素:a
被移除的最后一个元素:c
[b]

三十二、HashSet集合

1、HashSet集合的介绍
HashSet集合是Set集合的子类

package lianxi;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/*
    java.util.Set接口 extends Collection接口
    Set接口的特点:
        1.不允许存储重复的元素
        2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历
    java.util.HashSet集合 implements Set接口
    HashSet特点:
        1.不允许存储重复的元素
        2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历
        3.是一个无序的集合,存储元素和取出元素的顺序有可能不一致
        4.底层是一个哈希表结构(查询的速度非常的快)

*/
public class lianxi1<E> {
    public static void main(String[] args) {
        Set<Integer> set = new HashSet<>();
        //使用add方法往集合中添加元素
        set.add(1);
        set.add(3);
        set.add(2);
        set.add(1);
        //使用迭代器遍历set集合
        Iterator<Integer> it = set.iterator();
        while (it.hasNext()) {
            Integer n = it.next();
            System.out.println(n);//1,2,3
        }
        System.out.println("--------------");
        //使用增强for遍历set集合
        for (Integer i : set) {
            System.out.println(i);//1,2,3
        }
    }
}
1
2
3
--------------
1
2
3

2、哈希值

lianxi1.java

package lianxi;

/*
    哈希值:是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来得到的地址,不是数据实际存储的物理地址)
    在Object类有一个方法,可以获取对象的哈希值
    int hashCode() 返回该对象的哈希值
    hashCode方法的源码:
        public native int hashCode();
        native:代表该方法调用的是本地操作系统的方法
*/
public class lianxi1 {
    public static void main(String[] args) {
        //lianxi2类继承了Object类,所以可以使用Object类的hashCode方法
        lianxi2 p1 = new lianxi2();
        int h1 = p1.hashCode();
        System.out.println(h1);//189568618

        lianxi2 p2 = new lianxi2();
        int h2 = p2.hashCode();
        System.out.println(h2);//793589513

        /*
            toString方法的源码:
                return getClass().getName() + "@" + Integer.toHexString(hashCode());
         */
        System.out.println(p1);//lianxi.lianxi2@b4c966a
        System.out.println(p2);//lianxi.lianxi2@2f4d3709
        System.out.println(p1 == p2);
    }
}

lianxi2.java

package lianxi;

public class lianxi2 extends Object{
}

输出:

189568618
793589513
lianxi.lianxi2@b4c966a
lianxi.lianxi2@2f4d3709
false

重写hashCode方法:

package lianxi;

public class lianxi2 extends Object {
    //重写hashCode方法
    @Override
    public int hashCode() {
        return 1;
    }
}

输出:

1
1
lianxi.lianxi2@1
lianxi.lianxi2@1
false

lianxi3.java

package lianxi;

public class lianxi3 {
    public static void main(String[] args) {
        /*
            String类的哈希值
                String类重写了Object类的hashCode方法
         */
        String s1 = new String("abc");
        String s2 = new String("abc");
        System.out.println(s1.hashCode());//96354
        System.out.println(s2.hashCode());//96354

	    System.out.println("重地".hashCode());//1179395
        System.out.println("通话".hashCode());//1179395
    }
}

输出:

96354
96354
1179395
1179395

3、哈希表
在这里插入图片描述

4、Set集合存储元素不重复的原理

package lianxi;

import java.util.HashSet;
/*
    Set集合不允许存储重复元素的原理
 */
public class lianxi1 {
    public static void main(String[] args) {
        //创建HashSet集合对象
        HashSet<String> set = new HashSet<>();
        String s1 = new String("abc");
        String s2 = new String("abc");
        set.add(s1);
        set.add(s2);
        set.add("重地");
        set.add("通话");
        set.add("abc");
        System.out.println(set);//[重地, 通话, abc]
    }
}

输出:

[重地, 通话, abc]

在这里插入图片描述

5、HashSet存储自定义类型元素
lianxi1.java

package lianxi;

import java.util.HashSet;

/*
    HashSet存储自定义类型元素

    set集合保证元素唯一
        存储的元素(String,Integer,...Student,Person...),必须重写hashCode方法和equals方法

    要求:
        同名同年龄的人,视为同一个人,只能存储一次
 */
public class lianxi1 {
    public static void main(String[] args) {
        //创建HashSet集合存储Person
        HashSet<lianxi2> set = new HashSet<>();
        lianxi2 p1 = new lianxi2("小美女", 18);
        lianxi2 p2 = new lianxi2("小美女", 18);
        lianxi2 p3 = new lianxi2("小美女", 19);
        System.out.println(p1.hashCode());//189568618
        System.out.println(p2.hashCode());//793589513

        System.out.println(p1 == p2);//false
        System.out.println(p1.equals(p2));//false
        set.add(p1);
        set.add(p2);
        set.add(p3);
        System.out.println(set);
    }
}

lianxi2.java

package lianxi;

public class lianxi2 {
    private String name;
    private int age;

    public lianxi2() {
    }

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

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

输出:

189568618
793589513
false
false
[lianxi2{name='小美女', age=18}, lianxi2{name='小美女', age=18}, lianxi2{name='小美女', age=19}]

重写equals方法和hahsCode方法:
lianxi2.java

package lianxi;

import java.util.Objects;

public class lianxi2 {
    private String name;
    private int age;

    public lianxi2() {
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        lianxi2 lianxi2 = (lianxi2) o;
        return age == lianxi2.age &&
                Objects.equals(name, lianxi2.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

输出:

734175839
734175839
false
true
[lianxi2{name='小美女', age=19}, lianxi2{name='小美女', age=18}]

6、LinkedHashSet集合

package lianxi;

import java.util.HashSet;
import java.util.LinkedHashSet;

/*
    java.util.LinkedHashSet集合 extends HashSet集合
    LinkedHashSet集合特点:
        底层是一个哈希表(数组+链表/红黑树)+链表:多了一条链表(用来记录元素的存储顺序),保证元素有序
 */
public class lianxi1 {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        set.add("www");
        set.add("abc");
        set.add("abc");
        set.add("itcast");
        System.out.println(set);//[abc, www, itcast] 无序并且不允许重复

        LinkedHashSet<String> linked = new LinkedHashSet<>();
        linked.add("www");
        linked.add("abc");
        linked.add("abc");
        linked.add("itcast");
        System.out.println(linked);//[www, abc, itcast]
    }
}

输出:

[abc, www, itcast]
[www, abc, itcast]

三十三、可变参数

在这里插入图片描述

package lianxi;

/*
    可变参数:是JDK1.5之后出现的新特性
    使用前提:
        当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数
    使用格式:定义方法时使用
        修饰符 返回值类型   方法名(数据类型... 变量名){}
    可变参数的原理:
        可变参数底层就是一个数组,根据传递参数个数不同,会创建不同长度的数组,用来存储这些参数
        传递的参数个数可以是0,1,2..多个
 */
public class lianxi1 {
    public static void main(String[] args) {
        int i = add(10, 20, 30);
        System.out.println(i);
    }

    /*
        定义计算(0~n)整数和的方法
        已知:计算整数的和,数据类型已经确定int
        但是参数的个数不确定,不知道要计算几个整数的和,就可以使用可变参数
        add();  就会创建一个长度为0的数组,new int[]{}
        add(10);  就会创建一个长度为1的数组,new int[]{10}
        以此类推..
     */
    public static int add(int... arr) {
        //定义一个初始化的变量,记录累加求和
        int sum = 0;
        //遍历数组,获取数组中的每一个元素
        for (int i : arr) {
            //累加求和
            sum += i;
        }
        //把求和结果返回
        return sum;
    }

    /*
        可变参数的注意事项:
            1.一个方法的参数列表,只能有一个可变参数
            2.如果方法的参数有多个,那么可变参数必须写在参数列表的末尾
     */

    //1.一个方法的参数列表,只能有一个可变参数
    /*public static void method(int... a, String... b) {

    }(错误写法)*/

    //2.如果方法的参数有多个,那么可变参数必须写在参数列表的末尾
    /*public static void method(int... a, String b, double c, int d) {

    }(错误写法)*/

    public static void method(String b, double c, int d, int... a) {

    }   //正确写法

}

三十四、Collections集合

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

package lianxi;

import java.util.ArrayList;
import java.util.Collections;

/*
    - java.util.Collections是集合工具类,用来对集合进行操作。部分方法如下:
        - public static <T> boolean addAll(Collection<T> c, T... elements):往集合中添加一些元素。
        - public static void shuffle(List<?> list) 打乱顺序:打乱集合顺序。
        (都是静态方法,可以用类名直接调用)
 */

public class lianxi1 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        //往集合中添加多个元素
        /*list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");*/

        //public static <T> boolean addAll(Collection<T> c, T... elements):往集合中添加一些元素。
        Collections.addAll(list, "a", "b", "c", "d", "e");

        System.out.println(list);//[a, b, c, d, e]

        //public static void shuffle(List<?> list) 打乱顺序:打乱集合顺序。
        Collections.shuffle(list);
        System.out.println(list);//[c, a, d, e, b]
    }
}

输出:

[a, b, c, d, e]
[c, a, d, e, b]

2、
在这里插入图片描述
lianxi2.java

package lianxi;

public class lianxi2 implements Comparable<lianxi2> {
    private String name;
    private int age;

    public lianxi2() {
    }

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

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    //重写排序的规则
    @Override
    public int compareTo(lianxi2 o) {
        //return 0;//认为元素都是相同的
        //自定义比较的规则,比较两个人的年龄(this,参数Person)
        return this.getAge() - o.getAge();//年龄升序排序
        //return o.getAge() - this.getAge();//年龄降序排序
    }
}

lianxi1.java

package lianxi;

import java.util.ArrayList;
import java.util.Collections;

/*
    - java.util.Collections是集合工具类,用来对集合进行操作。部分方法如下:
        public static <T> void sort(List<T> list):将集合中元素按照默认规则排序

    注意:
        sort(List<T> list)使用前提:
        被排序的集合里边存储的元素,必须实现Comparable接口,重写接口中的方法compareTo定义排序的规则

    Comparable接口的排序规则:
        自己(this) - 参数:升序
 */

public class lianxi1 {
    public static void main(String[] args) {
        ArrayList<Integer> list01 = new ArrayList<>();
        list01.add(1);
        list01.add(3);
        list01.add(2);
        System.out.println(list01);//[1, 3, 2]

        //public static <T> void sort(List<T> list):将集合中元素按照默认规则排序
        Collections.sort(list01);//默认是升序

        System.out.println(list01);//[1, 2, 3]

        ArrayList<String> list02 = new ArrayList<>();
        list02.add("a");
        list02.add("c");
        list02.add("b");
        System.out.println(list02);//[a, c, b]

        Collections.sort(list02);
        System.out.println(list02);//[a, b, c]

        ArrayList<lianxi2> list03 = new ArrayList<>();
        list03.add(new lianxi2("张三", 18));
        list03.add(new lianxi2("李四", 20));
        list03.add(new lianxi2("王五", 15));
        System.out.println(list03);//[lianxi2{name='张三', age=18}, lianxi2{name='李四', age=20}, lianxi2{name='王五', age=15}]

        Collections.sort(list03);
        System.out.println(list03);
    }
}

输出:

[1, 3, 2]
[1, 2, 3]
[a, c, b]
[a, b, c]
[lianxi2{name='张三', age=18}, lianxi2{name='李四', age=20}, lianxi2{name='王五', age=15}]
[lianxi2{name='王五', age=15}, lianxi2{name='张三', age=18}, lianxi2{name='李四', age=20}]

3、
在这里插入图片描述
lianxi2.java

package lianxi;

public class lianxi2 {
    private String name;
    private int age;

    public lianxi2() {
    }

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

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

lianxi1.java

package lianxi;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

/*
    - java.util.Collections是集合工具类,用来对集合进行操作。部分方法如下:
        public static <T> void sort(List<T> list, Comparator<? super T> ):将集合中元素按照指定规则排序

    Comparator和Comparable的区别:
        Comparable:自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的规则compareTo方法
        Comparator:相当于找一个第三方的裁判,比较两个

    Comparable接口的升序规则:
        自己(this) - 参数:升序
 */

public class lianxi1 {
    public static void main(String[] args) {
        ArrayList<Integer> list01 = new ArrayList<>();
        list01.add(1);
        list01.add(3);
        list01.add(2);
        System.out.println(list01);//[1, 3, 2]

        Collections.sort(list01, new Comparator<Integer>() {
            //重写比较的规则
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;//升序
                //return o2 - o1;//降序
            }
        });

        System.out.println(list01);//[1, 2, 3]

        ArrayList<lianxi2> list02 = new ArrayList<>();
        list02.add(new lianxi2("a迪丽热巴", 18));
        list02.add(new lianxi2("古力娜扎", 20));
        list02.add(new lianxi2("杨幂", 17));
        list02.add(new lianxi2("b杨幂", 18));
        System.out.println(list02);

        //下面内容是扩展的,了解就行
        Collections.sort(list02, new Comparator<lianxi2>() {
            @Override
            public int compare(lianxi2 o1, lianxi2 o2) {
                //按照年龄升序排序
                int result = o1.getAge() - o2.getAge();
                //如果两个人年龄相同,再使用姓名的第一个字比较
                if (result == 0) {
                    result = o1.getName().charAt(0) - o2.getName().charAt(0);
                }
                return result;
            }
        });

        System.out.println(list02);
    }
}

输出:

[1, 3, 2]
[1, 2, 3]
[lianxi2{name='a迪丽热巴', age=18}, lianxi2{name='古力娜扎', age=20}, lianxi2{name='杨幂', age=17}, lianxi2{name='b杨幂', age=18}]
[lianxi2{name='杨幂', age=17}, lianxi2{name='a迪丽热巴', age=18}, lianxi2{name='b杨幂', age=18}, lianxi2{name='古力娜扎', age=20}]

三十五、Map集合

1、Map集合概述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、Map常用子类
在这里插入图片描述
在这里插入图片描述

3、Map接口中的常用方法
在这里插入图片描述

package lianxi;

import java.util.HashMap;
import java.util.Map;

public class lianxi1 {
    public static void main(String[] args) {
        show01();
        System.out.println("-------------------");
        show02();
        System.out.println("-------------------");
        show03();
        System.out.println("-------------------");
        show04();
    }

    /*
        public V put(K key, V value):把指定的键与指定的值添加到Map集合中。
            返回值:V
                存储键值对的时候,key不重复,返回值V是null
                存储键值对的时候,key重复,会使用新的value替换Map中重复的value,返回被替换的value值
     */
    private static void show01() {
        //创建Map集合对象,多态
        HashMap<String, String> map = new HashMap<>();

        String v1 = map.put("黄晓明", "杨颖1");
        System.out.println("v1:" + v1);//v1:null

        String v2 = map.put("黄晓明", "杨颖2");
        System.out.println("v2:" + v2);//v2:杨颖1

        System.out.println(map);//{黄晓明=杨颖2}

        map.put("冷锋", "龙小云");
        map.put("杨过", "小龙女");
        map.put("尹志平", "小龙女");
        System.out.println(map);
    }

    /*
        public V remove(Object key):把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
            返回值:V
                key存在,v返回被删除的值
                key不存在,v返回null
     */
    private static void show02() {
        //创建Map集合对象
        Map<String, Integer> map = new HashMap<>();
        map.put("赵丽颖", 168);
        map.put("杨颖", 168);
        map.put("林志玲", 168);
        System.out.println(map);//{林志玲=168, 赵丽颖=168, 杨颖=168}

        Integer v1 = map.remove("林志玲");
        System.out.println("v1:" + v1);//v1:168

        System.out.println(map);//{赵丽颖=168, 杨颖=168}

        //int v2 = map.remove("林志颖");//自动拆箱   会报NullPointerException异常
        Integer v2 = map.remove("林志颖");
        System.out.println("v2:" + v2);//v2:null

        System.out.println(map);//{赵丽颖=168, 杨颖=168}
    }

    /*
        public V get(Object key):根据指定的键,在Map集合中获取对应的值。
            返回值:
                key存在,返回对应的value值
                key不存在,返回null
     */
    public static void show03() {
        //创建Map集合对象
        Map<String, Integer> map = new HashMap<>();
        map.put("赵丽颖", 168);
        map.put("杨颖", 165);
        map.put("林志玲", 178);

        Integer v1 = map.get("杨颖");
        System.out.println("v1:" + v1);//v1:165

        Integer v2 = map.get("迪丽热巴");
        System.out.println("v2:" + v2);//v2:null
    }

    /*
        boolean containsKey(Object key):判断集合中是否包含指定的键。
        包含返回true,不包含返回false
     */
    private static void show04() {
        //创建Map集合对象
        Map<String, Integer> map = new HashMap<>();
        map.put("赵丽颖", 168);
        map.put("杨颖", 165);
        map.put("林志玲", 178);

        boolean b1 = map.containsKey("赵丽颖");
        System.out.println("b1:" + b1);//b1:true

        boolean b2 = map.containsKey("赵丽");
        System.out.println("b2:" + b2);//b2:false
    }
}

输出:

v1:null
v2:杨颖1
{黄晓明=杨颖2}
{杨过=小龙女, 尹志平=小龙女, 冷锋=龙小云, 黄晓明=杨颖2}
-------------------
{林志玲=168, 赵丽颖=168, 杨颖=168}
v1:168
{赵丽颖=168, 杨颖=168}
v2:null
{赵丽颖=168, 杨颖=168}
-------------------
v1:165
v2:null
-------------------
b1:true
b2:false

4、Map集合遍历键找值方式
在这里插入图片描述

package lianxi;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/*
    Map集合的第一种遍历方式:通过键找值的方式
    Map集合中的方法:
        Set<K> keySet() 返回此映射中包含的键的Set视图。
    实现步骤:
        1.使用Map集合中的方法keySet(),把Map集合所有的key取出来,存储到一个set集合中
        2.遍历set集合,获取Map集合中的每一个key
        3.通过Map集合中的方法get(key),通过key找到value
 */

public class lianxi1 {
    public static void main(String[] args) {
        //创建Map集合对象
        Map<String, Integer> map = new HashMap<>();
        map.put("赵丽颖", 168);
        map.put("杨颖", 165);
        map.put("林志玲", 178);

        //1.使用Map集合中的方法keySet(),把Map集合所有的key取出来,存储到一个set集合中
        Set<String> set = map.keySet();

        //2.遍历set集合,获取Map集合中的每一个key
        //使用迭代器遍历set集合
        Iterator<String> it = set.iterator();
        while (it.hasNext()) {
            String key = it.next();
            //3.通过Map集合中的方法get(key),通过key找到value
            Integer value = map.get(key);
            System.out.println(key + "=" + value);
        }
        System.out.println("---------------------");
        //使用增强for遍历set集合
        for (String key : set) {
            //3.通过Map集合中的方法get(key),通过key找到value
            Integer value = map.get(key);
            System.out.println(key + "=" + value);
        }
        System.out.println("---------------------");
        //使用增强for遍历set集合
        for (String key : map.keySet()) {
            //3.通过Map集合中的方法get(key),通过key找到value
            Integer value = map.get(key);
            System.out.println(key + "=" + value);
        }
    }
}

输出:

林志玲=178
赵丽颖=168
杨颖=165
---------------------
林志玲=178
赵丽颖=168
杨颖=165
---------------------
林志玲=178
赵丽颖=168
杨颖=165

5、Entry键值对对象
在这里插入图片描述
在这里插入图片描述

package lianxi;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/*
    Map集合的第二种遍历方式:使用Entry对象遍历
    Map集合中的方法:
        Set<Map.Entry<K,V> entrySet() 返回此映射中包含的映射关系的Set视图。
    实现步骤:
        1.使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个set集合中
        2.遍历set集合,获取每一个Entry对象
        3.使用Entry对象中的方法getKey()和getValue()获取键与值
 */

public class lianxi1 {
    public static void main(String[] args) {
        //创建Map集合对象
        Map<String, Integer> map = new HashMap<>();
        map.put("赵丽颖", 168);
        map.put("杨颖", 165);
        map.put("林志玲", 178);

        //1.使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个set集合中
        Set<Map.Entry<String, Integer>> set = map.entrySet();

        //2.遍历set集合,获取每一个Entry对象
        //使用迭代器遍历set集合
        Iterator<Map.Entry<String, Integer>> it = set.iterator();
        while (it.hasNext()) {
            Map.Entry<String, Integer> entry = it.next();
            //3.使用Entry对象中的方法getKey()和getValue()获取键与值
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(key + "=" + value);
        }
        System.out.println("---------------------");
        //使用增强for遍历set集合
        for (Map.Entry<String, Integer> entry : set) {
            //3.使用Entry对象中的方法getKey()和getValue()获取键与值
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(key + "=" + value);
        }
    }
}

输出:

林志玲=178
赵丽颖=168
杨颖=165
---------------------
林志玲=178
赵丽颖=168
杨颖=165

6、HashMap存储自定义类型键值
在这里插入图片描述
lianxi1.java

package lianxi;

/*
    HashMpa存储自定义类型键值
    Map集合保证key是唯一的:
        作为key的元素,必须重写hashCode方法和equals方法,以保证key唯一
 */

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class lianxi1 {
    public static void main(String[] args) {
        show01();
        System.out.println("--------------------");
        show02();
    }

    /*
        HashMpa存储自定义类型键值
        key:String类型
            String类已经重写hashCode方法和equals方法,可以保证key唯一
        value:Person类型
            value可以重复(同名同年龄的人视为同一个)
     */
    private static void show01() {
        //创建HashMap集合
        HashMap<String, Person> map = new HashMap<>();
        //往集合中添加元素
        map.put("北京", new Person("张三", 18));
        map.put("上海", new Person("李四", 19));
        map.put("广州", new Person("王五", 20));
        map.put("北京", new Person("张三", 18));
        //使用keySet加增强for遍历Map集合
        Set<String> set = map.keySet();
        for (String key : set) {
            Person value = map.get(key);
            System.out.println(key + "-->" + value);
        }
    }

    /*
        HashMpa存储自定义类型键值
        key:Person类型
            Person类必须重写hashCode方法和equals方法,以保证key唯一
        value:String类型
            可以重复
     */
    private static void show02() {
        //创建HashMap集合
        HashMap<Person, String> map = new HashMap<>();
        //往集合中添加元素
        map.put(new Person("女王", 18), "英国");
        map.put(new Person("秦始皇", 18), "秦国");
        map.put(new Person("普京", 18), "俄罗斯");
        map.put(new Person("女王", 18), "毛里求斯");
        //使用entrySet加增强for遍历Map集合
        Set<Map.Entry<Person, String>> set = map.entrySet();
        for (Map.Entry<Person, String> entry : set) {
            Person key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + "-->" + value);
        }
    }
}

person.java

package lianxi;

import java.util.Objects;

public class Person {
    private String name;
    private int age;

    public Person() {
    }

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

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

输出结果:

上海-->Person{name='李四', age=19}
广州-->Person{name='王五', age=20}
北京-->Person{name='张三', age=18}
--------------------
Person{name='女王', age=18}-->毛里求斯
Person{name='秦始皇', age=18}-->秦国
Person{name='普京', age=18}-->俄罗斯

如果Person类没有重写hashCode方法和equals方法,输出结果如下:

上海-->Person{name='李四', age=19}
广州-->Person{name='王五', age=20}
北京-->Person{name='张三', age=18}
--------------------
Person{name='普京', age=18}-->俄罗斯
Person{name='女王', age=18}-->毛里求斯
Person{name='女王', age=18}-->英国
Person{name='秦始皇', age=18}-->秦国

7、LinkedHashMap集合
在这里插入图片描述

package lianxi;

/*
    java.util.LinkedHashMap<K, V> extends HashMap<K, V>
    由Map接口的哈希表和链接列表实现,具有可预知的迭代顺序
    底层原理:
        哈希表 + 链表(记录元素的顺序)
 */

import java.util.HashMap;
import java.util.LinkedHashMap;

public class lianxi1 {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        map.put("a", "a");
        map.put("c", "c");
        map.put("d", "d");
        map.put("a", "d");
        System.out.println(map);//key不允许重复,无序 {a=d, c=c, d=d}

        LinkedHashMap<String, String> linked = new LinkedHashMap<>();
        linked.put("a", "a");
        linked.put("c", "c");
        linked.put("b", "b");
        linked.put("a", "d");
        System.out.println(linked);//key不允许重复,有序 {a=d, c=c, b=b}
    }
}

输出:

{a=d, c=c, d=d}
{a=d, c=c, b=b}

8、Hashtable集合

package lianxi;

/*
    java.util.Hashtable<K, V> implements Map<K, V>接口

    Hashtable:底层是一个哈希表,是一个线程安全的集合,是单线程集合,速度慢
    HashMap:底层是一个哈希表,是一个线程不安全的集合,是多线程集合,速度快

    HashMap集合(之前学的所有的集合):可以存储null值,null键
    Hashtable集合,不能存储null值,null键

    Hashtable和Vector集合一样,在jdk1.2版本之后被更先进的集合(HashMap, ArrayList)取代了
    Hashtable的子类Properties依然活跃在历史舞台
    Properties集合是一个唯一和IO流相结合的集合
 */

import java.util.HashMap;
import java.util.Hashtable;

public class lianxi1 {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        map.put(null, "a");
        map.put("b", null);
        map.put(null, null);
        System.out.println(map);//{null=null, b=null}

        Hashtable<String, String> table = new Hashtable<>();
        //table.put(null,"a");//NullPointerException
        //table.put("b", null);//NullPointerException
        //table.put(null, null);//NullPointerException
    }
}

9、Map集合练习
在这里插入图片描述
思路:
在这里插入图片描述

package lianxi;

/*
    练习:
        计算一个字符串中每个字符出现次数

    分析:
        1.使用Scanner获取用户输入的字符串
        2.创建Map集合,key是字符串中的字符,value是字符的个数
        3.遍历字符串,获取每一个字符
        4.使用获取到的字符,去Map集合判断key是否存在
            key存在:
                通过字符(key),获取value(字符个数)
                value++
                put(key,value)把新的value存储到Map集合中
            key不存在:
                put(key,1)
        5.遍历Map集合,输出结果
 */

import java.util.HashMap;
import java.util.Scanner;

public class lianxi1 {
    public static void main(String[] args) {
        //1.使用Scanner获取用户输入的字符串
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串:");
        String str = sc.next();
        //2.创建Map集合,key是字符串中的字符,value是字符的个数
        HashMap<Character, Integer> map = new HashMap<>();
        //3.遍历字符串,获取每一个字符
        for (char c : str.toCharArray()) {
            //4.使用获取到的字符,去Map集合判断key是否存在
            if (map.containsKey(c)) {
                //key存在
                Integer value = map.get(c);
                value++;
                map.put(c, value);
            } else {
                //key不存在
                map.put(c, 1);
            }
        }
        //5.遍历Map集合,输出结果
        for (Character key : map.keySet()) {
            Integer value = map.get(key);
            System.out.println(key + "=" + value);
        }
    }
}

输出结果:

请输入一个字符串:
aaabbbbcca
a=4
b=4
c=2

10、补充知识点:JDK9对集合添加的优化
在这里插入图片描述
在这里插入图片描述

package lianxi;

/*
    JDK9新特性:
        List接口,Set接口,Map接口:里边增加了一个静态的方法of,可以给集合一次性添加多个元素
        static <E> List<E> of (E... elements)
        使用前提:
            当集合中存储的元素的个数已经确定了,不在改变时使用

    注意:
        1.of方法只适用于List接口,Set接口,Map接口,不适用于接口的实现类
        2.of方法的返回值是一个不能改变的集合,集合不能再使用add,put方法添加元素,否则会抛出异常
        3.Set接口和Map接口在调用of方法的时候,不能有重复的元素,否则会抛出异常
 */

import java.util.List;
import java.util.Map;
import java.util.Set;

public class lianxi1 {
    public static void main(String[] args) {
        List<String> list = List.of("a", "b", "a", "c", "d");
        System.out.println(list);//[a, b, a, c, d]
        //list.add("w");//UnsupportedOperationException:不支持操作异常

        //Set<String> set = Set.of("a", "b", "a", "c", "d");//IllegalArgumentException:非法参数异常,有重复的元素
        Set<String> set = Set.of("a", "b", "c", "d");
        System.out.println(set);
        //set.add("w");//UnsupportedOperationException:不支持操作异常

        //Map<String, Integer> map = Map.of("张三", 18, "李四", 19, "王五", 20, "张三", 19);//IllegalArgumentException
        Map<String, Integer> map = Map.of("张三", 18, "李四", 19, "王五", 20);
        System.out.println(map);//{王五=20, 李四=19, 张三=18}
        //map.put("赵四", 30);//UnsupportedOperationException
    }
}

输出结果:

[a, b, a, c, d]
[b, c, d, a]
{张三=18, 李四=19, 王五=20}

11、综合练习:模拟斗地主洗牌发牌
在这里插入图片描述
案例需求分析:
在这里插入图片描述
实现代码步骤:

public class Poker {
    public static void main(String[] args) {
        /*
         * 1组装54张扑克牌
         */
        // 1.1 创建Map集合存储
        HashMap<Integer, String> pokerMap = new HashMap<Integer, String>();
        // 1.2 创建 花色集合 与 数字集合
        ArrayList<String> colors = new ArrayList<String>();
        ArrayList<String> numbers = new ArrayList<String>();

        // 1.3 存储 花色 与数字
        Collections.addAll(colors, "♦", "♣", "♥", "♠");
        Collections.addAll(numbers, "2", "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3");
        // 设置 存储编号变量
        int count = 1;
        pokerMap.put(count++, "大王");
        pokerMap.put(count++, "小王");
        // 1.4 创建牌 存储到map集合中
        for (String number : numbers) {
            for (String color : colors) {
                String card = color + number;
                pokerMap.put(count++, card);
            }
        }
        /*
         * 2 将54张牌顺序打乱
         */
        // 取出编号 集合
        Set<Integer> numberSet = pokerMap.keySet();
        // 因为要将编号打乱顺序 所以 应该先进行转换到 list集合中
        ArrayList<Integer> numberList = new ArrayList<Integer>();
        numberList.addAll(numberSet);

        // 打乱顺序
        Collections.shuffle(numberList);

        // 3 完成三个玩家交替摸牌,每人17张牌,最后三张留作底牌
        // 3.1 发牌的编号
        // 创建三个玩家编号集合 和一个 底牌编号集合
        ArrayList<Integer> noP1 = new ArrayList<Integer>();
        ArrayList<Integer> noP2 = new ArrayList<Integer>();
        ArrayList<Integer> noP3 = new ArrayList<Integer>();
        ArrayList<Integer> dipaiNo = new ArrayList<Integer>();

        // 3.2发牌的编号
        for (int i = 0; i < numberList.size(); i++) {
            // 获取该编号
            Integer no = numberList.get(i);
            // 发牌
            // 留出底牌
            if (i >= 51) {
                dipaiNo.add(no);
            } else {
                if (i % 3 == 0) {
                    noP1.add(no);
                } else if (i % 3 == 1) {
                    noP2.add(no);
                } else {
                    noP3.add(no);
                }
            }
        }

        // 4 查看三人各自手中的牌(按照牌的大小排序)、底牌
        // 4.1 对手中编号进行排序
        Collections.sort(noP1);
        Collections.sort(noP2);
        Collections.sort(noP3);
        Collections.sort(dipaiNo);

        // 4.2 进行牌面的转换
        // 创建三个玩家牌面集合 以及底牌牌面集合
        ArrayList<String> player1 = new ArrayList<String>();
        ArrayList<String> player2 = new ArrayList<String>();
        ArrayList<String> player3 = new ArrayList<String>();
        ArrayList<String> dipai = new ArrayList<String>();

        // 4.3转换
        for (Integer i : noP1) {
            // 4.4 根据编号找到 牌面 pokerMap
            String card = pokerMap.get(i);
            // 添加到对应的 牌面集合中
            player1.add(card);
        }

        for (Integer i : noP2) {
            String card = pokerMap.get(i);
            player2.add(card);
        }
        for (Integer i : noP3) {
            String card = pokerMap.get(i);
            player3.add(card);
        }
        for (Integer i : dipaiNo) {
            String card = pokerMap.get(i);
            dipai.add(card);
        }

        //4.5 查看
        System.out.println("令狐冲:"+player1);
        System.out.println("石破天:"+player2);
        System.out.println("鸠摩智:"+player3);
        System.out.println("底牌:"+dipai);
    }
}

输出结果:

令狐冲:[大王, 小王, ♥2, ♠2, ♣A, ♦K, ♥Q, ♦J, ♣J, ♦8, ♥8, ♥7, ♠7, ♦6, ♠6, ♦3, ♥3]
石破天:[♦2, ♣K, ♥K, ♣Q, ♥J, ♠J, ♠10, ♦9, ♣9, ♥9, ♣8, ♣7, ♣6, ♥5, ♣4, ♠4, ♣3]
鸠摩智:[♣2, ♦A, ♠A, ♠K, ♠Q, ♦10, ♣10, ♥10, ♠9, ♦7, ♥6, ♦5, ♣5, ♠5, ♦4, ♥4, ♠3]
底牌:[♥A, ♦Q, ♠8]

三十六、Debug追踪

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

三十七、异常

第一章 异常

1.1 异常概念
在这里插入图片描述
1.2 异常体系
在这里插入图片描述
在这里插入图片描述
1.3 异常分类
在这里插入图片描述

/*
    java.lang.Throwable类是Java语言中所有错误或异常的超类
        Exception:编译期异常,进行编译(写代码)java程序出现的问题
            RuntimeException:运行期异常,java程序运行过程中出现的问题
            异常就相当于程序得了一个小毛病(感冒,发烧),把异常处理掉,程序可以继续执行(吃点药,继续革命工作)
        Error:错误
            错误就相当于程序得了一个无法治愈的毛病,必须修改源代码,程序才能继续执行
 */

(1)编译期运行异常
采用throw处理异常:

package lianxi;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class lianxi1 {
    public static void main(String[] args) throws ParseException {
        //Exception:编译期异常,进行编译(写代码)java程序出现的问题
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");//用来格式化日期
        Date date = sdf.parse("1999-0909");//把字符串格式的日期,解析为Date格式的日期
        System.out.println(date);
    }
}

输出:

Exception in thread "main" java.text.ParseException: Unparseable date: "1999-0909"
	at java.base/java.text.DateFormat.parse(DateFormat.java:395)
	at lianxi.lianxi1.main(lianxi1.java:20)

采用try-catch处理异常:

package lianxi;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class lianxi1 {
    public static void main(String[] args) {
        //Exception:编译期异常,进行编译(写代码)java程序出现的问题
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");//用来格式化日期
        Date date = null;//把字符串格式的日期,解析为Date格式的日期
        try {
            date = sdf.parse("1999-0909");
        } catch (ParseException e) {
            e.printStackTrace();
        }
        System.out.println(date);
        System.out.println("后续代码");
    }
}

输出:(出现异常后能继续输出后面的东西)

java.text.ParseException: Unparseable date: "1999-0909"
	at java.base/java.text.DateFormat.parse(DateFormat.java:395)
	at lianxi.lianxi1.main(lianxi1.java:13)
null
后续代码

(2)运行期异常

package lianxi;

public class lianxi1 {
    public static void main(String[] args) {
        //RuntimeException:运行期异常,java程序运行过程中出现的问题
        int[] arr = {1,2,3};
        try{
            //可能会出现异常的代码
            System.out.println(arr[3]);
        }catch (Exception e){
            //异常的处理逻辑
            System.out.println(e);
        }
        System.out.println("后续代码");
    }
}

输出:

java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
后续代码

(3)Error错误

package lianxi;

public class lianxi1 {
    public static void main(String[] args) {
        /*
            Error:错误
            OutOfMemoryError: Java heap space
            内存溢出的错误,创建的数组太大了,超出了给JVM分配的内存
         */
        //int[] arr = new int[1024 * 1024 * 1024];
        //必须修改代码,创建的数组小一点
        int[] arr = new int[1024 * 1024];
        System.out.println("后续代码");
    }
}

1.4 异常的产生过程解析

例子1:
在这里插入图片描述
在这里插入图片描述
例子2:
在这里插入图片描述
第二章 异常的处理

Java异常处理的五个关键字:try、catch、finally、throw、throws

2.1 抛出异常throw

package lianxi;

/*
    throw关键字
    作用:
        可以使用throw关键字在指定的方法中抛出指定的异常
    使用格式:
        throw new xxxxException("异常产生的原因");
    注意:
        1.throw关键字必须写在方法的内部
        2.throw关键字后边new的对象必须是Exception或者Exception的子类对象
        3.throw关键字如果抛出指定的异常对象,我们就必须处理这个异常
            如果throw关键字后边创建的是RuntimeException或者是RuntimeException的子类对象,我们可以不处理,默认交给JVM处理(打印异常对象,中断程序)
            如果throw关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么throws,要么try...catch
 */

public class lianxi1 {
    public static void main(String[] args) {
        //int[] arr = null;
        int[] arr = new int[3];
        int e = getElement(arr, 3);
        System.out.println(e);
    }
    /*
        定义一个方法,获取数组指定索引处的元素
        参数:
            int[] arr
            int index
        以后(工作中)我们首先必须对方法传递过来的参数进行合法性校验
        如果参数不合法,那么我们就必须使用抛出异常的方式,告知方法的调用者传递的参数有问题
        注意:
            NullPointerException是一个运行期异常,我们不用处理,默认交给JVM处理
            ArrayIndexOutOfBoundsException是一个运行期异常,我们不用处理,默认交给JVM处理
     */

    public static int getElement(int[] arr, int index) {
        /*
            我们可以对传递过来的参数数组,进行合法性校验
            如果数组arr的值是anull
            那么我们就抛出空指针异常,告知方法的调用者"传递的数组的值是null"
         */
        if (arr == null) {
            throw new NullPointerException("传递的数组的值是null");
        }

        /*
            我们可以对传递过来的参数index进行合法性校验
            如果index的范围不在数组的索引范围内
            那么我们就抛出数组索引越界异常,告知方法的调用者"传递的索引超出了数组的使用范围"
         */
        if (index < 0 || index > arr.length - 1) {
            throw new ArrayIndexOutOfBoundsException("传递的索引超出了数组的使用范围");
        }

        int ele = arr[index];
        return ele;
    }

}

2.2 Objects非空判断
在这里插入图片描述

package lianxi;

import java.util.Objects;

public class lianxi1 {
    public static void main(String[] args) {
        method(null);
    }

    public static void method(Object obj) {
        //对传递过来的参数进行合法性判断,判断是否为null
        /*
        if(obj==null){
            throw new NullPointerException("传递的对象的值是null");
        }
        */

        //可以直接调用下面这个方法,不用自己手写上面的判断
        //Objects.requireNonNull(obj);
        Objects.requireNonNull(obj, "传递的对象的值是null");
    }
}

输出:

Exception in thread "main" java.lang.NullPointerException: 传递的对象的值是null
	at java.base/java.util.Objects.requireNonNull(Objects.java:246)
	at lianxi.lianxi1.method(lianxi1.java:20)
	at lianxi.lianxi1.main(lianxi1.java:7)

2.3 声明异常throws
在这里插入图片描述

package lianxi;

import java.io.FileNotFoundException;
import java.io.IOException;

public class lianxi1 {
    public static void main(String[] args) throws Exception {
        readFile("d:\\a.txt");
    }

    /*
        定义一个方法,对传递的文件路径进行合法性判断
        如果路径不是"c:\\a.txt",那么我们就抛出文件找不到异常对象,告知方法的调用者
        注意:
            FileNotFoundException是编译异常,抛出了编译异常,就必须处理这个异常
            可以使用throws继续声明抛出FileNotFoundException这个异常对象,让方法的调用者处理
     */

    /*
        FileNotFoundException extends IOException extends Exception
        如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可
     */
    //public static void readFile(String fileName) throws FileNotFoundException, IOException {
    //public static void readFile(String fileName) throws IOException {
    public static void readFile(String fileName) throws Exception {
        if (!fileName.equals("c:\\a.tx")) {
            throw new FileNotFoundException("传递的文件路径不是c:\\a.txt");
        }

        /*
            如果传递的路径不是.txt结尾
            那么我们就抛出IO异常对象,告知方法的调用者,文件的后缀名不对
         */
        if (!fileName.endsWith(".txt")) {
            throw new IOException("文件的后缀名不对");
        }

        System.out.println("路径没有问题,读取文件");
    }
}

2.4 捕获异常try…catch
在这里插入图片描述
在这里插入图片描述

package lianxi;

import java.io.IOException;

public class lianxi1 {
    public static void main(String[] args) throws IOException {
        try {
            //可能产生异常的代码
            readFile("d\\a.tx");
        } catch (IOException e) {//try中抛出什么异常对象,catch就定义什么异常变量,用来接收这个异常对象
            //异常的处理逻辑,产生异常对象之后怎么处理异常对象
            System.out.println("catch - 传递的文件后缀不是.txt");

            /*
                Throwable类中定义了3个异常处理的方法
                    String getMessage() 返回此 throwable 的简短描述
                    String toString() 返回此 throwable 的详细消息字符串
                    void printStackTrace() JVM打印异常对象,默认调用此方法,打印的异常信息是最全面的
             */
            System.out.println(e.getMessage());//文件的后缀名不对
            System.out.println(e.toString());//java.io.IOException: 文件的后缀名不对   (已经重写了Object类的toString方法)
            System.out.println(e);//java.io.IOException: 文件的后缀名不对

            /*
                java.io.IOException: 文件的后缀名不对
	                at lianxi.lianxi1.readFile(lianxi1.java:35)
	                at lianxi.lianxi1.main(lianxi1.java:9)
             */
            e.printStackTrace();
        }
        System.out.println("后续代码");
    }

    /*
        如果传递的路径不是.txt结尾
        那么我们就抛出IO异常对象,告知方法的调用者,文件的后缀名不对
    */
    public static void readFile(String fileName) throws IOException {
        if (!fileName.endsWith(".txt")) {
            throw new IOException("文件的后缀名不对");
        }

        System.out.println("路径没有问题,读取文件");
    }
}

输出:

catch - 传递的文件后缀不是.txt
文件的后缀名不对
java.io.IOException: 文件的后缀名不对
java.io.IOException: 文件的后缀名不对
后续代码
java.io.IOException: 文件的后缀名不对
	at lianxi.lianxi1.readFile(lianxi1.java:35)
	at lianxi.lianxi1.main(lianxi1.java:9)

2.5 finally 代码块
在这里插入图片描述
在这里插入图片描述
2.6 异常注意事项
在这里插入图片描述

package lianxi;

/*
    如果finally有return语句,永远返回finally中的结果,避免该情况
 */

public class lianxi1 {
    public static void main(String[] args) {
        int a = getA();
        System.out.println(a);
    }

    //定义一个方法,返回变量a的值
    public static int getA() {
        int a = 10;
        try {
            return a;
        } catch (Exception e) {
            System.out.println(e);
        } finally {
            a = 100;
            return a;
        }
    }
}

输出:

100
package lianxi;

/*
    子父类的异常:
        - 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
        - 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
    注意:
        父类异常时什么样,子类异常就什么样
 */
public class Fu {
    public void show01() throws NullPointerException, ClassCastException {
    }

    public void show02() throws IndexOutOfBoundsException {
    }

    public void show03() throws IndexOutOfBoundsException {
    }
}

class Zi extends Fu {
    //子类重写父类方法时,抛出和父类相同的异常
    public void show01() throws NullPointerException, ClassCastException {
    }

    //子类重写父类方法时,抛出父类异常的子类
    public void show02() throws ArrayIndexOutOfBoundsException {
    }

    //子类重写父类方法时,不抛出异常
    public void show03() {
    }

    //父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。
    //public void show04() throws Exception{} //这样写是错误的

    //此时子类产生该异常,只能捕获处理,不能声明抛出
    public void show04() {
        try {
            throw new Exception("编译期异常");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

总结一下就是子类继承父类只能抛出范围小于等于父类异常的异常(子类或者本身)

第三章 自定义异常

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

package lianxi;

/*
    自定义异常类:
        java提供的异常类,不够我们使用,需要自己定义一些异常类
    格式:
        public class XXXException extends Exception | RuntimeException{
            添加一个空参数的构造方法
            添加一个带异常信息的构造方法
        }
        注意:
            1.自定义异常类一般都是以Exception结尾,说明该类是一个异常类
            2.自定义异常类,必须得继承Exception或者RuntimeException
                继承Exception:那么自定义的异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try...catch
                继承RuntimeException:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)
 */

public class RegisterException extends Exception {
    //添加一个空参数的构造方法
    public RegisterException() {
        super();
    }

    /*
        添加一个带异常信息的构造方法
        查看源码发现,所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常信息
     */
    public RegisterException(String message) {
        super(message);
    }
}

3.2 自定义异常的练习
在这里插入图片描述
首先定义一个登陆异常类RegisterException:
RegisterException.java(继承编译期异常)

package lianxi;

public class RegisterException extends Exception {
    //添加一个空参数的构造方法
    public RegisterException() {
        super();
    }

    /*
        添加一个带异常信息的构造方法
        查看源码发现,所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常信息
     */
    public RegisterException(String message) {
        super(message);
    }
}

Demo01RegisterException.java
(1)用throws处理

package lianxi;

import java.security.PublicKey;
import java.util.Scanner;

public class Demo01RegisterException {
    //1.使用数组保存已经注册过的用户名(数据库)
    static String[] usernames = {"张三","李四","王五"};

    public static void main(String[] args) throws RegisterException {
        //2.使用Scanner获取用户输入的注册的用户名(前端,页面)
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入您要注册的用户名:");
        String username = sc.next();
        checkUsername(username);
    }

    //3.定义一个方法,对用户输入的注册的用户名进行判断
    public static void checkUsername(String username) throws RegisterException{
        //遍历存储已经注册过用户名的数组,获取每一个用户名
        for (String name : usernames) {
            //使用获取到的用户名和用户输入的用户名比较
            if(name.equals(username)){
                //true:用户名已经存在,抛出RegisterException异常,告知用户"亲,该用户名已经被注册";
                throw new RegisterException("亲,该用户名已经被注册");
            }
        }

        //如果循环结束了,还没有找到重复的用户名,提示用户"恭喜您,注册成功!"
        System.out.println("恭喜您,注册成功!");
    }
}

(2)用try…catch处理

package lianxi;

import java.security.PublicKey;
import java.util.Scanner;

public class Demo01RegisterException {
    //1.使用数组保存已经注册过的用户名(数据库)
    static String[] usernames = {"张三", "李四", "王五"};

    public static void main(String[] args) {
        //2.使用Scanner获取用户输入的注册的用户名(前端,页面)
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入您要注册的用户名:");
        String username = sc.next();
        checkUsername(username);
    }

    //3.定义一个方法,对用户输入的注册的用户名进行判断
    public static void checkUsername(String username) {
        //遍历存储已经注册过用户名的数组,获取每一个用户名
        for (String name : usernames) {
            //使用获取到的用户名和用户输入的用户名比较
            if (name.equals(username)) {
                //true:用户名已经存在,抛出RegisterException异常,告知用户"亲,该用户名已经被注册";
                try {
                    throw new RegisterException("亲,该用户名已经被注册");
                } catch (RegisterException e) {
                    e.printStackTrace();
                    return;//结束方法
                }
            }
        }

        //如果循环结束了,还没有找到重复的用户名,提示用户"恭喜您,注册成功!"
        System.out.println("恭喜您,注册成功!");
    }
}

输出结果:

请输入您要注册的用户名:
赵六
恭喜您,注册成功!
请输入您要注册的用户名:
张三
lianxi.RegisterException: 亲,该用户名已经被注册
	at lianxi.Demo01RegisterException.checkUsername(Demo01RegisterException.java:26)
	at lianxi.Demo01RegisterException.main(Demo01RegisterException.java:15)

RegisterException.java(继承运行期异常)

package lianxi;

public class RegisterException extends RuntimeException {
    //添加一个空参数的构造方法
    public RegisterException() {
        super();
    }

    /*
        添加一个带异常信息的构造方法
        查看源码发现,所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常信息
     */
    public RegisterException(String message) {
        super(message);
    }
}

Demo02RegisterException.java

package lianxi;

import java.util.Scanner;

public class Demo02RegisterException {
    //1.使用数组保存已经注册过的用户名(数据库)
    static String[] usernames = {"张三", "李四", "王五"};

    public static void main(String[] args) {
        //2.使用Scanner获取用户输入的注册的用户名(前端,页面)
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入您要注册的用户名:");
        String username = sc.next();
        checkUsername(username);
    }

    //3.定义一个方法,对用户输入的注册的用户名进行判断
    public static void checkUsername(String username) {
        //遍历存储已经注册过用户名的数组,获取每一个用户名
        for (String name : usernames) {
            //使用获取到的用户名和用户输入的用户名比较
            if (name.equals(username)) {
                //true:用户名已经存在,抛出RegisterException异常,告知用户"亲,该用户名已经被注册";
                throw new RegisterException("亲,该用户名已经被注册");//抛出运行期异常,无需处理,交给JVM处理,中断处理
            }
        }

        //如果循环结束了,还没有找到重复的用户名,提示用户"恭喜您,注册成功!"
        System.out.println("恭喜您,注册成功!");
    }
}

输出:

请输入您要注册的用户名:
小花
恭喜您,注册成功!
请输入您要注册的用户名:
张三
Exception in thread "main" lianxi.RegisterException: 亲,该用户名已经被注册
	at lianxi.Demo02RegisterException.checkUsername(Demo02RegisterException.java:24)
	at lianxi.Demo02RegisterException.main(Demo02RegisterException.java:14)

三十八、线程

第一章 线程

1.1 并发与并行
在这里插入图片描述
在这里插入图片描述
1.2 线程与进程
在这里插入图片描述
进程:
在这里插入图片描述
线程:
在这里插入图片描述
在这里插入图片描述
1.3 创建线程类

单线程:
在这里插入图片描述

Person.java

package lianxi;

public class Person {
    private String name;

    public void run() {
        //定义循环,执行5次
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "-->" + i);
        }
    }

    public Person() {
    }

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

    public String getName() {
        return name;
    }

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

lianxi1.java

package lianxi;

/*
    主线程:执行主(main)方法的线程

    单线程程序:java程序中只有一个线程
    执行从main方法开始,从上到下依次执行
 */

public class lianxi1 {
    public static void main(String[] args) {
        Person p1 = new Person("小强");
        p1.run();

        Person p2 = new Person("旺财");
        p2.run();
    }
}

输出:

小强-->0
小强-->1
小强-->2
小强-->3
小强-->4
旺财-->0
旺财-->1
旺财-->2
旺财-->3
旺财-->4

单线程弊端:
lianxi1.java

package lianxi;

public class lianxi1 {
    public static void main(String[] args) {
        Person p1 = new Person("小强");
        p1.run();
        System.out.println(0 / 0);//ArithmeticException: / by zero
        Person p2 = new Person("旺财");
        p2.run();
    }
}

输出:

小强-->0
小强-->1
小强-->2
小强-->3
小强-->4
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at lianxi.lianxi1.main(lianxi1.java:14)

可以发现,出现异常后,p2.run()没有运行

多线程:
在这里插入图片描述
MyThread.java

package lianxi;

//1.创建一个Thread类的子类
public class MyThread extends Thread {
    //2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("run:" + i);
        }
    }
}

DemoThread.java

package lianxi;

/*
    创建多线程程序的第一种方式:创建Thread类的子类
    java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类

    实现步骤:
        1.创建一个Thread类的子类
        2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
        3.创建Thread类的子类对象
        4.调用Thread类中的方法start方法,开启新的线程,执行run方法
            void start() 使该线程开始执行;Java虚拟机调用该线程的run方法。
            结果是两个线程并发地运行;当前线程(main线程)和另一个线程(创建的新线程,执行其run方法)。
            多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
        java程序属于抢占式调度,哪个线程的优先级高就优先执行,同一个优先级则随机选择一个执行
 */
public class DemoThread {
    public static void main(String[] args) {
        //3.创建Thread类的子类对象
        MyThread mt = new MyThread();
        //4.调用Thread类中的方法start方法,开启新的线程,执行run方法
        mt.start();

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

输出:

run:0
main:0
run:1
main:1
run:2
main:2
run:3
main:3
run:4
main:4

第二章 多线程

2.1 多线程原理
在这里插入图片描述
多线程内存图解:

图解1:
在这里插入图片描述
图解2:
在这里插入图片描述
2.2 Thread类
在这里插入图片描述
(1)获取线程名称的方法

DemoThread.java

package lianxi;

/*
    线程的名称:
        主线程:main
        新线程:Thread-0,Thread-1,Thread-2 ...
 */
public class DemoThread {
    public static void main(String[] args) {
        //创建Thread类的子类对象
        MyThread mt = new MyThread();
        //调用start方法,开启新线程,执行run方法
        mt.start();

        new MyThread().start();
        new MyThread().start();

        //链式编程
        System.out.println(Thread.currentThread().getName());
    }
}

MyThread.java

package lianxi;

/*
    获取线程的名称:
        1.使用Thread类中的方法getName()
            String getName() 返回该线程的名称。
        2.可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
            static Thread currentThread() 返回对当前正在执行的线程对象的引用。
 */
//定义一个Thread类的子类
public class MyThread extends Thread {
    //重写Thread类中的run方法,设置线程任务
    @Override
    public void run() {
        //获取线程名称
        String name = getName();
        System.out.println(name);
    }
}

输出:

Thread-0
Thread-2
main
Thread-1

MyThread.java

package lianxi;

//定义一个Thread类的子类
public class MyThread extends Thread {
    //重写Thread类中的run方法,设置线程任务
    @Override
    public void run() {
        //获取线程名称
		Thread t = Thread.currentThread();
        System.out.println(t);//Thread[Thread-x,5,main] 5是优先级
        String name1 = t.getName();
        System.out.println(name1);
    }
}

输出:

main
Thread[Thread-2,5,main]
Thread[Thread-0,5,main]
Thread-0
Thread[Thread-1,5,main]
Thread-1
Thread-2

MyThread.java

package lianxi;

//定义一个Thread类的子类
public class MyThread extends Thread {
    //重写Thread类中的run方法,设置线程任务
    @Override
    public void run() {
        //链式编程
        System.out.println(Thread.currentThread().getName());
    }
}

输出:

Thread-2
main
Thread-0
Thread-1

(2)设置线程名称的方法

MyThread.java

package lianxi;

/*
    设置线程的名称:(了解)
        1.使用Thread类中的方法setName(名字)
            void setName() 改变该线程的名称,使之与参数name相同。
        2.创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
            Thread (String name) 分配新的Thread对象。
 */
//定义一个Thread类的子类
public class MyThread extends Thread {
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        //获取线程的名称
        System.out.println(Thread.currentThread().getName());
    }
}

DemoThread.java

package lianxi;

public class DemoThread {
    public static void main(String[] args) {
        //开启多线程
        MyThread mt = new MyThread();
        mt.setName("小强");
        mt.start();

        //开启多线程
        new MyThread("旺财").start();
    }
}

输出:

旺财
小强

(3)sleep方法

package lianxi;

/*
    public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
    毫秒数结束之后,线程继续执行
 */

public class DemoThread {
    public static void main(String[] args) {
        //模拟秒表
        for (int i = 0; i < 60; i++) {
            System.out.println(i);

            //使用Thread类的sleep方法让程序睡眠1秒钟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2.3 创建线程方式二

DemoRunnable.java

package lianxi;

/*
    创建多线程程序的第二种方式:实现Runnable接口
    java.lang.Runnable
        Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
    java.lang.Thread类的构造方法
        Thread(Runnable target) 分配新的Thread对象
        Thread(Runnable target,String name) 分配新的Thread对象

    实现步骤:
        1.创建一个Runnable接口的实现类
        2.在实现类中重写Runnable接口的run方法,设置线程任务
        3.创建一个Runnable接口的实现类对象
        4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        5.调用Thread类 中的start方法,开启新的线程执行run方法
 */

public class DemoRunnable {
    public static void main(String[] args) {
        //3.创建一个Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t = new Thread(run);
        // 5.调用Thread类 中的start方法,开启新的线程执行run方法
        t.start();

        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

RunnableImpl.java

package lianxi;

//1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable {
    //2.在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

输出:

main-->0
Thread-0-->0
main-->1
main-->2
Thread-0-->1
main-->3
main-->4
Thread-0-->2
main-->5
main-->6
Thread-0-->3
main-->7
main-->8
Thread-0-->4
main-->9
main-->10
main-->11
main-->12
main-->13
main-->14
main-->15
Thread-0-->5
main-->16
Thread-0-->6
main-->17
Thread-0-->7
main-->18
Thread-0-->8
main-->19
Thread-0-->9
Thread-0-->10
Thread-0-->11
Thread-0-->12
Thread-0-->13
Thread-0-->14
Thread-0-->15
Thread-0-->16
Thread-0-->17
Thread-0-->18
Thread-0-->19

2.4 Thread和Runnable的区别
在这里插入图片描述

实现Runnable接口创建多线程程序的好处:
        1.避免了单继承的局限性
            一个类只能继承一个类,类继承了Thread类就不能继承其他的类
            实现了Runnable接口,还可以继承其他的类,实现其他的接口
        2.增强了程序的扩展性,降低了程序的耦合性(解耦)
            实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
            实现类中,重写了run方法:用来设置线程任务
            创建Thread类对象,调用start方法:用来开启新线程

2.5 匿名内部类方式实现线程的创建

package lianxi;

/*
    匿名内部类方式实现线程的创建

    匿名:没有名字
    内部类:写在其他类内部的类

    匿名内部类作用:简化代码
        把子类继承父类,重写父类的方法,创建子类对象合成一步完成
        把实现类实现接口,重写接口中的方法,创建实现类对象合成一步完成
    匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

    格式:
        new 父类/接口(){
            重写父类/接口中的方法
        };
 */

public class DemoInnerClassThread {
    public static void main(String[] args) {
        //线程的父类是Thread
        //new MyThread().start();
        new Thread() {
            //重写run方法,设置线程任务
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + "i");
                }
            }
        }.start();

        //线程的接口Runnable
        //Runnable r = new RunnableImpl();//多态
        Runnable r = new Runnable() {
            //重写run方法,设置线程任务
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + "i");
                }
            }
        };
        new Thread(r).start();

        //可以简化接口的方式
        new Thread(new Runnable() {
            //重写run方法,设置线程任务
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + "i");
                }
            }
        }).start();
    }
}

输出:

Thread-2-->i
Thread-0-->i
Thread-1-->i
Thread-0-->i
Thread-2-->i
Thread-0-->i
Thread-1-->i
Thread-0-->i
Thread-2-->i
Thread-0-->i
Thread-1-->i
Thread-2-->i
Thread-1-->i
Thread-2-->i
Thread-1-->i

第三章 线程安全

3.1 线程安全
在这里插入图片描述
在这里插入图片描述
模拟票:

public class Ticket implements Runnable {
    private int ticket = 100;

    /**
     * 执行卖票操作
     */
    @Override
    public void run() {
        //每个窗口卖票的操作
        //窗口 永远开启
        while (true) {
            if (ticket > 0) {//有票 可以卖
                // 出票操作
                //使用sleep模拟一下出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) { // TODO Auto‐generated catch block
                    e.printStackTrace();
                }
                //获取当前线程对象的名字
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在卖:" + ticket‐‐);
            }
        }
    }
}

测试类:

public class Demo {
    public static void main(String[] args) {
        //创建线程任务对象 
        Ticket ticket = new Ticket();
        //创建三个窗口对象 
        Thread t1 = new Thread(ticket, "窗口1");
        Thread t2 = new Thread(ticket, "窗口2");
        Thread t3 = new Thread(ticket, "窗口3");
        //同时卖票 
        t1.start();
        t2.start();
        t3.start();
    }
}

在这里插入图片描述
在这里插入图片描述
3.2 线程同步
在这里插入图片描述
3.3 同步代码块
在这里插入图片描述
RunnableImpl.java

package lianxi;

public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private int ticket = 100;

    //创建一个锁对象
    Object obj = new Object();

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复执行
        while (true) {
            //同步代码块
            synchronized (obj) {
                //先判断票是否存在
                if (ticket > 0) {
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                    ticket--;
                }
            }
        }
    }
}

DemoTicket.java

package lianxi;

public class DemoTicket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

同步技术的原理:
在这里插入图片描述

3.4 同步方法
在这里插入图片描述
RunnableImpl.java

package lianxi;

/*
    解决线程安全问题的第二种方案:使用同步方法
    使用步骤:
        1.把访问了共享数据的代码抽取出来,放到一个方法中
        2.在方法上添加synchronized修饰符

    格式就是定义方法的格式:
    修饰符 synchronized 返回值类型 方法名(参数列表){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }
 */

public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private int ticket = 50;

    //设置线程任务:卖票
    @Override
    public void run() {
        System.out.println("this:" + this);//this:lianxi.RunnableImpl@506e1b77
        while (ticket > 0) {
            payTicket();
        }
    }

    /*
        定义一个同步方法
        同步方法也会把方法内部的代码锁住
        只让一个线程执行
        同步方法的锁对象是谁?
        就是实现类对象 new RunnableImpl()
        也就是this
     */
    public synchronized void payTicket() {
        //先判断票是否存在
        if (ticket > 0) {
            //提高安全问题出现的概率,让程序睡眠
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //票存在,卖票 ticket--
            System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
            ticket--;
        }
    }
}

DemoTicket.java

package lianxi;

public class DemoTicket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        System.out.println("run:" + run);//run:lianxi.RunnableImpl@506e1b77
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

3.5 静态同步方法

RunnableImpl.java

package lianxi;

public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private static int ticket = 50;

    //设置线程任务:卖票
    @Override
    public void run() {
        while (ticket > 0) {
            payTicketStatic();
        }
    }

    /*
        静态的同步方法
        锁对象是谁?
        不能是this,因为this是创建对象之后产生的,静态方法优先于对象
        静态方法的锁对象是本类的class属性-->class文件对象(反射)
     */
    public static synchronized void payTicketStatic() {
        //先判断票是否存在
        if (ticket > 0) {
            //提高安全问题出现的概率,让程序睡眠
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //票存在,卖票 ticket--
            System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
            ticket--;
        }
    }
}

DemoTicket.java

package lianxi;

public class DemoTicket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

3.6 Lock锁
在这里插入图片描述
RunnableImpl.java

package lianxi;

/*
    解决线程安全问题的第三种方案:使用Lock锁
    java.util.concurrent.locks.Lock接口
    Lock实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作
    Lock接口中的方法:
        void lock() 获取锁
        void unlock() 释放锁
    java.util.concurrent.locks.ReentrantLock implements Lock接口

    使用步骤:
        1.在成员位置创建一个ReentrantLock对象
        2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
        3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

 */

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

public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private static int ticket = 50;

    //1.在成员位置创建一个ReentrantLock对象
    Lock l = new ReentrantLock();

    //设置线程任务:卖票
    @Override
    public void run() {
        while (ticket > 0) {
            //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
            l.lock();

            //先判断票是否存在
            if (ticket > 0) {
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(100);
                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
                    l.unlock();//无论程序是否异常,都会把锁释放
                }
            }
        }
    }
}

DemoTicket.java

package lianxi;

public class DemoTicket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

第四章 线程状态

4.1 线程状态概述
在这里插入图片描述
在这里插入图片描述
4.2 Timed Waiting(计时等待)
在这里插入图片描述
在这里插入图片描述
4.3 BLOCKED(锁阻塞)
在这里插入图片描述
4.4 Waiting(无限等待)

Wating状态在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。
在这里插入图片描述
在这里插入图片描述
代码实现:
在这里插入图片描述

package lianxi;

public class DemoWaitAndNotify {
    public static void main(String[] args) {
        //创建锁对象,保证唯一
        Object obj = new Object();
        //创建一个顾客线程(消费者)
        new Thread() {
            @Override
            public void run() {
                //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                synchronized (obj) {
                    System.out.println("告知老板要的包子的种类和数量");
                    //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //唤醒之后执行的代码
                    System.out.println("包子已经做好了,开吃!");
                }
            }
        }.start();

        //创建一个老板线程(生产者)
        new Thread() {
            @Override
            public void run() {
                //花5秒做包子
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                synchronized (obj) {
                    System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
                    obj.notify();
                }
            }
        }.start();
    }
}

4.5 Object类中wait带参方法和notifyAll方法
在这里插入图片描述
Object.notify() 如果有多个等待线程,随机唤醒一个
Object.notifyAll() 唤醒所有等待的线程

4.6 补充知识点
在这里插入图片描述

三十九、线程池

第一章 等待唤醒机制

1.1 线程间通信
在这里插入图片描述
1.2 等待唤醒机制
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1.3 生产者与消费者问题
在这里插入图片描述
在这里插入图片描述
代码演示:

包子资源类:

public class BaoZi {
     String  pier ;
     String  xianer ;
     boolean  flag = false ;//包子资源 是否存在  包子资源状态
}

吃货线程类:

public class ChiHuo extends Thread{
    private BaoZi bz;

    public ChiHuo(String name,BaoZi bz){
        super(name);
        this.bz = bz;
    }
    @Override
    public void run() {
        while(true){
            synchronized (bz){
                if(bz.flag == false){//没包子
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("吃货正在吃"+bz.pier+bz.xianer+"包子");
                bz.flag = false;
                bz.notify();
            }
        }
    }
}

包子铺线程类:

public class BaoZiPu extends Thread {

    private BaoZi bz;

    public BaoZiPu(String name,BaoZi bz){
        super(name);
        this.bz = bz;
    }

    @Override
    public void run() {
        int count = 0;
        //造包子
        while(true){
            //同步
            synchronized (bz){
                if(bz.flag == true){//包子资源  存在
                    try {

                        bz.wait();

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

                // 没有包子  造包子
                System.out.println("包子铺开始做包子");
                if(count%2 == 0){
                    // 冰皮  五仁
                    bz.pier = "冰皮";
                    bz.xianer = "五仁";
                }else{
                    // 薄皮  牛肉大葱
                    bz.pier = "薄皮";
                    bz.xianer = "牛肉大葱";
                }
                count++;

                bz.flag=true;
                System.out.println("包子造好了:"+bz.pier+bz.xianer);
                System.out.println("吃货来吃吧");
                //唤醒等待线程 (吃货)
                bz.notify();
            }
        }
    }
}

测试类:

public class Demo {
    public static void main(String[] args) {
        //等待唤醒案例
        BaoZi bz = new BaoZi();

        ChiHuo ch = new ChiHuo("吃货",bz);
        BaoZiPu bzp = new BaoZiPu("包子铺",bz);

        ch.start();
        bzp.start();
    }
}

执行效果:

包子铺开始做包子
包子造好了:冰皮五仁
吃货来吃吧
吃货正在吃冰皮五仁包子
包子铺开始做包子
包子造好了:薄皮牛肉大葱
吃货来吃吧
吃货正在吃薄皮牛肉大葱包子
包子铺开始做包子
包子造好了:冰皮五仁
吃货来吃吧
吃货正在吃冰皮五仁包子

第二章 线程池

2.1 线程池思想概述
在这里插入图片描述
2.2 线程池概念

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
在这里插入图片描述
在这里插入图片描述
2.3 线程池的使用
在这里插入图片描述
Runnable实现类代码:

package lianxi;

/*
    2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
 */
public class RunnableImpl implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "创建了一个新的线程执行");
    }
}

线程池测试类:

package lianxi;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPool {
    public static void main(String[] args) {
        //1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
        ExecutorService es = Executors.newFixedThreadPool(2);
        //3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
        es.submit(new RunnableImpl());
        //线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程池可以继续使用
        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());

        //4.调用ExecutorService中的方法shutdown销毁线程池
        es.shutdown();
    }
}

输出结果:

pool-1-thread-1创建了一个新的线程执行
pool-1-thread-2创建了一个新的线程执行
pool-1-thread-1创建了一个新的线程执行

第三章 Lambda表达式

3.1 函数式编程思想概述
在这里插入图片描述
3.2 冗余的Runnable代码
在这里插入图片描述
在这里插入图片描述
3.3 编程思想转换
在这里插入图片描述
3.4 体验Lambda的更优写法
在这里插入图片描述
3.5 回顾匿名内部类
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.6 Lambda标准格式
在这里插入图片描述
3.7 练习:使用Lambda标准格式(无参无返回)
在这里插入图片描述
解答:
Cook.java

package lianxi;

/*
    定义一个厨子Cook接口,内含唯一的抽象方法makeFood
 */
public interface Cook {
    //定义无参数无返回值的方法makeFood
    public abstract void makeFood();
}

DemoCook.java

package lianxi;

public class DemoCook {
    public static void main(String[] args) {
        //调用invokeCook方法,参数是Cook接口,传递Cook接口的匿名内部类对象
        invokeCook(new Cook() {
            @Override
            public void makeFood() {
                System.out.println("吃饭了");
            }
        });

        //使用Lambda表达式,简化匿名内部类的书写
        invokeCook(() -> {
            System.out.println("吃饭了");
        });
    }

    //定义一个方法,参数传递Cook接口,方法内部调用Cook接口中的方法makeFood
    public static void invokeCook(Cook cook) {
        cook.makeFood();
    }
}

3.8 Lambda的参数和返回值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.9 练习:使用Lambda标准格式(有参有返回)
在这里插入图片描述
3.10 Lambda省略格式
在这里插入图片描述
在这里插入图片描述
3.11 练习:使用Lambda省略格式
在这里插入图片描述
3.12 Lambda的使用前提
在这里插入图片描述

四十、File类、递归

第一章 File类

1.1 概述

java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。
在这里插入图片描述
1.2 File类的静态成员变量
在这里插入图片描述
在这里插入图片描述
1.3 绝对路径和相对路径
在这里插入图片描述
1.4 构造方法
在这里插入图片描述

package lianxi;

import java.io.File;

public class DemoFIle {
    public static void main(String[] args) {
        /*
            File类的构造方法
         */
        show01();
        System.out.println("--------------------");
        //show02("c:\\", "a.txt");//c:\a.txt
        show02("d:\\", "a.txt");//d:\a.txt
        System.out.println("--------------------");
        show03();
    }

    /*
        File(String pathname) 通过将给定路径名字符串转换为抽象路径名来创建一个新File实例
        参数:
            String pathname:字符串的路径名称
            路径可以是以文件结尾,也可以是文件夹结尾
            路径可以是相对路径,也可以是绝对路径
            路径可以是存在,也可以是不存在
            创建File对象,只是把字符串路径封装为File对象,不考虑路径的真假情况
     */
    private static void show01() {
        File f1 = new File("C:\\Users\\itcast\\IdeaProjects\\shungyuan\\a.txt");
        System.out.println(f1);//重写了Object类的toString方法 C:\Users\itcast\IdeaProjects\shungyuan\a.txt

        File f2 = new File("C:\\Users\\itcast\\IdeaProjects\\shungyuan");
        System.out.println(f2);//C:\Users\itcast\IdeaProjects\shungyuan

        File f3 = new File("b.txt");
        System.out.println(f3);//b.txt
    }

    /*
        File(String parent, String child) 根据 parent 路径名字符串和 child 路径名字符串创建一个新File实例
        参数:把路径分成了两部分
            String parent:父路径
            String child:子路径
        好处:
            父路径和子路径可以单独书写,使用起来非常灵活;父路径和子路径都可以变化
     */
    private static void show02(String parent, String child) {
        File file = new File(parent, child);
        System.out.println(file);
    }

    /*
        File(File parent, String child) 根据 parent 抽象路径名和 child 路径名字符串创建一个新File实例
        参数:把路径分成了两部分
            File parent:父路径
            String child:子路径
        好处:
            父路径和子路径可以单独书写,使用起来非常灵活;父路径和子路径都可以变化
            父路径是File类型,可以使用File的方法对路径进行一些操作,再使用路径创建对象
     */
    private static void show03() {
        File parent = new File("c:\\");
        File file = new File(parent, "hello.java");
        System.out.println(file);//c:\hello.java
    }
}

输出:

C:\Users\itcast\IdeaProjects\shungyuan\a.txt
C:\Users\itcast\IdeaProjects\shungyuan
b.txt
--------------------
d:\a.txt
--------------------
c:\hello.java

1.5 常用方法

(1)获取功能的方法
在这里插入图片描述

package lianxi;

import java.io.File;

public class DemoFIle {
    public static void main(String[] args) {
        show01();
        System.out.println("-------------------");
        show02();
        System.out.println("-------------------");
        show03();
        System.out.println("-------------------");
        show04();
    }

    /*
        public String getAbsolutePath() :返回此File的绝对路径名字符串。
        获取构造方法中传递的路径
        无论路径是绝对的还是相对的,getAbsolutePath方法返回的都是绝对路径
     */
    private static void show01() {
        File f1 = new File("D:\\IDEA\\IdeaProjects\\practice2\\a.txt");
        String absolutePath1 = f1.getAbsolutePath();
        System.out.println(absolutePath1);//D:\IDEA\IdeaProjects\practice2\a.txt

        File f2 = new File("a.txt");
        String absolutePath2 = f2.getAbsolutePath();
        System.out.println(absolutePath2);//D:\IDEA\IdeaProjects\practice2\a.txt
    }

    /*
        public String getPath() :将此File转换为路径名字符串。
        获取构造方法中传递的路径

        toString方法调用的就是getPath方法
        源码:
            public String toString() {
                return getPath();
            }
     */
    private static void show02() {
        File f1 = new File("D:\\IDEA\\IdeaProjects\\practice2\\a.txt");
        File f2 = new File("a.txt");
        String path1 = f1.getPath();
        System.out.println(path1);//D:\IDEA\IdeaProjects\practice2\a.txt
        String path2 = f2.getPath();
        System.out.println(path2);//a.txt

        System.out.println(f1);//D:\IDEA\IdeaProjects\practice2\a.txt
        System.out.println(f1.toString());//D:\IDEA\IdeaProjects\practice2\a.txt
    }

    /*
        public String getName()  :返回由此File表示的文件或目录的名称。
        获取构造方法传递路径的结尾部分(文件/文件夹)
     */
    private static void show03() {
        File f1 = new File("D:\\IDEA\\IdeaProjects\\practice2\\a.txt");
        String name1 = f1.getName();
        System.out.println(name1);//a.txt

        File f2 = new File("D:\\IDEA\\IdeaProjects\\practice2");
        String name2 = f2.getName();
        System.out.println(name2);//practice2
    }

    /*
        public long length()  :返回由此File表示的文件的长度。
        获取构造方法指定的文件的大小,以字节为单位
        注意:
            文件夹是没有大小概念的,不能获取文件夹的大小
            如果构造方法中给出的路径不存在,那么length方法返回0
     */
    private static void show04() {
        File f1 = new File("E:\\图片\\中转站\\89563074.jpg");
        System.out.println(f1.length());//1707738

        File f2 = new File("E:\\图片\\中转站\\89563075.jpg");
        System.out.println(f2.length());//0

        File f3 = new File("E:\\图片\\中转站");
        System.out.println(f3.length());//12288
    }
}

输出:

D:\IDEA\IdeaProjects\practice2\a.txt
D:\IDEA\IdeaProjects\practice2\a.txt
-------------------
D:\IDEA\IdeaProjects\practice2\a.txt
a.txt
D:\IDEA\IdeaProjects\practice2\a.txt
D:\IDEA\IdeaProjects\practice2\a.txt
-------------------
a.txt
practice2
-------------------
1707738
0
12288

(2)判断功能的方法
在这里插入图片描述
在这里插入图片描述
(3)创建删除功能的方法
在这里插入图片描述

package lianxi;

import java.io.File;
import java.io.IOException;

public class DemoFIle {
    public static void main(String[] args) throws IOException {
        show01();
        System.out.println("--------------------");
        show02();
        System.out.println("--------------------");
        show03();
    }

    /*
        public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。
        创建文件的路径和名称在构造方法中给出(构造方法的参数)
        返回值:布尔值
            true:文件不存在,创建文件,返回true
            false:文件存在,不会创建,返回false
        注意:
            1.此方法只能创建文件,不能创建文件夹
            2.创建文件的路径必须存在,否则会抛出异常

     */
    private static void show01() throws IOException {
        File f1 = new File("D:\\IDEA\\IdeaProjects\\practice2\\1.txt");
        boolean b1 = f1.createNewFile();
        System.out.println("b1:" + b1);

        File f2 = new File("practice2\\2.txt");
        System.out.println(f2.createNewFile());
    }

    /*
        public boolean mkdir() :创建单级空文件夹
        public boolean mkdirs() :既可以创建单级空文件夹,也可以创建多级文件夹
        创建文件夹的路径和名称在构造方法中给出(构造方法的参数)
        返回值:布尔值
            true:文件夹不存在,创建文件夹,返回true
            false:文件夹存在,不会创建,返回false;若构造方法中给出的路径不存在则返回false
        注意:
            此方法只能创建文件夹,不能创建文件
     */
    private static void show02() {
        File f1 = new File("practice2\\aaa");
        boolean b1 = f1.mkdir();
        System.out.println("b1:" + b1);

        File f2 = new File("practice2\\111\\222\\333\\444");
        boolean b2 = f2.mkdirs();
        System.out.println("b2:" + b2);

        File f3 = new File("practice2\\abc.txt");
        boolean b3 = f3.mkdirs();//看类型,是一个文件夹
        System.out.println("b3:" + b3);

        File f4 = new File("prac\\ccc");
        boolean b4 = f4.mkdir();//不会抛出异常,路径不存在,不会创建
        System.out.println("b4:" + b4);
    }

    /*
        public boolean delete() :删除由此File表示的文件或目录。
        此方法可以删除构造方法路径中给出的文件/文件夹
        返回值:布尔值
            true:文件/文件夹删除成功,返回true
            false:文件/文件夹中有内容,不会删除返回false;构造方法中路径不存在返回false
        注意:
            delete方法是直接在硬盘删除文件/文件夹,不走回收站,删除要谨慎
     */
    private static void show03() {
        File f1 = new File("practice2\\aaa");
        boolean b1 = f1.delete();
        System.out.println("b1:" + b1);
    }
}

1.6 目录的遍历
在这里插入图片描述

package lianxi;

import java.io.File;

public class DemoFIle {
    public static void main(String[] args) {
        show01();
        System.out.println("--------------------");
        show02();
    }

    /*
        public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
        遍历构造方法中给出的目录,会获取目录中所有文件/文件夹的名称,把获取到的多个名称存储到一个String类型的数组中
     */
    private static void show01() {
        //File file = new File("D:\\IDEA\\IdeaProjects\\practice2\\1.txt");//NullPointerException
        //File file = new File("D:\\IDEA\\IdeaProjects\\pract");//NullPointerException
        File file = new File("D:\\IDEA\\IdeaProjects\\practice2");
        String[] arr = file.list();
        for (String fileName : arr) {
            System.out.println(fileName);
        }
    }

    /*
        public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。  
        遍历构造方法中给出的目录,会获取目录中所有文件/文件夹的名称,把文件/文件夹封装为File对象,多个File对象存储到File数组中
     */
    private static void show02() {
        File file = new File("D:\\IDEA\\IdeaProjects\\practice2");
        File[] files = file.listFiles();
        for (File f : files) {
            System.out.println(f);
        }
    }
}

输出:

.idea
out
practice2.iml
src
--------------------
D:\IDEA\IdeaProjects\practice2\.idea
D:\IDEA\IdeaProjects\practice2\out
D:\IDEA\IdeaProjects\practice2\practice2.iml
D:\IDEA\IdeaProjects\practice2\src

第二章 递归

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

public class Demo01DiGui {
	public static void main(String[] args) {
		// a();
		b(1);
	}
	
	/*
	 * 3.构造方法,禁止递归
	 * 编译报错:构造方法是创建对象使用的,不能让对象一直创建下去
	 */
	public Demo01DiGui() {
		//Demo01DiGui();
	}


	/*
	 * 2.在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。
	 * 4993
	 * 	Exception in thread "main" java.lang.StackOverflowError
	 */
	private static void b(int i) {
		System.out.println(i);
		//添加一个递归结束的条件,i==5000的时候结束
		if(i==5000){
			return;//结束方法
		}
		b(++i);
	}

	/*
	 * 1.递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。 Exception in thread "main"
	 * java.lang.StackOverflowError
	 */
	private static void a() {
		System.out.println("a方法");
		a();
	}
}

2.2 递归打印多级目录

分析:多级目录的打印,就是当目录的嵌套。遍历之前,无从知道到底有多少级目录,所以我们还是要使用递归实现。

代码实现:

public class DiGuiDemo2 {
    public static void main(String[] args) {
      	// 创建File对象
        File dir  = new File("D:\\aaa");
      	// 调用打印目录方法
        printDir(dir);
    }

    public static void  printDir(File dir) {
      	// 获取子文件和目录
        File[] files = dir.listFiles();
      	// 循环打印
      	/*
      	  判断:
      	  当是文件时,打印绝对路径.
      	  当是目录时,继续调用打印目录的方法,形成递归调用.
      	*/
        for (File file : files) {
    		// 判断
            if (file.isFile()) {
              	// 是文件,输出文件绝对路径
                System.out.println("文件名:"+ file.getAbsolutePath());
            } else {
              	// 是目录,输出目录绝对路径
                System.out.println("目录:"+file.getAbsolutePath());
              	// 继续遍历,调用printDir,形成递归
                printDir(file);
            }
        }
    }
}

第三章 综合案例

3.1 文件搜索
在这里插入图片描述
代码实现:

public class DiGuiDemo3 {
    public static void main(String[] args) {
        // 创建File对象
        File dir  = new File("D:\\aaa");
      	// 调用打印目录方法
        printDir(dir);
    }

    public static void printDir(File dir) {
      	// 获取子文件和目录
        File[] files = dir.listFiles();
      	
      	// 循环打印
        for (File file : files) {
            if (file.isFile()) {
              	// 是文件,判断文件名并输出文件绝对路径
                if (file.getName().endsWith(".java")) {
                    System.out.println("文件名:" + file.getAbsolutePath());
                }
            } else {
                // 是目录,继续遍历,形成递归
                printDir(file);
            }
        }
    }
}

3.2 文件过滤器优化
在这里插入图片描述
案例一:

FileFilterImpl.java

package lianxi;

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

/*
    创建过滤器FileFilter的实现类,重写过滤方法accept,定义过滤规则
 */
public class FileFilterImpl implements FileFilter {
    @Override
    public boolean accept(File pathname) {
        return true;
    }
}

Filter.java

package lianxi;

import java.io.File;

public class Filter {
    public static void main(String[] args) {
        File file  = new File("c:\\abc");
        getAllFile(file);
    }

    public static void  getAllFile(File dir) {
        File[] files = dir.listFiles(new FileFilterImpl());//传递过滤器对象
        for (File f : files) {
            //对遍历得到的FIle对象f进行判断,判断是否是文件夹
            if (f.isDirectory()) {
                //f若是一个文件夹,则继续遍历这个文件夹
                getAllFile(f);
            } else {
                // f是一个文件,直接打印即可
                System.out.println(f);
            }
        }
    }
}

在这里插入图片描述
重写 FileFilterImpl.java 的accept方法

package lianxi;

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

/*
    创建过滤器FileFilter的实现类,重写过滤方法accept,定义过滤规则
 */
public class FileFilterImpl implements FileFilter {
    @Override
    public boolean accept(File pathname) {
        /*
            过滤的规则:
            在accept方法中,判断File对象是否以.java结尾
            是就返回true
            不是就返回false
         */
        //如果pathname是一个文件夹,返回true,继续遍历这个文件夹
        if(pathname.isDirectory()){
            return true;
        }

        return pathname.getName().toLowerCase().endsWith(".java");
    }
}

案例二:

代码实现:

public class DiGuiDemo4 {
    public static void main(String[] args) {
        File dir = new File("D:\\aaa");
        printDir2(dir);
    }
  
    public static void printDir2(File dir) {
      	// 匿名内部类方式,创建过滤器子类对象
        File[] files = dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.getName().endsWith(".java")||pathname.isDirectory();
            }
        });
      	// 循环打印
        for (File file : files) {
            if (file.isFile()) {
                System.out.println("文件名:" + file.getAbsolutePath());
            } else {
                printDir2(file);
            }
        }
    }
}      

3.3 Lambda优化

分析:FileFilter是只有一个方法的接口,因此可以用lambda表达式简写。

代码实现:

public static void printDir3(File dir) {
  	// lambda的改写
    File[] files = dir.listFiles(f -> f.getName().toLowerCase().endsWith(".java") || f.isDirectory());
  	
	// 循环打印
    for (File file : files) {
        if (file.isFile()) {
            System.out.println("文件名:" + file.getAbsolutePath());
      	} else {
        	printDir3(file);
      	}
    }
}
  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值