Java bàsic
- Programes
- Execució
- Hola, món
- Estructura i sintaxi
- Tipus de dades
- Cadenes
- Aritmètica i condicions
- Conversions
- Variables
- Mètodes
- Control de flux
- Classes i objectes
- Interfícies
- Arrays
- Col·leccions
- Definició de classes
- Enumerats
- Records
- Igualtat
- Definició d’interfícies
- Lambdas i interfícies funcionals
- Classes internes
- Definició d’iteradors
- Herència
- Classes abstractes
- Sealed classes
- Pattern matching per a switch
- Gestió d’errors
- Referències
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.
Text blocks
Des de Java 15, els text blocks permeten definir cadenes de múltiples línies amb tres cometes dobles ("""). El contingut s’escriu tal com apareixerà, sense necessitat de \n ni concatenació:
String json = """
{
"name": "Maria",
"age": 25
}
""";
El compilador elimina automàticament la sagnia comuna de totes les línies, determinada per la posició del tancament """.
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
Des de Java 10, la paraula clau var permet ometre el tipus quan el compilador el pot inferir a partir de l’expressió d’inicialització:
var list = new ArrayList<String>(); // el compilador dedueix ArrayList<String>
var total = 0; // int
var només és vàlid per a variables locals dins de mètodes, no per a camps ni paràmetres.
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++;
}
switch
El switch compara el valor d’una expressió contra múltiples casos i executa el bloc corresponent:
int day = 2;
switch (day) {
case 1:
System.out.println("Dilluns");
break;
case 2:
System.out.println("Dimarts");
break;
default:
System.out.println("Altre dia");
}
Sense break, l’execució continua cap als casos següents (fall-through).
Des de Java 14, les switch expressions usen la sintaxi -> i retornen un valor directament, sense break:
String dayName = switch (day) {
case 1 -> "Dilluns";
case 2 -> "Dimarts";
case 3, 4, 5 -> "Dia laborable";
default -> "Cap de setmana";
};
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
Java proporciona un conjunt ric de col·leccions genèriques a través del Java Collections Framework:
| Interfície | Implementació comuna | Ordenat? | Permet duplicats? |
|---|---|---|---|
List<E> | ArrayList, LinkedList | Sí (ordre d’inserció) | Sí |
Set<E> | HashSet | No | No |
Set<E> | LinkedHashSet | Sí (ordre d’inserció) | No |
Set<E> | TreeSet | Sí (ordre natural o Comparator) | No |
Map<K,V> | HashMap | No | Claus úniques |
Map<K,V> | LinkedHashMap | Sí (ordre d’inserció) | Claus úniques |
Map<K,V> | TreeMap | Sí (ordre natural o Comparator) | Claus úniques |
Queue<E> | LinkedList, ArrayDeque | Sí (FIFO) | Sí |
Deque<E> | ArrayDeque | Sí (doble FIFO) | Sí |
Stack<E> | Stack | Sí (LIFO) | Sí |
Totes les col·leccions de la taula són mutables. Per a un tractament detallat, vegeu Col·leccions i mapes.
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];
}
}
Enumerats
Un enumerat (enum) defineix un tipus amb un conjunt fix de constants nomenades. És una forma especial de classe.
public enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
Ús:
Day today = Day.TUESDAY;
if (today == Day.SATURDAY || today == Day.SUNDAY) {
System.out.println("Cap de setmana");
}
Els enumerats funcionen bé amb switch expressions:
String type = switch (today) {
case SATURDAY, SUNDAY -> "Cap de setmana";
default -> "Dia laborable";
};
Els enumerats poden tenir camps i mètodes propis:
public enum Planet {
MERCURY(3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6);
private final double mass;
private final double radius;
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
public double getMass() { return mass; }
public double getRadius() { return radius; }
}
Records
Un record (Java 16+) és una forma compacta de definir una classe immutable de dades. El compilador genera automàticament el constructor, els accessors, equals, hashCode i toString.
public record Point(int x, int y) {}
Equivalent aproximat en classe convencional:
public final class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int x() { return x; }
public int y() { return y; }
// equals, hashCode i toString generats automàticament
}
Ús:
Point p = new Point(3, 4);
System.out.println(p.x()); // 3
System.out.println(p); // Point[x=3, y=4]
Els records poden afegir validació al constructor compacte:
public record Interval(int min, int max) {
public Interval {
if (min > max)
throw new IllegalArgumentException("min > max");
}
}
Els records poden implementar interfícies però no poden estendre altres classes (ja extenen implícitament java.lang.Record). Els seus camps sempre són finals i privats.
Igualtat
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
Pattern matching amb instanceof
Des de Java 16, instanceof pot declarar una variable del tipus comprovat en la mateixa expressió, eliminant el cast manual:
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof Student s)) return false;
return this.name.equals(s.name);
}
El patró other instanceof Student s comprova el tipus i, si coincideix, declara s ja convertida, sense necessitat d’un cast explícit addicional.
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
}
}
Lambdas i interfícies funcionals
Una interfície funcional (Java 8+) és una interfície amb un únic mètode abstracte. El compilador pot representar-la amb una expressió lambda.
@FunctionalInterface
public interface Operation {
int calculate(int a, int b);
}
Una expressió lambda és una funció anònima que implementa la interfície funcional:
Operation sum = (a, b) -> a + b;
Operation product = (a, b) -> a * b;
System.out.println(sum.calculate(3, 4)); // 7
System.out.println(product.calculate(3, 4)); // 12
Java inclou interfícies funcionals predefinides al paquet java.util.function:
| Interfície | Mètode | Descripció |
|---|---|---|
Predicate<T> | boolean test(T t) | Condició sobre un valor |
Function<T,R> | R apply(T t) | Transforma T en R |
Consumer<T> | void accept(T t) | Consumeix un valor sense retornar |
Supplier<T> | T get() | Genera un valor sense arguments |
BiFunction<T,U,R> | R apply(T t, U u) | Transforma dos valors en un |
Exemples:
Predicate<String> isLong = s -> s.length() > 5;
Function<String, Integer> length = String::length;
Consumer<String> print = System.out::println;
Supplier<String> greet = () -> "Hola!";
System.out.println(isLong.test("Barcelona")); // true
System.out.println(length.apply("Java")); // 4
print.accept("Hola món");
System.out.println(greet.get()); // Hola!
Les referències a mètodes (::) són una forma abreujada de lambda que delega a un mètode existent:
Function<String, Integer> f = String::length; // equivalent a s -> s.length()
Consumer<String> c = System.out::println; // equivalent a s -> System.out.println(s)
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 <nom de la subclasse> extends <nom de la classe pare>{
<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;
}
}
}
Sealed classes
Una sealed class o interfície (Java 17+) restringeix quines classes poden estendre-la o implementar-la, usant la clàusula permits:
public sealed interface Shape permits Circle, Rectangle, Triangle {}
public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}
public record Triangle(double base, double height) implements Shape {}
Les classes permeses han de ser final, sealed o non-sealed. Les sealed classes permeten modelar jerarquies de tipus tancades de forma segura i, combinades amb records i pattern matching, el compilador pot verificar l’exhaustivitat dels casos.
Pattern matching per a switch
Des de Java 21, les switch expressions accepten patrons de tipus com a casos, en lloc de valors concrets:
double area = switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> 0.5 * t.base() * t.height();
};
Cada cas declara una variable ja convertida al tipus del patró. Quan el tipus del switch és sealed, el compilador verifica que tots els casos estan coberts (exhaustivitat).
Es poden afegir condicions addicionals amb when:
String description = switch (shape) {
case Circle c when c.radius() > 10 -> "Cercle gran";
case Circle c -> "Cercle petit";
case Rectangle r -> "Rectangle";
case Triangle t -> "Triangle";
};
Gestió d’errors
Java utilitza excepcions per gestionar errors durant l’execució. Hi ha dos tipus:
- Checked (verificades): el compilador obliga a capturar-les o declarar-les amb
throws. Representen errors recuperables (ex.IOException). - Unchecked (no verificades): subclasses de
RuntimeException, el compilador no les exigeix capturar. Representen errors de programació (ex.NullPointerException).
Captura d’excepcions
Les excepcions es gestionen amb try, catch i finally:
try {
int x = Integer.parseInt("abc"); // pot generar NumberFormatException
int resultat = 10 / x; // pot generar ArithmeticException
} catch (NumberFormatException e) {
System.out.println("No s’ha introduït un nombre vàlid.");
} catch (ArithmeticException e) {
System.out.println("No es pot dividir per zero.");
} finally {
System.out.println("Aquest bloc s’executa sempre.");
}
try: inclou el codi que pot llençar una excepció.catch: captura i gestiona excepcions específiques. Es poden tenir diversos blocscatch.finally: (opcional) s’executa sempre, hagi o no hagut excepció.- L’objecte d’excepció capturat permet obtenir informació addicional (
e.getMessage()).
Llençar excepcions
Es poden generar excepcions manualment amb throw:
public static int divideix(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("No es pot dividir per zero.");
}
return a / b;
}
Si un mètode pot llençar una excepció checked que no captura, cal declarar-ho amb throws:
public static void llegirFitxer(String nomFitxer) throws IOException {
// codi que pot llençar IOException
}
Excepcions comunes
| Excepció | Tipus | Quan apareix |
|---|---|---|
NumberFormatException | Unchecked | Conversió de cadena a número no vàlida |
ArithmeticException | Unchecked | Operació aritmètica invàlida (ex. dividir per zero) |
NullPointerException | Unchecked | Accedir a un objecte nul |
ArrayIndexOutOfBoundsException | Unchecked | Índex fora de rang en un array |
IOException | Checked | Errors d’entrada/sortida (fitxers, xarxa) |