##day08 笔记:
一、throw续和throws
1. throw用来对外主动抛出一个异常,通常两种情况会主动对外抛出异常:
- 当程序遇到一个满足语法,但是不满足业务要求时,可以抛出一个异常告知调用者。
- 程序执行遇到一个异常,但是该异常不应当在当前代码片段被解决时可以抛出给调用者
public void setAge(int age)throws Exception{
if (age<0||age>100){
//throw new RuntimeException("年龄超出了范围"); 只有这个异常时方法上面可以不用throws
throw new Exception("年龄超出了范围");
}
this.age = age;
}
2. 当我们使用throw主动对外抛出一个异常时,除了RuntimeException以外的其他异常都需要在方法上显示的声明throws来对外告知要抛出该异常
- 当我们调用一个含有throws声明异常抛出的方法时,编译器要求我们必须处理该异常。处理方式有两种:
1)使用try-catch主动捕获并处理该异常
2)在这句代码所在的方法(这里是main方法)上继续使用throws对外声明抛出给上层调用者
具体选取那种需要结合实际业务分析该异常处理的责任问题。
3)永远不要在main方法上声明throws
Person p = new Person();
//满足语法不满足业务场景
try {
p.setAge(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("此人年龄:"+p.getAge());
3. 当子类重写超类含有throws声明异常抛出的方法时,对throws的重写规则
public class ThrowsDemo {
public void dosome()throws IOException, AWTException { }
}
class SubClass extends ThrowsDemo{
1)允许抛出相同异常
public void dosome()throws IOException, AWTException { }
2)允许不再抛出任何异常
public void dosome(){ }
3)允许仅抛出部分异常
public void dosome()throws IOException{ }
4)允许抛出超类方法声明抛出异常的子类型异常
public void dosome()throws FileNotFoundException { }
5)不允许抛出额外异常(超类方法没有声明的且不存在继承关系的)
public void dosome()throws SQLException { }
6) 不允许抛出超类方法抛出异常的超类型异常
public void dosome()throws Exception { }
}
4. 异常分类
1)Java异常可以分为可检测异常,非检测异常:
- 可检测异常:可检测异常经编译器验证,对于声明抛出异常的任何方法,编译器将强制执行处理或声明规则,不捕捉这个异常,编译器就通不过,不允许编译
- 非检测异常:非检测异常不遵循处理或者声明规则。在产生此类异常时,不一定非要采取任何适当操作,编译器不会检查是否已经解决了这样一个异常
- RuntimeException类属于非检测异常,因为普通JVM操作引起的运行时异常随时可能发生,此类异常一般是由特定操作引发。但这些操作在java应用程序中会频繁出现。因此它们不受编译器检查与处理或声明规则的限制。实际上RuntimeException及其子类型表达的都是因为程序漏洞(BUG),即:逻辑不严谨等原因导致的。这类异常都是通过修复代码可完全避免的异常,因此不应当由异常处理机制来处理
2)常见的RuntimeException子类
- IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数
- NullPointerException:当应用程序试图在需要对象的地方使用 null 时,抛出该异常
- ArrayIndexOutOfBoundsException:当使用的数组下标超出数组允许范围时,抛出该异常
- ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常
- NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
5.异常API
1)Exception上有一些常用的方法
- void printStackTrace():用于输出当前异常的堆栈跟踪信息
便于程序员找到错误根源,分析错误原因制定B计划
- String getMessage():用于获取当前错误消息
多用于提示给用户或记录日志使用
System.out.println("程序开始了");
try {
String str = "abc";
System.out.println(Integer.parseInt(str));
} catch (NumberFormatException e) {
e.printStackTrace();//输出错误
//错误消息多用于提示给用户或记录日志使用
String message = e.getMessage();//获取错误消息
System.out.println(message);
}
System.out.println("程序结束了");
2)自定义异常
通常用来声明那些在项目中满足语法但是不满足业务场景的问题
自定义异常需要做一下几件事:
1:类名要做到见名知义
2:继承Exception(直接或间接继承都可以)
3:提供超类异常提供的所有构造器(单独建个类,后面使用时)
案例: 聊天室客户端
public class Client {
/* java.net.Socket 套接字
Socket封装了TCP协议的通讯细节,使用它可以通过网络与远端计算机进行TCP链接
并基于两条流(输入与输出)与远端计算机通讯。
Socket可以想象为我们使用的"手机"。 */
private Socket socket;
// 构造器,用来初始化客户端
public Client(){
try {
/* 实例化Socket时需要传入两个参数用于与远端计算机建立链接。
实例化的过程就是与远端计算机建立链接的过程。
如果实例化成功说明与远端计算机建立链接。如果建立链接失败则会抛出异常
参数1:远端计算机的IP地址
参数2:远端计算机上的服务端程序开启的服务端口
我们通过IP可以找到网络上服务端所在的计算机,通过端口找到该计算机上 运行的服务端应用程序
注:localhost代表本机 */
System.out.println("正在链接服务端...");
socket = new Socket("localhost",8088);
System.out.println("与服务端成功链接!");
} catch (IOException e) {
e.printStackTrace();
}
}
// 客户端开始工作的方法
public void start() {
try {
/* Socket提供的重要方法
OutputStream getOutputStream()
通过socket获取的字节输出流写出的数据就会发送给与之连接的远端
计算机。 */
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);
BufferedWriter bw = new BufferedWriter(osw);
PrintWriter pw = new PrintWriter(bw,true);
Scanner scanner = new Scanner(System.in);
while(true) {
String line = scanner.nextLine();//获取控制台输入的一行字符串
if("exit".equalsIgnoreCase(line)){
break;
}
pw.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//最终交互完毕后,要断开连接
try {
//close方法内部会进行TCP的四次挥手操作
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Client client = new Client();
client.start();
}
}
// 聊天室服务端
public class Server {
/* java.net.ServerSocket
* 运行在服务端的ServerSocket主要有两个工作:
* 1:打开服务端口,客户端就是通过这个端口与服务端建立连接的
* 2:监听服务端口,一旦一个客户端连接,则立即返回一个Socket实例 */
private ServerSocket serverSocket;
public Server(){
try {
/*ServerSocket实例化的同时指定服务端口
如果该端口被其他程序占用则会抛出异常:
java.net.BindException:address already in use
此时我们需要更换端口,或者杀死占用该端口的进程。
端口号范围:0-65535 */
System.out.println("正在启动服务端...");
serverSocket = new ServerSocket(8088);
System.out.println("服务端启动完毕!");
} catch (IOException e) {
e.printStackTrace();
}
}
// 服务端开始工作的方法
public void start(){
try {
/* ServerSocket的一个重要方法:
Socket accept()
该方法用于接受客户端的连接。这是一个阻塞方法,调用后会"卡住",直到一个客户端与ServerSocket连接,此时该方法会立即返回一个Socket实例
通过这个Socket实例与该客户端对等连接并进行通讯。
相当于"接电话"的动作 */
System.out.println("等待客户端连接...");
Socket socket = serverSocket.accept();
System.out.println("一个客户端连接了!");
/* Socket一个重要的方法
InputStream getInputStream()
通过socket获取的字节输入流可以读取远端计算机发送过来的数据*/
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr);
String line;
/* 当客户端断开连接时,如果是异常断开,服务端这里readLine方法会
抛出异常:ava.net.SocketException: Connection reset
客户端正常挥手操作,那么服务端这里readLine方法会返回null*/
while((line = br.readLine())!=null){
System.out.println("客户端说:" + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server = new Server();
server.start(); } }