Java第七章继承、多态、抽象类和接口

j目录

7.1 类的继承

7.2Object类


继承和多态是面向对象开发中非常重要的一组概念。继承和多态使用得当,整个程序的架构将变得非常有弹性,同时可以减少代码的冗余性。继承机制下,用户可以复用一些定义好的类,减少重复代码的编写。多态机制下,用户可以动态调整对象的调用,降低对象之间的依存关系。为了优化继承与多态,一些类除了可继承父类,还需要使用接口的形式。Java中的类可以同时实现多个接口,接口被用来建立类与类之间关联的标准。正因为具有这些灵活、高效的机制,Java语言才更具有生命力。

7.1 类的继承

继承在面向对象开发思想中是一个非常重要的概念,它使整个程序架构具有一定的弹性。在程将中复用一些已经定义完善的类,不仅可以减少软件开发周期,也可以提高软件的可维护性和可扩展性,本节将详细讲解类的继承。

在Java 语言中,一个类继承另一个类需要使用关键字extends,关键字extends的使用方法如下:class Child extends Parent{}

因为Java只支持单继承,即一个类只能有一个父类,所以类似下面的代码是错误的:

class Child extends Parent1,Parents2{}

子类在继承父类之后,创建子类对象的同时也会调用父类的构造方法。

【例7.1】创建子类对象,观察构造方法执行顺序

父类Parent 和子类Child 都各自有一个无参的构造方法,在main()方法中创建子类对象时,Java虚拟机会先执行父类的构造方法,然后再执行子类的构造方法。

运行结果如下

 

 子类继承父类之后可以调用父类创建好的属性和方法。

【例7.2】在电话类基础上衍生的建对的(实例位置:资源包\TMSI72)

Telephone 电话类作为父类衍出Mobile手机关,手机类可以直接使用电话类的按键属性和拨打电

话行为。 class Telephone{

//电话类

String button ="button:0~9";

//成员属性,10个按键

void call(){

//拨打电话功能

System.out.printin("开始拨打电话");

子}} class Mobile extends Telephone{

//手机类继承电话类

String screen="screen:液晶屏";

//成员属性,液晶屏幕

} 

public class Demo2{ public static void main(Stringl args) {

下:

Mobile motto=new Mobile(); System.out.println(motto.button);

//子类调用父类属性

System.out.println(motto.screen);

//子类调用父类没有的属性

motto.call();

//子类调用父类方法 }}
运行结果如下:

button:0~9

为f

screen:液晶屏 开始拨打电话 

子类Mobile 类仅创建了一个显示屏属性,剩余的其他属性和方法都是从父类Telephone 类中继

承的。 Java虽然不允许同时继承两个父类,但不代表没有多继承的关系,可以通过类似“祖父>父>儿子>

孙子”的方式实现多代继承。

例如,绝大多数动物有眼睛、鼻子和嘴,犬类继承动物类,所以犬类也有眼睛、鼻子和嘴,哈士

get

奇是犬类的一个品种,犬类有的特性哈士奇类都有。但哈士奇的眼睛、鼻子和嘴并不是从犬类继承的,

而是从动物类继承的。用Java代码编写则如下:

 class Animal{    //创建类

//父类:动物类

Eye eye; Mouth mouth; Nose nose; {   //定义变量

class Dog extends Animal{}

//子类:犬类

class Husky extends Dog{}   //创建类

//孙子类:哈士奇类

这三个类的继承关系就是Husky类继承Dog类继承Animal类,虽然Husky类没有直接继承Animal类,但是Husky类可以调用Animal类提供的可被继承的成员变量和方法。

7.2Object类

在开始学习使用class关键字定义类时,就应用到了继承原理,因为在Java中所有的类都直接或间接继承了java.lang.Object类。Object类是比较特殊的类,它是所有类的父类,是Java类层中的最高层类。用户创建一个类时,除非已经指定要从其他类继承,否则它就是从java.lang.Object类继承而来的。Java中的每个类都源于java.lang.Object类,如String类、Integer类等都是继承于Object类。除此之外,自定义的类也都继承于Object类。由于所有类都是 Object 类的子类,所以在定义类时可省略 extends等价于 Object在Object 类中,主要包括 clone()、finalize()、equals()、toString()等方法,其中常用的两个方法为equals()和 toString()方法。由于所有的类都是 Object 类的子类,所以任何类都可以重写Object类中的方法。

注意
Object 类中的getClass0、notifyO、notifyAll0)、waitO等方法不能被重写,因为这些方法被定义为 final 类型。
下面详细讲述Object类中的几个重要方法。
1. getClass()方法
getClass()方法是Object类定义的方法,它会返回对象执行时的 Class 实例,然后使用此实例调用 getNameO方法可以取得类的名称。语法如下:
getClass()getname();
可以将getClass()方法与toString0方法联合使用。
2. toString()方法
toString()方法的功能是将一个对象返回为字符串形式,它会返回一个 String实例。在实际的应用中通常重写toString0)方法,为对象提供一个特定的输出模式。当这个类转换为字符串或与字符串连接时,将自动调用重写的toString()方法。

【例7.3】让学生自我介绍
创建Student类,重写toString0方法,使该类的对象可以自定义输出自己的姓名和年龄 

public class Student {
String name; int age;      //定义数据类型和名字
public Student(String name, int age){    //输入参数
this.name =name; this.age = age;    //指定位置
  
public String toString(){   //调用方法
return"我叫"+name+",今年"+age+"岁。"  //返回参数

public static void main(Stringlargs){   //调用方法
Student s1=newStudent(“张三,16) System.outprintln(s1);      //“搞对象”
Student s2=new Student("李四",19); System.out.println(s2);}//“搞对象”
}
运行结果如下:
我叫张三,今年16岁。我叫李四,今年19岁。

3.equals()方法
在Java,种比较对象的方式,分别为“-”运算符与equalsO方法。两者的区别在二“”比较的是两个对象引用内存地址是否相等,而equalsO方法比较的是两个对象的实际内容。
下面的实例。
【例7.4】根据身份证号判断是否为同一人(实例位置:资源包\TM\sI\7\4)
为People类建身份号和个属性,重写equals方法,仅以身份证号作为区分条件。创个People对象,用equalsO方法和“--”运算符来判断是否存在多个对象代表同一个人。

public class People {
int id;
//身份证号
String name;	//名字	
public People(int id,String name){
this.id=id;
this.name=name;
public boolean equals(Object obj){	//重写Object 类的equals()方法	
if(this == obj)	//如果参数与本类是同一个对象	
return true;
if (obj == null)	//如果参数是null	
return false;
if(getClass()!=obj.getClass())	//如果参数与本类类型不同	
return false;
People other=(People)obj;	//将参数强转成本类对象	
if(id!= other.id)	//如果两者的身份证号不相等
return false;
 return true;}
public String toString() {
//重写Object 类的toString()方法
return name;	1/只输出名字	
}
publie static void main(Stringllargs){
People p1=nw People(220"tomy People p2=newPeople(220,“汤姆“) People p3=new People(330,张三) Object o =new Object():
System.out,printin(p1+"与"+p2+"是否为同一人?");
8ystemoutprintn("equals()方法的结果:"+p1.equals(p2)); Systemoutprintin(s=运算符的结果:"+(p1==p2));
System.out,printin();
Systemout.print(p1+“与"+p3+“是否为同一人?");
 System.out.println(p1.equals(p3));
System.out.print(p1+"与"+0+"是否为同一人?"); 
System.out.printin(p1.equals(o));}
运行结果如下:
tom与汤姆是否为同一人? equals()方法的结果:
true==运算符的结果:false
tom与张三是否为同一人?false
tom 与 java.lang.Object@48cf768c是否为同一人?false

从这个结果可以看出,“tom”和“汤姆”虽然名字不同,但是两者的身份证号相同,所以equals0方法判断出了这两个对象实际上是同一个,而“=-”运算符无法做出有效判断。如果两个对象的身份证号不同,或者两个对象类型都不同,equals()方法就会认为两者不是同一个人。

7.3对象类型的转换

对象类型的转换在Java编程中经常遇到,主要包括向上转型与向下转型,接下来讲解对象类型的转换。

7.3.1向上转型

向上转型可以被理解为将子类类型转换为父类类型的对象,即把子类类型的对象直接赋值给父类类型的对象,进而实现按照父类描述子类的效果。

例7.5 tom是谁?
使用向上转型模拟如下场景:这里有一个人他叫tom,他是一名教师。
class people{}        // 创建类
class teacher extends people{}        //继承
public class Demo3{  //创建类
    public static void main(String[]args){//主函数
        people tom = new teacher();//调用构造器搞对象

}
}

在上述代码中,“people tom = new people();"运用了向上转型的语法,那么该如何理解这行代码的含义呢?理解方式如图

图为向上转型结合实例的说明

综上所述,因为人类(People)是教师类(Teacher)的父类,所以通过向上转型,能够把教师类
(Teacher)类型的对象(newTeacherO;)直接赋值给人类(People)类型的变量(tom)。也就是说,
进行向上转型,父类类型的对象可以引用子类类型的对象。而且,向上转型是安全的,因为向上转型是将一个较具体的类的对象转换为一个较抽象的类的对象象。例如,可以说平行四边形是四边形,但不能说四边形是平行四边形。
那么,使用向上转型的过程中,父类的对象是否可以以调用子类独有的属性或者方法呢?下面以父类四边形类的对象调用子类平行四边形类独有的属性为例,阐述这一问题。
例如,四边形类是平行四边形类的父类,用 Quadrang gle表示四边形类、用Parallelogram表示平行四边形类;在Parallelogram类中,定义一个值为4的表示底边长度的变量edges;在 Parallelogram类的主方法中,运用向上转型,把平行四边形类(Parallelogram)类型的对象直接赋值给四边形类(Quadrangle)类型的对象。人为强制四边形类(Quadrangle)类型的对象可以调用变量edges,并将 edges的值修改为6,Eclipse能通过编译么?Eclipse的效果图如图73所示,从图中可以看出,Eclipse 在相关代码处显示波浪线等错误标志,说明代码有误。

class Quadrangle{    //四边形类
}
public class parallelogram extends Quadrangle{  //平行四边形类,继承了四边形类
  int edges = 4;                                //底边的长度为4
    public static void main(String args[]){
        Quadrangle  p  =  new  parallelogram();
        p.edges = 6; //四边形类(Quadrangle)类型的对象p调用变量edges,并将edges的值修改为6
}
}

图为父类的对象是否可以调用子类独有的属性

综上所述,在运用向上转型的过程中,父类的对象无法调用子类独有的属性或者方法。

7.3.2向下转型

向下转型可以被理解为将父类类型的对象转换为小子类类型的对象。但是,运用向下转型,如果把一个较抽象的类的对象转换为一个较具体的类的对象,这样的转型通常会出现错误。例如,可以说某只鸽子是一只鸟,却不能说某只鸟是一只鸽子。因为鸽子是具体的,鸟是抽象的。一只鸟除了可能是鸽子,还有可能是老鹰、麻雀等。因此,向下转型是不安全的。
 

【例7.6】谁是鸽子?
编写代码证明“可以说某只鸽子是一只鸟,却不能能说某只鸟是一只鸽子”:鸟类是鸽子类的父类,用Bird表示鸟类,用pigeon表示鸽子类。
class Bird{}
class pigeon extends Bird{}
public class Demo4{
    public static void main(String[]args){
        Bird bird =new pigeon();   //某只鸽子是一只鸟
        pigeon pigeon =new bird;   //某只鸟是一只鸽子
}
}

本题在运行之前,会报错,因为父类对象不能直接赋值给子类对象。

 如果想要告诉编译器“某只鸟就是一只鸽子”,应该如何修正?答案就是强制类型转换。也就是说,要想实现向下转型,需要借助强制类型转换。语法如下:
子类类型  子类对象  =  (子类类型)父类对象;
因此,要想实现把鸟类对象转换为鸽子类对象(相当于告诉编译器“某只鸟就是一只鸽子”),需要将图7.4中第8行代码修改为:

Pigeon pigeon=(Pigeon)bird;    //通过强制类型转换,告诉编译器“某只鸟就是一只鸽子” 

   
注意
(1)两个没有继承关系的对象不可以进行向上转型或者向下转型。
(2)父类对象可以强制转换为子类对象,但有一个前提:这个父类对象要先引用这个子类对象。
如果把上述实例中的代码:

Bird bird=new Pigeon();	//某只鸽子是一只鸟	
Pigeon pigeon=(Pigeon) bird;	//通过强制类型转换,告诉编译器“某只鸟就是一只鸽子”	

修改为如下代码:

Bird bird= new Bird();	//某只鸽子是一只鸟	
Pigeon pigeon=(Pigeon)bird;	//通过强制类型转换,告诉编译器“某只鸟就是一只鸽子”

7.4 使用instanceof关键字判断对象类型

当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例,发ClassCatException异常,所以在执行向下转型之前需要养成一个良好的习惯,就是判断父类对象是否为子类对象的这个判断通常使用instancof关字成。使用instanceof关键字判是否一个类实现了某个口(行),也可以用来判断一个实例对象是否属于一个类。
instanceof的语法格式如下:

myobject instanceof ExampleClass


使用instanceof关键字的表达式返回值为布尔值。如果返回值为true,说明myobject对象为 ExampleClass的实例对象;如果返回值为false,说明myobject对象不是ExampleClass的实例对象。
误区警示
instanceof是Java语言的关键字,Java 语言中的关键字都为小写。

下面来看一个例子

【例7.7】分析几何图形之间的继承关系
创建 Quadrangle 四边形类、Square 正方形类和Circular 圆形类。其中,Square 类继承 Quadrangle类,在主方法中分别创建这些类的对象,然后使使用instanceof关键字判断它们的类型并输出结果。
class Quadrangle {}
class Square extends Quadrangle{} class Circular { }
public class Demo5 {
public static void main(String args){
Quadrangleq=new Quadrangle();	//四边形对象	
Square s=new Square();	//正方形对象	
System.out.println(q instanceof Square	);	//判断四边形是否为正方形的子类	
System.out.println(s instanceof Quadra	angle);	//判断正方形是否为四边形的子类
System.out.println(q instanceof circular);  //判断正方形是否为圆形的子类
}
}

在运行这道题前,会报错,因为四边形类与圆形类没有继承关系,所以不能用关键词,正确结果如下

false
true

7.5    方法的重载    

在第6章中我们曾学习过构造方法,知道构造方法的名称由类名决定,所以构造方法只有一个名称。如果希望以不同的方式来实例化对象,就需要使用多个构造方法来完成。由于这些构造方法都需要根据类名进行命名,为了让方法名相同而形参不同的构造方法同时存在,必须用到方法重载。虽然方法重载起源于构造方法,但它也可以应用到其他方法中。本节将讲述方法的重载。
方法的重载就是在同一个类中允许存在一个以上的同名方法,只要这些方法的参数个数或类型不同即可。为了更好地解释重载,来看下面的实例。

【例7.8】编写不同形式的加法运算方法(实例位置:资源包\TM\sI\7\8)
创建OverLoadTest类,在该类中编写add)方法的多个重载形式,然后在主方法中分别输出这些方法的返回值。
public class OverLoadTest{
public static int add(int a, int b){	//定义一个方法	
return a + b;}
public static double add(double a, double b){	//与第一个方法名称相同、参数类型不同	
return a + b;
public static int add(int a){	//与第一个方法参数个数不同	
return a;
}
public static int add(int a, double b){	//先int参数,后double参数	
//输出int参数值
return a;
public static int add(double a, int b){	//先double参数,后int参数	
return b;	//输出int参数值	
public static void main(String args) {
System.outprintin("调用add(int,int)方法:"+add(1,2));
System.out.println("调用add(doubledouble)方法:"+add(2.1,3.3)); 
System.out.printin("调用add(int)方法:"+add(1));
System.out.printin("调用add(int,double)方法:"+add(5,8.0)) 
System.outprintin("调用add(doubleint)方法:"+add(5.0,8));
}
}

运行结果如下:
调用add(int,int)方法:3
调用add(doubledouble)方法:5.4调用add(int)方法:1
调用add(int,double)方法:5调用add(doubleint)方法:8

在本实例中分别定义了5个方法,在这5个方法中,前两个方法的参数类型不同,并且方法的返回值类型也不同,所以这两个方法构成重载关系;前两个方法与第3个方法相比,第3个方法的参个数少于前两个方法,所以这3个方法也构成了重载关系;最后两个方法相比,发现除了参数的出顺序不同,其他都相同,同样构成了重载关系。

参数个数可以确定两个方法是否具有重载关系时,会想到定义不定长参数方法。不定长方法的语法如下:
返回值 方法名(参数数据类型…参数名称)
如果将上述代码放在例7.8 中,关键代码如例 7.9所示。

【例7.9】使用不定长参数重载加法运算方法(实例位置:资源包\TM\s1\79)
创建 OverLoadTest2类,该类编写add法的多种重载形式,并编写该方法的不定长参数式。然后在主方法中调用这些重载方法,并输出返回值。
public class OverLoadTest2{
public static int add(int a, int b){
return a + b;
public static double add(double a, double b) {
return a + b;}
public static int add(int a) {
return a;
public static int add(int a, double b) {
return a;
public static int add(double a, int b) {
return b;
public static int add(int …. a) {	//定义不定长参数方法	
ints=0;
for (int i = 0; i < a.length;i++) {	//根据参数个数做循环操作	
s += a[1);	//将每个参数累加	
}
return s;	//将计算结果返回	
public static void main(String args[){
System.out.printin("调用add(intint)方法:"+add(1,2));
System.out.printin("调用add(double,double)方法:"+add(2.1,3.3)); System.out.printn(”调用add(int)方法:"+add(1));
System.out.printin("调用add(int,double)方法:"+add(5,8.0)); System.out.printin("调用add(double,int)方法:"+add(5.0,8));
//调用不定长参数方法
System.out.printin("调用不定长参数方法:"+add(1,2,3,4,5,6,7,8,9)) System.out.printin("调用不定长参数方法:"+add(1));
}
调用add(int,int)方法:3
调用add(doubledouble)方法:5.4
调用add(int)方法:1
调用add(intdouble)方法:5
调用add(double,int)方法:8
调用不定长参数方法:45
调用不定长参数方法:1

在上述实例中,在参数列表中使用“…”形式定义不定长参数,其实这个不定长参数a就是一数组,编译器会将“int…a”这种形式看作是“int]a”,所以在add方法体做累加操作时使用到了循环语句,在循环中是根据数组a的长度作为循环条件的,最后将累加结果返回。

7.6final关键字

final被翻译为最后的,最终的,final是Java关键字,凡是被final关键字修饰过的内容都是不可改变的。

7.6.1final变量

final关键字可用于变量明,一旦该变量被设定,就不可以再改变该变量的值。通常,由finalg义的变量为常量。例如,在类中定义PI值,可以使用如下语句:

final double P/=3.14;

当在程序中使用到PI这个常量时,它的值就是3.14。如果在程中再次对定义为final的常量赋值编译器将不会接受。
final关键字定义的变量必须在声明时对其进行赋值操作。final除了可以修饰基本数据类型的常量还可以修饰对象引用。由于数组也可以被看作一个对象来引用,所以final可以修饰数组。一旦一个对象引用被修饰为final后,它就只能恒定指向一个对象,无法将其改变以指向另一个对象。一个既是stat又是final的字段只占据一段不能改变的存储空间。为了深入了解 final 关键字,来看下面的实例。

【例7.10】定义不允许被修改的常量m(实例位置:资源包\TM\sI\7\10)创建FinalData类,该类中定义表示圆周率的常量PI,并尝试修改PI的值。
public class FinalData{   //调用方法
static final double P/=3.1415926;   //定义类型
public static void main(Stringargs){   //主函数
System.outprintln("圆周率的值为:"+PI);
System.outprintln("半径为3的圆的周长为:"+(2*3*PI));//尝试修改PI的值 PI=31415927;
}

7.6.2 final 方法

将方法定义为 final类型,可以防止子类修改父类的定义与实现方式,同时定义为final的方法的执行效率要高于非final方法。在修饰权限中曾经提到过private修饰符,如果一个父类的某个方法被设置为private,子类将无法访问该方法,自然无法覆盖该方法。也就是说,一个定义为private的方法隐式被指定为final类型,因此无须将一个定义为private的方法再定义为final类型。例如下面的语句:

private final void test(){
…//省略一些程序代码}
【例7.11】使用final关键字为电视机上儿童锁(实例位置:资源包\TM\sl\7\11)
创建Dad爸爸类,给Dad类定义一个打开电视机的方法,该方法使用final关键字修饰。创建Dad类的子类Baby 类,让Baby类尝试自己重写打开电视的方法。
class Dad {  //创建类
public final void turnOnTheTV(){  //调用方法
System.outprintn("爸爸打开了电视);  //输出

class Baby extends Dad {   //继承方法
public final void turnOnTheTV(){   //调用方法
System.out.printin("宝宝也要打开电视”);}  //输出

本实例在运行之前,Eclipse 就会报出如图 7.8所示的编译错误。因为打开电视这个方法是由final修饰 的,子类无法重写。所以 Baby 想要打开电视,就只能找爸爸来打开了。

7.6.3final类

定义为final的类不能被继承。如果希望一个类不被任何类继承,并且不允许其他人对这个类进行任何改动,可以将这个类设置为final类。final 类的语法如下:
final 类名{}

如果将某个类设置为final类,则该类中的所有方法都被隐式设置为final方法,但是final类中的成员变量可以被定义为final或非final形式。

7.7多态

利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。在7.3节中已经习过子类对象可以作为父类的对象实例使用,这种将子类对象视为父类对象的做法称为“向上转假如现在要编写一个绘制图形的方法draw,如果传入正方形对象就绘制正方形,如果传入圆对象就绘制圆形,这种场景可以使用重载来实现,定义如下:

public void draw(Square s){	//绘制正方形的方法	
public void draw(Circular c){	//绘制圆形的方法	

但是这种写法有个问题:正方形和圆形都是图形,这场景细分的重载方式不仅增加了代码量,还降低了“易用度”。如果定义一个图形类,让它处理所有继承该类的对象,根据“向上转型”原则可以使每个继承图形类的对象作为draw方法的参数,然后在draw方法中做一些限定就可以根据不同图形类对象绘制相应的图形。这样处理能够很好地解决代码冗余问题,同时程序也易于维护。

【例7.12】万能的绘图方法(实例位置:资源包\TMsI\7\12)	创建 Shape图为Squareirular形类父类创m6,并在该类	
创建一个绘图用的draw0方法,参数为Shape类型,Shape子类对可以作为方法的参并且方法会根据参数的类型绘制相应的图形。
class Shape{}
class Square extends Shape {)	//图形类	
class Circular extends Shape{)
//正方形类继承图形类
public class Demo6{
//圆形类继承图形类
public static void draw(Shape s){	//绘制方法	
if(s instanceof Square){	//如果是正方形	
System.out.printin(“绘制正方形);
} else if (s instanceof Circular){	//如果是圆形	
System.out.printn(“绘制圆形”);
} else {	//如果是其他类型	
System.out.printin("绘制父类图形);}
}
public static void main(Stringargs){
draw(new Shape()); draw(new Square()); draw(new Circular());
运行结果如下:绘制父类图形
绘制正方形
绘制圆形

从本实例的运行结果中可以看出,以不同类对象为参数调用draw0方法,可以处理不同的图形绘制问题。使用多态节省了开发和维护时间,因为程序员无须在所有的子类中定义执行相同功能的方法,避免了大量重复代码的编写。同时,只要实例化一个继承父类的子类对象,即可调用相应的方法,如果需求发生了变更,只需要维护一个draw()方法即可。

7.8抽象类与接口

通常可以说四边形具有四条边,或者更具体一点,平行四边形是具有对边平行且相等特性的特殊四边形,等腰三角形是其中两条边相等的三角形,这些描述都是合乎情理的。但仅对于“图形”这个词却不能使用具体的语言进行描述,它有几条边?它有几个角?它有多大?没有人能说清楚。同理,仅用来描述特征且极具抽象性类,在Java中被定义为抽象类。

7.8.1抽象类

在解决实际问题时,一般将父类定义为抽象类,需要使用这个父类进行继承与多态处理。回想继承和多态原理,继承树中越是在上方的类越抽象,如鸽子类继承鸟类、鸟类继承动物类等。在多态机制中,并不需要将父类初始化为对象,我们需要的只是子类对象,所以在Java语言中设置抽象类不可以实例化为对象。

使用abstract关键字定义的类称为抽象类,而使用这个关键字定义的方法称为抽象方法。抽象方法没有方法体,这个方法本身没有任何意义,除非它被重写,而承载这个抽象方法的抽象类必须被继承,实际上抽象类除了被继承没有任何意义。定义抽象类的语法如下:

public abstract class parent{
    abstract void testAbstract();  //定义抽象方法
}

反过来讲,如果声明一个抽象方法,就必须将承载这个抽象方法的类定义为抽象类,不能在非抽象类中获取抽象方法。换句话说,只要类中有一个抽象方法,此类就被标记抽象类。

抽象类被继承后需要实现其中所有的抽象方法,也就是保证以相同的方法名称、参数列表和返回值类型创建出非抽象方法,当然也可以是抽象方法。来看图

Java第七章继承、多态、抽象类和接口_第1张图片

 从图中可以看出,继承抽象类的所有子类需要将抽象类中的抽象方法进行覆盖。这样在多态机制中,就可以将父类修改为抽象类,将 draw()方法设置为抽象方法,然后每个子类都重写这个方法来处理。但这又会出现我们刚探讨多态时讨论的问题,程序中会有太多冗余的代码,同时这样的父类局限性很大,也许某个不需要draw()方法的子类也不得不重写draw()方法。如果将draw()方法放置在另外一个类中,让那些需要draw()方法的类继承该类,不需要draw()方法的类继承图形类,又会产生新的问题:所有的子类都需要继承图形类,因为这些类是从图形类中导出的,同时某些类还需要 draw0方法,而 Java中规定类不能同时继承多个父类。为了应对这种问题,接口的概念便出现了。

7.8.2接口

接口是抽象类的延伸,可以将它看作是纯粹的抽象类,接口中的所有方法都没有方法体。对于7.8.1 节中遗留的问题,可以将draw()方法封装到一个接口中,使需要draw0)方法的类实现这个接口,同时也继承图形类,这就是接口存在的必要性。在图中,描述了各个子类继承图形类后使用接口的关系。

Java第七章继承、多态、抽象类和接口_第2张图片
接口使用 interface 关键字进行定义,其语法如下:

public interface paintable{
    void draw();     //定义接口方法可省略public abstract关键字
}

 一个类继承一个父类的同时再实现一个接口,可以写成如下形式: 

public class Parallelogram extends Quadrangle implements Paintable {
...}

注意
(1)在接口中,方法必须被定义为 public 或 abstract形式,其他修饰权限不被Java 编译器认可。或者说,即使不将该方法声明为 public 形式,它也是 public 形式。
(2)在接口中定义的任何字段都自动是 static 和 final的。

【例7.13】将绘图方法设为接口方法(实例位置:资源包\TM\sl\7\13)
将图形对象的绘图方法剥离出来,作为Paintable可绘制接口中的抽象方法。创建四边形类作为平行四边形类和正方形类的父类,同时让这两个子类实现Paintable接口。创建圆形类实现Paintable接口,但不继承四边形类。


interface Paintable{            //可绘制接口
public void draw();            //绘制抽象方法

class Quadrangle {            //四边形类
public void doAnything() {
System.out.printin("四边形提供的方法");
}
}

//平行四边形类,继承四边形类,并实现了可绘制接口
class Parallelogram extends Quadrangle implements Paintable {
public void draw() {	//由于该类实现了接口,所以需要覆盖 draw()方法	
System.out.printin("绘制平行四边形");
}
}

//正方形类,继承四边形类,并实现了可绘制接口
class Square extends Quadrangle implements Paintable{
public void draw() {
System.out.printin("绘制正方形");
}
}

//圆形类,仅实现了可绘制接口
class Circular implements Paintable{
public void draw() {
System.out.printin("绘制圆形");
}
}
public class Demo7 {            //创建类输出
public static void main(Stringl args){
Square s= new Square(); s.draw();
s.doAnything();
Parallelogram p=new Parallelogram(); p.draw();
p.doAnything();
Circular c = new Circular(); c.draw();
}
}

运行结果:

绘制正方形
四边形提供的方法
绘制平行四边形
四边形提供的方法
绘制圆形

从这个结果可以看出,“绘制”这个行为可不是四边形独有的,圆形也能绘制,所以draw0方法被独立封装在了可绘制接口中。
正方形类与平行四边形类分别继承了四边形类并实现了可绘制接口,所以正方形类与平行四边形类既可以调用绘制方法,又可以调用四边形提共的doAnything0方法。但是,圆形不属于四边形,且可以绘制,所以最后圆形对象只调用了draw0方法法。
Java 中不允许出现多重继承,但使用接口就可以实现多重继承。一个类可以同时实现多个接口,因此可以将所有需要继承的接口放置在impler nents 关键字后并使用逗号隔开。实现多个接口的语法如下:

	class 类名implements接口1,接口2…,接口n	

但这可能会在一个类中产生庞大的代码量,    因为继承一个接口时需要实现接口中所有的方法。一 个接口可以继承另一个接口,其语法如下:

interface intf1 { }
interface intf2 extends intf1 { }	//接口继承接

Java语言每一个类只能继承一个父类

子类  父类  祖父类

继承 extends

父类 和普通类写法相同

public class 父类类名{

}

子类 需要继承父类

public class 子类类名 extends 父类类名{

}

所有类的构造方法第一行都有一个隐藏的“super();”

作用是在执行该构造方法之前调用其父类构造方法

object类

方法的重写

返回参数相同 方法名相同  传入参数相同  方法体不同

向上转型:将子类对象赋值给父类引用(需要进行自动类型转换)

animal a=new  dog

向下转型:将父类对象赋值给子类引用(需要进行强制类型转换)

dog a=(dog) new animal

方法的重载

方法名相同  参数不同 

final

final修饰变量----常量

final修饰方法----不可以被重写

final修饰类-------不可以被继承 

instanceof

对象名 instanceof 类名   

判断该对象是否属于该类或其子类

对象名 instanceof 接口名

判断该对象是否属于该接口的实现类

抽象方法

修饰符  abstract  返回参数  方法名(传入参数);

抽象类  有抽象方法的类一定是抽象类

修饰符abstract class 类名{

}

抽象类的继承

抽象父类:必须用abstract关键词

抽象子类:必须在抽象父类的里面,不然会报错里面也需要abstract关键词

如果普通类想要继承抽象父类必须使用方法的重写

接口:所有方法都是抽象方法

修饰符 interface 接口名{

}

Java语言每个类可以实现多个接口

实现 implements

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

}

假设接口一里面有三个抽象方法,但是我只实现了两个,那么解决方式是

1.把第三个方法重写

2.把那个接口改为抽象类

你可能感兴趣的:(java,开发语言)