天天看点

java8根据某个id删选_Java 8可选

java8根据某个id删选

在编程时,我们都面临着(最) 臭名昭著的NullPointerException 。 而且我相信我们所有人都同意,遇到NullPointerException也是一种痛苦。 为了使读者了解最新情况,著名的计算机科学家Tony Hoare引入了空引用,他认为这是一个百万美元的错误 。 众所周知,这很容易实现,但是也很难预测。 这就是为什么开发人员需要非常谨慎的原因。

平常的方式

让我们考虑以下3个简单的POJO。

public class Employee {
  private Car car;

  public Car getCar() {
    return car;
  }
}

public class Car {
  private Insurance insurance;

  public Insurance getInsurance() {
    return insurance;
  }
}

public class Insurance {
  private String name;

  public String getName() {
    return name;
  }
}
           

仅提供背景信息–员工可以拥有汽车(虽然不是强制性的),汽车可以具有保险(不一定),并且保险必须始终具有名称。 只要记住了解以下内容即可。

现在,我们想通过提供人员实例来获得保险的名称。

public String getInsuranceName(Employee employee) {
  if (employee != null) {
    Car car = employee.getCar();
    if (car != null) {
      Insurance insurance = car.getInsurance();
      if (insurance != null) {
        return insurance.getName();
      }
    }
  }
  return "UNKNOWN";
}
           

这是我们通常采取的预防措施,以免遇到可怕的NullPointerException异常。 我们还认为这也会污染源代码,根据我的观点,应将其视为反模式。

另一种惯用的方式

上一节中提到的对null检查的这种深层嵌套看起来有些晦涩。 有时人们会以不同的方式来做。

public String getInsuranceName(Employee employee) {
  if (employee == null) {
    return "UNKNOWN";
  }
  Car car = employee.getCar();
  if (car == null) {
    return "UNKNOWN";
  }
  Insurance insurance = car.getInsurance();
  if (insurance == null) {
    return "UNKNOWN";
  }
  return insurance.getName();
}
           

在我看来,这还算不错,因为它不包含深层嵌套的null检查。 但是它仍然遵循相同的反模式,以某种不同的方式检查空值。

为什么NULL不好?

  1. 这会降低源代码的可读性
  2. 呈现没有价值的东西在语义上是不正确的
  3. 它与Java的思想背道而驰,因为Java会向开发人员隐藏指针(除非存在空引用的情况)

NULL的替代

很少有语言,例如Scala,Groovy消除了对空引用的可怕使用,以表示没有值。 可以以非常简洁的方式用Groovy编写类似的代码。

def name = employee?.car?.insurance?.name
           

? 这在Groovy中被称为“ 安全导航”运算符 ,它清楚地显示了易读的代码,同时消除了遇到可怕的空引用的可能性。

Java的努力

现在我们应该问,Java开发人员可以做什么来实现类似的事情,从而在保持可读性和可维护性源代码的同时,防止NullPointerException的可能性。 Java语言设计人员选择了Groovy或Scala语言已经实现的类似方法,但是引入了一个新类-Optional

可选的

public final class Optional<T> {
  public static<T> Optional<T> empty() {}
  public static <T> Optional<T> of(T value) {}
  public static <T> Optional<T> ofNullable(T value) {}
  public T get() {}
  public boolean isPresent() {}
  public void ifPresent(Consumer<? super T> consumer) {}
  public Optional<T> filter(Predicate<? super T> predicate) {}
  public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {}
  public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {}
  public T orElse(T other) {}
  public T orElseGet(Supplier<? extends T> other) {}
  public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {}
}
           

此类主要用于表示值的不存在。 如果您认为一个值可以始终存在或不能始终存在,则最好使用Optional类型。 在我们之前的示例中,员工可能会或可能不会有汽车,这就是为什么最好返回Optional <Car>而不是简单地返回Car 。

让我们看看我们如何设计上一个示例:

public class Employee {
  private Car car;

  public Optional<Car> getCar() {
    return Optional.ofNullable(car);
  }
}

public class Car {
  private Insurance insurance;

  public Optional<Insurance> getInsurance() {
    return Optional.ofNullable(insurance);
  }
}

public class Insurance {
  private String name;

  public String getName() {
    return name;
  }
}
           

我没有讨论过静态工厂的Nullable(..)方法,而只是将其视为包装值的包装实用程序方法,而不管其引用如何。

只需查看API,就可以轻松了解遇到可选类型时需要执行的操作。 对于开发人员而言,遇到此类可选类型总是表示缺少值的可能性,因此开发人员可以为此采取适当的措施。

可选创作

从类概述中,我们可以清楚地看到可以以多种方式创建Optional 。

  1. of(..) :这允许创建包装非空值的Optional实例
  2. empty() :这将创建一个空的Optional
  3. ofNullable(..) :这允许创建一个包装任何值(空或非空)的Optional实例

可选的提取和转换

到目前为止,我们已经看到了如何创建Optional实例。 现在我们应该看看如何提取值或将其转换为另一个值。

  1. get()返回包含的值,如果Optional实例为空,则抛出NoSuchElementException

但是我们应该如何使用呢?

Car car = employee.getCar();
if (employee != null) {
  car = employee.getCar();
}
           

这是我们逃避NullPointerException的主要操作。 现在,使用Java 8 Optional ,我们可以编写如下代码:

Optional<Car> car = employee.getCar();
if (!car.isEmpty()) {
  Car car = car.get();
}
           

但是,您是否认为这是对讨厌的空检查的改进?

我曾经认为它是一种改进,因为它隐藏了空指针,但是后来,我觉得它会污染源代码。 但是我不反对使用从方法或包装变量中返回Optional作为类型的方法。 我将在以下各节中讨论其背后的原因。

让我们考虑以前的方法:

public String getInsuranceName(Employee employee) {
  return employee.getCar().getInsurance().getName();
}
           

这是一个非常干净的代码,但是NullPointerException潜伏在后面,这就是为什么我们需要合并几个空引用检查(我们之前已经看到过)的原因。

如果我们在设计一个好的API时合并了公共String Optional ,则可以通过更简洁的方式实现:

public String getInsuranceName(Optional<Employee> employee) {
  return employee.flatMap(Employee::getCar)
                 .flatMap(Car::getInsurance)
                 .map(Insurance::getName)
                 .orElse("UNKNOWN");
}
           

这是不是真的好又干净的方法? 我知道这会使一些对Java Streams API不满意的程序员感到困惑。 我强烈建议对Java 8 Streams有一个快速的了解,以了解Optional的优点。

另一个示例是如果人名以“ P”开头,则获得保险名称

public String getInsuranceName(Optional<Employee> employee) {
  return employee.filter(e-> e.getName().startsWith("P"))
                 .flatMap(Employee::getCar)
                 .flatMap(Car::getInsurance)
                 .map(Insurance::getName)
                 .orElse("UNKNOWN");
}
           

设计实践

现在,我想以一些不同的方式分享一些有关设计我们先前讨论的POJO的想法。

API设计实践1

public class Employee {
  private Optional<Car> car;

  public Optional<Car> getCar() {
    return car;
  }
}

public class Car {
  private Optional<Insurance> insurance;

  public Insurance getInsurance() {
    return insurance;
  }
}

public class Insurance {
  private String name;

  public String getName() {
    return name;
  }
}
           

在这里,我已声明成员变量为Optional类型。 根据我的观点,这也是非常用户友好的,并且此类的用户或消费者可以轻松理解此类的性质。 在这种情况下,员工的汽车是Optional的 ,也就是说,员工可能也可能没有汽车。

API设计实践2

public class Employee {
  private Car car;

  public Optional<Car> getCar() {
    return Optional.ofNullable(car);
  }
}

public class Car {
  private Insurance insurance;

  public Optional<Insurance> getInsurance() {
    return Optional.ofNullable(insurance);
  }
}

public class Insurance {
  private String name;

  public String getName() {
    return name;
  }
}
           

这也是非常直观的,但是缺乏清晰显示成员实例不存在的想法。 要了解任何系统,开发人员总是需要首先了解对象模型,而了解对象模型则需要我们了解领域对象。 在这种情况下,员工是拥有汽车的域对象,就像它对于员工是强制性的一样。 但实际上,员工可能会或可能不会有汽车。 我们可以在获取或检索其值( getCar() )时实现它,然后当该方法返回Optional时 ,我们可能会注意到其缺少包含值的可能性。

使用什么?

它完全取决于开发人员。 我个人更喜欢第一种方法,因为很明显在理解领域模型方面很明显,而第二种方法在序列化方面具有优势。 由于Optional不实现Serializable ,因此在我们的第一种方法中它不可序列化。 如果我们使用DTO,则可以使我们的实现适应第二种方法。

方法或构造函数参数中的可选

正如我之前提到的,“ 可选”在班级中清楚地表明了消费者应该做的事情。 因此,如果构造函数或方法接受Optional元素作为参数,则意味着该参数不是必需的。

另一方面,我们需要付出用Optional污染代码库的代价。 开发人员唯一要谨慎使用它。 我个人不希望在方法参数中使用Optional ,但如果需要,我们仍然可以将其包装在Optional实例中并对其执行必要的操作。

方法返回类型中的可选

Java语言架构师Brian Goetz还建议,如果有可能返回null,则在方法中返回Optional 。 我们已经在API设计规范2中看到了这一点。

从方法抛出异常或返回可选

多年来,Java开发人员遵循通常的方法抛出异常来表示方法调用中的错误情况。

public static InputStream getInputStream(final String path) {
        checkNotNull(path, "Path cannot be null");
        final URL url = fileSystem.getEntry(path);
        InputStream xmlStream;
        try {
            xmlStream = url.openStream();
            return xmlStream;
        } catch (final IOException ex) {
            throw new RuntimeException(ex);
        }
}
           

如果此方法的使用者遇到RuntimeException ,那是由于打开与指定URL的连接时出现问题。 另一方面,我们还可以通过以下方式使用Optional :

public static Optional<InputStream> getInputStream(final String path) {
        checkNotNull(path, "Path cannot be null");
        final URL url = fileSystem.getEntry(path);
        InputStream xmlStream;
        try {
            xmlStream = url.openStream();
            return Optional.of(xmlStream);
        } catch (final IOException ex) {
            return Optional.empty();
        }
}
           

我认为这很直观,因为它清楚地表明它返回了一个可能有值也可能没有值的Optional实例。 这就是为什么我倾向于从可能具有这种null遇到可能性的方法中返回Optional的原因。

私有方法中的可选返回类型

私有方法显然不是要理解或分析项目的任何重要部分。 因此,我认为我们仍然可以使用null检查来摆脱过多的Optional,但是如果您认为仍然可以以更简洁明了的方式使用该方法,则也可以返回Optional 。

为了更好地理解,我编写了一个示例,如下所示:

private void process(final String data) {
        try {
            final ItemList nList = doc.getChildNodes();

            for (int temp = 0; temp < nList.getLength(); temp++) {
                final Node nNode = nList.item(temp);
                final String key = nNode.getName();
                final String value = nNode.getValue();
                values.put(getAttribute(key).orElseThrow(IllegalArgumentException::new), value);
            }
        } catch (final Exception ex) {
            logger.error("{}", ex.getMessage(), ex);
        }
}

private Optional<Attribute> getAttribute(final String key) {
        return Arrays
                      .stream(Attribute.values())
                      .filter(x -> x.value()
                                    .filter(y -> y.equalsIgnoreCase(key))
                                    .isPresent())
                      .findFirst();
}

public static enum Attribute {

    A ("Sample1"),
    B ("Sample2"),
    C ("Sample3");
    
    private String value;
    
    private Attribute(String value) {
        this.value = value;
    }
    
    public Optional<String> value() {
        return Optional.ofNullable(value);
    }

}
           

我本可以以更常用的方式编写第二种方法:

private Attribute getAttribute(final String key) {
        for (final Attribute attribute : Attribute.values()) {
            Optional<String> value = attribute.value();
            if (value.isPresent() && value.get().equalsIgnoreCase(key)) {
                return attribute;
            }
        }
        throw new IllegalArgumentException();
}
           

私有方法中返回返回Collection或其任何子类型的可选返回类型

作为第一个示例,请考虑代码,您需要实现一种方法来从Java中的指定路径列出文件

public static List<String> listFiles(String file) {

    List<String> files;
    try {
        files = Files.list(Paths.get(path));
    } catch (IOException e) {
        files = Arrays.asList("Could not list");
    }

    return files;
}
           

我们可以实现更简洁的代码,如下所示:

public static List<String> listFiles(String path) {
        
    return Files.list(Paths.get(path))
                .filter(Files::isRegularFile)
                .collect(toList());
}
           

注意,简洁方法中的返回类型仍为List而不是Optional 。 最好遵循返回空列表的通常做法,而不是使用Optional 。

使用Optional的流方式更加简洁是非常有专利的。 可选的是实用程序数据容器,可帮助开发人员摆脱空引用。 此外,它确实提供了许多有用的方法来简化程序员的任务。 但是,如果开发人员不太了解Optional的主要用法,则Optional可能会被严重滥用,并可能污染代码库。 这就是为什么我强烈建议大家在Optional中使用面向流的方法,以帮助开发人员编写简洁且可维护的代码

翻译自: https://www.javacodegeeks.com/2017/07/java-8-optionals.html

java8根据某个id删选