目录
学习Java面向对象中的多态、Object类、内部类、异常
一、多态
1、多态的概述
多态性是面向对象思想中的一个非常重要的概念,在Java中,多态是指不同对象在调用同一个方法时表现出的多种不同行为。例如,要实现一个动物叫的方法,由于每种动物的叫声是不同的,因此可以在方法中接收一个动物类型的参数,当传入猫类对象时就发出猫类的叫声,传入犬类对象时就发出犬类的叫声。在同一个方法中,这种由于参数类型不同而导致执行效果不同的现象就是多态。
Java中多态主要有以下两种形式。
(1)方法的重载。
(2)对象的多态性(方法重写)。
案例:演示Java程序中的多态
package example014;
//定义抽象类Animal
abstract class Animal{
abstract void shout(); //定义抽象shout( ) 方法
}
//定义Cat类继承Animal抽象类
class Cat extends Animal{
//实现shout( ) 方法
public void shout(){
System.out.println("喵喵.......");
}
}
//定义Dog类继承Animal抽象类
class Dog extends Animal{
//实现shout( ) 方法
public void shout(){
System.out.println("汪汪......");
}
}
//定义测试类
public class Example014 {
public static void main(String[] args) {
Animal an1 = new Cat(); //创建Cat对象,使用Animal类型的变量an1引用
Animal an2 = new Dog(); //创建Dog对象,使用Animal类型的变量an2引用
an1.shout(); //调用Cat类中的shout()方法
an2.shout(); //调用Dog类中的shout()方法
}
}
/*
结果:
喵喵.......
汪汪......*/
2、对象类型的转换
对象类型转换主要分为以下两种情况。
(1)向上转型:子类对象→父类对象。
(2)向下转型:父类对象→子类对象。
对于向上转型,程序会自动完成,而向下转型时,必须指明要转型的子类对象。
对象类型的转换格式
对象向上转型:
父类类型 父类对象 = 子类实例;
对象向下转型:
父类类型 父类对象 = 子类实例;
子类类型 子类对象 = (子类) 父类对象;
案例:介绍如何进行对象的向下转型操作
package example15;
//定义类Animal
class Animal{
public void shout(){
System.out.println("喵喵.....");
}
}
//Dog类
class Dog extends Animal{
//重写shout()方法
public void shout(){
System.out.println("汪汪......");
}
public void eat(){
System.out.println("吃骨头.....");
}
}
//定义测视类
public class Example15 {
public static void main(String[] args) {
Dog dog = new Dog(); //创建Dog对象
Animal an = dog;
an.shout();
}
}
/*
结果:
汪汪......*/
要注意的是,父类Animal的对象an是无法调用Dog类中的eat( ) 方法的,因为eat( )方法只在子类中定义,没有在父类中定义。
在进行对象的向下转型前,必须发生对象向上转型,否则将出现对象转换异常。
案例:对象的向下转型
package example16;
//定义类Animal
class Animal{
public void shout(){
System.out.println("喵喵.....");
}
}
//Dog类
class Dog extends Animal {
//重写shout()方法
public void shout(){
System.out.println("汪汪......");
}
public void eat(){
System.out.println("吃骨头.....");
}
}
//定义测视类
public class Example16 {
public static void main(String[] args) {
Animal an = new Dog(); //此时发生了向上转型,子类→父类
Dog dog = (Dog) an; //此时发生了向下转型
dog.shout(); //使用dog对象调用shout()方法,由于Animal类的shout()方法已被子类Dog类重写,因此dog对象调用的方法是被子类重写过的方法
dog.eat();
}
}
注意:
在向下转型时,不能直接将父类实例强制转换子类实例,否则程序会报错
3、instanceof 关键字
Java中可以使用instanceof 关键字判断一个对象是否是某个类(或接口)的实例,
语法格式
对象 instanceof类 (或接口)
在上述格式中,如果对象是指定类的实例对象,则返回true,否则返回false。
案例:演示instanceof 关键字的用法
package example17;
//定义类Animal
class Animal{
public void shout(){
System.out.println("动物的叫......");
}
}
//定义Dog类
class Dog extends Animal{
//重写shout()方法
public void shout(){
System.out.println("汪汪......");
}
public void eat(){
System.out.println("吃骨头.......");
}
}
//定义测试类
public class Example17 {
public static void main(String[] args) {
Animal a1 = new Dog(); //通过向上转型实例化Animal对象
System.out.println("Animal a1 = new Dog():"+(a1 instanceof Animal)); //通过instanceof关键字判断对象a1是否是Animal类的实例
System.out.println("Animal a1 = new Dog():"+(a1 instanceof Dog)); //通过instanceof关键字判断对象a1是否是Dog类的实例
Animal a2 = new Animal(); //实例化Animal对象
System.out.println("Animal a2 = new Animal():"+(a2 instanceof Animal)); //通过instanceof关键字判断对象a2是否是Animal类的实例
System.out.println("Animal a2 = new Animal():"+(a2 instanceof Dog)); //通过instanceof关键字判断对象a2是否是Dog类的实例
}
}
/*
结果:
Animal a1 = new Dog():true
Animal a1 = new Dog():true
Animal a2 = new Animal():true
Animal a2 = new Animal():false*/
二、Object 类
Java提供了一个Object类,它是所有类,每个类都直接或间接继承Object类通常称为超类。当定义一个类时,如果没有使用extends关键字为这个类显式指定父类,那么该类会默认继承Object类。
方法名称 | 方法说明 |
---|---|
boolean equals( ) | 判断两个对象是否相等 |
int hashCode( ) | 返回对象的散列码值 |
String toString( ) | 返回对象的字符串表示形式 |
案例:演示Object类中toString( ) 方法的使用
package example18;
//定义Animal类
class Animal{
//定义动动物叫的方法
void shout(){
System.out.println("动物叫!");
}
}
//定义测试类
public class Example18 {
public static void main(String[] args) {
Animal animal = new Animal(); //创建Animal类实例化对象
System.out.println(animal.toString()); //调用toString()方法打印
}
}
/*
结果:
example18.Animal@14ae5a5*/
重写Object类中toString( ) 方法
package example19;
定义Animal类
class Animal{
//重写Object类的toString()方法
public String toString(){
return "这是一个动物";
}
}
//定义测试类
public class Example19 {
public static void main(String[] args) {
Animal animal = new Animal(); //创建Animal类实例化对象
System.out.println(animal.toString()); //调用toString()方法打印
}
}
/*
结果:
这是一个动物*/
三、内部类
1、成员内部类
在一个类中除了可以定义成员变量、成员方法外,还可以定义类,这样的类称为成员内部类。成员内部类可以访问外部类的所有成员。
案例:如何定义成员内部类
package example20;
class Outer {
int m = 0; // 定义类的成员变量
// 下面的代码定义了一个成员方法,方法中访问内部类
void test1() {
System.out.println("外部类成员方法");
}
// 下面的代码定义了一个成员内部类
class Inner {
int n = 1;
void show1() {
// 在成员内部类的方法中访问外部类的成员变量
System.out.println("外部成员变量m = " + m);
}
void show2() {
// 在成员内部类的方法中访问外部类的成员变量
System.out.println("内部成员方法");
}
}
void test2() { //外部类Outer的成员方法
Inner inner = new Inner();
//1.输出内部类Inner的变量n的值
System.out.println("内部成员变量n = " + inner.n);
//2.调用内部类Inner的方法show2()
inner.show2();
}
}
public class Example20 {
public static void main(String[] args) {
//创建Outer类的内部类Inner对象
//1.先创建外部类的对象
Outer outer = new Outer();
//1.创建内部类的对象
Outer.Inner inner =outer.new Inner();
//调用内部类Inner的方法show1()
inner.show1();
//调用外部类Outer的方法test2()
outer.test2();
}
}
/*案例总结:
1.成员内部类胡定义;
2.使用成员内部类:成员内部类可以直接调用外部类成员;外部类要访问内部类的成员要先创建内部类对象,再通过内部类对象调用内部类的变量*/
/*
结果:
外部成员变量m = 0
内部成员变量n = 1
内部成员方法*/
如果想通过外部类访问内部类,则需要通过外部类创建内部类对象,创建内部类对象的具体语法格式
外部类名.内部类名 变量名 = new 外部类名( ).new 内部类名( );
2、局部内部类
局部内部类,也称为方法内部类,是指定义在某个局部范围中的类,它与局部变量一样,都是在方法中定义的,在有效范围只限于方法内部。
在局部内部类中,局部内部类可以访问外部类的所有成员变量和方法,而局部内部类中变量和方法只能在所属方法中访问。
案例:局部内部类的定义和使用
package example21;
class Outer {
int m = 0; // 定义类的成员变量
// 下面的代码定义了一个成员方法,方法中访问内部类
void test1() {
System.out.println("外部类成员方法");
}
void test2() {
// 下面的代码定义了一个成员内部类
class Inner {
int n = 1;
void show() {
// 在局部内部类的方法中访问外部类的成员变量
System.out.println("外部成员变量m = " + m);
test1();
}
}
//在test2()方法中创建了局部内部类Inner对象,兵调用局部内部类的方法和变量
Inner inner = new Inner();
System.out.println("局部内部类变量n = " + inner.n);
inner.show();
}
}
public class Example21 {
public static void main(String[] args) {
Outer outer = new Outer();
outer.test2();
}
}
/*
结果:
局部内部类变量n = 1
外部成员变量m = 0
外部类成员方法
*/
/*
案例总结:
(局部内部类可以访问外部类的所有成员变量和方法,局部内部类中变量和方法只能在所属方法中访问)
1.定义在方法中的类是局部内部类(9-16行代码),只有在方法中有效;
2.局部内部类中胡方法可以直接访问外部类胡成员变量(第13行代码)和成员方法(第14行代码);
3.只能定义局部内部类的方法中创建局部内部类中创建局部内部类地对象
*/
3、静态内部类
静态内部类,就是使用static关键字修饰的成员内部类。与成员内部类相比,在形式上,静态内部类只是在内部类前增加了static关键字,但在功能上,静态内部类只能访问外部类的静态成员,通过外部类访问静态成员时,可以跳过外部类直接访问静态内部类。
创建静态内部类对象的基本语法格式
外部类名.静态内部类名 变量名 = new 外部类名( ) .静态内部类名( );
案例:静态内部类的定义和使用
package example22;
//静态内部类:成员内部类的前面加一个static关键字,只能访问静态成员的变量
class Outer{
static int m = 0; //定义类的成员变量
int n = 1;
//下面的代码定义了一个静态内部类
static class Inner{
int a = 2;
void show(){
//在静态内部类的方法中访问外部类的成员变量
System.out.println("外部静态变量m = " + m);
// System.out.println("外部静态变量n = " + n); //不能访问静态变量
}
}
}
public class Example22 {
public static void main(String[] args) {
Outer.Inner inner = new Outer.Inner();
inner.show();
}
}
/*案例总结:
1.静态内部类:成员内部类胡前面要加一个static关键字(第9-15行代码)
2.只能访问静态的成员变量,不能访问事静态变量(第12行代码)
3.创建静态内部类胡对象new 外部类名(第19行代码)*/
/*
结果:
外部静态变量m = 0*/
4、匿名内部类
匿名内部类是没有名称的内部类。在Java中调用某个方法时,如果该方法的参数时接口类型,除了可以传入一个接口实现类外,还可以使用实现接口的匿名内部类作为参数,在匿名内部类中直接完成方法的实现。
创建匿名内部类的基本语法格式
new 父接口( ){
//匿名内部类实现部分
}
案例:匿名内部类的定义和使用
package example23;
interface Animal{
void shout();
}
public class Example23 {
public static void main(String[] args) {
String name = "小花";
//匿名内部类
animalShout(new Animal(){
@Override
public void shout(){
System.out.println(name+"喵喵......");
}
});
}
public static void animalShout(Animal an){
an.shout();
}
}
/*结果:
小花喵喵......*/
/*
案例总结:1.匿名内部类是没有名称的内部类。在Java中调用某个方法时,如果该方法的参数是接口类型,
除了可以传入一个接口实现类(5-10,15-16),还可以使用实现接口的匿名内部类作为参数,在匿名内部类中直接完成方法的实现(18-23)
2.创建匿名内部类胡基本语法格式如下:
new 父接口(){
//匿名内部类实现部分
}*/
四、异常
1、什么是异常
案例:认识什么是异常
package example24;
public class Example24 {
public static void main(String[] args) {
int divide = divide(4,0); //调用divide()方法
System.out.println(divide);
}
public static int divide(int a,int b){
return a/b; //返回两数相除的结果
}
}
运行结果:
从图的运行结果可以看出,程序发生了算术异常(ArithmeticException),该异常是由于上面代码调用divide()方法时传入了参数0,运算时出现了被0除的情况。异常发生后,程序会立即结束,无法继续向下执行。
以上产生的ArithmeticException异常只是Java异类中的一种,Java提供大量的异常类,这些类都继承自java.lang.Throwable类。
Throwable类的继承体系图
通过图可以看出,Throwable有两个直接子类Error和Exception,其中,Error代表程序中产生的错误,Exception代表程序中产生的异常。
下面就对Error和Exception类进行详细讲解。
- Enor类称为错误类,它表示Java程序运行时产生的系统内部错误或资源耗尽的错误,这类错误比较严重,仅靠修改程序本身是不能恢复执行的。举一个生活中的例子,在盖楼的过程中因偷工减料导致大楼坍塌,这就相当于一个Error。例如,使用java命令去运行一个不存在的类就会出现Error错误。
- Exception 类称为异常类,它表示程序本身可以处理的错误,在Java程序中进行的异常处理,都是针对Exeption 类及其子类的。在Exeption 类的众多子类中有一个特殊的子类——RuntimeException类,RuntimeException类及其子类用于表示运行时异常。Exception类的其他子类都用于表示编译时异常。
将Thowable类中的常用方法罗列出来
方法声明 | 功能描述 |
---|---|
String getMessage( ) | 返回异常的消息字符串 |
String toString( ) | 返回异常的简单信息描述 |
void printStackTrace( ) | 获取异常类名和异常信息,以及异常出现在程序中的位置,把信息输出在控制台 |
2、try...catch 和 finally
为了解决异常,Java提供了对异常进行处理的方式——异常捕获。异常捕获使用try...catch 语句实现,try...catch具体语法格式如下
try{
//程序代码块
} catch (ExceptionType (Exception 类及其子类) e){
//对ExceptionType的处理
}
上述语法格式中,在try代码块中编写可能发生异常的Java语句,在catch代码块中编写针对异常进行处理的代码。当try代码块中的程序发生了异常,系统会将异常的信息封装成一个异常对象,并将这个对象传递给catch代码块进行处理。catch代码块需要一个参数指明它所能接收的异常类型,这个参数的类型必须是Exception类或其子类。
案例:使用try...catch 语句进行异常捕获
package example025;
public class Example025 {
public static void main(String[] args) {
//下面代码定义了一个try...catch 语句用于捕获异常
try{
int result = divide(4,0); //调用divide()方法
System.out.println(result);
} catch (Exception e) { //对异常进行处理
System.out.println("捕获的异常信息为:" + e.getMessage());
}
System.out.println("程序往下继续执行...");
}
//下面的方法实现了两个整数相除
public static int divide(int x, int y){
return x / y; //将结果返回
}
}
/*结果:
捕获的异常信息为:/ by zero
程序往下继续执行...*/
在上面try代码块中发生除0异常时,程序会通过catch语句捕获异常,在catch语句中通过调用Exception对象的getMessage ()方法,返回异常信息“/by zero” 。catch代码块对异常处理完毕,程序仍会向下执行,而不会终止程序。
需要注意的是,在ty代码块中,发生异常语句后面的代码是不会被执行的。
在程序中,有时候会希望有些语句无论程序是否发生异常都要执行,这时就可以在try..catch语句后加一个finally代码块。
案例:演示finally代码块的用法
package example25;
public class Example25 {
public static void main(String[] args) {
//下面代码定义了一个try...catch 语句用于捕获异常
try{
int result = divide(4,0); //调用divide()方法
System.out.println(result);
} catch (Exception e) { //对异常进行处理
System.out.println("捕获的异常信息为:" + e.getMessage());
System.exit(0);
return;
}finally {
System.out.println("进入finally语句块");
}
System.out.println("程序往下继续执行...");
}
//下面的方法实现了两个整数相除
public static int divide(int x, int y){
return x / y; //将结果返回
}
}
/*
案例总结:1.try代码块中编写可能发生异常的Java语句(6-7),catch代码块中编写针对异常进行处理的代码(9-10)。
在try代码块中,发生异常语句后面的代码是不会被执行的(7)。
2.无论是否发生异常,finally代码块是一定会执行的(12),但是需要注意的是,finally中的代码块在一种情况下不会执行,
那就是在执行到finally语句之前执行了System.exit(0)语句。*/
3、throws关键字
由于调用的是自己编写的divide()方法,因此很清楚该方法可能发生的异常。但是在实际开发中,大部分情况下会调用别人编写的方法,并不知道别人编写的方法是否会发生异常。针对这种情况,Java允许在方法的后面使用throws关键字对外声明该方法有可能发生的异常,这样调用者在调用方法时,就明确地知道该方法是否有异常,并且必须在程序中对异常进行处理,否则编译无法通过。
throws关键字声明抛出异常的语法格式
修饰符 返回值类型 方法名(参数1, 参数2......) throws 异常类1,异常类2......{
//方法体......
}
上述语法格式中可以看出,throws关键字需要写在方法声明的后面,throws后面需要声明方法中发生的异常。
案例:divide( ) 方法中声明可能出现的异常类型
package example27;
public class Exanple27 {
public static void main(String[] args) {
int result = divide(4,2); //调用divide()方法
System.out.println(result);
}
//下面通过方法实现了两个整数相除,使用throws关键字声明抛出异常
public static int divide(int x,int y)throws Exception{
int result = x/y ; //定义一个变量result记录两个数相除的结果
return result; //将结果返回
}
}
运行结果:
以上代码调用divide()方法时传入的第二个参数为2,程序在运行时不会发生被0除的异常,但是由于定义divide()方法时声明了抛出异常,调用者在调用divide()方法时就必须进行处理,否则就会发生编译错误。
案例:在try..catch处理divide()方法抛出的异常
package example28;
public class Example28 {
public static void main(String[] args) {
//下面代码定义一个try...catch语句用于捕获异常
try {
int result = divide(4,2); //调用divide()方法
System.out.println(result);
} catch (Exception e) { //对捕获到的异常进行处理
e.printStackTrace(); //打印捕获的异常信息
}
}
//下面通过方法实现了两个整数相除,使用throws关键字声明抛出异常
public static int divide(int x,int y)throws Exception{
int result = x/y ; //定义一个变量result记录两个数相除的结果
return result; //将结果返回
}
}
/*
结果:2*/
在上面调用divide()方法时,如果不知道如何处理声明抛出的异常,也可以使用throws关键字继续将异常抛出,这样程序也能编译通过。需要注意的是,程序一旦发生异常,并且异常没有被处理,程序就会非正常终止。
案例:将divide()方法抛出的异常继续抛出
package example29;
public class Exanple29 {
public static void main(String[] args)throws Exception {
int result = divide(4,0); //调用divide()方法
System.out.println(result);
}
//下面通过方法实现了两个整数相除,使用throws关键字声明抛出异常
public static int divide(int x,int y)throws Exception{
int result = x/y ; //定义一个变量result记录两个数相除的结果
return result; //将结果返回
}
}
运行结果:
在上面main()方法中继续使用throws关键字将Exception抛出,程序虽然可以通过编译,但从图的运行结果可以看出,在运行时期由于没有对“/by zero”的异常进行处理,最终导致程序终止运行。
4、编译时异常与运行时异常
(1)编译时异常
在Exception 类中,除了RurntimeException类及其子类外,Excepion 的其他子类都是编译时异常。编承异常的特点是Java编译器会对异常进行检查,如果出现异常就必须对异常进行处理,否则程序无法通过编译。
有两种方式处理编译时期的异常,具体如下。
- 使用try...catch语句对异常进行捕获处理。
- 使用throws关键字声明抛出异常,调用者对异常进行处理。
(2)运行时异常
RurntimeException类及其子类都是运行时异常。运行时异常的特点是Java编译器不会对异常进行检查。也就是说,当程序中出现这类异常时,即使没有使用try...catch语句捕获或使用throws关键字声明抛出,程序也能编译通过。运行时异常一般是由程序中的逻辑错误引起的,在程序运行时无法恢复。
例如,通过数组的角标访问数组的元素时,如果角标超过了数组范围,就会发生运行时异常,代码如下:
int [ ] arr = new int [5];
System.out.println (arr [6]);
上面代码中,由于数组arr的length为5,最大角标应为4,当使用arr [6] 访问数组中的元素时就会发生数组角标越界的异常。
5、自定义异常
自定义异常的具体代码如下:
package example0;
public class DivideByMinusException extends Exception{
public DivideByMinusException(){
super(); //调用Exception无参的构造方法
}
public DivideByMinusException(String message){
super(message); //调用Exception有参的构造方法
}
}
在实际开发中,如果没有特殊的要求,自定义的异常只需要继承Exception类,在构造方法中使用super ( ) 语句调用Exception类的构造方法即可。
自定义异常类中使用throw关键字在方法中声明异常的实例对象,语法格式
throw Exception 异常对象
案例:在divide()方法中判断被除数是否为负数,如果为负数,就使用thow 关键字在方法中向调用者地出自定义的 DivideBsMinausEscepion异常对象
package example30;
public class Example30 {
public static void main(String[] args) {
int result = divide(4,-2); //调用divide()方法
System.out.println(result);
}
//下面通过方法实现了两个整数相除
public static int divide(int x,int y){
if (y<0){
throw new DivideByMinusException("除数是负数");
}
int result = x/y ; //定义一个变量result记录两个数相除的结果
return result; //将结果返回
}
}
运行结果:
从图可以看出,程序在编译时就发生了异常。因为在一个方法内使用 throw关键字抛出异常对象时,需要使用ty···catch语句对抛出的异常进行处理,或者在divide()方法上使用throws关键字声明抛出异常,由该方法的调用者负责处理,但是以上代码没有这样做。
案例:在divide()方法上,使用throws 关键字声明抛出DivideByMinusException异常,并在调用divide()方法时使用ty...catch语句对异常进行处理
package example30;
//自定义一个异常类型继承自Exception
public class Example30 {
public static void main(String[] args) {
//下面的代码定义了一个try...catch 语句用于捕获异常
try {
int result = divide(4,-2);
System.out.println(result);
}catch (DivideByMinuseException e){ //对捕获到的异常进行处理
System.out.println(e.getMessage()); //打印捕获到的异常信息
}
}
//下面的方法实现了两个整数相除,并使用throw关键字声明抛出自定义异常
public static int divide(int x,int y) throws DivideByMinuseException{
if(y<0){
throw new DivideByMinuseException("除数是负数");
}
// int result = x / y;
// return result;
return x / y; //将结果返回
}
}
/*
结果:
除数是负数*/
package example30;
public class DivideByMinuseException extends Exception {
public DivideByMinuseException(){
super(); //调用Exception无参的构造方法
}
public DivideByMinuseException(String message) {
super(message); //调用Exception有参的构造方法
}
}
用try···catch语句捕获处理divide()方法抛出的异常。在调用divide()方法时,传入的除数不能为负数,否贝程序会抛出一个自定义的 DivideByMinusException异常,该异常最终被catch代码块捕获处理,并打印出异常信息。