Zapominamy o metodzie close

W C# mamy using, czyli taką konstrukcję, która przypomina trochę żonę – kurę domową (zakładając, że program to małżeństwo, a programista to mąż). Maż idzie do kibla robi swoje i zapomina wywołać close na desce. Żona samodzielnie zamknie deskę i nie będzie marudzić.
W Javie żona ma inne podejście. Nic nie mówi i nic nie robi. Po prostu jak raz na pewien czas z kibla będzie ulatniać się smród ponieważ nie zamknięto deski to maż w końcu nauczy się, żeby deskę opuszczać (nazywamy to tresurą małżeńską). Tu rolę gderającej i przypominającej o opuszczeniu deski teściowej pełnią narzędzia do sprawdzania poprawności kodu. Tak jak teściową zapraszamy do siebie raz na pewien czas tak i raz na pewien czas kod warto przepuścić przez findbuga, checkstyle czy PMD.
W Scali można zrobić jeszcze ciekawszą rzecz. Otóż o ile nie ma using to można sobie je napisać (za `Beginning Scala` D. Pollaka):

Listing 1. Definicja using w Scali

object Using{
    def using[A <: {def close(): Unit}, B](param: A)(f:A => B): B = {
       try{ 
          f(param)
       } finally {
          param.close();
       }
    }
}

I później użyć w kodzie:

Listing 2. Użycie using w Scali

using(statement.executeQuery("select * from dual")){ resultSet =>{
     // reszta kodu
  }
}

Taka konstrukcja przypomina żonę, która okazjonalnie zamknie deskę (o ile będzie w pobliżu czytaj mąż użyje using).
Generalnie w tym przypadku definiujemy funkcję, która jako parametr przyjmuje obiekt klasy, w której zdefiniowano metodę close() i dodatkowo przyjmuje jako drugi element coś, nazwane f co dokonuje transformacji A na B. To jest znany z Pythona tzw. „Duck Typing” (Jeżeli coś chodzi jak kaczka i kwacze jak kaczka to jest to kaczka – wnioskowanie typu na podstawie bebechów).

Jak widać konstrukcja jest fajna i przydatna. Zatem można by spróbować przenieść ją bezpośrednio do Javy (to też jest tresura małżeńska). Oczywiście jest to zabawka, której używanie w kodzie produkcyjnym jest średnio rozważne… ale niema ryzyka nie ma zabawy. Najwyżej się coś wywali.

Listing 3. Definicja using w Javie

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Emulator using ze Scali czy C#.
 * 
 * @author bartlomiejk
 * 
 */
public class Using {

	/**
	 * Wykonuje operacje z body poczym wywołuje close() z t.
	 * 
	 * @param 
	 *            typ generyczny z metodą close()
	 * @param t
	 *            zasób.
	 * @param body
	 *            operacja do wykonania.
	 * @throws RuntimeException
	 *             w dwóch przypadkach:
	 *             
    *
  • Brak metody close().
  • *
  • Security Manager niepozwala na wywołanie metody close.
  • *
*/ public static void using(T t, Unit body) throws RuntimeException { Class tClass = t.getClass(); try { Method method = tClass.getMethod("close"); method.setAccessible(true); try { body.perform(t); } finally { method.invoke(t); } } catch (SecurityException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } /** * Synchronizowana wersja {@link #using(Object, Unit)}. synchronizacja względem zasobu. * * @see Using#using(Object, Unit) * * @param * typ generyczny z metodą close() * @param t * zasób. * @param body * operacja do wykonania. * @throws RuntimeException * w dwóch przypadkach: *
    *
  • Brak metody close.
  • *
  • Security Manager niepozwala na wywołanie metody close.
  • *
*/ public static void s_using(T t, Unit body) { synchronized (t) { using(t, body); } } /** * Interfejs jednostki kodu wywołanej w ramach try/finally. * * @author bartlomiejk * * @param * typ generyczny wykorzystywanego zasobu. */ public interface Unit { /** * Wywoływana jednostka kodu. * * @param t * zasób. */ public void perform(T t); } }

Metoda using występuje też w synchronizowanej wersji s_using, a muteksem jest zasób. Do wykorzystania z zasobami wymagającymi synchronizacji np. plikami.

Listing 4. Użycie using w Javie

using(daoSupport.executeQuery(CACHE_SQL), new Unit() {               
	@Override                                                                   
	public void perform(ResultSet t) {                                          
		try {                                                                   
			while (t.next()) {                                                  
				//...   
			}                                                                   
		} catch (Exception e) {                                                 
			throw new RuntimeException(e);                                      
		}                                                                       
	}                                                                           
});

To tyle. Raz jeszcze kod jest ciekawostkowy, niedopracowany i pewno coś będzie wywalał. Zatem używacie na własne ryzyko. Licencja beerware.

15 myśli na temat “Zapominamy o metodzie close

  1. Jeżeli using jest jak żona, to znaczy, że wykona za nas ToiletSeat.close(), ale przy braku Shell.flush() rzuca RollException? 😀

  2. Ciekawostka historyczna: ta konstrukcja w różnych językach ma coś czterdzieści lat, a ludzie odkrywają ją teraz.

    Po raz pierwszy zaimplementował ją bodaj Kent Pitman w Maclispie we wczesnych latach 70. jako makro o nazwie IOTA, skąd via Lisp Machine Lisp i Zetalisp trafiła, już jako WITH-OPEN-FILE, do Common Lispu, a potem pojawiła się w C#, Pythonie (2.5), Clojure i Javie 7. (W międzyczasie C++-owcy wymyślili własną zepsutą/niedziałającą wersję, czyli RAII.)

  3. A nie łatwiej użyć try-with-resources z Javy 7? 🙂

    // Ech, no dobra, wiem – pewnie ktoś się nie zgadza na wyższą wersję JVM. Dziwne podejście..

  4. Tomku, można, ale nie zawsze. Wbrew pozorom update JVM do wyższej wersji w środowisku produkcyjnym nie jest taki prosty. Należy założyć, że mogą wyjść nowe ciekawe bugi zarówno w kodzie aplikacji jak i w samym JVM. Szczególnie, że Java 7 wprowadziła dużo nowości na poziomie samego języka i nikt nie zagwarantuje, że jest to rzeczywiście stabilne. Z updatem do wersji 7 na produkcji poczekałbym przynajmniej do końca tego roku.

  5. No dobra, argumentację przyjmuję. 😉 Drugie pytanie – nie lepiej jednak dać zwykły try-finally? Tutaj wprowadzamy dodatkowy kod i choć jest zrozumiały, to w razie problemów trzeba będzie i jego sprawdzać. A taki try-finally działa, jak powinno, monitoruje to mnóstwo osób na całym świecie. 😉 Czy to nie jest tylko zwykły synthetic sugar, który tak naprawdę niewiele zmienia? Tutaj też musimy pamiętać, że ma być „using” użyte, więc argument o zapominaniu odpada..

  6. Ok i dlatego piszę, że jest to tylko ciekawostka nie do użycia w realnym świecie (choć sam tego ostatnio użyłem, ale o tym kiedy indziej). Zresztą w Scali też jest to tylko ciekawostka. Tak naprawdę to using jako takie działa tylko w C# i nowej wersji Javy. Zresztą kod sam w sobie też jest upierdliwy, bo JDBC sypie na prawo i lewo SQLException, które i tak trzeba łapać.

  7. BTW – bawiłeś się już w zagnieżdżone, statyczne aspekty wewnątrz interfejsów? świetna zabawa 😀

  8. Zobacz sobie np. http://wklej.org/id/685011/

    Tworzysz sobie parę interfejsów tego typu, np. Identifable, Nameable etc. i masz z głowy powtarzanie tego samego kodu mutatorów bez potrzeby dziedziczenia po klasie abstrakcyjnej.

    Taki mixin możesz także dodawać do klas dostarczanych z zewnątrz – przy użyciu declare parents (patrz Inter-Type Declarations).

  9. O dzięki. Tu warto jeszcze powiedzieć o jednej rzeczy. Java jako taka jest fenomenalna m.n. ze względu na ogromną łatwość tworzenia rozszerzeń. Od najprostszych jak Lombok, kończąc na bardzo skomplikowanych jak aspekty w wersji AspectJ.
    Zastanawiam się tylko czy można by stworzyć taki static aspect z użyciem adnotacji. Bądź co bądź użycie słowa kluczowego aspect wymusza użycie kompilatora aspectj. Adnotacje pozwalają na skompilowanie kodu javac’iem przy założeniu, że bez aspetów przeżyjemy.

  10. Co do adnotacji – http://www.eclipse.org/aspectj/doc/released/adk15notebook/ataspectj-itds.html, sekcja dotycząca @DeclareMixin – osobiście nie bardzo do mnie przemawia taka forma, sprawę @Aspect generalnie komplikuje fakt, że adnotacje muszą być doczepione do klasy/metody/pola. Ponadto IDE nie ostrzeże cię przed błędami składni pointcutów czy innych konstrukcji, jeśli podajesz je w formie Stringowego argumentu adnotacji.. ble..

  11. PS – daty w komentarzach oszukują o godzinę 😉

  12. Widzisz coś za coś. Wolę mieć możliwość kompilacji czystym javac bez kombinowania z rozszerzeniami. Zresztą walnięte pointcuty i tak wyjdą kawałek dalej w testach.

    Co do dat to wiem. nie chce mi się walczyć z dostawcą.

Napisz odpowiedź

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax