7. Embalaje (1)

Resumen del capítulo

  • concepto de paquete
    • organización del código
    • Crear nombres de paquetes únicos
    • conflicto
    • Biblioteca de herramientas personalizada
    • Utilice la importación para cambiar el comportamiento
    • Consejos sobre el uso de paquetes.

El control de acceso (u ocultamiento de la implementación) tiene que ver con una "implementación inicial inadecuada".

Todos los buenos autores (incluidos los que escriben software) saben que un buen trabajo requiere mucho trabajo para ser bueno. Si deja un fragmento de código en una posición determinada por un tiempo y lo mira nuevamente más tarde, puede encontrar una mejor manera de implementarlo. Esta es una de las fuerzas impulsoras detrás de la refactorización, que consiste en reescribir el código de trabajo para hacerlo más legible, comprensible y, por lo tanto, más fácil de mantener.

Pero junto con el deseo de modificar y mejorar el código, también existe una presión tremenda. A menudo, algunos usuarios ( programadores clientes) quieren que su código permanezca sin cambios en algunos aspectos. Entonces quieres modificar el código, pero ellos quieren que el código permanezca igual. Esto lleva a una pregunta básica en el diseño orientado a objetos: "Cómo distinguir las cosas que cambian de las que no cambian".

Esta cuestión es especialmente importante para las bibliotecas. Los usuarios de una biblioteca de clases deben confiar en la parte de la biblioteca que utilizan y saber que no necesitan reescribir su código si se utiliza una nueva versión de la biblioteca. Por otro lado, los desarrolladores de bibliotecas deben tener la libertad de modificar y mejorar la biblioteca y garantizar que el código del cliente no se vea afectado por estos cambios.

Esto se puede resolver por convención. Por ejemplo, los desarrolladores de bibliotecas deben aceptar que al modificar una clase en la biblioteca, no eliminarán los métodos existentes, porque eso rompería el código del programador cliente. La situación contraria es más complicada. En el caso de las propiedades de los miembros, ¿cómo sabe el desarrollador de la biblioteca qué propiedades utiliza el programador del cliente? Lo mismo ocurre con los métodos que se crean únicamente para implementar clases de biblioteca y no están diseñados para ser llamados por programadores cliente. ¿Qué sucede si el desarrollador de la biblioteca quiere eliminar la implementación anterior y agregar una nueva? Los cambios en cualquiera de estos miembros pueden romper el código del programador del cliente. Entonces el desarrollador de la biblioteca está ocupado y no puede modificar nada.

Para resolver este problema, Java proporciona especificadores de acceso para que los desarrolladores de bibliotecas de clases indiquen cuáles están disponibles para los programadores cliente y cuáles no. Los niveles de permisos de control de acceso, desde "permisos máximos" hasta "permisos mínimos" son: público , protegido , acceso a paquetes (acceso a paquetes) (sin palabra clave) y privado . Según el párrafo anterior, podría pensar que, como diseñador de bibliotecas, haría que todo fuera lo más privado posible , exponiendo a los programadores cliente sólo los métodos que desea que utilicen. Eso es lo que normalmente haces, aunque es contrario a la intuición para las personas que programan en otros lenguajes (especialmente C) y están acostumbradas al acceso sin restricciones a cualquier cosa.

Sin embargo, el concepto de componentes de la biblioteca de clases y el control de acceso a los componentes de la biblioteca de clases aún están incompletos. El problema que persiste es cómo agrupar los componentes de la biblioteca en una unidad de biblioteca cohesiva. En Java, se controla mediante la palabra clave del paquete . Si la clase está en el mismo paquete o en un paquete diferente afectará el modificador de acceso. Entonces, al comienzo de este capítulo, aprenderá cómo colocar componentes de biblioteca en el mismo paquete y luego comprenderá el significado completo de los modificadores de acceso.

concepto de paquete

Un paquete contiene un conjunto de clases organizadas bajo un único espacio de nombres.

Por ejemplo, la distribución estándar de Java tiene una biblioteca de utilidades organizada bajo el espacio de nombres java.util . java.util contiene una clase llamada ArrayList . Una forma de utilizar ArrayList es por su nombre completo java.util.ArrayList .

// hiding/FullQualification.java

public class FullQualification {
    
    
    public static void main(String[] args) {
    
    
        java.util.ArrayList list = new java.util.ArrayList();
    }
}

Este enfoque hace que el programa sea tedioso, por lo que puedes utilizar la palabra clave import en su lugar . Si necesita importar una clase, debe declararla en la declaración de importación :

// hiding/SingleImport.java
import java.util.ArrayList;

public class SingleImport {
    
    
    public static void main(String[] args) {
    
    
        ArrayList list = new ArrayList();
    }
}

Ahora puedes usar ArrayList directamente sin agregar calificadores . Pero aún no puedes usar otras clases bajo el paquete java.util . Para importar todas las clases que contiene, simplemente use *****, como en otros ejemplos de este libro:

import java.util.*

Las importaciones se utilizan para proporcionar un mecanismo para administrar espacios de nombres. Todos los nombres de clases están aislados entre sí. Un método de la clase Af() no entrará en conflicto con un método de la clase B con la misma firma f(). Pero ¿qué pasa si los nombres de las clases entran en conflicto? ¿Qué debe hacer si crea una clase Stack y planea instalarla en una máquina que ya tiene una clase Stack escrita por otra persona? Este posible conflicto de nombres de clases es la razón por la que necesitamos un control total sobre los espacios de nombres en Java. Para resolver conflictos, creamos una combinación de identificador única para cada clase.

La mayoría de los ejemplos hasta ahora sólo han existido en un único archivo y se han utilizado localmente, por lo que no se han visto afectados por los nombres de los paquetes. Sin embargo, estos ejemplos ya están ubicados en un paquete, llamado paquete "sin nombre" o _paquete predeterminado_ (paquete predeterminado). Por supuesto, esta es una opción y, en aras de la simplicidad, el resto del libro intentará hacerlo tanto como sea posible. Sin embargo, si tiene la intención de crear bibliotecas de clases o programas amigables para otros programas Java en la misma máquina, debe considerar cuidadosamente para evitar conflictos de nombres de clases.

Un archivo de código fuente Java se denomina unidad de compilación (a veces también se denomina unidad de traducción) . El sufijo del nombre de archivo de cada unidad de compilación debe ser .java . Puede haber una clase pública en la unidad de compilación y su nombre de clase debe ser el mismo que el nombre del archivo (incluidas mayúsculas y minúsculas, pero sin el sufijo .java ). Solo puede haber una clase pública en cada unidad de compilación ; de lo contrario, el compilador no la aceptará. Si hay otras clases en esta unidad de compilación, no se puede acceder a estas clases fuera del paquete porque no son clases públicas . En este momento, proporcionan clases de "soporte" para la clase pública principal.

organización del código

Al compilar un archivo .java , habrá un archivo de salida para cada clase en el archivo .java . El nombre de archivo de cada salida es el mismo que el nombre de clase de cada clase en el archivo .java , pero el sufijo es .class . Por lo tanto, después de compilar una pequeña cantidad de archivos .java , obtendrá una gran cantidad de archivos .class . Si ha utilizado un lenguaje compilado, entonces probablemente esté acostumbrado a compilar para producir un archivo intermedio (a menudo llamado archivo "obj"), que luego se genera utilizando un vinculador (para crear un archivo ejecutable) o un generador de biblioteca de clases ( para crear una biblioteca de clases) Otros archivos similares se empaquetan juntos. Esta no es la forma en que funciona Java. En Java, un programa ejecutable es un conjunto de archivos .class que se pueden empaquetar y comprimir en un archivo Java (JAR, utilizando el generador de archivos jar). El intérprete de Java es responsable de buscar, cargar e interpretar estos archivos.

Una biblioteca de clases es un conjunto de archivos de clases. Cada archivo fuente normalmente contiene una clase pública y cualquier número de clases no públicas , por lo que cada archivo tiene un componente público . Si agrupa estos componentes, debe utilizar la palabra clave paquete .

Si utiliza una declaración de paquete , debe ser la primera línea de código del archivo, además de los comentarios. Cuando escribes así:

package hiding;

Significa que esta unidad de compilación es parte de una biblioteca de clases llamada ocultación . En otras palabras, los nombres de clases públicas en la unidad de compilación que estás declarando están bajo un paraguas llamado ocultación . Cualquiera que quiera usar este nombre debe especificar el nombre completo de la clase o importarlo oculto usando la palabra clave import . (Tenga en cuenta que los nombres de los paquetes Java siempre están en minúsculas por convención, incluso las palabras en el medio deben estar en minúsculas, lo cual es diferente de los nombres en mayúsculas y minúsculas)

Por ejemplo, suponiendo que el nombre del archivo es MyClass.java , esto significa que solo puede haber una clase pública en el archivo y el nombre de la clase debe ser MyClass (también en el mismo caso que el nombre del archivo):

// hiding/mypackage/MyClass.java
package hiding.mypackage;

public class MyClass {
    
    
    // ...
}

Ahora, si alguien quiere usar MyClass u otras clases públicas de hide.mypackage , debe usar la palabra clave import para que los nombres estén disponibles en hide.mypackage . Otra opción es utilizar el nombre completo:

// hiding/QualifiedMyClass.java

public class QualifiedMyClass {
    
    
    public static void main(String[] args) {
    
    
        hiding.mypackage.MyClass m = new hiding.mypackage.MyClass();
    }
}

La importación de palabras clave lo hace más conciso:

// hiding/ImportedMyClass.java
import hiding.mypackage.*;

public class ImportedMyClass {
    
    
    public static void main(String[] args) {
    
    
        MyClass m = new MyClass();
    }
}

Las dos palabras clave paquete e importación separan un único espacio de nombres global para evitar conflictos de nombres.

Crear nombres de paquetes únicos

Puede notar que un paquete nunca está realmente empaquetado en un solo archivo, puede estar compuesto por muchos archivos .class , por lo que las cosas se complican un poco. Para evitar esta situación, un enfoque lógico es colocar todos los archivos .class en un paquete específico en un directorio. En otras palabras, se utiliza la naturaleza jerárquica de la estructura de archivos del sistema operativo. Esta es una forma en que Java resuelve el desorden; verás otra forma más adelante cuando presentemos la herramienta jar .

Colocar todos los archivos en un subdirectorio también resuelve otros dos problemas: crear nombres de paquetes únicos y encontrar clases que pueden estar ocultas en algún lugar de la estructura del directorio. Esto se logra codificando la ubicación de la ruta donde reside el archivo .class en el nombre del paquete . Por convención, el nombre del paquete es el inverso del nombre de dominio de Internet del creador de la clase. Si sigue la convención, dado que los nombres de dominio de Internet son únicos, los nombres de sus paquetes también deben ser únicos para que no se produzcan conflictos de nombres. Si no tiene su propio nombre de dominio, tendrá que crear un nombre de paquete único mediante la construcción de un conjunto de combinaciones (como su nombre) que es poco probable que otros repitan. Si va a distribuir su código Java, vale la pena adquirir un nombre de dominio.

La segunda parte de este truco es dividir el nombre del paquete en un directorio de su máquina, de modo que cuando el intérprete de Java tenga que cargar un archivo .class, pueda localizar la ubicación donde se encuentra el archivo .class . Primero, busca la variable de entorno CLASSPATH (establecida por el sistema operativo y, a veces, también por el instalador de Java o las herramientas basadas en Java). CLASSPATH contiene uno o más directorios utilizados como directorio raíz para localizar archivos .class . A partir del directorio raíz, el intérprete de Java toma el nombre del paquete y reemplaza cada punto con una barra invertida, generando un nombre de ruta basado en el directorio raíz (dependiendo de su sistema operativo, el nombre del paquete foo.bar.baz se convierte en foo \ bar \ baz o foo/bar/baz u otros). Luego, esta ruta se concatena con varias entradas en CLASSPATH , y el intérprete busca en estos directorios el archivo .class asociado con el nombre de la clase que creó (el intérprete también busca en ciertos directorios estándar que involucran la ubicación del intérprete de Java).

Para entender esto, digamos que el nombre de dominio MindviewInc.com , si se invierte y se cambia a minúsculas, sería com.mindviewinc , que serviría como nombre global único para la clase que se está creando. (Las extensiones como com, edu, org, etc. estaban todas en mayúsculas en los paquetes de Java antes, pero después de Java 2, todas están en minúsculas). Decidí crear otra biblioteca de clases llamada simple para subdividir el nombre :

package com.mindviewinc.simple;

Este nombre de paquete se puede utilizar como un espacio de nombres general para los dos archivos siguientes:

// com/mindviewinc/simple/Vector.java
// Creating a package
package com.mindviewinc.simple;

public class Vector {
    
    
    public Vector() {
    
    
        System.out.println("com.mindviewinc.simple.Vector");
    }
}

Como se mencionó anteriormente, la declaración del paquete debe ser la primera línea de código no comentado en el archivo. El segundo archivo es similar:

// com/mindviewinc/simple/List.java
// Creating a package
package com.mindviewinc.simple;

public class List {
    
    
    System.out.println("com.mindview.simple.List");
}

Ambos archivos están ubicados en un subdirectorio de mi máquina de la siguiente manera:

C:\DOC\Java\com\mindviewinc\simple

(Tenga en cuenta que la primera línea de comentarios en cada archivo de este libro especifica la ubicación del archivo en el árbol del código fuente, para uso de las herramientas automatizadas de extracción de código de este libro).

Si vuelve a mirar la ruta, verá el nombre del paquete com.mindviewinc.simple , pero ¿qué pasa con la primera parte de la ruta? La variable de entorno CLASSPATH se encargará de ello. La sección de variables de entorno en mi máquina es la siguiente:

CLASSPATH=.;D:\JAVA\LIB;C:\DOC\Java

CLASSPATH puede contener varias rutas de búsqueda diferentes.

Pero cuando se trabaja con archivos JAR, es un poco diferente. Debe especificar el nombre real del archivo JAR en la ruta de clase, no solo el directorio donde se encuentra el archivo JAR. Por lo tanto, para un archivo JAR llamado uva.jar , la ruta de clase debe incluir:

CLASSPATH=.;D\JAVA\LIB;C:\flavors\grape.jar

Una vez configurado el classpath, los siguientes archivos se pueden colocar en cualquier directorio:

// hiding/LibTest.java
// Uses the library
import com.mindviewinc.simple.*;

public class LibTest {
    
    
    public static void main(String[] args) {
    
    
        Vector v = new Vector();
        List l = new List();
    }
}

Producción:

com.mindviewinc.simple.Vector
com.mindviewinc.simple.List

Cuando el compilador encuentra una declaración de importación que importa la biblioteca simple, primero buscará el subdirectorio com/mindviewinc/simple en el directorio especificado por CLASSPATH y luego buscará el nombre coincidente en el archivo compilado ( Vector Vector.classpor , que es List.class para List ). Tenga en cuenta que estas dos clases y los métodos a los que se accederá deben ser públicos .

Para los principiantes de Java, configurar CLASSPATH es algo problemático (lo pensé cuando lo usé por primera vez), y las versiones posteriores de JDK son más inteligentes. Descubrirá que cuando instala el JDK, puede compilar y ejecutar programas Java básicos incluso si no configura CLASSPATH. Sin embargo, para compilar y ejecutar los ejemplos de código de este libro (obtenidos de https://github.com/BruceEckel/OnJava8-examples ), debe agregar el directorio base del árbol de códigos de programa del libro a CLASSPATH (el directorio gradlew El comando administra su propio CLASSPATH, por lo que si desea usar javac y java directamente sin Gradle, debe configurar CLASSPATH).

conflicto

¿Qué sucede si dos bibliotecas que contienen el mismo nombre de clase se importan mediante *? Por ejemplo, supongamos que el programa es el siguiente:

import com.mindviewinc.simple.*;
import java.util.*;

Dado que java.util. * también contiene la clase Vector , existe un conflicto potencial. Pero mientras no escriba código que cause conflictos, no habrá ningún problema, lo cual está bien; de lo contrario, tendría que realizar muchas comprobaciones de tipos para evitar conflictos que nunca surgen.

Ahora, si desea crear una clase Vector, se producirá un conflicto:

Vector v = new Vector();

¿A quién se refiere aquí la clase Vector ? El compilador no lo sabe, y el lector tampoco. Entonces el compilador informa un error, lo que le obliga a especificarlo explícitamente. Para la clase estándar de Java Vector , puedes escribir:

java.util.Vector v = new java.util.Vector();

Esta forma de escribir especifica completamente la ubicación de la clase Vector (con CLASSPATH), por lo que no es necesario escribir la declaración import java.util. * a menos que se utilicen otras clases de java.util .

Alternativamente, se pueden importar clases individuales para evitar conflictos, siempre y cuando los nombres en conflicto no se utilicen en el mismo programa (si se utiliza un nombre en conflicto, se debe especificar explícitamente el nombre completo).

Biblioteca de herramientas personalizada

Armado con el conocimiento anterior, ahora puede crear su propia biblioteca de herramientas para reducir el código de programa duplicado.

Normalmente, usaría el nombre de dominio invertido para nombrar el kit de herramientas que quiero crear, como com.mindviewinc.util , pero para simplificar, nombré el kit de herramientas onjava .

Por ejemplo, en el capítulo "Flujo de control" se utiliza el siguiente método range(), que utiliza la sintaxis for-in para un recorrido simple:

// onjava/Range.java
// Array creation methods that can be used without
// qualifiers, using static imports:
package onjava;

public class Range {
    
    
    // Produce a sequence [0,n)
    public static int[] range(int n) {
    
    
        int[] result = new int[n];
        for (int i = 0; i < n; i++) {
    
    
            result[i] = i;
        }
        return result;
    }
    // Produce a sequence [start..end)
    public static int[] range(int start, int end) {
    
    
        int sz = end - start;
        int[] result = new int[sz];
        for (int i = 0; i < sz; i++) {
    
    
            result[i] = start + i;
        }
        return result;
    }
    // Produce sequence [start..end) incrementing by step
    public static int[] range(int start, int end, int step) {
    
    
        int sz = (end - start) / step;
        int[] result = new int[sz];
        for (int i = 0; i < sz; i++) {
    
    
            result[i] = start + (i * step);
        }
        return result;
    }
}

La ubicación de este archivo debe estar en un directorio que comience con una ubicación CLASSPATH y vaya seguido de onjava . Después de la compilación, puede utilizar la instrucción import onjava para utilizar estos métodos en cualquier parte del sistema .

De ahora en adelante, siempre que crees una nueva herramienta útil, podrás agregarla a tu biblioteca. A lo largo de este libro, verá más componentes agregados a la biblioteca de Onjava .

Utilice la importación para cambiar el comportamiento

Java no tiene la función de compilación condicional de C, que le permite alternar un interruptor para producir un comportamiento diferente sin cambiar ningún código de programa. La razón por la que Java eliminó esta característica puede deberse a que C usa esta característica en la mayoría de los casos para resolver problemas multiplataforma: diferentes partes del código del programa deben compilarse de acuerdo con diferentes plataformas. Dado que Java está diseñado para ser multiplataforma, esta función no es necesaria.

Sin embargo, la compilación condicional tiene otros usos. La depuración es un uso muy común: la función de depuración se habilita durante el proceso de desarrollo y se deshabilita en el producto lanzado. Esto se puede lograr cambiando el paquete importado cambiando el código en el programa de la versión de depuración a la versión de lanzamiento. Esta técnica se puede utilizar para cualquier tipo de código condicional.

Consejos sobre el uso de paquetes.

Al crear un paquete, la estructura del directorio está implícita en el nombre del paquete. Este paquete debe estar ubicado en el directorio especificado por el nombre del paquete, que debe poder buscarse en el directorio que comienza con CLASSPATH. Usar la palabra clave paquete puede resultar un poco incómodo al principio, porque a menos que siga la regla "el nombre del paquete corresponde a la ruta del directorio", recibirá muchos mensajes de error de tiempo de ejecución inesperados, como que no se encuentra una clase específica, incluso si la clase se encuentra en el mismo directorio middle. Si recibe un mensaje similar, intente comentar la declaración del paquete. Si el programa puede ejecutarse, sabrá dónde está el problema.

Tenga en cuenta que el código compilado normalmente se encuentra en un directorio diferente al del código fuente. Esto es estándar para muchos proyectos y los entornos de desarrollo integrados (IDE) a menudo lo hacen automáticamente. Se debe garantizar que la JVM pueda encontrar el código compilado a través de CLASSPATH.

おすすめ

転載: blog.csdn.net/GXL_1012/article/details/132130383