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 <: a="" b="" close="" unit=""> 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 <b>using</b> ze Scali czy C#.
*
* @author bartlomiejk
*
*/
public class Using {
/**
* Wykonuje operacje z body poczym wywołuje <samp>close()</samp> z t.
*
* @param <t>
* typ generyczny z metodą <samp>close()</samp>
* @param t
* zasób.
* @param body
* operacja do wykonania.
* @throws RuntimeException
* w dwóch przypadkach:
* <ul>
* <li>Brak metody <samp>close()</samp>.</li>
* <li>Security Manager niepozwala na wywołanie metody <samp>close</samp>.</li>
* </ul>
*/
public static <t> void using(T t, Unit<t> body) throws RuntimeException {
Class extends Object> 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 <t>
* typ generyczny z metodą <samp>close()</samp>
* @param t
* zasób.
* @param body
* operacja do wykonania.
* @throws RuntimeException
* w dwóch przypadkach:
* <ul>
* <li>Brak metody <samp>close</samp>.</li>
* <li>Security Manager niepozwala na wywołanie metody <samp>close</samp>.</li>
* </ul>
*/
public static <t> void s_using(T t, Unit<t> body) {
synchronized (t) {
using(t, body);
}
}
/**
* Interfejs jednostki kodu wywołanej w ramach <samp>try</samp>/<samp>finally</samp>.
*
* @author bartlomiejk
*
* @param <t>
* typ generyczny wykorzystywanego zasobu.
*/
public interface Unit<t> {
/**
* Wywoływana jednostka kodu.
*
* @param t
* zasób.
*/
public void perform(T t);
}
}</t></t></t></t></t></t></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<resultset>() {
@Override
public void perform(ResultSet t) {
try {
while (t.next()) {
//...
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});</resultset>
To tyle. Raz jeszcze kod jest ciekawostkowy, niedopracowany i pewno coś będzie wywalał. Zatem używacie na własne ryzyko. Licencja beerware.