03java基础(二)java面向对象

类和对象的基本使用

基础概念

面向对象编程(OOP)

本质:以类的方式组织代码,以对象的组织(封装)数据

核心思想:抽象

三大特征:

  • 封装
  • 继承
  • 多态

类和对象之间的联系

  • 是一种抽象的数据类型,是对某一类事物的整体描述/定义,但是不能代表某一个具体事物。

  • 对象类的实例化对象被创建之后它所代表的就是类里的某一个具体事物

类的初始化

用new关键字创建对象

使用new关键字创建实例对象,本质上是调用类中的构造器

注意:构造器名称与类名相同

  • 一个类即使是空的,里面也会有默认的无参构造器存在

示例如下:

public static void main(String[] args) {
    //实例化对象
    Person person = new Person();

}//main方法
public class Person {

    //存在默认的无参构造器
    /*
    public APerson(){}
    */

}
  • 当你给类增加了有参构造器,则无参构造器程序不再默认提供,需要自己显式定义

示例如下:

public class Person {

    String name;
	
	//定义有参构造器
    public APerson(String name){
        this.name = name;
    }
    //需要自己把无参构造器写出来
    public APerson(){}

}

创建对象的内存分析

代码如下:

public static void main(String[] args) {

    Pet dog = new Pet();
    dog.name = "旺财";
    dog.age = 5;
    dog.shout();

    System.out.println(dog.name);
    System.out.println(dog.age);
    
    Pet cat = new Pet();

}//main方法
public class Pet {

    String name;
    int age;

    public void shout(){
        System.out.println("叫了一声");
    }

}

对应内存图解如下:

  • 程序刚开始执行main()方法,把main()压入栈底,在方法区生成main()和Pet类的模版,类模版主要指的是类加载在加载阶段在方法区生成的静态数据结构。(同一时间,也会在堆中生成便于用户调用的java.lang.Class的对象)

注意:此时,Pet类还未调用构造函数初始化

注意:方法区也属于堆的一部分,但JVM根据存放数据类型的不同把堆分为"堆"区域和"方法区"区域

  • 执行下列代码,让Pet实例化,创建实例对象dog,把dog变量存放在栈中,而且dog变量中存放的是堆中新建对象的内存地址,之后给堆里的属性赋值,以及让堆里的shout()方法调用方法区Pet模版里的shout()方法
	APet dog = new APet();
    dog.name = "旺财";
    dog.age = 3;
    dog.shout();

  • 如法炮制,创建一个新对象cat
APet cat = new APet();

  • 方法区中的静态方法区用于存放所有带static修饰符的数据,所有对象都可以使用

OOP的三大特征

类的封装

程序设计核心思想:高内聚,低耦合。高内聚:类的内部操作细节自己完成,不允许外部干涉;低耦合:只暴露少量的方法给外部使用。

而封装就是对信息的隐藏,禁止对象中数据的实际表示,通过操作接口来访问。

常用方法:属性私有,get/set

示例如下:

public static void main(String[] args) {

    Student1 s1 = new Student1();
    s1.setAge(12);
    System.out.println(s1.getAge());
    s1.setAge(999);
    System.out.println(s1.getAge());

}//main方法
public class Student1 {

    //属性私有
    private String name;
    private int gender;
    private int id;
    private int age;

    //方法

    //属性的getter和setter方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getGender() {
        return gender;
    }

    public void setGender(int gender) {
        this.gender = gender;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {

        if(age >= 0 && age <= 150)
            this.age = age;
        else
            this.age = 3;
    }
}

类的继承

继承的基础使用

继承基本概念

继承的本质是对某一批类的抽象,从而实现更好的建模

extends关键字是子类对父类的继承。子类是父类的扩展。

Java中只有单继承,没有多继承,即一个类只能继承一个父类

  • 子类会继承父类中修饰符为public和protected的属性和方法,如果父类中的属性和方法使用private修饰,则不会被子类继承。

注意:默认修饰符为default,该类中的成员可在同一个包下被访问;protected可以在不同包下的子类和相同包下被访问;private只能在本类中被访问;public可以被任何包下访问。

extends关键字的基本使用

代码示例如下:

public static void main(String[] args) {

        ExtendSon son = new ExtendSon();

        son.say();
        System.out.println(son.money);

    }//main方法
public class ExtendFather {
    
    public int money = 1_000_000_000;

    public void say(){
        System.out.println("说些什么吧");
    }

}
public class ExtendSon extends ExtendFather{
}
public class ExtendDau extends ExtendFather{
}
  • 说明:Java中所有的类默认直接继承自Object类,其中Object类的继承与Java的单继承性无关,所有Java中的类直接继承只能有一个类,但可以同时直接继承Object类。
super关键字的基本使用

super关键字是代表父类,与this关键字代表本类相对应。

代码示例如下:

//main方法
    public static void main(String[] args) {
        
        Student student = new Student("liMIng");
        /*
            调用Student构造函数
            输出:
            LiMIng
            tom
            alex
        */
        student.test1();
        /*
        	输出:
        	Student
            Student
            Person
        */
        
    }
//Person 人:父类
public class Person {

    protected String name = "alex";

    public void print(){
        System.out.println("Person");
    }
}
//Student 学生:子类
public class Student extends Person{

    private String name = "tom";

    public Student(String name) {
        System.out.println(name); //LiMIng
        System.out.println(this.name);  //tom
        System.out.println(super.name);  //Alex
    }

    public void test1(){
        print();  //默认调用当前类的print()
        this.print();  //调用当前类的print()
        super.print();  //调用父类的print()
    }

    public void print(){
        System.out.println("Student");
    }
}

对于执行构造函数的顺序,父类的构造函数会在子类构造函数的第一行执行,就算什么都不写,也会执行;也可以在子类构造函数里显示调用super()方法调用父类构造函数,但是必须在子类构造函数的第一行。

示例如下:

//main方法
public static void main(String[] args) {

    Student student = new Student();
    /*
    	调用Student构造函数
    	输出:
    	PersonConstructor
		StudentConstructor
    	
    */

}
//Person 人:父类
public class Person {

    public Person() {
        System.out.println("PersonConstructor");
    }

}
//Student 学生:子类
public class Student extends Person{

    public Student() {
        super();
        System.out.println("StudentConstructor");
    }
}
  • 注意1:在子类构造器中同时写this()和super()会发生报错,因为都要求放在第一行,所以只能二选一。

  • 注意2:对于显示或隐式继承Object类的所有类,使用super关键字都访问不到Object中的成员,因此可以推断出只有使用extends关键字继承其他类时,super关键字才“有效”。

方法重写

通过一个静态方法示例浅了解一下

//main方法
public static void main(String[] args) {

    //静态方法的调用只和左边,定义的数据类型有关
    A a = new A();
    a.print();  //输出A->print(),调用的是本类的print()

    //父类的引用指向了子类
    B b = new A();
    b.print();  //输出B->print(),调用的是父类的print()

}
//重写都是方法的重写,与属性无关
public class B {

    public static void print(){
        System.out.println("B->print()");
    }

}
//继承
public class A extends B{

    public static void print(){
        System.out.println("A->print()");
    }

}

非静态方法则得出的结果与静态方法有所不同

//main方法
//静态方法和非静态方法区别很大!!没事儿别写静态方法玩
//静态方法:方法的调用只和左边,定义的数据类型有关
//非静态:才能被称作重写
public static void main(String[] args) {

    A a = new A();
    a.print();  //输出A

    //父类的引用指向了子类
    B b = new A(); //子类重写了父类的方法
    b.print();  //输出A
    
}
//重写都是方法的重写,与属性无关
public class B {

    protected void print(){
        System.out.println("B->print()");
    }

}
//继承
public class A extends B{

    //Override重写的意思
    @Override //注解:有功能的注释!
    public void print() {
        System.out.println("A->print()");
    }
}

通过上面两个例子,可以得出重写需要的条件

  • 需要有继承关系,子类重写父类的方法
  • 子类重写的方法与父类相同,只有里面的方法体不同

​ 1. 方法名必须相同

​ 2. 参数列表必须相同

​ 3. 修饰符:子类可以扩大父类的修饰符,但不能缩小

​ 4. 抛出异常:范围可以缩小,但不能扩大

  • 被重写的方法一定是非静态方法,静态方法不能被子类重写,只会被子类隐藏

需要重写的原因无非两个:子类不需要该方法里执行的方法体,或者不满足该方法的方法体。

重写的快捷键:Ctrl+O

浅谈我对静态方法不能被重写的理解

JVM虚拟机是先编译再运行,在编译阶段,静态方法就会被同时放入静态方法区的静态数据结构和用于调用的堆里java.lang.Class的对象之中,因此我们编写的父类的静态方法和子类的静态方法都会被放进去,也就导致直接存在了两份不同方法体但是其他都相同的方法,此时还不存在一个方法覆盖重写另一个方法的事,因为覆盖重写是运行阶段,也就是引用指向new关键字在堆里创建的对象时候的事情;之后到了运行阶段,在执行“父类引用指向new子类”的代码时,父类引用发现自己的静态方法已经在堆中了,可以直接拿过来,与此同时,父类在加载自己的成员方法发现实例子类对象先一步改了该方法,就使用子类重写后的方法。

一点类继承的疑问

  • 注释前瞻:

    1. 创建对象指的是在堆区开辟空间

    2. 编译器在运行子类构造器之前,必须先执行父类构造器;且调用父类构造器的语句必须在子类构造器的第一行。

    3. 构造方法的作用是为堆区中的对象的属性初始化,不是创建对象。(不一定是对象的属性,也有可能是父类的私有属性,该属性不属于任何对象,只会在运行时与父类class文件动态绑定)

    4. 静态绑定:(final、static、private)在程序执行前已经被绑定,也就是说在编译过程中就已经知道这个熟悉或方法是哪个类的方法,此时由编译器获取其他连接程序实现。

    5. 动态绑定:在运行根据具体对象的类型进行绑定。


  • 问题:当一个子类通过继承父类的方法对父类的私有属性进行操作时,此时这个私有属性既然不会被继承,没有在子类中,那么它是在父类里吗?如果是这样,那么是不是意味着父类在子类被创建的同时也被创建了,因此这个私有属性才能被方法操作呢?
  • 回答:该私有属性并不在父类中,不属于父类,在创建完子类后,虚拟机会单独在堆中开辟一个空间来保存这个私有属性(该空间也不属于子类对象),并且运行时该属性的空间会与方法区中的父类模版(.class)动态绑定;父类并没有被创建,父类只是作为模版(静态数据结构)在方法区当中,当子类调用父类继承的方法时,程序会找到与这个方法静态绑定的方法区中的父类模版,再通过父类模版找到与它动态绑定的私有属性的堆空间,从而对私有属性进行操作。
  • 同理,调用子类构造器时,父类构造器会被触发,也是因为调用了与方法区中的父类模版里对应的堆中的构造器

答疑来源

[博客园]Java碎片化知识——作者:Nylgwn

  • 未解决疑问:当子类继承了父类的属性和方法后,运行时,继承的属性是与子类动态绑定吗?此时调用父类的方法去操作这个属性,内存的调用过程又会是怎么样的呢?

类的多态

方法重写与多态

多态即动态编译,可以让类型具有可扩展性;同一方法根据发送对象的不同而采用不同的行为方式。

一个对象的实际类型是确定的,但是指向对象的引用类型是不确定的。

用代码初步了解多态

示例如下:

 //main方法
    public static void main(String[] args) {

//        一个对象的实际类型在编译时是确定的
//        new Student();   Student对象
//        new Person();     Person对象

//        但是指向的引用类型就不确定了:父类的引用指向子类

//        Student 能指向的类只是自己的或者继承的
        Student student = new Student();
//        Person 父类,可以指向子类,但不能调用子类独有的方法
        Person student1 = new Student();
        Object student2 = new Student();

        student.run();
        student1.run();//子类重写了父类方法,执行子类方法
        student.eat();
        ((Student) student1).eat(); //父类强制转换子类就能执行方法

    }
// Person 人:父类
public class Person {

    public void run(){
        System.out.println("PersonRun");
    }

}
//Student 学生:子类
public class Student extends Person{

    @Override
    public void run() {
        System.out.println("StudentRun");
    }

    public void eat(){
        System.out.println("StudentEat");
    }

}

几个注意的点:

  1. 多态是方法的多态,属性没有多态
  2. 父类和子类之间才存在多态,需要有联系,否则类型转换异常!ClassCastException
  3. 多态里的方法需要重写,父类引用指向子类对象!Father f1 = new Son();

instanceof关键字的使用

instanceof (类型转换)一般用于引用类型,能够用于判断一个对象是什么类型,引用对象与实例类之间是否存在继承关系。

  • 所有instanceof关键字使用的注意点都在以下代码中,请认真查看

示例如下:

//main方法
    public static void main(String[] args) {


        //Object    >   Person  >   Student
        //Object    >   String
        //Object    >   Person  >   Teacher
//        instanceof关键字会去找左边的实例对象以及它的原型链上的对象,
//        如果,右边的类不在左边的原型链上则判断为false

//        如果报错,则说明instanceof左边的实例对象
//        在自动转换(父 = new 子)和强制转换(父 = new 子;子 = (子)父)下
//        不能被左边的引用对象所指向
        Object o = new Student();

        System.out.println(o instanceof Student);//true
        System.out.println(o instanceof Person);//true
        System.out.println(o instanceof Object);//true
        System.out.println(o instanceof Teacher);//false
        System.out.println(o instanceof String);//false

        System.out.println("===========================");
        Person person = new Student();
        System.out.println(person instanceof Student);//true
        System.out.println(person instanceof Person);//true
        System.out.println(person instanceof Object);//true
        System.out.println(person instanceof Teacher);//false
//        System.out.println(person instanceof String);//编译错误

        System.out.println("===========================");
        Student student = new Student();
        System.out.println(student instanceof Student);//true
        System.out.println(student instanceof Person);//true
        System.out.println(student instanceof Object);//true
//        System.out.println(student instanceof Teacher);//编译错误
//        System.out.println(person instanceof String);//编译错误

    }

// Person 人:父类
public class Person {
    
}

//Student 学生:子类
public class Student extends Person{

}

// Teacher 老师:子类
public class Teacher extends Person{

}

类之间的类型转换

  1. 向上转型:当子类型转换为父类型时,发生会自动转换,但是可能会丢失自己本来的方法。(父类引用 = new 子类实例)。

  2. 向下转型:也可以使用强制转换把父类型转换为子类型。

//main方法
    public static void main(String[] args) {

        //类型之间的转换: 父   子

        //高       自动转换     低
        Person person = new Student();

        //将Person对象强制转换为Student类型,就可以使用Student类型的方法了
        Student student = (Student)person;
        student.go();

//        或者缩成一句话
        ((Student) person).go();

    }

// Person 人:父类
public class Person {
    
}

//Student 学生:子类
public class Student extends Person{

}
  • 注意:向下转型不能单独存在,“子 = (子)new 父”程序运行会报错。向下转型一定是在向上转型之后发生的。

浅分析不能向下转型的原因

当向下转型时,可能出现子类引用需要指向的自身的属性和方法在父类实例对象创建的堆空间中不存在,导致空指针,从而导致了类型转换异常的发生(ClassCastException)

小结:多态拥有子类重写和类型转换的主要目的还是为了方便方法的调用,减少重复的代码!简洁

static讲解

静态变量和静态方法

类的静态变量和静态方法可以被类调用,也可以被对象调用

成员变量和成员方法只能被对象调用

示例如下:

//main 方法
    public static void main(String[] args) {

        //静态属性和成员属性
        Student student = new Student();
        System.out.println(student.age);
        System.out.println(student.score);
        System.out.println(Student.age);

        //静态方法和成员方法
        new Student().run();
        new Student().go();
        Student.go();
    }

}

//static学习
public class Student {

    public static int age;//静态变量
    public double score;//非静态变量

    public void run(){
        
    }

    public static void go(){
        
    }

}

静态方法和成员方法之间的调用

静态方法可以调用静态方法

成员方法可以调用成员方法和静态方法

但是静态方法不可以调用成员方法

究其原因,还是因为静态方法在类加载时也加载了,但是成员方法在创建实例对象时才被创建。

示例如下:

//static学习
public class Student {

    public static int age;//静态变量
    public double score;//非静态变量

    public void run(){
        //三种调用静态方法的方式都可以
        Student.go();
        this.go();
        go();
    }

    public static void go(){
//        run();调用成员方法出错
        System.out.println("go");
    }

}

代码块

类中存在匿名代码块和静态代码块

静态代码块在类加载时执行,且之后创建实例对象时不会再执行,只会执行一次

匿名代码块则是在创建实例对象时被执行,执行顺序在构造函数之前,能执行多次

示例如下:

//main 方法
    public static void main(String[] args) {

        Person person = new Person();

        System.out.println("=================");

        Person person1 = new Person();
        /*
        
        输出:
        StaticCode
        AnonymousCode
        PersonConstructor
        =================
        AnonymousCode
        PersonConstructor
        
        */
    }

public class Person {

    {
        //代码块(匿名代码块)
        //创建实例对象时执行
        //在构造函数前执行
        System.out.println("AnonymousCode");
    }

    static{
        //静态代码块
        //类加载时执行,只执行一次
        System.out.println("StaticCode");
    }

    Person(){
        System.out.println("PersonConstructor");
    }

}

抽象类和接口

用abstract关键字声明类和方法

  • 抽象类用abstract修饰符声明,抽象类中可以有抽象方法和非抽象方法。
  • abstract不能修饰属性。
  • 抽象方法只会存在于抽象类中。
  • 抽象类不能被创建对象,只有非抽象的子类才能创建对象。

示例如下:

//abstract 抽象类
public abstract class Action {
    

    //抽象方法只是个约束
//    抽象方法只需要方法名,不需要方法体
    public abstract void doSomething();

    public void getSomthing(){

    }

}
  • 如果有子类继承了抽象类,必须要重写抽象类的抽象方法。除非子类也是一个抽象类

示例如下:

//A :子类
public class A extends Action{
    @Override
    public void doSomething() {

    }
}
  • 使用new关键字创建实例对象时,必须使用抽象类的非抽象子类才能创建。
  • 调用子类构造函数时,依然会调用抽象类的构造函数,这就不得不再次提到构造函数的功能。

构造函数的功能:为堆中的对象的属性初始化。继承自父类的属性也需要初始化,即使是不被继承的私有属性,也会开辟一个不属于任何对象的独立空间并初始化。因此需要抽象类的构造函数初始化属性。

示例如下:

//main方法
    public static void main(String[] args) {

        A a = new A();
//        Action b = new A();正确,向上转型是被允许的,抽象方法已经被子类重写
//        Action c = new Action();错误,创建抽象类实例对象不被允许
		/**
		
		输出:
		ActionConstructor
		AConstructor
		
		**/
    }

注意:就算是抽象类,也逃脱不了Java之中单继承的局限性,在需要能多继承的时候,接口就理所应当地接任了这个职责。

接口基本概念

普通类:只有具体实现的方法

抽象类:具体实现的方法和规范(抽象方法)都有

接口:只能有规范存在!因此接口被人直接称作规范,又称作专业的约束!实现约束和实现分离

接口就是规范,定义的是一种规则。如“如果你是某某角色的话,那么你就必须会某某功能”。如果你是汽车,那你必须能跑;如果你是植物,那你必须会进行光合作用;如果你是母鸡,那你必须会下蛋。接口定义了每个角色都需要遵循的规则。在绝对的规则下,不会有现实生活中遇到的例外,就不存在不会下蛋的母鸡。

用interface关键字声明接口

示例如下:

//接口
public interface UserService {

    //接口中的属性为常量,定义都省略了public static final修饰符。比较少用
    int AGE = 100;
    
    
    //接口中的定义都省略了public abstract修饰符
    void add(String name);
    void delete(String name);
    void update(String name);
    void query(String name);

}
public interface TimeService {
    void timer();
}

类可以使用implements关键字实现接口,并且能够实现多个接口

public class UserServiceImpl implements UserService,TimeService{
    @Override
    public void add(String name) {

    }

    @Override
    public void delete(String name) {

    }

    @Override
    public void update(String name) {

    }

    @Override
    public void query(String name) {

    }
    
    @Override
    public void timer() {
        
    }
}
  • 接口作用小结:

​ 1. 接口本身作为规范去约束

​ 2. 能定义一些规范,让不同的实现类实现具体方法 —— 多个方法体 ===> 同一个约束

​ 3. 属性默认修饰符:public static final

​ 4. 方法默认修饰符:public abstract

​ 5. 接口不能被实例化,接口中也没有构造函数

​ 6. implements可以实现多个接口,需要重写接口中定义的所有规范

内部类

一般地写在Java文件中的非public类称作内部类,写一个例子来说明。本小节主要介绍四种常见到的内部类

示例如下:

public class A{
    //A是A.java文件中的类,但是由public修饰,因此不是内部类
    //一个java文件只能有一个public class,但能拥有多个class
}
class B{
    //B类是内部类,但不在A类中
}

成员内部类

一个类里的成员除了可以有属性、方法,还可以写类。

成员内部类有以下特点:

  1. 内部类的实例化是通过外部类来实现
Outer outer = new Outer();
//实例化内部类对象
Outer.Inner inner = outer.new Inner();
  1. 内部类的方法可以访问并操作外部类的私有属性和方法

示例如下:

//main方法
public static void main(String[] args) {

    //new outer类
    Outer outer = new Outer();

    //通过外部类实例化内部类
    Outer.Inner inner = outer.new Inner();
    inner.in();
    inner.getID();//操作外部类属性

}
public class Outer {

    private int id = 99;

    public void out(){
        System.out.println("OuterClassMethond");
    }//out()

    //成员内部类
    public class Inner {

        public void in(){

            System.out.println("InnerClassMethond");

        }

        //获得外部类的私有属性
        public void getID(){

            System.out.println(id);

        }

    }//InnerClass

}

静态内部类

静态内部类的声明在成员内部类的修饰符加上static关键字

静态内部类有以下特点:

  1. 由于静态内部类是类加载时一起载入,不允许访问外部类的非静态成员

示例如下:

//静态内部类
    public static class Inner {

        public void in(){

            System.out.println("InnerClassMethond");

        }

        //获得外部类的私有属性
        public void getID(){

//            System.out.println(id);

        }

    }//InnerClass

匿名内部类

使用new关键字创建实例对象,但不使用引用对象去指向它,直接使用实例对象调用方法。

示例如下:

public static void main(String[] args){
    
    new Apple().grow();
    
}

class Apple{
	
    public void grow(){

        System.out.println("AppleGrowsTwoInchs");
        
    }
    
}

特殊地,匿名内部类还可以用来实现接口

public static void main(String[] args){
    //此时创建实例对象时实现了接口的方法,但没有去声明这个实现类的名字。
    UserService userService = new UserService{
        
        @Override
        public void sayHello(){
            
        }
        
    };
    
}

interface UserService{
    void sayHello();
}

局部内部类

局部内部类是写在方法里的一个类,它的局限性比静态内部类还多,一般都是方法自己在用

示例如下:

public class Outers {

    //局部内部类
    public void getTime(){

        class Time{
            public void in(){
                
            }
            
        }

    }

}

异常

异常基本概念

​ 异常(Exception),是程序运行过程中出现的不期而至的各种状况,如:文件不存在、网络连接失败、非法参数等。

​ 异常发生在程序运行期间,它影响了程序的正常流程。

​ 异常要和错误(Error)区分开,错误不是异常,错误在代码中通常会被忽略不去处理,在遇见程序运行错误的时候,可以查看调试台里写的是Exception还是Error来区分两者。

  • 异常的简单分类
  1. 检查性异常:大多数情况下都是用户错误或者问题引起的异常,这是程序员无法预见的。因此,需要测试人员把所有可能发生的情况都测试一遍,排除异常。
  2. 运行时异常:运行时异常是最容易避免的异常。这种异常在编译时被忽略,不会出错,但是在运行时会显示出错,程序员可以根据错误情况解决异常情况。

Java在解决异常的问题上,使用了异常处理框架。在这个框架当中,Java把异常当做一个对象处理,并定义一个java.lang.Throwable类作为所有异常的超类。

人们把Throwable也称作异常。在Throable类下定义了许多类,可以把它们分成Error和Exception两大类。(所以会有出现把异常分为Error和Exception两大类的说法,但其实Error是错误,错误不属于异常,这种矛盾的说法属于历史遗留问题明白即可,无需再讨论。)

Thrwoable类的部分继承关系图如下:

提醒:发生了Error之后,大多数和我们程序员没有关系,基本是Java虚拟机生成并抛出的,很多错误是不可查的,而且是程序运行时不允许发生的情况。

Error和Exception的区别

Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程; Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。

异常处理

异常处理的五个关键字:try、catch、finally、throw、throws。

使用try和catch监控并捕获异常

关键字常用格式:try{}catch(Exception Type){}

  • try关键字后面的代码块是一块监控区域,用于监控区域内的代码有没有出现异常,如果出现异常,try代码块后面的代码不再执行,但是之后的程序代码依然正常执行,不会中断。

  • catch关键字后面的(Exception Type)用于判断监控区域内出错代码的异常类型是否匹配,如果匹配,则捕获该异常,则执行catch后面代码块区域里的代码。catch可以写多个,用于捕获不同类型的异常。如果发生的异常匹配多个异常类型,只会被第一个catch捕获。

  • finally关键字跟在catch关键字后面,可写可不写,如果写,则finally后面的代码块无论监控区域里是否出现异常,都会执行finally的代码块。主要用于资源的关闭!

注意:如果不是监控区域中发生了异常,不会执行finally的代码块

示例如下:

    //main方法
    public static void main(String[] args) {

        int a = 2;
        int b = 0;


        try {//try代码块,监控区域

            new Application().a();//代码发生异常,监控区域之后的代码就不再执行
            System.out.println(a/b);

        }catch (ArithmeticException e){//捕获异常

            System.out.println("除数不能为0");

        }catch (Error e){

            System.out.println("发生了Error错误");

        }catch (Exception e){

            System.out.println("发生了Exception异常");

        }finally {//善后工作,无论监控区域是否发生异常都会执行

            System.out.println("finally代码执行");

        }
        
        /**
        
        输出:
        发生了Error错误
		finally代码执行
        
        **/

    }

    void a(){

        b();

    }

    void b(){

        a();

    }
try-catch在JDK7和JDK9之后拥有的新特性

JDK7的新特性:在try的后面可以增加一个(),在括号中可以定义流对象,流对象的作用域就在try中有效

try中的代码执行完毕,会自动把流对象释放,就不用写finally了

示例如下:

/**
格式:
try(定义流对象。。。。可以定义多个){
    可能会产生的异常代码
}catch(){
    异常的处理逻辑
}
**/
public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("C:\\1.jpg");
             FileOutputStream fos = new FileOutputStream("F:\\develop\\1.jpg");) {
 
            int len = 0;
            while ((len = fis.read()) != -1) {
                fos.write(len);
            }
        } catch (IOException e) {
            System.out.println(e);
        }
 
 
    }

JDK9的新特性:try的前面可以定义流对象,try后面的()括号中可以直接引用流对象的名称,在try代码执行完毕后,流对象也可以释放掉,也不用写finally了

示例如下:

/**
A a = new A();
B b = new B();
try(a,b){
    可能产生的异常代码
}catch(异常类名 变量名){
    异常处理的逻辑
}
**/
public static void main(String[] args) throws FileNotFoundException {
        FileInputStream fis = new FileInputStream("C:\\1.jpg");
        FileOutputStream fos = new FileOutputStream("F:\\develop\\2.jpg");
        try (fis; fos) {
            int len = 0;
            while ((len = fis.read()) != -1) {
                fos.write(len);
            }
        } catch (IOException e) {
            System.out.println(e);
        }
 
    }

以上小节参考来自:JDK7和JDK9 try catch处理流对象的新特性与区别 作者:崔凯洋

使用throw和throws抛出异常

  • throw关键字可以主动在方法体中抛出异常,即使没有发生异常,也能够通过new一个异常类主动发生异常因此不需要也不能去处理这个异常

示例如下:

//main 方法
public static void main(String[] args) {

    int a = 2;
    int b = 0;

    new Application1().divide(a, b);
	/**
	
	输出:
	Exception in thread "main" java.lang.ArithmeticException
	at com.lcz.java.oop.J_ExceptionStudy.Application1.divide(Application1.java:19)
	at com.lcz.java.oop.J_ExceptionStudy.Application1.main(Application1.java:11)
	
	**/

}//main方法

void divide(int a,int b){
    if (b == 0){
        throw new ArithmeticException();//主动抛出异常,一般在方法中使用
    }//if (b == 0)
}
  • 使用throws关键字并不会主动抛出异常,而是在发生异常时本身不去处理,把遇到的异常交给调用这个函数的方法去处理这个异常。throws能同时抛出多种异常

示例如下:

//main 方法
public static void main(String[] args) {

    int a = 2;
    int b = 0;

    try {

        new Application1().divideNum(a, b);

    }catch (ArithmeticException e){
        System.out.println("除数不能为0");
    }

    /**
    
    输出:
    除数不能为0
    
    **/

}//main方法


void divideNum(int a,int b)throws ArithmeticException,Exception{

    System.out.println(a/b);

}

自定义异常

用户自定义异常只需要继承Exception类即可。

大致分为以下步骤:

  1. 创建自定义异常类。
  2. 在方法中通过throw关键字抛出异常对象。
  3. 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法
    的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
  4. 在出现异常方法的调用者中捕获并处理异常。

示例如下:

//main 方法
public static void main(String[] args) {

    try {

        dealNum(11);

    } catch (MyException e) {
        
        System.out.println("发生了MyException错误!");

    }

}

static void dealNum(int a) throws MyException {
    System.out.println("输入的数字:" + a);
    if(a > 10){
        throw new MyException(a);
    }
    System.out.println("progress success");
}

package com.lcz.java.oop.J_ExceptionStudy;

public class MyException extends Exception{

    private int detail;

    public MyException(int detail) {
        this.detail = detail;
    }

    @Override
    public String toString() {
        return "MyException{" +
                "detail=" + detail +
                '}';
    }
}

看bilibili狂神说后的一些小白笔记与总结。边学习边成长。欢迎在文章下探讨~~
了解狂神说在以下链接:https://space.bilibili.com/95256449

posted @ 2023-02-08 22:39  嬉嘘aʊ  阅读(26)  评论(0编辑  收藏  举报