资讯详情

Java 编程问题:二、对象、不变性和`switch`表达式

原文:Java Coding Problems

协议:CC BY-NC-SA 4.0

贡献者:飞龙

本文来自【ApacheCN Java 谷歌翻译用谷歌翻译。

本章包括 18 参与对象、不变性和switch表达式问题。本章从处理开始null引用的几个问题。继续处理相关检查索引,equals()hashCode()以及不变性(例如,编写不变性和从不变性传输/返回可变对象)。本章的最后一部分讨论了克隆对象和 JDK12switch表达式。本章结束时,您将掌握对象和不变性的基本知识。此外,你将知道如何处理新的switch表达式。在任何地方 Java 在开发人员的武库中,这些都是有价值的、不可选的知识。

问题

使用以下问题来测试你的对象、不变性和switch表达式编程能力。在转向解决方案和下载示例程序之前,我强烈建议您尝试每个问题:

  1. 使用代码检查null函数引用:编写程序,引用给定的函数和命令代码null检查。
  2. 检查null引用并抛出定制NullPointerException错误:编写程序并执行给定的参考null检查并抛出带有定制信息的定制信息NullPointerException
  3. 检查null引用并抛出指定的异常(例如,IllegalArgumentException:编写一个程序,对给定的引用执行null检查并抛出指定异常。
  4. 检查null引用并返回非null默认引用:编写程序,对给定引用执行null检查,如果是非null,返回;否则返回非null默认引用。
  5. 检查从 0 索引在长度范围内:编写程序,检查给定索引是否在 0(含)在给定长度(不含)之间。如果给定索引超过 0 在给定长度范围内,抛出IndexOutOfBoundsException
  6. 检查从 0 长度范围内的子范围:编写程序,检查给定的子范围从开始到结束,是否在 0 在给定长度范围内。若给定子范围不在范围内,则抛出IndexOutOfBoundsException
  7. 解释equals()hashCode()并举例说明equals()hashCode()方法在 Java 如何工作。
  8. 不可变对象概述:解释和举例说明什么 Java 不可变变对象。
  9. 不可变字符串:解释String类别不可变的原因。
  10. 编写不可变类:编写表示不可变类的程序。
  11. 将可变对象传递给不可变类或从不可变类返回:编写程序,将可变对象传递给不可变类或从不可变类返回。
  12. 通过构建器模式编写不可变类:编写构建器模式实现的程序。. 避免不可变对象中的坏数据:编写防止不可变对象中的坏数据坏数据的程序。
  13. 克隆对象:编写程序,演示浅层和深层克隆技术。
  14. 覆盖toString():解释并举例说明覆盖toString()的实践。
  15. switch表达式:简要概述 JDK12 中的switch表达式。
  16. 多个case标签:写一段代码,使用多个代码case标签举例说明 JDK12switch
  17. 句子块:编写代码作为例子 JDK12 switch,其中的case标签指向花括号块。

以下部分介绍了上述每个问题的解决方案。记住,通常没有正确的方法来解决特定的问题。此外,请记住,这里显示的解释只包括解决问题所需的最有趣和最重要的细节。下载示例解决方案,查看更多详细信息,并尝试程序。

40 检查函数和命令代码中的空引

检查函数样式或命令代码null引用是减少名称的常用推荐技术NullPointerException异常发生。该检查广泛应用于方法参数,以确保引用不会导致传输NullPointerException或意外行为。

例如,将List<Integer>至少需要两种传递方法null检查。首先,该方法应确保列表引用本身不是null。其次,根据列表的使用方法,应确保列表不包括在内null对象:

List<Integer> numbers    = Arrays.asList(1, 2, null, 4, null, 16, 7, null); 

该列表将传递给以下方法:

public static List<Integer> evenIntegers(List<Integer> integers) { 
            if (integers == null) { 
          return Collections.EMPTY_LIST; } List<Integer> evens = new ArrayList<>(); for (Integer nr: integers) { 
          if (nr != null && nr % 2 == 0) { 
          evens.add(nr); } } return evens; } 

注意,前面的代码使用依赖于==!=运算符(integers==nullnr !=null的经典检查。从 JDK8 开始,java.util.Objects类包含两个方法,它们基于这两个操作符包装null检查:object == null包装在Objects.isNull()中,object != null包装在Objects.nonNull()中。

基于这些方法,前面的代码可以重写如下:

public static List<Integer> evenIntegers(List<Integer> integers) { 
        

  if (Objects.isNull(integers)) { 
        
    return Collections.EMPTY_LIST;
  }

  List<Integer> evens = new ArrayList<>();

  for (Integer nr: integers) { 
        
    if (Objects.nonNull(nr) && nr % 2 == 0) { 
        
      evens.add(nr);
    }
  }

  return evens;
}

现在,代码在某种程度上更具表现力,但这并不是这两种方法的主要用法。实际上,这两个方法是为了另一个目的(符合 API 注解)而添加的——在 Java8 函数式代码中用作谓词。在函数式代码中,null检查可以如下例所示完成:

public static int sumIntegers(List<Integer> integers) { 
        

  if (integers == null) { 
        
    throw new IllegalArgumentException("List cannot be null");
  }

  return integers.stream()
    .filter(i -> i != null)
    .mapToInt(Integer::intValue).sum();
}

public static boolean integersContainsNulls(List<Integer> integers) { 
        

  if (integers == null) { 
        
    return false;
  }

  return integers.stream()
    .anyMatch(i -> i == null);
}

很明显,i -> i != nulli -> i == null的表达方式与周围的代码不一样。让我们用Objects.nonNull()Objects.isNull()替换这些代码片段:

public static int sumIntegers(List<Integer> integers) { 
        

  if (integers == null) { 
        
    throw new IllegalArgumentException("List cannot be null");
  }

  return integers.stream()
    .filter(Objects::nonNull)
    .mapToInt(Integer::intValue).sum();
}

public static boolean integersContainsNulls(List<Integer> integers) { 
        

  if (integers == null) { 
        
    return false;
  }

  return integers.stream()
    .anyMatch(Objects::isNull);
}

或者,我们也可以使用Objects.nonNull()Objects.isNull()方法作为参数:

public static int sumIntegers(List<Integer> integers) { 
        

  if (Objects.isNull(integers)) { 
        
    throw new IllegalArgumentException("List cannot be null");
  }

  return integers.stream()
    .filter(Objects::nonNull)
    .mapToInt(Integer::intValue).sum();
}

public static boolean integersContainsNulls(List<Integer> integers) { 
        

  if (Objects.isNull(integers)) { 
        
    return false;
  }

  return integers.stream()
    .anyMatch(Objects::isNull);
}

令人惊叹的!因此,作为结论,无论何时需要进行null检查,函数式代码都应该依赖于这两种方法,而在命令式代码中,这是一种偏好。

41 检查空引用并引发自定义NullPointerException

检查null引用并用定制消息抛出NullPointerException可以使用以下代码完成(此代码执行这四次,在构造器中执行两次,在assignDriver()方法中执行两次):

public class Car { 
        

  private final String name;
  private final Color color;

  public Car(String name, Color color) { 
        

    if (name == null) { 
        
      throw new NullPointerException("Car name cannot be null");
    }

    if (color == null) { 
        
      throw new NullPointerException("Car color cannot be null");
    }

    this.name = name;
    this.color = color;
  }

  public void assignDriver(String license, Point location) { 
        

    if (license == null) { 
        
      throw new NullPointerException("License cannot be null");
    }

    if (location == null) { 
        
      throw new NullPointerException("Location cannot be null");
    }
  }
}

因此,这段代码通过结合==操作符和NullPointerException类的手动实例化来解决这个问题。从 JDK7 开始,这种代码组合隐藏在一个名为Objects.requireNonNull()static方法中。通过这种方法,前面的代码可以用表达的方式重写:

public class Car { 
        

  private final String name;
  private final Color color;

  public Car(String name, Color color) { 
        

    this.name = Objects.requireNonNull(name, "Car name cannot be 
      null");
    this.color = Objects.requireNonNull(color, "Car color cannot be 
      null");
  }

  public void assignDriver(String license, Point location) { 
        

    Objects.requireNonNull(license, "License cannot be null");
    Objects.requireNonNull(location, "Location cannot be null");
  }
}

因此,如果指定的引用是null,那么Objects.requireNonNull()将抛出一个包含所提供消息的NullPointerException。否则,它将返回选中的引用。

在构造器中,当提供的引用是null时,有一种典型的抛出NullPointerException的方法。但在方法上(例如,assignDriver()),这是一个有争议的方法。一些开发人员更喜欢返回一个无害的结果或者抛出IllegalArgumentException。下一个问题,检查空引用并抛出指定的异常(例如,IllegalArgumentException),解决了IllegalArgumentException方法。

在 JDK7 中,有两个Objects.requireNonNull()方法,一个是以前使用的,另一个是抛出带有默认消息的NullPointerException,如下例所示:

this.name = Objects.requireNonNull(name);

从 JDK8 开始,还有一个Objects.requireNonNull()。这个将NullPointerException的自定义消息封装在Supplier中。这意味着消息创建被推迟,直到给定的引用是null(这意味着使用+操作符连接消息的各个部分不再是一个问题)。

举个例子:

this.name = Objects.requireNonNull(name, () 
  -> "Car name cannot be null ... Consider one from " + carsList);

如果此引用不是null,则不创建消息。

42 检查空引用并引发指定的异常

当然,一种解决方案需要直接依赖于==操作符,如下所示:

if (name == null) { 
        
  throw new IllegalArgumentException("Name cannot be null");
}

因为没有requireNonNullElseThrow()方法,所以这个问题不能用java.util.Objects的方法来解决。抛出IllegalArgumentException或其他指定的异常可能需要一组方法,如下面的屏幕截图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ure4cICg-1657077359609)(img/fcd54997-7ebf-46d7-8eea-c80bb23be82a.png)]

让我们关注一下requireNonNullElseThrowIAE()方法。这两个方法抛出IllegalArgumentException,其中一个自定义消息被指定为StringSupplier(在null被求值为true之前避免创建):

public static <T> T requireNonNullElseThrowIAE(
    T obj, String message) { 
        

  if (obj == null) { 
        
    throw new IllegalArgumentException(message);
  }

  return obj;
}

public static <T> T requireNonNullElseThrowIAE(T obj,
    Supplier<String> messageSupplier) { 
        

  if (obj == null) { 
        
    throw new IllegalArgumentException(messageSupplier == null 
      ? null : messageSupplier.get());
  }

  return obj;
}

所以,投掷IllegalArgumentException可以通过这两种方法来完成。但这还不够。例如,代码可能需要抛出IllegalStateExceptionUnsupportedOperationException等。对于这种情况,最好采用以下方法:

public static <T, X extends Throwable> T requireNonNullElseThrow(
    T obj, X exception) throws X { 
        

  if (obj == null) { 
        
    throw exception;
  }

  return obj;
}

public static <T, X extends Throwable> T requireNotNullElseThrow(
    T obj, Supplier<<? extends X> exceptionSupplier) throws X { 
        

  if (obj != null) { 
        
    return obj;
  } else { 
        
    throw exceptionSupplier.get();
  }
}

考虑将这些方法添加到名为MyObjects的助手类中。如以下示例所示调用这些方法:

public Car(String name, Color color) { 
        

  this.name = MyObjects.requireNonNullElseThrow(name,
    new UnsupportedOperationException("Name cannot be set as null"));
  this.color = MyObjects.requireNotNullElseThrow(color, () ->
    new UnsupportedOperationException("Color cannot be set as null"));
}

此外,我们也可以通过这些例子来丰富MyObjects中的其他异常。

43 检查空引用并返回非空默认引用

通过if-else(或三元运算符)可以很容易地提供该问题的解决方案,如以下示例所示(作为变体,namecolor可以声明为非final,并在声明时用默认值初始化):

public class Car { 
        

  private final String name;
  private final Color color;
  public Car(String name, Color color) { 
        

    if (name == null) { 
        
      this.name = "No name";
    } else { 
        
      this.name = name;
    }

    if (color == null) { 
        
      this.color = new Color(0, 0, 0);
    } else { 
        
      this.color = color;
    }
  }
}

但是,从 JDK9 开始,前面的代码可以通过Objects类的两个方法简化。这些方法是requireNonNullElse()requireNonNullElseGet()。它们都有两个参数,一个是检查空值的引用,另一个是在检查的引用为null时返回的非null默认引用:

public class Car { 
        

  private final String name;
  private final Color color;

  public Car(String name, Color color) { 
        

    this.name = Objects.requireNonNullElse(name, "No name");
    this.color = Objects.requireNonNullElseGet(color,
      () -> new Color(0, 0, 0));
  }
}

在前面的示例中,这些方法在构造器中使用,但也可以在方法中使用。

44 检查从 0 到长度范围内的索引

首先,让我们用一个简单的场景来突出这个问题。此场景可能在以下简单类中实现:

public class Function { 
        

  private final int x;

  public Function(int x) { 
        

    this.x = x;
  }

  public int xMinusY(int y) { 
        

    return x - y;
  }

  public static int oneMinusY(int y) { 
        

    return 1 - y;
  }
}

注意,前面的代码片段没有对xy进行任何范围限制。现在,让我们施加以下范围(这在数学函数中非常常见):

  • x必须介于 0(含)和 11(不含)之间,所以x属于[0, 11)
  • xMinusY()方法中,y必须在 0(含)x(不含)之间,所以y属于[0, x)
  • oneMinusY()方法中,y必须介于 0(包含)和 16(排除)之间,所以y属于[0, 16)

这些范围可以通过if语句在代码中施加,如下所示:

public class Function { 
        

  private static final int X_UPPER_BOUND = 11;
  private static final int Y_UPPER_BOUND = 16;
  private final int x;

  public Function(int x) { 
        

    if (x < 0 || x >= X_UPPER_BOUND) { 
        
      throw new IndexOutOfBoundsException("..."); 
    }

    this.x = x;
  }

  public int xMinusY(int y) { 
        

    if (y < 0 || y >= x) { 
        
      throw new IndexOutOfBoundsException("...");
    }

    return x - y;
  }

  public static int oneMinusY(int y) { 
        

    if (y < 0 || y >= Y_UPPER_BOUND) { 
        
      throw new IndexOutOfBoundsException("...");
    }

    return 1 - y;
  }
}

考虑用更有意义的异常替换IndexOutOfBoundsException(例如,扩展IndexOutOfBoundsException并创建一个类型为RangeOutOfBoundsException的自定义异常)。

从 JDK9 开始,可以重写代码以使用Objects.checkIndex()方法。此方法验证给定索引是否在 0 到长度的范围内,并返回该范围内的给定索引或抛出IndexOutOfBoundsException

public class Function { 
        

  private static final int X_UPPER_BOUND = 11;
  private static final int Y_UPPER_BOUND = 16;
  private final int x;

  public Function(int x) { 
        

    this.x = Objects.checkIndex(x, X_UPPER_BOUND);
  }

  public int xMinusY(int y) { 
        

    Objects.checkIndex(y, x);

    return x - y;
  }

  public static int oneMinusY(int y) { 
        

    Objects.checkIndex(y, Y_UPPER_BOUND);

    return 1 - y;
  

标签: df37nc连接器ba附带连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台