Wyjątki - blok try catch

W większości programów, które będziemy mieli okazję napisać w swoim życiu występują błędy różnego rodzaju. Niektóre z nich są spowodowane naszą błędną implementacją, a inne na przykład błędnymi danymi podanymi przez użytkownika. Byłoby więc absurdem, gdybyśmy nie otrzymali od twórców języka Java prostego mechanizmu pozwalającego na efektywną i stosunkowo prostą ich obsługę. Lekcja ta nauczy Cię jak zgłaszać i obsługiwać wyjątki, a następnie spróbujemy także stworzyć własny wyjątek.

Kurs Java

Zaczynając od początku. Przypomnijmy sobie lekcję poświęconą tablicom. Zadeklarujmy tablicę o rozmiarze 5, a następnie spróbujmy wyświetlić element o indeksie wskazanym przez użytkownika.

import java.util.Scanner;
 
 public class Odczyt{
   public static void main(String[] args){
 	  int tab[] = {1,2,3,4,5};
 	  Scanner odczyt = new Scanner(System.in);
 	  int index = -1;
 
 	  System.out.println("Podaj indeks tablicy, który chcesz zobaczyć: ");
 	  index = odczyt.nextInt();
 
 	  System.out.println(tab[index]);
   }
 }

Oczywiście po uruchomieniu program działa poprawnie. W przypadku, gdy podamy wartość z zakresu od 0 do 4 (ponieważ jak pamiętamy indeksy tablicy w Javie numerowane są od 0) otrzymamy oczekiwaną wartość. Jednak, gdy podamy na przykład wartość 5 (typowy błąd popełniany przez początkujących programistów, na szczęście stosunkowo prosty do wykrycia) program się "wysypie" pokazując błąd:

"Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5 at Odczyt.main(Odczyt.java:12)"

Jest to spowodowane oczywiście tym, że komórka tablicy o indeksie 5 w naszym przypadku nawet nie istnieje.

Jak sobie z tym poradzić? Można oczywiście wstawić odpowiednią instrukcję warunkową if, która sprawdzi wprowadzony przez nas argument:

import java.util.Scanner;
 
 public class Odczyt{
   public static void main(String[] args){
 	  int tab[] = {1,2,3,4,5};
 	  Scanner odczyt = new Scanner(System.in);
 	  int index = -1;
 
 	  System.out.println("Podaj indeks tablicy, który chcesz zobaczyć: ");
 	  index = odczyt.nextInt();
 
 	  if(index>=0 && index<tab.length)
 		  System.out.println(tab[index]);
 	  else
 		  System.out.println("Niepoprawna wartość, rozmiar tablicy to: "+tab.length);
   }
 }

Teraz, program działa poprawnie. W przypadku wprowadzenia indeksu spoza zakresu (na przykład wartości ujemnej, lub tak jak wcześniej wykraczającej poza rozmiar tablicy) zostanie wyświetlony komunikat "Niepoprawna wartość" oraz rozmiar naszej tablicy.

Wyobraźmy sobie teraz sytuację, że niedopuszczalne jest, aby program nagle zakończył swoją pracę, ponieważ dalszy kod powinien się zawsze wykonać. W przypadku, gdy użytkownik wprowadzi zupełnie przypadkowo na przykład literę, lub ciąg znaków, niestety otrzymamy jeszcze bardziej nieprzyjemny błąd niż ostatnio - w tym przypadku InputMismatchException. Niestety tym razem nie jest już tak prosto jak poprzednio prawda?

Cóż czas więc powiedzieć, jak sobie z takim problemem poradzić. Z pomocą przychodzi tutaj blok try catch.

Schematycznie wygląda tak:

try{
 	kod programu mogący generować wyjątki
 }
 catch (TypWyjątku1 a){ Obsługa wyjątku a }
 catch (TypWyjątku2 b){ Obsługa wyjątku b }
 ...
 finally{ Blok instrukcji, który wykona się niezależnie, czy wyjątki wystąpią, czy nie }

Zastosujmy więc najpierw blok try catch zamiast instrukcji if else.

import java.util.Scanner;
 
 public class Odczyt{
   public static void main(String[] args){
 	  int tab[] = {1,2,3,4,5};
 	  Scanner odczyt = new Scanner(System.in);
 	  int index = -1;
 
 	  System.out.println("Podaj indeks tablicy, który chcesz zobaczyć: ");
 	  index = odczyt.nextInt();
 
 	  try {
 		  System.out.println(tab[index]);
 	  } catch (ArrayIndexOutOfBoundsException e) {
 		  System.out.println("Niepoprawny parametr, rozmiar tablicy to: "+tab.length);
 	  }
   }
 }

Początek jest taki sam i zrozumiały. Tworzymy tablicę, obiekt Scanner do pobrania danych od użytkownika oraz zmienną index do zapamiętania indeksu (można by ją pominąć).

Następnie w bloku try{ } próbujemy wyświetlić wartość komórki tablicy o indeksie podanym przez użytkownika. Przy wprowadzeniu np. 5 oczywiście wystąpi błąd (bo w javie tablice numerujemy od 0, u nas jest 5 elementów, więc maksymalny indeks wynosi 4), wykonywana jest więc obsługa odpowiedniego wyjątku, a tym przypadku ArrayIndexOutOfBoundsException (można także spróbować przechwycić ogólniejszy wyjątek IndexOutOfBoundsException - działanie byłoby takie samo). Wykonuje się blok catch{ }, w którym zadeklarowano wyjątek odpowiedniego typu i wyświetla się ten sam komunikat co poprzednio.

Wygląda to może mniej czytelnie niż rozwiązanie z instrukcją if, ale trzeba zauważyć, że użyteczność rośnie wraz z rozbudową kodu w bloku try{ } oraz rosnącą ilością sytuacji wyjątkowych, które mogą nastąpić.

Kurs Java

Wróćmy więc do problemu wprowadzenia nieodpowiednich danych przez użytkownika. Przyjmijmy, że dopóki użytkownik nie wprowadzi poprawnej wartości to będzie o nią proszony do skutku. Ze względu na specyfikację metody nextInt, zastąpimy ją odczytem danych przy pomocy obiektu BufferedReader, metody readLine() pobierającej od użytkownika String, a następnie dokonamy rzutowania na wartość int przy pomocy statycznej metody parseInt() klasy osłonowej Integer - w tym ostatnim kroku, jeśli zostanie wygenerowany wyjątek typu NumberFormatException poprosimy użytkownika o ponowne podanie wartości. Brzmi może zawile, ale po spojrzeniu na poniższy kod wszystko powinno stać się jasne:

import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
 
 public class Odczyt{
   public static void main(String[] args){
 	  int tab[] = {1,2,3,4,5};
 	  BufferedReader odczyt = new BufferedReader(new InputStreamReader(System.in));
 	  int index = -1;
 
 	  System.out.println("Który element tablicy chcesz zobaczyć: ");
 	  boolean czyPoprawne = false;
 
 	  while(!czyPoprawne) {
 		  try {
 		  index = Integer.parseInt(odczyt.readLine());
 		  } catch (NumberFormatException n) { System.out.println("Niepoprawne dane! " +
 		  		"\n Który element tablicy chcesz zobaczyć: ");
 		  } catch (IOException e) { System.out.println("Błąd odczytu danych");
 		  }
 
 		  czyPoprawne = index == -1? false : true;
 	  }
 
 	  try {
 		  System.out.println(tab[index-1]);
 	  } catch (ArrayIndexOutOfBoundsException e) {
 		  System.out.println("Niepoprawny parametr, rozmiar tablicy to: "+tab.length);
 	  }
   }
 }

W następnych lekcjach zajmiemy się zgłaszaniem wyjątków w inny sposób oraz tworzeniem własnych klas wyjątków.

Dyskusja i komentarze

Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.

Poniżej znajdziesz archiwalne wpisy z czasów, gdy strona była jeszcze hobbystycznym blogiem.

shpyo

Bardzo dobrze opisane! Dziękuję.

Tomek

Po zapoznaniu się z tą lekcją nasuwa mi sie takie pytanie, kiedy mam korzystać z Scanner, a kiedy z BufferedReader?

Amman

Dwa pytania: 1. Czym są znaki n i e przy rodzaju błędów? 2. Czy można prosić o wyjaśnienie linijki "czyPoprawne = index == -1? false : true;"? Domyślam się jaki ma efekt, ale nie rozumiem w jaki sposób ona działa. Pozdrawiam

Slawek

n, e to po prostu nazwy wyjątku jaki przechwytujemy. Zapis catch (ArrayIndexOutOfBoundsException e) pozwala nam na przechwycenie wyjątku ArrayIndexOutOfBounds, a e to zmienna tego typu, która pozwoli nam wyświetlić informacje o nim. Co do drugiego pytania - faktycznie nie napisałem o tym w lekcjach, będę musiał uzupełnić. Tutaj jest napisane co to takiego: http://javastart.pl/efektywne-programowanie/javatraps-002/ w skrócie, "jeśli index wynosi -1, to przypisz do zmiennej czyPoprawny wartość false, w innym przypadku przypisz wartość true".

Paweł

Dlaczego do zmiennej index przypisujemy -1? Po uruchomieniu programu i wpisaniu w konsole wartości -1 nic sie nie dzieje, program "czeka" na dalsze wpisanie wartości. I w jakim przypadku zostanie zwrócony wyjątek catch (IOException e) { System.out.println("Błąd odczytu danych"); Dzięki za cierpliwość i wcześniejsze odpowiedzi;)

Tomek

Bo gdybyśmy nic nie przypisali to wartość domyślna wynosiłaby 0. Wpisz sobie jako "element tablicy, który chcesz zobaczyć" 0 i sprawdź, co się stanie. Skrypt od razu przechodzi do ostatniego try. PS: Sam się uczę więc poprawcie mnie jeśli się pomylę :)

dakar

Jak ja wywalam -1 i zostawiam tylko int index; to nic to nie zmienia.

Paweł

I jeszcze jedno. try { System.out.println(tab[index-1]); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Niepoprawny parametr, rozmiar tablicy to: "+tab.length); } Jak zrobić żeby instrukcja wykonywała się dopóki poda się właściwy parametr bo coś nie mogę sobie z tym poradzić...

Tomek

Robiłem rozwiązanie dla Ciebie. Nie działa ale może uda Ci się poprawić mój błąd. Jeśli tak to wytłumacz mi co jest źle. 1 import java.io.BufferedReader; 2 import java.io.IOException; 3 mport java.io.InputStreamReader; 4 5 public class Odczyt{ 6 public static void main(String[] args){ 7 int tab[] = {100,200,300,400,500}; 8 BufferedReader odczyt = new BufferedReader(new InputStreamReader(System.in)); 9 int index = -1; 10 11 boolean czyPoprawne = false; 12 13 while(!czyPoprawne) { 14 try { 15 System.out.println("Który element tablicy chcesz zobaczyć: "); 16 index = Integer.parseInt(odczyt.readLine()); 17 System.out.println(tab[index-1]); 18 } catch (NumberFormatException n) { System.out.println("Niepoprawne dane! "); 19 } catch (IOException e) { System.out.println("Błąd odczytu danych"); 20 } catch (ArrayIndexOutOfBoundsException e) { 21 System.out.println("Niepoprawny parametr, rozmiar tablicy to: "+tab.length); 22 } 23 czyPoprawne = index == -1? false : true; 24 } 25 } 26 } Nie wiem dlaczego, jeśli wpiszę np. 12 to trzeci błąd(linijka 20) nie powtarza poleceń try(linijki 15,16,17). Działa na pewno, bo wyświetla komunikat o błędzie(linijka 21).

Slawek

Masz błędny warunek w wyrażeniu warunkowym. Zauważ, że zmienną index zmieniasz z -1 na jakąś wartość niezależnie, czy jest ona poprawna, czy nie. Dodaj w obsłudze wyjątku linijkę index=-1; albo zmień warunek na coś podobnego: czyPoprawne = (index < 0) || (index > tab.length) ? false : true;

TomekA12

Twoja rada poskutkowała. Nie wziąłem pod uwagę, że jeśli podamy niezgodną z typem index'u wartość to ona się nie zmieni z -1. Przyzwyczajenia z PHP ;) Dzięki za pomoc.

Janosch

Sławku, robię tak jak mówisz, ale możesz zobaczyć dlaczego nie wyświetla mi komunikatu w momencie kiedy podaję wartość większą od rozmiaru tabeli? Kod na forum http://forum.javastart.pl/Thread-Wyj%C4%85tki-i-blok-try-catch?pid=1032#pid1032

Witek

Witam Mam pytanie odnośnie sposobu zgłaszanie typu wyjątku np.: catch (ArrayIndexOutOfBoundsException e) Gdy robiłem te ćw. w Eclipsie to przy bloku try { } catch { } dostawałem taki zapis catch(Exception e) sprawdziłem obie opcje i wszystkie ćw. działają tak samo. Czy błędem będzie jeśli będę korzystał z podpowiedzi, która jest w Eclipsie, czy też muszę wpisywać tak jak podałeś w przykłądzie.

Slawek

Eclipse podpowiada najogólniejszy typ, który można obsłużyć. Przykładowo jeżeli w bloku try catch mamy fragmenty, które mogą zgłaszać 3 różne rodzaje wyjątków dziedziczące z Exception, to można je albo przechwycić wspólnie tak jak napisałeś, albo przechwycić każdy z osobna, co pozwala na wyszczególnienie specyficznych sytuacji.

Witek

Witam Dzięki za odpowiedź. Twój post wiele wyjaśnił.

blezus

chyba nie zrozumiałem do końca tego bloku try catch, dlaczego nie mogę go wykorzystać przy "index = odczyt.nextInt();" ? <code> import java.util.Scanner; public class Odczyt { public static void main(String[] args) { int tab[] = {1, 2, 3, 4, 5}; Scanner odczyt = new Scanner(System.in); int index = -1; System.out.println("Podaj indeks tablicy, który chcesz zobaczyć: "); try { index = odczyt.nextInt(); } catch (InputMismatchException e) { System.out.println("Tak nie może być"); } try { System.out.println(tab[index]); } catch (ArrayIndexOutOfBoundsException d) { System.out.println("Niepoprawny parametr"); } System.out.println("Koncowy tekst."); } } </code>

Slawek

Musisz zaimportować klasę wyjątku: <pre type="code" class="java">import java.util.InputMismatchException;</pre>

Janosch

Rozumiem, że jeżeli używamy klasy Scanner to musimy użyć wyjątku: import java.util.InputMismatchException - w celu wychwycenia znaków będących nie cyframi w naszym przykładzie? Czy oprócz tego są jeszcze jakieś różnice pomiędzy zastosowaniem klasy Scanner a klasy BufferedReader do naszego przykładu oprócz stosowania innych klas wyjątków do każdego z nich?

blezus

jeszcze jedna rzecz mnie ciekawi, "index = Integer.parseInt(odczyt.readLine());" - o co tutaj dokładnie chodzi? dlaczego jeśli nie użyję dla tej linii bloku try catch to dostaje błąd że wyjątek dla tej linii musi być zadeklarowany?

Slawek

Wyjątki generalnie dzielimy na dwie grupy, z których jedna to wyjątki, które muszą być obsłużone, a druga to takie, które możemy, ale nie musimy obsługiwać. Te drugie mają taką cechę, ponieważ zazwyczaj albo nie możemy z nimi zrobić nic sensownego, albo dużo lepiej jest poprawić kod tak, aby tym wyjątkom zapobiegać.

noi

a gdzie lekcja z tablicami ?

Ppp

Jakoś tak imho średnio opisany jest ta lekcja. Jeśli dobrze rozumiem, instrukcje w try są "cofane" w przypadku złapania wyjątku? Bo chyba inaczej trój-operator w zasadzie ustawiał by bool'a na true za każdym razem gdy podać coś innego niż -1 :/ ?

DDO

Jak wiesz że coś nie działą to tego nie wykonujesz. Jak wyskoczy błąd(wyjątek) to kod jest nie ważny

odp

mam pytanie co do tego " boolean czyPoprawne = false;" "czyPoprawne = index == -1? false : true;" pętla while wykonywana jest jeśli warunek jest prawdziwy więc warunek boolean zatwierdzony jako fałszywy jest fałszywy to wychodzi na to że jest prawdziwy?

Pav

Co oznacza "!" w 'while(!czyPoprawne) '

Sławek Ludwiczak

! (wykrzyknik) to zaprzeczenie, czyli "dopóki NIE poprawne" wykonuj to co jest w pętli.

Karol

Witam Czy mogłyby mi ktos wytłumaczyc krok po kroku co oznacza zapis czyPoprawne = index == -1? false : true; z góry dziekuje

Krzysztof

A nie możnaby np. jak w C++. czyPoprawne = (index != -1); ?

Damian

Chyba tak, sprawdź. Zapis czyPoprawne = (index == -1)?false : true; funkcjonuje zarówno w C++ jak i w Javie.

DDO

Jak i w PHP :-). Tz. operator trójkowy

Sławek Ludwiczak

to samo co: <pre name='code' class='java'> if(index==-1){ czyPoprawne = false; } else { czyPoprawne = true; } </pre>

Mikios

Nie rozumiem, zastosowania linijki int index = -1; Może mi ktoś wytłumaczyć?

Damian

To jest przyjęcie domyślnie indeksu który na pewno jest poza obszarem tablicy (indeksy tablicy numerowane są liczbami całkowitymi od 0 w górę). Generalnie wartość i tak jest zaraz potem nadpisywana przez "index = odczyt.nextInt();", więc myślę że taki sposób zapisu może być traktowany jako sugestia, że zmienna jest podatna na przyjmowanie różnego rodzaju błędów i warto się o nią jakoś lepiej zatroszczyć. O prawdziwych intencjach takiego zapisu może Cię poinformować tylko autor, ja Cię mogę zapewnić, że wartość zmiennej index w tej linijce nie ma najmniejszego znaczenia, jeśli chodzi o działanie programu :).

grzecho

hmmm. ok rozumiem wiekszosc, ale dlaczego trzeba koniecznie zainicjować wartość index, a nie mozna po prostu zostawic tylko zadeklarowanej zmiennej "int index;"

bf

Cześć, kolejne pytanie. Czy w Javie można skonstruować obsługę wyjątków tak jak w PL/SQL tzn. wymieniasz wyjątki, które chcesz obsłużyć + można użyć identyfikatora OTHERS, czyli po prostu wszystkie inne wyjątki. Z przykładu tutaj przedstawionego wynika, że jeżeli nie trafimy na wymieniony wyjątek do obsłużenia to i tak program przejdzie do następnej części, czyli obsługi kodu bez wyjątków. Pozdrawiam,

Piotrek

Da się. Wystarczy, że dodasz kolejny wyjątek Exception. Wtedy jak wcześniej wyszczególnione nic nie złapią to ten jest najogulniejszy i złapie wszystko co nie zostało wcześniej wyszczególnione. try { // sprawdzana funkcja } catch(/* jakiś wyjątek */) { // obsługa jakiegoś wyjątku }catch(Exception e) { // obsługa wszystkich pozostałych wyjątków }

Janosch

Czyli można to interpretować w ten sposób? try { //sprawdzana funkcja } catch(wyjatek1) {"wystąpił błąd 1"} catch(wyjątek2) {"wystąpił błąd 2"} catch(Exception e) {"wystąpił błąd nieznany"} ??

Arek

Kiedy pętla while wykona tę część kodu? catch (IOException e) { System.out.println("Błąd odczytu danych");

Ignacy

Cześć, wiecie może po co tworzyć int index = -1; skoro można to zrobić od razu w linijce int index = odczyt.nextInt(); ?

Marcin Kunert

Masz rację, twoja wersja też jest poprawna.

Faraen

Witam, mam pytanko, czy nie można by zamiast pisać skomplikowanej linijki: " czyPoprawne = index == -1? false : true " , której nie da się dopasować do każdego programu, nie można by zapisać przypisania wartości true do boolean czyPoprawnie w bloku try, w taki sposób: try { index = Integer.parseInt(odczyt.readLine()); czyPoprawnie = true; // ta linijka zostanie wykonana tylko jeśli index jest przypisany poprawnie } catch ... Czy może mylę się, lub nie jest to uniwersalna zasada?

name

Wydaje mi się, że tak jest trochę prościej: import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class Odczyt{ public static void main(String[] args){ int tab[] = {1,2,3,4,5}; BufferedReader odczyt = new BufferedReader(new InputStreamReader(System.in)); int index; System.out.println("Który element tablicy chcesz zobaczyć: "); boolean czyZle = true; while(czyZle) { try { index = Integer.parseInt(odczyt.readLine()); System.out.println(tab[index-1]); czyZle = false; } catch (NumberFormatException n) { System.out.println("Niepoprawne dane! " + "\n Który element tablicy chcesz zobaczyć? "); } catch (IOException e) { System.out.println("Błąd odczytu danych"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Niepoprawny parametr, rozmiar tablicy to: "+tab.length+"\nPytam ponownie - który element tablicy chciałbyś zobaczyć? "); } } } }

thingness

Dlaczego dalej już nie ma zadanek ?

Janosch

Jeżeli robiłeś poprzednie zadania i używałeś skanerów do wprowadzania danych przez użytkownika to możesz tam pododawać wyjątki, w ten sposób sobie to utrwalisz.

Verteks

W ostatnim listingu jest pewna nieścisłość przy wyświetlaniu wartości na konsolę - zamiast tab(index-1) według mnie powinno być po prostu tab(index). Dlaczego? Ponieważ elementy tablicy liczymy od zero, a nie od jeden, więc gdy ja jako użytkownik piszę, że chce wyświetlić tab[0], to piszę w konsoli zero, a nie jeden. Jeśli moje podejście do sprawy jest mylne - proszę mnie sprostować i wytłumaczyć, dlaczego ma być tak, a nie inaczej.

Verteks

O, to nie jestem człowiekiem w takim razie, bo bez zastanowienia wpisałem 0 i okazało się, że podałem błędną wartość, co mnie bardzo zdziwiło :P Jak najbardziej zgadzam się z tym, co Janosch napisał na temat użytkowników programów komputerowych, a sam chciałem tylko zwrócić fakt na numerowanie elementów tablicy.

Janosch

Myślę, że autorowi chodziło o to, że człowiek liczy intuicyjnie od 1, więc raczej chcąc wyświetlić pierwszy element z tablicy wprowadzi wartość 1 a nie 0. To taki ukłon programisty do użytkownika, żeby program był jak najbardziej intuicyjny w użytkowaniu.. Bo programy są pisane dla normalnych użytkowników, których nie musi interesować i nie muszą wcale wiedzieć, że Java liczy od zera.

Janosch

Sławku albo Dawidzie. Czy możecie mi powiedzieć, czy poprawne jest umieszczenie warunku końca pętli w bloku try? Coś takiego: try { zmienna = Integer.parseInt(input.readLine()); czyPoprawne = true; System.out.println("Wprowadziłeś zmienną: " + zmienna); } catch(NumberFormatException e) {System.out.println("Wprowadź poprawnie liczbę: ");} catch(IOException f){System.out.println("IOException");} catch(Exception g){System.out.println("Inny błąd");} }

Sławek Ludwiczak

Przechwytywanie Exception prawie nigdy nie ma sensu, chyba, że nie chce Ci się przechwytywać i obsługiwać 10 wyjątków różnego typu (np. gdy piszesz jakiś prosty skrypt, z którego nikt inny nie będzie korzystał). W przykładzie, który podałeś blok z Exception nie ma prawa się wykonać, bo nic wewnątrz bloku try nie wygeneruje wyjątku ogólniejszego niż NumberFormatException lub IOException, a te przechwytujesz wcześniej

Rafal

Czy w Javie jest cos takiego jak TryParse

lolo

A co to jest?

Rafal

Kiedy metoda parse sie nie uda, nie wyrzuca ci wyjatku

lolo

Z tego co się orientuję to w standardzie niema takiej funkcji ale łatwo można napisać sobie taką funkcję. A poza tym chyba dobrze jak zwróci wyjątek który można obsłużyć.

aleksanderwiel

Ktoś może się orientuje, jak się nazywają wyjątki, np. - gdy do zmiennej "int" będziemy chcieli przypisać jakiś tekst - gdy podamy złą liczbę, która jest typu "double" w zmiennej "int" - gdy podamy liczbę wykraczającą poza zakres danej zmiennej przykładowo "int", a zakresem jest liczba mieszcząca się pomiędzy -2 147 483 648, a 2 147 483 648 ?