Что вы понимаете под цепочками исключений в Java?

Связывание исключений или обертка исключений — это метод объектно-ориентированного программирования для обработки исключений. Цепочка исключений возникает, когда одно исключение вызывает другое исключение. сцепленное исключение — это исключение, вызванное другим исключением. Сцепленные исключения связаны таким образом, что предыдущее исключение вызывает каждое исключение в цепочке.

· Класс Throwable
· Использование цепочек исключений
· Почему цепочечные исключения?
· Трудности с цепочками исключений
· Программа

Метательный класс

Конструкторы класса Throwable, которые поддерживают связанные исключения в java:

  1. Throwable (выбрасываемая причина): где причина — это исключение, которое вызывает текущее исключение.
  2. Throwable(String msg, Throwable Reason): — Где msg — это сообщение об исключении, а причина — это исключение, которое вызывает текущее исключение.

Методы класса Throwable, которые поддерживают связанные исключения в java:

  1. Метод getCause(): этот метод возвращает фактическую причину исключения.
  2. Метод initCause(Throwable Cause): этот метод устанавливает причину вызывающего исключения.

Использование связанных исключений

Часто вы хотите поймать одно исключение и сгенерировать другое, но при этом сохранить информацию об исходном исключении — это называется цепочкой исключений. Исходное исключение является причиной второго исключения. Таким образом, цепочка исключений позволяет связать одно исключение с другим исключением, т. е. одно исключение описывает причину другого исключения.

До JDK 1.4 программистам приходилось писать собственный код, чтобы сохранить исходную информацию об исключении, но теперь все подклассы Throwable имеют возможность принимать объект причины в своем конструкторе. Предполагается, что причиной является исходное исключение, и, передавая его, вы поддерживаете трассировку стека обратно к его источнику, даже если вы создаете и выдаете новое исключение.

Цепочка исключений также называется обёрткой исключений, потому что мы делаем это, повторно выбрасывая перехваченное исключение после его включения в новое исключение. Исходное исключение сохраняется как свойство (например, причина) нового исключения.

Практика

  try {
     // creating an exception
     NullPointerException e = new NullPointerException("Actual cause");
     // wrapping the original exception in a new exception
     ArithmeticException exc = new ArithmeticException("Apearent cause",e);
     // throwing the exception
     throw exc;
  } catch(ArithmeticException e) {
     // display top level exception (aprearent cause)
     System.out.println("Caught: " + e);
     // Getting the actual cause of the exception
     System.out.println("Original cause: " + e.getCause());
  }
}

Вывод

Caught: java.lang.ArithmeticException: Apearent cause
Original cause: java.lang.NullPointerException: Actual cause

Таким образом, связанное Exception помогает определить ситуацию, в которой одно исключение вызывает другое Exception в приложении, а также может помочь отладить и отследить корень причина ошибки.

Например, рассмотрим метод, который генерирует ArithmeticException из-за попытки деления на ноль, но фактической причиной исключения была ошибка ввода-вывода что приводит к тому, что делитель равен нулю. Метод выдает ArithmeticException вызывающей стороне. Вызывающий не будет знать фактическую причину Exception. Цепочка Exception используется в таких ситуациях.

Интересно отметить, что единственными подклассами Throwable, которые предоставляют аргумент причины в конструкторе, являются три основных класса исключений Error (используемые JVM для сообщения о системных ошибках), Исключение и Исключение времени выполнения. Если вы хотите связать любые другие типы исключений, вы делаете это с помощью метода initCause(), а не конструктора.

Пример

Вот пример, иллюстрирующий механику обработки связанных исключений:

// Demonstrate exception chaining.
class ChainExcDemo {
     static void demoproc() {
         // create an exception
         NullPointerException e = new NullPointerException("top layer");
         // add a cause
         e.initCause(new ArithmeticException("cause"));
         throw e;
     }
public static void main(String args[]) {
        try {
             demoproc();
         } catch (NullPointerException e) {
             // display top level exception
               System.out.println("Caught: " + e);
              // display cause exception
              System.out.println("Original cause: " + e.getCause());
         }
    }
}

Выход:

Caught: java.lang.NullPointerException: top layer
Original cause: java.lang.ArithmeticException: cause

В этом примере исключение верхнего уровня — NullPointerException. К нему добавлено исключение причины, ArithmeticException. Когда исключение генерируется из метода demoproc(), оно перехватывается main(). Там отображается исключение верхнего уровня, за которым следует базовое исключение, которое получается путем вызова getCause().

Сцепленные исключения могут быть реализованы на любой необходимой глубине. Таким образом, исключение причины может само по себе иметь причину. Имейте в виду, что слишком длинные цепочки исключений могут указывать на плохой дизайн.

Связанные исключения — это не то, что нужно каждой программе. Однако в тех случаях, когда полезно знать основную причину, они предлагают элегантное решение.

Почему цепные исключения?

Цепочка исключений позволяет сопоставить один тип исключения с другим, чтобы метод мог генерировать исключения, определенные на том же уровне абстракции, что и сам метод, не отбрасывая важную отладочную информацию.

То есть, если у вас есть метод, который загружает какой-либо объект из базы данных, вам может понадобиться какое-то ResourceLoadException (более близкое к уровню абстракции методов) вместо низкоуровневого SQLException. strong> даже если это был первоисточник проблемы. Однако, если вы просто поймаете SQLException и вместо этого сгенерируете ResourceLoadException, вы можете потерять важную отладочную информацию.

Таким образом, цепочка исключений является хорошей альтернативой. Вы генерируете исключение «высокого уровня», хорошо подходящее для конкретного метода, но связываете его с исключением, вызвавшим его.

Нам нужно связать исключения, чтобы сделать журналы читабельными. Напишем два примера. Во-первых, без цепочки исключений, а во-вторых, со связанными исключениями. Позже мы сравним поведение журналов в обоих случаях.

Для начала создадим серию исключений:

class NoLeaveGrantedException extends Exception {

    public NoLeaveGrantedException(String message, Throwable cause) {
        super(message, cause);
    }

    public NoLeaveGrantedException(String message) {
        super(message);
    }
}

class TeamLeadUpsetException extends Exception {
    // Both Constructors
}

Без цепочки

Давайте напишем пример программы без цепочки наших пользовательских исключений.

public class MainClass {

    public void main(String[] args) throws Exception {
        getLeave();
    }

    void getLeave() throws NoLeaveGrantedException {
        try {
            howIsTeamLead();
        } catch (TeamLeadUpsetException e) {
            e.printStackTrace();
            throw new NoLeaveGrantedException("Leave not sanctioned.");
        }
    }

    void howIsTeamLead() throws TeamLeadUpsetException {
        throw new TeamLeadUpsetException("Team Lead Upset");
    }
}

В приведенном выше примере логи будут выглядеть так:

com.baeldung.chainedexception.exceptions.TeamLeadUpsetException: 
  Team lead Upset
    at com.baeldung.chainedexception.exceptions.MainClass
      .howIsTeamLead(MainClass.java:46)
    at com.baeldung.chainedexception.exceptions.MainClass
      .getLeave(MainClass.java:34)
    at com.baeldung.chainedexception.exceptions.MainClass
      .main(MainClass.java:29)
Exception in thread "main" com.baeldung.chainedexception.exceptions.
  NoLeaveGrantedException: Leave not sanctioned.
    at com.baeldung.chainedexception.exceptions.MainClass
      .getLeave(MainClass.java:37)
    at com.baeldung.chainedexception.exceptions.MainClass
      .main(MainClass.java:29)

С цепочкой

Далее давайте напишем пример с цепочкой наших пользовательских исключений:

public class MainClass {
    public void main(String[] args) throws Exception {
        getLeave();
    }

    public getLeave() throws NoLeaveGrantedException {
        try {
            howIsTeamLead();
        } catch (TeamLeadUpsetException e) {
             throw new NoLeaveGrantedException("Leave not sanctioned.", e);
        }
    }

    public void howIsTeamLead() throws TeamLeadUpsetException {
        throw new TeamLeadUpsetException("Team lead Upset.");
    }
}

Наконец, давайте посмотрим на логи, полученные с помощью цепочки исключений:

Exception in thread "main" com.baeldung.chainedexception.exceptions
  .NoLeaveGrantedException: Leave not sanctioned. 
    at com.baeldung.chainedexception.exceptions.MainClass
      .getLeave(MainClass.java:36) 
    at com.baeldung.chainedexception.exceptions.MainClass
      .main(MainClass.java:29) 
Caused by: com.baeldung.chainedexception.exceptions
  .TeamLeadUpsetException: Team lead Upset.
    at com.baeldung.chainedexception.exceptions.MainClass
  .howIsTeamLead(MainClass.java:44) 
    at com.baeldung.chainedexception.exceptions.MainClass
  .getLeave(MainClass.java:34) 
    ... 1 more

Мы можем легко сравнить показанные журналы и сделать вывод, что цепочка исключений приводит к более чистым журналам.

Трудности с цепочками исключений

Мы можем использовать цепочку исключений в ситуациях, когда другое исключение вызывает одно исключение. Однако важно отметить, что цепочка может затруднить чтение и понимание нашего кода. Поэтому мы должны использовать цепочку исключений экономно и только при необходимости.

Если мы используем цепочку исключений, рекомендуется задокументировать цепочку в нашем коде. Это поможет другим понять наш код и облегчит отладку в случае возникновения ошибки.

Программа

//: exceptions/DynamicFields.java
// A Class that dynamically adds fields to itself. 
// Demonstrates exception chaining. 
import static net.mindview.util.Print.*; 
class DynamicFieldsException extends Exception {} 
public class DynamicFields { 
   private Object[][] fields; 
   public DynamicFields(int initialSize) { 
     fields = new Object[initialSize][2]; 
     for(int i = 0; i < initialSize; i++) 
     fields[i] = new Object[] { null, null }; 
   } 

 public String toString() { 
   StringBuilder result = new StringBuilder(); 
   for(Object[] obj : fields) { 
     result.append(obj[0]); 
     result.append(": "); 
     result.append(obj[1]); 
     result.append("\n");
   } 
   return result.toString(); 
 } 

//...................hasField(String id)........................//
 private int hasField(String id) { 
   for(int i = 0; i < fields.length; i++) 
     if(id.equals(fields[i][0])) 
       return i; 
   return -1; 
  } 

//......................getFieldNumber(String id)....................//
 private int getFieldNumber(String id) throws NoSuchFieldException { 
   int fieldNum = hasField(id); 
   if(fieldNum == -1) 
     throw new NoSuchFieldException(); 
   return fieldNum; 
 }

 //........................makeField(String id).....................//
 private int makeField(String id) { 
   for(int i = 0; i < fields.length; i++) 
     if(fields[i][0] == null) { 
       fields[i][0] = id; 
       return i; 
     } 
   // No empty fields. Add one: 
   Object[][] tmp = new Object[fields.length + 1][2]; 
   for(int i = 0; i < fields.length; i++) 
     tmp[i] = fields[i]; 
   for(int i = fields.length; i < tmp.length; i++) 
     tmp[i] = new Object[] { null, null }; 
   fields = tmp; 
   // Recursive call with expanded fields: 
   return makeField(id); 
 } 

//.............................getField(String id).......................//
 public Object getField(String id) throws NoSuchFieldException { 
   return fields[getFieldNumber(id)][1]; 
 } 

//.........................setField(String id, Object value)..........................
 public Object setField(String id, Object value) throws DynamicFieldsException { 
   if(value == null) { 
     // Most exceptions don’t have a "cause" constructor. 
     // In these cases you must use initCause(), 
     // available in all Throwable subclasses. 
     DynamicFieldsException dfe = new DynamicFieldsException(); 
     dfe.initCause(new NullPointerException()); 
     throw dfe; 
   } 
   int fieldNumber = hasField(id); 
   if(fieldNumber == -1) 
   fieldNumber = makeField(id); 
   Object result = null; 
   try { 
     result = getField(id); // Get old value 
   } catch(NoSuchFieldException e) { 
     // Use constructor that takes "cause": 
     throw new RuntimeException(e); 
    } 
   fields[fieldNumber][1] = value; 
   return result; 
 } 

// ......................main...................//
 public static void main(String[] args) { 
   DynamicFields df = new DynamicFields(3); 
   print(df); 
   try { 
     df.setField("d", "A value for d"); 
     df.setField("number", 47); 
     df.setField("number2", 48); 
     print(df); 
     df.setField("d", "A new value for d"); 
     df.setField("number3", 11); 
     print("df: " + df); 
     print("df.getField(\"d\") : " + df.getField("d")); 
     Object field = df.setField("d", null); // Exception 
   } catch(NoSuchFieldException e) { 
     e.printStackTrace(System.out); 
   } catch(DynamicFieldsException e) { 
     e.printStackTrace(System.out); 
   } 
 } 
} 

вывод

null: null 
null: null 
null: null 

d: A value for d 
number: 47 
number2: 48 

df: d: A new value for d 
number: 47 
number2: 48 
number3: 11 

df.getField("d") : A new value for d 
DynamicFieldsException 
   at DynamicFields.setField(DynamicFields.java:64) 
   at DynamicFields.main(DynamicFields.java:94) 
Caused by: java.lang.NullPointerException 
   at DynamicFields.setField(DynamicFields.java:66) 
   ... 1 more 
*///:~

Каждый объект DynamicFields содержит массив пар объект-объект. Первый объект — это идентификатор поля (String), а второй — значение поля, которое может быть любого типа, кроме развернутого примитива. Когда вы создаете объект, вы делаете обоснованное предположение о том, сколько полей вам нужно. Когда вы вызываете setField(), она либо находит существующее поле по этому имени, либо создает новое и вставляет в него ваше значение. Если ему не хватает места, он добавляет новое пространство, создавая массив длины на единицу больше и копируя старые элементы. one и с помощью initCause() вставить NullPointerException в качестве причины.

В качестве возвращаемого значения setField() также извлекает старое значение в этом местоположении поля с помощью getField(), что может вызвать исключение NoSuchFieldException. Если клиентский программист вызывает getField(), он отвечает за обработку NoSuchFieldException, но если это исключение генерируется внутри setField(), оно ошибка программирования, поэтому NoSuchFieldException преобразуется в RuntimeException с помощью конструктора, который принимает аргумент причины.

Вы заметите, что toString() использует StringBuilder для создания своего результата. Вы узнаете больше о StringBuilder в главе "Строки", но в целом вы захотите использовать его всякий раз, когда пишете toString(), в котором используется цикл, например дело здесь.

Приятного обучения 😃

Присоединяйтесь к группе Mouad Oumous Java WhatsApp JOIN

Присоединяйтесь к Telegram-каналу Mouad Oumous JOIN

Поддержите нашу публикацию, подписавшись на нее