JVM,JDK , JRE的区别
字符型常量和字符串常量的区别
构造器Constructor是否可以被override?
重载和重写的区别
重载
重写
Java面向对象编程三大特性:封装、继承、多态
String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
可变性
线程安全性
性能
对于三者使⽤的总结:
自动装箱与拆箱
解析
在⼀个静态⽅法内调⽤⼀个⾮静态成员为什么是⾮法的?
在 Java 中定义⼀个不做事且没有参数的构造⽅法的作⽤
接口和抽象类的区别
成员变量与局部变量的区别有哪些?
构造方法有哪些特性
静态方法和实例方法有何不同
对象的相等与指向他们的引⽤相等,两者有什么不同?
== 与 equals(重要)
hashCode()与equals(重要)
Java 值传递
简述线程、程序、进程的基本概念。以及他们之间关系是什么?
final,static,this,super 关键字总结
final 关键字
static 关键字
this 关键字
super 关键字
深拷⻉ vs 浅拷⻉
Java 中的异常处理
Java 异常层次结构图
异常处理总结
JVM,JDK , JRE的区别
JVM概念
:Java虚拟机(JVM)是运行Java字节码的虚拟机。Java程序从源代码到运行一般有3 步:
JVM有针对不同系统的特定实现(Win, Linux, macOS),目的是使用相同的字节码,他们会给出相同的结果。字节码和不同系统的JVM实现,是Java语言"一次编译,”随处可以运行"的关键。
JDK概念
:Java Development Kit,它是功能⻬全的 Java SDK。它拥有 JRE 所拥有的⼀切,还有编译器 (javac)和⼯具(如 javadoc 和 jdb)。它能够创建和编译程序。(能创建和编译)
JRE概念
:Java 运⾏时环境。它是运⾏已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机 (JVM),Java 类库,java 命令和其他的⼀些基础构件。但是,它不能⽤于创建新程序。(能编译不能创建)
字符型常量和字符串常量的区别
- 形式上:
字符常量
是单引号
引起的单个字符;字符串常量
是双引号
引起的多个字符; - 含义上:
字符常量
相当于一个整形值(ASCII值)
,可以参加表达式运算;字符串常量
代表一个地址值
(该字符串在内存中的存放位置); - 占内存大小:字符串常量占2个字节;字符串常量占若干个字节。
构造器Constructor是否可以被override?
Constructor 不能被 override(重写),但是可以 overload(重载),所以你可以看到⼀个类中有多个 构造函数的情况。
tip: 构造方法有两个特点:构造方法名与类名相同、构造方法没有返回值和void值。如果重写构造方法没有返回值与void值会报错。
重载和重写的区别
重载
就是同样的⼀个⽅法能够根据输⼊数据的不同,做出不同的处理。重写
就是当⼦类继承⾃⽗类的相同⽅法,输⼊数据⼀样,但要做出有别于⽗类的响应时,你就要覆 盖⽗类⽅法。
重载
发⽣在同⼀个类
中,⽅法名必须相同,参数类型不同、个数不同、顺序不同,⽅法返回值和访问修饰符 可以不同。重载就是同⼀个类中多个同名⽅法根据不同的传参来执⾏不同的逻辑处理。
重写
重写发⽣在运⾏期
,是⼦类对⽗类的允许访问的⽅法的实现过程进⾏重新编写。
- 返回值类型、⽅法名、参数列表必须相同,抛出的异常范围⼩于等于⽗类,访问修饰符范围⼤于 等于⽗类。
- 如果⽗类⽅法访问修饰符为 private/final/static 则⼦类就不能重写该⽅法,但是被 static 修饰的⽅法能够被再次声明。
- 构造⽅法⽆法被重写 综上:重写就是⼦类对⽗类⽅法的重新改造,外部样⼦不能改变,内部逻辑可以改变。
区别 | 重载 | 重写 |
---|---|---|
发生范围 | 同一个类 | 子类 |
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可修改 | 一定不能修改 |
异常 | 可修改 | 可以减少或者删除,但是不能抛出新的或者范围更广的异常 |
访问修饰符 | 可修改 | 不能更严,只能降低限值 |
发生阶段 | 编译期 | 运行期 |
Java面向对象编程三大特性:封装、继承、多态
封装:封装把⼀个对象的属性私有化
,同时提供⼀些可以被外界访问的属性的⽅法,如果属性不想被外界访 问,我们⼤可不必提供⽅法给外界访问。但是如果⼀个类没有提供给外界访问的⽅法,那么这个类也没 有什么意义了。继承:继承是使⽤已存在的类的定义作为基础建⽴新类的技术,新类的定义可以增加新的数据或新的功能,也 可以⽤⽗类的功能。通过使⽤继承我们能够⾮常⽅便地复⽤以前的代码
。关于继承如下 3 点请记住:
- ⼦类拥有⽗类对象所有的属性和⽅法(包括私有属性和私有⽅法),但是⽗类中的私有属性和⽅ 法⼦类是⽆法访问,只是拥有。
- ⼦类可以拥有⾃⼰属性和⽅法,即⼦类可以对⽗类进⾏扩展。
- ⼦类可以⽤⾃⼰的⽅式实现⽗类的⽅法。多态:多态指的是一个
引用变量所指向
的具体类型
以及该引用变量调用
的是哪个类的方法
在编程时,必须在程序运行期间才能决定。
多态的存在要有 3 个必要条件:
继承
,方法重写
,父类引用指向子类对象
。
实现多态的 2 中形式:继承
,接口
。
String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
可变性
简单的来说:String 类中使⽤ final
关键字修饰字符数组来保存字符串, private final char value[]
,所以 String 对象是不可变的。
在 Java 9 之后,String 类的实现改⽤ byte 数组存储字符串 private final byte[] value
⽽ StringBuilder 与 StringBuffer 都继承⾃ AbstractStringBuilder
类,在 AbstractStringBuilder 中也是使⽤字符数组保存字符串 char[]value
但是没有⽤ final 关键字修 饰,所以这两种对象都是可变的。
线程安全性
String
中的对象是不可变的,也就可以理解为常量,线程安全
。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共⽗类,定义了⼀些字符串的基本操作,如 expandCapacity、 append、insert、indexOf 等公共⽅法。StringBuffer
对⽅法加了同步锁
或者对调⽤的⽅法加了同步 锁,所以是线程安全
的。StringBuilder
并没有对⽅法进⾏加同步锁,所以是⾮线程安全
的。
性能
每次对 String 类型进⾏改变的时候,都会⽣成⼀个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进⾏操作,⽽不是⽣成新的对象并改变对象 引⽤。相同情况下使⽤ StringBuilder 相⽐使⽤ StringBuffer 仅能获得 10%~15% 左右的性能提升, 但却要冒多线程不安全的⻛险。
对于三者使⽤的总结:
- 操作少量的数据: 适⽤ String
- 单线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuilder
- 多线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuffer
自动装箱与拆箱
装箱
:将基本类型⽤它们对应的引⽤类型包装起来;拆箱
:将包装类型转换为基本数据类型;
来看几个例子
eg1:
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);//true
System.out.println(i3==i4);//false
}
}
解析
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
eg2:
public static void main(String[] args) {
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);//false
System.out.println(i3==i4);//false
}
}
- Java 基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean;这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。
- 两种浮点数类型的包装类
Float
,Double
并没有实现常量池技术。
eg3:
谈谈Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别。
主要有以下这两点区别:
- 第一种方式不会触发自动装箱的过程;而第二种方式会触发;
- 在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)。
eg4(经典
):
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;
System.out.println(c==d);//true
System.out.println(e==f);//false
System.out.println(c==(a+b));//true
System.out.println(c.equals(a+b));//true
System.out.println(g==(a+b));//true
System.out.println(g.equals(a+b));//false
System.out.println(g.equals(a+h));//true
}
}
- 当 "
==
"运算符的两个操作数都是包装器类型的引用
,则是比较指向的是否是同一个对象
,而如果其中有一个操作数是表达式
(即包含算术运算)则比较的是数值
(即会触发自动拆箱的过程);- 对于包装器类型,
equals
方法并不会进行类型转换
解析:
- 第三句由于
a+b
包含了算术运算,因此会触发自动拆箱
过程(会调用intValue方法),因此它们比较的是数值是否相等
- 第四句
c.equals(a+b)
会先触发自动拆箱
过程,再触发自动装箱
过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较
在⼀个静态⽅法内调⽤⼀个⾮静态成员为什么是⾮法的?
由于静态⽅法可以不通过对象进⾏调⽤,因此在静态⽅法⾥,不能调⽤其他⾮静态变量,也不可以访问⾮静态变量成员。
在 Java 中定义⼀个不做事且没有参数的构造⽅法的作⽤
Java 程序在执⾏⼦类的构造⽅法之前,如果没有⽤ super() 来调⽤⽗类特定的构造⽅法,则会调⽤⽗类中“没有参数的构造⽅法”
。因此,如果⽗类中
只定义了有参数的构造⽅法,⽽在⼦类的构造⽅法中 ⼜没有⽤ super() 来调⽤⽗类中特定的构造⽅法,则编译时将发⽣错误
,因为 Java 程序在⽗类中找 不到没有参数的构造⽅法可供执⾏。解决办法是在⽗类⾥加上⼀个不做事且没有参数的构造⽅法。
接口和抽象类的区别
- 从设计层⾯来说,
抽象是对类的抽象
,是⼀种模板设计
,⽽接⼝是对⾏为的抽象
,是⼀种⾏为的规范
接⼝
的⽅法默认是public
,抽象⽅法
可以有public、protected 和 default
这些修饰符 (抽象⽅法就是为了被重写所以不能使⽤ private 关键字修饰!
)接⼝中除了 static、final 变量,不能有其他变量
,⽽抽象类中则不⼀定
成员变量与局部变量的区别有哪些?
从属关系
:成员变量是属于类的,⽽局部变量是在⽅法中定义的变量或是⽅法的参数;修饰符
:成员变量可以被 public,private,static 等修饰符所修饰,⽽局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰
。内存中的存储方式
:如果成员变量是使⽤static
修饰的,那么这个成员变量是属于类
的,如果没有使⽤ static 修饰,这个成员变量是属于实例
的。对象存于堆内存
,如果局部变量类型为基本数据类型
,那么存储在栈内存
,如果为引⽤数据类型
,那存放的是指向堆内存对象的引⽤
或者是指向常量池中的地址
。- 生存时间:
成员变量
是对象的⼀部分,它随着对象
的创建⽽存在,⽽局部变量
随着⽅法
的调⽤⽽⾃动消失。 - 赋值情况:成员变量如果没有被赋初值:则会⾃动以类型的默认值⽽赋值(⼀种情况例外:被 final 修饰的成员变量也必须显式地赋值),⽽
局部变量则不会⾃动赋值
。
构造方法有哪些特性
- 名字与类名相同。
- 没有返回值,但不能⽤ void 声明构造函数。
- ⽣成类的对象时⾃动执⾏,⽆需调⽤。
静态方法和实例方法有何不同
- 在外部调⽤
静态⽅法
时,可以使⽤"类名.⽅法名
"的⽅式,也可以使⽤"对象名.⽅法名
"的⽅式。⽽实例⽅法只有后⾯这种⽅式。也就是说,调⽤静态⽅法可以⽆需创建对象。 - 静态⽅法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态⽅法),⽽不允许访问实例成员变量和实例⽅法;实例⽅法则⽆此限制。
对象的相等与指向他们的引⽤相等,两者有什么不同?
对象
的相等,⽐的是内存中存放的内容
是否相等。⽽引⽤
相等,⽐的是他们指向的内存地址
是否相等。
== 与 equals(重要)
==
: 它的作⽤是判断两个对象的地址
是不是相等。即,判断两个对象是不是同⼀个对象(基本数据类型
比较的是值
,引⽤数据类型
比较的是内存地址
)。equals()
: 它的作⽤也是判断两个对象是否相等
。但它⼀般有两种使⽤情况:
情况 1:类没有覆盖 equals() ⽅法。则通过 equals() 比较该类的两个对象时,等价于通过 == 这两个对象。
情况 2:类覆盖了 equals() ⽅法。⼀般,我们都覆盖 equals() ⽅法来比较两个对象的内容是否相等;若它们的内容相等,则返回true (即,认为这两个对象相等)。
public static void main(String[] args) {
String a = new String("ab"); // a 为⼀个引⽤
String b = new String("ab"); // b为另⼀个引⽤,对象的内容⼀样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
if (aa == bb) // true
System.out.println("aaWXbb");
if (a == b) // false,⾮同⼀对象
System.out.println("aWXb");
if (a.equals(b)) // true
System.out.println("aEQb");
if (42 == 42.0) { // true
System.out.println("true");
}
}
}
解析:
- String 中的 equals ⽅法是被重写过的,因为
object 的 equals ⽅法是比较的对象的内存地址,⽽ String 的 equals ⽅法比较的是对象的值
。 - 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同 的对象,如果有就把它赋给当前引⽤。如果没有就在常量池中重新创建⼀个 String 对象。
hashCode()与equals(重要)
equals
:比较的是两个对象的内存地址值
(Object类)hashCode
:比较的是两个对象在hash表中的位置
hashCode的由来
:对象的内存地址通过hash函数算法得到hashcode 1.equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。2.hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。
扩展:
HashCode的存在主要是为了查找的快捷性
每当需要对比两个对象是否相等的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就不必再用equal()去对比了),如果hashCode()相同,此时再对比他们的equals(),如果equals()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性。
什么时候需要重写hashCode?
当类需要放在HashTable,HashMap、HsahSet等Hash结构的集合时才会重写hashCode。
源码(hashMap为例):
Node e; K k;
//key的hash相同,key的引用相同或者key equals,则覆盖if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
如果key的hash值相等
,且equals返回true
,则覆盖
;
如果key的hash值相等
,但equals返回false
,则插入链表尾部
;
可见,1:hash等结构的集合会重写hashCode;2:只要重写equals,就必须重写hashCode。
为什么需要重写hashCode?
如果重写了equals,比如说是基于对象的内容实现的,而保留的hashCode的实现不变,那么可能出现某两个对象明明是相等,而hashCode却不一样。
Java 值传递
Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝。exampl 1:
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a);
System.out.println("b = " + b);
}
结果:
b = 10
num1 = 10
num2 = 20
解析:
通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数
,而对象引用作为参数就不一样,请看 example2
example2 :
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]);//1
change(arr);
System.out.println(arr[0]);//0
}
public static void change(int[] array) {
// 将数组的第一个元素变为0
array[0] = 0;
}
解析:
总结:
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
- 一个方法可以改变一个对象参数的状态。
- 一个方法不能让对象参数引用一个新的对象。
简述线程、程序、进程的基本概念。以及他们之间关系是什么?
线程与进程相似,但线程是⼀个⽐进程更⼩的执⾏单位。⼀个进程在其执⾏的过程中可以产⽣多个线程。与进程不同的是同类的多个线程共享同⼀块内存空间和⼀组系统资源
,所以系统在产⽣⼀个线程,或是在各个线程之间作切换⼯作时,负担要⽐进程⼩得多,也正因为如此,线程也被称为轻量级进程
。程序是含有指令和数据的⽂件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码
。进程是程序的⼀次执⾏过程
,是系统运⾏程序的基本单位,因此进程是动态的。系统运⾏⼀个程序即是⼀个进程从创建,运⾏到消亡的过程。简单来说,⼀个进程就是⼀个执⾏中的程序,它在计算机中⼀个指令接着⼀个指令地执⾏着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,⽂件,输⼊输出设备的使⽤权等等。换句话说,当程序在执⾏时,将会被操作系统载⼊内存中。线程是进程划分成的更⼩的运⾏单位。线程和进程最⼤的不同在于基本上各进程是独⽴的,⽽各线程则不⼀定,因为同⼀进程中的线程极有可能会相互影响
。从另⼀⻆度来说,进程
属于操作系统
的范畴,主要是同⼀段时间内,可以同时执⾏⼀个以上的程序
,⽽线程
则是在同⼀程序内⼏乎同时执⾏⼀个以上的程序段
final,static,this,super 关键字总结
final 关键字
final关键字,意思是最终的、不可修改的,最见不得变化 ,用来修饰类、方法和变量,具有以下特点:
- final修饰的
类不能被继承
,final类中的所有成员方法
都会被隐式的指定为final
方法; - final修饰的
方法不能被重写
; - final修饰的变量是
常量
,如果是基本数据类型
的变量,则其数值一旦在初始化之后便不能更改
;如果是引用类型
的变量,则在对其初始化之后便不能让其指向另一个对象
。
说明:使用final方法的原因有两个。
第一个原因是把方法锁定,以防任何继承类修改它的含义
;第二个原因是效率
。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。
static 关键字
static 关键字主要有以下四种使用场景:
修饰成员变量和成员方法
: 被 static 修饰的成员属于类
,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量
,静态变量存放在 Java 内存区域的方法区
。调用格式:类名.静态变量名
类名.静态方法名()
静态代码块
: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法
)。该类不管创建多少对象,静态代码块只执行一次.静态内部类
(static修饰类的话只能修饰内部类):静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1.它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。静态导包
(用来导入类中的静态资源,1.5之后的新特性): 格式为:import static
这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。
this 关键字
this关键字用于引用类的当前实例。例如:
class Manager {
Employees[] employees;
void manageEmployees() {
int totalEmp = this.employees.length;
System.out.println("Total employees: " + totalEmp);
this.report();
}
void report() { }
}
在上面的示例中,this关键字用于两个地方:this.employees.length
:访问类Manager的当前实例的变量。this.report()
:调用类Manager的当前实例的方法。此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。但是,使用此关键字可能会使代码更易读或易懂。
super 关键字
super关键字用于从子类访问父类的变量和方法。例如:
public class Super {
protected int number;
protected showNumber() {
System.out.println("number = " + number);
}
}
public class Sub extends Super {
void bar() {
super.number = 10;
super.showNumber();
}
}
使用 this 和 super 要注意的问题:
在构造器中使用 super() 调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this调用本类中的其他构造方法时,也要放在首行。this、super不能用在static方法中
。
Tips: 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父的引用,指向父类对象;所以,
this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西
。
详情:https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/basic/final,static,this,super.md#finalstaticthissuper-%E5%85%B3%E9%94%AE%E5%AD%97%E6%80%BB%E7%BB%93
深拷⻉ vs 浅拷⻉
浅拷⻉
:对基本数据类型进⾏值传递,对引⽤数据类型进⾏引⽤传递般的拷⻉,此为浅拷⻉。深拷⻉
:对基本数据类型进⾏值传递,对引⽤数据类型,创建⼀个新的对象,并复制其内容
,此为深拷⻉。
Java 中的异常处理
Java 异常层次结构图
在 Java 中,所有的异常都有⼀个共同的祖先 java.lang 包中的 Throwable
类。Throwable:有两个重要的⼦类:Exception
(异常) 和 Error
(错误) ,⼆者都是 Java 异常处理的重要⼦类,各⾃都包含⼤量⼦类。
Error(错误)
:是程序⽆法处理的错误,表示运⾏应⽤程序中较严重问题。⼤多数错误与代码编写者执 ⾏的操作⽆关,⽽表示代码运⾏时 JVM(Java 虚拟机)出现的问题。例如,Java 虚拟机运⾏错误(Virtual MachineError),当 JVM 不再有继续执⾏操作所需的内存资源时,将出现OutOfMemoryError。这些异常发⽣时,Java 虚拟机(JVM)⼀般会选择线程终⽌。这些错误表示故障发⽣于虚拟机⾃身、或者发⽣在虚拟机试图执⾏应⽤时,如 Java 虚拟机运⾏错误 (Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应⽤程序的控制和处理能⼒之外,⽽且绝⼤多数是程序运⾏时不允许出现的状况。对于设计合理的应⽤程序来说,即使确实发⽣了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过 Error 的⼦类描述。Exception(异常)
:是程序本身可以处理的异常。Exception 类有⼀个重要的⼦类RuntimeException。RuntimeException 异常由 Java 虚拟机抛出。NullPointerException(要访问的变量没有引⽤任何对象时,抛出该异常)、ArithmeticException(算术运算异常,⼀个整数除以 0时,抛出该异常)和 ArrayIndexOutOfBoundsException (下标越界异常)。
Tips:注意:异常和错误的区别:异常能被程序本身处理,错误是⽆法处理。
异常处理总结
try 块:⽤于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟⼀个 finally 块。catch 块:⽤于处理 try 捕获到的异常。finally 块:⽆论是否捕获或处理异常,finally 块⾥的语句都会被执⾏。当在 try 块或catch 块中遇到 return语句时,finally 语句块将在⽅法返回之前被执⾏。在以下 4 种特殊情况下,finally 块不会被执⾏:
- 在 finally 语句块第⼀⾏发⽣了异常。因为在其他⾏,finally 块还是会得到执⾏
- 在前⾯的代码中⽤了 System.exit(int)已退出程序。exit 是带参函数 ;若该语句在异常语句 之后,finally 会执⾏
- 程序所在的线程死亡。
- 关闭 CPU。
Tips :注意:当 try 语句和 finally 语句中都有 return 语句时,在⽅法返回之前,finally 语句的内容 将被执⾏,并且 finally 语句的返回值将会覆盖原始的返回值。如下:
public static int f(int value) {
try {
return value * value;
} finally {
if (value WX 2) {
return 0;
}
}
}
如果调⽤ f(2) ,返回值将是 0,因为 finally 语句的返回值覆盖了 try 语句块的返回值。