zoukankan      html  css  js  c++  java
  • 基本工具

    使用和避免null

    Optional

      Guava用Optional<T>表示可能为null的T类型引用。一个Optional实例可能包含非null的引用(我们称之为引用存在),也可能什么也不包括(称之为引用缺失)。它从不说包含的是null值,而是用存在或缺失来表示。但Optional从不会包含null值引用。

    Optional<Integer> possible = Optional.of(5);
    possible.isPresent(); // returns true
    possible.get(); // returns 5

    Optional无意直接模拟其他编程环境中的”可选” or “可能”语义,但它们的确有相似之处。Optional最常用的一些操作被罗列如下:

    创建Optional实例(以下都是静态方法):

    Optional.of(T) 创建指定引用的Optional实例,若引用为null则快速失败
    Optional.absent() 创建引用缺失的Optional实例
    Optional.fromNullable(T) 创建指定引用的Optional实例,若引用为null则表示缺失

    用Optional实例查询引用(以下都是非静态方法):

    boolean isPresent() 如果Optional包含非null的引用(引用存在),返回true
    T get() 返回Optional所包含的引用,若引用缺失,则抛出java.lang.IllegalStateException
    T or(T) 返回Optional所包含的引用,若引用缺失,返回指定的值
    T orNull() 返回Optional所包含的引用,若引用缺失,返回null
    Set<T> asSet() 返回Optional所包含引用的单例不可变集,如果引用存在,返回一个只有单一元素的集合,如果引用缺失,返回一个空集合。

    前置条件

    前置条件:让方法调用的前置条件判断更简单。Guava在Preconditions类中提供了若干前置条件判断的实用方法。每个方法都有三个变种:

    • 没有额外参数:抛出的异常中没有错误消息;
    • 有一个Object对象作为额外参数:抛出的异常使用Object.toString() 作为错误消息;
    • 有一个String对象作为额外参数,并且有一组任意数量的附加Object对象:这个变种处理异常消息的方式有点类似printf,但考虑GWT的兼容性和效率,只支持%s指示符。例如:
      • checkArgument(i >= 0, "Argument was %s but expected nonnegative", i);
        checkArgument(i < j, "Expected i < j, but %s > %s", i, j);
    方法声明(不包括额外参数) 描述 检查失败时抛出的异常
    checkArgument(boolean) 检查boolean是否为true,用来检查传递给方法的参数。 IllegalArgumentException
    checkNotNull(T) 检查value是否为null,该方法直接返回value,因此可以内嵌使用checkNotNull NullPointerException
    checkState(boolean) 用来检查对象的某些状态。 IllegalStateException
    checkElementIndex(int index, int size) 检查index作为索引值对某个列表、字符串或数组是否有效。index>=0 && index<size * IndexOutOfBoundsException
    checkPositionIndex(int index, int size) 检查index作为位置值对某个列表、字符串或数组是否有效。index>=0 && index<=size * IndexOutOfBoundsException
    checkPositionIndexes(int start, int end, int size) 检查[start, end]表示的位置范围对某个列表、字符串或数组是否有效* IndexOutOfBoundsException

    常见Object方法

    equals

    当一个对象中的字段可以为null时,实现Object.equals方法会很痛苦,因为不得不分别对它们进行null检查。使用Objects.equal帮助你执行null敏感的equals判断,从而避免抛出NullPointerException。例如:

    Objects.equal("a", "a"); // returns true
    Objects.equal(null, "a"); // returns false
    Objects.equal("a", null); // returns false
    Objects.equal(null, null); // returns true

    注意:JDK7引入的Objects类提供了一样的方法Objects.equals

    hashCode

    用对象的所有字段作散列[hash]运算应当更简单。Guava的Objects.hashCode(Object...)会对传入的字段序列计算出合理的、顺序敏感的散列值。你可以使用Objects.hashCode(field1, field2, …, fieldn)来代替手动计算散列值

    注意:JDK7引入的Objects类提供了一样的方法Objects.hash(Object...)

    toString

    好的toString方法在调试时是无价之宝,但是编写toString方法有时候却很痛苦。使用 Objects.toStringHelper可以轻松编写有用的toString方法。例如:

    // Returns "ClassName{x=1}"
    Objects.toStringHelper(this).add("x", 1).toString();
    // Returns "MyObject{x=1}"
    Objects.toStringHelper("MyObject").add("x", 1).toString();

    compare/compareTo

    实现一个比较器[Comparator],或者直接实现Comparable接口有时也伤不起。考虑一下这种情况:

    class Person implements Comparable<Person> {
      private String lastName;
      private String firstName;
      private int zipCode;
    
      public int compareTo(Person other) {
        int cmp = lastName.compareTo(other.lastName);
        if (cmp != 0) {
          return cmp;
        }
        cmp = firstName.compareTo(other.firstName);
        if (cmp != 0) {
          return cmp;
        }
        return Integer.compare(zipCode, other.zipCode);
      }
    }

    这部分代码太琐碎了,因此很容易搞乱,也很难调试。我们应该能把这种代码变得更优雅,为此,Guava提供了ComparisonChain

    ComparisonChain执行一种懒比较:它执行比较操作直至发现非零的结果,在那之后的比较输入将被忽略。

    public int compareTo(Foo that) {
        return ComparisonChain.start()
                .compare(this.aString, that.aString)
                .compare(this.anInt, that.anInt)
                .compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
                .result();
    }

    Throwables:简化异常和错误的传播与检查

    有时候,你会想把捕获到的异常再次抛出。这种情况通常发生在Error或RuntimeException被捕获的时候,你没想捕获它们,但是声明捕获Throwable和Exception的时候,也包括了了Error或RuntimeException。Guava提供了若干方法,来判断异常类型并且重新传播异常。例如:

    try {
        someMethodThatCouldThrowAnything();
    } catch (IKnowWhatToDoWithThisException e) {
        handle(e);
    } catch (Throwable t) {
        Throwables.propagateIfInstanceOf(t, IOException.class);
        Throwables.propagateIfInstanceOf(t, SQLException.class);
        throw Throwables.propagate(t);
    }

    所有这些方法都会自己决定是否要抛出异常,但也能直接抛出方法返回的结果——例如,throw Throwables.propagate(t);—— 这样可以向编译器声明这里一定会抛出异常。Guava中的异常传播方法简要列举如下:

    RuntimeException   propagate(Throwable) 如果Throwable是Error或RuntimeException,直接抛出;否则把Throwable包装成RuntimeException抛出。返回类型是RuntimeException,所以你可以像上面说的那样写成throw Throwables.propagate(t),Java编译器会意识到这行代码保证抛出异常。
    void propagateIfInstanceOf( Throwable, Class<X extends   Exception>) throws X Throwable类型为X才抛出
    void propagateIfPossible( Throwable) Throwable类型为Error或RuntimeException才抛出
    void   propagateIfPossible( Throwable, Class<X extends Throwable>) throws X Throwable类型为X, Error或RuntimeException才抛出

    Throwables.propagate的用法

    模仿Java7的多重异常捕获和再抛出

    通常来说,如果调用者想让异常传播到栈顶,他不需要写任何catch代码块。因为他不打算从异常中恢复,他可能就不应该记录异常,或者有其他的动作。他可能是想做一些清理工作,但通常来说,无论操作是否成功,清理工作都要进行,所以清理工作可能会放在finallly代码块中。但有时候,捕获异常然后再抛出也是有用的:也许调用者想要在异常传播之前统计失败的次数,或者有条件地传播异常。

    当只对一种异常进行捕获和再抛出时,代码可能还是简单明了的。但当多种异常需要处理时,却可能变得一团糟:

    @Override public void run() {
        try {
            delegate.run();
        } catch (RuntimeException e) {
            failures.increment();
            throw e;
        }catch (Error e) {
            failures.increment();
            throw e;
        }
    }

    Java7用多重捕获解决了这个问题:

    } catch (RuntimeException | Error e) {
        failures.increment();
        throw e;
    }

    非Java7用户却受困于这个问题。他们想要写如下代码来统计所有异常,但是编译器不允许他们抛出Throwable(译者注:这种写法把原本是Error或RuntimeException类型的异常修改成了Throwable,因此调用者不得不修改方法签名):

    } catch (Throwable t) {
        failures.increment();
        throw t;
    }

    解决办法是用throw Throwables.propagate(t)替换throw t。在限定情况下(捕获Error和RuntimeException),Throwables.propagate和原始代码有相同行为。然而,用Throwables.propagate也很容易写出有其他隐藏行为的代码。尤其要注意的是,这个方案只适用于处理RuntimeException 或Error。如果catch块捕获了受检异常,你需要调用propagateIfInstanceOf来保留原始代码的行为,因为Throwables.propagate不能直接传播受检异常。

    总之,Throwables.propagate的这种用法也就马马虎虎,在Java7中就没必要这样做了。在其他Java版本中,它可以减少少量的代码重复,但简单地提取方法进行重构也能做到这一点。此外,使用propagate会意外地包装受检异常。

    非必要用法:把抛出的Throwable转为Exception

    有少数API,尤其是Java反射API和(以此为基础的)Junit,把方法声明成抛出Throwable。和这样的API交互太痛苦了,因为即使是最通用的API通常也只是声明抛出Exception。当确定代码会抛出Throwable,而不是Exception或Error时,调用者可能会用Throwables.propagate转化Throwable。这里有个用Callable执行Junit测试的范例:

    public Void call() throws Exception {
        try {
            FooTest.super.runTest();
        } catch (Throwable t) {
            Throwables.propagateIfPossible(t, Exception.class);
            Throwables.propagate(t);
        }
    
        return null;
    }

    在这儿没必要调用propagate()方法,因为propagateIfPossible传播了Throwable之外的所有异常类型,第二行的propagate就变得完全等价于throw new RuntimeException(t)。(题外话:这个例子也提醒我们,propagateIfPossible可能也会引起混乱,因为它不但会传播参数中给定的异常类型,还抛出Error和RuntimeException)

    这种模式(或类似于throw new RuntimeException(t)的模式)在Google代码库中出现了超过30次。(搜索’propagateIfPossible[^;]* Exception.class[)];’)绝大多数情况下都明确用了”throw new RuntimeException(t)”。我们也曾想过有个”throwWrappingWeirdThrowable”方法处理Throwable到Exception的转化。但考虑到我们用两行代码实现了这个模式,除非我们也丢弃propagateIfPossible方法,不然定义这个throwWrappingWeirdThrowable方法也并没有太大必要。

    Throwables.propagate的有争议用法

    争议一:把受检异常转化为非受检异常

    原则上,非受检异常代表bug,而受检异常表示不可控的问题。但在实际运用中,即使JDK也有所误用——如Object.clone()、Integer. parseInt(String)、URI(String)——或者至少对某些方法来说,没有让每个人都信服的答案,如URI.create(String)的异常声明。

    因此,调用者有时不得不把受检异常和非受检异常做相互转化:

    try {
        return publicInterfaceMethod.invoke();
    } catch (IllegalAccessException e) {
        throw new AssertionError(e);
    }

    有时候,调用者会使用Throwables.propagate转化异常。这样做有没有什么缺点?最主要的恐怕是代码的含义不太明显。throw Throwables.propagate(ioException)做了什么?throw new RuntimeException(ioException)做了什么?这两者做了同样的事情,但后者的意思更简单直接。前者却引起了疑问:”它做了什么?它并不只是把异常包装进RuntimeException吧?如果它真的只做了包装,为什么还非得要写个方法?”。应该承认,这些问题部分是因为”propagate”的语义太模糊了(用来抛出未声明的异常吗?)。也许”wrapIfChecked”更能清楚地表达含义。但即使方法叫做”wrapIfChecked”,用它来包装一个已知类型的受检异常也没什么优点。甚至会有其他缺点:也许比起RuntimeException,还有更合适的类型——如IllegalArgumentException。
    我们有时也会看到propagate被用于传播可能为受检的异常,结果是代码相比以前会稍微简短点,但也稍微有点不清晰:

    } catch (RuntimeException e) {
        throw e;
    }catch (Exception e) {
        throw new RuntimeException(e);
    }

    然而,我们似乎故意忽略了把检查型异常转化为非检查型异常的合理性。在某些场景中,这无疑是正确的做法,但更多时候它被用于避免处理受检异常。这让我们的话题变成了争论受检异常是不是坏主意了,我不想对此多做叙述。但可以这样说,Throwables.propagate不是为了鼓励开发者忽略IOException这样的异常。

    争议二:异常穿隧

    但是,如果你要实现不允许抛出异常的方法呢?有时候你需要把异常包装在非受检异常内。这种做法挺好,但我们再次强调,没必要用propagate方法做这种简单的包装。实际上,手动包装可能更好:如果你手动包装了所有异常(而不仅仅是受检异常),那你就可以在另一端解包所有异常,并处理极少数特殊场景。此外,你可能还想把异常包装成特定的类型,而不是像propagate这样统一包装成RuntimeException。

    争议三:重新抛出其他线程产生的异常

    try {
        return future.get();
    } catch (ExecutionException e) {
        throw Throwables.propagate(e.getCause());
    }

    对这样的代码要考虑很多方面:

    • ExecutionException的cause可能是受检异常,见上文”争议一:把检查型异常转化为非检查型异常”。但如果我们确定future对应的任务不会抛出受检异常呢?(可能future表示runnable任务的结果——译者注:如ExecutorService中的submit(Runnable task, T
      result)方法
      )如上所述,你可以捕获异常并抛出AssertionError。尤其对于Future,请考虑 Futures.get方法。(TODO:对future.get()抛出的另一个异常InterruptedException作一些说明)
    • ExecutionException的cause可能直接是Throwable类型,而不是Exception或Error。(实际上这不大可能,但你想直接重新抛出cause的话,编译器会强迫你考虑这种可能性)见上文”用法二:把抛出Throwable改为抛出Exception”。
    • ExecutionException的cause可能是非受检异常。如果是这样的话,cause会直接被Throwables.propagate抛出。不幸的是,cause的堆栈信息反映的是异常最初产生的线程,而不是传播异常的线程。通常来说,最好在异常链中同时包含这两个线程的堆栈信息,就像ExecutionException所做的那样。(这个问题并不单单和propagate方法相关;所有在其他线程中重新抛出异常的代码都需要考虑这点)

    异常原因链

    Guava提供了如下三个有用的方法,让研究异常的原因链变得稍微简便了,这三个方法的签名是不言自明的:

    Throwable   getRootCause(Throwable)
    List<Throwable>   getCausalChain(Throwable)
    String   getStackTraceAsString(Throwable)
  • 相关阅读:
    HDU 5835 Danganronpa 贪心
    HDU 5842 Lweb and String 水题
    HDU 5832 A water problem 水题
    Codeforces Beta Round #14 (Div. 2) A. Letter 水题
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem K. UTF-8 Decoder 模拟题
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem I. Alien Rectangles 数学
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem H. Parallel Worlds 计算几何
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem F. Turning Grille 暴力
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem C. Cargo Transportation 暴力
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem G. k-palindrome dp
  • 原文地址:https://www.cnblogs.com/hzzjj/p/10627943.html
Copyright © 2011-2022 走看看