Обработка исключений в Java
В Java исключения обрабатываются с помощью конструкции try-catch-finally, где finally – опциональная ветка, при наличии выполняется как после try, так и любой из веток catch. Кроме того, часто исключения не перехватываются в том методе, где возникают, а пробрасываются выше по стеку вызовов. В этом случае метод в своем заголовке должен содержать throws класс_исключения . Если может быть выброшено несколько исключений, они перечисляются через запятую.
Обработка на месте:
import java.io.*; public class ExTest { public static void main(String[] args) { try { FileInputStream file = new FileInputStream( "test.txt"); System.out.println("File ready"); } catch (FileNotFoundException e) { System.out.println("Input problem"); System.out.println(e); } } }
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class ExTest2 { public static void main(String[] args) throws IOException { String s = getString(); } public static String getString() throws IOException { BufferedReader reader = new BufferedReader( new InputStreamReader( System.in)); return reader.readLine(); } }
Во втором примере исключение можно было бы обработать в try-catch в методе main(). Можно указывать родительский класс исключения. Так вместо FileNotFoundException допустимо писать IOExctption или вообще класс Exception.
В Java исключения делятся на контролируемые (проверяемые) компилятором и неконтролируемые (непроверяемые). Приведенный выше исключения, которые могут возникать при вводе данных, – пример контролируемых. Если их не обработать или не передать вызывающему методу, то программу нельзя будет скомплировать.
Непроверяемые компилятором исключения должен контролировать сам программист. Так приведенная ниже программа удачно скомпилируется. Ошибка обнаружится лишь в процессе ее выполнения (runtime), если значение a окажется равным нулю.
import java.util.Scanner; public class ExTest3 public static void main(String[] args) Scanner scan = new Scanner(System.in); int a = scan.nextInt(); System.out.println(10/a); > >
Обработать исключение можно так:
import java.util.Scanner; public class ExTest3 public static void main(String[] args) Scanner scan = new Scanner(System.in); int a = scan.nextInt(); try System.out.println(10 / a); > catch (ArithmeticException e) System.out.println("0 is bad divisor"); > finally System.out.println("End of the program"); > > >
Классов исключений в Java несколько сотен. По большей части они связаны между собой наследственными связями. Исходным является дочерний от Object класс Throwable. У него есть два наследника: Error и собственно Exception.
Объекты, порожденные от наследников Error, возникают в момент выполнения программы и связаны с работой java-машины. Примером подобной ошибки является недостаток памяти. Такие ошибки в коде не обрабатывают. Они скорее свидетельствует, что код сам по себе плох, его надо переписывать. Все Error’ы относятся к непроверяемым на этапе компиляции.
Непосредственно от класса Exсeption происходят классы RuntimeException и IOException. Первая группа относится к непроверяемым, вторая – к проверяемым.
X Скрыть Наверх
Программирование на Java. Курс
Обработка ошибок и исключения
2. Коды возврата. Основная идея — в случае ошибки возвращать специальное значение, которое не может быть корректным. Например, если в методе есть операция деления, то придется проверять делитель на равенство нулю. Также проверим корректность аргументов a и b :
Double f(Double a, Double b) < if ((a == null) || (b == null)) < return null; > //. if (Math.abs(b) < EPS) < return null; > else < return a / b; > >
При вызове метода необходимо проверить возвращаемое значение:
Double d = f(a, b); if (d != null) < //. > else < //. >
Минусом такого подхода является необходимость проверки возвращаемого значения каждый раз при вызове метода. Кроме того, не всегда возможно определить тип ошибки.
3.Использовать флаг ошибки: при возникновении ошибки устанавливать флаг в соответствующее значение:
boolean error; Double f(Double a, Double b) < if ((a == null) || (b == null)) < error = true; return null; > //. if (Math.abs(b) < EPS) < error = true; return b; > else < return a / b; > >
error = false; Double d = f(a, b); if (error) < //. > else < //. >
Минусы такого подхода аналогичны минусам использования кодов возврата.
4.Можно вызвать метод обработки ошибки и возвращать то, что вернет этот метод.
Double f(Double a, Double b) < if ((a == null) || (b == null)) < return nullPointer(); > //. if (Math.abs(b) < EPS) < return divisionByZero(); > else < return a / b; > >
Но в таком случае не всегда возможно проверить корректность результата вызова основного метода.
5.В случае ошибки просто закрыть программу.
if (Math.abs(b) < EPS) < System.exit(0); return this; >
Это приведет к потере данных, также невозможно понять, в каком месте возникла ошибка.
Исключения
В Java возможна обработка ошибок с помощью исключений:
Double f(Double a, Double b) < if ((a == null) || (b == null)) < throw new IllegalArgumentException("arguments of f() are null"); > //. return a / b; >
Проверять b на равенство нулю уже нет необходимости, так как при делении на ноль метод бросит непроверяемое исключение ArithmeticException .
- разделить обработку ошибок и сам алгоритм;
- не загромождать код проверками возвращаемых значений;
- обрабатывать ошибки на верхних уровнях, если на текущем уровне не хватает данных для обработки. Например, при написании универсального метода чтения из файла невозможно заранее предусмотреть реакцию на ошибку, так как эта реакция зависит от использующей метод программы;
- классифицировать типы ошибок, обрабатывать похожие исключения одинаково, сопоставлять специфичным исключениям определенные обработчики.
Каждый раз, когда при выполнении программы происходит ошибка, создается объект-исключение, содержащий информацию об ошибке, включая её тип и состояние программы на момент возникновения ошибки. После создания исключения среда выполнения пытается найти в стеке вызовов метод, который содержит код, обрабатывающий это исключение. Поиск начинается с метода, в котором произошла ошибка, и проходит через стек в обратном порядке вызова методов. Если не было найдено ни одного подходящего обработчика, выполнение программы завершается.
Таким образом, механизм обработки исключений содержит следующие операции:
- Создание объекта-исключения.
- Заполнение stack trace’а этого исключения.
- Stack unwinding (раскрутка стека) в поисках нужного обработчика.
Классификация исключений
Класс Java Throwable описывает все, что может быть брошено как исключение. Наследеники Throwable — Exception и Error — основные типы исключений. Также RuntimeException , унаследованный от Exception , является существенным классом.
Иерархия стандартных исключений
Проверяемые исключения
Наследники класса Exception (кроме наслеников RuntimeException ) являются проверяемыми исключениями(checked exception). Как правило, это ошибки, возникшие по вине внешних обстоятельств или пользователя приложения – неправильно указали имя файла, например. Эти исключения должны обрабатываться в ходе работы программы, поэтому компилятор проверяет наличие обработчика или явного описания тех типов исключений, которые могут быть сгенерированы некоторым методом.
Все исключения, кроме классов Error и RuntimeException и их наследников, являются проверяемыми.
Error
Класс Error и его подклассы предназначены для системных ошибок. Свои собственные классы-наследники для Error писать (за очень редкими исключениями) не нужно. Как правило, это действительно фатальные ошибки, пытаться обработать которые довольно бессмысленно (например OutOfMemoryError ).
RuntimeException
Эти исключения обычно возникают в результате ошибок программирования, такие как ошибки разработчика или неверное использование интерфейса приложения. Например, в случае выхода за границы массива метод бросит OutOfBoundsException . Такие ошибки могут быть в любом месте программы, поэтому компилятор не требует указывать runtime исключения в объявлении метода. Теоретически приложение может поймать это исключение, но разумнее исправить ошибку.
Обработка исключений
Чтобы сгенерировать исключение используется ключевое слово throw . Как и любой объект в Java, исключения создаются с помощью new .
if (t == null) < throw new NullPointerException("t = null"); >
Есть два стандартных конструктора для всех исключений: первый — конструктор по умолчанию, второй принимает строковый аргумент, поэтому можно поместить подходящую информацию в исключение.
Возможна ситуация, когда одно исключение становится причиной другого. Для этого существует механизм exception chaining. Практически у каждого класса исключения есть конструктор, принимающий в качестве параметра Throwable – причину исключительной ситуации. Если же такого конструктора нет, то у Throwable есть метод initCause(Throwable) , который можно вызвать один раз, и передать ему исключение-причину.
Как и было сказано раньше, определение метода должно содержать список всех проверяемых исключений, которые метод может бросить. Также можно написать более общий класс, среди наследников которого есть эти исключения.
void f() throws InterruptedException, IOException < //.
try-catch-finally
Код, который может бросить исключения оборачивается в try -блок, после которого идут блоки catch и finally (Один из них может быть опущен).
try < // Код, который может сгенерировать исключение >
Сразу после блока проверки следуют обработчики исключений, которые объявляются ключевым словом catch.
try < // Код, который может сгенерировать исключение > catch(Type1 id1) < // Обработка исключения Type1 > catch(Type2 id2) < // Обработка исключения Type2 >
Сatch -блоки обрабатывают исключения, указанные в качестве аргумента. Тип аргумента должен быть классом, унаследованного от Throwable , или самим Throwable . Блок catch выполняется, если тип брошенного исключения является наследником типа аргумента и если это исключение не было обработано предыдущими блоками.
Код из блока finally выполнится в любом случае: при нормальном выходе из try , после обработки исключения или при выходе по команде return .
NB: Если JVM выйдет во время выполнения кода из try или catch , то finally -блок может не выполниться. Также, например, если поток выполняющий try или catch код остановлен, то блок finally может не выполниться, даже если приложение продолжает работать.
Блок finally удобен для закрытия файлов и освобождения любых других ресурсов. Код в блоке finally должен быть максимально простым. Если внутри блока finally будет брошено какое-либо исключение или просто встретится оператор return , брошенное в блоке try исключение (если таковое было брошено) будет забыто.
import java.io.IOException; public class ExceptionTest < public static void main(String[] args) < try < try < throw new Exception("a"); > finally < throw new IOException("b"); > > catch (IOException ex) < System.err.println(ex.getMessage()); > catch (Exception ex) < System.err.println(ex.getMessage()); > > >
После того, как было брошено первое исключение — new Exception(«a») — будет выполнен блок finally , в котором будет брошено исключение new IOException(«b») , именно оно будет поймано и обработано. Результатом его выполнения будет вывод в консоль b . Исходное исключение теряется.
Обработка исключений, вызвавших завершение потока
При использовании нескольких потоков бывают ситуации, когда поток завершается из-за исключения. Для того, чтобы определить с каким именно, начиная с версии Java 5 существует интерфейс Thread.UncaughtExceptionHandler . Его реализацию можно установить нужному потоку с помощью метода setUncaughtExceptionHandler . Можно также установить обработчик по умолчанию с помощью статического метода Thread.setDefaultUncaughtExceptionHandler .
Интерфейс Thread.UncaughtExceptionHandler имеет единственный метод uncaughtException(Thread t, Throwable e) , в который передается экземпляр потока, завершившегося исключением, и экземпляр самого исключения. Когда поток завершается из-за непойманного исключения, JVM запрашивает у потока UncaughtExceptionHandler , используя метод Thread.getUncaughtExceptionHandler() , и вызвает метод обработчика – uncaughtException(Thread t, Throwable e) . Все исключения, брошенные этим методом, игнорируются JVM.
Информация об исключениях
- getMessage() . Этот метод возвращает строку, которая была первым параметром при создании исключения;
- getCause() возвращает исключение, которое стало причиной текущего исключения;
- printStackTrace() печатает stack trace, который содержит информацию, с помощью которой можно определить причину исключения и место, где оно было брошено.
Exception in thread "main" java.lang.IllegalStateException: A book has a null property at com.example.myproject.Author.getBookIds(Author.java:38) at com.example.myproject.Bootstrap.main(Bootstrap.java:14) Caused by: java.lang.NullPointerException at com.example.myproject.Book.getId(Book.java:22) at com.example.myproject.Author.getBookIds(Author.java:35)
Все методы выводятся в обратном порядке вызовов. В примере исключение IllegalStateException было брошено в методе getBookIds , который был вызван в main . «Caused by» означает, что исключение NullPointerException является причиной IllegalStateException .
Разработка исключений
Чтобы определить собственное проверяемое исключение, необходимо создать наследника класса java.lang.Exception . Желательно, чтобы у исключения был конструкор, которому можно передать сообщение:
public class FooException extends Exception < public FooException() < super(); > public FooException(String message) < super(message); > public FooException(String message, Throwable cause) < super(message, cause); > public FooException(Throwable cause) < super(cause); > >
Исключения в Java7
- обработка нескольких типов исключений в одном catch -блоке:
catch (IOException | SQLException ex)
В таких случаях параметры неявно являются final , поэтому нельзя присвоить им другое значение в блоке catch .
Байт-код, сгенерированный компиляцией такого catch -блока будет короче, чем код нескольких catch -блоков.
- Try с ресурсами позволяет прямо в try -блоке объявлять необходимые ресурсы, которые по завершению блока будут корректно закрыты (с помощью метода close() ). Любой объект реализующий java.lang.AutoCloseable может быть использован как ресурс.
static String readFirstLineFromFile(String path) throws IOException < try (BufferedReader br = new BufferedReader(new FileReader(path))) < return br.readLine(); > >
В приведенном примере в качестве ресурса использутся объект класса BufferedReader , который будет закрыт вне зависимосити от того, как выполнится try -блок.
Можно объявлять несколько ресурсов, разделяя их точкой с запятой:
public static void viewTable(Connection con) throws SQLException < String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES"; try (Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery(query)) < //Work with Statement and ResultSet > catch (SQLException e) < e.printStackTrace; >>
Во время закрытия ресурсов тоже может быть брошено исключение. В try-with-resources добавленна возможность хранения «подавленных» исключений, и брошенное try -блоком исключение имеет больший приоритет, чем исключения получившиеся во время закрытия. Получить последние можно вызовом метода getSuppressed() от исключения брошенного try -блоком.
- Перебрасывание исключений с улучшенной проверкой соответствия типов.
Компилятор Java SE 7 тщательнее анализирует перебрасываемые исключения. Рассмотрим следующий пример:
static class FirstException extends Exception < >static class SecondException extends Exception < >public void rethrowException(String exceptionName) throws Exception < try < if ("First".equals(exceptionName)) < throw new FirstException(); > else < throw new SecondException(); > > catch (Exception ex) < throw e; > >
В примере try -блок может бросить либо FirstException , либо SecondException . В версиях до Java SE 7 невозможно указать эти исключения в декларации метода, потому что catch -блок перебрасывает исключение ex , тип которого — Exception .
В Java SE 7 вы можете указать, что метод rethrowException бросает только FirstException и SecondException . Компилятор определит, что исключение Exception ex могло возникнуть только в try -блоке, в котором может быть брошено FirstException или SecondException . Даже если тип параметра catch — Exception , компилятор определит, что это экземпляр либо FirstException , либо SecondException :
public void rethrowException(String exceptionName) throws FirstException, SecondException < try < // . > catch (Exception e) < throw e; > >
Если FirstException и SecondException не являются наследниками Exception , то необходимо указать и Exception в объявлении метода.
Примеры исключений
- любая операция может бросить VirtualMachineError . Как правило это происходит в результате системных сбоев.
- OutOfMemoryError . Приложение может бросить это исключение, если, например, не хватает места в куче, или не хватает памяти для того, чтобы создать стек нового потока.
- IllegalArgumentException используется для того, чтобы избежать передачи некорректных значений аргументов. Например:
public void f(Object a) < if (a == null) < throw new IllegalArgumentException("a must not be null"); > >
- IllegalStateException возникает в результате некорректного состояния объекта. Например, использование объекта перед тем как он будет инициализирован.
Гарантии безопасности
При возникновении исключительной ситуации, состояния объектов и программы могут удовлетворять некоторым условиям, которые определяются различными типами гарантий безопасности:
- Отсутствие гарантий (no exceptional safety). Если было брошено исключение, то не гарантируется, что все ресурсы будут корректно закрыты и что объекты, методы которых бросили исключения, могут в дальнейшем использоваться. Пользователю придется пересоздавать все необходимые объекты и он не может быть уверен в том, что может переиспозовать те же самые ресурсы.
- Отсутствие утечек (no-leak guarantee). Объект, даже если какой-нибудь его метод бросает исключение, освобождает все ресурсы или предоставляет способ сделать это.
- Слабые гарантии (weak exceptional safety). Если объект бросил исключение, то он находится в корректном состоянии, и все инварианты сохранены. Рассмотрим пример:
class Interval < //invariant: left double left; double right; //. >
Если будет брошено исключение в этом классе, то тогда гарантируется, что ивариант «левая граница интервала меньше правой» сохранится, но значения left и right могли измениться.
- Сильные гарантии (strong exceptional safety). Если при выполнении операции возникает исключение, то это не должно оказать какого-либо влияния на состояние приложения. Состояние объектов должно быть таким же как и до вызовов методов.
- Гарантия отсутствия исключений (no throw guarantee). Ни при каких обстоятельствах метод не должен генерировать исключения. В Java это невозможно, например, из-за того, что VirtualMachineError может произойти в любом месте, и это никак не зависит от кода. Кроме того, эту гарантию практически невозможно обеспечить в общем случае.
Источники
- Обработка ошибок и исключения — Сайт Георгия Корнеева
- Лекция Георгия Корнеева — Лекториум
- The Java Tutorials. Lesson: Exceptions
- Обработка исключений — Википедия
- Throwable (Java Platform SE 7 ) — Oracle Documentation
- try/catch/finally и исключения — www.skipy.ru
Методы (Руководство по программированию на C#)
Метод — это блок кода, содержащий ряд инструкций. Программа инициирует выполнение инструкций, вызывая метод и указывая все аргументы, необходимые для этого метода. В C# все инструкции выполняются в контексте метода.
Метод Main является точкой входа для каждого приложения C# и вызывается общеязыковой средой выполнения (CLR) при запуске программы. В приложении, использующем инструкции верхнего уровня, метод Main создается компилятором и содержит все инструкции верхнего уровня.
В этой статье рассматриваются названные методы. Дополнительные сведения об анонимных функциях см. в статье Лямбда-выражения.
Сигнатуры методов
Методы объявляются в классе, структуре или интерфейсе путем указания уровня доступа, такого как public или private , необязательных модификаторов, таких как abstract или sealed , возвращаемого значения, имени метода и всех параметров этого метода. Все эти части вместе представляют собой сигнатуру метода.
Тип возврата метода не является частью сигнатуры метода в целях перегрузки метода. Однако он является частью сигнатуры метода при определении совместимости между делегатом и методом, который он указывает.
Параметры метода заключаются в скобки и разделяются запятыми. Пустые скобки указывают, что параметры методу не требуются. Этот класс содержит четыре метода:
abstract class Motorcycle < // Anyone can call this. public void StartEngine()* Method statements here */ > // Only derived classes can call this. protected void AddGas(int gallons) < /* Method statements here */ >// Derived classes can override the base class implementation. public virtual int Drive(int miles, int speed) < /* Method statements here */ return 1; >// Derived classes must implement this. public abstract double GetTopSpeed(); >
Доступ к методу
Вызов метода в объекте аналогичен доступу к полю. После имени объекта добавьте точку, имя метода и круглые скобки. Аргументы перечисляются в этих скобках и разделяются запятыми. Таким образом, методы класса Motorcycle могут вызываться, как показано в следующем примере:
class TestMotorcycle : Motorcycle < public override double GetTopSpeed() < return 108.4; >static void Main() < TestMotorcycle moto = new TestMotorcycle(); moto.StartEngine(); moto.AddGas(15); moto.Drive(5, 20); double speed = moto.GetTopSpeed(); Console.WriteLine("My top speed is ", speed); > >
Параметры и аргументы метода
Определение метода задает имена и типы всех необходимых параметров. Когда вызывающий код вызывает метод, он предоставляет конкретные значения, называемые аргументами, для каждого параметра. Аргументы должны быть совместимы с типом параметра, но имя аргумента (если есть), используемое в вызывающем коде, не обязательно должно совпадать с именем параметра, указанным в методе. Например:
public void Caller() < int numA = 4; // Call with an int variable. int productA = Square(numA); int numB = 32; // Call with another int variable. int productB = Square(numB); // Call with an integer literal. int productC = Square(12); // Call with an expression that evaluates to int. productC = Square(productA * 3); >int Square(int i) < // Store input argument in a local variable. int input = i; return input * input; >
Передача по ссылке и передача по значению
По умолчанию при передаче в метод экземпляра типа значения вместо самого этого экземпляра передается его копия. Поэтому изменения в аргументе не оказывают влияния на исходный экземпляр в вызывающем методе. Чтобы передать экземпляр типа значения по ссылке, используйте ключевое слово ref . Дополнительные сведения см. в разделе Передача параметров типа значения.
При передаче в метод объекта ссылочного типа передается ссылка на этот объект. То есть метод получает не сам объект, а аргумент, который указывает расположение объекта. При изменении члена объекта с помощью этой ссылки это изменение отражается в аргументе в вызывающем методе, даже если объект передается по значению.
Ссылочный тип создается с помощью ключевого слова class , как показано в следующем примере.
public class SampleRefType
Теперь, если передать объект, основанный на этом типе, в метод, то будет передана ссылка на объект. В следующем примере объект типа SampleRefType передается в метод ModifyObject :
public static void TestRefType() < SampleRefType rt = new SampleRefType(); rt.value = 44; ModifyObject(rt); Console.WriteLine(rt.value); >static void ModifyObject(SampleRefType obj)
В этом примере, в сущности, делается то же, что и в предыдущем примере, — аргумент по значению передается в метод. Но поскольку здесь используется ссылочный тип, результат будет другим. В данном случае в методе ModifyObject изменено поле value параметра obj , а также изменено поле value аргумента, rt в методе TestRefType . В качестве выходных данных метод TestRefType отображает 33.
Дополнительные сведения о передаче ссылочных типов по ссылке и по значению см. в разделах Передача параметров ссылочного типа и Ссылочные типы.
Возвращаемые значения
Методы могут возвращать значение вызывающему объекту. Если тип возврата, указываемый перед именем метода, не void , этот метод может возвращать значение с помощью оператора return . Инструкция с ключевым словом return , за которым следует значение, соответствующее типу возврата, будет возвращать это значение объекту, вызвавшему метод.
Значение можно вернуть вызывающему объекту по значению или по ссылке. Значения возвращаются вызывающему объекту по ссылке, если ключевое слово ref используется в сигнатуре метода и указывается после каждого ключевого слова return . Например, следующая сигнатура метода и оператор return указывают, что метод возвращает переменную с именем estDistance вызывающему объекту по ссылке.
public ref double GetEstimatedDistance()
Ключевое слове return также останавливает выполнение метода. Если тип возврата — void , инструкцию return без значения по-прежнему можно использовать для завершения выполнения метода. Без ключевого слова return этот метод будет останавливать выполнение при достижении конца блока кода. Методы с типом возврата, отличным от void, должны использовать ключевое слово return для возврата значения. Например, в следующих двух методах ключевое слово return используется для возврата целочисленных значений.
class SimpleMath < public int AddTwoNumbers(int number1, int number2) < return number1 + number2; >public int SquareANumber(int number) < return number * number; >>
Чтобы использовать значение, возвращаемое из метода, вызывающий метод может применять сам вызов метода везде, где будет достаточно значения того же типа. Можно также назначить возвращаемое значение переменной. Например, следующие два примера кода достигают одной и той же цели.
int result = obj.AddTwoNumbers(1, 2); result = obj.SquareANumber(result); // The result is 9. Console.WriteLine(result);
result = obj.SquareANumber(obj.AddTwoNumbers(1, 2)); // The result is 9. Console.WriteLine(result);
Использование локальной переменной, в данном случае result , для сохранения значения является необязательным. Это может улучшить читаемость кода или может оказаться необходимым, если нужно сохранить исходное значение аргумента для всей области метода.
Чтобы использовать значение, возвращаемое по ссылке из метода, необходимо объявить локальную ссылочную переменную, если планируется изменение значения. Например, если метод Planet.GetEstimatedDistance возвращает значение Double по ссылке, можно определить его как локальную ссылочную переменную с использованием кода следующего вида:
ref double distance = ref Planet.GetEstimatedDistance();
Возвращать многомерный массив из метода M , который изменяет содержимое массива, необязательно, если вызывающая функция передает массив в M . В целях оптимизации можно возвращать полученный массив из M или функциональный поток значений, однако это необязательно. Это связано с тем, что C# передает все ссылочные типы по значению, а значение ссылки на массив представляет собой указатель на массив. В методе M любые изменения содержимого массива отслеживаются любым кодом, имеющим ссылку на массив, как показано в приведенном ниже примере:
static void Main(string[] args) < int[,] matrix = new int[2, 2]; FillMatrix(matrix); // matrix is now full of -1 >public static void FillMatrix(int[,] matrix) < for (int i = 0; i < matrix.GetLength(0); i++) < for (int j = 0; j < matrix.GetLength(1); j++) < matrix[i, j] = -1; >> >
Асинхронные методы
С помощью функции async можно вызывать асинхронные методы, не прибегая к использованию явных обратных вызовов или ручному разделению кода между несколькими методами или лямбда-выражениями.
Если пометить метод с помощью модификатора async, можно использовать в этом методе инструкцию await. Когда управление достигает выражения await в асинхронном методе, управление возвращается вызывающему объекту и выполнение метода приостанавливается до завершения выполнения ожидающей задачи. После завершения задачи выполнение в методе может быть возобновлено.
Асинхронный метод возвращается в вызывающий объект, когда он встречает первый ожидаемый объект, выполнение которого еще не завершено, или когда выполнение асинхронного метода доходит до конца — в зависимости от того, что происходит раньше.
В следующем примере метод Main служит примером асинхронного метода с типом возврата Task. Он переходит к методу DoSomethingAsync и, поскольку он выражается в одной строке, он может опустить ключевые слова async и await . Поскольку DoSomethingAsync является асинхронным методом, задача для вызова DoSomethingAsync должна быть ожидаемой, как показывает следующая инструкция: await DoSomethingAsync(); .
class Program < static Task Main() =>DoSomethingAsync(); static async Task DoSomethingAsync() < TaskdelayTask = DelayAsync(); int result = await delayTask; // The previous two statements may be combined into // the following statement. //int result = await DelayAsync(); Console.WriteLine($"Result: "); > static async Task DelayAsync() < await Task.Delay(100); return 5; >> // Example output: // Result: 5
Асинхронный метод не может объявить все параметры ref или out , но может вызывать методы, которые имеют такие параметры.
Определения текста выражений
Часто используются определения методов, которые просто немедленно возвращаются с результатом выражения или которые имеют единственную инструкцию в тексте метода. Для определения таких методов существует сокращенный синтаксис с использованием => :
public Point Move(int dx, int dy) => new Point(x + dx, y + dy); public void Print() => Console.WriteLine(First + " " + Last); // Works with operators, properties, and indexers too. public static Complex operator +(Complex a, Complex b) => a.Add(b); public string Name => First + " " + Last; public Customer this[long id] => store.LookupCustomer(id);
Если метод возвращает void или является асинхронным методом, то текст метода должен быть выражением инструкции (так же, как при использовании лямбда-выражений). Свойства и индексаторы должны быть только для чтения, и вы не должны использовать ключевое слово get метода доступа.
Итераторы
Итератор выполняет настраиваемую итерацию по коллекции, например по списку или массиву. Итератор использует инструкцию yield return для возврата всех элементов по одному. При достижении инструкции yield return текущее расположение в коде запоминается. При следующем вызове итератора выполнение возобновляется с этого места.
Итератор вызывается из клиентского кода с помощью инструкции foreach .
Дополнительные сведения см. в разделе Итераторы.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
- Руководство по программированию на C#
- Система типов C#
- Модификаторы доступа
- Статические классы и члены статических классов
- Наследование
- Абстрактные и запечатанные классы и члены классов
- params
- out
- ref;
- Параметры методов
Совместная работа с нами на GitHub
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.
The .NET documentation is open source. Provide feedback here.
Обратная связь
Отправить и просмотреть отзыв по
Глава 7 ИСКЛЮЧЕНИЯ
Плохо подогнанное снаряжение может заставить ваш гранатомет M203 выстрелить в самый неожиданный момент . Подобное происшествие плохо скажется на вашей репутации среди тех , кто останется в живых . Журнал PS армии США , август 1993 года Во время своей работы приложение иногда сталкивается с разного рода нештатными ситуациями . При вызове метода некоторого объекта он может обнаружить у себя внутренние проблемы ( неверные значения переменных ), найти ошибки в других объектах или данных ( например , в файле или сетевом адресе ), определить факт нарушения своего базового контракта ( чтение данных из закрытого потока ) и так далее . Многие программисты не проверяют все возможные источники ошибок , и на то есть веская причина : если при каждом вызове метода анализировать все мыслимые ошибки , текст программы становится совершенно невразумительным . Таким образом достигается компромисс между правильностью ( проверка всех ошибок ) и ясностью ( отказ от загромождения основной логики программы множеством проверок ). Исключения предоставляют удобную возможность проверки ошибок без загромождения текста программы . Кроме того , исключения непосредственно сигнализируют об ошибках , а не меняют значения флагов или каких — либо полей , которые потом нужно проверять . Исключения превращают ошибки , о которых может сигнализировать метод , в явную часть контракта этого метода . Список исключений виден программисту , проверяется компилятором и сохраняется в расширенных классах , переопределяющих данный метод . Исключение во збуждается , когда возникает неожиданное ошибочное состояние . Затем исключение перехватывается соответствующим условием в стеке вызова методов . Если исключение не перехвачено , срабатывает обработчик исключения по умолчанию , который обычно выводит полезную информацию об исключении ( скажем , содержимое стека вызовов ). 7.1. Создание новых типов исключений Исключения в Java представляют собой объекты . Все типы исключений ( то есть все классы , объекты которых возбуждаются в качестве исключений ) должны расширять класс языка Java, который называется Throwable, или один из его подклассов . Класс Throwable содержит строку , которая может использоваться для описания исключения . По соглашению , новые типы исключений расширяют класс Exception, а не Throwable. Исключения Java, главным образом , являются проверяемыми — это означает , что компилятор следит за тем , чтобы ваши методы возбуждали лишь те исключения , о которых объявлено в заголовке метода . Стандартные исключения времени выполнения и ошибки расширяют классы RuntimeException и Error, тем самым создавая непроверяемые исключения . Все исключения , определяемые программистом , должны расширять класс Exception, и , таким образом , они являются проверяемыми . Иногда хочется иметь больше данных , описывающих состояние исключения , — одной строки , предоставляемой классом Exception, оказывается недостаточно . В таких случаях можно расширить класс Exception и создать на его основе новый класс с дополнительными данными ( значения которых обычно задаются в конструкторе ).
converted to PDF by BoJIoc Например , предположим , что в интерфейс Attributed, рассмотренный в главе 4 , добавился метод replaceValue, который заменяет текущее значение именованного атрибута новым . Если атрибут с указанным именем не существует , возбуждается исключение — вполне резонно предположить , что заменить несуществующий атрибут не удастся . Исключение должно содержать имя атрибута и новое значение , которое пытались ему присвоить . Для работы с таким исключением создается класс NoSuchAttribiteException: public class NoSuchAttributeException extends Exception < public String attrName; public Object newValue; NoSuchAttributeException(String name, Object value) < super("No attribute named \"" + name + "\" found"); attrName = name; newValue = value; >> NoSuchAttribiteException расширяет Exception и включает конструктор , которому передается имя атрибута и присваиваемое значение ; кроме того , добавляются открытые поля для хранения данных . Внутри конструктора вызывается конструктор суперкласса со строкой , описывающей происходящее . Исключения такого рода могут использоваться во фрагменте программы , перехватывающем исключения , поскольку они выводят понятное человеку описание ошибки и данные , вызвавшие ее . Добавление полезной информации — одна из причин , по которым создаются новые исключения . Другая причина для появления новых типов исключений заключается в том , что тип является важной частью данных исключения , поскольку исключения перехватываются по их типу . Из этих соображений исключение NoSuch AttribiteException стоит создать даже в том случае , если вы не собираетесь включать в него новые данные ; в этом случае программист , для которого представляет интерес только это исключение , сможет перехватить его отдельно от всех прочих исключений , запускаемых методами интерфейса Attributed или иными методами , применяемыми к другим объектам в том же фрагменте программы . В общем случае исключения новых типов следует создавать тогда , когда программист хочет обрабатывать ошибки одного типа и пропускать ошибки другого типа . В этом случае он может воспользоваться новыми исключениями для выполнения нужного фрагмента программы , вместо того чтобы изучать содержимое объекта — исключения и решать , интересует ли его данное исключение или же оно не относится к делу и перехвачено случайно . 7.2. Оператор throw Исключения возбуждаются оператором throw, которому в качестве параметра передается объект . Например , вот как выглядит реализация replaceValue в классе AttributedImpl из главы 4: public void replaceValue(String name, Object newValue) throws NoSuchAttributeException <
| Attr attr = | find(name); | // Искать attr |
| if (attr == | null) | // Если атрибут не найден |
throw new NoSuchAttributeException(name, this); attr.valueOf(newValue); >
Метод replaceValue сначала ищет имя атрибута в текущем объекте Attr. Если атрибут не найден , то возбуждается объект — исключение типа NoSuch AttribiteException и его конструктору предоставляются содержательные данные . Исключения являются
converted to PDF by BoJIoc объектами , поэтому перед использованием их необходимо создать . Если атрибут не существует , то его значение заменяется новым . Разумеется , исключение может быть порождено вызовом метода , внутри которого оно возбуждается . 7.3. Условие throws Первое , что бросается в глаза в приведенном выше методе replace Value, — это список проверяемых исключений , которые в нем возбуждаются . В Java необходимо перечислить проверяемые исключения , возбуждаемые методом , поскольку программист при вызове метода должен знать их в такой же степени , в какой он представляет себе нормальное поведение метода . Проверяемые исключения , возбуждаемые методом , не уступают по своей важности типу возвращаемого значения — и то и другое необходимо объявить . Проверяемые исключения объявляются в условии throws, которое может содержать список значений , отделяемых друг от друга запятыми . Внутри метода разрешается возбуждать исключения , являющиеся расширениями типа Exception в условии throws, поскольку всегда допускается полиморфно использовать класс вместо его суперкласса . Метод может возбуждать несколько различных исключений , являющихся расширениями одного конкретного класса , и при этом объявить в условии throws всего один суперкласс . Тем не менее , поступая таким образом , вы скрываете от работающих с методом программистов полезную информацию , потому что они не будут знать , какие из возможных расширенных типов исключений возбуждаются методом . В целях надлежащего документирования условие throws должно быть как можно более полным и подробным . Контракт , определяемый условием throws, обязан неукоснительно соблюдаться — можно возбуждать лишь те исключения , которые указаны в данном условии . Возбуждение любого другого исключения ( прямое , с помощью throw, или косвенное , через вызов другого метода ) является недопустимым . Отсутствие условия throws не означает , что метод может возбуждать любые исключения ; наоборот , оно говорит о том , что он не возбуждает никаких исключений . Все стандартные исключения времени выполнения ( такие , как ClassCast Exception и ArithmeticException) представляют собой расширения класса RuntimeException. О более серьезных ошибках сигнализируют исключения , которые являются расширениями класса Error и могут возникнуть в произвольный момент в произвольной точке программы . RuntimeException и Error — единственные исключения , которые не нужно перечислять в условии throws; они являются общепринятыми и могут возбуждаться в любом методе , поэтому компилятор не проверяет их . Полный список классов стандартных непроверяемых исключений приведен в Приложении Б . Инициализаторы и блоки статической инициализации не могут возбуждать проверяемые исключения , как прямо , так и посредством вызова метода , возбуждающего исключение . Во время конструирования объекта нет никакого способа перехватить и обработать исключение . При инициализации полей выход заключается в том , чтобы инициализировать их внутри конструктора , который может возбуждать исключения . Для статических инициализаторов можно поместить инициализацию в статический блок , который бы перехватывал и обрабатывал исключение . Статические блоки не возбуждают исключений , но могут перехватывать их .
Java довольно строго подходит к обработке проверяемых исключений , поскольку это помогает избежать программных сбоев , вызванных невниманием к ошибкам . Опыт показывает , что программисты забывают про обработку ошибок или откладывают ее на будущее , которое так никогда и не наступает . Условие throws ясно показывает , какие исключения возбуждаются методом , и обеспечивает их обработку .
converted to PDF by BoJIoc При вызове метода , у которого в условии throws приведено проверяемое исключение , имеются три варианта : ∙ Перехватить исключение и обработать его . ∙ Перехватить исключение и перенаправить его в обработчик одного из ваших исключений , для чего возбудить исключение типа , объявленного в вашем условии throws. ∙ Объявить данное исключение в условии throws и отказаться от его обработки в вашем методе ( хотя в нем может присутствовать условие finally, которое сначала выполнит некоторые завершающие действия ; подробности приводятся ниже ). При любом из этих вариантов вам необходимо перехватить исключение , возбужденное другим методом ; это станет темой следующего раздела . Упражнение 7.1 Создайте класс — исключение ObjectNotFoundException для класса Linked List, построенного нами в предыдущих упражнениях . Включите в него метод find, предназначенный для поиска объектов в списке , который либо возвращает нужный объект LinkedList, либо возбуждает исключение , если объект отсутствует в списке . Почему такой вариант оказывается более предпочтительным , нежели возврат значения null для ненайденного объекта ? Какие данные должны входить в ObjectNotFoundException? 7.4. Операторы try, catch и finally Чтобы перехватить исключение , необходимо поместить фрагмент программы в оператор try. Базовый синтаксис оператора try выглядит следующим образом : try блок catch (тип-исключения идентификатор) блок catch (тип-исключения идентификатор) блок . finally блок Тело оператора try выполняется вплоть до возбуждения исключения или до успешного завершения . Если возникает исключение , то по порядку просматриваются все условия catch, пока не будет найдено исключение нужного класса или одного из его суперклассов . Если подходящее условие catch так и не найдено , то исключение выходит из текущего оператора try во внешний , который может обработать его . В операторе try может присутствовать любое количество условий catch, в том числе и ни одного . Если ни одно из условий catch внутри метода не перехватывает исключение , то оно передается в тот фрагмент программы , который вызвал данный метод . Если в try присутствует условие finally, то составляющие его операторы выполняются после того , как вся обработка внутри try будет завершена . Выполнение finally происходит независимо от того , как завершился оператор — нормально , в результате исключения или при выполнении управляющего оператора типа return или break. В приводимом ниже примере осуществляется подготовка к обработке одного из исключений , возбуждаемых в replaceValue: try < attributedObj.replaceValue("Age", new Integer(8)); >catch (NoSuchAttributeException e)
converted to PDF by BoJIoc // так не должно быть, но если уж случилось — восстановить Attr attr = new Attr(e.attrName, e.newValue); attrbuteObj.add(attr); > try содержит оператор ( представляющий собой блок ), который выполняет некоторые действия , в обычных условиях заканчивающиеся успешно . Если все идет нормально , то работа блока на этом завершается . Если же во время выполнения программы в try- блоке возбудилось какое — либо исключение ( прямо , посредством throw, либо косвенно , через внутренний вызов метода ), то выполнение кода внутри try прекращается , и просматриваются связанные с ним условия catch, чтобы определить , нужно ли перехватывать исключение . Условие catch чем — то напоминает внедренный метод с одним параметром — типом перехватываемого исключения . Внутри условия catch вы можете пытаться восстановить работу программы после произошедшего исключения или же выполнить некоторые действия и повторно возбудить исключение , чтобы вызывающий фрагмент также имел возможность перехватить его . Кроме того , catch может сделать то , что сочтет нужным , и прекратить свою работу — в этом случае управление передается оператору , следующему за оператором try ( после выполнения условия finally, если оно имеется ). Универсальное условие catch ( например , перехватывающее исключения типа Exception) обычно говорит о плохо продуманной реализации , поскольку оно будет перехватывать все исключения , а не только то , которое нас интересует . Если воспользоваться подобным условием в своей программе , то в результате при возникновении проблем с атрибутами будет обрабатываться , скажем , исключение ClassCastException. Условия catch в операторе try просматриваются поочередно , от первого к последнему , чтобы определить , может ли тип объекта — исключения присваиваться типу , объявленному в catch. Когда будет найдено условие catch с подходящим типом , происходит выполнение его блока , причем идентификатору в заголовке catch присваивается ссылка на объект — исключение . Другие условия catch при этом не выполняются . С оператором try может быть связано произвольное число условий catch, если каждое из них перехватывает новый тип исключения . Поскольку условия catch просматриваются поочередно , перехват исключения некоторого типа перед перехватом исключения расширенного типа является ошибкой . Первое условие всегда будет перехватывать исключение , а второе — никогда . По этой причине размещение условия catch для исключения — суперкласса перед условием для одного из его подклассов вызывает ошибку во время компиляции : class SuperException extends Exception < >class SubException extends SuperException < >class BadCatch < public void goodTry() < /* НЕДОПУСТИМЫЙ порядок перехвата исключений */ try < throw new SubException(); >catch (SuperException superRef) < // Перехватывает и SuperException, и SubException >catch (SubException subRef) < // Никогда не выполняется >> >
В каждом операторе try обрабатывается только один исключительный случай . Если catch или finally возбуждают новое исключение , то условия catch данного try не рассматриваются повторно . Код в условиях catch и finally находится за пределами защиты
