Java基础—面向对象(四)

面向对象(四)

一、内部类

1.内部类的定义
将一个类定义在另一个类的内部,这个内部的类就被成为内部类(内置类,嵌套类)。

2.内部类的特点
1)内部类可以直接访问外部类的成员,包括私有成员(成员前省略了外部类名.this.);
2)外部类要访问内部类的成员,必须建立内部类的对象,通过内部类对象访问

示例代码:

package com.heisejiuhuche;

public class TestInnerClass {
    public static void main(String[] args) {
        Outer out = new Outer();
        out.methodOut();
    }
}

class Outer {
    //声明私有成员变量x
    private int x = 3;

    //声明内部类
    class Inner {
        void methodIn() {
            //内部类直接调用外部类成员x
            System.out.println("Inner: " + x);
        }
    }

    void methodOut() {
        //外部类要访问内部类的成员,必须先创建内部类对象
        Inner in = new Inner();
        //由内部类对象调用内部类成员
        in.methodIn();
    }
}

程序输出结果为:

Inner: 3

3)当内部来在外部类的成员位置上时,可以被private关键字修饰,进行封装

示例代码:

package com.heisejiuhuche;

public class TestInnerClass {
    public static void main(String[] args) {

    }
}

class Outer {
    //声明私有内部类,内部类在成员位置上时,可以被private关键字修饰
    private class Inner {
        void methodIn() {

        }
    }
}

4)外部其他类直接访问内部类成员(内部类声明在外部来成员位置上,且非私有)

示例代码:

package com.heisejiuhuche;

public class TestInnerClass {
    public static void main(String[] args) {
        //直接访问内部类成员格式
        Outer.Inner oi = new Outer().new Inner();
        oi.methodIn();
    }
}

class Outer {
    //声明成员变量x
    int x = 3;
    //声明内部类
    class Inner {
        void methodIn() {
            //内部类直接调用外部类成员x
            System.out.println("Inner: " + x);
        }
    }
}

5)不涉及特有数据的时候,内部类可以被static关键字修饰,具备静态特性

内部类被静态修饰后,只能访问外部类的静态成员,有访问局限性。外部其他类直接访问静态内部类的非静态成员的方式为:

new 外部类名.内部类名().静态内部类的非静态成员;

示例代码:

package com.heisejiuhuche;

public class TestInnerClass {
    public static void main(String[] args) {
        //访问静态内部类的非静态成员的格式
        new Outer.Inner().methodIn();
    }
}

class Outer {
    //声明成员变量x
    static int x = 3;
    //声明静态内部类
    static class Inner {
        void methodIn() {
            System.out.println("Inner: " + x);
        }
    }
}

外部其他类直接访问静态内部类的静态成员的方式为:

package com.heisejiuhuche;

public class TestInnerClass {
    public static void main(String[] args) {
        //访问静态内部类的静态成员的格式
        Outer.Inner.methodIn();
    }
}

class Outer {
    //声明成员变量x
    static int x = 3;
    //声明静态内部类
    static class Inner {
        static void methodIn() {
            System.out.println("Inner: " + x);
        }
    }
}

6)内部类中若声明了静态成员,该内部类必须是静态的
7)外部类的静态方法访问内部类时,内部类必须是静态的
8)重名变量注意事项

示例代码:

package com.heisejiuhuche;

public class TestInnerClass {
    public static void main(String[] args) {
        Outer out = new Outer();
        out.methodOut();
    }
}

class Outer {
    //声明成员变量x
    int x = 3;

    //声明内部类
    class Inner {
        int x = 4;
        void methodIn() {
            int x = 5;
            //内部类直接调用外部类成员x
            System.out.println("Inner: " + x);
        }
    }

    void methodOut() {
        //外部类要访问内部类的成员,必须先创建内部类对象
        Inner in = new Inner();
        //由内部类对象调用内部类成员
        in.methodIn();
    }
}

程序输出结果为:

Inner: 5

如果要输出4,输出语句应为System.out.println("Inner: " + this.x);
如果要输出3,输出语句应为System.out.println("Inner: " + Outer.this.x);

9)内部类可以被定义在局部位置上
内部类定义在局部位置上的示例及特点

示例代码:

package com.heisejiuhuche;

public class TestInnerClass2 {
    public static void main(String[] args) {
        new Outer().methodOut();
    }
}

class Outer {
    int x = 3;

    void methodOut() {
        //声明内部类于外部类的方法中
        class Inner {
            void methodIn() {
                //同样可以直接访问外部类成员
                System.out.println("Inner: " + x);
            }
        }
        //创建Inner()匿名对象调用methodIn()方法
        new Inner().methodIn();
    }
}

特点:
1> 不可以被成员修饰符修饰,如private
2> 仍持有外部类的引用,仍可以直接访问外部类的成员
3> 如果要访问其所在局部的局部变量,要用final修饰该变量

3.内部类的应用
描述事物的时候,如果事物内部还包含有事物。由于该内部事物在使用外部事物的内容,所以该内部的事物用内部类来描述。如描述人体的时候,人体由器官组成,包括心脏;心脏有属性(成员变量),行为(成员方法),比较复杂(应声明成一个类),心脏又和人体其他器官有直接的联系(需要直接访问外部类的成员),那么应将心脏作为对象封装,形成人体的内部类,并私有,对外提供方法访问。

二、匿名内部类

1.定义
没有名字的内部类叫做匿名内部类,即内部类的简写格式

2.匿名内部类使用的前提
内部类必须继承一个类或者实现接口

示例代码:

package com.heisejiuhuche;

public class TestAnonymousInnerClass {
    public static void main(String[] args) {
        new Outer2().methodOut();
    }
}

abstract class Abs {
    abstract void show();
}

class Outer2 {
    int x = 3;

    public void methodOut() {
        //匿名内部类继承Abs类
        new Abs() {
            void show() {
                System.out.println("Anonymous Inner: " + x);
            }
        }.show();
    }
}

3.匿名内部类是一个匿名子类对象,是一个带内容的对象,是将定义类和创建对象封装为一体的表现方式,为了简化书写,方便方法调用而存在

匿名内部类就是一个子类继承父类的类体,除了复写父类的方法,还能在匿名内部类中定义子类的特有方法(面试用)

示例代码:

package com.heisejiuhuche;

public class TestAnonymousInnerClass {
    public static void main(String[] args) {
        new Outer2().methodOut();
    }
}

abstract class Abs {
    abstract void show();
}

class Outer2 {
    int x = 3;

    public void methodOut() {
        //匿名内部类继承Abs类
        new Abs() {
            void show() {
                System.out.println("Anonymous Inner Class: " + x);
            }

            void run() {
                System.out.println("Anonymous Inner Class run...")
            }
        }.run();
    }
}

匿名内部类中的方法不要超过3个

4.匿名内部类的应用

示例代码:

package com.heisejiuhuche;

public class TestAnonymInnerClassUse {

    public static void main(String[] args) {
        /*
         * 使用匿名内部类作为参数
         */
        show(new Inter() {
            public void method() {
                System.out.println("Run...");
            }
        });
    }

    /* 调用一个方法,这个方法需要接收一个接口对象作为参数
     * 并且这个接口里面的方法不超过3个
     * 那么就可以用匿名内部类
     */
    static void show(Inter in) {
        in.method();
    }
}

interface Inter {
    public abstract void method();
}

5.匿名内部类练习
根据main方法中的代码,补全其他代码

示例代码:

package com.heisejiuhuche;

/**
 * 根据main方法中的代码,补全Outer类
 * @author jeremy
 *
 */

public class TestAnonymInner {
    public static void main(String[] args) {
        /*
         * 从这行代码分析:
         * 1.由于由类名直接调用,所以function()方法是一个静态方法
         * 2.由于method()一定是被一个对象所调用,所以Outer类在调用function()方法之后,返回值一定是一个对象
         * 3.由于method()方法在Inter接口中,那么一定是一个类实现了Inter接口,然后复写了method()方法,并调用
         *   该类的对象一定是Inter类型的对象,所以function()方法的返回值为Inter类型
         */
        Outer.function().method();
    }
}

interface Inter {
    public abstract void method();
}

class Outer {

    static Inter function() {
        return new Inter() {
            public void method() {
                System.out.println("Anonymous Inner Class run...");
            }
        };
    }
}

小扩展(面试用):
没有实现接口,也没有继承任何父类,仍然可以使用匿名内部类调用方法;只需使用所有类的父类Object类即可
示例代码:

class InnerTest {
    public static void main(String[] args) {
        new Object() {
            public void method() {
                System.out.println("Run...");
            }
        }.method();
    }
}

三、异常机制

1.定义
异常是程序在运行(非编译)过程中出现的不正常情况。异常用来描述生活中出现的问题,也是一个对象。

2.问题的分类
Java对于问题的描述分为两大类,有严重与非严重之分。对于严重的问题,Java用Error类来描述,称为错误;对于非严重的问题,Java用Exception类进行描述,称为异常。
1)对于Error,一般不编写针对性的代码进行处理;
2)对于Exception,可以针对性的编写代码进行处理

ErrorException都是Throwable的子类

3.异常的处理
Java提供了特定的语句对异常进行处理:

示例代码:

try {
    运行时可能会出现异常的代码;
} catch() {
    处理异常的代码;
} finally {
    一定会执行的语句;
}

示例代码:

package com.heisejiuhuche;

public class TestException {
    public static void main(String[] args) {
        /* try catch语句,用于检测异常,并做出相应处理 */
        try {
            int x = Divide.div(7, 1);
            System.out.println(x);
        } catch(Exception e) {
            System.out.println("Divide by zero...");
        }
        System.out.println("End...");
    }
}

/* 除法类,有一个方法,返回两个int类型参数相除的结果 */
class Divide {
    public static int div(int a, int b) {
        return a / b;
    }
}

4.异常对象常见方法
1)getMessage()

示例代码:

//显示异常信息
System.out.println(e.getMessage());
    / by zero

2)toString()

示例代码:

//显示异常名称+异常信息
System.out.println(e.toString());
    java.lang.ArithmeticException: / by zero

3)printStackTrace()(JVM默认异常处理机制调用该方法)

示例代码:

//显示异常名称+异常信息+异常出现的位置
e.printStackTrace();
    java.lang.ArithmeticException: / by zero
        at com.heisejiuhuche.Divide.div(TestException.java:20)
        at com.heisejiuhuche.TestException.main(TestException.java:7)

5.throws关键字
由于编写功能代码的时候,不知道调用者会不会传入非法参数,导致异常。那么,开发者应该在功能上,通过throws关键字先声明,这个功能可能会抛出异常。

示例代码:

package com.heisejiuhuche;

public class TestException {
    public static void main(String[] args) {
            int x = Divide.div(7, 0);
            System.out.println(x);
        System.out.println("End...");
    }
}

class Divide {
    public static int div(int a, int b) throws Exception {
        return a / b;
    }
}

throws关键字表示该方法的调用者一定要处理这个异常,否则编译失败。上述代码,在主函数中没有对可能出现的异常做任何处理,程序编译报错:

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    Unhandled exception type Exception
    at com.heisejiuhuche.TestException.main(TestException.java:6)

处理throws关键字有两种方式:
1)抛出异常

示例代码:

package com.heisejiuhuche;

public class TestException {
    //在main方法上也加上throws声明
    public static void main(String[] args) throws Exception {
            int x = Divide.div(7, 1);
            System.out.println(x);
        System.out.println("End...");
    }
}

class Divide {
    public static int div(int a, int b) throws Exception {
        return a / b;
    }
}

在调用div()方法的主函数上,也加上throws关键字,将可能出现的异常抛出给虚拟机。抛给虚拟机之后,虚拟机启用默认异常处理机制,如果有异常,直接终止程序,打印异常信息。

2)捕捉异常

示例代码:

package com.heisejiuhuche;

public class TestException {
    public static void main(String[] args) {
        //用try catch捕捉异常并处理
        try {
            int x = Divide.div(7, 1);
            System.out.println(x);
        } catch(Exception e) {
            System.out.println(e.printStackTrace());
        }
        System.out.println("End...");
    }
}

class Divide {
    public static int div(int a, int b) throws Exception {
        return a / b;
    }
}

预先编写处理代码try catch,对可能出现的异常进行处理。

6.多异常处理
1)一个功能可能出现多个异常,那么在用throws关键字声明异常的时候,应该声明多个;并且声明更为具体的异常,便于处理

声明具体异常示例代码:

package com.heisejiuhuche;

public class TestException {
    public static void main(String[] args) {
        try {
            int x = Divide.div(7, 1);
            System.out.println(x);
        } catch(ArithmeticException e) {
            System.out.println(e.printStackTrace());
        }
        System.out.println("End...");
    }
}

class Divide {
    //声明具体的ArithmeticException 
    public static int div(int a, int b) throws ArithmeticException {
        return a / b;
    }
}

将异常声明为更加具体的ArithmeticException,那么在处理的时候直接处理这个具体异常即可。

声明多个异常示例代码:

package com.heisejiuhuche;

import javax.management.openmbean.ArrayType;

public class TestException {
    public static void main(String[] args) {
        try {
            int x = Divide.div(3, 1);
            System.out.println(x);
        } catch(ArithmeticException e) { //处理ArithmeticException
            System.out.println(e.toString());
            System.out.println("Divide by zero...");
        } catch(ArrayIndexOutOfBoundsException e) { //处理ArrayIndexOutOfBoundsException  
            System.out.println(e.toString());
            System.out.println("Index out of bounds...");
        }
        System.out.println("End...");
    }
}

class Divide {
    //功能可能抛出ArithmeticException和ArrayIndexOutOfBoundsException
    public static int div(int a, int b) throws ArithmeticException, ArrayIndexOutOfBoundsException {
        int[] arr = new int[a];
        System.out.println(arr[7]);
        return a / b;
    }
}

功能代码中抛出的所有异常,调用者要逐一进行具体捕捉,并处理。

2)注意,如果catch的异常出现继承关系,父类异常放在最下面(不建议这么做,具体处理最好,不要直接处理Exception

示例代码:

catch(ArithmeticException e) {
    System.out.println(e.toString());
    System.out.println("Divide by zero...");
} catch(ArrayIndexOutOfBoundsException e) {    
    System.out.println(e.toString());
    System.out.println("Index out of bounds...");
} catch(Exception e) {    
    System.out.println(e.toString());
    System.out.println("Over...");
}

3)现实开发异常处理
现实开发中,开发者不会将错误信息打印在屏幕上,这没有意义。开发中应该将错误信息,保存在硬盘某个文件里,由开发者及时查阅,并修正程序。

7.自定义异常
实际开发中,程序会出现特有的问题,而这些问题没有被Java描述到并封装成对象。那么对于这些问题,可以按照Java对问题的封装思路,对特有问题进行自定义封装。例如,如果在一个程序(功能)中,除数不能为0,同时除数不能是负数,如果是负数,也视为异常。这个异常,就需要开发者自定义。

Java已经描述并封装成对象的异常,开发者可以选择由程序自动抛出,也可以选择手动抛出;而自定义异常,开发者只能选择手动抛出。手动抛出异常,用throw关键字。

1)定义异常类

示例代码:

package com.heisejiuhuche;

class NegativeException extends Exception {

}

class Divide {
    public static int div(int a, int b) throws NegativeException {
        if(b < 0) {
            //通过throw关键字,手动抛出自定义异常对对象
            throw new NegativeException();
        }
        return a / b;
    }
}

当方法内出现throw抛出的异常对象,就必须要给出相应的处理。要么在方法内部try catch;要么在方法上声明异常,让调用者处理(运行时异常除外)。一般情况下,方法内部出现异常,方法上需要声明异常。那么主函数(方法调用者)必须对异常进行处理(运行时异常除外)。

示例代码:

public class TestException {
    public static void main(String[] args) throws Exception {
        try {
            int x = Divide.div(7, 1);
            System.out.println(x);
        } catch(NegativeException e) {
            System.out.println("Negative divisor...")
        }
        System.out.println("End...");
    }
}

程序输出结果为:

com.heisejiuhuche.NegativeException
Negative divisor...
End...

2)定义异常信息
由于Throwable类中已经对异常信息进行了操作(初始化,获取等),所以子类只需要在构造函数中调用父类的构造方法,将异常信息传递给父类,即可使用。

Throwable类对异常信息的基本处理代码示例:

class Throwable {
    pricate String message;

    Throwable(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

因此,子类自定义异常信息代码示例:

package com.heisejiuhuche;

public class CutomizeException {
    public static void main(String[] args) {
        try {
            int x = Divide1.div(3, -1);
            System.out.println(x);
        } catch(NegativeException e) { //捕捉自定义异常
            System.out.println(e.toString());
        }
        System.out.println("End...");
    }
}

class Divide1 {
    public static int div(int a, int b) throws NegativeException {
        if(b < 0) {
            //抛出自定义异常
            throw new NegativeException("Negative divisor..."); 
        }
        return a / b;
    }
}

class NegativeException extends Exception {
    //定义异常信息
    NegativeException(String msg) {
        //调用父类的构造方法,获取自定义异常信息
        super(msg);
    }
}

程序输出结果为:

com.heisejiuhuche.NegativeException: Negative divisor...
End...

3)自定义异常方法
上述代码中,定义一个异常方法,获取出错的负数值。

示例代码:

package com.heisejiuhuche;

public class CutomizeException {
    public static void main(String[] args) {
        try {
            int x = Divide1.div(3, -10);
            System.out.println(x);
        } catch(NegativeException e) {
            System.out.println(e.toString());
            System.out.println("Wrong divisor is: " + e.getNum());//打印错误值
        }
        System.out.println("End...");
    }
}

class Divide1 {
    public static int div(int a, int b) throws NegativeException {
        if(b < 0) {
            throw new NegativeException("Negative divisor...", b);
        }
        return a / b;
    }
}

class NegativeException extends Exception {
    private int num; //声明私有变量,用于存储错误数据

    NegativeException(String msg, int num) {
        super(msg);
        this.num = num; //初始化错误数据
    }

    public int getNum() {
        return num; //获取错误值
    }
}

程序输出结果为:

com.heisejiuhuche.NegativeException: Negative divisor...
Wrong divisor is: -10
End...

8.throws和throw关键字的区别
1)throws使用在方法上;throw使用在方法内;
2)throws后面跟异常类,可以跟多个;throw后面跟异常对象

9.运行时异常(RuntimeException)
1)异常的分类

1> 编译时被检测的异常(非运行时异常及其子类)
方法中抛出了非运行时异常及其子类,方法上必须声明;方法上声明了非运行时异常及其子类,调用者必须进行捕捉处理
2> 编译时不被检测的异常(运行时异常及其子类)

2)运行时异常定义
RuntimeException(运行时异常)是Exception里一个很特殊的异常。其特点有:

1> 运行时异常在方法内抛出,方法上不需要声明;
2> 运行时异常在方法上声明,调用者可以不用进行处理

示例代码:

package com.heisejiuhuche;

public class CutomizeException {
    public static void main(String[] args) {
            //运行时异常无须调用者进行处理,可以没有try catch,编译通过
            int x = Divide1.div(3, 0);
            System.out.println(x);
            System.out.println(e.toString());
        }
        System.out.println("End...");
    }
}

class Divide1 {
    public static int div(int a, int b) {
        if(b < 0) {
            //运行时异常无须在方法上声明,直接抛出即可,编译通过
            throw new ArithmeticException("Negative divisor...");
        }
        return a / b;
    }
}

因为运行时异常,不需要让调用者处理。当运行时异常发生时,出现了无法继续运行的情况,虚拟机希望程序停止,由调用者对程序进行修正,排除异常。

如果自定义异常发生时,程序无法再进行运算,那么就让该自定义异常继承RuntimeException

示例代码:

class NegativeException extends RuntimeException {
    NegativeException(String msg) {
        super(msg);
    }
}

10.异常练习
老师需要用电脑上课,电脑会出现蓝屏和温度过高烧毁这两个异常。利用异常机制,对每个异常进行处理。

示例代码:

package com.heisejiuhuche;

public class ExceptionTest {
    /* 将main方法想像成教育机构的老板,老板命令老师教课 */
    public static void main(String[] args) {
        Teacher teacher = new Teacher("Glen");
        try {
            teacher.teach();
        } catch(TeachingPlanFailException e) { /* 如果出现电脑烧毁,教学计划失败,老板就炒了老师鱿鱼! */
            System.out.println(e.toString());
            System.out.println("Glen is fired... 'Cause he broke a computer...");
        }
    }
}

class Teacher {
    private String name;
    private Computer computer;

    /* 老师初始化时有名字和一台电脑 */
    Teacher(String name) {
        this.name = name;
        computer = new Computer();
    }

    public void teach() throws TeachingPlanFailException {
        try {
            computer.run();
        } catch(BlueScreenException e) { /* 蓝屏的话,打印蓝屏信息,重启电脑 */
            System.out.println(e.toString());
            computer.restart();
        } catch(BurntException e) { /* 烧毁的话,打印烧毁信息,抛出教学计划失败异常 */
            System.out.println(e.toString());
            throw new TeachingPlanFailException("Teaching plan fials...");
        }
        System.out.println("Teacher teaching..."); /* 正常情况,老师上课 */
    }
}

class Computer {

    /* 电脑状态,1为正常,0为蓝屏,-1为烧毁 */
    private int state = -1;

    /* 电脑启动方法 */
    public void run() throws BlueScreenException, BurntException {
        /* 判断状态值,决定正常运行还是抛出相应异常对象 */
        if(state == 1) {
            System.out.println("Computer run...");
        } else if(state == 0) {
            throw new BlueScreenException("Blue screen occured...");
        } else if(state == -1) {
            throw new BurntException("Computer is down...");
        }
    }

    /* 电脑重启方法 */
    public void restart() {
        state = 1;
        System.out.println("Computer restarting...");
    }
}

/* 电脑蓝屏异常 */
class BlueScreenException extends Exception {
    BlueScreenException(String message) {
        super(message);
    }
}

/* 电脑烧毁异常 */
class BurntException extends Exception {
    BurntException(String message) {
        super(message);
    }
}

/* 不能直接将烧毁异常抛给老板,老板处理不了,电脑烧毁了,
 * 老师会出现教学计划失败异常,应该将这个异常封装成异常类
 * 再抛给老板,让老板处理
 */
class TeachingPlanFailException extends Exception {
    TeachingPlanFailException(String message) {
        super(message);
    }
}