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

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

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

使用接口作为类型

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

当你定义一个新接口时,你正在定义一个新的引用数据类型。你可以在任何可以使用其他数据类型名称的地方使用接口名称。如果你定义一个类型为接口的引用变量,那么你分配给它的任何对象必须是实现了该接口的类的实例。

举例来说,这里有一种方法可以找到一对对象中最大的对象,适用于任何从实现了Relatable接口的类实例化的对象:

public Object findLargest(Object object1, Object object2) {
   Relatable obj1 = (Relatable)object1;
   Relatable obj2 = (Relatable)object2;
   if ((obj1).isLargerThan(obj2) > 0)
      return object1;
   else 
      return object2;
}

通过将object1强制转换为Relatable类型,它可以调用isLargerThan方法。

如果你坚持在各种类中实现Relatable,那么从任何这些类实例化的对象都可以使用findLargest()方法进行比较——前提是这两个对象属于同一类。同样,它们也可以使用以下方法进行比较:

public Object findSmallest(Object object1, Object object2) {
   Relatable obj1 = (Relatable)object1;
   Relatable obj2 = (Relatable)object2;
   if ((obj1).isLargerThan(obj2) < 0)
      return object1;
   else 
      return object2;
}
public boolean isEqual(Object object1, Object object2) {
   Relatable obj1 = (Relatable)object1;
   Relatable obj2 = (Relatable)object2;
   if ( (obj1).isLargerThan(obj2) == 0)
      return true;
   else 
      return false;
}

这些方法适用于任何“可比较”的对象,无论它们的类继承关系如何。当它们实现了Relatable接口时,它们可以是自己类(或超类)类型和Relatable类型。这使它们具有多重继承的一些优势,可以同时具有来自超类和接口的行为。

接口的演变

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

考虑您开发的名为DoIt的接口:

public interface DoIt {
   void doSomething(int i, double x);
   int doSomethingElse(String s);
}

假设以后,您想要向DoIt添加第三个方法,使接口现在变成:

public interface DoIt {
   void doSomething(int i, double x);
   int doSomethingElse(String s);
   boolean didItWork(int i, double x, String s);
}

如果您进行此更改,则所有实现旧DoIt接口的类都将中断,因为它们不再实现旧接口。依赖于此接口的程序员将会强烈抗议。

尽量预见接口的所有用途并从一开始完全指定它。如果要向接口添加其他方法,您有几个选项。您可以创建一个扩展DoItDoItPlus接口:

public interface DoItPlus extends DoIt {
   boolean didItWork(int i, double x, String s);
}

现在,您的代码用户可以选择继续使用旧接口或升级到新接口。

或者,您可以将新方法定义为默认方法。以下示例定义了一个名为didItWork的默认方法:

public interface DoIt {
   void doSomething(int i, double x);
   int doSomethingElse(String s);
   default boolean didItWork(int i, double x, String s) {
       // Method body 
   }
}

请注意,您必须为默认方法提供实现。您还可以为现有接口定义新的静态方法。实现增强了新默认或静态方法的接口的类的用户无需修改或重新编译它们以适应额外的方法。

默认方法

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

接口部分描述了一个涉及计算机控制汽车制造商发布行业标准接口的示例,描述了可以调用哪些方法来操作他们的汽车。如果这些计算机控制汽车制造商为他们的汽车添加新功能,比如飞行,会怎么样?这些制造商需要指定新的方法来使其他公司(如电子导航仪制造商)能够调整他们的软件以适应飞行汽车。这些汽车制造商会在哪里声明这些新的与飞行相关的方法?如果他们将它们添加到原始接口中,那么已经实现这些接口的程序员将不得不重新编写他们的实现。如果将它们添加为静态方法,那么程序员会将它们视为实用方法,而不是必要的核心方法。

默认方法使您能够向库的接口添加新功能,并确保与为旧版本接口编写的代码的二进制兼容性。

考虑下面的接口,TimeClient,如问题和练习的答案:接口中所述:

import java.time.*; 
public interface TimeClient {
    void setTime(int hour, int minute, int second);
    void setDate(int day, int month, int year);
    void setDateAndTime(int day, int month, int year,
                               int hour, int minute, int second);
    LocalDateTime getLocalDateTime();
}

下面的类,SimpleTimeClient,实现了TimeClient

package defaultmethods;
import java.time.*;
import java.lang.*;
import java.util.*;
public class SimpleTimeClient implements TimeClient {
    private LocalDateTime dateAndTime;
    public SimpleTimeClient() {
        dateAndTime = LocalDateTime.now();
    }
    public void setTime(int hour, int minute, int second) {
        LocalDate currentDate = LocalDate.from(dateAndTime);
        LocalTime timeToSet = LocalTime.of(hour, minute, second);
        dateAndTime = LocalDateTime.of(currentDate, timeToSet);
    }
    public void setDate(int day, int month, int year) {
        LocalDate dateToSet = LocalDate.of(day, month, year);
        LocalTime currentTime = LocalTime.from(dateAndTime);
        dateAndTime = LocalDateTime.of(dateToSet, currentTime);
    }
    public void setDateAndTime(int day, int month, int year,
                               int hour, int minute, int second) {
        LocalDate dateToSet = LocalDate.of(day, month, year);
        LocalTime timeToSet = LocalTime.of(hour, minute, second); 
        dateAndTime = LocalDateTime.of(dateToSet, timeToSet);
    }
    public LocalDateTime getLocalDateTime() {
        return dateAndTime;
    }
    public String toString() {
        return dateAndTime.toString();
    }
    public static void main(String... args) {
        TimeClient myTimeClient = new SimpleTimeClient();
        System.out.println(myTimeClient.toString());
    }
}

假设您想要向TimeClient接口添加新功能,比如通过ZonedDateTime对象(类似于LocalDateTime对象,但它存储时区信息)指定时区的能力:

public interface TimeClient {
    void setTime(int hour, int minute, int second);
    void setDate(int day, int month, int year);
    void setDateAndTime(int day, int month, int year,
        int hour, int minute, int second);
    LocalDateTime getLocalDateTime();                           
    ZonedDateTime getZonedDateTime(String zoneString);
}

TimeClient接口进行这种修改后,您还需要修改SimpleTimeClient类并实现getZonedDateTime方法。但是,与其将getZonedDateTime留空(如前面的例子中),您可以定义一个默认实现。(请记住,抽象方法是声明而没有实现的方法。)

package defaultmethods;
import java.time.*;
public interface TimeClient {
    void setTime(int hour, int minute, int second);
    void setDate(int day, int month, int year);
    void setDateAndTime(int day, int month, int year,
                               int hour, int minute, int second);
    LocalDateTime getLocalDateTime();
    static ZoneId getZoneId (String zoneString) {
        try {
            return ZoneId.of(zoneString);
        } catch (DateTimeException e) {
            System.err.println("Invalid time zone: " + zoneString +
                "; using default time zone instead.");
            return ZoneId.systemDefault();
        }
    }
    default ZonedDateTime getZonedDateTime(String zoneString) {
        return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString));
    }
}

您可以在接口中的方法签名开头使用default关键字来指定一个方法定义是默认方法。接口中的所有方法声明,包括默认方法,都隐式地是public的,因此您可以省略public修饰符。

使用这个接口,你不需要修改SimpleTimeClient类,而这个类(以及任何实现TimeClient接口的类)将已经定义好getZonedDateTime方法。下面的例子,TestSimpleTimeClient,调用了SimpleTimeClient实例的getZonedDateTime方法:

package defaultmethods;
import java.time.*;
import java.lang.*;
import java.util.*;
public class TestSimpleTimeClient {
    public static void main(String... args) {
        TimeClient myTimeClient = new SimpleTimeClient();
        System.out.println("Current time: " + myTimeClient.toString());
        System.out.println("Time in California: " +
            myTimeClient.getZonedDateTime("Blah blah").toString());
    }
}

扩展包含默认方法的接口

当您扩展包含默认方法的接口时,可以执行以下操作:

  • 完全不提及默认方法,让您扩展的接口继承默认方法。
  • 重新声明默认方法,使其为abstract
  • 重新定义默认方法,覆盖它。

假设您扩展了接口TimeClient如下:

public interface AnotherTimeClient extends TimeClient { }

任何实现接口AnotherTimeClient的类都将具有默认方法TimeClient.getZonedDateTime指定的实现。

假设您扩展了接口TimeClient如下:

public interface AbstractZoneTimeClient extends TimeClient {
    public ZonedDateTime getZonedDateTime(String zoneString);
}

任何实现接口AbstractZoneTimeClient的类都必须实现方法getZonedDateTime;这个方法是一个abstract方法,就像接口中的所有其他非默认(非静态)方法一样。

假设您扩展了接口TimeClient如下:

public interface HandleInvalidTimeZoneClient extends TimeClient {
    default public ZonedDateTime getZonedDateTime(String zoneString) {
        try {
            return ZonedDateTime.of(getLocalDateTime(),ZoneId.of(zoneString)); 
        } catch (DateTimeException e) {
            System.err.println("Invalid zone ID: " + zoneString +
                "; using the default time zone instead.");
            return ZonedDateTime.of(getLocalDateTime(),ZoneId.systemDefault());
        }
    }
}

任何实现接口HandleInvalidTimeZoneClient的类都将使用此接口指定的getZonedDateTime实现,而不是接口TimeClient指定的实现。

静态方法

除了默认方法之外,您还可以在接口中定义静态方法。(静态方法是与定义它的类相关联的方法,而不是与任何对象相关联的方法。类的每个实例共享其静态方法。)这使您更容易在库中组织辅助方法;您可以将特定于接口的静态方法保留在同一接口中,而不是在单独的类中。以下示例定义了一个静态方法,用于检索与时区标识符对应的ZoneId对象;如果没有与给定标识符对应的ZoneId对象,则使用系统默认时区。(因此,您可以简化方法getZonedDateTime):

public interface TimeClient {
    // ...
    static public ZoneId getZoneId (String zoneString) {
        try {
            return ZoneId.of(zoneString);
        } catch (DateTimeException e) {
            System.err.println("Invalid time zone: " + zoneString +
                "; using default time zone instead.");
            return ZoneId.systemDefault();
        }
    }
    default public ZonedDateTime getZonedDateTime(String zoneString) {
        return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString));
    }    
}

就像类中的静态方法一样,您可以在接口中的方法定义之前使用static关键字指定一个方法是静态方法。接口中的所有方法声明,包括静态方法,都隐式为public,因此您可以省略public修饰符。

将默认方法集成到现有库中

默认方法使您可以向现有接口添加新功能,并确保与为旧版本接口编写的代码具有二进制兼容性。特别是,默认方法使您可以向现有接口添加接受 lambda 表达式作为参数的方法。本节演示了如何通过默认方法和静态方法增强了Comparator接口。

CardDeck类视为问题和练习:类中描述的那样。此示例将CardDeck类重写为接口。Card接口包含两个enum类型(SuitRank)和两个抽象方法(getSuitgetRank):

package defaultmethods;
public interface Card extends Comparable<Card> {
    public enum Suit { 
        DIAMONDS (1, "Diamonds"), 
        CLUBS    (2, "Clubs"   ), 
        HEARTS   (3, "Hearts"  ), 
        SPADES   (4, "Spades"  );
        private final int value;
        private final String text;
        Suit(int value, String text) {
            this.value = value;
            this.text = text;
        }
        public int value() {return value;}
        public String text() {return text;}
    }
    public enum Rank { 
        DEUCE  (2 , "Two"  ),
        THREE  (3 , "Three"), 
        FOUR   (4 , "Four" ), 
        FIVE   (5 , "Five" ), 
        SIX    (6 , "Six"  ), 
        SEVEN  (7 , "Seven"),
        EIGHT  (8 , "Eight"), 
        NINE   (9 , "Nine" ), 
        TEN    (10, "Ten"  ), 
        JACK   (11, "Jack" ),
        QUEEN  (12, "Queen"), 
        KING   (13, "King" ),
        ACE    (14, "Ace"  );
        private final int value;
        private final String text;
        Rank(int value, String text) {
            this.value = value;
            this.text = text;
        }
        public int value() {return value;}
        public String text() {return text;}
    }
    public Card.Suit getSuit();
    public Card.Rank getRank();
}

Deck接口包含各种操作牌组中卡片的方法:

package defaultmethods; 
import java.util.*;
import java.util.stream.*;
import java.lang.*;
public interface Deck {
    List<Card> getCards();
    Deck deckFactory();
    int size();
    void addCard(Card card);
    void addCards(List<Card> cards);
    void addDeck(Deck deck);
    void shuffle();
    void sort();
    void sort(Comparator<Card> c);
    String deckToString();
    Map<Integer, Deck> deal(int players, int numberOfCards)
        throws IllegalArgumentException;
}

PlayingCard实现了接口Card,而类StandardDeck实现了接口Deck

StandardDeck按如下方式实现了抽象方法Deck.sort

public class StandardDeck implements Deck {
    private List<Card> entireDeck;
    // ...
    public void sort() {
        Collections.sort(entireDeck);
    }
    // ...
}

方法Collections.sort对实现接口Comparable的元素类型为List的实例进行排序。成员entireDeck是一个List的实例,其元素类型为扩展了ComparableCard类型。类PlayingCard按如下方式实现了Comparable.compareTo方法:

public int hashCode() {
    return ((suit.value()-1)*13)+rank.value();
}
public int compareTo(Card o) {
    return this.hashCode() - o.hashCode();
}

方法compareTo导致方法StandardDeck.sort()首先按花色,然后按等级对牌组进行排序。

如果你想先按等级,然后按花色对牌组进行排序怎么办?你需要实现Comparator接口来指定新的排序标准,并使用方法sort(List list, Comparator c)(包含Comparator参数的sort方法版本)。你可以在类StandardDeck中定义以下方法:

public void sort(Comparator<Card> c) {
    Collections.sort(entireDeck, c);
} 

有了这个方法,你可以指定方法Collections.sort如何对Card类的实例进行排序。一种方法是实现Comparator接口来指定你希望如何对牌进行排序。示例SortByRankThenSuit就是这样做的:

package defaultmethods;
import java.util.*;
import java.util.stream.*;
import java.lang.*;
public class SortByRankThenSuit implements Comparator<Card> {
    public int compare(Card firstCard, Card secondCard) {
        int compVal =
            firstCard.getRank().value() - secondCard.getRank().value();
        if (compVal != 0)
            return compVal;
        else
            return firstCard.getSuit().value() - secondCard.getSuit().value(); 
    }
}

以下调用首先按等级,然后按花色对扑克牌组进行排序:

StandardDeck myDeck = new StandardDeck();
myDeck.shuffle();
myDeck.sort(new SortByRankThenSuit());

然而,这种方法太啰嗦了;如果你可以只指定排序标准而避免创建多个排序实现,那将更好。假设你是编写Comparator接口的开发人员。你可以向Comparator接口添加哪些默认或静态方法,以使其他开发人员更容易指定排序标准?

首先,假设你想按等级对扑克牌组进行排序,而不考虑花色。你可以如下调用StandardDeck.sort方法:

StandardDeck myDeck = new StandardDeck();
myDeck.shuffle();
myDeck.sort(
    (firstCard, secondCard) ->
        firstCard.getRank().value() - secondCard.getRank().value()
); 

因为Comparator接口是一个函数式接口,您可以使用 lambda 表达式作为sort方法的参数。在这个例子中,lambda 表达式比较两个整数值。

如果您的开发人员只需调用方法Card.getRank就能创建一个Comparator实例,那将更简单。特别是,如果您的开发人员可以创建一个比较任何可以从getValuehashCode等方法返回数值的对象的Comparator实例,那将很有帮助。Comparator接口已经通过静态方法comparing增强了这种能力:

myDeck.sort(Comparator.comparing((card) -> card.getRank()));  

在这个例子中,您可以使用方法引用:

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

这种调用更好地演示了如何指定不同的排序标准并避免创建多个排序实现。

Comparator接口已经通过其他版本的静态方法comparing(如comparingDoublecomparingLong)进行了增强,使您能够创建比较其他数据类型的Comparator实例。

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

相关文章
|
2天前
|
Web App开发 JavaScript 前端开发
《手把手教你》系列技巧篇(三十九)-java+ selenium自动化测试-JavaScript的调用执行-上篇(详解教程)
【5月更文挑战第3天】本文介绍了如何在Web自动化测试中使用JavaScript执行器(JavascriptExecutor)来完成Selenium API无法处理的任务。首先,需要将WebDriver转换为JavascriptExecutor对象,然后通过executeScript方法执行JavaScript代码。示例用法包括设置JS代码字符串并调用executeScript。文章提供了两个实战场景:一是当时间插件限制输入时,用JS去除元素的readonly属性;二是处理需滚动才能显示的元素,利用JS滚动页面。还给出了一个滚动到底部的代码示例,并提供了详细步骤和解释。
31 10
|
2天前
|
Java 测试技术 Python
《手把手教你》系列技巧篇(三十六)-java+ selenium自动化测试-单选和多选按钮操作-番外篇(详解教程)
【4月更文挑战第28天】本文简要介绍了自动化测试的实战应用,通过一个在线问卷调查(&lt;https://www.sojump.com/m/2792226.aspx/&gt;)为例,展示了如何遍历并点击问卷中的选项。测试思路包括找到单选和多选按钮的共性以定位元素,然后使用for循环进行点击操作。代码设计方面,提供了Java+Selenium的示例代码,通过WebDriver实现自动答题。运行代码后,可以看到控制台输出和浏览器的相应动作。文章最后做了简单的小结,强调了本次实践是对之前单选多选操作的巩固。
25 0
|
19小时前
|
算法 Java Python
保姆级Java入门练习教程,附代码讲解,小白零基础入门必备
保姆级Java入门练习教程,附代码讲解,小白零基础入门必备
|
23小时前
|
Web App开发 JavaScript 测试技术
《手把手教你》系列技巧篇(四十五)-java+ selenium自动化测试-web页面定位toast-上篇(详解教程)
【5月更文挑战第9天】本文介绍了在Appium中处理App自动化测试中遇到的Toast元素定位的方法。Toast在Web UI测试中也常见,通常作为轻量级反馈短暂显示。文章提供了两种定位Toast元素的技巧.
10 0
|
2天前
|
Web App开发 缓存 前端开发
《手把手教你》系列技巧篇(四十四)-java+ selenium自动化测试-处理https 安全问题或者非信任站点-下篇(详解教程)
【5月更文挑战第8天】这篇文档介绍了如何在IE、Chrome和Firefox浏览器中处理不信任证书的问题。作者北京-宏哥分享了如何通过编程方式跳过浏览器的证书警告,直接访问不受信任的HTTPS网站。文章分为几个部分,首先简要介绍了问题背景,然后详细讲解了在Chrome浏览器中的两种方法,包括代码设计和运行效果,并给出了其他浏览器的相关信息和参考资料。最后,作者总结了处理此类问题的一些通用技巧。
16 2
|
2天前
|
Java Android开发
【Java开发指南 | 第十八篇】Eclipse安装教程
【Java开发指南 | 第十八篇】Eclipse安装教程
11 2
|
2天前
|
Web App开发 JavaScript 前端开发
《手把手教你》系列技巧篇(四十三)-java+ selenium自动化测试-处理https 安全问题或者非信任站点-上篇(详解教程)
【5月更文挑战第7天】本文介绍了如何在Java+Selenium自动化测试中处理浏览器对不信任证书的处理方法,特别是针对IE、Chrome和Firefox浏览器。在某些情况下,访问HTTPS网站时会遇到证书不可信的警告,但可以通过编程方式跳过这些警告。
13 1
|
2天前
|
前端开发 Java 测试技术
《手把手教你》系列技巧篇(四十二)-java+ selenium自动化测试 - 处理iframe -下篇(详解教程)
【5月更文挑战第6天】本文介绍了如何使用Selenium处理含有iframe的网页。作者首先解释了iframe是什么,即HTML中的一个框架,用于在一个页面中嵌入另一个页面。接着,通过一个实战例子展示了在QQ邮箱登录页面中,由于输入框存在于iframe内,导致直接定位元素失败。作者提供了三种方法来处理这种情况:1)通过id或name属性切换到iframe;2)使用webElement对象切换;3)通过索引切换。最后,给出了相应的Java代码示例,并提醒读者根据iframe的实际情况选择合适的方法进行切换和元素定位。
9 0
|
2天前
|
前端开发 测试技术 Python
《手把手教你》系列技巧篇(四十一)-java+ selenium自动化测试 - 处理iframe -上篇(详解教程)
【5月更文挑战第5天】本文介绍了HTML中的`iframe`标签,它用于在网页中嵌套其他网页。`iframe`常用于加载外部内容或网站的某个部分,以实现页面美观。文章还讲述了使用Selenium自动化测试时如何处理`iframe`,通过`switchTo().frame()`方法进入`iframe`,完成相应操作,然后使用`switchTo().defaultContent()`返回主窗口。此外,文章提供了一个包含`iframe`的HTML代码示例,并给出了一个简单的自动化测试代码实战,演示了如何在`iframe`中输入文本。
17 3
|
2天前
|
JavaScript 前端开发 Java
《手把手教你》系列技巧篇(四十)-java+ selenium自动化测试-JavaScript的调用执行-下篇(详解教程)
【5月更文挑战第4天】本文介绍了如何使用JavaScriptExecutor在自动化测试中实现元素高亮显示。通过创建并执行JS代码,可以改变元素的样式,例如设置背景色和边框,以突出显示被操作的元素。文中提供了一个Java示例,展示了如何在Selenium中使用此方法,并附有代码截图和运行效果展示。该技术有助于跟踪和理解测试过程中的元素交互。
10 0