javase
JAVASE_STUDY
0703 一 基础
1.1 JDK JRE JVM关系
(jdk安装环境不带中文和空格)
1.2 JAVA编译过程
源程序(.java) -> 编译器 -> java字节码文件(.class) -> JVM运行
public class HelloWorld {
public static void main(String[] args){
System.out.println("Hello,World!");
}
}
cmd
D:/>javac HelloWorld.java
<- 编译生成.class文件
D:/>java HelloWorld
<- 运行
1.3 关键字、标识符
标识符:26个字母、10个数字、美元符号$和下划线_ (不以数字开头,不能是关键字)
命名规则:
- 类名:首字母大写,后面每个单词首字母大写(HelloWorld)
- 变量名:首字母小写,后面每个字母首字母大写(helloWorld)
- 方法名:同变量名
1.4 常量与变量
常量:
字符串常量:用双引号引起来的(”abc”)
整数常量:(2)
浮点数常量:(2.5)
字符常量:单引号(’a’、’中’)
布尔常量:(true、false)
空常量:(null)
变量:
- 创建:数据类型 变量名称 = 数据值;
- 注意事项:名称不重复;对于float或者long,字母后缀F和L不能丢
1.5 基本数据类型和引用数据类型
基本数据类型(4类8种)
整数型:byte(1字节)、short(2字节)、int(4字节)、long(8字节)
浮点型:float(4字节)、double(近似值,非精确值)
字符型:char
布尔型:boolean
注:并不是字节越大表示的数字范围越大,float就比long类型表示的数字范围大
引用数据类型
- 字符串
- 数组
- 类
- 接口
- Lambda
注:对于引用类型,==是地址值的比较,对于基本类型,==是数值的比较
1.6 数据类型转换
自动类型转换(隐式)
强制类型转换(显式):范围小的类型 变量名 = (范围小的类型)范围大的数据 -> int num = (int) 100L;
精度损失、数据溢出
byte、short、char它们之间进行加减乘除的时候会自动转换为int类型
1
2
3
4
5
6
7byte b1 = 40;
byte b2 = 40;
short s1 = 50;
char c1 = 'A'
int i1 = b1 + b2;
int i2 = b1 + s1;
int i3 = b1 + c1; // =1051.7 ASCII编码
(American Standard Code for Information Interchange,美国信息交换标准代码)
码表:http://c.biancheng.net/c/ascii/
Unicode码表:万国码。数字和符号的对照关系,开头0-127和ASCII一样,从128开始有更多的字符
1.8 运算符
- 算数运算符:+、-、*、/、%(取模)、++、—
注:前++是先+1再被使用,后++是先使用再+1
赋值运算符号:基本赋值运算符(=)、复合赋值运算符(+=、-=、*=、/=、%=)
注:复合赋值运算符含有强制转换
比较运算符:==、>、<、>=、<=、!=
逻辑运算符:&&、||、!
注:&&和||具有短路效应,若左边可以得到判断结果,则不执行右边,具有节省性能的作用
- 三元运算符: 变量类型 = 条件判断 ? 表达式A : 表达式B;(true执行A,false执行B)
1.9 方法
方法的定义:
1 | public static void 方法名称(){ |
方法的调用:
方法名称(参数)
1.10 Jshell
java 9更新
cmd -> 输入jshell即可
0704 二 结构及方法
2.1 顺序结构
2.2 选择结构
if 语句
1
2
3
4
5
6
7if(关系表达式1){
语句体1;
}else if(关系表达式2){
语句体2;
}else{
语句体3;
}
- switch语句
1 | switch(表达式){ |
switch后面小括号当中只能是下列数据类型:
基本数据类型:byte/short/char/int
引用数据类型:String字符串、enum枚举
2.3 循环结构
- for循环
1 | for(初始化语句;条件判断;步进语句){ |
- while循环
1 | 初始化语句; |
- do-while循环
1 | 初始化语句; |
三者的区别:
do-while 至少会执行一次,前两者可能执行0次就跳出;
for 循环的变量在括号内定义,出了循环不能使用,后两者出了循环还可以使用;
确定次数一般用for,否则一般用while,很少用do-while;
2.4 循环控制
- break 直接结束整个循环
- continue 立刻跳过当前循环,进行下一次循环
2.5 IDEA
- 项目结构:Project(项目)—->Module(模块)—->Package(包)
- 一些快捷键:
- alt+/ :自动补全代码
- alt+enter:自动修复代码
- ctrl+Y:删一行
- ctrl+D:加一行
- ctrl+alt+L:格式化
- ctrl+/:注释一行
- Debug
- f8:逐行执行
- f7:进入方法中
- shift+f8:跳出方法
- f9跳到下一个断点
- ctrl + f2:退出debug
2.6 方法定义及调用及方法的重载
1 | 修饰符 返回值类型 方法名称(参数类型 参数名称, ...){ |
return的作用:停止当前方法;将返回值还给调用处
方法的三种调用方法:
- 单独调用:方法名(参数)
- 打印调用:
- 赋值调用:数据类型 变量名称 = 方法名称(参数);
方法重载的情况
- 方法的参数个数不同
- 方法的参数类型不同
- 参数的多类型顺序不同
- 注:方法重载与参数名称无关;与返回值类型无关
0705 三 数组、变量
3.1 数组简介
数组是一种容器,可以同时存放多个数据值
特点:是一种引用数据类型;数组之中的多个数据类型必须一致;数组的长度在程序运行期间不可改变。
数组的初始化
- 动态初始化(指定长度)
1
数据类型[] 数组名称 = new 数据类型[数组长度];
- 静态初始化(指定内容)
1
2数据类型[] 数组名称 = new 数据类型[]{元素1, 元素2, ...};
数据类型[] 数组名称 = {元素1, 元素2, ...};//也可以省略一些数组的访问:数组名称[索引]; <—从0开始访问
3.2 JAVA内存划分
划分为5个部分:
栈(stack):存放方法的局部变量,一旦出了作用域立刻从栈中消失
堆(Heap):凡是new出来的都在堆内存中,堆内存中都有一个地址值(16进制)
若为整数默认为0;浮点数默认为0.0;字符默认为’/u0000’;布尔默认为false;引用类型默认为null
方法区(Method Area):储存.class相关信息,包含方法的信息
本地方法栈(Native Method Stack):与操作系统相关
寄存器(pc Register):与CPU相关
一个数组的内存图
- 两个引用指向同一个数组
3.3 数组常见问题
- 索引越界 —> ArrayIndexOutOfBoundsException
- 空指针异常 —> NullPointException —> 创建数组的时候补上new
3.4 数组常用
获取数组长度 :
数组名称.length
数组一但创建,长度不可改变遍历数组:
1
2
3for(int i = 0;i < array.length;i++){
数组操作
}idea里面可以直接array.fori
3.5 面向对象的思想
类与对象:对象是类的实体,类是对象的模板
类的定义:
1
2
3
4public class ClassName{
//成员变量
//类方法,成员方法没有static
}注:成员变量直接定义在类当中,在方法外面;
成员方法不要写static关键字(加了static后作用域只能用于代码本身了)。
一个对象的内存图
两个引用指向一个对象的内存图
使用对象类型作为方法的参数
注意:当对象作为参数输入的时候,传递的是一个地址值,所以在方法内修改
使用对象类型作为方法的返回值
注意:当一个对象作为方法的返回值的时候,返回值其实是对象的地址值
3.6 局部变量和成员变量
- 定义位置不一样。局部变量在方法内部定义,成员变量在外部定义;
- 作用范围不一样。局部变量出了方法就不能用了,成员变量在整个类通用;
- 默认值不一样。局部变量无默认值,成员变量有,规则和数组一样;
- 内存位置不一样。局部变量在栈中,成员变量在堆中;
- 生命周期不一样。局部变量随着方法进栈诞生,随着方法出栈消失,成员变量随着对象创建诞生,随着对象被垃圾回收而消失。
3.7 面向对象的三大特性
- 封装:方法就是一种封装;private也是一种封装
- 继承:继承是多态的前提,没有继承就没有多态
- 多态
3.8 private和this关键字
使用了private修饰,本类之外就不能直接访问了(定义getset方法可以间接访问)
当方法的局部变量和类的成员变量重名的时候,根据就就近原则,优先使用局部变量
所以当要访问本类中的成员变量,需要使用this关键字:
this.成员变量名
注:谁调用方法,谁就是this
3.9 构造方法
用于创建对象,当使用new创建对象的时候,就是在调用构造方法
public 类名称(参数类型 参数名称){方法体}
注:
- 构造方法名称必须和所在的类名称完全一样包括大小写
- 无返回值,也不写void
- 构造方法不能return
- 如果没有构造方法,则编译器会自动生成一个构造方法,没有参数什么也不做
- 一旦编写了至少一个构造方法,则编译器不自动生成构造方法
- 构造方法可以进行重载
3.10 定义一个标准的类
- 所用成员变量都用private修饰
- 为每一个成员变量编写一对getter/setter方法
- 编写一个无参数构造方法
- 编写一个全参数构造方法
这样一个标准的类也叫Java Bean
0706 四 对象、字符串
4.1 API
- Application Programming Interface
- 引用类的步骤:导包->创建->使用 同一个包下不需要导包,java.lang目录下也不需要导包
- Scanner练习:day0705/Scanner_test.java:键盘输入两数字求和
4.2 匿名对象
- 匿名对象就是只有右边的对象,没有左边的名字和赋值运算符
1 | new Person().name = "jack" //标准的是 Person p = new Person(),然后再调用.name()方法 |
- 匿名对象只能使用唯一的一次,下次再用不得不再创建一个新的对象
- 使用建议:如果确定有一个对象只需要使用唯一的一次,就可以创建匿名对象
- 匿名对象作为方法参数和返回值
1 | method(new Scanner(System.in));//System.in为InputStream类 |
4.3 Random类
4.4 对象数组与ArrayList
- ArrayList的长度可以改变而数组长度不能改变
- ArrayList有一个
表示泛型,泛型只能是引用类型,不能是基本类型(因为基本数据类型没有地址值),如果要用到基本数据类型,可以用它对应的包装类 - 对于ArrayList打印的是内容而不是地址值
- 常用方法:
1 | public boolean add(E e); |
- ArrayList练习:day0705/ArrayList_test.java
4.5 字符串
字符串特点:
- 字符串内容永不可改变(一些看起来修改了字符串的方法其实是相当于创建了一个新的字符串)
- 正因为不可改变,所以字符串是可以共享的
- 字符串效果上相当于是char[]字符数组,但是底层原理是byte[]字节数组
字符串的创建(常见3+1种)
1
2
3
4
5
6String str = new String();//创建空白字符串,不含内容
char[] array1 = {'A', 'B', 'C'};
byte[] array2 = {97, 98, 99};
String str1 = new String(char[] array1);//根据字符数组创建对应的字符串
String str2 = new String(byte[] array2);//根据字节数组的内容创建对应字符串-->是abc
String s = "hello";//直接创建,虽然没有new,但是它也是字符串对象字符串的常量池:程序中直接写上的双引号字符串,就在字符串常量池中。(在堆中)
- 对于引用类型,==是地址值的比较,对于基本类型,==是数值的比较
字符串的比较相关方法
==是对地址值的比较,如果要进行内容的比较,用
public boolean equals(Object obj)
推荐
"abc".equals(str)
而不是str.equals("abc")
(常量放前面变量放后面防止空指针异常)public int length()
public String concat(String str)
拼接public char charAt(int index)
获取指定索引位置的单个字符串public int indexOf(String str)
查找字符串首次出现的索引位置,若没有则返回-1public String subString(int index)
;public String subStirng(int begin, int end)
截取index之后的字符串以及[begin,end)的字符串
字符串的相关转换方法
public char[] toCharArray()
将当前字符串拆分为字符数组作为返回值public byte[] getBytes()
获得当前字符串底层的字节数组(IO流中用较多)public String replace(CharSequence oldString, CharSequence newString)
将所有出现的老字符串替换为新字符串,返回替换之后的结果新字符串
字符串的分割
public String[] split(String regex)
按照参数的规则,将字符串切分为若干部分- 注:split方法的参数其实是一个正则表达式,若要按照英文的”.”分,应该写”//.”
4.5 static关键字
一旦使用了static关键字,那么这样的内容不再属于对象自己,而属于类,凡是本类的对象,都共享同一份
static修饰成员变量
- 多个对象共享,如果有一个对象改变了这个变量,则全部对象的这个变量都会改变
static修饰方法,一旦修饰了就为静态方法
如果没有static关键字,必须创建对象然后通过对象使用
对于静态方法来说,可以通过对象名调用(不推荐),也可以直接通过类名来调用
1
2
3Myclass obj = new Myclass();
obj.method.Static();//通过对象名调用方法(编译之后也会被javac翻译为类名.静态方法)
Myclass.methodStatic();//直接通过类名来调用方法无论是成员变量还是成员方法,若有了static,都推荐使用类名称调用
对于本类的静态方法,可以省略类名称
注意:
- 静态不能直接访问非静态。(原因:在内存中先有静态,后有的非静态)
- 静态方法不能使用this关键字。(因为静态是直接通过类名调用的)
静态static的内存图
静态代码块
1
2
3
4
5public class 类名称(){
static{
//静态代码块内容
}
}- 当第一次用到本类的时候,静态代码块执行唯一的一次
- 由于静态总是优先于非静态执行,所以静态代码块比构造方法先执行
- 典型用途:一次性对静态成员变量进行赋值
4.6 数组工具类 Arrays
- java.util.Arrays是一个与数组相关的工具类,里面提供了大量的静态方法,用于实现数组的常见操作
public static String toString(数组)
:将参数变为字符串(按默认格式:[元素1,元素2,元素3,…])public static void sort(数组)
:升序排列- 如果是数字,升序
- 如果是字符串,默认字母升序
- 如果是自定义类型,则这个自定义类型需要有Comparable或者Comparator接口的支持
- Arrays练习:day0706/Arrays_test.java:对随机字符串进行升序排序并倒序打印
4.7 Math类
常见方法
public static double abs(double num)
绝对值public static double ceil(double num)
向上取整(floor为向下取整)public static long round(double num)
四舍五入Math.PI
- Math练习:day0706/Math_test.java:计算两个数之间的整数个数
0707 五 继承、抽象、接口、多态
5.1 继承
共性抽取,父类也叫基类、超类,子类也可以叫派生类
定义父类:
1
2
3public class 父类名称(){
}定义子类:
1
2
3public class 子类名称 extends 父类名称(){
}继承中成员变量的特点:在父子类继承关系中,如果成员变量重名,则创建子类对象的时候,有两种访问方法:
- 直接通过子类对象访问(等号左边是谁则优先是谁,没有则向上找——优先子类的变量)
- 间接通过成员方法访问(方法属于谁,就优先用谁,没有则向上找——优先本类的变量)
区分子类方法中重名的三种变量
- 局部变量:直接写
- 本类的成员变量:this.成员变量名
- 父类的成员变量:super.成员变量名
继承中成员方法的访问特点:
在父子类继承关系中,创建子类对象,访问成员方法的规则:创建的对象是谁,就优先用谁,没有则向上找
继承中方法的重写(覆盖/覆写):继承中方法的名称一样,参数列表也一样
重写(override):方法名称、参数列表都一样;重载(Overload):方法名称一样、参数列表不一样
创建的子类对象,则优先用子类的方法
注意事项:
必须保证父子类方法的名称相同,参数列表相同。
@Override
写在方法前面,用来检验是不是有效的正确覆盖重写(也可以不写)子类方法的返回值必须小于等于父类方法的返回值范围(比如父类为object,子类可以为String)
子类方法的权限必须大于等于父类方法的权限修饰符
public > protected > (default) > private (default代表什么都不写)
设计原则:对于已经使用的类,尽量不去修改,而是写一个新类,即继承
继承中构造方法的访问特点:
子类构造方法中有一个默认的super()调用,所以一定是先调用父类的构造,再调用子类的构造
子类构造方法必须调用父类的构造方法,不写则默认super()
子类构造可以通过super()关键字来调用父类重载构造
super的父类构造调用,必须是子类构造方法的第一个语句
继承的三大特点:
- 单继承:只有一个父类
- 多级继承
- 一个父类可以有多个子类
5.2 super、this关键字
super关键字
- 在子类的成员方法中,访问父类的成员变量
- 在子类的成员方法中,访问父类的成员方法
- 在子类的构造方法中,方法父类的构造方法
this关键字
- 在本类的成员方法中,访问本类的成员变量
- 在本类的成员方法中,访问本类另一个成员方法(不能是构造方法!)
- 在本类的构造方法中,访问本类的另一个构造方法(此时this的调用也必须是构造方法的第一个语句,所以super和this不能同时使用)
super、this图解
5.3 抽象
抽象方法:加上abstract关键字,然后去掉大括号,直接分号结束
抽象方法所在的类必须是抽象类
1
2
3
4
5
6public abstract class Animal{//抽象类定义
public abstract void eat();//抽象方法定义
public void normalMethod(){
}
}如何使用抽象方法:
- 不能直接创建抽象类对象
- 必须用一个子类来继承抽象父类
- 覆盖重写(实现):去掉abstract关键字,然后补上大括号
- 创建子类进行使用
注意事项:
- 抽象类不能直接创建对象
- 抽象类中可以有构造方法,初是提供子类创建对象时,始化父类成员使用的
- 抽象类中,不一定有抽象方法,但是有抽象方法的类必定是抽象类
- 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则编译无法通过,否则该子类也是抽象类,也要用abstract修饰
抽象练习:day0707/MainRedPacket.java:发红包案例
5.4 接口
接口:一种公共的规范标准,引用数据类型,最重要的内容就是其中的抽象方法
接口的定义:
1
2
3public interface 接口名称{
//接口内容
}注:换成了关键字interface之后,编译生成的字节码文件仍然是:.java —-> .class
接口可以包含:
- 常量(java 7)
- 抽象方法(java 7)
- 默认方法(java 8)
- 静态方法(java 8)
- 私有方法(java 9)
接口的抽象方法定义
1
public abstract 返回值类型 方法名称(参数列表);
注:修饰符必须是 public和abstract,这两者可以省略但不能改变
接口的使用:接口不能直接使用,必须有一个“实现类”来“实现”:
1
2
3public class 实现类名称 implements 接口名称{
//...
}注:接口中的抽象方法必须全部覆盖重写,创建实现类对象再使用
若没有覆盖全部的抽象方法,则该类也必须为抽象方法
接口默认方法的定义与使用
1
2
3public default 返回值类型 方法名称(参数列表){
方法体;
}注:接口中的默认方法可以解决接口升级的问题(当接口中新加了抽象方法后,之前定义的实现类还需要 一一更改,默认方法可以防止这样的情况产生)
- 接口的默认方法可以通过接口的实现类直接调用
- 接口的默认方法也可以被接口的实现类覆盖重写
接口静态方法的定义与使用
1
2
3public static 返回值类 方法名称(参数列表){
方法体;
}注:
- 不能通过接口实现类的对象来调用接口当中的静态方法
- 正确用法:通过接口的名称直接调用静态方法,格式:
接口名称.静态方法名(参数)
接口私有方法的定义和使用
- 普通私有方法:解决多个默认方法之间的重复代码问题
1
2
3private 返回值类型 方法名称(参数列表){
方法体;
}- 静态私有方法:解决多个静态方法之间的重复代码问题
1
2
3private static 返回值类型 方法名称(参数列表){
方法体;
}接口常量的定义和使用
定义:
public static final 数据类型 常量名称 = 数据值;
注:
- 可以省略
public static final
但是不写效果也一样 - 必须进行赋值,且不可改
- 常量名称全用大写
- 可以省略
接口使用注意事项:
- 接口没有静态代码块或者构造方法
- 一个类的直接父类是唯一的,但是一个类可以同时实现多个接口
5.5 继承父类并实现多个接口
- 格式:
1 | public class 类名称 extends 父名称 implements 接口1,接口2{ |
- 注:
- 如果多个接口中出现了同名的抽象方法,则只需要重写一个就好了
- 如果实现类没有覆盖重写所有接口的所有抽象方法,则该类必须是抽象类
- 如果实现类所实现的多个接口中,存在重复的默认方法,则该实现类一定要对冲突的默认方法进行重写
- 一个类如果直接父类当中的方法和接口中的默认方法产生冲突,则优先用父类当中的方法
5.6 接口之间的多继承
类与类单继承的,直接父类就一个;
类与接口之间是多实现的,一个类可以实现多个接口;
接口与接口之间是多继承的
多个父接口的方法重复,没关系
多个父接口的默认方法重复,那么子接口必须进行默认方法的覆盖重写,而且要带着default关键字
5.7 多态
- 指一个对象拥有多种形态
- 多态存在的三个必要条件:
- 继承
- 重写
- 父类引用指向子类对象
实现:extends或者implements实现是多态的前提
代码当中体现多态性:父类引用指向子类对象 格式:
父类名称 对象名 = new 子类名称();
(一只猫被当作动物来看待)或者
接口名称 对象名 = new 实现类名称();
1
2Fu obj = new Zi();
obj.Method();//优先使用Zi中的Method(new什么优先用哪个),子类没有则向上找<----前提:子类和父类都有Method这个方法,如果子类特有的方法则不能这样写,需要向下转型多态中成员变量的使用:
- 成员变量通过对象名称直接访问:看等号左边是谁,优先用谁,没有则向上找
1
2Fu obj = new Zi();
obj.num;//优先使用Fu中的num,没有则向上找- 成员变量通过成员方法间接访问:看该方法属于谁,优先用谁
1
2Fu obj = new Zi();
obj.shownum();//优先使用Zi中的shownum()方法,没有则向上找多态中成员方法的使用规则:
看new的是谁,则使用谁,没有则向上找,但是这个方法必须是子类和父类都有,若要使用子类特有的方法则需要向下转型.
在这个引用变量f指向的对象中,他的成员变量和静态方法与父类是一致的,他的非静态方法,在编译时是与父类一致的,运行时却与子类一致(发生了复写)(编译看左,运行看右)
就是说,方法和成员变量都用当作父类来用,如果子类有复写的方法则用子类的方法
成员变量:编译看左边,运行还看左边。
成员方法:编译看左边,运行看右边
多态的好处
多态的弊端:无法使用子类特有的方法和属性
5.8 对象的转型
向上转型:即多态的写法
父类名称 对象名 = new 子类名称();
含义:右侧创建一个子类对象,当作父类来使用(一只猫被当作动物来看待)
注:
- 向上转型一定是安全的(小范围转向了大范围)
向下转型:其实是一个还原的动作
子类名称 对象名 = (子类名称)父类对象
(将猫从动物还原为猫)1
2Animal animal = new Cat();
Cat cat = (Cat)animal;注:
- 必须保证原来本来创建的时候是猫,才能向下转为猫
- 如果原来创建的时候不是猫,向下转为狗则为报错 ———-> ClassCastException
用instanceof关键字进行类型判断
对象 instanceof 类名称
(返回boolean)
5.9 接口多态练习
接口多态练习:day0707/Multi_Interface:笔记本USB接口案例
0708 六
6.1 final关键字
用法:
修饰类
public final class 类名{}
- 不能有子类,所有的成员方法无法进行覆盖重写
修饰方法
public final 返回值类型 方法名(){}
- 该方法不能被覆盖重写
修饰局部变量
final 变量类型 变量名;
- 一次赋值,终生不变(如果为引用类型则是名字指向的地址值不变)
修饰成员变量
public final 变量名 = 值;
- 由于成员变量会有默认值,所有一旦使用了final修饰,就必须手动赋值
- 对于final成员变量,要么直接赋值,要么通过构造方法赋值(只能使用一种)
- 若使用第二种方法,必须保证类中所有重载的构造方法会对final成员变量赋值
对于类和方法来说,fianl和abstract不能同时使用
6.2 四种权限修饰符
PUBLIC | protected | (default) | private | |
---|---|---|---|---|
同一个类 | 1 | 1 | 1 | 1 |
同一个包 | 1 | 1 | 1 | 0 |
不同包子类 | 1 | 1 | 0 | 0 |
不同包非子类 | 1 | 0 | 0 | 0 |
6.3 内部类
一个事物内部包含另一个事物
成员内部类
定义格式
1
2
3
4
5
6修饰符 class 外部类名称{
修饰符 class 内部类{
//...
}
//...
}注:内用外随意,外用内需要内部类对象
成员内部类的使用
- 间接方式:在外部类中的方法中,使用内部类;然后main只是调用外部类方法
- 直接方式:
外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
内部类访问同名变量
外部类名称.this.外部类成员变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class Outer{
int num = 10;
public class Inner{
int num = 20;
public void methodInner(){
int num = 30;
System.out.println(num);//30
System.out.println(this.num);//20
System.out.println(Outer.this.num);//10
}
}
}
局部内部类(包含匿名内部类)(在方法里面)
定义格式
1
2
3
4
5
6
7
8修饰符 class 外部类名称{
修饰符 返回值类型 外部类方法名称(参数列表){
class 局部内部类名称{
//...
}
}
//...
}局部内部类的final问题
如果希望访问所在方法的局部变量,那么这个变量必须是有效final的
1
2
3
4
5
6
7
8
9
10
11
12public class MyOuter{
public void menthodOuter(){
final int num = 10;//final可以不写,但要保证num不变
class MyInnter{
public void methodinter(){
sout(num);
}
}
}
}注:从java 8开始,只要局部变量事实不变,那么final关键字可以省略
原因:
- new出来的对象在堆中
- 局部变量跟着方法走,在栈中
- 方法运行结束后立刻出栈,局部变量立刻消失
- 但是new出来的对象在堆中持续存在直到垃圾回收
定义一个类的时候,权限修饰符
- 外部类:public/(default)
- 成员内部类:所有
- 局部内部类:什么都不能写(和default不一样)
匿名内部类:如果接口的实现类(或者是父类的子类,只需要使用唯一的一次,那么这种情况下就可以省略掉该类的定义,而改为使用匿名内部类
定义格式:
```java
接口名称 对象名 = new 接口名称(){//覆盖重写所有的抽象方法;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
```java
public class test {
public static void main(String[] args) {
MyInterface my = new MyInterface() {
@Override
public void method() {
System.out.println("匿名内部类");
}
};//单独使用匿名内部类
my.method();
}
}1
2
3
4
5
6
7
8
9
10public class test {
public static void main(String[] args) {
new MyInterface() {
public void method() {
System.out.println("匿名内部类");
}
}.method();//匿名内部类和匿名对象一起使用
}
}
注意:匿名内部类在创建对象的时候,只使用唯一的一次
匿名对象在调用方法的时候,只能使用一次
匿名内部类是省略了实现类/子类名称,匿名对象是省略了对象名称
6.4 类作为成员变量类型
6.5 接口作为成员变量类型、方法的参数或返回值
1 | public class DEMO { |
0711 七 常用类
7.1 Object类
java.lang.Object
Object是类层次结构的根(父)类
每个类都使用Object作为父类
所有对象都实现这个类的方法
toString()方法
1
2
3public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}equals()方法
1
2
3public boolean equals(Object obj) {
return (this == obj);//谁调用了equals方法,谁就是this
}基本数据类型比较的是值,引用数据类型比较的是地址
重写eqals方法,问题:
隐含一个多态,无法使用子类特有的内容(方法和属性)
Object ob = new Person("张三", 19);
解决方法:使用向下转型,将Object转为Person
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public boolean equals(Object obj) {
if(obj == this){
return true;//提高效率
}
if(obj == null){
return false;//提高效率
}
//Person p = (Person)obj;//直接转换有风险
if(obj instanceof Person){
Person p = (Person)obj;
boolean b = this.name.eauqls(p.name) && this.age.equals(p.age);
return b;
}else{
return false;
}
}
7.2 Date类
java.util.Date:表示日期和时间
时间原点:1970.1.1 00:00:00
DateFormat:格式化的日期的抽象类(抽象类),作用:格式化和解析日期
DateFormat为抽象类,无法直接创建,需要使用它的子类
SimpleDateFormat的构造方法:
`SimpleDateFormat(String pattern)
`1
2
3
4SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
Date date = new date();
String d = sdf.format(date);
sout(d);
7.3 Calendar类
java.util.Calendar类不能直接创建使用(抽象类),里面提供了很多操作日历字段的方法
里面有一个静态方法叫getInstance(),该方法返回了一个Calendar类的子类对象
static Calendar getInstance()
1 | Calendar c = Calendar.getInstance();//多态 |
常用方法
1 | public int get(int field);//返回给定的日历字段值 |
7.4 System类
(java.lang包下无需导包,直接使用)
1 |
|
7.5 StringBuilder的原理
java.lang.StringBuilder
在内存中始终是一个数组,占用空间少,效率高,如果超出了StringBuilder的容量,会自动扩容
构造方法:
StringBuilder()
:构造一个不带任何字符的字符串生成器,初始容量为16字符StringBuilder(String str)
方法
public StringBuilder append()
:添加数据,返回的是this,即调用对象的地址,所以以后调用的时候直接.append()使用即可链式编程:方法返回值是一个对象,可以继续调用
public String toString()
:将当前的StringBuilder对象转换为String对象
7.6 包装类
基本数据类型使用很方便,但是没有对应的方法去操作这些基本数据类,可以使用一个类把基本数据类型装起来,通过其中的一些方法去操作这些数据
装箱与拆箱:基本数据类型和包装类的转换
自动拆箱与装箱:自动的转换
Integer in = 1; in = in + 2;//相当于in = in.intValue() + 2
字符串的转换
7.7 Collection集合
java.uitl.Collection
共性的方法
1
2
3
4
5
6
7public boolean() add(E e);
public void clear();
public boolean remove(E e);
public boolean contains(E e);
public boolean isEmpty();
public int size();
public Object[] toArray();//把集合中的元素储存到数组中
7.8 Iterator迭代器
java.uitl.Iterator
(接口)对集合进行遍历
常用方法
1
2boolean hasNext() //如果仍有元素可以迭代,则返回true
E next() //返回迭代的下一个元素Iterator迭代器是一个接口,不能直接使用,需要使用Iterator接口实现的对象,获取实现类的方式比较特殊
Collection接口中有一个方法:
iterator()
返回的就是迭代器Iterator<E> iterator
返回在此collection的元素上进行迭代的迭代器
使用步骤
- 使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
- 使用Iterator接口中的方法hasNext判断还有没有元素
- 使用Iterator接口中的方法next取出集合的下一个元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Collection<String> coll = new ArrayList<>();
coll.add("张三");
coll.add("李四");
coll.add("王五");
coll.add("赵六");
Iterator<String> it = coll.iterator();
while(it.hasNext()){
String e = it.next();
sout(e);
}
for(Iterator<String> it2 = coll.iterator; it2.hasNext();){
String e = it.next();
sout(e);
}//用的少迭代器实现原理
7.9 增强for循环
底层也是迭代器,只是使用了for循环的格式简化了迭代器的书写(jdk1.5以后)
Collection<E> extends Iterator<E>
:所有单列集合都可以使用增强forpublic interface Iterable<T>
实现这个接口允许对象成为“foreach”语句的目标格式:
1
2
3for(集合/数组的数据类型 变量名: 集合/数组名){
//操作
}1
2
3
4
5
6ArrayList<String> list = new ArrayList<>();
list.add(...)
...
for(String s:list){
sout(s);
}
7.10 泛型
一种未知的数据类型,当我们不知道使用什么数据类型的时候,就可以使用泛型,
泛型也可以看出是一个变量,用来接收数据类型
E e :Element 元素
T t :Type 类型
好处:
- 避免了数据转换的麻烦:不适用泛型,里面存储的数据默认都是是Object类(多态),假如要使用String类特有的方法,则需要向下转型
- 把运行期的异常,提升到了编译期(写代码的时候会报错)
弊端:泛型是什么类型,只能存储什么类型的数据
7.11 定义含有泛型的类、方法、接口
不写泛型默认是Object类型
类:
修饰符 class 类名<代表泛型的变量>{}
方法:
修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)){方法体}
1
2
3
4
5
6
7public <M> void method(M m){
//方法体
}//传递什么数据类型,泛型就是什么类型
public static <M> void method(M m){//含有泛型的静态方法
//方法体
}//传递什么数据类型,泛型就是什么类型接口:
public interface 接口名<泛型>{}
泛型通配符<?> :代表任意的数据类型
不能创建对象使用,只能作为方法的参数使用
比如定义一个方法遍历所有类型的ArrayList集合,这个时候不知道ArrayList集合使用什么数据类型,可以使用泛型的通配符?来接收数据
1
2
3
4
5
6public static void printArray(ArrayList<?> list){
Iterator iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
泛型的上限、下限限定(能看懂就好了)
? extends E:表示使用的泛型只能是E类型的子类/本身
? super E:表示使用的泛型只能是E类型的父类/本身
7.12 斗地主案例
综合练习:day0711/Fight_The_Landlord:斗地主案例
0712 八 数据结构、常用集合
8.1 数据结构
栈:先进后出
队列:先进先出
数组:查询快,增删慢
查询快:数组的地址是连续的,可以通过数组的首地址找到数组,通过索引快速找到某一个原色
增删慢:数组的长度是固定的,想要增删一个元素,必须创建一个新的数组,把源数组的数据复制过来
链表:查询慢,增删快
链表中每个元素称为一个节点,每个节点包含了一个数据源(存储数组)和两个指针(存储自己的地址和下一个节点的地址)
查询慢:链表中的地址不是连续的,每次查询都必须从头开始查询
增删快:链表中增删一个元素对链表的整体结构没有影响
分类:
单向链表:只有一条链,不能保证元素的顺序
双向链表:两条链子,有一条用来记录元素的顺序,是一个有序集合
红黑树:
二叉树:分支不超过两个
排序数/查找树:在二叉树的基础上,元素是有大小顺序的,特点:左子树小,右子树大
平衡树与不平衡树:左孩子和右孩子相等与不相等
红黑树:特点:趋于平衡树,查询的速度非常快,查询叶子节点最大次数和最小次数不能超过2倍
约束:
- 节点可以是红色或者黑色
- 根节点是黑色的
- 叶子节点(空节点)是黑色的
- 每个红色的节点的子节点都是黑色的
- 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
8.2 List集合
java.util.list接口 extends Collection接口
特点:
- 有序的集合,存取元素顺序一致(存123,取123)
- 有索引,包含了一些带索引的方法
- 允许存储重复的元素
list接口中带索引的方法(特有)
1
2
3
4public void add(int index, E element);
public E get(int index);
public E remove(int index);
public E set(int index, E element);创建一个list集合对象,多态:
List<String> list = new ArrayList<>();
8.3 ArrayList集合
- List接口的数组结构实现
- 底层数组结构,查询快,增删慢
8.4 LinkedList集合
List接口的链表列表实现
底层链表结构,查询慢,增删快
里面包含了大量操作首尾元素的方法
1
2
3
4
5
6
7
8
9
10
11
12public void addFirst(E e);
public void addLast(E e);
public void push(E e);//将元素推入此列表所表示的堆栈
public E getFirst();
public E getLast();
public E removeFirst();
public E removeLast();
public E pop();//从此列表所表示的堆栈处弹出一个元素
public boolean isEmpty();
8.5 Vector集合
- 可实现可增长的对象数组,即底层也是数组结构,后面被ArrayList取代了
8.6 Set集合
继承了Collection接口
特点:1.不允许重复元素;2.无索引没有带索引的方法,也不能使用普通的for循环遍历(用迭代器、增强for)
Set集合不允许重复的原理:(前提必须重写hashCode方法和equals方法)
Set集合调用add方法的时候,add会调用元素的hashCode方法和equals方法,判断元素是否重复
先判断有无相同的哈希值,没有则直接存储,有则调用equals方法和哈希值相同元素进行判断,相同则不存储
8.7 HashSet集合
Set接口的实现
特点:不重复;无索引;无序;底层是一个哈希表结构(查询快)
哈希值:一个十进制整数,由系统随机给出(对象的地址值,是一个逻辑值,是模拟出来得到地址,不是数据实际存储的物理地址)
获取:Object类中有一个
public native int hashCode()
(native表示该方法调用的是本地操作系统的方法)
- HashSet集合存储数据的结构(哈希表):
- jdk1.8之前哈希表 = 数组+链表;1.8之后为数组+红黑树(提高查询速度)
- 数组结构:把元素分组(哈希值相同的为一组),再用链表/红黑树把相同哈希值的元素连接到一起
- 注:
- 先用链表,如果链表长度超过8个,则用红黑树
- HashSet存储自定义类型元素
- 必须重写HashCode和equals方法 <—8.6有说
8.8 LinkedHashSet集合
- 继承了HashSet
- 底层是哈希表(数组+链表/红黑树) + 链表:多了一条链表(记录链表存储顺序),保证元素顺序
- 特点:有序(因为多了一条链表)
8.9 可变参数
- JDK1.5之后
- 前提:参数列表类型确定,个数不确定
- 格式:
修饰符 返回值类型 方法名(数据类型...变量名){}
- 原理:可变参数底层是一个数组,根据传参不同,创建不同长度的数组,来存储这些数据,传递的参数可以是0,1,2…
- 注意:
- 一个方法的可变参数只能有一个
public void method(Sting...a, int...b)是错误的
- 多个方法参数,那么可变参数必须写在参数列表末尾
- 一个方法的可变参数只能有一个
8.10 Collections集合工具类
java.utils.Collections集合工具类,用于操作集合,常用方法
1
2
3
4public static <T> boolean addAll(Collections<T> c, T...elements);//往集合加元素
public static void shuffle(list<?> list);//打乱顺序
public static <T> void sort(List<T> list);//默认规则排序(升序)
public static <T> void sort(List<T> list, Comparator<? super T>);//指定规则排序(方便)1
2
3ArrayList<String> list = new ArrayList<>();
Collections.allAll(list, "a", "b", "c");
Collections.shuffle(list);注:
- 使用sort默认排序,被排序的集合里面存储元素,必须实现Comparable,重写接口中的方法compareTo定义排序规则
- Comparator和Comparable区别
- Comparable:自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的规则compareTo方法
- Comparator:相当于找一个第三方的裁判进行比较
8.11 Map集合
接口Map
— 两个泛型,一个元素包含两个值,键和值,故叫双列集合 一个映射不能包含重复的键,一个键最多一个值,通过键找到对应的值(键不能重复,值可以)
常用方法
1
2
3
4
5
6public V put(K key, V value);//放入键值对。注:返回值V当key不重复,返回null,当key重复,使用新的value替换原来的value,并返回原来的value
public V remove(Object key);//删键值对
public V get(Object key);
boolean containsKey(Object key);
public Set<K> keySet();//获取所有的键存储在Set中
public Set<Map.Entry<K,V>> entrySet();//获取Map集合中所有的键值对对象的集合(Set集合)注:Map.Entry
:在map接口中有一个内部接口Entry 作用:在Map集合创建的时候会在Map集合中创建在一个Entry对象,用来记录键与值
遍历Map集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//使用keySet(),entrySet()方法
//通过keySet(),也就是键
Set<String> set = map.keySet();
Iterator<String> it = set.iterator();
while(it.hasNext()){
String key = it.next();
Integer value = map.get(key);
}//用增强for也可以
//通过entrySet方法
Set<Map.Entry<String, Integer>> set = map.entrySet();
for(Map.Entry<String, Integer> entry:set){
String key = entry.getKey();
Integer value = entry.getValue();
}//也可用迭代器
8.12 HashMap
- 无序,key不重复
- 底层是哈希表,查询快。JDK1.8之前为数组+单向链表,JDK1.8之后为数组+单向链表/红黑树
- 也有一个类似与LinkedHashSet的LinkedHashMap子类(有序)
- HashMap存储自定义数据类型
- 保证key是唯一的,所以要重写key的hashCode和eqauls方法
8.13 Hashtable
- 不允许存储null(之前的集合都可以存储null)
- 单线程,速度慢
- 和Vector一样,后面被HashMap取代
- 其子类Properties依然使用较多(IO流中)
8.14 JDK9对集合添加的优化
List Set Map接口添加了一个静态方法of,一次性添加多个元素
static <E> list of(E...elements)
注:
- 只适用于List Set Map接口,不适用于接口的实现类
- of方法返回一个不可变的集合,集合不能再使用add,put方法添加元素,会抛出异常
- Set接口和Map接口调用of方法的时候,不能有重复的元素
8.15 练习
- 练习:day0712/CalEachWordNum:统计一个字符串中每个字符出现次数
- 练习:day0712/Fight_The_Landlord_pro:斗地主案例(对牌进行了排序)
0714 九 异常、线程
9.1 异常
程序在执行过程中,出现的非正常现象
异常的根类为Java.lang.Throwable,下面有两个子类Error和Exception
Exception:编译器异常,(写代码)java程序出现的问题
- RuntimeException:运行期异常
Error:错误。必须修改源代码
异常产生过程解析
9.2 异常处理的关键字
throw关键字:在指定的方法中抛出指定的异常
格式:
throw new xxxException("异常产生的原因")
注:
- throw必须写在方法内部
- throw后面new的对象必须是Exception或者Exception的子类对象
- throw后抛出的指定异常对象,必须处理这个异常对象
- throw关键字后面创建的是RuntimeException及其子对象,可以不处理,默认给JVM处理(打印并中断)
- throw关键字后面创建的是编译异常,就必须处理,用throws或者try…catch
throws关键字:当方法内部抛出异常对象的时候,可以使用throws处理,会把异常对象声明抛出给方法的调用者(自己不处理,交给别人处理),最终交给JVM处理—>中断处理
格式:
1
2
3
4修饰符 返回值类型 方法名(参数列表) throws AAAException,BBBException...{
throw new AAAException("...");
throw new BBBException("...");
}注:
- throws必须写在方法声明处
- throws关键字后面声明的异常必须是Exception或者其子类
- 方法内部如果抛出了多个异常对象,那么throws后边必须也声明多个异常,如果是父子关系,明父类即可
try…catch与finally
格式
1
2
3
4
5
6
7
8try{
//可能产生的代码
}catch(定义一个异常变量用于接收){
//异常的处理逻辑
//一般会写到工作日志中
}finally{
//无论怎么样都会执行
}注
- try中可能会抛出多个异常对象,可以用多个catch(子类必须写在父类上面)
- finally 不能单独使用,得和try一起
- finally一般用于资源的释放(资源回收)
9.3 Throwable类中的三个处理异常方法
1 | public String getMessage();//返回该Throwable的简短描述 |
9.4 子类父类异常
- 如果父类抛出了多个异常,子类重写方法的时候,抛出和父类相同的异常或者是父类异常的子类或者不抛
- 父类方法没有异常,子类重写父类方法也不可以抛出异常,此时子类产生异常只能捕获处理不能声明抛出
- 父亲异常时什么样,子类异常就什么样
9.5 自定义异常类
java提供的异常类,不够我们自己使用,需要自己定义一些异常类
格式:
1
2
3
4public class XXXException extends Exception|RuntimeException{
//添加一个空参数方法
//添加一个带异常信息的构造方法
}注:
- 一般以Exception结尾
- 必须继承Exception或者RuntimeException,前者必须throws或者try catch,后面可以不处理
9.6 并发与并行
并发:两个或多个事件在同一个时间段内发生
并行:两个或多个事情在同一时刻发生(同时)
并行速度快
9.7 进程与线程
指一个内存中运行的应用程序,每个进程都有一个独立的空间,一个应用程序可以同时运行多个进程;进程是程序的一次执行过程,是系统运行过程中的基本单位;系统运行一个程序是一个进程从创建运行到死亡的过程
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少又一个线程,一个程序中可以有多个线程,这样的程序被称为多线程程序
线程调度
- 分时调度
- 抢占调度
9.8 线程类Thread
创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39//第一种方式:创建Thread类 java.lang.Thread
/*步骤
创建Thread类;
重写Thread类种的run方法,设置线程任务
创建Thread类的子类对象
调用Thread类的方法start方法,开启新的线程,执行run方法
*/
public class MyThread extends Thread{
public void run(){
//内容
}
}
//主方法中
MyThread mt = new MyThread();
mt.start();
//第二种方式:实现Runnable接口,然后重写run()方法,然后作为参数在Thread方法执行
//java.lang.Thread类的构造方法
// Thread(Runnable target) 分配新的Thread对象
// Thread(Runnable target, String name)
/*步骤
创建Runnable接口的实现类
重写run方法,设置线程任务
创建Runnable接口实现类对象
创建Thread类对象,构造方法中传递Runnable接口的实现类对象
调用Thread类中start方法
*/
public class RunnableImpl implements Runnable{
public void run(){
//内容
}
}
//主方法中
RunnableImpl run = new RunnableImpl();
Thread t = new Thread(run);
t.start();Thread和Runnable的区别(实现Runnable的好处)
Runnable避免了单继承的局限性,可以继承其他的类,实现其他的接口
增强了程序的扩展性,降低程序的耦合性
实现Runnable接口的方式,把设置线程任务和开启线程进行了分离(解耦)
实现类中,重写了run方法:用来设置任务
创建Thread类对象,调用start方法:用来开启多线程
多线程的随机执行过程
多线程的内存图解
常用方法
1
2
3
4
5public String getName(); //返回线程名称
public static Thread currentThread(); //返回对当前正在执行的线程对象的引用
public void setName(String name); //设置名称
Thread(String name);//也可以通过构造方法直接设置
public static void sleep(long millis);//当前的进程以指定的毫秒暂停,之后再继续执行匿名内部类实现线程的创建
格式
1
2
3
4//匿名内部类格式
new 父类/接口(){
重复父类/接口中的方法
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23new Thread(){
public void run(){
//线程任务
}
}.start();
//方式2
Runnable r = new Runnable(){
public void run(){
//线程任务
}
};//(多态写法,接口=实现类)
new Thread(r).start();
//简化方式2
new Thread(new Runnable(){
public void run(){
//线程任务
}
}).start();
9.9 线程安全问题
解决方法一:同步代码块
1
2
3synchronized(同步锁){
//需要同步操作的代码,即可能出现线程安全问题的代码(访问了共享数据的代码)
}通过代码块中的锁对象,可以使用任意的对象
但是必须保证多个线程使用的锁是同一个
锁对象作用:锁住同步代码块,只让一个线程在同步代码块中执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35public class RunnableImpl implements Runnable{
private int ticked = 100;
Object obj = new Object();//创建一个锁对象
public void run() {
while (true){
synchronized (obj){
//判断是否有票
if(ticked > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticked + "张票");
ticked--;
}
}
}
}
}
//主程序
public class Test {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}同步代码块原理
解决方法二:同步方法
步骤:
- 把访问了共享数据的代码抽取,放到一个方法中
- 在方法上添加synchronized修饰符
修饰符 synchronized 返回值类型 方法名(参数列表){}
静态同步方法
- 锁对象不能是this(this是创建对象后产生的,静态方法优先于对象)
- 静态方法的锁对象是本类的class属性 ——> .class文件对象(反射)
解决方法三:Lock锁
java.util.concurrent.locks.lock接口
lock接口实现了比synchronized方法和语句更广泛的锁定操作
方法
1
2void lock();//获取锁
void unlock();//释放锁java.util.concurrent.locks.Reentrantlock implements lock
使用步骤
- 在成员位置创建一个Reentrantlock对象
- 在可能会出现安全问题的代码前调用lock()方法
- 在可能会出现安全问题的代码后调用unlock()方法
0715 十 线程池、Lambda表达式
10.1 线程间通信
也叫等待唤醒机制:多个线程处理一个资源,处理的动作不一样,就存在线程之间的通信
为什么要处理线程之间的通信:多线程并发执行的时候,在默认的情况下CPU是随机切换线程的,当我需要多个线程处理一个任务的时候,并且希望他们有规律的执行,那么多线程之间则需要通信(即解决线程对同一个变量的使用和操作)
等待唤醒机制的三个方法:
- wait:线程不在活动,不去竞争锁,进入wait set集合中,因此不会浪费CPU资源,此时线程的状态是WAITING
- notify:选取所通知对象的wait set中的一个线程释放
- notifyAll:释放所通知对象的wait set上全部的线程
注:
- wait和notify方法必须要同一个锁对象调用,因为对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程
- wait方法和notify方法是属于Object类的方法
- wait方法和notify方法必须在同步代码块或者同步函数中使用,因为必须要通过锁对象调用这两个方法
- 调用obj.wait()会立即释放锁,以便其他线程可以执行obj.notify(),但是notify()不会立刻释放sycronized(obj)中的obj锁,必须要等notify()所在线程执行完synchronized(obj)块中的所有代码才会释放这把锁.
-
等待唤醒机制练习:day0715/wakeUp:生成包子吃包子案例
10.2 线程状态
阻塞状态BLOCKED:具有cpu的执行资格,等待cpu空闲执行
休眠状态TIME_WAITING:放弃cpu 的执行资格,cpu空闲也不执行
运行状态RUNNING
死亡状态TERMINATED
等待状态WAITING
- 注:进入到TimeWaiting(计时等待)有两种方式
- sleep(long m)方法在毫秒值结束之后,线程睡醒进入Runnable/Blocked状态
- wait(long m),wait在毫秒值结束后,还没有被notify唤醒,就自动醒来进入Runnable/Blocked状态
10.3 线程池
概念:一个容纳多个线程的容器,其中的线程可以反复利用,省去了频繁创建线程对象的操作而避免了创建线程消耗过多的资源
线程池:容器—>(ArrayList,Hashset,LinkedList
,HashMap)第三个最好 当程序第一次启动的时候,创建多个线程,保存到一个集合中,当想要使用的时候,就可以从集合中取出线程
Thread t = list.remove(o);//返回的是被移除的元素
Thread t = linked.remove(o);
当使用完毕,归还线程
list.add(t);
linked.addList(t);
JDK1.5之后,JDK就内置了线程池,我们可以直接使用
10.4 线程池的代码实现
JDK1.5后:java.util.concurrent.Executors
static ExecutorService newFixedThreadPool(int nThreads);//生产指定线程数量的线程池
返回值:java.util.concurrent.ExecutorService 接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService 接口接收(面向接口的编程)
其中有两个方法:
Future<?> submit(Runnable task);//提交执行一个Runnable任务并返回一个表示该任务的未来
void shutdown();//关闭/销毁线程池的方法
线程池使用步骤:
- 使用线程池的工厂类Executors里面提供的静态方法newFixedThreadPool生成指定数量的线程池
- 创建一个类,实现Runnable,重写run方法,设置线程任务
- 调用ExecutorService 中的方法submit,传递线程任务(实现类),开启线程,执行run方法
- 调用ExecutorService 中的方法shutdown,销毁线程池(不建议使用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class test {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(new RunnableImpl());
executorService.submit(new RunnableImpl());
executorService.submit(new RunnableImpl());
executorService.submit(new RunnableImpl());
}
}
public class RunnableImpl implements Runnable{
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
/*输出:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-2
*/
10.5 函数式编程思想
- 面向对象的思想:做一个事情,找一个能解决这个事情的对象,调用对象的方法完成事情
- 面向编程的思想:只要获取结果,怎么做的不重要,重视结果不重视过程
- 冗余的Runnable代码
1 | // 冗余的Runnable代码 |
10.6 Lambda表达式
JDK1.8
```java
public class LambdaDemo {public static void main(String[] args) { //使用匿名内部类的方法实现 new Thread(new Runnable() { @Override public void run() { System.out.println("new Runnable"); } }).start(); //使用Lambda表达式实现多线程 new Thread(()->{ System.out.println("new Runnable"); } ).start(); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
- Runnable接口中只有一个run方法的定义:`public abstract void run();`
制定了一种做事情的方案(函数):
- 无参数
- 无返回值
- 代码块
同样的语义在Lambda语法中更简单:`() -> System.out.println("new Runnable"); `
- Lambda表达式的标准格式由三部分:
- 一些参数
- 一个箭头
- 一段代码
`(参数列表) -> {一些重写方法的代码块}`
注:()是接口中抽象方法的参数列表,没有参数就空着;有参数就写出参数,多个参数用逗号分隔
->是传递参数的意思
- ```java
public interface Cook(){
public abstract void makefood();
}
punlic class Main(){
public static void main(String[] args) {
//使用invokeCook方法,参数为Cook接口,传递Cook接口的匿名内部类对象
invokeCook(new Cook(){
@Override
public void makefood(){
System.out.println("吃饭");
}
});
//使用Lambda表达式,调用invoke方法,参数是Cook接口,传递Cook接口的匿名内部类对象
invokeCook(()->{
System.out.println("吃饭");
});
}
public static void invokeCook(Cook cook){
cook.makefood();
}
}Lambda表达式练习:day0715/LambdaTest:使用数组存储多个Person对象,对数组中的Person对象使用Arrays的sort方法通过年龄升序排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public class Main {
public static void main(String[] args) {
Person[] arr = {
new Person("jack", 19),
new Person("mack", 17),
new Person("rose", 18),
};
//对数组中的Person对象进行升序排序
Arrays.sort(arr, new Comparator<Person>() {
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});
for (Person person : arr) {
System.out.println(person.toString());
}
//使用Lambda表达式简化匿名内部类
Arrays.sort(arr,(Person o1, Person o2)->{
return o1.getAge()-o2.getAge();
});
for (Person person : arr) {
System.out.println(person.toString());
}
}
}Lambda表达式练习2:day0715/LambdaTest/DemoInvokeCalc.java:给定一个计算器Calculator接口,内含抽象方法calc可以将两个int
数字相加得到和
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class DemoInvokeCalc{
public static void main(String[] args) {
invoke(3, 4, new Calculator() {
public int calc(int a, int b) {
return a+b;
}
});
//使用Lambda表达式
invoke(3, 4, (int a, int b) ->{
return a+b;
});
}
private static void invoke(int a, int b, Calculator cal){
int result = cal.calc(a, b);
System.out.println(result);
}
}
10.7 使用Lambda的前提
Lambda表达式是可推导可省略的:凡是根据上下文可以推导出来的内容都可以省略
可以省略的内容:
(参数列表):括号中的参数列表的数据类型可以省略不写
(参数列表):括号中的参数只有一个,那么类型和()都可以省略不写
(一些代码):如果{}中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)
注:要省略就一起省略
new Thread(()->System.out.println("new Runnable").start();
使用前提:
- 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法
- 使用Lambda必须具有上下文推断,也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
0718 十一 流
11.1 File类
java.io.file:文件和目录路径名的抽象表示形式
Java把电脑中的文件和文件夹封装为一个File类,可以使用File类对他们进行操作
File类是与系统无关的类,任何操作系统都可以使用
三个单词: file、directory、path
File的静态成员变量
1
2
3
4
5
6static String pathSeparator;//系统依赖路径分隔符的字符,表示为方便的字符串
static char pathSeparatorChar;//系统依赖路径分隔符的字符
// 路径分隔符windows:分号 linux:冒号
static String separator;//系统依赖的默认名称分隔符字符,表示为方便的字符串
static char separatorChar; //系统依赖的默认名称分隔符字符
//文件分隔符 windows:反斜杠/ linux:正斜杠/绝对路径和相对路径
绝对路径是以盘符开始的完整的路径
相对路径是相对于当前项目的根目录
注:不区分大小写;路径中文件名分隔符windows为反斜杠,反斜杠为转转义字符,两个普通的反斜杠代表一个反斜杠
C://Users//ManYile
File类的构造方法
1
2
3
4File(String pathname);//通过将给定的路径名的字符串转换成一个抽象路径名创建一个新的 File实例
File(File parent, String child);//创建从一个家长的抽象路径名和一个孩子的路径字符串的新 File实例
File(String parent, String child);//创建从父路径名的字符串和一个孩子的一个新的 File实例文件。
File(URI uri);//通过将给定的 file: URI到一个抽象路径名创建一个新的 File实例。注:
- pathname可以是以文件或者文件夹结尾
- 可以是相对路径/绝对路径
- 可以是存在,也可以是不存在
- 创建File对象,只是把字符串路径封装为File对象,不考虑路径存在真假情况
File类的常用方法
获取的方法
1
2
3
4
5public String getAbsolutePath();//获取绝对路径
public String getPath();//将此File转换为路径名字符串,绝对就是绝对,相对就是相对
//File类的toString方法用的就是getPath()
public String getName();//将此
public long length();//返回此File表示的文件长度,以字节为单位,不能获取文件夹大小(文件夹没有大小),若不存在则返回0判断的方法
1
2
3public boolean exists();//目录是否存在
public boolean isDirectory();//是否为目录
public boolean isFile();//是否为文件夹创建删除功能
1
2
3
4public boolean createNewFile();
public boolean delete();
public boolean mkdir();//创建由此File表示的目录
public boolean mkdirs();//创建由此File表示的目录,包括任何必须但不存在的父目录File类的遍历(文件夹)功能
1
2public String[] list();//返回一个String数组,表示该File目录中所有的子文件或者目录
public File[] listFiles();//返回一个File数组,表示所有的子文件或者目录
11.2 递归
- 注:
- 构造方法禁止递归
- 递归不宜太多或者无法跳出,否则会导致栈内存溢出
递归练习:day0718/SearchJava.java:搜索D盘的.java文件
11.3 FileFilter过滤器
java.io.FileFilter接口:用于抽象路径名(File对象)的过滤器
抽象方法:用来过滤文件
1
boolean accept(File pathname);//测试指定抽象路径名是否应该包含在某个路径名列表中
11.4 FileNameFilter
java.io.FileNameFilter接口
抽象方法:用于过滤文件
1
boolean accept(File dir, String name);//测试文件是否包含在某一文件夹中
注:两个过滤器没有实现类,我们需要自己重写实现类,重写accept方法,自己定义规则
1
2
3
4
5
6
7
8
9
10
11File[] file = dir.listFiles(new FileFilter(){
public boolean accept(File pathname){
return pathname.isDirectory() || pathname.getName().toLowerCase().endWith(".java");//返回以java结尾的文件
}
});
//lambda
File[] file = dir.listFiles(() -> {
return pathname.isDirectory() ||pathname.getName().toLowerCase().endWith(".java");
});
11.5 字节流
IO:input output
一切皆为字节
字节输出流:OutputStream,一切输出流的父类,是一个抽象类,已知直接子类:
ByteArrayOutputStream,FileOutputStream,FilterOutputStream,ObjectOutputStream,OutputStream,PipedOutputStream
1 | public void close(); |
FileOutputStream
构造方法
1
2
3
4
5FileOutputStream(String name);
FileOutputStream(File file);
FileOutputStream(String name, boolean append);
FileOutputStream(File name, boolean append);//第三第四个为追加写
//append为true则不会覆盖,为flase则会覆盖构造方法作用
- 创建一个FileOutputStream对象
- 会根据构造方法中传递的文件/文件路径,创建一个空的文件
- 会把FileOutputStream对象指向创建好的文件
写入数据的原理
- java程序 -> JVM(java虚拟机) -> OS系统 -> os调用写数据的方法 -> 把数据写入到文件
字节输出流的使用步骤
创建一个FileOutputStream对象,构造方法中传递输入数据目的地
调用FileOutputStream对象方法中的write,写入数据
释放资源(流会占用内存)
1
2
3FileOutputStream fos = new FileOutputStream("D//a.txt");
fos.wirte(97);
fos.close();
一次写多个字节的方法
public void write(byte[] b);
- 如果第一个字节为正数(0-127),则显示的时候会查询ASCII表
- 如果为负数,则第一个字节会和第二个字节,两个字节组成一个中文显示,查询系统默认码表(GBK)
写入字符的方法:使用String类中的
byte[] getBytes()
将字符串转换为字节数组,然后再输入字节输出流的续写和换行
- 续写:(第3第4中构造方法)
- 换行:写换行符号 windows:/r/n linux:/n mac::/r
fos.write("/r/n".getBytes())
字节输入流:InputStream ,同OutputSteam
子类:AudioInputStream,ByteArrayInputStream,FileInputStream,FilterInputStream,InputStream, ObjectInputStream,PipedInputStream,SequenceInputStream,StringBufferInputStream
1
2
3public int read();
public int read(byte[] b);
public void close();FileInputStream:文件字节输入流
构造方法
1
2FileInputStream(String name);
FileInputStream(File file);作用类似于FileOutputStream构造方法
读取数据原理与FileOutputStream相似
字节输入流的使用步骤
- 创建一个FileInputStream对象,绑定要读取的数据源
- 调用FileInputStream对象方法中的read,读取数据
- 释放资源(流会占用内存)
读取文件,while循环
1
2
3
4int len = 0;
while((len = fis.read()) != -1){
sout(char(len));//读取文件里面的字节用char表示
}一次读取多个字节:
public int read(byte[] b);
注:方法参数byte[]的作用为起到缓冲作用,存储每次读取到的多个字节,数组长度一般定义为1024的整数倍;
方法返回值int 是每次读取的有效字节数
1
2
3
4
5
6byte[] bytes = new byte[1024];
int len = 0;
while((len = fis.read(bytes)) != -1){
sout(new String(bytes));//这样会有很多空格(因为长度为1024,而文件可能没有这么多字节
sout(new String(bytes, 0, len));
}
- String类的构造方法:
String(bytes[] bytes):将bytes数组转为String
和String(bytes[] bytes, int off, int length)
字节流练习:day0718/Stream.java:复制文件
11.6 字符流
字节流的缺点:使用字节流读取文件,1个中文在GBK中占两个字节,在UFT-8占用3个字节
字符输入流:java.io.Reader,是字符输入流最顶层的父类
1
2
3public int read();
public int read(char[] cbuf);//一次读取多个字符
public void close();FileReader:文件字符输入流
FileReader extends InputStreamReader extends Reader
构造方法:
FileReader(String name);FileReader(File file);
使用步骤:同字节输出流
1
2
3
4
5
6
7
8
9
10
11
12
13
14FileReader fr = new FileReader("文件位置");
//一个字符一个字符的读取
int len = 0;
while((len = fr.read()) != -1){
sout(char(len));
}
//以字符数组读取
char[] cs = new char[1024];
int len = 0;
while((len = fr.read(cs)) != -1){
sout(new String(cs, 0, len));
}
fr.close();
字符输出流:java.io.Writer,是字符输出流最顶层的父类
1
2
3
4
5
6
7public void write(int c);//写入单个单词
public void wirte(char[] cbuf);
public abstract void write(char[] cbuf, int off, int len);
public void wirte(String str);
public void wirte(String str, int off, int len);
public void flush();
public void close();FileWriter:FileWriter extends OutputStreamWriter extends Writer
构造方法
1
2FileWriter(File file);
FileWriter(String name);使用步骤
1
2
3
4FileWriter fw = new FileWriter(name);
fw.write(97);
fw.flush();//需要刷新一下
fw.close();字符输出流写数据的其他方法(调用其他方法)
续写和换行
续写:
FileWriter(String name, boolean append)
或FileWriter(File name, boolean append)
append为true则不会创建新的文件覆盖,为false则会覆盖
换行:同字节输入流
11.7 JDK7和JDK9中的异常处理
JDK7新特性,在try后面可以增加一个(),在括号里面进行对象的定义,那么这个流对象的作用域就在try中有效,try中的代码执行完毕,会自动把流对象释放,不用写finally。
JDK9新特性:try前面可以定义流对象,try后面()中可以直接引入流对象的名称(变量名),在try代码执行完毕后,流对象也可以释放,不用写finally,格式:
1
2
3
4
5
6
7A a = new A();
B b = new B();
try(a, b){
//可能出现异常部分;
}catch(异常类变量 变量名){
//异常处理
}
11.8 属性集
java.util.Properties继承于HashTable:表示一个持久的属性集,可保存流中或从流中加载,属性列表中每个键以及对应值都是一个字符串
Properties是唯一一个和IO流相结合的集合
使用集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
void store(OutputStream out, String comments);
void store(Writer writer, String comments);
参数说明
- OutputStream out:字节输出流,不能写中文
- Writer writer:字符输出流,可以写中文
- String comments:注释,用来解释说明保存的文件,不能使用中文,一般使用空字符串
使用方法load,把硬盘中保存的文件,读取到集合中
双列集合,key和value都是默认字符串
Properties中一些操作字符串的方法
1
2
3Object setProperties(String key, String value);
String getProperties(String key);
Set<String> StringPropertyNames();//返回属性列表中的键集使用步骤:
- 创建Properties集合对象,添加数据
- 创建字节/字符输出流对象,构造方法中绑定输出目的地
- 使用Properties集合中的store方法,把集合中的临时数据持久化写入到硬盘中
- 释放资源
1
2
3
4
5Properties prop = new Properties();
prop.setProperties("张三", "19");
FileWriter fw = new FileWriter("地址");
prop.store(fw, "save data");
fw.close();
0719 十二 缓冲流、转换流、序列化
12.1 缓冲流
给基本的字节/字符输入/输出流增加一个缓冲区(数组),提高字节/字符输入/输出的效率
注:比使用一个数组更有效率
字节缓冲流:
BufferInputStream;BufferOutputStream
字符缓冲流:
BufferedReader;BufferedWriter
BufferedOutputStream extends OutputStream
:字节缓冲输出流- 构造方法:
BufferedOutputStream(OutputStream out);BufferedOutputStream(OutputStream out,int size)
,size为内部缓冲区的大小,不指定则默认 - 步骤
- 创建FileOutputStream对象,构造方法中要绑定输出目的地
- 创建BufferOutputStream对象,构造方法中传递FileOutputStream,以提高FileOutputStream对象效率
- 使用BufferOutputStream的write方法,把数据写入到内部缓冲区中
- 使用BufferOutputStream的flush方法,刷新(可省略)
- 关闭close
- 构造方法:
BufferedInputStream extends inputStream
:字节缓冲输入流- 构造方法:类似
- 步骤:类似
BufferedWriter extends Writer
:字符缓冲输出流- 构造方法:
BufferedWriter (Writer writer, int size);
size可以不写 - 特有的成员方法:
void newLine()://创建行分隔符
- 构造方法:
BufferedReader extends Reader
:字符缓冲输入流- 构造方法
- 特有成员方法:
String readLine();//读取一个文本行,读取一行数据
12.2 转换流
字符编码和解码
字符集:也叫编码表,常见的有:
ASCII字符集(英文)
GBK字符集(最常用的中文码表),两个字节一个中文
Unicode字符集(任意语言),三个字节一个中文
编码引出的问题:FileReader可以读取IDEA默认编码格式(UTF-8)的文件,它读取系统默认的编码格式(中文GBK)文件会产生乱码
转换流原理:
InputStreamReader:可以指定编码表
- 构造方法:类似下面
- 步骤:类似下面
OutputStreamWriter:可以使用指定的charset将要写入的字符编码成字节
构造方法
1
2
3OutputStreamWriter(OutputStream out, String charsetName);
//charsetName不分大写
OutputStreamWriter(OutputStream out);步骤
- 创建OutputStreamWriter对象
- 使用OutputStreamWriter的write方法
- flush
- close
12.3 序列化和反序列化
- 序列化:把对象以流的方式,写入到文件中进行保存,也叫写对象
- 对象中包含的不仅仅是字符,所以使用字节流
- ObjectOutputStream:对象的序列化流
void writeObject(Object obj)
- 反序列化:读取文件中保存的字节,
- 使用字节流
- ObjectInputStream:对象的反序列化流
Object readObject()
java.io.ObjectOutputStream extends OutputStream
- 构造方法
ObjectOutputStream(OutputStream out)
- 特有的成员方法
void writeObject(Object obj)
- 构造方法
java.io.ObjectInputStream extends InputStream
- 构造方法
ObjectInputStream(InputStream in)
- 特有的成员方法
Object readObject()
- 构造方法
12.4 transient关键字
- 瞬态关键字
- static关键字:静态关键字,优先于非静态加载到内存中(静态优先于对象进入到内存中),被static修饰的成员变量不能被序列化,序列化的都是对象
- 被transient修饰的成员变量不能被序列化、
12.5 InvalidClassException异常
- 原理:当JVM反序列化对象时,能找到.class文件,但是.class文件再序列化对象之后发生了修改,那么反序列化的操作也会失败,抛出
InvalidClassException
异常 - 原因:
- 该类的序列版本号于从流中读取的类描述的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
解决方案:
- 让相应的类实现Serializable接口,然后
private static final long serialVersionUID = 1L;(随便赋值)
(就是为了序列号不改变)
12.6 打印流
- 让相应的类实现Serializable接口,然后
java.io.PrintStream
:打印流特点:
- 只负责数据的输出,不负责输入
- 不抛出IOException
- 特有的方法:print、println,可以输出任意的数据类型
构造方法:
PrintStream(File file)
PrintStream(String filename)
PrintStream(OutputStream out)
注:如果使用继承父类的write()方法,那么查看数据的时候会查询编码表 97->a
如果使用字节的方法print/println方法写数据,写的数据原样输出 97->97
序列化集合练习:day0719/:序列化集合(当我们想在文件中保存多个对象的时候,可以把对象存储到一个集合中,对集合进行序列化和反序列化)
0720 十三 网络编程、函数式编程
13.1 网络编程入门
软件结构:C/S结构和B/S结构
网络通信协议:
TCP/IP协议(Internet最广泛的协议)
四层结构(物理层/数据链路层、网络层(核心)、传输层、应用层)
分类:UDP协议(无连接的通信,不能保证数据完整耗资小,一般视频会议用UDP)
TCP协议:三次握手,保证数据的安全
IP地址:Ipv4和Ipv6
端口号:是一个逻辑端口,无法直接看到,可以使用软件看到,当使用网络软件开打,操作系统会随机分配一个端口号,由两个字节组成,范围为:0-65535之前
注:1024之前的不能使用,已经被系统分配给已知的网络软件了
- 常用端口号: 80端口:网络端口口;数据库:mysql为3306,oracle为1521;Tomcat服务器为8080
13.2 TCP通信程序
能够实现两台计算机之间的数据交互,要严格分为客户端和服务端,服务端先启动,然后客户端主动连接服务端才能连接成功
TCP通信:面向连接的通信,客户端和服务端必须经过3次握手才能通信(安全)
- 使用IO对象进行通信,为字节流对象(不是字符流,因为不仅仅只有字符)
TCP通信的客户端:想服务器发送连接请求,给服务器发送数据,读取服务器回写的数据
表示客户端的类:java.net.Socket:此类实现客户端套字(两台机器间通信的端点,包含了IP地址和端口号)
构造方法:
Socket(String host, int port)
成员方法:
1
2
3OutputStream getOutputStream();//返回此套接字的输出流
InputStream getInputStream();
void close();//关闭套接字实现步骤:
- 创建一个客户端对象Socket,构造方法绑定服务器和ip地址和端口号
- getOutputStream()获取网络字节输出流对象
- 使用getOutputStream()对象中write方法给服务器发送数据
- 使用getInputStream()后去网络字节输入流对象,并使用read()方法读取服务器回写的数据
- 释放资源(Socket)
注:
- 客户端和服务器的交互必须使用Socket中提供的网络流,不能使用自己创建的对象
- 当创建客户端对象Socket对象的时候,就会去请求服务器和服务器经过3次握手建立连接网络,若服务器没有启动,则抛出异常,若服务器启动则可以交互了
TCP通信的服务器段:
- 表示服务器端的类:java.net.ServerSocket:此类实现服务器的套接字
- 构造方法:
ServerSocket(int port);//创建绑定到特定端口的雾浮起套接字
- 服务器端必须明确哪一个客户端请求的服务器,所以用accept方法获取到请求的客户端对象Socket
- 成员方法:
Socket accept();
- 实现步骤:
- 创建服务器ServerSocket对象和系统指定的端口号
- 使用accept方法获取请求的客户端对象Socket
- 使用getInputStream()后去网络字节输入流对象,并使用read()方法读取客户端发送的数据
- getOutputStream()获取网络字节输出流对象,使用getOutputStream()对象中write方法给客户端回写数据
- 释放资源(Socket,ServerSocket)
文件上传练习:day0720/FileUpload:TCP通信的文件上传
注:
Socket.shutdownOutput();
如果不加此代码就会阻塞。因为服务器会一直等待客户端的输出。既然服务器阻塞了,客户端等待着服务器的输出,也会被阻塞,所以导致客户端和服务端都被阻塞。调用
Socket.shutdownOutput()
方法后,客户端输出的数据都将被发送,并加上 TCP 的正常连接终止序列(-1,也就是服务端终止循环的判断条件),这样服务端读取数据时就不会被阻塞了。
web服务器练习:day0720/web:web服务器
13.3 函数式接口
定义:有且只有一个抽象方法的接口
适用于Lambda使用的接口,就是只有一个抽象方法,所以Lambda才能顺利使用
格式:
1
2
3
4修饰符 interface 接口名称{
public abstract 返回值 方法名称(可选参数信息);
//其他非抽象方法信息
}@FunctionalInterface
注解:检测接口是否是一个函数式接口调用函数式接口可以使用lambda表达式
13.4 函数式编程
函数式接口作为方法的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public static void startThread(Runnable run){
new Thread(run).start();
}
psvm{
startThread(new Runnable(){
public void run(){
sout("开启线程");
}
});
//优化
startThread(()->{
sout("开启线程");
});
}函数式接口作为方法的返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public static Comparator<String> getComparator(){
return new Comparator<String>(){
public int compare(String o1, String o2){
return o2.length()-o1.length();
}
};
//使用lambda
return(String o1, String o2) -> {
return o2.length()-o1.length();
};
//继续优化
return(o1, o2) -> o2.length()-o1.length();
}
13.5 常用的函数式接口
Supplier接口
java.util.function.Supplier<T>
:被称为生产型接口,指定的泛型是什么,接口中的get方法就会产生什么类型的数据仅包含一个无参的方法
T get()
使用
1
2
3
4
5
6
7
8
9
10
11
12
13public static String getString(Supplier<String> sup){
return suo.get();
}
psvm{
String s = getString(()->{
//重写get方法
return "get方法返回";
});
//优化
String s2 = getString(()->"get方法返回");
}
Supplier接口练习:day0720/SupplierDemo.java:使用Supplier接口求数组最大值
Consumer接口
java.util.Consumer<T>
:消费型接口,泛型是什么,就使用accpt方法消费什么类型数据void accept()
使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public static void method(String name, Consumer<String> con){
co.accept(name);
}
psvm{
method("jack", new Consumer<String>{
public void accept(String name){
sout(name);
}
});
method("jack",(name)->{
sout(name);
});
}Consumer接口的默认方法:
andThen
1
2
3
4
5
6
7
8
9
10
11
12
13default Consumer<T> andThen(Consumer<? super T> after){
Objects.requireNonNull(after);
return (T t) -> {accept(t);after.accept(t);};
}//组合使用两个Consumer接口的accept方法,谁写前面,谁先消费
Consumer<String> con1;
Consumer<String> con2;
String s = "hello";
那么
con1.accept(s);
con2.accept(s);
等价于
con1.andThen(con2).accept(s);Consumer接口练习:day0720/ConsumerDemo.java:使用Consumer接口拼接字符串
Predicate接口
java.function.Predicate<T>
接口:对某种数据类型进行判断,结果返回一个booleanboolean test(T t)
默认方法:
and()
1
2
3
4
5
6default Predicate<T> and(Predicate<? super T> other){
Objects.requireNonNull(after);
return (t) -> test(t) && other.test(t);
}//组合使用两个Consumer接口的accept方法,谁写前面,谁先消费
pre1.and(pre2).test(t);//t要满足pre1和pre2的两个才为trueor()//或者
negate()//取反
Function接口
java.function.Function<T,R>
接口:用来将T类型的数据转换为R类型的数据R apply(T t)
- 默认方法:
andThen
:用来组合操作
0721 十四
14.1 Stream流
传统集合循环遍历的弊端:若有多层条件。则需要多次循环筛选
使用Stream流的方式进行集合的遍历:JDK1.8,关注的是做什么而不是怎么做
1
2
3
4
5
6list.Stream().filter(name -> name.starsWith("张"))
.fliter(name -> name.length() == 3)
.forEach(name -> sout(name));//筛选出list中以张开头长度为三的对象
//filter方法内传入的是Predicate接口
//filter(Predicate<? super T> predicate)
//forEach(Consumer<? super T> action)使用流:获取数据源 -> 数据转换 -> 执行操作获取想要结果(每次转换原有的Stream对象不变,返回一个新的Stream对象)
获取流;
- 所有的Collection都可以通过
default Stream<E> stream()
方法获取流(必须是单列集合,Map集合要分别把键值单独生成为相应的单列集合再转换) - Stream接口的静态方法
static<T> Stream<T> of(T t)
可以获取数组对应的流
- 所有的Collection都可以通过
常用方法:
延迟方法:可以用来链式编程
1
2
3
4
5Stream<T> filter(Predicate<? super T> predicate);
<R> Stream<R> map(Function<? super T,? extends R> mapper);//将一个流映射到另一个流中
Stream<T> limit(long maxSize);//截取前maxSize个元素
Stream<T> skip(long n);//跳过前n个元素
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b);//组合两个流1
2
3
4
5Stream<String> stream = Stream.of("1", "2", "3");
//使用map方法将其转换为Integer数据类型
Stream<Integer> stream2 = stream.map((String s)->{
return Integer.parseInt(s);
})终结方法:使用之后就不能再使用Stream流的方法了
1
2void forEach(Consumer<? super T> action);//用来遍历
long count();//返回流中的元素个数
Stream流的特点:只能被使用一次,第一个Stream流调用方法完毕,返回一个Stream流,此时就不能再用了
- 集合元素处理练习:day0721/StreamDemo.java:使用Stream实现集合的元素处理
14.2 方法引用
在使用lambda表达式的时候,我们实际上传递进去的代码是一种解决方案:拿什么参数做什么操作,那么考虑一种情况:我们在lambda中所指定的操作已经有地方存在相同方案,则没有必要再写重复逻辑。
使用前提:对象和方法都是已经存在的
::
为引用运算符,它所在的表达式被称为方法引用lambda: s-> System.out.println(s);
方法引用:System.out::println
1
2
3
4
5
6
7private static void printString(Printable data){
data.print("Hello");
}
psvm(){
printString(System.out::println);
}通过对象名引用成员方法(前提:对象存在,成员方法存在)
1 | public static void printString(Printable p){ |
通过类名引用静态成员方法(前提:类存在,静态成员方法存在)
格式类似于上面,将对象改为类名
通过super引用父类成员方法(前提:super存在,成员方法存在)
格式类似
通过this引用本类成员方法
类的构造器(构造方法)的引用
类名称::new
数组的构造器引用