Sintaxis de Java - Java syntax

Image
Un fragmento de código Java con palabras clave resaltadas en fuente azul negrita

La sintaxis de Java se refiere al conjunto de reglas que definen cómo se escribe e interpreta un programa Java.

La sintaxis se deriva principalmente de C y C ++ . A diferencia de C ++, en Java no hay funciones o variables globales, pero hay miembros de datos que también se consideran variables globales . Todo el código pertenece a clases y todos los valores son objetos . La única excepción son los tipos primitivos , que no están representados por una instancia de clase por razones de rendimiento (aunque se pueden convertir automáticamente en objetos y viceversa mediante autoboxing ). Algunas características como la sobrecarga de operadores o los tipos de enteros sin signo se omiten para simplificar el lenguaje y evitar posibles errores de programación.

La sintaxis de Java se ha ampliado gradualmente en el transcurso de numerosas versiones importantes de JDK y ahora admite capacidades como programación genérica y literales de función (llamadas expresiones lambda en Java). Desde 2017, se lanza una nueva versión de JDK dos veces al año, y cada lanzamiento trae mejoras incrementales al lenguaje.

Lo esencial

Identificador

Un identificador es el nombre de un elemento en el código . Hay ciertas convenciones de nomenclatura estándar que se deben seguir al seleccionar nombres para elementos. Los identificadores en Java distinguen entre mayúsculas y minúsculas .

Un identificador puede contener:

  • Cualquier carácter Unicode que sea una letra (incluidas letras numéricas como números romanos ) o un dígito.
  • Signo de moneda (como ¥).
  • Conectando caracteres de puntuación (como _ ).

Un identificador no puede:

  • Empiece con un dígito.
  • Ser igual a una palabra clave reservada, literal nulo o literal booleano.

Palabras clave

abstracto Seguir por nuevo cambiar
afirmar defecto ir a paquete sincronizado
booleano hacer si privado esta
rotura doble implementos protegido lanzar
byte demás importar público lanza
caso enumeración en vez de regreso transitorio
captura se extiende En t pequeño tratar
carbonizarse final interfaz estático var
clase finalmente largo estrictofp vacío
constante flotador nativo súper volátil
tiempo

Literales

Enteros
binario (introducido en Java SE 7) 0b11110101 ( 0b seguido de un número binario)
octal 0365 ( 0 seguido de un número octal)
hexadecimal 0xF5 ( 0x seguido de un número hexadecimal)
decimal 245 (número decimal)
Valores de coma flotante
flotador 23.5F , .5f , 1.72E3F (fracción decimal con un indicador de exponente opcional, seguida de F )
0x.5FP0F , 0x.5P-6f ( 0x seguido de una fracción hexadecimal con un indicador de exponente obligatorio y un sufijo F )
doble 23.5D , .5 , 1.72E3D (fracción decimal con un indicador de exponente opcional, seguido de D opcional )
0x.5FP0 , 0x.5P-6D ( 0x seguido de una fracción hexadecimal con un indicador de exponente obligatorio y un sufijo D opcional )
Literales de carácter
carbonizarse 'a' , 'Z' , '\ u0231' (carácter o un carácter de escape, entre comillas simples)
Literales booleanos
booleano verdadero , falso
literal nulo
referencia nula nulo
Literales de cadena
Cuerda "Hola, mundo" (secuencia de caracteres y caracteres de escape entre comillas dobles)
Los personajes se escapan en cadenas
Carácter Unicode \ u3876 ( \ u seguido del código unicode hexadecimal apunta hasta U + FFFF)
octal de escape \ 352 (número octal que no exceda de 377, precedido por una barra invertida)
Linea de alimentación \norte
Retorno de carro \ r
Alimentación de formulario \F
Barra invertida \\
Una frase \ '
Cotización doble \ "
Pestaña \ t
Retroceso \B

Los literales enteros son de inttipo por defecto a menos que el longtipo se especifique añadiendo Lo lsufijo al literal, por ejemplo 367L. Desde Java SE 7, es posible incluir guiones bajos entre los dígitos de un número para aumentar la legibilidad; por ejemplo, un número 145608987 se puede escribir como 145_608_987 .

Variables

Las variables son identificadores asociados con valores. Se declaran escribiendo el tipo y el nombre de la variable y, opcionalmente, se inicializan en la misma instrucción asignando un valor.

int count;      //Declaring an uninitialized variable called 'count', of type 'int'
count = 35;     //Initializing the variable
int count = 35; //Declaring and initializing the variable at the same time

Se pueden declarar e inicializar varias variables del mismo tipo en una declaración utilizando una coma como delimitador.

int a, b;         //Declaring multiple variables of the same type
int a = 2, b = 3; //Declaring and initializing multiple variables of the same type

Desde Java 10, es posible inferir tipos para las variables automáticamente mediante el uso de var.

// stream will have the FileOutputStream type as inferred from its initializer
var stream = new FileOutputStream("file.txt");

// An equivalent declaration with an explicit type
FileOutputStream stream = new FileOutputStream("file.txt");

Bloques de código

Los separadores { y} significa un bloque de código y un nuevo alcance. Los miembros de la clase y el cuerpo de un método son ejemplos de lo que puede vivir dentro de estas llaves en varios contextos.

Dentro de los cuerpos del método, se pueden usar llaves para crear nuevos ámbitos, como se indica a continuación:

void doSomething() {
    int a;

    {
        int b;
        a = 1;
    }

    a = 2;
    b = 3; // Illegal because the variable b is declared in an inner scope..
}

Comentarios

Java tiene tres tipos de comentarios: comentarios tradicionales , comentarios de final de línea y comentarios de documentación .

Los comentarios tradicionales, también conocidos como comentarios de bloque, comienzan con /*y terminan con */, pueden extenderse a lo largo de varias líneas. Este tipo de comentario se derivó de C y C ++.

/* This is a multi-line comment.
It may occupy more than one line. */

Los comentarios de final de línea comienzan //y se extienden hasta el final de la línea actual. Este tipo de comentario también está presente en C ++ y en C.

// This is an end-of-line comment

Los comentarios de documentación en los archivos fuente son procesados ​​por la herramienta Javadoc para generar documentación. Este tipo de comentario es idéntico a los comentarios tradicionales, excepto que comienza /**y sigue las convenciones definidas por la herramienta Javadoc. Técnicamente, estos comentarios son un tipo especial de comentario tradicional y no se definen específicamente en la especificación del lenguaje.

/**
 * This is a documentation comment.
 * 
 * @author John Doe
 */

Tipos universales

Las clases del paquete java.lang se importan implícitamente en cada programa, siempre que ningún tipo importado explícitamente tenga los mismos nombres. Los importantes incluyen:

java.lang.Object
El tipo superior de Java . Superclase de todas las clases que no declaran una clase padre. Todos los valores se pueden convertir a este tipo, aunque para los valores primitivos esto implica el autoboxing .
java.lang.String
Tipo de cadena básica de Java. Inmutable . Algunos métodos tratan cada unidad de código UTF-16 como un "carácter", pero también están disponibles métodos para convertir a uno int[]que sea efectivamente UTF-32 .
java.lang.Throwable
supertipo de todo lo que se puede lanzar o capturar con declaraciones throwy de Java catch.

Estructura del programa

Las aplicaciones Java constan de colecciones de clases. Las clases existen en paquetes, pero también se pueden anidar dentro de otras clases.

main método

Cada aplicación Java debe tener un punto de entrada. Esto es cierto tanto para las aplicaciones de interfaz gráfica como para las aplicaciones de consola. El punto de entrada es el mainmétodo. Puede haber más de una clase con un mainmétodo, pero la clase principal siempre se define externamente (por ejemplo, en un archivo de manifiesto ). El método debe ser staticy se pasa argumentos de línea de comandos como una matriz de cadenas. A diferencia de C ++ o C # , nunca devuelve un valor y debe devolverlo void.

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

Paquetes

Los paquetes son parte de un nombre de clase y se utilizan para agrupar y / o distinguir entidades nombradas de otras. Otro propósito de los paquetes es gobernar el acceso al código junto con los modificadores de acceso. Por ejemplo, java.io.InputStreames un nombre de clase completamente calificado para la clase InputStreamque se encuentra en el paquete java.io.

Un paquete se declara al comienzo del archivo con la packagedeclaración:

package myapplication.mylibrary;

public class MyClass {
}

Las clases con el publicmodificador deben colocarse en los archivos con el mismo nombre y extensión java y colocarse en carpetas anidadas correspondientes al nombre del paquete. La clase anterior myapplication.mylibrary.MyClasstendrá la siguiente ruta: myapplication/mylibrary/MyClass.java.

Declaración de importación

Tipo de declaración de importación

Una declaración de importación de tipo permite hacer referencia a un tipo con nombre mediante un nombre simple en lugar del nombre completo que incluye el paquete. Las declaraciones de importación pueden ser declaraciones de importación de un solo tipo o declaraciones de importación bajo demanda . Las declaraciones de importación deben colocarse en la parte superior de un archivo de código después de la declaración del paquete.

package myPackage;

import java.util.Random; // Single type declaration

public class ImportsTest {
    public static void main(String[] args) {
        /* The following line is equivalent to
         * java.util.Random random = new java.util.Random();
         * It would've been incorrect without the import.
         */
        Random random = new Random();
    }
}

Las declaraciones de importación bajo demanda se mencionan en el código. Una "importación de tipo" importa todos los tipos de paquete. Una "importación estática" importa miembros del paquete.

import java.util.*;  /*This form of importing classes makes all classes
    in package java.util available by name, could be used instead of the
    import declaration in the previous example. */
import java.*; /*This statement is legal, but does nothing, since there
    are no classes directly in package java. All of them are in packages
    within package java. This does not import all available classes.*/

Declaración de importación estática

Este tipo de declaración está disponible desde J2SE 5.0 . Las declaraciones de importación estáticas permiten el acceso a miembros estáticos definidos en otra clase, interfaz, anotación o enumeración; sin especificar el nombre de la clase:

import static java.lang.System.out; //'out' is a static field in java.lang.System

public class HelloWorld {
    public static void main(String[] args) {
        /* The following line is equivalent to:
   System.out.println("Hi World!");

           and would have been incorrect without the import declaration. */
        out.println("Hello World!");
    }
}

Las declaraciones de importación bajo demanda permiten importar todos los campos del tipo:

import static java.lang.System.*;
    /* This form of declaration makes all
       fields in the java.lang.System class available by name, and may be used instead
       of the import declaration in the previous example. */

Las constantes de enumeración también se pueden usar con la importación estática. Por ejemplo, esta enumeración está en el paquete llamado screen:

public enum ColorName {
    RED, BLUE, GREEN
};

Es posible usar declaraciones de importación estáticas en otra clase para recuperar las constantes de enumeración:

import screen.ColorName;
import static screen.ColorName.*;

public class Dots {
    /* The following line is equivalent to 'ColorName foo = ColorName.RED',
       and it would have been incorrect without the static import. */
    ColorName foo = RED;

    void shift() {
        /* The following line is equivalent to:

           if (foo == ColorName.RED) foo = ColorName.BLUE; */
        if (foo == RED) foo = BLUE;
    }
}

Operadores

Los operadores en Java son similares a los de C ++ . Sin embargo, no existe un deleteoperador debido a los mecanismos de recolección de basura en Java, y no hay operaciones en los punteros ya que Java no los admite. Otra diferencia es que Java tiene un operador de desplazamiento a la derecha sin firmar ( >>>), mientras que el signo del operador de desplazamiento a la derecha de C depende del tipo. Los operadores en Java no se pueden sobrecargar .

Precedencia Operador Descripción Asociatividad
1 () Invocación de método De izquierda a derecha
[] Acceso a la matriz
. Selección de miembros de la clase
2 ++ -- Incremento y decremento de sufijo
3 ++ -- Incremento y decremento de prefijo De derecha a izquierda
+ - Unario más y menos
! ~ NOT lógico y NOT bit a bit
(type) val Tipo elenco
new Creación de una instancia o matriz de clase
4 * / % Multiplicación, división y módulo (resto) De izquierda a derecha
5 + - Adición y sustracción
+ Concatenación de cadenas
6 << >> >>> Desplazamiento a la izquierda bit a bit , desplazamiento a la derecha firmado y desplazamiento a la derecha sin firmar
7 < <= Relacional "menor que" y "menor o igual que"
> >= Relacional "mayor que" y "mayor o igual que"
instanceof Comparación de tipos
8 == != Relacional "igual a" y "no igual a"
9 & AND bit a bit y lógico
10 ^ XOR bit a bit y lógico (exclusivo o)
11 | OR bit a bit y lógico (inclusive o)
12 && Y condicional lógico
13 || OR condicional lógico
14 c ? t : f Ternaria condicional (ver :? ) De derecha a izquierda
15 = Asignación simple
+= -= Asignación por suma y diferencia
*= /= %= Asignación por producto, cociente y resto
<<= >>= >>>= Asignación por desplazamiento a la izquierda bit a bit, desplazamiento a la derecha con signo y desplazamiento a la derecha sin firmar
&= ^= |= Asignación mediante AND, XOR y OR bit a bit

Estructuras de Control

Declaraciones condicionales

if declaración

si las declaraciones en Java son similares a las de C y usan la misma sintaxis:

if (i == 3) doSomething();

ifLa declaración puede incluir un elsebloque opcional , en cuyo caso se convierte en una declaración if-then-else:

if (i == 2) {
    doSomething();
} else {
    doSomethingElse();
}

Al igual que C, la construcción else-if no implica ninguna palabra clave especial, se forma como una secuencia de declaraciones if-then-else independientes:

if (i == 3) {
    doSomething();
} else if (i == 2) {
    doSomethingElse();
} else {
    doSomethingDifferent();
}

Además, tenga en cuenta que el operador ?: Se puede utilizar en lugar de una declaración if simple, por ejemplo

int a = 1;
int b = 2;
int minVal = (a < b) ? a : b;

switch declaración

Sentencias switch en Java pueden utilizar byte, short, char, y int(nota: no long) tipos de datos primitivos o sus correspondientes tipos de envoltura. A partir de J2SE 5.0, es posible utilizar tipos de enumeración . A partir de Java SE 7, es posible utilizar Strings. No se pueden utilizar otros tipos de referencia en switchdeclaraciones.

Los valores posibles se enumeran mediante caseetiquetas. Estas etiquetas en Java pueden contener solo constantes (incluidas las constantes de enumeración y las constantes de cadena). La ejecución comenzará después de la etiqueta correspondiente a la expresión dentro de los corchetes. defaultPuede estar presente una etiqueta opcional para declarar que el código siguiente se ejecutará si ninguna de las etiquetas de caso corresponde a la expresión.

El código de cada etiqueta termina con la breakpalabra clave. Es posible omitirlo haciendo que la ejecución pase a la siguiente etiqueta, sin embargo, normalmente se informará una advertencia durante la compilación.

switch (ch) {
    case 'A':
        doSomething(); // Triggered if ch == 'A'
        break;
    case 'B':
    case 'C':
        doSomethingElse(); // Triggered if ch == 'B' or ch == 'C'
        break;
    default:
        doSomethingDifferent(); // Triggered in any other case
        break;
}
switch expresiones

Desde Java 14 es posible utilizar expresiones de cambio, que utilizan la nueva sintaxis de flechas:

var result = switch (ch) {
    case 'A' -> Result.GREAT;
    case 'B', 'C' -> Result.FINE;
    default -> throw new ThisIsNoGoodException();
};

Alternativamente, existe la posibilidad de expresar lo mismo con la yielddeclaración, aunque se recomienda preferir la sintaxis de flecha porque evita el problema de caídas accidentales.

var result = switch (ch) {
    case 'A':
        yield Result.GREAT;
    case 'B':
    case 'C':
        yield Result.FINE;
    default:
        throw new ThisIsNoGoodException();
};

Declaraciones de iteración

Las declaraciones de iteración son declaraciones que se ejecutan repetidamente cuando una condición determinada se evalúa como verdadera. Desde J2SE 5.0 , Java tiene cuatro formas de tales declaraciones.

while círculo

En el whileciclo, la prueba se realiza antes de cada iteración.

while (i < 10) {
    doSomething();
}

do ... while círculo

En el do ... whileciclo, la prueba se realiza después de cada iteración. En consecuencia, el código siempre se ejecuta al menos una vez.

// doSomething() is called at least once
do {
    doSomething();
} while (i < 10);

for círculo

forLos bucles en Java incluyen un inicializador, una condición y una contraexpresión. Es posible incluir varias expresiones del mismo tipo utilizando una coma como delimitador (excepto en la condición). Sin embargo, a diferencia de C, la coma es solo un delimitador y no un operador.

for (int i = 0; i < 10; i++) {
    doSomething();
}
 
// A more complex loop using two variables
for (int i = 0, j = 9; i < 10; i++, j -= 3) {
    doSomething();
}

Como C, las tres expresiones son opcionales. El siguiente ciclo es infinito:

for (;;) {
    doSomething();
}

forBucle mejorado

Los forbucles mejorados están disponibles desde J2SE 5.0 . Este tipo de bucle utiliza iteradores integrados sobre matrices y colecciones para devolver cada elemento de la colección dada. Cada elemento es devuelto y accesible en el contexto del bloque de código. Cuando se ejecuta el bloqueo, se devuelve el siguiente elemento hasta que no queden elementos. A diferencia de C # , este tipo de bucle no implica una palabra clave especial, sino que utiliza un estilo de notación diferente.

for (int i : intArray) {
    doSomething(i);
}

Saltar declaraciones

Etiquetas

A las etiquetas se les asignan puntos en el código que utilizan las declaraciones breaky continue. Tenga en cuenta que la gotopalabra clave Java no se puede utilizar para saltar a puntos específicos del código.

start:
someMethod();

break declaración

La breakdeclaración sale del bucle o switchdeclaración más cercana . La ejecución continúa en la declaración después de la declaración terminada, si la hubiera.

for (int i = 0; i < 10; i++) {
    while (true) {
        break;
    }
    // Will break to this point
}

Es posible salir del bucle externo usando etiquetas:

outer:
for (int i = 0; i < 10; i++) {
    while (true) {
        break outer;
    }
}
// Will break to this point

continue declaración

La continuedeclaración interrumpe la iteración actual de la declaración de control actual y comienza la siguiente iteración. El siguiente whilebucle en el código a continuación lee caracteres llamando getChar(), omitiendo las declaraciones en el cuerpo del bucle si los caracteres son espacios:

int ch;
while (ch == getChar()) {
    if (ch == ' ') {
        continue; // Skips the rest of the while-loop
    }

    // Rest of the while-loop, will not be reached if ch == ' '
    doSomething();
}

Las etiquetas se pueden especificar en continuedeclaraciones y breakdeclaraciones:

outer:
for (String str : stringsArr) {
    char[] strChars = str.toCharArray();
    for (char ch : strChars) {
        if (ch == ' ') {
            /* Continues the outer cycle and the next
            string is retrieved from stringsArr */
            continue outer;
        }
        doSomething(ch);
    }
}

return declaración

La returndeclaración se utiliza para finalizar la ejecución del método y devolver un valor. Un valor devuelto por el método se escribe después de la returnpalabra clave. Si el método devuelve algo menos void, debe usar la returndeclaración para devolver algún valor.

void doSomething(boolean streamClosed) {
    // If streamClosed is true, execution is stopped
    if (streamClosed) {
        return;
    }
    readFromStream();
}

int calculateSum(int a, int b) {
    int result = a + b;
    return result;
}

returninstrucción finaliza la ejecución inmediatamente, excepto en un caso: si la instrucción se encuentra dentro de un trybloque y se complementa con un finally, el control se pasa al finallybloque.

void doSomething(boolean streamClosed) {
    try {
        if (streamClosed) {
            return;
        }
        readFromStream();
    } finally {
        /* Will be called last even if 
        readFromStream() was not called */
        freeResources();
    }
}

Declaraciones de manejo de excepciones

try-catch-finally declaraciones

Las excepciones se gestionan dentro de try... catchbloques.

try {
    // Statements that may throw exceptions
    methodThrowingExceptions();
} catch (Exception ex) {
    // Exception caught and handled here
    reportException(ex);
} finally {
    // Statements always executed after the try/catch blocks
    freeResources();
}

Las instrucciones dentro del trybloque se ejecutan y, si alguna de ellas genera una excepción, la ejecución del bloque se interrumpe y el catchbloque maneja la excepción . Puede haber varios catchbloques, en cuyo caso se ejecuta el primer bloque con una variable de excepción cuyo tipo coincide con el tipo de la excepción lanzada.

Java SE 7 también introdujo cláusulas de captura múltiple además de cláusulas de captura única. Este tipo de cláusulas catch permite que Java maneje diferentes tipos de excepciones en un solo bloque, siempre que no sean subclases entre sí.

try {
    methodThrowingExceptions();
} catch (IOException | IllegalArgumentException ex) {
    //Both IOException and IllegalArgumentException will be caught and handled here
    reportException(ex);
}

Si ningún catchbloque coincide con el tipo de excepción lanzada, la ejecución del bloque externo (o método) que contiene la declaración try... catchse interrumpe y la excepción se pasa hacia arriba y fuera del bloque contenedor (o método). La excepción se propaga hacia arriba a través de la pila de llamadas hasta que se encuentra un catchbloque coincidente dentro de uno de los métodos actualmente activos. Si la excepción se propaga hasta el mainmétodo superior sin que se encuentre un catchbloque coincidente , se escribe una descripción textual de la excepción en el flujo de salida estándar.

Las sentencias dentro del finallybloque siempre se ejecutan después de los bloques tryy catch, tanto si se lanzó una excepción como si no, e incluso si returnse alcanzó una sentencia. Estos bloques son útiles para proporcionar código de limpieza que se garantiza que siempre se ejecutará.

Los bloques catchy finallyson opcionales, pero al menos uno u otro debe estar presente después del trybloque.

try-con-declaraciones de recursos

tryLas sentencias -with-resources son un tipo especial de try-catch-finallysentencias introducidas como una implementación del patrón dispose en Java SE 7. En una trysentencia -with-resources, la trypalabra clave es seguida por la inicialización de uno o más recursos que se liberan automáticamente cuando se tryejecuta el bloque Está terminado. Los recursos deben implementar java.lang.AutoCloseable. tryNo se requiere que las sentencias -with-resources tengan un catcho un finallybloque a diferencia de las try-catch-finallysentencias normales .

try (FileOutputStream fos = new FileOutputStream("filename");
    XMLEncoder xEnc = new XMLEncoder(fos)) {
    xEnc.writeObject(object);
} catch (IOException ex) {
    Logger.getLogger(Serializer.class.getName()).log(Level.SEVERE, null, ex);
}

Desde Java 9 es posible utilizar variables ya declaradas:

FileOutputStream fos = new FileOutputStream("filename");
XMLEncoder xEnc = new XMLEncoder(fos);
try (fos; xEnc) {
    xEnc.writeObject(object);
} catch (IOException ex) {
    Logger.getLogger(Serializer.class.getName()).log(Level.SEVERE, null, ex);
}

throw declaración

La throwdeclaración se usa para lanzar una excepción y finalizar la ejecución del bloque o método. La instancia de excepción lanzada se escribe después de la throwdeclaración.

void methodThrowingExceptions(Object obj) {
    if (obj == null) {
        // Throws exception of NullPointerException type
        throw new NullPointerException();
    }
    // Will not be called, if object is null
    doSomethingWithObject(obj);
}

Control de concurrencia de subprocesos

Java tiene herramientas integradas para la programación de subprocesos múltiples . A los efectos de la sincronización de subprocesos, la synchronizeddeclaración se incluye en lenguaje Java.

Para sincronizar un bloque de código, está precedido por la synchronizedpalabra clave seguida por el objeto de bloqueo dentro de los corchetes. Cuando el hilo en ejecución alcanza el bloque sincronizado, adquiere un bloqueo de exclusión mutua , ejecuta el bloque y luego libera el bloqueo. Ningún hilo puede entrar en este bloque hasta que se libere el bloqueo. Se puede utilizar como bloqueo cualquier tipo de referencia no nulo.

/* Acquires lock on someObject. It must be of
a reference type and must be non-null */
synchronized (someObject) {
    // Synchronized statements
}

assert declaración

assertLas declaraciones han estado disponibles desde J2SE 1.4 . Estos tipos de declaraciones se utilizan para hacer afirmaciones en el código fuente, que se pueden activar y desactivar durante la ejecución de clases o paquetes específicos. Para declarar una aserción, assertse usa la palabra clave seguida de una expresión condicional. Si evalúa falsecuando se ejecuta la instrucción, se lanza una excepción. Esta declaración puede incluir dos puntos seguidos de otra expresión, que actuará como mensaje de detalle de la excepción.

// If n equals 0, AssertionError is thrown
assert n != 0;
/* If n equals 0, AssertionError will be thrown
with the message after the colon */
assert n != 0 : "n was equal to zero";

Tipos primitivos

Los tipos primitivos en Java incluyen tipos enteros, números de punto flotante, unidades de código UTF-16 y un tipo booleano. No hay tipos sin firmar en Java, excepto chartype, que se utiliza para representar unidades de código UTF-16. La falta de tipos sin firmar se compensa con la introducción de la operación de desplazamiento a la derecha sin firmar ( >>>), que no está presente en C ++. No obstante, se han recibido críticas sobre la falta de compatibilidad con C y C ++ que esto provoca.

Tipos primitivos
Escribe un nombre Clase de envoltura Valor Distancia Tamaño Valor por defecto
byte java.lang.Byte entero −128 hasta +127 8 bits (1 byte) 0
short java.lang.Short entero −32,768 hasta +32,767 16 bits (2 bytes) 0
int java.lang.Integer entero −2,147,483,648 a +2,147,483,647 32 bits (4 bytes) 0
long java.lang.Long entero −9,223,372,036,854,775,808 hasta
+9,223,372,036,854,775,807
64 bits (8 bytes) 0
float java.lang.Float número de coma flotante ± 1,401298E − 45 hasta ± 3,402823E + 38 32 bits (4 bytes) 0.0f
double java.lang.Double número de coma flotante ± 4.94065645841246E − 324 hasta
± 1.79769313486232E + 308
64 bits (8 bytes) 0.0
boolean java.lang.Boolean Booleano true o false 1 bit (1 bit) false
char java.lang.Character Unidad de código UTF-16 ( carácter BMP
o parte de un par sustituto)
'\u0000' mediante '\uFFFF' 16 bits (2 bytes) '\u0000'

charno corresponde necesariamente a un solo carácter. Puede representar una parte de un par sustituto , en cuyo caso el punto de código Unicode está representado por una secuencia de dos charvalores.

Boxeo y unboxing

Esta función de idioma se introdujo en J2SE 5.0 . El boxing es la operación de convertir un valor de un tipo primitivo en un valor de un tipo de referencia correspondiente, que sirve como envoltorio para este tipo primitivo en particular. Unboxing es la operación inversa de convertir un valor de un tipo de referencia (previamente encuadrado) en un valor de un tipo primitivo correspondiente. Ninguna operación requiere una conversión explícita.

Ejemplo:

int foo = 42; // Primitive type
Integer bar = foo; /* foo is boxed to bar, bar is of Integer type,
                      which serves as a wrapper for int */
int foo2 = bar; // Unboxed back to primitive type

Tipos de referencia

Los tipos de referencia incluyen tipos de clase, tipos de interfaz y tipos de matriz. Cuando se llama al constructor, se crea un objeto en el montón y se asigna una referencia a la variable. Cuando una variable de un objeto sale del alcance, la referencia se rompe y cuando no quedan referencias, el objeto se marca como basura. El recolector de basura luego lo recolecta y lo destruye algún tiempo después.

Una variable de referencia es nullcuando no hace referencia a ningún objeto.

Matrices

Las matrices en Java se crean en tiempo de ejecución, al igual que las instancias de clase. La longitud de la matriz se define en el momento de la creación y no se puede cambiar.

int[] numbers = new int[5];
numbers[0] = 2;
numbers[1] = 5;
int x = numbers[0];

Inicializadores

// Long syntax
int[] numbers = new int[] {20, 1, 42, 15, 34};
// Short syntax
int[] numbers2 = {20, 1, 42, 15, 34};

Matrices multidimensionales

En Java, las matrices multidimensionales se representan como matrices de matrices. Técnicamente, están representados por matrices de referencias a otras matrices.

int[][] numbers = new int[3][3];
numbers[1][2] = 2;

int[][] numbers2 = {{2, 3, 2}, {1, 2, 6}, {2, 4, 5}};

Debido a la naturaleza de las matrices multidimensionales, las submatrices pueden variar en longitud, por lo que las matrices multidimensionales no están obligadas a ser rectangulares a diferencia de C:

int[][] numbers = new int[2][]; //Initialization of the first dimension only

numbers[0] = new int[3];
numbers[1] = new int[2];

Clases

Las clases son los fundamentos de un lenguaje orientado a objetos como Java. Contienen miembros que almacenan y manipulan datos. Las clases se dividen en nivel superior y anidadas . Las clases anidadas son clases ubicadas dentro de otra clase que pueden acceder a los miembros privados de la clase adjunta. Las clases anidadas incluyen clases miembro (que pueden definirse con el modificador estático para anidamiento simple o sin él para clases internas), clases locales y clases anónimas .

Declaración

Clase de primer nivel
class Foo {
    // Class members
}
Clase interior
class Foo { // Top-level class
    class Bar { // Inner class
    }
}
Clase anidada
class Foo { // Top-level class
    static class Bar { // Nested class
    }
}
Clase local
class Foo {
    void bar() {
        class Foobar {// Local class within a method
        }
    }
}
Clase anónima
class Foo {
    void bar() {
        new Object() {// Creation of a new anonymous class extending Object
        };
    }
}

Instanciación

Los miembros no estáticos de una clase definen los tipos de variables y métodos de instancia , que están relacionados con los objetos creados a partir de esa clase. Para crear estos objetos, se debe crear una instancia de la clase utilizando el newoperador y llamando al constructor de la clase.

Foo foo = new Foo();

Acceso a miembros

Se accede a los miembros de las instancias y clases estáticas con el .operador (punto).

Acceso a un miembro de la
instancia Se puede acceder a los miembros de la instancia a través del nombre de una variable.

String foo = "Hello";
String bar = foo.toUpperCase();

Acceso a un miembro de clase
estática Se accede a los miembros estáticos mediante el nombre de la clase o cualquier otro tipo. Esto no requiere la creación de una instancia de clase. Los miembros estáticos se declaran mediante el staticmodificador.

public class Foo {
    public static void doSomething() {
    }
}

// Calling the static method
Foo.doSomething();

Modificadores

Los modificadores son palabras clave que se utilizan para modificar declaraciones de tipos y miembros de tipos. En particular, hay un subgrupo que contiene los modificadores de acceso.

  • abstract - Especifica que una clase solo sirve como clase base y no se puede crear una instancia.
  • static - Usado solo para clases miembro, especifica que la clase miembro no pertenece a una instancia específica de la clase contenedora.
  • final- Las clases marcadas como finalno pueden extenderse y no pueden tener subclases.
  • strictfp- Especifica que todas las operaciones de punto flotante deben realizarse de acuerdo con IEEE 754 y prohíbe el uso de precisión mejorada para almacenar resultados intermedios.
Modificadores de acceso

Los modificadores de acceso , o modificadores de herencia , establecen la accesibilidad de clases, métodos y otros miembros. Los miembros marcados como publicpueden ser contactados desde cualquier lugar. Si una clase o su miembro no tiene modificadores, se asume el acceso predeterminado.

public class Foo {
    int go() {
        return 0;
    }

    private class Bar {
    }
}

La siguiente tabla muestra si el código dentro de una clase tiene acceso a la clase o al método dependiendo de la ubicación de la clase de acceso y el modificador para la clase o miembro de clase accedido:

Modificador Misma clase o clase anidada Otra clase dentro del mismo paquete Clase extendida dentro de otro paquete No extendido dentro de otro paquete
private no no no
predeterminado (paquete privado) no no
protected no
public
Image
Esta imagen describe el alcance del miembro de clase dentro de clases y paquetes.

Constructores e inicializadores

Un constructor es un método especial que se llama cuando se inicializa un objeto. Su propósito es inicializar los miembros del objeto. Las principales diferencias entre los constructores y los métodos ordinarios son que los constructores se llaman solo cuando se crea una instancia de la clase y nunca devuelven nada. Los constructores se declaran como métodos comunes, pero reciben el nombre de la clase y no se especifica ningún tipo de retorno:

class Foo {
    String str;

    Foo() { // Constructor with no arguments

        // Initialization
    }

    Foo(String str) { // Constructor with one argument
        this.str = str;
    }
}

Los inicializadores son bloques de código que se ejecutan cuando se crea una clase o instancia de una clase. Hay dos tipos de inicializadores, inicializadores estáticos e inicializadores de instancia .

Los inicializadores estáticos inicializan los campos estáticos cuando se crea la clase. Se declaran utilizando la staticpalabra clave:

class Foo {
    static {
        // Initialization
    }
}

Una clase se crea solo una vez. Por lo tanto, los inicializadores estáticos no se llaman más de una vez. Por el contrario, los inicializadores de instancia se llaman automáticamente antes de la llamada a un constructor cada vez que se crea una instancia de la clase. A diferencia de los constructores, los inicializadores de instancias no pueden aceptar ningún argumento y, por lo general, no pueden generar ninguna excepción marcada (excepto en varios casos especiales). Los inicializadores de instancia se declaran en un bloque sin palabras clave:

class Foo {
    {
        // Initialization
    }
}

Dado que Java tiene un mecanismo de recolección de basura, no hay destructores . Sin embargo, cada objeto tiene un finalize()método llamado antes de la recolección de basura, que se puede anular para implementar la finalización.

Métodos

Todas las declaraciones en Java deben residir dentro de métodos. Los métodos son similares a las funciones, excepto que pertenecen a clases. Un método tiene un valor de retorno, un nombre y, por lo general, algunos parámetros se inicializan cuando se llama con algunos argumentos. Al igual que en C ++, los métodos que no devuelven nada tienen el tipo de retorno declarado como void. A diferencia de C ++, los métodos en Java no pueden tener valores de argumento predeterminados y los métodos suelen estar sobrecargados.

class Foo {
    int bar(int a, int b) {
        return (a*2) + b;
    }

    /* Overloaded method with the same name but different set of arguments */
    int bar(int a) {
        return a*2;
    }
}

Un método se llama usando .notación en un objeto, o en el caso de un método estático, también en el nombre de una clase.

Foo foo = new Foo();
int result = foo.bar(7, 2); // Non-static method is called on foo

int finalResult = Math.abs(result); // Static method call

La throwspalabra clave indica que un método lanza una excepción. Todas las excepciones marcadas deben aparecer en una lista separada por comas.

void openStream() throws IOException, myException { // Indicates that IOException may be thrown
}
Modificadores
  • abstract- Los métodos abstractos solo pueden estar presentes en clases abstractas , tales métodos no tienen cuerpo y deben anularse en una subclase a menos que sea abstracto en sí mismo.
  • static- Hace que el método sea estático y accesible sin la creación de una instancia de clase. Sin embargo, los métodos estáticos no pueden acceder a miembros no estáticos de la misma clase.
  • final - Declara que el método no se puede invalidar en una subclase.
  • native- Indica que este método se implementa a través de JNI en código dependiente de la plataforma. La implementación real ocurre fuera del código Java, y tales métodos no tienen cuerpo.
  • strictfp- Declara conformidad estricta con IEEE 754 en la realización de operaciones de punto flotante.
  • synchronized- Declara que un hilo que ejecuta este método debe adquirir monitor. Para los synchronizedmétodos, el monitor es la instancia de la clase o java.lang.Classsi el método es estático.
  • Modificadores de acceso: idénticos a los que se utilizan con las clases.
Varargs

Esta función de idioma se introdujo en J2SE 5.0 . El último argumento del método puede declararse como un parámetro de aridad variable, en cuyo caso el método se convierte en un método de aridad variable (a diferencia de los métodos de aridad fija) o simplemente un método varargs . Esto permite pasar un número variable de valores, del tipo declarado, al método como parámetros, sin incluir parámetros. Estos valores estarán disponibles dentro del método como una matriz.

void printReport(String header, int... numbers) { //numbers represents varargs
    System.out.println(header);
    for (int num : numbers) {
        System.out.println(num);
    }
}

// Calling varargs method
printReport("Report data", 74, 83, 25, 96);

Los campos

Los campos, o variables de clase , se pueden declarar dentro del cuerpo de la clase para almacenar datos.

class Foo {
    double bar;
}

Los campos se pueden inicializar directamente cuando se declaran.

class Foo {
    double bar = 2.3;
}
Modificadores
  • static - Hace que el campo sea un miembro estático.
  • final - Permite que el campo se inicialice solo una vez en un constructor o dentro del bloque de inicialización o durante su declaración, lo que ocurra primero.
  • transient- Indica que este campo no se almacenará durante la serialización .
  • volatile- Si se declara un campo volatile, se garantiza que todos los subprocesos vean un valor coherente para la variable.

Herencia

Las clases en Java solo pueden heredar de una clase. Una clase puede derivarse de cualquier clase que no esté marcada como final. La herencia se declara mediante la extendspalabra clave. Una clase puede hacer referencia a sí misma usando la thispalabra clave y su superclase directa usando la superpalabra clave.

class Foo {

}

class Foobar extends Foo {

}

Si una clase no especifica su superclase, hereda implícitamente de la java.lang.Objectclase. Por tanto, todas las clases en Java son subclases de Objectclase.

Si la superclase no tiene un constructor sin parámetros, la subclase debe especificar en sus constructores qué constructor de la superclase usar. Por ejemplo:

class Foo {
    public Foo(int n) {
        // Do something with n
    }
}

class Foobar extends Foo {
    private int number;
    // Superclass does not have constructor without parameters
    // so we have to specify what constructor of our superclass to use and how

    public Foobar(int number) {
        super(number);
        this.number = number;
    }
}
Métodos primordiales

A diferencia de C ++, todos los finalmétodos que no son métodos en Java son virtuales y pueden ser anulados por las clases heredadas.

class Operation {
    public int doSomething() {
        return 0;
    }
}

class NewOperation extends Operation {
    @Override
    public int doSomething() {
        return 1;
    }
}
Clases abstractas

Una clase abstracta es una clase que está incompleta o que se considera incompleta. Las clases normales pueden tener métodos abstractos, es decir, métodos que están declarados pero aún no implementados, solo si son clases abstractas. Una clase C tiene métodos abstractos si se cumple alguna de las siguientes condiciones:

  • C contiene explícitamente una declaración de un método abstracto.
  • Cualquiera de las superclases de C tiene un método abstracto y C no declara ni hereda un método que lo implemente.
  • Una superinterfaz directa de C declara o hereda un método (que por lo tanto es necesariamente abstracto) y C ni declara ni hereda un método que lo implemente.
  • Se puede instanciar una subclase de una clase abstracta que no es abstracta en sí misma, lo que da como resultado la ejecución de un constructor para la clase abstracta y, por lo tanto, la ejecución de los inicializadores de campo para las variables de ejemplo de esa clase.
package org.dwwwp.test;

/**
 * @author jcrypto
 */
public class AbstractClass {
    private static final String hello;

    static {
        System.out.println(AbstractClass.class.getName() + ": static block runtime");
        hello = "hello from " + AbstractClass.class.getName();
    }

    {
        System.out.println(AbstractClass.class.getName() + ": instance block runtime");
    }

    public AbstractClass() {
        System.out.println(AbstractClass.class.getName() + ": constructor runtime");
    }

    public static void hello() {
        System.out.println(hello);
    }
}
package org.dwwwp.test;

/**
 * @author jcrypto
 */
public class CustomClass extends AbstractClass {

    static {
        System.out.println(CustomClass.class.getName() + ": static block runtime");
    }

    {
        System.out.println(CustomClass.class.getName() + ": instance block runtime");
    }

    public CustomClass() {
        System.out.println(CustomClass.class.getName() + ": constructor runtime");
    }

    public static void main(String[] args) {
        CustomClass nc = new CustomClass();
        hello();
        //AbstractClass.hello();//also valid
    }
}

Producción:

org.dwwwp.test.AbstractClass: static block runtime
org.dwwwp.test.CustomClass: static block runtime
org.dwwwp.test.AbstractClass: instance block runtime
org.dwwwp.test.AbstractClass: constructor runtime
org.dwwwp.test.CustomClass: instance block runtime
org.dwwwp.test.CustomClass: constructor runtime
hello from org.dwwwp.test.AbstractClass

Enumeraciones

Esta función de idioma se introdujo en J2SE 5.0 . Técnicamente, las enumeraciones son un tipo de clase que contiene constantes enum en su cuerpo. Cada constante de enumeración define una instancia del tipo de enumeración. Las clases de enumeración no se pueden instanciar en ningún lugar excepto en la propia clase de enumeración.

enum Season {
    WINTER, SPRING, SUMMER, AUTUMN
}

Se permite que las constantes de enumeración tengan constructores, que se llaman cuando se carga la clase:

public enum Season {
    WINTER("Cold"), SPRING("Warmer"), SUMMER("Hot"), AUTUMN("Cooler");

    Season(String description) {
        this.description = description;
    }

    private final String description;

    public String getDescription() {
        return description;
    }
}

Las enumeraciones pueden tener cuerpos de clase, en cuyo caso se tratan como clases anónimas extendiendo la clase enum:

public enum Season {
    WINTER {
        String getDescription() {return "cold";}
    },
    SPRING {
        String getDescription() {return "warmer";}
    },
    SUMMER {
        String getDescription() {return "hot";}
    },
    FALL {
        String getDescription() {return "cooler";}
    };
}

Interfaces

Las interfaces son tipos que no contienen campos y generalmente definen varios métodos sin una implementación real. Son útiles para definir un contrato con cualquier número de implementaciones diferentes. Cada interfaz es implícitamente abstracta. Los métodos de interfaz pueden tener un subconjunto de modificadores de acceso según la versión del idioma strictfp, lo que tiene el mismo efecto que para las clases, y también staticdesde Java SE 8.

interface ActionListener {
    int ACTION_ADD = 0;
    int ACTION_REMOVE = 1;
 
    void actionSelected(int action);
}

Implementando una interfaz

Una interfaz es implementada por una clase usando la implementspalabra clave. Se permite implementar más de una interfaz, en cuyo caso se escriben después de la implementspalabra clave en una lista separada por comas. La clase que implementa una interfaz debe anular todos sus métodos; de lo contrario, debe declararse como abstracta.

interface RequestListener {
    int requestReceived();
}

class ActionHandler implements ActionListener, RequestListener {
    public void actionSelected(int action) {
    }

    public int requestReceived() {
    }
}

//Calling method defined by interface
RequestListener listener = new ActionHandler(); /*ActionHandler can be
                                   represented as RequestListener...*/
listener.requestReceived(); /*...and thus is known to implement
                            requestReceived() method*/

Interfaces funcionales y expresiones lambda

Estas características se introdujeron con el lanzamiento de Java SE 8. Una interfaz se convierte automáticamente en una interfaz funcional si define solo un método. En este caso, una implementación se puede representar como una expresión lambda en lugar de implementarla en una nueva clase, lo que simplifica enormemente la escritura de código en el estilo funcional . Las interfaces funcionales se pueden anotar opcionalmente con la @FunctionalInterfaceanotación, que le indicará al compilador que verifique si la interfaz realmente se ajusta a una definición de interfaz funcional.

// A functional interface
@FunctionalInterface
interface Calculation {
    int calculate(int someNumber, int someOtherNumber);
}

// A method which accepts this interface as a parameter
int runCalculation(Calculation calculation) {
    return calculation.calculate(1, 2);
}

// Using a lambda to call the method
runCalculation((number, otherNumber) -> number + otherNumber);

// Equivalent code which uses an anonymous class instead
runCalculation(new Calculation() {
    @Override
    public int calculate(int someNumber, int someOtherNumber) {
        return someNumber + someOtherNumber;
    }
})

Los tipos de parámetros de Lambda no tienen que estar completamente especificados y se pueden inferir de la interfaz que implementa. El cuerpo de Lambda se puede escribir sin un bloque de cuerpo y una returndeclaración si es solo una expresión. Además, para aquellas interfaces que solo tienen un único parámetro en el método, se pueden omitir los corchetes.

// Same call as above, but with fully specified types and a body block
runCalculation((int number, int otherNumber) -> {
    return number + otherNumber;
});

// A functional interface with a method which has only a single parameter
interface StringExtender {
    String extendString(String input);
}

// Initializing a variable of this type by using a lambda
StringExtender extender = input -> input + " Extended";

Referencias de métodos

No es necesario usar lambdas cuando ya existe un método con nombre compatible con la interfaz. Este método se puede pasar en lugar de un lambda utilizando una referencia de método. Hay varios tipos de referencias a métodos:

Tipo de referencia Ejemplo Lambda equivalente
Estático Integer::sum (number, otherNumber) -> number + otherNumber
Ligado "LongString"::substring index -> "LongString".substring(index)
Sin consolidar String::isEmpty string -> string.isEmpty()
Constructor de clases ArrayList<String>::new capacity -> new ArrayList<String>(capacity)
Constructor de matrices String[]::new size -> new String[size]

El código arriba del cual las llamadas runCalculationpodrían reemplazarse con lo siguiente usando las referencias del método:

runCalculation(Integer::sum);

Herencia

Las interfaces pueden heredar de otras interfaces al igual que las clases. A diferencia de las clases, se permite heredar de múltiples interfaces. Sin embargo, es posible que varias interfaces tengan un campo con el mismo nombre, en cuyo caso se convierte en un único miembro ambiguo, al que no se puede acceder.

/* Class implementing this interface must implement methods of both
ActionListener and RequestListener */
interface EventListener extends ActionListener, RequestListener {    
}

Métodos predeterminados

Java SE 8 introdujo métodos predeterminados para las interfaces, lo que permite a los desarrolladores agregar nuevos métodos a las interfaces existentes sin romper la compatibilidad con las clases que ya implementan la interfaz. A diferencia de los métodos de interfaz normales, los métodos predeterminados tienen un cuerpo que se llamará en el caso si la clase de implementación no lo anula.

interface StringManipulator {
    String extendString(String input);
    
    // A method which is optional to implement
    default String shortenString(String input) {
        return input.substring(1);
    }
}

// This is a valid class despite not implementing all the methods
class PartialStringManipulator implements StringManipulator {
    @Override
    public String extendString(String input) {
        return input + " Extended";
    }
}

Métodos estáticos

Los métodos estáticos son otra característica del lenguaje introducida en Java SE 8. Se comportan exactamente de la misma manera que en las clases.

interface StringUtils {
    static String shortenByOneSymbol(String input) {
        return input.substring(1);
    }
}

StringUtils.shortenByOneSymbol("Test");

Métodos privados

Los métodos privados se agregaron en la versión de Java 9. Una interfaz puede tener un método con un cuerpo marcado como privado, en cuyo caso no será visible para las clases heredadas. Se puede llamar desde métodos predeterminados con el fin de reutilizar el código.

interface Logger {
    default void logError() {
        log(Level.ERROR);
    }

    default void logInfo() {
        log(Level.INFO);
    }

    private void log(Level level) {
        SystemLogger.log(level.id);
    }
}

Anotaciones

Las anotaciones en Java son una forma de incrustar metadatos en el código. Esta función de idioma se introdujo en J2SE 5.0 .

Tipos de anotaciones

Java tiene un conjunto de tipos de anotaciones predefinidos, pero se le permite definir otros nuevos. Una declaración de tipo de anotación es un tipo especial de declaración de interfaz. Se declaran de la misma forma que las interfaces, excepto que la interfacepalabra clave está precedida por el @signo. Todas las anotaciones se extienden implícitamente java.lang.annotation.Annotationy no se pueden extender desde ninguna otra cosa.

@interface BlockingOperations {
}

Las anotaciones pueden tener las mismas declaraciones en el cuerpo que las interfaces comunes, además se les permite incluir enumeraciones y anotaciones. La principal diferencia es que las declaraciones de métodos abstractos no deben tener parámetros ni generar excepciones. También pueden tener un valor predeterminado, que se declara usando la defaultpalabra clave después del nombre del método:

@interface BlockingOperations {
    boolean fileSystemOperations();
    boolean networkOperations() default false;
}
Uso de anotaciones

Las anotaciones se pueden usar en cualquier tipo de declaración, ya sea paquete, clase (incluidas las enumeraciones), interfaz (incluidas las anotaciones), campo, método, parámetro, constructor o variable local. También se pueden usar con constantes enum. Las anotaciones se declaran utilizando el @signo que precede al nombre del tipo de anotación, después del cual los pares elemento-valor se escriben entre corchetes. A todos los elementos sin valor predeterminado se les debe asignar un valor.

@BlockingOperations(/*mandatory*/ fileSystemOperations,
/*optional*/ networkOperations = true)
void openOutputStream() { //Annotated method
}

Además de la forma genérica, hay otras dos formas de declarar una anotación, que son abreviaturas. La anotación de marcador es una forma corta, se usa cuando no se asignan valores a los elementos:

@Unused // Shorthand for @Unused()
void travelToJupiter() {
}

La otra forma corta se llama anotación de elemento único . Se utiliza con tipos de anotaciones que contienen solo un elemento o en el caso de que haya varios elementos, pero solo uno de ellos carece de un valor predeterminado. En la forma de anotación de un solo elemento, el nombre del elemento se omite y en su lugar solo se escribe el valor:

/* Equivalent for @BlockingOperations(fileSystemOperations = true).
networkOperations has a default value and
does not have to be assigned a value */

@BlockingOperations(true)
void openOutputStream() {
}

Genéricos

Genéricos , o tipos parametrizados, o polimorfismo paramétrico es una de las principales características introducidas en J2SE 5.0 . Antes de que se introdujeran los genéricos, se requería declarar todos los tipos explícitamente. Con los genéricos fue posible trabajar de manera similar con diferentes tipos sin declarar los tipos exactos. El objetivo principal de los genéricos es garantizar la seguridad de los tipos y detectar errores de tiempo de ejecución durante la compilación. A diferencia de C #, la información sobre los parámetros utilizados no está disponible en tiempo de ejecución debido al borrado de tipo .

Clases genéricas

Las clases se pueden parametrizar agregando una variable de tipo entre paréntesis angulares ( <y >) después del nombre de la clase. Hace posible el uso de esta variable de tipo en miembros de clase en lugar de tipos reales. Puede haber más de una variable de tipo, en cuyo caso se declaran en una lista separada por comas.

Es posible limitar una variable de tipo a un subtipo de alguna clase específica o declarar una interfaz que debe ser implementada por el tipo. En este caso, la variable de tipo se agrega con la extendspalabra clave seguida del nombre de la clase o la interfaz. Si la variable está restringida tanto por la clase como por la interfaz o si hay varias interfaces, el nombre de la clase se escribe primero, seguido de los nombres de las interfaces con el & signo utilizado como delimitador.

/* This class has two type variables, T and V. T must be 
a subtype of ArrayList and implement Formattable interface */
public class Mapper<T extends ArrayList & Formattable, V> {
    public void add(T array, V item) {
        // array has add method because it is an ArrayList subclass
        array.add(item);
    }
}

Cuando se declara una variable de un tipo parametrizado o se crea una instancia, su tipo se escribe exactamente en el mismo formato que en el encabezado de la clase, excepto que el tipo real se escribe en lugar de la declaración de variable de tipo.

/* Mapper is created with CustomList as T and Integer as V.
CustomList must be a subclass of ArrayList and implement Formattable */
Mapper<CustomList, Integer> mapper = new Mapper<CustomList, Integer>();

Desde Java SE 7, es posible usar un diamante ( <>) en lugar de argumentos de tipo, en cuyo caso se inferirá este último. El siguiente código en Java SE 7 es equivalente al código del ejemplo anterior:

Mapper<CustomList, Integer> mapper = new Mapper<>();

Al declarar una variable para un tipo parametrizado, es posible utilizar comodines en lugar de nombres de tipos explícitos. Los comodines se expresan escribiendo un ?signo en lugar del tipo real. Es posible limitar los tipos posibles a las subclases o superclases de alguna clase específica escribiendo la extendspalabra clave o la superpalabra clave seguida correspondientemente por el nombre de la clase.

/* Any Mapper instance with CustomList as the first parameter
may be used regardless of the second one.*/
Mapper<CustomList, ?> mapper;
mapper = new Mapper<CustomList, Boolean>();
mapper = new Mapper<CustomList, Integer>();

/* Will not accept types that use anything but
a subclass of Number as the second parameter */
void addMapper(Mapper<?, ? extends Number> mapper) {
}

Métodos y constructores genéricos

El uso de genéricos puede estar limitado a algunos métodos particulares, este concepto también se aplica a los constructores. Para declarar un método parametrizado, las variables de tipo se escriben antes del tipo de retorno del método en el mismo formato que para las clases genéricas. En el caso del constructor, las variables de tipo se declaran antes del nombre del constructor.

class Mapper {
    // The class itself is not generic, the constructor is
    <T, V> Mapper(T array, V item) {
    }
}

/* This method will accept only arrays of the same type as
the searched item type or its subtype*/
static <T, V extends T> boolean contains(T item, V[] arr) {
    for (T currentItem : arr) {
        if (item.equals(currentItem)) {
            return true;
        }
    }
    return false;
}

Interfaces genéricas

Las interfaces se pueden parametrizar de manera similar a las clases.

interface Expandable<T extends Number> {
    void addItem(T item);
}

// This class is parameterized
class Array<T extends Number> implements Expandable<T> {
    void addItem(T item) {
    }
}

// And this is not and uses an explicit type instead
class IntegerArray implements Expandable<Integer> {
    void addItem(Integer item) {
    }
}

Ver también

Referencias

enlaces externos