面向对象(下)

类的继承

类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类称为子类,现有类称为父类,子类会自动拥有父类所有可继承的属性和方法。(用extends关键字)

 

//定义Animal类
class Animal{
    String name;    //定义name属性
    //定义动物叫的方法
    void shout(){
        System.out.println("动物发出叫声。");
    }
}
//定义Dog类继承Animal类
class Dog extends Animal{
    //定义一个打印name的方法
    public void printName(){
        System.out.println("name = "+name);     //父类的name
    }
}
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();//创建一个Dog类的实例对象
        dog.name = "tony";//为Dog类的name属性赋值
        dog.printName();//调用Dog类的printName()方法
        dog.shout();//调用Dog类继承来的shout()方法
    }
}

运行结果

name = tony

动物发出叫声。

 

注:①只支持单继承,不允许多重继承。②多个类可以继承一个父类。③多层继承是可以的,如A继承B,B继承C。

重写父类方法

在子类重写的方法要和父类被重写的方法具有相同的方法名、参数列表以及返回值类型。

//定义Animal类
class Animal{
    //定义动物叫的方法
    void shout(){
        System.out.println("动物发出叫声。");
    }
}
//定义Dog类继承Animal类
class Dog extends Animal{
    //这里重写父类,定义狗叫的方法
     void shout(){
        System.out.println("汪汪...");
    }
}
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();//创建一个Dog类的实例对象
        dog.shout();//调用Dog类继承来的shout()方法
    }
}

运行结果

汪汪...

注:子类重写父类方法时,不能使用比父类中重写的方法更严格的访问权限,如父类中的方法是public的,子类方法就不能是private。

super关键字

super关键字用于访问父类成员,例如访问父类的成员变量、成员方法和构造方法。

①使用super关键字调用父类的成员变量和成员方法。格式为:

super.成员变量
super.成员方法([参数1,参数2...])

例:

class Animal{
    String name = "动物";
    void shout(){
        System.out.println("动物发出叫声");
    }
}
class Dog extends Animal{
    //重写成员变量name
    String name = "犬类";
    //重写父类shout()方法
    void shout(){
        super.shout();      //访问父类的成员方法
    }
    //打印name的方法
    void printName(){
        System.out.println("name = "+super.name);   //访问父类的成员变量
    }
}
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.shout();
        dog.printName();
    }
}

运行结果

动物发出叫声
name = 动物

②使用super关键字调用父类的构造方法。格式为:

super([参数1,参数2...])

例:

class Animal{
    //定义Animal类有参的构造方法
    public Animal(String name){
        System.out.println("我是一只"+name);
    }
}
class Dog extends Animal{
   public Dog(){
       super("二哈");     //调用父类有参的构造方法
   }
}
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
    }
}

运行结果

我是一只二哈

注:通过super调用父类构造方法的代码必须位于子类构造方法的第一行,并且只能出现一次。还有,在子类的构造方法中一定会调用父类的某个构造方法,可以通过super指定调用父类的某个构造方法,如果没有指定,实例化子类对象时,会自动调用父类无参的构造方法(这时如果父类中没有无参的构造方法,会报错)。

定义一个类时,如果没有特殊要求,尽量在一个类中定义一个无参的构造方法,避免被继承时出现错误。

class Animal{
    //定义Animal类无参的构造方法
    public Animal(){
        System.out.println("我只是一只动物");
    }
    //定义Animal类有参的构造方法
    public Animal(String name){
        System.out.println("我是一只"+name);
    }
}
class Dog extends Animal{
    //定义Dog类无参的构造方法
   public Dog(){
      //方法中无代码
   }
}
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
    }
}

运行结果

我只是一只动物

final关键字

final关键字可用于修饰类、变量和方法,它有“这是无法改变的”或“最终”的含义。有以下特性:

①final修饰的类不能被继承

②final修饰的方法不能被子类重写

当在父类中定义某个方法时,如果不希望被子类重写,就可以使用final关键字修饰该方法。

③final修饰的变量(成员变量和局部变量)是常量,只能赋值一次。

注:使用final关键字修饰成员变量时,虚拟机不会对其进行初始化,因此使用final修饰成员变量时,需要在定义变量的同时赋予一个初始值。

抽象类

定义方法时不写方法体,该方法称为抽象方法,必须使用abstract关键字修饰。当类中包含了抽象方法,该类也必须使用abstract修饰,该类称为抽象类。

抽象类不可以被实例化,因为抽象类中的抽象方法没有方法体,不能被调用,如果想调用抽象类中的方法,需要创建子类,在子类中将抽象类中的抽象方法进行实现。

//定义抽象类Animal
abstract class Animal{
    //定义抽象方法shout()
    abstract void shout();
}
//定义Dog类继承抽象类Animal
class Dog extends Animal{
    //实现抽象方法shout()
    void shout(){
        System.out.println("汪汪...");
    }
}
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.shout();
    }
}

运行结果

汪汪...

接口

如果一个抽象类中所有的方法都是抽象的,则可以将这个类用另一种方式来定义,即接口。定义接口时,需要使用interface关键字来声明。

interface Animal{
    int ID = 1;     //定义全局常量
    void breathe();     //定义抽象方法
}

//接口中的变量默认使用“public static final”来修饰,即全局常量

//接口中定义的方法默认使用“public abstract”来修饰,即抽象方法

不能通过实例化调用接口中的方法,需要定义一个类,并使用implements关键字实现接口中的所有方法。

下面是类与接口之间实现的关系

//定义Animal接口
interface Animal{
    int ID = 1;     //定义全局常量
    void breathe();     //定义抽象方法breathe()
    void run();     //定义抽象方法run()
}
//Dog类实现了Animal接口
class Dog implements Animal{
    //实现breathe()方法
    public void breathe(){
        System.out.println("狗在呼吸");
    }
    //实现run()方法
    public void run(){
        System.out.println("狗在跑");
    }
}
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.breathe();
        dog.run();
    }
}

运行结果

狗在呼吸

狗在跑

还可以定义一个接口使用extends关键字去继承另一个接口

//定义Animal接口
interface Animal{
    int ID = 1;     //定义全局常量
    void breathe();     //定义抽象方法breathe()
    void run();     //定义抽象方法run()
}
//定义了LandAnimal接口,并继承了Animal接口
interface LandAnimal extends Animal{
    void liveOnload();      //定义C抽象方法liveOnload()
}
//Dog类实现了LandAnimal接口
class Dog implements LandAnimal{
    //实现breathe()方法
    public void breathe(){
        System.out.println("狗在呼吸");
    }
    //实现run()方法
    public void run(){
        System.out.println("狗在跑");
    }
    //实现liveOnload()方法
    public void liveOnload(){
        System.out.println("狗生活在陆地上");
    }
}
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.breathe();
        dog.run();
        dog.liveOnload();
    }
}

运行结果

狗在呼吸

狗在跑

狗生活在陆地上注:①接口中的方法都是抽象的,不能实例化对象。②当一个类实现接口时,如果这个类是抽象类,则实现接口中的部分方法即可,否则需要实现接口中的所有方法(“class A implements B{}”B中的方法必须全在A中实现出来,如果是“abstract class A implements B{}”则不需要全写)。③一个类通过implements关键字实现接口时,可以实现多个接口用逗号连接(class A implements B,C{})。④一个接口可以通过extends关键字继承多个接口用逗号连接(interface A extends B,C{})。⑤一个类在继承另一个类的同时还可以实现接口,此时,extends关键字必须位于implements关键字之前(class A extends B implements C{}    先继承,再实现)。

多态

在同一个方法中,由于参数类型不同而导致执行效果各异的现象就是多态。

为了实现多态,允许使用一个父类类型的变量来引用一个子类类型的对象,根据被引用子类对象特征的不同,得到不同的运行结果。

//定义接口Animal
interface Animal{
    void shout();
}
//定义Cat类实现Animal接口
class Cat implements Animal{
    //实现shout()方法
    public void shout(){
        System.out.println("喵喵");
    }
}
//定义Dog类实现Animal接口
class Dog implements Animal{
    //实现shout()方法
    public void shout(){
        System.out.println("汪汪");
    }
}
public class Test {
    //定义静态的animalShout()方法,接收一个Animal类型的参数
    public static void animalShout(Animal an){
        an.shout();     //调用实际参数的shout()方法
    }
    public static void main(String[] args) {
Animal an1 = new Cat();
Animal an2 = new Dog();
        animalShout(an1);       //调用animalShout()方法,将an1作为参数传入
        animalShout(an2);       //调用animalShout()方法,将an2作为参数传入
    }
}

运行结果

喵喵

汪汪

所以多态不仅解决了方法同名的问题,而且还使程序变得更加灵活,从而有效地提高程序的可扩展性和可维护性。

对象的类型转换

多态中,涉及到将子类对象当作父类类型使用的情况,如上面代码所写到的这两行:

Animal an1 = new Cat();        //将Cat对象当作Animal类型来使用

Animal an2 = new Dog();        //将Dog对象当作Animal类型来使用

注:此时不能通过父类变量去调用子类中的某些方法(这里的某些方法指的是子类中有的方法而父类中没有)。

//定义接口Animal
interface Animal{
    void shout();
}
//定义Cat类实现Animal接口
class Cat implements Animal{
    //实现抽象shout()方法
    public void shout(){
        System.out.println("喵喵");
    }
    //定义sleep()方法
    void sleep(){
        System.out.println("猫睡觉");
    }
}
public class Test {
    //定义静态的animalShout()方法,接收一个Animal类型的参数
    public static void animalShout(Animal animal){
        animal.shout();     //调用传入参数Animal的shout()方法
        //编译器检查到下面这行时,发现Animal类中没有定义sleep()方法,会报错
        animal.sleep();      //调用传入参数Animal的sleep()方法
    }
    public static void main(String[] args) {
        Cat cat = new Cat();     //创建Cat实例对象
        animalShout(cat);       //调用animalShout()方法,将cat作为参数传入
    }
}

以上代码会报错,所以需要进行强制类型转换,将animalShout()方法改为:

public static void animalShout(Animal animal){
    Cat cat = (Cat) animal;     //将animal对象强制转换为Cat类型
    cat.shout();     //调用传入参数Animal的shout()方法
    cat.sleep();      //调用传入参数Animal的sleep()方法
}

修改后运行结果

喵喵

猫睡觉

需要注意的是,在类型转换时也可能出现错误,当animalShout()方法传入Dog类型的对象时,会报错,例:

//定义接口Animal
interface Animal{
    void shout();
}
//定义Cat类实现Animal接口
class Cat implements Animal{
    //实现抽象shout()
    public void shout(){
        System.out.println("喵喵");
    }
    //定义sleep()方法
    void sleep(){
        System.out.println("猫睡觉");
    }
}
//定义Dog类实现Animal接口
class Dog implements Animal{
    public void shout(){
        System.out.println("汪汪");
    }
}
public class Test {
    //定义静态的animalShout()方法,接收一个Animal类型的参数
    public static void animalShout(Animal animal){
        Cat cat = (Cat) animal;     //将animal对象强制转换为Cat类型
        cat.shout();     //调用传入参数Animal的shout()方法
        cat.sleep();      //调用传入参数Animal的sleep()方法
    }
    public static void main(String[] args) {
        Dog dog = Dog();     //创建Cat实例对象
        animalShnew out(dog);       //调用animalShout()方法,将cat作为参数传入
    }
}

程序运行会出错,原因是在调用animalShout()方法时,传入一个Dog对象,在强制类型转换时,Animal类型的变量无法强转为Cat类型。

针对这种情况,Java提供了关键字instanceof,它能判断一个对象是否为某个类(或接口)的实例或者子类实例,如b instanceof A ,b为子类对象或者实现类对象,

语法:

对象(或者对象引用变量) instanceof 类(或接口)

对animalShout()方法进行修改后

public static void animalShout(Animal animal){
    if(animal instanceof Cat){      //判断animal是否是Cat类的实例对象
        Cat cat = (Cat) animal;     //将animal对象强制转换为Cat类型
        cat.shout();     //调用传入参数Animal的shout()方法
        cat.sleep();      //调用传入参数Animal的sleep()方法
    }
    else
        System.out.println("这个动物不是猫");
}

运行结果

这个动物不是猫

使用instanceof关键字判断animalShout()方法中传入的对象是否为Cat类型,如果是就进行类型转换,否则输出“这个动物不是猫”。

Object类

Object类是所有类的父类。比如toString()方法,在该方法中输出了对象的基本信息,但也可以在其它类中用对其重写。

 

class Animal{
    //重写Object类的toString()方法
    public String toString(){
        return "I am an Animal";
    }
}
public class Test {
    public static void main(String[] args) {
        Animal an = new Animal();
        System.out.println(an.toString());
    }
}

运行结果

I am an Animal

如果不对其重写会输出“类名+16进制表示的该对象哈希值”,如“Animal@b4c966a”

匿名内部类

之前都是用子类来实现接口,并根据该类进行实例化。除此之外,还可以使用匿名内部类来实现接口。

匿名内部类格式:

new 父类(参数列表)或父接口(){//匿名内部类实现部分}

 

//定义动物类接口
interface Animal{
    void shout();
}
public class Test {
    //定义静态方法animalShout()
    public static void animalShout(Animal an){
        an.shout();
    }
    public static void main(String[] args) {
        //定义匿名内部类作为参数传递给animalShout()方法
        animalShout(new Animal(){
            //实现shout()方法
            public void shout(){
                System.out.println("喵喵");
            }
        });
    }
}

运行结果

喵喵

接下来分两步来编写匿名内部类(以便容易理解):

①在new Animal()后面又一对大括号,表示创建的对象是Animal的子类实例,该子类是匿名的。

animalShout(new Animal(){});

②在大括号中编写匿名子类的实现代码。

animalShout(new Animal(){
    //实现shout()方法
    public void shout(){
        System.out.println("喵喵");
    }
});

异常

举一个ArithmeticExceotion异常:

public class Test {
    public static void main(String[] args) {
        int result = divide(4,0);
        System.out.println("结果: "+result);
    }
    public static int divide(int x,int y){
        int result = x/y;
        return result;
    }
}

运行结果

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at Test.divide(Test.java:7)
    at Test.main(Test.java:3)

Throwable类中包含两大类Error类和Exception类。

Error类称为错误类,表示Java运行时产生的系统内部错误或资源耗尽的错误,比较严重,仅靠修改程序本身是不能恢复执行的;

Exception类称为异常类,它表示程序本身可以处理的错误。

 

Throwable常用方法

方法声明

功能描述

String getMessage()

返回此throwable的详细消息字符串

void printStackTrace()

将此throwable及其追踪输出至标准错误流

void printStackTrace(PrintStream s)

将此throwable及其追踪输出到指定的输出流

 

try...catch和finally

由于发生异常,程序立即终止,无法继续执行下去,所以提供了异常的处理方式--异常捕获。异常捕获通常使用try...catch语句,格式:

try{
//程序代码块
}catch(ExceptionType(Exception类及其子类) e){
//对ExceptionType的处理
}

需要注意的是,在try代码块中,发生异常语句后面的代码不会被执行,如下面的“System.out.println(result);”没有执行。

public class Test {
    public static void main(String[] args) {
        //下面的代码定义了一个try...catch语句用于捕获异常
        try{
            int result = divide(4,0);
           //发生异常,下面这条语句没被执行
            System.out.println("结果: "+result);
        }catch (Exception e){   //对异常进行处理
            System.out.println("捕获的异常信息为:"+e.getMessage());
        }
    }
    public static int divide(int x,int y){
        int result = x/y;
        return result;
    }
}

运行结果

捕获的异常信息为:/ by zero

还有finally代码块,无论是否发生异常后面代码都会被执行。由于这种特性,在程序设计时,经常会在try...catch后使用finally代码块来完成必须做的事情,例如释放系统资源。

public class Test {
    public static void main(String[] args) {
        //下面的代码定义了一个try...catch语句用于捕获异常
        try{
            int result = divide(4,0);
            System.out.println("结果: "+result);
        }catch (Exception e){   //对异常进行处理
            System.out.println("捕获的异常信息为:"+e.getMessage());
            return;     //用于结束当前语句
        }finally{
            System.out.println("进入finally代码块");
        }
        System.out.println("程序继续向下执行...");
    }
    public static int divide(int x,int y){
        int result = x/y;
        return result;
    }
}

运行结果

捕获的异常信息为:/ by zero

进入finally代码块

return语句用于结束当前语句,所以“System.out.println("程序继续向下执行...");”就不会执行了,而finally中的代码仍然会执行。总的来说就是不会被return语句和异常影响。

注:当在try...catch中执行了System.exit(0)语句finally中的代码就不会执行了,System.exit(0)语句表示退出当前的Java虚拟机,任何代码都不会被执行了。还有一种是try代码块中没有代码运行finally也不会被运行。

throws关键字

在方法的后面使用throws关键字对外声明该方法有可能发生异常。格式:

修饰符 返回值类型 方法名([参数1,参数2...])throws  ExceptionType1[,ExceptionType2...]{}

如果在某方法上声明抛出异常,就必须使用try...catch对该方法进行处理,否则会报错。

public class Test {
    public static void main(String[] args) {
        //下面的代码定义了一个try...catch语句用于捕获异常
        try{
            int result = divide(4,2);
            System.out.println("结果: "+result);
        }catch (Exception e){   //对异常进行处理
            e.printStackTrace();   //打印捕获的异常信息
        }
    }
    //下面的方法实现了两个数相除,并使用throws关键字声明抛出异常
    public static int divide(int x,int y)throws Exception{
        int result = x/y;
        return result;
    }
}

运行结果

结果: 2

使用throws关键字声明抛出异常后,即使不会出现被0除的现象,但如果不用try...catch编译程序依然会报错。

自定义异常

在程序开发时,有可能会用到一些自己描述的特殊的异常情况,Java中允许用户自定义异常,但自定义的异常类必须继承自Exception或其子类。

自定义了异常,就需要用到throw关键字,throw关键字用于在方法中声明抛出异常的实例对象。格式:

throw Exception异常对象

下面是一个当被除数是负数时抛出自定义异常的例子。

//下面自定义一个异常类继承自Exception

class DivideByMinusExpetion extends Exception{
    public DivideByMinusExpetion(){
        super();    //调用Exception无参的构造方法
    }
    public DivideByMinusExpetion(String message){
        super(message);     //调用Exception有参的构造方法
    }
}
public class Test {
    public static void main(String[] args) {
        //下面的代码定义了一个try...catch语句用于捕获异常
        try{
            int result = divide(4,-2);
            System.out.println("结果: "+result);
        }catch (DivideByMinusExpetion e){   //对异常进行处理
            System.out.println("异常信息:"+e.getMessage());  //打印捕获的异常信息
        }
    }
    //下面的方法实现了两个数相除,并使用throws关键字声明抛出异常
throws
        if(y<0){
            //使用throw关键字声明异常对象
throw
        }
        int result = x/y;
        return result;
    }
}

运行结果

异常信息:被除数是负数

下面是直接用Exception类,输出结果同上

public class Test {
    public static void main(String[] args) {
        try{
            System.out.println(divide(4,-2));
        }catch (Exception e){
            System.out.println("异常信息:"+e.getMessage());
        }
    }
    public static int divide(int x,int y)throws Exception{
        if(y<0){
            throw new Exception("被除数是负数");
        }
        return x/y;
    }
}

Java中的包是专门用来存类的,通常功能相同的类存放在相同的包中。在声明包时,用package语句。

package cn.itcast;
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

运行结果

Hello World!

import语句

使用import语句导包。

如果两个类不在同一目录下,引用某个类时需要用import导包,格式:

import 包名.类名;

例如:import cn.itcast.Student;然后在实例化对象的时候就可以直接用Student s = new Student(); ,但如果没有导包,实例化对象时需指明位置,就得写成cn.itcast.Student s = new cn.itcast.Student(); 。

访问控制

Java中,针对类、成员方法和属性提供了四种访问级别,访问控制级别由小到大:private,default,protected,public。

  • private(类访问级别):如果类成员被其修饰,该成员只能被该类的其他成员访问。
  • default(包访问级别):如果一个类或者类的成员不使用任何访问控制修饰符修饰,则称则称它为默认访问控制级别,这个类或者类的成员只能被本包中的其他类访问。
  • protected(子类访问级别):如果类成员被其修饰,该成员既能被同一包下的其他类访问,也能被不同包下的子类访问。
  • public(公共访问级别):如果一个类或者类成员被其修饰,不管访问类与被访问类在不在同一个包中,都能被访问。

注:private和protected不能修饰类,只能修饰类成员。