第 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、线程池创建,准备好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();}}
}

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();@Overridepublic 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),如果生产者试图生产更多的产品,店
员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中
没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产
品。

分析:

  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;}@Overridepublic 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;}@Overridepublic 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<files.length;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流版学生信息管理系统

  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. 创建工作表SheetHSSFSheet 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. 创建工作表SheetHSSFSheet 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();}}
}

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,通过下标0Element 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集合返回}

测试代码

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

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方法,获取到请求的客户端对象SocketSocket 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. 关闭socketserver.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. 关闭socketsocket.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. 关闭socketsocket.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. 关闭socketserver.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. 关闭socketsocket.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<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();}       }
}

创建多线程文件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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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();}       }
}

第 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.StudentSystem.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;
}

枚举类更加直观,类型安全。使用常量会有缺陷:
  若一个方法中要求传入季节这个参数,用常量的话,形参就是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. 使用自定义的注解?
    @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.MySQLDBString 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 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(){
}
}

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

public class B{// 合成复用原则private A a;public B(A a){this.a = a;
}

//使用a对象的方法

public void say(){a.show();
}
}



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

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

  1. Java基础看这篇就足够用了(基础知识汇总)

    文章目录 第一章.java环境搭建 1.1.Java介绍 1.1.1. Java开发平台 1.1.2. Java开发环境搭建 1.1.3 .Java专业术语 1.1.4. 第一个Java程序 1.1. ...

  2. 图解 Kafka,看本篇就足够啦

    Kafka 是主流的消息流系统,其中的概念还是比较多的,下面通过图示的方式来梳理一下 Kafka 的核心概念,以便在我们的头脑中有一个清晰的认识. 基础 Kafka 是一套流处理系统,可以让后端服务轻 ...

  3. java 后端校验_如何实现Java后端数据校验?看这篇就足够!

    前言 每次我们在搭建一个开源项目的首要任务包括:项目的统一异常处理.统一结果封装以及做项目的数据校验,在前后端分离的情况下,不仅前端需要做数据校验,同样后端也要实现,前端主要使用一些类似与jQuery ...

  4. 掌握 Kafka,看这篇就足够了

    Apache Kafka 是一个快速.可扩展的.高吞吐的.可容错的分布式"发布-订阅"消息系统, 使用 Scala 与 Java 语言编写,能够将消息从一个端点传递到另一个端点.较 ...

  5. Flink面试,看这篇就足够了

    概述 2019 年是大数据实时计算领域最不平凡的一年,2019 年 1 月阿里巴巴 Blink (内部的 Flink 分支版本)开源,大数据领域一夜间从 Spark 独步天下走向了两强争霸的时代.Fl ...

  6. spring版本 jdk8_从JDK8升级到JDK11,看这篇就足够了

    原文地址:https://blog.codefx.org/java/java-11-migration-guide/. 在原文的基础上,增加了一些我遇到的具体的坑还有在特定场景下的解决方案,供大家参考 ...

  7. 深入理解JVM虚拟机13:JVM面试题,看这篇就足够了(87题详解)

    1.java中会存在内存泄漏吗,请简单描述. 会.自己实现堆载的数据结构时有可能会出现内存泄露,可参看effective java. 2.64 位 JVM 中,int 的长度是多数? Java 中,in ...

  8. JVM面试题,看这篇就足够了(87题详解)

    文章目录 1.java中会存在内存泄漏吗,请简单描述. 2.64 位 JVM 中,int 的长度是多数? 3.Serial 与 Parallel GC 之间的不同之处? 4.32 位和 64 位的 J ...

  9. 简历中的项目到底该如何写?面试中该如何介绍项目?看这篇就足够了

    简历中的那些项目 原文 前言 前几天有人私信问我,说项目这块没啥亮眼的地方,感觉面试官没啥可问的,就直接让他写算法了.后来看群里的问题有很多很相似,那么我抽时间和几位大佬聊了下,在他们面试候选人的过程 ...

  10. 全网PyCharm的使用教程看这篇就足够啦

    Jetbrains 家族和 Pycharm 版本划分: pycharm 是 Jetbrains 家族中的一个明星产品,Jetbrains开发了许多好用的编辑器 包括: Java 编辑器(Intelli ...

最新文章

  1. 清除Squid缓存的小工具
  2. Java项目:家庭财务管理系统(java+Springboot+ssm+mysql+maven)
  3. 23 种设计模式实战 pdf(很全)
  4. 近乎于“神”的任正非
  5. mysql导入100000000需要多久_MYSQL批量插入千万级数据只需百秒
  6. 医学科研如何快速掌握R语言?
  7. ITK:演示可用的阈值算法
  8. android sd大小,android用户的SD卡相同容量是否完全一样的大小
  9. 使用 ref 对已渲染到页面的节点进行标记
  10. httpd Server not started: (13)Permission denied: make_sock: could not bind to address [::]:88
  11. push declined due to email privacy restrictions (GH007 error code) 解决方法
  12. 9012 年了,Array 数组的方法赶紧用起来!
  13. PCL中采样一致性算法
  14. Tiptop CR报表axcr700采购入库月报增加tlf99“多角贸易序号”,部分资料无数据修复4gl文件bug
  15. ansible模块authorized_key
  16. 2022讯飞——糖尿病遗传风险检测挑战赛解决方案
  17. 算法----火柴拼正方形
  18. docker: error pulling image configuration
  19. Python实现线性回归(公式推导+源代码)
  20. (转)颈椎病自我治疗体操

热门文章

  1. 【MySQL】数据库的函数使用
  2. 阿里巴巴线上面试总结
  3. VLAN链路类型和接口类型
  4. 我在 GitHub 上发现了一个 狗屁不通 的Python开源项目...
  5. 是时候抛弃 Svelte、React 和 VUE 了吗?
  6. shell编程三大神器之sed
  7. clickhouse ARRAY JOIN函数
  8. 美团笔试题(5)考试策略
  9. 谷歌学术镜像mark
  10. unit系统与linux系统区别,python+unittet在linux与windows使用的区别