¿Las expresiones Java Lambda utilizan importaciones “ocultas” o de paquetes locales?

Esta pregunta se trata de las aparentes importaciones “ocultas” o locales de paquetes Java que las expresiones lambda parecen emplear.

El siguiente código de ejemplo se comstack y se ejecuta correctamente (solo enumera los archivos en el directorio dado):

package com.mbm.stockbot; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; public class Temp2 { public static void main(String[] args) { Temp2 t = new Temp2(); t.readDir(); } public void readDir() { try { Files.walk(Paths.get("C:/Users/mbmas_000/Downloads/SEC Edgar"), 1).forEach(filePath -> { if (Files.isRegularFile(filePath)) { System.out.println(filePath); } }); } catch (IOException e1) { e1.printStackTrace(); } } } 

Tenga en cuenta que la variable filePath es una instancia de Path , cuya implementación creo que está contenida en el paquete java.nio.file.Path , aunque no hay import para ese paquete.

Ahora, si hago una pequeña modificación, digamos refacturando la llamada a System.out.println a su propio método:

 package com.mbm.stockbot; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class Temp2 { public static void main(String[] args) { Temp2 t = new Temp2(); t.readDir(); } public void readDir() { try { Files.walk(Paths.get("C:/Users/mbmas_000/Downloads/SEC Edgar"), 1).forEach(filePath -> { if (Files.isRegularFile(filePath)) { printPath(filePath); } }); } catch (IOException e1) { e1.printStackTrace(); } } public void printPath(Path passedFilePath) { System.out.println(passedFilePath); } } 

Ahora debo ‘importar’ import java.nio.file.Path , de lo contrario me sale un error del comstackdor.

Entonces mis preguntas son:

  1. Si filePath es de hecho una instancia de java.nio.file.Path , ¿por qué no necesito importarlo en el primer ejemplo?

  2. Si al usar la expresión lambda se realiza la importación “debajo de las carátulas”, entonces ¿por qué necesito agregar la import cuando creo un método que toma una instancia de Path como argumento?

Los métodos disponibles para invocar tanto filePath como passedFilePath son idénticos, lo que me lleva a creer que ambos son instancias de java.nio.file.Path .

import declaraciones de import no están destinadas a declarar qué clases está usando su código; solo declaran qué usar para resolver identificadores no calificados. Por lo tanto, si usa la Path identificación no calificada en su código, debe usar import java.nio.file.Path; para declarar que debe resolverse para este tipo calificado. Esta no es la única forma de resolver un nombre, por cierto. Los nombres también pueden resolverse a través de la herencia de clase, por ejemplo, si coinciden con el nombre simple de una clase de miembro heredada.

Si está utilizando un tipo implícitamente sin referirse a su nombre, no necesita una statement de import , eso no está limitado a las expresiones lambda, ni siquiera es una característica especial de Java 8. Ej. Con

 Files.walk(Paths.get("C:/Users/mbmas_000/Downloads/SEC Edgar"), 1) 

ya está utilizando el tipo de Paths.get de Paths.get implícitamente ya que es el tipo de Paths.get de Paths.get y un tipo de parámetro de Files.walk , en otras palabras, está recibiendo una instancia de java.nio.file.Path y pasándola a otro método sin haciendo referencia a su nombre de tipo, por lo tanto, no necesita una import . Además, está llamando a un método varargs que acepta una cantidad arbitraria de instancias de FileVisitOption . No está especificando ninguna, por lo tanto, su código creará una matriz FileVisitOption[] longitud FileVisitOption[] y la pasará a Files.walk , de nuevo, sin una import .

Con la inferencia de tipo mejorada, existe otra posibilidad de usar un tipo sin hacer referencia a su nombre, por ejemplo, si llama:

 Files.newByteChannel(path, new HashSet<>()); 

No solo está creando una matriz FileAttribute[] longitud cero para el parámetro varargs sin hacer referencia a este tipo por nombre, HashSet también está creando un HashSet sin hacer referencia al tipo OpenOption por nombre. Por lo tanto, esto tampoco requiere ninguno, importando java.nio.file.attribute.FileAttribute ni java.nio.file.OpenOption .


Entonces, la conclusión es que si necesita una import no depende del uso del tipo, sino de si se refiere a ella por su nombre simple (y hay más de una forma de usar un tipo sin referirse a él por su nombre). En su segundo ejemplo, se refiere al nombre Path en su método printPath(Path passedFilePath) ; si lo cambia a printPath(Object passedFilePath) , todo volverá a funcionar sin una import explícita de java.nio.file.Path .

La diferencia es que en el segundo ejemplo, usted declara una variable local Path passedFilePath (como un parámetro de método). Cuando hace esto, necesita una import para decirle al comstackdor de java qué tipo de Path quiere decir, porque varios paquetes pueden tener una clase con el mismo nombre. Es posible que haya notado que cuando crea una variable de List something y le pide al IDE que cree automáticamente la importación, la mayoría de los IDEs le preguntarán si se java.util.List o java.awt.List . También podría crear una clase propia com.myorg.myproject.List que sería una tercera opción.

En el primer ejemplo, el tipo exacto de filePath está determinado por el tipo requerido por Paths.get(...).forEach , por lo que no necesita decirle al comstackdor java a qué class Path se refiere.

Por cierto, podría omitir la importación en el segundo ejemplo, cuando reescriba la firma del método como public void printPath(java.nio.file.Path passedFilePath) . Al proporcionar un nombre de clase completamente calificado, ya no necesita importar porque el nombre de clase no puede ser ambiguo.

Tal vez se pregunte “¿por qué necesito una importación o un nombre completo cuando solo hay una clase llamada Path en toda la biblioteca estándar y no tengo una clase propia de ese nombre?” – recuerde que Java está diseñado para la reutilización del código. Cuando su código se usa en otro proyecto, ese proyecto podría tener dicha clase o podría usar una biblioteca de terceros que tenga y luego su código sería ambiguo.

Necesita usar una importación en el segundo ejemplo porque está declarando una variable.

Esto no tiene nada que ver con las expresiones lambda. Tendrás exactamente lo mismo si usas una clase anónima.

Creo que el punto que intentas ilustrar se puede simplificar así:

Esta lambda requiere una importación

Paths.get("path").forEach((Path filePath) -> {});

Esta lambda no requiere una importación

Paths.get("path").forEach((filePath) -> {});

Como Path.forEach(...) toma un Consumer Consumer Supongo que el último caso está creando un nuevo tipo sobre la marcha de ? super Path ? super Path , y por lo tanto no necesita una importación ya que es un tipo nuevo (como un tipo genérico en tiempo de ejecución)