当前位置:   article > 正文

Java高级看这篇就足够了(高级知识汇总)

java高级

第 1 章封装性

1.1封装性介绍

编程语言中,封装有2层意思:
1.将重复的代码提取到一个公共的方法中,从而提升代码的复用性、可维护性
在这里插入图片描述

2.如果成员变量未加封装密封,可以在类的外部被肆无忌惮的修改
封装性是指,将对象的属性和行为进行密封,保护数据并隐蔽具体的细节,在类的外部不可直接访问,要想访问需要通过严格的接口控制。
封装性的目的:采用封装的思想保证了类内部数据结构的完整性,应用该类的用户不能轻易直接操纵该数据结构,
封装的原则就是要求在类的外部,不能随意存取对象的内部数据(成员属性和成员方法).从而有效地避免了外部错误对它的影响,使软件错误能够局部化,大大较少差错和排错的难度
在这里插入图片描述
在这里插入图片描述

使用封装性解决上面的问题
在这里插入图片描述
在这里插入图片描述

1.2封装的步骤:

1. 私有化成员变量,使用private关键字修饰;
2. 提供公共的get和set成员变量的方法,并在方法体中进行合理性的判断;
3. 在构造方法中调用set成员变量的方法来确保合理性;
在这里插入图片描述

1.3单例设计模式

单例,1个实例,1个对象
在有一些场景中,一个类只需要对外提供一个对象,这样的类称为单例类
编写单例类的方式,称为单例设计模式

首先,我们先看一个非单例模式例子:
在这里插入图片描述
在这里插入图片描述

单例设计模式的实现步骤:
1.私有化构造方法,阻止在类的外部创建对象
2.在类的内部提供私有的静态属性保存当前类的实例
3.在类的内部提供公共的静态方法,返回当前类的实例

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

第 2 章继承性

2.1先看一个需求:

需求:设计3个类:学生类、教师类、工人类
学生类:
属性:姓名、年龄、学号
方法:学习
教师类:
属性:姓名、年龄、薪水
方法:讲课
工人类:
属性:姓名、年龄、工号
方法:工作

通过需求发现多个类之间,拥有很多相同的内容,为了提升代码的复用性、可维护性,我们可以将多个类之间相同的内容提取到一个新类中,然后各个子类再继承该新类即可

语法格式:
修饰符 class 子类 extends 父类{}
例如:public class Student extends Person{}

代码演示:
将多个类之间相同的内容提取到公共的类中

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

新建测试类,进行测试
在这里插入图片描述

课堂练习
大家定义工人类,让工人类继承Person类,并新建测试文件进行测试

2.2继承性注意事项
1.如果一个子类继承了父类,那么这个子类拥有父类所有的成员属性和方法,即使是父类里有private属性的变量,子类也是继承的,只不过不能使用。
2. 当构造子类对象时,会先构造父类对象,会自动调用父类的无参构造方法
如果手动调用了父类的有参构造方法,不再执行父类的无参构造
3. 在java语言中,只支持单继承,也就是一个类只能有一个父类
4. 不能滥用继承,必须满足子类 is a 父类 的逻辑关系时才可以

代码演示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3this、super关键字

this,这个,表示当前类或当前类的实例(对象)
super,表示父类或父类对象

使用this.的方式可以访问本类对象的成员变量、成员方法
使用super.的方式可以访问父类对象的成员变量、成员方法
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

使用this()的方式放在构造方法中的第一行,表示调用本类中的无参构造方法
使用super()的方式放在构造方法中的第一行,表示调用父类的无参构造方法
this() 和 super() 必须出现在构造方法的第一行,因此不能同时出现
在这里插入图片描述

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

2.4方法重写

当子类从父类中继承的方法不能满足子类需求时,就可以在子类中声明一个和父类中一模一样的方法(方法名相同、参数列表相同、返回值类型相同),来覆盖父类中的方法,这就称为方法重写.

代码演示:

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

在这里插入图片描述

在这里插入图片描述

方法重写的注意事项:
1.相同的方法名、相同的参数列表、相同的返回值类型
2.访问权限不能缩小,可以放大
3.在子类方法中,可以使用super. 调用父类中原始的方法

2.5访问权限修饰符

通常情况下,成员变量(属性)使用private封装,成员方法使用public修饰
public,公共的,在任何类中都能访问
private,私有的,只能在当前类的内部访问
protected,受保护的,在当前包的其他类、当前类、子类中
默认修饰符,当前类、当前包的其他类中
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.6final关键字

final,本意为”最终的,不可更改的”,可以使用该关键字修饰类、成员方法、成员变量
使用final修饰类,表示该类是最终的,不能被继承,防止滥用继承
使用final修饰成员方法,表示该方法是最终的,不能被重写,防止不经意间重写方法
使用final修饰成员变量,表示该成员变量必须赋值,而且不能更改

通常情况,我们会使用static和final关键字组合使用,来声明常量:
所谓的常量,就是一旦声明不能更改
其作用就是,声明一些公共的数据,方便使用,例如:税率、圆周率等
常量名有一个特点:通常都是大写,例如:PI、TAX_RATE、STATUS等
在这里插入图片描述

2.7练习:

自定义矩形类,成员变量主要有:横坐标、纵坐标、长度、宽度,方法有:
构造方法、打印所有成员变量的方法,实现矩形类的封装。
自定义圆形类,成员变量主要有:横坐标、纵坐标、半径,方法有:构造方法、打印
所有成员变量的方法,实现圆形类的封装。
自定义测试类,在main()方法中分别创建矩形和圆形的对象,去调用各自的打印成员变量的方法。

2.8对象创建过程

单个对象创建过程
1.先执行静态代码块,当类加载完毕时,就会执行静态代码块
2.当new一个对象时,会执行构造块 {}
3.执行构造方法

代码演示:
在这里插入图片描述

子类对象创建过程
1.先加载父类到内存,再加载子类到内存,先执行父类的静态代码块,再执行子类的静态代码块
2.构造子类对象时,先构造父类对象,所以先执行父类的构造块、构造方法;再执行子类的构造块、构造方法

代码演示
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第 3 章多态性

3.1多态性介绍

多态性,是指一种事物的多种表现形态
举例:
1.让张三去买瓶饮料
可能会买到:红牛、雪碧、大个核桃、可乐等
2.让李四买一个宠物
可能会买到:松鼠、乌龟、小猫、小狗…
3.让大家声明一个整数
可能创建:byte 、short、int、long

3.2多态语法格式

父类类型 引用名 = new 子类();
//饮料 引用 = new红牛();

接口类型 引用 = new 实现类();

代码演示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.3多态效果

  1. 当父类类型的引用指向子类类型的对象时,父类类型的引用,当成父类对象使用?还是当成子类对象使用的?
    父类类型的引用,其实是当成父类对象使用的,所以可以直接访问父类对象的方法,不能直接访问子类对象的方法

  2. 如果子类重写了父类的方法,静态方法调用父类的,非静态的方法调用子类的
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

3.4多态必要条件

1.继承
2.重写
3.父类类型引用指向子类对象

3.5类型转换

如果需要访问子类对象的属性、方法,需要做类型转换
其中,父类类型转换为子类类型,称为向下造型,需要强制类型转换
其中,子类类型转换为父类类型,称为向上造型,是自动转换的

语法格式:
强制类型转换:子类类型 引用名 = (子类类型)父类对象;
例如:Student s = (Student)p;
在这里插入图片描述

自动类型转换:父类类型 引用名 = 子类对象;
在这里插入图片描述

注意事项:
1.引用类型的转换必须发生在父子类之间,否则编译报错
2.拥有父子关系的对象,在进行强制类型转换时,如果转换的目标类型并不是当前引用真正指向的类型时,会在运行阶段报类型转换的异常
为了避免这种错误的发生,在强制类型转换时,使用instanceof判断当前引用是否是目标类型的实例(对象)
语法格式:
if(引用变量名 instanceof 引用类型){ 语句块 }
在这里插入图片描述

3.6练习

自定义矩形类,成员变量主要有:横坐标、纵坐标、长度、宽度,成员方法有: 打印所有成员变量的方法,实现矩形类的封装。
自定义圆形类,成员变量主要有:横坐标、纵坐标、半径,成员方法有:打印所有成员变量的方法,实现圆形类的封装。
自定义测试类并实现如下方法:
在main()方法中分别创建矩形和圆形的对象,自定义成员方法,要求:
根据参数传入的圆形对象来调用该对象的draw方法
根据参数传入的矩形对象来调用该对象的draw方法

参考代码
在这里插入图片描述
在这里插入图片描述

3.7多态优点:

多态的作用在于屏蔽不同子类的差异性,从而实现通用的编程
经验:
在以后的开发中推荐使用父类的引用指向子类对象的形式,因为这种情况下引用直接调用的方法一定是父类拥有的方法,此时若更改指向的子类对象,那么后续直接调用的方法不需要做任何的修改直接生效,因此提供了代码的可维护性。

第 4 章抽象类

4.1抽象类介绍

当父类的一些方法不确定如何具体实现时,可以用abstract关键字修饰该方法,此时该方法称为“抽象方法”, 而这个类就称为“抽象类”。
也就是说,抽象类就是使用abstract关键字修饰的类。

4.2语法格式

抽象方法:访问修饰符 abstract 返回值类型 方法名(形参列表);
如:
public abstract void cry();
在这里插入图片描述

4.3抽象类作用

抽象类不在于实例化(创建对象),而在于被继承,因为一旦子类继承了抽象类,那么子类就必须实现抽象类中的抽象方法
所以说:抽象类对子类具有强制性、规范性
在这里插入图片描述

4.4抽象类注意事项

1.抽象方法没有方法体
2.抽象类不能实例化对象(构造对象)
3.抽象类中可以有成员变量、成员方法、构造方法
4.子类一旦继承了抽象类,就必须实现抽象类中的抽象方法(除非当前子类也声明为抽象类)

4.5练习

请设计抽象类超人 Superman,属性有: 名字,年龄。
方法有:run 跑, fly 飞, attack 攻击
然后写 蜘蛛侠,蝙蝠侠,和钢铁侠分别都继承 Superman ,并创建各自的对象实例

第 5 章接口

5.1接口介绍

接口就是比抽象类还抽象的类,体现在没有构造方法、没有成员变量

语法格式:
public interface 接口名称{
//常量
//抽象方法
}

有了抽象类,为什么还要使用接口?
历史遗留问题,类只能单继承,也就是说一个类继承了一个类之后,就不能再继承其他类,而接口的设计为了弥补单继承不足,一个类可以实现多个接口

5.2快速入门案例

设计一个USB接口,该接口有3个抽象方法:paixian()、chicun()、lianjie()
再定义3个实现类:数据线、U盘、鼠标,实现USB接口中的抽象方法
定义测试类,创建对象,并测试

创建接口步骤:
New—Interface—,通常接口使用interface结尾
在这里插入图片描述

实现类通过implements关键字实现接口,一旦实现类实现了接口,就必须实现接口中的所有抽象方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.3练习

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

在这里插入图片描述

5.4接口细节

接口不能实例化
接口中只能有抽象方法,没有方法体
接口中可以有属性,但是只能是常量,例如:public static final double PI = 3.14;
一个实现类可以实现多个接口,多个接口之间使用逗号隔开,例如:
public class MysqlImpl implements DBInterface,UsbInterface{}
接口与接口之间可以继承,使用extends关键字,例如:
public interface DBInterface extends UsbInterface{}

5.5抽象类和接口的区别

定义抽象类的关键字是abstract,定义接口的关键字是interface。
继承抽象类的关键字是extends,而实现接口的关键字是implements。
继承抽象类是单继承,而实现接口是多实现。
抽象类中有构造方法,但接口中没有。
抽象类中可以有成员变量,而接口中只有常量。
抽象类中可以有成员方法,而接口中只有抽象方法。
抽象类中增加方法可以不影响子类,但接口一定影响子类。

第 6 章异常处理

6.1异常介绍

在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美, 在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避 免的,比如:客户输入数据的格式,读取文件是否存在,网络是否始终保持 通畅等等。
异常:在Java语言中,将程序执行中发生的不正常情况称为“异常” 。 (开发过程中的语法错误和逻辑错误不是异常)

Java程序在执行过程中所发生的异常事件可分为两类:
1.Error:Java虚拟机无法解决的严重问题。
如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。一般不编写针对性 的代码进行处理。

2.Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使 用针对性的代码进行处理。

6.2异常体系结构

在这里插入图片描述

6.2.1 编译时异常

程序在编译阶段出现的异常,如果不解决,程序无法编译通过,又称为:Checked Exception
常见的编译时异常有:IOException、FileNotFoundException、ClassNotFoundException、IllegaArguementException 、SQLException等。
在这里插入图片描述
在这里插入图片描述

6.2.2 运行时异常

又称为Unchecked Exception,编译时没有检测出异常,但是在运行时发现异常。
java.lang.RuntimeException类及它的子类都是运行时异常。

6.3常见的运行时异常

ArithmeticException,算数异常
NullPointerException,空指针异常
ArrayIndexOutOfBoundsException,数组下标越界异常
ClassCastException,类型转换异常
NumberFormatexception,数字格式异常

在这里插入图片描述

6.4异常处理

6.4.1 默认异常处理

系统默认的异常初始,是指直接打印错误信息、错误原因、错误所在的行号
在这里插入图片描述

6.4.2 捕获异常

通过异常捕获,可以更加灵活的处理异常信息,例如我们拿到异常信息之后,可以写入到日志文件中。

语法格式:
try{
// 尝试执行可能出现异常的代码
}catch(异常类型 变量名){
// 如果出现异常,自动将异常对象传入到catch块
}finally{
// 最终要执行的代码
// 不管尝试执行的代码有没有出现异常,都执行这里
}

代码演示:
在这里插入图片描述

6.4.3 声明式抛出异常

如果异常信息暂时无法处理,暂时处理不了,可以将异常向外抛出去,抛给方法的调用者。
如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可 以是方法中产生的异常类型,也可以是它的父类。

语法格式:
public void show() throws 异常类型{

}
在这里插入图片描述

6.4.4 手动抛出异常

Java异常类对象除在程序执行过程中出现异常时由系统自动生成并 抛出,也可根据需要使用人工创建并抛出。
1.首先要生成异常类对象,然后通过throw语句实现抛出操作(提交给Java运行环境)。
IOException e = new IOException();
throw e;
2.可以抛出的异常必须是Throwable或其子类的实例。

6.5自定义异常

6.5.1 自定义异常类

一般地,用户自定义异常类都是RuntimeException的子类。
自定义异常类通常需要编写几个重载的构造器。
自定义异常需要提供serialVersionUID
自定义的异常通过throw抛出。
自定义异常最重要的是异常类的名字,当异常出现时,可以根据名字判断异常类型。

java内置的异常类,封装的异常信息都是内置的
通过自定义异常类,可以自己编写异常信息,例如:数据取值范围错误信息。

通常,自定义的异常类,需要提供2个构造:无参构造、有参构造(String类型)
在这里插入图片描述

如何抛出自定义的异常呢?
throw new 自定义异常类(“自定义的异常信息”);
throw new TravleException(“票已售罄…”);
在这里插入图片描述

6.5.2 练习

结合异常处理完善学生管理系统:
添加学生时,如果年龄大于150岁或小于0岁时,抛出异常:年龄不合法
1.自定义年龄的异常类
在这里插入图片描述

2.抛出自定义的异常对象
年龄不合法的时候,抛出异常对象
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.5.3 总结

上游排污,下游治污
在这里插入图片描述

第 7 章多线程

7.1基本概念

7.1.1 程序

保存在电脑硬盘上的可执行文件

7.1.2 进程

在计算机内存中运行的程序,叫进程
在这里插入图片描述
在这里插入图片描述

7.1.3 线程

在进程内部同时运行的程序,在java中线程就是方法
在这里插入图片描述

目前主流的操作系统都支持多进程,是为了让操作系统可以同时执行多个任务,但进程是重量级的,新建进程对系统资源的消耗比较大,因此进程的数量比较局限。
线程是进程内部的程序流,也就是说操作系统支持多进程,而每个进程的内部又支持多线程,并且线程是轻量级的,会共享所在进程的资源,因此以后主流的开发都采用多线程技术。

7.1.4 单线程

在进程内部,只运行一个程序,在java体现在只执行main方法
举例:
单线程好比是,1个窗口卖票,顾客排队
在java中,执行main方法的线程,称为主线程

7.1.5 多线程

在进程内部,同时运行多个程序,在java中体现在同时执行多个方法
举例:
你早上上班,正要打卡的时候,手机响了。。你如果先接了电话,等接完了,在打卡,就是单线程。如果你一手接电话,一手打卡。就是多线程。

多线程的好处:
可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。

7.1.6 CPU

  1. CPU个数即CPU芯片个数
  2. CPU的核心数是指物理上,也就是硬件上存在着几个核心。
    比如,双核就是包括2个相对独立的CPU核心单元组,四核就包含4个相对独立的CPU核心单元组。
  3. CPU线程数是一种逻辑的概念,简单地说,就是模拟出的CPU核心数。比如,可以通过一个CPU核心数模拟出2线程的CPU,也就是说,这个单核心的CPU被模拟成了一个类似双核心CPU的功能。
    在这里插入图片描述

一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

7.1.7 并发、并行

并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

7.2多线程优点

  1. 提高应用程序的响应,增强用户体验。
  2. 提高计算机系统CPU的利用率
  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和 修改

7.3多线程应用场景

1.高并发
系统接受实现多用户多请求的高并发时,通过多线程来实现。
2. 后台处理
一个程序是线性执行的。如果程序执行到要花大量时间处理的任务时,那主程序就得等待其执行完才能继续执行下面的。那用户就不得不等待它执行完。
这时候可以开线程把花大量时间处理的任务放在线程处理,这样线程在后台处理时,主程序也可以继续执行下去,用户就不需要等待。线程执行完后执行回调函数。
3. 大任务
大任务处理起来比较耗时,这时候可以起到多个线程并行加快处理(例如:分片上传)。

7.4多线程创建和使用

JDK1.5之前创建多线程有两种方法:
1.继承Thread类的方式
2.实现Runnable接口的方式

JDK5.0 新增了2种多线程创建方式:
1.实现Callable接口
2.使用线程池

第 8 章多线程的创建与启动

8.1方式一:继承Thread类
需求:使用多线程技术打印奇数、偶数,其中一个线程打印奇数,另一个线程打印偶数,这两个线程同时运行。

步骤:
1.自定义一个类,并继承java.lang.Thread类
2.重写run方法
3.调用start方法开启多线程
在这里插入图片描述
在这里插入图片描述

新建测试类,让上面的两个线程同时执行【注意:不是先后执行】
在这里插入图片描述

8.2多线程执行流程

1.执行main方法的线程,称为主线程,执行run方法的线程,称为子线程
2.执行start方法之前,只执行一次主线程,当调用start方法之后,线程数瞬间由1个变成2个,其中主线程继续执行main方法,然后新创建的子线程执行run方法
3.main方法执行完毕,主线程结束,run方法执行完毕,子线程结束

8.3Thread类常用方法

Thread():创建新的Thread对象
Thread(String threadname):创建线程并指定线程实例名

void start(): 启动线程,并执行对象的run()方法
run(): 线程在被调度时执行的操作
getName(),返回线程的名称
setName(),设置线程名称
static void sleep(long millis) - 用于让当前线程休眠参数指定的毫秒数。
static Thread currentThread(): 返回当前线程。在Thread子类中就 是this。

8.4练习(龟兔赛跑)

编写龟兔赛跑多线程程序,设赛跑长度为30米
兔子每跑完10米休眠10秒
乌龟跑完20米休眠1秒
乌龟和兔子每跑完1米输出一次结果,看看最后谁先跑完
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.5方式二实现runnable接口

需求:使用多线程技术实现多窗口卖早餐案例
步骤:
1.自定义类实现Runnable接口,并重写run方法,并编写卖早餐的代码
2.实例化该Runnable接口的实现类对象
3.实例化一个Thread类对象,并把该对象传递到参数中
4.调用start方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.6方式1和方式2的区别

继承Thread类和实现Runnable接口的区别:

使用继承Thread类的方式重新实现卖票
在这里插入图片描述
在这里插入图片描述

运行结果
在这里插入图片描述

区别:
1.继承Thread类的方式不适合资源共享,而实现Runnable接口的方式更适合资源共享
举例:
继承Thread类的方式,相当于拿出2件事即2个卖100张票的任务分别给三个
窗口,他们各自做各自的任务,即各自卖各自的100张票。

实现Runnable接口的方式, 相当于是拿出一个卖10张票的任务给三个窗口共
同去完成。
2.Java只能单继承,因此采用继承Thread的方式,在以后进行代码重构时,无法继承别的类,而接口是可以多实现的,可以实现多个接口

8.7方式3 Callable接口+FutureTask

特点:
实现Callable接口的方式可以将子线程的结果返回到主线程,也可以处理异常
步骤:
1.创建一个类,实现Callable接口
2.重写call方法,编写多线程代码,并返回结果
3.创建FutrueTask对象,并将Callable接口的实现类对象传递到FutrueTask构造方法
4.创建Thread对象,并将FutrueTask对象传递到构造方法,并调用start方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.8方式4 线程池

思路:
可以先初始化一个线程池,需要时从线程池中取出线程执行异步任务,使用完再归还到线程池,这样可以避免频繁的创建、销毁线程,从而提升系统的性能

步骤:
1.初始化一个线程池,并指定线程个数【通常是CPU内核数*2】
2.从线程池中取出线程执行异步任务
在这里插入图片描述
在这里插入图片描述

原生方式创建线程池

创建线程池另一种方式:new ThreadPoolExecutor(7个参数)
/*
	 *  1. corePoolSize,核心线程池的数量,初始化线程池时创建的线程个数
	 *  2. maximumPoolSize,线程池中最多创建多少个线程: 100
	 *  3. keepAliveTime,保持存活的时间,异步任务执行完毕后,等待多长时间销毁多余的线程:1000
	 *  4. unit,单位
	 *  5. workQueue,工作队列/阻塞队列,如果任务有很多,就会将多余的的任务放到队列里面,只要有线程空闲,就会去队列里面取出新的任务执行
	 *     通常使用 LinkedBlockingQueue(无限队列)
	 *  6. threadFactory,创建线程的工厂,采用默认值{Executors.defaultThreadFactory}
	 *  7. handler,阻塞队列满了,按照我们指定的拒绝策略拒绝执行任务
	 */

public class TestThreadPool2 {

	//原生【原始】初始化线程池的方式
	public static ThreadPoolExecutor executor = 
			new ThreadPoolExecutor(
					5,
					100, 
					10, 
					TimeUnit.SECONDS, 
					new LinkedBlockingQueue<>(1000), 
					Executors.defaultThreadFactory(), 
					new ThreadPoolExecutor.AbortPolicy());
	//面试题:	一个线程池core 7:max 20,queue:50, 100并发进来怎么分配?
	//1. 先安排7个线程去执行7个任务,剩下的50个先去排队:
	//2. 然后再创建13个线程,去执行任务
	//3. 剩下的30个线程采用拒绝策略拒绝服务...
	}
  • 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

运行流程:
1、线程池创建,准备好core数量的核心线程,准备接受任务
2、新的任务进来,用core准备好的空闲线程执行。
(1)core满了,就将再进来的人数放入阻塞队列中,空闲的core就会自己去阻塞队列会去任务执行
(2)阻塞对列满了,就直接开新线程执行,最大只能开到max指定的数量
(3)max都执行好了,max-core数量的空闲线程就会在keepAliveTime指定的时间后自动销毁,最终保持到core大小。
(4)如果线程数开到了max的数量,还有新任务进来,就会使用reject指定的拒绝策略进行处理

面试题:
一个线程池core7:max20,queue:50, 100并发进来怎么分配?
答:先有7个线程能直接执行,接下来50个会进入队列排队,在多开13个继续执行。现在70个都安排上了,剩下30个默认拒绝策略。

第 9 章线程安全

9.1线程安全问题

当多个线程操作同一个共享数据时,当一个线程还未结束时,其他的线程参与进去,此时就会导致共享数据的不一致。
例如:
在这里插入图片描述

线程1从账号中取钱时,还未取完时,线程2参与进来,就会造成数据不一致

解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
就好比是在火车上上厕所,进去时,先把门锁上,不用了再打开

Java对于多线程的安全问题提供了专业的解决方式:
同步机制:同步代码块、同步方法、锁
其中同步代码块、同步方法,底层采用是隐式锁的形式:synchronized
其中Lock锁,称为显示锁

9.2synchronized同步代码块

synchronized(对象){
需要同步的代码
}
在这里插入图片描述

懒汉单例模式中,也会出现线程安全的问题
在这里插入图片描述

解决之道:
将可能会出现安全问题的地方,改为同步
在这里插入图片描述

9.3synchronized同步方法

使用synchronized关键字,修饰成员方法,此时调用该方法的线程都采用同步的方式(排队执行)
在这里插入图片描述

区别:
同步代码块比同步方法更加灵活,因为同步方法的话,大家都得排着队

9.4Lock锁

1.从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
2.java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象 加锁,线程开始访问共享资源之前应先获得Lock对象。
3.ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和 内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

class A{
	private final ReentrantLock lock = new ReenTrantLock();
	public void m(){
		lock.lock();
		try{
			//保证线程安全的代码;
		}
		finally{
			lock.unlock();
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Lock和synchronized有以下几点不同:
  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
  2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  5)Lock可以提高多个线程进行读操作的效率。
  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

9.5练习

银行有一个账户。 有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打 印账户余额。
问题:该程序是否有安全问题,如果有,如何解决?

【提示】
1,明确哪些代码是多线程运行代码,须写入run()方法
2,明确什么是共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。

总结:
当多线程操作共享数据时,为了保证数据的一致性,尽量在操作数据部分加上锁
防止一个线程还未结束时,其他线程参与进来
在这里插入图片描述
在这里插入图片描述

第 10 章线程通信

10.1线程通信介绍

线程通信的例子:使用两个线程打印1-100。线程1,线程2交替打印
共享数据,会出现安全问题
wait让当前线程进入阻塞状态,同时会释放锁
涉及到的三个方法:
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行此方法,就会唤醒被wait阻塞的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
notifyAll():一旦执行此方法,就会唤醒所有被wait阻塞的线程。

注意事项:
1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
否则,会出现IllegalMonitorStateException异常
3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。

class Number implements Runnable{
    private int number = 1;
    private Object obj = new Object();
    @Override
    public void run() {

        while(true){

            synchronized (this) {

                notify();

                if(number <= 100){

                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;

                    try {
                        //使得调用如下wait()方法的线程进入阻塞状态
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }else{
                    break;
                }
            }
        }

    }
}


public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);

        t1.setName("线程1");
        t2.setName("线程2");

        t1.start();
        t2.start();
    }
}
  • 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

10.2面试题—wait、sleep异同

相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
不同点:
1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

10.3生产者消费者

线程通信的应用:经典例题:生产者/消费者问题
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店
员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中
没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产
品。

分析:

  1. 是否是多线程问题?是,生产者线程,消费者线程
  2. 是否有共享数据?是,店员(或产品)
  3. 如何解决线程的安全问题?同步机制,有三种方法
  4. 是否涉及线程的通信?是
class Clerk{

    private int productCount = 0;
    //生产产品
    public synchronized void produceProduct() {

        if(productCount < 20){
            productCount++;
            System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");

            notify();

        }else{
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    //消费产品
    public synchronized void consumeProduct() {
        if(productCount > 0){
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
            productCount--;

            notify();
        }else{
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class Producer extends Thread{//生产者

    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ":开始生产产品.....");

        while(true){

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.produceProduct();
        }

    }
}

class Consumer extends Thread{//消费者
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ":开始消费产品.....");

        while(true){

            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.consumeProduct();
        }
    }
}

public class ProductTest {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer p1 = new Producer(clerk);
        p1.setName("生产者1");

        Consumer c1 = new Consumer(clerk);
        c1.setName("消费者1");
        Consumer c2 = new Consumer(clerk);
        c2.setName("消费者2");

        p1.start();
        c1.start();
        c2.start();

    }
}
  • 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
  • 107
  • 108
  • 109
  • 110

第 11 章File类与IO流

11.1File类介绍

java.io.File类,用来描述文件/目录信息的,例如:通过File类可以获取文件名称、大小、修改时间等信息。但是不能访问文件的内容
java.io.File类常用方法:
File(String filename),构造方法,根据参数路径构造文件对象
boolean exists() - 用于判断文件或目录是否存在。
String getName() - 用于获取文件或目录的名称。
long length() - 用于获取文件的大小。
String getAbsolutePath() - 用于获取抽象路径名的绝对路径信息并返回
long lastModified() - 用于获取最后一次修改时间。
boolean delete() - 用于删除调用对象代表的文件或目录。
boolean createNewFile() - 用于创建新的空文件。
File[] listFiles() - 用于获取目录中的所有内容。
boolean isFile() - 用于判断是否为一个文件。
boolean isDirectory() - 用于判断是否为一个目录。
boolean mkdir() - 创建目录

代码演示:

package com.zhentao.file;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TestFile {

	public static void main(String[] args) {
		
		//1. 创建文件对象,和指定的文件进行关联
		File file = new File("C:/Users/Admin/Desktop/abc.txt");
		
		System.out.println(file.exists());	 
		System.out.println(file.getName());  // 获取文件名称
		System.out.println(file.length());   // 获取文件大小,单位:字节
		// 1GB = 1024mb  1MB = 1024KB 	1KB = 1024Byte
		// 一个英文字符 占1个字节,一个中文占2个字节
		System.out.println(file.getAbsolutePath()); // 获取绝对路径
		//绝对路径:唯一的路径,从盘符开始,C:/Users/Admin/Desktop/abc.txt
		//相对路径:从当前目录开始,找目标文件的路径: ./ 当前目录,  ../ 上一层目录 
		System.out.println(file.lastModified()); //最后一次修改时间,时间戳
		
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		Date date = new Date(file.lastModified());
		String time = sdf.format(date);
		System.out.println(time);
		
		boolean res = file.delete();	//删除文件 这里危险,一定要指定某个文件
		System.out.println(res);
		
		try {
			boolean res2 = file.createNewFile();	//创建file指向的文件
			System.out.println(res2);
			
			System.out.println(file.isFile());		//判断是否是一个文件
			System.out.println(file.isDirectory());	//判断是否是一个目录
			
			File file2 = new File("D:/1912A/Java基础/02.07/test");
			//file2.mkdir();	//创建目录
			
			File[] files = file2.listFiles();
			for(int i=0;i<files.length;i++){
				System.out.println(files[i].getName());
			}
			
		} catch (IOException e) {			
			e.printStackTrace();
		}		
		
	}
}
  • 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

11.2练习:

封装一个方法,递归的读取某个目录下面的所有文件名称、文件最后修改时间、文件大小。如果该目录下存在子目录,则读取子目录下面的文件信息。
在这里插入图片描述

11.3IO流介绍

在这里插入图片描述

IO,Input、Output两个单词的缩写
IO流,就像水流一样不间断的进行数据的读写操作
IO流分类:
1.根据数据的流向分为:输入流、输出流
输入流,简称入流,是指将文件中的内容读取到内存中
输出流,简称出流,是指将内存中的数据写入到文件中

2.根据数据单位分为:字节流、字符流
字符流,以字符为单位进行读写操作,主要针对文本文件的操作,一个字符一个字符的读写,例如:字符”a”、字符“中”
字节流,以字节为单位进行读写操作,可以对任何文件进行读写操作
字节,是计算机中最小的单位,通常一个英文字符占1个字节,1个中文字符占2个字节

11.4IO流体系结构

在这里插入图片描述

11.5字节流

什么是字节?
计算机中最小的容量单位
1TB = 1024GB
1GB=1024MB
1MB=1024KB
1KB=1024Byte
1Byte=8位(计算机底层只认识0101这样二进制数据)
1字节 = 0000 0000

所谓的字节流,就是一个字节一个字节的传输,通常用于图片、视频、音频等文件的读写

2.3.1 FileInputStream

java.io.FileInputStream类,用于对图片、视频、音频等文件的读取操作
常用方法:
FileInputStream(String name) - 根据参数指定的路径名来构造对象与之关联。
int read() - 用于从输入流中读取一个字节的数据并返回,若读取到文件尾则返回-1
int read(byte[] b) - 用于从输入流中读满整个参数指定的数组。
- 若读取到文件尾则返回-1,否则返回实际读取到的字节数。
int read(byte[] b, int off, int len) - 读取len个字节到数组b中。
int available() - 用于获取关联文件的大小并返回。
void close() - 关闭输入流并释放资源。

代码演示1:
一次读取一个字节
在这里插入图片描述

代码演示2:
一次性读满一个数组
在这里插入图片描述

代码演示3:
一次性读取指定的字节个数,然后再将读取的字节写入到数组中
int read(byte[] b, int off, int len)
参数1:将读取的字节保存的一个数组
参数2:向数组中写入字节时的偏移量(跳过的元素个数)
参数3:从输入流中读取的长度(字节个数)
在这里插入图片描述

2.3.2 FileOutputStream

java.io.FileOutputStream类,用于对图片、视频、音频文件的写入操作
常用方法
FileOutputStream(String name) - 根据参数指定的路径名来构造对象并关联起来。
FileOutputStream(String name, boolean append) - 以追加的方式构造对象。
void write(int b) - 用于将参数指定的单个字节写入输出流。
void write(byte[] b) - 用于将参数指定的字节数组内容全部写入输出流中。
void write(byte[] b, int off, int len)
void close() - 关闭输出流并释放有关的资源.

代码演示1:
一次性写入一个字节
在这里插入图片描述

代码演示2:
一次性写入一个字节数组
在这里插入图片描述

代码演示3:
void write(byte[] b, int off, int len)
在这里插入图片描述

2.3.3练习

使用3种方式实现文件的拷贝
方式一:
一次性读取一个字节,然后再将读取的字节写入到输出流
不足之处:如果文件较大,该方式效率较低
在这里插入图片描述

方式二:
一次性读满一个数组,再一次性将该数组写入到输出流
在这里插入图片描述

不足:如果文件过大,不能立即在内存中申请足够的空间

方式三:
一次性读1024个字节,再一次性写入1024个字节
如果你的计算机性能较高,可以一次性读10K…等
在这里插入图片描述

2.3.4 ObjectOutputStream

java.io.ObjectOutputStream类用于将对象写入到文件中,
前提是:只支持将实现了java.io.Serializable 接口的对象写入到文件中
一个类通过实现java.io.Serializable接口来启用其序列化功能,所谓的序列化就是将一个对象转换成字节码的过程

代码演示:
将对象写入到文件中
1.类先实现java.io.Serializable接口,来启用序列化功能
在这里插入图片描述

2.通过ObjectOutputStream类将对象写入到文件
在这里插入图片描述

2.3.5 ObjectInputStream

java.io.ObjectInputStream类,用于从一个文件中读取对象的信息
在这里插入图片描述

2.3.6 练习:

自定义Teacher类,实例化3个Teacher对象,并将这3个Teacher对象写入到文件
新建测试类,读取该文件中存储的3个Teacher对象并打印信息
1.自定义Teacher类,并实现java.io.Serializable接口
在这里插入图片描述

2.调用ObjectOutputStream对象的writeObject方法,将对象写入文件
在这里插入图片描述

3.从文件中读取集合对象信息
在这里插入图片描述

2.3.7 练习

大家预习ObjectInputStream、ObjectOutputStream,完成如下需求:
实现IO流版学生信息管理系统

  1. 添加学生时,将学生信息保存到本地文件中

  2. 遍历学生时,从本地文件中读取学生信息再遍历

  3. 修改、删除时也是一样

  4. 学号、年龄增加上异常管理

    提示:
    如果把学生对象写入到文件,学生类就必须实现Serializable接口
    在这里插入图片描述

11.6字符流

字符流,就是一个字符一个字符的传输,不管中文,还是英文,通常用于文本文件的读写。

11.6.1 FileWriter

java.io.FileWriter类,用于向文本文件中写入字符数据
在这里插入图片描述

11.6.2 FileReader

java.io.FileReader类,用于从文本文件中读取字符数据
在这里插入图片描述

11.6.3 BufferedReader

原理:
在这里插入图片描述
在这里插入图片描述

11.6.4 BufferedWriter

在这里插入图片描述

11.7Properties属性集

Properties类,表示一个属性集合,用来对键值对数据(key=value)进行读写操作

常用方法:
setProperty(String key,String value),向集合中添加数据
store,把集合中的数据持久化到文件中
load,把文件中的数据读取到内存中
getProperty(String key),从集合中根据key获取值

练习向文件中写入键值对数据
在这里插入图片描述

练习从文件中读取键值对数据
在这里插入图片描述

练习:
使用Properties类,创建一个属性文件db.properties,有如下内容:
host=localhost
user=root
pass=root
port=3306
然后再使用Properties类读取该属性文件,并输出
在这里插入图片描述

第 12 章Java爬虫项目

12.1Java解析Excel

一个Excel是由下面几个部分组成的:
一个Excel文件对应Workbook(工作簿)
一个Workook里面包括多个Sheet(工作表)
一个Sheet包含行、列
在这里插入图片描述

Java对Excel文件的操作通常使用POI进行的
POI是由Apache软件基金会开源的一个产品,用于对Excel文件进行读写操作

12.2POI 使用步骤

12.2.1 导包

将poi-3.9.jar 导入到java工程中
右击工程名(0211)— New — Source Folder— lib
再将poi-3.9.jar拷贝到lib目录下
右击poi-3.9.jar—Build Path(构建路径)—Add to Build Path(添加到构建路径)
在这里插入图片描述

到此,我们就可以在java工程中使用该jar包提供的工具类

12.2.2 创建excel文件

/**
 * 创建一个Excel文件
*/
public class TestCreateExcel {

	public static void main(String[] args) {
		
		//1. 创建一个Workbook(工作簿)
		HSSFWorkbook workbook = new HSSFWorkbook();
		
		//2. 创建工作表Sheet
		HSSFSheet sheet = workbook.createSheet("Java大数据");
		
		//3. 创建行: 0就表示第一行
		HSSFRow row = sheet.createRow(0);
		
		//4. 创建列
		HSSFCell cell1 = row.createCell(0);	// 第一列
		HSSFCell cell2 = row.createCell(1);	// 第二列
		HSSFCell cell3 = row.createCell(2);	// 第三列
		
		//5. 设置列的内容
		cell1.setCellValue("学号");
		cell2.setCellValue("姓名");
		cell3.setCellValue("年龄");
		
		try {
			//6. 生成一个excel文件
			OutputStream out = new FileOutputStream("d:/student.xls");
			workbook.write(out);
			System.out.println("创建成功");
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
  • 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

12.2.3 练习

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

12.2.4 设置excel的样式

/**
 * 创建一个Excel文件
 *
 */
public class TestCreateExcel3 {

	public static void main(String[] args) {
		
		//1. 创建一个Workbook(工作簿)
		HSSFWorkbook workbook = new HSSFWorkbook();
		
		//2. 创建工作表Sheet
		HSSFSheet sheet = workbook.createSheet("第一次月考成绩");
		
		//定义好字体对象
		Font font = workbook.createFont();
		//设置字体大小
		font.setFontHeightInPoints((short)12);
		//设置字体颜色
		font.setColor(Font.COLOR_RED);
		//设置字体粗细
		font.setBoldweight(Font.BOLDWEIGHT_BOLD);
		
		//创建cellStyle对象,该对象用来设置单元格的样式
		HSSFCellStyle cellStyle = workbook.createCellStyle();
		cellStyle.setFont(font);
		
		//添加水平居中
		cellStyle.setAlignment(CellStyle.ALIGN_CENTER);
		
		//3. 创建行: 0就表示第一行
		//第一行、第一列
		HSSFRow row1 = sheet.createRow(0);
		HSSFCell cell1 = row1.createCell(0);
		cell1.setCellValue("第一次月考成绩");
		
		//再将该cellStyle添加到cell单元格中
		cell1.setCellStyle(cellStyle);
		
		//合并单元格
		CellRangeAddress region = new CellRangeAddress(0, 0, 0, 2);
		sheet.addMergedRegion(region);
				
		// 第二行
		HSSFRow row2 = sheet.createRow(1);
		String[] titles = {"学号","姓名","成绩"};
		for(int i=0;i<titles.length;i++){
			HSSFCell cell = row2.createCell(i);
			cell.setCellValue(titles[i]);
		}
		
		// 再循环创建10行
		for(int i=2;i<13;i++){
			HSSFRow row = sheet.createRow(i);
			
			// 在当前行上创建3列
			HSSFCell cell01 = row.createCell(0);
			cell01.setCellValue(i-1);
			
			HSSFCell cell02 = row.createCell(1);
			cell02.setCellValue("a"+(i-1));
			
			HSSFCell cell03 = row.createCell(2);
			cell03.setCellValue(90);
		}
				
		try {
			//6. 生成一个excel文件
			OutputStream out = new FileOutputStream("d:/student.xls");
			workbook.write(out);
			System.out.println("创建成功");
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
  • 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

12.3Jsoup介绍

jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM方式,CSS选择器以及类似于jQuery的操作方法来取出和操作数据。
jsoup官网:http://jsoup.org

12.3.1 DOM介绍

DOM,Document Object Model,文档对象模型,将HTML标签解析成Java对象的
绘图说明:
在这里插入图片描述

12.3.2 Jsoup快速入门

1.先导包
将jsoup-1.11.3.jar包导入到java工程中
先创建lib目录,将jsoup-1.11.3.jar拷贝到lib目录下
右击jsoup-1.11.3.jar包,Build Path—Add to Build Path

2.快速体验
在这里插入图片描述

12.3.3 Jsoup常用方法

DOM将HTML标签解析成java对象之后,这些对象会在内存中存储,接下来我们要做的就是从内存中查找这些Java对象
getElementById通过id来获取
在这里插入图片描述

getElementsByClass通过class来获取
通过该方法来查找的话,返回值是集合类型,会返回多个元素
在这里插入图片描述

getElementsByTag通过标签名称来获取
在这里插入图片描述

getElementsByAttributeValue(),根据属性值获取元素节点
在这里插入图片描述

get(index), 通过下标从集合中获取元素
text()获取标签的文本,再次强调一下是文本
html()获取标签里面的所有字符串包括html标签
attr(attributeKey)获取属性里面的值,参数是属性名称
Jsoup.connect(url).post(); 连接到指定的html文件,然后将该文件解析成Document文档对象

12.4Java爬虫项目

大家使用Jsoup读取下面文件中所有的岗位信息:
https://search.51job.com/list/010000,000000,0000,00,9,99,java,2,1.html

12.4.1 先爬取招聘网站工作岗位

1.先封装一个工作类Job,用来封装岗位信息的
在这里插入图片描述

2.从HTML文件中读取到岗位信息后,将岗位信息封装到Job对象

public class GetJobs {

	 //定义一个集合
	public List<Job> list = new ArrayList<>();
	
	//封装一个方法,用来将岗位信息封装到list集合中
	public List<Job> getJobs() throws IOException{
		//1. 定义爬取哪个网页上面的岗位信息
		String html = "C:\\Users\\Admin\\Desktop\\51job.html";
		
		//2. 将上面的网页解析成Document对象【DOM树】
		File file = new File(html);		
		Document document = Jsoup.parse(file,"UTF-8");
		
		//3. 查找j_joblist节点
		//By...通过...方式,i go to school by bike
		//Class,类
		//by class,通过类名
		//get 获取,得到
		//Elements,Element的复数形式,多个元素/节点对象
		Elements joblist = document.getElementsByClass("j_joblist");
		
		//从集合中取出joblist,通过下标0
		Element job = joblist.get(0);
		//System.out.println(job);

		//4. 从joblist中取出所有的岗位:class="e"
		Elements es = job.getElementsByClass("e");
		//取出每个岗位,所以需要遍历集合
		for(int i=0;i<es.size();i++){
			Element e = es.get(i);
			//取出每个岗位中的信息【岗位名称、工作地点、发布日期、公司名称】
			String jobname = e.getElementsByClass("jname at").get(0).text();
			String area = e.getElementsByClass("d at").get(0).text();
			String salary =  e.getElementsByClass("sal").get(0).text();
			String company =  e.getElementsByClass("cname at").get(0).text(); 
			String pub =  e.getElementsByClass("time").get(0).text(); 
			
			//将上面的5个零散的变量,封装到一个实体类中
			Job jb = new Job(jobname, area, salary, company, pub);
			list.add(jb);
		}
		return list; // 将list集合返回
	}
  • 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

测试代码
在这里插入图片描述

12.4.2 将工作岗位保存到Excel中

//封装一个方法,将岗位集合存储到Excel中
	//参数1:岗位集合,参数2:目标文件名
	public void saveToExcel(List<Job> list,String file) throws IOException{
		//1. 创建一个工作簿
		HSSFWorkbook workbook = new HSSFWorkbook();
		
		//2. 创建工作表
		HSSFSheet sheet = workbook.createSheet("Java招聘岗位");
				
		//3. 创建第一行
		HSSFRow row = sheet.createRow(0);
		
		//4. 创建第一行的5列
		//设置第1行的5列【标题】
		String[] titles = {"岗位名称","工作地点","薪水","公司名称","发布时间"};
		for(int i=0;i<titles.length;i++){
			HSSFCell cell = row.createCell(i);
			cell.setCellValue(titles[i]);
		}
		
		//5. 循环创建剩余的行,list集合中有多少职位就创建多少行
		for(int i=0;i<list.size();i++){
			//拿到每个岗位
			Job job = list.get(i);
			
			//循环1次创建1行,然后将岗位信息保存到当前行
			HSSFRow r = sheet.createRow(i+1);
			//设置这一行的5列
			HSSFCell first = r.createCell(0);
			first.setCellValue(job.getJobname());
			
			HSSFCell sec = r.createCell(1);
			sec.setCellValue(job.getSalary());
			
			HSSFCell third = r.createCell(2);
			third.setCellValue(job.getArea());
			
			HSSFCell four = r.createCell(3);
			four.setCellValue(job.getPub());
			
			HSSFCell five = r.createCell(4);
			five.setCellValue(job.getCompany());			
		}
				
		//5. 将该该工作簿输出到文件中
		OutputStream out = new FileOutputStream(file);
		workbook.write(out);
		
		//6. 关闭资源
		out.close();
		
	}
  • 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

12.4.5 新建测试类测试

在这里插入图片描述

第 13 章网络编程

13.1网络编程常识

目前主流的网络通讯软件:QQ、微信、阿里旺旺…

七层网络模型:
ISO(国际标准委员会组织)将数据的传递从逻辑上划分为以下七层:
应用层、表示层、会话层、传输层、网络层、数据链路层、物理层

OSI(Open System Interconnect),即开放式系统互联,是ISO(国际标准化组织)在1985年研究的网络互联模型。
OSI七层模型和TCP/IP五层模型的划分如下:
TCP/IP对应用层、表示层、会话层进行了封装,所以包含5层
在这里插入图片描述

当发送数据时,需要对发送的内容按照上述7层模型进行层层加包,再发送出去
当接收数据时,需要对接收的内容按照上述7层模型相反的次序层层拆包解析出来
在这里插入图片描述

13.2网络通信

实现网络通信的两个要素:
IP地址和端口号
网络通信协议

13.2.1 IP地址

IP地址
唯一的表示Internet上的计算机的位置,通过IP地址可以找到对方的计算机。
在这里插入图片描述

DNS,Domain Name System 域名系统,它是将域名和IP地址相互映射的一个分布式数据库,例如:(baidu.com => 220.181.38.148)
本地回环地址,本地计算机内部使用的IP地址,127.0.0.1,主机名(hostName):localhost
IP地址分类:
IPV4、IPV6
IPV4:4个字节(32位)组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽,以点和10进制表示,如:192.168.0.1
IPV6:16个字节( 128位),写成8个无符号整数,每个整数用4个16进制位表示

公网地址、私有地址
公网地址,在万维网上的IP地址,全世界都能访问到的IP地址
私有地址,局域网内使用的IP地址,专门为组织机构内部使用,例如:
192.168开头的就是私有地址
IP地址相关的命令
ping,可以查看网络连接状态
ipconfig,可以查看当前计算机的ip地址

13.2.2 端口号

端口号,用来标识正在计算机上运行的进程(程序)
不同的进程有不同的端口号
被规定为一个0~65535之间的整数
端口号分类:
公认端口:0~1023,被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
注册端口:1024~49151,分配给用户进程或应用程序,如:tomcat占用端口8080,mysql占用端口3306,Oracle占用端口1521
动态/私有端口:49152~65535
IP地址和端口号的组合得出一个网络套接字(插槽):Socket
在这里插入图片描述

13.2.3 网络通信协议

TCP协议,Transmission Control Protocol传输控制协议,是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据。
在TCP协议中必须明确客户端和服务器端,由客户端向服务器端发出请求,每次连接都要经过三次握手,而终止一个连接要经过四次挥手

特点:
使用TCP协议前,须先建立TCP连接,形成传输数据通道
传输前,采用三次握手的方式,点对点通信,是可靠的
TCP协议须明确两个应用进程:客户端、服务端
在此连接中可进行大数据量的传输
传输完毕,须释放已建立的连接,采用四次挥手

  1. 三次握手
    客户端和服务器端通过3次握手,基本确定数据传输成功与失败
    在这里插入图片描述

  2. 四次挥手
    为什么建立一个连接需要三次握手,而终止一个连接要经过四次挥手?
    释放连接时,被动方服务器,突然收到主动方客户端释放连接的请求时并不能立即释放连接,因为还有必要的数据需要处理,所以服务器先返回ACK确认收到报文,经过CLOSE-WAIT阶段准备好释放连接之后,才能返回FIN释放连接报文。
    在这里插入图片描述

13.3基于TCP协议的编程模型

在这里插入图片描述

服务器:
1.创建ServerSocket类型的对象,并提供端口号
2.等待客户端的连接请求,调用accept方法
3.使用输入输出流进行通信
4.关闭socket
在这里插入图片描述

客户端:
1.创建Socket类型的对象,并提供服务器的IP地址和端口号
2.使用输入输出流进行通信
3.关闭Socket

在Java中,提供了两个类用于实现TCP通信程序:
java.net.Socket类,客户端类,用于向服务端发出请求,接收服务端响应
java.net.ServerSocket类,服务端类,相当于开启一个服务,等待客户端连接

13.3.1 Socket客户端程序

java.net.Socket类,TCP通信客户端类,向服务器发送连接请求,给服务器发送数据,读取服务器回写的数据
java.net.Socket类常用的方法如下:

Socket(String host,int port)根据指定主机名和端口号来构造对象
InputStream getInputStream()用于获取当前套接字的输入流
OutputStream getOutputStream()用于获取当前套接字的输出流
void close()用于关闭套接字

实现步骤:
创建一个客户端对象socket,构造方法绑定服务器的IP地址和端口号
使用socket对象中的方法getOutputStream(),获取网络字节输出流OutputStream对象
使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
释放资源(close)

说明:当我们创建客户端对象socket时,就会自动完成3次握手
在这里插入图片描述

13.3.2 ServerSocket服务端程序

ServerSocket,TCP通信服务端类,接收客户端的请求,读取客户端发送的数据,给客户端回写数据
服务端实现步骤:
创建一个服务端对象ServerSocket
通过ServerSocket对象的accept方法,获取到请求的客户端对象Socket
使用socket对象的getInputStream()方法获取网络字节输入流InputStream对象
通过网络字节输入流InputStream对象的read方法,读取客户端发送的数据
通过socket对象的getOutputStream获取网络字节输出流OutputStream对象
使用网络字节输出流OutputStream对象的write方法,给客户端回写数据
释放资源

public class SocketServer {
	public static void main(String[] args) {		
		try {
			//1. 创建一个服务端对象ServerSocket,启动服务
			ServerSocket server = new ServerSocket(8888);
			
			//2. 通过ServerSocket对象的accept方法,获取到请求的客户端对象Socket
			Socket socket = server.accept();
			
			//3. 使用socket对象的getInputStream()方法获取网络字节输入流InputStream对象
			InputStream in = socket.getInputStream();
			
			//4. 通过网络字节输入流InputStream对象的read方法,读取客户端发送的数据
			int res = 0;
			while((res = in.read())!=-1 ){
				System.out.println((char)res);
			}
			
			//5. 通过socket对象的getOutputStream获取网络字节输出流OutputStream对象
			OutputStream out = socket.getOutputStream();
			
			//6. 使用网络字节输出流OutputStream对象的write方法,给客户端回写数据
			out.write("i received".getBytes());
			
			//7. 释放资源
			socket.close();
			server.close();
			in.close();
			out.close();			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
  • 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

13.4多线程聊天室

13.4.1 客户端和服务端的连接

1.创建服务器端

public class Server {
	public static void main(String[] args) {
		
		try {
			//1. 创建ServerSocket对象,并提供端口号
			ServerSocket server = new ServerSocket(8888);
			
			//2. 等待客户端的连接请求,调用accept方法
			Socket socket = server.accept();
			
			//3. 开始通信(通过输入、输出流)
			
			//4. 关闭socket
			server.close();
			socket.close();
			
		} catch (IOException e) {			
			e.printStackTrace();
		}		
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

2.创建客户端

public class Client {

	public static void main(String[] args) {
		
		try {
			//1. 创建Socket类型的对象并提供服务器的IP地址和端口号
			Socket socket = new Socket("192.168.2.112", 8888);
			System.out.println("连接服务器成功");
			
			//2. 使用输入输出流进行通信
						
			//3. 关闭socket
			socket.close();
			
		} catch (Exception e) {			
			e.printStackTrace();
		}		
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

13.4.2 客户端向服务器发送数据

客户端发送消息

public class Client {

	public static void main(String[] args) {
		
		try {
			//1. 创建Socket类型的对象并提供服务器的IP地址和端口号
			Socket socket = new Socket("192.168.2.112", 8888);
			System.out.println("连接服务器成功");
			
			//2. 使用输入输出流进行通信
			PrintStream ps = new PrintStream(socket.getOutputStream());
			ps.println("在吗?");
			
			//3. 关闭socket
			socket.close();
			ps.close();
			
		} catch (Exception e) {			
			e.printStackTrace();
		}		
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

服务器读

public class Server {

	public static void main(String[] args) {
		
		try {
			//1. 创建ServerSocket对象,并提供端口号
			ServerSocket server = new ServerSocket(8888);
			
			//2. 等待客户端的连接请求,调用accept方法
			Socket socket = server.accept();
			
			//3. 开始通信(通过输入、输出流)
			// 接收客户端发送过来的消息,使用BufferedReader类
			BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			String content = reader.readLine();
			System.out.println("客户端发送过来的消息是:"+content);
			
			//4. 关闭socket
			server.close();
			socket.close();
			
		} catch (IOException e) {			
			e.printStackTrace();
		}		
	}
}
  • 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

13.4.3 客户端向服务器发送的内容由键盘输入

public class Client {

	public static void main(String[] args) {
		
		try {
			//1. 创建Socket类型的对象并提供服务器的IP地址和端口号
			Socket socket = new Socket("192.168.2.112", 8888);
			System.out.println("连接服务器成功");
			
			//2. 使用输入输出流进行通信
			PrintStream ps = new PrintStream(socket.getOutputStream());
			
			//客户端向服务器发送的内容由用户键盘输入
			System.out.println("请输入要发送的内容");
			Scanner sc = new Scanner(System.in);
			String msg = sc.next();
			
			ps.println(msg);
			System.out.println("客户端发送数据成功");
			
			//3. 关闭socket
			socket.close();
			ps.close();
			sc.close();
			
		} catch (Exception e) {			
			e.printStackTrace();
		}		
	}
}
  • 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

13.4.4 实现服务器向客户端回应消息

在这里插入图片描述

实现客户端对服务器回发消息的接收并打印
在这里插入图片描述

13.4.5 客户端不断跟服务器发送内容,直到发送bye,聊天结束

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

13.4.6 服务器端多线程

现在服务器只有一个main方法,既要接收客户端的请求,又要给客户端响应
采用多线程方式进行优化:
1.主线程接收客户端请求
2.创建子线程给客户端进行响应
Server.java

public class Server {
	public static void main(String[] args) {
		
		try {
			//1. 创建ServerSocket对象,并提供端口号
			ServerSocket server = new ServerSocket(8888);
			List<Socket> list = new ArrayList<Socket>();

			while(true){
				//2. 等待客户端的连接请求,调用accept方法
				System.out.println("等待客户端的连接请求");
				
				//没有客户端连接时,会阻塞在accept()这里
				Socket socket = server.accept();
				list.add(socket);
				
				System.out.println("客户端连接成功");
				
				//只要有客户端连接成功,此时就需要启动新的线程为之服务
				new ServerThread(socket,list).start();
			}			
			
		} catch (IOException e) {			
			e.printStackTrace();
		}		
	}
}
  • 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

创建多线程文件ServerThread.java

public class ServerThread extends Thread {
	private Socket socket;	
	private List<Socket> list;
	
	public ServerThread(Socket socket,List<Socket> list) {
		this.socket = socket;
		this.list = list;
	}

	@Override
	public void run() {

		//3. 开始通信(通过输入、输出流)
		// 接收客户端发送过来的消息,使用BufferedReader类
		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			
			while(true){
				String content = reader.readLine();
				System.out.println("客户端"+socket.getInetAddress()+"发送过来的消息是:" + content);
				
				//服务器向客户端发送消息
				//获取输出流对象
				if("bye".equals(content)){
					System.out.println("客户端"+socket.getInetAddress()+"已下线");
					break;
				}
				for(Socket s:list){
					if(s != socket){
						PrintStream ps = new PrintStream(s.getOutputStream());
						ps.println(content);
					}
				}
				System.out.println("服务器发送数据成功");
			}
			//4. 关闭socket		
			socket.close();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
  • 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

13.4.7 客户端多线程

创建ClientSendThread线程用来发送消息
创建ClientReceiveThread线程用来接收消息
ClientSendThread.java

public class ClientSendThread extends Thread {
	
	private Socket socket;
	public ClientSendThread(Socket socket){
		this.socket = socket;
	}

	@Override
	public void run() {
		try{
			PrintStream ps = new PrintStream(socket.getOutputStream());
		
			Scanner sc = new Scanner(System.in);		
				
			while(sc.next() != null){
				//客户端向服务器发送的内容由用户键盘输入
				System.out.println("请输入要发送的内容");
				
				String msg = sc.next();				
				ps.println(msg);								
			}
			ps.close();
			
		} catch (Exception e) {			
			e.printStackTrace();
		}	
	}
}
  • 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

创建ClientReceiveThread.java

public class ClientReceiveThread extends Thread {
	
	private Socket socket;
	public ClientReceiveThread(Socket socket){
		this.socket = socket;
	}

	@Override
	public void run() {

		try{
			BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			
			while(reader.readLine() != null){

				String content = reader.readLine();
				System.out.println(content);

				if("bye".equals(content)){
					System.out.println("聊天结束!");
					break;
				}
			}			
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}
  • 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

创建Client.java

public class Client {
	public static void main(String[] args) {
		
		try {
			//1. 创建Socket类型的对象并提供服务器的IP地址和端口号
			Socket socket = new Socket("192.168.2.112", 8888);
			System.out.println("连接服务器成功");
			
			//2. 使用输入输出流进行通信
			new ClientReceiveThread(socket).start();
			new ClientSendThread(socket).start();
		} catch (Exception e) {			
			e.printStackTrace();
		}		
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

第 14 章反射机制

14.1动态编程

通常情况下,编写的代码是固定的,运行结果也是固定的,如:
Person p = new Person(); 只能构造Person对象
p.show(); 只能调用show方法

有些情况,编写代码时,不确定会创建什么对象,也不确定会调用什么样的方法,而是由运行时传入的参数决定,这种编程方式称为动态编程。而我们今天要学习的反射机制就是动态的创建对象,动态的调用方法的机制
很多框架底层都是用动态编程实现的,在Java里面可以使用反射技术实现动态编程

14.2类的加载与ClassLoader的理解

在这里插入图片描述

编译期,将Java源文件也就是敲好的代码通过编译,转换成.class文件,也就是字节码文件(byte)
运行期,类装载器初始化字节码文件,通过本地类库来验证字节码文件的正确性,然后交给JVM的解释器和即时编译器,最后汇合给JVM内部的Java运行系统。
ClassLoader类加载器
将.class文件的字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。

14.3反射技术

类加载完毕后,会在堆内存的方法区创建一个Class类型的对象class。
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。
class对象包含了原始类的内部结构(成员变量、成员方法、构造方法),它就像一面镜子能够看到类的内部结构
所谓的反射技术,就是通过class对象获取原始类的构造方法、成员方法等,然后再构造对象

14.4如何获取class对象

可以通过4种方法获取class对象

  1. 类名.class; 例如:Student.class获取Student类的class对象
  2. 对象.getClass();例如:student.getClass(); 获取student对象的class对象
  3. 包装类.TYPE,例如:Integer.TYPE,获取Integer的class对象
  4. Class.forName(“类的全路径”),根据全路径获取该类的class对象

在这里插入图片描述

14.5class对象的功能

14.5.1 动态获取构造方法

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

14.5.2 动态获取成员变量

在这里插入图片描述

14.5.3 动态获取成员方法

在这里插入图片描述

14.6反射经典案例

在src目录下,创建一个配置文件:data.properties
在这里插入图片描述

根据该文件中定义的类,方法,动态的创建对象,并调用方法

  1. 通过IO流读取配置文件中的内容
  2. 根据从文件中读取的内容,动态创建对象,动态调用方法
public class TestClass4 {

	public static void main(String[] args) {
		
		try {
			//1.通过IO流的方式获取类的全路径
			Properties pro = new Properties();
			
			//创建文件输入流:移植性差
			//Reader reader = new FileReader("E:\\workspace\\java01\\0316\\src\\data.properties");
			
			//通过相对路径来读取文件的内容(通过ClassLoader这个类加载器)
			ClassLoader loader = TestClass4.class.getClassLoader();
			InputStream in = loader.getResourceAsStream("data.properties");
			
			pro.load(in);
						
			//从pro集合中读取cname这个键对应的值
			String cname = pro.getProperty("cname");
			String m = pro.getProperty("method");
			String param = pro.getProperty("param");
			
			System.out.println(cname); // com.wanshi.reflect.Student
			System.out.println(m);	   // dance
			
			//2. 使用反射技术,动态创建对象,动态调用方法
			Class clazz = Class.forName(cname);
			Constructor constructor = clazz.getDeclaredConstructor();
			
			//根据构造方法创建对象
			Object obj = constructor.newInstance();
			
			//调用这个对象的方法: 获取有参数的成员方法
			Method method = clazz.getDeclaredMethod(m, String.class);
			
			//调用obj这个对象的method这个方法,参数为:机械舞
			method.invoke(obj, param);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
}
  • 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

第 15 章枚举类、注解

15.1枚举类vs常量

类的对象只有有限个,确定的。举例如下:
星期类:Monday(星期一)、…、Sunday(星期天)
性别类:Man(男)、Woman(女)
季节类:Spring(春节)…Winter(冬天)
支付方式类:Cash(现金)、WeChatPay(微信)、Alipay(支付宝)、BankCard(银 行卡)、CreditCard(信用卡)
就职状态类:Busy、Free、Vocation、Dimission
订单状态类:Nonpayment(未付款)、Paid(已付款)、Delivered(已发货)、 Return(退货)、Checked(已确认)Fulfilled(已配货)

可以使用常量或枚举类
我们以Season类为例:

public class Season {
	public static final int SEASON_SPRING = 1;
    public static final int SEASON_SUMMER = 2;
    public static final int SEASON_FALL = 3;
    public static final int SEASON_WINTER = 4;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

枚举类更加直观,类型安全。使用常量会有缺陷:
  若一个方法中要求传入季节这个参数,用常量的话,形参就是int类型,开发者传入任意类型的int类型值就行,但是如果是枚举类型的话,就只能传入枚举类中包含的对象。
所以,当需要定义一组常量时,强烈建议使用枚举类

15.2枚举类介绍

JDK1.5之前需要自定义枚举类
JDK 1.5 新增的 enum 关键字用于定义枚举类
若枚举只有一个对象, 则可以作为一种单例模式的实现方式。

  1. 私有化类的构造器,保证不能在类的外部创建其对象
  2. 在类的内部创建枚举类的实例。声明为:public static final
  3. 对象如果有实例变量,应该声明为private final,并在构造器中初始化

15.3enum定义枚举类

使用Enum定义枚举类:
在这里插入图片描述

新建测试类进行测试
在这里插入图片描述

15.4练习

  1. 自定义一个季节类,提供的季节有4种:春天、夏天、秋天、冬天,然后新建测试类,并访问4种季节名称
    在这里插入图片描述

  2. 自定义支付方式类,提供的支付方式有4种:微信、支付宝、银行卡、现金,然后新建测试类访问到4种支付方式
    在这里插入图片描述

  3. 自定义订单状态类,提供的订单状态有6种:待支付、已支付、待出库、已发货、已收货、交易关闭,然后新建测试类并访问6种订单状态

15.5注解介绍

注释,是给程序员看的,对代码的说明,代码运行时不会执行注释的内容
注解,Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。
注解可以修饰类、成员变量、成员方法,注解都是以@符号开头的
在JavaSE阶段,注解功能比较简单,在JavaEE/Android中比较重要,可以代替冗余的代码,繁琐的XML配置文件
未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,注解是一种趋势,一定程度上可以说:框架 = 注解 + 反射 + 设计模式。

15.6常见的注解

示例一:生成文档相关的注解
@author 标明开发该类模块的作者,多个作者之间使用,分割
@version 标明该类模块的版本
@param 对方法中某参数的说明,如果没有参数就不能写
@return 对方法返回值的说明,如果方法的返回值类型是void就不能写
在这里插入图片描述

示例二:在编译时进行格式检查(JDK内置的三个基本注解)
@Override: 限定重写父类方法, 该注解只能用于方法
@SuppressWarnings: 抑制编译器警告
@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。
在这里插入图片描述
在这里插入图片描述

15.7自定义注解

  1. 使用@interface关键字自定义注解
    语法格式:
public @interface 注解名称{
	注解里面的成员变量是以无参方法的形式保存,例如:
	String className();
	String methodName();
}
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述

  1. 使用自定义的注解?
    @MyAnnotation(className=”com.wanshi.exam.MySQLDB”,methodName=”insert”)
    public class TestAnnotation{

}

15.8利用反射读取注解信息

在这里插入图片描述

//注解的作用:保存一些数据,程序运行时,可以从注解中获取数据,执行相应的处理
@MyAnnotation(className="com.wanshi.exam.MySQLDB",methodName="insert")
public class TestAnnotation {

	public static void main(String[] args) throws Exception {
		//1.先读取注解中的内容:com.wanshi.exam.MySQLDB和insert
		//1.1 获取当前类的class对象【镜子,保存了类的内部结构:成员变量、成员方法、构造方法】
		Class<TestAnnotation> clazz = TestAnnotation.class;
		
		//1.2 通过class对象获取当前类的注解
		MyAnnotation anno = clazz.getAnnotation(MyAnnotation.class);
		
		//1.3获取注解中的内容
		String cname = anno.className(); // com.wanshi.exam.MySQLDB
		String m = anno.methodName();	 // insert
		
		//2. 根据类的路径创建对象,然后调用方法
		//2.1 Class.forName()获取com.wanshi.exam.MySQLDB这个类的class对象
		Class c = Class.forName(cname);
		
		//2.2 获取构造方法
		Constructor con = c.getDeclaredConstructor(String.class);
		
		//2.3获取成员方法
		Method method = c.getMethod(m, String.class);
		
		//2.4根据构造方法实例化对象
		Object obj = con.newInstance("阿猫");
				
		//2/5调用对象的方法
		method.invoke(obj, "香蕉");
	}
}
  • 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

第 16 章设计模式

16.1设计模式的6大原则

设计模式(Design Pattern)是前辈们对代码开发经验的总结,反复使用、多数人知晓,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。

16.1.1 开-闭原则

ocp,open-close principle,开闭原则
程序开发完毕,对扩展是开放的,对修改是关闭的
如果允许修改的话,万一出了问题,整个项目都会受影响
在这里插入图片描述

如果需要给Beauty增加一个age属性,按照ocp原则,不应该直接修改
而应该再声明一个新类,声明age属性,再继承该新类
在这里插入图片描述
在这里插入图片描述

16.1.2 里氏代换原则

任何基类可以出现的地方,子类一定可以出现,里氏代换原则是对“开-闭”原则的补充。
因为使用继承,必须保证 子类 is a 父类,建议:尽量多使用继承和多态的形式实现通用编程
在这里插入图片描述

16.1.3 依赖倒转原则

尽量依赖抽象类或接口,而不是依赖具体的实现类
例如:下面的代码就把依赖具体的Dog类,转换为依赖一个Animal
在这里插入图片描述

16.1.4 接口隔离原则

尽量依赖于多个小接口,而不是依赖一个大接口
例如:

public interface Animal{
	public void run();  // 跑
	public void fly();	  // 飞
	public void eat();	  // 吃
}
  • 1
  • 2
  • 3
  • 4
  • 5

因为一旦实现了一个接口,必须得实现该接口的所有抽象方法

public class Pig implements Animal{
	public void run();  // 跑
	public void fly();	  // 飞
	public void eat();	  // 吃
}
  • 1
  • 2
  • 3
  • 4
  • 5

此时,Pig类只需要实现run、eat方法,该如何实现呢
将大接口拆分为多个小接口,接口与接口之间可以继承
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

16.1.5 迪米特法则

迪米特法则,最少知道原则
一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
专业术语:高内聚、低耦合

高内聚,把一个特点的功能提取到一个类内部(内聚)
低耦合,尽量减少类与类之间的依赖关系

尽量减少类与类之间的耦合,因为万一有一个类出现了点问题,和他关联的类都要受影响
在这里插入图片描述

16.1.6 合成复用原则

如果两个类不是父子类的时候,如何相互调用这两个类之间的方法
例如:
有一个A类

public class A{
	public void show(){
}
}
  • 1
  • 2
  • 3
  • 4

定义一个B类,没有继承A类,能否使用A类中的方法呢?

public class B{
	// 合成复用原则
	private A a;
	public B(A a){
		this.a = a;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

//使用a对象的方法

public void say(){
	a.show();
}
}
  • 1
  • 2
  • 3
  • 4

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

16.2常见设计模式

Java中常见的设计模式有23种,可以分为3大类:
总体来说设计模式分为三大类:

  1. 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  2. 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  3. 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

16.2.1 普通工厂模式

新建一个工厂类,对实现了某一个接口的实现类进行对象创建的模式
在这里插入图片描述

代码实现普通工厂模式
1.先定义Sender接口
在这里插入图片描述

2.定义该接口下面的2个实现类
在这里插入图片描述
在这里插入图片描述

3.通过工厂类构造对象
就类似于厂子,传递原材料进去,返回成品
在这里插入图片描述

测试一下:
在这里插入图片描述

总结:
普通工厂模式,根据传递的字符串创建对应的对象,如果字符串写错了,就找不到对象

16.2.2 工厂方法模式

是对普通工厂方法模式的改进,在普通工厂模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
在这里插入图片描述

代码实现
1.在工厂类中增加2个方法,分别创建Sms、Mail对象
在这里插入图片描述

2.分别调用2个方法实现发送短信、邮件
在这里插入图片描述

16.2.3 静态工厂方法模式

静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
在这里插入图片描述
在这里插入图片描述

16.2.4 抽象工厂模式

工厂方法模式的图解:
在这里插入图片描述

工厂方法模式有一个问题就是,如果想要拓展程序,必须对工厂类进行修改,这违背了开闭原则,所以,从设计角度考虑,有一定的问题。
如何解决?
采用抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。因为抽象工厂不太好理解,我们先看看图,然后结合代码,就比较容易理解。
在这里插入图片描述

1.根据Sender接口,创建发送邮件、发送短信的实现类
在这里插入图片描述
在这里插入图片描述

2.提供Provider接口,该接口中有一个produce生产的方法
在这里插入图片描述
在这里插入图片描述

3.测试
创建发送Mail的工厂,由该工厂生产一个MailSender对象
在这里插入图片描述

4 如果将来需要发送包裹了,只需要创建一个包裹工厂类、发送包裹的方法
在这里插入图片描述

创建发送包裹的工厂对象
在这里插入图片描述

测试一把
在这里插入图片描述

16.3 练习

完成多个工厂方法模式、抽象工厂模式,并进行比较抽象工厂模式的优点
在这里插入图片描述

16.4 单例模式

10分钟,手写一个单例设计模式的类出来
并解决多线程操作时的安全问题
在这里插入图片描述
在这里插入图片描述

第 17 章Java8新特性

17.1Java8新特性介绍

Java7(JDK1.7),2011年发布
Java8(JDK1.8),2014年发布,是自Java5以来最具革命性的版本,目前主流的版本就是Java8。

Java8新特性主要体现在:
速度更快
代码更少(Lambda表达式)
强大的Stream流
便于并行
最大化减少空指针异常:Optional

17.2Lambda表达式

Lambda表达式让代码更简洁
语法格式1:
在这里插入图片描述

语法格式2:
//1. ()里面的参数的数据类型可以自动推断得出,所以可以省略
自动推断类型,例如:List list = new ArrayList<>();
//2. 如果{}里面只有一条语句,{}和return都可以省略
//3. 如果()里面只有一个参数,()也可以省略
在这里插入图片描述

17.3函数式接口

17.3.1 函数式接口介绍

函数式接口,也是Java8新增的特性之一
只包含一个抽象方法的接口称为函数式接口,可以通过Lambda表达式创建该接口的实现类对象。
在函数式接口中,使用@FunctionalInterface注解检测该接口是否是函数式接口

快速入门案例:
在这里插入图片描述

17.3.2 函数式接口和Lambda表达式的关系

在Java8中,Lambda表达式就是一个函数式接口的实例,也就是说只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
在这里插入图片描述

Java从诞生起一直倡导”一切皆对象”,在Java里面面向对象编程(OOP)是一切。
但是随着Python、Scala、ES6等语言的兴起,和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,也即:Java不但可以支持OOP还可以支持OOF(面向函数编程)。

17.3.3 Java8内置的函数式接口

在这里插入图片描述

Consumer,消费者,消费型接口,accept(T) 传递参数但是无返回值
Supplier,供应商,供给型接口,get() 不需要传参,但是有返回值
Function,函数,函数型接口,apply(T),对传递的参数进行改造
Predicate,断言、断定,断定型接口,test(T) 断定传递的参数是否满足约束
在这里插入图片描述

Predicate函数式接口练习
内置的唯一的test(T)方法,用来断定传递的参数是否满足约束
在这里插入图片描述
在这里插入图片描述

17.4方法引用

方法引用可以看做是Lambda表达式深层次的表达。
引用,就是通过一个方法名字来指向 一个方法。

语法格式:

17.4.1 对象::实例方法

表示引用对象的实例方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

17.4.2 类名::静态方法

表示引用类的静态方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

17.5强大的Stream流

17.5.1 Stream流介绍

Stream流,作用在于像流水线一样对数据进行处理
在这里插入图片描述
在这里插入图片描述

代码演示:
需求:
有一个集合,需要先从该集合中遍历所有姓张的成员,存到新的集合中,然后再遍历姓张的成员
此时就可以使用Stream流
传统的实现方式:
在这里插入图片描述

17.5.2 Stream快速入门案例

在这里插入图片描述

17.5.3 获取Stream的2种方式

  1. 所有collection集合都可以通过stream()方法获取
  2. Stream接口的静态方法of
    在这里插入图片描述
    在这里插入图片描述

17.5.4 Stream流常用API

filter(Predicate p),过滤
forEach(Consumer con),进行遍历操作
map(Function f),对元素进行加工处理
count(),统计元素数量
limit(),限制获取的数量
skip(),跳过
static concat(),合并多个流称为一个流

这些API可以分为两类:
1.返回值为Stream流的,接着调用其他方法,称为链式调用!
2.返回值不是Stream流的,不能调用stream流的其他方法,不能链式调用!
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/661649
推荐阅读
相关标签
  

闽ICP备14008679号