重拾JavaSE基础——抽象类、接口、代码块、final和枚举

今天继续回顾Java基础,有些东西用得不多,大家看看知道语法就好

主要内容

  1. 抽象类

    1. 抽象方法

      1. 抽象方法的写法
      2. 抽象方法是否可以私有化
    2. 抽象类的特征

      1. 抽象类有无构造器,能否实例化对象
      2. 抽象类的结构
    3. 抽象类的核心意义

      1. 被继承
      2. 部分实现,部分抽象
  2. 接口

    1. 为什么要使用接口
    2. 接口和实现类的格式
    3. 接口的结构

      1. JDK1.8之前
      2. JDK1.8之后新增
    4. 接口VS父类
    5. 接口VS抽象类
  3. 代码块

    1. 静态代码块
    2. 构造代码块
    3. 普通代码块
    4. 构造器和代码块的执行顺序
  4. final 关键字

    1. 修饰类
    2. 修饰方法
    3. 修饰变量
    4. finalabstract的关系
  5. 单例模式(概念)
  6. 枚举

    1. 为什么要用枚举
    2. 枚举的写法
    3. 枚举是个普通的类
  7. 写在最后

抽象类

自我学习Java以来,我在实际开发中基本没用过抽象类。但框架和JDK中很喜欢使用抽象类。抽象类作为一个父类,被用来描述一类事物应该具有的基本特征和功能,子类在此基础上进行扩展。是对子类的一种约束。

举个例子吧,学校的老师、校长和学生都是学校的一员,必须具备工作的能力(学习可以看作学生的工作),但是三者具体怎么工作是有差异的,具体怎么工作是自己决定的。看完这篇文章就会有概念了

构造方法

就上面的例子,如果写成代码的话应该是这样的,SchoolMember类代表学校成员,是一个父类,TeacherStudentPrinciple都继承SchoolMember类,他们都有work方法

public class SchoolMember {
    public String schoolName = "GDPU";
    public void work() {
        System.out.println("作为学校的成员一定要工作")
    }
}
class Teacher extends SchoolMember{
    @Override
    public void work() {
        System.out.println("我是个普通教师,教好自己的科目就可以了")
    }
}
class Student extends SchoolMember{
    @Override
    public void work() {
        System.out.println("我是个学生,读好书对得住爸妈就ok了")
    }
}
class Principal extends SchoolMember{
    @Override
    public void work() {
        System.out.println("我是校长,要让学校有条有理")
    }
}

大家应该注意到了,其实SchoolMember类中的work方法中的内容并没有什么作用,反而浪费了内存空间。还不如不写

public class SchoolMember {
    public String schoolName = "GDPU";
    public void work() {
        
    }
}

有一天,小明被爸妈安排到这个学校,小明便成为了学校的一员,但他无心向学在学校混日子,没有工作(学习)的能力

class Principal extends SchoolMember{
    public void play() {
        System.out.println("我就不读怎么滴")
    }
}

学校觉得小明老这样很影响学校风气,于是把小明赶出了学校,学校一片祥和。但过一段时间又有像小明一样的学生进了这个学校,学校受不了了,最后决定只让一心向学的学生入学,便在自己的work方法上加了abstract关键字,变成抽象方法,学生必须重写这个方法才能算是学校的一员,才能继承SchoolMember

抽象方法的写法

抽象方法被abstract修饰,没有方法体,只有方法签名

`public abstract 返回类型 方法签名(参数列表);

抽象方法是否可以私有化

可以,但是作为抽象方法不被子类重写还不如写成普通方法

抽象类的特征

我们可以把抽象类的特征概括为有得有失,抽象类得到了抽象方法的能力,却失去创建对象的能力。这里要注意,虽然抽象类有对方法进行抽象的能力,但他可以选择使用或不使用这种能力,也就是说,抽象类不一定有抽象方法,但是有抽象方法的类一定是抽象类

抽象类有无构造器,能否实例化对象

  • 具有构造器

    抽象类诞生的意义就是要给子类继承的,子类初始化一定会调用父类构造器super(...),所以抽象类必须有构造器

  • 不能实例化对象

    就拿上面的例子,如果`SchoolMember类被实例化,调用抽象方法将没有意义

    SchoolMember s = new SchoolMember();
    s.work(); // 方法没有实现,没有意义

    抽象类本来就意味着一类事物是抽象的(不具体的),没有必要将他实例化

抽象类的结构

抽象类除了可以有普通类都有可以有的成员变量成员方法代码块内部类*和构造器以外,还可以有抽象方法

再次强调,可以有不代表一定有哈

抽象类的核心意义

抽象类有没有体现他存在的价值,主要是看两方面,一是有没有被子类继承,二是有没有做到部分实现部分抽象的效果

被继承

一个抽象类如果不被子类继承,还不如做回普通类,还能被实例化

部分实现,部分抽象

一个抽象类作为父类,它可以先为子类实现相同的部分公共代码,剩下的由子类自由发挥。这里运用了设计模式中的模板模式

  • 设计模式:前辈在生产实践中发明的优秀软件架构或思想,后人可以直接使用这些优秀架构和思想生产优秀的代码,
  • 模板模式:使用部分实现部分抽象的思想编写模板,相同的功能不需要重复写,提高代码的可扩性,系统的可维护性,这就类似于我们使用英语作文模板,作文比较容易拿高分,第一段和最后一段都模板已经写好了,中间的部分自己写就得了

    abstract class EnglishModel{
        public void wirteTitle() {
            System.out.println("这是模板的标题");
        }
        public void wirteHead() {
            System.out.println("这是模板的第一段");
        }
        public void wirteTail() {
            System.out.println("这是模板的最后一段");
        }
        public abstract void wirteTitle();
    }
    
    class MyEnglishWord extends EnglishModel{
        
        @Override
        public void wirteTitle() {
             System.out.println("用模板真的爽");
        }
    }

    这样我们只要负责写MyEnglishWord类就可以完成这份连半个英文都没有的英语作文了

接口

说到接口,一个带我做项目的师兄跟我说,接口是业务代码的抽象。当时没懂,之后自己学框架搭项目写代码的时候慢慢就有感觉啦,我们写Service层的代码应该都是先创建一个接口再创建一个实现类实现这个接口的吧,为什么要这样做呢

为什么要使用接口

我的理解是这样的,通常一个项目是由很多人一起做,你负责一个模块我负责另一个模块,试想一下,如果没有接口,我开发的是用户基础信息模块,你开发的是订单管理模块,现在有一个场景,你创建订单后需要查一下这个用户是不是VIP,是的话给他免运费,那你的代码会有这样一句话

UserService userService = new UserService();
boolean isVIP = userService.isVIP(userId);

但是我可能还没有写这个isVIP方法,可能我判断是不是VIP的方法不叫这个名字,甚至我连这个UserService类都还没创建呢,那你的代码一定是被编译器标红,这就很难受了,你得跑过来问我判断VIP的方法叫什么名字,要传入什么参数,出来什么结果,什么时候才开始写这个方法

如果我先创建接口可以解决上面的问题

  • 你不用问我判断VIP的方法叫什么,你看我的接口文件就可以了
  • 你的代码不会被标红,因为判断`VIP的方法存在,只是还没实现
  • 你不用管我是怎么实现这个方法的,你调用就行了

所以总结一下:

  1. 接口是一种代码规范,你我都遵守才能同时一起做开发
  2. 接口也是业务代码的抽象,调用者不需要知道这个方法内部的实现过程
  3. 同时Java的接口可以弥补类的单继承的短板

接口和实现类的格式

接口

public interface 接口名 extends 接口1, 接口2...

实现类

修饰符 class 实现类的名称 implements 接口1, 接口2...

接口的结构

jdk1.8以后对接口的结构进行了修改,这里分开讲

JDK1.8以前

接口只有全局常量抽象方法l两部分

  • 全局变量

    public static final 类型 常量名 = 常量;
    常量名一般用英文大写加下划线的形式,public static可以省略不写
  • 抽象方法

    `public abstract 返回类型 方法签名(参数列表);
    这里的`public abstract可以省略不写

JDK1.8以后新增

  • 默认方法 (相当于实例成员方法,接口实现类可以直接调用)

    class Main implements A{
        public static void main(String[] args) {
            Main m = new Main();
            m.test();
        }
    }
    interface A {
        public default void test() {
            // 默认方法
        }
    }

    其中public default中的public可以省略。还有一种情况,如果Main类实现了两个接口,两个接口都有一样名字的方法怎么办?

    class Main implements A{
        public static void main(String[] args) {
            Main m = new Main();
            m.test();
        }
        
        @Override
        public void test() {
            // 真正执行的方法
            System.out.println("重写方法");
        }
    }
    interface A {
        public default void test() {
            // 默认方法A
            System.out.println("默认方法A");
        }
    }
    interface B {
        public default void test() {
            // 默认方法B
            System.out.println("默认方法B");
        }
    }
    重写方法
  • 静态方法 (必须用接口名调用)

    public static void test(){
        // 静态方法
        system.out.println("静态方法");
    }
    Test.test();
  • 私有方法 (只有内部可以调用)

    private void run() {
        // 私有方法
        system.out.println("私有方法");
    }

    这是JDK1.9后才有的部分

接口VS父类

  • 继承父类的叫子类,父类只有一个父类,可以有多个子类
  • 实现接口的叫实现类,接口可以有多个父接口,多个实现类
  • 如果父类和接口同时存在一样的方法,优先执行父类中的方法

接口VS抽象类

  • 相同点:接口和抽象类都不能被实例化,都可以包含抽象方法
  • 不同点:抽象类具有普通类的结构,但他只能单继承一个父类,接口的组成成分比抽象类少,但可以继承多个父接口

代码块

代码块也是类的五个组成成分之一,分为静态代码块构造代码块普通代码块

静态代码块

静态代码块属于类,语法如下,主要作用是初始化静态资源,如静态成员变量、数据库连接等

public static String schoolName

static{
    // 静态代码块
    schoolName = "GDPU";
}

构造代码块

构造代码块属于对象,对象被创建的时候内部代码会被执行,用于初始化对象的资源

private String studentName;

{
    // 动态代码块
    this.studentName = "Rhythm";
}

普通代码块

在成员方法里我们可以用大括号把一段代码包起来,在里面声明的局部变量在外面不能使用

public void test() {
    {
        int i = 0;
    }
    System.out.println(i); // 报错
}

构造器和代码块的执行顺序

如果是个普通类

  • 静态代码块
  • 构造代码块
  • 构造器
  • 普通代码块

如果是个子类,这个非常重要,大家要理解并记住

  • 父类静态代码块(先加载父类.class文件)
  • 子类静态代码块(再加载子类.class文件)
  • 父类构造代码块(父类对象被创建前执行)
  • 父类构造器(子类构造器执行super()
  • 子类构造代码块(子类对象被创建前执行)
  • 子类构造器(创建子类对象)

final 关键字

这个大家平时应该用过吧,它可以在类、变量和方法上出现

修饰类

该类不能被继承,断子绝孙。String类就是用final修饰的

修饰方法

  • 修饰静态成员方法 (没有什么效果)
  • 修饰实例成员方法 (方法不能被子类重写)

变量

Java中变量分为局部变量成员变量

成员变量
  • 静态成员变量:这个其实就是我们平时定义常量的方式

    public static final String USER_NAME = "Rhythm"
  • 实例成员变量:只允许一次复赋值

    private final int i = 10;
局部变量

final修饰局部变量是为了保护数据,防止程序员不小心把值给改了。比如下面这段代码,parse方法计算折后价,rate表示折扣,我们不希望rate在计算中被修改,可以加上final关键字

public class Test {
    
    public static void main(String[] args) {
        System.out.println(parse(10, 0.8));
        System.out.println(parse(100, 0.7));
    }
    
    public double parse(double rmb, final double rate) {
        return rmb * rate;
    }
}

finalabstract的关系

互斥关系final修饰的类不能被继承,但抽象类不被继承没有意义,final修饰方法不能被重写,抽象方法也没有意义

单例模式 (概念)

单例模式保证对象在运行过程中只被实例化一次,具体实现方式有很多种,这里只是对单例模式的简单引出,没有考虑并发情况

饿式

就是在类中的资源被使用时初始化对象

public class Main {

    public static void main(String[] args) {
        Demo demo = Demo.getDemo();
    }

}
class Demo {
    public static Demo demo;

    static {
        demo = new Demo();
        System.out.println("对象被初始化");
    }

    private Demo() {
        // 构造器私有
    }

    public static Demo getDemo() {
        return demo;
    }
    
    public static void other() {
        System.out.println("这是个其他方法");
    }
}

缺点就是如果类中有其他静态成员方法如other被调用的时候对象也会被初始化

懒式

就是真的需要的时候才加载,大家看代码就懂了

class Demo {
    public static Demo demo;

    private Demo() {
        // 构造器私有
    }

    public static Demo getDemo() {
        if (demo == null) {
            demo = new Demo();
        }
        return demo;
    }

    public static void other() {
        System.out.println("这是个其他方法");
    }
}

调用类中的其他方法不会初始化对象

注意:

  1. 构造器必须私有化
  2. 这里只是简单介绍下概念,以后再来总结详细的单例模式

枚举

枚举在实际开发中常常使用到,这里对他进行了总结

为什么要使用枚举

大家在代码中常常会使用常量来代替一些数字,提高代码可读性,对比一下下面两段代码,显然第二种可读性更高

public static void control(Integer i) {
    if (i = 1) {
        // 打开    
    } else {
        // 关闭
    }
}
public static void main(String[] args) {
    control(1)
}
public static final Integer OPEN = 1;
public static final Integer CLOSE = 2;

public static void control(Integer i) {
    if (i = OPEN) {
        // 打开    
    } else {
        // 关闭
    }
}
public static void main(String[] args) {
    control(OPEN)
}

但是对于我们来说,我们还是可以通过control(1)来调用方法的,为了保证代码的可读性,我们可以对这些常量进行约束管理,把常量抽出来放到一个类中

class Counst {
    public static final Integer OPEN = 1;
    public static final Integer CLOSE = 2;
}

以后使用就要这样写

if(i = Counst.OPEN) {
    // 打开
}

于是Java将这些专门存放常量的类称为枚举类,并赋予他特殊的语法

枚举的写法

举个例子,我们运行完业务代码之后需要将数据返回到前端时要带上状态码code和提示信息message,这些值都是固定的,我们可以把它们抽出来做成枚举

public enum ResultCodeEnum {

    SUCCESS(200, "操作成功"),

    FAILED(500, "操作失败");
   
    private long code;

    private String message;

    ResultCodeEnum(long code, String message) {
        this.code = code;
        this.message = message;
    }

    public long getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

总结起来就是:

  1. enum定义类
  2. 定义常量对应的变量,可以定义多个,如上面的Integer codeString message
  3. 定义常量,如上面的SUCCESS(200, "操作成功"),用逗号隔开
  4. 提供getset方法
  5. 定义一些自定义方法,如

    public static String getMessageCode(Integer code) {
    
        for (ResultCodeEnum item : ResultCodeEnum.values()) {
            if (item.getCode() == code) {
                return item.message;
            }
        }
        
        return null;
    }

枚举类是个普通的类

我们可以用javap查看一下枚举类的字节码

public final class ResultCodeEnum extends java.lang.Enum<ResultCodeEnum> {
  public static final ResultCodeEnum SUCCESS;
  public static final ResultCodeEnum FAILED;
  public static ResultCodeEnum[] values();
  public static ResultCodeEnum valueOf(java.lang.String);
  public long getCode();
  public java.lang.String getMessage();
  public static java.lang.String getMessageCode(java.lang.Integer);
  static {};

枚举类继承了lang.Enum,并初始化了SUCCESSFAILED两个ResultCodeEnum对象,提供了几个方法,所以我们在使用过程中把枚举类看成普通类就可以了

写在最后

这篇文章主要讲述了抽象类、接口、代码块、final关键字、单例模式和枚举,有些我们平时用不上的记住语法就好,面试的时候还能说一说,如果我的理解有误的话欢迎大家评论告诉我

阅读 508

推荐阅读