目录
- 一、数据类型
- 二、左移与右移
- 三、switch语句
- 四、方法
- 五、数组
- 六、Java内存
- 七、变量
- 八、类
- 九、字符串
- 十、静态static关键字
- 十一、Arrays工具类
- 十二、Math工具类
- 十三、继承
- 十四、抽象
- 十五、接口
- 十六、多态
- 十七、final关键字
- 十八、四种权限修饰符
- 十九、内部类
- 二十、Objects类
- 二十一、Object类&Objects类总结
- 二十二、Date类的构造方法和成员方法
- 二十三、DateFormat类&SimpleDateFormat类
- 二十四、Calendar类
- 二十五、System类 & StringBuilder类
- 二十六、包装类
- 二十七、Collection集合
- 二十八、Iterator迭代器
- 二十九、泛型
- 三十、List集合
- 三十一、LinkedList集合
- 三十二、HashSet集合
- 三十三、可变参数
- 三十四、Collections集合
- 三十五、Map集合
- 三十六、Debug追踪
- 三十七、异常
- 三十八、线程
- 三十九、线程池
- 四十、File类、递归
一、数据类型
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);
}
}
}