文章目录

  • 前言
  • 一、异常处理
  • 二、异常类型
  • 三、异常处理的其它方面
    • 1.声明异常
    • 2.抛出异常
    • 3.捕获异常
    • 4.示例学习:声明、抛出和捕获异常
  • 四、finally子句
  • 五、何时使用异常
  • 六、重新抛出异常
  • 七、链式异常
  • 八、创建自定义异常类

前言

异常是运行时错误,异常处理使得程序可以处理运行时错误,并且继续通常的执行。异常机制提供了程序退出时的安全通道。当出现错误后,程序执行流程发生改变,程序的控制权转移到异常处理器。

编译错误:程序没有遵循语法规则,编译程序能够自己发现并提示我们错误的原因和位置。

运行时错误:程序在执行时,运行环境发现了不能执行的操作。

逻辑错误:程序没有按照预期的逻辑顺序执行。

在程序运行过程中,如果JVM检测出一个不可能执行的操作,就会出现运行时错误。例如,如果使用一个越界的下标访问数组,程序就会产生一个ArrayIndexOutOfBoundsException的运行时错误;如果程序需要输入一个整数的时候用户输入了一个double值,会得到InputMismatchException的运行时错误。

在Java中,运行时错误会被作为异常抛出。异常就是一种对象,表示阻止正常运行的错误或者情况。如果异常没有处理,那么程序将会非正常终止。


一、异常处理

异常是从方法抛出的,方法的调用者可以捕获并处理该异常。

为了演示异常处理,包括异常是如何创建以及抛出的,我们从读取两个整数并显示它们商的例子开始。

import java.util.Scanner;/*** @author MNH* @version 1.0* @project Name: JAVA基础* @file Name: Quotient* @desc 功能描述* @by IDE: IntelliJ IDEA*/
public class Quotient {public static void main(String[] args) {Scanner input=new Scanner(System.in);System.out.println("Enter two integer:");int num1=input.nextInt();int num2=input.nextInt();System.out.println(num1+"/"+num2+"="+(num1/num2));}
}


如果输入0赋值给第二个数字,那就会产生一个运行时错误,因为不能用一个整数除以0(注意,一个浮点数除以0是不会产生异常的)。解决这个错误的一个简单方法就是添加一个if语句来测试第二个数字。

import java.util.Scanner;/*** @author MNH* @version 1.0* @project Name: JAVA基础* @file Name: QuotientWithIf* @desc 功能描述* @by IDE: IntelliJ IDEA*/
public class QuotientWithIf {public static void main(String[] args) {Scanner input=new Scanner(System.in);System.out.println("请输入两个整数:");int num1=input.nextInt();int num2=input.nextInt();if(num2!=0){System.out.println(num1+"+"+num2+"="+(num1/num2));}else{System.out.println("被除数不能为0");}}
}

为了介绍异常处理,我们使用一个方法计算商,程序如下:

import java.util.Scanner;/*** @author MNH* @version 1.0* @project Name: JAVA基础* @file Name: QuotientWithMethod* @desc 功能描述* @by IDE: IntelliJ IDEA*/
public class QuotientWithMethod {public static int quotient(int num1,int num2){if(num2==0){System.out.println("被除数不能为0");System.exit(1);}return num1/num2;}public static void main(String[] args) {Scanner input=new Scanner(System.in);System.out.println("请输入两个整数:");int num1=input.nextInt();int num2=input.nextInt();System.out.println(num1+"/"+num2+"="+quotient(num1,num2));}
}



方法quotient返回两个整数的商,如果num2为0,则不能返回一个值,程序在System.exit(1)处终止,这显然是一个问题。不应该让方法来终止程序—应该由调用者决定是否终止程序。

方法如何通知它的调用者一个异常产生了呢?Java可以让一个方法抛出一个异常,该异常可以被调用者捕获和处理。

import java.util.Scanner;/*** @author MNH* @version 1.0* @project Name: JAVA基础* @file Name: QuotientWithException* @desc 功能描述* @by IDE: IntelliJ IDEA*/
public class QuotientWithException {public static void quotient(int num1, int num2) {try {if (num2 == 0) {throw new ArithmeticException("被除数不能等于0");//抛出一个异常}System.out.println(num1 + "/" + num2 + "=" +(num1/num2));}catch (ArithmeticException e) {System.out.println("被除数不能为0");}System.out.println("程序继续......");}public static void main(String[] args) {Scanner input = new Scanner(System.in);System.out.println("请输入两个整数:");int num1 = input.nextInt();int num2 = input.nextInt();quotient(num1,num2);}
}//异常就是从一个异常类创建的对象//本程序中异常就是Java.lang.ArithmeticException的构造方法ArithmeticException(str)被调用以创建异常类对象,str是描述异常的消息


如果num2等于0,,方法通过执行以下语句抛出一个异常:throw new ArithmeticException(“被除数不能为0”)。在这种情况下,抛出的值为new ArithmeticException(“被除数不能为0”),称为一个异常。throw语句的执行称为抛出一个异常。异常就是从异常类创建的对象。在这种情况下,异常类就是java.lang.ArithmeticException。构造方法ArithmeticException(str)被调用以构建一个异常对象,其中str是描述异常的消息。

当异常被抛出时,正常的执行流程被中断。就像他的名字所提示的,“抛出异常”就是将异常从一个地方传递到另一个地方。调用方法的语句也包含在一个try块和一个catch块中。try块包含了正常情况下执行的代码,异常被catch块所捕获。catch块中的代码被执行以处理异常,之后,catch块之后的语句被执行。

throw语句类似方法的调用,但不同于调用方法的是,它调用的是catch块。从某种意义上讲,catch块就像带参数的方法定义,这些参数匹配抛出的值的类型。但是它不像方法,在执行完catch块之后,程序控制不返回到throw语句,而是执行catch块后的下一条语句。

catch(ArithmeticException ex)中的标识符ex的作用很像是方法中的参数,这个参数称为catch块中的参数。ex之前的类型(如,ArithmeticException)指定了catch块可以捕获的异常类型。一旦捕获该类型,就能从catch块体中的参数访问这个抛出的值。

一个异常可能是通过try块中的throw语句直接抛出,或者调用一个可能会抛出异常的方法而抛出。

异常处理的优点:它能使方法抛出一个异常给它的调用者,并由调用者处理该异常。如果没有这个能力,那么被调用的方法就必须自己处理异常或者终止该程序。被调用的方法通常不知道在出现错误时应该做些什么,只有调用者自己清楚出现错误时需要做些什么。异常处理最根本的优势就是将检测错误(由被调用的方法完成)从处理错误中分离出来。

import java.util.Scanner;/*** @author MNH* @version 1.0* @project Name: JAVA基础* @file Name: InputMismatchException* @desc 功能描述* @by IDE: IntelliJ IDEA*/
public class InputMismatchException {public static void main(String[] args) {Scanner input=new Scanner(System.in);boolean continueInput=true;while(continueInput){try{System.out.println("请输入一个整数:");input.nextInt();continueInput=false;}catch(java.util.InputMismatchException e){System.out.println("输入的不是整数,请重新输入");input.nextLine();}}}
}//变量continueInput控制循环,它的初始值是true,当键入一个整数时,该值就变成false


当执行input.nextInt()时,如果输入的不是一个整数,就会产生一个InputMismatchException异常。

二、异常类型

异常是对象,对象都采用类来定义。异常的根类是java.lang.Throwable。Throwable类是所有异常类的根,所有Java异常类都直接或间接地继承自Throwable,可以通过继承Exception或者Exception的子类来创建自己的异常类。

这些异常类可以分为三种主要类型:系统错误、异常或运行时异常。(1)系统错误:系统错误是由Java虚拟机抛出的,用Error类表示的。Error类描述的是内部系统错误,这样的错误很少发生。如果发生,除了通知用户以及尽量稳妥地终止程序外,几乎什么也做不了。(2)异常:异常是用Exception类表示的,它描述的是由你的程序和外部环境引起的错误,这些错误能被程序捕获和处理。(3)运行时异常:运行时异常是用RuntimeException类表示的,它描述的是程序设计错误,例如,错误的类型转换、访问一个越界数组或数值错误,运行时异常通常表明了编程错误。

RuntimeException、Error以及它们的子类都称为免检异常,在大多数情况下,免检异常都会反映出程序设计上不可恢复的逻辑错误。例如,如果通过一个引用变量访问一个对象之前并未将一个对象赋值给它,就会抛出NullPointerException异常;如果访问一个数组的越界元素,就会抛出IndexOutOfBoundsException异常。这些都是程序中必须纠正的逻辑错误。免检异常可能在程序的任何一个地方出现,为避免过多地使用try-catch块,Java语言不强制要求编写代码捕获或声明免检异常。所有其它异常称为必检异常,意味着编译器会强制程序员检查并通过try-catch块处理它们,或者在方法头中声明。

必检异常增加了代码的健壮性,但是为了对异常进行抛出、捕获和处理,需要增加较多代码,会降低代码的可读性。如果异常影响到系运行的安全性和正确性的时候,必须对必检异常进行处理,否则这些必检异常是可以转换成免检异常。

三、异常处理的其它方面

Java的异常处理模型基于三种基本操作:声明一个异常、抛出一个异常、捕获一个异常。

1.声明异常

Java解释器调用main方法开始执行一个程序,当前执行的语句必属于某个方法,每个方法都必须声明它可能抛出的必检异常的类型,称为声明异常。因为任何代码都可能发生系统错误和运行时错误,因此Java不要求在方法中显示地声明Error和RuntimeException。然而,方法要跑出的其它异常都必须在方法头中显示声明,这样,方法的调用者会被告知有异常。

为了在方法中声明一个异常,就要在方法头中使用关键字throws,如:public void method() throws IOException,关键字throws表明method()方法可能会抛出异常IOException。如果方法可能抛出多个异常,就可以在关键字throws后添加一个用逗号分隔的异常列表:public void method() throws Exception1,Exception2,…,ExceptionN。如果父类中的方法没有声明异常,那么就不能在子类中对其重写时声明异常。

2.抛出异常

检测到错误的程序可以创建一个合适的异常类型的实例并抛出它,这称为抛出一个异常,如throw new IllegalArgumentException(“Wrong argument”)。IllegalArgumentException是Java API中的一个异常类,通常,Java API中每个异常类都至少有两个构造方法:一个无参构造方法和一个带有可以描述这个异常的String参数的构造方法,该参数称为异常消息,它可以通过一个异常对象调用getMessage()获取。

声明异常的关键字是throws,抛出异常的关键字是throw。

3.捕获异常

当抛出一个异常时,可以在try-catch块中捕获和处理它。

try{}catch(Exception1 ex1){}catch(Exception2 ex2){}
...
catch(ExceptionN exN){}

如果在执行try块的过程中没有出现异常,则跳过catch子句。如果try块中的某条语句抛出一个异常,Java就会跳过try块中剩余的语句,然后开始查找处理这个异常的代码,处理异常的这个代码称为异常处理器。从第一个到最后一个逐个检查catch块,判断在catch块中的异常类实例是否是该异常对象的类型。如果是,就将该异常对象赋值给所声明的变量,然后执行catch块中的代码。如果没有发现异常处理器,Java会退出这个方法,把异常传递给这个方法的调用者,继续同样的过程来查找处理器。如果在调用的方法链中找不到处理器,程序就会终止并且在控制台上打印出错误信息。查找处理器的过程称为捕获一个异常。

假设main方法调用method1()方法,method1()方法调用method2()方法,method2()方法调用method3()方法,method3()方法抛出一个异常,考虑下面的情形:

main method{try{method1();statement1}catch(Exception1 ex1){}statement2
}class method1{try{method2();statement3}catch(Exception2 ex2){}statement4
}class method2{try{method3();//一个异常在method3()方法中抛出statement5}catch(Exception3 ex3){}statement6
}

如果异常类型是Exception3,它就会被method2()中处理异常ex3的catch块捕获,跳过statement5,然后执行statement6。

如果异常类型是Exception2,,则退出method2()方法,控制被返回给method1()方法,而这个异常就会被method1()中处理异常ex2的catch块捕获,跳过statement3,然后执行statement4。

如果异常类型是Exception1,则退出method1()方法,控制被返回给main()方法,而这个异常就会被main()方法中处理异常ex1的catch块捕获,跳过statement1,然后执行statement2。

如果异常类型没有在method2、method1、main方法中捕获,程序就会终止,不执行statement1和statement2。

各种异常类都可以从一个共同的父类中派生。如果一个catch块可以捕获一个父类的异常对象,那么它就可以捕获那个父类的所有子类的异常对象。

在catch块中异常被指定的顺序是非常重要的,如果父类的catch块出现在子类的catch块之前,就会导致编译错误。

try{}catch(Exception e){}catch(RuntimeException ex){}//错误的顺序
try{}catch(RuntimeException e){}catch(Exception ex){}//正确的顺序

对于使用同样的代码处理多种异常的情况,可以使用JDK7的新的多捕获特征简化异常的代码书写。语法是:

catch(Exception1 | Exception2 | ... |ExceptionN e){}

每个异常使用竖线(|)与下一个分隔,如果其中一个异常被捕获,则执行处理的代码。

4.示例学习:声明、抛出和捕获异常

创建一个CircleWithException类的对象,如果setRadius(double radius)中radius是负数,则抛出一个IllegalArgumentException异常。

/*** @author MNH* @version 1.0* @project Name: JAVA基础* @file Name: CircleWithException* @desc 功能描述* @by IDE: IntelliJ IDEA*/
public class CircleWithException {private double radius;private static int numberOfCircle=0;public CircleWithException(double radius){setRadius(radius);numberOfCircle++;}public CircleWithException(){this(0);}public double getRadius(){return radius;}public void setRadius(double radius){if(radius>=0) {this.radius = radius;}else{throw new IllegalArgumentException("半径不能为负数");}}public double area(){return radius*radius*3.14;}public static int getNumberOfCircle(){return numberOfCircle;}public static void main(String[] args) {try{CircleWithException circleWithException1=new CircleWithException(2.0);CircleWithException circleWithException2=new CircleWithException(-2.0);CircleWithException circleWithException3=new CircleWithException();}catch(IllegalArgumentException e){System.out.println(e.toString());}System.out.println("共有"+numberOfCircle+"个圆被创建");}
}


IllegalArgumentException是异常类RuntimeException(免检异常)的子类,所以,如果不使用try语句,这个测试程序也能编译成功。

万一出现了异常,程序仍然会继续,而如果处理器没有捕获到这个异常,程序就会突然中断。

当没有异常发生时,try-catch的存在对系统性能的影响很小,可以说不会引起额外的系统开销。

异常发生后:(1)初始化异常对象(2)从调用栈返回(3)沿方法调用链来传播异常,以找到它的异常处理器。

四、finally子句

无论异常是否发生,finally子句总会被执行。

有时候,不论异常是否出现或是否被捕获,都希望执行某些代码。Java的finally子句可以用来实现这个目的,finally子句的语法如下:

try{statements;
}catch(Exception e){}finally{finalStatements;
}

在任何情况下,finally块中的代码都会执行,不论try块中是否出现异常或者是否被捕获。考虑以下三种可能出现的情况:(1)如果try块中没有出现异常,执行fianlStatements,然后执行try语句的下一条语句;(2)如果try块中有一条语句引起了异常并被catch块捕获,会跳过try块的其它语句,执行catch块和finally子句,并执行try语句后的下一条语句;(3)如果try块中的一条语句引起异常,但是没有被任何catch块捕获,就会跳过try块中的其它语句,执行finally子句,并且将异常传递给这个方法的调用者。

即使在到达finally块之前有一个return语句,finally块还是会执行,同时使用finally子句时可以省略catch块。

五、何时使用异常

当错误需要被方法的调用者处理的时候,方法应该抛出一个异常。

try块包含正常情况下执行的代码,catch块包含异常情况下执行的代码。异常处理将错误处理代码从正常的编程中分离出来,这样,就可以使程序更易读、更易修改。由于异常处理需要初始化新的异常对象,需要从调用栈返回,而且还需要沿着方法调用链来传播异常以便找到它的异常处理器,所以异常处理需要更多的时间和资源。

异常发生在方法中,如果想让该方法的调用者处理异常,应该创建一个异常对象并将其抛出。如果能在发生异常的方法中处理异常,那么就不需要抛出或使用异常。一般来说,一个项目中多个类都会发生的共同异常应该考虑设计为一个异常类。对于发生在个别方法中的简单错误最好进行局部处理,无需抛出异常,这可以通过使用if语句来检测错误并实现。

六、重新抛出异常

如果异常处理器不能处理一个异常,或者只是简单地希望他的调用者注意到该异常,Java允许该异常处理器重新抛出异常。

try{statements;
}catch(Exception ex){throw ex;
}

语句throw ex重新抛出异常给调用者,以便调用者的其它处理器获得处理异常ex的机会。

七、链式异常

与另一个异常一起抛出一个异常,构成了链式异常。

有时候,可能需要同最初异常一起抛出一个新异常(带有附加信息),称为链式异常。

/*** @author MNH* @version 1.0* @project Name: JAVA基础* @file Name: ChainedExceptionDemo* @desc 功能描述* @by IDE: IntelliJ IDEA*/
public class ChainedExceptionDemo {public static void method3() throws Exception{throw new Exception("异常来自method3()方法");}public static void method2() throws Exception{try{method3();}catch(Exception exc){throw new Exception("异常来自method2()方法",exc);}}public static void method1() throws Exception{try{method2();}catch(Exception ex){throw new Exception("异常来自method1()方法",ex);}}public static void main(String[] args) {try{method1();}catch(Exception e){e.printStackTrace();}}
}

八、创建自定义异常类

可以通过继承Java.lang.Exception类来定义一个自定义异常类。

Java提供相当多的异常类,尽量使用它们而不要创建自己的异常类。如果遇到一个不能用预定义异常类来充分描述的问题,那就可以通过继承Exception类或其子类来创建自己的异常类。

/*** @author MNH* @version 1.0* @project Name: JAVA基础* @file Name: InvalidRadiusException* @desc 功能描述* @by IDE: IntelliJ IDEA*/
public class InvalidRadiusException extends Exception{private double radius;public InvalidRadiusException(double radius){super("Invalid radius:"+radius);this.radius=radius;}public double getRadius(){return radius;}
}

这个自定义异常类继承自java.lang.Exception,而Exception类继承自java.lang.Throwable,Exception类中的所有方法(例如,getMessage()、toString()、printStackTrace())都是从Throwable继承而来。

Exception类的构造方法:
Exception():构建一个没有信息的异常
Exception(String message):构建一个给定信息的异常
Exception(String message,Exception cause):构建一个具有指定信息和子句的异常,这形成了一个链式异常

/*** @author MNH* @version 1.0* @project Name: JAVA基础* @file Name: TestCircleWithCustomException* @desc 功能描述* @by IDE: IntelliJ IDEA*/
public class TestCircleWithCustomException {public static void main(String[] args) {try {CircleWithCustomException circle1 = new CircleWithCustomException(2);CircleWithCustomException circle2 = new CircleWithCustomException(-2);CircleWithCustomException circle3 = new CircleWithCustomException();} catch (InvalidRadiusException ex) {System.out.println(ex);}System.out.println("创建圆的个数:" + CircleWithCustomException.getNumberOfCircle());}
}class CircleWithCustomException {private double radius;private static int numberOfCircle = 0;public CircleWithCustomException(double radius) throws InvalidRadiusException {setRadius(radius);numberOfCircle++;}public CircleWithCustomException() throws InvalidRadiusException {this(0);}public void setRadius(double radius) throws InvalidRadiusException {if (radius >= 0) {this.radius = radius;} else {throw new InvalidRadiusException(radius);}}public double getRadius() {return radius;}public static int getNumberOfCircle() {return numberOfCircle;}public double area() {return 3.14 * radius * radius;}
}


也可以继承RuntimeException声明一个自定义类异常,但这不是一个好方法,因为这会使自定义异常成为免检异常。最好使自定义异常是必检的,这样编译器就可以在程序中强制捕获这些异常。

一篇文章带你理解并掌握Java的异常相关推荐

  1. java ee 值范围_一篇文章带你读懂: Java EE

    原标题:一篇文章带你读懂: Java EE 点击上图,查看教学大纲 何为 Java EE Java EE是一个标准中间件体系结构 不要被名称"Java PlatformEnterprise ...

  2. 一篇文章带你速度过完Java基础

    Java基础 Java 数据类型 基本数据类型 包装数据类型 引用类型 数据类型转换 缓存池 经典面试题1 经典面试题2 变量 内存分析 作用范围 生命周期 Java 关键字 Java关键字汇总 fi ...

  3. spring框架_一篇文章带你理解Spring框架

    虽然现在流行用SpringBoot了,很多配置已经简化和封装了,但是对于Spring的一些基础我们了解一些是对我们自己的架构思想很有帮助的!接下来和笔者一起来探讨一下Spring框架吧! 1.什么是S ...

  4. 一篇文章带你理解爬虫究竟是什么?

    目录 前言 爬虫的应用场景 爬虫的技术选型 简单的爬虫 脑洞大开的爬虫解决思路 复杂的爬虫设计 音视频爬虫实战 一.先从几个方面来简单介绍我们音视频爬虫项目的体系 二.分步来讲下细节 三.遇到的问题和 ...

  5. 一篇文章带你理解套接字Socket的各个接口

    文章目录 前言 Socket函数 connect函数 bind函数 listen函数 accept函数 为什么要有监听描述符和已连接描述符,他们间的区别是什么 大家好,我是Song同学,时间过得很快, ...

  6. 一篇文章带你快速理解JVM运行时数据区 、程序计数器详解 (手画详图)值得收藏!!!

    受多种情况的影响,又开始看JVM 方面的知识. 1.Java 实在过于内卷,没法不往深了学. 2.面试题问的多,被迫学习. 3.纯粹的好奇. 很喜欢一句话:"八小时内谋生活,八小时外谋发展. ...

  7. (面经总结)一篇文章带你整理面试过程中关于Java 中多线程的创建方式的最全整理

    文章目录 一.Java线程的创建方式 二.继承Thread类 三.实现 Runnable 接口 四.通过ExecutorService和`Callable`实现有返回值的线程 五.基于线程池 六.面试 ...

  8. java 不重启部署_一篇文章带你搞定SpringBoot不重启项目实现修改静态资源

    一.通过配置文件控制静态资源的热部署 在配置文件 application.properties 中添加: #表示从这个默认不触发重启的目录中除去static目录 spring.devtools.res ...

  9. 一篇文章带你领悟 Frida 的精髓(基于安卓8.1)

    转载(一篇文章带你领悟Frida的精髓(基于安卓8.1)):https://www.freebuf.com/articles/system/190565.html <Frida操作手册>: ...

最新文章

  1. 中国团队 Nature 新冠论文被指图片造假,科研论文不端“何时休”
  2. 19年BAT常问面试题汇总:JVM+微服务+多线程+锁+高并发性能
  3. python字符串、列表和文件对象总结
  4. 剑指Offer——二叉树的镜像
  5. Python:一行代码将以e为结尾的科学计算法类型的数值转为小数点类型数值
  6. 信号量、互斥体和自旋锁
  7. 稀缺:百分之二的选择
  8. spring boot示例_Spring Boot上的Spring社交示例,或者我如何停止担心和喜欢自动配置...
  9. 网站架构之缓存应用(摘录)
  10. 关于JavaBean
  11. html三个div相同高度,两个并列div高度相同的问题_html/css_WEB-ITnose
  12. 开启MyBatis(一)
  13. 电音插件auto_Antares 发布新型人声制作插件 Auto-Tune EFX +
  14. 诊断(UDS)协议栈设计-系列讲解
  15. java junit测试类怎么写_15.junit测试类使用及注解
  16. php做文章伪原创,php简单的伪原创程序
  17. 人工智能5秒免费抠图软件将改变设计行业???
  18. AcrelEMS-IDC综合能效管理系统在某大型数据中心的应用方案
  19. MT7658芯片组资料,MT7658处理器参数介绍
  20. web前端期末大作业:旅游网页主题网站设计——桂林旅游网站的设计 (7页)HTML+CSS+JavaScript web网页设计与开发 静态网页的制作 web期末作业设计网页 web结课作业的源

热门文章

  1. 索莫菲模型的一些理解 Smomerfeld Model
  2. 淘宝店铺装修 免费扶植版教程
  3. Office2016,2019等家庭和学生版中word打开时显示“word无法启动转换器RECOVR32.CNV”的解决办法
  4. 使用bat自动执行cmd命令(多个命令或单个命令)
  5. Android--数据持久化存储概述
  6. TestCaseManageSystem 自动化解决方案 [开源项目] 基于 AgileTC 的测试用例自动化框架完美版
  7. linux后台运行nohup | 进程查看、终止 | linux命令记录
  8. 农产品生鲜电商+供应链一体化:降低物流成本,终端商更易获利
  9. 51c语言数码管编程,51单片机的数码管循环显示编程实例
  10. matlab洗碗机节水模型的优化设计-这是个课题名称,不是买洗碗机,审核的人仔细看下,谢谢