JavaSE - 面向对象-类的三大特性

JavaSE - 面向对象-类的三大特性

本节学习目标:

  • 了解并掌握封装的概念与实现;
  • 了解并掌握继承的概念与实现;
  • 了解Object类;
  • 了解并掌握对象类型的转换;
  • 了解并掌握方法的相关操作;
  • 了解并掌握多态的概念与实现;
  • 了解并掌握抽象类和接口。

1. 封装

在面向对象编程思想中,封装(Encapsulation)是指一种将方法的实现细节包装,隐藏起来的方法。
封装可以被认为是一种保护机制,防止外部代码直接访问本类的成员变量或成员方法。要访问本类的代码和数据,必须通过严格的控制。
封装最主要的功能在于降低耦合,修改自己的实现代码而不用修改外部调用的代码。适当的封装可以让代码更容易理解和维护,同时也加强了代码的安全性。

如果个人信息是公有的(public),任何程序都能随意访问,那么生活中人的个人信息就可以毫无阻挡的传遍世界,随意变卖。垃圾短信源源不断,骚扰电话接二连三,
垃圾邮件铺天盖地,身份信息,银行存款都将变得极不安全,社会陷入恐慌,所以封装的重要性不言而喻。

封装的特点:

  • 合适的封装可以降低类与类之间的依赖性(耦合);
  • 类内部的结构可以自由修改;
  • 使代码更简洁,容易理解和维护;
  • 可以对成员变量和成员方法进行更精确地控制;
  • 可以隐藏代码内部实现细节。

1.1 类的封装

在Java中使用访问权限修饰符publicprotectedprivate)来实现封装:

public class Person {
    private String name;
    private int age;
    private String tel;
}

使用private修饰符可以防止外部类直接访问成员变量。我们可以提供对每个成员变量定义对外的公共方法访问(创建一对赋取值方法(Getter和Setter))用于外部对私有属性的访问:

public class Person {
    private String name;
    private int age;
    private String tel;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getTel() {
        return tel;
    }
    public void setTel(String tel) {
        this.tel = tel;
    }
}

1.2 功能的封装

对某个功能的封装可以提高代码可重用性,将常用的代码块可以封装成类或者方法,使用时可以直接调用,无需重新编写。

比如在数组章节经常用的生成指定长度的随机数数组,可以封装为一个工具类:

import java.util.Random;
public class RandomArrayGenerator {
    public static int[] generate(int length) {
        if (length <= 0) {
            throw new ArrayIndexOutOfBoundsException(length); // 判断如果指定的长度小于等于0,则抛出异常
        }
        int[] arr = new int[length];
        Random random = new Random();
        for (int i = 0; i < arr.length; i++) {
            arr[i] = random.nextInt(100);
        }
        return arr;
    }
}

之后使用这个功能就无需编写这些代码,直接调用RandomArrayGenerator.generate()方法即可。

2. 类的继承

继承(Inheritance)在面向对象编程思想中是一个非常重要的概念,它使整个程序架构有一定的弹性,在程序中可以复用一些已经定义完善的类不仅可以减少软件开发周期,
也可以提高软件的可维护性和可扩展性。

在Java中使用extends关键字来标识两个类之间的继承关系。编写代码来演示继承及其意义:

定义Teacher类:

public class Teacher {
    private String name;
    public Teacher(String name) {
        this.name = name;
    }
    public void dining() {
        System.out.println(this.name + "正在用餐");
    }
    public void sleep() {
        System.out.println(this.name + "正在睡觉");
    }
}

定义Driver类:

public class Driver {
    private String name;
    public Driver(String name) {
        this.name = name;
    }
    public void dining() {
        System.out.println(this.name + "正在用餐");
    }
    public void sleep() {
        System.out.println(this.name + "正在睡觉");
    }
}

上面两个类的内容可以看出内容大量重复,代码量大且臃肿,而且维护性差。所以需要继承来解决这种问题,将两段代码中相同的部分抽取出来组成一个父类Person

public class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
    public void dining() {
        System.out.println(this.name + "正在用餐");
    }
    public void sleep() {
        System.out.println(this.name + "正在睡觉");
    }
}

然后将Teacher类和Driver类继承Person类:

public class Teacher extends Person {
    public Teacher(String name) {
        super(name);
    }
}
public class Driver extends Person {
    public Driver(String name) {
        super(name);
    }
}

继承之后,子类就拥有父类当中的属性和方法(使用super关键字访问),子类中就不必再写重复的代码。代码变得更简洁,维护性提高,也提高了代码的可重用性。

Java语言只支持单继承不支持多继承。继承类型示意图( 继承类型 - Java 继承/菜鸟教程 ):

继承类型示意图

继承的特点:

  • Java只支持单继承,不支持多继承;
  • 子类拥有父类非private的属性和方法;
  • 子类也可以定义自己的属性和方法(对父类进行扩展);
  • 子类可以使用自己的方式实现父类的方法(重写父类方法);
  • 提高了类与类之间的耦合性(缺点)。

3. Object类

在之前的学习中就已经接触过Object类,了解了继承的概念后,我们就需要深入学习Object类。

Object类在java.lang包下,是Java中所有类的父类,所有的类都直接或间接继承Object类。当创建一个类时,如果没有明确指定此类继承于其他类,
那么就默认继承于Object类。因为所有的类都是Object类的子类,所以在定义类时可以省略extends Object

因为所有的类都继承于Object类,所以任何类都能使用Object类提供的方法:

方法描述
Class<?> getClass()获取当前对象运行时的类的Class对象
int hashCode()获取当前对象的hash值
boolean equals(Object obj)比较两个对象是否相同,Object类提供的默认实现为(this == obj)
Object clone()创建并返回当前对象的一个拷贝
String toString()将当前对象以字符串方式表示
void notify()唤醒在当前对象上等待的某个线程
void notifyAll()唤醒在当前对象上等待的所有线程
void wait()使当前线程进入等待状态,直到另一个线程调用了当前对象的notify()方法或notifyAll()方法
void wait(long timeout)使当前线程进入等待状态,直到另一个线程调用了当前对象的notify()方法或notifyAll()方法,或者超过了指定的超时时间timeout
void wait(long timeout, int nanos)void wait(long timeout)方法相似,此方法多了一个nanos参数,用于指定超时的额外时间
void finalize()如果当前对象成为垃圾时,由Java虚拟机的垃圾回收器调用此方法

其中getClass()方法、notify()方法、notifyAll()方法、wait()及其重载的方法不能被重写(被final关键字修饰),
其他方法都可以重写,以自定义实现功能。

Object类中常用的方法:

3.1 getClass() 方法

调用一个对象的getClass()方法会返回此对象的类的一个Class实例(具体细节将在“反射”章节讲解),可以再调用toString()方法获得当前对象的类名:

public class Book {
    public static void main(String[] args) {
        Book book = new Book();
        System.out.println(book.getClass().toString());
    }
}

运行结果:

class Book

3.2 toString() 方法

toString()方法的功能是将当前对象返回为字符串形式,在实际应用中通常需要重写toString()方法,以自定义对象以字符串输出的形式。
当这个类需要转换为字符串或者与字符串拼接时,将自动调用它的toString()方法:

public class Car {
    private String type;
    private String brand;
    public Car(String brand, String type) {
        this.type = type;
        this.brand = brand;
    }
    @Override
    public String toString() {
        return this.brand + this.type;
    }
    public static void main(String[] args) {
        Car car = new Car("宝马", "SUV越野车");
        System.out.println(car);
    }
}

运行结果:

宝马SUV越野车

Object类对toString()方法的默认实现:return getClass().getName() + "@" + Integer.toHexString(hashCode());

3.3 equals() 方法

在上一个章节了解了==运算符与equals()方法的区别,==运算符是比较的两个对象在内存中的地址是否相同,String类重写的equal()方法是比较两个字符串内容是否相同。
Object类对equals()方法的默认实现为return (this == obj);,编写代码进行测试:

public class Book {
    private String title;
    private String author;
    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }
    public static void main(String[] args) {
        Book book1 = new Book("巴黎圣母院", "雨果");
        Book book2 = new Book("巴黎圣母院", "雨果");
        System.out.println(book1.equals(book2));
    }
}

book1属性值与book2属性值完全相同,但都使用了new关键字,所以它们是两个独立的对象,拥有独立的内存占用,所以内存地址不同。
如果使用Object类默认的equals()方法比较(不重写equals()方法),运行结果:

false

4. 对象类型的转换

在实际开发中,经常遇到需要转换对象类型的情况,对象类型的转换主要是向上转型向下转型操作。

4.1 向上转型

以手机为例,智能手机是手机的一种,那么就可以将智能手机看做是一个手机对象。用代码来表示这个关系:

public class SmartPhone extends Phone {
    public SmartPhone(String name) {
        super(name);
    }
    public static void main(String[] args) {
        SmartPhone smartPhone = new SmartPhone("小米手机");
        Phone.use(smartPhone);
    }
}
class Phone {
    private String name;
    public Phone(String name) {
        this.name = name;
    }
    public static void use(Phone p) {
        System.out.println("使用了" + p.name);
    }
}

上述例子中,Phone类中的use()方法的参数为Phone类型,而在主方法中使用use()方法时传入的smartphone对象的类型却为SmartPhone
因为智能手机也是手机的一种,所以可以将智能手机对象看做是一个手机对象。用代码实现就是Phone phone = new SmartPhone();。这个操作称为向上转型

向上转型操作可以理解为将具体的类抽象化,转换为更抽象的类。

向上转型操作可以做到在父类中定义一个方法来完成所有子类的功能。提高了代码的可重用性,降低了维护成本。向上转型也是多态机制的基本思想。

用基本数据类型作对比,低精度数据类型(如int)可以直接转换为高精度数据类型(如double)。

4.2 向下转型

向下转型和向上转型完全相反,是把抽象的类具体化,但是这样不符合逻辑,例如不能说一个包就是书包,一部手机就是智能手机,所有的鸟都是鸽子等。
子类对象都是父类的一个实例,但是父类对象不一定就是子类的一个实例。使用上节的例子演示在Java中的向下转型操作:

public class Phone {
    private String name;
    public Phone(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public static void main(String[] args) {
        Phone phone = new SmartPhone("小米手机");
        // SmartPhone smartPhone = phone; 不能直接将父类对象赋值给子类引用,会报错
        SmartPhone smartPhone = (SmartPhone) phone; // 使用强制类型转换将父类对象转换为子类引用
        SmartPhone.scanQRCode(smartPhone);
    }
}
class SmartPhone extends Phone {
    public SmartPhone(String name) {
        super(name);
    }
    public static void scanQRCode(SmartPhone smartPhone) {
        System.out.println("使用了" + smartPhone.getName() + "扫描了二维码");
    }
}

如果直接将父类对象给子类引用,将发生编译器异常:
向下转型编译器异常

越是具体的对象具有的特性(数据)就越多,相对的越抽象的对象具有的特性越少。在做向下转型操作时将具体的对象抽象化就会出现问题。
所以要告知编译器这个手机就是智能手机。使用强制类型转换把父类对象转换为子类对象。

用基本数据类型作对比,高精度数据类型(如double)转换为低精度数据类型(如int)将会丢失精度,不能直接转换。
但可以使用强制类型转换把高精度转换为低精度。

4.3 instanceof 关键字

执行向下转型操作时,如果父类对象不是子类对象的实例时,使用强制类型转换就会发生异常(ClassCastException),所以需要先进行判断父类对象是否为子类对象的实例。
可以使用instanceof关键字完成:

public class Triangle { // 三角形
    public static void main(String[] args) {
        RightTriangle r = new RightTriangle();
        System.out.println("直角三角形是三角形吗?" + (r instanceof Triangle));
        Triangle t = new Triangle();
        System.out.println("三角形是等腰三角形吗?" + (t instanceof IsoscelesTriangle));
        // 由于RightTriangle对象不为Quadrangle类的对象,所以执行这句语句时会发生异常
        // System.out.println("直角三角形是四边形吗?" + (r instanceof Quadrangle));
    }
}
class RightTriangle extends Triangle { // 直角三角形,继承于三角形
    // input your code here...
}
class IsoscelesTriangle extends Triangle { // 等腰三角形,继承于三角形
    // input your code here...
}
class Quadrangle { // 四边形
    // input your code here...
}

运行结果:

直角三角形是三角形吗?true
三角形是等腰三角形吗?false
  • instanceof关键字只能用于父类与子类之间的比较,不能与其他类比较。

5. 方法的相关操作

5.1 方法的重写

父类的方法可以由子类自己实现,子类重新实现父类方法的操作称为重写(Override,覆盖)。比如下面的例子:

public class ColorPrinter extends Printer {
    @Override // 表示重写的注解,没有实际作用,可让编译器帮助检查重写是否有问题。
    public void print() { // 重写父类的方法
        super.print(); // 调用父类的print方法
        System.out.println("我还能打印其他各种颜色");
    }
    public static void main(String[] args) {
        Printer printer = new ColorPrinter();
        printer.print();
    }
}
class Printer {
    public void print() {
        System.out.println("我能打印黑白颜色");
    }
}

运行结果:

我能打印黑白颜色
我还能打印其他各种颜色

重写的特点:

  • 重写后的方法必须与原方法一致,包括修饰符返回值类型方法名参数列表。方法体内部实现可以自主实现;
  • 子类只能重写父类中非private成员方法不包括静态方法);
  • 如果原方法使用了throws关键字指定了可能会抛出的异常,则重写后的方法只能指定可能抛出相同的异常或者继承于这个异常的异常(“异常”章节详细说明)。
  • 如果子类重写了父类的某个方法,子类对象向上转型为父类对象后,调用父类对象的方法会执行被子类重写过的方法。

5.2 可变参数

可变参数(Varargs,全称为Variable arguments)是JDK1.5的新特性。比如int add(int x, int... addend)方法中的addend就为int类型的可变参数。
可变参数适用于参数类型确定,但参数个数不确定的情况。Java把可变参数视为数组处理。

可变参数的特性:

  • 可变参数只能出现在参数列表(方法名后的括号里面的参数列表,参数之间用逗号隔开,最左边的是首位参数,最右边的末尾参数)的最后(只能是最后一个参数);
  • 每个方法的参数列表只能有一个可变参数;
  • 调用含有可变参数的方法时,编译器将会为该可变参数创建一个数组参数名为该数组的引用,可以在方法体中使用数组的相关操作访问可变参数。

编写代码进行测试:

public class Varargs {
    public static void main(String[] args) {
        System.out.println(sum(9, 87, -56, 42, 64, 87, 24)); // 第一个参数为x,之后的参数都为addend可变参数的元素
    }
    public static int sum(int x, int... addend) {
        int sum = x;
        for (int i = 0; i < addend.length; i++) { // 可以使用for循环遍历可变参数
            sum += addend[i]; // 可以以索引的方式访问可变参数中的元素
        }
        return sum;
    }
}

运行结果:

257

5.3 方法的重载

在类中可以定义多个同名方法,但这些方法的参数列表不同,定义同名方法的操作称为重载(Overload)。

重载的构成条件:

  • 方法的参数类型不同,构成重载:如double add(int x, double y)double add(double x, double y)
  • 方法的参数顺序不同,构成重载:如double add(int x, double y)double add(double x, int y)
  • 方法的参数个数不同,构成重载:如double add(int x, double y)double add(int x, double y, double z)

需注意:

  • 虽然在方法重载中可以使两个方法的返回类型不同,但只有返回类型不同并不足以区分两个方法的重载,所以重载的方法主要是基于参数列表来区分的。
  • 使用可变参数依然构成重载。如int sum(int x, int y)int sum(int x, int... y)
    当参数个数只有两个时,即可变参数只有一个,则优先执行前面的sum方法,有两个以上的参数时执行后面的sum方法。

编写代码进行测试:

public class MethodOverload {
    public static void main(String[] args) {
        System.out.println(multiplier(9, 4));
        System.out.println(multiplier(6, 7, 12, 24));
    }
    public static int multiplier(int x, int y) {
        System.out.println("执行了第一个multiplier方法");
        return x * y;
    }
    public static int multiplier(int x, int... factors) {
        System.out.println("执行了第二个multiplier方法");
        int product = x;
        for (int factor : factors) {
            product *= factor;
        }
        return product;
    }
}

运行结果:

执行了第一个multiplier方法
36
执行了第二个multiplier方法
12096

5.4 方法的参数传递机制

方法的参数有两个概念:

  • 形参(形式参数):方法声明时定义的参数,如int sum(int x, int y)中的xy就是形参;
  • 实参(实际参数):调用方法时传入的参数,如sum(57, 68)中的5768就是实参。

在Java中,方法参数的传递方式只有值传递,即将实参的副本传入方法内,而实参本身不受影响。

  • 形参是基本数据类型,则将实参的数据值传递给形参。
  • 形参是引用数据类型,则将实参的地址值传递给形参。

编写代码进行测试:

public class ParamTransfer {
    public static void main(String[] args) {
        int i = 10;
        System.out.println("基本类型参数方法执行前:" + i);
        transfer(i);
        System.out.println("基本类型参数方法执行后:" + i);
        int[] arr = {5, 6};
        System.out.println("引用类型参数方法执行前:" + Arrays.toString(arr));
        transfer(arr);
        System.out.println("引用类型参数方法执行后:" + Arrays.toString(arr));
    }
    public static void transfer(int i) {
        i += 5;
    }
    public static void transfer(int[] arr) {
        arr[arr.length - 1] = 10;
    }
}

运行结果:

基本类型参数方法执行前:10
基本类型参数方法执行后:10
引用类型参数方法执行前:[5, 6]
引用类型参数方法执行后:[5, 10]

解析:引用数据类型实参把地址值传递给形参,所以实参和形参指向同一个对象。对这个对象进行操作,访问实参时也可以看到操作。

5.5 方法的递归

程序直接或间接调用自身的操作称为递归(Recursion),递归作为一种算法在许多编程语言中广泛应用。比如在数组的相关算法章节中,快速排序算法就使用了递归。

使用递归计算给定整数的阶乘:

public class MethodRecursion {
    public static int factorial(int x) {
        if (x == 0 || x == 1) {
            return 1;
        } else if (x > 1) {
            return factorial(x-1) * x; // 在方法体中调用方法自身
        } else {
            throw new ArithmeticException("负数的阶乘没有意义");
        }
    }
    public static void main(String[] args) {
        System.out.println(factorial(10));
    }
}

运行结果:

3628800

递归相当于多次调用方法,而每次调用方法都会产生一次栈帧,所以递归次数较多时非常影响内存,注意使用递归调用时不要递归次数太多,否则会出现栈溢出异常(StackOverFlowError)。

6. 多态

利用多态(Polymorphism)可以是程序具有良好的扩展性,并可以对所有类对象进行通用的处理。向上转型操作也是多态的一种体现。

多态可以说是“一个接口,多种实现”,子类进行父类的行为可以有多种实现方式。比如吃饭这一行为,中国人爱好使用筷子,西方人爱好使用刀叉等。

Java中多态分为两种类型:

  • 编译时多态:比如方法的重载;
  • 运行时多态:比如方法的重写,运行时多态依赖于继承重写向上转型

使用继承和重写演示多态:

public class Person { // 个人类
    private String name;
    public Person(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void work() { // 个人工作
        System.out.println(this.getName() + "正在工作");
    }
    public static void main(String[] args) {
        Person person1 = new Teacher("小明");
        Person person2 = new Doctor("小红");
        Person person3 = new Police("小刚");
        person1.work();
        person2.work();
        person3.work();
    }
}
class Teacher extends Person { // 教师类,继承于个人类
    public Teacher(String name) {
        super(name);
    }
    @Override
    public void work() { // 教师上课
        System.out.println(this.getName() + "正在上课");
    }
}
class Doctor extends Person { // 医生类,继承于个人类
    public Doctor(String name) {
        super(name);
    }
    @Override
    public void work() { // 医生看病
        System.out.println(this.getName() + "正在看病");
    }
}
class Police extends Person { // 警察类,继承于个人类
    public Police(String name) {
        super(name);
    }
    @Override
    public void work() { // 警察执勤
        System.out.println(this.getName() + "正在执勤");
    }
}

运行结果:

小明正在上课
小红正在看病
小刚正在执勤

可以看到三个人都是在工作(调用work()方法),但他们的职业不同(不同子类的对象),所以工作内容也不一样(重写的方法体不同)。

多态的特点:

  • 降低了类型之间的耦合关系;
  • 减少代码冗余,易于维护;
  • 多态拥有可替换性:在父类工作的代码,在子类也同样适用;
  • 多态拥有可扩充性:增加新的子类不影响已存在类的多态性,继承性,而且很容易获得更多功能;
  • 多态拥有接口性:父类提供方法,子类可以重写父类方法来完善和适配自身;
  • 多态拥有灵活性与简化性:在应用中体现了灵活多样的操作,简化了代码量,提高了使用效率。

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

  • 子类继承父类;
  • 子类重写父类方法;
  • 父类引用指向子类对象(向上转型)。

7. 抽象类与接口

7.1 抽象类

生活中很多事物是不能具体描述的,比如一支笔,它有可能是铅笔,圆珠笔,钢笔等。但是无论这支笔是什么类型的笔,它都可以进行书写。
这种类在Java中被定义为抽象类(Abstract Class)。

实际应用中,一般将父类定义为抽象类,使用抽象类进行继承与多态处理。在多态机制下,我们实际使用的是子类对象来处理问题,不需要父类对象。
所以在Java中,抽象类不能实例化为对象

Java中使用abstract关键字定义抽象类与抽象方法:

public abstract class Pen { // 抽象类:笔
    private String type;
    public abstract void write(); // 抽象方法:书写
}

使用abstract关键字定义的类称为抽象类;而使用这个关键字定义的方法称为抽象方法(Abstract Method)。抽象方法没有方法体
这个方法本身没有意义,除非被子类重写。抽象类本身除了被继承之外无任何意义。

同理,只要一个类中定义了抽象方法,那么这个类必须为抽象类(或接口,下节学习)。不能在普通类中定义抽象方法。
抽象类被普通类继承后需要实现所有的抽象方法(重写所有的抽象方法并实现方法体),抽象类被抽象类继承则不强制实现抽象方法

抽象类和抽象方法的特点:

  • 抽象类不能实例化(不能对抽象类使用new关键字);
  • 抽象方法没有方法体,只能由普通类子类实现;
  • 抽象类的意义在于定义一个标准,让子类去实现这个标准(继承与多态);
  • 如果普通类继承了抽象类,那么需要实现所有的抽象方法。抽象类继承了抽象类则不强制实现所有的抽象方法;
  • 抽象类中可以定义主方法main()方法),抽象类可以运行。
  • 抽象类中可以使用静态代码块静态方法

7.2 接口

继承抽象类的所有普通类子类需要实现所有的的抽象方法,这样在多态机制中,就可以将父类定义为抽象类。
将方法设置为抽象方法。然后每个普通类子类都重写这个方法来处理,但这又会让程序中有太多的冗余代码。
可能有某些子类不需要实现父类的抽象方法,但不得不实现。还可能有不需要继承抽象类的类也想使用抽象方法,
但是Java不允许多继承,所以出现了一种新的概念:接口。

接口(Interface)是抽象类的延伸,可以把它看做是纯粹的抽象类,接口中只能定义抽象方法。一个类通过继承接口的方式来实现接口的抽象方法。

接口并不是类,只是编写方式与类相似,但是它们属于不同的概念,类描述对象的属性和方法,接口只包含类要实现的方法。
普通类需要实现接口中的所有方法,而抽象类不需要。

接口同样无法实例化,但是可以被实现。

接口使用interface关键字定义:

public interface Writable {
    void write();
}

接口可以像类一样被权限修饰符修饰(仅限public缺省)。在接口中定义的抽象方法默认都有public abstract修饰符修饰,所以编写时不需再写。
在接口中的抽象方法只能被定义为publicabstract形式,不能使用其他权限修饰符

一个类实现一个接口使用implements关键字:

public class Pencil implements Writable {
    @Override
    public void write() {
        // input your code here.
    }
}

接口中可以定义静态常量(只能使用public static final修饰符,修饰符可以省略):

public interface Writable {
    String TEXT = "我是一个常量";
}

(JDK1.8新特性)接口中可以定义静态方法及其方法体(只能使用public修饰符,修饰符可以省略):

public interface Writable {
    static String method() {
        return "我是一个静态方法";
    }
}

(JDK1.8新特性)接口中的抽象方法可以有默认实现默认方法),使用default关键字修饰(只能使用public修饰符修饰,public可以省略,default不能省略)。
默认方法可以被实现此接口的类重写:

public interface Writable {
    default void write() {
        System.out.println("书写");
    }
}

(JDK1.8新特性)如果一个接口中有且仅有一个抽象方法,则称这个接口是一个函数式接口(Functional Interface)
函数式接口可以被隐式转换为Lambda表达式(后续章节讲解):

@FunctionalInterface // 函数式接口注解
public interface Writable {
    void write();
}

接口也可以继承接口,而且支持多继承

public interface Writable extends Writable1, Writable2 {
    
}
interface Writable1 {
    
}
interface Writable2 {
    
}

接口的特点:

  • 接口不能进行实例化(不能使用new关键字);
  • 接口中没有构造方法
  • 接口中所有的方法必须为抽象方法,JDK1.8之后可以有静态方法默认方法
  • 接口中不能包含成员变量,但可以有静态常量
  • 只能说类实现接口,不能说类继承接口;
  • 接口中不能使用静态代码块
  • 一个类可以实现多个接口;
  • 接口可以继承接口,而且接口支持多继承
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值