¿Los métodos predeterminados de Java 8 rompen la compatibilidad de fuentes?


Generalmente ha sido el caso de que el código fuente de Java haya sido compatible con forward. Hasta Java 8, que yo sepa, las clases compiladas y source han sido compatibles con versiones posteriores de JDK/JVM. [Actualización: esto no es correcto, ver comentarios re 'enum', etc, a continuación.] Sin embargo, con la adición de métodos predeterminados en Java 8, esto ya no parece ser el caso.

Por ejemplo, una biblioteca que he estado usando tiene una implementación de java.util.List que incluye un List<V> sort(). Este método devuelve una copia del contenido de la lista ordenada. Esta biblioteca, implementada como una dependencia de archivos jar, funcionó bien en un proyecto que se estaba construyendo utilizando JDK 1.8.

Sin embargo, más tarde tuve ocasión de recompilar la propia biblioteca usando JDK 1.8 y Encontré que la biblioteca ya no compila: la clase List-implementing con su propio método sort() ahora entra en conflicto con el método predeterminado de Java 8 java.util.List.sort(). El método predeterminado Java 8 sort() ordena la lista en su lugar( devuelve void); sort() el método - ya que devuelve una nueva lista ordenada - tiene una firma incompatible.

Así que mi pregunta básica es:

  • ¿JDK 1.8 no introduce una incompatibilidad hacia adelante para el código fuente de Java debido a los métodos predeterminados?

También:

  • ¿Es este el primer cambio forward incompatible?
  • ¿Se consideró o discutió esto cuando se diseñaron e implementaron los métodos por defecto? ¿Está documentado en alguna parte?
  • Fue el (ciertamente pequeño) inconvenientes descontados frente a los beneficios?

El siguiente es un ejemplo de un código que compila y se ejecuta bajo 1.7 y se ejecuta bajo 1.8 - pero no compila bajo 1.8:

import java.util.*;

public final class Sort8 {

    public static void main(String[] args) {
        SortableList<String> l = new SortableList<String>(Arrays.asList(args));
        System.out.println("unsorted: "+l);
        SortableList<String> s = l.sort(Collections.reverseOrder());
        System.out.println("sorted  : "+s);
    }

    public static class SortableList<V> extends ArrayList<V> {

        public SortableList() { super(); }
        public SortableList(Collection<? extends V> col) { super(col); }

        public SortableList<V> sort(Comparator<? super V> cmp) {
            SortableList<V> l = new SortableList<V>();
            l.addAll(this);
            Collections.sort(l, cmp);
            return l;
        }

    }

}

Lo siguiente muestra este código siendo compilado (o fallando) y siendo ejecutado.

> c:\tools\jdk1.7.0_10\bin\javac Sort8.java

> c:\tools\jdk1.7.0_10\bin\java Sort8 this is a test
unsorted: [this, is, a, test]
sorted  : [this, test, is, a]

> c:\tools\jdk1.8.0_05\bin\java Sort8 this is a test
unsorted: [this, is, a, test]
sorted  : [this, test, is, a]

> del Sort8*.class

> c:\tools\jdk1.8.0_05\bin\javac Sort8.java
Sort8.java:46: error: sort(Comparator<? super V>) in SortableList cannot implement sort(Comparator<? super E>) in List
                public SortableList<V> sort(Comparator<? super V> cmp) {
                                       ^
  return type SortableList<V> is not compatible with void
  where V,E are type-variables:
    V extends Object declared in class SortableList
    E extends Object declared in interface List
1 error
Author: Paul, 2015-07-02

5 answers

¿JDK 1.8 no introduce una incompatibilidad hacia adelante para el código fuente de Java debido a los métodos predeterminados?

Cualquier método nuevo en una superclase o interfaz puede romper la compatibilidad. Los métodos predeterminados hacen que sea menos probable que un cambio en una interfaz rompa la compatibilidad. En el sentido de que los métodos predeterminados abren la puerta a la adición de métodos a las interfaces, se podría decir que los métodos predeterminados pueden contribuir a una compatibilidad rota.

Es este el primer cambio incompatible hacia adelante?

Es casi seguro que no, ya que hemos estado subclasificando clases de la biblioteca estándar desde Java 1.0.

¿Se tuvo esto en cuenta o se discutió cuando se diseñaron e implementaron los métodos por defecto? ¿Está documentado en alguna parte?

Sí, se consideró. Véase el documento de Brian Goetz de agosto de 2010 "Evolución de la interfaz a través de métodos de "defensor público" ":

  1. Fuente compatibilidad

Es posible que este esquema pueda introducir incompatibilidades de código fuente en la medida en que las interfaces de biblioteca se modifiquen para insertar nuevos métodos que sean incompatibles con los métodos de las clases existentes. (Por ejemplo, si una clase tiene un método xyz() con valor flotante e implementa Collection, y agregamos un método xyz() con valor int a Collection, la clase existente ya no compilará.)

Fue el (sin duda pequeño) inconveniente descontado ¿contra los beneficios?

Antes, cambiar una interfaz definitivamente rompería la compatibilidad. Ahora, podría . Ir de 'definitivamente' a 'podría' puede verse positiva o negativamente. Por un lado, permite añadir métodos a las interfaces. Por otro lado, abre la puerta al tipo de incompatibilidad que viste, no solo con las clases, sino también con las interfaces.

Los beneficios son mayores que los inconvenientes, sin embargo, como se cita en la parte superior del documento de Goetz:

  1. Declaración del problema

Una vez publicado, es imposible agregar métodos a una interfaz sin romper las implementaciones existentes. Cuanto más tiempo haya transcurrido desde que se publicó una biblioteca, más probable será que esta restricción cause dolor a sus mantenedores.

La adición de cierres al lenguaje Java en JDK 7 pone un énfasis adicional en las interfaces de colección obsoletas; una de las más significativas los beneficios de los cierres es que permite el desarrollo de bibliotecas más potentes. Sería decepcionante agregar una función de idioma que permita mejores bibliotecas y, al mismo tiempo, no extender las bibliotecas principales para aprovechar esa función.

 57
Author: Andy Thomas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-02 15:38:22

¿JDK 1.8 no introduce una incompatibilidad hacia adelante para el código fuente de Java debido a los métodos predeterminados?

Sí, como has visto a ti mismo.

¿Es este el primer cambio forward incompatible?

No. La palabra clave Java 5 enumtambién se estaba rompiendo porque antes de eso podrías tener variables nombradas que ya no compilarían en Java 5 +

¿Se consideró o discutió esto cuando se diseñaron e implementaron los métodos por defecto? Es ¿documentado en alguna parte?

Yes Orcale Java 8 source incompatibility description

¿Se descontaron los inconvenientes (ciertamente pequeños) frente a los beneficios?

 9
Author: dkatzel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-02 15:14:32

Podemos trazar un paralelo con la clase abstracta. Una clase abstracta está destinada a ser subclase para que los métodos abstractos puedan ser implementados. La clase abstracta en sí contiene métodos concretos que invocan los métodos abstractos. La clase abstracta es libre de evolucionar agregando métodos más concretos; y esta práctica puede romper subclases.

Por lo tanto, el problema exacto que describiste existía incluso antes de Java8. El problema se manifiesta mucho más en las API de colección porque hay un muchas subclases en la naturaleza.

Mientras que la motivación principal del método predeterminado era agregar algunos métodos útiles a las API de colección existentes sin romper subclases, tenían que ejercer un gran autocontrol de hacerlo demasiado, por miedo a romper subclases. Un método predeterminado se agrega solo si es absolutamente necesario. La verdadera pregunta aquí es, por qué List.sort se considera absolutamente necesario. Creo que eso es discutible.

Independientemente de por qué se introdujo el método predeterminado en primer lugar, ahora es una gran herramienta para los diseñadores de API, y debemos tratarlo de la misma manera que los métodos concretos en clases abstractas: deben diseñarse cuidadosamente por adelantado; y los nuevos deben introducirse con gran precaución.

 3
Author: ZhongYu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-02 17:10:20

Irónicamente, se introdujeron métodos predeterminados en las interfaces para permitir que las bibliotecas existentes que utilizan esas interfaces no se rompan, al tiempo que se introduce una funcionalidad masiva nueva en las interfaces. (compatibilidad con versiones anteriores.)

Conflictos como ese método sort podrían surgir. Algo para pagar por la funcionalidad adicional. En su caso también algo para investigar (¿debería usarse una nueva funcionalidad en su lugar?).

Saltos de compatibilidad hacia adelante Java son poco, más en su sistema de mecanografía, que se ampliaba constantemente. Primero con tipos genéricos y ahora con tipos inferidos de interfaces funcionales. De una versión a otra y de un compilador a otro hubo ligeras diferencias.

 2
Author: Joop Eggen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-02 15:32:16

Leyendo este tema, estaba pensando en su solución.
Los métodos predeterminados han resuelto los problemas de compatibilidad hacia atrás, pero existirán problemas de compatibilidad hacia adelante.
Creo que en lugar de extender las clases existentes, en tales casos, podemos tener nuestras interfaces específicas de aplicación para agregar algún comportamiento deseado a nuestra clase. Podemos implementar esta interfaz específica de la aplicación y usarla.

 0
Author: pfulara,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-19 07:39:38