2015-06-11 10 views
5

Chciałbym napisać kod bezpiecznikowy. Oto, co starałem:Pisanie klasy w bezpiecznym trybie

public interface ResultTronsformer<T>{ 
    public T tranform(T t); 
} 

public class BigDecimalTransformer implements ResultTRansformer<BigDecimal>{ 

    public BigDecimal transform(BigDecimal t){ 
     return t.setScale(0); 
    } 
} 

Teraz zdefiniować interfejs kolumny, która wygląda jak

public interface Column{ 
    public ResultTransformer<?> getTransformer(); 
} 

i chciałby używać go w metodzie

public class Report{ 
    private Map<Column, Object> columnValuePairs; 

    public void putIntoACollection(Column c, Object columnsValue){ 
     ResultTransformer<?> rt = c.getTransformer(); 
     columnValuePairs.put(c, rt.transform(o)); //Error: Couldn't convert Object 
                //to the capture of wildcard 
    } 
} 

Jak mogę zmienić kolejność projekt do osiągnięcia pożądanego bezpieczeństwa typu? Może powinienem zamiast tego robić sprawdzanie typów w czasie wykonywania (zgłaszanie wyjątku)?

+1

System typu Java nie jest silnie enoug h, aby to zrobić. Możesz jednak korzystać z niektórych bibliotek otoki, które udostępniają ** zewnętrzny ** bezpieczny interfejs API. Na przykład: http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/ClassToInstanceMap.html – Max

+0

@Max Intersting, nie słyszałem o tym podejściu. Może możesz podać przykład jako asnwer? – user3663882

Odpowiedz

1

Można zmienić klasę Column i uczynić go parametryzowane:

public interface Column<T> { 
    public ResultTransformer<T> getTransformer(); 
} 

Następnie trzeba parametrize metodę putIntoACollection (nie trzeba parametrize Report):

public class Report { 
    private Map<Column, Object> columnValuePairs; 

    public <T> void putIntoACollection(Column<T> c, T columnsValue) { 
     final ResultTransformer<T> rt = c.getTransformer(); 
     columnValuePairs.put(c, rt.transform(columnsValue)); 
    } 
} 

ten sposób nigdy trzeba użyć typu przechwytywania.

Oto przykład jak można go używać:

private class BigDecimalColumn implements Column<BigDecimal> { 
    @Override 
    public ResultTransformer<BigDecimal> getTransformer() { 
     return new BigDecimalTransformer(); 
    } 
} 

public static void main(String[] args) { 
    final Report report = new Report(); 
    report.putIntoACollection(new BigDecimalColumn(), new BigDecimal("3.14")); 
} 
+0

Ale jak myślisz, w jaki sposób mogę zaimplementować interfejs kolumny? Na przykład potrzebuję 'BigDecimalColumn', która z kolei jest podklasą' ResultTransformer '. Otrzymamy komunikat odznaczający ostrzeżenie. – user3663882

+0

Masz rację. Zaktualizowałem swoją odpowiedź. –

+0

@ user3663882 to pytanie będzie niemożliwe do rozwiązania w sposób, który nie generuje tego ostrzeżenia.Jedyne, co można zrobić, to napisać kod bezpieczeństwa typu provably i dodać '@SuppressWarning (" niezaznaczony ")'. – Max

1

Gdy pojawi się transformator, trzeba określić typ bo kompilator nie będzie wiedział, to w tym czasie.

Możliwe rozwiązanie to dodanie klasy jako parametru do getTransformer kolumny i zwrócenie specjalistycznego ResultTransformer.

public interface ResultTransformer<T> { 
    public T transform(T t); 
} 

public interface Column{ 
    public <T> ResultTransformer<T> getTransformer(Class<T> theClass); 
} 

public class Report{ 
    private Map<Column, Object> columnValuePairs; 

    public void putIntoACollection(Column c, Object o){ 
     ResultTransformer<Object> rt = c.getTransformer(Object.class); 
     columnValuePairs.put(c, rt.transform(o)); 
    } 

public interface ResultTransformer<T> { 
    public T transform(T t); 
} 

Innym sposobem byłoby uogólnienie kolumny interfejsu.

+0

Ponownie, implementacja interfejsu kolumny będzie niebezpieczna. Albo nie rozumiem czegoś ...? – user3663882

+0

Można również uogólnić interfejs kolumny na kolumnę , ale spowoduje to powiązanie parametrów typu kolumny z wynikiem ResultTransformer, który nie jest zależnością, którą wybiorę. W przyszłości możesz chcieć mieć wiele transformatorów dla jednej Kolumny i to nie będzie już możliwe. Zależy to od tego, która klasa będzie odpowiedzialna za określenie typu powrotu transformatora. Jeśli jest to kolumna, należy utworzyć kolumnę . Jeśli jest to ResultTransformer, to jest to dobry sposób. – Maarten

2

Możesz myśleć o kolumnie tak, jakby był to jakiś kontener, który ma określony typ. W ten sposób możesz wprowadzić typ ogólny w deklaracji kolumny.

public interface Column<T>{ 
    public ResultTransformer<T> getTransformer(); 
} 

Następnie można zmienić metodę Zgłoś następująco:

public <T> void putIntoACollection(Column<T> c, T columnsValue){ 
     ResultTransformer<T> rt = c.getTransformer(); 
     columnValuePairs.put(c, rt.transform(columnsValue)); 
} 
+0

Witamy w stackoverflow - to świetny pierwszy post! Spot na miejscu. – Bohemian

+0

Nie rozwiązuje to problemu, ponieważ można wstawiać tylko kolumny jednego typu. Mięso problemu polega na tym, że kolumny przechowują różne typy. – Max

+0

Nie mam teraz całego kontekstu tej funkcji raportowania, ale rozumiem, że kolumna to kontener zawierający pewną wartość. Możesz utworzyć dowolną liczbę kolumn dla każdego obsługiwanego typu. Wartość w kolumnie można przekształcić przez odniesienie ResultTransformer. Ten ResultTransformer, jako bezpieczny składnik typu, powinien działać na tym samym typie danych, który jest przechowywany w kolumnie. Jeśli będziesz mieć jakąkolwiek hierarchię typów, pojedynczy ResultTransformer może zostać rozszerzony do działania na wielu typach na tym samym poziomie hierarchii. – ppro

0

Możemy porzucić wszelkie pozory, wystarczy użyć Boga przeklęty typ surowy

 ResultTransformer rt = c.getTransformer(); 

A bardziej pretensjonalne rozwiązanie -

static <T> T transform (ResultTransformer<T> rt, Object obj) 
    { 
     T t = (T)obj; // unchecked cast 
     return rt.transform(t); 
    } 


public void putIntoACollection(Column c, Object obj){ 
    ResultTransformer<?> rt = c.getTransformer(); 
    columnValuePairs.put(c, transform(rt, obj)); 

} 
Powiązane problemy