¿Por qué no hay métodos estáticos en Interfaces, pero los campos estáticos y las clases internas están bien?

Se han formulado algunas preguntas sobre por qué no se pueden definir métodos estáticos dentro de las interfaces, pero ninguna de ellas soluciona una incoherencia básica: ¿por qué se pueden definir campos estáticos y tipos internos estáticos dentro de una interfaz, pero no métodos estáticos?

Los tipos internos estáticos quizás no sean una comparación justa, ya que eso es solo azúcar sintáctico que genera una clase nueva, pero ¿por qué campos pero no métodos?

Un argumento en contra de los métodos estáticos dentro de las interfaces es que rompe la estrategia de resolución de tabla virtual utilizada por la JVM, pero ¿no debería aplicarse igualmente a los campos estáticos, es decir, el comstackdor puede simplemente alinearlo?

La coherencia es lo que deseo, y Java no debería soportar ninguna estática de ninguna forma dentro de una interfaz, o debería ser consistente y permitirlos.

Se ha realizado una propuesta oficial para permitir métodos estáticos en las interfaces en Java 7. Esta propuesta se realiza bajo Project Coin .

Mi opinión personal es que es una gran idea. No hay dificultad técnica en la implementación, y es algo muy lógico y razonable de hacer. Hay varias propuestas en Project Coin que espero nunca se conviertan en parte del lenguaje Java, pero este es uno que podría limpiar muchas API. Por ejemplo, la clase Collections tiene métodos estáticos para manipular cualquier implementación de List ; esos podrían ser incluidos en la interfaz de la List .


Actualización: En Java Posse Podcast # 234, Joe D’arcy mencionó la propuesta brevemente, diciendo que era “compleja” y que probablemente no entraría en Project Coin.


Actualización: si bien no llegaron a Project Coin para Java 7, Java 8 sí admite funciones estáticas en las interfaces.

Voy a ir con mi teoría favorita con esta, que es que la falta de consistencia en este caso es una cuestión de conveniencia en lugar de diseño o necesidad, ya que no he escuchado ningún argumento convincente de que sea cualquiera de esos dos .

Los campos estáticos están ahí (a) porque estaban allí en JDK 1.0, y muchas decisiones dudosas se hicieron en JDK 1.0, y (b) los campos finales estáticos en las interfaces son lo más parecido que java tenía a las constantes en ese momento.

Se permitieron las clases internas estáticas en las interfaces porque eso es azúcar sintáctico puro: la clase interna en realidad no tiene nada que ver con la clase padre.

Entonces, los métodos estáticos no están permitidos simplemente porque no hay una razón convincente para hacerlo; la consistencia no es lo suficientemente convincente para cambiar el status quo.

Por supuesto, esto podría permitirse en futuras versiones de JLS sin romper nada.

Nunca hay un punto para declarar un método estático en una interfaz. No pueden ser ejecutados por la llamada normal MyInterface.staticMethod (). (EDITAR: Dado que la última frase confundió a algunas personas, llamar a MyClass.staticMethod () ejecuta precisamente la implementación de staticMethod en MyClass, que si MyClass es una interfaz no puede existir!) Si las llamas especificando la clase de implementación MyImplementor.staticMethod () entonces debe conocer la clase real, por lo que es irrelevante si la interfaz la contiene o no.

Más importante aún, los métodos estáticos nunca se anulan, y si intenta hacer:

 MyInterface var = new MyImplementingClass(); var.staticMethod(); 

las reglas para static dicen que el método definido en el tipo de var declarado debe ser ejecutado. Como esta es una interfaz, esto es imposible.

Por supuesto, siempre puede eliminar la palabra clave estática del método. Todo funcionará bien. Puede que tenga que suprimir algunas advertencias si se llama desde un método de instancia.

Para responder algunos de los comentarios a continuación, la razón por la que no puede ejecutar “result = MyInterface.staticMethod ()” es que debería ejecutar la versión del método definido en MyInterface. Pero no puede haber una versión definida en MyInterface, porque es una interfaz. No tiene código por definición.

El objective de las interfaces es definir un contrato sin proporcionar una implementación. Por lo tanto, no puede tener métodos estáticos, ya que tendrían que tener una implementación en la interfaz, ya que no puede anular los métodos estáticos. En cuanto a los campos, solo se permiten los final fields estáticos, que son, esencialmente, constantes (en 1.5+ también puede tener enumeraciones en las interfaces). Las constantes están ahí para ayudar a definir la interfaz sin números mágicos.

Por cierto, no es necesario especificar explícitamente modificadores static final para los campos en las interfaces, ya que solo se permiten los campos finales estáticos.

Este es un hilo viejo, pero esta es una pregunta muy importante para todos. Como me di cuenta de esto hoy solo así que estoy tratando de explicarlo de una manera más limpia:

El objective principal de la interfaz es proporcionar algo que no se puede implementar, por lo que si proporcionan

métodos estáticos para ser permitidos

entonces puede llamar a ese método usando interfaceName.staticMethodName () , pero este es el método no implementado y no contiene nada. Por lo tanto, es inútil permitir métodos estáticos. Por lo tanto, no proporcionan esto en absoluto.

campos estáticos están permitidos

porque los campos no son implementables, por implementable quiero decir que no puede realizar ninguna operación lógica en el campo, puede hacer la operación en el campo. Entonces no cambias el comportamiento de campo, es por eso que están permitidos.

Las clases internas están permitidas

Las clases internas están permitidas porque después de la comstackción se crea un archivo de clase diferente de la clase interna, digamos InterfaceName $ InnerClassName.class , por lo que básicamente está proporcionando implementación en diferentes entidades, pero no en la interfaz. Entonces se proporciona la implementación en clases internas.

Espero que esto ayude.

En realidad, a veces hay razones por las cuales alguien puede beneficiarse de los métodos estáticos. Se pueden usar como métodos de fábrica para las clases que implementan la interfaz. Por ejemplo, esa es la razón por la que ahora tenemos la interfaz de Colección y la clase Colecciones en openjdk. Entonces, hay soluciones como siempre: proporcionar otra clase con un constructor privado que servirá como un “espacio de nombres” para los métodos estáticos.

Antes de Java 5, un uso común para campos estáticos era:

 interface HtmlConstants { static String OPEN = "< "; static String SLASH_OPEN = ""; static String SLASH_CLOSE = " />"; static String HTML = "html"; static String BODY = "body"; ... } public class HtmlBuilder implements HtmlConstants { // implements ?!? public String buildHtml() { StringBuffer sb = new StringBuffer(); sb.append(OPEN).append(HTML).append(CLOSE); sb.append(OPEN).append(BODY).append(CLOSE); ... sb.append(SLASH_OPEN).append(BODY).append(CLOSE); sb.append(SLASH_OPEN).append(HTML).append(CLOSE); return sb.toString(); } } 

Esto significaba que HtmlBuilder no tendría que calificar cada constante, por lo que podría usar OPEN en lugar de HtmlConstants.OPEN

Usar implementos de esta manera es en última instancia confuso.

Ahora con Java 5, tenemos la syntax estática de importación para lograr el mismo efecto:

 private final class HtmlConstants { ... private HtmlConstants() { /* empty */ } } import static HtmlConstants.*; public class HtmlBuilder { // no longer uses implements ... } 

No hay una razón real para no tener métodos estáticos en las interfaces, excepto que los diseñadores de lenguaje Java no lo querían así. Desde un punto de vista técnico, tendría sentido permitirlos. Después de todo, una clase abstracta también puede tenerlos. Supongo, pero no lo probé, que puede “elaborar a mano” código de bytes donde la interfaz tiene un método estático y que debería funcionar sin problemas para llamar al método y / o utilizar la interfaz como siempre.

A menudo me pregunto por qué métodos estáticos en absoluto? Tienen sus usos, pero los métodos de nivel de paquete / espacio de nombres probablemente cubrirían 80 de los métodos estáticos utilizados.

Dos razones principales vienen a la mente:

  1. Los métodos estáticos en Java no pueden ser reemplazados por subclases, y esto es mucho más importante para los métodos que los campos estáticos. En la práctica, nunca quise anular un campo en una subclase, pero anulo los métodos todo el tiempo. Por lo tanto, tener métodos estáticos evita que una clase que implementa la interfaz suministre su propia implementación de ese método, lo que en gran medida frustra el propósito de usar una interfaz.

  2. Se supone que las interfaces no tienen código; para eso están las clases abstractas. El objective de una interfaz es permitirle hablar sobre objetos posiblemente no relacionados que tienen un cierto conjunto de métodos. En realidad, proporcionar una implementación de esos métodos está fuera de los límites de las interfaces que se pretende que sean.

Los métodos estáticos están vinculados a una clase. En Java, una interfaz no es técnicamente una clase, es un tipo, pero no una clase (por lo tanto, la palabra clave se implementa, y las interfaces no extienden el objeto). Debido a que las interfaces no son clases, no pueden tener métodos estáticos, porque no hay una clase real para adjuntar.

Puede llamar a InterfaceName.class para obtener el objeto de clase correspondiente a la interfaz, pero la clase Class indica específicamente que representa clases e interfaces en una aplicación Java. Sin embargo, la interfaz en sí misma no se trata como una clase y, por lo tanto, no se puede adjuntar un método estático.

Solo se pueden declarar campos finales estáticos en una interfaz (al igual que los métodos, que son públicos incluso si no incluye la palabra clave “pública”, los campos estáticos son “finales” con o sin la palabra clave).

Estos son solo valores, y se copiarán literalmente donde sea que se usen en tiempo de comstackción, por lo que nunca “llama” campos estáticos en tiempo de ejecución. Tener un método estático no tendría la misma semántica, ya que implicaría llamar a una interfaz sin una implementación, que Java no permite.

La razón es que todos los métodos definidos en una interfaz son abstractos ya sea que declares explícitamente o no ese modificador. Un método estático abstracto no es una combinación permisible de modificadores ya que los métodos estáticos no pueden anularse.

En cuanto a por qué las interfaces permiten campos estáticos. Tengo la sensación de que debería considerarse una “característica”. La única posibilidad que se me ocurre sería agrupar las constantes que interesarían a las implementaciones de la interfaz.

Estoy de acuerdo en que la coherencia hubiera sido un mejor enfoque. No se deben permitir miembros estáticos en una interfaz.

Creo que se puede acceder a los métodos estáticos sin crear un objeto y la interfaz no permite crear un objeto para restringir a los progtwigdores el uso directo de los métodos de interfaz en lugar de su clase implementada. Pero si define un método estático en una interfaz, puede acceder directamente sin su implementación. Por lo tanto, los métodos estáticos no están permitidos en las interfaces. No creo que la coherencia sea una preocupación.

El método estático de la interfaz Java 1.8 solo es visible para los métodos de interfaz, si eliminamos el método methodSta1 () de la clase InterfaceExample, no podremos usarlo para el objeto InterfaceExample. Sin embargo, al igual que otros métodos estáticos, podemos usar métodos estáticos de interfaz usando el nombre de clase. Por ejemplo, una statement válida será: exp1.methodSta1 ();

Entonces, después de mirar debajo del ejemplo, podemos decir: 1) El método estático de la interfaz Java es parte de la interfaz, no podemos usarlo para los objetos de la clase de implementación.

2) Los métodos estáticos de la interfaz Java son buenos para proporcionar métodos de utilidad, por ejemplo, verificación nula, clasificación de colecciones, registro, etc.

3) El método estático de la interfaz Java nos ayuda a proporcionar seguridad al no permitir que las clases de implementación (InterfaceExample) los anulen.

4) No podemos definir el método estático de la interfaz para los métodos de la clase Object, obtendremos el error del comstackdor como “Este método estático no puede ocultar el método de instancia desde Object”. Esto se debe a que no está permitido en Java, ya que Object es la clase base para todas las clases y no podemos tener un método estático de nivel de clase y otro método de instancia con la misma firma.

5) Podemos usar métodos estáticos de la interfaz de Java para eliminar clases de utilidad como Colecciones y mover todos sus métodos estáticos a la interfaz correspondiente, que sería fácil de encontrar y usar.

 public class InterfaceExample implements exp1 { @Override public void method() { System.out.println("From method()"); } public static void main(String[] args) { new InterfaceExample().method2(); InterfaceExample.methodSta2(); // < --------------------------- would not compile // methodSta1(); // <--------------------------- would not compile exp1.methodSta1(); } static void methodSta2() { // <-- it compile successfully but it can't be overridden in child classes System.out.println("========= InterfaceExample :: from methodSta2() ======"); } } interface exp1 { void method(); //protected void method1(); // <-- error //private void method2(); // <-- error //static void methodSta1(); // <-- error it require body in java 1.8 static void methodSta1() { // <-- it compile successfully but it can't be overridden in child classes System.out.println("========= exp1:: from methodSta1() ======"); } static void methodSta2() { // <-- it compile successfully but it can't be overridden in child classes System.out.println("========= exp1:: from methodSta2() ======"); } default void method2() { System.out.println("--- exp1:: from method2() ---");} //synchronized default void method3() { System.out.println("---");} // <-- Illegal modifier for the interface method method3; only public, abstract, default, static // and strictfp are permitted //final default void method3() { System.out.println("---");} // <-- error }