Java小白开始每日刷题,并记录刷题遇到的一些知识点,大佬们多多包涵,内容可能会比较杂乱,如果不太详细尽情谅解!!!希望对一些人有所帮助!!!
本次更新了与Java线程相关的知识点
整理了与多态、泛型、集合相关的一些基础指点!
上期链接牛客Java专项练习笔记(4)
本次更新内容
23. Java线程相关
23.1 进程与线程
进程
程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理IO的
当一个程序被运行,从磁盘加载这个程序代码至内存,这时就开启了一个进程
进程可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如笔记本、画图等),也有的程序只能启动一个实例进程(例如网易云音乐、360安全卫士等)
线程
一个进程之内可以分为一到多个线程
一个人线程就是一个指令流,将指令流中的一条条指令以一定顺序交给CPU执行
Java中,线程作为最小调度单位,进程作为资源分配最小单位,在Windows中进程是不活动的,只是作为线程的容器
二者对比
进程基本上相互独立,而线程存在于进程内,是进程的一个子集
进程拥有共享的资源,如内存空间等,供其内部的线程共享
进程间通信较为复杂
同一台计算机的进程通信称为IPC(Inter-process communication)
不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如HTTP
线程通信相对简单,因为他们共享进程的内存,一个例子是多线程可以访问同一个共享变量
线程更轻量,线程上下文切换成本一般要比进程上下文切换低
23.2 并行与并发
并发
在操作系统中是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都在同一个处理器上运行。
单核CPU下,线程实际还是串行执行的。操作系统中有一个组件叫任务调度器,将CPU的时间片(Windows下时间片最小约为15毫秒)分给不同的程序使用,只是由于CPU在线程间(时间片很短)的切换非常快,人类感觉是同时运行的。总结一句话就是:微观串行,宏观并行。一般将这种线程轮流使用CPU对的做法称为并发(Concurrent)。
并行
当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,我们称之为并行(Parallel)
二者对比
并发,指的是多个事情,在同一段时间内同时发生了。
并行,指的是多个事情,在同一时间点同时上发生了。
23.3 join()方法详解
如果一个线程A执行了thread.join()语句,其含义时:当前线程A等待thread线程终止之后才从thread.join()返回。
23.3.1 join()同步应用
以调用方角度来讲,如果:
需要等待结果返回,才能继续运行就是同步
不需要等待结果返回,就能继续运行就是异步
等待多个结果
static int r1 = 0;
static int r12 = 0;
public static void main(String[] args) throws InterruptedException {
test();
}
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r1 = 10;
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r2 = 20;
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
log.debug("join begin");
t1.join();
log.debug("t1 join end");
t2.join();
log.debug("t2 join end");
long end = System.currentTimeMillis();
log.debug("r1: {} | r2: {} cost: {}", r1, r2, end - start);
}
输出结果:
/**
* 第一个 join:等待 t1 时, t2 并没有停止, 而在运行
* 第二个 join:1s 后, 执行到此, t2 也运行了 1s, 因此也只需再等待 1s 总共2s
*/
20:33:32.525 [DEBUG] Thread.JoinTest2 [main] : join begin
20:33:33.524 [DEBUG] Thread.JoinTest2 [main] : t1 join end
20:33:34.524 [DEBUG] Thread.JoinTest2 [main] : t2 join end
20:33:34.524 [DEBUG] Thread.JoinTest2 [main] : r1: 10 | r2: 20 cost: 2001
运行流程:
graph TD;
main-->t1.join;
main-->t1.start;
main-->t2.start;
t1.start--1s后-->r=10;
t1.join-->t2.join-仅需等1s;
r=10--t1终止-->t1.join;
t2.start--2s后-->r=20;
r=20--t2终止-->t2.join-仅需等1s;
有时效的join:
join(long n) 等待线程运行结束,最多等待n毫秒
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test3();
}
public static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r1 = 10;
});
long start = System.currentTimeMillis();
t1.start();
// 线程执行结束会导致 join 结束 如果等待时间小于休眠时间 线程结束则会立马执行
t1.join(1500);
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
输出:20:52:19.790 [DEBUG] Thread.JoinTest3 [main] : r1: 10 r2: 0 cost: 1001
23.4 Daemon线程
默认情况下,Java进程需要等待所有线程都运行结束才会结束。有一种特殊的线程叫做守护线程,只要其他非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
注意:Daemon属性需要在启动线程之前设置,不能在启动线程之后设置
案例:
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
break;
}
log.debug("结束");
}
});
// 设置该线程为守护线程
t1.setDaemon(true);
t1.start();
Thread.sleep(1000);
log.debug("结束");
}
//输出
14:38:28.157 [DEBUG] Thread.DaemonTest [main] : 结束
本次整理内容
24. 多态相关
24.1 多态的形式
多态是继封装、继承之后,面向对象的第三大特征。
多态是出现在继承或者实现关系中的
多态体现的格式:
父类类型 变量名 = new 子类/实现类构造器;
变量名.方法名();
多态的前提:
有继承关系,子类对象是可以赋值给父类类型的变量。例如Animal是一个动物类型,而Cat是一个猫类型。Cat继承了Animal,Cat对象也是Animal类型,自然可以赋值给父类类型的变量。
有父类引用指向子类对象
有方法重写
24.2 多态调用成员的特点
变量调用:编译看左边,运行也看左边
方法调用:编译看左边,运行看右边
/**
*理解:
*Animal a = new Dog();
*现在是a去调用变量和方法,a是Animal类型的,所以默认都会去Animal这个类中去找
*
*变量:在子类的对象中,会把父类的成员变量也继承下来。父:name,子:name
*方法:如果子类对方法进行了重写,那么在虚方法表中是会把父类的方法进行覆盖的
*/
class Test{
public static void main(String[] args){
//创建对象
//Fu f = new Zi();
Animal a = new Dog();
/**
*成员变量调用:编译看左边,运行也看左边
*编译看左边:javac编译代码的时候,会看左边父类中有没有这个变量,如果有,编译成功;如果没有,编译失败
*运行看左边:java运行代码的时候,实际获取的就是左边父类中的成员变量的值
*/
System.out.println("a.name") //输出:动物
/**
*方法调用:编译看左边,运行看右边
*编译看左边:javac编译代码的时候,会看左边父类中有没有这个方法,如果有,编译成功;如果没有,编译失败
*运行看右边:java运行代码的时候,实际获取的就是右边子类中的方法
*/
a.eat(); //输出:狗吃骨头
}
}
class Animal{
String name = "动物";
public void eat(){
System.out.println("动物吃东西!")
}
}
class Cat extends Animal {
String name = "狗";
public void eat() {
System.out.println("狗吃骨头");
}
}
class Dog extends Animal {
String name = "猫";
public void eat() {
System.out.println("猫吃鱼");
}
}
动画演示:
![](https://i-blog.csdnimg.cn/blog_migrate/4e24749211de3480da94e7ae8f47fd5a.gif)
24.3 引用类型转换
多态的弊端:多态的写法无法访问子类的独有功能
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。所以,想要调用子类特有的方法,必须做向下转型。
回顾基本数据类型转换
自动转换: 范围小的赋值给范围大的.自动完成:double d = 5;
强制转换: 范围大的赋值给范围小的,强制转换:int i = (int)3.14
多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种。
24.3.1 向上转型(自动转换)
向上转型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的
当父类引用指向一个子类对象时,便是向上转型
格式:
父类类型 变量名 = new 子类类型();
如:Animal a = new Cat();
24.3.2 向下转型(强制转换)
向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的
一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型
格式:
子类类型 变量名 = (子类类型) 父类变量名;
如:Aniaml a = new Cat();
Cat c =(Cat) a;
案例:
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void watchHouse() {
System.out.println("看家");
}
}
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
}
}
24.3.3 转型的异常
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}
这段代码可以通过编译,但是运行时,却报出了 ClassCastException ,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。
24.3.4 instanceof关键字
为了避免ClassCastException的发生,Java提供了instanceof关键字,给引用变量做类型的校验
格式:
变量名 instanceof 数据类型
如果变量属于该数据类型或者其子类类型,返回true。
如果变量不属于该数据类型或者其子类类型,返回false。
案例:转型前,最好做一个判断
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
}
instanceof新特性:把判断和强转合并成了一行
//新特性
//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
//如果不是,则不强转,结果直接是false
if(a instanceof Dog d){
d.lookHome();
}else if(a instanceof Cat c){
c.catchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}
25. 泛型相关
25.1 泛型
泛型:可以在编译阶段约束操作的数据类型,并进行检查
格式: <数据类型>
注意:泛型只支持引用数据类型
示例:
ArrayList<Integer> list = new ArrayList<>();
细节:
泛型中不能写基本数据类型
指定泛型具体类型后,传递数据时,可以传入该类型或者其子类类型
如果不写泛型,默认为Object
25.2 泛型类
当一个类中,某个变脸的数据类型不确定时,就可以定义带有泛型的类
格式:
修饰符 classs 类名<泛型> {
}
public class ArrayList<E> {
//E可以理解为变量,不是来用来记录数据的,而是记录数据类型的,可以写成T、E、K、V等
//创建该类对象时,E就确定类型
}
25.3 泛型方法
方法中形参类型不确定时:
使用类名后面定义的类型
在方法声明上定义自己的泛型
格式:
修饰符 <类型> 返回值类型 方法名(类型 变量名) {
}
public <T> void show(T t) {
//T可以理解为变量,不是来用来记录数据的,而是记录数据类型的,可以写成T、E、K、V等
//调用方法时,T就确定类型
}
25.4 泛型接口
如何使用一个带泛型的接口:
实现类给出具体类型
实现类延续泛型,创建对象时再确定
格式:
修饰符 interface 接口名<泛型> {
}
public interface List<E> {
//E可以理解为变量,不是来用来记录数据的,而是记录数据类型的,可以写成T、E、K、V等
//创建该类对象时,E就确定类型
}
25.5 泛型的继承和通配符
泛型不具备继承性,但是数据具备继承性
public class Test{
public static void main(String[] args) {
//创建集合的对象
ArrayList<Ye> list1 = new ArrayList<>();
ArrayList<Fu> list2 = new ArrayList<>();
ArrayList<Zi> list3 = new ArrayList<>();
//调用method方法,泛型不具备继承性
method(list1);
method(list2); //报错
method(list3); //报错
//数据具备继承性
list1.add(new Ye());
list1.add(new Fu());
list1.add(new Zi());
}
//泛型不具备继承性,泛型里面写的什么类型,那么只能传递什么类型的数据
public static void method(ArrayList<Ye> list){
}
}
class Ye{}
class Fu extends Ye{}
class Zi extends Fu{}
泛型的通配符
public class Test{
public static void main(String[] args) {
//创建集合的对象
ArrayList<Ye> list1 = new ArrayList<>();
ArrayList<Fu> list2 = new ArrayList<>();
ArrayList<Zi> list3 = new ArrayList<>();
ArrayList<Student> list3 = new ArrayList<>();
method(list1);
method(list2);
method(list3);
method(list4); //报错
}
/**
* 此时泛型里面写的什么类型,那么只能传递什么类型的数据
* 弊端:利用泛型方法有一个小弊端,此时他可以接受任意的数据类型
* Ye Fu Zi ......
* 希望:希望只能传递Ye Fu Zi三种类型
*
* public static<E> void method(ArrayList<E> list){
*
* }
*/
/**
* 泛型的通配符:
* ? 也表示不确定的类型,但是可以进行类型的限定
* ? extends E:表示可以传递E或者E所有的子类类型
* ? super E:表示可以传递E或者E所有的父类类型
*/
public static<E> void method(ArrayList<? extends Ye> list){
}
}
class Ye{}
class Fu extends Ye{}
class Zi extends Fu{}
class Student{}
26. 集合相关
26.1 集合体系结构
26.2 Collection
![](https://i-blog.csdnimg.cn/blog_migrate/e7a4d520c29650f29586639017a2157c.png)
List系列集合:添加的元素是有序(存和取的顺序)、可重复、有索引
Set系列集合:添加的元素是无序、不重复、无索引
Collection:Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的
Collection集合常用方法:
方法名 | 说明 |
boolean add(E e) | 添加元素 |
boolean remove(Object o) | 从集合中移除指定元素 |
boolean removeIf(Object o) | 根据条件进行移除 |
void clear() | 清空集合中的元素 |
boolean contains(Object o) | 判断集合中是否存在指定元素 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中元素的个数 |
细节:
contains方法底层依赖equals方法进行判断是否存在的,所以如果集合中存的是自定义对象,也想通过contains方法来判断是否包含,那么在JavaBean类中,一定要重写equals方法
26.3 迭代器
迭代器:再Java中的类是Iterator,是集合的专用遍历方式
Iterator<E> iterator() //返回此集合中元素的迭代器,默认指向当前集合的0索引
Iterator中的常用方法
boolean hasNext() //判断当前位置是否有元素,有元素返回true,没有元素返回false
E next() //获取当前位置的元素,并将迭代器对象移向下一个位置
void remove() //删除迭代器对象当前指向的元素
Collection集合的遍历
public class IteratorDemo1 {
public static void main(String[] args) {
//创建集合对象
Collection<String> c = new ArrayList<>();
//添加元素
c.add("hello");
c.add("world");
//Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
Iterator<String> it = c.iterator();
//用while循环改进元素的判断和获取
while (it.hasNext()) { //创建指针
String s = it.next(); //判断是否有元素
System.out.println(s); //获取元素,移动指针
}
}
}
细节点:
如果当前指针指向最后没有元素的位置,如果再次强行调用,则会报错NoSuchElementException
迭代器遍历完毕,指针不会复位(重新获取一个迭代器对象)
循环中只能用一次next方法
迭代器遍历时,不能用集合的方法进行增加或删除,如果需要删除,需要使用迭代器提供的remove()方法
迭代器中删除的方法
public class IteratorDemo2 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("b");
list.add("c");
list.add("d");
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
if("b".equals(s)){
//指向谁,那么此时就删除谁.
it.remove();
}
}
System.out.println(list);
}
}