¿Cómo soporta Java la programación funcional?

antecedentes

Durante mucho tiempo, Java ha sido un lenguaje orientado a objetos, todo es un objeto, si quieres llamar a una función, la función debe pertenecer a una clase u objeto, y luego usar la clase u objeto para llamar. Pero en otros lenguajes de programación, como JS y C ++, podemos escribir una función directamente y luego llamarla cuando sea necesario. Se puede decir que es programación orientada a objetos o programación funcional. Desde un punto de vista funcional, no hay nada malo en la programación orientada a objetos, pero desde el punto de vista del desarrollo, la programación orientada a objetos escribirá muchas líneas de código que pueden repetirse. Por ejemplo, al crear una clase anónima de Runnable:

Runnable runnable = new Runnable () {  
    @Override  
    public void run () {  
        System.out.println ("hacer algo ..."); 
    }  
}; 

La única parte útil de este código es el contenido del método de ejecución. La parte restante es una parte estructural del lenguaje de programación Java, que es inútil, pero debe escribirse. Afortunadamente, desde Java 8, se han introducido interfaces de programación funcional y expresiones Lambda para ayudarnos a escribir código cada vez más elegante:

// 一行 即可 Ejecutable  
runnable = () -> System.out.println ("hacer algo ..."); 

Hay principalmente tres paradigmas de programación principales: programación orientada a procesos, orientada a objetos y programación funcional.

La programación funcional no es algo muy nuevo, apareció hace más de 50 años. En los últimos años, la programación funcional ha atraído cada vez más atención, y han surgido muchos nuevos lenguajes de programación funcional, como Clojure, Scala, Erlang, etc. Algunos lenguajes de programación no funcionales también han agregado muchas características, sintaxis y bibliotecas de clases para admitir la programación funcional, como Java, Python, Ruby, JavaScript, etc. Además, Google Guava también tiene mejoras en la programación funcional.

Debido a la particularidad de la programación, la programación funcional solo puede aprovechar sus ventajas en la computación científica, el procesamiento de datos, el análisis estadístico y otros campos, por lo que no puede reemplazar completamente el paradigma de programación orientado a objetos más general. Pero como complemento, también tiene una gran importancia para la existencia, el desarrollo y el aprendizaje.

Que es la programación funcional

La traducción al inglés de programación funcional es Programación funcional.

Entonces, ¿qué es exactamente la programación funcional? En realidad, no existe una definición oficial estricta de programación funcional. Estrictamente hablando, la "función" en la programación funcional no se refiere al concepto de "función" en nuestro lenguaje de programación, sino a la "función" o "expresión" exponencial (por ejemplo: y = f (x)). Sin embargo, al programar, para "funciones" o "expresiones" matemáticas, generalmente las diseñamos como funciones. Por tanto, si no se profundiza en ella, la "función" en la programación funcional también puede entenderse como la "función" en un lenguaje de programación.

Cada paradigma de programación tiene su propio lugar único, por lo que se abstraen como paradigma. La característica más importante de la programación orientada a objetos es que toma clases y objetos como la unidad de organización del código y sus cuatro características. La característica más importante de la programación orientada a procesos es que las funciones se utilizan como la unidad de organización del código y los datos y los métodos están separados. ¿Cuál es la parte más singular de la programación funcional? De hecho, la parte más singular de la programación funcional radica en sus ideas de programación. La programación funcional cree que un programa se puede representar mediante una combinación de una serie de funciones o expresiones matemáticas. La programación funcional es una abstracción de programación matemática de nivel inferior, que describe el proceso de cálculo como una expresión. Sin embargo, definitivamente tendrá preguntas de esta manera: ¿Se puede expresar cualquier programa como un conjunto de expresiones matemáticas?

En teoría, es posible. Sin embargo, no todos los programas son adecuados para esto. La programación funcional tiene sus propios escenarios de aplicación adecuados, como computación científica, procesamiento de datos, análisis estadístico, etc. En estas áreas, los programas suelen ser más fáciles de expresar en expresiones matemáticas En comparación con la programación no funcional, que logra la misma función, la programación funcional se puede hacer con muy poco código. Sin embargo, para el desarrollo de un sistema comercial a gran escala relacionado con negocios sólidos, es difícil abstraerlo en una expresión matemática, y usar la programación funcional para lograrlo obviamente está buscando problemas. Por el contrario, en este escenario de aplicación, la programación orientada a objetos es más apropiada y el código escrito es más legible y fácil de mantener.

Cuando se trata de programación, la programación funcional, como la programación orientada a procesos, utiliza funciones como una unidad de organización del código. Sin embargo, se diferencia de la programación procedimental en que sus funciones no tienen estado. En pocas palabras, las variables involucradas en la función son todas variables locales. No compartirán variables miembro de clase como la programación orientada a objetos, ni compartirán variables globales como la programación orientada a procesos. El resultado de la ejecución de la función solo está relacionado con los parámetros de entrada y no tiene nada que ver con otras variables externas. La misma entrada, no importa cómo la ejecutes, el resultado es el mismo. Este es en realidad el requisito básico de las funciones matemáticas o expresiones matemáticas. por ejemplo:

// Función con estado: el resultado de la ejecución depende del valor de b. Incluso si los parámetros de entrada son los mismos, 
// el valor de retorno de la función puede ser diferente si la  función se ejecuta varias veces, porque el valor de b puede ser diferente. 
int b;  
int aumento (int a) {  
  return a + b;  
 
// Función sin estado: el resultado de la ejecución no depende del valor de ninguna variable externa  
// Mientras los parámetros de entrada sean los mismos, no importa cuántas veces se ejecute, el valor de retorno de la función será el mismo  
int aumento (int a, int b) {  
  return a + b;  

Los diferentes paradigmas de programación no son completamente diferentes, siempre hay algunas reglas de programación comunes. Por ejemplo, ya sea programación orientada a procesos, orientada a objetos o funcional, todas tienen el concepto de variables y funciones, el nivel superior debe tener la entrada de ejecución de la función principal para ensamblar unidades de programación (clases, funciones, etc.). Sin embargo, la unidad de programación orientada a objetos es una clase u objeto, la unidad de programación orientada a procesos es una función y la unidad de programación de programación funcional es una función sin estado.

Soporte de Java para programación funcional

La programación orientada a objetos no necesariamente tiene que usar lenguajes de programación orientados a objetos, de manera similar, la programación funcional no necesariamente tiene que usar lenguajes de programación funcionales. Hoy en día, muchos lenguajes de programación orientados a objetos también proporcionan la sintaxis correspondiente y las bibliotecas de clases para soportar la programación funcional.

Java, un lenguaje de programación orientado a objetos, admite la programación funcional a través de un ejemplo:

Demostración de clase pública {  
  public static void main (String [] args) {  
    <Integer> opcional resultado = Stream.of ("a", "be", "hola")  
            .map (s -> s.length ())  
            . filtro (l -> l <= 3)  
            .max ((o1, o2) -> o1-o2); 
    System.out.println (result.get ()); // 输出 2  
  }  

La función de este código es filtrar las cadenas con una longitud menor o igual a 3 de un conjunto de matrices de cadenas y encontrar la longitud máxima entre ellas.

Java presenta tres nuevos conceptos gramaticales para la programación funcional: clase Stream, expresión Lambda e interfaz funcional (Functional Inteface). La clase Stream se utiliza para admitir el método de escritura de código de las operaciones de múltiples funciones en cascada mediante "."; La función de introducir expresiones Lambda es simplificar la escritura de código; la función de la interfaz de función es permitirnos envolver la función en una interfaz de función para realizar la función Úselo como parámetro (Java no admite punteros de función como C, puede usar funciones directamente como parámetros).

Clase de corriente

Supongamos que queremos calcular dicha expresión: (3-1) * 2 + 5. Si está escrito en la forma de llamadas de función ordinarias, se ve así:

sumar (multiplicar (restar (3,1), 2), 5); 

Sin embargo, escribir un código como este parece más difícil de entender. Cambiemos a una forma de escritura más legible, como se muestra a continuación:

restar (3,1) .multiplicar (2) .add (5); 

En Java, "." Significa llamar a un método de un objeto. Para admitir el método de llamada en cascada anterior, permitimos que cada función devuelva un objeto de clase Stream genérico. Hay dos tipos de operaciones en la clase Stream: operaciones intermedias y operaciones de terminación. La operación intermedia devuelve un objeto de clase Stream y la operación de terminación devuelve un resultado de valor definido.

Veamos de nuevo el ejemplo anterior y expliquemos el código. Entre ellos, el mapa y el filtro son operaciones intermedias, que devuelven objetos de clase Stream, y puede continuar conectando otras operaciones en cascada; max es una operación de terminación, y lo que se devuelve no es un objeto de clase Stream y ya no puede continuar descendiendo en cascada.

public class Demo {  
  public static void main (String [] args) {  
    Opcional <Integer> result = Stream.of ("f", "ba", "hello") // de devoluciones Stream <String>  
            object.map (s -> s.length ()) // el mapa devuelve el objeto Stream <Integer>.  
            filter (l -> l <= 3) // el filtro devuelve el objeto Stream <Integer>.  
            max ((o1, o2) -> o1-o2 ); // operación de terminación máxima: return Opcional <Integer>  
    System.out.println (result.get ()); // salida 2  
  }  

Expresión lambda

Como se mencionó anteriormente, la función principal de la introducción de expresiones Lambda en Java es simplificar la escritura de código. De hecho, también podemos escribir el código en el ejemplo sin expresiones Lambda. Tomemos la función de mapa como ejemplo.

Los siguientes tres fragmentos de código, el primer fragmento de código muestra la definición de la función de mapa. De hecho, el parámetro que recibe la función de mapa es una interfaz de función, que también es una interfaz de función. El segundo código muestra cómo utilizar la función de mapa. El tercer fragmento de código es una forma simplificada de escribir el segundo fragmento de código con expresiones Lambda. De hecho, la expresión Lambda es solo un azúcar sintáctico en Java. La capa inferior se implementa en base a una interfaz funcional, que es la forma en que se muestra el segundo código.

// La definición de la función de mapa en la clase Stream:  
interfaz pública Stream <T> extiende BaseStream <T, Stream <T>> {  
  <R> Stream <R> map (Función <? Super T ,? Extiende R> mapper);  
  / / ... Omitir otras funciones ...  
 
// Ejemplos de cómo usar map en la clase Stream:  
Stream.of ("fo", "bar", "hello"). Map (new Function <String, Integer> () {  
  @Override  
  public Integer apply (String s) {  
    return s.length ();  
  }  
});  
 
// Escritura simplificada con expresión Lambda:  
Stream.of ("fo", "bar", "hello"). mapa (s -> s.length ()); 

Las expresiones lambda incluyen tres partes: entrada, cuerpo de función y salida. La expresión es la siguiente:

(a, b) -> {declaración 1; declaración 2; ...; retorno de salida;} // a, b son parámetros de entrada 

De hecho, las expresiones Lambda son muy flexibles. La anterior es la notación estándar y hay muchas notaciones simplificadas. Por ejemplo, si solo hay un parámetro de entrada, puede omitir () y escribirlo directamente como -> {...}; si no hay un parámetro de entrada, puede omitir directamente tanto la entrada como la flecha, y solo conservar el cuerpo de la función; si el cuerpo de la función tiene solo una declaración, entonces Puede omitir {}; si la función no devuelve un valor, la declaración de retorno se puede omitir.

<Integer> opcional result = Stream.of ("f", "ba", "hello")  
        .map (s -> s.length ())  
        .filter (l -> l <= 3)  
        .max ((o1 , o2) -> o1-o2); 
         
// 还原 为 函数 接口 的 实现 方式  
Opcional <Integer> result2 = Stream.of ("fo", "bar", "hello")  
        .map (new Function <String, Integer> () {  
          @Override  
          public Integer apply ( String s) {  
            return s.length ();  
          }  
        })  
        .filter (new Predicate <Integer> () {  
          @Override  
          public boolean test (Integer l) {  
            return l <= 3; 
          public int compare (Integer o1, Integer o2) {  
            return o1 - o2; 
          }  
        }); 

Las similitudes y diferencias entre las expresiones Lambda y las clases anónimas se concentran en los siguientes tres puntos:

  • Lambda nació para optimizar las clases internas anónimas. Lambda es mucho más conciso que las clases anónimas.
  • Lambda solo se aplica a interfaces funcionales y las clases anónimas no están restringidas.
  • Es decir, esto en la clase anónima es el "objeto de clase anónimo" en sí mismo; esto en la expresión Lambda se refiere al "objeto que llama a la expresión Lambda".

Interfaz de función

De hecho, la función, el predicado y el comparador en el código anterior son todas interfaces funcionales. Sabemos que el lenguaje C admite punteros de función, que pueden usar funciones directamente como variables.

Sin embargo, Java no tiene una sintaxis para punteros de función. Entonces envuelve la función en la interfaz a través de la interfaz de función y la usa como una variable. De hecho, una interfaz funcional es una interfaz. Sin embargo, también tiene su propio lugar especial, es decir, solo requiere un método no implementado. Porque solo de esta manera, la expresión Lambda puede saber claramente qué método coincide. Si hay dos métodos no implementados y los parámetros de entrada y los valores de retorno de la interfaz son los mismos, cuando Java traduce la expresión Lambda, no sabe a qué método corresponde la expresión.

La interfaz funcional también es una especie de interfaz Java, pero también debe cumplir:

  • Una interfaz funcional tiene solo un método abstracto (método abstracto único);
  • El método abstracto público de la clase Object no se considerará un método abstracto único;
  • Las interfaces funcionales pueden tener métodos predeterminados y métodos estáticos;
  • Las interfaces funcionales se pueden decorar con anotaciones @FunctionalInterface.

Una interfaz que cumple estas condiciones puede considerarse una interfaz funcional. Por ejemplo, la interfaz Comparator en Java 8:

@FunctionalInterface  
public interface Comparator <T> {  
    / **  
     * método abstracto único  
     * @since 1.8  
     * /  
    int compare (T o1, T o2); 
 
    / **  
     * Object 类 中 的 método abstracto público   
     * @since 1.8  
     * /  
    boolean equals (Object obj); 
 
    / **  
     * 默认 方法  
     * @since 1.8  
     * /  
    default Comparator <T> reversed () {  
        return Collections.reverseOrder (this); 
    }  
 
     
    / **  
     * 静态 方法  
     * @ desde 1.8  
     * /   
    public static <T se extiende Comparable <? super T >> Comparador <T> reverseOrder () { 
        return Collections.reverseOrder (); 
    } 
 
    // omitido ...  

¿Cuál es el uso de interfaces funcionales? En una palabra, la mayor ventaja de las interfaces funcionales es que podemos usar expresiones lambda mínimas para instanciar interfaces. ¿Por qué dices eso? Tenemos interfaces más o menos utilizadas con un solo método abstracto, como Runnable, ActionListener, Comparator, etc. Por ejemplo, necesitamos usar Comparator para implementar algoritmos de ordenación. Nuestros métodos de procesamiento generalmente no son más que dos:

  • Escriba una clase Java que implemente la interfaz Comparator correctamente para encapsular la lógica de clasificación. Si la empresa requiere varios métodos de clasificación, debe escribir varias clases para proporcionar varias implementaciones, y estas implementaciones a menudo solo deben usarse una vez.
  • Otro enfoque más inteligente no es más que crear clases internas anónimas donde sea necesario, como:
Prueba de clase pública {   
    público estático vacío principal (String args []) {   
        Lista <Persona> personas = new ArrayList <Persona> (); 
        Colecciones.ordenar (personas, nuevo Comparador <Persona> () {  
            @Override  
            public int comparar (Persona o1, Persona o2) {  
                return Integer.compareTo (o1.getAge (), o2.getAge ());  
            }  
        }); 
    }   

La cantidad de código implementado por clases internas anónimas no es mucha y la estructura es bastante clara. La implementación de la interfaz Comparator en Jdk 1.8 agrega la anotación FunctionalInterface, lo que significa que Comparator es una interfaz funcional y los usuarios pueden instanciarla de forma segura a través de expresiones lambda. Luego, echemos un vistazo al código que debe escribirse para usar expresiones lambda para crear rápidamente un comparador personalizado:

Comparador <Persona> comparador = (p1, p2) -> Integer.compareTo (p1.getAge (), p2.getAge ()); 

-> El frente () es la lista de parámetros del método de comparación en el método de comparación, y el reverso -> es el cuerpo del método del método de comparación.

El siguiente es un extracto del código fuente de las dos interfaces de función proporcionadas por Java: Función y Predicado:

@FunctionalInterface  
interfaz pública Función <T, R> {  
    R aplicar (T t); // 只有 这 一个 未 实现 的 方法 por  
 
    defecto <V> Función <V, R> compose (Función <? Super V 
        ,? Extiende T> antes) {  Objects.requireNonNull (antes); 
        return (V v) -> aplicar (antes de aplicar (v)); 
    } función  
 
    <V> predeterminada <T, V> y luego (Función <? super R 
        ,? extiende V> después) {  Objects.requireNonNull (después); 
        return (T t) -> after.apply (aplicar (t)); 
    }  
 
    static <T> Función <T, T> identity () {  
        return t -> t; 
    }  
 
@FunctionalInterface  
interfaz pública Predicate <T> 
 
    Predicate <T> predeterminado y (Predicate <? super T> otro) {  
        Objects.requireNonNull (otro); 
        return (t) -> prueba (t) && otra.prueba (t); 
    }  
 
    Predicate predeterminado <T> negate () {  
        return (t) ->! test (t); 
    }  
 
    Predicate <T> predeterminado o (Predicate <? super T> otro) {  
        Objects.requireNonNull (otro); 
        return (t) -> prueba (t) || otra.prueba (t); 
    }  
 
    static <T> Predicate <T> isEqual (Object targetRef) {  
        return (null == targetRef)  
                ? Objects :: isNull  
                : object -> targetRef.equals (objeto); 
    }  

Escenarios de uso de anotaciones @FunctionalInterface

Sabemos que siempre que una interfaz cumpla con la condición de un solo método abstracto, se puede usar como una interfaz funcional, no importa si tiene @FunctionalInterface o no. Sin embargo, debe haber una razón por la cual jdk define esta anotación. Para los desarrolladores, el uso de esta anotación debe pensarse dos veces antes de continuar.

Si se usa esta anotación y se agrega un método abstracto a la interfaz, el compilador informará un error y la compilación fallará. En otras palabras, @FunctionalInterface es una promesa de que solo este método abstracto existirá en la interfaz durante generaciones. Por lo tanto, los desarrolladores pueden usar Lambda para crear una instancia de cualquier interfaz que use esta anotación. Por supuesto, las consecuencias de hacer un mal uso de @FunctionalInterface son extremadamente desastrosas: si un día elimina esta anotación y agrega un método abstracto, todo el código de cliente que usa Lambda para crear una instancia de la interfaz se compilará incorrectamente.

En particular, cuando una interfaz tiene solo un método abstracto, pero no está decorada con la anotación @FunctionalInterface, significa que otros no han prometido que la interfaz no agregará métodos abstractos en el futuro, por lo que se recomienda no usar Lambda para instanciar, o usar honestamente el anterior. El método es relativamente seguro.

resumen

La programación funcional está más en línea con la idea del mapeo de funciones matemáticas. Específicamente para el nivel del lenguaje de programación, podemos usar expresiones Lambda para escribir rápidamente asignaciones de funciones, y las funciones se conectan a través de llamadas en cadena para completar la lógica empresarial requerida. Las expresiones lambda en Java se introdujeron más tarde y, debido a las ventajas de la programación funcional en el procesamiento en paralelo, se están utilizando ampliamente en el campo de la computación de big data.

Supongo que te gusta

Origin blog.csdn.net/sinat_37903468/article/details/108735418
Recomendado
Clasificación