Java 封装、继承、多态(含接口、重写、重载、抽象类)

面向对象的三大特征:封装、继承和多态,是Java编程的重要基石。

通过封装,我们可以隐藏对象的内部细节,保护数据的安全;

通过继承,我们可以实现代码的重用和扩展;

通过多态,我们可以使对象在不同的情况下表现出不同的行为。

一、封装

软件工程所要追求的一个标准就是模块内的高内聚低耦合,在面向对象程序设计方法中尤其如此。封装是指一种将抽象性函数接口的实现细节部分包装、隐藏起来的方法

封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问

要访问该类的代码和数据,必须通过严格的接口控制。

封装最主要的功能在于在修改自己的实现代码时,而不用修改那些调用此代码的程序片段

适当的封装可以让程序更容易理解与维护,也加强了程序的安全性。

封装的特点:

1. 低耦合。

2. 对成员变量进行更精确的控制。

3. 隐藏信息和实现细节。

实现Java封装的步骤

1. 修改属性的可见性来限制对属性的访问(一般限制为private),例如:

public class Person {
    private String name;
    private int age;
}
//将 name 和 age 属性设置为私有的,只能本类才能访问,其他类都访问不了

2. 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问,例如:

public class Person{
    private String name;
    private int age;
​
    public int getAge(){
      return age;
    }
​
    public String getName(){
      return name;
    }
​
    public void setAge(int age){
      if(age > 120){  
     System.out.println("ERROR");
    }else{  
       this.age = age;  
    }
​
    public void setName(String name){
      this.name = name;
    }
}
/*
采用 this 关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突,也就是所谓的set和get方法
*/

实例:getter和setter方法

public class Test{
 
   private String name;
   private int age;
 
   public int getAge(){
      return age;
   }
 
   public String getName(){
      return name;
   }
 
 
   public void setAge( int newAge){
      age = newAge;
   }
 
   public void setName(String newName){
      name = newName;
   }

}

public class Runt{
   public static void main(String args[]){
      Test t = new Test();
      t.setName("y");
      t.setAge(23);
 
      System.out.print("Name : " + t.getName()+ " -- Age : "+ t.getAge());
    }
}
//Name : y -- Age : 23

二、继承 

2.1 简述

1、继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类

2、继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率。

3、继承者是被继承者的特殊化,它除了拥有被继承者的特性外,还拥有自己的独有特性。例如猫有抓老鼠、爬树等老鼠没有的特性。同时在继承关系中,继承者完全可以替换被继承者,反之则不可以,例如我们可以说猫是动物,但不能说动物是猫,对于这个我们将其称之为“向上转型”。

4、为什么需要继承,对于若干个相同或相似的类,我们可以抽象出他们共有的行为属性并将其定义成一个父类或者超类,然后继承该父类,子类不仅可以拥有父类的属性、方法还可以定义自己独有的属性或者方法,就不需要写很多遍重复的子类个体属性了。

继承的特点:

1、子类拥有父类非private的属性和方法。

2、子类可以拥有自己属性和方法,即子类可以对父类进行扩展。

3、子类可以用自己的方式实现父类的方法。

4、高耦合。

5、破坏封装性而提高了代码可复用性。

6、Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,而多重继承,例如 B 类继承 A 类,C 类继承 B 类,所以C 类继承了A类和B类,这是 Java 继承区别于 C++ 继承的一个特性。

7、 子类可以继承父类中的静态方法,但是不能重写父类中的静态方法(向上转型中)。

//在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的
class 父类 {
}
 
class 子类 extends 父类 {
}
//实例:企鹅类继承,子类不能直接继承父类中的 private 属性和方法
public class Animal { 
    private String name;  
    private int id; 
    public Animal(String myName, int myid) { 
        name = myName; 
        id = myid;
    } 
    public void eat(){ 
        System.out.println(name+"正在吃"); 
    }
    public void sleep(){
        System.out.println(name+"正在睡");
    }
    public void introduction() { 
        System.out.println("大家好!我是"         + id + "号" + name + "."); 
    } 
}

public class Penguin extends Animal { 
    public Penguin(String myName, int myid) { 
        super(myName, myid); // 声明继承父类中的两个属性
    } 
}
public class PenguinQQ {
    public static void main(String[] args) {
        //具体通过有参构造函数进行继承
        Penguin QQ = new Penguin("小冰",10086);
        //调用一个有参构造方法
        QQ.eat();
        QQ.sleep();
        QQ.introduction();
    }
}
/*
小冰正在吃
小冰正在睡
大家好!我是10086号小冰.
*/

2.2 继承类型

Java 封装、继承、多态(含接口、重写、重载、抽象类)_第1张图片

 Java 不支持多继承,但支持多重继承,具体请看 参考文献2:java实现多继承的三种方式-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_43369592/article/details/121733590

2.3 继承关键字

继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承 Object(这个类在 java.lang 包中,所以不需要 import)祖先类。

extends关键字

在 Java 中,类的继承是单一继承,一个子类只能拥有一个父类,所以 extends 只能继承一个类。

public class Animal { 
    private String name;   
    private int id; 
    public Animal(String myName, int myid) { 
        //初始化属性值
    } 
    public void eat() {  //吃的具体实现  } 
    public void sleep() { //睡的具体实现  } 
} 
 
public class Penguin  extends  Animal{ 
}

implements关键字

使用 implements 关键字可以使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。

public interface A {
    public void eat();
    public void sleep();
}
 
public interface B {
    public void show();
}
 
public class C implements A,B {
}

super 与 this 关键字

我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。

this关键字:指向自己的引用

class Animal {
  void eat() {
    System.out.println("animal : eat");
  }
}
 
class Dog extends Animal {
  void eat() {
    System.out.println("dog : eat");
  }
  void eatTest() {
    this.eat();   // this 调用自己的方法
    super.eat();  // super 调用父类方法
  }
}
 
public class Test {
  public static void main(String[] args) {
    Animal a = new Animal();
    a.eat();
    Dog d = new Dog();
    d.eatTest();
  }
}
/*
animal : eat
dog : eat
animal : eat
*/

final 关键字

final 可以用来修饰变量(包括类属性、对象属性、局部变量和形参)、方法(包括类方法和对象方法)和类。使用 final 关键字声明类,就是把类定义定义为最终类,不能被继承,或者用于修饰方法,该方法不能被子类重写。 final 定义的类,其中的属性、方法不是 final 的。

特点:

1、final 修饰类中的属性或者变量

无论属性是基本类型还是引用类型,final 所起的作用都是变量里面存放的"值"不能变。

这个值,对于基本类型来说,变量里面放的就是实实在在的值,如 1,"abc" 等。

而引用类型变量里面放的是个地址,所以用 final 修饰引用类型变量指的是它里面的地址不能变,并不是说这个地址所指向的对象或数组的内容不可以变,这个一定要注意。

例如:类中有一个属性是 final Person p=new Person("name"); 那么你不能对 p 进行重新赋值,但是可以改变 p 里面属性的值 p.setName('newName');

final 修饰属性,声明变量时可以不赋值,而且一旦赋值就不能被修改了。对 final 属性可以在三个地方赋值:声明时、初始化块中、构造方法中,总之一定要赋值。

2、final修饰类中的方法

作用:可以被继承,但继承后不能被重写。

3、final修饰

作用:类不可以被继承。

//1、声明类:
final class 类名 {

     //类体

}
//2、声明方法:
public/private/default/protected final 返回值类型 方法名(){

     //方法体

}

2.4 构造器

子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表,子类构造函数中第一条语句如未写带参或不带参的super(),则默认为 super(),所以自动调用父类的无参构造器

如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器

class SuperClass {
  private int n;
  SuperClass(){
    System.out.println("SuperClass()");
  }
  SuperClass(int n) {
    System.out.println("SuperClass(int n)");
    this.n = n;
  }
}
// SubClass 类继承
class SubClass extends SuperClass{
  private int n;
  
  SubClass(){ // 自动调用父类的无参数构造器
    System.out.println("SubClass");
  }  
  
  public SubClass(int n){ 
    super(300);  // 调用父类中带有参数的构造器
    System.out.println("SubClass(int n):"+n);
    this.n = n;
  }
}
// SubClass2 类继承
class SubClass2 extends SuperClass{
  private int n;
  
  SubClass2(){
    super(300);  // 调用父类中带有参数的构造器
    System.out.println("SubClass2");
  }  
  
  public SubClass2(int n){ // 自动调用父类的无参数构造器
    System.out.println("SubClass2(int n):"+n);
    this.n = n;
  }
}
public class TestSuperSub{
  public static void main (String args[]){
    System.out.println("------SubClass 类继承------");
    SubClass sc1 = new SubClass();
    SubClass sc2 = new SubClass(100); 
    System.out.println("------SubClass2 类继承------");
    SubClass2 sc3 = new SubClass2();
    SubClass2 sc4 = new SubClass2(200); 
  }
}
/*
------SubClass 类继承------
SuperClass()
SubClass
SuperClass(int n)
SubClass(int n):100
------SubClass2 类继承------
SuperClass(int n)
SubClass2
SuperClass()
SubClass2(int n):200
*/

2.5 向上转型与向下转型

向上转型

所有的人类都是动物,所以父类是动物,人类是子类。a是父类animal引用,new Human()创建的是子类对象。简单来说就是:前面是父类,后面new的是子类,就叫向上转型。

特点:

  1. 关于方法:a可以调用子类和父类公用的方法(如果子类重写了父类的方法,则调用子类的方法),子类特有的方法无法调用
  2. 关于属性: a可以调用父类的属性,不可以调用子类的属性。
  3. a 实际指向的是 Human 子类,故调用时会调用子类本身的方法。需要注意的是向上转型时 a 会遗失除与父类对象共有的其他自有方法

Human h=new Human();
h.eat();//人类
Animal a=new Animal();
a.eat();//动物
Animal a=new Human();//向上转型

向下转型

子类对象指向父类引用,就是向下转型。

特点:

  1. 关于方法:可以调用子类的所有方法和属性,类型转为子类。
  2. 关于属性:调用的属性为子类的属性。
  3. 向上转型是为了调用父类与子类共有的方法中,父类的方法(重写了就用子类的),向下转型是为了使用完后转回子类,以此才能调用父类中没有的方法
  4. 先向上转型,才能向下转型。 

实例1:

public class Animal {
    public String name = "Animal:动物";
    public static String staticName = "Animal:可爱的动物";

    public void eat() {
        System.out.println("Animal:吃饭");
    }

    public static void staticEat() {
        System.out.println("Animal:动物在吃饭");
    }
}
public class Cat extends Animal {
    public String name = "Cat:猫";
    public String str = "Cat:可爱的小猫";
    public static String staticName = "Dog:我是喵星人";

    public void eat() {
        System.out.println("Cat:吃饭");
    }

    public static void staticEat() {
        System.out.println("Cat:猫在吃饭");
    }

    public void eatMethod() {
        System.out.println("Cat:猫喜欢吃鱼");
    }

    public static void main(String[] args) {
        Animal animal = new Cat();//向上转型
        Cat cat = (Cat) animal; // 向下转型
        System.out.println(animal.name); // 输出Animal类的name变量
        System.out.println(animal.staticName); // 输出Animal类的staticName变量
        animal.eat(); // 输出Cat类的eat()方法
        animal.staticEat(); // 有static修饰,所以输出Animal类的staticEat()方法
        System.out.println(cat.str); // 调用Cat类的str变量
        cat.eatMethod(); // 调用Cat类的eatMethod()方法
    }
}

/*
Animal:动物
Animal:可爱的动物
Cat:吃饭
Animal:动物在吃饭
Cat:可爱的小猫
Cat:猫喜欢吃鱼
*/

以上代码中:
对于向下转型,必须进行强制类型转换;对于向上转型,不必使用强制类型转换。
如果:
animal.str = "";    // 编译出错,提示Animal类中没有str属性
animal.eatMethod();    // 编译出错,提示Animal类中没有eatMethod()方法
但是:
((Cat)animal).str = "";    // 进行强制类型转换,编译成功
((Cat)animal).eatMethod();    // 编译成功

实例2:

import java.io.IOException;
public class Main {
    public static void main(String[] args) throws IOException {
        People a=new People();
        People b=new downcasting("第三行的输出在向下转型后才能使用");
        a.say();
        b.say();
        System.out.println(((downcasting)b).thing); // 强制类型转换
    }
}

class People{
    public void say(){
        System.out.println("父类的say输出");
    }
}

class downcasting extends People{
    public String thing;
    public downcasting(String s){
        thing=s;
    }

    public void say(){
        System.out.println("子类的say输出");
    }
}
/*
父类的say输出
子类的say输出
第三行的输出在向下转型后才能使用
*/

三、多态

3.1 简述

多态是同一个行为具有多个不同表现形式或形态的能力

多态就是同一个接口,使用不同的实例而执行不同操作,例如:

现实中,比如我们键盘按下 F1 键:

  • 如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
  • 如果当前在 Word 下弹出的就是 Word 帮助;
  • 在 Windows 下弹出的就是 Windows 帮助和支持。

同一个事件发生在不同的对象上会产生不同的结果。

优点:

  • 1. 消除类型之间的耦合关系
  • 2. 可替换性
  • 3. 可扩充性
  • 4. 接口性
  • 5. 灵活性
  • 6. 简化性

多态存在的三个必要条件:

  • 继承
  • 重写
  • 父类引用指向子类对象:Parent p = new Child();——向上转型

Java 封装、继承、多态(含接口、重写、重载、抽象类)_第2张图片

实例:

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();  
        }  
    }  
}
 
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("看家");  
    }  
}
/*
吃鱼
抓老鼠
吃骨头
看家
吃鱼
抓老鼠
*/

3.2 虚函数

虚函数的存在是为了多态。

Java 中其实没有虚函数的概念,它的普通函数就相当于 C++ 的虚函数,动态绑定是Java的默认行为。如果 Java 中不希望某个函数具有虚函数特性,可以加上 final 关键字变成非虚函数。

3.3 重写与重载

重写(Override)

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都不能改变。

重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法

重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,抛出 IOException 异常或者 IOException 的子类异常。

在面向对象原则里,重写意味着可以重写任何现有方法

//最简单的重写:修改继承的父类方法
class Animal{
   public void move(){
      System.out.println("动物可以移动");
   }
}
 
class Dog extends Animal{
   //重写
   @Override
   public void move(){
      System.out.println("狗可以跑和走");
   }
   public void bark(){
      System.out.println("狗可以吠叫");
   }
}
 
public class TestDog{
   public static void main(String args[]){
      Animal a = new Animal(); // Animal 对象
      Animal b = new Dog(); // Dog 对象,向上转型
 
      a.move();// 执行 Animal 类的方法,输出动物可以移动
      b.move();//执行 Dog 类的方法,输出狗可以跑和走
      b.bark();//由于向上转型,父类无bark()方法,所以b无法调用,会出错
   }
}
/*
TestDog.java:30: cannot find symbol
symbol  : method bark()
location: class Animal
                b.bark();
                 ^
*/
方法的重写规则
  • 参数列表与被重写方法的参数列表必须完全相同。

  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。

  • 子类访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。(public>protected>default>private)

  • 父类的成员方法只能被它的子类重写。

  • 声明为 final 的方法不能被重写。

  • 声明为 static 的方法不能被重写,但是能够被再次声明。

  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。

  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。

  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。

  • 构造方法不能被重写。

  • 如果不能继承一个类,则不能重写该类的方法。 

重载(Overload)

在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,每个重载的方法(或者构造函数)都必须有一个独有的参数类型列表。

重载规则:

  • 被重载的方法必须改变参数列表(参数个数或类型不一样);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载;
  • 无法以返回值类型作为重载函数的区分标准;
public class Overloading {
    public int test(){
        System.out.println("test1");
        return 1;
    }
 
    public void test(int a){
        System.out.println("test2");
    }   
 
    //以下两个参数类型顺序不同
    public String test(int a,String s){
        System.out.println("test3");
        return "returntest3";
    }   
 
    public String test(String s,int a){
        System.out.println("test4");
        return "returntest4";
    }   
 
    public static void main(String[] args){
        Overloading o = new Overloading();
        System.out.println(o.test());
        o.test(1);
        System.out.println(o.test(1,"test3"));
        System.out.println(o.test("test4",1));
    }
}
/*
test1
1
test2
test3
returntest3
test4
returntest4
*/

 Java 封装、继承、多态(含接口、重写、重载、抽象类)_第3张图片

Java 封装、继承、多态(含接口、重写、重载、抽象类)_第4张图片

方法的重载(Overloading)和重写(Overriding)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式,前者实现的是编译时的多态性,而后者实现的是运行时的多态性。

  • (1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
  • (2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)就称为重写(Overriding)。
  • (3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。

3.4 接口(Interface)

一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

接口的特点

  • 1、接口不能用于实例化对象,没有构造方法。
  • 2、接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。默认方法(default method):JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;静态方法(static method):JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)。
  • 3、接口不能包含成员变量,除了 static 和 final 变量。
  • 4、接口不是被类继承了,而是要被类实现。
  • 5、接口支持多继承。
  • 6、接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
  • 7、接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
  • 8、接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
  • 9、接口是隐式抽象的,当声明一个接口或者方法的时候,不必使用abstract关键字。
  • 10、接口中的方法都是公有的。
  • 11、一个类只能继承一个类,但是能继承实现多个接口。
  • 12、一个接口能继承另一个接口,这和类之间的继承比较相似。

实例1:普通重写

interface Animal {
   public void eat();
   public void travel();
}
public class MammalInt implements Animal{
 
   public void eat(){
      System.out.println("Mammal eats");
   }
 
   public void travel(){
      System.out.println("Mammal travels");
   } 
 
   public int noOfLegs(){
      return 0;
   }
 
   public static void main(String args[]){
      MammalInt m = new MammalInt();
      m.eat();
      m.travel();
   }
}
/*
Mammal eats
Mammal travels
*/

实例2: 接口的默认方法、静态方法实现

//定义一个接口

public interface Inter {

    void show(); //抽象方法   

    default void method() { //默认方法
        System.out.println("默认方法被实现了");    }

    static void test(){ //静态方法
        System.out.println("静态方法被实现了");    }
}

//定义接口的一个实现类

public class Interlmpl implements Inter {
    @Override    
    public void show() {
        System.out.println("show方法");    }
}

//定义测试类

public class InterDemo {
  public static void main(String[] args) {
    Inter i = new Interlmpl();        
    i.show();        //抽象方法强制被重写
    i.method();      //默认方法不强制被重写,但可以被重写,重写时去掉default关键字        
    Inter.test();   //静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
  }
}
/*
show方法,已经重写
默认方法被实现了,可以重写
静态方法被实现了,只能接口名调用,不能被重写
*/

接口的继承 

public interface Sports
{
   public void setHomeTeam(String name);
   public void setVisitingTeam(String name);
}
 
public interface Football extends Sports
{
   public void homeTeamScored(int points);
   public void visitingTeamScored(int points);
   public void endOfQuarter(int quarter);
}
 
public interface Hockey extends Sports
{
   public void homeGoalScored();
   public void visitingGoalScored();
   public void endOfPeriod(int period);
   public void overtimePeriod(int ot);
}
/*
Hockey接口自己声明了四个方法,从Sports接口继承了两个方法,这样,实现Hockey接口的类
必须实现六个方法;实现Football接口的类必须实现五个方法,其中两个来自于Sports接口。*/

在Java中,类的多继承是不合法,但接口允许多继承。

public interface Hockey extends Sports, Event {......}

标记接口

最常用的继承接口是没有包含任何方法的接口。标记接口是没有任何方法和属性的接口.它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。简单来说就是让外人知道这类接口主要用来干什么,然后在这个接口下面继承并细分小接口,主要用于以下两种目的:

package java.util;
public interface EventListener
{  }

  • 建立一个公共的父接口:

    正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。

  • 向一个类添加数据类型:

    这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。

3.5 抽象类

抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。由于抽象类不能实例化对象,所以抽象类必须被继承才能被使用。

抽象类和接口有一定相似性,接口是子类继承后必须把父类有的方法全部实现,而抽象类则不需要子类全部实现父类方法,只需要实现父类中的抽象方法,如果连父类中的抽象方法都不用实现,那么此子类也是一个抽象类。

抽象类的特点

  • 1. 抽象类不能被实例化,只有抽象类的非抽象子类可以创建对象。

  • 2. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

  • 3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。

  • 4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。

  • 5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。

  • 6. 抽象方法必须被重写,所以static、final、native、private不能被用来修饰abstract。

实例1:普通继承抽象类

//继承一个抽象类和继承一个普通类没什么不同
public abstract class Employee
{
   private String name;
   private String address;
   private int number;
   public Employee(String name, String address, int number)
   {
      System.out.println("Constructing an Employee");
      this.name = name;
      this.address = address;
      this.number = number;
   }
   public double computePay()
   {
     System.out.println("Inside Employee computePay");
     return 0.0;
   }
   public void mailCheck()
   {
      System.out.println("Mailing a check to " + this.name
       + " " + this.address);
   }
   public String toString()
   {
      return name + " " + address + " " + number;
   }
   public String getName()
   {
      return name;
   }
   public String getAddress()
   {
      return address;
   }
   public void setAddress(String newAddress)
   {
      address = newAddress;
   }
   public int getNumber()
   {
     return number;
   }
}
public class Salary extends Employee
{
   private double salary; 
   public Salary(String name, String address, int number, double
      salary)
   {
       super(name, address, number);
       setSalary(salary);
   }
   public void mailCheck()
   {
       System.out.println("Within mailCheck of Salary class ");
       System.out.println("Mailing check to " + getName()
       + " with salary " + salary);
   }
   public double getSalary()
   {
       return salary;
   }
   public void setSalary(double newSalary)
   {
       if(newSalary >= 0.0)
       {
          salary = newSalary;
       }
   }
   public double computePay()
   {
      System.out.println("Computing salary pay for " + getName());
      return salary/52;
   }
}
public class AbstractDemo
{
   public static void main(String [] args)
   {
      //继承且重写了Employee
      Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00);
      //向上转型,还是用的子类方法
      Employee e = new Salary("John Adams", "Boston, MA", 2, 2400.00);
 
      System.out.println("Call mailCheck using Salary reference --");
      s.mailCheck();
 
      System.out.println("\n Call mailCheck using Employee reference--");
      e.mailCheck();
    }
}
/*
Constructing an Employee
Constructing an Employee
Call mailCheck using  Salary reference --
Within mailCheck of Salary class
Mailing check to Mohd Mohtashim with salary 3600.0

Call mailCheck using Employee reference--
Within mailCheck of Salary class
Mailing check to John Adams with salary 2400.
*/

实例2:继承抽象方法

public abstract class Employee
{
   private String name;
   private String address;
   private int number;
   
   public abstract double computePay();
   
   //实例1中其余代码
}
public class Salary extends Employee
{
   private double salary; 
   //由于父类是抽象方法,必须继承下来
   public double computePay()
   {
      System.out.println("Computing salary pay for " + getName());
      return salary/52;
   }
 
   //实例1中其余代码
}

抽象类与接口的区别

1、抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。

2、设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,因为接口可以多继承,所以它是一种辐射式设计。对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动

下面看门和警报的例子:门都有 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() {
      //....
    }
}

以下是多态的经典实例:这个还是自己去悟吧,不太好讲清楚,把向上向下转型研究清楚就好说了,实在不行请看参考文献1: 

面向对象编程三大特性------封装、继承、多态-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/jianyuerensheng/article/details/51602015

public class A {  
    public String show(D obj) {  
        return ("A and D");  
    }  
  
    public String show(A obj) {  
        return ("A and A");  
    }   
  
}  
  
public class B extends A{  
    public String show(B obj){  
        return ("B and B");  
    }  
      
    public String show(A obj){  
        return ("B and A");  
    }   
}  
  
public class C extends B{  
  
}  
  
public class D extends B{  
  
}  
  
public class Test {  
    public static void main(String[] args) {  
        A a1 = new A();  
        A a2 = new B();  
        B b = new B();  
        C c = new C();  
        D d = new D();  
          
        System.out.println("1--" + a1.show(b));  
        System.out.println("2--" + a1.show(c));  
        System.out.println("3--" + a1.show(d));  
        System.out.println("4--" + a2.show(b));  
        System.out.println("5--" + a2.show(c));  
        System.out.println("6--" + a2.show(d));  
        System.out.println("7--" + b.show(b));  
        System.out.println("8--" + b.show(c));  
        System.out.println("9--" + b.show(d));        
    }  
}  
/*
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D
*/

参考资料:菜鸟教程 - 学的不仅是技术,更是梦想!

参考文献1:面向对象编程三大特性------封装、继承、多态-CSDN博客

参考文献2:java实现多继承的三种方式-CSDN博客

参考文献4:三分钟带你搞懂什么是向上转型和向下转型_什么是想上转型-CSDN博客

参考文献5:Java对象类型转换:向上转型和向下转型 (biancheng.net)

参考文献6:Java—重写与重载的区别_重载和重写的区别-CSDN博客

参考文献7:Java中的静态static方法能否被重写_javastatic方法可以重写吗-CSDN博客

你可能感兴趣的:(JAVA,软件工程,java,开发语言,软件工程)