Pomyślałem, że zaoferuję tę softball każdemu, kto chciałby ją uderzyć z parku. Co to są leki generyczne, jakie są zalety leków generycznych, dlaczego, gdzie, jak ich używać? Proszę, niech to będzie dość podstawowe. Dzięki.
Pomyślałem, że zaoferuję tę softball każdemu, kto chciałby ją uderzyć z parku. Co to są leki generyczne, jakie są zalety leków generycznych, dlaczego, gdzie, jak ich używać? Proszę, niech to będzie dość podstawowe. Dzięki.
Odpowiedzi:
Naprawdę nienawidzę się powtarzać. Nienawidzę pisać tego samego częściej niż muszę. Nie lubię powtarzać rzeczy wielokrotnie z niewielkimi różnicami.
Zamiast tworzyć:
class MyObjectList {
MyObject get(int index) {...}
}
class MyOtherObjectList {
MyOtherObject get(int index) {...}
}
class AnotherObjectList {
AnotherObject get(int index) {...}
}
Mogę zbudować jedną klasę wielokrotnego użytku ... (w przypadku, gdy z jakiegoś powodu nie chcesz używać kolekcji surowej)
class MyList<T> {
T get(int index) { ... }
}
Jestem teraz 3 razy wydajniejszy i muszę zachować tylko jedną kopię. Dlaczego NIE chcesz utrzymywać mniej kodu?
Dotyczy to również klas niebędących kolekcjami, takich jak a Callable<T>lub a, Reference<T>które muszą współdziałać z innymi klasami. Czy naprawdę chcesz rozszerzyć Callable<T>i Future<T>każdą inną skojarzoną klasę, aby utworzyć wersje bezpieczne dla typów?
Ja nie.
Brak konieczności rzutowania jest jedną z największych zalet generycznych języków Java , ponieważ sprawdza typ w czasie kompilacji. Zmniejszy to prawdopodobieństwo, że ClassCastExceptions, które mogą zostać wyrzucone w czasie wykonywania, i może prowadzić do bardziej niezawodnego kodu.
Ale podejrzewam, że jesteś tego w pełni świadomy.
Za każdym razem, gdy patrzę na leki generyczne, boli mnie głowa. Uważam, że najlepszą częścią Javy jest prostota i minimalna składnia, a typy generyczne nie są proste i dodają znaczną ilość nowej składni.
Na początku też nie widziałem korzyści ze stosowania leków generycznych. Zacząłem uczyć się Javy od składni 1.4 (mimo że Java 5 była wtedy niedostępna) i kiedy napotkałem generics, poczułem, że pisanie wymaga więcej kodu i naprawdę nie rozumiałem korzyści.
Nowoczesne IDE ułatwiają pisanie kodu za pomocą typów ogólnych.
Większość nowoczesnych, przyzwoitych IDE jest wystarczająco inteligentnych, aby pomagać w pisaniu kodu za pomocą typów ogólnych, zwłaszcza w uzupełnianiu kodu.
Oto przykład tworzenia pliku Map<String, Integer>z rozszerzeniem HashMap. Kod, który musiałbym wpisać, to:
Map<String, Integer> m = new HashMap<String, Integer>();
I rzeczywiście, to dużo pisać tylko po to, by stworzyć nowy HashMap. Jednak w rzeczywistości musiałem wpisać tylko tyle, zanim Eclipse wiedział, czego potrzebuję:
Map<String, Integer> m = new Ha Ctrl+Space
To prawda, musiałem dokonać wyboru HashMapz listy kandydatów, ale w zasadzie IDE wiedziało, co dodać, w tym typy ogólne. Dzięki odpowiednim narzędziom używanie leków generycznych nie jest takie złe.
Ponadto, ponieważ typy są znane, podczas pobierania elementów z kolekcji generycznej IDE będzie działało tak, jakby ten obiekt był już obiektem zadeklarowanego typu - nie ma potrzeby rzutowania, aby IDE wiedział, jaki jest typ obiektu jest.
Główną zaletą leków generycznych jest ich dobre współdziałanie z nowymi funkcjami Java 5. Oto przykład podrzucania liczb całkowitych do a Seti obliczania jego sumy:
Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(42);
int total = 0;
for (int i : set) {
total += i;
}
W tym fragmencie kodu są obecne trzy nowe funkcje Java 5:
Po pierwsze, typy generyczne i autoboxing prymitywów pozwalają na następujące wiersze:
set.add(10);
set.add(42);
Liczba całkowita 10jest automatycznie umieszczana w ankiecie o Integerwartości 10. (I to samo dla 42). Następnie Integerjest wrzucany do tego, o Setktórym wiadomo, że ma Integers. Próba dodania a Stringspowodowałaby błąd kompilacji.
Następnie pętla for-each przyjmuje wszystkie trzy z nich:
for (int i : set) {
total += i;
}
Po pierwsze, Setzawierające Integers są używane w pętli for-each. Każdy element jest zadeklarowany jako inti jest dozwolony, ponieważ Integerjest rozpakowywany z powrotem do prymitywu int. Fakt, że następuje rozpakowanie, jest znany, ponieważ używano leków generycznych do określenia, że były one Integerprzechowywane w Set.
Generics mogą być spoiwem łączącym nowe funkcje wprowadzone w Javie 5 i po prostu sprawiają, że kodowanie jest prostsze i bezpieczniejsze. W większości przypadków IDE są wystarczająco sprytne, aby pomóc Ci z dobrymi sugestiami, więc ogólnie nie będzie to o wiele więcej pisanie.
I szczerze mówiąc, jak widać na Setprzykładzie, uważam, że wykorzystanie funkcji Java 5 może uczynić kod bardziej zwięzłym i solidnym.
Edycja - przykład bez typów ogólnych
Poniżej znajduje się ilustracja powyższego Setprzykładu bez użycia typów ogólnych. Jest to możliwe, ale niezbyt przyjemne:
Set set = new HashSet();
set.add(10);
set.add(42);
int total = 0;
for (Object o : set) {
total += (Integer)o;
}
(Uwaga: powyższy kod wygeneruje ostrzeżenie o niezaznaczonej konwersji w czasie kompilacji).
W przypadku korzystania z kolekcji innych niż ogólne typy, które są wprowadzane do kolekcji, są obiektami typu Object. Dlatego w tym przykładzie a Objectjest tym, co jest addwstawiane do zestawu.
set.add(10);
set.add(42);
W powyższych liniach w grę wchodzi autoboxing - prymitywna intwartość 10i 42są automatycznie dodawane do Integerobiektów, które są dodawane do Set. Należy jednak pamiętać, że Integerobiekty są obsługiwane jako Objects, ponieważ nie ma informacji o typie, które pomogłyby kompilatorowi dowiedzieć się, jakiego typu Setpowinien się spodziewać.
for (Object o : set) {
To jest ta część, która jest kluczowa. Powodem, dla którego pętla for-each działa, jest to, że Setimplementuje Iterableinterfejs, który zwraca Iteratorinformację o typie, jeśli występuje. ( Iterator<T>to znaczy.)
Ponieważ jednak nie ma informacji o typie, Setzwróci an, Iteratorktóry zwróci wartości w Setas Objects, dlatego element pobierany w pętli for-each musi być typu Object.
Teraz, gdy plik Objectjest pobierany z Setpliku, należy go Integerręcznie rzutować na plik, aby wykonać dodanie:
total += (Integer)o;
Tutaj, typecast jest wykonywany od an Objectdo an Integer. W tym przypadku wiemy, że to zawsze zadziała, ale ręczne rzutowanie zawsze sprawia, że czuję, że jest to delikatny kod, który może zostać uszkodzony, jeśli wprowadzona zostanie niewielka zmiana w innym miejscu. (Czuję, że każda seria ClassCastExceptionczeka na to, aby się wydarzyć, ale dygresję ...)
IntegerObecnie opakowania, w produkt inti pozostawiono do przeprowadzenia dodanie do tej intzmiennej total.
Mam nadzieję, że mógłbym zilustrować, że nowe funkcje Java 5 są możliwe do użycia z kodem nieogólnym, ale po prostu nie jest to tak przejrzyste i proste, jak pisanie kodu za pomocą typów generycznych. Moim zdaniem, aby w pełni wykorzystać nowe funkcje w Javie 5, należy przyjrzeć się rodzajom generycznym, jeśli przynajmniej zezwala na sprawdzanie czasu kompilacji, aby zapobiec rzucaniu wyjątków w czasie wykonywania przez nieprawidłowe typy typów.
Gdybyś przeszukał bazę błędów Javy tuż przed wydaniem wersji 1.5, znalazłbyś siedem razy więcej błędów z NullPointerExceptionniż ClassCastException. Nie wydaje się więc, aby znajdowanie błędów lub przynajmniej błędów, które utrzymują się po krótkich testach z dymem, jest świetną funkcją.
Dla mnie ogromną zaletą typów generycznych jest to, że dokumentują w kodzie ważne informacje o typach. Gdybym nie chciał, aby te informacje o typie były udokumentowane w kodzie, użyłbym języka dynamicznie wpisywanego lub przynajmniej języka z bardziej niejawnym wnioskiem o typie.
Utrzymywanie kolekcji obiektu dla siebie nie jest złym stylem (ale typowym stylem jest skuteczne ignorowanie hermetyzacji). Raczej zależy to od tego, co robisz. Przekazywanie kolekcji do „algorytmów” jest nieco łatwiejsze do sprawdzenia (w czasie kompilacji lub przed nią) za pomocą typów ogólnych.
Generics w Javie ułatwiają parametryczny polimorfizm . Za pomocą parametrów typu można przekazywać argumenty do typów. Podobnie jak metoda String foo(String s)modeluje pewne zachowanie, nie tylko dla określonego ciągu, ale dla dowolnego ciągu s, tak typ taki jak List<T>modeluje pewne zachowanie, nie tylko dla określonego typu, ale dla dowolnego typu . List<T>mówi, że dla każdego typu Tistnieje typ, Listktórego elementy to Ts . Więc Listjest to właściwie konstruktor typu . Przyjmuje typ jako argument i konstruuje inny typ jako wynik.
Oto kilka przykładów typów ogólnych, których używam na co dzień. Po pierwsze, bardzo przydatny interfejs ogólny:
public interface F<A, B> {
public B f(A a);
}
Ten interfejs mówi, że dla niektórych dwóch typów Ai Bistnieje funkcja (nazywana f), która przyjmuje Ai zwraca B. Po zaimplementowaniu tego interfejsu Ai Bmogą być dowolne typy, o ile zapewnisz funkcję, fktóra przyjmuje pierwszy i zwraca drugi. Oto przykładowa implementacja interfejsu:
F<Integer, String> intToString = new F<Integer, String>() {
public String f(int i) {
return String.valueOf(i);
}
}
Przed odmianami generycznymi polimorfizm był uzyskiwany przez tworzenie podklas za pomocą extendssłowa kluczowego. Dzięki rodzajom generycznym możemy w rzeczywistości pozbyć się podklas i zamiast tego użyć parametrycznego polimorfizmu. Na przykład rozważmy sparametryzowaną (ogólną) klasę używaną do obliczania kodów skrótów dla dowolnego typu. Zamiast przesłonić Object.hashCode (), użylibyśmy klasy ogólnej takiej jak ta:
public final class Hash<A> {
private final F<A, Integer> hashFunction;
public Hash(final F<A, Integer> f) {
this.hashFunction = f;
}
public int hash(A a) {
return hashFunction.f(a);
}
}
Jest to znacznie bardziej elastyczne niż stosowanie dziedziczenia, ponieważ możemy pozostać przy motywie stosowania kompozycji i polimorfizmu parametrycznego bez blokowania kruchych hierarchii.
Jednak typy generyczne Javy nie są doskonałe. Możesz abstrahować od typów, ale nie możesz na przykład abstrahować od konstruktorów typów. Oznacza to, że możesz powiedzieć „dla dowolnego typu T”, ale nie możesz powiedzieć „dla dowolnego typu T, który przyjmuje parametr typu A”.
Napisałem tutaj artykuł o tych ograniczeniach generycznych Java.
Ogromną zaletą w przypadku leków generycznych jest to, że pozwalają uniknąć podklas. Tworzenie podklas prowadzi zwykle do kruchych hierarchii klas, których rozszerzenie jest niewygodne, oraz klas, które są trudne do zrozumienia indywidualnie bez patrzenia na całą hierarchię.
Wereas przed generycznych może mieć zajęcia jak Widgetprzedłużony o FooWidget, BarWidgeti BazWidget, z rodzajowych można mieć jedną klasę ogólną Widget<A>, która trwa Foo, Barlub Bazw jego konstruktora, aby dać Ci Widget<Foo>, Widget<Bar>i Widget<Baz>.
Leki generyczne unikają uderzenia w wydajność boksu i rozpakowywania. Zasadniczo spójrz na ArrayList vs List <T>. Oba robią te same podstawowe rzeczy, ale List <T> będzie znacznie szybszy, ponieważ nie musisz boksować do / z obiektu.
Największą korzyścią ze stosowania Generics jest ponowne wykorzystanie kodu. Powiedzmy, że masz wiele obiektów biznesowych i zamierzasz napisać BARDZO podobny kod dla każdej jednostki, aby wykonywać te same czynności. (Operacje IE z Linq do SQL).
Dzięki rodzajom możesz stworzyć klasę, która będzie w stanie działać na dowolnym typie dziedziczącym z danej klasy bazowej lub zaimplementować dany interfejs w następujący sposób:
public interface IEntity
{
}
public class Employee : IEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int EmployeeID { get; set; }
}
public class Company : IEntity
{
public string Name { get; set; }
public string TaxID { get; set }
}
public class DataService<ENTITY, DATACONTEXT>
where ENTITY : class, IEntity, new()
where DATACONTEXT : DataContext, new()
{
public void Create(List<ENTITY> entities)
{
using (DATACONTEXT db = new DATACONTEXT())
{
Table<ENTITY> table = db.GetTable<ENTITY>();
foreach (ENTITY entity in entities)
table.InsertOnSubmit (entity);
db.SubmitChanges();
}
}
}
public class MyTest
{
public void DoSomething()
{
var dataService = new DataService<Employee, MyDataContext>();
dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 });
var otherDataService = new DataService<Company, MyDataContext>();
otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" });
}
}
Zwróć uwagę na ponowne użycie tej samej usługi z różnymi typami w metodzie DoSomething powyżej. Naprawdę eleganckie!
Jest wiele innych wspaniałych powodów, dla których warto używać leków generycznych w swojej pracy, to mój ulubiony.
Po prostu je lubię, ponieważ pozwalają szybko zdefiniować typ niestandardowy (ponieważ i tak ich używam).
Na przykład zamiast definiować strukturę składającą się z ciągu znaków i liczby całkowitej, a następnie zaimplementować cały zestaw obiektów i metod w celu uzyskania dostępu do tablicy tych struktur i tak dalej, możesz po prostu utworzyć słownik
Dictionary<int, string> dictionary = new Dictionary<int, string>();
A kompilator / IDE wykonuje resztę ciężkiej pracy. W szczególności Słownik pozwala na użycie pierwszego typu jako klucza (bez powtarzających się wartości).
Kolekcje typowane - nawet jeśli nie chcesz ich używać, prawdopodobnie będziesz musiał się nimi zajmować z innych bibliotek lub z innych źródeł.
Ogólne wpisywanie w tworzeniu klas:
public class Foo <T> {public T get () ...
Unikanie castingu - zawsze nie lubiłem takich rzeczy
nowy komparator {public int compareTo (Object o) {if (o instanceof classIcareAbout) ...
Gdzie zasadniczo sprawdzasz warunek, który powinien istnieć tylko dlatego, że interfejs jest wyrażony w postaci obiektów.
Moja początkowa reakcja na leki generyczne była podobna do Twojej - „zbyt niechlujna, zbyt skomplikowana”. Z mojego doświadczenia wynika, że po pewnym czasie przyzwyczajasz się do nich, a kod bez nich wydaje się mniej jasno określony i po prostu mniej wygodny. Poza tym reszta świata java używa ich, więc w końcu będziesz musiał zabrać się za program, prawda?
Aby dać dobry przykład. Wyobraź sobie, że masz klasę o nazwie Foo
public class Foo
{
public string Bar() { return "Bar"; }
}
Przykład 1 Teraz chcesz mieć kolekcję obiektów Foo. Masz dwie opcje, LIst lub ArrayList, które działają w podobny sposób.
Arraylist al = new ArrayList();
List<Foo> fl = new List<Foo>();
//code to add Foos
al.Add(new Foo());
f1.Add(new Foo());
W powyższym kodzie, jeśli spróbuję dodać klasę FireTruck zamiast Foo, ArrayList doda ją, ale Generic List of Foo spowoduje wyrzucenie wyjątku.
Przykład drugi.
Teraz masz dwie listy tablic i chcesz wywołać funkcję Bar () na każdej z nich. Ponieważ hte ArrayList jest wypełniona obiektami, musisz je rzutować, zanim będziesz mógł wywołać bar. Ale ponieważ Ogólna lista Foo może zawierać tylko Foos, możesz wywołać Bar () bezpośrednio na nich.
foreach(object o in al)
{
Foo f = (Foo)o;
f.Bar();
}
foreach(Foo f in fl)
{
f.Bar();
}
Czy nigdy nie napisałeś metody (lub klasy), w której kluczowa koncepcja metody / klasy nie była ściśle powiązana z określonym typem danych parametrów / zmiennych instancji (myśl, że lista połączona, funkcje max / min, wyszukiwanie binarne itp.).
Czy nigdy nie żałowałeś, że nie możesz ponownie użyć algorytmu / kodu bez uciekania się do ponownego użycia typu wytnij i wklej lub narażania na szwank silnego pisania (np. Chcę mieć ciąg znaków List, a nie Listrzeczy, które mam nadzieję są łańcuchami!)?
Dlatego powinieneś chcieć używać leków generycznych (lub czegoś lepszego).
Nie zapominaj, że typy generyczne są używane nie tylko przez klasy, ale mogą być również używane przez metody. Na przykład weź następujący fragment:
private <T extends Throwable> T logAndReturn(T t) {
logThrowable(t); // some logging method that takes a Throwable
return t;
}
Jest prosty, ale można go używać bardzo elegancko. Fajne jest to, że metoda zwraca to, co została podana. To pomaga, gdy obsługujesz wyjątki, które muszą zostać ponownie przesłane z powrotem do dzwoniącego:
...
} catch (MyException e) {
throw logAndReturn(e);
}
Chodzi o to, że nie tracisz typu, przepuszczając go metodą. Możesz zgłosić poprawny typ wyjątku zamiast tylkoThrowable zgłosić , co byłoby wszystkim, co możesz zrobić bez ogólnych.
To tylko prosty przykład zastosowania metod ogólnych. Istnieje kilka innych fajnych rzeczy, które można zrobić za pomocą metod ogólnych. Moim zdaniem najfajniejszy jest typ wywodzący się z generyków. Weźmy następujący przykład (zaczerpnięty z Effective Java 2nd Edition Josha Blocha):
...
Map<String, Integer> myMap = createHashMap();
...
public <K, V> Map<K, V> createHashMap() {
return new HashMap<K, V>();
}
Nie robi to wiele, ale pozwala zaoszczędzić trochę bałaganu, gdy typy ogólne są długie (lub zagnieżdżone; tj Map<String, List<String>>.).
Throwablez treści metody, która ma określone zadeklarowane wyjątki. Alternatywą jest napisanie oddzielnych metod zwracających każdy typ wyjątku lub samodzielne wykonanie rzutowania przy użyciu metody innej niż ogólna, która zwraca plik Throwable. Pierwsza jest zbyt rozwlekła i zupełnie bezużyteczna, a druga nie otrzyma żadnej pomocy od kompilatora. Używając typów generycznych, kompilator automatycznie wstawi dla Ciebie poprawne rzutowanie. Tak więc pytanie brzmi: czy to jest warte złożoności?
Podstawową zaletą, jak wskazuje Mitchel, jest mocne pisanie bez konieczności definiowania wielu klas.
W ten sposób możesz robić takie rzeczy jak:
List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();
Bez typów ogólnych musiałbyś rzutować blah [0] na właściwy typ, aby uzyskać dostęp do jego funkcji.
jvm i tak rzuca ... niejawnie tworzy kod, który traktuje typ ogólny jako „Object” i tworzy rzuty do żądanej instancji. Generics Java to tylko cukier syntaktyczny.
Wiem, że to jest pytanie C #, ale ogólne w języku są również używane w innych językach, a ich użycie / cele są dość podobne.
Kolekcje Java używają typów ogólnych od wersji Java 1.5. Dlatego dobrym miejscem do ich wykorzystania jest tworzenie własnego obiektu przypominającego kolekcję.
Przykładem, który widzę prawie wszędzie, jest klasa Pair, która zawiera dwa obiekty, ale musi radzić sobie z tymi obiektami w ogólny sposób.
class Pair<F, S> {
public final F first;
public final S second;
public Pair(F f, S s)
{
first = f;
second = s;
}
}
Za każdym razem, gdy używasz tej klasy Pair, możesz określić, z jakim rodzajem obiektów ma się ona obchodzić, a wszelkie problemy z rzutowaniem typu pojawią się w czasie kompilacji, a nie w czasie wykonywania.
Rodzaje mogą również mieć swoje granice zdefiniowane za pomocą słów kluczowych „super” i „extends”. Na przykład, jeśli chcesz poradzić sobie z typem ogólnym, ale chcesz się upewnić, że rozszerza on klasę o nazwie Foo (która ma metodę setTitle):
public class FooManager <F extends Foo>{
public void setTitle(F foo, String title) {
foo.setTitle(title);
}
}
Chociaż samo w sobie nie jest to zbyt interesujące, warto wiedzieć, że za każdym razem, gdy masz do czynienia z FooManager, wiesz, że będzie on obsługiwał typy MyClass i że MyClass rozszerza Foo.
Z dokumentacji Sun Java, w odpowiedzi na pytanie „dlaczego powinienem używać typów ogólnych?”:
„Typy generyczne umożliwiają przekazanie kompilatorowi typu kolekcji, aby można było go sprawdzić. Gdy kompilator zna typ elementu kolekcji, może sprawdzić, czy kolekcja została użyta konsekwentnie i może wstawić poprawne rzutowanie na wartości pobierane z kolekcji ... Kod używający typów ogólnych jest bardziej przejrzysty i bezpieczniejszy .... kompilator może zweryfikować w czasie kompilacji, czy ograniczenia typu nie są naruszane w czasie wykonywania [podkreślenie moje]. program kompiluje się bez ostrzeżeń, możemy z całą pewnością stwierdzić, że nie zgłosi on wyjątku ClassCastException w czasie wykonywania. Efektem netto używania typów ogólnych, szczególnie w dużych programach, jest poprawiona czytelność i niezawodność . [podkreślenie moje] "
Generics umożliwiają tworzenie obiektów, które są silnie wpisane, ale nie musisz definiować określonego typu. Myślę, że najlepszym użytecznym przykładem jest Lista i podobne klasy.
Korzystając z ogólnej listy, możesz mieć List List List, cokolwiek chcesz i zawsze możesz odwołać się do silnego pisania, nie musisz konwertować ani niczego takiego, jak w przypadku tablicy lub standardowej listy.
Generics pozwalają na użycie silnego pisania dla obiektów i struktur danych, które powinny być w stanie pomieścić dowolny obiekt. Eliminuje również żmudne i kosztowne typowanie podczas pobierania obiektów ze struktur ogólnych (boxing / unboxing).
Jednym z przykładów, w którym są używane oba, jest lista połączona. Jaki pożytek przyniosłaby połączona klasa listy, gdyby mogła używać tylko obiektu Foo? Aby zaimplementować listę połączoną, która może obsługiwać dowolny rodzaj obiektu, lista połączona i węzły w klasie wewnętrznej hipotetycznego węzła muszą być ogólne, jeśli lista ma zawierać tylko jeden typ obiektu.
Kolejną zaletą używania typów ogólnych (szczególnie w przypadku kolekcji / list) jest sprawdzanie typu czasu kompilacji. Jest to naprawdę przydatne, gdy używasz listy ogólnej zamiast listy obiektów.
Jednym z głównych powodów jest to, że zapewniają bezpieczeństwo typu
List<Customer> custCollection = new List<Customer>;
w przeciwieństwie do
object[] custCollection = new object[] { cust1, cust2 };
jako prosty przykład.
Podsumowując, typy ogólne umożliwiają dokładniejsze określenie tego, co zamierzasz zrobić (silniejsze pisanie).
Ma to kilka zalet:
Ponieważ kompilator wie więcej o tym, co chcesz zrobić, pozwala pominąć wiele rzutowania typów, ponieważ już wie, że typ będzie zgodny.
Dzięki temu uzyskasz również wcześniejsze opinie na temat poprawności Twojego programu. Rzeczy, które poprzednio zawiodłyby w czasie wykonywania (np. Ponieważ obiekt nie mógł zostać rzucony w żądanym typie), teraz zawodzą w czasie kompilacji i możesz naprawić błąd, zanim Twój dział testów prześle tajemniczy raport o błędzie.
Kompilator może wykonać więcej optymalizacji, takich jak unikanie pudełek itp.
Kilka rzeczy do dodania / rozszerzenia (mówiąc z punktu widzenia .NET):
Typy ogólne umożliwiają tworzenie klas i interfejsów opartych na rolach. Zostało to już powiedziane w bardziej podstawowych terminach, ale uważam, że zaczynasz projektować swój kod za pomocą klas, które są zaimplementowane w sposób niezależny od typu - co powoduje, że kod jest wysoce wielokrotnego użytku.
Ogólne argumenty dotyczące metod mogą zrobić to samo, ale pomagają też zastosować zasadę „Nie pytaj” do rzutowania, tj. „Daj mi to, czego chcę, a jeśli nie możesz, powiedz mi dlaczego”.
Używam ich np. W pliku Genericxpress zaimplementowanym w SpringORM i Hibernate, które wyglądają tak
public abstract class GenericDaoHibernateImpl<T>
extends HibernateDaoSupport {
private Class<T> type;
public GenericDaoHibernateImpl(Class<T> clazz) {
type = clazz;
}
public void update(T object) {
getHibernateTemplate().update(object);
}
@SuppressWarnings("unchecked")
public Integer count() {
return ((Integer) getHibernateTemplate().execute(
new HibernateCallback() {
public Object doInHibernate(Session session) {
// Code in Hibernate for getting the count
}
}));
}
.
.
.
}
Używając typów generycznych, moje implementacje tego DAO zmuszają programistę do przekazania im tylko encji, dla których są przeznaczone, przez podklasę Generic replace
public class UserDaoHibernateImpl extends GenericDaoHibernateImpl<User> {
public UserDaoHibernateImpl() {
super(User.class); // This is for giving Hibernate a .class
// work with, as generics disappear at runtime
}
// Entity specific methods here
}
Mój mały framework jest bardziej niezawodny (ma takie rzeczy jak filtrowanie, leniwe ładowanie, wyszukiwanie). Po prostu uprościłem to, aby dać przykład
Ja, podobnie jak Ty i Steve, powiedziałem na początku: „Zbyt bałaganiarskie i skomplikowane”, ale teraz widzę jego zalety
Oczywiste korzyści, takie jak „bezpieczeństwo typu” i „brak rzucania”, są już wspomniane, więc może mógłbym porozmawiać o innych „korzyściach”, które, mam nadzieję, pomogą.
Po pierwsze, generyczne są koncepcją niezależną od języka i IMO może mieć więcej sensu, jeśli w tym samym czasie pomyślisz o zwykłym polimorfizmie (w czasie wykonywania).
Na przykład polimorfizm, jaki znamy z projektowania obiektowego, ma pojęcie czasu wykonywania, w którym obiekt wywołujący jest obliczany w czasie wykonywania programu, a odpowiednia metoda jest wywoływana odpowiednio w zależności od typu środowiska wykonawczego. W generyce pomysł jest nieco podobny, ale wszystko dzieje się w czasie kompilacji. Co to oznacza i jak z tego korzystasz?
(Trzymajmy się metod ogólnych, aby zachować zwartość) Oznacza to, że nadal możesz mieć tę samą metodę na oddzielnych klasach (tak jak wcześniej w klasach polimorficznych), ale tym razem są one automatycznie generowane przez kompilator w zależności od ustawionych typów w czasie kompilacji. Parametryzujesz swoje metody na typie, który podajesz w czasie kompilacji. Więc zamiast pisać metody od podstaw dla każdego typu , jak to robisz w przypadku polimorfizmu w czasie wykonywania (nadpisywanie metody), pozwalasz kompilatorom wykonywać pracę podczas kompilacji. Ma to oczywistą zaletę, ponieważ nie musisz wywnioskować wszystkich możliwych typów, które mogą być używane w twoim systemie, co czyni go znacznie bardziej skalowalnym bez zmiany kodu.
Zajęcia działają w podobny sposób. Parametryzujesz typ, a kod jest generowany przez kompilator.
Kiedy już zrozumiesz pojęcie "czasu kompilacji", możesz użyć "ograniczonych" typów i ograniczyć to, co może być przekazane jako sparametryzowany typ przez klasy / metody. Możesz więc kontrolować, przez co ma zostać przepuszczone, co jest potężną rzeczą, szczególnie jeśli struktura jest konsumowana przez innych ludzi.
public interface Foo<T extends MyObject> extends Hoo<T>{
...
}
Nikt nie może teraz ustawić czegoś innego niż MyObject.
Możesz także „wymusić” ograniczenia typu w argumentach metody, co oznacza, że możesz się upewnić, że oba argumenty metody będą zależeć od tego samego typu.
public <T extends MyObject> foo(T t1, T t2){
...
}
Mam nadzieję, że to wszystko ma sens.
Kiedyś wygłosiłem wykład na ten temat. Moje slajdy, kod i nagrania audio można znaleźć pod adresem http://www.adventuresinsoftware.com/generics/ .
Używanie typów ogólnych do kolekcji jest proste i przejrzyste. Nawet jeśli będziesz go łowić wszędzie indziej, zysk z kolekcji jest dla mnie wygraną.
List<Stuff> stuffList = getStuff();
for(Stuff stuff : stuffList) {
stuff.do();
}
vs
List stuffList = getStuff();
Iterator i = stuffList.iterator();
while(i.hasNext()) {
Stuff stuff = (Stuff)i.next();
stuff.do();
}
lub
List stuffList = getStuff();
for(int i = 0; i < stuffList.size(); i++) {
Stuff stuff = (Stuff)stuffList.get(i);
stuff.do();
}
Już samo to jest warte krańcowego „kosztu” leków generycznych i nie musisz być generycznym Guru, aby używać tego i uzyskać wartość.
Generics daje również możliwość tworzenia obiektów / metod wielokrotnego użytku, jednocześnie zapewniając obsługę specyficzną dla typu. W niektórych przypadkach zyskujesz również dużą wydajność. Nie znam pełnej specyfikacji Java Generics, ale w .NET mogę określić ograniczenia dla parametru Type, takie jak Implements a Interface, Constructor i Derivation.
Umożliwienie programistom implementowania algorytmów ogólnych - używając typów ogólnych programiści mogą implementować algorytmy ogólne, które działają na kolekcjach różnych typów, można je dostosowywać, są bezpieczne dla typów i łatwiejsze do odczytania.
Silniejsze sprawdzanie typów w czasie kompilacji - kompilator Java stosuje silne sprawdzanie typów w kodzie ogólnym i generuje błędy, jeśli kod narusza bezpieczeństwo typów. Naprawianie błędów w czasie kompilacji jest łatwiejsze niż naprawianie błędów czasu wykonania, które mogą być trudne do znalezienia.
Eliminacja odlewów.