【JAVA】异常
作者主页:paper jie 的博客
本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。
本文录入于《JAVASE语法系列》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精力)打造,将javaSE基础知识一网打尽,希望可以帮到读者们哦。
其他专栏:《JAVA》《算法详解》《C语言》等
目录
什么是异常
异常的体系结构
异常的分类
编译时异常
运行时异常
异常的处理
防御式编程
异常的抛出
异常的捕获
异常声明 throws
try - catch捕获并异常
finally
异常的处理流程
自定义类
什么是异常
在java中,将程序执行过程中发生不正常行为称为异常。比如我们写代码经常遇到的:
算数异常
int a = 10 / 0;

数组越界异常
int[] arr = {1,2,3,4,5};
System.out.println(arr[10]);

空指针异常
int[] arr = null;System.out.println(arr.length);

我们可以发现,java中不同的类型的异常有对应的类来描述。
异常的体系结构
异常种类有很多种,为了对这些异常可以比较好的管理起来,Java内部维护了一个异常的体系结构:
Throwable:异常体系的顶层,还有派生出的两个类,Error和Execption
Error:是java虚拟机JVM无法解决的严重问题,比如:JVM中的内部错误,资源耗尽等。典型代表:StackOverflowErrow和OutofMemoryError,一但发生了程序就会崩溃,没有办法解决。
Execption:异常产生后程序员可以通过代码进行处理,让程序继续运行。

异常的分类
异常可以在编译期间发生,也可以在运行时发生,根据发生的不同时机,可以分为:
编译时异常
在程序编译期间发生的异常,称为编译时异常,也可以叫受查异常
class person {String name;int age;@Overrideprotected Object clone() {return super.clone();}
}

运行时异常
在程序期间发生的异常,称为运行时异常,也可以叫为非受查异常
RunTimeExecption和它的子类对应的异常,都是运行时异常。比如:NullPointerException、
ArrayIndexOutOfBoundsException、ArithmeticException
注意:这里编译的时候的语法错误可不能叫做异常。运行时异常是指程序已经通过编译得到class文件,在JVM执行的过程中出现的错误。
异常的处理
防御式编程
错误的代码是时常会出现的,在出现问题的时候我们要让程序出现问题的时候及时通知程序猿。处理方式一般有两种:
事前防御型,即在操作之前就做好充分的检查
public static void main(String[] args) {boolean ret = false;ret = 登陆游戏();if (!ret) {处理登陆游戏错误;return;} ret = 开始匹配();if (!ret) {处理匹配错误;return;} ret = 游戏确认();if (!ret) {处理游戏确认错误;return;} ret = 选择英雄();if (!ret) {处理选择英雄错误;return;} ret = 载入游戏画面();if (!ret) {处理载入游戏错误;return;}
......}
这种方式还是存在比较大的缺陷,正常的流程和处理错误的流程代码都在一起,让代码整体看起来比较混乱。
事后认错型,先操作,遇到问题再处理
try {登陆游戏();开始匹配();游戏确认();选择英雄();载入游戏画面();
...} catch (登陆游戏异常) {处理登陆游戏异常;} catch (开始匹配异常) {处理开始匹配异常;} catch (游戏确认异常) {处理游戏确认异常;} catch (选择英雄异常) {处理选择英雄异常;} catch (载入游戏画面异常) {处理载入游戏画面异常;}
......
它的优势就是可以将正常的流程和错误流程分开,让程序猿更加关注正常流程,代码更清晰,更容易理解代码,异常处理的核心思想就是EAFP
在java中,处理异常主要有5个关键字:throw,throws,try,catch,final,throws
异常的抛出
在编写程序的时候,如果程序出现了问题,就需要把程序的问题和错误信息告诉调用者。在java中,可以借助关键字throw,抛出一个指定的异常对象,将错误信息告知给调用者。
使用方式:
thow new xxxExcption("异常的原因");
举个栗子:实现一个获取数组的任意位置元素的方法
public static int getelment(int[] array, int index) {if(null == array) {throw new NullPointerException("空指针异常");}if(index < 0 || index >= array.length) {throw new ArrayIndexOutOfBoundsException("越界异常");}return array[index];}public static void main(String[] args) {int[] array = {1,2,3,4,5,6,7,8};getelment(array, 10);}

注意:
throw必须在方法体中使用
抛出的对象必须是Exception或者Exception的子类
如果抛出的是RuntimeException或者RuntimeException的子类,则可以不用处理,直接交给JVM来处理
如果抛出的是编译时异常,用户必须处理,否则无法通过编译
异常一旦抛出,后面的代码就不会执行了
异常的捕获
异常的捕获,也就是异常的具体处理方式,主要有两种:异常声明throws和try - catch捕获处理
异常声明 throws
throws在方法声明时参数列表的后面,当方法中抛出编译时异常,用户不想处理该异常,可以借助throws将异常抛给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者来处理异常
使用形式:
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{}
举个栗子:加载指定文件congfig.ini
public class Config {File file;/*FileNotFoundException : 编译时异常,表明文件不存在此处不处理,也没有能力处理,应该将错误信息报告给调用者,让调用者检查文件名字是否给错误了*/public void OpenConfig(String filename) throws FileNotFoundException{if(filename.equals("config.ini")){throw new FileNotFoundException("配置文件名字不对");}// 打开文件}public void readConfig(){}
}
注意:
throws必须在方法的参数列表之后
声明的异常必须是Exception或者Exception的子类
方法内部出现多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果异常具有父子关系,可以直接声明父类。
class Config {File file;// public void OpenConfig(String filename) throws IOException,FileNotFoundException{
// FileNotFoundException 继承自 IOExceptionpublic void OpenConfig(String filename) throws IOException {if(filename.endsWith(".ini")){throw new IOException("文件不是.ini文件");} if(filename.equals("config.ini")){throw new FileNotFoundException("配置文件名字不对");} // 打开文件}public void readConfig(){}
}
调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出
public class Test {public static void main(String[] args) {Config config = new Config();config.OpenConfig(".ini");}
有两种方法:
继续用throws抛出

用try - catch处理

try - catch捕获并异常
throws对异常并没有真正的处理,而是将异常报告告诉给调用者,让调用者来处理。如果真正要对异常处理,就要使用try - catch
使用形式:
try{
// 将可能出现异常的代码放在这里
}catch(要捕获的异常类型 e){
// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类
时,就会被捕获到
// 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码
}[catch(异常类型 e){
// 对异常进行处理
}finally{
// 此处代码一定会被执行到
}]
// 后序代码
// 当异常被捕获到时,异常就被处理了,这里的后序代码一定会执行
// 如果捕获了,由于捕获时类型不对,那就没有捕获到,这里的代码就不会被执行
举个栗子:读取配置文件,如果配置文件名字不是指定名字,抛出异常,调用者进行异常处理
class Config {File file;public void openConfig(String filename) throws FileNotFoundException{if(!filename.equals("config.ini")){throw new FileNotFoundException("配置文件名字不对");} // 打开文件}public void readConfig(){}public static void main(String[] args) {Config config = new Config();try {config.openConfig("config.txt");System.out.println("文件打开成功");} catch (IOException e) {
// 异常的处理方式
//System.out.println(e.getMessage()); // 只打印异常信息
//System.out.println(e); // 打印异常类型:异常信息e.printStackTrace(); // 打印信息最全面} // 一旦异常被捕获处理了,此处的代码会执行System.out.println("异常如果被处理了,这里的代码也可以执行");}
}
异常的处理方式
异常的种类有很多, 我们要根据不同的业务场景来决定.
对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果
对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿
对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试.
在我们当前的代码中采取的是经过简化的第二种方式. 我们记录的错误日志是出现异常的方法调用信息, 能很快速的让我们找到出现异常的位置. 以后在实际工作中我们会采取更完备的方式来记录异常信息.
注意:
try快内抛出异常位置之后的代码将不会被执行
如果抛出异常类型与catch时异常类型不匹配,也就不会处理,继续向外抛,直到JVM收到后中断系统
try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获
public static void main(String[] args) {
int[] arr = {1, 2, 3};
try {
System.out.println("before");
// arr = null;
System.out.println(arr[100]);
System.out.println("after");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("这是个数组下标越界异常");
e.printStackTrace();
} catch (NullPointerException e) {
System.out.println("这是个空指针异常");
e.printStackTrace();
} S
ystem.out.println("after try catch");
}
如果多个异常的处理方法一样,也可以这样写:
catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
...
}
如果异常之间具有父子关系,一定要子类在前,父类在后,否则语法错误:
public static void main(String[] args) {int[] arr = {1, 2, 3};try {System.out.println("before");arr = null;System.out.println(arr[100]);System.out.println("after");} catch (NullPointerException e) { // Exception可以捕获到所有异常e.printStackTrace();}catch (Exception e){ // 永远都捕获执行到e.printStackTrace();} System.out.println("after try catch");}
一个catch可以捕获所有的异常,但是没有必要
catch进行类型匹配的时候,不光会匹配相同类型的异常对象,还会捕捉目标异常类型的子类对象
finally
在写程序的时候,有一些代码就是不论程序是否发生异常,都需要执行,比如程序中打开的资源,在程序正常,异常退出时必须对资源进行回收。又因为异常会导致程序的跳转,一些代码执行不到,finally就是用来解决这个问题的。
使用形式:
语法格式:
try{
// 可能会发生异常的代码
}catch(异常类型 e){
// 对捕获到的异常进行处理
}finally{
// 此处的语句无论是否发生异常,都会被执行到
} // 如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行
public static void main(String[] args) {
try{
int[] arr = {1,2,3};
arr[100] = 10;
arr[0] = 10;
}catch (ArrayIndexOutOfBoundsException e){
System.out.println(e);
}finally {
System.out.println("finally中的代码一定会执行");
} S
ystem.out.println("如果没有抛出异常,或者异常被处理了,try-catch后的代码也会执行");
}
finally的作用
class TestFinally {public static int getData(){Scanner sc = null;try{sc = new Scanner(System.in);int data = sc.nextInt();return data;}catch (InputMismatchException e){e.printStackTrace();}finally {System.out.println("finally中代码");} System.out.println("try-catch-finally之后代码");if(null != sc){sc.close();}return 0;}public static void main(String[] args) {int data = getData();System.out.println(data);}
} // 正常输入时程序运行结果:/*100finally中代码100*/
通过这个代码,可以发现如果正常输入,成功后接到输入就返回了,后面的close就没有被执行,输入流没有被释放,造成资源泄露。而finally会被执行,就可以将close写在finally中,就不会造成资源泄露了。
注意:finally中的代码一定会被执行,因此可以在finally中进行一些资源清理的扫尾工作。
异常的处理流程
什么是调用栈:
方法之间是存在相互调用关系的,这种关系我们可以用“调用栈”来描述。在JVM中有一块内存空间称为“虚拟机栈”专门存储方法之间的调用关系。当代码出现异常的时候,我们就可以使用e.printStackTrace();的方式来查看出现异常的调用栈。
当方法中没有合适的处理异常的方式时,就会延调用栈向上传递
public static void main(String[] args) {
try {
func();
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
} System.out.println("after try catch");
}
public static void func() {
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
}
如果向上传递都没有合适的方法处理异常,就会交给JVM处理,程序就会异常终止。
public static void main(String[] args) {
func();
System.out.println("after try catch");
}
public static void func() {
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
} // 执行结果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
at demo02.Test.func(Test.java:14)
at demo02.Test.main(Test.java:8)
程序异常终止,没有执行最后一行的打印
异常处理的流程:
程序先执行try中的代码
如果try中的代码出现异常,就会结束try中的代码,看和catch中的异常类型是否匹配
如果找到匹配的异常类型,就会执行catch中的代码
如果没有找到,就会将异常向上传递到上层调用者
无论是否找到匹配的异常类型,都会执行finally
如果上层调用者也没有处理异常,就继续向上传递
一直到main方法也没有合适的代码处理异常,就会交给JVM来进行处理此时程序就会异常终止
自定义类
java中的异常类并不能完全满足我们日常开发中遇到的一些异常,此时就需要我们维护符合实际情况的异常结构
举个栗子:登陆功能
class LogIn {private String userName = "admin";private String password = "123456";public static void loginInfo(String userName, String password) {if (!userName.equals(userName)) {} if(!password.equals(password)) {} System.out.println("登陆成功");}public static void main(String[] args) {loginInfo("admin", "123456");}
}
我们发现这里我们就需要两个异常,这时我们可以基于已有的异常类进行拓展,创建与我们业务相关的异常类
方式:
自定义异常类,然后继承Exception或者RunTimeException
实现一个带有String类型参数的构造方法,作用就是告诉出现异常的原因
代码实现;
class LogIn {private static String Name = "admin";private static String word = "123456";public static void loginInfo(String userName, String password) throws UernameException,PasswordException {if (!userName.equals(Name)) {throw new UernameException("用户名错误");}if(!password.equals(password)) {throw new PasswordException("密码错误");}System.out.println("登陆成功");}public static void main(String[] args) {try {loginInfo("adin", "1236");}catch(UernameException | PasswordException e) {e.printStackTrace();}}
}
注意:
自定义异常通常会继承Exception或者RunTimeException
继承的Exception是编译时异常
RunTimeException是运行时异常
相关文章:
【JAVA】异常
作者主页:paper jie 的博客 本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。 本文录入于《JAVASE语法系列》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和…...
合同矩阵充要条件
两个实对称矩阵合同的充要条件是它们的正负惯性指数相同。 正惯性指数是矩阵正特征值个数,负惯性指数是矩阵负特征值个数。 即合同矩阵的充分必要条件是特征值的正负号个数相同。 证明: 本论证中的所有矩阵都是对称矩阵。 根据定义,若矩…...
数据分析三剑客之Pandas
1.引入 前面一篇文章我们介绍了numpy,但numpy的特长并不是在于数据处理,而是在它能非常方便地实现科学计算,所以我们日常对数据进行处理时用的numpy情况并不是很多,我们需要处理的数据一般都是带有列标签和index索引的࿰…...
Spring Boot自动装配原理
简介 Spring Boot是一个开源的Java框架,旨在简化Spring应用程序的搭建和开发。它通过自动装配的机制,大大减少了繁琐的配置工作,提高了开发效率。本文将深入探讨Spring Boot的自动装配原理。 自动装配的概述 在传统的Spring框架中…...
VMware Workstation虚拟机网络配置及配置自动启动
目录 一、网络配置二、配置自动启动1.VMware 中配置虚拟机自启动2.系统服务中配置 VMware 服务自启动 一、网络配置 本文将虚拟机 IP 与主机 IP 设置为同一个网段。 点击 “编辑” -> “虚拟网络编辑器(N)…”: 点击 “更改设置”: 将 VMnet0 设置…...
智能语音机器人竞品调研
一、腾讯云-智能客服机器人 链接地址:智能客服机器人_在线智能客服_智能客服解决方案 - 腾讯云 二、阿里云-智能语音机器人 链接地址:智能对话机器人-阿里云帮助中心 链接地址:智能外呼机器人的业务架构_智能外呼机器人-阿里云帮助中心 三、火…...
【操作系统】进程的概念、组成、特征
概念组成 程序:静态的放在磁盘(外存)里的可执行文件(代码) 作业:代码+数据+申请(JCB)(外存) 进程:程序的一次执行过程。 …...
大二第二周总结
问题: 想到了之前追的辩论赛,主题是“被误解是表达者的宿命”, 反方认为被误解不是表达者的宿命: 由于表达者表意含混造成误解的可能性是人力可控的,表达者可在真诚沟通的基础之上,根据对方反应不断调整…...
JDK、eclipse软件的安装
一、打开JDK安装包 二、复制路径 三、点击我的电脑,找到环境变量 四、新建环境 变量名:JAVA_HOME 变量值就是刚刚复制的路径 五、在path中建立新变量 双击path 打印以下文字 最后一直双击确定,安装环境完成。 六、双击eclipse 选择好安装…...
235. 二叉搜索树的最近公共祖先 Python
文章目录 一、题目描述示例 1示例 2 二、代码三、解题思路 一、题目描述 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足…...
Apollo介绍和入门
文章目录 Apollo介绍配置中心介绍apollo介绍主流配置中心功能特性对比 Apollo简介 入门简单的执行流程Apollo具体的执行流程Apollo对象执行流程分步执行流程 核心概念应用,环境,集群,命名空间企业部署方案灰度发布全量发布 配置发布的原理发送…...
一文看懂Oracle 19c OCM认证考试(需要Oracle OCP证书)
Oracle OCM的认证全称是Oracle Certified Master,是比OCP更高一级的认证,姚远老师的很多OCP学员都对OCM考试有兴趣,这里跟大家做个介绍。 OCM考试全部是上机的实操考试,没有笔试,要到Oracle原厂参加两天的考试。参加1…...
回归预测 | MATLAB实现PSO-SDAE粒子群优化堆叠去噪自编码器多输入单输出回归预测(多指标,多图)
回归预测 | MATLAB实现PSO-SDAE粒子群优化堆叠去噪自编码器多输入单输出回归预测(多指标,多图) 目录 回归预测 | MATLAB实现PSO-SDAE粒子群优化堆叠去噪自编码器多输入单输出回归预测(多指标,多图)效果一览…...
python自学
自学第一步 第一个简单的基础,向世界说你好 启动python 开始 print是打印输出的意思,就是输出引号内的内容。 标点符号必须要是英文的,因为他只认识英文的标点符号。 exit()推出python。 我们创建一个文本文档&…...
元宇宙安全与著作权相关市场与技术动态:韩国视角
元宇宙市场动态 元宇宙安全与著作权维护技术现状 元宇宙有可能为商业创造巨大价值,尤其是在零售和时尚领域。时尚产品的象征性价值不仅在物理空间中得以保持,在虚拟空间中也是如此。通过元宇宙平台,企业可以开发虚拟产品,降低供…...
springboot整合neo4j--采用Neo4jClient和Neo4jTemplate方式
1.背景 看了spring-boot-starter-data-neo4j的源码之后发现,该starter内已经实现了Neo4jClient和Neo4jTemplate,我们只需要使用Autowire就能直接使用它操作neo4j。 Neo4jClient方式与我的另一篇springboot整合neo4j-使用原生cypher Java API博客方式一样…...
【算法与数据结构】701、LeetCode二叉搜索树中的插入操作
文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析:这道题关键在于分析插入值的位置,不论插入的值是什么(插入值和原有树中的键值都…...
前端--HTML
文章目录 HTML结构快速生成代码框架HTML常见标签 表格标签 编写简历信息 填写简历信息 Emmet 快捷键 HTML 特殊字符 一、HTML结构 1.认识HTML标签 HTML 代码是由 "标签" 构成的. 形如: <body>hello</body> 标签名 (body) 放到 < > 中 大部分标…...
安装配置 zookeeper(单机版)
目录 一 准备并解压安装包 二 修改zoo.cfg文件 三 创建相应两个目录 四 创建文件myid 五 修改环境变量 六 启动 zookeeper 一 准备并解压安装包 这里提供了网盘资源 http://链接: https://pan.baidu.com/s/1BybwSQ_tQUL23OI6AWxwFw?pwdd4cf 提取码: d4cf 这里的安装包是…...
2023/9/7 -- C++/QT
作业 1> 思维导图 2> 封装一个结构体,结构体中包含一个私有数组,用来存放学生的成绩,包含一个私有变量,用来记录学生个数, 提供一个公有成员函数,void setNum(int num)用于设置学生个数 提供一个…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...
浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化
是不是受够了安装了oracle database之后sqlplus的简陋,无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话,配置.bahs_profile后也能解决上下翻页这些,但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可,…...
