Java 17中的密封类是什么?

uhry853o  于 5个月前  发布在  Java
关注(0)|答案(9)|浏览(45)

今天我把Java版本从16更新到了17,发现sealed类是其中的一个新特性,我觉得可以这样声明:

public sealed class Main permits AClass, AnotherClass {
}

字符串
那么,Java中的密封类有什么用呢?
我也知道这是JDK 15中的一个 * 预览 * 功能。

gg0vcinb

gg0vcinb1#

You can follow this link for examples.
简而言之,密封类使您可以控制哪些模型,类等可以实现或扩展该类/接口。
链接的例子:

public sealed interface Service permits Car, Truck {

    int getMaxServiceIntervalInMonths();

    default int getMaxDistanceBetweenServicesInKilometers() {
        return 100000;
    }
}

字符串
这个接口只允许Car和Truck实现它。

rseugnpd

rseugnpd2#

JEP 409解释为
密封的类或接口只能由那些被允许这样做的类和接口来扩展或实现。
更实际的解释如下:

  • 过去的情况是:*
  • 不能限制一个接口被另一个接口扩展
  • 你不能限制哪些类可以实现一个特定的接口。
  • 你必须声明一个类为final,这样就不会被其他类扩展。这样就没有类可以扩展声明的final类。这是一种非黑即白的白色方法。
  • 当前密封关键字的情况为:*
  • 现在,您可以限制一个接口被其他接口扩展,并为仅允许扩展它的某些特定接口制定规则。

范例:

public sealed interface MotherInterface permits ChildInterfacePermitted {}

//Has to be declared either as sealed or non-sealed
public non-sealed interface ChildInterfacePermitted extends MotherInterface {}  

public interface AnotherChildInterface extends MotherInterface {} 
//compiler error! It is not included in the permits of mother inteface

字符串

  • 现在你可以创建一个接口,并只选择允许实现该接口的特定类。所有其他类都不允许实现它。

范例:

public sealed interface MotherInterface permits ImplementationClass1 {} 

 //Has to be declared either as final or as sealed or as non-sealed
 public final class ImplementationClass1 implements MotherInterface {} 

 public class ImplementationClass2 implements MotherInterface {} 
 //compiler error! It is not included in the permits of mother inteface

  • 你现在可以限制一个类被扩展(和以前的final一样),但是你现在可以允许一些特定的类扩展它。

范例:

public sealed class MotherClass permits ChildClass1 {}

//Has to be declared either as final or as sealed or as non-sealed
public non-sealed class ChildClass1 extends MotherClass {} 

 public class ChildClass2 extends MotherClass {} 
 //compiler error! It is not included in the permits of MotherClass

* 重要提示:*

  • 密封类及其允许的子类必须属于同一个模块,如果在未命名的模块中声明,则必须属于同一个包。

范例:
假设我们有相同的未命名模块和以下包

-packageA
     -Implementationclass1.java
  -packageB
     -MotherClass.java


-root
      -MotherClass.java
      -packageA
         -Implementationclass1.java


你会得到错误 Class is not allowed to extend sealed class from another package.所以如果你有一个未命名的模块,所有参与的类和sealed函数的接口必须完全放在同一个包上。

  • 每个允许的子类必须直接扩展密封类。
iih3973s

iih3973s3#

封闭类

密封类是一种约束,只允许给定的类实现它。这些被允许的类必须显式地扩展密封类,并且还具有sealednon-sealedfinal修饰符之一。该功能自Java 17(JEP 409)起提供,并且在更早的时候(Java 15)作为预览版提供。

sealed interface IdentificationDocument permits IdCard, Passport, DrivingLicence { }

个字符

使用模式匹配

我发现这个新特性与Java 17(JEP 406)的预览版中引入的模式匹配结合起来非常棒!
允许的类限制确保所有子类在编译时都是已知的。使用switch表达式(Java 14的JEP 361),编译器需要列出所有允许的类,或者对剩余的类使用default关键字。考虑以下使用上述类的示例:

final String code = switch(identificationDocument) {
    case IdCard idCard -> "I";
    case Passport passport -> "P";
};


编译器在javac Application.java --enable-preview -source 17上会导致错误:

Application.java:9: error: the switch expression does not cover all possible input values
                final String code = switch(identificationDocument) {
                                    ^
Note: Application.java uses preview features of Java SE 17.
Note: Recompile with -Xlint:preview for details.
1 error


一旦使用了所有允许的类或default关键字,编译就成功了:

final String code = switch(identificationDocument) {
    case IdCard idCard -> "I";
    case Passport passport -> "P";
    case DrivingLicence drivingLicence -> "D";
};

vohkndzv

vohkndzv4#

密封类是Java语言的一个附加功能,它使类作者能够细粒度地控制哪些类可以扩展它。以前,你可以允许所有人继承你的类,也可以完全禁止继承(使用“final”)。它也适用于接口。
此外,它是模式匹配特性的先决条件,因为在编译期间所有的后代都是已知的。
像往常一样,有一个缺点-密封的类和接口不能被模仿/伪造,这是一个测试障碍。

6tr1vspr

6tr1vspr5#

根据这个documentation,密封类和接口限制了其他类或接口可以扩展或实现它们。它更像是一种声明性的方式来限制超类的使用,而不是使用访问修饰符。
在Java中,一个类可以是final的,所以没有其他类可以继承它。如果一个类不是final的,那么它对所有其他类都是开放的,以支持代码的可重用性。这样做会引起数据建模的关注。
下面的NumberSystem类对所有类开放,所以任何子类都可以扩展它。如果你想将这个NumberSystem限制为一组固定的子类(Binary,Decimal,Octal和HexaDecimal)呢?。这意味着你不希望任何其他任意类扩展这个NumberSystem类。

class NumberSystem { ... }
final class Binary extends NumberSystem { ... }
final class Decimal extends NumberSystem { ... }
final class Octal extends NumberSystem { ... }
final class HexaDecimal extends NumberSystem { ... }

字符串
使用密封类,您可以通过控制可以扩展它的子类来实现它,并防止任何其他任意类这样做。

u7up0aaq

u7up0aaq6#

Java中什么是密封类?

final修饰符可以被认为是一种强密封形式,其中扩展/实现被完全禁止。
概念上:final = sealed+空permits子句。
final完全禁止扩展/实现不同,Sealedclass/interface限制其他类或接口可以扩展或实现它们。

历史记录

1.密封类由JEP 360提出,并在JDK 15中作为预览功能提供。

  1. JEP 397再次提出了它们,并进行了改进,并在JDK 16中作为预览功能提供。
    1.这个JEP建议在JDK 17中完成密封类,与JDK 16没有任何变化。

目标

  • 允许类或接口的作者控制负责实现它的代码。
  • 提供一种比访问修饰符更具声明性的方式来限制超类的使用。

说明

1.通过将sealed修饰符应用于类/接口的声明,可以密封该类/接口。
1.然后,在任何extends和implements子句之后,permits子句指定允许扩展密封类的类。

示例

例如,下面的Loan声明指定了允许的UnsecuredLoanSecuredLoan子类:

sealed interface Loan permits UnsecuredLoan,SecuredLoan{}

final class UnsecuredLoan implements Loan {}

record SecuredLoan() implements Loan{}

字符串

使用模式匹配密封类的好处

使用模式匹配,而不是用 if-else 链检查密封类的示例,我们可以使用一个用类型测试模式增强的switch
这将允许Java编译器检查所有允许的类都为我们覆盖。
例如,考虑以下代码:

void checkLoanType(Loan loan) {
    if (loan instanceof UnsecuredLoan unsecuredLoan) {
//    something
    } else if (loan instanceof SecuredLoan securedLoan) {
//     something
    }
}

**Java编译器无法确保instanceof测试覆盖了Loan的所有允许子类。**因此,如果省略了任何instanceof Loan,则不会发出编译时错误消息。

相反,使用模式匹配switch表达式,编译器可以确认Loan的每个允许的子类都被覆盖了。此外,如果缺少任何case,编译器将发出错误消息

void checkLoanType(Loan loan) {
     switch (loan) { 
       case SecuredLoan securedLoan -> {} //generated by compiler.
       case UnsecuredLoan unsecuredLoan -> {} //generated by compiler.
     }
}


参考:JEP 360: Sealed Classes

polkgigr

polkgigr7#

所有sealed java类或接口必须使用permits关键字。例如:
Parent.class:

public sealed class Parent permits Child1, Child2 {
  void parentMethod() {
    System.out.println("from a sealed parent class ");
  }
}

字符串
Child1.java:

public final class Child1 extends Parent {
  public static void main(String[] args) {
    Child1 obj = new Child1();
    obj.parentMethod();
  }
}


Child2.java:

public final class Child2 extends Parent {
  public static void main(String[] args) {
    Child2 obj = new Child2();
    obj.parentMethod();
  }
}


Child3.java

public final class Child3 extends Parent {
  public static void main(String[] args) {
    Child3 obj = new Child3();
    obj.parentMethod();
  }
}


这个Child3类代码将抛出一个编译时错误,说扩展密封类Parent的类型Child3应该是Parent的允许子类型(permits Child3,就像Child1Child2一样)。

jqjz2hbq

jqjz2hbq8#

封印职业:密封类是一种允许开发人员控制其他类可以从它们继承的程度的功能。通过将类声明为密封,您可以指定允许哪些其他类继承它。此功能增强了封装并提供了对类层次结构和扩展的更多控制。https://java-speed.blogspot.com/2023/07/what-is-sealed-classes-in-java-17.html

fsi0uk1n

fsi0uk1n9#

其他的答案都有标题问题(“Java 17中的密封类是什么?”)但是你在正文中的问题(“密封类的用途是什么?”)还没有真正得到解决。
密封类/接口是创建tagged union的一种方式。标记联合对于类就像Java枚举对于对象一样。
Java枚举允许您将类可以示例化的可能对象限制为一组特定的值。这有助于您像这样建模一周中的几天:

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

boolean isWeekday(Day day) {
    switch (day) {
    case SUNDAY:
    case SATURDAY:
      return true;
    default:
      return false;
}

字符串
而不是这样:

boolean isWeekday(int day) {
  switch (day) {
  case 0:
  case 6:
    return true;
  case 1:
  case 2:
  case 3:
  case 4:
  case 5:
    return false;
  default:
    throw new IllegalArgumentException("day must be between 0 and 6");
}


如果你有一个枚举类型,那么你知道你有每一个可能的有效值。你可以保证你的switch语句穷尽地处理所有输入。
枚举的局限性在于它们只适用于单个类的对象。每个枚举值必须具有相同的示例变量,相同的方法和相同的构造函数。每个枚举值都是同一个类的单个对象。
密封的类/接口克服了这个限制。由于每个子类都是自己的类,所以你可以改变任何你可以在类中改变的东西,例如示例变量,方法,构造函数,额外的实现接口。下面是一个例子:

sealed interface UpsertUserResponse
    permits UserCreated, UserUpdated, InvalidEmail, Unauthorized {
}

record UserCreated(UUID id) implements UpsertUserResponse {}
record UserUpdated(String oldEmail) implements UpsertUserResponse {}
record InvalidEmail(String reason) implements UpsertUserResponse {}
record Unauthorized implements UpsertUserResponse {}

String getResponseMessage(UpsertUserResponse response) {
  return switch (shape) {
    case UserCreated id -> "New user created with id " + id.toString();
    case UserUpdated oldEmail -> "Email updated from previous value of " + oldEmail;
    case InvalidEmail reason -> "The email you entered was invalid because " + reason;
    case Unauthorized -> "You can't do that!"
        }
}


你知道你的case语句已经处理了所有可能的情况。你可以灵活地支持不同的用户id和电子邮件验证错误,而不必求助于可能处于不可能状态的可空变量。(例如,没有办法错误地设置用户id和验证错误。)
如何在测试中模拟出密封类/接口的子类?你不会。密封类/接口与普通数据聚合(如记录)很好地配对。你不会为枚举编写模拟,也不会为密封类编写模拟。

相关问题