方法引用

方法引用

  • 双冒号::式引用运算符,其所在的表达式被称为方法引用

  • 当对象和方法已经存在时,可以使用方法引用优化Lambda表达式

  • 分析:

    下面程序中Lambda表达式的目的:打印参数传递的字符串
    printstring中把参数s传递给了System.out对象,调用out中的方法println对字符串输出
    因为System.out对象和println已经存在,所以可使用方法引用优化表达式
    使用System.out直接引用println方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@FunctionalInterface
public interface DemoInterface {
void print(String s);//打印字符串的抽象方法
}

public class DemoPrintable {
public static void printstring(DemoInterface p){
p.print("HelloWorld");
}
public static void main(String[] args) {
printstring((s)->{
System.out.println(s);
});
printstring(System.out::println);
}
}

实现引用成员方法

  • 自定义一个类MethodReObject
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MethodReObject {
String name;
public MethodReObject() {
}
public MethodReObject(String name) {
this.name = name;
}
public String getName() {
return name;
}
//定义一个成员方法,传递字符串,把字符串大写输出
public void printUppercase(String s){
System.out.println(s.toUpperCase());
}
public static int calc(int a,int b){ //静态成员方法
return a+b;
}
public void sayHello(){
System.out.println("Hello");;
}
}

1. 通过对象引用成员方法

  • 定义一个接口DemoInterface,包含一个抽象方法print
  • 在主类中定义printstring方法,参数为接口DemoInterface类型,方法中调用接口的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@FunctionalInterface
public interface DemoInterface {
void print(String s);//打印字符串的抽象方法
}
public class test {
public static void printstring(DemoInterface p){
p.print("hello");
}
public static void main(String[] args) {
printstring((s)->{
MethodReObject mp = new MethodReObject();
mp.printUppercase(s);
});
MethodReObject object = new MethodReObject();
printstring(object::printUppercase);
}
}

2. 通过类名引用静态方法

  • 定义一个函数式接口Calcable,包含抽象方法可以计算两个数的和
  • 在主类中定义方法method1,参数传递Calcable接口,调用其中的给抽象方法
1
2
3
4
5
6
7
8
9
10
11
12
13
@FunctionalInterface
public interface Calcable {
int cal(int a,int b);
}
public class test {
public static int method1(int a,int b,Calcable p){
return p.cal(a,b);
}
public static void main(String[] args) {
int r = method1(1,2,MethodReObject::calc); //通过类名引用静态方法
System.out.println(r);
}
}

3. 通过super和this引用成员方法

  • 如果存在继承关系,当Lambda中需要super调用时,也可以使用方法引用进行替代

  • this代表当前对象,如果需要引用的方法就是当前类中的成员方法,可以使用 this::成员方法 来进行方法引用

    • 定义函数式接口Greetable

    • 定义子类SubReObject 继承 MethodReObject

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
@FunctionalInterface
public interface Greetable {
void greet();
}
public class SubReObject extends MethodReObject{
@Override
public void sayHello() {
System.out.println("hello,i'm hxx");
}
//定义一个方法参数传递函数式接口Greetable
public static void method2(Greetable g){
g.greet();
}
//定义一个方法调用method
public void show(){
method2(()->{
MethodReObject obj = new MethodReObject();//创建父类对象
obj.sayHello();
});
method2(()->{
super.sayHello();
});
method2(super::sayHello);
method2(this::sayHello);//通过this引用本类的方法

}
public static void main(String[] args) {
new SubReObject().show();
}
}

4. 类的构造器引用

  • 构造器引用使用 类名称::new 的格式表示
  • 定义函数式接口ObjectBuilder ,创建一个MethodReObject对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@FunctionalInterface
public interface ObjectBuilder {
//创建一个MethodReObject对象
MethodReObject BuilderObject(String name);
}

public class test {
public static void method2(String name,ObjectBuilder ob){
MethodReObject object = ob.BuilderObject(name);
System.out.println(object.getName());
}
public static void main(String[] args) {
method2("hxx",(String name)->{
return new MethodReObject(name);
});
method2("hxx",MethodReObject::new);//构造器引用
}
}

5. 数组的构造器引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@FunctionalInterface
public interface ArrayBuilder {
//定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组
int []builderArray(int len);
}
public class test {
public static int[] creatArray(int len,ArrayBuilder ab){
int[] array = ab.builderArray(len);
return array;
}
public static void main(String[] args) {
//调用creatArray方法
int[] array = creatArray(3, len -> {
return new int[len];
});
//int[]引用new
int[] array1 = creatArray(3, int[]::new);
}
}

迷茫中(github.io无法访问,尚未解决)

  • 网址hxx-98.github.io访问出现了问题,让其他朋友亲戚试着打开这个网址,发现有的可以,但有的不行,很是困惑,不知道后期会不会恢复。

  • 不知所以的排查中,虽然还是没解决……..
  • GitHub能正常访问,检查自己的GitHub用户名就是hxx-98,仓库名和域名均设置正确………..

  • ping一下看看,怎么成了回环地址。

  • 看看hosts文件(C:\Windows\System32\drivers\etc),是不是被改了,好像并没有哎。。。。

  • 逐渐放弃,文章可以部署上去,只是现在自己电脑没法访问了,纠结了下,自我安慰可以当个笔记用吧。

Stream流

1 引言

  • I/O流是用于读写,Stream用于对数组和集合进行简化操作。我们可以将集合和数组转换成Stream流,使用Stream流中的方法对其中的元素进行操作。
1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hxx");
list.add("hq");
list.add("qzy");
//使用Stream流的方式遍历集合,对集合中的数据进行过滤
list.stream()
.filter(name->name.startsWith("h"))
.filter(name->name.length()==3)
.forEach(name->System.out.println(name));
}

2 流式思想概述

  • Stream流是一个集合元素的函数模型,并不是集合,也不是数据结构,其本身并不存储任何元素或其地址值

  • Stream是一个来自数据源的元素队列

    • 元素是特定类型的对象,形成一个队列。
    • 数据源可以是集合、数组
  • 和之前的Collection相比,Stream操作还有两个基础的特征:

    • Pipelining:中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格。这样可以对操作进行优化,比如延迟执行和短路

    • 内部迭代:以前对集合遍历都是通过iterator或者增强for的方式,显式的在集合外部进行迭代,即外部迭代。Stream提供了内部迭代的方式,流可以直接调用遍历方法

  • Stream属于管道流,只能使用一次。第一个Stream流调用完毕,数据会流转到下一个Stream上。而这时第一个Stream流已经使用完毕并且关闭。

  • 使用流的步骤:

    • 获取一个数据源
    • 数据转换:每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换)。
    • 执行操作获取想要的结果

3 获取流

  • 获取流的方式:

    • 所有的Collection都可以通过stream默认方法获取流
    • Stream接口的静态方法of 可以获取数组对应的流
  • java.util.Collection 接口中加入了default方法 stream 来获取流,所以其所有实现类都可获取流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void main(String[] args) {		
//将集合转换为stream
List<String> list1 = new ArrayList<>();
Stream<String> stream1 = list1.stream();

Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();

Map<String,String> map = new HashMap<>();
//获取键,存储到一个set集合中
Set<String> set1 = map.keySet();
Stream<String> stream3 = set1.stream();
//获取值,存储到Collection集合中
Collection<String> values = map.values();
Stream<String> stream4 = values.stream();
//获取键值对
Set<Map.Entry<String, String>> entryset = map.entrySet();
Stream<Map.Entry<String, String>> stream5 = entryset.stream();

//将数组转换为stream流
Stream<Integer> integerStream = Stream.of(1, 2, 3);
Integer[]arr = {1,2,3};
Stream<Integer> arr1 = Stream.of(arr);
}

4 常用方法

  • 延迟方法:返回值类型仍然是Stream接口自身类型的方法,支持链式调用
  • 终结方法:返回值类型不再是Stream自身类型的方法,不咋支持类似于StringBuilder那样的链式调用。比如有countforeach方法

forEach

  • 该方法接收一个Consumer接口函数,将每一个硫元素交个该函数进行处理
1
void forEach(Consumer<? super T> action);
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
//获取stream流
Stream<String> stringStream = Stream.of("hxx", "qzy");
//调用forEach
stringStream.forEach(
(String name)->{
System.out.println(name);
}
);
}

过滤方法filter

1
Stream<T> filter(Predicate<? super T> predicate);

该函数接收一个Predicate函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
Stream<String> stream = Stream.of("hxx", "qzy");
Stream<String> h = stream.filter(
(String name) -> {
return name.startsWith("h");
}
);
System.out.println(h);
stream.forEach(name-> System.out.println(name));//报错,使用完stream之后转换到了h流,stream已经关闭
}

映射:map

  • 如果需要将流中的元素映射到另一个流中,可以使用map方法,方法签名:
1
2
3
4
5
6
7
8
//<R> Stream <R> map(Function<? siper T,? extends R> maper);
public static void main(String[] args) {
Stream<String> stream = Stream.of("1","2");
Stream<Integer> integerStream = stream.map((String s) -> {
return Integer.parseInt(s);
});
integerStream.forEach(i-> System.out.println(i));
}

count、limit、skip、contact

1
2
3
4
5
6
7
8
9
10
//long count(); 终结方法
public static void main(String[] args) {
Stream<String> stream = Stream.of("1","2","3","4","5");
/*Stream<String> limit = stream.limit(3);
System.out.println(limit.count());
Stream<String> skip = stream.skip(3);*/
Stream<String> stream1 = Stream.of("hxx");
Stream<String> concat = Stream.concat(stream, stream1);//保证流没有关闭
concat.forEach(i-> System.out.println(i));
}

函数式接口-Java

第一章 函数式接口

1.1 概念

  • 函数式接口在Java中指有且仅有一个抽象方法的接口,接口中可以包含其他的方法(私有,默认,静态)体现方式是Lambda

1.2 格式

1
2
3
4
@FunctionalInterface
修饰符 interface 接口名称{
public abstract 返回值类型 方法名称(参数);
}

1.3 @FunctionalInterface

  • 注解,检测接口是否是一个函数式接口

第二章 函数式编程

2.1 Lambda的延迟执行

日志案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class logger {
//定义一个根据日志的级别,显示日志的方法
public static void showLog(int level,String message){
if(level == 1){
System.out.println(message);
}
}

public static void main(String[] args) {
//定义三个日志信息
String msg1 = "hello";
String msg2 = "hxx";
String msg3 = "qzy";
//调用showLog
showLog(1,msg1+msg2+msg3);
showLog(2,msg1+msg2+msg3);//不会打印,字符串拼接操作浪费
}
}

日志案例优化

  • 使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中,只有满足条件,才会调用MessageBuilder中的方法builderMessage进行字符串的拼接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@FunctionalInterface
public interface MessageBuilder {
public abstract String builderMessage();
}
public class logger {
public static void showLog(int level,MessageBuilder mb){
if(level == 1){
System.out.println(mb.builderMessage());
}
}
public static void main(String[] args) {
//定义三个日志信息
String msg1 = "hello";
String msg2 = "hxx";
String msg3 = "qzy";
showLog(1,()->{
return msg1+msg2+msg3;
});
}
}

函数式接口作为方法的返回值

  • 如果一个方法的返回值类型是一个函数式接口,那么可以直接返回一个Lambda表达式。当需要通过一个方法来获取一个java.util.Comparator接口类型的对象作为排序算法时,就可调该方法获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static Comparator<String> getComparable(){
/*return new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.length()-o1.length();
}
};*/
/*return (o1,o2)->{
return o2.length()-o1.length();
};*/
return (o1,o2)->o2.length()-o1.length();

}
public static void main(String[] args) {
String[] str = {"aa","b","dssd"};
Arrays.sort(str,getComparable());
System.out.println(Arrays.toString(str));
}

第三章 常用函数式接口

3.1 Supplier接口

  • java.util.function.Supplier< T >接口仅包含一个无参的方法:T get()。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,所以Lambda表达式需要对外提供一个符合泛型类型的对象数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    //定义一个方法,参数传递Supplier接口
public static String getString(Supplier<String> sup) {
return sup.get();
}
public static int getMax(Supplier<Integer>sup){
return sup.get();
}
public static void main(String[] args) {
String s = getString(() -> {
return "hxx";
});
System.out.println(s);
int[] arr = {10,20,50};
int maxVaule = getMax(()->{
int max = arr[0];
for (int i : arr) {
if(i>max) max = i;
}
return max;
});
System.out.println(maxVaule);
}

3.2 Consumer接口

抽象方法:accept

Consumer 接口中包含抽象方法void accept(T t)消费一个指定泛型的数据

1
2
3
4
5
6
7
8
9
10
public static void method(String name, Consumer<String> cos){
cos.accept(name);
}
public static void main(String[] args) {
method("hxx",(String name)->{
System.out.println(name);
String rName = new StringBuffer(name).reverse().toString();
System.out.println(rName);
});
}

默认方法andThen

  • 需要两个Consumer接口,可以把两个Consumer接口连接起来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void method1(String name,Consumer<String> con1,Consumer<String>con2){
con1.accept(name);
con2.accept(name);
//使用andThen方法
con1.andThen(con2).accept(name);
}
public static void main(String[] args) {
String str = "hxx";
//判断字符串是否长度大于5
checkString(str,(s)->{
return s.length()<5;
});
method1("hxx",
(name)-> System.out.println(name.toUpperCase()),
(name)-> System.out.println(name));
}

3.3 Predicate接口

  • 当需要对某种类型的数据进行判断,可以使用 java.util.function.Predicate<T>接口

抽象方法:boolean test(T t)用于条件判断

1
2
3
4
5
6
7
8
9
10
11
public static boolean checkString(String str, Predicate<String> pre){
return pre.test(str);
}

public static void main(String[] args) {
String str = "hxx";
//判断字符串是否长度大于5
checkString(str,(s)->{
return s.length()<5;
});
}

默认方法:and、or、negate

  • 将两个Predicate条件使用逻辑连接起来实现与或非效果时,可以使用default方法and
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static boolean checkString1(String str, Predicate<String> pre1, Predicate<String> pre2){
//return pre1.test(str) && pre2.test(str);
//return pre1.and(pre2).test(str);
return pre1.or(pre2).test(str);
//return pre1.negate().test(str);
}
public static void main(String[] args) {
String str = "hxxqzy";
//判断字符串是否长度大于5
boolean b=checkString1(str,
(s)->{ return s.length()<5; },
(s)->{ return s.contains("qzy");}
);
}

3.4 Function接口

  • java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据

抽象方法:apply

  • R apply(T t)根据类型T的参数获取类型R的结果。如将String转换为Integer类型
1
2
3
4
5
6
7
8
9
10
11
public static void change(String str, Function<String,Integer> fun){
Integer in = fun.apply(str);
System.out.println(in);
}
public static void main(String[] args) {

String str = "1234";
change(str,(String s)->{
return Integer.parseInt(s);
});
}

andThen方法:组合操作

  • 将String类型的”123“转换为Integer类型,加10之后转换为String类型
1
2
3
4
5
6
7
8
9
10
11
12
public static void change(String str, Function<String,Integer> fun1,Function<Integer,String> fun2){
String ss = fun1.andThen(fun2).apply(str);
System.out.println(ss);
}

public static void main(String[] args) {
String str = "1234";
change(str,
(String s)->{ return Integer.parseInt(s)+10; },
(Integer i)->{return i+"";});

}

TCP通信-Java

1. 概述

通信步骤

​ 服务器端先启动,服务器端不会主动的请求客户端,必须使用客户端请求服务器端。客户端和服务器端会建立一个逻辑链接,而这个连接中包含一个IO对象,客户端使用这个IO对象进行通信。通信的数据不只是字符,所以是字节流对象。

服务器端必须明确两件事

  1. 多个客户端同时和服务器端进行交互,服务器端必须明确和哪个客户端进行的交互。服务器端有个方法,叫accept客户端获取到请求的客户端对象。

  2. 多个客户端同时和服务器进行交互,就需要使用多个IO流对象。

  3. 服务器端是没有IO流的,服务器可以获取到请求的客户流对象Socket,使用每个客户端Socket中提供的IO流和客户端进行交互。

    • 服务器端使用客户端的字节输入流读取客户端发送数据

    • 服务器端使用客户端的字节输出流给客户端回写数据

在Java中提供了两个类实现TCP通信程序

  1. 客户端:java.net.Socket类表示,创建Socket对象,向服务端发出连接请求,服务器端响应请求,两者建立连接开始通信
  2. 服务端:java.net.SeverSocket类表示。创建SeverSocket对象,先当与开启一个服务,并等待客户端的连接

2. Socket类

Socket类:该类实现客户端套接字(两台设备之间通讯的端点)

  • public Socket(String host,int port):创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null,则相当于指定地址为回送地址。
  • OutputStream getOutputStream():返回此套接字的输出流
  • InputStream getInputStream:返回此套接字的输入流
  • void close():关闭此套接字

3. SeverSocket类

  • SeverSocket(int port):创建绑定到端口的服务器套接字
  • Socket accept():侦听并接收此套接字的连接
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
//客户端代码
public class TCPClient {
public static void main(String[] args) throws IOException {
//创建客户端对象中的方法Socket,构造方法绑定服务器的IP地址和端口号
Socket socket = new Socket("127.0.0.1", 8888);
//使用Socket对象中的getOutputStream()获取网络字节输出流对象
OutputStream os = socket.getOutputStream();
//使用网络字节输出流对象中的write方法,给服务器发送数据
os.write("你好,服务器".getBytes());
//使用Socket对象中的getInputStream方法获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//使用网络字节输入流对象中的方法read,读取服务器回写的数据
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes, 0, len));
socket.close();
}
}
//服务器代码
public class TCPSever {
public static void main(String[] args) throws IOException {
//创建服务器SeverSocket对象和系统要指定的端口号
ServerSocket server = new ServerSocket(8888);
//使用SeverSocket对象中的方法accept,获取到请求的客户端对象Socket
Socket socket = server.accept();
//使用Socket对象中的getInputStream方法获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//使用网络字节输入流对象中的方法read,读取客户端发送来的数据
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes, 0, len));
//使用Socket对象中的getOutputStream()获取网络字节输出流对象
OutputStream os = socket.getOutputStream();
//使用网络字节输出流对象中的write方法,给客户端发送数据
os.write("收到谢谢".getBytes());
socket.close();
server.close();
}
}

文件上传案例

  • 当客户端向服务器发送数据时,不会将结束标记发送过去,服务器将会处于循环等待,所以要使用shutdownOutput方法。

    void shutdownOutput():禁用此套接字的输出流。对于TCP套接字,任何以前写入的数据都将被发送,并且后跟TCP的正常连接终止序列

  • 为防止同名文件被覆盖,自定义一个文件的命名规则:域名+毫秒值+随机数

  • 服务器要一直处于监听状态(死循环accept方法)。使用多线程技术,提高程序的效率,有一个客户端上传文件就开启一个线程,完成文件上传

服务器端代码

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
public class TCPSever {
public static void main(String[] args) throws IOException {
//创建服务器SeverSocket对象和系统要指定的端口号
ServerSocket server = new ServerSocket(8888);
while (true){
//使用SeverSocket对象中的方法accept,获取到请求的客户端对象Socket
Socket socket = server.accept();
new Thread(new Runnable() {
@Override
public void run() {
try{
File file = new File("D:\\Blog\\images\\JAVA");
if(!file.exists()){
file.mkdirs();
}
String filename = "hxx"+System.currentTimeMillis()+ new Random(9999).nextInt();
//创建一个本地输出流
FileOutputStream fos = new FileOutputStream(file+"\\"+filename+".png");

//使用Socket对象中的getInputStream方法获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();

//使用网络字节输入流对象中的方法read,读取客户端发送来的数据
byte[] bytes = new byte[1024];
int len;
while ((len = is.read(bytes))!=-1){
fos.write(bytes,0,len);//写到硬盘
}
//使用Socket对象中的getOutputStream()获取网络字节输出流对象
//使用网络字节输出流对象中的write方法,给客户端发送数据
OutputStream os = socket.getOutputStream();
os.write("上传成功".getBytes());
socket.shutdownOutput();
}catch (IOException e){
System.out.println(e);
}
}
}).start();
}
//服务器不停机
//server.close();
}
}

客户端代码

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
public class TCPClient {
public static void main(String[] args) throws IOException {
//读取本地文件,创建本地输入流
FileInputStream fis = new FileInputStream("xin.png");
//创建客户端对象中的方法Socket,构造方法绑定服务器的IP地址和端口号
Socket socket = new Socket("127.0.0.1", 8888);
//使用Socket对象中的getOutputStream()获取网络字节输出流对象
OutputStream os = socket.getOutputStream();

byte[] bytes = new byte[1024];
int len = 0;
//使用本地字节输入流FileInputStream对象中的方法read读取本地文件
while ((len = fis.read(bytes))!= -1){
//使用网络字节输出流对象中的write方法,给服务器发送数据
os.write(bytes,0,len);
}
socket.shutdownOutput();

//使用Socket对象中的getInputStream方法获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//使用网络字节输入流对象中的方法read,读取服务器回写的数据
len = 0;
while ((len = is.read(bytes))!= -1){
System.out.println(new String(bytes,0,len));
}
socket.close();
fis.close();
}
}

模拟BS服务器代码

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
//http://127.0.0.1:8080/day01_code/web/index.html
public class BSserver {
public static void main(String[] args) throws IOException {
//创建服务器SeeverSocket类,
ServerSocket server = new ServerSocket(8080);
//浏览器解析服务器回写的页面,如果有图片那么浏览器会单独开一个线程,所以服务器要一直监听
while(true){
Socket socket = server.accept();
new Thread(new Runnable() {
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
//向浏览器端回写页面数据
//把is网络字节输入流对象,转换为字符缓冲输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//把请求信息的第一行读取出来
String s = br.readLine();
//切割信息
String[] s1 = s.split(" ");
String htmlPath = s1[1].substring(1);//day01_code/web/index.html

//创建本地字节输入流,构造方法中绑定要读取的HTML文件路径
FileInputStream fis = new FileInputStream(htmlPath);
OutputStream os = socket.getOutputStream();
//写入HTTP协议响应头,固定写法
os.write(("HTTP/1.1 200 OK\r\n").getBytes());
os.write(("Content-Type:text/html\r\n").getBytes());
//必须写入空行,否则浏览器不解析
os.write(("\r\n").getBytes());

//复制文件,将服务器读取的HTML文件回写到客户端
byte[] bytes = new byte[1024];
int len = 0;
while ((len = fis.read(bytes)) != -1) {
os.write(bytes, 0, len);
}
fis.close();
socket.close();
}catch (IOException e){
System.out.println(e);
}
}
}).start();
}
}
}

IO顶级父类

输入流 输出流
字节流 字节输入流InputStream 字节输出流OutputStream
字符流 字符输入流Reader 字符输出流Writer

第一章 字节流

1.1 字节输出流

  • java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写道目的地。其基本共性方法:

1.2 文件字节输出流

  • java.io.FileOutputStream 文件字节输出流。将内存中的数据写入到硬盘的文件中

构造方法

FileOutputStream(String name):创建一个向指定名称的文件中写入数据的输出文件流

FileOutputStream(File file):创建一个向指定File对象文件中写入数据的输出文件流

写出字节数据

  • public void write(byte[] b):将字节数组的内容写入文件

  • public void write(byte[] b,int off,int len):将字节数组从off位置开始的长度为len的内容写入文件

  • FileOutputStream(String name,boolean append) :创建一个向指定name的文件中写入数据的输出文件流,append为true时创建对象不会覆盖原文件,而是继续追加

  • FileOutputStream(File file,boolean append) :创建一个向指定File的文件中写入数据的输出文件流。

  • 写换行符

    Windows:\r\n;Linux:/n;Mac:/r

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) throws IOException {
FileOutputStream file = new FileOutputStream("hxx.txt",true);
file.write("\r\n".getBytes());//换行
file.write(98);//b
byte []bytes = {65,66,67};
file.write(bytes);//ABC
byte[] bytes1 = "你好".getBytes();
file.write(bytes1);//你好
file.write(bytes,1,2);//BC
file.close();
}

1.3 字节输入流

  • java.io.InputStream抽象类表示字节输入流所有类的超类,可以读取字节信息到内存中。
    • public void close():关闭此输入流并释放相关资源
    • public int read():从输入流读取数据的一个字节
    • public int read(byte[] b):从输入流中读取一些字节,并将它们存储在字节数组b中

1.4 文件字节输入流

  • java.io.FileOutputStream 文件字节输出流。将硬盘中的数据读取到内存中

构造方法

  • FileInputStream(String name):创建一个向指定名称的文件中读取数据的输入文件流

  • FileInputStream(File file):创建一个向指定File对象文件中读取数据的输入文件流

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("捕获.PNG");
FileOutputStream fos = new FileOutputStream("xin.PNG");
int len = 0;
while((len = fis.read())!=-1){
fos.write(len);//文件复制
}
fos.close();
fis.close();
}

第二章 字符流

2.1 字符输入流

  • java.io.Reader抽象类是读取字符流的所有类的超类
  • public void close():关闭此输入流并释放相关资源
  • public int read():从输入流读取一个字符
  • public int read(char[] b):从输入流中读取一些字符

2.2 FileReader类

  • FileReader(String name) 创建一个新的FileReader,给定要读取的File对象

  • FileReader(File file)

1
2
3
4
5
6
7
8
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("hxx.txt");
char[] ch = new char[1024];
int len = 0;
while ((len = fr.read())!= -1){
System.out.print((char)len);
}
}

2.3 字符输出流Writer

2.4 FileWriter类

1
2
3
4
5
6
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("hxx.txt",true);
fw.write("helloWorld");
fw.flush();
fw.close();
}

第三章 IO异常处理

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) { 
try(FileInputStream fis = new FileInputStream("捕获.PNG");
FileOutputStream fos = new FileOutputStream("xin.PNG");){ //try执行完后fis fos自动释放
int len = 0;
while((len = fis.read())!=-1){
fos.write(len);
}
}catch(IOException e){
System.out.println(e);
}
}

第四章 属性集

  • java.io.Properties继承于Hashtable,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。

4.1 Properties类

构造方法

  • public Properties():创建一个空的属性列表

基本的存储方法

  • public Object setProperty(String key,String value):保存一对属性
  • public String getProperty(String):使用此属性列表中指定的键搜索属性值
  • public Set< String > stringPropertyName():所有键名称的集合
1
2
3
4
5
6
7
8
public static void main(String[] args) {
Properties pro = new Properties();
pro.setProperty("hxx","qzy");
Set<String> set = pro.stringPropertyNames();
for (String s : set) {
System.out.println(pro.getProperty(s));
}
}

Store

  • 可以使用Properties集合中的方法store,把集合中的临时数据持久化写入到硬盘中存储
  • void store(OutputStream out,String comments):不能写中文,comment是注释,说明保存的文件作用,不能使用中文,会产生乱码。一般使用空字符串
  • void store(Writer writer,String comments):能写中文

load

  • void load(InputStream instream):不能含有中文键值对
  • void loadFileReader reader):不能含有中文键值对
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) throws IOException {
Properties pro = new Properties();
pro.setProperty("hxx","qzy");
//创建字节输出流/字符输出流对象
FileWriter fw = new FileWriter("hxx.txt");
//使用Properties集合中的方法store,把集合中的临时数据写入目的地
pro.store(fw,"Save Data");
fw.close();

//新建字符输入流
FileReader reader = new FileReader("hxx.txt");
Properties pro2 = new Properties();
pro2.load(reader);
reader.close();
System.out.println(pro2);
}

第五章 缓冲流

5.1 字节缓冲流

  • public BufferedOutputStream(OutputStream out)

  • public BufferedOutputStream(OutputStream out,int size) :指定缓冲流内部缓冲区的大小

  • BufferedInputStream(InputStream in)

  • BufferedInputStream(InputStream in,int size)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("hxx.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write("hxxqzy".getBytes());
bos.flush();//刷新将缓冲区的内容刷新到文件中
bos.close();

FileInputStream fis = new FileInputStream("hxx.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
// int len = 0;
// while((len = bis.read())!= -1){
// System.out.print((char) len);
// }
byte[] bytes = new byte[1024];
int len2 = 0;//记录每次读取的有效字节个数
while((len2 = bis.read(bytes))!= -1){
System.out.print(new String(bytes,0,len2));
}
bis.close();
}

4.2 字符缓冲流

  • BufferedReader(Reader in)

  • BufferedReader:public String readLine():读一行文字

  • BufferedWriter(Writer out)

  • BufferedWriter:public void newLine():写一行行分隔符,由系统属性定义符号

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("hxx.txt",true));
bw.newLine();
bw.write("韩晓璇");
bw.close();
BufferedReader br = new BufferedReader(new FileReader("hxx.txt"));
String line;
while((line = br.readLine())!= null){
System.out.println(line);
}
br.close();
}

第五章 转换流

5.1 字符编码和字符集

字符编码

按照某种规则将字符存储到计算机中称之为编码

将存储在计算机中的二进制数据按照某种规则解析显示出来叫做解码

  • 字符编码:Character Encoding

字符集

  • 字符集(编码表):Charset 是一个系统支持的所有字符的集合,包括国家文字、标点符号等

5.2 InputStreamReader

  • java.io.InputStreamReader继承了Reader,是字节流通向字符流的桥梁,可使用指定的charset读取字节将其解码为字符。
  • InputStreamReader(InputStream in):使用默认字符编码
  • InputStreamReader(InputStream in,String charSetName):指定字符编码名称不区分大小写。

5.2 OutputStreamWriter

  • java.io.OutputStreamWriter继承了Writer,是字符流通向字节流的桥梁,可使用指定的charset将要写入流中的字符编码成字节。
  • OutputStreamWriter(OutputStream out):使用默认字符编码
  • OutputStreamWriter(OutputStream out,String charSetName):指定字符编码名称不区分大小写。
1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("hxx.txt"),"GBK");
osw.write("你好");
osw.flush();
osw.close();

InputStreamReader isr = new InputStreamReader(new FileInputStream("hxx.txt"),"gbk");
int len = 0;
while((len = isr.read())!=-1){
System.out.print((char) len);
}
isr.close();
}

第六章 序列化

  • java提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、类型和存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
  • 反之,该字节序列可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、类型和存储的属性等信息都可以用来在内存中创建对象。

6.1 ObjectOutputStream类

java.io.ObjectOutputStream类,将java对象的原始数据类型写出到文件,实现对象的持久存储

构造方法

  • public ObjectOutputStream(OutputStream out):创建一个指定OutputStream 的ObjectOutputStream。

序列化操作

  1. 一个对象需要序列化必须满足两个条件
    • 该类必须实现java.io.Serializable接口,Serializable是一个标记接口,不实现此接口的类将不会使任何状态序列化或者反序列化,会抛出NoSerializableException
    • 该类的所有属性必须使可序列化的。如果有一个属性不需要可序列化,则该属性必须注明是瞬态的,使用trasient关键字修饰
  2. void writeObject(Object obj):将指定的对象写入ObjectOutputStream
1
2
3
4
5
6
7
8
public class Person implements Serializable {
private String name;
}
public static void main(String[] args) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hxx.txt"));
oos.writeObject(new Person("hxx"));
oos.close();
}

6.2 ObjectInputStream类

  • ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原属数据恢复为对象

构造方法

  • public ObjectInputStream(IntputStream In)

反序列化操作1

  • 如果找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream读取对象的方法:

    public final Object readObject()

1
2
3
4
5
6
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hxx.txt"));
Object o = ois.readObject();
System.out.println(o);
ois.close();
}
  • 静态优先于非静态加载到内存中(静态优先于对象进入到内存中),被static修饰的成员变量不能被序列化,序列化的都是对象。
  • transient关键字:瞬态关键字,所修饰的变量不能被序列化

反序列化操作2

当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化时也会失败,抛出InvalidClassException异常,产生这个异常的原因如下:

  • 该类的序列版本号和从流中读取的类描述符的版本号不匹配
  • 该类包含位置数据类型
  • 该类没有可访问的无参构造方法

Serializable接口给需要序列化的类提供了一个序列版本号。serialVersionUID该版本号的目的在于验证序列化的对象和对应类是否版本匹配。

1
2
3
4
public class Person implements Serializable {
public static final long serialVersionUID = 1;
private String name;
}

第七章 打印流

PrintStream类

继承OutputStream类。

  • PrintStream(String fileName):使用指定的文件名创建一个新的打印流
  • PrintStream(OutputStream out)
  • PrintStream(File file)
1
2
3
4
5
6
7
8
public static void main(String[] args) throws FileNotFoundException {
PrintStream ps = new PrintStream("hxx.txt");
ps.write(97);
ps.print("hxx");
System.setOut(ps);//改变打印流
System.out.println("qzy");
ps.close();
}

File类-Java

File类的静态成员变量

  • static String pathSeparator:和系统有关的路径分隔符
  • static charpathSeparatorChar
  • static String separator:与系统有关的默认名称分隔符
  • static char separatorChar
1
2
3
System.out.println(File.pathSeparator);
System.out.println(File.separator);
String file = "c:" + File.separator + "hxx.txt";

File类的构造方法

  • public File(String pathname):通过将给定的路径字符串转换为抽象路径名来创建新的File实例
  • public File(String parent,String child):从父路径名字符串和子路径创建新的file实例
  • public File(File parent,String child):从父抽象路径名和子路径字符串创建新的file实例

File类获取方法

  • public String getAbsolutePath():返回绝对路径名字符串
  • public String getPath():将FIle转换为路径名字符串
  • public String getName():返回此File表示的文件或者目录的名称
  • public long length():返回此文件的长度

File类判断

  • public boolean exists():此File表示的文件或者目录是否存在
  • public boolean isDirectory():此File是否为目录
  • public boolean isFile():此File是否为文件

File创建删除

  • public boolean createNewFile():当且仅当具有该名称的文件尚不存在时,创建一个新的文件
  • public boolean delete():删除此文件或者目录
  • public boolean mkdir():创建目录
  • public boolean mkdirs():创建目录,包括任何必须但不存在的父目录
1
2
3
4
5
6
7
8
9
10
//String file = "f:" + File.separator + "hxx.java";
String file ="hxx.txt";
File f = new File(file);
System.out.println("文件绝对路径" + f.getAbsolutePath());
System.out.println("文件构造路径" + f.getPath());
System.out.println("文件名称" + f.getName());
System.out.println("文件长度" + f.length());
System.out.println(f.getAbsoluteFile() + "是否存在:" + f.exists());
System.out.println("是否创建:" + f.createNewFile());
//System.out.println("是否删除:" + f.delete());

File目录的遍历

  • public String[] list():返回一个String数组,表示该File目录中的所有子文件或目录
  • public File[] listFiles():返回一个File数组,表示该File目录中的所有子文件或目录

FileFilter过滤器

  • public File[] listFiles(FileFilter filter)

  • java.io.FileFilter接口用于过滤文件,其中只有一个抽象方法accept(File pathname),用来设置过虑规则。

  • java.io.FilenameFilter接口用于过滤文件名称,其抽象方法accept(File dir,String name),用来测试指定文件是否应该包含在某一个列表中。

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
public static void main(String[] args){
File file = new File("F:");
getAllFile(file);
}

public static void getAllFile(File dir){
/*File []files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if(pathname.isDirectory())return true;
return pathname.getName().toLowerCase().endsWith(".txt");
}
});*/
File []files = dir.listFiles(new FilenameFilter() {

@Override
public boolean accept(File dir, String name) {
return new File(dir,name).isDirectory() || name.toLowerCase().endsWith(".txt");
}
});
for (File file : files) {
if(file.isDirectory()){//递归遍历目录
getAllFile(file);
}
else{
System.out.println(file);
}
}
}

线程问题-Java

第一章 线程

1.1 创建多线程类对象

  • Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java通过继承Thread类来创建并启动多线程的步骤:
    1. 定义Thread类的子类,并重写该类的run()方法,该方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体
    2. 创建Thread子类的实例,即创建线程对象
    3. 调用线程对象的start()方法启动线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0;i < 5;i++){
System.out.println("run:"+i);
}
}
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
for (int i = 0;i < 5;i++){
System.out.println("main:"+i);
}
}
  • 相关方法

1
2
3
4
5
6
7
8
9
10
11
//获取当前执行线程的名称
System.out.println(Thread.currentThread().getName());
//疫苗打印一个数字
for (int i = 0;i < 5;i++){
System.out.println("main:"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

1.2 创建线程实现Runable接口

  • 实现步骤
    1. 创建一个Runable接口的实现类,在实现类中重写run方法,设置线程任务
    2. 创建Runable接口的实现类对象
    3. 创建Thread类对象,构造方法中传递Runable接口的实现类对象,调用start开启线程
1
2
3
4
5
6
7
8
9
10
11
12
public class MyRunable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
MyRunable run = new MyRunable();
Thread thread = new Thread(run);
thread.start();
System.out.println(Thread.currentThread().getName());
}
  • 实现Runable接口创建多线程的好处
    1. 避免了单继承的局限性:一个类只能继承一个类,类继承了Thread类就不能继承其他的类但是实现了Runable接口,还是可以继承其他类,实现其他的接口
    2. **增强了程序的扩展性,降低了程序的耦合性(解耦)**:实现Runable接口的方式,把设置线程任务和开启新线程进行了分离。实现类中重写了run方法:用来设置线程任务;创建Thread类对象,调用start方法:用来开启新线程

1.3 匿名内部类方式实现线程的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}.start();

new Thread(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();

第二章 线程安全

  • 多线程访问了共享的数据会容易产生线程安全问题,Java中提供了同步机制(synchronized)

2.1 同步代码块

  • synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问

    synchronized(同步锁){ //同步操作代码 }

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
public class MyRunable implements Runnable {
private int ticket = 100;
//创建一个锁对象,可以是任意类型
Object obj = new Object();

@Override
public void run() {
while (true) {
synchronized (obj){
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖" + ticket + "张票");
ticket--;
}
}
}
}
}
public static void main(String[] args) {
//创建三个线程,同时开启,对共享的票出售
MyRunable run = new MyRunable();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
t1.start();
t2.start();
t3.start();
}

2.2 同步方法

  • 使用synchronized修饰的方法。保证A线程执行该方法的时候,其他线程只能在方法外等待

    public synchronized void method(){ //........... }

  • 同步锁:对于非静态方法是this;对于static方法,是当前方法所在字节码对象(类名.class)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyRunable implements Runnable {
private int ticket = 100;
public synchronized void method(){
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖" + ticket + "张票");
ticket--;
}
}
@Override
public void run() {
while (true) {
method();
}
}
}

2.3 Lock锁机制

  • public void lock():加同步锁
  • public void unlock():释放同步锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyRunable implements Runnable {
private int ticket = 100;
Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
l.lock();
if (ticket > 0) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "正在卖" + ticket + "张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
l.unlock();//无论是否出现安全问题都会释放锁对象
}
}
}
}
}

3 线程池

  • 使用线程池的工厂类Excutors里面提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
  • 创建一个类,实现Runable接口,重写run方法设置线程任务
  • 调用ExcutorService的方法submit,传递线程任务(实现类),开启线程
1
2
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(new MyRunable());

异常-Java

1 异常概念

  • 异常

    在执行过程中,出现非正常情况导致JVM停止。在Java中异常本身是一个类,产生异常就是创建异常对象并抛出一个异常对象。

  • 异常体系

    1. 异常的根类是java.lang.Throwable,其下有两个子类java.lang.Error和java.lang.Exception

    2. Error:严重错误,无法处理的错误

      Exception:异常,程序员可以通过代码纠正

    3. 常用方法:

      public void printStackTrace():打印异常的详细信息,包含了异常的类型,异常的原因,出现的位置。在开发和调试阶段都得使用。

      public String getMessage():获取发生异常的原因,提示给用户的时候就提示错误原因

异常的处理

  • Java中异常处理的五个关键字: try、catch、finally、throw、throws

1. 抛出异常throw

  • 在指定的方法中抛出指定的异常:throw new xxxException (“异常产生的原因”)

  • 注意事项

    • throw关键字必须写在方法内部

    • throw关键字后边new的对象必须是Exception或者它的子类对象

    • throw关键字抛出指定的异常独享就必须处理这个异常对象。若创建的是RuntimeException或者RuntimeException的子类对象,可以交给JVM处理。若创建的是编译异常,就必须处理(throws或者try catch)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static int getElemnt(int[] arr,int index){
if(arr == null){
throw new NullPointerException("传递的数组值是空");
}
if(index >= arr.length || index < 0){
throw new ArrayIndexOutOfBoundsException("数组越界异常");
}
int ele = arr[index];
return ele;
}
public static void main(String[] args) {
// int[] arr = null;
// System.out.println(getElemnt(arr,0));
int[] arr1 = new int[3];
System.out.println(getElemnt(arr1,3));
}

2. Objects非空判断

  • public static < T > requireNonNull(T obj):查看指定引用对象不是null
1
2
int[] arr = null;
Objects.requireNonNull(arr,"传递的对象值为空");

3. 声明异常throws

  • 当方法内部抛出异常对象的时候,可以使用throws关键字处理异常对象,把异常对象申明抛出给方法的调用者处理(throws或者try catch)。

    修饰符 返回值类型 方法名(参数列表)throws AAAException,BBBException{

    ​ throw new AAAException(“产生原因”);

    ​ throw new BBBException(“产生原因”);

    }

  • throws关键字后面声明的异常必须是Exception或者其子类,方法内部抛出多个异常都必须声明。若存在父子类关系,就直接声明父类

1
2
3
4
5
6
7
8
9
10
11
12
public static void readFile(String fileName) throws IOException {
if(!fileName.endsWith(".txt")){
throw new IOException("文件后缀名不对");
}
if(!fileName.equals("D:\\hxx.txt")){
throw new FileNotFoundException("文件路径错误");
}
System.out.println("读取");
}
public static void main(String[] args) throws IOException {
readFile("D:\\qzy.txt");
}

4. 捕获异常try catch

try{//可能会产生异常的代码}

catch(异常类名 变量名){//异常的处理逻辑 }

1
2
3
4
5
6
7
public static void main(String[] args)  {
try {
readFile("D:\\qzy.txt");
} catch (IOException e) {
e.printStackTrace();
}
}

5. finally代码块

  • 一些特定的代码无论是异常是否发生,都需要执行。因为异常会引起程序跳转,导致有些语句执行不到
  • try....catch......finally:finally一般用于资源释放。
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args)  {
try {
readFile("D:\\qzy.txt");
} catch (IOException e) {
e.printStackTrace();
}
finally {
System.out.println("释放资源");
}
}

6. 注意事项

  • 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常
  • 父类方法没有抛出异常,子类重写父类该方法时不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出

自定义异常类

  • 格式

    public class XXXException extends Exception | RuntimeException{

    //添加一个空参数的构造方法

    //添加一个带异常信息的构造方法

    }

1
2
3
4
5
6
7
8
public class RegisterException extends Exception{
public RegisterException() {
}

public RegisterException(String message) {
super(message);
}
}