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

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

方法引用

原文:docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

你可以使用 lambda 表达式来创建匿名方法。然而,有时候 lambda 表达式仅仅是调用一个已存在的方法。在这种情况下,通过名称引用现有方法通常更清晰。方法引用使你能够做到这一点;它们是紧凑、易读的 lambda 表达式,用于已经有名称的方法。

再次考虑在 lambda 表达式部分讨论的Person类:

public class Person {
    // ...
    LocalDate birthday;
    public int getAge() {
        // ...
    }
    public LocalDate getBirthday() {
        return birthday;
    }   
    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }
    // ...
}

假设你的社交网络应用的成员被包含在一个数组中,并且你想按年龄对数组进行排序。你可以使用以下代码(在示例MethodReferencesTest中找到本节描述的代码片段):

Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);
class PersonAgeComparator implements Comparator<Person> {
    public int compare(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
Arrays.sort(rosterAsArray, new PersonAgeComparator());

此次调用sort的方法签名如下:

static <T> void sort(T[] a, Comparator<? super T> c)

注意Comparator接口是一个函数式接口。因此,你可以使用 lambda 表达式来代替定义并创建一个实现Comparator的类的新实例:

Arrays.sort(rosterAsArray,
    (Person a, Person b) -> {
        return a.getBirthday().compareTo(b.getBirthday());
    }
);

然而,比较两个Person实例的出生日期的方法Person.compareByAge已经存在。你可以在 lambda 表达式的主体中调用这个方法:

Arrays.sort(rosterAsArray,
    (a, b) -> Person.compareByAge(a, b)
);

因为这个 lambda 表达式调用了一个已存在的方法,你可以使用方法引用代替 lambda 表达式:

Arrays.sort(rosterAsArray, Person::compareByAge);

方法引用Person::compareByAge在语义上与 lambda 表达式(a, b) -> Person.compareByAge(a, b)相同。它们各自具有以下特征:

  • 其形式参数列表是从Comparator.compare复制的,即(Person, Person)
  • 其主体调用方法Person.compareByAge

方法引用的种类

有四种方法引用的种类:

种类 语法 示例
引用静态方法 *ContainingClass*::*staticMethodName* Person::compareByAge MethodReferencesExamples::appendStrings
引用特定对象的实例方法 *containingObject*::*instanceMethodName* myComparisonProvider::compareByName myApp::appendStrings2
引用特定类型的任意对象的实例方法 *ContainingType*::*methodName* String::compareToIgnoreCase String::concat
引用构造函数 *ClassName*::new HashSet::new

以下示例,MethodReferencesExamples,包含了前三种方法引用的示例:

import java.util.function.BiFunction;
public class MethodReferencesExamples {
    public static <T> T mergeThings(T a, T b, BiFunction<T, T, T> merger) {
        return merger.apply(a, b);
    }
    public static String appendStrings(String a, String b) {
        return a + b;
    }
    public String appendStrings2(String a, String b) {
        return a + b;
    }
    public static void main(String[] args) {
        MethodReferencesExamples myApp = new MethodReferencesExamples();
        // Calling the method mergeThings with a lambda expression
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", (a, b) -> a + b));
        // Reference to a static method
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", MethodReferencesExamples::appendStrings));
        // Reference to an instance method of a particular object        
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", myApp::appendStrings2));
        // Reference to an instance method of an arbitrary object of a
        // particular type
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", String::concat));
    }
}

所有的System.out.println()语句都打印相同的内容:Hello World!

BiFunctionjava.util.function包中许多函数接口之一。BiFunction函数接口可以表示接受两个参数并产生结果的 lambda 表达式或方法引用。

静态方法引用

方法引用Person::compareByAgeMethodReferencesExamples::appendStrings是对静态方法的引用。

引用特定对象的实例方法

下面是引用特定对象实例方法的示例:

class ComparisonProvider {
    public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }
    public int compareByAge(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);

方法引用myComparisonProvider::compareByName调用myComparisonProvider对象的compareByName方法。JRE 推断方法类型参数,本例中为(Person, Person)

类似地,方法引用myApp::appendStrings2将调用myApp对象的appendStrings2方法。JRE 推断方法类型参数,本例中为(String, String)

引用特定类型任意对象的实例方法

下面是一个引用特定类型任意对象的实例方法的示例:

String[] stringArray = { "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

方法引用String::compareToIgnoreCase的等效 lambda 表达式将具有形式参数列表(String a, String b),其中ab是用于更好描述此示例的任意名称。方法引用将调用a.compareToIgnoreCase(b)方法。

类似地,方法引用String::concat将调用a.concat(b)方法。

构造函数引用

你可以通过使用名称new来引用构造函数,与引用静态方法的方式相同。以下方法将元素从一个集合复制到另一个集合:

public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
    DEST transferElements(
        SOURCE sourceCollection,
        Supplier<DEST> collectionFactory) {
    DEST result = collectionFactory.get();
    for (T t : sourceCollection) {
        result.add(t);
    }
    return result;
}

函数接口Supplier包含一个名为get的方法,不接受参数并返回一个对象。因此,你可以使用 lambda 表达式调用方法transferElements,如下所示:

Set<Person> rosterSetLambda =
    transferElements(roster, () -> { return new HashSet<>(); });

你可以使用构造函数引用来替代 lambda 表达式,如下所示:

Set<Person> rosterSet = transferElements(roster, HashSet::new);

Java 编译器推断你想要创建一个包含类型为Person的元素的HashSet集合。或者,你可以按照以下方式指定:

Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);

何时使用嵌套类、局部类、匿名类和 Lambda 表达式

原文:docs.oracle.com/javase/tutorial/java/javaOO/whentouse.html

如在嵌套类一节中所述,嵌套类使您能够逻辑地将仅在一个地方使用的类分组,增加封装的使用,并创建更易读和可维护的代码。局部类、匿名类和 Lambda 表达式也具有这些优点;但是,它们旨在用于更具体的情况:

  • 局部类:如果需要创建一个类的多个实例、访问其构造函数或引入一个新的命名类型(例如,因为您需要稍后调用其他方法),请使用它。
  • 匿名类:如果需要声明字段或额外方法,请使用它。
  • Lambda 表达式:
  • 如果您要封装要传递给其他代码的单个行为单元,请使用它。例如,如果您希望对集合的每个元素执行某个操作,当进程完成时,或者当进程遇到错误时,您将使用 Lambda 表达式。
  • 如果需要一个功能接口的简单实例,并且前述条件均不适用(例如,您不需要构造函数、命名类型、字段或额外方法),请使用它。
  • 嵌套类:如果您的需求类似于局部类,并且希望使类型更广泛可用,且不需要访问局部变量或方法参数时,请使用它。
  • 如果需要访问封闭实例的非公共字段和方法,请使用非静态嵌套类(或内部类)。如果不需要此访问权限,请使用静态嵌套类。

问题和练习:嵌套类

原文:docs.oracle.com/javase/tutorial/java/javaOO/QandE/nested-questions.html

问题

  1. 程序Problem.java无法编译。你需要做什么才能使其编译?为什么?
  2. 使用 Java API 文档中Box类(位于javax.swing包中)的文档来帮助回答以下问题。
  1. Box定义了哪个静态嵌套类?
  2. Box定义了哪个内部类?
  3. Box的内部类的超类是什么?
  4. 从任何类中可以使用Box的哪些嵌套类?
  5. 如何创建BoxFiller类的实例?

练习

  1. 获取文件Class1.java。编译并运行Class1。输出是什么?
  2. 以下练习涉及修改类DataStructure.java,该类在内部类示例部分讨论。
  1. 定义一个名为print(DataStructureIterator iterator)的方法。使用EvenIterator类的实例调用此方法,使其执行与printEven方法相同的功能。
  2. 调用方法print(DataStructureIterator iterator),使其打印具有奇数索引值的元素。使用匿名类作为方法的参数,而不是接口DataStructureIterator的实例。
  3. 定义一个名为print(java.util.function.Function iterator)的方法,执行与print(DataStructureIterator iterator)相同的功能。使用 lambda 表达式调用此方法,以打印具有偶数索引值的元素。再次使用 lambda 表达式调用此方法,以打印具有奇数索引值的元素。
  4. 定义两个方法,使得以下两个语句打印具有偶数索引值和具有奇数索引值的元素:
DataStructure ds = new DataStructure()
// ...
ds.print(DataStructure::isEvenIndex);
ds.print(DataStructure::isOddIndex);

检查你的答案。

枚举类型

原文:docs.oracle.com/javase/tutorial/java/javaOO/enum.html

枚举类型是一种特殊的数据类型,允许变量成为一组预定义的常量之一。变量必须等于为其预定义的值之一。常见示例包括罗盘方向(NORTH、SOUTH、EAST 和 WEST 的值)和一周的天数。

由于它们是常量,枚举类型字段的名称必须是大写字母。

在 Java 编程语言中,您可以使用enum关键字定义枚举类型。例如,您可以指定一个星期几的枚举类型如下:

public enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY 
}

每当需要表示一组固定常量时,都应该使用枚举类型。这包括自然枚举类型,如我们太阳系中的行星和在编译时知道所有可能值的数据集,例如菜单上的选项、命令行标志等。

这里是一些代码,向您展示如何使用上面定义的Day枚举:

public class EnumTest {
    Day day;
    public EnumTest(Day day) {
        this.day = day;
    }
    public void tellItLikeItIs() {
        switch (day) {
            case MONDAY:
                System.out.println("Mondays are bad.");
                break;
            case FRIDAY:
                System.out.println("Fridays are better.");
                break;
            case SATURDAY: case SUNDAY:
                System.out.println("Weekends are best.");
                break;
            default:
                System.out.println("Midweek days are so-so.");
                break;
        }
    }
    public static void main(String[] args) {
        EnumTest firstDay = new EnumTest(Day.MONDAY);
        firstDay.tellItLikeItIs();
        EnumTest thirdDay = new EnumTest(Day.WEDNESDAY);
        thirdDay.tellItLikeItIs();
        EnumTest fifthDay = new EnumTest(Day.FRIDAY);
        fifthDay.tellItLikeItIs();
        EnumTest sixthDay = new EnumTest(Day.SATURDAY);
        sixthDay.tellItLikeItIs();
        EnumTest seventhDay = new EnumTest(Day.SUNDAY);
        seventhDay.tellItLikeItIs();
    }
}

输出为:

Mondays are bad.
Midweek days are so-so.
Fridays are better.
Weekends are best.
Weekends are best.

Java 编程语言的枚举类型比其他语言中的对应类型更强大。enum声明定义了一个(称为枚举类型)。枚举类体可以包括方法和其他字段。编译器在创建枚举时会自动添加一些特殊方法。例如,它们具有一个静态values方法,返回一个包含枚举值的数组,按照它们声明的顺序排列。此方法通常与 for-each 结构结合使用,以遍历枚举类型的值。例如,下面Planet类示例中的代码遍历太阳系中的所有行星。

for (Planet p : Planet.values()) {
    System.out.printf("Your weight on %s is %f%n",
                      p, p.surfaceWeight(mass));
}

注意: 所有枚举隐式扩展java.lang.Enum。因为一个类只能扩展一个父类(参见声明类),Java 语言不支持状态的多重继承(参见状态、实现和类型的多重继承),因此枚举不能扩展其他任何内容。


在下面的示例中,Planet是一个表示太阳系行星的枚举类型。它们定义了常量质量和半径属性。

每个枚举常量都声明了质量和半径参数的值。这些值在创建常量时传递给构造函数。Java 要求常量在任何字段或方法之前定义。此外,当存在字段和方法时,枚举常量列表必须以分号结尾。


注意: 枚举类型的构造函数必须是包私有或私有访问。它会自动创建在枚举体开头定义的常量。您不能自己调用枚举构造函数。


除了其属性和构造函数外,Planet 还有一些方法,可以让你获取每个行星上物体的表面重力和重量。以下是一个示例程序,它接受你在地球上的体重(以任何单位)并计算并打印出你在所有行星上的体重(以相同单位):

public enum Planet {
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN  (5.688e+26, 6.0268e7),
    URANUS  (8.686e+25, 2.5559e7),
    NEPTUNE (1.024e+26, 2.4746e7);
    private final double mass;   // in kilograms
    private final double radius; // in meters
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }
    private double mass() { return mass; }
    private double radius() { return radius; }
    // universal gravitational constant  (m3 kg-1 s-2)
    public static final double G = 6.67300E-11;
    double surfaceGravity() {
        return G * mass / (radius * radius);
    }
    double surfaceWeight(double otherMass) {
        return otherMass * surfaceGravity();
    }
    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("Usage: java Planet <earth_weight>");
            System.exit(-1);
        }
        double earthWeight = Double.parseDouble(args[0]);
        double mass = earthWeight/EARTH.surfaceGravity();
        for (Planet p : Planet.values())
           System.out.printf("Your weight on %s is %f%n",
                             p, p.surfaceWeight(mass));
    }
}

如果你在命令行中运行 Planet.class 并带上参数 175,你会得到以下输出:

$ java Planet 175
Your weight on MERCURY is 66.107583
Your weight on VENUS is 158.374842
Your weight on EARTH is 175.000000
Your weight on MARS is 66.279007
Your weight on JUPITER is 442.847567
Your weight on SATURN is 186.552719
Your weight on URANUS is 158.397260
Your weight on NEPTUNE is 199.207413

问题和练习:枚举类型

原文:docs.oracle.com/javase/tutorial/java/javaOO/QandE/enum-questions.html

问题

  1. 真或假:Enum类型可以是java.lang.String的子类。

练习

  1. 重写问题和练习:类中的Card类,使其使用枚举类型表示卡牌的等级和花色。
  2. 重写Deck类。

检查你的答案。

课程:注解

原文:docs.oracle.com/javase/tutorial/java/annotations/index.html

注解,一种元数据形式,提供关于程序的数据,这些数据不是程序本身的一部分。注解对其注释的代码的操作没有直接影响。

注解有多种用途,其中包括:

  • 编译器的信息 — 编译器可以使用注解来检测错误或抑制警告。
  • 编译时和部署时处理 — 软件工具可以处理注解信息以生成代码、XML 文件等。
  • 运行时处理 — 一些注解可以在运行时被检查。

本课程解释了注解可以在哪里使用,如何应用注解,在 Java 平台标准版(Java SE API)中有哪些预定义的注解类型可用,如何将类型注解与可插入类型系统结合使用以编写具有更强类型检查的代码,以及如何实现重复注解。

注解基础知识

原文:docs.oracle.com/javase/tutorial/java/annotations/basics.html

注解的格式

在其最简单的形式下,注解看起来像下面这样:

@Entity

符号@告诉编译器接下来是一个注解。在下面的例子中,注解的名称是Override

@Override
void mySuperMethod() { ... }

注解可以包括元素,这些元素可以是命名的或未命名的,并且这些元素有值:

@Author(
   name = "Benjamin Franklin",
   date = "3/27/2003"
)
class MyClass { ... }

@SuppressWarnings(value = "unchecked")
void myMethod() { ... }

如果只有一个名为value的元素,则名称可以省略,如:

@SuppressWarnings("unchecked")
void myMethod() { ... }

如果注解没有元素,则括号可以省略,如前面的@Override示例所示。

也可以在同一声明上使用多个注解:

@Author(name = "Jane Doe")
@EBook
class MyClass { ... }

如果注解具有相同的类型,则称为重复注解:

@Author(name = "Jane Doe")
@Author(name = "John Smith")
class MyClass { ... }

从 Java SE 8 发布开始支持重复注解。更多信息,请参见重复注解。

注解类型可以是 Java SE API 的java.langjava.lang.annotation包中定义的类型之一。在前面的示例中,OverrideSuppressWarnings是预定义的 Java 注解。还可以定义自己的注解类型。前面示例中的AuthorEbook注解是自定义注解类型。

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

相关文章
|
9天前
|
Java 测试技术 Python
《手把手教你》系列技巧篇(二十九)-java+ selenium自动化测试- Actions的相关操作上篇(详解教程)
【4月更文挑战第21天】本文介绍了Selenium中处理特殊测试场景的方法,如鼠标悬停。Selenium的Actions类提供了鼠标悬停功能,用于模拟用户在网页元素上的悬停行为。文中通过实例展示了如何使用Actions悬停并展开下拉菜单,以及在搜索时选择自动补全的字段。代码示例包括了打开百度首页,悬停在“更多”元素上显示下拉菜单并点击“音乐”,以及在搜索框输入关键词并自动补全的过程。
33 0
|
2天前
|
Java 测试技术 Python
《手把手教你》系列技巧篇(三十六)-java+ selenium自动化测试-单选和多选按钮操作-番外篇(详解教程)
【4月更文挑战第28天】本文简要介绍了自动化测试的实战应用,通过一个在线问卷调查(&lt;https://www.sojump.com/m/2792226.aspx/&gt;)为例,展示了如何遍历并点击问卷中的选项。测试思路包括找到单选和多选按钮的共性以定位元素,然后使用for循环进行点击操作。代码设计方面,提供了Java+Selenium的示例代码,通过WebDriver实现自动答题。运行代码后,可以看到控制台输出和浏览器的相应动作。文章最后做了简单的小结,强调了本次实践是对之前单选多选操作的巩固。
10 0
|
2天前
|
Java 测试技术 项目管理
Java基础教程(22)-构建工具Maven的基本使用
【4月更文挑战第22天】Maven是Java项目管理及构建工具,简化构建、测试、打包和部署等任务。遵循约定优于配置原则,核心是`pom.xml`配置文件,用于管理依赖和项目信息。安装涉及下载、解压、配置环境变量。在IDEA中使用Maven创建项目,通过`pom.xml`添加依赖和管理版本。常用命令包括`clean`、`compile`、`test`、`package`、`install`和`deploy`。IDEA支持直接执行这些命令。
|
2天前
|
NoSQL Java 关系型数据库
Java基础教程(21)-Java连接MongoDB
【4月更文挑战第21天】MongoDB是开源的NoSQL数据库,强调高性能和灵活性。Java应用通过MongoDB Java驱动与之交互,涉及MongoClient、MongoDatabase、MongoCollection和Document等组件。连接MongoDB的步骤包括:配置连接字符串、创建MongoClient、选择数据库和集合。伪代码示例展示了如何建立连接、插入和查询数据。
|
3天前
|
存储 前端开发 测试技术
《手把手教你》系列技巧篇(三十五)-java+ selenium自动化测试-单选和多选按钮操作-下篇(详解教程)
【4月更文挑战第27天】本文介绍了使用Java+Selenium进行Web自动化测试时,如何遍历和操作多选按钮的方法。文章分为两个部分,首先是一个本地HTML页面的示例,展示了多选按钮的HTML代码和页面效果,并详细解释了遍历多选按钮的思路:找到所有多选按钮的共同点,通过定位这些元素并放入list容器中,然后使用for循环遍历并操作。 第二部分介绍了在JQueryUI网站上的实战,给出了被测网址,展示了代码设计,同样使用了findElements()方法获取所有多选按钮并存储到list中,然后遍历并进行点击操作。最后,文章对整个过程进行了小结,并推荐了作者的其他自动化测试教程资源。
11 0
|
3天前
|
Java 关系型数据库 MySQL
Java基础教程(20)-Java连接mysql数据库CURD
【4月更文挑战第19天】MySQL是流行的关系型数据库管理系统,支持SQL语法。在IDEA中加载jar包到项目类路径:右击项目,选择“Open Module Settings”,添加库文件。使用JDBC连接MySQL,首先下载JDBC驱动,然后通过`Class.forName()`加载驱动,`DriverManager.getConnection()`建立连接。执行CRUD操作,例如创建表、插入数据和查询,使用`Statement`或`PreparedStatement`,并确保正确关闭数据库资源。
|
4天前
|
设计模式 算法 Java
Java基础教程(19)-设计模式简述
【4月更文挑战第19天】设计模式是软件设计中反复使用的代码设计经验,旨在提升代码的可重用性、可扩展性和可维护性。23种模式分为创建型、结构型和行为型三类。创建型模式如工厂方法、抽象工厂、建造者、原型和单例,关注对象创建与使用的分离。结构型模式涉及对象组合,如适配器、装饰器、外观等,增强结构灵活性。行为型模式专注于对象间职责分配和算法合作,包括责任链、命令、观察者等。设计模式提供标准化解决方案,促进代码交流和复用。
|
4天前
|
前端开发 测试技术 Python
《手把手教你》系列技巧篇(三十三)-java+ selenium自动化测试-单选和多选按钮操作-上篇(详解教程)
【4月更文挑战第25天】本文介绍了自动化测试中如何处理单选和多选按钮的操作,包括它们的定义、HTML代码示例以及如何判断和操作这些元素。文章通过一个简单的HTML页面展示了单选和多选框的示例,并提供了Java+Selenium实现的代码示例,演示了如何检查单选框是否选中以及如何进行全选操作。
11 0
|
5天前
|
网络协议 Java 网络架构
Java基础教程(18)-Java中的网络编程
【4月更文挑战第18天】Java网络编程简化了底层协议处理,利用Java标准库接口进行TCP/IP通信。TCP协议提供可靠传输,常用于HTTP、SMTP等协议;UDP协议则更高效但不保证可靠性。在TCP编程中,ServerSocket用于监听客户端连接,Socket实现双进程间通信。UDP编程中,DatagramSocket处理无连接的数据报文。HTTP编程可以通过HttpURLConnection发送请求并接收响应。
|
6天前
|
前端开发 Java 测试技术
《手把手教你》系列技巧篇(三十二)-java+ selenium自动化测试-select 下拉框(详解教程)
【4月更文挑战第24天】本文介绍了在自动化测试中处理HTML下拉选择(select)的方法。使用Selenium的Select类,可以通过index、value或visible text三种方式选择选项,并提供了相应的取消选择的方法。此外,文章还提供了一个示例HTML页面(select.html)和相关代码实战,演示了如何使用Selenium进行选择和取消选择操作。最后,文章提到了现代网页中类似下拉框的新设计,如12306网站的出发地选择,并给出了相应的代码示例,展示了如何定位并选择特定选项。
16 0