Java中的抽象类

一、抽象类定义

Java语言提供了两种类:具体类和抽象类

在Java中,abstract是抽象的意思,可以修饰类,成员方法

abstract修饰类,就是抽象类,abstract修饰方法,就是抽象方法

格式如下

修饰符 abstract class 类名{

            修饰符 abstract 返回值类型  方法名称(形参列表)

}

注意:一个类中有抽象方法的话,该类也要被abstract修饰,否则会报错

示例:

//abstract修饰的抽象类
public abstract class People {

    //abstract修饰的抽象方法
    public abstract void eat();
}

特点:

  • 修饰符必须为public或者protected,不能为private,因为创建了抽象类,就要被其他类继承,设为private就无法被继承
  • 抽象类不能被实例化,只能通过普通子类来实现实例化
  • 如果一个普通子类继承于抽象父类,则该类一定要重写实现该父类的抽象方法,如果子类仍然的hi一个抽象类,也是允许的,就不必冲写父类的抽象方法,但必须用abstract修饰
  • 抽象级别:抽象类是对整体类的抽象,包括属性和方法
  • 抽象类是现有子类,将子类的共同属性和方法提取出来放在抽象类中,是一种从下往上的构建法则
  • 父类包含了子类集合的常见方法,但由于父类本身是抽象的,不能使用这些方法

二、抽象方法

抽象方法特点:

  • 抽象方法中没有方法体
  • 抽象方法必须存在于抽象类中
  • 子类重写父类时,必须重写父类的所有抽象方法
  • 抽象方法不能使用private final static 来修饰,因为这些关键字都是和重写违背的

抽象方法代码示例如下:

//abstract修饰的抽象类
public abstract class People {


    //普通方法,带 {} 方法体
    public void fun(){
        System.out.println("存在方法体的普通方法");
    }


    //abstract修饰的抽象方法,没有方法体 {},被abstract修饰
    public abstract void eat();
}

三、抽象类举例

首先声明一点:为什么要使用抽象类

答:当父类知道子类要完成某种行为,但每个子类的实现方式又不一样的时候,于是该父类就把行为定义成抽象方法的形式,具体实现交给子类去完成,此时这个类就可以声明为抽象类,下面举三个例子来说明抽象类的使用,

3.1、抽象类例子1

先声明一个Cat类,类中有睡觉这一行为

public class Cat {

    public void sleep(){
        System.out.println("我在睡觉");
    }
}

再声明一个Dog类,类中也有睡觉这一行为

public class Dog {

    public void sleep(){
        System.out.println("我在睡觉");
    }

}

这里的Dog和Cat都有共同的行为sleep();

但这时给Dog和Cat添加一个eat()的行为,不同的是,Dog吃的是骨头,Cat吃的是鱼,相同点是都是eat()这个行为,不同点是吃的食物不同,就可以将Dog和Cat作为子类,抽象出eat()这个行为作为抽象方法放在父类中,再在子类重写父类方法来调用实现吃不同的食物。

父类Animal:定义共同行为sleep(),对于子类来说sleep()行为没有差别,不需要作为抽象方法,

但是对于吃这个行为,Cat和Dog两者都有这个属性,具体的内容确是有区别的,因此作为抽象方法,需要注意的是抽象方法由abstract修饰,没有方法体。

//定义一个abstract修饰的抽象类
public abstract class Animal {

    //通用的行为sleep也放在父类中
    public void sleep(){
        System.out.println("我在睡觉");
    }
    
    //定义抽象方法
    //抽象方法没有方法体{}
    public abstract void eat();
}

子类Cat:引入@Override注解是为了重写父类的方法,共同行为sleep()可以不用写,对于两个子类来说没有区别,只在抽象类中定义一次即可。

public class Cat extends Animal{

    //@Override注解是重写父类的方法
   @Override
    public void eat(){
       System.out.println("我在吃鱼");
   }
}

子类Dog:引入@Override注解是为了重写父类的方法,共同行为sleep()可以不用写,对于两个子类来说没有区别,只在抽象类中定义一次即可。

public class Dog extends Animal{

    //@Override注解是重写父类方法需要引入的注解
    @Override
    public void eat(){
        System.out.println("我在吃骨头");
    }

}

在启动项中:由于父类Animal是抽象类,无法实例化成对象,因此只能通过对子类的实例化,对象来调用父类Animal中对于两个子类没有区别的sleep()方法,和分别调用从父类中重写的方法eat()

public class DemoApplication {

    public static void main(String[] args) {

        System.out.println("狗的行为--------------------------------");
        //在启动项中New一个对象
        Dog dog = new Dog();
        //利用对象调用抽象类中的sleep()方法
        dog.sleep();
        //利用对象调用子类中重写的方法eat()
        dog.eat();

        System.out.println("猫的行为--------------------------------");
        //在启动项中New一个对象
        Cat cat = new Cat();
        //利用对象调用抽象类中的sleep()方法
        cat.sleep();
        //利用对象调用抽象类中的eat()方法
        cat.eat();

    }
}

控制台打印输出:

狗的行为--------------------------------
我在睡觉
我在吃骨头
猫的行为--------------------------------
我在睡觉
我在吃鱼

这样每个对象都没有重写公用方法eat(),而是直接调用了,如果是接口来实现的话,则需要实现类来重写一下eat(),就会又很多重复代码

区别:这里对于Cat吃鱼和Dog吃骨头,可以用接口去实现他们的行为吗,当然可以,但是这种利用接口方式会产生很多重复性的代码,这样的话就得每一个每个实现类都写一遍睡觉的这个逻辑,因为接口的设计只能满足多态(之后的内容会介绍什么是多态),像我们这种既有多态(Cat吃鱼Dog吃骨头),又有复用(都有sleep()行为),最佳设计就是优先考虑使用抽象类了,集成以后调用一次即可。

3.2、抽象类例子2

  每个人的吃饭前的准备和吃饭后的处理都是一样的,吃饭前:“饭前勤洗手”,吃饭后:“饭后勤洗碗”,只有吃饭的过程不同,跟例1一样,公共行为是吃饭前和吃饭后的行为,不同的是中间吃饭的过程,那么就可以把吃饭的过程变成一个抽象方法,而吃饭前和吃饭后的行为作为普通方法一起放在抽象类中

首先,在只有学生一类人的时候,吃饭的过程是一样的,不需要抽象类和抽象方法

public class Student {

    public void eat(){
        System.out.println("饭前洗手");
        System.out.println("我是学生,我要吃的健康且有营养");
        System.out.println("饭前洗碗");
    }

}

启动类New一个对象,调用eat()方法

public class DemoApplication {

    public static void main(String[] args) {

        //new一个对象出来调用eat()方法
        Student student = new Student();
        student.eat();

    }
}

控制台打印输出为:

饭前洗手
我是学生,我要吃的健康且有营养
饭前洗碗

此时增加一类人为运动员,吃饭的过程为“我是运动员,要吃的多一些”,这个时候就需要构建一个抽象类People作为父类,定义抽象方法process(),其他的作为普通方法

//abstract修饰的抽象类
public abstract class People {

    //设置公共的不同人也无差异的饭前和饭后行为
    public void eat() {
        System.out.println("饭前洗手");
        //调用本类的抽象方法
        this.process();
        System.out.println("饭后洗碗");
    }

    //抽象方法:吃饭的过程,abstract修饰,无方法体,无具体的方法实现
    public abstract void  process();
}

子类Student,继承父类People,引入注解@Override再进行重写父类的抽象方法process()

//子类Student继承父类People
public class Student extends People{

    //@Override注解是为了重写父类夫人方法引进的注解
    @Override
    //重写父类的方法
    public void process(){
        System.out.println("我是学生,我要吃的健康且营养");
    }

}

子类Player,继承父类People,引入注解@Override再进行重写父类的抽象方法process()

//子类Player继承父类People
public class Player extends People{

    //@Override注解是为了重写父类方法引进的注解
    @Override
    //重写父类的方法
    public void process(){
        System.out.println("我是运动员,我要吃的健康一点");
    }
}

启动项,通过new关键字分别创建子类Player和子类Student的对象 (因为父类是抽象类无法实例化),再通过对象调用父类People的普通方法eat()

public class DemoApplication {

    public static void main(String[] args) {


        System.out.println("学生行为-----------------------");
        //new关键字创建对象
        Student student = new Student();
        //调用父类中的普通方法
        student.eat();

        System.out.println("运动员行为---------------------");
        //new关键字创建对象
        Player player = new Player();
        //调用父类中的普通方法
        player.eat();

    }
}

控制台打印输出为:

学生行为-----------------------
饭前洗手
我是学生,我要吃的健康且营养
饭后洗碗
运动员行为---------------------
饭前洗手
我是运动员,我要吃的多一点
饭后洗碗


这个例子也说明了通过使用模板,我们只需要改变一部分,其他的直接拿过来用即可

3.3、抽象类例子3

不同的几何体,面积计算公式是不一样的,但他们具有的特性是一样的,都有长和宽的属性,也有面积计算方法,那么就可以定义成一个抽象类,抽象类的有两个属性width和height,还有面积计算方法area()

首先创建一个图形抽象类Shape,类中定义属性和抽象方法、

//抽象类Shape,用abstract修饰
public abstract class Shape {


    public int width; //几何图形的长
    public int height;  //几何图形的宽

    //有属性就使用有参构造器
    public Shape(int width, int height) {
        this.width = width;
        this.height = height;
    }

    //抽象方法,abstract修饰,没有方法体{}
    public abstract void area();
}

子类正方形类Square,继承父类Shape,通过引入@Override注解进行父类方法的重写

//子类Square继承父类Shape
public class Square extends Shape{

    //利用super访问父类的属性width和height
    public Square(int width, int height) {
        super(width, height);
    }


    //通过注解@Override的引入实现父类方法的重写
    @Override
    //重写父类方法,方法体中写入具体的实现
    public double area(){
         double y = width * height;
        return y;
    }
}

子类三角形类Triangle,继承父类Shape,通过引入@Override注解进行父类方法的重写

public class Triangle extends Shape{


    //通过右键选择Generate快捷生成,利用super访问父类的属性
    public Triangle(int width, int height) {
        super(width, height);
    }

    //通过注解@Override的引入实现父类方法的重写
    @Override
    //重写父类方法
    public double area(){
        double x = (width * height )/2;
        return x;
    }
}

在启动项中,通过对两个子类的实例化,调用各自类中的重写方法,注意因为是调用了有参构造器,所以可以在实例化的时候直接赋值

public class DemoApplication {

    public static void main(String[] args) {


        System.out.println("三角形-----------------------");
        //new关键字创建对象
        Triangle triangle = new Triangle(3,4);//调用了有参构造器,直接赋值即可
        //调用子类中重写的方法
        triangle.area();
        System.out.println("三角形的面积为"+triangle.area());


        System.out.println("长方形---------------------");
        //new关键字创建对象
        Square square = new Square(5,6);//调用了有参构造器,直接赋值即可
        //调用子类中重写的方法
        square.area();
        System.out.println("长方形的面积为"+square.area());

    }
}

控制台打印输出为:

三角形-----------------------
三角形的面积为6.0
长方形---------------------
长方形的面积为30.0

四、抽象类使用注意事项

  1. 一个类如果定义为抽象类,那里面可以没有抽象方法
  2. 一个类中如果有抽象方法,那所在类必为抽象类
  3. 抽象类不能被实例化,可以实例化这个抽象类的非抽象子类,
  4. 抽象类中的所有非抽象子类必须重写抽象类中的抽象方法
  5. 抽象类中,可以有构造方法,是供给子类创建对象时,初始化父类成员使用的
  6. 抽象类中的抽象方法只是声明,不包含方法体,也就是不给出具体的实现细节
  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值