Java SE笔记(其三)

IO流

File类

  • java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关
  • File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。
  • 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
  • File对象可以作为参数传递给流的构造器

常用构造器:

public File(String pathname)
//以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储(Idea软件中默认是当前工程,单元测试模块则默认为当前module)

public File(String parent,String child)
//以parent为父路径,child为子路径创建File对象。

public File(File parent,String child)
//根据一个父File对象和子文件路径创建File对象
  • 路径中的每级目录之间用一个 路径分隔符隔开。
    路径分隔符和系统有关:
    windows和DOS系统默认使用“\”来表示
    UNIX和URL使用“/”来表示
    Java程序支持跨平台运行,因此路径分隔符要慎用。
    为了解决这个隐患,File类提供了一个常量:
    public static final String separator。根据操作系统,动态的提供分隔符
    在这里插入图片描述

常用方法

//File 类的获取功能
public String getAbsolutePath():获取绝对路径

public String getPath() :获取路径

public String getName() :获取名称

public String getParent():获取上层文件目录路径。若无,返回null

public long length() :获取文件长度(即:字节数)。不能获取目录的长度。

public long lastModified() :获取最后一次的修改时间,毫秒值

public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组

public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
//File 类的重命名功能
public boolean renameTo(File dest):把文件重命名为指定的文件路径
//要返回true,则dest不能在硬盘中存在,调用该方法的File对象必须在硬盘中存在
//File 类的判断功能
public boolean isDirectory():判断是否是文件目录
public boolean isFile() :判断是否是文件
public boolean exists() :判断是否存在
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏
 //File 类的创建功能
public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false

public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。

public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建
//注意事项:如果你创建文件或者 文件 目录没有 写 盘符路径 , 那么 , 默认在项目路径下
 //File 类的删除功能
public boolean delete():删除文件或者文件夹
//删除注意事项:
//Java中的删除不走 回收站。要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录

在这里插入图片描述

IO流的分类

  • 按操作 数据单位不同分为:字节流(8 bit) ,字符流(16bit)
  • 按数据流的流向不同分为:输入流,输出流
  • 按流的角色的不同分为: 节点流,处理流
    在这里插入图片描述
  1. Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的。
  2. 由这四个类派生出来的子类名称都是以其父类名作为子类名后缀

在这里插入图片描述
在这里插入图片描述
节点流:直接从数据源或目的地读写数据
在这里插入图片描述
处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能
在这里插入图片描述

四个抽象基类

  • InputStream 和 Reader 是所有输入流的基类
  • OutputStream 和 Writer是所有输出类的基类
  • 程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件 IO 资源。
  • FileInputStream 从文件系统中的某个文件中获得输入字节。FileInputStream用于读取非文本数据之类的原始字节流。要读取字符流,需要使用 FileReader
  • FileOutputStream 从文件系统中的某个文件中获得输出字节。FileOutputStream用于写出非文本数据之类的原始字节流。要写出字符流,需要使用 FileWriter

InputStream

int read()
//从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。

//int read(byte[] b)
从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取的字节数。

int read(byte[] b, int off,int len)
//将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值 -1。

public void close() throws IOException
//关闭此输入流并释放与该流关联的所有系统资源。

Reader

int read()
//读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个字节的Unicode码),如果已到达流的末尾,则返回 -1

int read(char[] cbuf)
//将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。

//int read(char[] cbuf,int off,int len)
将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。

public void close() throws IOException
//关闭此输入流并释放与该流关联的所有系统资源。

OutputStream

void write(int b)
//将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255范围的。

void write(byte[] b)
将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该与调用 write(b, 0, b.length) 的效果完全相同。

void write(byte[] b,int off,int len)
//将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。

public void flush()throws IOException
刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立即写入它们预期的目标。

public void close() throws IOException
//关闭此输出流并释放与该流关联的所有系统资源。

Writer

void write(int c)
//写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即写入0 到 65535 之间的Unicode码。

void write(char[] cbuf)
//写入字符数组。

void write(char[] cbuf,int off,int len)
//写入字符数组的某一部分。从off开始,写入len个字符

void write(String str)
//写入字符串。

void write(String str,int off,int len)
//写入字符串的某一部分。

void flush()
//刷新该流的缓冲,则立即将它们写入预期目标。

public void close() throws IOException
//关闭此输出流并释放与该流关联的所有系统资源

节点流

  1. FileInputStream
  2. FileReader
  3. FileOutputStream
  4. FileWriter

读文件操作

注:为了保证流资源成功执行关闭操作,需要用try-catch-finally处理

//1.建立一个流对象,将已存在的一个文件加载进流。
FileReader fr = new FileReader(new File(“Test.txt”));

//2.创建一个临时存放数据的数组。
char[] ch = new char[1024];

//3.调用流对象的读取方法将流中的数据读入到数组中。
fr.read(ch);

//4. 关闭资源。
fr.close();
//通用示例
FileReader fr = null;
try {
	fr = new FileReader(new File("c:\\test.txt"));
	char[] buf = new char[1024];
	int len;
	while ((len = fr.read(buf)) != -1) {
		System.out.print(new String(buf, 0, len));
	}
} catch (IOException e) {
	System.out.println("read-Exception :" + e.getMessage());
} finally {
	if (fr != null) {
		try {
			fr.close();
		} catch (IOException e) {
			System.out.println("close-Exception :" + e.getMessage());
		}
	}
}

写文件操作

//1.创建流对象,建立数据存放文件
FileWriter fw = new FileWriter(new File(“Test.txt”));

//2.调用流对象的写入方法,将数据写入流
fw.write(“atguigu-songhongkang”);

//3.关闭流资源,并将流中的数据清空到文件中。
fw.close();
FileWriter fw = null;
try {
	fw = new FileWriter(new File("Test.txt"));
	fw.write("atguigu-songhongkang");
} catch (IOException e) {
	e.printStackTrace();
} finally {
	if (fw != null)
	try {
		fw.close();
	} catch (IOException e) {
		e.printStackTrace();
	}
}

说明

  • 定义文件路径时,注意:可以用“/”或者“\”。
  • 在写入一个文件时,如果使用构造器FileOutputStream(file),则目录下有同名文件将被覆盖。
  • 如果使用构造器FileOutputStream(file,true),则目录下的同名文件不会被覆盖,在文件内容末尾追加内容。
  • 在读取文件时,必须保证该文件已存在,否则报异常。
  • 字节流操作字节,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt
  • 字符流操作字符,只能操作普通文本文件。最常见的文本文件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意.doc,excel,ppt这些不是文本文件

缓冲流(处理流)

  • 为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用8192个字节(8Kb)的缓冲区。

在这里插入图片描述

  • 缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:
  1. BufferedInputStream
  2. BufferedReader
  3. BufferedOutputStream
  4. BufferedWriter
  • 当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区
  • 当使用BufferedInputStream读取字节文件时,BufferedInputStream会一次性从文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组。
  • 向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。使用方法flush()可以强制将缓冲区的内容全部写入输出流
  • 关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流
  • flush()方法的使用:手动将buffer中内容写入文件
  • 如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出
    在这里插入图片描述
BufferedReader br = null;
BufferedWriter bw = null;
try {
	// 创建缓冲流对象:它是处理流,是对节点流的包装
	br = new BufferedReader(new FileReader("d:\\IOTest\\source.txt"));
	bw = new BufferedWriter(new FileWriter("d:\\IOTest\\dest.txt"));
	String str;
	while ((str = br.readLine()) != null) { // 一次读取字符文本文件的一行字符
		bw.write(str); // 一次写入一行字符串
		bw.newLine(); // 写入行分隔符
	}
	bw.flush(); // 刷新缓冲区
} catch (IOException e) {
	e.printStackTrace();
} finally {
	// 关闭IO流对象
	try {
		if (bw != null) {
			bw.close(); // 关闭过滤流时,会自动关闭它所包装的底层节点流
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
	try {
		if (br != null) {
			br.close();
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
}

转换流(处理流)

  • 转换流提供了在字节流和字符流之间的转换
  • 字节流中的数据都是字符时,转成字符流操作更高效。
  • 很多时候我们使用转换流来处理文件乱码问题。实现编码和解码的功能。

InputStreamReader

  • 实现将字节的输入流按指定字符集转换为字符的输入流。
  • 需要和InputStream“套接”。
  • 构造器
public InputStreamReader(InputStream in)

public InputSreamReader(InputStream in,String charsetName)
如: Reader isr = new InputStreamReader(System.in,”gbk”);

OutputStreamWriter

  • 实现将字符的输出流按指定字符集转换为字节的输出流。
  • 需要和OutputStream“套接”。
  • 构造器
public OutputStreamWriter(OutputStream out)

public OutputSreamWriter(OutputStream out,String charsetName)

在这里插入图片描述

public void testMyInput() throws Exception {
	FileInputStream fis = new FileInputStream("dbcp.txt");
	FileOutputStream fos = new FileOutputStream("dbcp5.txt");
	InputStreamReader isr = new InputStreamReader(fis, "GBK");
	OutputStreamWriter osw = new OutputStreamWriter(fos, "GBK");
	BufferedReader br = new BufferedReader(isr);
	BufferedWriter bw = new BufferedWriter(osw);
	String str = null;
	while ((str = br.readLine()) != null) {
		bw.write(str);
		bw.newLine();
		bw.flush();
	}
	bw.close();
	br.close();
}

标准输入、输出流

  • System.in和System.out分别代表了系统标准的输入和输出设备
  • 默认输入设备是:键盘,输出设备是:显示器
  • System.in的类型是InputStream
  • System.out的类型是PrintStream,其是OutputStream的子类FilterOutputStream 的子类
  • 重定向:通过System类的setIn,setOut方法对默认设备进行改变。
public static void setIn(InputStream in)
public static void setOut(PrintStream out)

打印流

  • 实现将基本数据类型的数据格式转化为字符串输出
  • 打印流:PrintStream和PrintWriter
  • 提供了一系列重载的print()和println()方法,用于多种数据类型的输出
  • PrintStream和PrintWriter的输出不会抛出IOException异常
  • PrintStream和PrintWriter有自动flush功能
  • PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
  • System.out返回的是PrintStream的实例

数据流

  • 为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流。
  • 数据流有两个类:(用于读取和写出基本数据类型、String类的数据)
  • DataInputStream 和 DataOutputStream
  • 分别“套接”在 InputStream 和 和 OutputStream 子类的流上
  • DataInputStream 中的方法
boolean readBoolean() 

byte readByte()

char readChar() 

float readFloat()

double readDouble() 

short readShort()

long readLong() 

int readInt()

String readUTF() 

void readFully(byte[] b)
  • DataOutputStream 中的方法
    将上述的方法的read改为相应的write即可

对象流

  • ObjectInputStream和OjbectOutputSteam
  • 用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
  • 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制
  • 反序列化:用ObjectInputStream类读取基本类型数据或对象的机制
  • ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量

对象的序列化

  • 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原来的Java对象
  • 序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原
  • 序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE的基础。因此序列化机制是JavaEE 平台的基础
  • 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常
    1)Serializable
    2)Externalizable
  • 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
private static final long serialVersionUID;
  • serialVersionUID用来表明类的不同版本间的兼容性。 简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
  • 如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议,显式声明。
  • 简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)

使用对象流序列化对象

  • 若某个类实现了 Serializable 接口,该类的对象就是可序列化的:
    1)创建一个 ObjectOutputStream
    2)调用 ObjectOutputStream 对象的 writeObject( 对象) 方法输出可序列化对象
    3)注意写出一次,操作flush() 一次
  • 反序列化
    1)创建一个 ObjectInputStream
    2)调用 readObject() 方法读取流中的对象
  • 强调:如果某个类的属性不是基本数据类型或 String 类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的Field 的类也不能序列化

理解

  • 实现了Serializable 接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。 这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在Windows 机器上创建一个对象,对其序列化,然后通过网络发给一台Unix 机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。
  • 由于大部分作为参数的类如String 、Integer 等都实现了java.io.Serializable 的接口,也可以利用多态的性质,作为参数使接口更灵活。

RandomAccessFile类

  • RandomAccessFile类即随机存取文件流,声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也可以写。
  • RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意地方来读、写文件
    1)支持只访问文件的部分内容
    2)可以向已存在的文件后追加内容
  • RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。RandomAccessFile 类对象可以自由移动记录指针:
long getFilePointer():获取文件记录指针的当前位置
void seek(long pos):将文件记录指针定位到 pos 位置
  • 构造器:
public RandomAccessFile(File file, String mode)
public RandomAccessFile(String name, String mode)
  • 创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式:
r:  以只读方式打开
rw :打开以便读取和写入
rwd: 打开以便读取和 写入;同步文件内容的更新
rws: 打开以便读取和 写入; 同步文件内容和元数据的更新
  • 如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。 如果模式为rw读写。如果文件不存在则会去创建文件,如果存在则不会创建
  • 读操作:
RandomAccessFile raf = new RandomAccessFile(“test.txt”, “rw”);
raf.seek(5);
byte [] b = new byte[1024];
int off = 0;
int len = 5;
raf.read(b, off, len);
String str = new String(b, 0, len);
System.out.println(str);
raf.close();
  • 写操作:
RandomAccessFile raf = new RandomAccessFile("test.txt", "rw");
raf.seek(5);
//先读出来
String temp = raf.readLine();
raf.seek(5);
raf.write("xyz".getBytes());
raf.write(temp.getBytes());
raf.close();

NIO.2中Path、Paths、Files类的使用

Java NIO概述

  • Java NIO (New IO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
  • Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO
    在这里插入图片描述

Path 、Paths 和Files核心API

  • 随着 JDK 7 的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为 NIO.2。因为 NIO 提供的一些功能,NIO已经成为文件处理中越来越重要的部分
  • 早期的Java只提供了一个File类来访问文件系统,但File类的功能比较有限,所提供的方法性能也不高。而且,大多数方法在出错时仅返回失败,并不会提供异常信息。
  • NIO. 2为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可以看成是File类的升级版本,实际引用的资源也可以不存在。
  • 在以前IO操作都是这样写的:
import java.io.File;
File file = new File("index.html");
  • 但在Java7 中,我们可以这样写:
import java.nio.file.Path;
import java.nio.file.Paths;
Path path = Paths.get("index.html");
  • 同时,NIO.2在java.nio.file包下还提供了Files、Paths工具类,Files包含了大量静态的工具方法来操作文件;Paths则包含了两个返回Path的静态工厂方法。
  • Paths 类提供的静态 get() 方法用来获取 Path 对象:
static Path get(String first, String … more) : 用于将多个字符串串连成路径
static Path get(URI uri): 返回指定uri对应的Path路径
//Path接口 常用方法:
String toString() : 返回调用 Path 对象的字符串表示形式

boolean startsWith(String path) : 判断是否以 path 路径开始

boolean endsWith(String path) : 判断是否以 path 路径结束

boolean isAbsolute() : 判断是否是绝对路径

Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径

Path getRoot() :返回调用 Path 对象的根路径

Path getFileName() : 返回与调用 Path 对象关联的文件名

int getNameCount() : 返回Path 根目录后面元素的数量

Path getName(int idx) : 返回指定索引位置 idx 的路径名称

Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
Path resolve(Path p) :合并两个路径,返回合并后的路径对应的Path对象

File toFile(): 将Path转化为File类的对象
//java.nio.file.Files  用于操作文件或目录的工具类。
//Files类 常用方法:
Path copy(Path src, Path dest, CopyOption … how) : 文件的复制

Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录

Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件

void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错

void deleteIfExists(Path path) : Path对应的文件/目录如果存在,执行删除

Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest位置

long size(Path path) : 返回 path 指定文件的大小
Files类常用方法:用于判断
boolean exists(Path path, LinkOption … opts) : 判断文件是否存在

boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录

boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件

boolean isHidden(Path path) : 判断是否是隐藏文件

boolean isReadable(Path path) : 判断文件是否可读

boolean isWritable(Path path) : 判断文件是否可写

boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在

//Files 常用方法:用于操作内容
SeekableByteChannel newByteChannel(Path path, OpenOption…how): 获取与指定文件的连接,how 指定打开方式。

DirectoryStream<Path> newDirectoryStream(Path path) : 打开 path 指定的目录

InputStream newInputStream(Path path, OpenOption…how):获取InputStream 对象

OutputStream newOutputStream(Path path, OpenOption…how) : 获取OutputStream 对象

网络编程

  • Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序。
  • Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并且 Java 实现了一个跨平台的网络库, 程序员面对的是一个统一的网络编程环境。
  • 计算机网络:
    把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。
  • 网络编程的目的:
    直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。
  • 网络编程中有两个主要的问题:
    1)如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
    2)找到主机后如何可靠高效地进行数据

网络通信要素概述

  • 通信双方地址
    IP
    端口号
  • 一定的规则(即:网络通信协议。有两套参考模型)
    1)OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广
    2)TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。
    在这里插入图片描述
    在这里插入图片描述

IP和端口号

  • IP地址:InetAddress
  1. 唯一的标识 Internet 上的计算机(通信实体)
  2. 本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost
  3. IP地址分类方式1:IPV4 和 IPV6
  4. IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如192.168.0.1
  5. IPV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示,数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
  6. IP地址分类方式2: 公网地址( 万维网使用)和 私有地址( 局域网使用)。192.168.开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用
  7. 特点:不易记忆
  • 端口号标识正在计算机上运行的进程(程序)
  1. 不同的进程有不同的端口号
  2. 被规定为一个 16 位的整数 0~65535。
  3. 端口分类:
    1)公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
    2)注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
    3)动态/ 私有端口:49152~65535。
  4. 端口号与IP 地址的组合得出一个网络套接字:Socket

InetAdress类

  • InetAddress 类没有提供公共的构造器,而是提供了如下几个静态方法来获取InetAddress 实例
public static InetAddress getLocalHost()
public static InetAddress getByName(String host)
  • InetAddress 提供了如下几个常用方法
public String getHostAddress() :返回 IP 地址字符串(以文本表现形式)
public String getHostName() :获取此 IP 地址的主机名
public boolean isReachable(int timeout): :测试是否可以达到该地址

网络协议

  • 网络通信协议

计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。

  • 问题:网络协议太复杂

计算机网络通信涉及内容很多,比如指定源地址和目标地址,加密解密,压缩解压缩,差错控制,流量控制,路由控制,如何实现如此复杂的网络协议呢?

  • 通信协议分层的思想

在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系。各层互不影响,利于系统的开发和扩展

TCP/IP协议簇

  • 传输层协议中有两个非常重要的协议:
  • 传输控制协议TCP(Transmission Control Protocol)
  • 用户数据报协议UDP(User Datagram Protocol)。
  • TCP/IP 以其两个主要协议:传输控制协议(TCP) 和网络互联协议(IP)而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。
  • IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信。
  • TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即物理链路层、IP层、传输层和应用层

TCP

TCP 协议:

  • 使用TCP协议前,须先建立TCP连接,形成传输数据通道
  • 传输前,采用“ 三次握手”方式,点对点通信,是可靠的
  • TCP协议进行通信的两个应用进程:客户端、服务端。
  • 在连接中可进行大数据量的传输
  • 传输完毕,需释放已建立的连接,效率低

UDP 协议:

  • 将数据、源、目的封装成数据包,不需要建立连接
  • 每个数据报的大小限制在64K内
  • 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
  • 可以广播发送
  • 发送数据结束时无需释放资源,开销小,速度快
    在这里插入图片描述
    在这里插入图片描述

Socket

  • 利用套接字(Socket)开发网络应用程序早已被广泛的采用,以至于成为事实上的标准。
  • 网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
  • 通信的两端都要有Socket,是两台机器间通信的端点。
  • 网络通信其实就是Socket间的通信。
  • Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
  • 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
  • Socket分类:
    1)流套接字(stream socket):使用TCP提供可依赖的字节流服务
    2)数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务
  • Socket类的常用构造器:
public Socket(InetAddress address,int port)   //创建一个流套接字并将其连接到指定IP 地址的指定端口号。

public Socket(String host,int port)   //创建一个流套接字并将其连接到指定主机上的指定端口号
  • Socket类的常用方法:
public InputStream getInputStream():返回此套接字的输入流。可以用于接收网络消息

public OutputStream getOutputStream():返回此套接字的输出流。可以用于发送网络消息

public InetAddress getInetAddress():此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。

public InetAddress getLocalAddress():获取套接字绑定的本地地址。 即本端的IP地址

public int getPort():此套接字连接到的远程端口号;如果尚未连接套接字,则返回0。

public int getLocalPort():返回此套接字绑定到的本地端口。 如果尚未绑定套接字,则返回 -1。即本端的端口号。

public void close():关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和OutputStream。

public void shutdownInput():如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。即不能在从此套接字的输入流中接收任何数据。

public void shutdownOutput():禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流,则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。

TCP网络编程

  • Java语言的基于套接字编程分为服务端编程和客户端编程,其通信模型如图所示:
    在这里插入图片描述
  • 客户端Socket 的工作过程包含以下四个基本的步骤
    1)创建 Socket :根据指定服务端的 IP 地址或端口号构造Socket 类对象。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。
    2)到打开连接到 Socket 的输入/ 出流: 使用getInputStream()方法获得输入流,使用getOutputStream()方法获得输出流,进行数据传输
    3)对 按照一定的协议对 Socket 进行读/ 写操作:通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线程。
    4)关闭 Socket:断开客户端到服务器的连接,释放线路
  • 客户端程序可以使用Socket类创建对象, 创建的同时会自动向服务器方发起连接。Socket的构造器是:
Socket(String host,int port)throws UnknownHostException,IOException:向服务器(域名是host。端口号为port)发起TCP连接,若成功,则创建Socket对象,否则抛出异常。

Socket(InetAddress address,int port)throws IOException:根据InetAddress对象所表示的IP地址以及端口号port发起连接。
  • 客户端建立socketAtClient对象的过程就是向服务器发出套接字连接请求
Socket s = new
Socket(“192.168.40.165”,9999);
OutputStream out = s.getOutputStream();
out.write(" hello".getBytes());
s.close();
  • 服务器程序的工作过程包含以下四个基本的步骤:
    1)调用 ServerSocket(int port):创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。
    2)调用 accept():监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。
    3)调用该Socket 类对象的 getOutputStream() 和getInputStream ():获取输出流和输入流,开始网络数据的发送和接收。
    4)关闭ServerSocket 和Socket 对象:客户端访问结束,关闭通信套接字。
  • ServerSocket 对象负责等待客户端请求建立套接字连接,类似邮局某个窗口中的业务员。也就是说, 服务器必须事先建立一个等待客户请求建立套接字的连接的ServerSocket
  • 所谓“接收”客户的套接字请求,就是accept()方法会返回一个 Socket 对象
ServerSocket ss = new ServerSocket(9999);
Socket s = ss.accept ();
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int num = in.read(buf);
String str = new String(buf,0,num);
System.out.println(s.getInetAddress().toString()+”:”+str);
s.close();
ss.close();

UDP网络编程

  • 类 DatagramSocket 和 DatagramPacket 实现了基于 UDP协议网络程序。
  • UDP数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
  • DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。
  • UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接。如同发快递包裹一样。
  • DatagramSocket 类的常用方法:
public DatagramSocket(int port):创建数据报套接字并将其绑定到本地主机上的指定端口。套接字将被绑定到通配符地址,IP 地址由内核来选择。

public DatagramSocket(int port,InetAddress laddr):创建数据报套接字,将其绑定到指定的本地地址。本地端口必须在 0 到 65535 之间(包括两者)。如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地址,IP地址由内核选择。

public void close():关闭此数据报套接字。

public void send(DatagramPacket p):从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的IP 地址和远程主机的端口号。

public void receive(DatagramPacket p):从此套接字接收数据报包。当此方法返回时,DatagramPacket的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。

public InetAddress getLocalAddress():获取套接字绑定的本地地址。

public int getLocalPort():返回此套接字绑定的本地主机上的端口号。

public InetAddress getInetAddress():返回此套接字连接的地址。如果套接字未连接,则返回null。

public int getPort():返回此套接字的端口。如果套接字未连接,则返回-1。
  • DatagramPacket常用方法
public DatagramPacket(byte[] buf,int length):构造 DatagramPacket,用来接收长度为 length 的数据包。 length 参数必须小于等于 buf.length。

public DatagramPacket(byte[] buf,int length,InetAddress address,int port):构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。length参数必须小于等于 buf.length。

public InetAddress getAddress():返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。

public int getPort():返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。

public byte[] getData():返回数据缓冲区。接收到的或将要发送的数据从缓冲区中的偏移量 offset 处开始,持续 length 长度。

public int getLength():返回将要发送或接收到的数据的长度
  • UDP网络通信流程
  1. DatagramSocket与DatagramPacket
  2. 建立发送端,接收端
  3. 建立数据包
  4. 调用Socket的发送、接收方法
  5. 关闭Socket
//发送端
DatagramSocket ds = null;
try {
	ds = new DatagramSocket();
	byte[] by = "hello,atguigu.com".getBytes();
	DatagramPacket dp = new DatagramPacket(by, 0, by.length,InetAddress.getByName("127.0.0.1"), 10000);
	ds.send(dp);
} catch (Exception e) {
	e.printStackTrace();
} finally {
	if (ds != null)
		ds.close();
}
//接收端
DatagramSocket ds = null;
try {
	ds = new DatagramSocket(10000);
	byte[] by = new byte[1024];
	DatagramPacket dp = new DatagramPacket(by, by.length);
	ds.receive(dp);
	String str = new String(dp.getData(), 0, dp.getLength());
	System.out.println(str + "--" + dp.getAddress());
} catch (Exception e) {
	e.printStackTrace();
} finally {
	if (ds != null)
		ds.close();
}

URL编程

URL类

  • URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址。
  • 它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
  • 通过 URL 我们可以访问 Internet 上的各种网络资源,比如最常见的 www,ftp站点。浏览器通过解析给定的 URL 可以在网络上查找相应的文件或其他资源。
  • URL的基本结构由5部分组成:
    在这里插入图片描述
  • 为了表示URL,java.net 中实现了类 URL。我们可以通过下面的构造器来初始化一个 URL 对象:
public URL (String spec):通过一个表示URL地址的字符串可以构造一个URL对象。

public URL(URL context, String spec):通过基 URL 和相对 URL 构造一个 URL 对象。例如:URL downloadUrl = new URL(url, “download.html")

public URL(String protocol, String host, String file); 例如:new URL("http","www.atguigu.com", “download. html");

public URL(String protocol, String host, int port, String file); 例如: URL gamelan = newURL("http", "www.atguigu.com", 80, “download.html");
  • URL类的构造器都声明抛出非运行时异常,必须要对这一异常进行处理,通常是用 try-catch 语句进行捕获。
  • 一个URL对象生成后,其属性是不能被改变的,但可以通过它给定的方法来获取这些属性:
public String getProtocol( ) 获取该URL的协议名
public String getHost( ) 获取该URL的主机名
public String getPort( ) 获取该URL的端口号
public String getPath( ) 获取该URL的文件路径
public String getFile( ) 获取该URL的文件名
public String getQuery( ) 获取该URL的查询名

针对HTTP 协议的URLConnection类

  • URL的方法 openStream():能从网络上读取数据
  • 若希望输出数据,例如向服务器端的 CGI (公共网关接口-Common Gateway Interface-的简称,是用户浏览器和服务器端的应用程序进行连接的接口)程序发送一些数据,则必须先与URL建立连接,然后才能对其进行读写,此时需要使用URLConnection
  • URLConnection:表示到URL所引用的远程对象的连接。当与一个URL建立连接时,首先要在一个 URL 对象上通过方法openConnection() 生成对应的 URLConnection对象。如果连接过程失败,将产生IOException.
URL netchinaren = new URL ("http://www.atguigu.com/index.shtml");
URLConnectonn u = netchinaren.openConnection( );
  • 通过URLConnection对象获取的输入流和输出流,即可以与现有的CGI程序进行交互
public Object getContent( ) throws IOException
public int getContentLength( )
public String getContentType( )
public long getDate( )
public long getLastModified( )
public InputStream getInputStream( )throws IOException
public OutputSteram getOutputStream( )throws IOException

反射

Java反射机制

  • Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

  • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为: 反射
    在这里插入图片描述

  • Java反射机制提供的功能
    在运行时判断任意一个对象所属的类
    在运行时构造任意一个类的对象
    在运行时判断任意一个类所具有的成员变量和方法
    在运行时获取泛型信息
    在运行时调用任意一个对象的成员变量和方法
    在运行时处理注解
    生成动态代理

  • 反射相关的主要API

java.lang.Class: 代表一个类
java.lang.reflect.Method: 代表类的方法
java.lang.reflect.Field: 代表类的成员变量
java.lang.reflect.Constructor: 代表类的构造

理解Class类&获取Class实例*

Class类是什么

在Object类中定义了以下的方法,此方法
将被所有子类继承:

public final Class getClass()
//以上的方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称
  • 对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息
    1)Class本身也是一个类
    2)Class 对象只能由系统建立对象
    3)一个加载的类在 JVM 中只会有一个Class实例
    4)一个Class对象对应的是一个加载到JVM中的一个.class文件
    5)每个类的实例都会记得自己是由哪个 Class 实例所生成
    6)通过Class可以完整地得到一个类中的所有被加载的结构
    7)Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

Class类常用方法

在这里插入图片描述

获取Class 类的实例(四种方法)

1) 前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高

实例:Class clazz = String.class;

2) 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象

实例:Class clazz = “www.atguigu.com”.getClass();

3) 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException

实例:Class clazz = Class.forName(“java.lang.String”);

4)其他方式(不做要求)

ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass(“类的全类名”);

哪些类型可以有Class 对象

(1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[]:数组
(4)enum:枚举
(5)annotation:注解@interface
(6)primitive type:基本数据类型
(7)void

类的加载&ClassLoader的理解

类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化
在这里插入图片描述

  • 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与
  • 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
    1)验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
    2)准备:正式为类变量(static)分配内存并 设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
    3)解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
  • 初始化:
    1)执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
    2)当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
    3)虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。

什么时候会发生类初始化

  • 类的主动引用( 一定会发生类的初始化 )
    1)当虚拟机启动,先初始化main方法所在的类
    2)new一个类的对象
    3)调用类的静态成员(除了final常量)和静态方法
    4)使用java.lang.reflect包的方法对类进行反射调用
    5)当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类

  • 类的被动引用( 不会发生类的初始化 )
    1)当访问一个静态域时,只有真正声明这个域的类才会被初始化
    2)当通过子类引用父类的静态变量,不会导致子类初始化
    3)通过数组定义类引用,不会触发此类的初始化
    4)引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
    在这里插入图片描述

  • 类加载器的作用:
    1)类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
    2)类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象

ClassLoader

类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类型的类的加载器
在这里插入图片描述

//1.获取一个系统类加载器
ClassLoader classloader = ClassLoader.getSystemClassLoader();
System.out.println(classloader);
//2.获取系统类加载器的父类加载器,即扩展类加载器
classloader = classloader.getParent();
System.out.println(classloader);
//3.获取扩展类加载器的父类加载器,即引导类加载器
classloader = classloader.getParent();
System.out.println(classloader);
//4.测试当前类由哪个类加载器进行加载
classloader = Class.forName("exer2.ClassloaderDemo").getClassLoader();
System.out.println(classloader);
//5.测试JDK提供的Object类由哪个类加载器加载
classloader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classloader);
//*6.关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流
InputStream in = null;
in = this.getClass().getClassLoader().getResourceAsStream("exer2\\test.properties");
System.out.println(in);

创建运行时类的对象*

  • 通过Class实例创建类的对象:调用Class对象的newInstance()方法
    要求:
    1)类必须有一个无参数的构造器。
    2)类的构造器的访问权限需要足够
  • 没有无参的构造器就不能创建对象了吗?

不是!只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。
步骤如下:
1)通过Class类的getDeclaredConstructor(Class …parameterTypes)取得本类的指定形参类型的构造器
2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
3)通过Constructor实例化对象。

//1. 根据全类名获取对应的Class 对象
String name = “atguigu.java.Person";
Class clazz = null;
clazz = Class.forName(name);
//2. 调用指定参数结构的构造器,生成Constructor 的实例
Constructor con = clazz.getConstructor(String.class,Integer.class);
//3. 通过Constructor 的实例创建对应类的对象,并初始化类属性
Person p2 = (Person) con.newInstance("Peter",20);
System.out.println(p2);

获取运行时类的完整结构

Field 、Method 、Constructor 、Superclass 、Interface 、Annotation

  • 实现的全部接口
  • 所继承的父类
  • 全部的构造器
  • 全部的方法
  • 全部的Field
//使用反射可以取得:
//1. 实现的全部接口
public Class<?>[] getInterfaces()
//确定此对象所表示的类或接口实现的接口。

//2. 所继承的父类
public Class<? Super T> getSuperclass()
//返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的Class。

//3. 全部的构造器
public Constructor<T>[] getConstructors()
//返回此 Class 对象所表示的类的所有public构造方法。

public Constructor<T>[] getDeclaredConstructors()
//返回此 Class 对象表示的类声明的所有构造方法。
//Constructor类中:
	//取得修饰符: public int getModifiers();
	//取得方法名称: public String getName();
	//取得参数的类型:public Class<?>[] getParameterTypes();

//4. 全部的方法
public Method[] getDeclaredMethods()
//返回此Class对象所表示的类或接口的全部方法

public Method[] getMethods()
//返回此Class对象所表示的类或接口的public的方法
//Method类中:
	public Class<?> getReturnType()取得全部的返回值
	public Class<?>[] getParameterTypes()取得全部的参数
	public int getModifiers()取得修饰符
	public Class<?>[] getExceptionTypes()取得异常信息

//5. 全部的Field
public Field[] getFields()
//返回此Class对象所表示的类或接口的public的Field。

public Field[] getDeclaredFields()
//返回此Class对象所表示的类或接口的全部Field。
//Field方法中:
	public int getModifiers() 以整数形式返回此Field的修饰符
	public Class<?> getType() 得到Field的属性类型
	public String getName() 返回Field的名称。

//6. Annotation 相关
get Annotation(Class<T> annotationClass)
getDeclaredAnnotations()

//7. 泛型相关
//获取父类泛型类型:Type getGenericSuperclass()
//泛型类型:ParameterizedType
//获取实际的泛型类型参数数组:getActualTypeArguments()

//8. 类所在的包 
Package getPackage()

调用运行时类的指定结构*

  • 调用指定方法
    通过反射,调用类中的方法,通过Method类完成。步骤:
    1)通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
    2)之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。
    在这里插入图片描述
Object invoke(Object obj, Object … args)
说明:
1.Object 对应原方法的返回值,若原方法无返回值,此时返回null
2.若原方法若为静态方法,此时形参Object obj可为null
3.若原方法形参列表为空,则Object[] args为null
4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用
方法对象的setAccessible(true)方法,将可访问private的方法。
  • 调用指定属性
    在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作
public Field getField(String name) 返回此Class对象表示的类或接口的指定的public的Field。

public Field getDeclaredField(String name)返回此Class对象表示的类或接口的指定的Field。

在Field 中:
public Object get(Object obj) 取得指定对象obj上此Field的属性内容
public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容
  • 关于setAccessible
    1)Method和Field、Constructor对象都有setAccessible()方法。
    2)setAccessible启动和禁用访问安全检查的开关。
    3)参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
    4)提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
    5)使得原本无法访问的私有成员也可以访问
    6)参数值为false则指示反射的对象应该实施Java语言访问检查。

反射的应用:动态代理

代理设计模式的原理:

使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上

  • 之前讲解过代理机制的操作,属于静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。 最好可以通过一个代理类完成全部的代理功能
  • 动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
  • 动态代理使用场合:调试&远程方法调用
  • 动态代理相比于静态代理的优点:
    抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。

Java 动态代理相关API:

1)Proxy :专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。
2)提供用于创建动态代理类和动态代理对象的静态方法

在这里插入图片描述

动态代理步骤

1)创建一个实现接口InvocationHandler的类,它必须实现invoke方法,以完成代理的具体操作
在这里插入图片描述
2)创建被代理的类以及接口
在这里插入图片描述
3)通过Proxy的静态方法
newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 创建一个Subject接口代理

RealSubject target = new RealSubject();
// Create a proxy to wrap the original implementation
DebugProxy proxy = new DebugProxy(target);
// Get a reference to the proxy through the Subject interface
Subject sub = (Subject) Proxy.newProxyInstance(
Subject.class.getClassLoader(),new Class[] { Subject.class }, proxy);

4)通过 Subject代理调用RealSubject实现类的方法

String info = sub.say(“Peter", 24);
System.out.println(info);

动态代理与AOP(Aspect Orient Programming)

前面介绍的Proxy和InvocationHandler,很难看出这种动态代理的优势,下面介绍一种更实用的动态代理机制
在这里插入图片描述
在这里插入图片描述
改进后的说明:代码段1 、代码段2 、代码段3 和深色代码段分离开了,但代码段1 、2 、3 又和法 一个特定的方法A 耦合了!最理想的效果是:代码块1 、2 、3 既可以执行方法A ,又无须在程序中以硬编码的方式直接调用深色代码的方法

  • 使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理
  • 这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP 代理里的方法可以在执行目标方法之前、之后插入一些通用处理

在这里插入图片描述

JDK8新特性

Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。
Java 8 是oracle公司于2014年3月发布,可以看成是自Java 5 以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性
在这里插入图片描述

  • 速度更快
  • 代码更少(增加了新的语法:Lambda 表达式)
  • 强大的 Stream API
  • 便于并行
  • 最大化减少空指针异常:Optional
  • Nashorn引擎,允许在JVM上运行JS应用

并行流与串行流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。
Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。

Lambda表达式

  • Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是 一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
  • 从匿名类到 Lambda 的转换举例1
    在这里插入图片描述
  • 从匿名类到 Lambda 的转换举例2
    在这里插入图片描述
  • Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda操作符或箭头操作符。它将 Lambda 分为两个部分:
    左侧:指定了 Lambda 表达式需要的参数列表
    右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能。
    在这里插入图片描述
    在这里插入图片描述
  • 类型推断
    上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”
    在这里插入图片描述

函数式(Functional)接口

什么是函数式 式(Functional) 接口

  • 只包含一个抽象方法的接口,称为 函数式接口。
  • 你可以通过 Lambda 表达式来创建该接口的对象。(若Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
  • 我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
  • 在java.util.function包下定义了Java 8 的丰富的函数式接口

如何理解函数式接口

  • Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还可以支持OOF(面向函数编程)
  • 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。
  • 简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
  • 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写

作为参数传递 Lambda表达式

在这里插入图片描述
作为参数传递 Lambda 表达式:为了将 Lambda表达式作为参数传递,接收Lambda表达式的参数类型必须是与该Lambda 表达式兼容的函数式接口的类型

Java内置四大核心函数式接口

在这里插入图片描述

其他接口

在这里插入图片描述

方法引用与构造器引用

方法引用(Method References)

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
  • 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
  • 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
  • 格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
  • 如下三种主要使用情况:
对象:: 实例方法名
类:: 静态方法名
类:: 实例方法

在这里插入图片描述
在这里插入图片描述
注意: 当函数式接口方法的 第一个参数 是 需要引用方法的调用者,并且第二个参数是需要引用方法的参数( 或无参数) 时:ClassName::methodName

构造器引用

  • 格式:ClassName::new
  • 与函数式接口相结合,自动与函数式接口中方法兼容。
  • 可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象
  • 方法的参数列表一致!且方法的返回值即为构造器对应类的对象
    在这里插入图片描述

数组引用

  • 格式:type[] :: new
    在这里插入图片描述

Stream API

Stream API说明

  • Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。
  • Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
  • Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式

为什么要使用Stream API

  • 实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要Java层面去处理。
  • Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算

什么是Stream

Stream 到底是什么呢?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
“集合讲的是数据,Stream 讲的是计算!”

Stream的操作三个步骤

  1. 创建 Stream
    一个数据源(如:集合、数组),获取一个流
  2. 中间操作
    一个中间操作链,对数据源的数据进行处理
  3. 终止操作( 终端操作)
    一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
    在这里插入图片描述
创建Stream
  1. 方式一:通过集合
Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:
default Stream<E> stream() :  返回一个顺序流
default Stream<E> parallelStream() : 返回一个并行流
  1. 方式二:通过数组
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
static <T> Stream<T> stream(T[] array):  返回一个流
重载形式,能够处理对应基本类型的数组:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)
  1. 方式三:通过Stream的of()
可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。
public static<T> Stream<T> of(T... values) :  返回一个流
  1. 方式四:创建无限流
可以使用静态方法 Stream.iterate() 和 Stream.generate(),创建无限流。
//迭代
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
//生成
public static<T> Stream<T> generate(Supplier<T> s)
// 方式四:创建无限流
@Test
public void test4() {
	// 迭代
	// public static<T> Stream<T> iterate(final T seed, final
	// UnaryOperator<T> f)
	Stream<Integer> stream = Stream.iterate(0, x -> x + 2);
	stream.limit(10).forEach(System.out::println);
	// 生成
	// public static<T> Stream<T> generate(Supplier<T> s)
	Stream<Double> stream1 = Stream.generate(Math::random);
	stream1.limit(10).forEach(System.out::println);
}
Stream的中间操作

多个中间操作可以连接起来形成一个 流水线,除非流水线上触发终止操作,否则 中间操作不会执行任何的处理!而在 终止操作时一次性全部处理,称为“惰性求值”

  1. 筛选与切片
    在这里插入图片描述
  2. 映射
    在这里插入图片描述
  3. 排序
    在这里插入图片描述
Stream的终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
流进行了终止操作后,不能再次使用。

  1. 匹配与查找
    在这里插入图片描述
    在这里插入图片描述

  2. 归约
    在这里插入图片描述
    注:map 和 reduce 的连接通常称为 map-reduce 模式,因Google用它来进行网络搜索而出名。

  3. 收集
    在这里插入图片描述
    Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。
    另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
    在这里插入图片描述
    在这里插入图片描述

Optional类

  • 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
  • Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
  • Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
  • Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
创建Optional 类对象的方法:
Optional.of(T t) : 创建一个 Optional 实例,t必须非空;
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t) :t可以为null

判断Optional 容器中是否包含对象:
boolean isPresent() : 判断是否包含对象
void ifPresent(Consumer<? super T> consumer)  :如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。

获取Optional 容器的对象:
T get(): 如果调用对象包含值,返回该值,否则抛异常
T orElse(T other)  :如果有值则将其返回,否则返回指定的other对象。
T orElseGet(Supplier<? extends T> other) : :如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
T orElseThrow(Supplier<? extends X> exceptionSupplier) : :如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。

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

JDK9新特性

JDK和JRE目录结构的改变

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

模块化系统: Jigsaw->Modularity

  • 谈到 Java 9 大家往往第一个想到的就是 Jigsaw 项目。众所周知,Java 已经发展超过 20 年(95 年最初发布),Java 和相关生态在不断丰富的同时也越来越暴露出一些问题:
    1)Java 运行环境的膨胀和臃肿。每次JVM启动的时候,至少会有30~60MB的内存加载,主要原因是JVM需要加载rt.jar,不管其中的类是否被classloader加载,第一步整个jar都会被JVM加载到内存当中去(而模块化可以根据模块的需要加载程序运行需要的class)
    2)当代码库越来越大,创建复杂,盘根错节的“意大利面条式代码”的几率呈指数级的增长。不同版本的类库交叉依赖导致让人头疼的问题,这些都阻碍了 Java 开发和运行效率的提升。
    3)很难真正地对代码进行封装, 而系统并没有对不同部分(也就是 JAR 文件)之间的依赖关系有个明确的概念。每一个公共类都可以被类路径之下任何其它的公共类所访问到,这样就会导致无意中使用了并不想被公开访问的 API。
  • 本质上讲也就是说,用模块来管理各个package,通过声明某个package暴露,,模块(module)的概念,其实就是package外再裹一层,不声明默认就是隐藏。因此,模块化使得代码组织上更安全,因为它可以指定哪些部分可以暴露,哪些部分隐藏。
  • 实现目标
    1)模块化的主要目的在于减少内存的开销
    2)只须必要模块,而非全部jdk模块,可简化各种类库和大型应用的开发和维护
    3)改进 Java SE 平台,使其可以适应不同大小的计算设备
    4)改进其安全性,可维护性,提高性能
  • 模块将由通常的类和新的模块声明文件(module-info.java)组成。该文件是位于java代码结构的顶层,该模块描述符明确地定义了我们的模块需要什么依赖关系,以及哪些模块被外部使用。在exports子句中未提及的所有包默认情况下将封装在模块中,不能在外部使用
    在这里插入图片描述
    在这里插入图片描述
    要想在java9demo模块中调用java9test模块下包中的结构,需要在java9test的module-info.java中声明:
module java9test {
	//package we export
	exports com.atguigui.bean;
	//exports :控制着哪些包可以被其它模块访问到 。 所有不被导出的包默认都被封装在模块里
}

对应在java 9demo 模块的src 下创建module-info.java文件:

module java9demo {
	requires java9test;
	/requires :指明对其它模块的依赖
}

Java的REPL 工具:jShell命令

  • 产生背景:
    像Python 和 Scala 之类的语言早就有交互式编程环境 REPL(read - evaluate - print -loop)了,以交互式的方式对语句和表达式进行求值。开发者只需要输入一些代码,就可以在编译前获得对程序的反馈。而之前的Java版本要想执行代码,必须创建文件、声明类、提供测试方法方可实现。
  • 设计理念:即写即得 、 快速运行
  • 实现目标:
    1)Java 9 中终于拥有了 REPL工具:jShell。让Java可以像脚本语言一样运行,从控制台启动jShell,利用jShell在没有创建类的情况下直接声明变量,计算表达式,执行语句。即开发时可以在命令行里直接运行Java的代码,而无需创建Java文件,无需跟人解释”public static void main(String[] args)”这句废话。
    2)jShell也可以从文件中加载语句或者将语句保存到文件中。
    3)jShell也可以是tab键进行自动补全和自动添加分号

语法改进:接口的私有方法

Java 8中规定接口中的方法除了抽象方法之外,还可以定义静态方法和默认的方法。一定程度上,扩展了接口的功能,此时的接口更像是一个抽象类。
在Java 9中,接口更加的灵活和强大,连方法的访问权限修饰符都可以声明为private的了,此时方法将不会成为你对外暴露的API的一部分
在这里插入图片描述
在这里插入图片描述

语法改进: 钻石操作符使用

我们将能够与匿名实现类共同使用钻石操作符(diamond operator)在Java 8中如下的操作是会报错的:
在这里插入图片描述
编译报错信息:Cannot use “<>” with anonymous inner classes.
Java 9中如下操作可以正常执行通过:
在这里插入图片描述

语法改进:try

Java 8 中,可以实现资源的自动关闭,但是要求执行后必须关闭的所有资源必须在try子句中初始化,否则编译不通过。如下例所示:
在这里插入图片描述
Java 9 中,用资源语句编写try将更容易,我们可以在try子句中使用已经初始化过的资源,此时的资源是final的:
在这里插入图片描述

String存储结构变更

String(StringBuffer和StringBuilder)再也不用 char[] 来存储啦,改成了 byte[] 加上编码标记,节约了一些空间
在这里插入图片描述

集合工厂方法:快速创建只读集合

要创建一个只读、不可改变的集合,必须构造和分配它,然后添加元素,最后包装成一个不可修改的集合
在这里插入图片描述
在这里插入图片描述
Java 9因此引入了方便的方法,这使得类似的事情更容易表达
在这里插入图片描述
List firsnamesList = List.of(“Joe”,”Bob”,”Bill”);调用集合中静态方法of(),可以将不同数量的参数传输到此工厂方法中。此功能,可用于Set和List,也可用于Map的类似形式。此时得到的集合,是不可变的:在创建后,继续添加元素到这些集合会导致 “UnsupportedOperationException” 。
由于Java 8中接口方法的实现,可以直接在List,Set和Map的接口内定义这些方法,便于调用
在这里插入图片描述

InputStream加强

InputStream 终于有了一个非常有用的方法:transferTo,可以用来将数据直接传输到 OutputStream,这是在处理原始数据流时非常常见的一种用法,如下示例
在这里插入图片描述

增强的StreamAPI

  • Java 的 Steam API 是java标准库最好的改进之一,让开发者能够快速运算,从而能够有效的利用数据并行计算。Java 8 提供的 Steam 能够利用多核架构实现声明式的数据处理。
  • 在 Java 9 中,Stream API 变得更好,Stream 接口中添加了 4 个新的方法:takeWhile, dropWhile, ofNullable,还有个 iterate 方法的新重载方法,可以让你提供一个 Predicate (判断条件)来指定什么时候结束迭代。
  • 除了对 Stream 本身的扩展,Optional 和 Stream 之间的结合也得到了改进。现在可以通过 Optional 的新方法 stream() 将一个 Optional 对象转换为一个(可能是空的) Stream 对象。
  • takeWhile() 的使用:
//用于从 Stream 中获取一部分数据,接收一个 Predicate 来进行选择。在有序的Stream 中,takeWhile 返回从开头开始的尽量多的元素。
List<Integer> list = Arrays.asList(45, 43, 76, 87, 42, 77, 90, 73, 67, 88);
list.stream().takeWhile(x -> x < 50).forEach(System.out::println);
System.out.println();
list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
list.stream().takeWhile(x -> x < 5).forEach(System.out::println);
  • dropWhile()的使用:
//dropWhile 的行为与 takeWhile 相反,返回剩余的元素。
List<Integer> list = Arrays.asList(45, 43, 76, 87, 42, 77, 90, 73, 67, 88);
list.stream().dropWhile(x -> x < 50).forEach(System.out::println);
System.out.println();
list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
list.stream().dropWhile(x -> x < 5).forEach(System.out::println);
  • ofNullable()的使用:
//Java 8 中 Stream 不能完全为null,否则会报空指针异常。而 Java 9 中的ofNullable 方法允许我们创建一个单元素 Stream,可以包含一个非空元素,也可以创建一个空Stream。
// 报NullPointerException
// Stream<Object> stream1 = Stream.of(null);
// System.out.println(stream1.count());
// 不报异常,允许通过
Stream<String> stringStream = Stream.of("AA", "BB", null);
System.out.println(stringStream.count());// 3
// 不报异常,允许通过
List<String> list = new ArrayList<>();
list.add("AA");
list.add(null);
System.out.println(list.stream().count());// 2
// ofNullable():允许值为null
Stream<Object> stream1 = Stream.ofNullable(null);
System.out.println(stream1.count());// 0
Stream<String> stream = Stream.ofNullable("hello world");
System.out.println(stream.count());// 1
  • iterate()重载的使用
//这个iterate 方法的新重载方法,可以让你提供一个 Predicate (判断条件)来指定什么时候结束迭代。
// 原来的控制终止方式:
Stream.iterate(1, i -> i + 1).limit(10).forEach(System.out::println);
// 现在的终止方式:
Stream.iterate(1, i -> i < 100, i -> i + 1).forEach(System.out::println);

Optional获取Stream的方法

//Optional 类中stream()的使用
List<String> list = new ArrayList<>();
list.add("Tom");
list.add("Jerry");
list.add("Tim");
Optional<List<String>> optional = Optional.ofNullable(list);
Stream<List<String>> stream = optional.stream();
stream.flatMap(x -> x.stream()).forEach(System.out::println);

Javascript 引擎升级:Nashorn

  • Nashorn 项目在 JDK 9 中得到改进,它为 Java 提供轻量级的 Javascript 运行时。Nashorn 项目跟随 Netscape 的 Rhino 项目,目的是为了在 Java 中实现一个高性能但轻量级的Javascript 运行时。Nashorn 项目使得 Java 应用能够嵌入Javascript。它在 JDK 8 中为 Java 提供一个 Javascript 引擎。
  • JDK 9 包含一个用来解析 Nashorn 的 ECMAScript 语法树的 API。这个 API 使得IDE 和服务端框架不需要依赖 Nashorn 项目的内部实现类,就能够分析ECMAScript 代码

JDK10新特性

局部变量类型推断

  • 产生背景:
    开发者经常抱怨Java中引用代码的程度。局部变量的显示类型声明,常常被认为是不必须的,给一个好听的名字经常可以很清楚的表达出下面应该怎样继续。
  • 好处:
    减少了啰嗦和形式的代码,避免了信息冗余,而且对齐了变量名,更容易阅读!
  • 场景一 :
//作为 Java开发者,在声明一个变量时,我们总是习惯了敲打两次变量类型,第一次用于声明变量类型,第二次用于构造器
LinkedHashSet<Integer> set = new LinkedHashSet<>();
  • 场景二 :
//变量的声明类型书写复杂且较长,尤其是加上泛型的使用
Iterator<Map.Entry<Integer, Student>> iterator = set.iterator();
  • 场景三:
    我们也经常声明一种变量,它只会被使用一次,而且是用在下一行代码中,比如:
URL url = new URL("http://www.atguigu.com");
URLConnection connection = url.openConnection();
Reader reader = new BufferedReader(new
InputStreamReader(connection.getInputStream()));
  • 局部变量类型推断适用于以下情况:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 工作原理:
    在处理 var时,编译器先是查看表达式右边部分,并根据右边变量值的类型进行推断,作为左边变量的类型,然后将该类型写入字节码当中
  • 注意:
    1)var 不是一个关键字:
    你不需要担心变量名或方法名会与 var发生冲突,因为 var实际上并不是一个关键字,而是一个类型名,只有在编译器需要知道类型的地方才需要用到它。除此之外,它就是一个普通合法的标识符。也就是说,除了不能用它作为类名,其他的都可以,但极少人会用它作为类名。
    2)这不是JavaScript:
    首先我要说明的是,var并不会改变Java是一门静态类型语言的事实。编译器负责推断出类型,并把结果写入字节码文件,就好像是开发人员自己敲入类型一样。下面是使用 IntelliJ(实际上是 Fernflower的反编译器)反编译器反编译出的代码:
    在这里插入图片描述

从代码来看,就好像之前已经声明了这些类型一样。事实上,这一特性只发生在编译阶段,与运行时无关,所以对运行时的性能不会产生任何影响。所以请放心,这不是 JavaScript。

集合新增创建不可变集合的方法

自 Java 9 开始,Jdk 里面为集合(List / Set / Map)都添加了 of (jdk9新增)和copyOf (jdk10新增)方法,它们两个都用来创建不可变的集合,来看下它们的使用和区别
在这里插入图片描述
从源码分析,可以看出copyOf方法会先判断来源集合是不是AbstractImmutableList 类型的,如果是,就直接返回,如果不是,则调用 of 创建一个新的集合。示例2因为用的 new 创建的集合,不属于不可变 AbstractImmutableList 类的子类,所以 copyOf 方法又创建了一个新的实例,所以为false。
注意:使用of和copyOf创建的集合为不可变集合,不能进行添加、删除、替换、排序等操作,不然会报java.lang.UnsupportedOperationException 异常。上面演示了 List 的 of 和 copyOf 方法,Set 和 Map 接口都有。

JDK11新特性

String新方法

在这里插入图片描述

Optional新方法

Optional 也增加了几个非常酷的方法,现在可以很方便的将一个 Optional 转换成一个 Stream, 或者当一个空 Optional 时给它一个替代的
在这里插入图片描述

局部变量类型推断升级

在var上添加注解的语法格式,在jdk10中是不能实现的。在JDK11中加入了这样的语法
在这里插入图片描述

全新的HTTP客户端API

  • HTTP,用于传输网页的协议,早在1997年就被采用在目前的1.1版本中。直到2015年,HTTP2才成为标准。
  • HTTP/1.1和HTTP/2的主要区别是如何在客户端和服务器之间构建和传输数据。HTTP/1.1依赖于请求/响应周期。HTTP/2允许服务器“push”数据:它可以发送比客户端请求更多的数据。这使得它可以优先处理并发送对于首先加载网页至关重要的数据。
  • 这是 Java 9 开始引入的一个处理 HTTP 请求的的 HTTPClient API,该API 支持同步和异步,而在 Java 11 中已经为正式可用状态,你可以在java.net 包中找到这个 API。
  • 它将替代仅适用于blocking 模式的HttpURLConnection(HttpURLConnection是在HTTP 1.0的时代创建的,并使用了协议无关的方法),并提供对WebSocket 和 HTTP/2的支持。
    在这里插入图片描述

更简化的编译运行程序

看下面的代码。
// 编译

javac Javastack.java

// 运行

java Javastack

在我们的认知里面,要运行一个 Java 源代码必须先编译,再运行,两步执行动作。而在未来的 Java 11 版本中,通过一个 java 命令就直接搞定了,如以下所示:

java Javastack.java

一个命令编译运行源代码的注意点:
1)执行源文件中的第一个类, 第一个类必须包含主方法。
2)并且不可以使用其它源文件中的自定义类, 本文件中的自定义类是可以使用的

废弃Nashorn引擎

废除Nashorn javascript引擎,在后续版本准备移除掉,有需要的可以考虑使用GraalVM

ZGC

  • GC是java主要优势之一。 然而, 当GC停顿太长, 就会开始影响应用的响应时间。消除或者减少GC停顿时长, java将对更广泛的应用场景是一个更有吸引力的平台。此外, 现代系统中可用内存不断增长,用户和程序员希望JVM能够以高效的方式充分利用这些内存, 并且无需长时间的GC暂停时间。
  • ZGC, A Scalable Low-Latency Garbage Collector(Experimental)ZGC, 这应该是JDK11最为瞩目的特性, 没有之一。 但是后面带了Experimental,说明这还不建议用到生产环境。
  • ZGC是一个并发, 基于region, 压缩型的垃圾收集器, 只有root扫描阶段会STW(stop the world), 因此GC停顿时间不会随着堆的增长和存活对象的增长而变长。
  • 优势:
    1)GC暂停时间不会超过10ms
    2)既能处理几百兆的小堆, 也能处理几个T的大堆(OMG)
    3)和G1相比, 应用吞吐能力不会下降超过15%
    4)为未来的GC功能和利用colord指针以及Load barriers优化奠定基础
    5)初始只支持64位系统
  • ZGC的设计目标是:支持TB级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于15%。 将来还可以扩展实现机制,以支持不少令人兴奋的功能,例如多层堆(即热对象置于DRAM和冷对象置于NVMe闪存),或压缩堆。

其它新特性

  • Unicode 10
  • Deprecate the Pack200 Tools and API
  • 新的Epsilon垃圾收集器
  • 完全支持Linux容器(包括Docker)
  • 支持G1上的并行完全垃圾收集
  • 最新的HTTPS安全协议TLS 1.3
  • Java Flight Recorder
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值