Java 8: Bagaimana cara saya bekerja dengan pengecualian melempar metode dalam aliran?

172

Misalkan saya memiliki kelas dan metode

class A {
  void foo() throws Exception() {
    ...
  }
}

Sekarang saya ingin memanggil foo untuk setiap contoh yang Adikirimkan oleh aliran seperti:

void bar() throws Exception {
  Stream<A> as = ...
  as.forEach(a -> a.foo());
}

Pertanyaan: Bagaimana cara saya menangani pengecualian dengan benar? Kode tidak dikompilasi di mesin saya karena saya tidak menangani kemungkinan pengecualian yang dapat dilempar oleh foo (). The throws Exceptionof bartampaknya tidak berguna di sini. Mengapa demikian?

Bastian
sumber

Jawaban:

141

Anda harus memasukkan panggilan metode Anda ke panggilan lain, di mana Anda tidak membuang pengecualian yang dicentang . Anda masih dapat membuang apa pun yang merupakan subkelas dari RuntimeException.

Idiom pembungkus yang normal adalah sesuatu seperti:

private void safeFoo(final A a) {
    try {
        a.foo();
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
}

(Supertype pengecualian Exceptionyang hanya digunakan sebagai contoh, tidak pernah mencoba untuk menangkapnya sendiri)

Kemudian Anda bisa menyebutnya dengan: as.forEach(this::safeFoo).

skiwi
sumber
1
Jika Anda ingin menjadi metode pembungkus saya akan menyatakannya statis. Itu tidak menggunakan apa pun dari 'ini'.
aalku
207
Sangat menyedihkan kita harus melakukan ini alih-alih menggunakan pengecualian asli kita .... oh Jawa, itu memberi dan kemudian mengambil
Erich
1
@Stephan Jawaban itu telah dihapus, tetapi masih tersedia di sini: stackoverflow.com/a/27661562/309308
Michael Mrozek
3
Itu akan langsung gagal tinjauan kode di perusahaan saya: kami tidak diizinkan untuk melemparkan pengecualian yang tidak dicentang.
Stelios Adamantidis
7
@SteliosAdamantidis aturan itu sangat membatasi dan kontra produktif. apakah Anda sadar bahwa semua metode, di semua api, diizinkan untuk membuang semua rasa pengecualian runtime tanpa Anda bahkan harus mengetahuinya (tentu saja Anda)? apakah Anda melarang javascript karena tidak menerapkan konsep pengecualian yang diperiksa? Jika saya adalah pengembang utama Anda, saya akan melarang pengecualian yang diperiksa.
spi
35

Jika semua yang Anda inginkan adalah memanggil foo, dan Anda lebih memilih untuk menyebarkan pengecualian seperti apa adanya (tanpa membungkus), Anda juga dapat menggunakan forloop Java saja (setelah mengubah Stream menjadi Iterable dengan beberapa tipu daya ):

for (A a : (Iterable<A>) as::iterator) {
   a.foo();
}

Ini, setidaknya, apa yang saya lakukan dalam tes JUnit saya, di mana saya tidak ingin melalui masalah membungkus pengecualian diperiksa (dan sebenarnya lebih suka tes saya untuk membuang yang asli yang terbuka)

avandeursen
sumber
16

Pertanyaan ini mungkin agak lama, tetapi karena saya pikir jawaban "benar" di sini hanya satu cara yang dapat menyebabkan beberapa masalah tersembunyi di kode Anda nanti. Bahkan jika ada sedikit Kontroversi , Pengecualian Diperiksa ada karena suatu alasan.

Cara paling elegan menurut saya dapat Anda temukan diberikan oleh Misha di sini Pengecualian runtime agregat di Java 8 stream dengan hanya melakukan tindakan dalam "masa depan". Jadi, Anda dapat menjalankan semua bagian yang bekerja dan mengumpulkan Pengecualian yang tidak berfungsi sebagai satu bagian. Kalau tidak, Anda bisa mengumpulkan semuanya dalam Daftar dan memprosesnya nanti.

Pendekatan serupa datang dari Benji Weber . Dia menyarankan untuk membuat tipe sendiri untuk mengumpulkan bagian yang bekerja dan tidak.

Bergantung pada apa yang Anda benar-benar ingin capai, pemetaan sederhana antara nilai input dan Nilai Output yang terjadi Pengecualian juga berlaku untuk Anda.

Jika Anda tidak menyukai cara-cara ini, pertimbangkan untuk menggunakan (tergantung pada Pengecualian Asli), setidaknya pengecualian sendiri.

Mariano
sumber
3
Ini harus ditandai sebagai jawaban yang benar. +. Tapi saya tidak bisa mengatakan itu posting yang bagus. Anda harus menguraikannya secara mendalam. Bagaimana pengecualian itu sendiri bisa membantu? Apa saja pengecualian yang terjadi? Mengapa Anda tidak membawa varian yang berbeda di sini? Memiliki semua kode contoh dalam referensi dianggap gaya posting yang bahkan dilarang di sini di SO. Teks Anda lebih terlihat seperti kumpulan komentar.
Gangnus
2
Anda harus dapat memilih pada level mana Anda ingin menangkap mereka dan mengalirkan kekacauan dengan itu. Stream API harus membiarkan Anda membawa pengecualian sampai operasi terakhir (seperti kumpulkan) dan ditangani di sana dengan handler atau dilemparkan sebaliknya. stream.map(Streams.passException(x->mightThrowException(x))).catch(e->whatToDo(e)).collect(...). Itu sholud mengharapkan pengecualian dan memungkinkan Anda untuk menanganinya seperti masa depan.
aalku
10

Saya sarankan untuk menggunakan kelas Google Guava Throwables

merambat ( Throwable throwable)

Propagate yang dapat dilempar apa adanya adalah jika itu adalah turunan dari RuntimeException atau Error, atau sebagai upaya terakhir, membungkusnya dalam RuntimeException dan kemudian menyebar. **

void bar() {
    Stream<A> as = ...
    as.forEach(a -> {
        try {
            a.foo()
        } catch(Exception e) {
            throw Throwables.propagate(e);
        }
    });
}

MEMPERBARUI:

Sekarang sudah tidak digunakan lagi:

void bar() {
    Stream<A> as = ...
    as.forEach(a -> {
        try {
            a.foo()
        } catch(Exception e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    });
}
yanefedor
sumber
4
Metode ini sudah usang (sayangnya).
Robert Važan
9

Anda dapat membungkus dan membuka bukaan pengecualian dengan cara ini.

class A {
    void foo() throws Exception {
        throw new Exception();
    }
};

interface Task {
    void run() throws Exception;
}

static class TaskException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    public TaskException(Exception e) {
        super(e);
    }
}

void bar() throws Exception {
      Stream<A> as = Stream.generate(()->new A());
      try {
        as.forEach(a -> wrapException(() -> a.foo())); // or a::foo instead of () -> a.foo()
    } catch (TaskException e) {
        throw (Exception)e.getCause();
    }
}

static void wrapException(Task task) {
    try {
        task.run();
    } catch (Exception e) {
        throw new TaskException(e);
    }
}
aalku
sumber
9

Anda mungkin ingin melakukan salah satu dari yang berikut:

  • memperbanyak pengecualian yang diperiksa,
  • bungkus dan sebarkan pengecualian yang tidak dicentang, atau
  • tangkap pengecualian dan hentikan propagasi.

Beberapa perpustakaan memungkinkan Anda melakukannya dengan mudah. Contoh di bawah ini ditulis menggunakan pustaka NoException saya .

// Propagate checked exception
as.forEach(Exceptions.sneak().consumer(A::foo));

// Wrap and propagate unchecked exception
as.forEach(Exceptions.wrap().consumer(A::foo));
as.forEach(Exceptions.wrap(MyUncheckedException::new).consumer(A::foo));

// Catch the exception and stop propagation (using logging handler for example)
as.forEach(Exceptions.log().consumer(Exceptions.sneak().consumer(A::foo)));
Robert Važan
sumber
Kerja bagus di perpustakaan! Saya akan menulis sesuatu yang serupa.
Jasper de Vries
2

Cara yang lebih mudah dibaca:

class A {
  void foo() throws MyException() {
    ...
  }
}

Cukup sembunyikan di RuntimeExceptionuntuk melewatinyaforEach()

  void bar() throws MyException {
      Stream<A> as = ...
      try {
          as.forEach(a -> {
              try {
                  a.foo();
              } catch(MyException e) {
                  throw new RuntimeException(e);
              }
          });
      } catch(RuntimeException e) {
          throw (MyException) e.getCause();
      }
  }

Meskipun pada titik ini saya tidak akan menahan seseorang jika mereka mengatakan melewatkan aliran dan pergi dengan for for loop, kecuali:

  • Anda tidak membuat aliran menggunakan Collection.stream(), yaitu terjemahan tidak langsung ke for for loop.
  • Anda mencoba menggunakannya parallelstream()
Kashyap
sumber