“今日起,重学java”
java-阶段一
第一周
print和println
println换行
print不换行
数字不能作为标识符的首字母
goto保留字 不用做标识符
变量/常量
变量
三元素:变量类型、变量名、变量值
驼峰命名法
数据类型
- 基本数据类型
- 数值型
- 整型(byte、short、int、long)
- 浮点类型(float、double)
- 字符型(char)
- 布尔型(boolean)
- 数值型
- 引用数据类型
- 类
- 接口
- 数组
变量声明格式:数据类型 变量名;
= 赋值运算符
== 这才是等于
八进以0开头,16进制以0x开头
局部变量存储
内存里: 栈 常量池 堆
局部变量存在栈里
转义字符
\'表示’
\"表示”
\n表示换行
\t表示tab
1
double d = 1.23E5 //表示1.23*10^5
类型转换
隐式(自动)类型转换 显式(强制)类型转换
范围小的可以隐式转换成范围大的
范围大的必须显式转换成范围小的
注意: int->float,long->float/double可能会造成数据丢失
常量
定义时比变量多一个final
例:
1
final int n = 5;
表达式
运算符和操作数组成
运算符
-
算数运算符
- 加减乘除 取余 自增自减
-
赋值运算符
-
关系运算符
- 大于小于等于(==)不等于(!=)
- 比较结果为布尔值
-
逻辑运算符
- 与或非
-
条件运算符
-
三目运算
1 2
//布尔表达式?表达式1:表达式2 //当布尔表达式的值为true,则返回表达式1的值,否则返回表达式2的值
-
-
位运算符
x++ 先参加运算,再++
++x 先++ 再参加运算
运算符的优先级
圆括号–>逻辑非,自增,自减–>乘法,除法,取余–>加法,减法–>大于,大于等于–>等于,不等于–>逻辑与–>逻辑或–>赋值运算符,复合赋值运算符
流程控制
-
顺序
-
条件选择
-
if结构,if - else
-
多重if,if - else if - else
-
嵌套if
-
switch结构
1 2 3 4 5 6 7 8
switch(表达式){ case 常量表达式1: 语句1; break; case 常量表达式2: 语句2; break; //break是跳出,不写的话会穿透 default: 语句3; }
toUpperCase()把字符串中的字符全部改为大写
-
第二周
循环
-
for
1 2 3 4
for(表达式1;表达式2;表达式3) { 语句; }
-
while
1 2 3 4
while(循环条件) { 语句; }
-
do - while
1 2 3
do{ 语句 }while(循环条件);//与上一个不同,这个循环至少执行一次,注意最后的分号不要丢
-
循环嵌套
debug基础
- 设置断点
- 调试模式
一维数组
-
数组的声明
1 2
数据类型[] 数组名; 数据类型 数组名[];
-
数组创建
- 先声明后创建
1 2
数据类型[] 数组名; 数组名 = new 数据类型[数组长度];
注意:数组长度必须指定
-
数组在内存中的存储
连续的 默认值为0或null
-
数组的初始化
例:
1
int[] arr ={1,2,3,4,5,6,7,8}; -
元素的引用
数组名[下标];
foreach循环
例:
1
2
3
int[] arr = {1,2,3,4,5};
for(int n:arr)
sout(n);
冒泡排序
从小到大
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ForDemo {
public static void main(String[] args) {
int[] arr = {34,53,12,56,32,17};
for (int i = 0; i < arr.length-1; i++) {
int temp;
for (int j = 0; j < arr.length-1-i; j++) {
if (arr[j] > arr[j+1]) {
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
二维数组
多个一维数组,列可不一样
方法
什么是方法? 功能模块
-
方法的声明
1 2 3
访问修饰符 返回类型 方法名(参数列表){ 方法体 }-
无参无返回值方法
1 2 3 4 5 6 7 8 9 10 11
public class ForDemo { public static void main(String[] args) { ForDemo fd = new ForDemo(); fd.test(); } void test(){ System.out.println("***********"); System.out.println("java"); System.out.println("***********"); } }
-
无参带返回值方法
-
带参无返回值方法
-
带参带返回值方法
-
方法的重载(方法参数的灵活性)
写在同一个类中,方法名必须相同,参数列表必须不同
注意:仅返回类型不同不算重载,可以有不同的访问修饰符
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Calculator {
// 方法重载
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
}
-
方法的传值
普通数据类型+String,传入方法后,主方法中原参数不变,而数组类型会变
String和数组都是引用类型数据,为什么效果不一样呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
根本原因: String的不可变性 数组的可变性,String在方法中表现类似"值传递",而数组表现真正的"引用传递"行为 例: public class ParameterPassing { public static void main(String[] args) { // String示例 String str = "hello"; modifyString(str); System.out.println("修改后String: " + str); // 输出: hello // 数组示例 int[] arr = {1, 2, 3}; modifyArray(arr); System.out.println("修改后数组: " + Arrays.toString(arr)); // 输出: [100, 2, 3] } public static void modifyString(String s) { s = "world"; // 创建了新对象,不影响原引用 } public static void modifyArray(int[] array) { array[0] = 100; // 修改了原数组对象的内容 } }
-
可变参数列表
1
public void sum(int... n){}- 可变参数必须是最后一个
- 一个方法只能有一个可变参数
- 可变参数本质是数组
- 优先匹配固定参数方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
public class VarargsDemo { // 可变参数方法 public static void printNumbers(int... numbers) { for (int num : numbers) { System.out.print(num + " "); } System.out.println(); } public static void main(String[] args) { printNumbers(); // 输出: (空行) printNumbers(1); // 输出: 1 printNumbers(1, 2); // 输出: 1 2 printNumbers(1, 2, 3, 4); // 输出: 1 2 3 4 } }
第三周
类和对象
-
什么是面向对象? 关注事物信息
-
类 确定对象的特征+方法
-
对象是类的实例表现
类—->实例化对象—–>完成具体的程序
-
创建类与实例化对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
public class Person { // 属性(字段) String name; int age; // 方法 public void introduce() { System.out.println("你好,我叫" + name + ",今年" + age + "岁。"); } public void celebrateBirthday() { age++; System.out.println(name + "过生日啦!现在" + age + "岁了。"); } } public class PersonTest { public static void main(String[] args) { // 实例化对象 - 使用 new 关键字 Person person1 = new Person(); // 设置对象的属性 person1.name = "张三"; person1.age = 25; // 调用对象的方法 person1.introduce(); person1.celebrateBirthday(); // 创建另一个对象 Person person2 = new Person(); person2.name = "李四"; person2.age = 30; person2.introduce(); } }
对象实例化后,对象的属性会有默认值(与方法不同)
-
单一职责原则
一个类只有一个功能,只干一件事,功能越多,耦合越大,复用的可能性越低
PersonTest如何找到的Person? 找本类,后找同一包 -
new
- 声明对象
Person person1仅仅是栈中开辟空间,还不能真正使用 - 实例化对象
new Person();堆中存数据,实例化后,栈中对象指向堆中的数据地址 - 同一作用范围内,不能定义同名对象
- 声明对象
堆与栈
- 当存储内容是由基本数据类型(byte、short、int、long、fioat、double、char、bolean)声明的局部变量时,在栈中存储的是它们对应的具体数值。
- 当存储的是局部的对象的引用(定义在方法体中的引用类型的变量),存储的是具体对象在堆中的地址。当然,如果对象的引用没有指向具体的空间,则是null。
构造方法
-
构造方法与类同名且没有返回值
-
构造方法的语法格式
1 2 3
public 构造方法名(){ //初始化代码 }
-
只有在对象实例化的时候被调用
-
当没有指定构造方法时,系统会自动添加无参的构造方法
-
当有指定构造方法,无论是有参、无参的构造方法,都不会自动添加无参的构造方法
-
一个类中可以有多个构造方法(重载)
封装
- 将类的某些信息隐藏在类内部,不允许外部程序直接访问
- 通过该类提供的方法来实现对隐藏信息的操作和访问
- 隐藏对象的信息
- 留出访问的接口
封装的代码实现
- 修改属性的可见性(private),限定只能在当前类内访问
- 创建getter/setter方法(public)
- 在getter/setter方法中加入属性控制语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class Student {
// 私有属性 - 外部无法直接访问
private String name;
private int age;
private double gpa;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age >= 0 && age <= 120) {
this.age = age;
} else {
System.out.println("年龄必须在0-120之间!");
}
}
public double getGpa() {
return gpa;
}
public void setGpa(double gpa) {
this.gpa = gpa;
}
}
//测试类
public class StudentTest {
public static void main(String[] args) {
// 创建学生对象
Student student = new Student();
// 使用setter方法修改属性
student.setAge(21);
student.setGpa(3.9);
student.setName("李四");
// 调用getter方法
System.out.println("学生1 - 姓名: " + student.getName() +
", 年龄: " + student.getAge() +
", GPA: " + student.getGpa());
}
}
包
- Java中一个包里不能存在同名类
- 命名:域名倒序+模块+功能
- 导包用import
- 加载类的顺序跟import导入语句的位置无关,先找直接能解析的类
import 包名.*;只能访问指定包名下的类,无法访问子包下的类
static关键字
- 表示静态信息,静态成员/类成员/全局属性
- 类对象共享
- 类加载时产生,销毁时释放,生命周期长
- 静态变量只能在类主体中定义,不能在方法中定义
如下代码的输出结果是什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Test { public int aMethod(){ static int i = 0; i++; return i; } public static void main(String args[]){ Test test = new Test(); test.aMethod(); int j = test.aMethod(); System.out.println(j); } }编译错误,静态变量只能在类主体中定义,不能在方法中定义
- 静态成员访问方式
- 对象.成员
- 类.成员
- 通过该类实例化的所有对象都共享类中静态资源,任一对象中信息的修订都将影响所有对象。
- 静态方法中不能直接调用同一类中的非静态成员,只能通过对象实例化
- 构造代码块–创建对象时调用,优先于构造方法执行(与构造方法同级),每次实例化的时候都会执行
- 静态代码块–创建对象时调用,优先于构造代码块执行,多个按顺序执行,无论实例化多少对象,只执行一次,只能访问静态成员
继承
- 利用代码复用,缩短开发周期
- 一种类与类之间的关系
- 使用已存在的类的定义作为基础建设新类
- 子类的定义可以增加新的数据或功能,也可以使用父类的功能,但不能选择性的继承父类
继承的实现
- 简单演示
1
2
3
4
5
6
7
8
9
10
11
12
//父类
class Animal{
//公共的属性和方法
}
//子类
class Dog extends Animal{
//子类特有的属性和方法
}
class Cat extends Animal{
}
- 一个子类只能有唯一父类
方法的重写
- 在子类中定义
- 方法名、参数(类型、顺序、个数)都要和父类相同,参数名不做要求
- 当子类重写父类方法后,子类对象调用的是重写后的方法
- 当方法返回值是void或基本数据类型时,不允许修改;当返回值是引用类型时,可以是父类或其子类
- 访问修饰符需要大于等于父类的访问修饰符
- 静态方法不能重写
访问修饰符
- 公有(public):允许在任意位置访问
- 私有(private):只允许在本类中进行访问
- 受保护的(protected):允许在同类、同包子类/非子类、跨包子类调用
- 默认(啥也不写):允许在当前类、同包子类/非子类调用,不允许跨包
super关键字
在子类中,如果一个方法重写后,子类中另一个方法仍然想调用继承下来的同名方法,可以使用super.方法名
继承后的初始化顺序
父类静态成员—>子类静态成员–>父类对象构造–>子类对象构造
子类构造默认调用父类无参构造方法,可以用super指定调用父类的哪个构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 父类
class Parent {
private String name;
private int age;
// 父类无参构造方法
public Parent() {
this.name = "未知";
this.age = 0;
System.out.println("调用了父类无参构造方法");
}
// 父类有参构造方法
public Parent(String name, int age) {
this.name = name;
this.age = age;
System.out.println("调用了父类有参构造方法:name=" + name + ", age=" + age);
}
}
// 子类
class Child extends Parent {
private String hobby;
// 子类构造方法1:默认调用父类无参构造方法(隐式调用)
public Child(String hobby) {
// 这里隐式调用了 super(),即父类的无参构造方法
this.hobby = hobby;
System.out.println("调用了子类构造方法1,hobby=" + hobby);
}
// 子类构造方法2:使用super显式调用父类有参构造方法
public Child(String name, int age, String hobby) {
super(name, age); // 显式调用父类有参构造方法,必须放在第一行
this.hobby = hobby;
System.out.println("调用了子类构造方法2,hobby=" + hobby);
}
}
第四周
Object类
-
object类是所有类的父类
-
一个类没有使用extends关键字明确标识继承关系,则默认继承Object类(包括数组)
-
Java中的每个类都可以使用Object中定义的方法
-
继承object中的equals方法时,比较的是两个引用是否指向同一个对象
-
子类可以通过重写equals方法的形式,改变比较的内容
-
字符串重写了equals方法,比较的是内容
-
输出对象名时,默认会直接调用类中的toString方法,object类中的toString方法是打印包名@内存地址哈希,在实体类中经常被重写
问:
==到底比较的是值还是地址呢?答:比较的是变量中存储的值,对于基本数据类型,比较栈中的实际数值,引用数据类型 - 比较栈中存储的引用地址
final关键字
- final class表示该类没有子类,禁止继承
- final 方法表示该方法不能被子类重写,但是不影响子类继承使用(不能修饰构造方法),禁止重写
- final 局部变量 表示该变量只能被赋值一次,变为常量
- final 对象属性亦如此,但是有三种赋值方式:1、定义时赋值 2、构造方法中赋值3、构造代码块中赋值
- 可配合static使用,表示全局加载一次且不能被修改
注解
- 可以声明在包、类、属性、方法、局部变量、方法参数等的前面,用来对这些元素进行说明、注释
- 按照运行机制分为三类
- 源码注解:注解只在源码阶段保留在编译阶段会被丢弃
- 编译时注解
- 运行时注解:在运行阶段还起作用,甚至会影响运行逻辑的注解。
- 按照来源分
- 来自JDK的注解
- 来自第三方的注解
- 我们自己定义的注解
Java单例模式
- 什么是设计模式?设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。
- 目的:使得类的一个对象成为该类系统中的唯一实例
- 一个类有且只有一个实例,并且自行实例化向整个系统提供
- 实现:
- 只提供私有的构造方法
- 含有一个该类的静态私有对象
- 提供一个静态的共有方法用于创建、获取静态私有对象
- 优:
- 在内存中只有一个对象,节省内存空间
- 避免频繁的创建销毁对象,提高性能
- 避免对共享资源的多重占用
- 缺:
- 扩展困难
- 如果实例化后的对象长期不利用,系统将默认为垃圾进行回收,造成对象状态丢失
饿汉式单例模式
- 对象创建过程中实例化
- 空间换时间
代码实现演示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.qy.singleton;
public class SingletonOne {
//创建私有构造方法
private SingletonOne() {
}
//私有静态实例化对象
private static SingletonOne singletonOne = new SingletonOne();
//共有静态方法返回静态实例对象
public static SingletonOne getSingleton(){
return singletonOne;
}
}
package com.qy.test;
import com.qy.singleton.SingletonOne;
public class SingletTest {
public static void main(String[] args) {
//使用类.方法调用
SingletonOne one = SingletonOne.getSingleton();
SingletonOne two = SingletonOne.getSingleton();
System.out.println(one);
System.out.println(two);
}
}

懒汉式单例模式
-
静态公有方法中实例化
-
类内实例对象创建时并不直接初始化,直到第一次调用get方法时,才完成初始化操作
-
时间换空间
-
存在线程风险
-
同步锁
-
双重校验锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public class DoubleCheckedLockingSingleton { // 使用volatile确保多线程环境下的可见性和有序性 private static volatile DoubleCheckedLockingSingleton instance; private DoubleCheckedLockingSingleton() { // 私有构造函数 } public static DoubleCheckedLockingSingleton getInstance() { if (instance == null) { // 第一次检查 synchronized (DoubleCheckedLockingSingleton.class) { if (instance == null) { // 第二次检查 instance = new DoubleCheckedLockingSingleton(); } } } return instance; } }
-
静态内部类
-
枚举
-
代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.qy.singleton;
public class SingletonTwo {
//创建私有构造方法
private SingletonTwo() {
}
//私有静态实例化对象
private static SingletonTwo singletonTwo;
//共有静态方法返回静态实例对象
public static SingletonTwo getSingleton(){
if(singletonTwo == null){ //如果不加判断,就违背了单例模式的初衷,重复创建和销毁对象
singletonTwo = new SingletonTwo();
}
return singletonTwo;
}
}
package com.qy.test;
import com.qy.singleton.SingletonTwo;
public class SingletTest {
public static void main(String[] args) {
//使用类.方法调用
SingletonTwo singletonOne = SingletonTwo.getSingleton();
SingletonTwo singletonTwo = SingletonTwo.getSingleton();
System.out.println(singletonOne);
System.out.println(singletonTwo);
}
}
多态
问:你怎么理解多态的?
答:说到多态,就不得不提到方法的重写和重载,重写和重载就是多态的具体体现,简单来说,多态就是一个方法名,多种实现,首先从重写的方面,比如说我有一个父类,定义了Hello方法,打印hello1,子类里我重写了这个方法,打印hello2,同时实例化子类和父类,调用Hello方法,打印的是不同内容,这就是多态的表现。
封装和继承–为多态而生
- 同一个方法调用,作用于不同对象时,会产生不同的行为。
- 一个方法名,多种实现。
- 编译时多态
- 运行时多态
- 必要条件:
- 满足继承关系
- 父类引用指向子类对象
演示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.qy.polymorphic;
public class Animal {
private String name;
private int age;
public Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.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;
}
public void eat() {
System.out.println("动物都要吃东西");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.qy.polymorphic;
public class Cat extends Animal {
private String sex;
public Cat() {
}
public Cat(String name, int age, String sex) {
super(name, age);
this.sex = sex;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.qy.polymorphic;
public class Dog extends Animal{
private int weight;
public Dog() {
}
public Dog(String name, int age, int weight) {
super(name, age);
this.weight = weight;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.qy.test;
import com.qy.polymorphic.Animal;
import com.qy.polymorphic.Cat;
import com.qy.polymorphic.Dog;
public class PolymorphicTest {
public static void main(String[] args) {
Animal animal = new Animal();
//向上转型
Animal animal2 = new Cat();
Animal animal3 = new Dog();
animal.eat();
animal2.eat();
animal3.eat();
//向下转型
Cat cat = (Cat) animal2;
cat.eat();
cat.run();
//报错
Cat cat1 = (Cat) animal;
cat1.eat();
cat1.run();
//报错
Cat cat2 = (Cat) animal3;
cat2.eat();
cat2.run();
}
}
输出结果:

向上转型
-
隐式转型/自动转型
-
父类引用指向子类实例
1
Animal animal2 = new Cat();
-
小类转为大类
-
可以调用子类重写父类的方法以及父类派生的方法,无法调用子类独有方法
-
目的:实现多态、编写通用代码、方法参数通用化
向下转型
- 子类引用指向父类对象,此处必须进行强转,可以调用子类特有的方法
- 必须存在继承关系。只能将父类引用转为它真正的、具体的子类类型。
- 目的:恢复对子类特有方法和字段的访问权限
1
2
3
4
if (父类引用 instanceof 子类类型) {
// 安全地进行转换
子类类型 变量 = (子类类型) 父类引用;
}
instanceof运算符
-
返回布尔值
-
检查一个对象是否是指定类型(类、子类或接口)的实例
1
对象引用 instanceof 类型(类或接口)
abstract
- 这个关键词修饰的类,叫抽象类,抽象方法同理
抽象类
- 不允许实例化,但是可以通过向上转型,指向子类实例
- 适用:某个父类只是知道其子类应该包含怎样的方法,但无法准确知道这些子类如何实现这些方法。
- static、final、private不能与abstract共存
抽象方法
abstract修饰的方法叫做抽象方法
- 抽象方法不允许包含方法体
- 子类中需要重写父类的抽象方法
- 包含抽象方法的类必须是抽象类
- 抽象类中可以没有抽象方法
接口
-
接口定义了某一批类所需要遵守的规范
-
接口不关心这些类的内部数据,也不关心这些类里方法的实现细节,它只规定这些类里必须提供某些方法。
-
命名习惯,在类的命名规则基础上,加个I
-
访问修饰符默认或public
-
接口中抽象方法可以不写
abstract关键字 -
当类实现接口时,需要去实现接口中的所有抽象方法,否则需要将该类设置成抽象类
-
接口中可以包含常量,默认
public static final -
接口中的一般方法,不能有方法体,实现类必须重写这些方法,例外:默认方法,可以有方法体,实现类里也可以不用重写该方法,如
connectiond() -
在多接口的实现类里面,如果接口里有同名的默认方法,则在业务中调用的时候会报错(不知道调哪个),解决方案:在实现类里重写该方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
//接口访问修饰符:public或默认 public interface INet { /*接口中抽象方法可以不写abstract关键字 *访问修饰符默认public *当类实现接口时,需要去实现接口中的所有抽象方法,否则需要将该类设置为抽象类 */ //实现类必须重写 void network(); //接口中可以包含常量,默认public static finalint TEMP-20; int TEMP=20; //默认方法,实现类可以重写,也可以不重写,可以通过接口的引用调用 default void connectiond(){ System.out.println("我是默认方法"); }; //静态方法,实现类不可重写,可以用接口名调用 static void stop(){ System.out.println("我是默认方法"); } }
-
一个类可以既继承
extends又实现接口implements,但是继承必须写在接口前,如果父类和接口中的默认方法有同名方法,接口中的默认方法不生效1
public class 类名 extends 父类 implements 接口1,接口2...
-
实现接口需要使用implements
-
接口类也可以实现继承,且可以继承多个父接口
-
当多个接口或父类有同名常量时,与方法不同,实现类中无法识别同名常量是哪个,可以用以下方式区分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
interface InterfaceA { int VALUE = 10; } interface InterfaceB { int VALUE = 20; } class Parent { public static final int VALUE = 30; } // 常量冲突 - 必须明确指定来源 class ConflictClass extends Parent implements InterfaceA, InterfaceB { public void showValues() { System.out.println("父类VALUE: " + Parent.VALUE); System.out.println("接口A VALUE: " + InterfaceA.VALUE); System.out.println("接口B VALUE: " + InterfaceB.VALUE); // 直接使用VALUE会有歧义,编译错误 // System.out.println("VALUE: " + VALUE); // 编译错误 } }
在java中,实现类实现接口和子类继承父类有什么区别?
- 继承:用于建立”是什么”的关系,强调代码复用和层次结构
- 接口:用于建立”能做什么”的关系,强调行为契约和多态性
- 实际开发:通常结合使用,用继承建立核心层次结构,用接口扩展额外能力
- 设计原则:优先使用接口组合,减少深层次的继承关系,提高代码灵活性
| 特性 | 继承父类 | 实现接口 |
|---|---|---|
| 关系类型 | “is-a” 关系 | “has-a” 能力 |
| 数量限制 | 单继承 | 多实现 |
| 方法实现 | 可以继承具体实现 | 必须实现所有抽象方法 |
| 构造方法 | 可以继承 | 没有构造方法 |
| 字段 | 可以继承实例变量 | 只能有常量(public static final) |
| 设计目的 | 代码复用、层次化设计 | 定义契约、多态行为 |
内部类
-
成员内部类
-
也称普通内部类
-
内部类在外部使用时无法直接实例化,需要借由外部类信息才能完成实例化
-
内部类可以直接访问外部类的成员(属性,方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
// 外部类 class OuterClass { private String outerField = "外部类字段"; // 成员内部类 class InnerClass { private String innerField = "内部类字段"; public void display() { // 内部类可以访问外部类的私有成员 System.out.println("访问外部类字段: " + outerField); System.out.println("内部类字段: " + innerField); } } // 外部类方法:创建内部类实例 public void createInner() { InnerClass inner = new InnerClass(); inner.display(); } } // 测试类 public class Test { public static void main(String[] args) { OuterClass outer = new OuterClass(); // 方式1:通过外部类实例创建内部类 OuterClass.InnerClass inner1 = outer.new InnerClass(); inner1.display(); // 方式2:在外部类内部创建(如createInner方法中) outer.createInner(); } }
-
-
静态内部类
-
静态内部类中,只能直接访问外部类的静态成员,如果需要调用非静态成员,可以通过对象实例
-
实例化静态内部类对象实例时,可以不依赖于外部类对象
-
可以通过外部类,内部类.静态成员的方式,访问内部类中的静态成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
class OuterClass { private static String staticField = "静态字段"; private String instanceField = "实例字段"; // 静态内部类 static class StaticInnerClass { public void display() { // 只能访问外部类的静态成员 System.out.println("静态字段: " + staticField); // System.out.println(instanceField); // 错误!不能访问实例成员 } } } // 测试 public class Test { public static void main(String[] args) { // 静态内部类实例化:不需要外部类实例 OuterClass.StaticInnerClass staticInner = new OuterClass.StaticInnerClass(); staticInner.display(); } }
-
-
方法内部类
-
局部内部类
-
定义在方法内部,作用范围也在方法内
-
类中不能包含静态成员
-
和方法内部成员使用规则一样,class前面不可以添加public、private、protected、static
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
class OuterClass { private String outerField = "外部字段"; public void method() { String localVar = "局部变量"; // 必须是final或等效final // 局部内部类 class LocalInnerClass { public void display() { System.out.println("外部字段: " + outerField); System.out.println("局部变量: " + localVar); // JDK8+ 自动视为final } } // 在方法内部实例化 LocalInnerClass localInner = new LocalInnerClass(); localInner.display(); } } // 测试 public class Test { public static void main(String[] args) { OuterClass outer = new OuterClass(); outer.method(); // 在method内部实例化局部内部类 } }
-
-
匿名内部类
-
只用到类的一个实例
-
类在定义后马上用到
-
给类命名并不会导致代码更容易被理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
// 接口 interface Greeting { void greet(); } class OuterClass { public void createAnonymousClass() { // 匿名内部类:实现Greeting接口 Greeting greeting = new Greeting() { @Override public void greet() { System.out.println("Hello from anonymous inner class!"); } }; greeting.greet(); // 另一种写法:直接调用 new Greeting() { @Override public void greet() { System.out.println("直接创建的匿名类"); } }.greet(); } } // 测试 public class Test { public static void main(String[] args) { OuterClass outer = new OuterClass(); outer.createAnonymousClass(); } }
-
第五周
异常
- 什么是异常?程序错误
- 背离我们程序本身的意图的表现
- 异常的分类
Throwable:异常的根类Error类:表示程序无法处理的错误,表示运行应用程序中较严重问题,常见:VirtualMachineError虚拟机错误OutOfMemoryError内存溢出ThreadDeath线程死锁
Exception是程序本身可以处理的异常。异常处理通常指针对这种类型异常的处理。
- 异常处理机制
- 抛出异常–>捕获异常
- 对于可查异常必须捕捉、或者声明抛出
- 允许忽略不可查的RuntimeException(含子类)和Error(含子类)
- 捕获异常:
try(执行可能产生异常的代码)–catch(捕获异常)—finally(无论是否发生异常代码总能执行) - 声明异常:
throws声明可能要抛出的异常 - 手动抛出异常
throw
try–catch—finally
-
try块后可接零个或多个catch块,如果没有catch块则必须跟一个finally块。- 在try块中放置可能抛出异常的业务代码
- 在catch块中处理异常
- 在finally块中进行资源释放,如关闭文件、关闭数据库连接等清理工作
-
常见异常类型:
ArithmeticException:数学运算异常,涉及到数学运算的地方可能出现失误,比如程序中出现了除以零这样的运算NumberFormatException:数字格式化异常,涉及到类型转换时,比如不符合转换格式的字符串被转换成数字ArraylndexOutOfBoundsException:数组下标越界异常,涉及到使用超出数组下标范围的下标。NullPointerException:空指针异常,当使用了未经初始化的对象或者是不存在的对象时。ClassCastException:类型转换异常,如进行向下转型时,转换对象无法完成正常转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
import java.util.Scanner; import java.util.InputMismatchException; public class TryDemoOne { public static void main(String[] args) { // 要求:定义两个整数,接受用户的键盘输入,输出两数之商 Scanner input = new Scanner(System.in); System.out.println("=====运算开始====="); try { System.out.print("请输入第一个整数:"); int one = input.nextInt(); System.out.print("请输入第二个整数:"); int two = input.nextInt(); System.out.println("one和two的商是:" + (one / two)); } catch (ArithmeticException e) { System.out.println("除数不允许为零"); e.printStackTrace(); } catch (InputMismatchException e) { System.out.println("请输入整数"); e.printStackTrace();//输出异常 }catch(Exception e){//一般在最后一个异常父类,防止前面异常捕获不到,而且父类catch块只能写到子类后面 e.printStackTrace(); System.out.println("出错啦~~"); } finally { System.out.println("=====运算结束====="); input.close(); } } }
-
当
try、catch、finally中都有return的时候,try/catch中的返回会被finally中的覆盖掉 -
即便在
try、catch中写了return,finally块也会正常执行
throws
-
可以通过throws声明将要抛出何种类型的异常,通过throw将产生的异常抛出
-
如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常。
-
throws语句用在方法定义时声明该方法要抛出的异常类型
-
区别于
try、catch、finally,throws关注的是将异常传递给调用者处理,而前者是在当前方法中立即处理异常1 2 3
public void method() throws Exception1,Exception2,...,ExceptionN{ // 可能产生异常的代码 }1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
import java.util.Scanner; import java.util.InputMismatchException; public class TryDemoTwo { public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.println("=====运算开始====="); try { // 调用可能抛出异常的方法 performCalculation(input); } catch (ArithmeticException e) { System.out.println("除数不允许为零"); e.printStackTrace(); } catch (InputMismatchException e) { System.out.println("请输入整数"); e.printStackTrace(); } catch (Exception e) { System.out.println("出错啦~~"); e.printStackTrace(); } finally { System.out.println("=====运算结束====="); input.close(); } } // 这个方法使用throws声明可能抛出的异常 // 它不自己处理异常,而是让调用者来处理 public static void performCalculation(Scanner input) throws ArithmeticException, InputMismatchException { System.out.print("请输入第一个整数:"); int one = input.nextInt(); // 可能抛出InputMismatchException System.out.print("请输入第二个整数:"); int two = input.nextInt(); // 可能抛出InputMismatchException System.out.println("one和two的商是:" + (one / two)); // 可能抛出ArithmeticException } }
throw
-
throw用来抛出一个异常。 -
例如:throw new IOException();
-
throw 抛出的只能是Throwable类或者其子类的实例对象。
-
处理特殊业务逻辑产生的需求
-
分两种
- 通过
try...catch语句包含throw———自己抛出异常自己处理 - 通过throw在方法声明出抛出异常类型——-自己抛出异常由调用者处理
- 通过
自定义异常
- 描述特定业务产生的异常类型
- 定义一个类,继承Throwable或者它的子类
- 自定义异常是否属于检查异常由其父类决定
e.toString():获得异常类型和描述信息,当直接输出对象e时,默认调用e.toString()方法。e.getMessage():获得异常描述信息e.printStackTrace():打印出异常产生的堆栈信息,包括种类、描述信息、出错位置等- 自定义异常需先经过throw抛出,才能被catch捕获,是无法自动被程序捕获并处理的。
异常链
将异常发生的原因一个传一个串起来,把底层的异常信息传给上层,这样逐层抛出。
异常链的存在目的是什么?
这是异常链最主要、最重要的目的。在复杂的系统或多层架构中(如Web应用的分层架构:Controller -> Service -> DAO),底层的异常(如数据库连接失败)在向上传递时,可能会被捕获并包装成更抽象的、对当前层有意义的异常。比如没有异常链,在顶层你只能看到“业务处理失败”,但不知道是为什么失败。有异常链:在顶层你看到“业务处理失败”,但通过异常链可以追溯到“数据访问失败”,再进一步追溯到“网络连接超时”。这让你能快速定位到是数据库服务器或网络的问题。
包装类
-
包装类与基本数据类型
基本类型 对应包装类 byte Byte short Short int Integer long Long float Float double Double char Character boolean Boolean -
装箱
基本数据类型的值转换成对应包装类的对象
-
拆箱
包装类的对象转换成基本数据类型的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
public class TestOne { public static void main(String[] args) { int t1 = 2; //自动装箱 Integer t2 = t1; //手动装箱 Integer t3 = new Integer(t1); Integer t4 = Integer.valueOf(t1); System.out.println(t2); System.out.println(t3); System.out.println(t4); System.out.println("********"); //自动拆箱 int t5 = t3; //手动拆箱 int t6 = t4.intValue(); double t7 = t4.doubleValue(); System.out.println(t5); System.out.println(t6); System.out.println(t7); }

-
基本数据类型与字符串之间的转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public class TestOne { public static void main(String[] args) { int t1 = 2; String t2 = Integer.toString(t1); System.out.println(t2); int t3 = Integer.parseInt(t2); int t4 = Integer.valueOf(t2); double t5 = Double.valueOf(t2); double t6 = Double.parseDouble(t2); System.out.println(t3); System.out.println(t4); System.out.println(t5); System.out.println(t6); }
-
包装类如果不进行初始化,值为NULL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
public class TestOne { public static void main(String[] args) { Integer one=new Integer(100); Integer two=new Integer(100); System.out.println("one==two的结果: "+(one==two));//false Integer three=100;//自动装箱 //Integer three=Integer.valueOf(100); System.out.println("three==100的结果: "+(three==100));//true 自动拆箱 //Integer four=100; Integer four=Integer.valueOf(100); System.out.println("three==four的结果: "+(three==four));//true Integer five=200; System.out.println("five==200的结果: "+(five==200));// true Integer six=200; System.out.println("five==six的结果: "+(five==six));// false } }
-
如上代码,在针对自动拆箱的时候,
Integer three=100;实际执行的是Integer three=Integer.valueOf(100);,而.valueOf()中的参数小于等于127或大于等于-128时,会将对象缓存在缓存区(对象池),下面再有valueOf()时,如果在这个区间内,会优先在缓存区检查有无已存在的相同对象,若有,直接引用,如无,隐式调用new,不过Double和Float类型没有缓存区这个概念
字符串
-
创建String对象的方法
-
1
String s1 = "test";
-
1
String s2 = new String(); //创建一个空的字符串对象
-
1
String s3 = new String("test2");
-
可以使用字符数组创建字符串,先定义一个字符数组,创建字符串对象时使用字符数组作为参数。
1 2
char[] ch= {'i','m','o','o','s'}; String s=new string(ch);
-
除了使用字符数组的所有元素创建字符串以外,还可以使用字符数组的一部分创建字符串。这里的参数 1 和 2 的含义是:1 表示从数组元素的第几位开始,2 表示一共几个数组元素。从第一位开始也就是从 m 这个字符开始,2 表示从 m 开始的两个字符,因此字符串 s 的值为 ‘mo’
1 2
char[] ch= {'i','m','o','o','c'}; String s=new String(ch,1,2);
-
可以利用字节数组生成字符串,与字符数组都在输入输出流部分应用较多,用于存储传输的二进制数据。下面是它的使用方式
1 2 3 4 5 6 7 8
byte[] b= {54,69,70,71,72}; String s=new String(b); String s1=new String(b,1,2); System.out.println("s="+s); System.out.println("s1="+s1); //输出结果 s=6EFGH s1=EF
-
-
常用方法
方法 说明 int length() 返回当前字符串的长度 int indexOf(int ch) 查找 ch 字符在该字符串中第一次出现的位置 int indexOf(String str) 查找 str 子字符串在该字符串中第一次出现的位置(返回的是子字符串第一个字符的索引) int lastIndexOf(int ch) 查找 ch 字符在该字符串中最后一次出现的位置(区分大小写) int lastIndexOf(String str) 查找 str 子字符串在该字符串中最后一次出现的位置(返回的是子字符串第一个字符的索引) String substring(int beginIndex) 获取从 beginIndex 位置开始到结束的子字符串 String substring(int beginIndex, int endIndex) 获取从 beginIndex 位置开始到 endIndex 位置的子字符串(左闭右开) String trim() 返回去除了前后空格的字符串 boolean equals(Object obj) 将该字符串与指定对象比较,返回 true 或 false String toLowerCase() 将字符串转换为小写 String toUpperCase() 将字符串转换为大写 char charAt(int index) 获取字符串中指定位置的字符 String[] split(String regex, int limit) 将字符串分割为子字符串,返回字符串数组 byte[] getBytes() 将该字符串转换为 byte 数组 -
字符串与数组的转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
import java.io.UnsupportedEncodingException; public class StringDemo3 { public static void main(String[] args) throws UnsupportedEncodingException { // 字符串和byte数组之间的相互转换 // 定义一个字符串 String str = new String("JAVA 编程 基础"); // 将字符串转换为byte数组,并打印输出 byte[] arrs = str.getBytes("GBK"); for (int i = 0; i < arrs.length; i++) { System.out.print(arrs[i] + " "); } System.out.println(); // 将byte数组转换为字符串 String str1 = new String(arrs, "GBK"); System.out.println(str1); } }
-
StringBuilder
- String具有不可变性,而StringBuilder不具备。
- 建议当频繁操作字符串时,使用StringBuilder。
- 构造方法
StringBuilder(),调用时直接开辟一个16字符的空间
addend()在字符串末尾增加新的内容delete(int start,int end)删除从start到end的字符toString()转换成string类型insert(int offset, String str),在offset处插入strreplace(int start, int end, String str)length()返回当前字符串序列中的字符数,capacity()返回当前StringBuffer的容量。容量不会自动回收,手动回收方法capacity()缩减到当前长度
第六周
常见集合与应用
List集合体系及应用–讲解ArrayList、LinkedList使用方法与底层原理
Set集合体系及应用–讲解HashSet、LinkedHashSet、TreeSet使用方法与底层原理
Map映射体系及应用–讲解HashMap、LinkedHashMap、TreeMap使用方法与底层原理
应用Collections实现集合排序–介绍Collections.sort()方法的使用
- Java集合包含List、Set、Map,以及JDK1.5推出的Queue四种体系
- 四种存储结构
- List代表有序、可重复集合
- Queue代表队列特性
- Set代表无序、不可重复集合
- Map代表存储映射关系的集合
List集合
- List集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引
- List集合允许使用重复元素,通过索引访问指定位置的元素
- List集合默认按元素的添加顺序设置元素的索引

-
ArrayList基于数组实现的List类,是java数组的有效替代品
-
ArrayList会自动对容量进行扩容,多数情况下无须指定最大长度
-
ArrayList的数据在内存中是连续紧密存储的,基于数据访问速度快
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public class ArrayListSample { public static void main(String[] args) { //实例化ArrayList ArrayList<String> bookList = new ArrayList<String>(); //新增元素,尾部追加 bookList.add("三国演义"); bookList.add("水浒传"); //返回第一个元素,索引从0开始 String bookName1 = bookList.get(0); bookList.add("三国演义"); System.out.println(bookName1.getClass()); System.out.println(bookList); } } //输出结果 class java.lang.String [三国演义, 水浒传, 三国演义]
-
add()可以直接传元素,也可以在指定位置添加元素add(index,element),此方法可以返回一个boolean值,表示列表是否发生变化 -
set(index,element)表示将index处的元素改为element,此方法返回元素修改前的值 -
remove()该方法可以传元素具体内容,返回一个boolean值,表示列表是否发生变化;也可以直接传元素索引,返回值为被删除的值 -
size()返回列表中元素数量
LinkedList
- 同时实现了List和Deque连个接口
- LinkedList在保障有序、允许重复的前提下,也可以作为队列在队首、队尾快速追加数据
add()用法与List相同addFirst()从列表首部添加数据,addLast()从尾部添加元素
三种List集合的遍历方式
-
for循环遍历(for循环和增强for循环)
-
forEach方法遍历(基于 Lambda 表达式的遍历方式)
-
lterator迭代器遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
public class ListLoopSample { public static void main(String[] args) { ArrayList<String> bookList = new ArrayList<String>(); //新增元素,尾部追加 bookList.add("三国演义"); bookList.add("水浒传"); bookList.add("三国演义"); System.out.println("-------------"); for (String i : bookList){ System.out.println(i); } System.out.println("-------------"); bookList.forEach(book ->{ System.out.println(book); }); System.out.println("-------------"); Iterator<String> itr = bookList.iterator(); while(itr.hasNext()){ String book = itr.next(); //提取出下一个元素,同时将指针向后移动 System.out.println(book); } } }
-
迭代器本身的使用是“一次性”的,如果需要多次使用迭代器对集合进行遍历,那么每次都需要重新获取迭代器对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
public class Test { public static void main(String[] args) { Test test = new Test(); //创建一个List集合,要求其中只能添加字符串 List<String> list = new ArrayList<>(); //向集合中添加一些元素 list.add("hello"); list.add("imooc"); //获取存储list中所有元素的迭代器 Iterator<String> iterator = list.iterator(); //使用迭代器进行遍历 while (iterator.hasNext()){ String s = iterator.next(); System.out.println(s); } //做一条分隔线 System.out.println("-------------------------"); //再次获取迭代器(此时直接利用上面的变量,重新赋值) iterator = list.iterator(); while (iterator.hasNext()){ String s = iterator.next(); System.out.println(s); } System.out.println("遍历结束"); } }
Set集合
- Set集合代表一个元素无序、不可重复的集合
- Set集合与List集合使用方法基本相同,只是处理行为略有不同
- Set集合常用的实现类是:HashSet与TreeSet
- 与List不同的是,Set中所有关于元素索引的方法都不好使了
add()返回一个boolean值,表示列表是否发生变化size()返回列表中元素数量- 根据业务类型不同,可能会在实体类中重写hashCode()和
equals()
Set集合如何确保数据的唯一性?
Set集合在新增数据时先判断数据的hashCode()是否已存在,若hashCode()在set集合存在再调用equals()进行值比较,hashcode()与equals()都存在的情况下,Set集合才认为数据已存在,不予新增
为什么要用对象的hashCode()直接用equals()判断不行吗?
hashcode()返回的整数结果决定了set集合中的存放位置,hashcode()计算速度很快,但可能出现哈希碰撞 equals()则对值进行比较,处理速度相对较。
HashSet
- HashSet是Set接口的典型实现,大多数时候使用Set集合时就是使用这个实现类
- HashSet按Hash算法来决定集合元素的顺序,具有很好的查找性能
- 当向HashSet集合中存入一个元素时,根据该对象的hashCode值决定该对象在HashSet中的存储位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.util.HashSet;
import java.util.Iterator;
public class HashSetDemo {
public static void main(String[] args) {
// 1. 实例化HashSet对象
HashSet<String> fruitsSet = new HashSet<>();
// 2. 存入值
fruitsSet.add("Apple");
fruitsSet.add("Banana");
fruitsSet.add("Orange");
fruitsSet.add("Grape");
fruitsSet.add("Apple"); // 重复元素,不会被添加
// 添加null值(HashSet允许一个null元素)
fruitsSet.add(null);
System.out.println("HashSet中的元素: " + fruitsSet);
System.out.println("HashSet大小: " + fruitsSet.size());
// 3. 多种遍历方式
System.out.println("\n=== 方式1: 增强for循环 ===");
for (String fruit : fruitsSet) {
System.out.println("水果: " + fruit);
}
System.out.println("\n=== 方式2: 迭代器 ===");
Iterator<String> iterator = fruitsSet.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.println("水果: " + fruit);
// 可以在遍历时删除元素
if ("Banana".equals(fruit)) {
iterator.remove();
}
}
System.out.println("\n删除Banana后的HashSet: " + fruitsSet);
System.out.println("\n=== 方式3: forEach + Lambda表达式 (Java 8+) ===");
fruitsSet.forEach(fruit -> System.out.println("水果: " + fruit));
// 4. 其他常用操作
System.out.println("\n=== 其他操作 ===");
System.out.println("是否包含Apple: " + fruitsSet.contains("Apple"));
System.out.println("是否为空: " + fruitsSet.isEmpty());
// 删除元素
fruitsSet.remove("Orange");
System.out.println("删除Orange后: " + fruitsSet);
// 清空HashSet
fruitsSet.clear();
System.out.println("清空后HashSet大小: " + fruitsSet.size());
}
}
LinkedHashSet
- LinkedHashSet是HashSet的子类,除HashSet的特性外,它同时使用链表维护元素的次序,可以保障按插入顺序提取数据
- LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能
- 迭代访问Set里的全部元素时将有很好的性能,因为它以链表来维护内部顺序
- 根据插入时的顺序来决定数据提取时的次序,但是在内存中并不是真正的连续
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.*;
public class SetExample {
public static void main(String[] args) {
// HashSet - 不保证顺序
Set<String> hashSet = new HashSet<>();
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.add("Cherry");
hashSet.add("Date");
System.out.println("HashSet顺序: " + hashSet);
// 输出可能是: [Apple, Cherry, Date, Banana] - 顺序不确定
// LinkedHashSet - 保证插入顺序
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Apple");
linkedHashSet.add("Banana");
linkedHashSet.add("Cherry");
linkedHashSet.add("Date");
System.out.println("LinkedHashSet顺序: " + linkedHashSet);
// 输出总是: [Apple, Banana, Cherry, Date] - 插入顺序
}
}
HashSet和LinkedHashSet的数据存储方式
HashSet是数组中存了一个或多个冲突链表/红黑树,而LinkedHashSet又单独维护了一个双向链表,保证插入顺序,数组的每个元素是一个桶(bucket),每个桶内是一个链表或红黑树,用于存储哈希值相同的元素。LinkedHashSet在HashSet的基础上,额外维护了一个双向链表,这个链表将所有的元素按照插入顺序连接起来。因此,迭代时按照这个链表的顺序输出。
TreeSet
- TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态
- TreeSet采用红黑树的数据结构来存储集合元素
- TreeSet默认采用自然排序对元素升序排列,也可以实现Comparable接口自定义排序方式
Map接口
- Map映射特点
- Map用于保存具有映射关系的数据,每组映射都是Key(键)与Value(值)组合而成
- Key和Value可以是任何引用类型数据,但是Key通常是String
- Map中的Key是不允许重复的,重复为同一个Key设置Value,后者Value会覆盖前者Value
HashMap
- HashMap是Map接口的典型实现类,对Key进行无需存储
- HashMap不能保证数据按存储顺序读取,且Key全局唯一
HashMap与HashSet的关系?
Java先有Map后有Set,HashSet从HashMap精简而来,区别在于每一个HashSet的数据都只有单个数值,对数值进行Hash,然后确认存储位置,而HashMap是对Key进行Hash
put():添加元素,键重复时会覆盖旧值,然后返回被覆盖的Valueget(K):根据Key 值获取对应的ValuecontainsKey(K):判断K值存不存在,返回boolean类型containsValue(V):判断V值存不存在remove(K):移除K值对应的Value
在HashMap和HashSet中,删除元素都是用remove(),这里会不会因为哈希碰撞导致删除的元素错误?
简短回答:不会。 HashMap和HashSet的
remove()方法在设计时已经考虑了哈希碰撞的情况,能够正确删除目标元素而不会误删其他元素。首先比较hash值(快速筛选),然后比较key是否相等(通过equals()方法精确匹配)即使哈希碰撞也能正确识别:相同哈希值的不同键会存储在同一个桶中,删除时会遍历该桶内的所有元素,用equals()方法精确匹配
TreeMap
- TreeMap存储key-value对时,需要根据key对节点进行排序
- TreeMap支持两种Key排序:自然排序与定制排序
- 与TreeSet相同,TreeMap也是基于红黑树结构对数据进行排序
Map的遍历方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class MapDemo {
public static void main(String[] args) {
// 实例化HashMap(使用String作为键,Object作为值以便存储多种类型)
Map<String, Object> dataMap = new HashMap<>();
// 添加数据
dataMap.put("name", "张三");
dataMap.put("age", 25);
dataMap.put("height", 175.5);
dataMap.put("isStudent", false);
dataMap.put("hobbies","篮球");
System.out.println(dataMap);
//遍历数据
//1
Set<String> dataSet = dataMap.keySet();
for (String key : dataSet) {
System.out.println(dataMap.get(key));
}
//2
dataMap.forEach((k,v) ->{
System.out.println(k +":"+ v);
});
//3 迭代器
Iterator<Map.Entry<String, Object>> iterator = dataMap.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String, Object> next = iterator.next();
System.out.println(next.getKey()+":" + next.getValue());
}
}
通过Colections实现List排序
Java中针对List排序操作,给出了Collections工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.Collections;
import java.util.List;
public class CollectionsTest {
public List<Integer> sort(List<Integer> list) {
Collections.sort(list);
return list;
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(30);
list.add(50);
list.add(20);
list.add(79);
System.out.println(list);
CollectionsTest collectionsTest = new CollectionsTest();
collectionsTest.sort(list);
System.out.println(list);
}
}
//输出
[30, 50, 20, 79]
[20, 30, 50, 79]
- 值得注意的是,
collectionsTest.sort()直接操作的就是原集合 - 上面的代码只是能进行升序排序,若要进行降序排序,那么就要请出
Comparator接口了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.*;
public class CollectionsTest {
class TestComparator implements Comparator<Integer> {
@Override
//结果>0,则交换位置
//结果=0或小于0,则位置不变
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
}
public List<Integer> sort(List<Integer> list) {
Collections.sort(list, new TestComparator());
return list;
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(30);
list.add(50);
list.add(20);
list.add(79);
System.out.println(list);
CollectionsTest collectionsTest = new CollectionsTest();
collectionsTest.sort(list);
System.out.println(list);
}
}
- 上述代码实现了
Comparator接口,并在调用Collections.sort()时实例化了TestComparator
自定义类型如何集合排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import java.util.*;
class Student {
private String name;
private int score;
private int age;
public Student(String name, int score, int age) {
this.name = name;
this.score = score;
this.age = age;
}
public String getName() { return name; }
public int getScore() { return score; }
public int getAge() { return age; }
@Override
public String toString() {
return name + "(分数:" + score + ", 年龄:" + age + ")";
}
}
public class StudentSortingExample {
// 内部类:按分数排序的比较器
static class ScoreComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
return Integer.compare(s1.getScore(), s2.getScore());
}
}
// 内部类:按姓名排序的比较器
static class NameComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
return s1.getName().compareTo(s2.getName());
}
}
// 内部类:按年龄降序排序的比较器
static class AgeDescComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
return Integer.compare(s2.getAge(), s1.getAge()); // 降序
}
}
// 内部类:多重排序(先按分数,再按年龄)
static class ScoreThenAgeComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
int scoreCompare = Integer.compare(s1.getScore(), s2.getScore());
if (scoreCompare != 0) {
return scoreCompare;
}
return Integer.compare(s1.getAge(), s2.getAge());
}
}
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("王五", 85, 20));
students.add(new Student("张三", 92, 22));
students.add(new Student("李四", 85, 19));
students.add(new Student("赵六", 78, 21));
students.add(new Student("孙七", 92, 20));
System.out.println("原始列表:");
students.forEach(System.out::println);
// 使用内部类比较器进行排序
// 1. 按分数排序
Collections.sort(students, new ScoreComparator());
System.out.println("\n按分数升序排序:");
students.forEach(System.out::println);
// 2. 按姓名排序
Collections.sort(students, new NameComparator());
System.out.println("\n按姓名排序:");
students.forEach(System.out::println);
// 3. 按年龄降序排序
Collections.sort(students, new AgeDescComparator());
System.out.println("\n按年龄降序排序:");
students.forEach(System.out::println);
// 4. 多重排序:先按分数,再按年龄
Collections.sort(students, new ScoreThenAgeComparator());
System.out.println("\n先按分数再按年龄排序:");
students.forEach(System.out::println);
// 5. 也可以使用匿名内部类(另一种形式的内部类)
Collections.sort(students, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// 按分数降序
return Integer.compare(s2.getScore(), s1.getScore());
}
});
System.out.println("\n按分数降序排序(使用匿名内部类):");
students.forEach(System.out::println);
}
}
泛型
- 对输入的内容加以约束,允许在定义类、接口、方法时使用类型形参(泛型),这个类型形参将在声明变量、创建对象、调用方法时动态地指定,进而让程序具备编译时检查能力
- 提供了编译时纠错的能力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//不使用泛型
import java.util.ArrayList;
import java.util.List;
public class WithoutGenerics {
public static void main(String[] args) {
// 创建一个不使用泛型的ArrayList
List list = new ArrayList();
// 添加各种类型的对象
list.add("Hello");
list.add("World");
list.add(123); // 自动装箱为Integer,但编译时不会报错
// 遍历集合
for (Object obj : list) {
// 需要强制类型转换
String str = (String) obj; // 这里会抛出ClassCastException!
System.out.println(str.length());
}
}
}
//输出
5
5
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//使用泛型
import java.util.ArrayList;
import java.util.List;
public class WithGenerics {
public static void main(String[] args) {
// 创建一个使用泛型的ArrayList,指定只能存储String类型
List<String> list = new ArrayList<String>();
// Java 7+ 可以使用钻石运算符:List<String> list = new ArrayList<>();
// 添加元素
list.add("Hello");
list.add("World");
// list.add(123); // 编译错误!无法通过编译
// 遍历集合 - 不需要强制类型转换
for (String str : list) {
System.out.println(str.length()); // 直接调用String的方法
}
}
}
//输出
5
5
泛型类的创建与使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 自定义泛型类
class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
public class CustomGenericExample {
public static void main(String[] args) {
// 存储字符串的盒子
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello Generics");
String str = stringBox.getContent(); // 不需要类型转换
// 存储整数的盒子
Box<Integer> intBox = new Box<>();
intBox.setContent(100);
Integer num = intBox.getContent(); // 不需要类型转换
// stringBox.setContent(123); // 编译错误!
}
}
- 注意:
Box<T>中的字母是随意的
泛型方法
- JDK 1.5以后还提供了泛型方法的支持,允许在类没有声明泛型的前提下让方法独立使用泛型进行开发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.qy.test;
public class TestOne {
// 泛型方法的基本语法:在返回类型前添加 <T>
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
public static <T> int printAll(T all) {
int count = 0;
System.out.println(all.toString());
return count;
}
// 多个类型参数的泛型方法
public static <T, U> void printPair(T first, U second) {
System.out.println("First: " + first + " (" + first.getClass().getSimpleName() + ")");
System.out.println("Second: " + second + " (" + second.getClass().getSimpleName() + ")");
}
// 有返回值的泛型方法
public static <T> T getFirstElement(T[] array) {
if (array == null || array.length == 0) {
return null;
}
return array[0];
}
public static void main(String[] args) {
// 测试泛型方法
Integer[] intArray = {1, 2, 3, 4, 5};
String[] strArray = {"Hello", "World", "Java"};
System.out.println("整数数组:");
printArray(intArray); // 类型推断,自动识别为Integer
System.out.println("字符串数组:");
printArray(strArray); // 类型推断,自动识别为String
System.out.println("打印不同类型对:");
printPair("年龄", 25); // T=String, U=Integer
printPair(3.14, true); // T=Double, U=Boolean
System.out.println("ssssssssssssssss");
int i = printAll(1);
System.out.println(i);
System.out.println("ssssssssssssssss");
System.out.println("获取第一个元素:");
Integer firstInt = getFirstElement(intArray);
String firstStr = getFirstElement(strArray);
System.out.println("第一个整数: " + firstInt);
System.out.println("第一个字符串: " + firstStr);
}
}
泛型通配符
- 泛型对<>中的类型是精准,方法定义时是父类,调用时是子类就会报错
- 当明确指定泛型类型后,就必须强制使用该类型传入,该类型的子类也同样会报”类型不匹配”错误
1
2
3
4
5
6
//方法定义:
public void doSth(List<Shape> shapeList)
//调用时使用Shape的子类Circle就会报错
List<Circle> circleList= new ArrayList<>;
obj.doSth(circleList);
-
为了增加泛型的匹配范围,泛型通配符<?>应运而生
-
单独使用<?>的话基本没啥意义,要和extends与super配合使用限定范围
-
extends关键字代表必须传入Shape或者子类才通过检查
1
public void doSth(List<? extends Shape> shapeList)
-
super关键字代表必须传入Rectangle或者其父类才能通过检查
1
public void doSth(List<? super Rectangle> shapeList)
多线程
程序、进程与线程的关系?
一个程序可以启动多个进程,每个进程间内存隔离,进程的资源是彼此隔离的,其他进程不允许访问
一个进程至少包含一个线程(主线程)
- 线程是进程内的一个”基本任务“,每个线程都有自己的功能,是CPU分配与调度的基本单位
- 进程内至少拥有一个”线程”,这个线程叫”主线程”,主线程消亡则进程结束
- 并发执行:单核CPU的时间片循环分给多线程;并行执行:多核CPU的时间片分别分给不同的线程
- 每个java进程至少包含两个线程:
main主线程和垃圾回收线程
创建多线程的三种方式
-
继承Thread类创建线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
package com.qy.test; import java.util.Random; public class ThreadTest { class Runner extends Thread{ public void run() { Integer speed = new Random().nextInt(10); for (int i = 0; i < 10; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("第"+i+"秒"+this.getName()+"跑了"+(speed*i)+"米"); } } } public void start(String name){ Runner runner = new Runner(); runner.setName(name); runner.start(); } public static void main(String[] args) { new ThreadTest().start("A"); new ThreadTest().start("B"); new ThreadTest().start("C"); } }
-
实现Runnable接口创建线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
public class ThreadTest2 { class Runner implements Runnable{ @Override public void run() { Integer speed = new Random().nextInt(10); for (int i = 1; i < 10; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("第"+i+"秒"+Thread.currentThread().getName()+"跑了"+(speed*i)+"米"); } } } public void start(String name){ Runnable r = new Runner(); Thread t = new Thread(r); t.setName(name); t.start(); } public static void main(String[] args) { ThreadTest2 t = new ThreadTest2(); t.start("A"); t.start("B"); } }
-
Callable接口创建线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
import java.util.Random; import java.util.concurrent.*; public class ThreadTest3 { class Runner implements Callable<Integer>{ public String name; @Override public Integer call() throws Exception { Integer speed = new Random().nextInt(10); Integer result = 0; for (int i = 1; i < 10; i++) { Thread.sleep(1000); result = speed * i; System.out.println("第"+i+"秒"+this.name+"跑了"+result+"米"); } return result; } } public void start() throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(3); Runner r1 = new Runner(); r1.name = "A"; Runner r2 = new Runner(); r2.name = "B"; Runner r3 = new Runner(); r3.name = "C"; Future<Integer> s1 = executorService.submit(r1); Future<Integer> s2 = executorService.submit(r2); Future<Integer> s3 = executorService.submit(r3); executorService.shutdown(); System.out.println(r1.name+"跑了"+s1.get()+"米"); System.out.println(r2.name+"跑了"+s2.get()+"米"); System.out.println(r3.name+"跑了"+s3.get()+"米"); } public static void main(String[] args) throws ExecutionException, InterruptedException { ThreadTest3 t = new ThreadTest3(); t.start(); } }
- 注意接收返回值的方法
线程的生命周期
- NEW 新创建状态
- RUNNABLE 可运行状态
- BLOCKED 阻塞状态
- WAITING 等待状态
- TIMED_WAITING 超时等待状态
- TERMINATED 结束状态

大致的可以总结成四个状态,刚刚创建还未运行–运行阶段–中断阶段(阻塞/等待)–死亡状态
线程同步
synchronized(同步锁)关键字的作用就是利用一个特定的对象设置一个锁lock(绣球),在多线程(游客)并发访问的时候,同时只允许一个线程(游客)可以获得这个锁,执行特定的代码(迎娶新娘)。执行后释放锁,继续由其他线程争抢。synchronized关键字保证共享对象在同一时刻只能被一个线程访问。
synchronized的锁对象
-
synchronized代码块-任意对象即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
public class SyncBlockWithRunnable { private final Object lock = new Object(); // 锁对象 private int count = 0; // 共享的任务类 class MyTask implements Runnable { @Override public void run() { for (int i = 0; i < 3; i++) { // synchronized代码块 synchronized(lock) { count++; System.out.println(Thread.currentThread().getName() + " - Count: " + count); } try { Thread.sleep(100); // 模拟处理时间 } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { SyncBlockWithRunnable demo = new SyncBlockWithRunnable(); MyTask task = demo.new MyTask(); // 创建共享任务 // 创建多个线程共享同一个任务 for (int i = 0; i < 5; i++) { new Thread(task, "Thread-" + (i + 1)).start(); } } }
-
synchronized方法-this当前对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
public class SyncMethodWithRunnable { private int count = 0; // 共享的任务类 class MyTask implements Runnable { @Override public void run() { for (int i = 0; i < 3; i++) { increment(); // 调用同步方法 try { Thread.sleep(100); // 模拟处理时间 } catch (InterruptedException e) { e.printStackTrace(); } } } // synchronized方法 private synchronized void increment() { count++; System.out.println(Thread.currentThread().getName() + " - Count: " + count); } } public static void main(String[] args) { SyncMethodWithRunnable demo = new SyncMethodWithRunnable(); MyTask task = demo.new MyTask(); // 创建共享任务 // 创建多个线程共享同一个任务 for (int i = 0; i < 5; i++) { new Thread(task, "Thread-" + (i + 1)).start(); } } }
-
synchronized静态方法-该类的字节码对象
什么是线程安全?
在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况
对象锁(synchronized加在方法上)
对象的某个同步方法被一个线程访问后,其他线程不能访问该对象的其他同步方法。某个线程得到了对象锁之后,该对象的其他同步方法是锁定的,其他线程是无法访问的。
用法
1
2
3
4
5
6
7
8
9
public synchronized void test(){
// TODO
}
public void test(){
synchronized (this) {
// TODO
}
}
package com.test.run;
public class TestSynchronized {
public synchronized void minus() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
public synchronized void minus2() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//测试类
public class Run {
public static void main(String[] args) {
final TestSynchronized test = new TestSynchronized();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
test.minus();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
test.minus2();
}
});
thread1.start();
thread2.start();
}
}
//输出
Thread-0 - 4
Thread-0 - 3
Thread-0 - 2
Thread-0 - 1
Thread-0 - 0
Thread-1 - 4
Thread-1 - 3
Thread-1 - 2
Thread-1 - 1
Thread-1 - 0
如果某个线程得到了对象锁,但是另一个线程还是可以访问没有进行同步的方法或者代码。进行了同步的方法(加锁方法)和没有进行同步的方法(普通方法)是互不影响的,一个线程进入了同步方法,得到了对象锁,其他线程还是可以访问那些没有同步的方法(普通方法)。
类锁(synchronized修饰静态方法或者使用代码块引用当前类)
1
2
3
4
5
6
7
8
9
public static synchronized void test(){
// TODO
}
public static void test(){
synchronized (TestSynchronized.class) {
// TODO
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class TestSynchronized {
public static synchronized void minus() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
//测试类
public class Run {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
TestSynchronized.minus();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
TestSynchronized.minus();
}
});
thread1.start();
thread2.start();
}
}
//输出
Thread-0 - 4
Thread-0 - 3
Thread-0 - 2
Thread-0 - 1
Thread-0 - 0
Thread-1 - 4
Thread-1 - 3
Thread-1 - 2
Thread-1 - 1
Thread-1 - 0
- 静态方法是类所有对象共用的,所以进行同步后,该静态方法的锁也是所有对象唯一的。每次只能有一个线程来访问对象的该非静态同步方法。
- 类锁和对象锁是不一样的锁,是互相独立的。
死锁
死锁是在多线程情况下最严重的问题,在多线程对公共资源(文件、数据)等进行操作时,彼此不释放自己的资源,而去试图操作其他线程的资源,而形成交叉引用,就会产生死锁
解决死锁
- 尽量减少公共资源的引用
- 用完马上释放公共资源
- 增加超时失败机制
线程池–concurrent包
- Runnable接口的弊端
- Runnable新建线程,性能差
- 线程缺乏统一管理,可能无限制的新建线程,相互竞争,严重时会占用过多系统资源导致死机或内存溢出
线程复用
ThreadPool线程池
- 重用存在的线程,减少线程对象创建、消亡的开销
- 线程总数可控,提高资源的利用率
- 提供额外功能,定时执行、定期执行、监控等、
在concurrent中包中,提供了工具类Executors(调度器)对象来创建线程池,可创建的线程池有四种:
-
FixedThreadPool - 定长线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
package com.qy.pool; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolTest1 { public static void main(String[] args) { //定长线程池的特点是固定线程总数,空闲线程用于执行任务,如果线程都在使用,后续任务则处于等待状态 ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 1000; i++) { final int index =i; //不需要返回值,使用execute方法执行Runnable对象 executorService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+":"+index); } } //需要返回值,使用submit方法执行callable对象,利用Future对象接收返回值 ); } executorService.shutdown(); } }
-
CachedThreadPool-可缓存线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
package com.qy.pool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolTest2 { public static void main(String[] args) { //可缓存线程池的特点是,无限大,如果线程池中没有可用的线程则创建,有空闲线程则利用起来 ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 1000; i++) { final int index =i; //不需要返回值,使用execute方法执行Runnable对象 executorService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+":"+index); } } ); } executorService.shutdown(); } }
-
SingleThreadExecutor-单线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
package com.qy.pool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolTest3 { public static void main(String[] args) { //只能有一个线程 ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 1000; i++) { final int index =i; //不需要返回值,使用execute方法执行Runnable对象 executorService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+":"+index); } } ); } executorService.shutdown(); } }
-
ScheduledThreadPool-调度线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
package com.qy.pool; import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ThreadPoolTest4 { public static void main(String[] args) { ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5); scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println(new Date() + "延迟一秒执行,每三秒执行一次"); } },1,3, TimeUnit.SECONDS); } }