文章目录
前言
本章节内容主要介绍面向特征中的继承和多态两个特征,以及接口和抽象类的内容。这一章节的内容是面向对象编程思想的重中之重,可以提高代码的复用性和可扩展性,使我们的程序更加高效。
一、 面向对象特征–继承
1.继承的基本概念
继承:继承是面向对象程序设计不可缺少的设计思想,是实现代码可重用的根基,是提高代码可扩展性的主要途径。
1.在JAVA中使用extends关键字来表示继承关系。
2.JAVA不支持多继承,单继承使JAVA的继承关系很简单,一个类只能有一个直接父类。
3.继承之后子类可以调用父类的所有非私有属性和非私有方法
4.虽然一个类只能有一个直接父类,但继承具有传递性,类从B类继承,B类又从A类继承,那么C类就具有B类和A类的所有非私有属性和非私有方法
5.当一个没有继承任何一个类时,jvm会默认让类继承Object类,Object是 java为所有类提供的基类
怎么用?
继承与真实世界类似,例如猫是动物,狗也是动物,是一种is的关系,可以将子类共有的属性和行为放到父类中。
2.继承中的构造方法
1.子类构造方法总是先调用父类构造方法,默认情况下,调用父类无参构造方法。
2.可以在子类构造方法的第一行,使用super关键字调用父类任意一个构造方法。
3.如果用super,必须写在构造方法的第一句。
4.如果子类的构造方法中没有显式地调用类构造方法,则系统默认调用基类无参数的构造方法。
注意:一般情况下,在我们定义了有参的构造方法后,最好在定义一个无参的构造方法,因为当我们显性的定义了一个有参的构造方法,系统不会默认提供无参的构造方法,这在以后的使用中可能会出现问题。
3.super关键字
super关键字:代表父类的引用,可以在子类中调用父类的构造方法以及成员变量和成员方法等。
1.在子类构造方法中要调用父类的构造方法,需要注意:super语句只能出现在子类构造方法体的第一行。
2.用“super.成员变量名”来引用父类成员变量
3.用“super.方法名(参数列表)”的方式访问父类的方法。
4.方法的重写
什么是方法重写?
当父类的方法实现不能满足子类需求时,可以对方法进行重写(override),子类只能对继承过来的方法进行重写。
方法重写规则:
1.方法名相同、参数列表相同;
2.返回值类型相同;
3.访问权限不能小于父类权限;
注意:构造方法,静态方法不能重写,成员变量不存在重写(子类不会去重写覆盖父类的成员变量,所以成员变量的访问不能像方法一样使用多态去访问)。
5. 继承的代码部分
package com.ffyc.oop.day3.demo1;
public class Animal {
/*当一个没有继承任何一个类时,jvm会默认让类继承Object类
Object是 java为所有类提供的基类*/
private int age;
String name;
public Animal(){
super();
System.out.println("调用了Animal的无参构造方法");
}
public void eat(){
System.out.println("吃东西");
}
public void sleep(){
System.out.println("睡觉");
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Cat extends Animal {
public void yell(){
System.out.println("学猫叫");
}
}
public class Dog extends Animal{
private String type;
String name;
public Dog(){
super();
System.out.println("调用了dog的无参构造方法");
}
public Dog(String type){
this.type=type;
System.out.println("调用了dog的有参构造方法");
}
@Override
/*当父类的功能已经不能满足子类时,子类可以重写父类的方法
重写要求参数列表,返回值和方法名相同,构造方法、静态方法不能重写,成员变量不存在重写'
重写访问权限不能小于父类权限;*/
public void eat() {
System.out.println("调用了dog类重写animal类的方法");
super.eat(); //也可以调用原本父类的方法;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public void play(){
System.out.println("狗会玩");
}
}
public class XiaoTianQuan extends Dog{
public XiaoTianQuan(){
/*super(); 调用父类的构造方法,不写系统会默认调用父类无参的构造方法
显性调用父类有参的构造方法;
当用super()调用父类的构造方法时必须写在子类构造方法的第一行,调用其他方法可以不写在第一行;
因为父类的变量要先初始化子类接下来可能要马上使用*/
super("导盲犬");
super.play();
System.out.println("调用了哮天犬无参的构造方法");
}
public void fly(){
System.out.println("哮天犬会飞");
}
}
public class Test {
public static void main(String[] args) {
// 继承:提高代码的复用性,一个类只能有一个父类
//如果一个类没有显性的继承一个父类,那么这个类会自动继承object类
//将子类共有的属性和行为放到父类中
//子类构造方法总是先调用父类构造方法(默认无参)
//可以在子类构造方法的第一行,使用super关键字调用父类任意一个构造方法
//如果用super,必须写在构造方法的第一句
Dog xh=new Dog();
xh.setAge(10); //子类通过set方法调用父类的私有属性
xh.sleep(); //子类调用父类的公开方法;
xh.play(); //子类调用自身的方法;
System.out.println(xh.getAge());
Cat gg=new Cat();
gg.yell(); //子类调用自身方法;
gg.setName("乖乖");
System.out.println(gg.getName());
XiaoTianQuan xt=new XiaoTianQuan();
xt.fly(); //调用自身方法;
xt.play(); //调用父类的方法
xt.eat(); //调用父类的父类的方法;
System.out.println(xt.getType());
}
}
public class Test2 {
public static void main(String[] args) {
Dog xb=new Dog();
xb.eat(); //调用重写的方法
//成员变量不存在重写,每个类里面要有自己的一份成员变量
xb.name="小白"; //这是dog类自己的成员变量
System.out.println(xb.getName()); //这里调用的是Animal的name成员变量(没有显性赋值,所以为默认值null)
System.out.println(xb.name);//调用Dog类的name成员变量
}
}
6.抽象方法和抽象类
1.抽象方法
抽象方法:一种特殊的方法,它只有声明,而没有具体的实现,用abstract关键字进行修饰。
2.抽象类
如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
1.抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法。
2.用abstract修饰的类就是抽象类。如果某个类中包含有抽象方法,那么该类就必须定义成抽象类。
1.抽象类不能被实例化,但可以有构造方法,因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。
2.一个类如果继承了抽象类 要么重写抽象类中所有的抽象方法,要么将该类也定义为抽象类
3.抽象类中可以没有抽象方法,有抽象方法必定是抽象类
3.抽象方法和抽象类的层面
抽象方法和抽象类在软件开发过程中都是设计层面的概念。也就是说,设计人员会设计出抽象类,抽象方法,程序员都是来继承这些抽象类并覆盖抽象方法,实现具体功能。
4.抽象类和抽象方法的代码部分
package com.ffyc.oop.day3.demo2;
public abstract class Animal {
/*如果一个类没有足够的信息来描述一个具体的对象,那么这个类可以定义为抽象类;
抽象类中可以没有抽象方法,如果包含抽象方法一定要声明成抽象类
抽象类的其他功能依然存在,成员变量,构造方法,成员方法*/
int number;
public abstract void eat();//抽象方法没有具体的代码块;
/*抽象类不能被实例化,但可以有构造方法,因为抽象类中含有无具体实现的方法,
所以不能用抽象类创建对象。*/
public Animal(){
System.out.println("调用了Animal的构造方法");
}
public void sleep(){
System.out.println("睡觉");
}
}
//继承抽象类必须实现抽象类中的所有抽象方法,不然该类也要被定义成抽象类
public class Dog extends Animal{
/*抽象类,抽象方法,在软件开发过程中都是设计层面的概念。也就是说,
设计人员会设计出抽象类,抽象方法,程序员都是来继承这些抽象类并
覆盖抽象方法,实现具体的功能*/
@Override
public void eat() { //实现抽象方法 已实现方法的参数、返回值要和抽象类中的方法一致
System.out.println("狗在吃");
}
}
public class Test {
public static void main(String[] args) {
Dog xb=new Dog();
xb.eat(); //调用实现了的抽象方法
xb.sleep(); //调用普通方法
System.out.println(xb.number);
}
}
二、面向对象特征–多态
1.多态的基本概念
多态:同一种事物,在不同时刻表现不同的状态。
多态存在的三个必要条件:
1.要有继承(包括接口的实现)(前提条件)
2.要有重写(前提条件)
3.父类引用指向子类对象
class Animal{
.....
}
class Cat extends Animal{
.....
}
Animal x=new Cat() //父类引用指向子类对象
2.多态的三种调用
1.多态环境下对成员方法的调用(编译看左边,运行看右边)。
class Animal{
void show(){
System.out.println("Animal");
}
}
class Cat extends Animal{
void show(){
System.out.println("Cat");
}
}
...........
Animal x=new Cat() //父类引用指向子类对象
x.show(); //调用的是Cat中的方法,因为重写了父类的方法,这就是多态的特性
2.多态环境下对静态成员方法的调用(编译看左边,运行看左边)。
class Animal{
static void show(){
System.out.println("Animal");
}
}
class Cat extends Animal{
static void show(){
System.out.println("Cat");
}
}
...........
Animal x=new Cat() //父类引用指向子类对象
x.show(); //调用的是Animal中的方法,因为静态的不存在重写,为类所有。
3.多态环境下对成员变量的调用(编译看左边,运行看左边)。
class Animal{
static void show(){
int num=3;
}
}
class Cat extends Animal{
static void show(){
int num=4;
}
}
...........
Animal x=new Cat() //父类引用指向子类对象
x.num; //调用的是Animal的成员变量,成员变量不存在重写
3.多态的转型
向上转型:父类引用指向子类对象,为了提高程序的可扩展性。
例如:可以定义一个方法,参数是Animal,我们可以把Cat类和Dog类和Bird都丢到这个方法中去执行,这样就可以做到一个方法接收不同的对象类型。
缺点:不能调用子类特有的方法,需要向下转型。
class Animal{
static void show(){
System.out.println("Animal");
}
}
class Cat extends Animal{
static void show(){
System.out.println("Cat");
}
void look(){
........
}
}
...........
Animal x=new Cat() //向上转型,Cat对象提升到Animal对象。
x.show(); //只能调用子类重写父类的方法
x.look(); //报错,不能使用子类中特有的方法
向下转型:把父类引用向下强制转换为子类引用,目的是使用子类中特有的方法。
class Animal{
static void show(){
System.out.println("Animal");
}
}
class Cat extends Animal{
static void show(){
System.out.println("Cat");
}
void look(){
........
}
}
...........
Animal x=new Cat() //向上转型,Cat对象提升到Animal对象。
Cat m=(Car)x; //向下转型
m.show();
m.look(); //子父类的方法都可以调用
总结:我们可以把向上转型和向下转型结合起来,进一步提高程序的可扩展性。
4.多态的代码部分
public abstract class Animal {
public abstract void eat();
}
public class Cat extends Animal{
@Override
public void eat() { //重写
System.out.println("猫吃鱼");
}
}
public class Dog extends Animal {
@Override
public void eat() { //重写
System.out.println("狗吃肉");
}
public void play(){
System.out.println("狗会玩");
}
}
public class Test {
// 多态:同一种事务在不同时刻表现不同的状态
//多态的三个条件:1.要有继承 2.要有重写 3.父类引用指向子类对象
//多态环境下对成员方法的调用:编译看左边,运行看右边
//多态环境下对静态成员方法的调用:编译和运行都看左边
//多态环境下对成员变量的调用:编译和运行都看等号左边
public static void main(String[] args) {
Animal xb=new Dog(); //父类引用指向子类对象;
xb.eat();
xb=new Cat(); //父类引用指向子类对象
xb.eat();
}
}
public class Test2 {
public static void main(String[] args) {
//方法参数多态性的好处:提高代码的扩展性
Animal dog=new Dog();
Animal cat=new Cat(); //向上转型 :特高程序的扩展性
feed(dog);
feed(cat);
}
public static void feed(Animal animal){
animal.eat(); //调用子类重写父类的方法
if(animal instanceof Dog){ //使用instance关键字判断是否为特定类型
((Dog) animal).play(); //向下转型 :为了使用子类中的特有的方法
}
}
}
三、final关键字
final 用于声明属性,方法和类。
1.修饰属性的时候:定义就必须直接赋值或者在构造方法中进行赋值,并且后期都不能修改。
2.修饰方法的时候:子类里不可以重写。
3.修饰类的时候:不能被定义为抽象类或是接口,不可被继承。
public class FinalDemo {
//final关键字可以修饰类,属性,方法
//final修饰的类不能被继承,不能被定义为抽象类或者接口,因为抽象类和接口必须被继承才能实现
//final修饰的属性必须直接赋值或者在构造方法里面赋值,并且后期不能修改
//final修饰的方法不能被重写
public static final int number=1; //直接赋值,一般加上static关键字,所有对象共享一份,值相同
final int id; //使用构造方法赋值; 每个对象可以有不同的值
public FinalDemo(final int a) { //在方法参数前面加final关键字,为了防止数据在方法体中被修改。
// a=9; 报错,因为a是final类型的形参不能被修改值
id = a;
}
public FinalDemo(){
id=0;
}
public final void play(){
System.out.println("测试");
}
public static void main(String[] args) {
FinalDemo f1=new FinalDemo(10);
FinalDemo f2=new FinalDemo(20);
//总的原则:保证创建每一个对象的时候,final属性的值是确定的
System.out.println(f1.id);
System.out.println(f2.id);
}
}
四、接口
1.什么是接口
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过实现接口的方式,从而来继承接口的抽象方法。
例如:在日常的生活之中,接口这一名词经常听到的,例如:USB接口、打印接口、充电接口等等。
如果要进行开发,要先开发出USB接口标准,然后设备厂商才可以设计出USB设备。
接口存在的意义:java中一个类只能有一个父类,所以用接口可以实现多继承的逻辑 (一个类可以实现多个接口)。
2.接口的定义
接口的定义:使用 interface 关键字用来声明一个接口。
public interface Myinterface extends Myinterface3, Myinterface2{ //一个接口能继承其它多个接口。
/* 接口不可以被实例化
实现类必须重写接口的所有方法
实现类可以实现多个接口
接口中的变量都是静态常量*/
//所有属性默认为public static final
int number=10;
//所有方法都是public abstract(java8之前)
//定义抽象方法
public abstract void eat();
//定义默认方法(java8之后还可以定义默认方法和静态方法) 用子类对象调用
default void play(){
System.out.println("玩");
}
//定义静态方法 直接用接口名调用
static void sleep(){
System.out.println("睡觉");
}
}
接口的使用:类使用implements关键字实现接口。
public class MyinterfaceImpl implements Myinterface,Myinterface2 {
@Override //实现接口中的方法
//实现类可以实现多个接口;
/* 当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明
为抽象的类*/
public void eat() {
System.out.println("吃");
}
@Override
public void jump() {
System.out.println("跳");
}
@Override
public void work() {
System.out.println("工作");
}
public static void main(String[] args) {
MyinterfaceImpl m1=new MyinterfaceImpl();
m1.play(); //子类对象调用默认方法;
Myinterface.sleep(); //接口名调用静态方法
m1.eat();
m1.jump();
m1.work();
}
}
3.抽象类和接口的区别
1.在语法方面
抽象类:
1.由abstract关键词修饰的类称之为抽象类。
2.抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法。
3.抽象类中也可以没有抽象方法。
4.继承抽象类的非抽象类必须实现其中的所有抽象方法,而已实现方法的参数、返回值要和抽象类中的方法一样。否则,该类也必须声明为抽象类。
接口:
1.由interface关键词修饰的称之为接口;
2.接口中可以定义成员变量,但是这些成员变量默认都是public static final的常量。
3.接口中的所有方法都是public abstract(java8之前)
4.接口中没有已经实现的方法,全部是抽象方法(java8之后还可以定义默认方法和静态方法)
5.一个类可以实现多个接口,一个接口可以继承多个接口。
6.接口不能实例化对象(无构造方法),但可以声明对象的引用(多态性)。
2.在使用方面
先来举一个简单的例子:
狗都具有 eat() 、sleep() 方法,我们分别通过抽象类和接口定义这个抽象概念。
//通过抽象类定义
public abstract class Dog {
public abstract void eat();
public abstract void sleep();
}
//通过接口定义
public interface Dog {
public abstract void eat();
public abstract void sleep();
}
思考:但是我们现在如果需要让狗拥有一项特殊的技能——钻火圈 DrillFireCircle(),如何增加这个行为呢?
1.将钻火圈方法与前面两个方法一同写入抽象类中,但是这样的话,但凡继承这个抽象类狗都具有了钻火圈技能,明显不合适。
2.将钻火圈方法与前面两个方法一同写入接口中,当需要使用钻火圈功能的时候,就必须实现 接口中的eat() 、sleep() 方法(重写该接口中所有的方法)显然也不合适 。
那么该如何解决呢 ? 我们可以仔细想一想,eat和sleep都是狗本身所应该具有的一种行为,而钻火圈这种行为则是后天训练出来的,只能算是对狗类的一种附加或者延伸, 两者不应该在同一个范畴内,所以我们考虑将这个单独的行为,独立的设计一个接口,其中包含DrillFireCircle()方法, Dog设计为一个抽象类, 其中又包括eat() 、sleep() 方法.一个SpecialDog即可继承Dog类并且实现DrillFireCircle()接口。
//定义接口,含有钻火圈方法
public interface DrillFireCircle() {
public abstract void drillFireCircle();
}
//定义抽象类狗类
public abstract class Dog {
public abstract void eat();
public abstract void sleep();
}
//继承抽象类且实现接口
class SpecialDog extends Dog implements drillFireCircle {
public void eat() {
//....
}
public void sleep() {
//....
}
public void drillFireCircle() () {
//....
}
}
总结:
继承是一个 "是不是"的关系,而接口实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如狗是否能钻火圈,能则可以实现这个接口,不能就不实现这个接口。
五、总结
本章节的内容知识点较多,难度较大,需要大家勤加练习,在自己动手编写代码的过程中感受JAVA继承和多态的魅力。同时,因为接口和抽象类的存在,赋予了JAVA强大的面向对象的能力。