目录
一、抽象类概述:
我们知道,类用来模拟现实事物。一个类可以模拟一类事物,而某个类的一个实例化对象可以模拟某个属于该类的具体的事物。类中描绘了该类所有对象共同的特性,当一个类中给出的信息足够全面时,我们就可以实例化该类;比方说,在Dog类中定义了name,age,fur_color,sex等属性,以及habit,eat等行为时,我们就可以创建一个Dog类对象,来模拟某个具体的Dog,比如你家的宠物狗,或者是神犬小七等。但是,当一个类中给出的信息不够全面时,(比方说有无法确定的行为),它给出的信息不足以描绘出一个具体的对象,这时我们往往不会实例化该类,这种类就是抽象类。打个比方,对于Animal类,是,所有的动物都有吃喝的行为,定义eat方法可以描述动物“吃”这一行为,但是每种动物吃的都不一样,因此一个eat方法并不能准确描述吃什么,怎么吃。这时Animal给出的信息就不足够描述一个对象,我们就不能去实例化Animal类。
在Java中,我们通过在类前添加关键字abstract(抽象的)来定义抽象类。如下图所示 :
publicabstractclassAnimal { //Animal类此时就是一个抽象类。} classDogextendsAnimal { //Dog类继承了Animal类,是Animal类的子类。}
二、抽象方法 :
1.概述 :
我们将“只有方法声明,没有方法体”的一类方法统称为抽象方法,抽象方法用关键字abstract修饰。需要注意的是,如果一个方法已经确定是抽象类,那么它绝不能再有方法体,即不能出现大括号,只需要在后面添加一个分号即可,否则IDEA会提示报错信息,如下图所示 :
还要注意一点,如果某个类中已经出现了抽象方法,那这个类必须定义成抽象类,否则会报错,如下GIF动图演示——我们删掉Animal类前的abstract修饰符,IDEA立马就会给出提示信息,如下 :
也就是说,拥有抽象方法的类一定是抽象类,但是抽象类不一定有抽象方法。
2.应用 :
当父类需要定义一个方法,却不能明确该方法的具体实现细节时,可以将方法定义为abstract,具体实现细节延迟到子类。(让子类重写这个方法)
就比如我们刚才说的,Animal类中的eat() 方法——我们可以将其先定义为抽象类,然后在子类,比如说Dog类中重写eat() 方法,给出对Dog对象“吃”这一行为的一些描述。如下 :
up以Animal类,Dog类和Test类为例,代码如下 :
packageknowledge.polymorphism.about_abstract.introduction; publicabstractclassAnimal { /** 父类 *///将Animal类中的eat() 方法定义为抽象类,具体实现延迟到子类。publicabstractvoideat(); } classDogextendsAnimal { /** 子类 *///子类重写父类的抽象方法,也称为子类实现了该抽象方法。publicvoideat() { System.out.println("狗是杂食性动物,喜食肉类,喂养时应该以动物蛋白为主,素食为辅。"); } } classTest { publicstaticvoidmain(String[] args) { Dogdog=newDog(); dog.eat(); } }
运行结果 :
3.特点 :
①若父类中定义了一个抽象方法,要求其所有非抽象子类都必须重写该抽象方法。否则IDEA会报错,如下图所示 :
②前面我们说了,抽象方法用abstract关键字修饰。这里再补充一点——抽象方法不能再使用private,final 或者static关键字来修饰,即abstract不能与private,final和static共同出现,这是因为定义抽象方法的目的就是想将方法的具体实现延迟到子类,最终是要被子类重写的,而private,final,static这几个关键字都和“方法重写”的目的背道而驰。
如果你固执己见,非要让abstract和这几个关键字一同出现,IDEA也是毫不客气,直接报错,如下图所示 :
三、抽象类特点 :
1.关于abstract关键字 :
abstract关键字只能用于修饰类和方法,用于声明抽象类和抽象方法。抽象类必须使用abstract关键字修饰。声明时格式如下 :
访问权限修饰符 abstract class 类名{ // }
访问权限修饰符 abstract 返回值类型 方法名(形参列表);
举个例子,如下 :
//抽象类publicclassAnimal { //抽象方法publicvoideat(); //抽象方法最后加一个分号即可,不可以有大括号。}
2.抽象类不能被实例化,只能创建其子类对象 :
即,我们不能创建抽象类对象(这里的对象指的是堆空间中真正的对象,即不能“new 抽象类”),原因我们在开篇抽象类的概述中也提到了,这里不再赘述。如果你头铁,非要创建抽象类对象,IDEA也不是吃素的,直接报错,如下图所示 :
当然,如果抽象类的子类没有用abstract关键字修饰,那么我们可以创建其子类对象,如下图所示 :
3.抽象类子类的两个选择 :
如果某个类继承了一个抽象类,那么这个类有两个选择——要么实现父类所有的抽象方法,要么子类本身也定义成抽象类。当然,肯定也不会是瞎jb想定义成抽象类就定义成抽象类😂,要满足我们我们上面所说的定义抽象类的条件——类中提供的信息不足以描述一个对象,或者类中有无法确定的行为需要延迟到子类实现。
还是给大家举个栗子。up现在在character包下创建一个Food类,将Food类定义为抽象类,并定义showNutrition()抽象方法,该方法将来要打印出食物的主要营养,具体实现延迟到子类;然后分别创建子类Meat类和Fruit类去继承Food类,我们在Meat类中重写Food类的showNutrition() 方法,使其打印出肉类的主要营养价值;同时,另一个子类Fruit类不去实现showNutrition() 方法,而是将其定义为抽象类。最后,以Test类为测试类,在测试类中创建子类对象并调用showNutrition() 方法。
Food类,Meat类,Fruit类,Test类代码如下 :
packageknowledge.polymorphism.about_abstract.character; publicabstractclassFood { /** 父类 : Food类 *///记住,抽象方法没有方法体publicabstractvoidshowNutrition(); } classMeatextendsFood { /** 子类 : Meat类 */publicvoidshowNutrition() { System.out.println("肉类是蛋白质、脂肪、维生素B2、维生素B1、烟酸和铁的重要来源。"); } } abstractclassFruitextendsFood { /** 子类 : Fruit类 */} classTest { /** 测试类 : Test类 */publicstaticvoidmain(String[] args) { Meatmeat=newMeat(); meat.showNutrition(); } }
运行结果 :
我们也可以再定义一个CiLi类表示水果中的刺梨,然后让刺梨类去继承Fruit类,并在CiLi类中去实现showNutrition() 方法;Fruit类不变,Test类和CiLi类代码如下 :
classCiLiextendsFruit { publicvoidshowNutrition() { System.out.println("刺梨是当之无愧的水果界的VC之王,VC含量高达2585mg/100g!"); } } classTest { publicstaticvoidmain(String[] args) {/** 测试类 : Test类 */Meatmeat=newMeat(); meat.showNutrition(); System.out.println("----------------------------------------"); CiLiciLi=newCiLi(); ciLi.showNutrition(); } }
运行结果 :
四、抽象类的成员 :
1.成员变量 :
抽象类既可以有静态的成员变量,也可以有非静态的成员变量。
既可以有静态的成员常量,也可以有非静态的成员常量。
2.成员方法 :
抽象类既可以有(非私有的)抽象方法(注意一定是非私有非静态,因为abstract关键字与private关键字,final关键字,static关键字不能同时存在);
也可以有非抽象方法(非抽象方法就可以用private,final和static关键字来修饰了,具体使用时,根据实际需求合理应用)。
3.构造器 :
抽象类可以和非抽象类一样拥有构造器,并且支持构造器的重载。
4.总结 :
其实吧,说上面一大堆都是废话😂。
抽象类中的成员只比非抽象类多一种——抽象方法。其他都和非抽象类一样。
大家只要记住抽象方法怎么写,怎么用就彳亍了。😎
5.代码演示 :
up以Fruit类为演示类,代码如下 :
packageknowledge.polymorphism.about_abstract.about_members; publicabstractclassFruit { //Fruit类是抽象类//抽象类中可定义的成员://1.非静态变量和静态变量privateStringname="水果名儿是有长有短"; privatestaticStringsize="水果的大小是有大有小"; //2.非静态常量和静态常量publicfinalStringCOLOR="水果的颜色是五光十色"; publicstaticfinalStringFORM="水果的形态是千奇百怪"; //3.抽象方法和非抽象方法publicabstractvoidnutrition(); privatefinalstaticvoidsuitCrowds() { System.out.println("人人都适合吃水果!"); } publicvoidinvokeSuitCrowds() { Fruit.suitCrowds(); } //4.构造器可以重载publicFruit() { System.out.println("Fruit's name = "+name); } publicFruit(Stringname) { this.name=name; } }
这些成员都可以在抽象类中定义,只要语法正确,IDEA是不会报错的,当然,具体怎么使用这些成员就看你自己了,根据实际情况来定。
五、抽象类课堂练习 :
1.要求 :
如上图所示 :
已知西风骑士团的三位骑士分别是琴,可莉和优菈,请分别定义类来描述它们,要求给出每一个西风骑士的姓名,年龄和性别;并且,这三人均可以使用元素之力,分别是元素战技和元素爆发,但每位骑士的战技和爆发都不一样。其中,琴使用风元素,元素战技为风压箭,元素爆发为蒲公英之风;可莉使用火元素,元素战技为蹦蹦炸弹,元素爆发为轰轰火花;优菈使用冰元素,元素战技为冰朝的涡旋,元素爆发为凝浪之光剑。请在这些类中正确选择一个类定义成抽象类,并在该类中定义抽象方法elemental_kill() 和 elemental_burst(),要求这两个抽象方法将来在实现时,需要在控制台打印出当前骑士的元素战技和元素爆发;并在该抽象类中定义非抽象方法,要求该方法可以在控制台打印出当前骑士的基本信息。
2.思路 :
①阅读提干后我们得知,总共有三个角色,这三个角色均属于名为“西风骑士团”的一个组织。因此,我们可以分别定义四个类来分别描述“西风骑士团”,“琴”,“可莉”,“优菈”,再加上测试类,因此我们总共需要定义五个类。
②“西风骑士团”可以代表一类人,由于每位骑士的元素战技和元素爆发均不相同,这个类并不能提供足够的信息来描述一个具体的“骑士”对象。所以,我们可以定义Knights类来表示“西风骑士团”,并将其定义为抽象类。又因为琴,可莉,优菈均属于西风骑士团的一员,因此我们可以分别定义Qin类,Keli类以及Youla类来描述这三位骑士,并让Qin类,Keli类和Youla类继承Knights类。
③题干要求定义两个抽象方法elemental_kill() 和 elemental_burst()来分别打印出当前骑士的元素战技和元素爆发。既然我们已经确定Knights类为抽象类,这就没啥好说了,在Knights类中定义这两个方法即可。
④又因为题干还要求我们在抽象类中定义方法打印出当前骑士的基本信息,因此,我们可以在Knights类定义name,age,sex这些属性;根据JavaBean标准,我们需要将这些属性全部设为私有,并给出公共的访问这些属性的方法,然后给出Knights类的无参构造和带参构造(注意,Knights是抽象类,无法被实例化,因此我们给出Knights构造器的目的不是为了创建Knights类对象,而是为了在子类的带参构造中使用super语句调用父类构造器;接着,再定义一个printInfo方法,用于打印出当前骑士的姓名,年龄和性别。
⑤最后,我们可以定义测试类Test类,并分别创建Qin类,Keli类和Youla类对象,调用elemental_kill()方法,elemental_burst() 方法,以及printInfo方法。
3.代码 :
packageknowledge.polymorphism.about_abstract.exercise; publicabstractclassKnights { /** 骑士类 */privateStringname; privateintage; privateStringsex; publicKnights() { } publicKnights(Stringname, intage, Stringsex) { this.name=name; this.age=age; this.sex=sex; } publicStringgetName() { returnname; } publicvoidsetName(Stringname) { this.name=name; } publicintgetAge() { returnage; } publicvoidsetAge(intage) { this.age=age; } publicStringgetSex() { returnsex; } publicvoidsetSex(Stringsex) { this.sex=sex; } publicabstractvoidelemental_skill(); publicabstractvoidelemental_burst(); publicvoidprintInfo() { System.out.println("西风骑士——"+getName() +","+getAge() +"岁,性别"+getSex()); } } classQinextendsKnights{ /** 琴类 */publicQin(Stringname, intage, Stringsex) { super(name, age, sex); } publicvoidelemental_skill() { System.out.println("琴的元素战技是风压箭。"); } publicvoidelemental_burst() { System.out.println("琴的元素战技是蒲公英之风。"); } } classKeliextendsKnights{ /** 可莉类 */publicKeli(Stringname, intage, Stringsex) { super(name, age, sex); } publicvoidelemental_skill() { System.out.println("可莉的元素战技是蹦蹦炸弹。"); } publicvoidelemental_burst() { System.out.println("可莉的元素战技是轰轰火花。"); } } classYoulaextendsKnights{ /** 优菈类 */publicYoula(Stringname, intage, Stringsex) { super(name, age, sex); } publicvoidelemental_skill() { System.out.println("优菈的元素战技是冰朝的涡旋。"); } publicvoidelemental_burst() { System.out.println("优菈的元素战技是凝浪之光剑。"); } } classTest { /** 测试类 */publicstaticvoidmain(String[] args) { Qinqin=newQin("琴", 22, "female"); qin.elemental_skill(); qin.elemental_burst(); qin.printInfo(); System.out.println("-------------------------------------------"); Kelikeli=newKeli("可莉", 500, "female"); keli.elemental_skill(); keli.elemental_burst(); keli.printInfo(); System.out.println("-------------------------------------------"); Youlayoula=newYoula("优菈", 21, "female"); youla.elemental_skill(); youla.elemental_burst(); youla.printInfo(); } }
六、总结 :
🆗,以上就是本节抽象类相关的全部内容了,大家一定要牢记抽象类和抽象方法的特点,牢记抽象类和抽象方法之间的关系,掌握abstract关键字的使用。下一节内容是多态章的final关键字,我们不见不散😆。感谢阅读!