面向对象三大特征的理解(为啥定义抽象类)

本文深入解析面向对象的三大特性:封装、继承(包括重写和重载)、多态,并讨论抽象类的作用与抽象方法,同时对比抽象类和接口的区别,助你理解Java编程中的核心概念。

一、基本介绍

1. 封装

    封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外 
 部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法 
 来操作属性。

下面就是一个例子

public class Student {
    private int id;//id属性私有化
    private String name;//name属性私有化

    //获取id的方法
    public int getId() {
        return id;
    }

    //设置id的方法
    public void setId(int id) {
        this.id = id;
    }

    //获取name的方法
    public String getName() {
        return name;
    }

    //设置name的方法
    public void setName(String name) {
        this.name = name;
    }
}

2.继承

   继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以
增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父
类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序
的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。

关于继承如下 3 点请记住:

(1)子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),
但是父类中的私有属性和方法子类是无法访问,只是拥有。
(2)子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
(3)子类可以用自己的方式实现父类的方法。

final关键字

final 关键字声明类可以把类定义为不能继承的,即最终类;或
者用于修饰方法,该方法不能被子类重写

2.1、重写

重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。

返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访
问修饰符范围大于等于父类。

如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是
被 static 修饰的方法能够被再次声明。
构造方法无法被重写
public class Hero {
    public String name() {
        return "超级英雄";
    }
}
public class SuperMan extends Hero{
    @Override
    public String name() {
        return "超人";
    }
}

方法的重写要遵循“两同两小一大”

“两同”即方法名相同、形参列表相同;
“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,
子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。

2.2、重载

发生在同一个类中(或者父类和子类之间),方法名必须相同,参数
类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。

3、多态

多态,顾名思义,表示一个对象具有多种的状态。具体表现为父类的引用指向子类的实例。
多态存在的三个必要条件
继承
重写
父类引用指向子类对象:Parent p = new Child();

多态的特点:

对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
多态不能调用“只在子类存在但在父类不存在”的方法;
如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的
方法,执行的是父类的方法。
//定义一个抽象类
abstract class Animal {  
	    abstract void eat();  
}  
  
class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
    public void work() {  
        System.out.println("抓老鼠");  
    }  
}  
  
class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
    public void work() {  
        System.out.println("看家");  
    }  
}

public class Test {
    public static void main(String[] args) {
      show(new Cat());  // 以 Cat 对象调用 show 方法
      show(new Dog());  // 以 Dog 对象调用 show 方法
                
      Animal a = new Cat();  // 向上转型  
      a.eat();               // 调用的是 Cat 的 eat
      Cat c = (Cat)a;        // 向下转型  
      c.work();        // 调用的是 Cat 的 work
  }  
            
    public static void show(Animal a)  {
      a.eat();  
        // 类型判断
        if (a instanceof Cat)  {  // 猫做的事情 
            Cat c = (Cat)a;  
            c.work();  
        } else if (a instanceof Dog) { // 狗做的事情 
            Dog c = (Dog)a;  
            c.work();  
        }  
    }  
}

4、为什么要定义抽象类???

如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实
现由它的子类确定,那么你可以在父类中声明该方法为抽象方法

Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法	
名,而没有方法体。
抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。

声明抽象方法会造成以下两个结果:
如果一个类包含抽象方法,那么该类必须是抽象类。
任何子类必须重写父类的抽象方法,或者声明自身为抽象类。

继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象
类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的
子类都不能用来实例化对象。
public abstract class Employee
{
   private String name;
   private String address;
   private int number;
   
   public abstract double computePay();
   
   //其余代码
}
java 中抽象类不能被实例化

代码示例:
class abstract A {

}
驱动函数:
public static void  main(String[] args){

   //下面这行会造成编译不通过
    A a=new A();

}

抽象类和接口的区别

1、语法层面上的区别
 1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
 2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
 3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
 4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
2、设计层面上的区别
1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,
包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,
但是它们都有一个共性,就是都会飞。

2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。
什么是模板式设计?最简单例子,大家都用过 ppt 里面的模板,如果用模板 A 设计了 ppt B 和 ppt C,ppt B 和 ppt C 
公共的部分就是模板 A 了,如果它们的公共部分需要改动,则只需要改动模板 A 就可以了,不需要重新对 ppt B 和 ppt C 
进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,

如果需要添加新的方法,可以直接在抽象类中添加具体的实现,
子类可以不进行变更;而对于接口则不行,如果接口进行
了变更,则所有实现这个接口的类都必须进行相应的改动。

下面看一个网上流传最广泛的例子:门和警报的例子:门都有 open() 和 close() 两个动作,
此时我们可以定义通过抽象类和接口来定义这个抽象概念:

abstract class Door {
    public abstract void open();
    public abstract void close();
}

或者:

interface Door {
    public abstract void open();
    public abstract void close();
}

但是现在如果我们需要门具有报警 的功能,那么该如何实现?下面提供两种思路:

1)将这三个功能都放在抽象类里面,但是这样一来所有继承于这个抽象类的子类都具备了报警功能,
但是有的门并不一定具备报警功能;

2)将这三个功能都放在接口里面,需要用到报警功能的类就需要实现这个接口中的 open( ) 和 close( ),
也许这个类根本就不具备 open( ) 和 close( ) 这两个功能,比如火灾报警器。

从这里可以看出, Door 的 open() 、close() 和 alarm() 根本就属于两个不同范畴内的行为,open() 和
close() 属于门本身固有的行为特性,而 alarm() 属于延伸的附加行为。因此最好的解决办法是单独将报
警设计为一个接口,包含 alarm() 行为,Door 设计为单独的一个抽象类,包含 open 和 close 两种行为。
再设计一个报警门继承 Door 类和实现 Alarm 接口。

interface Alram {
    void alarm();
}
 
abstract class Door {
    void open();
    void close();
}
 
class AlarmDoor extends Door implements Alarm {
    void oepn() {
      //....
    }
    void close() {
      //....
    }
    void alarm() {
      //....
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值