Typy i metody sparametryzowane (generics) Typy generyczne

background image

8.06.2018

Typy i metody sparametryzowane (generics)

http://edu.pjwstk.edu.pl/wyklady/poj/scb/Generics/Generics.html

1/9

Typy i metody sparametryzowane (generics)

Przedstawiono tu najistotniejsze informacje o parametryzacji typów (generics). Są one ważne dla sprawnego posługiwania

się wieloma już sparametryzowanymi interfejsami i klasami ze standardu Javy, a także przydadzą się, gdy będziemy chcieli

tworzyć własne typy sprametryzowane.

1. Wprowadzenie

Często konstrukcje różnych klas oraz metod w klasach są funkcjonalnie podobne (czyli służą wykonaniu tych samych

czynności), różnią się natomiast tylko typami danych na których czynności te są wykonywane.

Po to by nie powielać tego samego kodu dla różnych przypadków  do języków programowania wprowadzono szablony

(templates) - klasy oraz metody parametryzowane typami przetwarzanych danych.

Takie podejście stosowane jest w wielu językach (m.in. C++, Ada).

W Javie - poczynając od wersji 1.5 - pojawił się również odpowiednik szablonów tzw. generics.

Przy wprowadzaniu koncepcji generics do Javy w dużo mniejszym stopniu akcent położono na uogólnianie kodu (pierwotny

motyw szablonów C++). Dość wysoki stopień uogólniania kodu był i jest bowiem w Javie dostępny poprzez:

zagwarantowane dziedziczenie klasy Object,

implementację interfejsów,

mechanizmy refleksji (czyli np. dynamiczne, w fazie wykonania programu, odwołania do pól i metod klas) . 

Do tych - od dawna dostępnych możliwości - wprowadzenie generics (typów sparametryzowanych) dodaje ułatwienia w

postaci:

unikania konwersji zawężających,

tworzenia bardziej czytelnego kodu,

wykrywania błędów w fazie kompilacji i unikania wyjątku ClassCastException.

Zobaczmy.

Można było np. zawsze  napisać ogólną klasę reprezentującą dowolne pary:

class ParaObj {
Object first;
Object last;

public ParaObj(Object f, Object l) {
first = f;
last = l;
}


public Object getFirst() { return first; }
public Object getLast() { return last; }

public void setFirst(Object f) { first = f; }
public void setLast(Object l) { last = l; }
}

Ale przy takim podejściu mamy pewne problemy:

kompilator nie ma możliwości dokładnego sprawdzenia zgodności typów i błędy związane z użyciem  niewłaściwego

typu pojawią się dopiero w fazie wykonania, być może w odległej przyszłości, w jakimś  innym module systemu,

jesteśmy zmuszeni do stosowania konwersji zawężających, co czasem może być uciążliwe i zmniejsza czytelność

kodu.

Oba problemy obrazuje następujący kod.

background image

8.06.2018

Typy i metody sparametryzowane (generics)

http://edu.pjwstk.edu.pl/wyklady/poj/scb/Generics/Generics.html

2/9

ParaObj po = new ParaObj("Ala", new Integer(3));
System.out.println(po.getFirst() + " " + po.getLast());

// Problem 1
// konieczne konwersje zawężające

String name = (String) po.getFirst();
int nr = (Integer) po.getLast();
po.setFirst(name + " Kowalska");
po.setLast( new Integer(nr + 1));
System.out.println(po.getFirst() + " " + po.getLast());


// Problem 2
// możliwe błędy


po.setLast("kot");
System.out.println(po.getFirst() + " " + po.getLast());

// Błąd może być wykryty w fazie wykonania
// późno, czasem w innym module
Integer n = (Integer) po.getLast(); // ClassCastException

Wynik:


Ala 3

Ala Kowalska 4

Ala Kowalska kot

Exception in thread "main"
java.lang.ClassCastException: java.lang.String

        at GenTest1.main(GenTest1.java:62)

Zastosowanie generics (poprzez parametryzację typów) do dotychczasowych możliwości uogólniania kodu dodaje

rozwiązanie w/w problemów. Na czym polega parametryzacja typów?

Typ sparametryzowany - to typ (wyznaczany przez nazwę klasy lub interfejsu) z dołączonym jednym lub większą liczbą

parametrów.

Definicję typu sparametryzowanego wprowadzamy słowem kluczowym class lub interface podając po nazwie (klasy lub

interfejsu) parametry w nawiasach kątowych. Parametrów tych następnie używamy w ciele klasy (interfejsu) w miejscu

"normalnych" typów

Typ sparametryzowany - elementy składni


class | interface  Nazwa < ParametrTypu1, ParametrTypu2, ... ParametrTypuN> {

//....

}

Przykład podano na wydruku:

class Para<S, T> {
S first;
T last;

public Para(S f, T l) {
first = f;
last = l;
}

public S getFirst() { return first; }
public T getLast() { return last; }

background image

8.06.2018

Typy i metody sparametryzowane (generics)

http://edu.pjwstk.edu.pl/wyklady/poj/scb/Generics/Generics.html

3/9

public void setFirst(S f) { first = f; }
public void setLast(T l) { last = l; }
}

Możemy teraz tworzyć różne pary:

Para<String, String> p1 = new Para<String, String> ("Jan", "Kowalski");

Para<String, Data> p2 = new Para<String, Data> ("Jan Kowalski", new Data("2005-01-01"));

Para<Integer, Integer> p = new Para<Integer, Integer>(1,2);  // autoboxing działa;

Tutaj  <String, String>, <String, Data>, <String, Integer> oznaczają konkretne typy, które są podstawiane w miejscu

parametrów w klasie Para<S, T> (ale - jak zobaczymy zaraz - tylko chwilowo, w fazie kompilacji). Nazywane są one 

argumentami typu.

Para<String, String>, Para<String, Data> Para<Integer, Integer> nazywają się konkretnymi instancjami sparametryzowanej

klasy Para<S, T>.

Nawiązując do  poprzedniego przykładu, testującego uogólnione pary można podać kod używający sparametryzowanej klasy

Para<S, T> - zob. kod na listingu 5 oraz wynik jego działania na listingu 6.

Para<String, Integer> pg = new Para<String, Integer>("Ala", 3); //autoboxing
System.out.println(pg.getFirst() + " " + pg.getLast());
String nam = pg.getFirst(); // bez konwersji!
int m = pg.getLast(); // bez konwersji!
pg.setFirst(name + " Kowalska");
pg.setLast(m+1); // autoboxing
System.out.println(pg.getFirst() + " " + pg.getLast());

Listing 6 - wynik działania kodu z listingu 5


Ala 3

Ala Kowalska 4

Przy tym błędy są wykrywane w fazie kompilacji

pg.setLast("kot");


GenTest1.java:77: setLast(java.lang.Integer) in
Para<java.lang.String,java.lang

Integer> cannot be applied to (java.lang.String)

     pg.setLast("kot"); 

       ^

1 error

Szczególnie użyteczna jest parametryzacja kolekcji (i był to niewątpliwie główny motyw wprowadzenia generics do Javy).

2. Typy surowe i czyszczenie typów

W Javie - inaczej niż w C++ - po kompilacji dla każdego "szablonu" (typu sparametryzowanego) powstaje tylko jedna klasa

(plik klasowy), współdzielona przez wszystkie instancje tego typu sparametryzowanego.

Skoro tak, to jaki - w fazie wykonania - będzie typ wyznaczany przez skompilowaną klasę  sparametryzowaną i jaki

formalny typ uzyskają  parametry typu używane w definicji tej klasy?

Aby się przekonać jak w fazie wykonania prezentują się klasy sparametryzowane możemy użyć następującego programiku.

import java.lang.reflect.*;

background image

8.06.2018

Typy i metody sparametryzowane (generics)

http://edu.pjwstk.edu.pl/wyklady/poj/scb/Generics/Generics.html

4/9

class Para<S, T> {

static int nr;
S first;
T last;

public static int getNr() { return nr; }

public Para(S f, T l) {
first = f;
last = l;
nr++;
}

public S getFirst() { return first; }
public T getLast() { return last; }

public void setFirst(S f) { first = f; }
public void setLast(T l) { last = l; }
}


public class GenTest2 {

public static void main(String[] args) {
Para<String, Integer> p1 = new Para<String, Integer>("Ala", 3);
System.out.println(p1.getNr());
Para<String, Integer> p2 = new Para<String, Integer>("Ala", 3);
System.out.println(p2.getNr());
Para<String, String> p3 = new Para<String, String>("Ala", "Kowalska");
System.out.println(p3.getNr());

// Co jest - tylko klasa Para
// "Raw Type"

Class p1Class = p1.getClass();
System.out.println(p1Class);

// Metodami refleksji możemy się przekonać, że
// w definicji klasy Para typem fazy wykonania dla parametrów jest Object
// "type erasure"!!!

Method[] mets = p1Class.getDeclaredMethods(); // zwraca tablicę metod deklarwoanych w klasie
for (Method m : mets) System.out.println(m);

// Surowego typu ("Raw Type") możemy też używać
// ale czasem wiąże się to z niuansami
// i kompilator może nas ostrzegać o możliwych błędach


Para p = new Para("B", new Double(3.1));
String f = (String) p.getFirst();
double d = (Double) p.getLast();
System.out.println(f + " " + d);

}
}

Po jego uruchomieniu uzyskamy następujący wydruk.

1

2

3

class Para

public static int Para.getNr()

public java.lang.Object Para.getFirst()

public java.lang.Object Para.getLast()

public void Para.setFirst(java.lang.Object)

public void Para.setLast(java.lang.Object)

B 3.1

Wydruk ten oznacza, że:

jest tylko jedna klasa Para dla wszystkich instancji klasy sparametryzowanej Para<S, T>; typ wyznaczany przez tę

klasę nazywa się typem surowym

("raw type"

),

background image

8.06.2018

Typy i metody sparametryzowane (generics)

http://edu.pjwstk.edu.pl/wyklady/poj/scb/Generics/Generics.html

5/9

z definicji klasy Para zniknęły wszystkie parametry typu i zostały zastąpione przez Object; ten mechanizm nazywa

się czyszczeniem typów (

"type erasure"

) ,

ponieważ jest tylko jedna klasa Para - zmienne reprezentowane przez pola statyczne są wspólne dla wszystkich

instancji typu sparametryzowanego; zmienna nr jest wspólna dla Para<String, Integer> i Para<String, String> -

dlatego zwiększa się w sposób ciągły (1, 2, 3).

3. Restrykcje

Przyjęte w Javie rozwiązanie (jedna klasa w fazie wykonania, czyszczenie typów) ma swoje zalety i wady.

Do zalet zaliczyć można:

mniejszą liczbę klas po kompilacji,

zgodność kodu binarnego z kodem nie używającym "generics" ("czyszczenie typów" stanowi właśnie o

kompatybilności kodów używających generics z kodami nie używającymi ich).

Wady wydają się jednak przeważać nad zaletami. Do wad zaliczymy:

ograniczenia na możliwości użycia parametrów typu i wynikające stąd:

znacznie mniejsze (niż np. w C++) możliwości prostego uogólniania klas i metod (ale funkcjonalnie

uogólnianie jest dostępne na takim samym poziomie jak w C++, choćby dzięki refleksji),

pewna trudność w definiowaniu typów sparametryzowanych - trzeba pamiętać o niuansach wprowadzanych

przez wybrany sposób kompilacji,

ograniczenia na możliwości parametryzacji typów.

Istotnie, w definicjach klas (i metod) sparametryzowanych nie do końca możemy traktować parametry typu jak zwykłe typy.

Możemy

:

podawać je jako typy pól i zmiennych lokalnych,

podawać je jako typy parametrów i wyników metod,

dokonywać jawnych konwersji do typów oznaczanych przez nie (ale to będzie tylko ważne na etapie kompilacji, po to

by uniknąć błędów niezgodności typów, natomiast nie uzyskamy w fazie wykonania faktycznych konwersji np.

zawężających, no bo jak?),

wywoływać na rzecz zmiennych oznaczanych typami sparametryzowanymi metody klasy Object (i ew. właściwe dla

klas i interfejsów, które stanowią tzw. górne ograniczenia danego parametru typu).

Nie możemy

(w definicjach sparametryzowanych klas i metod):

tworzyć obiektów typów sparametryzowanych (new T() jest niedozwolone, no bo na poziomie definicji generics nie

wiadomo co to konkretnie jest T),

używać operatora instanceOf ( z powodu j.w.),

używać ich w statycznych kontekstach (bo statyczny kontekst jest jeden dla wszystkich różnych instancji typu

sparametryzowanego),

używać ich w literałach klasowych,

wywoływać metod z konkretnych klas i interfejsów, które nie są zaznaczone jako górne ograniczenia parametru typu

(w najprostszym przypadku tą górną granicą jest Object, wtedy możemy używać tylko metod klasy Object).

Również użycie typów sparametryzowanych obarczone jest restrykcjami.

Nie wolno np.:

używać typów sparametryzowanych przy tworzeniu tablic (podając je jako typ elementu tablicy),

w obsłudze wyjątków (bo jest to mechanizm fazy wykonania),

w literałach klasowych (bo oznaczają typy fazy wykonania).

Dlaczego nie możemy mieć tablic elementów sparametryzowanego typu?

Wynika to z istoty pojęcia tablicy oraz ze sposobu kompilacji generics.

Tablica jest zestawem elementów tego samego typu (albo jego podtypu). Informacja o typie elementów tablicy jest

przechowywana i JVM korzysta z niej w fazie wykonania, aby zapewnić, że do tablicy nie jest wstawiany element

niewłaściwego typu (wtedy generowany jest wyjątek ArrayStoreException).

Gdyby dopuścić tablice elementów typów sparametryzowanych kontrakt ten zostałby zerwany (bowiem w fazie wykonania

nic nie wiadomo o konkretnych instancjach typów sparametryzowanych, zatem nie można zapewnić odpowiedniej

dynamicznej kontroli typów).

Zobaczmy.

background image

8.06.2018

Typy i metody sparametryzowane (generics)

http://edu.pjwstk.edu.pl/wyklady/poj/scb/Generics/Generics.html

6/9

Para<String, Integer>[] pArr = new Para<String, Integer>[5];  

// (1) niedozwolone

Object[] objArr = p;                                                              

objArr[0] = new Para<String, String>("A", "B");   // przejdzie, jeśli dopuścimy (1)

a błąd pojawi się (jako ClassCastException) kiedyś później, gdy np. sięgniemy po pierwszy element tablicy pArr i zapytamy

go o drugi składnik pary (pArr[0].getLast()). Skąd błąd? Bo do tego odwołania kompilator powinien był dopisać konwersję

do Integer, a faktycznie mamy String, a nie Integer.

 

Oczywiście, to ograniczenie można obejść, stosując następujące rozwiązania:

tablice typów surowych (niebezpieczne),

tablice uniwersalnych instancji typów sparametryzowanych (uniwersalna instancja wprowadzana jest z użyciem

parametru typu ?, co oznacza dowolny typ; zob. dalej); uniwersalne instancje też nie są dobrym rozwiązaniem - choć

semantycznie są zbliżone do typów surowych, to składniowa różnica powoduje, że są przez kompilator traktowane

inaczej niż typy surowe i w wielu przypadkach zamiast ostrzeżeń "unchecked cast" dostaniemy raczej błędy w

kompilacji,

i rozwiązanie najlepsze - zastosowanie kolekcji (list) konkretnych instancji typu sparametryzowanego.

4. Ograniczenia parametrów typu

Jednym ze sposobów zwiększania funkcjonalności generics Javy jest użycie (jawnych) ograniczeń parametrów typu. Dzięki

temu w klasach i metodach sparametryzowanych możemy korzystać z metod, specyficznych dla podanych ograniczeń.

Ograniczenie parametru typu określa zestaw typów, które mogą być używane jako argumenty typu (i
podstawiane w szablonie w miejscu parametrów typu), a w konsekwencji zestaw metod, które mogą być
wołane na rzecz zmiennych oznaczanych parametrami typu

Ograniczenia parametru typu wprowadzamy za pomocą składni:


    ParametrTypu
extends Typ1 & Typ2 & Typ3  & ... & TypN


gdzie:

Typ1 - nazwa klasy lub interfejsu

Typ2-TypN - nazwy interfejsów


Uwaga:

typy Typ1-TypN mogą być sparametryzowane,
typy ograniczające nie mogą się powtarzać, w tym nie mogą występować powtórzenia dla typów
sparametryzowanych TP<X> TP<Y> (ze względu na czyszczenie typów).

W przypadku ograniczanych parametrów typu "type erasure" daje typ pierwszego ograniczenia.

Np. w fazie wykonania, w kontekście class A <T extends Appendable>, T staje się Appendable.

Przykład wykorzystania ograniczeń typów pokazuje wydruk, zawierający uogólniony kod, pozwalający na określanie

maksymalnego i minimalnego elementu tablicy danych dowolnego typu, implementującego interfejs Comparable (z jedną

metodą compareTo pozwalająca na porównywanie obiektów).

class MinMax<T> {
private T min;
private T max;

public MinMax(T f, T l) {
min = f;
max = l;
}

public T getMin() { return min; }
public T getMax() { return max; }
}

background image

8.06.2018

Typy i metody sparametryzowane (generics)

http://edu.pjwstk.edu.pl/wyklady/poj/scb/Generics/Generics.html

7/9


class GenArr<T extends Comparable<T>> {

private T[] arr;
private MinMax<T> minMax;

public GenArr(T[] a) { init(a); }

public void init(T[] a) {
minMax = null;
arr = a;
}


public MinMax<T> getMinMax() {
if (minMax != null) return minMax;
if (arr == null || arr.length == 0) return null;
T min = arr[0];
T max = arr[0];
for (int i=1; i<arr.length; i++) {
if (arr[i].compareTo(min) < 0) min = arr[i]; // dzięki T extends Comparable
if (arr[i].compareTo(max) > 0) max = arr[i];
}
minMax = new MinMax<T>(min, max);
return minMax;
}

}

public class Bounds {

public Bounds() {
Integer[] arr1 = { 1, 2 , 7, -3 };
Integer[] arr2 = { 1, 7 , 8, -10 };
String[] napisy = { "A", "Z", "C" };

GenArr<Integer> ga = new GenArr<Integer>(arr1);
MinMax<Integer> imx = ga.getMinMax();
System.out.println(imx.getMax() + " " + imx.getMin());
ga.init(arr2);
imx = ga.getMinMax();
System.out.println(imx.getMax() + " " + imx.getMin());

GenArr<String> gas = new GenArr<String>(napisy);
System.out.println(gas.getMinMax().getMax() + " " +
gas.getMinMax().getMin());
}

public static void main(String[] args) {
new Bounds();
}


}

Wynik:


7 -3

8 -10

Z A

5. Parametry uniwersalne (wildcards)

Weźmy jakąś kolekcję  sparametryzowaną np. listę ArrayList:

ArrayList<Integer> list1 = new ArrayList<Integer>();

background image

8.06.2018

Typy i metody sparametryzowane (generics)

http://edu.pjwstk.edu.pl/wyklady/poj/scb/Generics/Generics.html

8/9

Czy ArrayList<Object> jest nadtypem dla typu ArrayList<Integer>?

Gdyby tak  było, to moglibyśmy zrobić tak:

ArrayList<Object> list2 = list1; // hipotetyczna konwersja rozszerzająca

a wtedy kompilator nie mógłby protestować przeciwko czemuś takiemu:

list2.add(new Object());

Co jednak doprowadziłoby do katastrofy:

Integer n = list1.get(0);  // próba przypisania Object na Integer

I wobec tego konstrukcja: list2 = list1 jest zabroniona w fazie kompilacji (

"incompatible types"

), co oznacza, że w Javie

pomiędzy typami sparametryzowanymi za pomocą konkretnych parametrów nie zachodzą żadne relacje w rodzaju

dziedziczenia (typ-nadtyp itp.).

A jednak takie relacje są czasem potrzebne.

Jeśli ArrayList<Integer> i ArrayList<String> nie są podtypami ArrayList<Object>- to jak stworzyć metodę wypisującą

zawartośc dowolnej listy ArrayList?

Do tego służą parametry uniwersalne (wildcards) - oznaczenie "?".

Są trzy typy takich parametrów:

ograniczone z góry <? extends X> - oznacza "wszystkie podtypy X"

ograniczone z dołu <? super X> - oznacza "wszystkie nadtypy X"

nieograniczone <?> -  oznacza "wszystkie typy"

Notacja ta wprowadza do Javy

wariancję

typów sparametryzowanych.

Typ sparametryzowany C<T> jest kowariantny względem parametru T, jeśli dla dowolnych typów A i B,
takich, że B jest podtypem A, typ sparametryzowany C<B> jest podtypem C<A> (kowariancja - bo
kierunek dziedziczenia typów sparametryzowanych jest zgodny z kierunkiem dziedziczenia parametrów
typu)

 

Kowariancję uzyskujemy za pomocą symbolu <? extends X>, co oznacza np. że

 List<? extends Number> jest nadtypem wszystkich typów sparametryzowanych, gdzie parametrem typu jest Number albo

typ pochodny od Number.

Typ sparametryzowany C<T> jest kontrawariantny względem parametru T, jeżeli dla dowolnych typów A
i B, takich że B jest podtypem A, typ sparametryzowany C<A> jest podtypem typu sparametryzowanego
C<B> (kontra - bo kierunek dziedziczenia jest przeciwny).

Kontrawariancję uzyskujemy za pomocą symbolu <? super X>. Np. Integer jest podtypem Number, a List<Number> jest

podtypem List<? super Integer>, wobec czego możemy podstawiać:

List<? super Integer> list = new ArrayList<Number>;

Biwariancja oznacza rownoczesną kowariancję i kontrawariancję typu sparametryzowanego

Biwariancję uzyskujemy za pomocą symbolu <?>, który oznacza wszystkie typy. Faktycznie ArrayList<?> oznacza

wszystkie możliwe listy ArrayList z dowolnym parametrem typu T. Czyli ArrayList<?> jest nadtypem ArrayList<? extends

Integer> i nadtypem dla ArrayList<? super Integer>.

Kowariancja typów sparametryzowanych umożliwia pisanie uniwersalnych metod (w rodzaju "wypisz dowolną kolekcję"

albo "pokaż dowolną listę",

Np.

void showEmployee(ArrayList <? extends Pracownik>) {

background image

8.06.2018

Typy i metody sparametryzowane (generics)

http://edu.pjwstk.edu.pl/wyklady/poj/scb/Generics/Generics.html

9/9

// ...

}

Bez tego nie moglibyśmy jako argumentów przekazywać listy dyrektorów, kierowników, asystentów etc.

( bo między ArrayList<Pracownik> i ArrayList<Asystent> nie ma relacji nadtyp - podtyp).

Ale jeśli mamy gdzieś dostęp do typu sparametryzowanego <? extends X>, to  zabronione jest podstawianie na ten typ

konkretniejszych podtypów.

Inaczej mielibyśmy sytuację, w której do przekazanej listy dyrektorów dopisywani mogliby być np. asystenci.

Nie możemy "podstawiać", ale możemy pobierać (dostajemy coś calkiem bezpiecznego typu - np. typu wyznaczanego przez

dolną granicę).

6. Metody sparametryzowane i konkludowanie typów

Parametryzacji mogą podlegać nie tylko klasy czy interfejsy, ale również metody.

Definicja metody sparametryzowanej ma postać:

        specyfikatorDostępu [static] <ParametryTypu> typWyniku nazwa( lista parametrów) {
        // ...
        }

 Argumenty typów (podstawiane w fazie kompilacji w miejsce parametrów, choćby po to by zapewnić zgodność typów oraz

automatyczne konwersje zawężające) są określane na podstawie faktycznych typów użytych przy wywołaniu metody. Proces

wyznaczania aktualnych argumentów typów nazywa się konkludowaniem typów (ang. type inferring).

Poniższy program zawiera przykład sparametryzowanej metody wyznaczającej maksimum z tablicy elementów dowolnego

typu pochodnego od Comparable. Konkretne argumenty typu (odpowiadające parametrowi T użytemu zarówno na liście

parametrów metody, jak i jako typ jej wyniku) są konkludowane z wywołań.

public class Metoda {

public static <T extends Comparable<T>> T max(T[] arr) {
T max = arr[0];
for (int i=1; i<arr.length; i++)
if (arr[i].compareTo(max) > 0) max = arr[i];
return max;
}


public static void main(String[] args) {

Integer[] ia = { 1, 2, 77 };
int imax = max(ia); // w wyniku konkluzji T staje się Integer

Double[] da = {1.5, 231.7 };
double dmax = max(da); // w wyniku konkluzji T staje się Double

System.out.println(imax + " " + dmax);
}

}


Wyszukiwarka

Podobne podstrony:
Typy generyczne w języku Java Samouczek Programisty
typy-charakteru-wg-heymansa, Metody i narzędzia
3 Typy i metody badan, Pedagogika, Andragogika
Typy dokumentacji, ►► UMK TORUŃ - wydziały w Toruniu, ►► Archiwistyka, Metodyka kształtowania zasobu
TYPY BADAN1, Pedagogika, Metody Badan pedagogicznych, KOZUH
typy-charakteru-wg-heymansa, Metody i narzędzia
Wzorniki cz 3 typy serii 2008 2009
typy kobiet www prezentacje org 3
Państwo Pojęcie, funkcje, typy
Wykład 9 Kultura typy i właściwości
4 Temperament typy osobowosci
Rozne typy zrodel historycznych
76 Omow znane Ci typy kanalow jonowych
AMI 25 1 Rachunek calkowy podstawowe typy zadan id 59059 (2)
Typy maryjne, ikonografia
Typy wybrzeży, geomorfologia

więcej podobnych podstron