Java的基础学习总结(下)
来源:001_Java语言发展史_哔哩哔哩_bilibili
IO流
一、File
File类概述
File是文件和目录路径名的抽象表示。文件和目录是可以通过File封装成对象的。对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径,将来要通过具体的操作把这个路径的内容转换为具体存在的。
File类创建功能
不管是目录还是文件,重名不能创建。目录和文件,不是看路径的后缀来判断,创建的方式不同,mkdir创建的是目录,creatNewFile创建的是文件。
File类判断和获取功能
File类删除功能
绝对路径:完整的路径名,带盘符,例如E:\itcast\java.txt
相对路径:必须使用取自其他路径名的信息进行解释,例如myFile\java.txt(在idea项目里的相对路径可以在Run/Edit Configurations的Working directory里修改)
删除目录,要先删除目录里面的内容,再删除目录。
递归
以编程的角度,递归指的是方法定义中调用方法本身的现象。递归解决问题的思路,把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算。
递归解决问题要找到两个内容:
- 递归出口,否则会出现内存溢出
- 递归规则,与原问题相似的规模较小的问题
二、字节流
IO流概述和分类
IO流:输入、输出(Input,Output);流:是一种抽象概念,是对数据传输的总称,也就是数据在设备间的传输称为流,流的本质是数据传输。IO流就是用来处理设备间数据传输的。常见的应用:文件复制,文件上传,文件下载。
按照数据流向:输入流(读数据)、输出流(写数据)
按照数据类型:字节流(字节输入输出流)、字符流(字符输入输出流)
字节流
字节流抽象基类:InputStream、OutputStream
InputStream:这个抽象类是表示字节输入流所有类的超类
OutputStream:这个抽象类是表示字节输出流所有类的超类
子类都是以其父类名作为子类名的后缀
FileOutputStream:文件输出流,用于将数据写入File
构造方法:FileOutputStram(String name),创建文件输出流以指定的名称写入文件。
使用字节输出流写数据的步骤
- 创建字节输出流对象(调用系统功能创建了文件,创建字节输出流对象,让字节输出流对象指向文件)
- 调用字节输出流对象的写数据方法
- 释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源 close)
字节流写数据的3种方式
如果不是从文件开头写入而是在文章的末尾追加,需要使用构造方法public FileOutPutStream(String name,boolean append),第二个参数为true。
字节流写数据加异常处理
finally:在异常处理时提供finally块来执行所有清除操作,比如IO流中的释放资源。被finally控制的语句一定会执行,除非JVM退出。
FileOutputStream fos = null;
try {
fos = new FileOutputStream(new File("an.txt"));
fos.write("ananisyours".getBytes());
}catch (IOException e){
e.printStackTrace();
}finally {
if(fos!=null){
try {
fos.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
字节流读数据(一次读一个字节数据)
FileInputStream:从文件系统中的文件获取输入字节。构造方法FileInputStream(String name)通过打开与实际文件的连接来创建一个FileInputStram,该文件由文件系统中的路径名name命名。
使用字节输入流读数据的步骤:
- 创建字节输入流对象
- 调用字节输入流对象的读数据方法
- 释放资源
FileInputStream fis = new FileInputStream("anan.txt");
int by;
while ((by=fis.read())!=-1){
System.out.print((char)by);
}
fis.close();
字节流读数据(一次读一个字节数组数据)
FileInputStream fis = new FileInputStream("anan.txt");
byte[] bys = new byte[5];
int len;
len=fis.read(bys);
System.out.println(len);
System.out.println(new String(bys));
len=fis.read(bys);
System.out.println(len);
System.out.println(new String(bys));
len=fis.read(bys);
System.out.println(len);
System.out.println(new String(bys));
/* 5
hello
5
wor
4
ld
r*/
// 覆盖数据
fis.close();
改进:
FileInputStream fis = new FileInputStream("anan.txt");
byte[] bys = new byte[1024];
int len;
while ((len=fis.read(bys))!=-1){
System.out.print(new String(bys,0,len));
}
/* hello
world*/
fis.close();
字节缓冲流
- BufferedOutputStream:该类实现缓冲输出流。通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致系统底层的调用。
- BufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组。当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节。
构造方法:
- 字节缓冲输出流:BufferedOutputStream(OutputStrem out)
- 字节缓冲输入流:BufferedInputStream(InputStream in)
为什么构造方法需要的是字节流,而不是具体的文件或者路径呢? 因为字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作。
四种方式复制文件的比较
基本字节流一次读写一个字节 最慢
基本字节流一次读写一个字节数组
字节缓冲流一次读写一个字节
字节缓冲流一次读写一个字节数组 最快
三、字符流
字符流=字节流+编码表。一个汉字存储,如果是GBK编码,占用2个字节;如果是UTF-8编码,占用3个字节。无论哪种编码,第一个字节都是负数。
计算机储存的信息都是二进制表示的。在屏幕上看到的英文、汉字等字符是二进制数据转换之后的结果。
按某种规则,字符存储到计算机,称为编码。将二进制按某种规则解析显示出来,称为解码。这里强调下,按照A编码存储,必须按照A编码解析,这样才能显示正确的文本符号,否则会乱码。
字符编码:就是一套自然语言的字符与二进制之间的对应规则。(A,65)
字符集:是一个系统支持的所有字符的集合,包括各国文字、标点符号、图形符号、数字等。计算机要准确存储和识别,就需要进行字符编码。一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集,GBXXX字符集(GBK常见的中文码表),Unicode字符集。
字符串中的编码解码问题
编码:
- byte[] getBytes() 使用平台默认字符集将String编码为一系列字节,将结果存储到新的字节数组中。
- byte[] getBytes(String charsetName):使用指定的字符集将该String编码为一系列字节,将结果存储到新的字节数组中。
解码:
- String(byte[] bytes):通过使用平台的默认字符集解码指定的字节数组来构造新的String
- String(byte[] bytes,String charsetName):通过指定的字符集解码指定的字节数组来构造新的String
字符流中的编码解码问题
字符流抽象基类:
- Reader:字符输入流的抽象类
- Writer:字符输出流的抽象类
字符流中和编码解码问题相关的两个类:
- InputStreamReader
- OutputStreamWriter
字符流写数据的5种方式
字符流读数据的2种方式
转换流的名字比较长,为了简化书写,转换流提供了对应的子类:
- FileReader:用于读取字符文件的便捷类 FileReader(String fileName)
- FilrWriter:用于写入字符文件的便捷类 FileWriter(String fileName)
字符缓冲流
- BufferdWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组,字符串的高效写入。可以指定缓冲区大小或接受默认大小。
- BufferdReader:从字符输入流读取文本,缓冲字符,以提供字符,数组,行的高效读取。可以指定缓冲区大小或使用默认大小。
构造方法: BufferedWriter(Writer out) BufferedReader(Reader in)
字符缓冲流特有功能
BufferedWriter:void newLine() 写一行行分割符,行分割符字符串由系统属性定义。
BufferdReader:String readLine() 读一行文字,结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null
IO流小结
- 字节流(字节输入流+字节输出流)
字节输入流InputStream
(FileInputStream+BufferedInputStream)
字节输出流OutputStream
(FileOutputStream+BufferedOutputStream)
字节流可以复制任意文件数据,4种方式一般采用字节缓冲流一次读写一个字节数组的方式。
- 字符流(字符输入流+字符输出流)
字符输入流Reader
(InputStreamReader+BufferedReader)
字符输出流Writer
(OutputStreamWriter+BuffededWriter)
为了简写,使用子类FileReader、FileWriter
字符流只能复制文本数据,5种方式一般采用字符缓冲流的特有功能。
复制文件的异常处理
四、特殊操作流
标准输入输出流
System中两个静态的成员变量:
- InputStream in 标准输入流 通常该流对应于键盘输入或由主机环境或用户指定的另一个输入源。
- PrintStream out 标准输出流 通常该流对应于显示输出或由主机环境或用户指定的另一个输出目标
自己实现键盘录入数据
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
Java提供了一个类实现键盘录入 Scanner
Scanner sc = new Scanner(System.in);
System.out的本质是一个字节输出流 PrintStream类的方法,System.out都可以使用。
PrintStream ps = System.out;
打印流
-
字节打印流 PrintStream PrintStream(String fileName)使用指定的文件名创建新的打印流。使用继承父类的方法写数据,查看的时候会转码;使用自己的特有方法写数据,查看的数据原样输出。
-
字符打印流 PrintWriter 打印流的特点,只负责输出数据,不负责读取数据;有自己的特有方法。
对象序列化流
对象序列化:就将对象保存到磁盘中,或者在网络中传输对象。这种机制就是使用一个字节序列表示一个对象,该字节序列包括:对象的类型、对象的数据、对象的属性等信息。字节序列写到文件之后,相当于文件中持久保存了一个对象的信息。反之,该字节序列还可以从文件中读取回来,重构对象,对他进行反序列化。
实现序列化和反序列化就要使用对象序列化流和对象反序列化流:
- 对象序列化流 ObjectOutputStream
将Java对象的原始数据类型和图形写入OutputStream。可以使用ObjectInputStream读取(重构)对象。可以通过使用流的文件来实现对象的持久存储。如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象。
构造方法:ObjectOutputStream(OutputStream out):创建一个写入指定的OutputStream的ObjectOutputStream
序列化对象的方法:void writeObject(Object obj):将指定的对象写入ObjectOutputStream
注意:一个对象要被序列化,该对象所属的类必须实现Serializable接口。Serializable是一个标记接口,实现该接口,不需要重写任何方法
- 对象反序列化流 ObjectInputStream
ObiectInputStream反序列化先前使用ObiectOutputStream编写的原始数据和对象。 构造方法:ObiectlnputStream(InputStream in):创建从指定的InputStream读取的ObiectlnputStream 反序列化对象的方法: Object readObject():从ObjectInputStream读取一个对象
问题
如果用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?
会抛出异常:java.io.InvalidclassException
当序列化运行时检测到类中的以下问题之一时抛出:
1、类的串行版本与从流中读取的类描述符的类型不匹配
【解决办法】给对象所属的类加一个serialVersionUID,即给对象所属的类加一个值: private static final long serialVersionUID = 42L;
2、该类包含未知的数据类型
3、该类没有可访问的无参数构造函数
如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
【解决办法】给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
Properties
是一个Map体系的集合类
Properties可以保存到流中或从流中加载
作为集合的特有方法
和IO流结合的方法
多线程
一、实现多线程
进程:正在运行的程序
是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源。
线程:是进程中的单个顺序控制流,是一条执行路径。
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序
多线程的实现方式
方式1:继承Thread类
- 定义一个类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程
为什么要重写run)方法?
因为run()是用来封装被线程执行的代码
run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用。
start():启动线程,然后由JVM调用此线程的run()方法。
设置和获取线程的名称
Thread类中设置和获取线程名称的方法:
void setName(String name):将此线程的名称更改为等于参数name
String getName():返回此线程的名称。
通过构造方法也可以设置线程名称。
如何获取main()方法所在的线程名称?
public static Thread currentThread():返回对当前正在执行的线程对象的引用
线程调度
线程有两种调度模型:
- 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
- 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些。 Java使用的是抢占式调度模型
假如计算机只有一个CPU,那么CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的。
Thread类中设置和获取线程优先级的方法:
public final int getPriority():返回此线程的优先级
public final void setPriority(int newPriority):更改此线程的优先级
线程默认优先级(Thread.NORM PRIORITY)是5;线程优先级的范围是:1-10。(Thread.MIN_PRIORITY)(Thread.MAX_PRIORITY)
线程优先级高仅仅表示线程获取的CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果
线程控制
线程生命周期
多线程的实现方式2
方式2:实现Runnable接口
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程
多线程的实现方案有两种:
- 继承Thread类
- 实现Runnable接口(推荐)
相比继承Thread类,实现Runnable接口的好处:
- 避免了Java单继承的局限性
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
二、线程同步
判断多线程程序是否会有数据安全问题的标准:
- 是否是多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据
如何解决多线程安全问题呢?
基本思想:让程序没有安全问题的环境,破坏其中一个条件。因此把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
同步代码块
锁多条语句操作共享数据,可以使用同步代码块实现
【格式】synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁。
synchronized (任意对象){
多条语句操作共享数据的代码
}
同步的好处和弊端:
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
同步方法,就是把synchronized关键字加到方法上。
【格式】修饰符 synchronized 返回值类型 方法名(方法参数){}
同步方法的锁对象是什么呢? this
同步静态方法:就是把synchronized关键字加到静态方法上
【格式】修饰符 static synchronized 返回值类型 方法名(方法参数){}
同步静态方法的锁对象是什么呢? 类名.class
线程安全的类
与此同时,Collections类有synchronizedList、synchronizedMap方法。
Lock锁
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
Lock中提供了获得锁和释放锁的方法:
void lock():获得锁
void unlock():释放锁(一般用finally包起来)
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。
ReentrantLock的构造方法:
ReentrantLock():创建一个ReentrantLock的实例
三、生产者消费者
为了体现生产和消费过程中的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法在Object类中。
Object类的等待和唤醒方法(给奶箱送牛奶案例):
网络编程
网络编程入门
计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
网络编程:
在网络通信协议下,实现网络互连的不同计算机上运行的程序间可以进行数据交换。
网络编程三要素:IP、端口、协议
IP地址
网络中设备的唯一标识
常用命令:
ipconfig:查看本机IP地址
ping lP地址:检查网络是否连通
特殊IP地址:127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用
为了方便我们对IP地址的获取和操作,Java提供了一个类
InetAddress:此类表示Internet协议(IP)地址
端口
设备上应用程序的唯一标识
协议
计算机网络中,连接和通信的规则被称为网络通信协议
UDP协议:用户数据报协议(User Datagram Protocol)
TCP协议:传输控制协议(Transmission Control Protocol)
三次握手
UDP通信程序
UDP通信原理 UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送、接收数据的对象。因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念。 Java提供了DatagramSocket类作为基于UDP协议的Socket。
UDP发送数据数据的步骤:
- 创建发送端的Socket对象 DatagramSocket()
- 创建数据,并把数据打包 DatagramPacket(byte[] buf, int length, InetAddress address, int port)
- 调用DatagramSocket对象的方法发送数据 void send(DatagramPacket p)
- 关闭发送端 void close()
UDP接收数据的步骤:
- 创建接收端的Socket对象 DatagramSocket(int port)
- 创建一个数据包,用于接收数据 DatagramPacket(byte[] buf, int length)
- 调用DatagramSocket对象的方法接收数据 void receive(DatagramPacketp)
- 解析数据包,并把数据在控制台显示
byte[]getData()
int getLength() - 关闭接收端 void close()
TCP通信程序
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象,从而在通信的两端形成网络虚拟链路,—旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。
Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。Java为客户端提供了Socket类,为服务器端提供了ServerSocket类。
TCP发送数据的步骤
- 创建客户端的Socket对象 Socket(String host, int port)
- 获取输出流,写数据 OutputStream getOutputStream()
- 释放资源 void close()
TCP接收数据的步骤
- 创建服务器端的Socket对象 ServerSocket(int port)
- 监听客户端连接,返回一个Socket对象 Socket accept()
- 获取输入流,读数据,并把数据显示在控制台 InputStream getInputStream()
- 释放资源 void close()
Lambda表达式
函数式编程思想概述 在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”;面向对象思想强调“必须通过对象的形式来做事情”;函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”。 要学习的Lambda表达式就是函数式思想的体现。
匿名内部类:
Lambda表达式:
组成Lambda表达式的三要素:形式参数,箭头,代码块
Lambda表达式的使用前提
- 有一个接口
- 接口中有且仅有一个抽象方法
Lambda表达式的省略模式
省略规则:
- 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
- 如果参数有且仅有一个,那么小括号可以省略
- 如果代码块的语句只有一条,可以省略大括号和分号,甚至是return
Lambda表达式的注意事项
- 使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
- 必须有上下文环境,才能推导出Lambda对应的接口
根据局部变量的赋值得知Lambda对应的接口:Runnable r = () -> System.out.printIn("Lambda表达式");
根据调用方法的参数得知Lambda对应的接口: new Thread(()-> System.out.printIn("Lambda表达式")).start();
Lambda表达式和匿名内部类的区别
- 所需类型不同
匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
Lambda表达式:只能是接口 - 使用限制不同
如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式 - 实现原理不同
匿名内部类:编译之后,产生一个单独的.class字节码文件
Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成
接口组成更新
常量 public static final
抽象方法 public abstract
默认方法(Java 8)
静态方法(Java 8)
私有方法(Java 9)
接口中默认方法
【定义格式】public default 返回值类型 方法名(参数列表){}
范例: public default void show() { }
接口中默认方法的注意事项:
public可以省略,default不能省略!
默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉default关键字
接口中静态方法
【定义格式】public static返回值类型 方法名(参数列表){}
范例: public static void show() { }
接口中静态方法的注意事项:
静态方法只能通过接口名调用,不能通过实现类名或者对象名调用public可以省略, static不能省略
接口中私有方法
【定义格式】
格式1: private 返回值类型 方法名(参数列表){}
范例1: private void show() {}
格式2: private static 返回值类型 方法名(参数列表){}
范例2: private static void method(){ }
接口中私有方法的注意事项:
默认方法可以调用私有的静态方法和非静态方法
静态方法只能调用私有的静态方法(常用于共享部分的代码)
方法引用
方法引用符::
该符号为引用运算符,而它所在的表达式被称为方法引用
推导与省略
如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导
如果使用方法引用,也是同样可以根据上下文进行推导
方法引用是Lambda的孪生兄弟
Lambda表达式支持的方法引用
常见的引用方式:
引用类方法
引用对象的实例方法
引用类的实例方法
引用构造器
引用类方法
引用类方法,其实就是引用类的静态方法。Lambda表达式被类方法替代的时候,它的形式参数全部传递给静态方法作为参数
【格式】类名::静态方法
范例: Integer::parselnt
Integer类的方法: public static int parseInt(String s)将此String转换为int类型数据
引用对象的实例方法
引用对象的实例方法,其实就引用类中的成员方法。Lambda表达式被对象的实例方法替代的时候,它的形式参数全部传递给该方法作为参数。
【格式】对象::成员方法
范例:"HelloWorld"::toUpperCase
String类中的方法: public String toUpperCase()将此 String所有字符转换为大写
引用类的实例方法
引用类的实例方法,其实就是引用类中的成员方法。Lambda表达式被类的实例方法替代的时候,第一个参数作为调用者,后面的参数全部传递给该方法作为参数
【格式】类名::成员方法
范例: String:substring
String类中的方法: public String substring(int beginIndex,int endIndex)
从beginIndex开始到endIndex结束,截取字符串。返回一个子串,子串的长度为endIndex-beginIndex
引用构造器
引用构造器,其实就是引用构造方法。Lambda表达式被构造器替代的时候,它的形式参数全部传递给构造器作为参数
格式:类名::new
范例: Student::new
函数式接口
函数式接口概述
函数式接口:有且仅有一个抽象方法的接口。Java中的函数式编程体现就是Lambda表达式,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
如何检测一个接口是不是函数式接口呢?
@Functionallnterface
放在接口定义的上方:如果接口是函数式接口,编译通过;如果不是,编译失败
注意
我们自己定义函数式接口的时候,@Functionallnterface是可选的,就算我不写这个注解,只要保证满足函数式接口定义的条件,也照样是函数式接口。但是,建议加上该注解
函数式接口作为方法的参数
如果方法的参数是一个函数式接口,我们可以使用Lambda表达式作为参数传递 startThread(()-> System.out.println(Thread.currentThread().getName() +"线程启动了"));
函数式接口作为方法的返回值
如果方法的返回值是一个函数式接口,我们可以使用Lambda表达式作为结果返回。private static Comparator< String > getComparator() {
return (s1, s2) -> s1.length() - s2.length();
}
常用的函数式接口
Java 8在java.util.function包下预定义了大量的函数式接口供我们使用我们重点来学习下面的4个接口
- Supplier接口
- Consumer接口
- Predicate接口
- Function接口
Supplier接口
Supplier< T >:包含一个无参的方法
T get():获得结果
该方法不需要参数,它会按照某种实现逻辑(由Lambda表达式实现)返回一个数据
Supplier< T >接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用。
Consumer接口
Consumer< T >:包含两个方法
void accept(T t):对给定的参数执行此操作
default Consumer< T > andThen(Consumer after):返回一个组合的Consumer,依次执行此操作,然后执行after操作。Consumer< T >接口也被称为消费型接口,它消费的数据的数据类型由泛型指定。
Predicate接口
Predicate< T >:常用的四个方法
boolean test(T t):对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值
default Predicate< T > negate():返回一个逻辑的否定,对应逻辑非
default Predicate< T > and (Predicate other):返回一个组合判断,对应短路与
default Predicate< T > or (Predicate other):返回一个组合判断,对应短路或
Predicate< T >接口通常用于判断参数是否满足指定的条件
Function接口
Function<T,R>︰常用的两个方法
R apply(T t):将此函数应用于给定的参数
default < V > Function andThen(Function after):返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果
Function<T,R>接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),然后返回一个新的值
Stream流
Stream流把真正的函数式编程风格引入到Java中
Stream流的生成方式
Stream流的使用
- 生成流 通过数据源(集合,数组等)生成流list.stream()
- 中间操作 一个流后面可以跟随零个或多个中间操作,其目的主要是打开流,做出某种程度的数据过滤/映射,然后返回一个新的流,交给下一个操作使用 filter()
- 终结操作 一个流只能有一个终结操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作forEach()
Stream流的常见生成方式
- Collection体系的集合可以使用默认方法stream()生成流 default Stream< E > stream()
- Map体系的集合间接的生成流
- 数组可以通过Stream接口的静态方法of(T... values)生成流
Stream流的常见中间操作方法
1、Stream< T > filter(Predicate predicate):用于对流中的数据进行过滤
Predicate接口中的方法 boolean test(T t):对给定的参数进行判断,返回一个布尔值
2、Stream< T > limit(long maxSize):返回此流中的元素组成的流,截取前指定参数个数的数据
3、Stream< T > skip(long n):跳过指定参数个数的数据,返回由该流的剩余元素组成的流
4、static< T > Stream< T > concat(Stream a, Stream b)合并a和b两个流为一个流
5、stream< T > distinct():返回由该流的不同元素(根据Object.equals(Object))组成的流
6、Stream< T > sorted():返回由此流的元素组成的流,根据自然顺序排序
7、Stream< T > sorted(Comparator comparator):返回由该流的元素组成的流,根据提供的Comparator进行排序
8、< R > Stream< R> map(Function mapper):返回由给定函数应用于此流的元素的结果组成的流
Function接口中的方法 R apply(T t)
8、IntStream mapTolnt(ToIntFunction mapper):返回一个IntStream其中包含将给定函数应用于此流的元素的结果
lntStream表示原始int流
TolntFunction接口中的方法 int applyAslnt(T value)
Stream流的常见终结操作方法
1、void forEach(Consumer action):对此流的每个元素执行操作
Consumer接口中的方法 void accept(T t):对给定的参数执行此操作
2、long count():返回此流中的元素数
Stream流的收集操作
对数据使用Stream流的方式操作完毕后,我想把流中的数据收集到集合中,该怎么办呢?
Stream流的收集方法 R collect(Collector collector)
但是这个收集方法的参数是一个Collector接口
工具类Collectors提供了具体的收集方式
1、public static < T> Collector tolist():把元素收集到List集合中
2、public static < T> Collector toSet():把元素收集到Set集合中
3、public static Collector toMap(Function keyMapper,Function valueMapper):把元素收集到Map集合中
反射
类加载器
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载,类的连接,类的初始化这三个步骤来对类进行初始化。如果不出现意外情况,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或者类初始化
类的初始化
类的初始化步骤:
- 假如类还未被加载和连接,则程序先加载并连接该类
- 假如该类的直接父类还未被初始化,则先初始化其直接父类(系统对直接父类的初始化步骤也遵循初始化步骤)
- 假如类中有初始化语句,则系统依次执行这些初始化语句
类的初始化时机:
- 创建类的实例
- 调用类的类方法
- 访问类或者接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
类加载器
类加载器的作用:负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象
ClassLoader:是负责加载类的对象
类加载器的继承关系: System的父加载器为Platform,而Platform的父加载器为Bootstrap
ClassLoader中的两个方法:
- static ClassLoader getSystemClassLoader():返回用于委派的系统类加载器
- ClassLoader getParent():返回父类加载器进行委派
反射
Java反射机制:是指在运行时去获取一个类的变量和方法信息。然后通过获取到的信息来创建对象,调用方法的一种机制。由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍然可以扩展。
获取Class类的对象
我们要想通过反射去使用一个类,首先我们要获取到该类的字节码文件对象,也就是类型为Class类型的对象这里我们提供三种方式获取Class类型的对象
1、使用类的class属性来获取该类对应的Class对象。举例: Student.class将会返回Student类对应的Class对象
2、调用对象的getClass()方法,返回该对象所属类对应的Class对象。该方法是Object类中的方法,所有的Java对象都可以调用该方法。
3、使用Class类中的静态方法forName(String className),该方法需要传入字符串参数,该字符串参数的值是某个类的全路径,也就是完整包名的路径
反射获取构造方法并使用
基本数据类型也可以通过.class得到对应的Class类型
对于私有的构造方法创建对象,暴力反射:public void setAccessible(boolean flag):值为true,取消访问检查
反射获取成员变量并使用
Class类中用于获取成员变量的方法:
Field类中用于给成员变量赋值的方法:
void set(Object obj, Object value):给obj对象的成员变量赋值为value。
存在暴力反射
反射获取成员方法并使用
Class类中用于获取成员方法的方法:
Method类中用于调用成员方法的方法:
Object invoke(Object obj, Object...args):调用obj对象的成员方法,参数是args,返回值是Object类型
存在暴力反射
反射之越过泛型检查
反射之通过配置文件运行类的方法
模块化
模块的基本使用步骤:
模块服务的使用
模块服务的使用步骤:
进制
一、进制基础知识
进位制,是一种记数方式,也称为进位计数法或位值计数法 R进制:由O~(R-1)组成,并且每一位上的数据逢R进1
二、进制转换
R进制到十进制的转换
规则:按权展开法(系数 * 基数的权次幂相加)
十进制到R进制的转换
规则:重复相除法(除基取余,直到商为0,余数反转)
快速转换
2 <-> 10 【8421码】
2 <-> 8 【3合一】
2 <-> 16 【4合一】
Java内置的进制转换:
三、有符号数据表示
计算机中的数据:0表示正数1表示负数
而对于计算机识别的数据来说,0和1本身也表示数据值,那么我们怎么判断他是数值位还是符号位呢?
规定:符号位位于数值第一位
有符号数据表示法
原码表示法:是最简单的机器数表示法。用最高位表示符号位,0表示正号,1表示负号。其余位表示数值的大小。
反码表示法:正数的反码和原码相同。负数的反码就是它的原码除符号位外,按位取反(1变0,0变1)
补码表示法:正数的补码和原码相同。负数的补码等于反码+1
在计算机中,存储和运算采用的都是补码进行的。计算过程中,符号进位舍弃。
四、整数强制转换之数据溢出
符号位不变,补码到反码是减1,反码到原码其余取反。
五、浮点数存储
二进制浮点数转换为十进制浮点数转换
规则:按权展开法(系数 * 基数的权次幂相加)
十进制浮点数转换为二进制浮点数转换
规则:整数部分重复相除法,小数部分重复相乘法
六、浮点数运算之结果有误
浮点数运算,在存储和运算过程都可能会有精度的丢失,故出现的结果和我们以为的结果就会有出入在实际开发中不太建议直接使用float或者double做运算。
使用java.math.BigDecimal中的方法即可
构造方法: BigDecimal(String val)
成员方法: public BigDecimal subtract(BigDecimal subtrahend):减法操作。其他方法,看文档。
位运算符
位运算就是直接对整数在内存中的二进制位进行操作。
应用:
- 计算2*8,可以写2<<3;
- 判断一个数是偶数,a&1==0;
- 交换a和b,数和本身异或是0,数和0异或是本身。 a=a^b; b=a^b; a=a^b;
- 找重复元素
java8 日期API
Date如果不格式化,输出的日期可读性差;而Java8的时间类直接输出可读性好; Date存在线程安全问题;而Java8的时间类都是线程安全的
LocalDateTime格式化和解析
设计模式
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
设计模式和具体的语言无关,学习设计模式就是要建立面向对象的思想,尽可能的面向接口编程,低耦合,高内聚,使设计的程序可复用
单例设计模式
单例模式就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供。在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
(例如Runtime类,exec方法可以运行命令)
1、饿汉式(类一加载就创建对象)【推荐,不会出现线程安全问题】
2、懒汉式(用的时候,才创建对象)【防止线程安全,在getTeacher()方法前加synchronized】
枚举
是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内。举例:一周只有7天,一年只有12个月等。回想单例设计模式:单例类是一个类只有一个实例,那么多例类就是一个类有多个实例,但不是无限个数的实例,而是有限个数的实例,这才能是枚举类
自己定义一个枚举类,比较麻烦,所以,java就提供了枚举类供我们【格式】只有枚举项的枚举类
public enum枚举类名{
枚举项1,枚举项2,枚举项3...;
}
注意事项
BigDecimal
用于小数的精确运算,参数是字符串
除法运算时:
注解
注解(Annotation),也叫元数据,一种代码级别的说明,它是JDK1.5及以后版本引入的一个特性·它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明。注解:用来说明程序,给计算机看的。
常见的注解:
元注解
对注解进行注解的注解。也就是写在注解上面的注解
自定义注解
使用注解
- 如果注解有多个属性,则可以在注解括号中用“,”号隔开分别给对应的属性赋值
- 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值
- 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可
- 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{} 可以省略
解析注解
注解可以替代使用配置文件运行类中方法。
m.invoke(obj);
XML
XML的全称为(EXtensible Markup Language),是一种可扩展的标记语言。
标记语言:通过标签来描述数据的一门语言(标签有时我们也将其称之为元素) 可扩展:标签的名字是可以自己定义的
XML的作用
- 用于进行存储数据和传输数据(把数据按照xml文件的格式存储起来,并且可以把xml文件作为数据的载体在多个系统之间进行传输)
- 作为软件的配置文件(可以把软件在运行时所需要的一些信息按照xml文件的格式配置到文件中)
XML语法规则
XML解析\
所谓的解析就是从xml中获取到数据 常见的解析思想:DOM(Document Obiect Model)文档对象模型,就是把文档的各个组成部分看做成对应的对象。
单元测试
在程序中,一个单元可以是一个完整的模块,但它通常是一个单独的方法或者程序。
在面向对象的编程中,一个单元通常是整个界面,例如类,但可能是单个方法。 JUnit是一个Java编程语言的单元测试框架
通过先为最小的可测试单元编写测试,然后编写这些单元之间的复合行为,就可以为复杂的应用程序建立全面的测试
编码约定:
- 类放在test包中
- 在test包下创建和原包名相同的包
- 类名用xxxTest结尾
- 方法用testXxx命名
断言
常用注解