Dariusz Brzeziński Politechnika Poznańska, Instytut Informatyki
Problem: Jak zaimplementować stos? Co się stanie gdy dodamy do stosu obiekt typu Czlowiek? Co się stanie, gdy spróbujemy ten obiekt odczytać ze stosu? class EltStosu public Object elt; public EltStosu nast; public EltStosu(Object elt, EltStosu nast) this.elt = elt; this.nast = nast; public class Stos private EltStosu wierzch; public Stos() wierzch = null; public boolean Pusty() return wierzch == null; public void Wstaw(Object elt) wierzch = new EltStosu(elt, wierzch); public Object Pobierz() if (Pusty()) throw new InvalidOperationException(); Object wynik = wierzch.elt; wierzch = wierzch.nast; return wynik; Typy uogólnione 2
Czlowiek os1 = new Czlowiek("Jasio"); Stos s = new Stos(); s.wstaw(os1); Czlowiek os2 = s.pobierz(); //błąd Czlowiek os3 = (Czlowiek)s.Pobierz(); //OK Kompilator nie wie jakiego typu jest obiekt przechowywany na stosie Konieczność rzutowania Brak sprawdzania typów Typy uogólnione 3
Rozwiązanie: Typ jako parametr klasy lub metody class EltStosu<T> public T elt; public EltStosu<T> nast; public EltStosu(T elt, EltStosu<T> nast) this.elt = elt; this.nast = nast; Czlowiek os1 = new Czlowiek("Jasio"); Stos<Czlowiek> s = new Stos<Czlowiek>(); s.wstaw(os1); Czlowiek os2 = s.pobierz(); //OK public class Stos<T> private EltStosu<T> wierzch; public Stos() wierzch = null; public boolean Pusty() return wierzch == null; public void Wstaw(T elt) wierzch = new EltStosu<T>(elt, wierzch); public T Pobierz() if (Pusty()) throw new InvalidOperationException(); T wynik = wierzch.elt; wierzch = wierzch.nast; return wynik; Typy uogólnione 4
Typy uogólnione (typy generyczne) pozwalają parametryzować klasy i metody typami Dzięki temu można tworzyć silnie typowane metody dla wielu typów Brak jawnego rzutowania W Javie zaimplementowane przez wymazywanie typów (działa tylko dla typów referencyjnych) W C# zaimplementowane z wykorzystaniem dodatkowych instrukcji języka pośredniego Typy uogólnione 5
Parametryzować typami można: Klasy Metody public class Para<T1, T2> public Para(T1 pierwszy, T2 drugi) this.pierwszy = pierwszy; this.drugi = drugi; public T1 Pierwszy get; private set; public T2 Drugi get; private set; static void Zamien<U>(ref U lhs, ref U rhs) U temp; temp = lhs; lhs = rhs; rhs = temp; public static void TestZamiany() int a = 1; int b = 2; Zamien<int>(ref a, ref b); Zamien(ref a, ref b); Para<int,int> p = new Para<int,int>(2,3); Typy uogólnione 6
Parametryzować typami można: Klasy Metody public class Para<T1, T2> public Para(T1 pierwszy, T2 drugi) this.pierwszy = pierwszy; this.drugi = drugi; public T1 Pierwszy get; private set; public T2 Drugi get; private set; static void Zamien<T>(ref T lhs, ref T rhs) T temp; temp = lhs; lhs = rhs; rhs = temp; public static void TestZamiany() int a = 1; int b = 2; Zamien<int>(ref a, ref b); Zamien(ref a, ref b); Para<int,int> p = new Para<int,int>(2,3); Typy uogólnione 7
Java Wskazanie typu i interfejsów nadrzędnych public class GenericClass<T extends Number & Comparable<T>> void print(t t) System.out.println(o()); // OK Dżokery (wildcards) static void printall (Collection<?> c) for (Object o : c) System.out.println(o); static <T> void copy(list<? extends T> source, List<? super T> dest) for (T t : source) dest.add(t); Typy uogólnione 8
C# Wskazanie typu i interfejsów nadrzędnych Wymaganie żeby T był klasą Wymaganie żeby T był strukturą Wymaganie żeby T miał domyślny konstruktor static void Copy<T, U, V, W> (IEnumerable<T> source, ICollection<U> dest) where T : U, IComparable<T>, new() where U : new() where V : class where W : struct foreach (T t in source) dest.add (t); Typy uogólnione 9
Dlaczego ten kod zadziała Czlowiek[] array = new Student[10]; a ten nie? ArrayList<Czlowiek> list = new ArrayList<Student>(); Typy uogólnione 10
Dlaczego ten kod zadziała Czlowiek[] array = new Student[10]; a ten nie? ArrayList<Czlowiek> list = new ArrayList<Student>(); Bo tablice w Javie i C# są kowariantne a typy generyczne nie. Typy uogólnione 11
W C# można sterować wariancją typów generycznych Kowariancja (np. IEnumerable<T>) List<Czlowiek> list = new List<Student>(); // Bo definicja IEnumerable wygląda tak public interface IEnumerable<out T> : IEnumerable Kontrawariancja (np. Action<T>) Action<Student> action = new Action<Czlowiek>(); // Bo definicja Action wygląda tak public delegate void Action<in T>(T obj) Typy uogólnione 12
Java: T obj = new T(); //błąd C#: T obj = new T(); //OK Java: obj instanceof T //błąd C#: obj is T //OK Java: public Stos<String>[] x = new Stos<String>[6]; //błąd C#: public Stos<string>[] x = new Stos<string>[6]; //OK Typy uogólnione 13
W Javie jeśli chcemy stworzyć typ generyczny przechowujący typy proste, będzie musiało być wykonywane opakowywanie (boxing/unboxing) Wolniejsze przetwarzanie kolekcji typów prostych w Javie W C# typ generyczny jest znany podczas wykonywania programu, w Javie ten typ jest gubiony Możliwe korzystanie z refleksji na typach generycznych w C# Typy uogólnione 14