11.抽象类和接口

本章目标

  • 抽象类
  • 接口
  • 内部类

本章内容

在父类中,有时我们引入了某个方法,但是它存在的目的就是为了利用多态性,在父类中不能提供有意义的实现,更合适的做法是把方法的具体实现留给子类去完成,那这个方法该怎么定义?

一、抽象类

1、抽象概念

抽象:考察特定应用程序相关问题的某些方面的过程

抽象分为两类:

  • 数据抽象:识别与特定的应用程序相关的属性过程抽象:
  • 识别方法,将注意力集中在过程的参数和返回值,而不是实现

image.png

2、抽象方法

2.1、什么是抽象方法

在父类中,有时我们引入了某个方法,但是它存在的目的就是为了利用多态性,在父类中不能提供有意义的实现,更合适的做法是把方法的具体实现留给子类去完成。

为了使类、方法设计的目的更加明确,对于这种准备留给子类实现的方法使用abstract关键字将其设置为抽象方法。

抽象方法没有方法体,在声明时直接用;号结束

 public abstract void working();

2.2、抽象方法的特征:

  • 声明时没有方法体
  • 使用abstract关键字修饰
  • 不能与private同时使用
  • 不能与final同时使用
  • 不能与static同时使用
  • 不能修饰构造方法

3、抽象类

在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类

抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。

含有抽象方法的类,即成为抽象类,必须使用abstract关键字修饰。

3.1、抽象类的特点:

  • 不能实例化,可以有对象变量用以指向子类的对象
  • 必须被继承,并在子类中实现全部功能
  • 如果一个子类没有实现抽象基类中所有的抽象方法,则子类也成为一个抽象类。
  • 我们可以将一个没有任何抽象方法的类声明为abstract,避免由这个类产生任何的对象。
  • 抽象类可以有构造方法,抽象类中可能包含一些需要初始化的字段或属性。当子类继承抽象类时,子类的构造函数会隐式地调用父类的构造函数,以确保父类的字段被正确初始化

3.2、对于抽象类与抽象方法的限制如下:

  • 凡是用abstract修饰符修饰的类被称为抽象类。
  • 凡是用abstract修饰符修饰的成员方法被称为抽象方法。
  • 抽象类中可以有零个或多个抽象方法,也可以包含非抽象的方法。
  • 抽象类中可以没有抽象方法,但是,有抽象方法的类必须是抽象类。
  • 对于抽象方法来说,在抽象类中只指定其方法名及其类型,而不书写其实现代码。
  • 抽象类不能创建对象,创建对象的工作由抽象类派生的子类来实现。

4、示例一

4.1、声明抽象类

 public abstract class Employee{
     private String empId;
     private String empName;

     public abstract void training();
     public abstract void working();
     ……
 }

4.2、Salesman子类继承

 public class Salesman extends Employee {
     ……
     @Override
     public void training(){
         System.out.println(`背资料!……再背一遍资料!……`);
     }
 
     @Override
     public void working(){
         System.out.println(`签订单!……再签一张订单!……`);
     }
 ……
 }

4.3、Manager子类继承

 public class Manager extends Employee {
     ……
     @Override
     public void training() {
         System.out.println(`拓展训练!……研讨会?旅游?……`);
     }
 
     @Override
     public void working() {
         System.out.println(`开会!……分任务!……罚款!……`);
     }
     ……
 } 

5、示例二

5.1、声明抽象类

声明抽象类Quadrangle(四边形),其中有两个方法,一个是求周长getPerimeter(),一个是求面积getArea()

 public abstract class Quadrangle {
 
     public double getPerimeter(double... side) {
         return side[0]+side[1]+side[2]+side[3];
     }
 
     abstract double getArea(double... side);
 
 }
 

5.2、Square子类继承

 public class Square extends Quadrangle {
 
     @Override
     void getArea(double... side) {
         return side[0]*side[1];
     }
 
 }

其它示例效果一样

二、接口

类与类之间的协议,一套标准、约束、规范等

1、什么是接口

interface(接口)关键字使抽象的概念更深入了一层。我们可将其想象为一个抽象类。

  • 仅仅定义了类应该提供哪些功能,而不涉及如何实现这些功能;
  • 接口中仅仅包括了常量以及一系列方法的定义(但并不进行实现);
  • 接口中定义的方法需要靠其他类来实现;
  • 接口不是类,因此不能通过new关键字来实例化一个接口,但是可以声明接口类型的变量,变量必须引用该接口某个实现类的对象。

2、语法

2.1、声明接口

 [修饰符] interface 接口名[extends 父接口名列表]{
     常量数据成员声明;
     抽象方法声明;
     default方法声明;
 }

2.2、接口成员

接口中的数据成员都是用public static final修饰的,写法如下:

 修饰符 数据成员类型 数据成员名=常量值;
      或
  数据成员名=常量值;

2.3、接口方法

接口中的方法成员都是用public abstract修饰的,写法如下:

 修饰符 返回值类型 方法名(参数列表);
          或
  返回值类型 方法名(参数列表);

2.4、default方法

从jdk1.8开始,接口中可以定义多个default修饰的方法,可以带方法体

 default void method1(){//default修饰的方法
 
  }

默认方法主要优势:

1、提供了一种扩展接口的方法,而不破坏现有代码。

如果一个已经投入使用的接口需要扩展一个新的方法,在JDK8以前,我们必须再该接口的所有实现类中都添加该方法的实现,否则编译会出错。如果实现类数量很少且我们有修改的权限,可能工作量会少,但是如果实现类很多或者我们没有修改代码的权限,这样的话就很难解决了。而默认方法提供了一个实现,当没有显式提供时就默认采用这个实现,这样新添加的接口就不会破坏现有的代码。

2、默认方法另一个优势是该方法是可选的,子类可以根据不同的需求而且经override或者采用默认实现

2.5、static方法

从jdk1.8开始,接口中可以定义一个或者更多个静态方法(带方法体)。接口中的静态方法只能通过接口本身调用,不能通过实现类的实例或子类的实例调用。静态方法与接口本身关联,与具体的实现类无关。

1.8之前针对字符串操作,通常通过另一个工具类StringUtil 来提供静态操作,并不是最好的选择。Java 8 为接口新增静态方法后,可以把常用的工具方法直接写在接口上,可以更好地组织代码,更易阅读和使用

     interface StaticInterface {
         static void print() {
             System.out.println("static interface");
         }
     }
 //调用,接口名称.静态方法名(),只能通过接口本身调用,不能通过实现类的实例或子类的实例调用
  public static void main(String[] args) {
         StaticInterface.print();
   }

注意:

接口的static方法不能被子接口继承; 使用static修饰的接口中的方法必须有主体; 接口的static不能被实现类重写或直接调用; 接口的static方法只能被接口本身调用:接口名.静态方法名。

3、接口的特征

  • 使用interface关键字,必须声明为public或者不加任何修饰符。接口的修饰符隐含决定接口内方法和变量的修饰符。
  • 实现接口的类同接口一样,修饰符要么是public的,要么没有修饰符。而且如果类中实现了某个接口中的方法,那么所有实现的方法必须声明为public的。
  • 如果有类implements某个接口,但没有实现接口中所有的方法,那么该类必须声明为abstract的。可以声明一个接口的引用变量,那么所有实现此接口的类的对象引用都可以赋给此引用变量。
  • 一个接口可以继承(extends)其他接口,实现一个继承了其他接口的接口时,必须实现该接口中声明的和所继承的所有其他接口中的方法。
  • 在类中,用implements关键字就可以实现接口。一个类若要调用多个接口时,可在implements后用逗号隔开多个接口的名字

4、示例

4.1、声明图形接口

 public interface Shapes {
     public static final double PI = 3.14159;
 
     public abstract double getArea();
 
     public abstract double getPerimeter();
 
 }

4.2、声明圆实现接口

一个类实现接口,那么该类必须实现接口中所有方法

 /**
  * 声明表示圆形的实现类Circle
  *
  * @author 冯Sir
  *
  */
 public class Circle implements Shapes {
     private int radius;
 
     public double getArea() {
         return this.radius * this.radius * PI;
     }
 
     public double getPerimeter() {
         return 2 * this.radius * PI;
     }
 
     public Circle(int radius) {
         this.radius = radius;
     }
 
 }

4.3、声明四边形实现接口

四边形只实现了求周长的方式,一个类实现接口,如果不能实现接口中所有方法,那么该类要定义为抽象类

 public abstract class Quadrangle implements Shapes {
     private double sidea;
     private double sideb;
     private double sidec;
     private double sided;
 
     public Quadrangle(double sidea, double sideb, double sidec, double sided) {
         super();
         this.sidea = sidea;
         this.sideb = sideb;
         this.sidec = sidec;
         this.sided = sided;
     }
 
     @Override
     public double getPerimeter() {
         double perimeter = sidea + sideb + sidec + sided;
         return perimeter;
     }
     ……
 }

4.4、创建正方形继承四边形

 public class Square extends Quadrangle {
     public Square(double sidea, double sideb, double sidec, double sided) {
         super(sidea, sideb, sidec, sided);
     }
 
     @Override
     public double getArea() {
         double area = getSidea() * getSidec();
         return area;
     }
 
 }

4.5、测试

 public class Test {
 
     public static void main(String[] args) {
         Square square = new Square(1,1,1,1);
         double area = square.getArea();
         System.out.println(area);
     }
 
 }

5、案例(贯穿项目相关)

针对Employee类建立一个接口(IEmployeeDao)

接口里有对应的操作方法如:填加,删除,查询,修改,比如addEmployee(Employee emp)

针对接口建立一个实现类(EmployeeDaoImpl),并实现接口中的所有方法

5.1、创建接口

该接口只包含部分方法,

 package com.woniuxy.dao;
 
 import com.woniuxy.entity.Employee;
 
 public interface IEmployeeDao {
 
     public int addEmp(Employee employee);
 
     public int updateEmp(Employee employee);
 
     public Employee[] queryAll();
 
     public Employee queryById(int empId);
 
     public int delete(int empId);
 
 }

5.2、创建实现类

数组版实现方式

 package com.woniuxy.dao.impl;
 
 import com.woniuxy.dao.IEmployeeDao;
 import com.woniuxy.entity.Employee;
 
 public class EmployeeDaoImpl implements IEmployeeDao {
     Employee[] emps = new Employee[10];
     private int count = 0;
 
     @Override
     public int addEmp(Employee employee) {
         emps[count++] = employee;
         return 1;
     }
 
     @Override
     public int updateEmp(Employee employee) {
         for (int i = 0; i < emps.length; i++) {
             if (emps[i].getEmpId() == employee.getEmpId()) {
                 emps[i] = employee;
             }
         }
         return 1;
     }
 
     @Override
     public Employee[] queryAll() {
         // TODO Auto-generated method stub
         return emps;
     }
 
     @Override
     public Employee queryById(int empId) {
         Employee employee = new Employee();
         for (int i = 0; i < emps.length; i++) {
             if (emps[i].getEmpId() == empId) {
                 employee = emps[i];
             }
         }
         return employee;
     }
 
     @Override
     public int delete(int empId) {
         for (int i = 0; i < emps.length; i++) {
             if (emps[i].getEmpId() == empId) {
                 emps[i] = null;
             }
         }
         return 1;
     }
 
 }
 

5.3、测试类

测试类只测试一下添加即可,完整操作可以放到后面集合中

 package com.woniuxy;
 
 import java.util.Scanner;
 
 import com.woniuxy.dao.IEmployeeDao;
 import com.woniuxy.dao.impl.EmployeeDaoImpl;
 import com.woniuxy.entity.Employee;
 
 public class Main {
 
     public static void main(String[] args) {
         System.out.println("欢迎进入员工管理系统!!");
         Scanner scanner = new Scanner(System.in);
         IEmployeeDao empDao = new EmployeeDaoImpl();
         while(true) {
             System.out.println("请选择操作:1、查询全部 2、添加 3、修改 4、删除 5、根据id查询  9 、退出 ");
             int num = scanner.nextInt();
             if(num==9) {
                 break;
             }else if(num==1) {
                 Employee[] queryAll = empDao.queryAll();
                 for (Employee employee : queryAll) {
                     if(employee!=null) {
                         System.out.println(employee);
                     }
                 }

             }else if(num==2) {
                 System.out.println("请输入员工编号");
                 int id = scanner.nextInt();
                 System.out.println("请输入员工姓名");
                 String name = scanner.next();
                 System.out.println("请输入性别 ");
                 String sex = scanner.next();
                 System.out.println("请输入薪水");
                 double salary = scanner.nextDouble();
                 System.out.println("请输入年龄");
                 int age = scanner.nextInt();
                 Employee employee = new Employee(id, name, age, sex, salary);
                 int result = empDao.addEmp(employee);
                 if(result>0) {
                     System.out.println("插入成功");
                 }else {
                     System.out.println("插入失败");
                 }

             }else if(num==3) {
                 System.out.println("请输入员工编号");
                 int id = scanner.nextInt();
                 System.out.println("请输入员工姓名");
                 String name = scanner.next();
                 System.out.println("请输入性别 ");
                 String sex = scanner.next();
                 System.out.println("请输入薪水");
                 double salary = scanner.nextDouble();
                 System.out.println("请输入年龄");
                 int age = scanner.nextInt();
                 Employee employee = new Employee(id, name, age, sex, salary);
                 int result = empDao.updateEmp(employee);
                 if(result>0) {
                     System.out.println("修改成功");
                 }else {
                     System.out.println("修改失败");
                 }

             }else if(num==4) {
                 System.out.println("请输入员工编号");
                 int id = scanner.nextInt();
                 int result = empDao.deleteEmp(id);
                 if(result>0) {
                     System.out.println("删除成功");
                 }else {
                     System.out.println("删除失败");
                 }

             }else if(num==5) {
                 System.out.println("请输入员工编号");
                 int id = scanner.nextInt();
                 Employee employee = empDao.queryById(id);
                 System.out.println(employee);

             }

         }
 
     }
 
 }

三、内部类(理解)

内部类从JDK1.1开始引入,它的作用域由包含它的类的作用域决定。

1、定义

可以在一个类的内部定义另一个类,这种类称为内部类。

内部类分为两种类型:

  • 成员内部类
  • 方法内部类
  • 匿名内部类
  • 静态嵌套类

平常我们使用的最多的是匿名内部类

2、场景

使用内部类的主要原因包括:

  • 内部类可以方便的访问该类定义所在作用域中的数据,包括私有数据;
  • 可以对同一个包中的其他类隐藏;
  • 使用匿名内部类可以很方便的定义回调方法,这一优点在Swing开发中大量地被使用到

3、特性

  • 内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类命和$符号。
  • 内部类不能用普通的方式访问。
  • 内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否是private的

4、成员内部类

作为类的一个成员存在,和类成员是同级别的

 public class Outer {
     private int i = 10;
 
     public void makeInner() {
         Inner in = new Inner();
         in.seeOuter();
     }
 
     class Inner {
         public void seeOuter() {
             System.out.print(i);
         }
     }
 
     public static void main(String[] args) {
         Outer out = new Outer();
         out.makeInner();
     }
 }
 

5、方法内部类

在方法内定义的类

  • 定义在方法中的内部类只在方法内部可见,在外部类及外部类的其它方法中都不可见。
  • 方法内部类只能在定义该内部类的方法内实例化,不可以在此方法外对其实例化
public class Outer {
  public void doSomething() {
	  final int a = 10;
	  class Inner {
		  public void seeOuter() {
			  System.out.println(a);
		  }
	  }
	  Inner in = new Inner();
	  in.seeOuter();
  }
 
  public static void main(String[] args) {
	  Outer out = new Outer();
	  out.doSomething();
	  }
 }

6、匿名内部类

顾名思义,没有名字的内部类,只有实现的接口或继承的父类的名字

 public class Test {
 
     public static void main(String[] args) {
         Car car = new Car() {
             public void drive() {
                 System.out.println("Driving another car!");
             }
         };
         car.drive();
     }
 }
 
 class Car {
     public void drive() {
         System.out.println("Driving a car!");
     }
 }

思维导图

image

posted @ 2025-03-31 17:45  icui4cu  阅读(57)  评论(0)    收藏  举报