1、本章学习目标:第七章第七章 继承与多态继承与多态 了解类与类之间的关系 掌握继承的概念和特点 掌握方法的重写和应用 掌握super关键字和 final关键字的应用 掌握多态向上转型的应用 了解引用变量的强制类型转换 了解内部类的概念、分类和 基本应用第第1 1节节part类之间关系概述 在面向对象的程序设计中,通常不会存在一个孤立的类,类和类之间总是存在一定的关系,通过这些关系,才能实现软件的既定功能。掌握类与类之间的关系对于深入理解面向对象概念具有非常重要的作用,也有利于程序员从专业的、合理的角度使用面向对象分析问题和解决问题。类之间关系概述类之间关系概述 根据UML(Unified Mod
2、eling Language,统一建模语言)规范,类与类之间存在以下六种关系。(1)继承:一个类可以继承另外一个类,并在此基础上添加自己的特有功能。继承也称为泛化,表现的是一种共性与特性的关系。(2)实现:一个类实现接口中声明的方法,其中接口对方法进行声明,而类完成方法的定义,即实现具体功能。实现是类与接口之间常用的关系,一个类可以实现一个或多个接口中的方法。(3)依赖:在一个类的方法中操作另外一个类的对象,这种情况称为第一个类依赖于第二个类。(4)关联:在一个类中使用另外一个类的对象作为该类的成员变量,这种关系称为关联关系。关联关系体现的是两个类之间语义级别的一种强依赖关系。(5)聚合:聚合
3、关系是关联关系的一种特例,体现的是整体与部分的关系,即has-a的关系。通常表现为一个类(整体)由多个其他类的对象(部分)作为该类的成员变量,此时整体与部分之间是可以分离的,整体和部分都可以具有各自的生命周期,部分可以属于多个整体对象,与可以为多个整体对象共享。(6)组成:组成关系也是关联关系的一种特例,与聚合关系一样也是体系整体与部分的关系,但组成关系中的整体与部分是不可分离的,即contains-a的关系,这种关系比聚合更强,也称为强聚合,当整体的生命周期结束后,部分的生命周期也随之结束。类与类之间的这六种关系中,继承和实现体现了类与类之间的一种纵向关系,而其余四种则体现了类与类之间的横向
4、关系。其中,关联、聚合和组成这三种关系更多体现的是一种语义上的区别,而在代码上则是无法区分的。类之间关系概述第第2 2节节part继承 继承是面向对象的三大特征之一,也是实现程序重复利用的重要手段。Java继承具有单继承的特点,也就是每个子类只有一个直接父类。类的继承性类似与自然界中的继承特性。例如,人类特征的遗传,子女或多或少地体现了父母的特征。不过类的继承子类除了具有父类原有的特性和功能之外,还可以增加一些新的特性和功能。例如,人类安装工作性质大致可以分为工、农、兵、学、商等,但学生又可以根据学龄分为大、中、小学生,大学生又可以根据学校不同继续细分,如图7.1所示。继承本节概述 从上图可以
5、看出,子类对象是一种特殊的父类对象,对对象(实例)进行归类时,由于类型之间具有相似类似的特征,描述“某种东西是(或像)另外一种东西”这种关系时,使用继承性。7.2.1继承的特点继承的特点 Java的继承通过extends关键字来实现,实现继承的类被称为子类,有的也称其为派生类,被继承的类被称为父类,有的也称其为基类或超类。父类和子类的关系,是一种一般和特殊的关系。例如水果和苹果的关系,苹果继承了水果,苹果是水果的子类,则苹果是一种特殊的水果。Java里子类继承父类的声明格式如下;【访问符】【修饰符】class 子类名 extends 父类名 属性 方法 7.2.1继承的特点 其中,访问符、修饰
6、符、class、子类名、大括号、属性和方法与第六章类的定义完全相同,这里就不再赘述。Java使用extends作为继承的关键字,extends关键字在英文中是扩展,而不是继承。这个关键字很好地体现了子类和父类的关系:子类是对父类的扩展,子类是一种特殊的父类。下述代码示例了子类继承父类的基本格式:public class SubClass extends SuperClass.上述代码通过使用extends关键字使子类SubClass继承了父类SuperClass。如果定义一个类没有使用extends关键字继承任何父类,则自动继承java.lang.Object类。Object类是所有类的顶级父
7、类,在Java中,所有类都是直接或间接地继承了Object类。下述程序代码示例了继承的创建与继承相应的特点,代码如下:7.2.1继承的特点【代码7.1】Student.javapackage com;/创建父类class Person private int age;/私有成员变量,年龄String name;/默认成员变量 姓名static String id;/静态变量 id编号/声明一个私有成员方法private void showAge()System.out.println(年龄为:+age);void showName()System.out.println(姓名为:+name);
8、/声明一个静态成员方法public static void showId()System.out.println(id编号为:+id);7.2.1继承的特点/创建子类public class Student extends Person String stuid;/学生证号void display()System.out.println(姓名为:+name+,id编号为:”+id+,学生证号为:+stuid);public static void main(String args)Student stu=new Student();/为属性赋值stu.agestu.age=20;=20;/错误
9、,不能继承父类私有成员变量stu.name=张三;/为姓名赋值Student.id=001;/为静态成员变量id编号赋值stu.stuid=2018001;/为学生证号赋值/调用方法stu.showAgestu.showAge();();/错误,不能继承父类私有成员方法Student.showId();stu.showName();stu.display();7.2.1继承的特点 程序运行结果如下:id编号为:001 姓名为:张三 姓名为:张三,id编号为:001,学生证号为:2018001 通过程序运行结果可以发现,Student类继承了Person类,所以它具有Person类非私有的成员变
10、量和方法,调用Person类的属性和方法就像调用自己的属性和方法一样。7.2.1继承的特点类的继承性具有如下的特点:(1)Java类继承只支持单继承。(2)子类能够继承父类的非私有成员变量和成员方法,包括类成员变量和类成员方法。(3)子类不能继承父类的构造方法。因为父类构造方法创建的是父类对象,子类必须声明自己的构造方法,创建子类自己的对象。(4)创建子类对象时,首先默认要执行父类不带参数的构造方法进行初始化。(5)子类不能删除从父类继承过来的成员。(6)子类可以增加自己的成员变量和成员方法。(7)子类可以重写继承自父类的成员变量和成员方法。(8)继承具有传递性。7.2.1继承的特点 在继承过
11、程中,子类拥有父类所定义的所有属性和方法,但父类可以通过“封装”思想隐藏某些数据,只对子类提供可访问的属性和方法。实例化一个子类对象时,会先调用父类构造方法进行初始化,再调用子类自身的构造方法进行初始化,即构造方法的执行次序是父类子类。下述代码示例了在继承关系中父类和子类构造方法的调用次序。7.2.1继承的特点【代码7.2】SubClass.javapackage com;class SuperClass int number;/声明一个属性public SuperClass()this.number=1;System.out.println(调用父类不带参数的构造方法.number=+thi
12、s.number);public SuperClass(int number)this.number=number;System.out.println(调用父类带参数的构造方法.number=+this.number);7.2.1继承的特点public class SubClass extends SuperClasspublic SubClass()this.number=20;System.out.println(调用子类不带参数的构造方法.number=+this.number);public SubClass(int number)this.number=number;System.
13、out.println(调用子类带参数的构造方法.number=+this.number);public static void main(String args)SubClass s1=new SubClass();System.out.println(s1.number=+s1.number);SubClass s2=new SubClass(15);System.out.println(s2.number=+s2.number);7.2.1继承的特点 程序运行结果如下:调用父类不带参数的构造方法.number=1 调用子类不带参数的构造方法.number=20 s1.number=20
14、调用父类不带参数的构造方法.number=1 调用子类带参数的构造方法.number=15 s2.number=15 通过运行结果可以发现,在构造一个子类对象时,会首先调用父类不带参数的构造方法进行初始化,而后再调用子类的构造方法进行初始化。在上述代码中,如果注销掉父类不带参数的构造构造方法,会发现子类的两个构造方法都报错;如果注销掉父类带参数的构造方法,运行结果完全一样。7.2.2方法的重写方法的重写 子类继承了父类,子类也是一个特殊的父类。大部分时候,子类总是以父类为基础,额外增加新的属性和方法。但有一种情况例外,就是子类需要重写父类的方法。方法的重写也是多态的一种。例如,鸟类都包含了飞翔
15、方法,其中有一种特殊的鸟不会飞翔,那就是鸵鸟,因此它也将从鸟类继承飞翔方法,但这个方法明显不适合鸵鸟,为此,鸵鸟需要重写鸟类的飞翔方法。这种子类包含与父类同名方法的现象被称为方法重写,也被称为方法覆盖(Override)。可以说子类重写了父类的方法,也可以说子类覆盖了父类的方法。下述代码演示了鸵鸟对鸟类飞翔方法的重写。7.2.2方法的重写【代码7.3】OverrideExample.javapackage com;class Bird /定义Bird类的fly()方法 public void fly()System.out.println(我是鸟类,我能在天空自由自在地飞翔.);class O
16、strich extends Bird /重写Bird类的fly()方法 public void fly()System.out.println(我虽然是鸟类,我却不能在天空飞翔,我只可以在陆地上奔跑.);public class OverrideExample public static void main(String args)Ostrich os=new Ostrich();/创建Ostrich的对象os.fly();/执行Ostrich对象的fly()方法 7.2.2方法的重写 程序运行结果如下:我虽然是鸟类,我却不能在天空飞翔,我只可以在陆地上奔跑.执行上面的程序可以看出,执行os
17、.fly()时,执行的不再是Bird类的fly()方法,而是执行的Ostrich类的fly()方法。7.2.2方法的重写 方法的重写要遵循以下几点原则:(1)方法名、返回值类型、参数列表必须完全相同。(2)子类方法声明抛出的异常类应该比父类方法声明抛出的异常类更小或相等。(3)子类方法的访问权限应比父类方法的访问权限更大或相等。(4)覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法一个是实例方法。例如,下述代码将会引发编译错误。7.2.2方法的重写class Father public static double add(int a,int b)return a+b;cl
18、ass Son extends Father public double add(int a,int b)return a+b;7.2.2方法的重写 当子类覆盖了父类方法后,子类的对象将无法访问父类中被覆盖的方法,但可以在子类方法中调用父类中被覆盖的方法。如果需要在子类方法中调用父类中被覆盖的方法,则可以使用super(被覆盖的是实例方法)关键字或者父类类名(被覆盖的是类方法)作为调用者来调用父类中被覆盖的方法。如果父类方法具有private访问权限,则该方法对其子类是隐藏的,因此子类无法重写该方法。如果子类中定义了一个与父类private方法完全相同的方法,依然不是重写,只是相当于在子类中重
19、新定义了一个新方法。例如下面代码所示。7.2.2方法的重写class Father private double add(int a,int b)return a+b;class Son extends Father /此处不是方法重写,而是增加了一个新方法 private double add(int a,int b)return a+b;另外,父类方法和子类方法之间也可以发生方法重载现象,因为子类会获得父类方法,如果子类定义了一个与父类方法名相同而参数列表不同的方法,那么就形成了父类方法和子类方法的重载。7.2.3super关键字super关键字 super是Java提供的一个关键字,su
20、per用于限定该对象调用它从父类继承得到的属性和方法。正如this一样,super也不能出现在static修饰的方法中。super关键字代表父类对象,其主要用途有两种。一种是在子类的构造方法中调用父类的构造方法;另一种是在子类方法中访问父类的属性和方法。7.2.3super关键字 1.1.调用父类构造方法调用父类构造方法 在Java中,子类不能继承父类的构造方法,但子类构造方法里可以通过super调用父类构造方法,执行父类构造方法里的初始化代码,类似于我们前面学习的this调用同类重载的构造方法一样。在一个构造方法中调用另一个重载的构造方法使用this调用来完成,在子类构造器中调用父类构造方法
21、用super调用来完成。super关键字调用父类构造方法的基本语法如下:super(参数列表);其中,使用super调用父类构造方法必须放在子类构造方法方法体的第一行,所以this调用和super调用不会同时出现。参数列表里面参数初始化的属性,必须是从父类继承得到的属性,而不是该类自己定义的属性。下述代码示例了super调用父类构造方法的应用,代码如下所示。7.2.3super关键字【代码7.4】StudentTwo.javapackage com;class Person int age;/年龄 String name;/姓名 public Person(int age,String nam
22、e)this.age=age;this.name=name;public class StudentTwo extends Person String stuid;/学生证号 public StudentTwo(int age,String name,String stuid)super(age,name);this.stuid=stuid;7.2.3super关键字 void display()System.out.println(姓名为:+name+,年龄为:+age+,学生证号为:+stuid);public static void main(String args)StudentTwo
23、 stu=new StudentTwo(20,张三,001);stu.display();程序运行结果如下:姓名为:张三,年龄为:20,学生证号为:001 从上面程序中可以看出,使用super调用和使用this调用很相似,区别只是在于super调用的是其父类的构造方法,this调用的是同一个类中重载的构造方法。7.2.3super关键字 不管我们是否使用super调用父类构造方法,子类构造方法总会调用父类构造方法一次。子类调用父类构造方法分如下几种情况。(1)子类构造方法方法体第一行使用super显示调用父类构造方法,系统将根据super调用里传入的实参列表调用父类对应的构造方法。(2)子类构
24、造方法方法体的第一行代码使用this显示调用本类中重载的构造方法,系统将根据this调用里传入的实参列表调用本类中的另一个构造方法。执行本类中另一个构造方法时即会调用父类构造方法。(3)子类构造方法中既没有super调用,也没有this调用,系统将会在执行子类构造方法之前,隐式调用父类无参数的构造方法。不管上面哪种情况,当调用子类构造方法来初始化子类对象时,父类构造方法总会在子类构造方法之前执行。根据继承的传递性可以推出,创建任何Java对象时,最先执行的总是java.lang.Object类的构造方法。7.2.3super关键字2.2.调用父类的属性和方法调用父类的属性和方法 当子类定义的属
25、性与父类的属性同名时,这样子类从父类继承的这个属性将被隐藏。如果需要使用父类被隐藏的属性,可以使用“super.属性名”格式来引用父类的属性。当子类重写了父类的方法时,可以使用“super.方法名()”格式来调用父类的方法。下述代码示例了super关键字调用父类隐藏的属性和重写的方法,代码如下所示。7.2.3super关键字【代码7.5】SuperExample.javapackage com;class Person int age=30;/年龄 static String name=张三;/姓名 String id=001;/证件号码 void display()System.out.pr
26、intln(调用父类中的display()方法。姓名为:+name+,年龄为:+age+,证件号码为:+id);7.2.3super关键字class Student extends Person int age=18;static String name=李四;/姓名 void display()System.out.println(调用子类中的display()方法。姓名为:+name+,年龄为:+age+,证件号码为:+id);void print()System.out.println(父类中的年龄属性为:+super.age);System.out.println(父类中的姓名属性为:
27、+Person.name);System.out.println(子类中的年龄属性为:+age);System.out.println(子类中的姓名属性为:+name);super.display();/调用父类中的display()方法display();/调用子类中的display()方法 7.2.3super关键字public class SuperExample public static void main(String args)Student stu=new Student();stu.print();程序运行结果如下:父类中的年龄属性为:30父类中的姓名属性为:张三子类中的年龄
28、属性为:18子类中的姓名属性为:李四调用父类中的display()方法。姓名为:张三,年龄为:30,证件号码为:001调用子类中的display()方法。姓名为:李四,年龄为:18,证件号码为:0017.2.3super关键字 从上面的程序可以看出,如果子类重写了父类的方法和隐藏了父类定义的属性,如果需要调用父类被重写的方法和被隐藏的属性,我们可以通过super关键字来实现。如果子类没有隐藏父类的属性,那么在子类实例中访问该属性时,则不须使用super关键字。如果在某个方法中访问名为a的属性,则系统查找a的顺序为:(1)查找该方法中是否有名为a的局部变量。(2)查找当前类中是否包含有名为a的属
29、性。(3)查找该类的直接父类中是否包含有名为a的属性,依次上溯到所有父类,直到java.lang.Object类,如果最终没有找到该属性,则系统出现编译错误。当程序创建一个子类对象时,系统 不仅会为该类中定义的实例变量分配内存,也会为它从父类继承得到的所有实例变量分配内存,即使子类定义了与父类中同名的实例变量,都同样分配内存。例如,A类定义了2个属性,B类定义了3个属性,C类定义了2个属性,并且A类继承了B类,B类继承了C类,那么创建A类对象时,系统将会分配2+3+2个内存空间给实例变量。7.2.4final关键字final关键字 final关键字表示“不可改变的,最终的”的意思,可用于修饰类
30、、变量和方法。当final关键字修饰变量时,表示该变量一旦被初始化,就不可被改变的量,即常量;当final关键字修饰方法时,表示该方法不可被子类重写,即最终方法;当final关键字修饰类时,表示该类不可被子类继承,即最终类。7.2.4final关键字 1.final 1.final成员变量成员变量 在Java语法中规定,final修饰的成员变量必须由程序员显示地指定初始值。final修饰的类成员变量和实例成员变量能指定初始值的地方如下:(1)类成员变量必须在静态初始化块中或声明该变量时指定初始值。(2)实例成员变量必须在非静态初始化块、声明该变量时或构造方法中指定初始值。下述代码示例了fina
31、l修饰的成员变量的各种具体情况。代码如下所示。7.2.4final关键字【代码7.6】FinalVariableExample.javapackage com;public class FinalVariableExample final int a=10;/定义final成员变量时初始化值 final static double b=9.8;/定义final类成员变量时初始化值 final String str;final char c;final static byte d;/在非静态块中为final成员变量赋初始值 c=a;/在静态块中为final成员变量赋初始值 staticd=r;/
32、在构造方法中为final成员变量赋初始值 public FinalVariableExample()str=String;7.2.4final关键字 public static void main(String args)FinalVariableExample fve=new FinalVariableExample();System.out.println(a的值为:+fve.a);System.out.println(b的值为:+FinalVariableExample.b);System.out.println(c的值为:+fve.c);System.out.println(d的值为:
33、+FinalVariableExample.d);System.out.println(str的值为:+fve.str);程序运行结果如下:a的值为:10 b的值为:9.8 c的值为:a d的值为:114 str的值为:String 与普通成员变量不同的是,final成员变量必须由程序员显示初始化,系统不会对final成员变量进行隐式初始化。7.2.4final关键字 2.final 2.final方法方法 使用final修饰的方法不能被子类重写。如果某些方法完成了关键性的、基础性的功能,不需要或不允许被子类改变,则可以将这些方法声明为final的。下述代码示例了final修饰的方法不能被重写
34、。代码如下所示。【代码7.7】FinalMethodExample.javapackage com;class FinalMethod public final void method()System.out.println(“final修饰的方法不能被重写,可以被继承);class FinalMethodExample extends FinalMethod public final void method()public final void method()/错误,final方法不能被重写 public static void main(String args)FinalMethodEx
35、ample fme=new FinalMethodExample();fme.method();注销掉报错代码,程序运行结果如下:final修饰的方法不能被重写,可以被继承7.2.4final关键字 3.final 3.final类类 使用final修饰的类不能被继承。例如下述代码所示:final class Father class Son extends Father/错误,final类不能被继承 一个final类中的所有方法都被默认为final的,因此final类中的方法不必显示声明为final。其实,Java基础类库中的类都是final类,如String、Integer等,都无法被子类
36、继承。第第3 3节节part多态 多态性一般发生在子类和父类之间,就是同一种事物,由于条件不同,产生了不同的结果。多态性分为静态性多态和动态性多态。前面接触到的方法的重载和重写就是静态性多态。本节主要介绍的是动态性多态。Java引用变量有两个类型:一个是编译的类型,一个是运行时类型。编译时类型由声明变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,则称为动态性多态。多 态本节概述7.3.1上转型对象上转型对象 所谓上转型对象就是一个父类类型的引用变量可以指向其子类的对象,即将子类对象赋给一个父类类型的引用变量。上转型对象能够访问到父类所有成员变量和父
37、类中没有被子类重写的方法,还可以访问到子类重写父类的方法,而不能访问到子类新增加的成员变量和方法。下述代码示例了上转型对象的访问特性,代码所示如下。7.3.1上转型对象【代码7.8】PolymorphismExample.javapackage com;class BaseClass int a=6;public void base()System.out.println(父类的普通方法);public void test()System.out.println(父类将被子类重写的方法);7.3.1上转型对象public class PolymorphismExample extends Ba
38、seClass int a=20;/重新定义一个实例变量a,隐藏父类的实例变量int b=10;public void test()System.out.println(子类重写父类的方法);public void sub()System.out.println(子类新增的普通方法);7.3.1上转型对象public static void main(String args)/编译时类型和运行时类型完全一致,不存在多态BaseClass bc=new BaseClass();System.out.println(bc.a=+bc.a);bc.base();bc.test();Polymorph
39、ismExample sc=new PolymorphismExample();System.out.println(sc.a=+sc.a);sc.base();sc.test();/编译时类型和运行时类型不一致,多态性发生BaseClass bsc=new PolymorphismExample();System.out.println(bsc.a=+bsc.a);bsc.base();bsc.test();/下面两行代码错误,原因是运行时多态不能调用子类新增的属性和方法/bsc.sub();/System.out.println(bsc.b);程序运行结果如下:bc.a=6父类的普通方法父
40、类将被子类重写的方法sc.a=20父类的普通方法子类重写父类的方法bsc.a=6父类的普通方法子类重写父类的方法7.3.2引用变量的强制类型转换引用变量的强制类型转换 编写Java程序时,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它实际所引用的对象确实包含该方法。如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制转换成运行时类型,强制类型转换需要借助类型转换运算符。类型转换运算符是一对小括号,类型转换运算符的用法是:(type)variable,这种用法可以将variable变量转换成一个type类型的变量。这种强制类型转换不是万能的,当进行强制类型转换时
41、需要注意:(1)基本类型之间的转换只能在数值类型之间进行,这里所说的数值类型包括整数型、字符型和浮点型。但数值类型和布尔类型之间不能进行类型转换。(2)引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出现错误。如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(即编译时类型是父类类型,而运行时类型是子类类型),否则将会运行时引发ClassCastException异常。下述代码示例了引用变量强制类型的转换,代码所示如下。7.3.2引用变量的强制类型转换【代码7.9】SchoolStudent.ja
42、vapackage com;class Student String name=张三;int age=20;public void study()System.out.println(学生就要好好学习,天天向上);public class SchoolStudent extends Student String school=重庆大学;public void major()System.out.println(我的专业是计算机软件技术);7.3.2引用变量的强制类型转换public static void main(String args)Student stu=new SchoolStude
43、nt();if(stu instanceof SchoolStudent)SchoolStudent stu2=(SchoolStudent)stu;System.out.println(姓名为:+stu2.name+,年龄为:+stu2.age+,学校为:+stu2.school);stu2.major();stu2.study();程序运行结果如下:姓名为:张三,年龄为:20,学校为:重庆大学我的专业是计算机软件技术学生就要好好学习,天天向上 考虑到进行强制类型转换时,可能会出现异常,因此进行引用变量强制类型转换之前先通过instanceof运算符来判断是否可以成功转换,从而避免出现Cla
44、ssCastException异常,这样可以保证程序更加健壮。7.3.3instanceof运算符instanceof运算符 instanceof运算符是一个二目运算符,左边操作数通常是一个引用类型的变量,右边操作数通常是一个类(也可以是接口),它用于判断左边的对象是否是后面的类,或者其子类、实例类的实例。如果是,则返回true,否则返回false。在使用instanceof运算符时需要注意:instanceof运算符左边操作数的编译时类型要么与右边的类相同,要么具有父子继承关系,否则会引起编译错误。下述代码示例了instanceof运算符的用法,代码示例如下。7.3.3instanceof运
45、算符【代码7.10】InstanceofExample.javapackage com;public class InstanceofExample public static void main(String args)Object hello=hello;System.out.println(字符串是否是Object类的实例:+(hello instanceof Object);System.out.println(字符串是否是String类的实例:+(hello instanceof String);System.out.println(字符串是否是Math类的实例:+(hello in
46、stanceof Math);System.out.println(字符串是否是Comparable接口的实例:+(hello instanceof Comparable);String str=Hello;/下面代码编译错误 System.out.println(字符串是否是Math类的实例:+(strstr instanceofinstanceof Math)Math);注销错误代码,程序运行结果如下:字符串是否是Object类的实例:true字符串是否是String类的实例:false字符串是否是Math类的实例:false字符串是否是Comparable接口的实例:true7.3.3i
47、nstanceof运算符 上述程序通过定义Object hello=hello,这个变量的编译时类型是Object类,但实际类型是String类。因为Object类是所有类、接口的父类,因此可执行hello instanceof Math和hello instanceof Comparable等。但如果使用String str=Hello代码定义的str变量,就不能执行str instanceof Math,因为str的编译时类型是String类,而String类既不是Math类型,也不是Math类型的父类,所以这行代码编译就会出错。instanceof运算符的作用是在进行引用变量强制类型转换
48、之前,首先判断前一个对象是否是后一个类的实例,是否可以转换成功,从而保证代码的健壮性。instanceof和(type)variable是Java提供的两个相关的运算符,通常先用instanceof判断一个对象是否可以强制类型转换,然后再使用(type)variable运算符进行强制类型转换,从而保证程序不会出现错误。第第4 4节节part内部类 Java语法中,允许在一个类的类体之内再定义一个类,这个定义在其他类内部的类就被称为内部类(或嵌套类),包含内部类的类也被称为外部类(或宿主类)。内部类主要有如下作用。(1)内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包的其他类
49、访问该类。(2)内部类成员可以直接访问外部类的私有数据,因为内部类被当成外部类成员,同一个类的成员之间可以互相访问。但外部类不能访问内部类的成员。(3)匿名内部类适合用于创建那些仅需要一次使用的类。Java内部类主要分为非静态内部类、局部内部类、静态内部类和匿名内部类四种。内 部 类本节概述7.4.1非静态内部类 定义内部类非常简单,只要把一个类放在另一个类内部定义即可。此处的“类内部”包括类中的任何位置,甚至在方法中也可以定义内部类(局部内部类)。大部分时候,内部类都被作为成员内部类定义,而不是作为局部内部类。成员内部类是一种与属性、成员方法、构造方法和初始化语句块相似的类成员。局部内部类和
50、匿名内部类则不是类成员。成员内部类分为静态内部类和非静态内部类两种,使用static修饰的成员内部类是静态内部类,没有使用static修饰的成员内部类是非静态成员内部类。由于内部类作为其外部类的成员,所以可以使用任意访问权限控制符如private、protected和public等修饰。下述代码示例了非静态内部类的定义和使用,代码如下所示。非静态内部类7.4.1非静态内部类【代码7.11】InnerClassExample1.javapackage inner;import inner.Cow.CowLeg;class Cow/外部类属性private double weight;/外部类构造