文章目录
- 前言
- 1 Java基础篇
- 1.1 面向对象
- 1.1.1 类与对象
- 1.1.2 对象内存的解释
- 1.1.3 匿名对象
- 1.2 继承
- 1.2.1 继承的概念:
- 1.2.2 继承的格式
- 1.2.3 继承的好处
- 1.2.4 继承后的特点
- 1.2.5 super 和 this
- 1.3 抽象类
- 1.3.1 概述
- 1.3.2 抽象类的定义
- 1.3.3 抽象类的使用
- 1.3.4 注意事项
- 1.4 接口
- 1.4.1 概述
- 1.4.2 接口基本的实现
- 1.4.3 接口的多实现
- 1.4.4 接口和抽象类的区别
- 1.5 多态
- 1.5.1 概述
- 1.5.2 多态的定义
- 1.5.3 多态的好处
- 1.5.4 引用类型转型
- 1.6 修饰符
- 1.6.1 final关键字
- 1.6.2 static关键字
- 1.7 权限修饰符
- 1.7.1 概述
- 1.7.2 不同权限的访问能力
- 1.8 Object类 及常用API
- 1.8.1 概述
- 1.8.2 常用 API
- 1.9 异常
- 1.9.1 概述
- 1.9.2 异常的体系
- 1.9.3 异常的处理
- 1.9.4 异常注意事项
- 1.9.5 自定义异常
- 1.10 反射
- 1.10.1 概述
- 1.10.2 反射的基本步骤
- 1.10.3 获取class对象的三种方式
- 1.10.4 反射的用法
前言
1 Java基础篇
1.1 面向对象
1.1.1 类与对象
什么是类?类是现实事物的抽象,将现实事物描述成对应的类,其行为封装为方法,属性封装为成员变量。比如说人,人都有哪些属性?姓名,年龄,性别…等等这些都属于人的属性,可以将其封装为类的成员变量。人都有哪些行为?吃饭,睡觉…等等都属于人的行为,可以将其封装为类的成员方法。那么就可以定义一个Person类来描述人这一类事物!
public class Person {
private String name;//姓名
private int age;//年龄
//吃饭
public void eat(){
System.out.println("人吃了");
}
//空参构造
public Person() {
}
//带参构造
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//get/set
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
什么是对象?对象是从类中得到的具体实例!在定义Person类并没有一个具体的人出现,而创建对象后,就会出现一个具体的人,比如:
public class Test {
public static void main(String[] args) {
Person p = new Person();
p.setName("张三");
p.setAge(18);
p.eat();
}
}
1.1.2 对象内存的解释
对象在内存中的位置 : 对象由new关键字创建,如同数组,实体存在于堆内存中.任何事物均可以定义成类,创建对
象,属于引用类型 . 而对象的引用变量是一个普通变量。存储的值是该对象堆内存中的地址.
引用类型赋值代码解释
引用类型赋值对应内存图
1.1.3 匿名对象
匿名对象是指创建对象时,只有创建对象的语句,却没有把对象地址值赋值给某个变量。
public class Person{
public void eat(){
System.out.println();
}
}
public class Test {
public static void main(String[] args) {
//创建一个普通对象
Person p = new Person();
//创建一个匿名对象
new Person();
}
}
匿名对象的特点
1.创建匿名对象直接使用,没有变量名。
new Person().eat() //eat方法被一个没有名字的Person对象调用了。
2.匿名对象在没有指定其引用变量时,只能使用一次。
new Person().eat(); 创建一个匿名对象,调用eat方法
new Person().eat(); 想再次调用eat方法,重新创建了一个匿名对象
3.匿名对象可以作为方法的参数和返回值使用
public class Test {
public static void main(String[] args) {
//调用method方法
Person p = new Person();
method(p);
//匿名对象作为方法接收的参数
Demo.method(new Person());
}
public static void method(Person p){
p.eat();
}
public static Person getPerson(){
//匿名对象作为方法的返回值。
return new Person();
}
}
1.2 继承
在现实生活中,继承一般指的是子女继承父辈的财产。在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。例如学校中的讲师、助教、班主任都属于员工。这些员工之间会形成一个继承体系,具体如下图所示。
在Java中,类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称作子类,现有类被称作父类,子类会自动拥有父类所有可继承的属性和方法。
1.2.1 继承的概念:
当要定义一个类(讲师)时,发现已有类(员工)和要定义的类相似,并且要定义的类属于已有类的一种时,可以将要定义类定义为已有类的子类。同时也可以反过来思考,当多个类(讲师,助教,班主任)有共性内容,可以将共性内容向上抽取,抽取到一个新的类(员工)中,那么多个类和新的类形成的关系叫做继承。
1.2.2 继承的格式
通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:
class 父类 {
...
}
class 子类 extends 父类 {
...
}
继承演示,代码如下:
class Employee {
String name; // 定义name属性
// 定义员工的工作方法
public void work() {
System.out.println("尽心尽力地工作");
}
}
class Teacher extends Employee {
// 定义一个打印name的方法
public void printName() {
System.out.println("name=" + name);
}
}
public class ExtendDemo01 {
public static void main(String[] args) {
// 创建一个讲师类对象
Teacher t = new Teacher();
// 为该员工类的name属性进行赋值
t.name = "小明";
// 调用该员工的printName()方法
t.printName(); // name = 小明
// 调用Teacher类继承来的work()方法
t.work(); // 尽心尽力地工作
}
}
在上述代码中,Teacher类通过extends关键字继承了Employee类,这样Teacher类便是Employee类的子类。从运行结果不难看出,子类虽然没有定义name属性和work()方法,但是却能访问这两个成员。这就说明,子类在继承父类的时候,会自动拥有父类的成员。
1.2.3 继承的好处
提高代码的复用性。
类与类之间产生了关系,是多态的前提。
1.2.4 继承后的特点
成员变量
当类之间产生了关系后,其中各类中的成员变量,又产生了哪些影响呢?
成员变量不重名的情况
如果子类父类中出现不重名的成员变量,这时的访问是没有影响的。代码如下:
class Fu {
// Fu中的成员变量。
int num = 5;
}
class Zi extends Fu {
// Zi中的成员变量
int num2 = 6;
// Zi中的成员方法
public void show() {
// 访问父类中的num,
// 访问子类中的num2
System.out.println("Zi num2="+num2);
}
}
class ExtendDemo02 {
public static void main(String[] args) {
// 创建子类对象
Zi z = new Zi();
// 调用子类中的show方法
z.show();
}
}
演示结果:
Fu num = 5
Zi num2 = 6
成员变量重名的情况
如果子类父类中出现重名的成员变量,这时的访问是有影响的。代码如下:
class Fu {
// Fu中的成员变量。
int num = 5;
}
class Zi extends Fu {
// Zi中的成员变量
int num = 6;
public void show() {
// 访问父类中的num
System.out.println("Fu num=" + num);
// 访问子类中的num
System.out.println("Zi num=" + num);
}
}
class ExtendsDemo03 {
public static void main(String[] args) {
// 创建子类对象
Zi z = new Zi();
// 调用子类中的show方法
z.show();
}
}
演示结果:
Fu num = 6
Zi num = 6
子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用 super 关键字,修饰父类成员变量,类似于 this 。
使用格式:
super.父类成员变量名
子类方法需要修改,代码如下:
class Zi extends Fu {
// Zi中的成员变量
int num = 6;
public void show() {
//访问父类中的num
System.out.println("Fu num=" + super.num);
//访问子类中的num
System.out.println("Zi num=" + this.num);
}
}
演示结果:
Fu num = 5
Zi num = 6
小贴士:Fu 类中的成员变量是非私有的,子类中可以直接访问。若Fu 类中的成员变量私有了,子类是不能直接访问的。通常编码时,我们遵循封装的原则,使用private修饰成员变量,那么如何访问父类的私有成员变量呢?对!可以在父类中提供公共的getXxx方法和setXxx方法。
成员方法
成员方法不重名的情况
如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应
的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。代码如下:
class Fu{
public void show(){
System.out.println("Fu类中的show方法执行");
}
}
class Zi extends Fu{
public void show2(){
System.out.println("Zi类中的show2方法执行");
}
}
public class ExtendsDemo04{
public static void main(String[] args) {
Zi z = new Zi();
//子类中没有show方法,但是可以找到父类方法去执行
z.show();
z.show2();
}
}
成员方法重名
如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)。
方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效
果,也称为重写或者复写。声明不变,重新实现。
class Fu {
public void show() {
System.out.println("Fu show");
}
}
class Zi extends Fu {
//子类重写了父类的show方法
public void show() {
System.out.println("Zi show");
}
}
public class ExtendsDemo05{
public static void main(String[] args) {
Zi z = new Zi();
// 子类中有show方法,只执行重写后的show方法
z.show(); // Zi show
}
}
重写的应用
子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。比如新的手机增加来电显示头像的功能,代码如下:
class Phone {
public void sendMessage(){
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
public void showNum(){
System.out.println("来电显示号码");
}
}
//智能手机类
class NewPhone extends Phone {
//重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能
public void showNum(){
//调用父类已经存在的功能使用super
super.showNum();
//增加自己特有显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");
}
}
public class ExtendsDemo06 {
public static void main(String[] args) {
// 创建子类对象
NewPhone np = new NewPhone();
// 调用父类继承而来的方法
np.call();
// 调用子类重写的方法
np.showNum();
}
}
小贴士:这里重写时,用到super.父类成员方法,表示调用父类的成员方法。
注意事项
-
子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
-
子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
-
私有方法不能被重写(父类私有成员子类是不能继承的)
构造方法
当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?
首先我们要回忆两个事情,构造方法的定义格式和作用。
-
构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
-
构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造
方法中默认有一个 super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。代码如
下
class Fu {
private int n;
Fu(){
System.out.println("Fu()");
}
}
class Zi extends Fu {
Zi(){
// super(),调用父类构造方法
super();
System.out.println("Zi()");
}
}
public class ExtendsDemo07{
public static void main (String args[]){
Zi zi = new Zi();
}
}
输出结果:
Fu()
Zi()
1.2.5 super 和 this
在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空
间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构
造方法调用时,一定先调用父类的构造方法。理解图解如下:
super和this的含义
super:代表父类的存储空间标识(可以理解为父亲的引用)。
this :代表当前对象的引用(谁调用就代表谁)。
super和this的用法
- 访问成员
this.成员变量 – 本类的
super.成员变量 – 父类的
this.成员方法名() – 本类的
super.成员方法名() – 父类的
- 访问构造方法
this(…) – 本类的构造方法
super(…) – 父类的构造方法
子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
注意事项
-
Java只支持单继承,不支持多继承。
-
Java支持多层继承(继承体系)
-
所有的类都直接或者间接继承了Object类,Object类是所有类的父类。
1.3 抽象类
1.3.1 概述
当编写一个类时,我们往往会为该类定义一些方法,这些方法是用来描述该类的功能具体实现方式,那么这些方法都有具体的方法体。分析事物时,发现了共性内容,就出现向上抽取。会有这样一种特殊情况,就是方法功能声明相同,但方法功能主体不同。那么这时也可以抽取,但只抽取方法声明,不抽取方法主体。那么此方法就是一个抽象方法。如:
描述讲师的行为:工作。
描述助教的行为:工作。
描述班主任的行为:工作。
讲师、助教、班主任之间有共性,可以进行向上抽取。抽取它们的所属共性类型:员工。由于讲师、助教、班主任都具有工作功能,但是他们具体工作内容却不一样。这时在描述员工时,发现了有些功能不能够具体描述,那么,这些不具体的功能,需要在类中标识出来,通过java中的关键字abstract(抽象)修饰。当定义了抽象函数的类也必须被abstract关键字修饰,被abstract关键字修饰的类是抽象类。
定义
抽象方法 :没有方法体的方法。
抽象类:包含抽象方法的类。
使用 abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。定义格式:
修饰符 abstract 返回值类型 方法名 (参数列表);
代码举例:
public abstract void run();
1.3.2 抽象类的定义
如果一个类包含抽象方法,那么该类必须是抽象类。
定义格式:
public abstract class 类名字 {
}
代码举例:
public abstract class Animal {
public abstract void eat();
}
1.3.3 抽象类的使用
继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。
代码举例:
public class Cat extends Animal {
public void eat (){
System.out.println("猫吃猫粮");
}
}
public class CatTest {
public static void main(String[] args) {
// 创建子类对象
Cat c = new Cat();
// 调用run方法
c.eat();
}
}
输出结果:
猫吃猫粮
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法。
1.3.4 注意事项
-
抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
-
抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
-
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象。
-
抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
1.4 接口
1.4.1 概述
接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接
口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8)。
接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并
不是类,而是另外一种引用数据类型。
public class 类名.java–>.class
public interface 接口名.java–>.class
引用数据类型:数组,类,接口。
接口的使用,它不能创建对象,但是可以被实现( implements ,类似于被继承)。一个实现接口的类(可以看做是
接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。
1.4.2 接口基本的实现
实现的概述
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似
继承,格式相仿,只是关键字不同,实现使用 implements 关键字。
非抽象子类实现接口:
-
必须重写接口中所有抽象方法。
-
继承了接口的默认方法,即可以直接调用,也可以重写。
实现格式:
class 类名 implements 接口名 {
// 重写接口中抽象方法【必须】
// 重写接口中默认方法【可选】
}
1.4.3 接口的多实现
一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。
实现格式:
class 类名 [extends 父类名] implements 接口名1,接口名2,接口名3... {
// 重写接口中抽象方法【必须】
// 重写接口中默认方法【不重名时可选】
}
接口中,有多个抽象方法时,实现类必须重写所有抽象方法**。如果抽象方法有重名的,只需要重写一次。
1.4.4 接口和抽象类的区别
相同点
都位于继承的顶端,用于被其他类实现或继承;
都不能直接实例化对象;
都包含抽象方法,其子类都必须覆写这些抽象方法;
区别
抽象类为部分方法提供实现,避免子类重复实现这些方法,提高代码重用性;
接口只能包含抽象方法;
一个类只能继承一个直接父类(可能是抽象类),却可以实现多个接口;(接口弥补了Java的单继承)抽象类为继承体系中的共性内容,接口为继承体系中的扩展功能。
成员区别
抽象类:变量,常量;有构造方法;有抽象方法,也有非抽象方法
接口:常量;抽象方法
关系区别
类与类:继承,单继承
类与接口:实现,可以单实现,也可以多实现
接口与接口:继承,单继承,多继承
设计理念区别
抽象类:对类抽象,包括属性、行为
接口:对行为抽象,主要是行为
1.5 多态
1.5.1 概述
引入
多态是继封装、继承之后,面向对象的第三大特性。
生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。
1.5.2 多态的定义
多态: 是指同一行为,具有多个不同表现形式。
前提【重点】
-
继承或者实现【二选一】
-
方法的重写【意义体现:不重写,无意义】
-
父类引用指向子类对象【格式体现】
多态体现的格式:
父类类型 变量名 = new 子类对象;
变量名.方法名();
父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
public abstract class Animal {
public abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
}
public class Test {
public static void main(String[] args) {
// 多态形式,创建对象
Animal a1 = new Cat();
// 调用的是 Cat 的 eat
a1.eat();
// 多态形式,创建对象
Animal a2 = new Dog();
// 调用的是 Dog 的 eat
a2.eat();
}
}
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写后的方法
1.5.3 多态的好处
实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。
1.5.4 引用类型转型
向上转型
向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。
当父类引用指向一个子类对象时,便是向上转型。
使用格式:
父类类型 变量名 = new 子类类型();
Animal a = new Cat();
向下转型
向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。
一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
使用格式:
子类类型 变量名 = (子类类型) 父类变量名;
Cat c =(Cat) a;
为什么要转型
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类有而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。
1.6 修饰符
1.6.1 final关键字
子类可以在父类的基础上改写父类内容,比如,方法重写。那么我们能不能随意的继承API中提供的类,改写其内容呢?显然这是不合适的。为了避免这种随意改写的情况,Java提供了 final 关键字,用于修饰不可改变内容。final: 不可改变。可以用于修饰类、方法和变量。
类:被修饰的类,不能被继承。
方法:被修饰的方法,不能被重写。
变量:被修饰的变量,不能被重新赋值。
public final class String 、 public final class Math 、 public final class Scanner均为final类
修饰方法
修饰符 final 返回值类型 方法名(参数列表){
//方法体
}
重写被 final 修饰的方法,编译时就会报错。
修饰变量
- 局部变量——基本类型
基本类型的局部变量,被fifinal修饰后,只能赋值一次,不能再更改。
- 局部变量——引用类型
引用类型的局部变量,被fifinal修饰后,只能指向一个对象,地址不能再更改。但是不影响对象内部的成员变量值的修改,
被final修饰的常量名称,一般都有书写规范,所有字母都大写。
1.6.2 static关键字
定义:static是静态修饰符,一般修饰成员。被static修饰的成员属于类,不属于单个这个类的某个对象。static修饰的成员被多个对象共享。static修饰的成员属于类,但是会影响每一个对象。被static修饰的成员又叫类成员,不叫对象的成员。
定义和使用格式
1,类变量
当 static 修饰成员变量时,该变量称为类变量。该类的每个对象都共享同一个类变量的值。任何对象都可以更改
该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作。
2,静态方法
当 static 修饰成员方法时,该方法称为类方法 。静态方法在声明中有 static ,建议使用类名来调用,而不需要
创建类的对象。调用方式非常简单。
静态方法调用的注意事项:
静态方法可以直接访问类变量和静态方法。
静态方法不能直接访问普通成员变量或成员方法。成员方法可以直接访问类变量或静态方法。
静态方法中,不能使用this关键字。
静态方法只能访问静态成员。
静态原理图解
static 修饰的内容:
是随着类的加载而加载的,且只加载一次。
存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。
它优先于对象存在,所以,可以被所有对象共享。
静态代码块:定义在成员位置,使用static修饰的代码块{ }。
位置:类中方法外。
执行:随着类的加载而执行且执行一次,优先构造方法的执行。
格式:
public class Person {
private String name;
private int age;
//静态代码块
static{
System.out.println("静态代码块执行了");
}
}
1.7 权限修饰符
1.7.1 概述
在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限,
public:公共的。
protected:受保护的
default:默认的
private:私有的
1.7.2 不同权限的访问能力
可见,public具有最大权限。private则是最小权限。
编写代码时,如果没有特殊的考虑,建议这样使用权限:
成员变量使用 private ,隐藏细节。
构造方法使用 public ,方便创建对象。
成员方法使用 public ,方便调用方法。
不加权限修饰符,其访问能力与default修饰符相同
1.8 Object类 及常用API
1.8.1 概述
java.lang.Object 类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。在对象实例化的时候,最终找的父类就是Object。如果一个类没有特别指定父类, 那么默认则继承自Object类。例如:
public class MyClass {
// ...
}
1.8.2 常用 API
常用api的使用,可查阅相关jdk api手册,这里不再赘述。
1.9 异常
1.9.1 概述
异常,就是不正常的意思。在生活中:医生说,你的身体某个部位有异常,该部位和正常相比有点不同,该部位的功能将受影响.在程序中的意思就是:程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行.
1.9.2 异常的体系
异常机制其实是帮助我们找到程序中的问题,异常的根类是 java.lang.Throwable ,其下有两个子类:java.lang.Error 与 java.lang.Exception ,平常所说的异常指 java.lang.Exception 。
Throwable 体系:
Error:严重错误Error,无法通过处理的错误,只能事先避免,好比绝症。
Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。好比
感冒、阑尾炎。
Throwable 方法:
public void printStackTrace() :打印异常的详细信息。
包含了异常的类型*,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace*。
public String getMessage() :获取发生异常的原因。
提示给用户的时候*,*就提示错误原因。
public String toString() :获取异常的类型和异常描述信息(不用)。
异常(Exception)的分类:根据在编译时期还是运行时期去检查异常?
编译时期异常:checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常)
运行时期异常:runtime异常。在运行时期,检查异常.在编译时期,运行异常不会编译器检测(不报错)。(如数学异常)
1.9.3 异常的处理
Java异常处理的五个关键字:try catch fifinally、throw、throws
抛出异常throw
在编写程序时,我们必须要考虑程序出现问题的情况。比如,在定义方法时,方法需要接受参数。那么,当调用方法使用接受到的参数时,首先需要先对参数数据进行合法的判断,数据若不合法,就应该告诉调用者,传递合法的数据进来。这时需要使用抛出异常的方式来告诉调用者。
在java中,提供了一个throw关键字,它用来抛出一个指定的异常对象。那么,抛出一个异常具体如何操作呢?
-
创建一个异常对象。封装一些提示信息(信息可以自己编写)。
-
需要将这个异常对象告知给调用者。怎么告知呢?怎么将这个异常对象传递到调用者处呢?通过关键字throw就可以完成。throw 异常对象。
throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。
使用格式:
throw new 异常类名(参数);
throw new NullPointerException("要访问的arr数组不存在");
throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");
注意:如果产生了问题,我们就会throw将问题描述类即异常进行抛出,也就是将问题返回给该方法的调用者。
那么对于调用者来说,该怎么处理呢?一种是进行捕获处理,另一种就是继续将问题抛出去,使用throws声
明处理。
Throws 声明异常(将异常抛给方法的调用者)
声明异常:将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理,那么必须通过throws进行声明,让调用者去处理。关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常).
throws用于进行异常类的声明,若该方法可能有多种异常情况产生,那么在throws后面可以写多个异常类,用逗号隔开。
public class ThrowsDemo2 {
public static void main(String[] args) throws IOException {
read("a.txt");
}
public static void read(String path)throws FileNotFoundException, IOException {
if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
// 我假设 如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常 throw
throw new FileNotFoundException("文件不存在");
}
if (!path.equals("b.txt")) {
throw new IOException();
}
}
}
try-catch-finally 捕获异常
如果异常出现的话,会立刻终止程序,所以我们得处理异常:
throws不处理异常,而是声明抛出,由该方法的调用者来处理。
在方法中使用try-catch的语句块来处理异常。
try-catch的方式就是捕获异常。
捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。捕获异常语法如下
try{
//编写可能会出现异常的代码
}catch(异常类型 e){
//处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
注意:try和catch都不能单独使用,必须连用。
finally:有一些特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些语句执行不到。而fifinally就是解决这个问题的,在fifinally代码块中存放的代码都是一定会被执行的。什么时候的代码必须最终执行?
当我们在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),我们都得在使用完之后,最终关闭打开的资源。
finally的语法:
try…catch…fifinally:自身需要处理异常,最终还得关闭资源。
注意:fifinally不能单独使用
try catch finally 中 return的执行顺序
- 无异常的情况下,如果try 和 finally中都有return 则finally中的return 会覆盖 try中的return,如果只有try中有return,则finally中的操作,不会影响try中return的返回值。即将变量 从局部变量区复制到操作数栈顶,结果就是操作数栈顶的值。
- 有异常的情况,如果try和catch中都有return,则catch中的return同样会覆盖try中的return值
1.9.4 异常注意事项
1.运行时异常被抛出可以不处理。即不捕获也不声明抛出。
2.如果父类抛出了多个异常,子类覆盖父类方法时,只能抛出相同的异常或者是他的子集。
3.父类方法没有抛出异常,子类覆盖父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
4.当多异常处理时,捕获处理,前边的类不能是后边类的父类。
5.在try/catch后可以追加fifinally代码块,其中的代码一定会被执行,通常用于资源回收。
多个异常使用捕获又该如何处理呢?
多个异常分别处理。
多个异常一次捕获,多次处理。
多个异常一次捕获一次处理
格式如下:
try{
//编写可能会出现异常的代码
}catch(异常类型A e){ 当try中出现A类型异常,就用该catch来捕获.
//处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}catch(异常类型B e){ //当try中出现B类型异常,就用该catch来捕获.
//处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
注意:这种异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。
1.9.5 自定义异常
异常类如何定义
自定义一个编译期异常: 自定义类 并继承于 java.lang.Exception 。
自定义一个运行时期的异常类:自定义类 并继承于 java.lang.RuntimeException 。
实例代码:
// 业务逻辑异常
public class LoginException extends Exception {
public LoginException() {
}
public LoginException(String message) {
super(message);
}
}
1.10 反射
1.10.1 概述
**反射技术:**其实就是动态加载一个指定的类,并获取该类中的所有的内容。并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个类进行解剖。
反射的好处:大大的增强了程序的扩展性。
反射的基本步骤:
1.10.2 反射的基本步骤
1、获得Cl ass对象,就是获取到指定的名称的字节码文件对象。
2、实例化对象,获得类的属性、方法或构造函数。
3、访问属性、调用方法、调用构造函数创建对象。
1.10.3 获取class对象的三种方式
1:通过每个对象都具备的方法getClass来获取。弊端:必须要创建该类对象,才可以调用getClass方法。
String classname = "com.test.XXX";// 来自配置文件 Class clazz = Class.forName(classname);// 此对象代表Person.class
2:每一个数据类型(基本数据类型和引用数据类型)都有一个静态的属性class。弊端:必须要先明确该类。
前两种方式不利于程序的扩展,因为都需要在程序使用具体的类来完成。
Object obj = new Person(); Class clazz1 = obj.getClass();// 获得对象具体的类型
3:使用的Class类中的方法,静态的forName方法。指定什么类名,就获取什么类字节码文件对象,这种方式的扩展性最强,只要将类名的字符串传入即可。
Class clazz2 = Person.class;
1.10.4 反射的用法
反射a类的各个组成部分,首先需要获得类的Class对象,获得Class对象的三种方式:
Class.forName(classname) 用于做类加载
obj.getClass() 用于获得对象的类型
类名.class 用于获得指定的类型,传参用
2)、反射类的成员方法:
Class clazz = Person.class;
Method method = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2});
method.invoke();
3)、反射类的构造函数:
Constructor con = clazz.getConstructor(new Class[]{paramClazz1, paramClazz2,...})
con.newInstance(params...)
4)、反射类的属性:
Field field = clazz.getField(fieldName);
field.setAccessible(true);
field.setObject(value);
获取了字节码文件对象后,最终都需要创建指定类的对象:
创建对象的两种方式(其实就是对象在进行实例化时的初始化方式):
1,调用空参数的构造函数:使用了Class类中的newInstance()方法。
2,调用带参数的构造函数:先要获取指定参数列表的构造函数对象,然后通过该构造函数的对象的newInstance(实际参数) 进行对象的初始化。
综上所述,第二种方式,必须要先明确具体的构造函数的参数类型,不便于扩展。所以一般情况下,被反射的类,内部通常都会提供一个公有的空参数的构造函数。
如何生成获取到字节码文件对象的实例对象。
Class clazz = Class.forName("cn.xx.xxx");//类加载
// 直接获得指定的类型
clazz = Person.class;
// 根据对象获得类型
Object obj = new Person("zhangsan", 19);
clazz = obj.getClass();
Object obj = clazz.newInstance();//该实例化对象的方法调用就是指定类中的空参数构造函数,给创建对象进行初始化。当指定类中没有空参数构造函数时,该如何创建该类对象呢?请看method_2();// 直接获得指定的类型
public static void method_2() throws Exception {
Class clazz = Class.forName("com.xx.xxx");
//既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。
//获取一个带参数的构造器。
Constructor constructor = clazz.getConstructor(String.class,int.class);
//想要对对象进行初始化,使用构造器的方法newInstance();
Object obj = constructor.newInstance("zhagnsan",30);
//获取所有构造器。
Constructor[] constructors = clazz.getConstructors();//只包含公共的
constructors = clazz.getDeclaredConstructors();//包含私有的
for(Constructor con : constructors) {
System.out.println(con);
}
}
反射指定类中的方法:
// 1 获取类中所有的方法。
public static void method_1() throws Exception {
Class clazz = Class.forName("cn.xx.xxx");
Method[] methods = clazz.getMethods();//获取的是该类中的公有方法和父类中的公有方法。
methods = clazz.getDeclaredMethods();//获取本类中的方法,包含私有方法。
for(Method method : methods) {
System.out.println(method);
}
}
//2 获取指定方法;
public static void method_2() throws Exception {
Class clazz = Class.forName("cn.xx.Person");
//获取指定名称的方法。
Method method = clazz.getMethod("show", int.class,String.class);
//想要运行指定方法,当然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法即可,但是方法运行必须要明确所属的对 象和具体的实际参数。
Object obj = clazz.newInstance();
method.invoke(obj, 39,"hehehe");//执行一个方法
}
//3 想要运行私有方法。
public static void method_3() throws Exception {
Class clazz = Class.forName("cn.xx.Person");
//想要获取私有方法。必须用getDeclearMethod();
Method method = clazz.getDeclaredMethod("method", null);
// 私有方法不能直接访问,因为权限不够。非要访问,可以通过暴力的方式。
method.setAccessible(true);//一般很少用,因为私有就是隐藏起来,所以尽量不要访问。
}
//4 反射静态方法。
public static void method_4() throws Exception {
Class clazz = Class.forName("cn.xx.xxx.Person");
Method method = clazz.getMethod("function",null);
method.invoke(null,null);
}