Java bàsic

Programes

Els programes consten d'interfícies i classes. Les interfícies i les classes es troben en fitxers font (extensió .java). Un fitxer font es compila en un o més fitxers de bytecode executables (extensió .class).

Les classes i les interfícies poden formar part d'un package. Un package és una mica com un mòdul, des del qual es poden importar recursos. Els fitxers de bytecode d'un package solen estar continguts en un directori el nom del qual és el nom del package.

Execució

Els programes primer s'han de compilar abans de poder ser executats. Un cop compilats, es poden executar en un ordinador. Per poder executar un programa Java compilat cal un intèrpret de bytecode anomenat màquina virtual Java (JVM).

Primer s'ha de compilar una aplicació Java, de la manera següent:

$ javac HelloWorld.java

El fitxer de bytecodes es pot executar de la manera següent:

$ java HelloWorld

Els errors de sintaxi i tipus es detecten en temps de compilació. Tots els altres errors es detecten en temps d'execució.

Hola, món

La versió més senzilla d'aquest programa defineix un mètode main dins d'una classe:

public class HelloWorld { static public void main(String[] args) { System.out.println("Hola món!"); } }

El mètode println, quan s'executa amb la variable de classe System.out, converteix les dades en text, les mostra i mou el cursor a la línia següent. Si es vol evitar la sortida d'un salt de línia, utilitzeu el mètode print.

El codi font defineix però no crida aquest mètode. A l'inici del programa, la classe compilada HelloWorld es carrega a la JVM. La JVM crida llavors main, que és un mètode de classe a HelloWorld.

Una aplicació Java ha d'incloure almenys una classe que defineixi un mètode main. El fitxer de codi de bytes per a aquesta classe és el punt d'entrada per a l'execució del programa.

Estructura i sintaxi

  • Els literals inclouen números, caràcters i cadenes.
  • Els identificadors inclouen els noms de variables, classes, interfícies i mètodes.
  • Les paraules reservades inclouen les de les principals sentències de control (if, while, for, import, etc.), operadors (instanceof, throw, etc.), definicions (public, class, etc.), valors especials (true, false, null, this, super, etc.) i noms de tipus estàndard (int, double, String, etc.).
  • La sagnia no és significativa, de manera que tots els elements lèxics estan separats per zero o més espais.
  • Els blocs de codi en les sentències i definicions estan entre claus ({}).
  • Les sentències simples acaben amb un punt i coma (;).
  • Les expressions booleanes en els bucles i les sentències if estan entre parèntesis.
  • Un comentari de final de línia comença amb el símbol //.
    // Això és un comentari de final de // línia // (en tres línies).
    Un comentari de diverses línies comença amb /* i acaba amb */.
    /* Aquest és un comentari de diverses línies. */

Tipus de dades

Hi ha dues grans categories de tipus de dades: els tipus primitius i els tipus de referència.

  • Els tipus primitius inclouen els tipus numèrics (double, int, char) i els booleans. Els valors dels tipus primitius són immutables i no són instàncies de classes.
    • "int" representa nombres enters que van des de -231 fins a 231-1 (4 bytes).
    • "long" representa nombres enters més grans que van des de -263 fins a 263-1 (8 bytes).
    • "short" representa nombres enters més petits que van des de -215 fins a 215-1 (2 bytes).
    • "float" representa nombres de coma flotant més petits amb 7 dígits de precisió (4 bytes).
    • "double" representa nombres de coma flotant més grans amb 16 dígits de precisió (8 bytes).
  • Els tipus de referència són classes. Per tant, qualsevol objecte o instància d'una classe és d'un tipus de referència. Aquests inclouen cadenes, matrius, llistes, mapes, etc.

Cadenes

  • Els literals de cadena es formen utilitzant les cometes dobles com a delimitadors.
  • Les cadenes són instàncies de la classe String. Les cadenes són objectes immutables.
  • Els literals de caràcters es formen utilitzant les cometes simples com a delimitadors.
  • Els caràcters són valors del tipus de dades char. Aquest tipus utilitza 2 bytes per representar el conjunt Unicode.
  • Una seqüència d'escapada, ja sigui com a caràcter o com a cadena, es forma utilitzant el caràcter '' seguit d'una lletra adequada com ara 'n' o 't'.

L'operador de concatenació + uneix dues cadenes per formar una tercera cadena nova. Si un dels operands és una cadena, l'altre operand pot ser de qualsevol tipus.

Exemples:

"35" + " pàgines de llargada." 35 + " pàgines de llargada."

Qualsevol objecte no numèric també es pot utilitzar en una concatenació de cadenes, perquè tots els objectes Java reconeixen el mètode toString(). Aquest mètode retorna el nom de la classe de l'objecte per defecte, però es pot substituir per retornar una cadena més descriptiva. Així, si x i y són objectes, el codi

x.toString() + y.toString() // o bé x + y

concatena les seves representacions de cadenes.

Una cadena és una seqüència de 0 o més caràcters.

Les cadenes són instàncies de la classe String. Les cadenes són objectes immutables.

El mètode length() retorna el nombre de caràcters d'una cadena.

El mètode charAt accedeix a un caràcter en una posició determinada.

Exemple:

String aString = "Hola món!" System.out.println(aString.length()); System.out.println(aString.charAt(2));

La classe String inclou molts mètodes útils per cercar, obtenir subcadenes, etc.

Les cadenes s'han de comparar utilitzant els mètodes equals i compareTo.

Aritmètica i condicions

Els operadors aritmètics inclouen +, -, *, / i %.

Dos operands sencers donen un resultat sencers. Com a mínim un operand float dóna un resultat float.

La classe Math inclou mètodes de classe com ara round, max, min, abs i pow (exponenciació), així com mètodes per a trigonometria, logaritmes, arrels quadrades, etc.

Exemples:

Math.round(3.14) Math.sqrt(2)

Els operadors de comparació són ==, !=, <, >, <= i >=.

Tots els operadors de comparació retornen True o False.

Tots els valors dels tipus primitius són comparables.

Els valors dels tipus de referència són comparables si i només si reconeixen el mètode compareTo. compareTo retorna 0 si els dos objectes són iguals (utilitzant el mètode equals), un enter negatiu si l'objecte receptor és menor que l'objecte paràmetre i un enter positiu si l'objecte receptor és major que l'objecte paràmetre. Les classes d'objectes comparables generalment implementen la interfície Comparable, que inclou el mètode compareTo.

Exemple:

String s = "AAB"; System.out.println(s.compareTo("AAA") > 0);

El tipus booleà inclou els valors constants true i false.

Els operadors lògics són ! (not), && (and) i || (or).

Les expressions booleanes compostes consten d'un o més operands booleans i un operador lògic. L'avaluació en curtcircuit s'atura quan hi ha prou informació disponible per retornar un valor. ! s'avalua abans de &&, que s'avalua abans de ||.

Conversions

Els tipus numèrics es poden convertir en altres tipus numèrics mitjançant els operadors de conversió adequats. Un operador de conversió es forma tancant el nom del tipus de destinació entre parèntesis.

Exemples:

(int) 3.14 (double) 3 (char) 45 (int) 'a'

Quan es converteix un nombre enter en un caràcter o viceversa, se suposa que el nombre enter és el valor Unicode del caràcter.

La manera més senzilla de convertir qualsevol valor en una cadena és concatenar-lo amb una cadena buida, de la manera següent:

"" + 3.14 "" + 45

Variables

<tipus> <variable>, …, <variable>;

<tipus> <variable> = <expressió>;

Exemples:

int x, y; x = 1; y = 2; int z = 3;

Una variable té un tipus, que s'especifica quan es declara. A una variable només se li pot assignar un valor que sigui compatible amb el seu tipus. Les incompatibilitats de tipus es detecten en temps de compilació.

A totes les variables d'instància i de classe se'ls donen valors per defecte quan es declaren. Tanmateix, el compilador requereix que el programador assigni un valor a les variables temporals dins dels mètodes abans que es puguin fer referència a elles. Per tant, es garanteix que totes les variables tinguin un valor en temps d'execució.

Les assignacions tenen la forma:

Forma:

<variable> = <expressió>;

Exemple:

int x; x = 1; x += 1;

Una variable té un tipus, que s'especifica quan es declara. A una variable només se li pot assignar un valor que sigui compatible amb el seu tipus.

Els valors de tipus menys inclusius es poden assignar a variables de tipus més inclusius. Invertir aquest ordre requereix una conversió de tipus explícita abans de l'assignació.

Exemple:

double d; d = 34; int i; i = (int)3.14

Mètodes

Una crida a un mètode d'instància normalment consisteix en una referència d'objecte (també anomenada objecte receptor), seguida d'un punt, seguit del nom del mètode i una llista d'arguments entre parèntesis.

Exemple:

aList.set(3, 45);

Una crida a un mètode de classe consisteix en un nom de classe, seguit d'un punt, seguit del nom del mètode i una llista d'arguments entre parèntesis.

Exemple:

double d = Math.sqrt(2);

L'instrucció return surt d'un mètode. Si no s'especifica cap expressió, es retorna el valor void. El valor retornat ha de ser de tipus compatible amb el tipus de retorn del mètode.

return 0;

Tots els mètodes estan definits per retornar un tipus de valor específic. Quan no s'espera el valor de retorn, el tipus de retorn del mètode és void. En cas contrari, el tipus de valor retornat ha de ser compatible, en temps de compilació, amb el tipus de valor esperat (l'exemple mostra una assignació d'un double a un double).

Un recurs de paquet s'importa mitjançant la forma:

import <nom del paquet>.<nom del recurs>;

A continuació, es fa referència al recurs sense el nom del package com a qualificador.

Exemple:

import javax.swing.JButton; JButton b = new JButton("Restablir");

Alternativament, es poden importar tots els recursos del paquet mitjançant el formulari

import <nom del paquet>.*;

De vegades, dos recursos tindran el mateix nom en paquets diferents. Per utilitzar tots dos recursos, no els importeu, sinó que només feu-hi referència utilitzant els noms dels paquets com a qualificadors.

Exemple:

java.util.List<String> names = new java.util.ArrayList<String>(); java.awt.List namesView = new java.awt.List();

Control de flux

if

Forma:

if (<expressió booleana>) { // instruccions } else if (<expressió booleana>) { // instruccions } else { // instruccions }

Les instruccions del conseqüent i de cada alternativa estan marcades amb claus ({}). Quan només hi ha una instrucció, es poden ometre les claus.

Cada expressió booleana està entre parèntesis.

while

Els bucles poden fer-se amb while:

while (<expressió booleana>) { // instruccions }

Les instruccions del cos del bucle estan marcades amb claus ({}). Quan només hi ha una instrucció al cos del bucle, es poden ometre les claus.

L'expressió booleana s'inclou entre parèntesis.

L'instrucció break surt d'un bucle.

while (true) break;

for

Hi ha dos tipus, un bucle per visitar cada element d'un objecte iterable i un bucle amb el mateix comportament que un bucle while.

Forma del primer tipus (també anomenada bucle for millorat):

for (<tipus> <variable>: <iterable>) { // instruccions }

Exemple:

for (String s: aListOfStrings) { System.out.println(s); }

La variable recull el valor de cada element de l'objecte iterable i és visible al cos del bucle.

Les instruccions del cos del bucle estan marcades amb claus ({}). Quan només hi ha una instrucció al cos del bucle, es poden ometre les claus.

Forma del segon tipus:

for (<inicialitzador>; <continuació>; <actualització>) { // instruccions }

Exemple:

for (int i = 1; i <= 10; i++) { System.out.println(i); }

Aquest bucle té el mateix comportament que el següent bucle while:

int i = 1; while (i <= 10) { System.out.println(i); i++; }

Classes i objectes

La forma per a la instanciació d'objectes és

new <nom de la classe>(<arguments>)

Exemple:

CompteCorrent compte = new CompteCorrent("Ken", "1111", 500.00);

Les col·leccions genèriques també requereixen un o més paràmetres de tipus per als tipus d'elements, de la forma

new <nom de la classe><tipus d'elements>(<arguments>)

Exemple:

List<String> noms = new ArrayList<String>();

Les variables i els paràmetres es declaren amb un tipus. A una variable o paràmetre només se li pot assignar un valor que sigui compatible amb el seu tipus.

Els valors de tipus menys inclusius es poden assignar a variables o paràmetres de tipus més inclusius. Invertir aquest ordre requereix una conversió de tipus explícita abans de l'assignació.

Exemple:

String s1 = "Hello"; Object obj = s1; String s2 = (String)obj; void aMethod(String s) { String other = s; return; } aMethod(s1); aMethod((String)obj);

Interfícies

Una interfície consta d'un nom i un conjunt de capçaleres de mètode. Especifica el conjunt de mètodes que una classe d'implementació ha d'incloure. Les interfícies de les classes integrades es poden veure al JavaDoc.

Una sola classe pot implementar diverses interfícies diferents.

Una interfície garanteix un comportament abstracte comú de totes les classes d'implementació. Per exemple, la interfície java.util.List especifica mètodes per a totes les classes de llistes, incloent-hi java.util.ArrayList i java.util.LinkedList.

Una interfície pot estendre una altra interfície més general. Per exemple, la interfície List estén la interfície Collection. Això significa que tots els mètodes requerits per la interfície Collection també seran necessaris per a totes les llistes.

Les interfícies són com la cola intersticial que uneix els components del programa. Sempre que sigui possible, utilitzeu noms d'interfície per als tipus de variables, paràmetres i tipus de retorn de mètodes.

Usos de les interfícies:

List<String> aList = new ArrayList<String>(); aList.add(("Mary"); aList.add("Bill"); Collections.sort(aList);

La classe ArrayList implementa la interfície List. Observeu l'ús de List en lloc d'ArrayList i LinkedList per escriure les variables list1 i list2.

El mètode Collections.sort espera una col·lecció de comparables com a argument. Com que la classe String implementa la interfície Comparable i la interfície List estén la interfície Collection, el compilador no es queixa i tot va bé.

Finalment, observeu que el constructor LinkedList pot acceptar una col·lecció com a argument. Això permet construir una nova llista a partir dels elements continguts en una altra llista, independentment de la implementació.

Arrays

Una matriu és una seqüència d'elements del mateix tipus. S'accedeix a cada element en temps constant mitjançant una posició d'índex. A diferència d'una llista, la longitud d'una matriu és fixa quan es crea i no es pot canviar. A més, les úniques operacions en una matriu són l'accés o la substitució d'elements mitjançant subíndexs.

Com altres estructures de dades, les matrius són objectes i, per tant, són de tipus referència. El tipus d'una matriu està determinat pel seu tipus d'element, que s'especifica quan s'instancia la matriu.

Exemples:

int[] ages = new int[10]; String[] names = new String[10];

Les variables ages i names ara fan referència a matrius capaces de contenir 10 sencers i 10 cadenes, respectivament. Cada element de la matriu a ages té un valor per defecte de 0. Cada element de la matriu a names té un valor per defecte de null (igual que una nova matriu els elements de la qual són de qualsevol tipus de referència).

La longitud d'una matriu es pot obtenir de la variable de longitud de l'objecte matriu, de la següent manera:

System.out.println(names.length);

Els elements de la matriu names es poden reiniciar amb un bucle for, utilitzant el subíndex i la variable length, de la manera següent:

java.util.Scanner input = new java.util.Scanner(System.in); for (int i = 0; i < names.length; i++) names[i] = reader.nextLine("Introduïu un nom: ");

El bucle for millorat es pot utilitzar només per fer referència als elements d'una matriu:

for (String name: names) System.out.println(name);

Col·leccions

Llistes

Una llista és una seqüència mutable de 0 o més objectes de qualsevol tipus. Una llista genèrica restringeix els seus elements al mateix supertipus.

La interfície List inclou els mètodes comuns a totes les classes d'implementació.

Les classes d'implementació inclouen ArrayList i LinkedList.

Una llista genèrica especifica el tipus d'element de la variable de llista i l'objecte de llista instanciat, de la manera següent:

List<String> names = new ArrayList<String>(); List<Integer> ages = new LinkedList<Integer>();

Tingueu en compte primer que ambdues variables de llista es veuen com a de tipus List, tot i que fan referència a instàncies de classes de llista diferents. Tots dos objectes de llista respondran a qualsevol mètode de la interfície List.

Tingueu en compte en segon lloc que la primera llista només pot contenir cadenes, mentre que la segona llista només pot contenir instàncies de la classe Integer.

Tingueu en compte en tercer lloc que la classe Integer és una classe contenidora, que permet emmagatzemar valors de tipus int en una llista. Quan s'insereix un enter a la segona llista, la JVM l'embolica en un objecte Integer. Quan s'accedeix a un objecte Integer en aquesta llista, es retorna el valor int que conté.

Exemple:

ages.add(63); ages.set(0, ages.get(0) + 1); // Incrementa l'edat

Sets

Un conjunt és una col·lecció mutable de 0 o més objectes únics de qualsevol tipus. Un conjunt genèric restringeix els seus elements al mateix supertipus.

La interfície Set inclou els mètodes comuns a totes les classes d'implementació. La interfície SortedSet estén la interfície Set per incloure mètodes per a conjunts ordenats.

Les classes d'implementació inclouen HashSet i TreeSet. Un TreeSet pot retornar els seus elements en ordre ordenat. Per tant, també implementa la interfície SortedSet.

Un conjunt genèric especifica el tipus d'element de la variable de conjunt i l'objecte de conjunt instanciat, de la manera següent:

Set<String> names = new HashSet<String>(); SortedSet<Integer> ints = new TreeSet<Integer>();

Exemple:

// Suma 10 enters aleatoris entre 1 i 10 for (int i = 1; i <= 10; i++) ints.add((int)(Math.random() * 10 + 1)); // Imprimeix tots els elements ordenats for (int element : ints) System.out.println(element);

Observeu l'ús d'un bucle for per accedir als elements d'un conjunt.

També es pot crear un conjunt a partir d'una llista, de la manera següent:

Set<String> names = new HashSet<String>(listOfNames);

Les col·leccions Java solen incloure un constructor que accepta una altra col·lecció com a argument i afegeix els seus elements a la col·lecció recentment instanciada.

Diccionaris o mapes

Un mapa és una col·lecció mutable de 0 o més parells clau/valor únics. Dit d'una altra manera, un mapa conté un conjunt de claus, on cada clau està associada a un valor. Un mapa genèric restringeix les seves claus al mateix supertipus i els valors al mateix supertipus.

La interfície Map inclou els mètodes comuns a totes les classes d'implementació. La interfície SortedMap estén la interfície Map per incloure mètodes per a mapes ordenats.

Les classes d'implementació inclouen HashMap i TreeMap. Un TreeMap pot retornar les seves claus en ordre ordenat. Per tant, també implementa la interfície SortedMap.

Un mapa genèric especifica els tipus clau/valor de la variable de mapa i l'objecte de mapa instanciat, de la manera següent:

Map<String, Integer> names = new HashMap<String, Integer>(); SortedMap<Integer, Integer> ints = new TreeMap<Integer, Integer>();

Exemple:

// Associa 10 edats aleatòries entre 1 i 10 // amb noms consecutius for (int i = 1; i <= 10; i++) { String name = "Name" + i; names.put(name, (int)(Math.random() * 10 + 1)); } // Imprimeix totes les claus i els seus valors for (String key : names.keySet()) System.out.println(key + " " + names.get(key));

Observeu l'ús d'un bucle for per accedir al conjunt de claus retornades per keySet().

Iterators

Un iterador és un objecte que admet el recorregut d'una col·lecció. El compilador genera codi que utilitza un iterador sempre que veu un bucle for millorat.

Totes les col·leccions implementen la interfície Iterable. Aquesta interfície inclou un únic mètode, iterator(), que retorna un objecte iterador.

Un objecte iterador implementa la interfície Iterator. Aquesta interfície inclou els mètodes next(), hasNext() i remove().

Com les col·leccions, els iteradors poden ser genèrics. Per tant, s'ha d'especificar el tipus d'element de la col·lecció genèrica quan es declara una variable de tipus Iterator.

Exemple d'ús:

// Crea una llista de cadenes List<String> list = new ArrayList<String>(); // Afegeix algunes cadenes a list // Obre un iterador a list Iterator<String> i = list.iterator(); // Imprimeix totes les cadenes de text de list utilitzant l'iterador while (i.hasNext()) { String s = i.next(); System.out.println(s); }

Definició de classes

Les definicions de classe tenen la forma general:

<modificador de visibilitat> class <nom> extends <nom de la superclasse> implements <llista de noms> { <variables de classe> <mètodes de classe> <variables d'instància> <mètodes d'instància> <classes internes> }

Les classes que no estenen explícitament una altra classe estenen la classe Object per defecte. Una classe pot implementar zero o més interfícies.

Exemple:

public class Student { public static final int NUM_GRADES = 5; private String name; private int[] grades; public Student(String name) { this.name = name; this.grades = new int[NUM_GRADES]; } public String getName() { return this.name; } public int getGrade(int i) { return this.grades[i – 1]; } public void setGrade(int i, int newGrade) { this.grades[i - 1] = newGrade; } public String toString() { String result = this.name + '\n'; for (String grade : this.grades) result += grade + ' '; return result; } }

Ús:

Student s = new Student("Maria"); for (int i = 1; i <= Student.NUM_GRADES; i++) s.setGrade(i, 100); System.out.println(s);

Visibilitat

Hi ha quatre nivells d'accés a les classes i als elements definits dins d'elles. Hi ha tres modificadors de visibilitat que especifiquen l'accés: public, private i protected.

L'accés public permet que qualsevol component del programa faci referència a un element.

Exemple:

public static final int NUM_GRADES = 0;

L'accés privat permet l'accés a un element només per part d'altres elements dins de la definició de la classe que l'envolta.

Exemple:

private int[] grades;

L'accés protegit estén l'accés a un element des de la classe que el defineix a totes les subclasses.

Exemple:

protected String name;

Quan s'omet un modificador de visibilitat, l'element té accés al package. Això significa que l'accés s'estén des de la classe que el defineix a tots els components del mateix package. Per a la majoria de propòsits, l'accés al package és equivalent a l'accés public.

Variables i constructors

Les variables d'instància es declaren al mateix nivell que els mètodes dins d'una definició de classe. Normalment se'ls dóna accés privat per restringir la visibilitat. Poden rebre valors inicials quan es declaren o en un constructor.

Les referències a variables d'instància poden o no tenir el prefix reservat this.

A l'exemple següent, les variables this.name i this.grades són variables d'instància, mentre que la variable NUM_GRADES és una variable de classe:

public class Student { public static final int NUM_GRADES = 5; private String name; private int[] grades; public Student(String name) { this.name = name; this.grades = new int[NUM_GRADES]; } }

La JVM crida automàticament el constructor quan el programador sol·licita una nova instància de la classe, de la següent manera:

Student s = new Student("Mary");

El constructor pot rebre un o més arguments per subministrar valors inicials per a les dades de l'objecte.

Un constructor per defecte no espera cap argument de la persona que truca i assigna valors per defecte raonables a les variables d'instància d'un objecte. Altres constructors esperen un o més arguments que permetin al programador especificar aquests valors.

La sobrecàrrega de mètodes permet definir més d'un constructor, sempre que tinguin nombres i/o tipus d'arguments diferents.

A l'exemple següent, la classe Student rep un constructor per defecte que no espera cap argument. El nou constructor crida l'altre constructor mitjançant la paraula clau this i l'argument apropiat:

public class Student { public static final int NUM_GRADES = 5; private String name; private int[] grades; public Student(String name) { this.name = name; this.grades = new int[NUM_GRADES]; } public Student() { this(""); } }

Ús:

Student s1 = new Student("Mary"); Student s2 = new Student();

Mètodes d'instància

La forma de la definició d'un mètode d'instància és

<modificador de visibilitat> <tipus de retorn> <nom>(<arguments>) { <sentències> }

Tots els mètodes han d'especificar un tipus de retorn. Si no es retorna cap valor, aquest tipus ha de ser void. El valor retornat per qualsevol sentència de retorn ha de ser compatible amb el tipus de retorn del mètode. Un mètode no void ha de tenir almenys una sentència de retorn accessible.

Cada paràmetre de la capçalera del mètode, si n'hi ha, ha d'incloure el tipus del paràmetre. La sintaxi d'aquests és similar a la de les declaracions de variables.

Quan es crida un mètode, els seus arguments han de coincidir en nombre i tipus amb els paràmetres corresponents de la definició del mètode. Tota la comprovació de tipus es fa en temps de compilació.

Exemple:

public class Student { // definició dels constructors i variables public String getName() { return this.name; } public int getGrade(int i) { return this.grades[i – 1]; } public void setGrade(int i, int novaGrade) { this.grades[i - 1] = novaGrade; } }

La signatura d'un mètode consisteix en el seu nom i els tipus de paràmetre. El tipus de retorn no s'inclou a la signatura. Dos mètodes es sobrecarreguen si tenen el mateix nom però signatures diferents.

Per exemple, una classe Student pot tenir tres mètodes anomenats resetGrades per restablir totes les qualificacions. Tots són mètodes diferents.

El primer mètode, que no espera cap argument, restableix cada qualificació a 0:

public void resetGrades() { resetGrades(0); }

El segon mètode, que espera un únic argument sencer, restableix cada qualificació a aquest sencer.

public void resetGrades(int grade) { for (int i = 0; i < Student.NUM_GRADES; i++) this.grades[i] = grade; }

El tercer mètode, que espera una matriu d'enters, restableix les qualificacions a aquests enters a les posicions corresponents de la matriu.

public void resetGrades(int[] notes) { for (int i = 0; i < Student.NUM_GRADES; i++) this.grades[i] = notes[i]; }

Ús:

Student s = new Student(); s.resetGrades(100); s.resetGrades(); int[] newGrades = {85, 66, 90, 100, 73}; s.resetGrades(newGrades);

Mètodes i variables de classe

Les variables de classe anomenen dades que comparteixen totes les instàncies d'una classe.

Una declaració de variable de classe es qualifica amb la paraula reservada static.

Fora de la definició de la classe, les referències a variables de classe tenen com a prefix el nom de la classe.

Les variables de classe s'escriuen en majúscules per convenció.

Exemple:

public class Student { public static final int NUM_GRADES = 5; private String name; private int[] grades; public Student(String name) { this.name = name; this.grades = new int[NUM_GRADES]; } }

Altres usos:

System.out.println(Student.NUM_GRADES);

Els mètodes de classe són mètodes que no saben res sobre les instàncies d'una classe, però poden accedir a variables de classe i cridar altres mètodes de classe per a diversos propòsits. Per exemple, un mètode per convertir una nota numèrica en una nota amb lletra es pot definir com un mètode de classe a la classe Student.

Una capçalera de mètode de classe es qualifica amb la paraula reservada static.

Fora de la definició de la classe, les crides a mètodes de classe tenen com a prefix el nom de la classe.

Exemple:

public class Student { // Definicions de mètodes d'instància i declaracions de variables public static char getLetterGrade(int grade) { if (grade > 89) return 'A'; else if (grade > 79) return 'B'; else return 'F'; } }

Ús:

s = new Student(); for (int i = 1; i <= Student.NUM_GRADES; i++) System.out.println(Student.getLetterGrade(s.getGrade(i));

Modificador final

Les variables finals serveixen com a constants simbòliques. Una declaració de variable final es qualifica amb la paraula reservada final. La variable s'estableix a un valor a la declaració i no es pot reiniciar. Qualsevol intent d'aquest tipus es detecta en temps de compilació.

Exemple:

public class Student { public static final int NUM_GRADES = 5; private String name; private int[] notes; public Student(String name) { this.name = name; this.grades = new int[NUM_GRADES]; } }

Equals

El mètode equals() compara dos objectes per verificar la igualtat. Aquest mètode utilitza l'operador == per defecte. L'operador == comprova la igualtat de dues referències d'objectes: es refereixen exactament al mateix objecte a la memòria? Sovint, aquesta prova és massa restrictiva. Una versió més relaxada compararia un o més dels atributs dels objectes per verificar la igualtat. Per exemple, dos objectes Student podrien tenir el mateix nom i considerar-se iguals, tot i que també són objectes diferents. Aquest tipus d'igualtat s'anomena equivalència estructural, a diferència del tipus més restrictiu d'identitat d'objecte.

El programador pot anul·lar la definició per defecte d'equals incloent una definició d'aquest mètode en una classe determinada. Els atributs que es comparen són els de l'objecte receptor (this) i l'objecte paràmetre (other).

La capçalera del mètode per a equals és:

public boolean equals(Object other)

Tingueu en compte que el tipus de l'objecte paràmetre és Object. Això permet comparar qualsevol objecte amb l'objecte receptor per verificar la igualtat. En conseqüència, en realitat hi ha tres proves per realitzar en equals. La primera compara els dos objectes per verificar la identitat mitjançant ==. La segona utilitza l'operador instanceof per determinar si el tipus de l'objecte paràmetre és el mateix que el de l'objecte receptor. La tercera prova compara els atributs rellevants dels dos objectes utilitzant el mètode equals amb ells.

Abans d'accedir als atributs del paràmetre, el tipus del paràmetre s'ha de convertir al tipus de l'objecte receptor mitjançant un operador de conversió.

Exemple:

public class Student { public static final int NUM_GRADES = 5; private String name; private int[] notes; public Student(String name) { this.name = name; this.grades = new int[NUM_GRADES]; } public boolean equals(Object other) { if (this == other) return true; else if !(other instanceof Student) return false; else { Student otherStudent = (Student)other; return this.name.equals(otherStudent.name); } } }

Ús:

Student s1 = new Student("Mary"); Student s2 = new Student("Bill"); Student s3 = new Student("Bill"); System.out.println(s1.equals(s2)); // mostra false System.out.println(s2.equals(s3)); // mostra true System.out.println(s1 == s3); // mostra false System.out.println(! s2.equals(s3)); // mostra false

Comparable

Una classe d'objectes comparables implementa la interfície Comparable. Per exemple, la classe String implementa Comparable. Aquesta interfície especifica un únic mètode compareTo, de la següent manera:

public interface Comparable<E> { public int compareTo(E element); }

Aquesta interfície és genèrica; el tipus d'element E s'emplena amb la classe que l'implementa (normalment, el nom de la classe mateixa). El mètode compara els atributs rellevants de l'objecte receptor i l'objecte paràmetre. compareTo retorna 0 si els dos objectes són iguals (utilitzant el mètode equals), un enter menor que 0 si el receptor és menor que el paràmetre i un enter major que 0 en cas contrari. L'exemple següent utilitza els noms dels estudiants com a atributs comparables:

Exemple:

public class Student implements Comparable<Student> { private String name; public Student(String name) { this.name = name; } public int compareTo(Student other) { return this.name.compareTo(other.name); } }

Definició d'interfícies

La forma d'una interfície és

public interface <name> extends <name> { <variables finals> <capçaleres de mètode> }

L'extensió d'una altra interfície és opcional. Una capçalera de mètode acaba amb un punt i coma.

En el següent exemple, la interfície TrueStack es defineix per a totes les implementacions que restringeixen les operacions a les estàndard de les piles. Com que TrueStack estén Iterable, cada implementació també ha d'incloure un mètode iterator(), i cada pila es pot recórrer amb un bucle for millorat.

El codi d'aquesta interfície es col·locaria al seu propi fitxer font, anomenat TrueStack.java. Aquest fitxer es pot compilar abans de definir cap classe d'implementació.

Exemple d'interfície:

public interface TrueStack<E> extends Iterable<E> { public boolean isEmpty(); public E peek(); public E pop(); public void push(E element); public int size(); }

Cada classe d'implementació es col·locaria al seu propi fitxer. Una interfície s'ha de compilar correctament abans de qualsevol de les seves classes d'implementació.

Exemple d'implementació, basat en una ArrayList:

import java.util.*; public class ArrayStack<E> implements TrueStack<E> { private List<E> list; public ArrayStack() { list = new ArrayList<E>(); } public boolean empty() { return list.isEmpty(); } public E peek() { return list.get(list.size() - 1); } public E pop() { return list.remove(list.size() - 1); } public void push(E element) { list.add(element); } public int size() { return list.size(); } public Iterator<E> iterator() { return null; // TODO } }

Classes internes

Una classe es pot definir només per al seu ús en una altra definició de classe. Per exemple, una classe LinkedStack pot utilitzar una classe OneWayNode. La definició de OneWayNode es pot imbricar dins de LinkedStack. OneWayNode s'especifica com una classe privada, de vegades també anomenada classe interna. Aquí teniu una implementació:

public class LinkedStack<E> implements TrueStack<E>{ private OneWayNode<E> items; private int size; public LinkedStack() { this.items = null; this.size = 0; } public void push(E element) { this.items = new OneWayNode<E>(element, items); this.size += 1; } public E pop() { E element = this.items.data; this.items = this.items.next; this.size -= 1; return element; } public E peek() { return this.items.data; } public boolean isEmpty() { return this.size() == 0; } public int size() { return this.size; } public Iterator<E> iterator() { return null; // TODO } private class OneWayNode<E> { private E data; private OneWayNode next; private OneWayNode(E data, OneWayNode next) { this.data = data; this.next = next; } } }

Definició d'iteradors

El mètode iterator() retorna un iterador en un objecte iterable. L'usuari d'un iterador pot esperar que el mètode next() retorni el següent objecte en una iteració, mentre que el mètode hasNext() retorna True.

Suposem que la classe LinkedStack ara inclou un mètode iterador. Aleshores, es poden visitar els objectes d'una pila, de dalt a baix, de qualsevol de les maneres següents:

TrueStack<String> stack = new LinkedStack<String>(); i = stack.iterator(); while (i.hasNext()) { String element = i.next() // processament } for (String element: stack) // processament

La classe d'implementació defineix un mètode iterator() que retorna una instància d'una classe interna. Aquesta classe implementa la interfície Iterator. Els seus mètodes next() i hasNext() rastregen un punter de posició actual als elements de la col·lecció.

Tingueu en compte que diversos iteradors poden estar oberts simultàniament a la mateixa col·lecció. Per mantenir la coherència de cada iterador amb les dades de la col·lecció, no es permeten modificacions basades en la col·lecció (push, pop) durant el funcionament de cap iterador. La col·lecció ara manté un recompte de les seves modificacions. Quan s'instancia un iterador, estableix el seu propi recompte de modificacions al recompte de la col·lecció. A cada crida del mètode next(), l'iterador compara els dos recomptes. Si no són iguals, s'ha produït una modificació basada en la col·lecció i es genera una excepció.

Exemple:

import java.util.iterator; public class LinkedStack<E> implements TrueStack<E> { private OneWayNode<E> items; private int size; private int modCount; public LinkedStack() { this.items = null; this.size = 0; this.modCount = 0; } public void push(E element) { this.items = new OneWayNode<E>(element, items); this.size += 1; this.modCount += 1; } public E pop() { E element = this.items.data; this.items = this.items.next; this.size -= 1; this.modCount += 1; return element; } public E peek() { return this.items.data; } public boolean isEmpty() { return this.size() == 0; } public int size() { return this.size; } public Iterator<E> iterator() { return new StackIterator<E>(); } private class StackIterator<E> implements Iterator<E> { private OneWayNode curPos; private int curModCount; private StackIterator() { this.curPos = items; this.curModCount = modCount; } public boolean hasNext() { return curPos != null; } public E next() { if (! this.hasNext() throw new IllegalStateException(); if (this.curModCount != modCount) throw new ConcurrentModificationException() E data = this.curPos.data; this.curPos = this.curPos.next(); return data; } public void remove() { throw new UnsupportedOperationException(); } } private class OneWayNode<E> { private E data; private OneWayNode next; private OneWayNode(E data, OneWayNode next) { this.data = data; This.next = next; } } }

Herència

En un llenguatge de programació orientat a objectes, es pot definir una nova classe que reutilitza el codi d'una altra classe. La nova classe esdevé una subclasse de la classe existent, que també s'anomena la seva classe pare. La subclasse hereta tots els atributs, inclosos els mètodes i les variables, sempre que s'especifiquin com a públics o protegits, de la seva classe pare i de qualsevol classe ancestre de la jerarquia.

Fer subclasses és una manera convenient de proporcionar funcionalitats addicionals o més especialitzades a un recurs existent. Per exemple, una cua garanteix l'accés als seus elements en ordre de primer a entrar, primer a sortir. Una cua de prioritat es comporta en la majoria dels aspectes igual que una cua, excepte que els elements de més prioritat s'eliminen primer. Si els elements tenen la mateixa prioritat, s'eliminen en ordre FIFO estàndard. Una cua de prioritat és, doncs, una subclasse de cua, amb un mètode especialitzat per a insercions que garanteix l'ordre adequat dels elements. La cua de prioritat obté tots els seus altres mètodes de la classe de cua gratuïtament.

La classe LinkedQueue fa que les seves variables d'instància siguin visibles per a les subclasses però no per a les altres declarant-les com a protegides. De la mateixa manera, la classe OneWayNode interna i els seus atributs també es defineixen com a protegits.

Aquí teniu la definició d'una classe LinkedQueue per a cues ordinàries:

import java.util.*; public class LinkedQueue<E> implements TrueQueue<E> { protected OneWayNode<E> front, rear; Protected int size; public LinkedQueue() { this.front = this.rear = null; this.size = 0; } public void enqueue(E element) { OneWayNode<E> n = OneWayNode<E>(element, null); if (this.isEmpty()) this.rear = this.front = n; else { this.rear.next = n; this.rear = n; } this.size += 1; } public E dequeue() { E element = this.front.data; this.front = this.front.next; this.size -= 1; if (this.isEmpty()) this.rear = null; return element; } public E peek() { return this.front.data; } public boolean isEmpty() { return this.size() == 0; } public int size() { return this.size == 0; } protected class OneWayNode<E> { protected E data; protected OneWayNode<E> next; protected OneWayNode(E data, OneWayNode next) { this.data = data; this.next = next; } } }

La forma per definir una subclasse és

public class extends {

<variables i mètodes>

}

La classe LinkedPriorityQueue és una subclasse de LinkedQueue. La nova classe inclou un constructor i el mètode enqueue. El constructor de LinkedPriorityQueue és opcional, però crida explícitament el constructor a la seva classe pare, utilitzant l'instrucció

super();

Això fa que la classe pare inicialitzi les seves variables d'instància.

El nou mètode enqueue primer comprova si hi ha una cua buida i, si això és cert, crida el mètode enqueue a la classe pare utilitzant la forma:

super.<nom del mètode>(<arguments>)

En cas contrari, el mètode busca la posició adequada del nou element i l'insereix allà. Com que la part frontal de la cua es troba al capdavant de l'estructura enllaçada, la cerca només s'atura quan l'element entrant és estrictament menor que l'element de la cua. Per tant, un element entrant es col·loca darrere de tots els elements de la mateixa prioritat.

Els elements d'una cua de prioritat han de ser comparables. Per tant, el tipus d'element de la classe LinkedPriorityQueue s'especifica com a Comparable. La sintaxi per relacionar els tipus d'element LinkedPriorityQueue i LinkedQueue a la capçalera de la classe és una mica complicada, però fa la feina.

Aquí teniu la definició de LinkedPriorityQueue:

import java.util.*; public class LinkedPriorityQueue<E extends Comparable> extends LinkedQueue<E> { public LinkedPriorityQueue() { super(); } public void enqueue(E element) { if (this.isEmpty()) super.enqueue(element); else { OneWayNode<E> probe = this.front; OneWayNode<E> trailer = null; while (true) { if (probe == null || element.compareTo(probe.data) < 0) break; trailer = probe; probe = probe.next; } if (probe == null) // final de la cua this.rear.next = new OneWayNode<E>(element, null); this.rear = this.rear.next; else if (probe == this.front) // començament de la cua this.front = new OneWayNode<E>(element, this.front.next); else // al mig trailer.next = new OneWayNode<E>(element, probe); this.size += 1; } } }

Classes abstractes

Quan dues o més classes contenen codi redundant, es pot factoritzar en una classe pare comuna. Aquesta classe normalment s'anomena classe abstracta, perquè no està instanciada.

Considereu dues implementacions de piles, ArrayStack i LinkedStack. Els seus contenidors interns d'elements són diferents, així com els seus mètodes per afegir, eliminar i iterar a través d'aquestes estructures. Tanmateix, si suposem que cada pila fa un seguiment de la seva mida de la mateixa manera, els mètodes len i isEmpty seran els mateixos per a qualsevol implementació. I si suposem que cada implementació inclou un mètode iter, podem implementar diversos altres mètodes, com ara str i addAll, només una vegada per a totes les piles.

L'exemple següent defineix una classe AbstractStack que inclou la informació comuna a totes les implementacions de pila. Per accedir a aquests recursos, una implementació de pila, com ara ArrayStack o LinkedStack, simplement estén AbstractStack. Aquesta classe s'especifica com a abstract amb la paraula reservada abstract. La classe també ha d'incloure tots els mètodes de la interfície TrueStack. Els mètodes que s'han de definir a les subclasses s'especifiquen com a mètodes abstractes, de nou amb la paraula reservada abstracte.

import java.util.*; abstract public AbstractStack<E> implements TrueStack<E> { protected int size, modCount; public AbstractStack() { this.size = 0; this.modCount = 0; } public boolean empty() { return this.size() == 0; } public int size() { return size; } public String toString() { String result = ''; for (E element : this) result += str(element) + '\n'; return result; } public void addAll(Collection<E> col) { for (E element : col) this.push(element); } abstract public E peek(); abstract public E pop(); abstract public void push(E element); abstract public Iterator<E> iterator(); }

AbstractStack és responsable d'inicialitzar dues variables d'instància, size i modCount. Cal tenir en compte que el mètode toString utilitza un bucle for sobre això, cosa que implica que l'objecte de pila és iterable. En conseqüència, cada implementació de pila ha d'incloure la seva pròpia definició del mètode iterator. Finalment, cal tenir en compte que el mètode addAll copia elements de qualsevol objecte Collection a la pila. Qualsevol objecte Collection reconeix el mètode iterator i, per tant, es pot utilitzar amb un bucle for.

La primera implementació, ArrayStack, inclou dos constructors. Un permet al programador especificar un objecte Collection com a argument. Aquest objecte es passa al mètode addAll per copiar els seus elements a la nova pila. També es pot cridar addAll per afegir diversos elements en qualsevol moment posterior. ArrayStack també és responsable de mantenir la seqüència d'elements. Per tant, inclou les definicions de peek, pop, push i iterator.

import java.util.*; public class ArrayStack<E> extends AbstractStack<E> { private List<E> list; public ArrayStack() { super(); this.list = new ArrayList<E>(); } public ArrayStack(Collection<E> col) { this(); this.addAll(col); } public E peek() { if (this.isEmpty()) throw new EmptyStackException(); return list.get(this.size() - 1); } public E pop() { if (this.isEmpty()) throw new EmptyStackException(); this.size -= 1; this.modCount += 1; return this.list.remove(this.size()); } public void push(E element) { this.list.add(element); this.size += 1; this.modCount += 1; } public Iterator<E> iterator() { return new StackIterator<E>(); } private class StackIterator<E> implements Iterator<E> { private int curPos; private int curModCount; private StackIterator() { this.curPos = this.size() - 1; this.curModCount = modCount; } public boolean hasNext() { return curPos >= 0; } public E next() { if (! this.hasNext() throw new IllegalStateException(); if (this.curModCount != modCount) throw new ConcurrentModificationException() E data = list.get(this.curPos); this.curPos -= 1; return data; } public void remove() { throw new UnsupportedOperationException(); } } }

Els mateixos mètodes es defineixen a LinkedStack, però és clar que accedeixen a elements en un tipus d'estructura interna molt diferent.

import java.util.iterator; public class LinkedStack<E> extends AbstractStack<E> { private OneWayNode<E> items; public LinkedStack() { super(); this.items = null; } public LinkedStack(Collection<E> col) { this(); this.addAll(col); } public void push(E element) { this.items = new OneWayNode<E>(element, items); this.size += 1; this.modCount += 1; } public E pop() { E element = this.items.data; this.items = this.items.next; this.size -= 1; this.modCount += 1; return element; } public E peek() { return this.items.data; } public Iterator<E> iterator() { return new StackIterator<E>(); } private class StackIterator<E> implements Iterator<E> { private OneWayNode curPos; private int curModCount; private StackIterator() { this.curPos = items; this.curModCount = modCount; } public boolean hasNext() { return curPos != null; } public E next() { if (! this.hasNext() throw new IllegalStateException(); if (this.curModCount != modCount) throw new ConcurrentModificationException() E data = this.curPos.data; this.curPos = this.curPos.next(); return data; } public void remove() { throw new UnsupportedOperationException(); } } private class OneWayNode<E> { private E data; private OneWayNode next; private OneWayNode(E data, OneWayNode next) { this.data = data; This.next = next; } } }

Referències