前言今天笔者将会开启新的专栏后端开发在这之前笔者先总结一下所有JAVA基础知识以及后续JVM的深度解析以供后续阅览参考由于篇幅原因Java专栏将分为上、中、下三期。本篇仅供查阅复习使用。后几期也会进行总结本期总结的知识是《Java编程的逻辑》这本书的知识点。长文警告基础知识基本数据类型整数类型byte/short/int/long4个分别占1、2、4、8字节小数类型float/double 2个分别占 4、8字节字符类型: char 1个2字节真假类型: boolean 1个1字节JAVA中除了基础数据类型其余的都是对象类型(有基础类型构成)。我们定义一个变量比如int a就是内存分配了一块空间并且a指向这片空间的所在位置地址。PS1 给long类型赋值时侯要在常量后面加上大写L或小写字母l因为数字常量默认是int类型。2 给float类型赋值时候也需要加上大写F或小写f因为小数常量默认是double类型。数组类型基本类型的数组有三种赋值形式1 int[] arr {1,2,3}2 int[] arr new int[]{1,2,3}3 int[] arr new int[3]; arr[0]1;arr[1]2;arr[2]3;注意1 数组长度可以动态确定一旦确定就不能给改了。2 不能在给定初始值的同时给定长度即有初值又确定长度。(初始值已决定长度)基本运算算术运算算术运算符有加、减、乘、除符号分别是、-、*、/另外还有取模运算符%以及自增和自减–运算符。取模运算适用于整数和字符类型其他算术运算适用于所有数值类型和字符类型。比较运算比较运算就是计算两个值之间的关系结果是一个布尔类型boolean的值。比较操作符有大于​、大于等于​、小于​、小于等于​、等于​、不等于! ​。注意 比较两个数组是否想等时不能用 因为 判断的是两个变量指向的是不是同一个数组。逻辑运算与​两个都为true才是true只要有一个是false就是false或|​只要有一个为true就是true都是false才是false非!​针对一个变量true会变成false, false会变成true异或^​两个相同为false两个不相同为true短路与​和类似不同之处稍后解释短路或||​与|类似不同之处稍后解释。条件执行包括if、if/else、if/else if/else、三元运算符以及switch跟C语言类似不多累述。实现原理:if、if/else、if/else if/else、三元运算符通过条件跳转和无条件跳转switch通过跳转表实现。注意switch表达式中可以是byte、short、int、char、枚举和String不能是long因为跳转表值的存储空间一般为32位容纳不下long。循环结构while、do/while、for和foreach其他的相对简单跟C语言类似我就举一下foreach的例子。int[] arr {1,2,3,4};for(int element : arr){System.out.println(element);}foreach不是一个关键字它使用冒号:​冒号前面是循环中的每个元素包括数据类型和变量名称冒号后面是要遍历的数组或集合​每次循环element都会自动更新。对于不需要使用索引变量只是简单遍历的情况foreach语法上更为简洁。函数Java中任何函数都需要放在一个类中。函数结构如下修饰符 返回值类型 函数名字(参数类型 参数名字…) {操作return返回值}函数一般也称为方法类里面可以定义一个main的方法类似这样public static void main(String[] args) {…}String[] args 表示从控制台接收到的参数。注意1 在参数传递过程中如果数组作为参数的话在函数中修改数组那么调用者的数组也会直接被修改。2 Java中具有可变长度的参数实际上参数会转化为数组参数。可变参数语法是在基础变量类型后面加…示例public static int max(int min, int … a){int max min;for(int i0; ia.length; i){if(maxa[i]){max a[i];}}return max;}public static void main(String[] args) {System.out.println(max(0));System.out.println(max(0,2));System.out.println(max(0,2,4));System.out.println(max(0,2,4,5));}3 函数重载指的是函数名相同参数不同的情况。栈栈一般是从高位地址向低位地址扩展换句话说栈底的内存地址是最高的栈顶的是最低的。计算机系统主要使用栈来存放函数调用过程中需要的数据包括参数、返回地址以及函数内定义的局部变量。而如果是数组或对象类型它们存放实际内容的地址放在栈中而存放的实际内容在堆中。整数的二进制表示正整数的二进制表示在二进制中每个位置只能是0或1从右到左第一位为1然后依次乘以2即第二为2第三位为4以此类推我们称这种表示方法叫原码表示法。负整数的二进制表示负整数采用补码表示法我们先用原码表示法对其用原码表示再对其取反1。举例-1:1的原码表示是00000001取反是11111110然后再加1就是11111111。小数的二进制表示浮点数的表示采用IEEE 754标准它定义了两种格式一种是32位的对应于Java的float另一种是64位的对应于Java的double。根据精度不同IEEE 754 定义了两种常用格式单精度浮点数float32 位其中符号位 1 位指数位 8 位尾数位 23 位。双精度浮点数double64 位其中符号位 1 位指数位 11 位尾数位 52 位。这里以单精度浮点数举例在这里插入图片描述偏置值Bias用于将指数转换为非负数。单精度的 Bias 为,双精度为 Bias 为隐含最高位尾数的最高位始终为 1二进制科学计数法因此在存储时省略以节省空间。举例在这里插入图片描述在IEEE 754标准里还定义了一些特殊值用于处理计算中的异常情况非规格数无穷大(Infinity)当指数全为1且尾数全为0时表示无穷大。符号位决定正负(如Infinity和-Infinity)非数(NaNNot a Number): 当指数全为1且尾数非0的时候表示无效操作。零(Zero)分为正零和负零符号位决定正负指数和尾数全为0float和double的取值范围在这里插入图片描述字符的编码编码有两大类一类是非Unicode编码另一类是Unicode编码。我们先介绍非Unicode编码。非Unicode编码包括ASCII、ISO 8859-1、Windows-1252、GB2312、GBK、GB18030和Big5。ASCll用一个字节存储字符最高位设置为0其它位表示字符。数字32126表示的字符都是可打印字符031和127表示一些不可以打印的字符。在这里插入图片描述ASCII码对美国是够用了但对其他国家是不够用的相对的各个国家都有自己的编码方式不过为了保持与ASCll码的兼容性一般设置最高位为1当最高位为1时表示各个国家自己的字符当为0时表示ASCll码。在这些扩展的编码中在西欧国家中流行的是ISO 8859-1和Windows-1252在中国是GB2312、GBK、GB18030和Big5。Unicode编码Unicode给所有字符分配了唯一数字编号再从编号对应到二进制表示又有多种方案主要有UTF-32、UTF-16和UTF-8。UTF-32用4个字节表示编号但有个细节就是字节的排列顺序如果第一个字节是整数二进制中的最高位最后一个字节是整数二进制中的最低位那这种字节序就叫“大端”​Big Endian, BE​否则就叫“小端”​Little Endian, LE​。对应的编码方式分别是UTF-32BE和UTF-32LE。UTF-16使用变长字节表示常用字符集(编号在U0000UFFFF)用两个字节表示增补字符集(编号在U10000U10FFFF)用4个字节表示。区分是两个字节还是4个字节表示一个字符就看前两个字节的编号范围如果是UD800UDBFF就是4个字节否则就是两个字节。UTF-8也使用变长字节表示编号小的使用的字节就少编号大的使用的字节就多使用的字节个数为14不等。编码转化有了Unicode之后每一个字符就有了多种不兼容的编码方式编码转换的具体过程可以是一个字符从A编码转到B编码先找到字符的A编码格式通过A的映射表找到其Unicode编号然后通过Unicode编号再查B的映射表找到字符的B编码格式。编码转换改变了字符的二进制内容但并没有改变字符看上去的样子。字符乱码与恢复乱码有两种常见原因一种比较简单就是简单的解析错误另外一种比较复杂在错误解析的基础上进行了编码转换。第一种就是没有找到正确的编码解析方式导致乱码。这个我们通过找到正确的解析方式就能恢复。第二种则在文本错误解析的基础上还进行了Unicode编码转化。这种就需要先转化为我们错误解析的方式的二进制再正确解析它。以下代码是用来恢复乱码的方法。public static void recover(String str)throws UnsupportedEncodingException{String[] charsets new String[]{“windows-1252”, “GB18030”, “Big5”, “UTF-8”};for(int i0; icharsets.length; i){for(int j0; jcharsets.length; j){if(i! j){String s new String(str.getBytes(charsets[i]), charsets[j]);System.out.println(“---- 原来编码(A)假设是 charsets[j]”, 被错误解读为了(B): charsets[i]);System.out.println(s);System.out.println();}}}}char的实质在Java内部进行字符处理时采用的都是Unicode具体编码格式是UTF-16BE。char本质上是一个固定占用两个字节的无符号正整数这个正整数对应于Unicode编号用于表示那个Unicode编号对应的字符。PS这跟C中的char有显著差异 C 标准规定char的大小要足以存储系统基本字符集中的任意字符满足ASCll字符集它仅需 7 位就能表示 128 个不同字符所以 1 个字节8 位完全能够涵盖这些字符。面向对象类类可以是函数的容器同时它更多的是表示自定义数据类型。函数容器类中有很多方法举个例子。Java API中的类Math。在这里插入图片描述要使用这些函数直接在前面加Math.即可。这些函数都有相同的修饰符public static也叫静态方法与类方法相对的是实例方法。自定义数据类型我们将类看作自定义数据类型用于表示和处理基本类型以外的其他数据。一个数据类型由其包含的属性和该类型可以进行的操作组成属性可以分为类本身的属性也可以是实例具有的属性操作也类似。这样一个数据类型就主要由4部分组成类型本身具有的属性通过类变量体现。类型本身可以进行的操作通过类方法体现。类型实例具有的属性通过实例变量体现。类型实例可以进行的操作通过实例方法体现。类变量和实例变量都叫成员变量也就是类的成员类变量也叫静态变量或静态成员变量。类方法和实例方法都叫成员方法也都是类的成员类方法也叫静态方法。PS1 类方法只能访问类变量不能访问实例变量可以调用其他的类方法不能调用实例方法。2 实例方法既能访问实例变量也能访问类变量既可以调用实例方法也可以调用类方法。3 在操作实例变量时我们一般把实例变量设置为private用实例方法去访问/修改这样能避免误操作。实际上Java编译器一般也会将对访问/修改的方法的调用转换为直接访问实例变量而避免函数调用的开销。类的组合类中可以嵌套类同时一个类定义中还可以引用它自己实例变量不需要一开始就有值。举例说明。public class MyFile {//文件名称private String name;//创建时间private Date createtime;//文件大小private int size;//上级目录private MyFolder parent;//其他方法……public int getSize() {return size;}}public class MyFolder {//文件夹名称private String name;//创建时间private Date createtime;//上级文件夹private MyFolder parent;//包含的文件private MyFile[] files;//包含的子文件夹private MyFolder[] subFolders;public int totalSize(){int totalSize 0;if(files! null){for(MyFile file : files){totalSizefile.getSize();}}if(subFolders! null){for(MyFolder folder : subFolders){totalSizefolder.totalSize();}}return totalSize;}//其他方法……}在这个例子中两个类之间可以互相引用MyFile引用了MyFolder而MyFolder也引用了MyFile同时MyFolder也引用了它自己。代码的组织机制包的概念包在Java中主要是为了解决命名冲突。它类似于计算机中的文件夹包有包名这个名称以点号(.)分隔表示层次结构比如我们之前常用的String类就位于包java.lang下。完整包名的类名称为其完全限定名比如String类的完全限定名为java.lang.String。Java API中所有的类和接口都位于包Java或javax下Java是标准包javax是扩展包。1 声明包默认情况下类位于默认包默认包属于Java中自带的如果一个 Java 源文件开头没有使用 package 语句来声明包那么该源文件中的所有类就都属于默认包。此外包的声明需要和文件目录匹配因为Java 编译器和 JVM 会按照包名所对应的目录结构去查找类文件。2 通过包使用类使用有两种方式一种是通过类的完全限定名另外一种是将用到的类引入当前类。例外java.lang包下的类可以直接使用。此外还有一种特殊类型的导入称为静态导入它有一个static关键字可以直接导入类的公开静态方法和成员。看个例子import java.util.Arrays;import static java.util.Arrays.; //静态导入Arrays中的所有静态方法import static java.lang.System.out; //导入静态变量outpublic class Hello {public static void main(String[] args) {int[] arr new int[]{1,4,2,3};sort(arr); //可以直接使用Arrays中的sort方法out.println(Arrays.toString(arr)); //可以直接使用out变量}}3 包范围可见性包的可见性范围就是同一个包内同一个包内的其他类可以访问而其他包内的类则不可以访问。jar包为方便使用第三方代码也为了方便我们写的代码给其他人使用各种程序语言大多有打包的概念打包的一般不是源代码而是编译后的代码。在Java中编译后的一个或多个包的Java class文件可以打包为一个文件Java中打包命令为jar打包后的文件扩展名为jar一般称之为jar包。在编译后的java class文件根目录运行以下命令jar -cvf 包名.jar 最上层包名使用jar包的话我们将其加入类路径classpath中即可。程序的编译与链接编译是将源代码文件变成.class的一种字节码通过javac命令完成。链接是在运行时动态执行的。所谓链接就是根据引用到的类加载相应的字节码并执行。java编译和运行时都需要以参数指定一个classpath即类路径。类路径可以是多个对于jar包路径是jar包的完整名称在Windows系统中多个路径用分号;“分隔在其他系统中以冒号”:分隔。总的来说编译会确定引用的每个类的完全限定名链接时则会根据类的完全限定名去寻找加载类。类的继承根父类在Java中即使没有声明父类也有一个隐含的父类Object。包括以下这些方法toString 方法返回一个对象的文本描述。类名对象的hash值。方法重写我们需要在函数前面加上Override类的继承1 Java使用extends关键字表示继承关系一个类最多只能有一个父类2 子类不能直接访问父类的私有属性和方法。除了私有的外子类继承了父类的其他属性和方法。3 super关键字用于指代父类可用于调用父类构造方法访问父类方法和变量。super和this区别this引用一个对象是实实在在存在的可以作为函数参数可以作为返回值但super只是一个关键字不能作为参数和返回值它只是用于告诉编译器访问父类的相关变量和方法。注意如果子类没有通过super调用则会自动调动父类的默认构造方法。多态多态即一种类型的变量可引用多种实际类型对象。举个shape例子public class Shape {private static final String DEFAULT_COLOR “black”;private String color;public Shape() {this(DEFAULT_COLOR);}public Shape(String color) {this.color color;}public String getColor() {return color;}public void setColor(String color) {this.color color;}public void draw(){System.out.println(“draw shape”);}}public class ShapeManager {private static final int MAX_NUM 100;private Shape[] shapes new Shape[MAX_NUM];private int shapeNum 0;public void addShape(Shape shape){if(shapeNumMAX_NUM){shapes[shapeNum] shape;}}public void draw(){for(int i0; ishapeNum; i){shapes[i].draw();}}}public static void main(String[] args) {ShapeManager manager new ShapeManager();manager.addShape(new Circle(new Point(4,4),3));manager.addShape(new Line(new Point(2,3), new Point(3,4), “green”));manager.addShape(new ArrowLine(new Point(1,2),new Point(5,5), “black”, false, true));manager.draw();}对于变量shape它就有两个类型类型Shape我们称之为shape的静态类型类型Circle/Line/ArrowLine我们称之为shape的动态类型。在ShapeManager的draw方法中shapes[i].draw()调用的是其对应动态类型的draw方法这称之为方法的动态绑定重名与静态绑定静态绑定通过静态类型访问变量和方法即访问绑定到变量的静态类型。静态绑定在程序编译阶段即可决定而动态绑定则要等到程序运行时。实例变量、静态变量、静态方法、private方法都是静态绑定的。重载和重写重载是指方法名称相同但参数签名不同参数个数、类型或顺序不同​重写是指子类重写与父类相同参数签名的方法。父子类型转换一个父类的变量能不能转换为一个子类的变量取决于这个父类变量的动态类型即引用的对象类型是不是这个子类或这个子类的子类。我们能够通过instanceof关键字进行判断看下面代码public boolean canCast(Base b){return b instanceof Child;}继承访问权限protectedprotected表示虽然不能被外部任意访问但可被子类访问。可见性重写重写时子类方法不能降低父类方法的可见性。不能降低是指父类如果是public则子类也必须是public父类如果是protected子类可以是protected也可以是public即子类可以升级父类方法的可见性但不能降低。总的来说子类的可见性范围是越来越大的。防止继承finalfinal这个关键字可以修饰变量也可以限制继承。一个Java类默认情况下都是可以被继承的但加了final关键字之后就不能被继承了public final class Base {//主体代码}类加载过程在Java中类的加载是指将类的相关信息加载到内存。此外类是动态加载的当第一次使用这个类的时候才会加载加载一个类时会先加载其父类。类初始化代码是先执行父类的再执行子类的。在Java中还有一个内存区存放类的信息称为方法区。对象创建的过程在类加载之后创建对象过程包括1分配内存2对所有实例变量赋默认值3执行实例初始化代码。每个对象实际内存分配在堆中除了保存类的实例变量之外还保存着实际类信息的引用。接口继承容易破坏封装那我们可以使用接口替代继承。双方对象并不直接互相依赖它们只是通过接口间接交互。我们看一张图。在这里插入图片描述public interface MyComparable {int compareTo(Object other);}我们定义一下接口通过interface这个关键字定义修饰符一般为public接口定义里面(Java 8 之前)不能实现方法只能声明方法接口方法不需要修饰符。后面会介绍能实现的方法只能说静态方法和默认方法。接口还需要至少两个参与者一个需要实现接口另一个使用接口。实现接口1)Java使用implements这个关键字表示实现接口前面是类名后面是接口名。2)实现接口必须要实现接口内声明的所有方法。3)一个类可以实现多个接口,各个接口之间以逗号分隔。使用接口1)接口不能new需要通过new一个实现接口的类的对象或者引用实现接口的类对象。类似下面的代码。MyComparable p1 new Point(2,3);MyComparable p2 new Point(1,2);System.out.println(p1.compareTo(p2));通过这个方法接口类型变量可以不关心具体的类型但却可以对任意实现了MyComparable接口的类型进行操作。2)针对接口而非具体类型进行编程是计算机程序的一种重要思维方式接口更重要的是降低了耦合提高了灵活性。接口的一些细节1)接口中可以定义变量,变量修饰符默认为public static final通过接口名.变量名进行访问。public interface Interface1 {public static final int a 0;}2) 接口可以被继承。一个接口可以继承其他接口和类 类似,也是extends关键词。public interface IBase1 {void method1();}public interface IBase2 {void method2();}public interface IChild extends IBase1, IBase2 {}3) 类可以继承类的同时实现接口extends要放在implements之前。 java public class Child extends Base implements IChild { //主体代码 } 4) 接口中也可以用instanceof关键字判断对象的类是否实现了接口。 Point p new Point(2,3); if(p instanceof MyComparable){ System.out.println(comparable); } 5) Java 8允许在接口中定义两类新方法静态方法和默认方法它们有实现体。静态方法通过(接口名.函数名)调用。引入默认方法主要是函数式数据处理的需求是为了便于给接口增加功能。 public interface IDemo { void hello(); public static void test() { System.out.println(hello); } default void hi() { System.out.println(hi); } } 抽象类 抽象类就是抽象的类是一种高层的概括抽象。 1) 抽象类与具体类最大的区别就是抽象类不能创建对象。 2) 抽象类中如果有抽象方法类必须申明为抽象类。 3) 抽象类和抽象方法都通过abstract这个关键词修饰。 4) 一个类继承了抽象类必须实现它的抽象方法除非它自己也是抽象类。 5) 抽象类跟接口不同抽象类中可以定义实例变量。同时一个类可以实现多个接口但只能继承一个类。 6) 抽象类和接口是配合而非替代关系它们经常一起使用接口声明能力抽象类提供默认实现实现全部或部分方法一个接口经常有一个对应的抽象类。 我们看一个抽象类的代码 public abstract class Shape { //其他代码 public abstract void draw(); } 内部类 一个类在另一个类的内部我们称之为内部类包含它的类称之为外部类。内部类与包含它的外部类有比较密切的关系而与其他类关系不大定义在类内部可以实现对外部完全隐藏可以有更好的封装性代码实现上也往往更为简洁。 1) 每个内部类最后都会被编译为一个独立的类 2) 内部类可以访问外部类的私有变量可以声明为private从而实现对外完全隐藏 3) 内部类又分为静态内部类、成员内部类、方法内部类和匿名内部类 4) 静态内部类,可以访问外部类的静态变量和方法注意不能访问实例变量和方法在类中可以直接使用外部类.静态内部类方法使用 public class Outer { private static int shared 100; public static class StaticInner { public void innerMethod(){ System.out.println(inner shared); } } public void test(){ StaticInner si new StaticInner(); si.innerMethod(); } } 5) 成员内部类相较于静态内部类少了static修饰词。 public class Outer { private int a 100; public class Inner { public void innerMethod(){ System.out.println(outer a a); Outer.this.action(); } } private void action(){ System.out.println(action); } public void test(){ Inner inner new Inner(); inner.innerMethod(); } } 成员内部类还可以通过“外部类this.xxx”的方式引用外部类的实例变量和方法如Outer.this. action() 此外与静态内部类不同成员内部类对象总是与一个外部类对象相连的。在外部使用时它不能直接通过new Outer.Inner()的方式创建对象而是要先将创建一个Outer类对象。创建内部类对象的语法是“外部类对象.new 内部类()“ Outer outer new Outer(); Outer.Inner inner outer.new Inner(); inner.innerMethod(); 6 方法内部类方法内部类只能在定义的方法内被使用。如果方法是**静态方法**则方法内部类只能访问外部类的**静态变量和方法**。 public class Outer { private int a 100; public void test(final int param){ final String str hello; class Inner { public void innerMethod(){ System.out.println(outer a a); System.out.println(param param); System.out.println(local var str); } } Inner inner new Inner(); inner.innerMethod(); } } 7 匿名内部类与前面介绍的内部类不同匿名内部类没有单独的类定义它在创建对象的同时定义类。匿名内部类只能被使用一次用来创建一个对象。与方法内部类一样匿名内部类也可以访问外部类的所有变量和方法可以访问方法中的final参数和局部变量。 new 父类(参数列表) { //匿名内部类实现部分 } public class Outer { public void test(final int x, final int y){ Point p new Point(2,3){ Override public double distance() { return distance(new Point(x, y)); } }; System.out.println(p.distance()); } } 枚举 在Java中枚举是一种特殊的数据它的取值是有限的是可以枚举出来的。举例 我们定义一个枚举类型Size包括三个尺寸小、中、大。编号为012 public enum Size { SMALL, MEDIUM, LARGE } 使用方法 Size size Size.SMALL; System.out.println(size.toString()); System.out.println(Just a moment...()); 枚举类型也都有一个静态的values方法返回一个包括所有枚举值的数组顺序与声明时的顺序一致。 for(Size size : Size.values()){ System.out.println(size); } 枚举类型本质上也是类但由于编译器自动做了很多事情因此它的使用更为简洁、安全和方便。在其中我们可以定义实例变量和方法。使用也用普通类类似。 public enum Size { SMALL(S, 小号), MEDIUM(M, 中号), LARGE(L, 大号); private String abbr; private String title; private Size(String abbr, String title){ this.abbr abbr; this.title title; } public String getAbbr() { return abbr; } public String getTitle() { return title; } public static Size fromAbbr(String abbr){ for(Size size : Size.values()){ if(size.getAbbr().equals(abbr)){ return size; } } return null; } } Size s Size.MEDIUM; System.out.println(s.getAbbr()); //输出M s Size.fromAbbr(L); System.out.println(s.getTitle()); //输出“大号” 枚举中的id编号默认是按0,1,2...递增但也可以自己定义代码如下 public enum Size { XSMALL(10), SMALL(20), MEDIUM(30), LARGE(40); private int id; private Size(int id){ this.id id; } public int getId() { return id; } } 异常 常见异常 1 NullPointerException(空指针异常) ,举例报空指针异常。变量s没有初始化就调用其实例方法indexOf。 public class ExceptionTest { public static void main(String[] args) { String s null; s.indexOf(a); System.out.println(end); } } 当执行s.indexOf(a)的时候Java虚拟机发现s的值为null没有办法继续执行了这时就启用异常处理机制首先创建一个异常对象这里是类NullPointerException的对象然后查找看谁能处理这个异常在示例代码中没有代码能处理这个异常因此Java启用默认处理机制即打印异常栈信息到屏幕并退出程序。 2 NumberFormatException数字格式异常 public class ExceptionTest { public static void main(String[] args) { if(args.length1){ System.out.println(请输入数字); return; } int num Integer.parseInt(args[0]); System.out.println(num); } } 如果用户命令行输入的不是数字就会报这个异常。 异常类 NullPointerException和NumberFormatException都是异常类所有异常类都有一个共同的父类Throwable。 Throwable类 Throwable类有两个主要参数一个是message表示异常消息另一个是cause表示触发该异常的其他异常。它有四种构造方法。 1. public Throwable() 2. public Throwable(String message) 3. public Throwable(String message, Throwable cause) 4. public Throwable(Throwable cause) 在所有构造方法的内部都有一句重要的函数调用fillInStackTrace(); 它会将异常栈信息保存下来这是我们能看到异常栈的关键。 Throwable有一些常用方法用于获取异常信息比如 void printStackTrace() //打印异常栈信息到标准错误输出流 void printStackTrace(PrintStream s)//打印栈信息到指定的流 void printStackTrace(PrintWriter s) String getMessage() //获取设置的异常message Throwable getCause() //获取异常的cause StackTraceElement[] getStackTrace()//获取异常栈每一层的信息 每个StackTraceElement包括文件名、类名、函数名、行号等信息 在这里插入图片描述 Throwable是所有异常的基类它有两个子类Error和Exception。 1Error表示系统错误或资源耗尽由Java系统自己使用应用程序不应抛出和处理。 2Exception表示应用程序错误它有很多子类应用程序也可以通过继承Exception或其子类创建自定义异常。 3 RuntimeException比较特殊它表示的实际含义是未受检异常(unchecked exception)​受检(checked)和未受检(unchecked)的区别在于Java如何处理这两种异常。对于受检异常Java会强制要求程序员进行处理否则会有编译错误而对于未受检异常则没有这个要求。未受检异常理论上是可以通过良好的编程实践和代码审查来避免的因此编译器不会强制要求处理这些异常。这使得开发者可以将注意力集中在程序的核心逻辑上而不是花费大量的精力在异常处理上。 自定义异常类 我们自定义异常类一般是继承Exception或者它的某个子类。 public class AppException extends Exception { public AppException() { super(); } public AppException(String message, Throwable cause) { super(message, cause); } public AppException(String message) { super(message); } public AppException(Throwable cause) { super(cause); } } 异常处理 在java中异常处理包括catch、throw、finally、try-with-resources和throws等关键字。 1) catch匹配。 异常处理机制将根据抛出的异常类型找第一个匹配的catch块找到后执行catch块内的代码不再执行其他catch块。 在catch块内处理完后可以重新抛出异常异常可以是原来的也可以是新建的 try{ //可能触发异常的代码 }catch(NumberFormatException e){ System.out.println(not valid number); }catch(RuntimeException e){ System.out.println(runtime exception e.getMessage()); }catch(Exception e){ e.printStackTrace(); } try{ //可能触发异常的代码 }catch(NumberFormatException e){ System.out.println(not valid number); throw new AppException(输入格式不正确, e);//可以是新建的异常 }catch(Exception e){ e.printStackTrace(); throw e; } 2) try/catch/finally,在这其中catch可以有多条。 try{ //可能抛出异常 }catch(Exception e){ //捕获异常 }finally{ //不管有无异常都执行 } 在try/catch/finally语法中catch不是必需的finally也不是必须的也就是可以只有try/finallytry/catchtry。 finally内的代码不管有无异常发生都会执行具体来说: - 如果没有异常发生在try内的代码执行结束后执行 - 如果有异常发生且被catch捕获在catch内的代码执行结束后执行。 - 如果有异常发生但没被捕获则在异常被抛给上层之前执行。 由于finally的这个特点它一般用于释放资源如数据库连接、文件流等。 try{ //可能触发异常的代码 }catch(NumberFormatException e){ System.out.println(not valid number); }catch(RuntimeException e){ System.out.println(runtime exception e.getMessage()); }catch(Exception e){ e.printStackTrace(); } 3try-with-resources 对于一些使用资源的场景比如文件和数据库连接典型的使用流程是首先打开资源最后在finally语句中调用资源的关闭方法针对这种场景Java 7开始支持一种新的语法称之为try-with-resources。这种语法针对实现了java.lang.AutoCloseable接口的对象。 public interface AutoCloseable { void close() throws Exception; } public static void useResource() throws Exception { try(AutoCloseable r new FileInputStream(hello)) { //创建资源 //使用资源 } } 原来还需要关闭资源的使用。 public static void useResource() throws Exception { AutoCloseable r new FileInputStream(hello); //创建资源 try { //使用资源 } finally { r.close(); } } 4) throws 异常机制中还有一个和throw很像的关键字throws用于声明一个方法可能抛出的异常而throw 是一个语句用于在方法内部手动抛出一个异常对象。 语法如下所示 public void test() throws AppException, SQLException, NumberFormatException { //主体代码 } throws后面可以跟多个异常类型多个异常类型之间用逗号分隔对于未受检异常是不要求使用throws进行声明的但对于受检异常则必须进行声明trows如果没有声明则不能抛出。 public void test() throws AppException, SQLException, NumberFormatException { //主体代码 } 异常应该且仅用于异常情况在真正出现异常的时候应该抛出异常而不是返回特殊值。例如String的substring()方法返回一个子字符串 public String substring(int beginIndex) { if(beginIndex 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen value.length - beginIndex; if(subLen 0) { throw new StringIndexOutOfBoundsException(subLen); } return(beginIndex 0) ? this : new String(value, beginIndex, subLen); } 总结 由于篇幅原因本专栏总共分为3期本期总结了Java的基本数据类型、基本语句、函数、类、继承、多态、接口、抽象类、内部类、枚举及异常等。下期会继续总结常见继承类、泛型和容器、文件以及最后一期总结并发和函数式编程。 参考文献 Java编程的逻辑 马俊昌