第4章 面向对象(下)

发布时间:2024年01月24日

4.1 继承

4.1.1 继承的概念

在现实生活中,继承一般指的是子女继承父辈的财产。在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。例如,猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物,同理,波斯猫和巴厘猫继承猫科,而沙皮狗和斑点狗继承自犬科。

在Java中,类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称作子类,现有类被称作父类。子类继承父类的属性和方法,使得子类对象(实例)具有父类的特征和行为。

在程序中,如果想声明一个类继承另一个类,需要使用extends关键字,语法格式如下所示。

接下来通过一个案例学习子类是如何继承父类。

// 定义Animal类
2 class Animal {
3    private String name;   // 定义name属性
4    private int age;           // 定义name属性
5    public String getName() {
6        return name;
7    }
8    public void setName(String name) {
9        this.name = name;
10    }
11 public int getAge() {
12        return age;
13    }
14 public void setAge(int  age) {
15        this.age = age;
16    }
17 }
18 // 定义Dog类继承Animal类
19 class Dog extends Animal {
20    //此处不写任何代码
21 }
22 // 定义测试类
23 public class Example01 {
24    public static void main(String[] args) {
25   Dog dog = new Dog();    	// 创建一个Dog类的实例对象
26       dog.setName("牧羊犬");   	// 此时访问的方法时父类中的,子类中并没有定义
27 dog.setAge(3);           		// 此时访问的方法时父类中的,子类中并没有定义
28 System.out.println("名称:"+dog.getName()+",年龄:"+dog.getAge());
29    }
30 }

上述代码中,第2~17行代码定义了一个Animal类,第19~21行代码定义了一个Dog类,Dog类中并没有定义任何操作,而是通过extends关键字继承了Animal类,成为了Animal类的子类。从运行结果可以看出,子类虽然没有定义任何属性和方法,但是却能调用父类的方法。这就说明,子类在继承父类的时候,会自动继承父类的成员。

除了继承父类的属性和方法,子类也可以定义自己的属性和方法。

1 // 定义Animal类
2 class Animal {
3    private String name; // 定义name属性
4    private int age;         // 定义name属性
5    public String getName() {
6        return name;
7    }
8    public void setName(String name) {
9        this.name = name;
10    }
11 public int getAge() {
12        return age;
13    }
14 public void setAge(int age) {
15        this.age = age;
16    }
17 }
18 // 定义Dog类继承Animal类
19class Dog extends Animal {
20    private String color; // 定义name属性
21    public String getColor() {
22    return color;
23 }
24 public void setColor(String color) {
25    this.color = color;
26 }
27 }
28 // 定义测试类
29 public class Example02 {
30    public static void main(String[] args) {
31       Dog dog = new Dog();    // 创建一个Dog类的实例对象
32      dog.setName("牧羊犬");   // 此时访问的方法时父类中的,子类中并没有定义
33      dog.setAge(3);           // 此时访问的方法时父类中的,子类中并没有定义
34      dog.setColor("黑色");
35      System.out.println("名称:"+dog.getName()+",年龄:"+dog.getAge()+",
36      颜色:"+dog.getColor());
37    }
38 }

在上述代码中,Dog类扩充了Animal类,增加了color属性及getColor()和setColor()方法。此时的Dog类已经存在了3个属性和6个方法。在main()方法中,第31行代码创建并实例化dog对象;第32~34行代码通过dog对象调用Animal类和Dog类的setter方法,设置名称、年龄和颜色;第35行代码通过dog对象调用Animal类和Dog类的getter方法获取名称、年龄和颜色。由运行结果可知,程序成功设置并获取了dog对象的名称、年龄和和颜色。

继承中需要注意的问题。 (1)在Java中,类只支持单继承,不允许多重继承。也就是说一个类只能有一个直接父类,例如下面这种情况是不合法的。 class A{} class B{} class C extends A,B{} // C类不可以同时继承A类和B类

(2)多个类可以继承一个父类,例如下面这种情况是允许的。 class A{} class B extends A{} class C extends A{} // 类B和类C都可以继承类A

(3)在Java中,多层继承也是可以的,即一个类的父类可以再继承另外的父类。例如,C类继承自B类,而B类又可以继承自A类,这时,C类也可称作A类的子类。例如下面这种情况是允许的。 class A{} class B extends A{} // 类B继承类A,类B是类A的子类 class C extends B{} // 类C继承类B,类C是类B的子类,同时也是类A的子类

(4)在Java中,子类和父类是一种相对概念,一个类可以是某个类的父类,也可以是另一个类的子类。例如,在第(3)种情况中,B类是A类的子类,同时又是C类的父类。 在继承中,子类不能直接访问父类中的私有成员,子类可以调用父类的非私有方法,但是不能调用父类的私有成员。

4.1.2 方法的重写

在继承关系中,子类会自动继承父类中定义的方法,但有时在子类中需要对继承的方法进行一些修改,即对父类的方法进行重写。在子类中重写的方法需要和父类被重写的方法具有相同的方法名、参数列表以及返回值类型,且在子类重写的方法不能拥有比父类方法更加严格的访问权限。

下面通过一个案例讲解方法的重写。

1 // 定义Animal类
2 class Animal {		
3    //定义动物叫的方法		
4	void shout() {			  
5		System.out.println("动物发出叫声");
6	}
7 }
8 // 定义Dog类继承动物类
9 class Dog extends Animal {    
10	//重写父类Animal中的shout()方法
11	void shout() {			 
12		System.out.println("汪汪汪……");  
13	}
14 }
15 // 定义测试类
16 public class Example03 {	
17	public static void main(String[] args) {
18		Dog dog = new Dog(); // 创建Dog类的实例对象
19		dog.shout();           // 调用dog重写的shout()方法
20	}
21 }

上述代码中,第2~7行代码定义了一个Animal类,并在Animal类中定义了一个shout()方法。第9~14行代码定义了Dog类继承Animal类,并在类中重写了父类Aniaml的shout()方法。第18~19行代码创建并实例化Dog类对象dog,并通过dog对象调用shout()方法。从图4-4可以看出,dog对象调用的是子类重写的shout()方法,而不是父类的shout()方法。

脚下留心:子类重写父类方法时的访问权限

子类重写父类方法时,不能使用比父类中被重写的方法更严格的访问权限。例如,父类中的方法是public权限,子类的方法就不能是private权限。如果子类在重写父类方法时定义的权限缩小,则在编译时将出现错误提示。

1 // 定义Animal类
2 class Animal {		
3    //定义动物叫的方法		
4   public void shout() {			  
5		System.out.println("动物发出叫声");
6	}
7 }
8 // 定义Dog类继承动物类
9 class Dog extends Animal {    
10	//重写父类Animal中的shout()方法
11	private void shout() {			 
12		System.out.println("汪汪汪……");  
13	}
14 }
15 // 定义测试类
16 public class Example04 {	
17	public static void main(String[] args) {
18		Dog dog = new Dog(); // 创建Dog类的实例对象
19		dog.shout();         // 调用dog重写的shout()方法
20	}
21 }

在上述代码中,第4行代码在Animal类中定义了一个shout()方法并将访问权限定义为public,第9~14行代码定义了一个Dog类并继承Animal类,第11行代码在声明shout()方法时,将shou()方法的访问权限定义为private。由运行结果可知,编译文件会报错,这是因为子类重写父类方法时,不能使用比父类中被重写的方法更严格的访问权限。

4.1.3 super关键字

当子类重写父类的方法后,子类对象将无法访问父类被重写的方法,为了解决这个问题,Java提供了super关键字,super关键字可以在子类中调用父类的普通属性、方法以及构造方法。

接下来详细讲解super关键字的具体用法。 (1)使用super关键字访问父类的成员变量和成员方法,具体格式如下: super.成员变量 super.成员方法(参数1,参数2…)

接下来通过一个案例学习super关键字访问父类的成员变量和成员方法。

1 // 定义Animal类
2 class Animal {		
3    String name = "牧羊犬";
4    //定义动物叫的方法		
5	void shout() {			  
6		System.out.println("动物发出叫声");
7	}
8 }
9 // 定义Dog类继承动物类
10 class Dog extends Animal {    
11     //重写父类Animal中的shout()方法,扩大了访问权限
12	public void shout() {			 
13         super.shout();      //调用父类中的shout()方法
14		System.out.println("汪汪汪……");  
15	}
16     public void printName(){
17         System.out.println("名字:"+super.name);      //调用父类中的name属性
18     }
19 }
20 // 定义测试类
21 public class Example05 {	
22	public static void main(String[] args) {
23		Dog dog = new Dog();  // 创建Dog类的实例对象
24		dog.shout();            // 调用dog重写的shout()方法
25         dog.printName();        // 调用Dog类中的printName()方法
26	}
27 }

上述代码中,第2~8行代码定义了一个Animal类,并在Animal类中定义了name属性和shout()方法。第10~19行代码定义了Dog类并继承了Animal类。在Dog类的shout()方法中使用“super.shout()”调用了父类被重写的shout()方法。在printName()方法中使用“super.name”访问父类的成员变量name。从运行结果中可以看出,子类通过super关键字可以成功地访问父类成员变量和成员方法。

(2)使用super关键字访问父类中指定的构造方法,具体格式如下:super(参数1,参数2…) 接下来就通过一个案例学习如何使用super关键字调用父类的构造方法。

1 // 定义Animal类
2 class Animal {
3    private String name;
4    private int age;
5    public Animal(String name, int age) {
6        this.name = name;
7        this.age = age;
8    }
9 	 public String getName() {
10        return name;
11    }
12    public void setName(String name) {
13        this.name = name;
14    }
15    public int getAge() {
16        return age;
17    }
18    public void setAge(int age) {
19        this.age = age;
20    }
21 public String info() {
22        return "名称:"+this.getName()+",年龄: "+this.getAge();
23   }
24 }
25 // 定义Dog类继承动物类
26 class Dog extends Animal {
27    private String color;
28    public Dog(String name, int age, String color) {
29        super(name, age);
30        this.setColor(color);
31    }
32    public String getColor() {
33        return color;
34    }
35 public void setColor(String color) {
36        this.color = color;
37    }
38    //重写父类的info()方法
39    public String info() {
40        return super.info()+",颜色:"+this.getColor();    //扩充父类中的方法
41    }
42 }
43 // 定义测试类
44 public class Example06 {
45    public static void main(String[] args) {
46        Dog dog = new Dog("牧羊犬",3,"黑色"); // 创建Dog类的实例对象
47        System.out.println(dog.info());
48    }
49 }

上述代码中,第29行代码使用super()调用了父类中有两个参数的构造方法;39~41行代码是在子类Dog中重写了父类Animal中的info()方法;第46~47行代码是实例化了一个Dog对象并调用了info()方法。由运行结果可知,程序输出的内容是在子类中定义的内容。这说明,如果在子类中重写了父类的info()方法,使用子类的实例化对象调用info()方法时,会优先调用子类中的info()方法。

通过super()调用父类构造方法的代码必须位于子类构造方法的第一行,并且只能出现一次。

super与this关键字的作用非常相似,都可以调用构造方法、普通方法和属性,但是两者之间还是有区别的,super与this的区别如下表。

区别点thissuper
属性访问访问本类中的属性,如果本类中没有该属性,则从父类中查找。访问父类中的属性
方法访问本类中的方法,如果本类中没有该方法,则从父类中继续查找直接访问父类中的方法
调用构造调用本类构造,必须放在构造方法的首行调用父类构造,必须放在子类构造方法的首行

this和super两者不可以同时出现,因为this和super在调用构造方法时都要求必须放在构造方法的首行。

4.2 final关键字

final的英文意思是“最终”。在Java中,可以使用final关键字声明类、属性、方法,在声明时需要注意以下几点: (1)使用final修饰的类不能有子类。 (2)使用final修饰的方法不能被子类重写。 (3)使用final修饰的变量(成员变量和局部变量)是常量,常量不可修改。

4.2.1 final关键字修饰类

Java中的类被final关键字修饰后,该类将不可以被继承,即不能够派生子类。

1 // 使用final关键字修饰Animal类
2 final class Animal {
3	// 方法体为空
4 }
5 // Dog类继承Animal类
6 class Dog extends Animal {
7	// 方法体为空
8 }
9 // 定义测试类
10 public class Example07 {
11	public static void main(String[] args) {
12		Dog dog = new Dog(); 	// 创建Dog类的实例对象
13	}
14 }

4.2.2 final关键字修饰方法

当一个类的方法被final关键字修饰后,这个类的子类将不能重写该方法。接下来通过一个案例验证。

1 // 定义Animal类
2 class Animal {
3     // 使用final关键字修饰shout()方法
4	public final void shout() {
5	}
6 }
7 // 定义Dog类继承Animal类
8 class Dog extends Animal {
9     // 重写Animal类的shout()方法
10	public void shout() {
11	}
12 }
13 // 定义测试类
14 public class Example08 {
15	public static void main(String[] args) {
16		Dog dog=new Dog(); // 创建Dog类的实例对象
17	}
18 }

在上述代码中,第10行代码在Dog类中重写了父类Animal中的shout()方法,编译报错。这是因为Animal类的shout()方法被final修饰,而被final关键字修饰的方法为最终方法,子类不能对该方法进行重写。因此,当在父类中定义某个方法时,如果不希望被子类重写,就可以使用final关键字修饰该方法。

4.2.3 final关键字修饰变量

Java中被final修饰的变量是为常量,常量只能在声明时被赋值一次,在后面的程序中,其值不能被改变。如果再次对该常量赋值,则程序会在编译时报错。接下来通过一个案例进行验证。

1 public class Example09 {
2	public static void main(String[] args) {
3		final int AGE = 18;    // 第一次可以赋值
4		AGE = 20;               // 再次赋值会报错
5	}
6 }

在使用final声明变量时,要求全部的字母大写。如果一个程序中的变量使用public static final声明,则此变量将成为全局变量,如下面代码所示。

public static final String NAME = "哈士奇";

4.3 抽象类和接口

4.3.1 抽象类

当定义一个类时,常常需要定义一些成员方法描述类的行为特征,但有时这些方法的实现方式是无法确定的。例如,前面在定义Animal类时,shout()方法用于描述动物的叫声,但是针对不同的动物,叫声也是不同的,因此在shout()方法中无法准确描述动物的叫声。

针对上面描述的情况,Java提供了抽象方法来满足这种需求。抽象方法是使用abstract关键字修饰的成员方法,抽象方法在定义时不需要实现方法体。抽象方法的定义格式如下:

abstract void 方法名称(参数); 

当一个类包含了抽象方法,该类必须是抽象类。抽象类和抽象方法一样,必须使用abstract关键字进行修饰。

抽象类的定义格式如下:

abstract class 抽象类名称{
    访问权限 返回值类型 方法名称(参数){
           return [返回值];
    }
    访问权限 abstract 返回值类型 抽象方法名称(参数);     //抽象方法,无方法体
}

抽象类的定义规则如下: (1)包含一个以上抽象方法的类必须是抽象类。 (2)抽象类和抽象方法都要使用abstract关键字声明。 (3)抽象方法只需声明而不需要实现。 (4)如果一个类继承了抽象类,那么该子类必须实现抽象类中的全部抽象方法。

接下来通过一个案例学习抽象类的使用。

1 // 定义抽象类Animal
2 abstract class Animal { 
3     // 定义抽象方法shout()
4	abstract void shout(); 
5 }
6 // 定义Dog类继承抽象类Animal
7 class Dog extends Animal {
8     // 实现抽象方法shout()
9	void shout() {
10		System.out.println("汪汪...");
11	}
12 }
13 // 定义测试类
14 public class Example10 {
15	public static void main(String[] args) {
16		Dog dog = new Dog(); 	// 创建Dog类的实例对象
17		dog.shout(); 		// 调用dog对象的shout()方法
18	}
19 }

上述代码中,第2~5行代码是声明了一个抽象类Animal,并在Animal类中声明了一个抽象方法shout();第9~11行代码在子类Dog中实现父类Animal的抽象方法shout();第17行代码通过子类的实例化对象调用shout()方法。

使用abstract关键字修饰的抽象方法不能使用private修饰,因为抽象方法必须被子类实现,如果使用了private声明,则子类无法实现该方法。

4.3.2 接口

如果一个抽象类的所有方法都是抽象的,则可以将这个类定义接口。接口是Java中最重要的概念之一,接口是一种特殊的类,由全局常量和公共的抽象方法组成,不能包含普通方法。 在JDK8之前,接口是由全局常量和抽象方法组成的,且接口中的抽象方法不允许有方法体。JDK 8对接口进行了重新定义,接口中除了抽象方法外,还可以有默认方法和静态方法(也叫类方法),默认方法使用default修饰,静态方法使用static修饰,且这两种方法都允许有方法体。

接口使用interface关键字声明,语法格式如下:

public interface 接口名 extends 接口1,接口2... {
	public static final 数据类型 常量名 = 常量值; 
	public default 返回值类型 抽象方法名(参数列表);
     public abstract 返回值类型 方法名(参数列表){
        //默认方法的方法体
     }
     public abstract 返回值类型方法名(参数列表){
        //类方法的方法体
     }
}

在上述语法中,“extends 接口1,接口2...”表示一个接口可以有多个父接口,父接口之间使用逗号分隔。Java使用接口的目的是为了克服单继承的限制,因为一个类只能有一个父类,而一个接口可以同时继承多个父接口。接口中的变量默认使用“public static final”进行修饰,即全局常量。接口中定义的方法默认使用“public abstract”进行修饰,即抽象方法。如果接口声明为public,则接口中的变量和方法全部为public。

在很多的Java程序中,经常看到编写接口中的方法时省略了public ,有很多读者认为它的访问权限是default,这实际上是错误的。不管写不写访问权限,接口中的方法访问权限永远是public。与此类似,在接口中定义常量时,可以省略前面的“public static final”,此时,接口会默认为常量添加“public static final”。

与抽象类一样,接口的使用必须通过子类,子类通过implements关键字实现接口,并且子类必须实现接口中的所有抽象方法。需要注意的是,一个类可以同时实现多个接口,多个接口之间需要使用英文逗号(,)分隔。 定义接口的实现类,语法格式如下:

修饰符 class 类名 implements 接口1,接口2,...{
    ...
}

下面通过一个案例学习接口的使用。

1 // 定义抽象类Animal
2 interface Animal {
3    int ID = 1;                     		// 定义全局常量
4    String NAME = "牧羊犬";
5    void shout();                		// 定义抽象方法shout()
6    static int getID(){
7        return Animal.ID;
8    }
9    public void info();                 	// 定义静态方法info()
10 }
11 interface Action {
12    public void eat();                 	// 定义抽象方法eat()
13 }
14 // 定义Dog类实现Animal接口和Action接口
15 class Dog implements Animal,Action{
16   // 重写Action接口中的抽象方法eat()
17    public void eat() {
18        System.out.println("喜欢吃骨头");
19    }
20    // 重写Animal接口中的抽象方法shout()
21    public void shout() {
22        System.out.println("汪汪...");
23    }
24 // 重写Animal接口中的抽象方法info()
25    public void info() {
26        System.out.println("名称:"+NAME);
27    }
28 }
29 // 定义测试类
30 class Example11 {
31  public static void main(String[] args) {
32        System.out.println("编号"+Animal.getID());
33        Dog dog = new Dog();         // 创建Dog类的实例对象
34        dog.info();
35        dog.shout();                // 调用Dog类中重写的shout()方法
36        dog.eat();                    // 调用Dog类中重写的eat()方法
37    }}

上述代码中,第2~10行代码定义了一个Animal接口,在Animal接口中定义了全局常量id和NAME、抽象方法shout()、info()和静态方法getID()。第11~13行代码定义了一个Action接口,在Action接口中定义了一个抽象方法eat()。第15~28行代码定义了一个Dog类,Dog类通过implements实现了Animal接口和Action接口,并实现了这两个接口中的方法。第32行代码使用Animal接口名直接访问了Animal接口中的静态方法getID()。第33~36行代码创建并实例化了Dog类对象dog,通过dog对象访问了Animal接口和Action接口中的常量、抽象方法 。

从运行结果可以看出,Dog类的实例化对象可以访问接口中的常量、实现的接口方法以及本类内部的方法,而接口中的静态方法则可以直接使用接口名调用。需要注意的是,接口的实现类,必须实现接口中的所有方法,否则程序编译报错。

如果在开发中一个子类既要实现接口又要继承抽象类,则可以按照以下格式定义子类。

修饰符class 类名 extends 父类名implements 接口1,接口2,... {
    ...
}

下面通过一个案例演示子类既实现接口又继承抽象类的情况。

1 // 定义抽象类Animal
2 interface Animal {
3    public String NAME = "牧羊犬";
4    public void shout();                // 定义抽象方法shout()
5    public void info();                 // 定义抽象方法info()
6 }
7 abstract class Action {
8    public abstract void eat();       // 定义抽象方法eat()
9 }
10 // 定义Dog类实现Animal接口和Action接口
11 class Dog extends Animal implements Action{
12    // 重写Action接口中的抽象方法eat()
13    public void eat() {
14        System.out.println("喜欢吃骨头");
15    }
16    // 重写Animal接口中的抽象方法shout()
17    public void shout() {
18        System.out.println("汪汪...");
19    }
20    // 重写Animal接口中的抽象方法info()
21    public void info() {
22      System.out.println("名称:"+NAME);
23    }
24 }
25 // 定义测试类
26 class Example12 {
27    public static void main(String[] args) {
28        Dog dog = new Dog();    // 创建Dog类的实例对象
29        dog.info();               	// 调用Dog类中重写的info()方法
30        dog.shout();  	// 调用Dog类中重写的shout()方法
31        dog.eat();   		// 调用Dog类中重写的eat()方法
32    }
33 }

上述代码中,Dog类通过extends实现了Animal接口,同时通过implements实现了抽象类Action。因为Animal接口和抽象类Action本身都有抽象方法,所以子类中必须实现。

在Java中,接口是不允许继承抽象类的,但是允许一个接口继承多个接口。接下来通过一个案例讲解接口的继承

1 // 定义抽象类Animal
2 interface Animal {
3    public String NAME = "牧羊犬";
4    public void info();     	// 定义抽象方法info()
5 }
6 interface Color {
7    public void black();       	// 定义抽象方法shout()
8 }
9 interface Action extends Animal,Color{
10    public void shout();       	// 定义抽象方法black()
11 }
12 // 定义Dog类实现Action接口
13 class Dog implements Action{
14    // 重写Animal接口中的抽象方法info()
15    public void info() {
16        System.out.println("名称:"+NAME);
17    }
18    // 重写Color接口中的抽象方法black()
19    public void black() {
20        System.out.println("黑色");
21    }
22    // 重写Action接口中的抽象方法shout()
23    public void shout() {
24        System.out.println("汪汪...");
25    }
26 }
27 // 定义测试类
28 class Example13 {
29    public static void main(String[] args) {
30        Dog dog = new Dog(); // 创建Dog类的实例对象
31        dog.info();     // 调用Dog类中重写的info()方法
32        dog.shout();    // 调用Dog类中重写的shout()方法
33        dog.black();    // 调用Dog类中重写的eat()方法
34    }
35 }

在上述代码中,第9~11行代码定义了接口Action并继承接口Animal和Color,这样接口Action中就同时拥有Animal和Color接口中info()和black()方法以及本类中shout()方法。在第13~26行代码定义了一个Dog类并实现了Action接口,这样Dog类就必须同时实现这3个抽象方法。

【案例4-1】打印不同的图形

本案例要求编写一个程序,可以根据用户要求在控制台打印出不同的图形。例如,用户自定义半径的圆形和用户自定义边长的正方形。

(1)创建父类MyPrint类,包含show()方法,用于输出图形的形状。

(2)创建子类MyPrintSquare类,重写show ()方法,用“*”打印出边长为5的正方形。

(3)创建子类MyPrintCircle类,重写show ()方法, 用“*”打印出半径为5的圆。

(4)创建测试类,设计一个myshow(MyPrint a)方法,实现输出的功能:如果为MyPrintSquare, 输出边长为5的正方形,如果为MyPrintCircle对象,输出半径为5的圆;主函数中创建MyPrintSquare、MyPrintCircle的对象,分别调用myshow,检查输出结果。

 1 abstract class MyPoint {
 2     public abstract void show();
 3 }
 4 //打印正方形
 5 class MyPrintSquare extends MyPoint {
 6     @Override
 7     public void show() {
 8         for(int i=0;i<5;++i){
 9             for(int j=0;j<5;++j){
 10                 if(j==0 || j==4)
 11                     System.out.print('*');
 12                 else if(i==0 || i==4)
 13                     System.out.print('*');
 14                 else System.out.print(' ');
 15             }
 16             System.out.println();
 17         }   
 18     }
 19 }
 20 //打印圆形
 21 class MyPrintCircle extends MyPoint{
 22     @Override
 23     public void show() {
 24         for (int y = 0; y <= 2 * 5; y += 2) {
 25             int x = (int)Math.round(5 - Math.sqrt(2 * 5 * y - y * y));
 26             int len = 2 * (5 - x);
 27             for (int i = 0; i <= x; i++) {
 28                 System.out.print(' ');
 29             }   
 30             System.out.print('*');
 31             for (int j = 0; j <= len; j++) {
 32                 System.out.print(' ');
 33             }
 34             System.out.println('*');
 35     }
 36    }
 37 }
 38 public class MyPointTest {
 39     public static void myShow(MyPoint a){
 40         a.show();
 41     }
 42     public static void main(String[] args){
 43         MyPoint mp1 = new MyPrintSquare();
 44         MyPoint mp2 = new MyPrintCircle();
 45         myShow(mp1);
 46         myShow(mp2);
 47     }
 48 }

【案例4-2】饲养员喂养动物

饲养员在给动物喂食时,给不同的动物喂不同的食物,而且在每次喂食时,动物都会发出欢快的叫声。例如,给小狗喂骨头,小狗会汪汪叫;给小猫喂食,小猫会喵喵叫。 本案例要求编写一个程序模拟饲养员喂食动物的过程,案例要求如下: (1)饲养员给小狗喂骨头,小狗汪汪叫。 (2)饲养员给小猫喂小鱼,小猫喵喵叫。

在这个动物园里,涉及的对象有饲养员,各种不同动物以及各种不同的食物。这样很容易抽象出3个类Feeder、Animal和Food。假设只考虑猫类和狗类动物,则由Animal类派生出Cat类、Dog类、同样由Food类可以进一步派生出其子类Bone、Fish。因为他们之间存在这明显的is-a关系。

同样的,鱼是一种食物,但实际上,鱼也是一种动物,Cat类和Dog类继承了Animal的很多属性和方法,如果将Animal定义为接口,Animal中是不能定义成员变量和成员方法的,Food类中虽然也有变量但是数量比Animal少,所以我们考虑将Food定义为接口,此时可以说“鱼是一种动物,同时也是一种食物”。

 1 public abstract class Animal {
 2     private String name;
 3     public Animal(String name) {
 4         this.name = name;
 5     }
 6     public abstract void shout();
 7     public abstract void eat(Food food);
 8     public String getName() {
 9         return name;
 10     }
 11     public void setName(String name) {
 12         this.name = name;
 13     }
 14 }
 1 public class Dog extends Animal {
 2     public Dog(String name) {
 3         super(name);
 4     }
 5         @Override
 6     public void shout() {
 7         System.out.print ("汪汪汪~~~");
 8     }
 9     @Override
 10 public void eat(Food food) {
 11     System.out.println(getName() + "正在啃着香喷喷的" + food.getName());
 12  }
 13 }
 1 public class Cat extends Animal{
 2     public Cat(String name) {
 3         super(name);
 4     }
 5     public void shout() {
 6         System.out.print("喵喵喵~~~");
 7     }
 8     public void eat(Food food) {
 9         System.out.println(getName() + "正在吃着香喷喷的"+food.getName());
 10 }
 11 }
 1 public interface Food {
 2     public abstract String getName();
 3 }
 1 public class Bone implements Food{
 2     @Override
 3     public String getName() {
 4         return "骨头";
 5     }
 6 }
 1 public class Fish extends Animal implements Food{
 2     public Fish(String name) {
 3         super(name);
 4     }
 5     @Override
 6     public void shout() {
 7     }
 8     @Override
 9     public void eat(Food food) {
 10 }
 11 }
 1 public class Feeder {
 2     private String name;
 3     public Feeder(String name) {
 4         this.name = name;
 5     }
 6     public void speak() {
 7         System.out.println("欢迎来到动物园!");
 8         System.out.println("我是饲养员 "+getName());
 9     }
 10     public void feed(Animal a, Food food) {
 11         a.eat(food);
 12     }
 13     public String getName() {
 14         return name;
 15     }
 16     public void setName(String name) {
 17         this.name = name;
 18     }
 19 }
 1 public class DongWuTest {
 2     public static void main(String[] args) {
 3         Feeder feeder = new Feeder("小华");
 4         feeder.speak();
 5         Dog dog = new Dog("小狗");
 6         dog.shout();
 7         Food food = new Bone();
 8         feeder.feed(dog, food);
 9         Cat cat = new Cat("小猫");
 10    cat.shout();
 11    food = new Fish("黄花鱼");
 12    feeder.feed(cat, food);
 13     }
 14 }

【案例4-3】多彩的声音

狗是人类最忠诚的伙伴,当狗遇到不同的人会有不同的反应,下面要求编写一个案例模拟狗遇到不同的人时的反应。首先需要有一只狗,不同的人去喂狗,狗会有不同的反应,具体如下: (1)主人喂狗时,狗会很开心的欢跳. (2)主人的家人喂狗时,狗会很平常的吃东西。 (3)陌生人喂狗时,狗会很有警惕,并发出叫声。 本案例要求使用抽象类实现。

(1)通过任务的描述可知,此程序包含了一个发声接口Soundable,Soundable接口的有三个声音设备实现类实现了Soundable接口,分别是收音机类Radio,随身听类Walkman和手机类Mobilephon。

(2)Radio、Walkman和MobilePhon这三个声音设备实现类需要分别实现Soundable接口接口的相关功能。

(3)然后,还需设计一个应用程序类来使用这些实现Soundable接口的声音设备。

(4)最后编写测试类,在main()方法中,编写程序,使用户可以自主选择要使用的设备;创建使用设备的对象,根据用户选择的声音设备,调用相关方法,模拟使用声音设备对应的功能。

 1 public interface Soundable {
 2     // 发出声音
 3     public void playSound() ;
 4     // 降低声音
 5     public void decreaseVolume();
 6    // 停止声音
 7     public void stopSound();
 8 }
 1 import java.util.Scanner;
 2 public class SampleDisplay {
 3 	 public void display(Soundable soundable) {
 4 	        soundable.playSound();
 5 	        System.out.println("您是否要降低音量?");
 6 	        System.out.println("1-是  2-否");
 7 	        Scanner in = new Scanner(System.in);
 8 	        if(in.nextLine().equals("1")) {
 9 	        	soundable.decreaseVolume();
 10 	        }
 11 	        System.out.println("您是否要关机?");
 12 	        System.out.println("1-是  2-否");
 13 	        Scanner ins = new Scanner(System.in);
 14 	        if(in.nextLine().equals("1")) {
 15 		        soundable.stopSound();
 16 	        }
 17 	    }
 18 }
 1 public class Radio implements Soundable{
 2     @Override
 3     public void playSound() {
 4         System.out.println("收音机播放广播:第八套小学生广播体操");
 5     }
 6     @Override
 7     public void decreaseVolume() {
 8         System.out.println("已降低收音机音量。");
 9     }
 10     @Override
 11     public void stopSound() {
 12         System.out.println("已关闭收音机。");
 13     }
 14 }
 1 public class Walkman implements Soundable{
 2     @Override
 3     public void playSound() {
 4         System.out.println("正在播放歌曲:七里香");
 5     }
 6     @Override
 7     public void decreaseVolume() {
 8         System.out.println("已降低随身听音量");
 9     }
 10     @Override
 11     public void stopSound() {
 12         System.out.println("已关闭随身听");
 13     }
 14 }
 1 public class MobilePhone implements Soundable{
 2     @Override
 3     public void playSound() {
 4         System.out.println("手机发出来电铃声:叮当 、 叮当");
 5     }
 6     @Override
 7     public void decreaseVolume() {
 8         System.out.println("已降低手机音量");
 9     }
 10 @Override
 11 public void stopSound() {
 12     System.out.println("已关闭手机");
 13   }  
 14 }
 1 import java.util.Scanner;
 2 public class TestDemo {
 3     public static void main(String[] args) {
 4         Scanner in = new Scanner(System.in);
 5         System.out.println("你想听什么?请输入:");
 6         System.out.println("0-收音机  1-随身听  2-手机");
 7         int choice;
 8         choice = in.nextInt();
 9         SampleDisplay sampledisplay = new SampleDisplay();
 10         if (choice == 0)
 11             sampledisplay.display(new Radio());
 12         else if(choice == 1)
 13             sampledisplay.display(new Walkman());
 14         else if(choice == 2)
 15             sampledisplay.display(new MobilePhone());
 16         Else  
 17             System.out.println("您的输入有误,请重新输入!");
 18         in.close();
 19     }
 20 }

【案例4-4】学生和老师

在班级中上课时,老师在讲台上讲课,偶有提问,会点名学生回答问题。虽然老师和学生都在讲话,但讲话的具体内容却不相同。本案例要求使用抽象类的知识编写一个程序实现老师上课的情景。

(1)定义一个抽象类Person,在Person类中声明两个属性:name和age,并设置其对应的getter方法,用于获取人的姓名和年龄;在Person类中声明一个有参构造方法,用于对Person类中的属性进行初始化;在Person类中声明一个say()方法和一个getContent()方法,在say()方法中调用getContent()方法。

(2)定义类 Student,并继承Person类。在Student类中声明一个属性score;声明Student类的构造方法并重写Person类的getContent()方法。

(3)定义类 Worker,并继承Person类。在Worker类中声明一个属性salary;声明Worker类的构造方法并重写Person类的getContent()方法。

(6)在main() 方法中使用父类引用指向子类对象的方式(向上转型),分别使用Person类和Worker类实例化两个Person对象per1和per2,并分别使用per1和per2调用say()方法。

1abstract class Person {
2    private String name;
3    private int age;
4    public Person(String name, int age) {
5        this.name = name;
6        this.age = age;
7    }
8    public String getName() {
9        return name;
10    }
11    public int getAge() {
12        return age;
13    }
14    public void say(){
15        System.out.println(this.getContent());
16    }
17    public abstract String getContent();
18}
19class Student extends Person{
20    private float score;
21    public Student(String name, int age,float score) {
22        super(name, age);
23        this.score = score;
24    }
25    @Override
26    public String getContent() {
27        return "学生信息--》姓名:"+super.getName()+";年龄:
28						"+super.getAge()+";成绩:"+this.score;
29    }
30}
31class Worker extends Person{
32    private float salary;
33    public Worker(String name, int age,float salary) {
34        super(name, age);
35        this.salary = salary;
36    }
37    @Override
38    public String getContent() {
39        return "工人信息--》姓名:"+super.getName()+";年龄:
40						"+super.getAge()+";工资:"+this.salary;
41    }
42}
43public class AbstractDemo{
44    public static void main(String[] args){
45        Person per1 = null;
46        Person per2 = null;
47        per1 = new Student("张三",20,99.0f);
48        per2 = new Worker("李四",30,3000.0f);
49        per1.say();
50        per2.say();
51    }
52}

【案例4-5】图形的面积与周长计算程序

(1)定义父类Shape作为抽象类,并在类中定义抽象方法求周长和面积。

(2)定义Shape子类圆形(circle),具有半径属性和常量PI,同时必须实现父类中的抽象方法。

(3)定义Shape子类长方形(rectangle),具有长和宽的属性,同时必须实现父类的抽象方法。

(4)创建图形面积周长计算器(ShapeCalculate),具有计算不同图形面积和周长的方法。

(5)创建测试类TestShape类,在其main()方法中对ShapeCalculate计算面积和周长方法进行测试。

 1 public abstract class Shape {
 2     // 抽象方法: 获取面积
 3     public abstract double getArea();
 4     // 抽象方法:获取周长
 5     public abstract double getPerimeter();
 6 }
 1 public class Circle extends Shape {
 2     private double radius = 0;    // 圆的半径
 3     private final static double PI = 3.14;    // 常量,圆周率
 4         // 有参构造,初始化圆半径
 5     public Circle(double radius) {   
 6         this.radius = radius;
 7     }
 8     // 求圆面积
 9     public double getArea() {
 10         return PI*radius*radius;
 11     }
 12     // 求圆周长
 13     public double getPerimeter() {
 14         return 2*radius*PI;
 15     }
 16 }
 1 public class Rectangle extends Shape {
 2     private double length = 0;    // 长方形的长
 3     private double width = 0;     // 长方形的宽
 4     // 有参构造,初始化长方形的长和宽
 5     public Rectangle(double length, double width) {
 6         super();
 7         this.length = length;
 8         this.width = width;
 9     }
 10     public double getArea() {
 11         return this.length*this.width;
 12     }
 13     public double getPerimeter() {
 14         return 2*(this.length+this.width);
 15     }
 16 }
 1 public class ShapeCaculate {
 2     // 可以计算任意shape子类的面积
 3     public void calArea (Shape shape) {
 4         System.out.println(shape.getArea());
 5     }
 6     // 可以计算任意shape子类的周长
 7     public void calPerimeter(Shape shape) {
 8         System.out.println(shape.getPerimeter());
 9     }
}
 1 public class TestShape {
 2     public static void main(String[] args) {
 3         // 创建图形计算器
 4         ShapeCaculate sc = new ShapeCaculate();
 5         // 创建长方形和圆形对象
 6         Shape rectangle = new Rectangle(3, 4);         // <-------多态
 7         Circle circle = new Circle(3);
 8         // 求长方形和圆形的面积
 9         System.out.println("长方形的面积:");
 10         sc.calArea(rectangle);
 11         System.out.println("圆形的面积:");
 12         sc.calArea(circle);
 13         // 求长方形和圆形的周长
 14         System.out.println("长方形的周长:");
 15         sc.calPerimeter(rectangle);
 16         System.out.println("圆形的周长:");
 17         sc.calPerimeter(circle);
 18     }
 19 }

【案例4-6】研究生薪资管理

在学校中,学生每个月需要交相应的生活费,老师每个月有相应的工资,而在职研究生既是老师又是学生,所以在职研究生既需要交学费又会有工资。下面要求编写一个程序来统计在职研究生的收入与学费,如果收入减去学费不足2000 元,则输出“provide a loan”(需要贷款)信息。 本案例要求使用接口实现该程序。

(1)定义两个接口,在StudentManageInterface接口中声明两个方法:getFree()和setFree(),用于设置和获取学生的学费;在TeacherManageInterface类中声明两个方法:gePay()和setPaye(),用于设置和获取教师的工资。

(2)定义主类 Graduate,分别实现StudentManageInterface和TeacherManageInterface接口。

(3)定义Graduate类的成员变量,和构造方法。

(4)给出四个接口方法的实现。

(5)给出一个计算是否需要贷款的方法,在里面统计年收入和学费,并输出是否需要贷款的信息。

(6)在main() 方法中创建一个姓名为“zhangsan”的研究生,调用计算是否需要贷款的方法。

 1 class Graduate implements StudentManageInterface, 
 2 TeacherManageInterface {
 3     private String name, sex;
 4     private int age;
 5     private double fee, pay;
 6     Graduate() {
 7     }
 8     Graduate(String name, String sex, int age, double fee, double pay) {
 9         this.name = name;
 10         this.sex = sex;
 11         this.age = age;
 12    this.fee = fee;
 13    this.pay = pay;
 14 }
 15     public String getName() {
 16         return name;
 17     }
 18     public String getSex() {
 19         return sex;
 20     }
 21     public int getAge() {
 22         return age;
 23     }
 24     public void setFree(double fee) {
 25         this.fee = fee;
 26     }
 27     public double getFree() {
 28         return fee;
 29     }
 30     public void setPay(double pay) {
 31         this.pay = pay;
 32     }
 33     public double getPay() {//对接口的抽象方法必须重写
 34         return pay;
 35     }
 36 }
 1 interface StudentManageInterface{              //interface 接口名
 2     public abstract void setFree(double fee);  //抽象方法
 3     public abstract double getFree();
 4 }
 37 interface TeacherManageInterface{
 38     public abstract double getPay();
 39     public abstract void setPay(double pay);
 40 }
 1 public class TestDemo {
 2     public static void main(String[] args) {
 3         Graduate gr = new Graduate("zhangsan", "男", 5, 8000, 3000);
 4         judgeLoan(gr);
 5     }
 6     public static void judgeLoan(Graduate gr) {//对象作形参
 7         if (gr.getPay() * 12 - gr.getFree() * 2 < 2000) {
 8             System.out.println("provide a loan");
 9         } else
 10             System.out.println("don't need a loan");
 11     }
 12 }

4.4 多态

4.4.1 多态概述

多态性是面向对象思想中的一个非常重要的概念,在Java中,多态是指不同对象在调用同一个方法时表现出的多种不同行为。例如,要实现一个动物叫的方法,由于每种动物的叫声是不同的,因此可以在方法中接收一个动物类型的参数,当传入猫类对象时就发出猫类的叫声,传入犬类对象时就发出犬类的叫声。在同一个方法中,这种由于参数类型不同而导致执行效果不同的现象就是多态。

Java中多态主要有以下两种形式: (1)方法的重载。 (2)对象的多态性(方法重写)。

接下来通过一个案例演示Java程序中的多态。

1 // 定义抽象类Anmal
2 abstract class Animal {  
3  abstract  void shout();     	// 定义抽象shout()方法
4 }
5 // 定义Cat类继承Animal抽象类
6 class Cat extends Animal {
7 // 实现shout()方法
8	public void shout() {
9		System.out.println("喵喵……");
10	}
11 }
1 // 定义Dog类继承Animal抽象类
2 class Dog extends Animal {
3     // 实现shout()方法
4	public void shout() {
5		System.out.println("汪汪……");
6	}
7 }
8 // 定义测试类
9 public class Example14 {
10	public static void main(String[] args) {
11		Cat an1 = new Cat(); // 创建Cat对象,使用Animal类型的变量an1引用
12		Dog an2 = new Dog(); // 创建Dog对象,使用Animal类型的变量an2引用
13		an1.shout();        
14		an2.shout();        
15	}
16 }

上述代码中,第2~4行代码定义了一个抽象类Animal,在抽象类Animal中定义了抽象方法shout()。第6~18行代码定义了两个继承Animal的类Cat和Dog,并在Cat类和Dog类中重写了Animal类中的shout()方法。第22~25行代码是创建了Cat类对象和Dog类对象,并将Cat类对象和Dog类对象向上转型成了Animal类型的对象,然后通过Animal类型的对象an1和an2调用shout()方法。从运行结果可以看出,对象an1和an2调用的分别是Cat类和Dog类中的shout()方法。

4.4.2 对象类型的转换

对象类型转换主要分为以下两种情况: (1)向上转型:子类对象→父类对象。 (2)向下转型:父类对象→子类对象。 对于向上转型,程序会自动完成,而向下转型时,必须指明要转型的子类类型。

对象类型的转换格式如下所示。 对象向上转型:父类类型 父类对象 = 子类实例; 对象向下转型: 父类类型 父类对象 = 子类实例; 子类类型 子类对象 = (子类)父类对象;

下面通过一个案例介绍如何进行对象的向上转型操作。

1 // 定义类Anmal
2 class Animal {
3   public void shout(){
4    System.out.println("喵喵……"); 
5  }     
6 }
7 // Dog类
8 class Dog extends Animal {
9   // 重写shout()方法
10   public void shout() {
11       System.out.println("汪汪……"); 
12  }
13   public void eat() {
14       System.out.println("吃骨头……"); 
15  }
16 }
17 // 定义测试类
18 public class Example15 {
19   public static void main(String[] args) {
20       Dog dog = new Dog(); // 创建Dog对象
21       Animal an = dog;
22       an.shout();
23   }
24 }

上述代码中,第20~22行代码是创建了一个dog对象,并将dog对象向上转型成Animal类型的对象an,然后使用对象an调用shout()方法。从程序的运行结果中可以发现,虽然是使用父类对象an调用了shout()方法,但实际上调用的是被子类重写过的shout()方法。也就是说,如果对象发生了向上转型关系后,所调用的方法一定是被子类重写过的方法。

父类Animal的对象an是无法调用Dog类中的eat()方法的,因为eat()方法只在子类中定义,而没有在父类中定义。

在进行对象的向下转型前,必须发生对象向上转型,否则将出现对象转换异常。接下来通过一个案例演示对象进行向下转型。

1 // 定义类Anmal
2 class Animal {
3   public void shout(){ 
4    System.out.println("喵喵……"); 
5  }     
6 }
7 // Dog类
8 class Dog extends Animal {
9   // 重写shout()方法
10   public void shout() {
11       System.out.println("汪汪……"); 
12  }
13   public void eat() {
14       System.out.println("吃骨头……"); 
15  }
16 }
17 // 定义测试类
18 public class Example16 {
19   public static void main(String[] args) {
20       Animal an = new Dog();  // 此时发生了向上转型,子类→父类
21       Dog dog = (Dog)an;       // 此时发生了向下转型
22       dog.shout();
23       dog.eat();
24   }
25 }

在上述程序中,第20行代码发生了向上转型,将Dog类的实例转换成了Animal类的实例an,第21行代码是将Animal类的实例转换为Dog类的实例。第22行代码使用dog对象调用shout()方法,由于Animal类的shout()方法已被子类Dog类重写,因此dog对象调用的方法是被子类重写过的方法。

在向下转型时,不能直接将父类实例强制转换为子类实例,否则程序会报错。例如,将文件4-16中的第20~21行代码修改为下面一行代码,则程序报错。 Dog dog = (Dog)new Animal(); //编译错误

4.4.3 instanceof关键字

Java中可以使用instanceof关键字判断一个对象是否是某个类(或接口)的实例,语法格式如下所示。 对象 instanceof类(或接口) 在上述格式中,如果对象是指定的类的实例对象,则返回true,否则返回false。

接下来通过一个案例演示instanceof关键字的用法。

1 // 定义类Anmal
2 class Animal {
3   public void shout(){ 
4    System.out.println("动物叫……"); 
5  }    
6 }
7 // Dog类
8 class Dog extends Animal {
9   // 重写shout()方法
10   public void shout() {
11       System.out.println("汪汪……"); 
12  }
13   public void eat() {
14       System.out.println("吃骨头……"); 
15  }
16}
17 // 定义测试类
18 public class Example17 {
19  public static void main(String[] args) {
20	Animal a1 = new Dog();         // 通过向上转型实例化Animal对象
21	System.out.println("Animal a1 = new Dog():"+(a1 instanceof Animal));
22	System.out.println("Animal a1 = new Dog():"+(a1 instanceof Dog));
23	Animal a2 = new Animal();         // 实例化Animal对象
24	System.out.println("Animal a1 = new Animal():"+(a2 instanceof Animal));
25	System.out.println("Animal a1 = new Animal():"+(a2 instanceof Dog));
26   }
27 }

上述代码中,第2~6行代码定义了Animal类;第8~16行代码定义了Dog类继承Animal类;第20行代码实例化Dog类对象,并将Dog类实例向上转型为Animal类对象a1。第21行代码是通过instanceof关键字判断对象a1是否是Animal类的实例,第22行代码是通过instanceof关键字判断对象a1是否是Dog类的实例;第23行代码是实例化了一个Animal类对象a2,第24行代码是通过instanceof关键字判断对象a2是否是Animal类的实例,第25行代码通过instanceof关键字判断对象a2是否是Dog类的实例。

【案例4-7】经理与员工工资案例

某公司的人员分为员工和经理两种,但经理也属于员工中的一种,公司的人员都有自己的姓名和地址,员工和经理都有自己的工号、工资、工龄等属性,但经理不同员工的是,经理有自己在公司对应的级别。假设每次给员工涨工资一次能涨10,经理能涨20%。本案例要求利用多态实现给员工和经理涨工资。

(1)创建父类Person类,在Person类中定义name和address属性,并定义该类的构造方法。

(2)创建抽象类Employee类并继承Person类,创建构造方法,在构造方法中调用父类的构造方法。在Employee类中定义员工的ID、工资wage、年龄age等属性;在类中定义涨工资的抽象方法add(),通过对职位的判断来给员工或经理涨工资。

(3)创建子类Manager类并继承Employee类;创建构造方法,在构造方法中调用父类的构造方法;由于经理有两种身份,既是Employee又是Manager,所以Manager类继承Employee类,在Manager类中定义等级属性level,并给出level的getter和setter方法;实现Employee类的add()抽象方法。

(4)创建测试类,对Manager进行实例化,传入参数,调用涨薪方法,传入级别level参数,根据级别level输出涨薪工资。

 1 public class Person {
 2     private String name = "";
 3     private String address = "";
 4     //定义构造方法
 5     public Person(String name, String address){
 6         this.name = name;
 7         this.address = address;      
 8     }
 9 }
 1 public abstract class  Employee extends Person {
 2     private String ID = "";
 3     private double wage = 0;
 4     private int age = 0;
 5         public Employee(String name, String address, String ID, double
 6  wage, int age){
 7         super(name, address);
 8         this.ID = ID;
 9         this.wage = wage;
 10     this.age = age;
 11     }
 12    //定义抽象方法
 13     public abstract void add(String position);
 14     //设置get/set方法
 15     public double getWage() {
 16         return wage;
 17     }
 18     public void setWage(double wage) {
 19         this.wage = wage;
 20     }
 21 }
 1 public class Manager extends Employee{
 2     private String level = "";
 3     public Manager(String name, String address, String ID, double wage, 
 4 int age, String level){
 5         super(name, address, ID, wage, age);
 6         this.level = level;
 7     }
 8     //实现抽象方法
 9     public void add(){
 10         double wage = super.getWage();
 11         super.setWage(wage*1.1);
 12     }
 13 public void add(String position){
 14         double wage = super.getWage();
 15         super.setWage(wage*1.2);
 16     }
 17 public String getLevel() {
 18         return level;
 19     }
 20 public void setLevel(String level) {
 21         this.level = level;
 22     }
 23 }
 1 public class Test {
 2     public static void main(String[] args) {
 3         Manager normal = new Manager("wsl", "jit", "12", 1000, 2, "1");
 4        Manager manager = new Manager("ctl", "jitt", "123", 10000, 10, 
 5         "0");
 6         normal.add();
 7         manager.add(manager.getLevel());
 8         System.out.println("normal wage is:"+normal.getWage());
 9         System.out.println("manager wage is:"+manager.getWage());
 10     }
 11 }

【案例4-8】模拟物流快递系统

网购已成为人们生活的重要组成部分,当人们在购物网站中下订单后,订单中的货物就会在经过一系列的流程后,送到客户的手中。而在送货期间,物流管理人员可以在系统中查看所有物品的物流信息。编写一个模拟物流快递系统的程序,模拟后台系统处理货物的过程。

(1)运输货物首先需要有交通工具,所以需要定义一个交通工具类。由于交通工具可能有很多,所以可以将该交通工具类定义成一个抽象类,类中需要包含该交通工具的编号,型号以及运货负责人等属性,还需要定义一个抽象的运输方法。

(2)当运输完成后,需要对交通工具进行保养,所以需要定义保养接口,具备交通工具的保养功能。

(3)交通工具可能有很多种,这里可以定义一个专用运输车类,该类需要继承交通工具类,并实现保养接口。

(4)有了运输的交通工具后,就可以开始运送货物了。货物在运输前,运输时和运输后,都需要检查和记录,并且每一个快递都有快递单号,这时可以定义一个快递任务类包含快递单号和货物重量的属性,以及送前、发送货物途中和送后的方法。

(5)在货物运输过程中,需要对运输车辆定位,以便随时跟踪货物的位置信息。定位功能可以使用GPS,而考虑到能够实现定位功能的设备可能有很多(如手机、专用定位仪器等),这时可以定义一个包含定位功能的GPS接口,以及实现了该接口的仪器类(如Phone等)。

(6)编写测试类,运行查看结果。

 1/*
 2 * 交通工具类
 3 */
 4public abstract class Transportation {	
 5	private String number; 	// 编号
 6	private String model; 	// 型号
 7	private String admin; 	// 运货负责人
 8	public Transportation() {
 9		super();//可省略
 10	}
 11	public Transportation(String number, String model, String admin) {
 12		this.number = number;
 13		this.model = model;
 14		this.admin = admin;
 15	}
 16	// 运输方法
 17	public abstract void transport();
 18	// 编号
 19	public void setNumber(String number) {
 20		this.number = number;
 21	}
 22	public String getNumber() {
 23		return number;
 24	}
 25	// 型号
 26	public void setModel(String model) {
 27		this.model = model;
 28	}
 29	public String getModel() {
 30		return model;
 31	}
 32	// 负责人
 33	public void setAdmin(String admin) {
 34		this.admin = admin;
 35	}
 36	public String getAdmin() {
 37		return admin;
 38	}
 39}
 1/*
 2 * 定义保养接口,具备保养功能。
 3 */
 4public interface Careable{
 5    //保养方法
 6	public abstract void upKeep();
}
 1/*
 2 * 专用运输车类
 3 */
 4public class ZTransportation extends Transportation implements Careable{
 5	//无参构造
 6	public ZTransportation() {
 7		super();
 8	}
 9    //有参构造:车辆编号、型号、负责人
 10	public ZTransportation(String number, String model, String admin) {
 11		super(number, model, admin);
 12	}
 13	// 运输方法
 14	public void transport() {
 15		System.out.println("运输进行中。。。");
 16	}	
 17	// 重写车辆保养方法
 18	public void  upKeep() {
 19		System.out.println("货物运输车辆保养完毕!");
 20	}
 21}
 1/*
 2 * 快递任务类
 3 */
 4public class SendTask {
 5	private String number; // 快递单号
 6	private double goodsWeight; // 货物重量	
 7	public SendTask() {
 8		super(); //可省略
 9	}
 10	public SendTask(String number, double goodsWeight) {
 11		this.number = number;
 12		this.goodsWeight = goodsWeight;
 13	}
 14	//送前准备
 15	public void sendBefore () {		
 16		System.out.println("订单开始处理,仓库验货中。。。");
 17		System.out.println("货物重量:"+this.getGoodsWeight()+"kg");
 18		System.out.println("货物检验完毕!");
 19		System.out.println("货物填装完毕!");
 20		System.out.println("运货人已通知!");		
 21		System.out.println("快递单号:"+this.getNumber());
 22	}	
 23	//发送货物
 24	public void send(Transportation t,GPS tool) {		
 25		System.out.println("运货人"+t.getAdmin()
 26				          +"正在驾驶编号为"+t.getNumber()
 27				          +"的"+t.getModel()+"发送货物!");
 28		t. transport();
 29		String showCoordinate = tool.showCoordinate();
 30		System.out.println("货物当前的坐标为:"+showCoordinate);
 31	}
 32	//送后操作
 33	public void sendAfter(Transportation t) {		
 34		System.out.println("货物运输任务已完成!");
 35		System.out.println("运货人"+t.getAdmin()
 36				          +"所驾驶的编号为"+t.getNumber()
 37				          +"的"+t.getModel()+"已归还!");
 38	}	
 39	public String getNumber() {
 40		return number;
 41	}
 42	public void setNumber(String number) {
 43		this.number = number;
 44	}
 45	public double getGoodsWeight() {
 46		return goodsWeight;
 47	}
 48	public void setGoodsWeight(double goodsWeight) {
 49		this.goodsWeight = goodsWeight;
 50	}
 51}
 1/*
 2 * 定义GPS接口,具备GPS定位功能。
 3 */
 4public interface GPS{
 5	//显示坐标的方法
 6	public String showCoordinate();
}
 1/*
 2 *定义一个手机类,实现GPS接口,拥有定位功能。
 3 */
 4class Phone implements GPS{	
 5	public Phone() { //空参构造
 6		super();
 7	}
 8    //定位方法
 9	public String  showCoordinate() {
 10		String location = "193,485";
 11		return location;
 12	}
 13}
 1/*
 2 * 定义测试类
 3 */
 4public class Task02Test {
 5	public static void main(String[] args) {
 6		// 快递任务类对象
 7		SendTask task = new SendTask("HYX600235",76.34);
 8		// 调用送前准备方法
 9		task.sendBefore();
 10		System.out.println("======================");		
 11		// 创建交通工具对象
 12		ZTransportation t = new ZTransportation("Z025","大奔","小韩");
 13		//创建GPS工具对象
 14		Phone p = new Phone();	
 15		// 将交通工具与GPS工具传入送货方法
 16		task.send(t,p);
 17		System.out.println("======================");
 18		// 调用送后操作方法
 19		task.sendAfter(t);
 20		t.upKeep();		
 21	}
 22}

4.5 Object类

Java提供了一个Object类,它是所有类的父类,每个类都直接或间接继承Object类,因此Object类通常被称之为超类。当定义一个类时,如果没有使用extends关键字为这个类显式地指定父类,那么该类会默认继承Object类。

Object类常用方法如下表。

方法名称方法说明
boolean equals()判断两个对象是否“相等”
int hashCode()返回对象的哈希码值
String toString()返回对象的字符串表示形式

下面通过一个例子演示Object类中toString()方法的使用。

?

1 // 定义Animal类
2 class Animal {  
3     // 定义动物叫的方法 
4	void shout() {	
5		System.out.println("动物叫!");
6	}
7 }
8 // 定义测试类
9 public class Example18 {
10	public static void main(String[] args)  {
11		Animal animal = new Animal();  	          // 创建Animal类对象
12		System.out.println(animal.toString());	// 调用toString()方法并打印
13	}
14}

在实际开发中,通常希望对象的toString()方法返回的不仅仅是基本信息,而是对象特有的信息,这时可以重写Object类的toString()方法。

1 // 定义Animal类
2 class Animal {
3   //重写Object类的toString()方法
4   public String toString(){
5	return "这是一个动物。";
6   }
7 }
8 // 定义测试类
9 public class Example19 {
10	public static void main(String[] args) {
11		Animal animal = new Animal(); 			// 创建Animal类对象
12		System.out.println(animal.toString()); // 调用toString()方法并打印
13	}
14 }

4.6 内部类

在Java中,允许在一个类的内部定义类,这样的类称作内部类,内部类所在的类称作外部类。在实际开发中,根据内部类的位置、修饰符和定义方式的不同,内部类可分为4种,分别是成员内部类、局部内部类、静态内部类、匿名内部类。

4.6.1 成员内部类

在一个类中除了可以定义成员变量、成员方法,还可以定义类,这样的类被称作成员内部类。成员内部类可以访问外部类的所有成员。

接下来通过一个案例学习如何定义成员内部类。

1 class Outer {
2    int m = 0; 		// 定义类的成员变量
3   // 下面的代码定义了一个成员方法,方法中访问内部类
4    void test1() {
5       System.out.println("外部类成员方法");
6    }
7    // 下面的代码定义了一个成员内部类
8    class Inner {
9        int n = 1;
10        void show1() {
11           // 在成员内部类的方法中访问外部类的成员变量
12           System.out.println("外部成员变量m = " + m);
13       }
14 	 void show2() {
15            // 在成员内部类的方法中访问外部类的成员变量
16           System.out.println("内部成员方法");
17       }
18    }
19    void test2() {
20        Inner inner = new Inner();
21        System.out.println("内部成员变量n = " + inner.n);
22        inner.show2();
23    }
24 }
25 public class Example20 {
26    public static void main(String[] args) {
27        Outer outer = new Outer();
28        Outer.Inner inner = outer.new Inner();
29        inner.show1();
30       outer.test2();
31    }
32 }  

上述代码中,第1~24行代码定义了一个Outer类,Outer类就是一个外部类。第8~23行代码是在Outer类内部定义了Inner类,Inner类就是Outer类的成员内部类。Outer类中定义了test1()和test2()两个方法。Inner类定义了一个show()方法,在show()方法中访问外部类的成员变量num;test2()方法中创建了内部类Inner的实例对象,并通过该对象调用show()方法,将num值输出。从运行结果可以看出,内部类可以在外部类中使用,并能访问外部类的成员。

如果想通过外部类访问内部类,则需要通过外部类创建内部类对象,创建内部类对象的具体语法格式如下:

?外部类名.内部类名 变量名 = new 外部类名().new 内部类名();

4.6.2 局部内部类

局部内部类,也叫作方法内部类,是指定义在某个局部范围中的类,它和局部变量一样,都是在方法中定义的,有效范围只限于方法内部。 在局部内部类中,局部内部类可以访问外部类的所有成员变量和方法,而局部内部类中变量和方法却只能在所属方法中访问。

接下来通过一个案例学习局部内部类的定义和使用。

1 class Outer {
2     int m = 0;  		// 定义类的成员变量
3    // 下面的代码定义了一个成员方法,方法中访问内部类
4     void test1() {
5        System.out.println("外部类成员方法");
6    }
7 	  void test2() {
8        // 下面的代码定义了一个成员内部类
9        class Inner {
10            int n = 1;
11            void show() {
12                // 在成员内部类的方法中访问外部类的成员变量
13                System.out.println("外部成员变量m = " + m);
14                test1();
15           }
16        }
17        Inner inner = new Inner();
18        System.out.println("局部内部类变量n = " + inner.n);
19        inner.show();
20  }
21}
22 public class Example21 {
23    public static void main(String[] args) {
24        Outer outer = new Outer();
25        outer.test2();
26    }
27 }

在上述代码中,第1~21行代码定义了一个外部类Outer,并在该类中定义了成员变量m、成员方法test1()和test2()。第9~15行代码是在外部类的成员方法test2()中定义了一个局部内部类Inner;然后在局部内部类Inner中,编写了show()方法。第13~14行代码是对外部类变量和方法的调用;第17~19行代码是在test2()方法中创建了局部内部类Inner对象,并调用局部内部类的方法和变量。

4.6.3 静态内部类

所谓静态内部类,就是使用static关键字修饰的成员内部类。与成员内部类相比,在形式上,静态内部类只是在内部类前增加了static关键字,但在功能上,静态内部类只能访问外部类的静态成员,通过外部类访问静态内部类成员时,可以跳过外部类直接访问静态内部类。

创建静态内部类对象的基本语法格式如下:

?外部类名.静态内部类名 变量名 = new 外部类名().静态内部类名();

接下来通过一个案例学习静态内部类的定义和使用。

1 class Outer {
2    static int m = 0; // 定义类的成员变量
3    // 下面的代码定义了一个静态内部类
4    static class Inner {
5         int n = 1;
6         void show() {
7             // 在静态内部类的方法中访问外部类的成员变量
8             System.out.println("外部静态变量m = " + m);
9           }
10     }
11  }
12 public class Example22 {
13    public static void main(String[] args) {
14        Outer.Inner inner = new Outer.Inner();
15        inner.show();
16    }
17 }

上述代码中,第1~11行代码定义了一个外部类Outer,其中第2~10行代码是在Outer类中定义了静态成员变量和静态内部类Inner。然后在静态内部类Inner中,编写了一个show()方法,在show()方法中打印了外部静态变量m,第14~15行代码是声明了一个内部类对象inner,并使用inner对象调用show()方法测试对外部类静态变量m的调用。

4.6.4 匿名内部类

匿名内部类是没有名称的内部类。在Java中调用某个方法时,如果该方法的参数是接口类型,除了可以传入一个接口实现类,还可以使用实现接口的匿名内部类作为参数,在匿名内部类中直接完成方法的实现。

创建匿名内部类的基本语法格式如下:

new 父接口(){
? ?//匿名内部类实现部分
}
1 interface Animal{
2   void shout();
3 }
4 public class Example23{
5    public static void main(String[] args){
6        String name = "小花";
7        animalShout(new Animal(){
8           @Override
9            public void shout() {
10                System.out.println(name+"喵喵...");
11            }
12       });
13   }
14   public static void animalShout(Animal an){
15       an.shout();
16    }
17 }

第1~3行代码创建了Animal接口;第7~12行代码是调用animalShout()方法,将实现Animal接口的匿名内部类作为animalShout()方法的参数,并在匿名内部类中重写了Animal接口的show()方法。 在上述代码中的匿名内部类中访问了局部变量name,而局部变量name并没有使用final修饰符修饰,程序也没有报错。这是JDK 8的新增特性,允许在局部内部类、匿名内部类中访问非final修饰的局部变量,而在JDK 8之前,局部变量前必须加final修饰符,否则程序编译时报错。

匿名类的编写步骤: (1)在调用animalShout()方法时,在方法的参数位置写上new Animal(){},这相当于创建了一个实例对象,并将对象作为参数传给animalShout()方法。在new Animal()后面有一对大括号,表示创建的对象为Animal的子类实例,该子类是匿名的。具体代码如下所示: animalShout(new Animal(){});

(2)在大括号中编写匿名子类的实现代码,具体如下所示:

animalShout(new Animal() {
	public void shout() {
		System.out.println("喵喵……");
	}
});

4.7 异常

4.7.1 什么是异常

尽管人人希望自己身体健康,处理的事情都能顺利进行,但在实际生活中总会遇到各种状况,比如感冒发烧,工作时电脑蓝屏、死机等。同样,在程序运行的过程中,也会发生各种非正常状况,例如,程序运行时磁盘空间不足、网络连接中断、被装载的类不存在等。针对这种情况, Java语言引入了异常,以异常类的形式对这些非正常情况进行封装,通过异常处理机制对程序运行时发生的各种问题进行处理。

接下来通过一个案例认识一下什么是异常。

1 public class Example24 {                      
2	public static void main(String[] args) {
3		int result = divide(4, 0);    // 调用divide()方法
4		System.out.println(result);    
5	}
6    //下面的方法实现了两个整数相除
7	public static int divide(int x, int y) { 
8		int result = x / y;     	// 定义一个变量result记录两个数相除的结果
9		return result;           	// 将结果返回
10	}
11 }

从运行结果可以看出,程序发生了算术异常(ArithmeticException),该异常是由于文件4-24中的第3行代码调用divide()方法时传入了参数0,运算时出现了被0除的情况。异常发生后,程序会立即结束,无法继续向下执行。

上述程序产生的ArithmeticException异常只是Java异常类中的一种,Java提供了大量的异常类,这些类都继承自java.lang.Throwable类。 接下来通过一张图展示Throwable类的继承体系。

Throwable有两个直接子类Error和Exception,其中,Error代表程序中产生的错误,Exception代表程序中产生的异常。 ● Error类称为错误类,它表示Java程序运行时产生的系统内部错误或资源耗尽的错误,这类错误比较严重,仅靠修改程序本身是不能恢复执行的。举一个生活中的例子,在盖楼的过程中因偷工减料,导致大楼坍塌,这就相当于一个Error。例如,使用java命令去运行一个不存在的类就会出现Error错误。

Throwable类中的常用方法如下表。

方法声明功能描述
String getMessage()返回异常的消息字符串
String toString()返回异常的简单信息描述
void printStackTrace()获取异常类名和异常信息,以及异常出现在程序中的位置,把信息输出在控制台。

4.7.2 try…catch和finally

出现异常后,程序会立即终止。为了解决异常,Java提供了对异常进行处理的方式一一异常捕获。异常捕获使用try…catch语句实现,try…catch具体语法格式如下:

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

上述语法格式中,在try代码块中编写可能发生异常的Java语句,catch代码块中编写针对异常进行处理的代码。当try代码块中的程序发生了异常,系统会将异常的信息封装成一个异常对象,并将这个对象传递给catch代码块进行处理。catch代码块需要一个参数指明它所能够接收的异常类型,这个参数的类型必须是Exception类或其子类。

接下来通过一个案例演示用try…catch语句对异常进行捕获。

1 public class Example25 {
2	public static void main(String[] args) {
3         //下面的代码定义了一个try…catch语句用于捕获异常
4		try {                                      
5			int result = divide(4, 0);    //调用divide()方法
6			System.out.println(result);   
7		} catch (Exception e) {            //对异常进行处理
8			System.out.println("捕获的异常信息为:" + e.getMessage());
9		}
10		System.out.println("程序继续向下执行...");
11	}
12 //下面的方法实现了两个整数相除
13	public static int divide(int x, int y) { 
14		int result = x / y;           //定义一个变量result记录两个数相除的结果
15		return result;                 //将结果返回
16	}
17 }

上述代码中,第4~9行代码是对可能发生异常的代码用try…catch语句进行了处理。在try代码块中发生除0异常时,程序会通过catch语句捕获异常,第8行代码在catch语句中通过调用Exception对象的getMessage()方法,返回异常信息“/ by zero”。catch代码块对异常处理完毕后,程序仍会向下执行,而不会终止程序。 需要注意的是,在try代码块中,发生异常语句后面的代码是不会被执行的,如上述代码中第6行代码的打印语句就没有执行。

在程序中,有时候会希望有些语句无论程序是否发生异常都要执行,这时就可以在try…catch语句后,加一个finally代码块。

1 public class Example26 {
2	public static void main(String[] args) {
3          //下面的代码定义了一个try…catch…finally语句用于捕获异常
4		try {
5			int result = divide(4, 0);       //调用divide()方法
6			System.out.println(result);
7		} catch (Exception e) {               //对捕获到的异常进行处理
8			System.out.println("捕获的异常信息为:" + e.getMessage());
9             			 return;                             //用于结束当前语句
10		} finally {                             
11			System.out.println("进入finally代码块");
12		}
13 System.out.println("程序继续向下执行…");
14	}
15     //下面的方法实现了两个整数相除
16	public static int divide(int x, int y) {
17		int result = x / y;           //定义一个变量result记录两个数相除的结果
18		return result;                 //将结果返回
19	}
20 }

上述代码中,第9行代码是在catch代码块中增加了一个return语句,用于结束当前方法,程序第13行代码就不会执行了,而finally代码块中的代码仍会执行,不受return语句影响。也就是说不论程序是发生异常还是使用return语句结束,finally中的语句都会执行。因此,在程序设计时,通常会使用finally代码块处理完成必须做的事情,如释放系统资源。

finally中的代码块在一种情况下是不会执行的,那就是在try...catch中执行了System.exit(0)语句。System.exit(0)表示退出当前的Java虚拟机,Java虚拟机停止了,任何代码都不能再执行了。

4.7.3 throws关键字

在实际开发中,大部分情况下我们会调用别人编写方法,并不知道别人编写的方法是否会发生异常。针对这种情况,Java允许在方法的后面使用throws关键字对外声明该方法有可能发生的异常,这样调用者在调用方法时,就明确地知道该方法有异常,并且必须在程序中对异常进行处理,否则编译无法通过。

throws关键字声明抛出异常的语法格式如下:

?修饰符 返回值类型 方法名(参数1,参数2.....)throws 异常类1, 异常类2.....{
? ? ? ? ?//方法体.....
?}

从上述语法格式中可以看出,throws关键字需要写在方法声明的后面,throws后面需要声明方法中发生异常的类型。

接下来通过一个案例演示throws关键字的用法。

1 public class Example27 {
2	public static void main(String[] args) {
3		int result = divide(4, 2);    //调用divide()方法
4		System.out.println(result);
5	}
6     //下面的方法实现了两个整数相除,并使用throws关键字声明抛出异常
7	public static int divide(int x, int y) throws Exception {
8		int result = x / y;           //定义一个变量result记录两个数相除的结果
9		return result;                 //将结果返回
10	}
11 }

在上述代码中,第3行代码调用divide()方法时传入的第二个参数为2,程序在运行时不会发生被0除的异常,但是由于定义divide()方法时声明了抛出异常,调用者在调用divide()方法时就必须进行处理,否则就会发生编译错误。

接下来修改上述程序,在try…catch处理divide()方法抛出的异常

1 public class Example28 {
2	public static void main(String[] args) {
3         //下面的代码定义了一个try…catch语句用于捕获异常
4		try {
5			int result = divide(4, 2);   //调用divide()方法
7			System.out.println(result); 
8		} catch (Exception e) {           //对捕获到的异常进行处理
9			e.printStackTrace();          //打印捕获的异常信息
10		}
11	}
12 //下面的方法实现了两个整数相除,并使用throws关键字声明抛出异常
13	public static int divide(int x, int y) throws Exception {
14		int result = x / y;      //定义一个变量result记录两个数相除的结果
15		return result;            //将结果返回
16	}
17 }

上述代码由于使用了try…catch对divide()方法进行了异常处理,因此程序可以编译通过,运行后正确的打印出了运行结果2。程序运行结果如下图。

由于使用了try…catch对divide()方法进行了异常处理,因此程序可以编译通过,运行后正确的打印出了运行结果2。下面修改上述程序,将divide()方法抛出的异常继续抛出。

1 public class Example29 {
2	public static void main(String[] args)throws Exception {
3		int result = divide(4, 0);   // 调用divide()方法
4		System.out.println(result);
5	}
6    // 下面的方法实现了两个整数相除,并使用throws关键字声明抛出异常
7	public static int divide(int x, int y) throws Exception {
8		int result = x / y;          // 定义一个变量result记录两个数相除的结果
9		return result;                // 将结果返回
10	}
11 }

上述代码,在main()方法继续使用throws关键字将Exception抛出,程序虽然可以通过编译,但从运行结果可以看出,在运行时期由于没有对“/by zero”的异常进行处理,最终导致程序终止运行。

4.7.4 运行时异常与编译时异常

在实际开发中,经常会在程序编译时产生一些异常,这些异常必须要进行处理,这种异常被称为编译时异常,也称为checked异常。另外还有一种异常是在程序运行时产生的,这种异常即使不编写异常处理代码,依然可以通过编译,因此被称为运行时异常,也称为unchecked异常。

1.编译时异常 在Exception类中,除了RuntimeException类及其子类,Exception的其他子类都是编译时异常。编译时异常的特点是Java编译器会对异常进行检查,如果出现异常就必须对异常进行处理,否则程序无法通过编译。 处理编译时期的异常有两种方式,具体如下: (1)使用try…catch语句对异常进行捕获处理。 (2)使用throws关键字声明抛出异常,调用者对异常进行处理。

2.运行时异常 RuntimeException类及其子类都是运行时异常。运行时异常的特点是Java编译器不会对异常进行检查。也就是说,当程序中出现这类异常时,即使没有使用try..catch语句捕获或使用throws关键字声明抛出,程序也能编译通过。运行时异常一般是由程序中的逻辑错误引起的,在程序运行时无法恢复。

例如,通过数组的角标访问数组的元素时,如果角标超过了数组范围,就会发生运行时异常,代码如下所示:

?int[] arr=new int[5];
?System.out.println(arr[6]); 

在上面的代码中,由于数组arr的length为5,最大角标应为4,当使用arr[6]访问数组中的元素就会发生数组角标越界的异常。

4.7.5 自定义异常

JDK中定义了大量的异常类,虽然这些异常类可以描述编程时出现的大部分异常情况,但是在程序开发中有时可能需要描述程序中特有的异常情况,例如,前面讲解的程序中的divide()方法,不允许被除数为负数。为了解决这个问题,Java允许用户自定义异常,但自定义的异常类必须继承自Exception或其子类。

// 下面的代码是自定义一个异常类继承自Exception
public class DivideByMinusException extends Exception{
	public DivideByMinusException (){
		super();          	// 调用Exception无参的构造方法
	}
	public DivideByMinusException (String message){
		super(message); 	// 调用Exception有参的构造方法
	}
}

在实际开发中,如果没有特殊的要求,自定义的异常类只需继承Exception类,在构造方法中使用super()语句调用Exception的构造方法即可。 自定义异常类中使用throw关键字在方法中声明异常的实例对象,格式如下: throw Exception异常对象

下面通过一个案例演示自定义异常类。

1 public class Example30 {
2	public static void main(String[] args) {
3		int result = divide(4, -2);   			
4         System.out.println(result);
5	}
6	//下面的方法实现了两个整数相除
7	public static int divide(int x, int y) {
8		if(y<0){ 
9			throw new DivideByMinusException("除数是负数");		}
10		int result = x / y;   	// 定义一个变量result记录两个数相除的结果
11		return result;         	// 将结果返回
12	}
13 }

从运行结果可以看出,程序在编译时就发生了异常。因为在一个方法内使用throw关键字抛出异常对象时,需要使用try…catch语句对抛出的异常进行处理,或者在divide()方法上使用throws关键字声明抛出异常,由该方法的调用者负责处理。但是程序没有这样做。 为了解决上面的问题,对程序进行修改,在divide()方法上,使用throws关键字声明抛出DivideByMinusException异常,并在调用divide()方法时使用try…catch语句对异常进行处理。

1 public class Example31 {
2	public static void main(String[] args) {
3              // 下面的代码定义了一个try…catch语句用于捕获异常
4		try {
5			int result = divide(4, -2);  			
6              System.out.println(result);
7		} catch (DivideByMinusException e) {     // 对捕获到的异常进行处理
8			System.out.println(e.getMessage()); // 打印捕获的异常信息
9		}
10	}
11 // 下面的方法实现了两个整数相除,并使用throws关键字声明抛出自定义异常
12	public static int divide(int x, int y) throws DivideByMinusException {
13		if (y < 0) {
14			throw new DivideByMinusException("除数是负数");
15		}
16		int result = x / y;     // 定义一个变量result记录两个数相除的结果
17		return result;           // 将结果返回
18	}
19 }

上述代码中的main()方法中,第4~9行代码使用try…catch语句捕获处理divide()方法抛出的异常。在调用divide()方法时,如果传入的被除数不能为负数,程序会抛出一个自定义的DivideByMinusException异常,该异常最终被catch代码块捕获处理,并打印出异常信息。

4.8 本章小结

本章主要介绍了面向对象的继承、多态特性,与第3章学习的面向对象的封装性构成了面向对象语言程序设计的三大特性,这是Java语言的精髓所在。此外,本章还介绍了final关键字、抽象类和接口、Object类、内部类、异常的概念以及异常的处理机制等。本章和第3章,是本书最重要的两章,熟练掌握这两章内容,才是掌握了Java语言的精髓。

文章来源:https://blog.csdn.net/u010321564/article/details/135822474
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。