2012-03-09 26 views
6

He estado buscando mucho código recientemente (para mi propio beneficio, ya que todavía estoy aprendiendo a programar), y he notado una serie de proyectos de Java (de lo que parecen ser programadores muy respetados) en donde utilizan algún tipo de down-casting inmediato.¿Cuál es el beneficio del down-casting inmediato?

que en realidad tienen múltiples ejemplos, pero aquí hay uno que me sacó directamente desde el código:

public Set<Coordinates> neighboringCoordinates() { 
    HashSet<Coordinates> neighbors = new HashSet<Coordinates>(); 
    neighbors.add(getNorthWest()); 
    neighbors.add(getNorth()); 
    neighbors.add(getNorthEast()); 
    neighbors.add(getWest()); 
    neighbors.add(getEast()); 
    neighbors.add(getSouthEast()); 
    neighbors.add(getSouth()); 
    neighbors.add(getSouthWest()); 
    return neighbors; 
} 

Y desde el mismo proyecto, aquí hay otro (tal vez más concisa) ejemplo:

private Set<Coordinates> liveCellCoordinates = new HashSet<Coordinates>(); 

En el primer ejemplo, puede ver que el método tiene un tipo de devolución de Set<Coordinates>; sin embargo, ese método específico siempre devolverá un HashSet, y ningún otro tipo de Set.

En el segundo ejemplo, liveCellCoordinates se definió inicialmente como , pero se convierte inmediatamente en HashSet.

Y no es solo este proyecto único y específico: he encontrado que este es el caso en varios proyectos.

Tengo curiosidad por saber cuál es la lógica detrás de esto? ¿Hay algunas convenciones de código que consideren esta buena práctica? ¿Hace que el programa sea más rápido o más eficiente de alguna manera? ¿Qué beneficio tendría?

+2

Estos son en realidad ejemplos de up-casting (de un tipo específico a un tipo más amplio). Consulte, por ejemplo, la [entrada de Wikipedia en downcasting] (http://en.wikipedia.org/wiki/Downcasting). –

+0

@TedHopp Tome el segundo ejemplo que proporcioné, en ese ejemplo, empiezo como 'Conjunto ', y luego convierto a' HashSet' (creo que 'HashSet' es más específico que' Set'). ¿No sería eso malo? ¿Desde que estoy pasando de un amplio tipo de 'Set' a un tipo específico de' HashSet'? – Bob

+1

En el segundo ejemplo, está creando un 'HashSet' y convirtiéndolo en un' Set' al asignarlo a 'liveCellCoordinates'. No existe el objeto 'Set' hasta que se evalúe la 'nueva' expresión y no se 'convierte' a' HashSet'. –

Respuesta

12

Cuando está diseñando una firma de método, generalmente es mejor anotar solo lo que debe fijarse. En el primer ejemplo, al especificar solo que el método devuelve un Set (en lugar de un HashSet específicamente), el implementador puede cambiar la implementación si resulta que un HashSet no es la estructura de datos correcta. Si se hubiera declarado que el método devolvía un HashSet, entonces también sería necesario revisar todo el código que dependía del objeto siendo específicamente un HashSet en lugar del tipo más general Set.

Un ejemplo realista sería si se decidiera que neighboringCoordinates() necesitaba devolver un objeto Set seguro para hilos.Como está escrito, esto sería muy simple de hacer — sustituir la última línea del método con:

return Collections.synchronizedSet(neighbors); 

Como resultado, el objeto devuelto por SetsynchronizedSet() no es HashSet con una asignación compatible. ¡Qué bueno que el método fue declarado para devolver un Set!

Una consideración similar se aplica al segundo caso. El código de la clase que usa liveCellCoordinates no debería necesitar saber nada más que el Set. (De hecho, en el primer ejemplo, habría esperado ver:.

Set<Coordinates> neighbors = new HashSet<Coordinates>(); 

en la parte superior del método)

+2

+1: se trata de ser deliberado acerca de sus elecciones para que sus requisitos mínimos sean claros. El uso de tipos que implican cosas que en realidad no se requieren es más difícil de comprender y mantener. –

4

Porque ahora si cambian el tipo en el futuro, no es necesario actualizar ningún código que dependa de neighborCoordinates.

Vamos que tenía:

HashedSet<Coordinates> c = neighboringCoordinates() 

Ahora, digamos que cambian su código para utilizar una implementación diferente del conjunto. Adivina qué, tienes que cambiar tu código también.

Pero, si usted tiene:

Set<Coordinates> c = neighboringCoordinates() 

Mientras su colección todavía por objetivo la aplicación activada, que pueden cambiar lo que quieran internamente sin afectar a su código.

Básicamente, es solo lo menos específico posible (dentro de lo razonable) por el simple hecho de ocultar detalles internos. Su código solo se preocupa de que pueda acceder a la colección como un conjunto. No importa qué tipo específico de conjunto sea, si tiene sentido. Por lo tanto, ¿por qué hacer que su código se acople a un HashedSet?

1

Es muy sencillo.

En su ejemplo, el método devuelve Set. Desde el punto de vista de un diseñador de API, esto tiene una ventaja significativa, en comparación con la devolución de HashSet.

Si en algún momento, el programador decide usar SuperPerformantSetForDirections, entonces puede hacerlo sin cambiar la API pública, si la nueva clase se extiende Set.

2

En el primer ejemplo, que el método siempre devolverá solo un HashSet es un detalle de implementación que los usuarios de la clase no deberían tener que saber. Esto libera al desarrollador para usar una implementación diferente si es deseable.

2

El principio de diseño en juego aquí es "siempre prefiera especificar tipos abstractos".

Set es abstracto; no existe tal clase concreta Set - es una interfaz, que es por definición abstracta. El contrato del método es devolver un Set - es hasta el desarrollador para elegir tipo de Set para devolver.

usted debe hacer esto con los campos, así, por ejemplo:

private List<String> names = new ArrayList<String>; 

no

private ArrayList<String> names = new ArrayList<String>; 

Más tarde, es posible que desee cambiar al uso de una LinkedList - especificando el tipo abstracto le permite hacer esto sin cambios de código (a excepción de la inicialización del curso).

1

El truco es "code to the interface".

La razón de esto es que en el 99.9% de los casos, solo desea el comportamiento de HashSet/TreeSet/WhateverSet que cumple con la interfaz Set implementada por todos ellos. Mantiene su código más simple, y la única razón por la que realmente necesita decir HashSet es para especificar qué comportamiento tiene .

Como sabrá, HashSet es relativamente rápido pero devuelve los elementos en orden aparentemente aleatorio. TreeSet es un poco más lento, pero devuelve los elementos en orden alfabético. A su código no le importa, siempre que se comporte como un conjunto.

Esto proporciona un código más simple, más fácil de trabajar.

Tenga en cuenta que las opciones típicas para un conjunto es un HashSet, el mapa es HashMap y la lista es ArrayList. Si utiliza una implementación no típica (para usted), debe haber una buena razón para ello (como, por ejemplo, necesitar la ordenación alfabética) y esa razón debe ponerse en un comentario junto a la declaración new. Hace la vida más fácil para los futuros mantenedores.

2

La pregunta es cómo desea utilizar la variable. p.ej. ¿Es en su contexto importante que es un HashSet? De lo contrario, debe decir lo que necesita, y esto es solo un Set.

Las cosas eran diferentes si utilizaras p. Ej. TreeSet aquí. Entonces perdería la información de que el Set está ordenado, y si su algoritmo se basa en esta propiedad, cambiar la implementación a HashSet sería un desastre. En este caso, la mejor solución sería escribir SortedSet<Coordinates> set = new TreeSet<Coordinates>();. O imagina que escribirías List<String> list = new LinkedList<String>();: Está bien si quieres usar list como lista, pero ya no podrás usar el LinkedList como deque, ya que los métodos como offerFirst o peekLast no están en la interfaz List.

Por lo tanto, la regla general es: Sea lo más general posible, pero tan específico como sea necesario. Pregúntate lo que realmente necesitas. ¿Una cierta interfaz proporciona toda la funcionalidad y las promesas que necesita? Si es así, entonces úselo. De lo contrario, sea más específico, use otra interfaz o la clase misma como tipo.

2

Aquí hay otra razón. Se debe a que los tipos más generales (abstractos) tienen menos comportamientos, lo que es bueno porque hay menos espacio para desordenar.

Por ejemplo, supongamos que ha implementado un método como este: List<User> users = getUsers(); cuando de hecho podría haber usado un tipo más abstracto como este: Collection<User> users = getUsers();. Ahora Bob podría asumir erróneamente que su método devuelve a los usuarios en orden alfabético y crea un error. Si hubiera usado Collection, no habría habido tanta confusión.

Cuestiones relacionadas