【Java SE】抽象类和接口(上)

简介: 首先来看到抽象这两个字,抽象其实是与具体对应的概念,在我们面向对象的概念中,所有的对象都是由类来描述的,如果我们反过来呢?

1、抽象类

1.1 抽象类的概念

首先来看到抽象这两个字,抽象其实是与具体对应的概念,在我们面向对象的概念中,所有的对象都是由类来描述的,如果我们反过来呢?所有类都是用来描述对象的吗?不一定!如果一个类中没有包含足够的信息来描述对象,这样的类就是抽象类。

拿我们上期举过的动物类来说,狗和猫,分别吃的是狗粮和猫粮,为了实现多态,子类重写了父类的 eat 方法,那其实很显然我们父类 eat 其实并没有什么实际的工作,主要的工作都是子类重写的 eat 方法,像这种没有实际工作的方法,我们就可以把他设计成一个抽象方法(abstract mewthod),而包含抽象方法的类,我们称为抽象类(abstract class)。

1.2 抽象类的语法

在Java中,被 abstract 修饰的类为抽象类, 抽象类中被 abstract 修饰的方法为抽象方法,抽象方法不能有具体的实现。

public abstract class Animal {
    private String name;
    private int age;
    //抽象类中可以有构造方法,用来初始化抽象类的成员变量
    public Animal() {};
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    //抽象类的方法没有加限定符,默认是public
    int getAge() {
        return age;
    }
    abstract void eat(); //抽象方法没有加访问限定符时,默认是public
    abstract void cry(); //抽象方法没有加访问限定符时,默认是public
}

上述代码我们来分析一下:

首先 Animal 是一个抽象类,从上往下看,定义了两个成员变量,还有构造方法,普通方法,所以可以说明抽象类也是类,内部可以包含普通方法和属性以及构造方法,在往下, 被 abstrac t修饰的方法就是抽象方法,抽象方法不能实现。

1.3 抽象类的特性

那么既然上面说抽象类也是类,抽象类可以实例化吗?不可以,他是抽象的,天生就是被继承的!

抽象类的抽象方法可以被 private 修饰吗?这显然不可以啊,private 是只能在当前类访问,抽象类是被继承的,如果是非抽象类继承,是需要重写抽象类里面的方法的,你都只能在本类中访问,如何重写呢?(抽象方法没有加访问限定符,默认是public)

抽象方法可以被 static 和 final 修饰吗?先说 static,被他修饰的,是类的属性,每个对象公共的,不是用来给你继承的,所以你如何重写被static修饰的抽象方法?再来说 final,final 修饰的不能被重写,之前就提到过。

抽象类必须被继承吗?这个问题你想一想,抽象类本身不能实例化对象,如果你不继承的话,你这个类有什么意义呢?并且子类是普通类继承了抽象类则必须重写抽象类的抽象方法,如果不想重写子类也必须被 abstract 修饰,也就是子类也得是抽象类,那如果还有一个类去继承了这个子类,则要重写这两个抽象类的抽象方法。

抽象类一定要包含抽象方法吗?不一定,只要你想就可以不包含,但是包含抽象方法的一定是抽象类!

抽象类可以有构造方法吗? 为什么抽象类不能实例化还要有构造方法?如果有这个问题,完全是前面没有学好,抽象类是被继承的,他是父类,那么子类总要实例化吧?实例化子类先通过子类构造方法得调用父类的构造方法吧,父类构造方法是用来初始化父类成员变量的吧?你说抽象类能不能有构造方法?

1.4 抽象类的作用

既然抽象类不能被实例化,想使用还必须创建该子类对象,还得子类重写抽象方法,那有什么意义呢?我用普通类也可以这样啊,普通的方法难道不能被重写吗?我就不用抽象类!

如果思维真的被这样限制的话,请你一定要打开,上面确实没毛病,但是,使用抽象类相当于多了一重编译器的校验,就比如说,实际工作并不需要父类去完成,如果你不小心使用了父类呢?如果父类是抽象类,编译器则会提示错误,让我们尽早发现,很多语法存在都是为了"预防出错",比如 final 修饰变量,我就告诉你编译器了,这个变量我不能修改,不小心修改也能提醒我们,所以合理的利用编译器的校验,在实际开发中是有意义的。

2、接口

2.1 接口的概念

在我们实际生活中,有特别多的接口,随便想几个就是,手机的充电器接口,3.5毫米耳机接口,但现在几乎消失了,再比如我们电脑USB接口,插座接口,只要你使用规范合理,都能使用这些接口,那如果你不规范,你拿一个耳机线的接口,想去接USB接口,显然不符合规范啊,那你还能用吗?

所以从上述的讲述中,接口就是公共行为规范标准,在大家实现(使用)接口时,只要符合规范标准,就可以通用,在Java中,可以把接口看作:多个类的公共规范,是一种引用数据类型。

2.2 接口的语法规则

接口的定义与类的定义是大幅度相同的,把 class 关键字 换成 interface 关键字:

public interface ITestInterface {
    public static final int a = 10;
    int b = 10; //接口中的变量默认是 public static final修饰的
    public abstract void test1(); //默认是 public abstract修饰的,可以不写
    void test2(); //如果没有写修饰符,默认也是public abstract 修饰的
}

建议(软性):

  • 创建接口的时候,用大写字母 I 开口,显然易见是表示接口
  • 接口的方法和属性不要加任何修饰符,保持代码的简洁

2.3 接口的简单使用

前面学习我们知道父类和子类之间是 extends 继承关系,而接口与类之间是使用 implements 实现关系。

接口不能直接使用,必须要有一个类来实现接口,并实现接口中的所有抽象方法,如果不想,则用抽象类来实现接口。

这里我们就来结合前面的学习,简单实现一个USB接口:

  1. USB接口:包含打开设备和关闭设备功能
  2. 鼠标类:实现USB接口,并且具备点击功能
  3. 电脑类:实现USB接口,并具备输入功能
  4. 电脑类:包含开机关机功能,使用USB设备功能
//USB接口
public interface IUsb {
    void openDevice();
    void closeDevice();
}
//鼠标类
class Mouse implements IUsb {
    @Override
    public void openDevice() {
        System.out.println("打开鼠标!");
    }
    @Override
    public void closeDevice() {
        System.out.println("关闭鼠标!");
    }
    public void click() {
        System.out.println("单击鼠标!");
    }
}
//键盘类
class Keyboard implements IUsb {
    @Override
    public void openDevice() {
        System.out.println("打开键盘!");
    }
    @Override
    public void closeDevice() {
        System.out.println("关闭键盘!");
    }
    public void input() {
        System.out.println("键盘输入!");
    }
}
//电脑类
class Computer implements IUsb {
    @Override
    public void openDevice() {
        System.out.println("打开电脑!");
    }
    @Override
    public void closeDevice() {
        System.out.println("关闭电脑!");
    }
    public void useDevice(IUsb usb) {
        usb.openDevice(); //根据你传递的对象执行打开对应设备
        if (usb instanceof Mouse) {
            Mouse m = (Mouse)usb;
            m.click(); //鼠标单击
        } else if(usb instanceof Keyboard) {
            Keyboard k = (Keyboard)usb;
            k.input(); //键盘输入
        }
        usb.closeDevice();
    }
}
//测试部分:
class TestComputer {
    public static void main(String[] args) {
        Computer c = new Computer();
        c.openDevice(); //打开电脑
        c.useDevice(new Mouse()); //传递鼠标引用
        c.useDevice(new Keyboard()); //传递键盘引用
        c.closeDevice(); //关闭电脑
    }
}

这个代码可以读一读,涉及到前面的知识点还是有一点的,比如用到了重写,以及向下转型,进行向下转型要先进行判断,然后main方法通过传递的引用不同调用不同的方法,实现了多态,最终打印的结果大家可以简单分析测试下。

2.4 接口的特性

接口可以实例化吗?虽然接口是一种引用类型,但不能直接 new 接口的对象

接口中可以实现方法吗?接口中默认的方法被 public abstract 修饰,从 Java8 开始,接口允许实现方法,但必须被 default 修饰,同时从 Java8 开始,也允许有静态的方法.

default void function1() {
        //code...
    }
static void function2() {
        //code...
    }

接口中可以包含变量吗?与其说是变量,不是说是常量, 因为在接口中定义的变量都会隐式的指定为 public static final 修饰的。

接口中能包包含构造方法和代码块吗?不可以,因为接口不能实例化,虽然能出现变量,但是是静态的是在类加载就开辟的,接口只能理解成是特殊的类,并不是类,也不存在构造方法!

接口不是类那他会生成什么字节码文件呢?虽然接口不是类,但生成的字节码也是 class 为后缀的!

类实现接口可以不重写方法吗?这个问题跟抽象类问题很像,必须重写,接口就是用来实现的,如果不想重写,用抽象类实现接口!

相关文章
|
5天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
30 4
|
12天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
10天前
|
Java
Java基础(13)抽象类、接口
本文介绍了Java面向对象编程中的抽象类和接口两个核心概念。抽象类不能被实例化,通常用于定义子类的通用方法和属性;接口则是完全抽象的类,允许声明一组方法但不实现它们。文章通过代码示例详细解析了抽象类和接口的定义及实现,并讨论了它们的区别和使用场景。
|
10天前
|
Java 测试技术 API
Java零基础-接口详解
【10月更文挑战第19天】Java零基础教学篇,手把手实践教学!
16 1
|
12天前
|
Java 测试技术 开发者
Java零基础-抽象类详解
【10月更文挑战第17天】Java零基础教学篇,手把手实践教学!
11 2
|
14天前
|
Java 测试技术 开发者
Java零基础-抽象类详解
【10月更文挑战第15天】Java零基础教学篇,手把手实践教学!
17 2
|
15天前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
27 2
|
13天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
93 38
|
10天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
1天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。