Java 中文官方教程 2022 版(四)(4)

简介: Java 中文官方教程 2022 版(四)

Java 中文官方教程 2022 版(四)(3)https://developer.aliyun.com/article/1486288

假设您的开发人员想要创建一个可以根据多个标准比较对象的Comparator实例。例如,如何先按等级,然后按花色对扑克牌进行排序?与以前一样,您可以使用 lambda 表达式来指定这些排序标准:

StandardDeck myDeck = new StandardDeck();
myDeck.shuffle();
myDeck.sort(
    (firstCard, secondCard) -> {
        int compare =
            firstCard.getRank().value() - secondCard.getRank().value();
        if (compare != 0)
            return compare;
        else
            return firstCard.getSuit().value() - secondCard.getSuit().value();
    }      
); 

如果您的开发人员可以从一系列Comparator实例构建一个Comparator实例,那将更简单。Comparator接口已经通过默认方法thenComparing增强了这种能力:

myDeck.sort(
    Comparator
        .comparing(Card::getRank)
        .thenComparing(Comparator.comparing(Card::getSuit)));

Comparator接口已经通过其他版本的默认方法thenComparing(如thenComparingDoublethenComparingLong)进行了增强,使您能够构建比较其他数据类型的Comparator实例。

假设您的开发人员想要创建一个Comparator实例,使他们能够以相反的顺序对对象集合进行排序。例如,如何按照牌面从大到小的顺序对扑克牌进行排序,从 A 到 2(而不是从 2 到 A)?与以前一样,您可以指定另一个 lambda 表达式。但是,如果开发人员可以通过调用方法来反转现有的Comparator,那将更简单。Comparator接口已经通过默认方法reversed增强了这种能力:

myDeck.sort(
    Comparator.comparing(Card::getRank)
        .reversed()
        .thenComparing(Comparator.comparing(Card::getSuit)));

这个例子演示了如何通过默认方法、静态方法、lambda 表达式和方法引用增强了Comparator接口,以创建更具表现力的库方法,程序员可以通过查看它们的调用方式快速推断出其功能。使用这些构造来增强您的库中的接口。

接口概要

docs.oracle.com/javase/tutorial/java/IandI/summary-interface.html

接口声明可以包含方法签名、默认方法、静态方法和常量定义。唯一有实现的方法是默认方法和静态方法。

实现接口的类必须实现接口中声明的所有方法。

接口名称可以在任何需要类型的地方使用。

问题和练习:接口

原文:docs.oracle.com/javase/tutorial/java/IandI/QandE/interfaces-questions.html

问题

  1. 一个实现java.lang.CharSequence接口的类需要实现哪些方法?
  2. 以下接口有什么问题?
public interface SomethingIsWrong {
    void aMethod(int aValue){
        System.out.println("Hi Mom");
    }
}
  1. 修复问题 2 中的接口。
  2. 以下接口是否有效?
public interface Marker {
}

练习

  1. 编写一个实现java.lang包中CharSequence接口的类。你的实现应该将字符串倒序返回。从本书中选择一句话作为数据。编写一个小的main方法来测试你的类;确保调用所有四个方法。
  2. 假设你已经编写了一个定期通知其客户端当前日期和时间的时间服务器。编写一个接口,服务器可以使用它来强制执行特定的协议。

检查你的答案。

继承

原文:docs.oracle.com/javase/tutorial/java/IandI/subclasses.html

在前面的课程中,您已经多次看到继承的提及。在 Java 语言中,类可以从其他类派生,从而继承那些类的字段和方法。


定义: 从另一个类派生的类称为子类(也称为派生类扩展类子类)。从子类派生的类称为超类(也称为基类父类)。

除了Object没有超类之外,每个类只有一个直接超类(单继承)。在没有其他显式超类的情况下,每个类都隐式地是Object的子类。

类可以从派生自其他类的类派生,而这些类又从其他类派生,依此类推,最终都是从顶级类Object派生而来。这样的类被称为继承自继承链中一直延伸到Object的所有类。


继承的概念简单而强大:当您想要创建一个新类,并且已经有一个包含您想要的一些代码的类时,您可以从现有类派生您的新类。通过这样做,您可以重用现有类的字段和方法,而无需自己编写(和调试!)它们。

子类从其超类继承所有成员(字段、方法和嵌套类)。构造函数不是成员,因此它们不会被子类继承,但是可以从子类中调用超类的构造函数。

Java 平台类层次结构

Object类,定义在java.lang包中,定义并实现了所有类共有的行为,包括您编写的类。在 Java 平台中,许多类直接从Object派生,其他类从其中一些类派生,依此类推,形成一个类的层次结构。

[外链图片转存中…(img-l02ciPtq-1712902080546)]

Java 平台中的所有类都是 Object 的子类

在层次结构的顶部,Object是所有类中最通用的类。层次结构底部附近的类提供更专业化的行为。

继承的示例

这是一个可能实现的Bicycle类的示例代码,该代码在类和对象课程中提供:

public class Bicycle {
    // the Bicycle class has three *fields*
    public int cadence;
    public int gear;
    public int speed;
    // the Bicycle class has one *constructor*
    public Bicycle(int startCadence, int startSpeed, int startGear) {
        gear = startGear;
        cadence = startCadence;
        speed = startSpeed;
    }
    // the Bicycle class has four *methods*
    public void setCadence(int newValue) {
        cadence = newValue;
    }
    public void setGear(int newValue) {
        gear = newValue;
    }
    public void applyBrake(int decrement) {
        speed -= decrement;
    }
    public void speedUp(int increment) {
        speed += increment;
    }
}

一个MountainBike类的类声明,它是Bicycle的子类,可能如下所示:

public class MountainBike extends Bicycle {
    // the MountainBike subclass adds one *field*
    public int seatHeight;
    // the MountainBike subclass has one *constructor*
    public MountainBike(int startHeight,
                        int startCadence,
                        int startSpeed,
                        int startGear) {
        super(startCadence, startSpeed, startGear);
        seatHeight = startHeight;
    }   
    // the MountainBike subclass adds one *method*
    public void setHeight(int newValue) {
        seatHeight = newValue;
    }   
}

MountainBike继承了Bicycle的所有字段和方法,并添加了字段seatHeight和一个设置它的方法。除了构造函数外,就好像你完全从头开始编写了一个新的MountainBike类,有四个字段和五个方法。但是,你不必做所有的工作。如果Bicycle类中的方法很复杂并且花费了大量时间来调试,这将特别有价值。

在子类中可以做什么

子类继承其父类的所有publicprotected成员,无论子类位于何种包中。如果子类与其父类在同一包中,它还会继承父类的package-private成员。你可以直接使用继承的成员,替换它们,隐藏它们,或者用新成员补充它们:

  • 继承的字段可以直接使用,就像任何其他字段一样。
  • 你可以在子类中声明一个与超类中相同名称的字段,从而隐藏它(不建议)。
  • 你可以在子类中声明超类中没有的新字段。
  • 继承的方法可以直接使用。
  • 你可以在子类中编写一个新的实例方法,其签名与超类中的方法相同,从而覆盖它。
  • 你可以在子类中编写一个新的静态方法,其签名与超类中的方法相同,从而隐藏它。
  • 你可以在子类中声明超类中没有的新方法。
  • 你可以编写一个子类构造函数,隐式地或使用关键字super调用超类的构造函数。

本课程的以下部分将扩展这些主题。

超类中的私有成员

子类不继承其父类的private成员。但是,如果超类有用于访问其私有字段的公共或受保护方法,子类也可以使用这些方法。

嵌套类可以访问其封闭类的所有私有成员—包括字段和方法。因此,一个由子类继承的公共或受保护的嵌套类间接访问了超类的所有私有成员。

对象转型

我们已经看到,一个对象的数据类型是它实例化的类的数据类型。例如,如果我们写

public MountainBike myBike = new MountainBike();

那么myBike的类型是MountainBike

MountainBike是从BicycleObject继承而来的。因此,MountainBike是一个Bicycle,也是一个Object,可以在需要BicycleObject对象的任何地方使用。

反之未必成立:Bicycle可能是MountainBike,但不一定。同样,Object可能是BicycleMountainBike,但不一定。

转型展示了在继承和实现允许的对象之间使用一个类型的对象代替另一个类型的对象。例如,如果我们写

Object obj = new MountainBike();

那么obj既是一个Object,也是一个MountainBike(直到obj被分配为不是MountainBike的另一个对象为止)。这被称为隐式转换

另一方面,如果我们写

MountainBike myBike = obj;

我们会得到一个编译时错误,因为编译器不知道obj是一个MountainBike。然而,我们可以告诉编译器,我们承诺将一个MountainBike分配给obj,通过显式转换

MountainBike myBike = (MountainBike)obj;

这个转换插入了一个运行时检查,以确保obj被分配为MountainBike,这样编译器可以安全地假定obj是一个MountainBike。如果在运行时obj不是MountainBike,则会抛出异常。


注意: 您可以使用instanceof运算符对特定对象的类型进行逻辑测试。这可以避免由于不正确的转换而导致运行时错误。例如:

if (obj instanceof MountainBike) {
    MountainBike myBike = (MountainBike)obj;
}

这里的instanceof运算符验证obj指向一个MountainBike,这样我们可以进行转换,并确保不会抛出运行时异常。


状态、实现和类型的多重继承

原文:docs.oracle.com/javase/tutorial/java/IandI/multipleinheritance.html

类和接口之间的一个重要区别是类可以有字段,而接口不能。此外,您可以实例化一个类来创建一个对象,而接口不能这样做。正如在什么是对象?一节中所解释的,对象将其状态存储在字段中,这些字段在类中定义。Java 编程语言不允许您扩展多个类的一个原因是为了避免状态的多重继承问题,即从多个类继承字段的能力。例如,假设您能够定义一个新类,该类扩展多个类。当您通过实例化该类创建对象时,该对象将从所有超类继承字段。如果不同超类的方法或构造函数实例化相同字段会怎样?哪个方法或构造函数将优先?由于接口不包含字段,您不必担心由于状态的多重继承而导致的问题。

实现的多重继承是从多个类继承方法定义的能力。这种类型的多重继承会引发问题,例如名称冲突和模糊性。当支持这种类型多重继承的编程语言的编译器遇到包含相同名称方法的超类时,有时无法确定要访问或调用哪个成员或方法。此外,程序员可能会通过向超类添加新方法无意中引入名称冲突。默认方法引入了一种实现的多重继承形式。一个类可以实现多个接口,这些接口可以包含具有相同名称的默认方法。Java 编译器提供了一些规则来确定特定类使用哪个默认方法。

Java 编程语言支持类型的多重继承,即一个类可以实现多个接口的能力。一个对象可以有多种类型:它自己类的类型以及类实现的所有接口的类型。这意味着如果一个变量声明为接口的类型,那么它的值可以引用任何实例化自实现该接口的任何类的对象。这在将接口用作类型一节中讨论。

与多重继承实现一样,一个类可以继承在其扩展的接口中定义的方法的不同实现(作为默认或静态)。在这种情况下,编译器或用户必须决定使用哪一个。

覆盖和隐藏方法

原文:docs.oracle.com/javase/tutorial/java/IandI/override.html

实例方法

子类中具有与超类中实例方法相同签名(名称,以及其参数的数量和类型)和返回类型的实例方法覆盖了超类的方法。

子类覆盖方法的能力允许一个类从一个行为“足够接近”的超类继承,然后根据需要修改行为。覆盖方法具有与其覆盖的方法相同的名称、参数数量和类型以及返回类型。覆盖方法还可以返回被覆盖方法返回类型的子类型。这个子类型被称为协变返回类型

在覆盖方法时,您可能希望使用@Override注解,指示编译器您打算覆盖超类中的方法。如果由于某种原因,编译器检测到该方法在任何一个超类中不存在,则会生成一个错误。有关@Override的更多信息,请参见Annotations

静态方法

如果一个子类定义了一个与超类中静态方法相同签名的静态方法,则子类中的方法隐藏超类中的方法。

隐藏静态方法和覆盖实例方法之间的区别具有重要的影响:

  • 调用的覆盖实例方法的版本是在子类中的版本。
  • 调用的隐藏静态方法的版本取决于它是从超类还是从子类调用的。

考虑一个包含两个类的示例。第一个是Animal,包含一个实例方法和一个静态方法:

public class Animal {
    public static void testClassMethod() {
        System.out.println("The static method in Animal");
    }
    public void testInstanceMethod() {
        System.out.println("The instance method in Animal");
    }
}

第二个类,Cat,是Animal的一个子类:

public class Cat extends Animal {
    public static void testClassMethod() {
        System.out.println("The static method in Cat");
    }
    public void testInstanceMethod() {
        System.out.println("The instance method in Cat");
    }
    public static void main(String[] args) {
        Cat myCat = new Cat();
        Animal myAnimal = myCat;
        Animal.testClassMethod();
        myAnimal.testInstanceMethod();
    }
}

Cat类覆盖了Animal中的实例方法,并隐藏了Animal中的静态方法。这个类中的main方法创建了一个Cat的实例,并在类上调用testClassMethod(),在实例上调用testInstanceMethod()

这个程序的输出如下:

The static method in Animal
The instance method in Cat

如约定,调用的隐藏静态方法的版本是在超类中的版本,调用的覆盖实例方法的版本是在子类中的版本。

接口方法

默认方法和抽象方法在接口中像实例方法一样被继承。然而,当一个类或接口的超类型提供了多个具有相同签名的默认方法时,Java 编译器遵循继承规则来解决名称冲突。这些规则受以下两个原则驱动:

  • 实例方法优先于接口默认方法。
    考虑以下类和接口:
public class Horse {
    public String identifyMyself() {
        return "I am a horse.";
    }
}
public interface Flyer {
    default public String identifyMyself() {
        return "I am able to fly.";
    }
}
public interface Mythical {
    default public String identifyMyself() {
        return "I am a mythical creature.";
    }
}
public class Pegasus extends Horse implements Flyer, Mythical {
    public static void main(String... args) {
        Pegasus myApp = new Pegasus();
        System.out.println(myApp.identifyMyself());
    }
}
  • 方法Pegasus.identifyMyself返回字符串I am a horse.
  • 已经被其他候选方法覆盖的方法将被忽略。当超类型共享一个共同的祖先时,就会出现这种情况。
    考虑以下接口和类:
public interface Animal {
    default public String identifyMyself() {
        return "I am an animal.";
    }
}
public interface EggLayer extends Animal {
    default public String identifyMyself() {
        return "I am able to lay eggs.";
    }
}
public interface FireBreather extends Animal { }
public class Dragon implements EggLayer, FireBreather {
    public static void main (String... args) {
        Dragon myApp = new Dragon();
        System.out.println(myApp.identifyMyself());
    }
}
  • 方法Dragon.identifyMyself返回字符串I am able to lay eggs.

如果两个或更多独立定义的默认方法冲突,或者默认方法与抽象方法冲突,则 Java 编译器会产生编译错误。您必须显式覆盖超类型方法。

考虑一下关于现在可以飞行的计算机控制汽车的例子。您有两个接口(OperateCarFlyCar),它们为相同方法(startEngine)提供默认实现:

public interface OperateCar {
    // ...
    default public int startEngine(EncryptedKey key) {
        // Implementation
    }
}
public interface FlyCar {
    // ...
    default public int startEngine(EncryptedKey key) {
        // Implementation
    }
}

实现OperateCarFlyCar的类必须覆盖方法startEngine。您可以使用super关键字调用任何默认实现中的任何一个。

public class FlyingCar implements OperateCar, FlyCar {
    // ...
    public int startEngine(EncryptedKey key) {
        FlyCar.super.startEngine(key);
        OperateCar.super.startEngine(key);
    }
}

super之前的名称(在本例中为FlyCarOperateCar)必须引用直接定义或继承了被调用方法的超接口。这种形式的方法调用不仅限于区分包含具有相同签名的默认方法的多个实现接口。您可以使用super关键字在类和接口中调用默认方法。

从类中继承的实例方法可以覆盖抽象接口方法。考虑以下接口和类:

public interface Mammal {
    String identifyMyself();
}
public class Horse {
    public String identifyMyself() {
        return "I am a horse.";
    }
}
public class Mustang extends Horse implements Mammal {
    public static void main(String... args) {
        Mustang myApp = new Mustang();
        System.out.println(myApp.identifyMyself());
    }
}

方法Mustang.identifyMyself返回字符串I am a horse.Mustang继承自类Horse的方法identifyMyself,该方法覆盖了接口Mammal中同名的抽象方法。

注意:接口中的静态方法不会被继承。

修饰符

覆盖方法的访问修饰符可以允许更多的访问权限,但不能少于被覆盖方法的访问权限。例如,超类中的受保护实例方法可以在子类中变为公共方法,但不能变为私有方法。

如果您尝试将超类中的实例方法更改为子类中的静态方法,或者反之,则会收到编译时错误。

总结

以下表格总结了当您定义一个与超类中方法具有相同签名的方法时会发生什么。

定义一个与超类方法具有相同签名的方法

超类实例方法 超类静态方法
子类实例方法 覆盖 生成编译时错误
子类静态方法 生成编译时错误 隐藏

注意:在子类中,您可以重载从超类继承的方法。这样重载的方法既不隐藏也不覆盖超类实例方法——它们是子类独有的新方法。

相关文章
|
4天前
|
前端开发 Java Maven
【前端学java】全网最详细的maven安装与IDEA集成教程!
【8月更文挑战第12天】全网最详细的maven安装与IDEA集成教程!
21 2
【前端学java】全网最详细的maven安装与IDEA集成教程!
|
9天前
|
存储 网络协议 Oracle
java教程
java教程【8月更文挑战第11天】
14 5
|
1月前
|
SQL 安全 Java
「滚雪球学Java」教程导航帖(更新2024.07.16)
《滚雪球学Spring Boot》是一个面向初学者的Spring Boot教程,旨在帮助读者快速入门Spring Boot开发。本专通过深入浅出的方式,将Spring Boot开发中的核心概念、基础知识、实战技巧等内容系统地讲解,同时还提供了大量实际的案例,让读者能够快速掌握实用的Spring Boot开发技能。本书的特点在于注重实践,通过实例学习的方式激发读者的学习兴趣和动力,并引导读者逐步掌握Spring Boot开发的实际应用。
42 1
「滚雪球学Java」教程导航帖(更新2024.07.16)
WXM
|
25天前
|
Oracle Java 关系型数据库
Java JDK下载安装及环境配置超详细图文教程
Java JDK下载安装及环境配置超详细图文教程
WXM
129 3
|
1月前
|
测试技术 API Android开发
《手把手教你》系列基础篇(九十七)-java+ selenium自动化测试-框架设计篇-Selenium方法的二次封装和页面基类(详解教程)
【7月更文挑战第15天】这是关于自动化测试框架中Selenium API二次封装的教程总结。教程中介绍了如何设计一个支持不同浏览器测试的页面基类(BasePage),该基类包含了对Selenium方法的二次封装,如元素的输入、点击、清除等常用操作,以减少重复代码。此外,页面基类还提供了获取页面标题和URL的方法。
44 2
|
1月前
|
Web App开发 XML Java
《手把手教你》系列基础篇(九十六)-java+ selenium自动化测试-框架之设计篇-跨浏览器(详解教程)
【7月更文挑战第14天】这篇教程介绍了如何使用Java和Selenium构建一个支持跨浏览器测试的自动化测试框架。设计的核心是通过读取配置文件来切换不同浏览器执行测试用例。配置文件中定义了浏览器类型(如Firefox、Chrome)和测试服务器的URL。代码包括一个`BrowserEngine`类,它初始化配置数据,根据配置启动指定的浏览器,并提供关闭浏览器的方法。测试脚本`TestLaunchBrowser`使用`BrowserEngine`来启动浏览器并执行测试。整个框架允许在不同浏览器上运行相同的测试,以确保兼容性和一致性。
47 3
|
1月前
|
存储 Web App开发 Java
《手把手教你》系列基础篇(九十五)-java+ selenium自动化测试-框架之设计篇-java实现自定义日志输出(详解教程)
【7月更文挑战第13天】这篇文章介绍了如何在Java中创建一个简单的自定义日志系统,以替代Log4j或logback。
136 5
|
1月前
|
Java 数据安全/隐私保护
Java无模版导出Excel 0基础教程
经常写数据导出到EXCEL,没有模板的情况下使用POI技术。以此作为记录,以后方便使用。 2 工具类 样式工具: 处理工具Java接口 水印工具 导出Excel工具类 3 测试代码 与实际复杂业务不同 在此我们只做模拟 Controller Service 4 导出测试 使用Postman进行接口测试,没接触过Postman的小伙伴可以看我这篇博客Postman导出excel文件保存为文件可以看到导出很成功,包括水印 sheet页名称自适应宽度。还有一些高亮……等功能可以直接搜索使用
Java无模版导出Excel 0基础教程
|
1月前
|
设计模式 测试技术 Python
《手把手教你》系列基础篇(九十二)-java+ selenium自动化测试-框架设计基础-POM设计模式简介(详解教程)
【7月更文挑战第10天】Page Object Model (POM)是Selenium自动化测试中的设计模式,用于提高代码的可读性和维护性。POM将每个页面表示为一个类,封装元素定位和交互操作,使得测试脚本与页面元素分离。当页面元素改变时,只需更新对应页面类,减少了脚本的重复工作和维护复杂度,有利于团队协作。POM通过创建页面对象,管理页面元素集合,将业务逻辑与元素定位解耦合,增强了代码的复用性。示例展示了不使用POM时,脚本直接混杂了元素定位和业务逻辑,而POM则能解决这一问题。
43 6
|
1月前
|
设计模式 Java 测试技术
《手把手教你》系列基础篇(九十四)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-下篇(详解教程)
【7月更文挑战第12天】在本文中,作者宏哥介绍了如何在不使用PageFactory的情况下,用Java和Selenium实现Page Object Model (POM)。文章通过一个百度首页登录的实战例子来说明。首先,创建了一个名为`BaiduHomePage1`的页面对象类,其中包含了页面元素的定位和相关操作方法。接着,创建了测试类`TestWithPOM1`,在测试类中初始化WebDriver,设置驱动路径,最大化窗口,并调用页面对象类的方法进行登录操作。这样,测试脚本保持简洁,遵循了POM模式的高可读性和可维护性原则。
27 2