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

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多态效果

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

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

如果子类重写了父类的方法,静态方法调用父类的,非静态的方法调用子类的

如果子类重写了父类的方法,静态方法调用父类的,非静态的方法调用子类的

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

CPU个数即CPU芯片个数

CPU的核心数是指物理上,也就是硬件上存在着几个核心。 比如,双核就是包括2个相对独立的CPU核心单元组,四核就包含4个相对独立的CPU核心单元组。

CPU线程数是一种逻辑的概念,简单地说,就是模拟出的CPU核心数。比如,可以通过一个CPU核心数模拟出2线程的CPU,也就是说,这个单核心的CPU被模拟成了一个类似双核心CPU的功能。 一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

7.1.7 并发、并行

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

7.2多线程优点

提高应用程序的响应,增强用户体验。

提高计算机系统CPU的利用率

改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和 修改

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个线程采用拒绝策略拒绝服务...

}

创建线程池另一种方式: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、线程池创建,准备好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();

}

}

}

class A{

private final ReentrantLock lock = new ReenTrantLock();

public void m(){

lock.lock();

try{

//保证线程安全的代码;

}

finally{

lock.unlock();

}

}

}

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();

}

}

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();

}

}

10.2面试题—wait、sleep异同

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

10.3生产者消费者

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

分析:

是否是多线程问题?是,生产者线程,消费者线程

是否有共享数据?是,店员(或产品)

如何解决线程的安全问题?同步机制,有三种方法

是否涉及线程的通信?是

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();

}

}

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();

}

}

第 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

System.out.println(files[i].getName());

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

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

System.out.println(files[i].getName());

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

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流版学生信息管理系统

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

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

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

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

修改、删除时也是一样

修改、删除时也是一样

学号、年龄增加上异常管理 提示: 如果把学生对象写入到文件,学生类就必须实现Serializable接口

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

提示: 如果把学生对象写入到文件,学生类就必须实现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();

}

}

}

/**

* 创建一个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();

}

}

}

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

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();

}

}

}

/**

* 创建一个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

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();

}

}

}

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 list = new ArrayList<>();

//封装一个方法,用来将岗位信息封装到list集合中

public List 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

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集合返回

}

public class GetJobs {

//定义一个集合

public List list = new ArrayList<>();

//封装一个方法,用来将岗位信息封装到list集合中

public List 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

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集合返回

}

测试代码

12.4.2 将工作岗位保存到Excel中

//封装一个方法,将岗位集合存储到Excel中

//参数1:岗位集合,参数2:目标文件名

public void saveToExcel(List 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

HSSFCell cell = row.createCell(i);

cell.setCellValue(titles[i]);

}

//5. 循环创建剩余的行,list集合中有多少职位就创建多少行

for(int i=0;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();

}

//封装一个方法,将岗位集合存储到Excel中

//参数1:岗位集合,参数2:目标文件名

public void saveToExcel(List 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

HSSFCell cell = row.createCell(i);

cell.setCellValue(titles[i]);

}

//5. 循环创建剩余的行,list集合中有多少职位就创建多少行

for(int i=0;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();

}

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协议须明确两个应用进程:客户端、服务端 ?在此连接中可进行大数据量的传输 ?传输完毕,须释放已建立的连接,采用四次挥手

三次握手 客户端和服务器端通过3次握手,基本确定数据传输成功与失败

三次握手 客户端和服务器端通过3次握手,基本确定数据传输成功与失败

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

四次挥手 为什么建立一个连接需要三次握手,而终止一个连接要经过四次挥手? 释放连接时,被动方服务器,突然收到主动方客户端释放连接的请求时并不能立即释放连接,因为还有必要的数据需要处理,所以服务器先返回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,构造方法绑定服务器的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();

}

}

}

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();

}

}

}

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();

}

}

}

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();

}

}

}

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();

}

}

}

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();

}

}

}

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();

}

}

}

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();

}

}

}

服务器读

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();

}

}

}

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();

}

}

}

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();

}

}

}

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();

}

}

}

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 list = new ArrayList();

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();

}

}

}

public class Server {

public static void main(String[] args) {

try {

//1. 创建ServerSocket对象,并提供端口号

ServerSocket server = new ServerSocket(8888);

List list = new ArrayList();

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();

}

}

}

创建多线程文件ServerThread.java

public class ServerThread extends Thread {

private Socket socket;

private List list;

public ServerThread(Socket socket,List 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();

}

}

}

public class ServerThread extends Thread {

private Socket socket;

private List list;

public ServerThread(Socket socket,List 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();

}

}

}

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();

}

}

}

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();

}

}

}

创建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();

}

}

}

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();

}

}

}

创建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();

}

}

}

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();

}

}

}

第 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对象

类名.class; 例如:Student.class获取Student类的class对象

对象.getClass();例如:student.getClass(); 获取student对象的class对象

包装类.TYPE,例如:Integer.TYPE,获取Integer的class对象

Class.forName(“类的全路径”),根据全路径获取该类的class对象

14.5class对象的功能

14.5.1 动态获取构造方法

14.5.2 动态获取成员变量

14.5.3 动态获取成员方法

14.6反射经典案例

在src目录下,创建一个配置文件:data.properties

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

通过IO流读取配置文件中的内容

根据从文件中读取的内容,动态创建对象,动态调用方法

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();

}

}

}

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();

}

}

}

第 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;

}

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;

}

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

15.2枚举类介绍

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

私有化类的构造器,保证不能在类的外部创建其对象

在类的内部创建枚举类的实例。声明为:public static final

对象如果有实例变量,应该声明为private final,并在构造器中初始化

15.3enum定义枚举类

使用Enum定义枚举类:

新建测试类进行测试

15.4练习

自定义一个季节类,提供的季节有4种:春天、夏天、秋天、冬天,然后新建测试类,并访问4种季节名称

自定义一个季节类,提供的季节有4种:春天、夏天、秋天、冬天,然后新建测试类,并访问4种季节名称

自定义支付方式类,提供的支付方式有4种:微信、支付宝、银行卡、现金,然后新建测试类访问到4种支付方式

自定义支付方式类,提供的支付方式有4种:微信、支付宝、银行卡、现金,然后新建测试类访问到4种支付方式

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

自定义订单状态类,提供的订单状态有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自定义注解

使用@interface关键字自定义注解 语法格式:

public @interface 注解名称{

注解里面的成员变量是以无参方法的形式保存,例如:

String className();

String methodName();

}

public @interface 注解名称{

注解里面的成员变量是以无参方法的形式保存,例如:

String className();

String methodName();

}

使用自定义的注解? @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 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, "香蕉");

}

}

//注解的作用:保存一些数据,程序运行时,可以从注解中获取数据,执行相应的处理

@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 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, "香蕉");

}

}

第 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(); // 吃

}

public interface Animal{

public void run(); // 跑

public void fly(); // 飞

public void eat(); // 吃

}

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

public class Pig implements Animal{

public void run(); // 跑

public void fly(); // 飞

public void eat(); // 吃

}

public class Pig implements Animal{

public void run(); // 跑

public void fly(); // 飞

public void eat(); // 吃

}

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

16.1.5 迪米特法则

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

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

尽量减少类与类之间的耦合,因为万一有一个类出现了点问题,和他关联的类都要受影响

16.1.6 合成复用原则

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

public class A{

public void show(){

}

}

public class A{

public void show(){

}

}

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

public class B{

// 合成复用原则

private A a;

public B(A a){

this.a = a;

}

public class B{

// 合成复用原则

private A a;

public B(A a){

this.a = a;

}

//使用a对象的方法

public void say(){

a.show();

}

}

public void say(){

a.show();

}

}

16.2常见设计模式

Java中常见的设计模式有23种,可以分为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种方式

所有collection集合都可以通过stream()方法获取

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流的其他方法,不能链式调用!

相关推荐

结婚送日子书写范文
日博365体育

结婚送日子书写范文

🕒 10-12 👀 3878
经纬度和米的换算(度差与距离换算)
365bet体育在线主页

经纬度和米的换算(度差与距离换算)

🕒 06-29 👀 3743