Daten-tipo priskribas aron da metodoj, kiujn posedas iu objekto en Ĝavo. (Ni ignoru publikajn datenajn membrojn de klasoj; estas bona stilo, ke datenaj membroj estu privataj.)
Kvar specoj de elementoj en Ĝavo bezonas daten-tipon:
Ni vidis, ke klasoj en Ĝavo estas daten-tipoj: Klaso posedas difinitan, karakterizan aron da metodoj, kiuj estas aplikeblaj al ĉiu ekzemplero de la klaso.
Se iu metodo havas parametron de certa klaso, ĝi do "scias", kiujn metodojn ĝi povas apliki al tiu parametro. Ni konsideru iun metodon, kiu trairas iun liston kaj "desegnas" la elementojn de la listo (ni supozu, ke eblas desegni ilin). La listo estu de la klaso ArrayList:
/** * Tiu chi metodo desegnas chiujn elementojn en listo. * @param elementojPorDesegni la listo de la desegnotaj elementoj */ public void desegnu(ArrayList elementojPorDesegni) { … } |
En multaj situacioj ne necesas scii la precizan klason de iu objekto. Ekzemple, ekzistas en Ĝavo pluraj klasoj, kiuj realigas listojn (kolektaĵojn kun difinita sinsekvo). Unu el ili estas ArrayList (tabelo, kiu povas dinamike kreski), alia LinkedList. Ambaŭ estas utilaj: En ArrayList oni povas rapide trovi elementojn laŭ donita vic-numero (indico), en LinkedList oni povas rapide enŝovi novan elementon. Tra ambaŭ oni povas rapide kaj komforte trairi.
La menciitan metodon desegnu principe ne interesas, ĉu la listo donita al ĝi estas ArrayList, LinkedList aŭ io simila. Tamen en la supra ekzemplo ĝi povas akcepti nur ekzempleron de ArrayList. Ni povus havi duan metodon, kiu akceptas nur LinkedList, sed tio estus nenecesa komplikeco.
Ni jam konas eblan solvon de la problemo: La du (aŭ pli da) klasoj havu komunan superklason. Tia klaso vere ekzistas, ĝi nomiĝas AbstractList. Kiel sugestas ĝia nomo, ĝi estas abstrakta; la realigo de ArrayList kaj LinkedList ja estas tiom malsama, ke ne eblas meti partojn de ĝi en superklason. Sed nia metodo desegnu povas akcepti parametron de la klaso (kaj datentipo) AbstractList kaj tiel labori pri diversaj realigoj de listoj:
/** * Tiu chi metodo desegnas chiujn elementojn en listo. * @param elementojPorDesegni la listo de la desegnotaj elementoj; * ghi povas esti de diversaj listo-klasoj */ public void desegnu(AbstractList elementojPorDesegni) { … } |
Ankaŭ abstrakta klaso do reprezentas apartan daten-tipon kaj estas uzebla por specifi la tipon de parametro, variablo aŭ rezulto. Ofte ĝi havas malpli da metodoj ol la konkretaj sub-klasoj, kiuj realigas ĝin. En tiu okazo la daten-tipo de la abstrakta klaso estas "sub-tipo" de la konkretaj tipoj. Ne konfuziĝu: La abstrakta tipo estas sub-tipo de la aliaj, ĉar ĝi havas malpli da metodoj, sed ĝia klaso tamen estas super-klaso de la aliaj, ĉar en la klasa arbo ĝi staras super ili.
La uzo de abstraktaj klasoj havas en Ĝavo gravan limon: Kiel ni vidis, ĉiu klaso povas havi nur unu rektan superklason. La "plurobla heredado" en Ĝavo ne ekzistas (kun bona kialo: ĝi konsiderinde komplikas la programadon).
Do, kion fari, se iu klaso realigu du daten-tipojn kaj estu uzebla por ambaŭ? Ĝavo prezentas elegantan solvon por tio: Eblas grupigi kelkajn metodojn de klaso kaj doni al tiu grupo nomon. Tia grupo kun nomo nomiĝas interfaco (interface). Klaso povas realigi plurajn interfacojn kaj devas deklari tion per la vorto implements (= "realigas").
Interfaco en Ĝavo estas plenrajta daten-tipo, same kiel klaso. Ĝi estas uzebla por specifi la tipon de variablo, parametro aŭ rezulto de metodo. Sed por krei objekton, kiu realigas iun interfacon, necesas krei ekzempleron de konkreta klaso; interfacoj ne havas memstaran ekziston kiel objektoj, same kiel abstraktaj klasoj.
Kio estas la diferenco inter abstraktaj klasoj kaj interfacoj? Kiel dirite, klasoj povas "plivastigi" nur unu superklason (abstraktan aŭ konkretan), sed realigi plurajn interfacojn. Plie, abstraktaj klasoj povas realigi centrajn metodojn, kiuj estas egalaj por ĉiuj (aŭu almenaŭ multaj) konkretigaj subklasoj. Interfaco ne povas tion fari; en interfaco neniam estas rulebla program-kodo. Alivorte, ĉiuj metodoj de interfaco estas abstraktaj. (Pro tio ne necesas meti la vorton abstract en la difino de interfaca metodo.)
Metodoj en interfacoj estas implicite publikaj. Ne necesas meti la ŝlosilvorton public.
La objektema programado evoluigis metodojn por bilde (grafike) priskribi la interrilatojn inter klasoj kaj interfacoj. La plej grava el tiuj metodoj estas UML, la "Unueca Modeliga Lingvo" (angle: Unified Modelling Language). Ĉi permesas priskribi ne nur klasojn kaj interfacojn, sed multajn aliajn aspektojn de program-desegnado kaj programado. UML estas pure grafika lingvo; ĝi ne difinas manieron por "traduki" la bildojn al tekstoj.
Klasojn kaj interfacojn UML reprezentas kiel kestojn, kiuj supre havas titolon, la nomon de la klaso/interfaco. Sube povas esti la metodoj kaj – ĉe klasoj – la datenaj membroj.
Se klaso B estas subklaso de klaso A ("vastigas klason A"), oni desegnas sagon (kun triangula kapo) de B al A; same por interfacoj. Se klaso K realigas interfacon L, oni desegnas interrompitan sagon de K al L.
Abstraktajn kaj ne-abstraktajn klasojn oni distingas per la skrib-tipo: La titoloj de abstraktaj klasoj uzas kursivan skribon, aliaj normalan. Interfacojn oni distingas per la aldono interfaco (angle interface). Ĉar ĉiuj metodoj de interfacoj estas abstraktaj, ankaŭ iliaj nomoj uzas kursivan skribon.
Malgranda UML-ekzemplo (alklaku ĝin por malfermi ĝin en aparta fenestro) prezentas kelkajn klasojn el la pakaĵo java.util, inter ili (sube) la gravajn klasojn java.util.Vector, java.util.ArrayList kaj java.util.LinkedList. Kiel la bildo montras, ili ĉiuj estas derivitaj de la abstrakta klaso java.util.AbstractList, kiu realiga la interfacon java.util.List. Metodoj, kiuj ne interesiĝas pri la konkreta realigo de listo, do prefere uzu nur tiun interfacon; tiel ili lasas al la programisto la liberon ŝanĝi la realigon, kiam tio estas dezirinda.
La klaso java.util.AbstractList estas derivita de java.util.AbstractCollection, kiu realigas la interfacon java.util.Collection. Tio esprimas la fakton, ke ĉiu listo estas ankaŭ kolektaĵo.
Sed, inverse, ne ĉiuj kolektaĵoj estas listoj. Aroj (java.util.Set) estas kolektaĵoj, sed ne listoj; al ili mankas la difinita ordo inter iliaj anoj. Ankaŭ ne ĉiuj listoj estas aroj: Listo povas enhavi plurajn identajn anojn, aro ne povas.
Tiujn ĉi interrilatojn esprimas la UML-bildo. Ĉar ĉiuj Ĝavo-klasoj estas derivitaj de Object, la UML-grafo de la Ĝavo-klasoj estas arbo (kun unu sola radiko), se oni konsideras nur klasojn, ne interfacojn.
Kiu estas la diferenco inter la klaso AbstractList kaj la interfaco List? Unue, aliaj klasoj povas realigi plurajn interfacojn, sed deriviĝi nur de unu (vico da) klaso(j), do la interfaco ja estas utila. Sed por kio utilas la abstrakta klaso?
Kvankam AbstractList estas abstrakta, ne ĉiuj ĝiaj metodoj estas abstraktaj. Ekzemple ĝi realigas la metodon equals (= egalas), kiu komparas du listojn; du listoj estas egalaj, se ili enhavas egalajn elementojn en la sama sinsekvo. Tiun ĉi metodon povas utiligi la subklasoj, ne necesas, ke ili mem realigu ĝin. Sed se iu subklaso deziras mem realigi tian jam ekzistantan metodon, ĝi povas tion fari, kaj la dinamika bindado de Ĝavo aŭtomate uzos tiun metodon.
Ekzerceto: La klasoj java.util.Vector kaj java.util.ArrayList realigas plian interfacon, java.util.RandomAccess. Ĝi signifas, ke tiuj klasoj permesas rapidan aliron al siaj elementoj per la indica numero. java.util.LinkedList ne realigas tiun interfacon. Desegnu tiujn faktojn en la bildon.
Klarigo: "Rapida aliro" signifas, ke la tempo por aliri elementon laŭ numero ne dependas de tiu numero. Tabela listo (ekzemple ArrayList permesas tion. En ligita listo (ekzemple LinkedList necesas komenci ĉe la unua elemento kaj paŝe iri ĝis la dezirata; tio daŭras des pli longe, ju pli granda estas la indico (numero).