diff --git a/README.md b/README.md index e5de94e..fe4ad5f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ +# Fuente del commit inicial +https://github.com/sierisimo/JavaSE-Functional-platzi + +# Link del curso platzi +https://platzi.com/new-home/clases/1826-java-funcional/26226-inmutabilidad/ + # JavaSE-Functional-platzi Codigo de ejemplos para el curso sobre programacion funcional de Platzi diff --git a/build.gradle b/build.gradle index 6a4e841..f6c26eb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,38 @@ allprojects { apply plugin: 'java' - sourceCompatibility = 1.8 -} \ No newline at end of file + java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + + repositories { + mavenCentral() + } + + dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.3' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.3' + } +} + +project(':jobs-search-reporter') { + apply plugin: 'application' + + group = 'com.platzi' + version = '0.0.1' + + application { + mainClass = 'com.platzi.jobsearch.JobSearch' + applicationName = 'job-search' + } + + dependencies { + implementation 'com.beust:jcommander:1.82' + implementation 'io.github.openfeign:feign-core:13.3' + implementation 'io.github.openfeign:feign-gson:13.3' + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 87b738c..d64cd49 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b6b20bb..1af9e09 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Thu Dec 12 15:29:14 CST 2019 -distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index af6708f..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -1,78 +1,127 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,92 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" fi +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 0f8d593..6689b85 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,4 +1,20 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -9,19 +25,23 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,38 +65,26 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/jobs-search-reporter/README.md b/jobs-search-reporter/README.md new file mode 100644 index 0000000..c571621 --- /dev/null +++ b/jobs-search-reporter/README.md @@ -0,0 +1,33 @@ +Jobs Search Reporter + +Un pequeño CLI para buscar trabajo con Github + +--- + +## Descripcion + +Nuestro pequeño CLI utilizara [la API de Github](https://jobs.github.com/) para buscar empleos. +Para que nuestro CLI pueda funcionar debemos pasar algunas opciones/parametros de busqueda. + +La manera de invocar a nuestro CLI sera: + +``` +jobs-search [OPTIONS] +``` + +Donde `[OPTIONS]` son: + +``` +--location # : Algun lugar del mundo, tambien disponible como: -l +--page # : Los resultados se muestran de 50 en 50, cada 50 resultados se le conoce como pagina empezando en 0. Default: 0. Tambien disponible como: -p +--full-time # Si queremos que sean solo empleos de tiempo completo. default: false +--markdown # Mostrar los resultados en Markdown +``` + +Y `` es el tipo de skill del que queremos encontrar trabajos. + +### Ejemplo + +Para buscar trabajos de `Java` en Tokyo, usariamos: + +`jobs-search --location tokyo java` \ No newline at end of file diff --git a/jobs-search-reporter/src/main/java/com/platzi/jobsearch/CommanderFunctions.java b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/CommanderFunctions.java new file mode 100644 index 0000000..fa5422a --- /dev/null +++ b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/CommanderFunctions.java @@ -0,0 +1,52 @@ +package com.platzi.jobsearch; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.ParameterException; + +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +public interface CommanderFunctions { + /** + * Con esta funcion, facilitamos crear una configuracion inicial de JCommander, pidiendo el nombre del + * programa y un Supplier de tipo T para los argumentos. Asi podemos usar alguna funcion que nos devuelva + * un objeto que funcione como argumentos de JCommander. + * + * @param name nombre que se mostrara en el CLI + * @param argumentsSupplier una funcion que devuelva un objeto de argumentos de JCommander + * @param Tipo que se usara para los argumentos + * @return una instancia de {@link JCommander} ya configurada con el nombre y los argumentos. + */ + static JCommander buildCommanderWithName(String name, Supplier argumentsSupplier) { + // JCommander permite generar opciones de terminal de cualquier clase, por eso se le agrega un object con el metodo addObject(...) + // argumentsSupplier.get() es la clase de la cual se generaran los argumentos de JCommander + // Queda definida una instancia de JCommander. Idealmente con CLIArguments como objeto pasado. + JCommander jCommander = JCommander.newBuilder().addObject(argumentsSupplier.get()).build(); + + jCommander.setProgramName(name); + return jCommander; + } + + /** + * Funcion utilizada para tomar los datos de JCommander, los argumentos esperados y en caso de que algo falle, + * una funcion con el JCommander que genero el error. + */ + static Optional> parseArguments( + JCommander jCommander, + String[] arguments, + OnCommandError onCommandError) { // en caso que las validaciones de CLIKeywordValidaror o CLIHelpValidator fallen, se generará un ParameterException + try { + jCommander.parse(arguments); + return Optional.of(jCommander.getObjects()); + } catch (ParameterException exception) { // atrapará el parameter exception y devuelve un optional vacio + onCommandError.onError(jCommander); + return Optional.empty(); + } + } + + @FunctionalInterface + interface OnCommandError { + void onError(JCommander jCommander); + } +} diff --git a/jobs-search-reporter/src/main/java/com/platzi/jobsearch/JobPosition.java b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/JobPosition.java new file mode 100644 index 0000000..3b3b49d --- /dev/null +++ b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/JobPosition.java @@ -0,0 +1,164 @@ +package com.platzi.jobsearch; + +import com.google.gson.annotations.SerializedName; + +import java.util.Objects; + +/** + * Clase que representa los resultados de una busqueda + */ +public final class JobPosition { + private String id; + + private String type; + + private String ulr; + + @SerializedName("created_at") + private String createdAt; + + private String company; + + @SerializedName("company_url") + private String companyUrl; + + private String location; + + private String title; + + private String description; + + @SerializedName("how_to_apply") + private String howToApply; + + @SerializedName("company_logo") + private String companyLogo; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getUlr() { + return ulr; + } + + public void setUlr(String ulr) { + this.ulr = ulr; + } + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public String getCompany() { + return company; + } + + public void setCompany(String company) { + this.company = company; + } + + public String getCompanyUrl() { + return companyUrl; + } + + public void setCompanyUrl(String companyUrl) { + this.companyUrl = companyUrl; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getHowToApply() { + return howToApply; + } + + public void setHowToApply(String howToApply) { + this.howToApply = howToApply; + } + + public String getCompanyLogo() { + return companyLogo; + } + + public void setCompanyLogo(String companyLogo) { + this.companyLogo = companyLogo; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + JobPosition that = (JobPosition) o; + return getId().equals(that.getId()) && + getType().equals(that.getType()) && + getUlr().equals(that.getUlr()) && + getCreatedAt().equals(that.getCreatedAt()) && + getCompany().equals(that.getCompany()) && + Objects.equals(getCompanyUrl(), that.getCompanyUrl()) && + Objects.equals(getLocation(), that.getLocation()) && + getTitle().equals(that.getTitle()) && + Objects.equals(getDescription(), that.getDescription()) && + Objects.equals(getHowToApply(), that.getHowToApply()) && + Objects.equals(getCompanyLogo(), that.getCompanyLogo()); + } + + @Override + public int hashCode() { + return Objects.hash(getId(), getType(), getUlr(), getCreatedAt(), getCompany(), getCompanyUrl(), getLocation(), getTitle(), getDescription(), getHowToApply(), getCompanyLogo()); + } + + @Override + public String toString() { + return "JobPosition{" + + "id='" + id + '\'' + + ", type='" + type + '\'' + + ", ulr='" + ulr + '\'' + + ", createdAt='" + createdAt + '\'' + + ", company='" + company + '\'' + + ", companyUrl='" + companyUrl + '\'' + + ", location='" + location + '\'' + + ", title='" + title + '\'' + + ", description='" + description + '\'' + + ", howToApply='" + howToApply + '\'' + + ", companyLogo='" + companyLogo + '\'' + + '}'; + } +} diff --git a/jobs-search-reporter/src/main/java/com/platzi/jobsearch/JobSearch.java b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/JobSearch.java new file mode 100644 index 0000000..eef687f --- /dev/null +++ b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/JobSearch.java @@ -0,0 +1,49 @@ +package com.platzi.jobsearch; + +import com.beust.jcommander.JCommander; +import com.platzi.jobsearch.api.JobsAPI; +import com.platzi.jobsearch.cli.CLIArguments; +import com.platzi.jobsearch.cli.CLIFunctions; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import static com.platzi.jobsearch.CommanderFunctions.buildCommanderWithName; +import static com.platzi.jobsearch.CommanderFunctions.parseArguments; +import static com.platzi.jobsearch.api.APIFunctions.buildAPI; + +public class JobSearch { + public static void main(String[] args) { + //Creacion de nuestro CLI con JCommander + JCommander jCommander = buildCommanderWithName("job-search", CLIArguments::newInstance); + + //Obtenemos las opciones que se le dieron a JCommander + Stream streamOfCLI = parseArguments(jCommander, args, JCommander::usage) //Nos retorna un Optional> + .orElse(Collections.emptyList()) //En caso de un Optional.empty() + .stream() + .map(CLIArguments.class::cast); // map(obj -> (CLIArguments) obj) give same result + + //Tomamos nuestro Stream y obtenemos las opciones que se dieron en el CLI + Optional cliOptional = streamOfCLI + .filter(cli -> !cli.isHelp()) //Solo nos interesan los casos donde no sea la solicitud de ayuda + .filter(cli -> cli.getKeyword() != null) //Y que contengan un keyword, en otros caso no tenemos que buscar + .findFirst(); + + //Si el Optional tiene un dato, lo convertimos a Map + cliOptional.map(CLIFunctions::toMap) + .map(JobSearch::executeRequest) //Convertimos el Map en un request + .orElse(Stream.empty()) //Aun seguimos operando sobre un Optional… en caso de que no hubiera datos generamos un stream vacio + .forEach(System.out::println); //Imprimos los datos por pantalla. + } + + private static Stream executeRequest(Map options) { + JobsAPI api = buildAPI(JobsAPI.class, "https://jobs.github.com"); + + return Stream.of(options) + .map(api::jobs) + .flatMap(Collection::stream); + } +} \ No newline at end of file diff --git a/jobs-search-reporter/src/main/java/com/platzi/jobsearch/api/APIFunctions.java b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/api/APIFunctions.java new file mode 100644 index 0000000..54ff234 --- /dev/null +++ b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/api/APIFunctions.java @@ -0,0 +1,22 @@ +package com.platzi.jobsearch.api; + +import feign.Feign; +import feign.gson.GsonDecoder; + +public interface APIFunctions { + /** + * Para construir la llamada fon Feign, necesitamos generar un cliente de Feign, + * esconder que internamente este es el cliente que se usa, nos facilita hacer facil + * el reemplazo de la libreria para http en el futuro. + * + * @param api una Class de tipo T para construir nuestra api + * @param url la URL base donde estaremos haciendo los requests + * @param el tipo de API que contruiremos + * @return una instancia de T para usar como cliente de API + */ + static T buildAPI(Class api, String url) { + return Feign.builder() + .decoder(new GsonDecoder()) + .target(api, url); + } +} diff --git a/jobs-search-reporter/src/main/java/com/platzi/jobsearch/api/JobsAPI.java b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/api/JobsAPI.java new file mode 100644 index 0000000..349ad2b --- /dev/null +++ b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/api/JobsAPI.java @@ -0,0 +1,22 @@ +package com.platzi.jobsearch.api; + +import com.platzi.jobsearch.JobPosition; +import feign.Headers; +import feign.Param; +import feign.QueryMap; +import feign.RequestLine; + +import java.util.List; +import java.util.Map; + +/** + * Esta interfaz sera usada por Feign para hacer las peticiones a la API de github. + */ +@Headers("Accept: application/json") +public interface JobsAPI { + @RequestLine("GET /positions.json") + List jobs(@QueryMap Map queryMap); + + @RequestLine("GET /positions/{id}.json") + JobPosition job(@Param("id") String id); +} diff --git a/jobs-search-reporter/src/main/java/com/platzi/jobsearch/cli/CLIArguments.java b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/cli/CLIArguments.java new file mode 100644 index 0000000..35f4a6a --- /dev/null +++ b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/cli/CLIArguments.java @@ -0,0 +1,96 @@ +package com.platzi.jobsearch.cli; + +import com.beust.jcommander.Parameter; + +public final class CLIArguments { + /** + * Constructor default para permitir que solo clases en el paquete, puedan crear objetos + * de esta clase. + * De esta manera obligamos a que la construccion se haga mediante funciones publicas o + * builders. + *

+ * Las instancias deberan crearse mediante: CLIArguments#newInstance + */ + CLIArguments() { + } + + @Parameter( + required = true, + descriptionKey = "KEYWORD", + description = "KEYWORD", + validateWith = CLIKeywordValidator.class) + private String keyword; + + @Parameter( + names = {"--location", "-l"}, + description = "Ciudad, codigo postal o algun otro termino para buscar una ubicacion") + private String location; + + @Parameter( + names = {"--page", "-p"}, + description = "Cada busqueda contiene 50 posiciones, puedes paginar mas resultados cambiando el numero, la paginacion empieza en 0") + private int page = 0; + + @Parameter( + names = "--full-time", + description = "Agregar esta bandera si se desea unicamente listar trabajos de 'full time'" + ) + private boolean isFullTime = false; + + @Parameter( + names = "--markdown", + description = "Agregar esta bandera si se desea obtener los resultados en markdown" + ) + private boolean isMarkdown = false; + + @Parameter( + names = {"--help", "-h"}, + help = true, + validateWith = CLIHelpValidator.class, + description = "Muestra esta ayuda") + private boolean help; + + public String getKeyword() { + return keyword; + } + + public String getLocation() { + return location; + } + + public int getPage() { + return page; + } + + public boolean isFullTime() { + return isFullTime; + } + + public boolean isMarkdown() { + return isMarkdown; + } + + public boolean isHelp() { + return help; + } + + @Override + public String toString() { + return "CLIArguments{" + + "keyword='" + keyword + '\'' + + ", location='" + location + '\'' + + ", pages=" + page + + ", isFullTime=" + isFullTime + + ", help=" + help + + ", isMarkdown=" + isMarkdown + + '}'; + } + + /** + * Esta funcion es equivalente a CLIArguments::new, sin embargo, si en el futuro queremos agregar + * parametros adicionales, podemos limitar la manera de construir objetos mediante esta funcion. + */ + public static CLIArguments newInstance() { + return new CLIArguments(); + } +} diff --git a/jobs-search-reporter/src/main/java/com/platzi/jobsearch/cli/CLIFunctions.java b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/cli/CLIFunctions.java new file mode 100644 index 0000000..a011172 --- /dev/null +++ b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/cli/CLIFunctions.java @@ -0,0 +1,24 @@ +package com.platzi.jobsearch.cli; + +import java.util.HashMap; +import java.util.Map; + +public interface CLIFunctions { + /** + * Funcion que tomara los argumentos del CLI y los convertira en algo que se pueda + * usar en la API de github. + */ + static Map toMap(CLIArguments cliArguments) { + Map params = new HashMap<>(); + params.put("description", cliArguments.getKeyword()); + params.put("location", cliArguments.getLocation()); + params.put("full_time", cliArguments.isFullTime()); + params.put("page", cliArguments.getPage()); + + if (cliArguments.isMarkdown()) { + params.put("markdown", true); + } + + return params; + } +} diff --git a/jobs-search-reporter/src/main/java/com/platzi/jobsearch/cli/CLIHelpValidator.java b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/cli/CLIHelpValidator.java new file mode 100644 index 0000000..6313ac2 --- /dev/null +++ b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/cli/CLIHelpValidator.java @@ -0,0 +1,20 @@ +package com.platzi.jobsearch.cli; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.ParameterException; + +/** + * Clase usada internamente por JCommande para validar ciertos argumentos. + * En nuestro caso la usamos para frenar el parseo de argumentos si encontramos que se solicito + * la opcion de ayuda. + */ +public class CLIHelpValidator implements IParameterValidator { + + @Override + public void validate(String name, String value) throws ParameterException { + boolean actualValue = Boolean.parseBoolean(value); + if (actualValue) { + throw new ParameterException("Help passed"); + } + } +} diff --git a/jobs-search-reporter/src/main/java/com/platzi/jobsearch/cli/CLIKeywordValidator.java b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/cli/CLIKeywordValidator.java new file mode 100644 index 0000000..b554714 --- /dev/null +++ b/jobs-search-reporter/src/main/java/com/platzi/jobsearch/cli/CLIKeywordValidator.java @@ -0,0 +1,19 @@ +package com.platzi.jobsearch.cli; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.ParameterException; + +/** + * Clase usada por JCommander para validar argumentos. + *

+ * En nuestro caso la usamos para validar que el skill solicitiado (keyword) sea unicamente letras y numeros. + */ +public class CLIKeywordValidator implements IParameterValidator { + @Override + public void validate(String name, String value) throws ParameterException { + if (!value.matches("^[a-zA-Z]+\\d*$")) { + System.err.println("Keyword: " + value + " no es un Keyword valido, keywords deben ser alfanumericas.\n"); + throw new ParameterException("Only alphanumerics are supported"); + } + } +} diff --git a/modules/build.gradle b/modules/build.gradle deleted file mode 100644 index ae9491f..0000000 --- a/modules/build.gradle +++ /dev/null @@ -1,20 +0,0 @@ -group 'com.platzi' -version '0.0.1' - -def createTask(taskName, classMain) { - tasks.create(taskName, JavaExec) { - group = "Execution" - description = "Run the demo class" - classpath = sourceSets.main.runtimeClasspath - main = "com.platzi.functional.$classMain" - } -} - -createTask("impuras", "Main") - -repositories { - mavenCentral() -} - -dependencies { -} \ No newline at end of file diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_03_inmutable/C8InmutObjWithMutAttributes.java b/modules/src/main/java/com/platzi/functional_student_practice/_03_inmutable/C8InmutObjWithMutAttributes.java new file mode 100644 index 0000000..5db5d05 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_03_inmutable/C8InmutObjWithMutAttributes.java @@ -0,0 +1,4 @@ +package com.platzi.functional_student_practice._03_inmutable; + +public class C8InmutObjWithMutAttributes { +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_04_functional/C11y12MathFunctions.java b/modules/src/main/java/com/platzi/functional_student_practice/_04_functional/C11y12MathFunctions.java new file mode 100644 index 0000000..c88af73 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_04_functional/C11y12MathFunctions.java @@ -0,0 +1,4 @@ +package com.platzi.functional_student_practice._04_functional; + +public class C11y12MathFunctions { +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_04_functional/C13CLIUtils.java b/modules/src/main/java/com/platzi/functional_student_practice/_04_functional/C13CLIUtils.java new file mode 100644 index 0000000..0bad459 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_04_functional/C13CLIUtils.java @@ -0,0 +1,4 @@ +package com.platzi.functional_student_practice._04_functional; + +public class C13CLIUtils { +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_04_functional/C14StringFunctions.java b/modules/src/main/java/com/platzi/functional_student_practice/_04_functional/C14StringFunctions.java new file mode 100644 index 0000000..e9e221d --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_04_functional/C14StringFunctions.java @@ -0,0 +1,4 @@ +package com.platzi.functional_student_practice._04_functional; + +public class C14StringFunctions { +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_05_sam/C15AgeUtils.java b/modules/src/main/java/com/platzi/functional_student_practice/_05_sam/C15AgeUtils.java new file mode 100644 index 0000000..1a4d224 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_05_sam/C15AgeUtils.java @@ -0,0 +1,4 @@ +package com.platzi.functional_student_practice._05_sam; + +public class C15AgeUtils { +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_06_reference_operator/C16NombresUtils.java b/modules/src/main/java/com/platzi/functional_student_practice/_06_reference_operator/C16NombresUtils.java new file mode 100644 index 0000000..4b9e516 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_06_reference_operator/C16NombresUtils.java @@ -0,0 +1,4 @@ +package com.platzi.functional_student_practice._06_reference_operator; + +public class C16NombresUtils { +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_07_inference/C17Inference.java b/modules/src/main/java/com/platzi/functional_student_practice/_07_inference/C17Inference.java new file mode 100644 index 0000000..a2e9bcd --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_07_inference/C17Inference.java @@ -0,0 +1,4 @@ +package com.platzi.functional_student_practice._07_inference; + +public class C17Inference { +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_08_lambda/C18Syntax.java b/modules/src/main/java/com/platzi/functional_student_practice/_08_lambda/C18Syntax.java new file mode 100644 index 0000000..e56cc65 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_08_lambda/C18Syntax.java @@ -0,0 +1,4 @@ +package com.platzi.functional_student_practice._08_lambda; + +public class C18Syntax { +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_09_defaults/C19StringFunctions.java b/modules/src/main/java/com/platzi/functional_student_practice/_09_defaults/C19StringFunctions.java new file mode 100644 index 0000000..0c6706c --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_09_defaults/C19StringFunctions.java @@ -0,0 +1,4 @@ +package com.platzi.functional_student_practice._09_defaults; + +public class C19StringFunctions { +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_10_chaining/C20Chaining.java b/modules/src/main/java/com/platzi/functional_student_practice/_10_chaining/C20Chaining.java new file mode 100644 index 0000000..9f4218d --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_10_chaining/C20Chaining.java @@ -0,0 +1,5 @@ +package com.platzi.functional_student_practice._10_chaining; + + +public class C20Chaining { +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_11_composition/C21MathOperations2.java b/modules/src/main/java/com/platzi/functional_student_practice/_11_composition/C21MathOperations2.java new file mode 100644 index 0000000..d654893 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_11_composition/C21MathOperations2.java @@ -0,0 +1,4 @@ +package com.platzi.functional_student_practice._11_composition; + +public class C21MathOperations2 { +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_14_optionals/C22Optionals.java b/modules/src/main/java/com/platzi/functional_student_practice/_14_optionals/C22Optionals.java new file mode 100644 index 0000000..3e48b7f --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_14_optionals/C22Optionals.java @@ -0,0 +1,4 @@ +package com.platzi.functional_student_practice._14_optionals; + +public class C22Optionals { +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_15_streams/C23Y24Streams.java b/modules/src/main/java/com/platzi/functional_student_practice/_15_streams/C23Y24Streams.java new file mode 100644 index 0000000..08abcf4 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_15_streams/C23Y24Streams.java @@ -0,0 +1,4 @@ +package com.platzi.functional_student_practice._15_streams; + +public class C23Y24Streams { +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_15_streams/C25CollectorsTheory.java b/modules/src/main/java/com/platzi/functional_student_practice/_15_streams/C25CollectorsTheory.java new file mode 100644 index 0000000..1266d1d --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_15_streams/C25CollectorsTheory.java @@ -0,0 +1,181 @@ +package com.platzi.functional_student_practice._15_streams; + +import com.platzi.functional_teacher_theory.util.Utils; + +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class C25CollectorsTheory { + + /* + + Operaciones y Collectors + + Usando Stream nos podemos simplificar algunas operaciones, como es el filtrado, el mapeo, + conversiones y más. Sin embargo, no es del tod0 claro cuándo una operación nos devuelve otro + Stream para trabajar y cuándo nos da un resultado final… + + ¡O al menos no era claro hasta ahora! + + Cuando hablamos de pasar lambdas a una operación de Stream, en realidad, estamos delegando a + Java la creación de un objecto basado en una interfaz. + + Por ejemplo: + + */ + + public static void main(String[] args) { + + Stream coursesStream = Utils.getListOf("Java", "Node.js", "Kotlin").stream(); + List javaCoursesStream = coursesStream.filter(course -> course.contains("Java")).toList(); + + // En realidad, es lo mismo que: + List explicitOperationStream = coursesStream.filter(new Predicate() { + public boolean test(String st) { + return st.contains("Java"); + } + }).toList(); + } + + /* + + Estas interfaces las mencionamos en clases anteriores. Solo como repaso, listo algunas a + continuación: + + Consumer: recibe un dato de tipo T y no genera ningún resultado + Function: toma un dato de tipo T y genera un resultado de tipo R + Predicate: toma un dato de tipo T y evalúa si el dato cumple una condición + Supplier: no recibe ningún dato, pero genera un dato de tipo T cada vez que es invocado + UnaryOperator recibe un dato de tipo T y genera un resultado de tipo T + + Estas interfaces (y otras más) sirven como la base de donde generar los objetos con las lambdas + que pasamos a los diferentes métodos de Stream. Cada una de ellas cumple esencialmente con + recibir el tipo de dato de el Stream y generar el tipo de retorno que el método espera. + + Si tuvieras tu propia implementación de Stream, se vería similar al siguiente ejemplo: + + public class PlatziStream implements Stream { + private List data; + + public Stream filter(Predicate predicate) { + List filteredData = new LinkedList<>(); + for(T t : data){ + if(predicate.test(t)){ + filteredData.add(t); + } + } + + return filteredData.stream(); + } + } + + Probablemente, tendría otros métodos y estructuras de datos, pero la parte que importa es justamente + cómo se usa el Predicate. Lo que hace Stream internamente es pasar cada dato por este objeto que + nosotros proveemos como una lambda y, según el resultado de la operación, decidir si debe incluirse + o no en el Stream resultante. + + Como puedes notar, esto no tiene mucha complejidad, puesto que es algo que pudimos fácilmente replicar. + Pero Stream no solo incluye estas operaciones “triviales”, también incluye un montón de utilidades + para que la máquina virtual de Java pueda operar los elementos de un Stream de manera más rápida y + distribuida. + + + + Operaciones + + A estas funciones que reciben lambdas y se encargan de trabajar (operar) sobre los datos de un Stream + generalmente se les conoce como Operaciones. + + Existen dos tipos de operaciones: intermedias y finales. + + Cada operación aplicada a un Stream hace que el Stream original ya no sea usable para más operaciones. + Es importante recordar esto, pues tratar de agregar operaciones a un Stream que ya esta siendo + procesado es un error muy común. + + En este punto seguramente te parezcan familiares todas estas operaciones, pues vienen en forma de + métodos de la interfaz Stream. Y es cierto. Aunque son métodos, se les considera operaciones, + puesto que su intención es operar el Stream y, posterior a su trabajo, el Stream no puede volver a + ser operado. + + En clases posteriores hablaremos más a detalle sobre cómo identificar una operación terminal de una + operación intermedia. + + + + Collectors + + Una vez que has agregado operaciones a tu Stream de datos, lo más usual es que llegues a un punto + donde ya no puedas trabajar con un Stream y necesites enviar tus datos en otro formato, por ejemplo, + JSON o una List a base de datos. + + Existe una interfaz única que combina todas las interfaces antes mencionadas y que tiene como única + utilidad proveer de una operación para obtener todos los elementos de un Stream: Collector. + + Collector es una interfaz que tomará datos de tipo T del Stream, un tipo de dato mutable A, + donde se iran agregando los elementos (mutable implica que podemos cambiar su contenido, como un + LinkedList), y generara un resultado de tipo R. + + Suena complicado… y lo es. Por eso mismo, Java 8 incluye una serie de Collectors ya definidos para + no rompernos las cabeza con cómo convertir nuestros datos. + + Veamos un ejemplo: + + */ + + public List getJavaCourses(Stream coursesStream) { + List javaCourses = coursesStream + .filter(course -> course.contains("Java")) + .collect(Collectors.toList()); + return javaCourses; + } + + /* + + Usando java.util.stream.Collectors podemos convertir muy sencillamente un Stream en un Set, Map, + List, Collection, etc. La clase Collectors ya cuenta con métodos para generar un Collector que + corresponda con el tipo de dato que tu Stream está usando. Incluso vale la pena resaltar que + Collectors puede generar un ConcurrentMap que puede ser de utilidad si requieres de multiples + threads. + + Usar Collectors.toXXX es el proceso inverso de usar Collection.stream(). Esto hace que sea fácil + generar APIs publicas que trabajen con estructuras/colecciones comunes e internamente utilizar + Stream para agilizar las operaciones de nuestro lado. + + + + Tipos de retorno + + Hasta este punto, la única manera de obtener un dato que ya no sea un Stream es usando Collectors, + pues la mayoría de operaciones de Stream se enfocan en operar los datos del Stream y generar un + nuevo Stream con los resultados de la operación. + + Sin embargo, algunas operaciones no cuentan con un retorno. Por ejemplo, forEach, que es una + operación que no genera ningún dato. Para poder entender qué hace cada operación basta con + plantear qué hace la operación para poder entender qué puede o no retornar. + + Por ejemplo: + + La operación de findAny trata de encontrar cualquier elemento que cumpla con la condición + del Predicate que le pasamos como parámetro. Sin embargo, la operación dice que se devuelve + un Optional. ¿Qué pasa cuando no encuentra ningún elemento? ¡Claro, por eso devuelve un + Optional! Porque podría haber casos en que ningún elemento del Stream cumpla la condición. + + En las clases posteriores haremos un listado más a detalle y con explicaciones de qué tipos de + retorno tiene cada operación. Y entenderemos por qué se categorizan como operaciones finales e + intermedias. + + + Conclusiones + + Por ahora, hemos entendido que cada operación en un Stream consume hasta agotar el Stream. + Y lo hace en un objeto no reusable. Esto implica que tenemos que decidir en nuestro código + cuándo un Stream es un elemento temporal para una función o cuándo realmente una función + sera la última en tocar los datos del Stream. + + Las siguientes clases y lecturas cubrirán mas a detalle las múltiples operaciones y cómo + afectan a los datos del Stream. + + */ +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_15_streams/C26Y29TypeStream.java b/modules/src/main/java/com/platzi/functional_student_practice/_15_streams/C26Y29TypeStream.java new file mode 100644 index 0000000..d106c0d --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_15_streams/C26Y29TypeStream.java @@ -0,0 +1,4 @@ +package com.platzi.functional_student_practice._15_streams; + +public class C26Y29TypeStream { +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_15_streams/C27TerminalTheory.java b/modules/src/main/java/com/platzi/functional_student_practice/_15_streams/C27TerminalTheory.java new file mode 100644 index 0000000..f812c3b --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_15_streams/C27TerminalTheory.java @@ -0,0 +1,261 @@ +package com.platzi.functional_student_practice._15_streams; + +import com.platzi.functional_teacher_theory.util.Utils; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class C27TerminalTheory { + + public static void main(String[] args) { + + /* + + Operaciones Terminales + + Las operaciones terminales son aquellas operaciones que como resultado no generan un nuevo Stream. + Su resultado puede variar según la operación. La utilidad de estas es poder generar un valor final + a todas nuestras operaciones o consumir los datos finales. La razón principal para querer esto es + que los datos deberán salir en algún punto de nuestro control y es con las operaciones terminales + que hacemos esto. + + Pensemos, por ejemplo, en un servidor web. Recibe una petición de datos, convierte la petición en + un Stream, procesa los datos usando filter o map, convierte de JSON a datos locales que sean + manipulables por código Java y hace consumo de una base de datos. Tod0 esto mediante streams de + diferentes tipos. Pero eventualmente tiene que devolver una respuesta para quien le hizo la petición. + + ¿Qué pasa si quien hizo la petición no esta usando Java? No podemos enviarle un objeto de tipo + Stream a un código hecho en Python o en JavaScript… es ahi donde una operación final nos ayuda + a convertir nuestro Stream de Java en algún tipo de dato que sea mas comprensible. + + Otro ejemplo claro es si estamos creando una librería o creando código que más gente en nuestro + equipo usará. Al crear nuestros métodos y clases usamos streams por aquí y lambdas por allá, pero + al exponer estos métodos para uso de otros desarrolladores no podemos obligarlos a usar Stream. + + Las razones son variadas. No queremos obligar y limitar a quienes usen nuestro código a trabajar + con un solo tipo dato. No sabemos qué versión de Java está usando quien use nuestro código. No + sabemos si Stream está disponible en su parte del código (por ejemplo, en Android no estaba + disponible del tod0), etc. + + Es por ello que quisiéramos proveer de algo mas simple: listas, primitivos o incluso dar algún + mecanismo para poder usar código externo de nuestro lado. + + Las operaciones terminales más comunes que se encuentran en Stream son: + anyMatch() + allMatch() + noneMatch() + findAny() + findFirst() + min() + max() + reduce() + count() + toArray() + collect() + forEach() + + Revisaremos qué hacen y qué utilidad tienen durante esta lectura. + + + A. Operaciones terminales de coincidencia + + anyMatch, allMatch, noneMatch + + Las operaciones anyMatch, allMatch y noneMatch sirven para determinar si en un Stream hay + elementos que cumplan con un cierto Predicate. + Esto puede ser una forma simple de validar los datos de un Stream. Son terminales pues + las tres retornan un boolean: + */ + + //Nos indica si un stream contiene un elemento según el Predicate que le pasemos: + java.util.stream.Stream numbersStream = Stream.of(1, 2, 3, 4, 5, 6, 7, 11); + boolean biggerThanTen = numbersStream.anyMatch(i -> i > 10); //true porque tenemos el 11 + + //allMatch + //Nos indica si todos los elementos de un Stream cumplen con un cierto Predicate: + Stream agesStream = Stream.of(19, 21, 35, 45, 12); + boolean allLegalDrinkingAge = agesStream.allMatch(age -> age > 18); //false, tenemos un menor + + //noneMatch + //Nos indica si todos los elementos de un Stream NO CUMPLEN un cierto Predicate: + Stream oddNumbers = Stream.of(1, 3, 5, 7, 9, 11); + boolean allAreOdd = oddNumbers.noneMatch(i -> i % 2 == 0); + + /* + + B. Operaciones terminales de búsqueda + findAny, findFirst + + Estas operaciones retornan un Optional como resultado de buscar un elemento dentro del Stream. + + La diferencia entre ambas es que findFirst retornara un Optional conteniendo el primer elemento + en el Stream si el Stream tiene definida previamente una operación de ordenamiento o para encontrar + elementos. De lo contrario, funcionará igual que findAny, tratando de devolver cualquier elemento + presente en el Stream de forma no determinista (random) + + Si el elemento encontrado es null, tendrás que lidiar con una molesta NullPointerException. Si el + Stream esta vacío, el retorno es equivalente a Optional.empty(). + + La principal razón para usar estas operaciones es poder usar los elementos de un Stream después + haber filtrado y convertido tipos de datos. Con Optional nos aseguramos que, aún si no hubiera + resultados, podremos seguir trabajando sin excepciones o escribiendo condicionales para validar + los datos. + + + C. Operaciones terminales de reducción + 1. min, max + + Son dos operaciones cuya finalidad es obtener el elemento más pequeño (min) o el elemento más + grande (max) de un Stream usando un Comparator. Puede haber casos de Stream vacíos, es por ello + que las dos operaciones retornan un Optional para en esos casos poder usar Optional.empty. + + La interfaz Comparator es una @FunctionalInterface, por lo que es sencillo usar min y max con + lambdas: + + */ + + Stream bigNumbers = Stream.of(100L, 200L, 1000L, 5L); + Optional minimumOptional = bigNumbers.min((numberX, numberY) -> (int) Math.min(numberX, numberY)); + + /* + + 2. reduce + + Esta operación existe en tres formas: + reduce(valorInicial, BinaryOperator) + reduce(BinaryAccumulator) + reduce(valorInicial, BinaryFunction, BinaryOperator) + + La diferencia entre los 3 tipos de invocación: + + 2.1. reduce(BinaryAccumulator) + + Retorna un Optional del mismo tipo que el Stream, con un solo valor resultante de aplicar el BinaryAccumulator + sobre cada elemento o Optional.empty() si el stream estaba vacío. Puede generar un NullPointerException en + casos donde el resultado de BinaryAccumulator sea null. + + */ + + Stream aLongStoryStream = Stream.of("Cuando", "despertó,", "el", "dinosaurio", "todavía", "estaba", "allí."); + Optional longStoryOptional = aLongStoryStream.reduce((previousStory, nextPart) -> previousStory + " " + nextPart); + longStoryOptional.ifPresent(System.out::println); //"Cuando despertó, el dinosaurio todavía estaba allí." + + /* + + + 2.2. reduce(valorInicial, BinaryOperator) + + Retorna un valor del mismo tipo que el Stream después de aplicar BinaryOperator sobre cada elemento del Stream. + En caso de un Stream vacío, el valorInicial es retornado. + + */ + + Stream firstTenNumbersStream = Stream.iterate(0, i -> i + 1).limit(10); + int sumOfFirstTen = firstTenNumbersStream.reduce(0, Integer::sum); //45 -> 0 + 1 + … + 9 + + /* + + + 2.3. reduce(valorInicial, BinaryFunction, BinaryOperator) + + Genera un valor de tipo V después de aplicar BinaryFunction sobre cada elemento de tipo T en + el Stream y obtener un resultado V. + + Esta version de reduce usa el BinaryFunction como map + reduce. Es decir, por cada elemento en + el Stream se genera un valor V basado en el valorInicial y el resultado anterior de la BinaryFunction. + BinaryOperator se utiliza en streams paralelos (stream.parallel()) para determinar el valor que se debe + mantener en cada iteración. + + */ + + Stream aLongStoryStreamAgain = Stream.of("Cuando", "despertó,", "el", "dinosaurio", "todavía", "estaba", "allí."); + int charCount = aLongStoryStreamAgain.reduce(0, (count, word) -> count + word.length(), Integer::sum); + + /* + + 3. count + + Una operación sencilla: sirve para obtener cuantos elementos hay en el Stream. + + */ + + Stream yearsStream = Stream.of(1990, 1991, 1994, 2000, 2010, 2019, 2020); + long yearsCount = yearsStream.count(); //7, solo nos dice cuantos datos tuvo el stream. + + /* + + La principal razón de usar esta operación es que, al aplicar filter o flatMap, + nuestro Stream puede crecer o disminuir de tamaño y, tal vez, de muchas operaciones + solo nos interese saber cuántos elementos quedaron presentes en el Stream. + Por ejemplo, cuantos archivos se borraron o cuantos se crearon por ejemplo. + + 4. toArray + + Agrega todos los elementos del Stream a un arreglo y nos retorna dicho arreglo. + La operación genera un Object[], pero es sposible hacer castings al tipo de dato del Stream. + + 5. collect + + Mencionamos la operación collect en la lectura sobre operaciones y collectors, donde mencionamos que: + + Collector es una interfaz que tomara datos de tipo T del Stream, un tipo de dato mutable A, + donde se irán agregando los elementos (mutable implica que podemos cambiar su contenido, como un + LinkedList) y generara un resultado de tipo R. + + Usando java.util.stream.Collectors podemos convertir sencillamente un Stream en un Set, Map, List, + Collection, etc. La clase Collectors ya cuenta con métodos para generar un Collector que corresponda + con el tipo de dato que tu Stream esta usando. Incluso vale la pena resaltar que Collectors puede + generar un ConcurrentMap que puede ser de utilidad si requieres de multiples threads. + + */ + + Stream coursesStream = Utils.getListOf("Java!", "JavaScript", "FrontEnd", "Backend", "FullStack").stream(); + List javaCourses = coursesStream.filter(course -> course.contains("Java")).collect(Collectors.toList()); + + /* + + D. Operaciones terminales de iteración + forEach + + Tan simple y tan lindo como un clásico for. forEach es una operación que recibe un Consumer + y no tiene un valor de retorno (void). La principal utilidad de esta operación es dar un uso + final a los elementos del Stream. + + */ + + Stream> courses = Stream.of( + Arrays.asList("Java!", "JavaScript"), + Arrays.asList("FrontEnd", "Backend", "FullStack") + ); + courses.forEach(courseList -> System.out.println("Cursos disponibles: " + courseList)); + + /* + + Conclusiones + + Las operaciones terminales se encargan de dar un fin y liberar el espacio usado por un Stream. + Son también la manera de romper los encadenamientos de métodos entre streams y regresar a + nuestro código a un punto de ejecución lineal. Como su nombre lo indica, por lo general, son + la ultima operación presente cuando escribes chaining: + + */ + + Stream infiniteStream = Stream.iterate(0, x -> x + 1); + List numbersList = infiniteStream.limit(1000) + .filter(x -> x % 2 == 0) // Operación intermedia + .map(x -> x * 3) //Operación intermedia + .collect(Collectors.toList()); //Operación final + + /* + + Por ultimo, recuerda que una vez que has agregado una operación a un Stream, el Stream original + ya no puede ser utilizado. Y más aun al agregar una operación terminal, pues esta ya no crea un + nuevo Stream. Internamente, al recibir una operación, el Stream en algún punto llama a su + método close, que se encarga de liberar los datos y la memoria del Stream. + + */ + } +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_15_streams/C28IntermediateTheory.java b/modules/src/main/java/com/platzi/functional_student_practice/_15_streams/C28IntermediateTheory.java new file mode 100644 index 0000000..4879a3f --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_15_streams/C28IntermediateTheory.java @@ -0,0 +1,347 @@ +package com.platzi.functional_student_practice._15_streams; + +public class C28IntermediateTheory { + /* + + Operaciones Intermedias + + En clases anteriores hablamos de dos tipos de operaciones: intermedias y finales. No se + explicaron a profundidad, pero en esta lectura iremos más a fondo en las operaciones + intermedias y trataremos de entender qué sucede por dentro de cada una. + + + + ¿Qué son las operaciones intermedias? + + Se le dice operación intermedia a toda operación dentro de un Stream que como resultado + devuelva un nuevo Stream. Es decir, tras invocar una operación intermedia con un cierto + tipo de dato, obtendremos como resultado un nuevo Stream conteniendo los datos ya + modificados. + + El Stream que recibe la operación intermedia pasa a ser “consumido” posterior a la + invocación de la operación, quedando inutilizable para posteriores operaciones. Si + decidimos usar el Stream para algún otro tipo de operaciones tendremos un + IllegalStateException. + + Viéndolo en código con un ejemplo debe ser mas claro: + + Stream initialCourses = Stream.of("Java", "Spring", "Node.js"); + + Stream lettersOnCourses = initialCourses.map(course -> course.length()); + //De este punto en adelante, initialCourses ya no puede agregar mas operaciones. + + Stream evenLengthCourses = lettersOnCourses.filter(courseLength -> courseLength % 2 == 0); + //lettersOnCourses se consume en este punto y ya no puede agregar mas operaciones. + No es posible usar el Stream mas que como referencia. + + El ejemplo anterior puede ser reescrito usando chaining. Sin embargo, la utilidad de + este ejemplo es demostrar que las operaciones intermedias generan un nuevo Stream. + + + + Operaciones disponibles + + La interfaz Stream cuenta con un grupo de operaciones intermedias. A lo largo de esta + lectura explicaremos cada una de ellas y trataremos de aproximar su funcionalidad. + Cada operación tiene implementaciones distintas según la implementación de Stream, en + nuestro caso, haremos solo aproximaciones de la lógica que sigue la operación. + + Las operaciones que ya están definidas son: + + filter(…) + map(…) + flatMap(…) + distinct(…) + limit(…) + peek(…) + skip(…) + sorted(…) + + Analicemos qué hace cada una de ellas y hagamos código que se aproxime a lo que hacen + internamente. + + + + filter + + La operación de filtrado de Stream tiene la siguiente forma: + + Stream filter(Predicate predicate) + + Algunas cosas que podemos deducir únicamente viendo los elementos de la operación son: + + La operación trabaja sobre un Stream y nos devuelve un nuevo Stream del mismo tipo (T) + Sin embargo, el Predicate que recibe como parámetro trabaja con elementos de + tipo T y cualquier elemento siempre que sea un subtipo de T. Esto quiere decir que + si tenemos la clase PlatziStudent extends Student y tenemos un Stream donde + también tenemos elementos de tipo PlatziStudent, podemos filtrarlos sin tener que + revisar o aclarar el tipo + Predicate es una @FunctionalInterface (como lo viste en la clase 11), lo cual nos + permite pasar como parámetro objetos que implementen esta interfaz o lambdas + + El uso de esta operación es sencillo: + + public Stream getJavaCourses(List courses){ + return courses.stream() + .filter(course -> course.contains("Java")); + } + + Lo interesante radica en la condición que usamos en nuestra lambda, con ella determinamos + si un elemento debe permanecer o no en el Stream resultante. En la lectura anterior + hicimos una aproximación de la operación filter: + + public Stream filter(Predicate predicate) { + List filteredData = new LinkedList<>(); + for(T t : this.data){ + if(predicate.test(t)){ + filteredData.add(t); + } + } + + return filteredData.stream(); + } + + filter se encarga de iterar cada elemento del Stream y evaluar con el Predicate si el + elemento debe estar o no en el Stream resultante. Si nuestro Predicate es sencillo y + no incluye ningún ciclo o llamadas a otras funciones que puedan tener ciclos, la + complejidad del tiempo es de O(n), lo cual hace que el filtrado sea bastante rápido. + + Usos comunes de filter es limpiar un Stream de datos que no cumplan un cierto criterio. + Como ejemplo podrías pensar en un Stream de transacciones bancarias, mantener el Stream + solo aquellas que superen un cierto monto para mandarlas a auditoria, de un grupo de + calificaciones de alumnos filtrar únicamente por aquellos que aprobaron con una + calificación superior a 6, de un grupo de objetos JSON conservar aquellos que tengan + una propiedad en especifico, etc. + + Entre mas sencilla sea la condición de filtrado, más legible sera el código. Te + recomiendo que, si tienes más de una condición de filtrado, no le temas a usar + varias veces filter: + + courses.filter(course -> course.getName().contains("Java")) + .filter(course -> course.getDuration() > 2.5) + .filter(course -> course.getInstructor().getName() == Instructors.SINUHE_JAIME) + + Tu código sera más legible y las razones de por qué estás aplicando cada filtro + tendrán más sentido. Como algo adicional podrías mover esta lógica a funciones + individuales en caso de que quieras hacer más legible el código, tener más facilidad + de escribir pruebas o utilices en más de un lugar la misma lógica para algunas lambdas: + + courses.filter(Predicates::isAJavaCourse) + .filter(Predicates::hasEnoughDuration) + .filter(Predicates::hasSinuheAsInstructor); + + // La lógica es la misma: + public final class Predicates { + public static final boolean isAJavaCourse(Course course){ + return course.getName().contains("Java"); + } + } + + + + map + + La operación map puede parecer complicada en un principio e incluso confusa si + estas acostumbrado a usar Map, pero cabe resaltar que no hay relación entre + la estructura y la operación. La operación es meramente una transformación de un + tipo a otro. + + Stream map(Function mapper) + + Los detalles a resaltar son muy similares a los de filter, pero la diferencia clave + está en T y R. Estos generics nos dicen que map va a tomar un tipo de dato T, cualquiera + que sea, le aplicara la función mapper y generara un R. + + Es algo similar a lo que hacías en la secundaria al convertir en una tabla datos, + para cada x aplicabas una operación y obtenías una y (algunos llaman a esto tabular). + map operará sobre cada elemento en el Stream inicial aplicando la Function que le + pases como lambda para generar un nuevo elemento y hacerlo parte del Stream resultante: + + Stream ids = DatabaseUtils.getIds().stream(); + + Stream users = ids.map(id -> db.getUserWithId(id)); + + O, puesto de otra forma, por cada DatabaseID en el Stream inicial, al aplicar map + genera un User: + + DatabaseID(1234) -> map(…) -> User(Sinuhe Jaime, @Sierisimo) + DatabaseID(4321) -> map(…) -> User(Diego de Granda, @degranda10) + DatabaseID(007) -> map(…) ->User(Oscar Barajas, @gndx) + + Esto resulta bastante practico cuando queremos hacer alguna conversión de datos y + realmente no nos interesa el dato completo (solo partes de él) o si queremos convertir + a un dato complejo partiendo de un dato base. + + Si quisiéramos replicar qué hace internamente map sería relativamente sencillo: + + public Stream map(Function mapper) { + List mappedData = new LinkedList<>(); + for(T t : this.data) { + R r = mapper.apply(t); + mappedData.add(r); + } + + return mappedData.stream(); + } + + La operación map parece simple ya vista de esta manera. Sin embargo, por dentro de + las diferentes implementaciones de Stream hace varias validaciones y optimizaciones + para que la operación pueda ser invocada en paralelo, para prevenir algunos errores + de conversión de tipos y hacer que sea mas rápida que nuestra versión con un for. + + + + flatMap + + En ocasiones no podremos evitar encontrarnos con streams del tipo Stream>, + donde tenemos datos con muchos datos… + + Este tipo de streams es bastante común y puede pasarte por multiples motivos. Se puede + tornar difícil operar el Stream inicial si queremos aplicar alguna operación a cada uno + de los elementos en cada una de las listas. + + Si mantener la estructura de las listas (o colecciones) no es importante para el + procesamiento de los datos que contengan, entonces podemos usar flatMap para simplificar + la estructura del Stream, pasándolo de Stream> a Stream. + + Visto en un ejemplo más “visual”: + + Stream> coursesLists; + // Stream{List["Java", "Java 8 Functional", "Spring"], List["React", "Angular", "Vue.js"], List["Big Data", "Pandas"]} + Stream allCourses; + // Stream{ ["Java", "Java 8 Functional", "Spring", "React", "Angular", "Vue.js", "Big Data", "Pandas"]} + + flatMap tiene la siguiente forma: + + Stream flatMap(Function> mapper) + + Lo interesante es que el resultado de la función mapper debe ser un Stream. + Stream usará el resultado de mapper para “acumular” elementos en un Stream desde otro Stream. + Puede sonar confuso, por lo que ejemplificarlo nos ayudará a entenderlo mejor: + + //Tenemos esta clase: + public class PlatziStudent { + private boolean emailSubscribed; + private List emails; + + public boolean isEmailSubscribed() { + return emailSubscribed; + } + + public List getEmails(){ + return new LinkedList<>(emails); //Creamos una copia de la lista para mantener + la clase inmutable por seguridad + } + } + + //Primero obtenemos objetos de tipo usuario registrados en Platzi: + Stream platziStudents = getPlatziUsers().stream(); + + // Despues, queremos enviarle un correo a todos los usuarios pero… solo nos interesa obtener su correo para notificarlos: + Stream allEmailsToNotify = platziStudents + .filter(PlatziStudent::isEmailSubscribed) //Primero evitamos enviar correos a quienes no estén subscritos + .flatMap(student -> student.getEmails().stream()); // La lambda genera un nuevo Stream de la lista de emails de cada studiante. + + sendMonthlyEmails(allEmailsToNotify); + //El Stream final solo es un Stream de emails, sin mas detalles ni información adicional. + + flatMap es una manera en la que podemos depurar datos de información adicional que no sea necesaria. + + + + distinct + + Esta operación es simple: + + Stream distinct() + + Lo que hace es comparar cada elemento del Stream contra el resto usando el método equals. De + esta manera, evita que el Stream contenga elementos duplicados. La operación, al ser intermedia, + retorna un nuevo Stream donde los elementos son únicos. Recuerda que para garantizar esto es + recomendable que sobrescribas el método equals en tus clases que representen datos. + + + + limit + + La operación limit recibe un long que determina cuántos elementos del Stream original seran + preservados. Si el número es mayor a la cantidad inicial de elementos en el Stream, + básicamente, todos los elementos seguirán en el Stream. Un detalle interesante es que + algunas implementaciones de Stream pueden estar ordenadas (sorted()) o explícitamente no + ordenadas (unordered()), lo que puede cambiar drásticamente el resultado de la operación y + el performance. + + Stream limit(long maxSize) + + La operación asegura que los elementos en el Stream resultante serán los primeros en aparecer + en el Stream. Es por ello que la operación es ligera cuando el Stream es secuencial o se usó + la operación unordered() (no disponible en todos los Streams, ya que la operación pertenece + a otra clase). + + Como reto adicional, crea el código para representar lo que hace la operación limit. + + + + peek + + peek funciona como una lupa, como un momento de observación de lo que está pasando en el + Stream. Lo que hace esta operación es tomar un Consumer, pasar los datos conforme van estando + presentes en el Stream y generar un nuevo Stream idéntico para poder seguir operando. + + La estructura de peek es simple: + + Stream peek(Consumer consumer) + + Usarlo puede ayudarnos a generar logs o registros de los datos del Stream, por ejemplo: + + Stream serverConnections = + server.getConnectionsStream() + .peek(connection -> logConnection(connection, new Date())) + .filter(…) + .map(…) + //Otras operaciones… + + + + skip + + Esta operación es contraria a limit(). Mientras limit() reduce los elementos presentes en + el Stream a un numero especifico, skip descarta los primeros n elementos y genera un Stream + con los elementos restantes en el Stream. + + Esto es: + + Stream first10Numbers = Stream.of(0,1,2,3,4,5,6,7,8,9); + Stream last7Numbers = first10Numbers.skip(3); // 3,4,5,6,7,8,9 + + Esto puede ser de utilidad si sabemos qué parte de la información puede ser descartable. + Por ejemplo, descartar la primera línea de un XML (), descartar metadatos de una + foto, usuarios de prueba al inicio de una base de datos, el intro de un video, etc. + + + + sorted + + La operación sorted() requiere que los elementos presentes en el Stream implementen la + interfaz Comparable para poder hacer un ordenamiento de manera natural dentro del Stream. + El Stream resultante contiene todos los elementos pero ya ordenados, hacer un ordenamiento + tiene muchas ventajas, te recomiendo los cursos de algoritmos de Platzi para poder conocer + a mas detalle estas ventajas. + + + + Conclusiones + + Las operaciones intermedias nos permiten tener control sobre los streams y manipular sus + contenidos de manera sencilla sin preocuparnos realmente por cómo se realizan los cambios. + + Recuerda que las operaciones intermedias tienen la funcionalidad de generar nuevos streams + que podremos dar como resultado para que otras partes del código los puedan utilizar. + + Aunque existen otras operaciones intermedias en diferentes implementaciones de Stream, las + que aquí listamos están presentes en la interfaz base, por lo que entender estas operaciones + te facilitara la vida en la mayoría de los usos de Stream. + + */ + +} diff --git a/modules/src/main/java/com/platzi/functional_student_practice/_20_job_search_reporter/C30A40JobSearchReporter.java b/modules/src/main/java/com/platzi/functional_student_practice/_20_job_search_reporter/C30A40JobSearchReporter.java new file mode 100644 index 0000000..c79d4b8 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_student_practice/_20_job_search_reporter/C30A40JobSearchReporter.java @@ -0,0 +1,13 @@ +package com.platzi.functional_student_practice._20_job_search_reporter; + +public class C30A40JobSearchReporter { + /* + al 24 de julio de 2024, la API https://jobs.github.com/api se encuentra fuera de servicio, por lo que no se + puede realizar practica real sobre los conceptos desarrollados en clase. Las clases y los contenidos se siguen + de manera teórica de aquí en adelante, usando como referencia el código del directorio root/jobs-search-reporter + y los conceptos desarrollados en videos y recursos del curso https://platzi.com/cursos/java-funcional/ + + Link al repositorio de un compañero que aplica los conceptos sobre la API de rick y morty, + pero de forma más limitada: https://github.com/barreracarlosandres/CLIRickAndMorty + */ +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_01_pure/PureFunctions.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_01_pure/PureFunctions.java new file mode 100644 index 0000000..fbbd541 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_01_pure/PureFunctions.java @@ -0,0 +1,75 @@ +package com.platzi.functional_teacher_theory._01_pure; + +public class PureFunctions { + /** + * Aunque hoy dia conocemos a los metodos estaticos como metodos de clase o simplemente metodos estaticos, + * en realidad un metodo puede ser considerado o visto como una funcion. + *

+ * Basandonos en nuestra definicion de funcion, sabemos que para cada X genera una Y. En este caso, + * nuestra "x" es en realidad el par (x, y) y nuestra "y" sera el resultado de sumarlas. + *

+ * Habra algun cambio en el resultado si yo ejecuto 90 veces sum(3,5)? + *

+ * La respuesta es: NO. + *

+ * Una funcion pura no depende de estados exteriores (propiedades, objetos, variables + * externas a su definicion, etc.) ni ve afectado su resultado por agentes externos. + */ + public static int sum(int x, int y) { + return x + y; + } + + + /** + * Imagina que la siguiente clase es parte de un sistema financiero + */ + static class Person { + //Nos enfocaremos solo en esta propiedad por ahora… + private double balance; + + public Person(double balance) { + this.balance = balance; + } + + public double getBalance() { + return balance; + } + + public void setBalance(double balance) { + this.balance = balance; + } + } + + /** + * Teniendo esta funcion, se le puede considerar pura? + */ + public static boolean hasAvailableFunds(double funds) { + return funds > 0.0; + } + + + /** + * La respuesta es: Si! + *

+ * porque la funcion revisa unicamente si un numero es mayor a 0, no importa la referencia de donde vengan + * los fondos, mientras esos fondos sean menores o iguales a 0, la respuesta siempre sera false. + *

+ * Por otro lado, cuando una persona consigue fondos en su cuenta, la funcion en realidad no ve ese cambio. + * Para la funcion la respuesta cambiara hasta que le den un nuevo valor a evaluar. La funcion no depende + * de la presencia de los datos en ningun lugar o de un contexto. + *

+ * Mira el ejemplo abajo: + */ + public static void main(String[] args) { + Person sinuhe = new Person(-20.00); + //False, Sinuhe esta endeudado hasta los dientes + System.out.println(hasAvailableFunds(sinuhe.getBalance())); + + Person ricardo = new Person(1300.00); + //True, Ricardo tiene dinero disponible! + System.out.println(hasAvailableFunds(ricardo.getBalance())); + + //La funcion es evaluada al momento y no depende de que objeto es quien la manda a invocar, + //es por ello que se considera pura. + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_02_sideeffects/SideEffects.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_02_sideeffects/SideEffects.java new file mode 100644 index 0000000..7d27e13 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_02_sideeffects/SideEffects.java @@ -0,0 +1,73 @@ +package com.platzi.functional_teacher_theory._02_sideeffects; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +public class SideEffects { + + /** + * Funcion impura, el resultado de ejecutarla puede ser observado desde fuera + * del codigo. Por ejemplo en una consola. + */ + static void helloWorld() { + System.out.println("Hello World!"); + } + + + + + + + + + + /** + * Funcion impura, depende de la existencia y el estado de un archivo, + * eso provoca que sea no determinista + * Que sucede si esta funcion se ejecuta en mi computadora y en tu computadora? + * - Podemos determinar la salida en ambos casos? + * - Como nos aseguramos que nadie mas este modificando el archivo? + */ + static boolean containsMexico(File file) { + try (BufferedReader bfReader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = bfReader.readLine()) != null) { + if (line.contains("Mexico")) { + return true; + } + } + } catch (IOException ignored) { + return false; + } + + return false; + } + + + + + + + + + + + + + + /** + * Funcion impura. Aunque el codigo no esta implementado, con entender lo que hace + * sabemos que es no determinista y que no podemos garantizar los resultados para + * un cierto parametro + */ + static String getLastNameForGivenName(String name) { + //Obtener una conexion a la Base de datos + //Ejecutar un query en la base de datos + //Revisar los resultados del query… + //retornar el valor del lastName o un valor por default en caso de ausencia + //... + return ""; + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/immutable/ImmutablePerson.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/immutable/ImmutablePerson.java new file mode 100644 index 0000000..8dff029 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/immutable/ImmutablePerson.java @@ -0,0 +1,49 @@ +package com.platzi.functional_teacher_theory._03_immutable.immutable; + +import java.util.LinkedList; +import java.util.List; + +/** + * Clase final de nuestro diseño. + * + * Cuenta con mas de una mejora: + * + * 1. Es final, asi nadie puede extender de ella. No mas suplantaciones + * 2. Las propiedades son finales, una vez creado un objeto no puede mutar + * 3. El constructor exige todas las propiedades para generar un objeto + * (podria incluso generarse un builder derivado de este constructor) + * 4. Cuando se accede a los emails, se quenera una copia! no se envia la lista mutable! + */ +public final class ImmutablePerson { + private final String firstName; + private final String lastName; + + private final List emails; + + public ImmutablePerson(String firstName, String lastName, List emails) { + this.firstName = firstName; + this.lastName = lastName; + this.emails = emails; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public final List getEmails() { + return new LinkedList<>(emails); + } + + @Override + public String toString() { + return "ImmutablePerson{" + + "firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", emails=" + emails + + '}'; + } +} \ No newline at end of file diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/immutable/Outsider.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/immutable/Outsider.java new file mode 100644 index 0000000..c0f254b --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/immutable/Outsider.java @@ -0,0 +1,30 @@ +package com.platzi.functional_teacher_theory._03_immutable.immutable; + +import java.util.LinkedList; +import java.util.List; + +public class Outsider { + public static void main(String[] args) { + String firstName = "Sinuhe"; + String lastName = "Jaime"; + + List emails = new LinkedList<>(); + emails.add("sier@sier.com"); + + ImmutablePerson sier = new ImmutablePerson(firstName, lastName, emails); + + System.out.println(sier); + badIntentionedMethod(sier); + System.out.println(sier); + } + + /** + * No importa que el metodo intente modificar a la persona, la persona esta diseñada + * para no recibir modificaciones. + */ + static void badIntentionedMethod(ImmutablePerson person) { + List emails = person.getEmails(); + emails.clear(); + emails.add("imnotthebadguy@mail.com"); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/mutable/MutablePerson.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/mutable/MutablePerson.java new file mode 100644 index 0000000..95f4348 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/mutable/MutablePerson.java @@ -0,0 +1,49 @@ +package com.platzi.functional_teacher_theory._03_immutable.mutable; + +import java.util.List; + +/** + * POJO comun. Incluye propiedades y metodos para acceder y modificar dichas propiedades + */ +public class MutablePerson { + private String firstName; + private String lastName; + + private List emails; + + public MutablePerson() { + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public List getEmails() { + return emails; + } + + public void setEmails(List emails) { + this.emails = emails; + } + + @Override + public String toString() { + return "MutablePerson{" + + "firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", emails=" + emails + + '}'; + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/mutable/MutablePerson_2.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/mutable/MutablePerson_2.java new file mode 100644 index 0000000..2a5644d --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/mutable/MutablePerson_2.java @@ -0,0 +1,48 @@ +package com.platzi.functional_teacher_theory._03_immutable.mutable; + +import java.util.List; + +/** + * Clase mejorada. + * Ahora obligamos a quien use esta clase a crear instancias usando el constructor. + * Quitamos el setter para evitar que hagan modificaciones peligrosas… + */ +public class MutablePerson_2 { + private String firstName; + private String lastName; + + private List emails; + + public MutablePerson_2(List emails) { + this.emails = emails; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public List getEmails() { + return emails; + } + + @Override + public String toString() { + return "MutablePerson_2{" + + "firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", emails=" + emails + + '}'; + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/mutable/MutablePerson_3.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/mutable/MutablePerson_3.java new file mode 100644 index 0000000..7a44887 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/mutable/MutablePerson_3.java @@ -0,0 +1,49 @@ +package com.platzi.functional_teacher_theory._03_immutable.mutable; + +import java.util.List; + +/** + * Mas mejoras. Ahora nuestra lista de emails es final. Eso nos garantiza que nadie sobre escribe + * el valor de la propiedad y una vez creado siempre sera la misma lista. + * + * Eso deberia resolver el problema… cierto? + */ +public class MutablePerson_3 { + private String firstName; + private String lastName; + + private final List emails; + + public MutablePerson_3(List emails) { + this.emails = emails; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public List getEmails() { + return emails; + } + + @Override + public String toString() { + return "MutablePerson_3{" + + "firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", emails=" + emails + + '}'; + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/mutable/MutablePerson_4.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/mutable/MutablePerson_4.java new file mode 100644 index 0000000..11e9bb5 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/mutable/MutablePerson_4.java @@ -0,0 +1,26 @@ +package com.platzi.functional_teacher_theory._03_immutable.mutable; + +import java.util.LinkedList; +import java.util.List; + +/** + * Alguien decide que puede extender de nuestra clase y cambiar solo algunos + * elementos… ¡nos hackea! + * + * De nada sirvieron las modificaciones en la clase MutablePerson_3, esta clase nos + * esta suplantando! + */ +public class MutablePerson_4 extends MutablePerson_3 { + public MutablePerson_4(List emails) { + super(emails); + } + + @Override + public List getEmails() { + List spammyEmails = new LinkedList<>(); + spammyEmails.add("tubanco@mibanco.banco.com"); + spammyEmails.add("cheapfoods@blackmarket.com"); + + return spammyEmails; + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/mutable/Outsider.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/mutable/Outsider.java new file mode 100644 index 0000000..33f2205 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_03_immutable/mutable/Outsider.java @@ -0,0 +1,87 @@ +package com.platzi.functional_teacher_theory._03_immutable.mutable; + +import java.util.LinkedList; +import java.util.List; + +public class Outsider { + public static void main(String[] args) { + List sierEmail = new LinkedList<>(); + sierEmail.add("sier@sier.com"); + + MutablePerson sier = new MutablePerson(); + sier.setEmails(sierEmail); + sier.setFirstName("Israel"); + sier.setFirstName("Sergio"); + + + //Veamos un poco los peligros de una clase mutable + System.out.println(sier); + badFunction(sier); + System.out.println(sier); + + + System.out.println("///////////////////////////////"); + + + MutablePerson_2 sinuhe = new MutablePerson_2(sierEmail); + System.out.println(sinuhe); + otherBadFunction(sinuhe); + System.out.println(sinuhe); + + + System.out.println("///////////////////////////////"); + + + MutablePerson_3 sinuhe_3 = new MutablePerson_3(sierEmail); + System.out.println(sinuhe_3); + otherBadFunctionPart3(sinuhe_3); + System.out.println(sinuhe_3); + + + + + + System.out.println("///////////////////////////////"); + + + + + + MutablePerson_3 sinuhe_4 = new MutablePerson_4(sierEmail); + System.out.println(sinuhe_4); + } + + /** + * Este metodo modifica la lista mediante un setter. + * Tener el setter es peligroso… + */ + static void badFunction(MutablePerson person) { + List emails = new LinkedList<>(); + emails.add("imnotevil@mail.com"); + + person.setEmails(emails); + } + + /** + * Este metodo toma el objeto devuelto por el getter… pero el + * objeto es mutable, asi que podemos modificarlo sin restricciones… + */ + static void otherBadFunction(MutablePerson_2 person) { + List emails = person.getEmails(); + emails.clear(); + + emails.add("imnotevil@mail.com"); + } + + static void otherBadFunctionPart3(MutablePerson_3 person) { + List spammyEmails = new LinkedList<>(); + spammyEmails.add("tubanco@mibanco.banco.com"); + spammyEmails.add("cheapfoods@blackmarket.com"); + + List emails = person.getEmails(); + emails.clear(); + + emails.add("imnotevil@mail.com"); + emails.addAll(spammyEmails); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/CLIArguments.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/CLIArguments.java new file mode 100644 index 0000000..0ed99f5 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/CLIArguments.java @@ -0,0 +1,9 @@ +package com.platzi.functional_teacher_theory._04_functional; + +public class CLIArguments { + private boolean isHelp; + + public boolean isHelp() { + return isHelp; + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/CLIArgumentsUtils.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/CLIArgumentsUtils.java new file mode 100644 index 0000000..260bb98 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/CLIArgumentsUtils.java @@ -0,0 +1,27 @@ +package com.platzi.functional_teacher_theory._04_functional; + +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class CLIArgumentsUtils { + static void showHelp(CLIArguments cliArguments) { + Consumer consumerHelper = cliArguments1 -> { + if (cliArguments1.isHelp()) { + System.out.println("Manual solicitado"); + } + }; + + consumerHelper.accept(cliArguments); + } + + static CLIArguments generateCLI() { + Supplier generator = () -> new CLIArguments(); + + return generator.get(); + } + + static void multiplicacion() { + BiFunction multi = (x, y) -> x * y; + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/MathFunctions.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/MathFunctions.java new file mode 100644 index 0000000..a19a306 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/MathFunctions.java @@ -0,0 +1,41 @@ +package com.platzi.functional_teacher_theory._04_functional; + +import java.util.function.Function; +import java.util.function.Predicate; + +public class MathFunctions { + public static void main(String[] args) { + Function squareFunction = + new Function() { + @Override + public Integer apply(Integer x) { + return x * x; + } + }; + System.out.println(squareFunction.apply(5)); + System.out.println(squareFunction.apply(25)); + + Function isOdd = x -> x % 2 == 1; + + Predicate isEven = x -> x % 2 == 0; + + isEven.test(4); //true + + Predicate isApproved = student -> student.getCalificacion() >= 6.0; + + Student sinuhe = new Student(5.9); + System.out.println(isApproved.test(sinuhe)); + } + + static class Student { + private double calificacion; + + public Student(double calificacion){ + this.calificacion = calificacion; + } + + public double getCalificacion() { + return calificacion; + } + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/StringFunctions.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/StringFunctions.java new file mode 100644 index 0000000..eb50267 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/StringFunctions.java @@ -0,0 +1,19 @@ +package com.platzi.functional_teacher_theory._04_functional; + +import java.util.function.BiFunction; +import java.util.function.UnaryOperator; + +public class StringFunctions { + public static void main(String[] args) { + UnaryOperator quote = text -> "\"" + text + "\""; + UnaryOperator addMark = text -> text + "!"; + System.out.println(quote.apply("Hola Estudiante de platzi!")); + + System.out.println(addMark.apply("Hola")); + + BiFunction leftPad = + (text, number) -> String.format("%" + number + "s", text); + + System.out.println(leftPad.apply("Java", 10)); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/_01_FunctionDemo.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/_01_FunctionDemo.java new file mode 100644 index 0000000..683c112 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/_01_FunctionDemo.java @@ -0,0 +1,329 @@ +package com.platzi.functional_teacher_theory._04_functional; + +import com.platzi.functional_teacher_theory.util.Utils; + +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; + +public class _01_FunctionDemo { + + + /** + * La interface Function trabaja sobre generics, siendo el primero el tipo de entrada + * y el segundo el resultado de ejecutar esa funcion. + */ + private static void metodoDeEjemplo() { + //Podemos crear instancias bajo demanda… + Function someFun = new Function() { + /** + * El unico metodo que hay que implementar es el metodo apply. + * Este metodo tomara el TYPE y debera generar un RESULT + */ + @Override + public RESULT apply(TYPE t) { + return null; + } + }; + + //Y luego invocar el metodo apply con parametros del tipo correspondiente… + someFun.apply(null); + } + + + /** + * Veamos un ejemplo simple… una funcion que nos retorna si un numero es par + */ + private static void functionExample() { + Function isEven = new Function() { + @Override + public Boolean apply(Integer integer) { + return integer % 2 == 0; + } + }; + + isEven.apply(2); // True + isEven.apply(200); // True + isEven.apply(18); // True + + isEven.apply(25); // False + isEven.apply(31); // False + isEven.apply(191); // False + } +// + + + // +// El proceso es muy similar a tener definidos metodos o instancias anonimas como al ordenar numeros. + private static void sortNumbers(List numbers) { + //Aqui generamos una instancia anonima de comparator + numbers.sort(new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return o1 - o2; + } + }); + } +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + + /** + * Tambien podemos crear clases mas complejas que implementen esta interface… + */ + class FunctionBy2 implements Function { + @Override + public Integer apply(Integer x) { + return x * 2; + } + } + + /** + * Y despues usar esas instancias… + * hasta ahora nada nuevo o realmente funcional… + * en realidad esto es solo lo que ya conocemos de usar interfaces y OOP. + */ + private static void useBy2(FunctionBy2 by2) { + int y = by2.apply(5); + } + +// +// + +// +// +// + +// +// +// +// +// +// +// +// +// +// +// +// +// +// + + /** + * Lo interesante viene cuando entendemos que una funcion es un tipo… + * (mantengamonos con funciones y enteros por ahora) + */ + private static void functionsAreTypes() { + /* + Si una funcion es un tipo, tambien podemos usarla con generics. + Por ejemplo, devolver una funcion como resultado de ejecutar una funcion… + + Es algo parecido a obtener una sublista de una lista: + ->> List sub = list.sublist(0, 5); + */ + Function> multiply = + new Function>() { + @Override + public Function apply(Integer x) { + //Creamos una nueva funcion y la retornamos + return new Function() { + @Override + public Integer apply(Integer y) { + return x * y; + } + }; + } + }; + + //Usando nuestra nueva funcion… + multiply.apply(5).apply(4); // Resultado: 20 + multiply.apply(15).apply(3); // Resultado: 45 + multiply.apply(2).apply(4); // Resultado: 8 + multiply.apply(9).apply(7); // Resultado: 63 + + //O podemos crear funciones derivados de la primer funcion: + Function multiplyBy3 = multiply.apply(3); + + multiplyBy3.apply(2); //6 + multiplyBy3.apply(13); //39 + multiplyBy3.apply(9); //27 + } + +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + + /** + * Pero la sintaxis de crear instancias de interfaces es demasiado extensa y complicada de leer… + * usemos la nueva sintaxis de java 8 para funciones + * (explicaremos todas las sintaxis y las diferencias mas adelante) + */ + private static void syntaxFixing() { + + Function multiplyBy5 = x -> x * 5; + + //Con esto nos ahorramos crear una clase que implemente la interfaz + //Crear funciones se hace mas simple… + multiplyBy5.apply(10); //Resulta en: 50 + + //Incluso para funciones que retornan funciones (high order functions recuerdas?)… + Function> multiplyXByY = + x -> + y -> x * y; + + //Sabemos que al mandar a llamar al metodo apply obtendremos una nueva funcion… + Function multiply2ByY = multiplyXByY.apply(2); + + multiply2ByY.apply(7); // Nos resultara en 14 + + //O podemos seguir llamando al resultado directamente + multiplyXByY.apply(9).apply(8); //72 + } + +// +// +// +// +// +// +// +// +// +// +// +// +// +// + +// +// +// +// +// +// +// +// + + /** + * Lo bueno empieza no cuando las definimos… + * empieza cuando las empezamos a pasar como parametros y recibir como resultados… + */ + private static void funWithFuns() { + List myNumbers = Utils.getListOf(1, 2, 3, 4, 5, 6); + + //Funcion que eleva un numero al cuadrado + Function square = x -> x * x; + + //Funcion que eleva un numero al cubo + Function cube = x -> x * x * x; + + //Funcion que convierte un entero en negativo + Function toNegative = x -> -1 * x; + + applyMathToList(myNumbers, square); // [1, 4, 9, 16, 25, 36] + applyMathToList(myNumbers, cube); // [1, 8, 27, 64, 125, 216] + applyMathToList(myNumbers, toNegative); // [-1, -2, -3, -4, -5, -6] + } + +// +// +// + + /** + * Podemos decir que algunos metodos son funciones, en este caso, este metodo es una + * funcion de orden mayor que toma como parametro otra funcion y la usa para operar + * sobre la lista. + */ + private static List applyMathToList( + List items, + Function operation + ) { + List resultItems = new LinkedList<>(); + for (Integer x : items) { + resultItems.add(operation.apply(x)); + } + return resultItems; + } + + +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + + /** + * Incluso podriamos hacer cosas mas interesantes como aplicar muchas funciones sobre un dato + */ + private static String transformText(String text) { + List> transformations = new LinkedList<>(); + + transformations.add(s -> s.toUpperCase()); + transformations.add(s -> s.replaceAll("SI", "TI")); + transformations.add(s -> s.replaceAll("RO", "YO")); + transformations.add(s -> s.replaceAll("O", "OoO")); + + String result = text; + for (Function function : transformations) { + result = function.apply(result); + } + return result; + } + + + private static void runTransform() { + System.out.println(transformText("Hello")); //HELLOoO + System.out.println(transformText("World")); //WOoORLD + System.out.println(transformText("Claro que si roncas")); //CLAYOoO QUE TI YOoONCAS + } + + public static void main(String[] args) { + runTransform(); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/_02_PredicatesDemos.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/_02_PredicatesDemos.java new file mode 100644 index 0000000..67655cd --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/_02_PredicatesDemos.java @@ -0,0 +1,42 @@ +package com.platzi.functional_teacher_theory._04_functional; + +import java.util.function.*; + +public class _02_PredicatesDemos { + /** + * Pero definir funciones no lo es t o d o, tambien podemos definir otro tipo de funciones + * que nos sirvan para definir criterios y validaciones: Predicados + */ + private static void checkPassword(String password) { + Predicate isAllWhite = s -> s.trim().isEmpty(); + Predicate hasGoodLength = s -> s.length() > 8; + Predicate hasAtLeastOneNumber = s -> s.matches("\\d+"); + Predicate hasAnUpperCaseLetter = s -> s.matches("[A-Z]+"); + + Predicate isAValidPassword = s -> + !isAllWhite.test(s) + && hasGoodLength.test(s) + && hasAtLeastOneNumber.test(s) + && hasAnUpperCaseLetter.test(s); + + isAValidPassword.test(password); + } + + /** + * La interfaz Predicate te ayuda con las validaciones de objetos complejos, + * para algunos datos ya definidos en el lenguaje, existen sus equivalentes + */ + private static void validations() { + IntPredicate intPredicate; + DoublePredicate doublePredicate; + LongPredicate longPredicate; + + /* + Y si necesitas hacer alguna validacion mas compleja de dos elementos como + comparar que un dato sea mas grande que otro + */ + BiPredicate isXLargerThanY = (x, y) -> x.length() > y.length(); + + isXLargerThanY.test("Lobo", "Perrito"); // False + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/_03_ConsumerSupplierDemos.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/_03_ConsumerSupplierDemos.java new file mode 100644 index 0000000..dce26c6 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/_03_ConsumerSupplierDemos.java @@ -0,0 +1,110 @@ +package com.platzi.functional_teacher_theory._04_functional; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public class _03_ConsumerSupplierDemos { + /** + * Con un Consumer podemos generar una funcion simple que toma un dato y no regresa ningun valor + * El ejemplo mas comun es tomar un valor e imprimirlo por pantalla, o en nuestro ejemplo + * tomar un dato y almacenarlo en un archivo y una base de datos. + */ + private static void persistAccount(Account account) { + Consumer toFile = acnt -> saveToFile(acnt); + + Consumer toDB = acnt -> getDataBaseConnection().insert(acnt); + + //saveAccountTo no le interesa lo que el Consumer haga, simplemente necesita + //una manera de pasarle el dato para almacenarlo. + saveAccountTo(account, toFile); + saveAccountTo(account, toDB); + } + + private static void saveAccountTo(Account account, Consumer saveFunction) { + //Ejecutar validaciones antes de almacenar + //… + // + //Despues almacenamos sin preocuparnos como + saveFunction.accept(account); + } + + // +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + + /** + * Un Supplier es una funcion que sabe como conseguir datos. + * Este tipo de funciones se utiliza principalmente cuando tenemos secuencias + * o requerimos de algun proceso mas que un simple constructor para generar un dato. + *

+ * No existe una regla o condicion de si el Supplier debe generar un nuevo objeto + * o si puede devolver siempre el mismo o generar alguno de manera aleatoria. + */ + private void supplierDemo() { + Supplier numberSupplier = () -> Math.random(); + + Supplier dbSupplier = () -> getDataBaseConnection(); + + Function dbFunction = sId -> dbSupplier.get().select(sId); + + //La principal razon para usar Suppliers es crear funciones que no sepan la procedencia + //de los datos. En la programacion funcional debes preocuparte mas por el "como" hacer algo + //no por el "que" debe hacerse con algo. + + //Adicionalmente, un supplier al ser un objeto, puede ser usado en diferentes lados + //manteniendo solo la referencia. + } +// +// +// +// +// +// +// +// +// +// +// +// +// +// + + + private static void saveToFile(Account account) { + + } + + private static DataBaseExecutor getDataBaseConnection() { + return null; + } + + private class Account { + private final String id; + private final double balance; + + public Account(String id, double balance) { + this.id = id; + this.balance = balance; + } + } + + private interface DataBaseExecutor { + void insert(T instance); + + T select(String id); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/_04_OperatorsAndBiFunctions.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/_04_OperatorsAndBiFunctions.java new file mode 100644 index 0000000..5182970 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_04_functional/_04_OperatorsAndBiFunctions.java @@ -0,0 +1,65 @@ +package com.platzi.functional_teacher_theory._04_functional; + +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; +import java.util.function.UnaryOperator; + +public class _04_OperatorsAndBiFunctions { + + /** + * Existen algunas simplificaciones de Funciones que te ayudan a entender mas + * directamente que hacen o sobre que trabajan, un ejemplo es UnaryOperator + */ + private static void unaries() { + //Aunque esta funcion luce muy parecida a una que ya usamos previamente, + //el tenerla como UnaryOperator nos deja en claro que es una operacion que + //trabaja sobre un tipo y nos retorna el mismo tipo. + UnaryOperator square = x -> x * x; + + UnaryOperator quote = s -> "\"" + s + "\""; + + quote.apply("Programming is the art of doing one thing at a time"); + } + + +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + + /** + * Pero a veces, las operaciones simples como una suma requieren de dos parametros… + * como podrias definir una funcion que toma dos parametros sin complicarte la vida? + * BiFunction! + */ + public static void twiceTheFun() { + BiFunction sum = (x, y) -> x + y; + + BiFunction leftPad = (s, i) -> String.format("%" + i + "s", s); + + System.out.println( + leftPad.apply("Hello", 10) // " Hello" + ); + + + //Pero si los parametros son del mismo tipo y el resultado es del mismo tipo, + //podemos usar operators! + BinaryOperator multiply = (x, y) -> x * y; + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_05_sam/AgeUtils.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_05_sam/AgeUtils.java new file mode 100644 index 0000000..d5f094f --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_05_sam/AgeUtils.java @@ -0,0 +1,27 @@ +package com.platzi.functional_teacher_theory._05_sam; + +import java.time.LocalDate; +import java.time.Period; +import java.util.function.Function; + +public class AgeUtils { + public static void main(String[] args) { + Function addCeros = x -> x < 10 ? "0" + x : String.valueOf(x); + + TriFunction parseDate = + (day, month, year) -> LocalDate.parse(year + "-" + + addCeros.apply(month) + "-" + addCeros.apply(day)); + + TriFunction calculateAge = + (day, month, year) -> Period.between( + parseDate.apply(day, month, year), LocalDate.now() + ).getYears(); + + System.out.println(calculateAge.apply(10, 10, 1992)); + } + + @FunctionalInterface + interface TriFunction { + R apply(T t, U u, V v); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_05_sam/SingleAbstractMethod.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_05_sam/SingleAbstractMethod.java new file mode 100644 index 0000000..3c76719 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_05_sam/SingleAbstractMethod.java @@ -0,0 +1,116 @@ +package com.platzi.functional_teacher_theory._05_sam; + +import java.util.List; +import java.util.Scanner; + +public class SingleAbstractMethod { + /** + * Se le considera SAM a una interface cuando tiene un unico metodo, sin valor definido. + * Por ejemplo: + */ + interface SAMInterface { + int doSomething(); + } + + /** + * Si la interface tiene mas de un metodo sin implementacion, no es considerada una SAM + */ + interface NotASAMInterface { + int doFoo(); + + void doBar(); + } + +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + + /** + * Este concepto es importante empezando en Java 8 por varias razones, la principal + * es que el compilador nos permitira usar una anotacion disponible UNICAMENTE para + * SAMs: @FunctionalInterface + */ + @FunctionalInterface + interface AnotherSAM { + String getText(); + } + + /** + * De nuevo, esto solo funciona en interfaces de tipo SAM + */ +// @FunctionalInterface + interface NotAValidSAM { + String getText(); + + String getSubText(); + } + + // +// +// +// +// +// +// +// +// +// +// +// +// + @FunctionalInterface + interface MySAMInterfaceIsAlsoAFunction { + String someMethod(int x); + } + + + /** + * Con las interfaces anotadas, podemos usar la forma de lambdas/funciones para definir + * el comportamiento + */ + private static void fooSAM() { + MySAMInterfaceIsAlsoAFunction myFunction = x -> "Result: " + x; + } + +// +// +// +// +// +// +// +// +// +// +// +// +// + + /** + * Incluso en interfaces con muchos parametros + */ + @FunctionalInterface + interface OverComplicatedSAM { + int someWeirdNameForAMethod(String s, int x, Scanner sc, List values); + } + + public static void somethingCalling() { + OverComplicatedSAM stillAFunction = (s, x, sc, list) -> 0; + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_06_reference_operator/NombresUtils.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_06_reference_operator/NombresUtils.java new file mode 100644 index 0000000..d9e9e16 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_06_reference_operator/NombresUtils.java @@ -0,0 +1,21 @@ +package com.platzi.functional_teacher_theory._06_reference_operator; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + +public class NombresUtils { + public static void main(String[] args) { + List profesores = getList("Nicolas", "Juan", "Zulema"); + + Consumer printer = System.out::println; + profesores.forEach(printer); + System.out.println("//////////"); + profesores.forEach(System.out::println); + } + + @SafeVarargs + public static List getList(T... elements) { + return Arrays.asList(elements); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_06_reference_operator/_01_Operator.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_06_reference_operator/_01_Operator.java new file mode 100644 index 0000000..a574dee --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_06_reference_operator/_01_Operator.java @@ -0,0 +1,233 @@ +package com.platzi.functional_teacher_theory._06_reference_operator; + +import com.platzi.functional_teacher_theory.util.Utils; + +import java.util.LinkedList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +public class _01_Operator { + /** + * Un pequeño ejemplo de como el operador :: funciona + */ + public static void main(String[] args) { + List names = new LinkedList<>(); + names.add("Fer"); + names.add("Orly"); + names.add("Sier"); + names.add("Chris"); + names.add("Eryx"); + + //Procesamos en una lambda + names.forEach(s -> System.out.println(s)); + +// +// +// +// +// + + //Pero sabemos algunas cosas de este punto: + /* + 1. forEach toma un Consumer + 2. System.out.println(Object obj) es un metodo que toma un elemento y no tiene retorno + 3. Los Consumer son funciones que toman un elemento y no retornan un resultado… + + … Entonces … + + System.out.println puede funcionar como un Consumer! + */ + +// +// +// +// +// +// + + //En efecto… + names.forEach(System.out::println); + +// +// +// +// +// +// +/// +// + + //Pero no solo eso, la cuestion esta en que toda funcion cuya definicon sea: + // + // void nombre(Type type) + // + // puede ser usada siempre que el tipo de parametro sea el mismo. + names.forEach(_01_Operator::coolStuffWithAString); + } + +// +// +// +// +// +// +// +// +// +// +// +// + + private static void coolStuffWithAString(String str) { + System.out.println( + str.toUpperCase() + .trim() + .replaceAll("S", "Z") + .replaceAll("i", "i1Ii") + ); + } + +// +// +// +// +// +// +// +// +// +// +// +// + + /** + * Hasta ahora hemos usado unicamente funciones estaticas pero existen algunos casos + * en los que quisieramos usar un metodo de un objeto… porque tal vez el objeto ya + * tiene un valor para operar, tiene alguna propiedad que puede ayudarnos en la operacion, + * metodos que puedan ayudar a procesar los datos, etc. + */ + private void weirdStuff() { + //Podemos usar directamente el paso de una funcion + giveMeAFunction(stringParam -> stringParam.length()); + + //O usar una referencia a un metodo… + //Lo interesante aqui es que Java se encarga de entender que `::length` va a + //corresponder con el metodo ::length de lo que arriba definimos como `stringParam` + //Es decir… + //Java hace la conexion entre el objeto en memoria y hace la invocacion de su metodo + //con el contexto del objeto en memoria… + giveMeAFunction(String::length); +// +// +// +// +// +// +// +// +// +// +// +// +// +// + + //Otro ejemplo de ello, es si nosotros tenemos un objeto totalmente ajeno + //a las operaciones, pero este objeto tiene un metodo cuya definicion coincide + //con la definicion de la funcion que necesitamos: + + /* + La clase tiene definido este metodo que recibe un string y retorna un int. + + + class HelperOperator { + public int sayNameAge(String s){…} + … + } + + + La funcion que tenemos que pasar, justamente, recibe un string y retorna un int… + + giveMeAFunction(Function function) + + + El match perfecto! + */ + HelperOperator sier = new HelperOperator("Sier"); + giveMeAFunction(sier::sayNameAge); + } + +// +// +// +// +// + + private class HelperOperator { + private String name; + + public HelperOperator(String name) { + this.name = name; + } + + public int sayNameAge(String s) { + System.out.println("My name is " + name); + return s.length(); + } + } + + +// +// +// +// +// +// +// +// +// +// +// + + private static void giveMeAFunction(Function function) { + function.apply("Hello"); + } + +// +// +// +// +// +// +// + private static void howItWorks(){ + List names = Utils.getListOf("Fer", "Orly", "Sinuhe", "Ana"); + + + + /* + No solo es posible referenciar metodos de objetos o metodos estaticos, + el operador :: en realidad se encarga de crear objetos sin que nosotros los + tengamos que definir. + + Es por eso que podemos asignar el resultado del operador en variables: + */ + Consumer printer = _01_Operator::coolStuffWithAString; + + + + + //Esto nos puede reducir las definiciones + Consumer outPrinter = s -> System.out.println(s); + //Exactamente lo mismo que la linea de arriba + Consumer systemPrinter = System.out::println; + + + + //Todas las invocaciones son valid + names.forEach(printer); + names.forEach(outPrinter); + names.forEach(systemPrinter); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_07_inference/Inference.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_07_inference/Inference.java new file mode 100644 index 0000000..a2ee4fa --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_07_inference/Inference.java @@ -0,0 +1,102 @@ +package com.platzi.functional_teacher_theory._07_inference; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Stream; + +public class Inference { + private static void typeInference() { + List numbersList = getNumbers(); + + /* + La parte interesante de la inferencia de tipos es que + Java puede identificar de una funcion que tipo de parametro y retorno tiene y + con ello darnos acceso a ese tipo en la siguiente funcion. + */ + getOperable(numbersList) + .filter(n -> n % 10 == 0) + .map(n -> { + char[] repeater = new char[n]; + Arrays.fill(repeater, '!'); + return repeater; + }) + .map(String::new) + .forEach(System.out::println); + + + + + + + + + + + + + /* + Sin la inferencia el codigo de arriba se veria muchisimo mas largo y menos declarativo + + Aunque estos factores no son malos, en muchas ocasiones restan legibilidad en el codigo. + */ + getOperable(numbersList) + .filter((Integer n) -> n % 10 == 0) + .map((Integer n) -> { + char[] repeater = new char[n]; + Arrays.fill(repeater, '!'); + return repeater; + }) + .map((char[] chars) -> new String(chars)) + .forEach((String str) -> System.out.println(str)); + } + + + + + + + + + + + + + + private static List getNumbers(){ + List numbers = new LinkedList<>(); + numbers.add(1); + numbers.add(10); + numbers.add(11); + numbers.add(100); + numbers.add(1001); + numbers.add(1010); + numbers.add(1100); + numbers.add(1111); + + return numbers; + } + + + + + + + + + + + + + + + + + + + + + private static Stream getOperable(List list) { + return list.stream(); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_07_inference/Inferencia.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_07_inference/Inferencia.java new file mode 100644 index 0000000..62cbd70 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_07_inference/Inferencia.java @@ -0,0 +1,18 @@ +package com.platzi.functional_teacher_theory._07_inference; + +import com.platzi.functional_teacher_theory._06_reference_operator.NombresUtils; + +import java.util.List; +import java.util.function.Function; + +public class Inferencia { + public static void main(String[] args) { + Function funcionConvertidora = + x -> "Al doble:" + (x * 2) ; + + List alumnos = NombresUtils.getList("Hugo", "Paco", "Luis"); + alumnos.forEach((String name) -> System.out.println(name)); + alumnos.forEach(name -> System.out.println(name)); + alumnos.forEach(System.out::println); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_08_lambda/Sintaxis.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_08_lambda/Sintaxis.java new file mode 100644 index 0000000..960e401 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_08_lambda/Sintaxis.java @@ -0,0 +1,58 @@ +package com.platzi.functional_teacher_theory._08_lambda; + +import com.platzi.functional_teacher_theory._06_reference_operator.NombresUtils; + +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Predicate; + +public class Sintaxis { + public static void main(String[] args) { + List cursos = NombresUtils.getList("Java", "Functional"); + + cursos.forEach(curso -> System.out.println(curso)); + + usarZero(() -> 2); + + usarPredicado(text -> text.isEmpty()); + + usarBiFunction((x, y) -> x * y); + + usarBiFunction((x, y) -> { + System.out.println("X: " + x + ", Y:" + y); + return x - y; + }); + + usarNada(() -> { + System.out.println("Hola Alumno"); + }); + + usarBiFunction((Integer x, Integer y) -> x * y); + } + + static void usarZero(ZeroArgumentos zeroArgumentos) { + + } + + static void usarPredicado(Predicate predicado) { + + } + + static void usarBiFunction(BiFunction operacion) { + + } + + static void usarNada(OperarNada operarNada) { + + } + + @FunctionalInterface + interface ZeroArgumentos { + int get(); + } + + @FunctionalInterface + interface OperarNada { + void nada(); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_08_lambda/Syntax.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_08_lambda/Syntax.java new file mode 100644 index 0000000..9fb4203 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_08_lambda/Syntax.java @@ -0,0 +1,111 @@ +package com.platzi.functional_teacher_theory._08_lambda; + +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public class Syntax { + private static void sintaxis() { + //Una funcion puede ser definida de la siguiente manera: + Function funFunction = s -> ""; + + + + + + + + + + + + + + //Y como vimos en la leccion anterior, si queremos ser muy especificos con el tipo: + Function boringFunction = (String s) -> ""; + + + + + + + + + + + + + //Tambien hemos mencionado que si nuestra funcion es muy extensa podemos usar {} + Function notFun = s -> { + System.out.println("Ejecutando… 1"); + System.out.println("Ejecutando… 2"); + System.out.println("Ejecutando… 3"); + return ""; + }; + + + + + + + + + + + + + + + //La variante empieza cuando tenemos mas de un parametro, pues nos vemos obligados + //a agrupar los parametros entre parentesis. + BiFunction biFunFunction = (xs, s) -> 0; + + //Incluso si ponemos el tipo explisito + BiFunction notFunBiFunction = (String xs, String s) -> 0; + + + + + + + + + + + + + + + + + + + + + + //Si nuestra funcion (un Consumer tambien es una funcion) no hace nada, + //Java nos exige que usemos { } como cuerpo de nuestra funcion. + Consumer consumer = s -> { }; + + + + + + + + + + + + + + + + + + //Y si nuestra funcion no toma parametros… + //Java nos exige que usemos () para indicar la ausencia de los mismos: + Supplier textSupplier = () -> "Hello Platzi"; + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_09_defaults/Defaults.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_09_defaults/Defaults.java new file mode 100644 index 0000000..89d92d2 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_09_defaults/Defaults.java @@ -0,0 +1,86 @@ +package com.platzi.functional_teacher_theory._09_defaults; + +import java.util.Date; + +public class Defaults { + @FunctionalInterface + interface Operational { + int operate(String s); + + /** + * Es posible usar metodos default en nuestras interfaces funcionales. + * Con estos metodos la intencion es definir operaciones pero delegar parte + * de la logica a otros. + */ + default void repeat(String s) { + int times = operate(s); + while (times-- > 0) { + System.out.println(s); + } + } + } + +// +// +// +// +// +// +// +// +// + + /** + * En este ejemplo, asignamos una funcion a Operational y el metodo repeat usa esta para + * determinar el numero de veces que debe repetir un texto. + */ + private static void repeatUsingDefaults(String str) { + Operational operational = String::length; + operational.repeat(str); + + System.out.println("<------------->"); + + Operational twice = s -> 2; + twice.repeat(str); + } + +// +// +// +// +// +// +// +// +// +// +// + + /** + * Como nota final, cabe mencionar que comenzando con Java 8 las interfaces pueden tener + * metodos estaticos: + */ + @FunctionalInterface + interface DayConter { + int countDays(Date startDate, Date endDate); + + /** + * Esto puede ser de utilidad si queremos mantener una funcion como functional interface pero + * mantener logica ligada a esa interfaz. + */ + static boolean isLeapYear(int year) { + if (year % 4 == 0) { + if (year % 400 == 0) return true; + + return year % 100 != 0; + } + return false; + } + } + + public static void main(String[] args) { + repeatUsingDefaults("Perrito"); + System.out.println(""); + repeatUsingDefaults("dog"); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_09_defaults/StringFunctions.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_09_defaults/StringFunctions.java new file mode 100644 index 0000000..c3db87a --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_09_defaults/StringFunctions.java @@ -0,0 +1,34 @@ +package com.platzi.functional_teacher_theory._09_defaults; + +public class StringFunctions { + @FunctionalInterface + interface StringOperation { + int getAmount(); + + default void operate(String text) { + int x = getAmount(); + while(x-- > 0){ + System.out.println(text); + } + } + } + + @FunctionalInterface + interface DoOperation { + void take(String text); + + default void execute(int x, String text){ + while(x-- > 0){ + take(text); + } + } + } + + public static void main(String[] args) { + StringOperation six = () -> 6; + six.operate("Alumno"); + + DoOperation operateFive = text -> System.out.println(text); + operateFive.execute(5,"Platzi"); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/Chaining.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/Chaining.java new file mode 100644 index 0000000..f378e7b --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/Chaining.java @@ -0,0 +1,82 @@ +package com.platzi.functional_teacher_theory._10_chaining; + +import com.platzi.functional_teacher_theory._10_chaining.builder.BuilderNoChaining; +import com.platzi.functional_teacher_theory._10_chaining.builder.BuilderWithChaining; +import com.platzi.functional_teacher_theory._10_chaining.data.Account; + +import java.util.function.Function; + +public class Chaining { + + /** + * Aunque el chaining no es algo exclusivo o a conocer de la programacion funcional, + * vale la pena mencionarlo, puesto que puede ser de ayuda al usar funciones de java o + * en temas futuros… + *

+ * Chaining es la estrategia de retornar siempre un objeto, tal que puedas invocar + * metodos con cada invocacion. + */ + static void examplesOfChaining() { + //Cuando no usas chaining tu codigo se ve aburrido y repetitivo + BuilderNoChaining builderNoChaining = new BuilderNoChaining(""); + builderNoChaining.withBalance(100.00); + builderNoChaining.withFirstName("Sinuhe"); + builderNoChaining.withLastName("Jaime"); + builderNoChaining.withPhone("+52155555555"); + + Account sinuheAccount = builderNoChaining.buildAccount(); + + //Pero con un buen chaining puedes hacer las cosas mas simples. + Account anotherAccount = + new BuilderWithChaining("1") + .withBalance(100.00) + .withFirstName("Sinuhe") + .withLastName("Jaime") + .withPhone("+52155555555") + .buildAccount(); + + //Es relativamente comun verlo en Strings: + char[] saludo = "Bye Platzi!".replaceAll("Bye", "Hola") + .toLowerCase() + .toCharArray(); + + //O como en el primer ejemplo, con builders… + StringBuilder builderJava = new StringBuilder() + .append("E").append("s").append("t").append("o").append("y") + .append(" ") + .append("A") + .append("p") + .append("r") + .append("e") + .append("n") + .append("d") + .append("i") + .append("e") + .append("n") + .append("d") + .append("o") + .append(" Java ") + .append(8); + + System.out.println(builderJava.toString()); + + + //La reelevancia en este curso, de esta estrategia, viene al crear funciones mas complejas + //o que se crean a partir de otras funciones. Posteriormente veremos que es de mucha utilidad + //con ciertos tipos de datos. + + //Por ejemplo, creamos una funcion que reemplace las "f" por asteriscos + Function replacer = s -> s.replaceAll("f", "*"); + + + Function, Function> replacerPlus = + f -> f.andThen(s -> s.replaceAll("a", "/")); + + System.out.println( + replacerPlus + .apply(replacer) + .andThen(s -> s.replaceFirst("Ho", "Mo")) + .apply("Hablando de chaining, esto Hola, fucho") + ); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/ChainingSimplified.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/ChainingSimplified.java new file mode 100644 index 0000000..f9917a7 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/ChainingSimplified.java @@ -0,0 +1,29 @@ +package com.platzi.functional_teacher_theory._10_chaining; + +public class ChainingSimplified { + public static void main(String[] args) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("Hola") + .append("alumno") + .append("de") + .append("platzi"); + + Chainer chainer = new Chainer(); + + Chainer chainer2 = chainer.sayHi(); + Chainer chainer3 = chainer2.sayBye(); + chainer.sayHi().sayBye(); + } + + static class Chainer { + public Chainer sayHi() { + System.out.println("Hola"); + return this; + } + + public Chainer sayBye() { + System.out.println("Bye"); + return this; + } + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/builder/BuilderNoChaining.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/builder/BuilderNoChaining.java new file mode 100644 index 0000000..b743338 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/builder/BuilderNoChaining.java @@ -0,0 +1,59 @@ +package com.platzi.functional_teacher_theory._10_chaining.builder; + +import com.platzi.functional_teacher_theory._10_chaining.data.Account; +import com.platzi.functional_teacher_theory._10_chaining.data.Phone; + +public class BuilderNoChaining { + private String id; + + private String firstName; + private String lastName; + + private double currentBalance; + + private Phone phone; + + public BuilderNoChaining(String id) { + checkStringField(id, "ID"); + this.id = id; + firstName = ""; + lastName = ""; + currentBalance = 0.0; + phone = null; + } + + public void withFirstName(String firstName) { + checkStringField(firstName, "First Name"); + this.firstName = firstName; + } + + public void withLastName(String lastName) { + checkStringField(lastName, "First Name"); + this.lastName = lastName; + } + + public void withBalance(double balance) { + currentBalance = balance; + } + + public void withPhone(String phone){ + checkStringField(phone, "Phone"); + this.phone = new Phone(phone); + } + + public Account buildAccount() { + return new Account( + id, + firstName, + lastName, + currentBalance, + phone + ); + } + + private void checkStringField(String field, String fieldName) { + if (field == null || field.length() == 0) { + throw new IllegalArgumentException(fieldName + " is not valid"); + } + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/builder/BuilderWithChaining.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/builder/BuilderWithChaining.java new file mode 100644 index 0000000..5324add --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/builder/BuilderWithChaining.java @@ -0,0 +1,63 @@ +package com.platzi.functional_teacher_theory._10_chaining.builder; + +import com.platzi.functional_teacher_theory._10_chaining.data.Account; +import com.platzi.functional_teacher_theory._10_chaining.data.Phone; + +public class BuilderWithChaining { + private String id; + + private String firstName; + private String lastName; + + private double currentBalance; + + private Phone phone; + + public BuilderWithChaining(String id) { + checkStringField(id, "ID"); + this.id = id; + firstName = ""; + lastName = ""; + currentBalance = 0.0; + phone = null; + } + + public BuilderWithChaining withFirstName(String firstName) { + checkStringField(firstName, "First Name"); + this.firstName = firstName; + return this; + } + + public BuilderWithChaining withLastName(String lastName) { + checkStringField(lastName, "First Name"); + this.lastName = lastName; + return this; + } + + public BuilderWithChaining withBalance(double balance) { + currentBalance = balance; + return this; + } + + public BuilderWithChaining withPhone(String phone) { + checkStringField(phone, "Phone"); + this.phone = new Phone(phone); + return this; + } + + public Account buildAccount() { + return new Account( + id, + firstName, + lastName, + currentBalance, + phone + ); + } + + private void checkStringField(String field, String fieldName) { + if (field == null || field.length() == 0) { + throw new IllegalArgumentException(fieldName + " is not valid"); + } + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/data/Account.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/data/Account.java new file mode 100644 index 0000000..d480c80 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/data/Account.java @@ -0,0 +1,40 @@ +package com.platzi.functional_teacher_theory._10_chaining.data; + +public class Account { + private String id; + + private String firstName; + private String lastName; + + private double currentBalance; + + private Phone phone; + + public Account(String id, String firstName, String lastName, double currentBalance, Phone phone) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.currentBalance = currentBalance; + this.phone = phone; + } + + public String getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public double getCurrentBalance() { + return currentBalance; + } + + public Phone getPhone() { + return phone; + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/data/Address.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/data/Address.java new file mode 100644 index 0000000..12f262f --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/data/Address.java @@ -0,0 +1,53 @@ +package com.platzi.functional_teacher_theory._10_chaining.data; + +public class Address { + private String street; + + private String interiorNumber; + + private String exteriorNumber; + + private String state; + + private String zip; + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getInteriorNumber() { + return interiorNumber; + } + + public void setInteriorNumber(String interiorNumber) { + this.interiorNumber = interiorNumber; + } + + public String getExteriorNumber() { + return exteriorNumber; + } + + public void setExteriorNumber(String exteriorNumber) { + this.exteriorNumber = exteriorNumber; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getZip() { + return zip; + } + + public void setZip(String zip) { + this.zip = zip; + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/data/Phone.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/data/Phone.java new file mode 100644 index 0000000..d52b780 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_10_chaining/data/Phone.java @@ -0,0 +1,40 @@ +package com.platzi.functional_teacher_theory._10_chaining.data; + +public class Phone { + private String country; + + private char[] countryCode; + + private char[] digits; + + public Phone() { + } + + public Phone(String phoneString) { + //TODO: Descomponer los valores de un string + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public char[] getCountryCode() { + return countryCode; + } + + public void setCountryCode(char[] countryCode) { + this.countryCode = countryCode; + } + + public char[] getDigits() { + return digits; + } + + public void setDigits(char[] digits) { + this.digits = digits; + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_11_composition/Composition.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_11_composition/Composition.java new file mode 100644 index 0000000..b0ac856 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_11_composition/Composition.java @@ -0,0 +1,100 @@ +package com.platzi.functional_teacher_theory._11_composition; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; +import java.util.function.UnaryOperator; + +public class Composition { + /** + * Dado que las funciones son tipos, podemos almacenarlas como datos en nuestra clase + */ + private static final Function createFile = File::new; //path -> new File(path) + + /** + * Las funciones pueden ser generadas a partir de una referencia. + */ + private static final Function> linesFromFile = Composition::getLinesFromFile; + + private static final UnaryOperator> filter = list -> { //Function, List> + List resultList = new LinkedList<>(); + list.forEach(s -> addIfNotEmpty(resultList, s)); + return resultList; + }; + + /** + * Creamos entonces una funcion simple, que toma un string y nos dara las lineas en ese archivo + * que tengan contenido. + *

+ * Las lineas sin caracteres no seran consideradas. + */ + static List getLinesWithContentCompose(String pathToFile) { + //Compose toma como parametro una funcion cuyo valor de retorno sea el valor de entrada + //de la funcion desde donde invocamos. + //En este caso: + // 1. `filter` necesita un `List` para poder operar. + // 2. `linesFromFile` genera ese `List` pero necesita un `File` para poder retornar la lista + // 3. `createFile` puede generar un archivo desde un String, que es el parametro de nuestro metodo! + // + // La funcion resultante de invocar a `compose` desde `filter` se ejecuta con el metodo `apply` + return filter + .compose(linesFromFile) + .compose(createFile) + .apply(pathToFile); + } + + /** + * Viendo paso a paso cada creacion de funciones que `compose` realiza. + */ + static List stepsGetLinesWithContentCompose(String pathToFile) { + Function> createFileAndGetLines = linesFromFile.compose(createFile); + + Function> createFileGetLinesFilter = filter.compose(createFileAndGetLines); + return createFileGetLinesFilter.apply(pathToFile); + + //Tambien podriamos haber ejecutado la primer funcion y ejecutar filter con el resultado: + //List lines = createFileAndGetLines.apply(pathToFile) + //return filter.apply(lines) + } + + /** + * versión con andThen() + */ + static List getLinesWithContent(String pathToFile) { + return createFile + .andThen(linesFromFile) + .andThen(filter) + .apply(pathToFile); + } + + static List stepsGetLinesWithContentAndThen(String pathToFile) { + Function> createFileAndGetLines = createFile.andThen(linesFromFile); + + Function> createFileGetLinesAndFilter = createFileAndGetLines.andThen(filter); + + return createFileGetLinesAndFilter.apply(pathToFile); + } + + public static void main(String[] args) { + String pathToFile = "C:/Users/Martin F - PC Desk/Desktop/file.txt"; // String pathToFile = "/path/to/file.extension" + System.out.println(getLinesWithContentCompose(pathToFile)); + } + + private static List getLinesFromFile(File file) { + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + return br.lines().toList(); + } catch (IOException fileNotFoundEx) { + fileNotFoundEx.printStackTrace(); + } + return Collections.emptyList(); + } + + private static void addIfNotEmpty(List list, String s) { + if (s != null && !s.trim().isEmpty()) list.add(s); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_11_composition/MathOperations2.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_11_composition/MathOperations2.java new file mode 100644 index 0000000..7b1ecf4 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_11_composition/MathOperations2.java @@ -0,0 +1,30 @@ +package com.platzi.functional_teacher_theory._11_composition; + +import java.util.function.Function; + +public class MathOperations2 { + public static void main(String[] args) { + Function multiplyBy3 = x -> { + System.out.println("Multiplicando x: " + x + " por 3"); + return x * 3; + }; + + Function add1MultiplyBy3 = + multiplyBy3.compose(y -> { + System.out.println("Le agregare 1 a: " + y); + return y + 1; + }); + + Function andSquare = + add1MultiplyBy3.andThen(x -> { + System.out.println("Estoy elevando " + x + " al cuadrado"); + return x * x; + }); + + System.out.println( + add1MultiplyBy3.apply(5) + ); + + System.out.println(andSquare.apply(3)); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_12_currying/Currying.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_12_currying/Currying.java new file mode 100644 index 0000000..c259523 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_12_currying/Currying.java @@ -0,0 +1,62 @@ +package com.platzi.functional_teacher_theory._12_currying; + +import java.util.function.Function; + +public class Currying { + /** + * Empecemos con algo que ya conocemos: una FunctionalInterface que toma 3 tipos de datos + * F, S, T y retorna un tipo de dato R cuando se manda a llamar su metodo apply. + *

+ * Hasta que no deberia haber nada nuevo. + */ + @FunctionalInterface + interface ThreeFunction { + R apply(F f, S s, T t); + } + + static void curryingExample() { + /* + Tener tres parametros puede hacer que nos confundamos en el orden mientras programamos. + Y tenemos un detalle, tal vez, por alguna razon, al momento de querer ejecutar esta funcion + no contamos con los 3 datos. + */ + ThreeFunction threeFunction = (i, s, d) -> ""; + threeFunction.apply(1, "", 0.0); + + /* + Que tal si pudieramos simplificar nuestra funcion? + + A reducir la complejidad de una funcion partiendola en subfunciones, se le conoce como currying. + + Currying es una manera de crear funciones mas dinamicas basados en al reduccion de parametros. + */ + Function>> curried = curryThree(threeFunction); + + curried.apply(1) + .apply("") + .apply(0.0); + } + + /** + * Esta funcion, se encarga de tomar una funcion compleja de 3 parametros y simplificarla a una funcion de un + * solo parametro. + *

+ * Desafortunadamente, los generics de java complican un poco su lectura. pero es relativamente sencillo entender + * lo que pasa: + *

+ * curryThree toma una funcion de tres parametros ThreeFunction y genera una version mas "simple". + * + * Esa version "simple" es una funcion que retorna otra funcion que retorna otra funcion. + * + * Es decir, tendremos 3 funciones que se pueden encadenar y generar el mismo resultado o ir ejecutando + * una funcion a la vez conservando un estado anterior. + * + * El beneficio es que ahora tenemos una funcion mas simple de un solo parametro y que podemos reutilizar + * para generar funciones "intermedias". + */ + static Function>> curryThree(ThreeFunction threeFunction) { + return first -> + second -> + third -> threeFunction.apply(first, second, third); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_13_partial_application/Partial.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_13_partial_application/Partial.java new file mode 100644 index 0000000..029e097 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_13_partial_application/Partial.java @@ -0,0 +1,173 @@ +package com.platzi.functional_teacher_theory._13_partial_application; + +import java.util.function.BiFunction; +import java.util.function.Function; + +public class Partial { + + static void partialApplication() { + /* + Usando currying podemos hacer uso de otra estrategia que se conoce como aplicacion parcial. + + Hacer uso de la aplicacion parcial es almacenar la version "curried" de una funcion pero + con un parametro. + + Tomemos una BiFunction por ejemplo: + */ + BiFunction multiplyXbyY = (x, y) -> x * y; + + //Es facil usarla: + System.out.println( + multiplyXbyY.apply(5, 4) //20 + ); + + //Pero si quisieramos asignar un valor fijo a una funcion? Partial application al rescate. + + /* + Primero, definamos una funcion que hace currying: + */ + Function, Function>> + curryBiFunction = biFun -> X -> Y -> biFun.apply(X, Y); + + /* + Ahora podemos definir una funcion que genera mas funciones, usando la BiFunction que ya tenemos: + */ + Function> multiplyBy = + x -> curryBiFunction.apply(multiplyXbyY).apply(x); + + //Y crear funciones para valores especificos: + Function multiplyBy5 = y -> multiplyBy.apply(5).apply(y); + + System.out.println( + multiplyBy5.apply(20) + ); + + Function multiplyBy2 = x -> multiplyBy.apply(2).apply(x); + + System.out.println( + multiplyBy2.apply(10) + ); + } + + +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + + /** + * Aqui un ejemplo mas complejo: + */ + static void ejemploDB(DBConfiguration connectionConf) { + //Imagina que tuvieramos esta funcion, que puede recibir una configuracion + // de conexion a una base de datos y un query a ejecutar sobre dicha conexion. + BiFunction biDB = (conf, query) -> + new DataBaseConnection(conf).executeQuery(query); + + //Por cada corrida sobre esa funcion, tendremos que pasar la configuracion + QueryResult result1 = biDB.apply(connectionConf, new Query("SELECT …")); + //Que aunque no esta mal, parece algo redundante… + result1 = biDB.apply(connectionConf, new Query("INSERT …")); + + //Podemos aplicar curry a nuestra bifunction para simplificar las llamadas: + Function> dbFunCreator = curryBiFunction(biDB); + + //Creamos una funcion que trabajara sobre postgres… + Function postgresExecutor = + pgQuery -> dbFunCreator.apply(new DBConfiguration(/*Postgres configs*/)).apply(pgQuery); + + //Y otra funcion para MariaDB + Function mariaDBExecutor = + query -> dbFunCreator.apply(connectionConf).apply(query); + + postgresExecutor.apply(new Query("SELECT…")); + postgresExecutor.apply(new Query("INSERT…")); + + mariaDBExecutor.apply(new Query("UPDATE…")); + } +// +// +// +// +// +// +// +// +// +// +// +// +// +// + + static Function> curryBiFunction(BiFunction biFunction) { + return f -> s -> biFunction.apply(f, s); + } +// +// +// +// +// +// +// +// +// +// +// +// +// +// + + static class QueryResult { + + } + + static class Query { + public Query() { + + } + + public Query(String query) { + } + } + + static class DBConfiguration { + private String host; + private String user; + private String password; + + private int port; + + public DBConfiguration() { + } + + public DBConfiguration(String host, String user, String password, int port) { + this.host = host; + this.user = user; + this.password = password; + this.port = port; + } + } + + static class DataBaseConnection { + public DataBaseConnection(DBConfiguration dbConfiguration) { + } + + public QueryResult executeQuery(Query query) { + return null; + } + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_14_optionals/Optionals.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_14_optionals/Optionals.java new file mode 100644 index 0000000..f979ceb --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_14_optionals/Optionals.java @@ -0,0 +1,60 @@ +package com.platzi.functional_teacher_theory._14_optionals; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +public class Optionals { + public static void main(String[] args) { + List names = getNames(); + if (names != null) { + //Operar con nombres. + } + + Optional> optionalNames = getOptionalNames(); + if (optionalNames.isPresent()) { + + } + + optionalNames.ifPresent(namesValue -> namesValue.forEach(System.out::println)); + + Optional valuablePlayer = optionalValuablePlayer(); + + String valuablePlayerName = valuablePlayer.orElseGet(() -> "No player"); + } + + static List getNames() { + List list = new LinkedList<>(); + + return Collections.emptyList(); + } + + static String mostValuablePlayer() { +// return ""; + return null; + } + + static int mostExpensiveItem(){ + return -1; + } + + static Optional> getOptionalNames(){ + List namesList = new LinkedList<>(); + //Obtencion de nombres + return Optional.of(namesList); + } + + static Optional optionalValuablePlayer(){ + // +// return Optional.ofNullable() + try { + //Accesos + return Optional.of("Sinuhe"); + }catch (Exception e){ + e.printStackTrace(); + } + + return Optional.empty(); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_14_optionals/OptionalsTheory.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_14_optionals/OptionalsTheory.java new file mode 100644 index 0000000..6b96a02 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_14_optionals/OptionalsTheory.java @@ -0,0 +1,232 @@ +package com.platzi.functional_teacher_theory._14_optionals; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class OptionalsTheory { + /* + * La clase Optional nos ayuda en java 8 a resolver un problema comun en Java: null + * Con Optional tendremos la posibilidad de operar sobre una clase que nos protege del infame NPE + * + * La idea de Optional es que previo a hacer una operacion, se haga una validacion dentro de + * el Optional para evitar problemas. + */ + static Optional optionals() { + //La clase optional nos ofrece diferentes maneras de crear un optional segun los datos que tengamos + + + + + //La primera de ellas es crear un Optional de un dato que SI tenemos: + Optional optional = Optional.of("Java 8"); + + + + //Si no estamos seguros del valor que pondremos en el Optional, podemos usar ofNullable: + optional = Optional.ofNullable(uknownResult()); + + + + + //Y si lo que queremos es evitar devolver un null pero no tenemos un valor para regresar, + //podemos usar simplemente: + return Optional.empty(); + } + +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + + static void obtenerUnDato() { + Optional optional = Optional.ofNullable(uknownResult()); + + //Una vez que tenemos un optional, obtener el dato puede ser tan simple como: + String dato = optional.get(); + + + //Sin embargo, eso nos provoca el mismo problema que tratamos de solucionar, + //pues si el dato es null, `get` nos devolera un null. + + + + //Podemos entonces, checar la presencia de dato: + if (optional.isPresent()) { + //Pero esto es muy similar a checar si el dato es null. + dato = optional.get(); + } + + + + //Hagamoslo de manera mas funcional: + dato = optional.orElse(""); + + + //Y en caso de que nuestro dato sea muy complejo: + dato = optional.orElseGet(OptionalsTheory::complexFunction); + + + //O con una lambda: + dato = optional.orElseGet(() -> /*Cosas magicas para generar el dato*/ ""); + + + + //Y esta es la parte donde entendemos que optional tiene mas poderes que validar contra un null. + + + + //No solo eso, Optional nos permite operar el dato en caso de que este presente: + optional.ifPresent(System.out::println); + optional.ifPresent(s -> someComplexOperation(s)); + + + + //Incluso hacer operaciones para generar nuevos optionals segun sea necesario: + Optional subOptional = optional.filter(String::isEmpty); + + + + //O transformar el dato: + Optional integerOptional = optional.map(s -> s.length() * 2); + + + + //Es importante mencionar que Optional no ejecutara ninguna de estas funciones + //en casos donde el dato no existe (null, empty() ). Asi que es una manera segura de + //escribir codigo sin preocuparnos por la presencia del dato. + + //En casos muy especificos donde quisieramos generar un Exception cuando haya una ausencia + //de datos, podemos usar el metodo `orElseThrow`: + integerOptional.orElseThrow(() -> new DatoNecesarioException()); + + //Optional nos da un acercamiento hacia un concepto que en FP se conoce como Monad + } + + + /** + * Ejemplo al codigo antes de Optional + */ + static String antesDeOptional(List names) { + //Antes de optional, era comun tener un pequeño `if` validando la presencia de valor + // en los argumentos de nuestros metodos + if (names == null) { + + //Y una mala practica era, en error o ausencia de datos, retornar un null. + //Esto es una mala practica porque es una manera de evadir operaciones. Forzando que tambien + //el codigo que invoco nuestra funcion tenga que validar si el resultado es null. + return null; + } + + return Arrays.toString(names.toArray()); + } + +// +// +// +// +// +// +// +// +// +// + +// +// +// +// +// +// + + /** + * Con la clase optional le damos una mayor seguridad a quien ejecuta nuestro codigo, + * pues le ahorramos operaciones de validacion y puede decidir que hacer con los datos + * de una manera mas directa. + */ + static Optional conOptional(List names) { + if (names == null || names.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(Arrays.toString(names.toArray())); + } + + + // +// +// +// +// +// +// +// +// +// +// +// + + /** + * Funcion usando ambos casos: + */ + static void outside() { + //Invocando a un metodo que no sabemos que retorna: + String directResult = antesDeOptional(null); + if (directResult != null) { + directResult = directResult.replace("Sierisimo", "Sinuhe"); + } + + //Con optional: + Optional optionalResult = conOptional(null); + + directResult = optionalResult + .map(s -> s.replace("Sierisimo", "Sinuhe")) + .orElse("Sinuhe"); + + //Incluso podriamos hacer chaining directo: + conOptional(null).filter(s -> !s.isEmpty()) + .map(s -> s.replace("@Sierisimo", "Sinuhe")) + .orElse("Sinuhe"); + + //Como vemos, optional nos facilita operar sobre datos + } + + // +// +// +// +// +// +// +// +// +// +// + static String uknownResult() { + return null; + } + + static String complexFunction() { + return "Complex Result"; + } + + static void someComplexOperation(String s) { + + } + + static class DatoNecesarioException extends IllegalArgumentException { + + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_15_streams/IntroStreamsAndIterables.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_15_streams/IntroStreamsAndIterables.java new file mode 100644 index 0000000..c95f90c --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_15_streams/IntroStreamsAndIterables.java @@ -0,0 +1,123 @@ +package com.platzi.functional_teacher_theory._15_streams; + +import com.platzi.functional_teacher_theory.util.Utils; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +public class IntroStreamsAndIterables { + static void iterables() { + /* + Una de las principales caracteristicas de Java fue que introdujo las Colecciones de datos. + Listas, mapas, sets, arboles, etc. y diferentes implementaciones de ellos. + + Antes de las colecciones teniamos los arreglos. Pero recorrer/iterar los elementos de una lista + o un arreglo era casi el mismo proceso: + */ + List namesList = Utils.getListOf("Sinuhe", "Brenda", "Ada"); + + for (String name : namesList) { + System.out.println(name); + } + +// +// +// + + +// +// +// +// +// +// + /* + Aunque esto no esta mal, tiene algunas limitantes. Ademas que hace que el codigo + se vuelva algo grande cuando muchas operaciones se tienen que hacer sobre una misma coleccion. + */ + for (String name : namesList) { + if (name != null && !name.isEmpty()) { + //Hacer algo… + } + } + + //E intentar hacer este tipo de iteracion en paralelo seria muy complicado. + } + + /** + * Java 8 introduce un nuevo elemento al juego: Streams. + */ + static void streams() { + /* + Podemos ver o imaginar a un stream como un flujo de datos, un rio de datos. + Donde los datos van moviendose sin esperar a que alguien los mueva. + + Es decir, un Stream es una estructura auto-iterable. Cuando generamos un Stream, + puede ser con datos ya definidos o proveer una manera de ir generando los datos que pasaran + mediante este flujo. + + Podemos generar streams de cualquier fuente. + */ + Stream stringStream = Stream.of("Hola", "Adios"); + +// +// +// +// + + /* + Incluso de fuente externas: + */ + BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); + Stream bufferStream = in.lines(); + +// +// + +// +// +// +// +// + /* + Algo muy importante es que podemos obtener streams de coleciones existentes: + */ + List coolNames = Utils.getListOf("Sinuhe", "Ada", "Victoria", "Brenda"); + Stream coolNamesStream = coolNames.stream(); + + + Set platziCourses = new HashSet<>(coolNames); + Stream coursesStream = platziCourses.stream(); + +// +// + +// +// +// +// + + /* + E incluso obtener nuestros datos desde funciones generadoras: + */ + AtomicInteger x = new AtomicInteger(); + Stream countingStream = Stream.generate(() -> x.getAndIncrement()); + + + + /* + Algo todavia mas interesante de estas nuevas estructuras es que podemos paralelizar + la iteracion de las mismas sin preocuparnos mucho + */ + Stream countingInParallelStream = countingStream.parallel(); + + /* + pero lo interesante viene despues, al obtener los datos de ese stream… + */ + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_15_streams/Streams.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_15_streams/Streams.java new file mode 100644 index 0000000..31c56a3 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_15_streams/Streams.java @@ -0,0 +1,44 @@ +package com.platzi.functional_teacher_theory._15_streams; + +import com.platzi.functional_teacher_theory._06_reference_operator.NombresUtils; + +import java.util.List; +import java.util.stream.Stream; + +public class Streams { + public static void main(String[] args) { + List courseList = NombresUtils.getList( + "Java", + "FrontEnd", + "Backend", + "FullStack"); + for (String course : courseList) { + String newCourseName = course.toLowerCase().replace("!", "!!"); + System.out.println("Platzi: " + newCourseName); + } + + Stream coursesStream = Stream.of("Java", + "FrontEnd", + "Backend", + "FullStack"); + +// Stream courseLengthStream = coursesStream.map(course -> course.length()); + +// Optional longest = courseLengthStream.max((x, y) -> y - x); + + Stream emphasisCourses = coursesStream.map(course -> course + "!"); + Stream justJavaCourses = emphasisCourses.filter(course -> course.contains("Java")); + justJavaCourses.forEach(System.out::println); + + Stream coursesStream2 = courseList.stream(); + + addOperator( + coursesStream2.map(course -> course + "!!") + .filter(course -> course.contains("Java")) + ).forEach(System.out::println); + } + + static Stream addOperator(Stream stream){ + return stream.peek(data -> System.out.println("Dato: " + data)); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_15_streams/TypeStream.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_15_streams/TypeStream.java new file mode 100644 index 0000000..9dfb7bb --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_15_streams/TypeStream.java @@ -0,0 +1,17 @@ +package com.platzi.functional_teacher_theory._15_streams; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class TypeStream { + public static void main(String[] args) { + IntStream infiniteStream = IntStream.iterate(0, x -> x + 1); + List numbersList = infiniteStream.limit(1000) + .filter(x -> x % 2 == 0) + .boxed() + .collect(Collectors.toList()); + + + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_16_listeners/StreamListeners.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_16_listeners/StreamListeners.java new file mode 100644 index 0000000..95a6958 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_16_listeners/StreamListeners.java @@ -0,0 +1,123 @@ +package com.platzi.functional_teacher_theory._16_listeners; + +import com.platzi.functional_teacher_theory.util.Utils; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +public class StreamListeners { + static void listeners() { + /* + Mencionamos que los Streams son "auto iterables" + Entonces… como podemos obtener los datos para procesarlos? + + Es facil, la clase Stream tiene un API funcional que nos permite pasar Suppliers, Consumers, + Predicates, etc. Lambdas a final de cuentas. + */ + Stream coursesStream = Stream.of("Java", "Functional"); + coursesStream.forEach(course -> System.out.println("Curso de platzi sobre: " + course)); + + /* + A estas lambdas o funciones que agregamos para procesar los streams, se les conoce como listeners. + + Aunque este nombre/definicion depende del autor, tiene mucho sentido llamarles asi, + pues el Stream se empezara a iterar una vez que encuentre el set de datos y las operaciones + a aplicar e ira invocando a cada una de estas operaciones con los valores correspondientes. + */ + + /* + Hay diferentes tipos de operaciones que aceptan diferentes tipos de listeners, por ejemplo, + el filtrado de datos: + */ + AtomicInteger x = new AtomicInteger(); + Stream countingStream = Stream.generate(() -> x.getAndIncrement()); + + //Filtramos los datos para tener solo los numeros pares producidos por el stream + countingStream.filter(i -> i % 2 == 0); + + //O elevamos cada numero al cuadrado… + countingStream.map(i -> i * i); + +// +// +// +// + + /* + Una situacion que se presenta en muchas ocasiones es tener un Stream> donde + tenemos un Stream que emite colecciones. + Si intentamos operar sobre estre Stream no podremos acceder a los Strings de cada lista, + solo a las listas como tal… operar de vuelta la lista no es una opcion si estamos haciendo + cosas en paralelo. + */ + Stream> coursesModules = Stream.of(/*Obentemos los cursos de una DB*/); + //Nos retorna un Stream de List donde las listas tienen literalmente el elemento "Java" + //Tal vez nuestra busqueda era sobre cursos que contuvieran la palabra Java como parte del nombre… + coursesModules.filter(s -> s.contains("Java")); + + /* + Para poder operar este tipo de streams, usaremos una operacion llamada flatMap + + flatMap toma un Stream> y nos retorna un Stream. + + Es decir, flatMap se encarga de combianr todos los elementos de las colecciones de los streams + en un solo Stream. Para hacer esto, debemos proveer una lambda que emita un stream como resultado + + Si nuestro Stream inicial tenia: + Stream{ List{ "Node.js", "JavaScript"}, List{"Android", "Kotlin"}, List{"JavaSE 8", "Java FP"}} + + Aplicar flatMap retorna: + Stream{ "Node.js", "JavaScript", "Android", "Kotlin", "JavaSE 8", "Java FP" } + */ + List nodeCourses = Utils.getListOf("Node.js", "Express.js", "Eventloop"); + List javaCourses = Utils.getListOf("Spring", "Maven", "Gradle", "Funtional"); + + Stream> courses = Stream.of(nodeCourses, javaCourses); + + //Sin flatMap + long jsCourses = courses.filter(course -> course.contains("js")).count(); + System.out.println(jsCourses); // 0 + + jsCourses = courses.flatMap(list -> list.stream()) //Tambien Collection::stream es valido + .filter(course -> course.contains("js")) + .count(); // 2 (Node.js, Express.js) + +// +// +// +// +// + /* + Hablamos un poco sobre como crear streams desde una funcion y que estos streams pueden + emitir datos invocando a esta funcion. + + Sin embargo… que los detiene? Estos streams son infinitos, para detenerlos podemos utilizar `limit` + */ + Stream firstTen = Stream.iterate(0, i -> i + 1); + firstTen.limit(10) + .forEach(System.out::println); + + + + + /* + Tambien es posible que usemos objetos en lugar de lambdas. Recordemos que al final las lambdas + son funciones y los metodos son funciones. Podemos usar objetos siempre que sus + metodos cumplan con la necesidad de tener los parametros correctos y devolver el tipo correcto. + */ + NumericOperator numericOperator = new NumericOperator(); + Stream numbers = Stream.iterate(0, numericOperator::operate); + firstTen.limit(10) + .forEach(System.out::println); + } +// +// +// +// + static class NumericOperator { + public int operate(int x) { + return x + 3; + } + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_17_operators_collectors/Operators.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_17_operators_collectors/Operators.java new file mode 100644 index 0000000..48838b7 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_17_operators_collectors/Operators.java @@ -0,0 +1,166 @@ +package com.platzi.functional_teacher_theory._17_operators_collectors; + +import com.platzi.functional_teacher_theory.util.Utils; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class Operators { + static void operadores() { + /* + Hasta ahora hemos usado lambdas indiscriminadamente en nuestros streams sin revisar o validar + el estado del stream. Es decir, si ejecutas algunos ejemplos anteriores puede que te encuentres con + algunas excepciones no explicadas. Este es el tema donde explicaremos que diferencia hay entre usar + + forEach vs map por ejemplo. + */ + + /* + En nuestros streams, generalmente nos encontramos con elementos y realizamos operaciones sobre ellos. + */ + Stream numbers = Utils.getListOf(1, 2, 3, 4).stream(); + numbers.map(i -> i * 2); + + /* + Sin embargo, ciertas operaciones nos retornan un resultado, por ejemplo, map genera un nuevo + Stream partiendo de un Stream. Es decir, cada que realizamos una operacion `map` en un stream + lo que hace en realidad es generar un nuevo Stream, donde a cada elemento se le ira aplicando + la operacion que nosotros definimos. + + Esto puede ser representado: + */ + Stream numbersV2 = Utils.getListOf(1, 2, 3, 4).stream(); + Stream numbresBy2 = numbersV2.map(i -> i * 2); + + /* + Mas importante aun es entender que una vez aplicado un operador sobre un stream este stream no puede ser + usado de nuevo: + */ + Stream squares = numbersV2.map(i -> i * i); //Genera un IllegalStateException. + //Lo cual representa una pequeña limitante sobre el uso de streams. + + /* + Hay metodos que modifican los datos de nuestro stream y metodos que nos dejan obtener un resultado de + la iteracion de los elementos del stream. En ambos casos se les conoce como Operations + + Es importante identificar que muchos de estos retornan un nuevo Stream. Tras realizar una operacion + sobre un stream, el dato que existe en el stream puede cambiar, aqui un ejemplo: + */ + Stream courses = Stream.of( + "Java:Introductorio", + "Python:Introductorio", + "Machine Learning:Avanzado", + "JavaScript:Introductorio", + "Node.js:Intermedio", + "Android:Intermedio", + "iOS:Intermedio" + ); + + Stream introductoryCourses = courses.filter(course -> course.contains("Introductorio")); + + Stream partsNames = introductoryCourses.map(course -> course.split(":")); + + Stream partsWithData = partsNames.filter(parts -> parts.length > 1); + + Stream justNamesStream = partsWithData.map(courseData -> courseData[0]); + + Stream justActualNamesPresent = justNamesStream.filter(name -> !name.isEmpty()); + + /* + O en una version con chaining: + + Stream justNamesStream = courses.filter(c -> c.contains("Introductorio")) + .map(c -> c.split(":")) + .filter(parts -> parts.length > 1) + .map(courseData -> courseData[0]) + .map(c -> c[0]) + .filter(name -> !name.isEmpty()); + */ + + /* + Existen operaciones que nos permite obtener un resultado final despues de operar sobre los elementos de un stream. + + Cuando obtenemos un resultado final de la iteracion de un stream, se dice que se invoco una Operacion Final. + + Es importante entender que aunque tengamos multiples operaciones sobre un Stream, el Stream no sera iterado + hasta no agregar una operacion final. Esto es benefico pues podemos ir pasando el Stream por diferentes metodos/funciones + hasta que sea necesario obtener el valor resultante. + */ + Stream> coursesStream = getCourses(); + Stream courseDataStream = flatMapCourses(coursesStream); + Stream partsStream = splitInformation(courseDataStream); + Stream filteredPartsStream = filterAdvanceCourses(partsStream); + Stream advanceCourseNamesStream = getNamesStream(filteredPartsStream); + + /* + hasta este punto, no se ha procesado ningun stream a pesar de las llamadas a metodos. + Esto es porque no se ha agregado una operacion final que desencadene la iteracion en el + ningun stream. + + Agregar una operacion final es tan sencillo como agregar una operacion no final: + */ + //long totalAdvanceCourses = advanceCourseNamesStream.count(); + + /* + Es hasta este punto que los datos se empiezan a iterar para obtener un resultado. + Sabemos que `count()` es una operacion final porque no retorna un Stream, retorna un + resultado de aplicar la operacion correspondiente. + + En muchas ocasiones lo mas comun es encontrarte con una operacion de recoleccion de datos + utilizando una de las implementaciones de Collector. + + Esta es la manera ideal de convertir un Stream a una coleccion (Set, Map, List, Collection) + o convertir todos los datos de un Stream a un tipo en concreto (por ejemplo, concatenar todos los elementos) + */ + List advanceCourseNamesList = advanceCourseNamesStream.collect(Collectors.toList()); + +// +// +// +// + + /* + El ejemplo anterior representa algo sucediendo entre multiples partes de un proyecto, + una que procese el dato de una peticion web, otro que lo almacene en una base de datos + otro que haga conversion de datos… etc. + */ + } + + // +// +// +// +// +// +// +// +// +// +// +// + public static Stream> getCourses() { + List nodeCourses = Utils.getListOf("Node.js:Intermedio", "Express.js:Intermedio", "Eventloop:Avanzado"); + List javaCourses = Utils.getListOf("Spring:Introductorio", "Maven:Intermedio", "Gradle:Avanzado", "Funtional:Introductorio"); + + return Stream.of(nodeCourses, javaCourses); + } + + static Stream flatMapCourses(Stream> courses) { + return courses.flatMap(Collection::stream); + } + + static Stream splitInformation(Stream coursesData) { + return coursesData.map(courseData -> courseData.split(":")); + } + + static Stream filterAdvanceCourses(Stream courses) { + return courses.filter(data -> data.length > 1) + .filter(data -> data[1] == "Avanzado"); + } + + static Stream getNamesStream(Stream coursesData) { + return coursesData.map(courseData -> courseData[0]); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_18_intermediate_ops/IntermediateOperations.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_18_intermediate_ops/IntermediateOperations.java new file mode 100644 index 0000000..f808baf --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_18_intermediate_ops/IntermediateOperations.java @@ -0,0 +1,101 @@ +package com.platzi.functional_teacher_theory._18_intermediate_ops; + +import com.platzi.functional_teacher_theory.util.Utils; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +public class IntermediateOperations { + static void operaciones() { + /* + Las operaciones intermedias, como se vio en el modulo anterior, son operaciones que + retornan un nuevo Stream. + + Estas operaciones son: + + - filter() + - map() + - flatMap() + - distinct() + - limit() + - peek() + */ + + //filter + //Toma un Predicate que indica si debemos o no considerar el elemento para el nuevo Stream + Stream evenNumbersStream = Stream.iterate(0, i -> i + 1); + evenNumbersStream.filter(i -> i % 2 == 0); //Solo los numeros pares. + + +// +// +// +// +// +// +// + + //map + //Convertir un Stream de tipo T a un Stream de tipo V. Es posible que T y V sean el mismo tipo: + Stream namesStream = Stream.of("Sinuhe", "Brenn", "Ricardo", "Sebastian", "Luisa"); + Stream lengthNameStream = namesStream.map(String::length); + + //flatMap + /* + Como mencionamos en el modulo sobre Listeners, flatMap convierte un Stream complejo en un Stream lineal. + Es decir, flatMap opera sobre un Stream que contenga datos "anidados" como puede ser una Collection o un Array, + tomando una lambda que retorne otro Stream de los datos anidados y concatenando todos los datos en un solo stream. + + Si nuestro Stream inicial tenia: + Stream{ List{ "Node.js", "JavaScript"}, List{"Android", "Kotlin"}, List{"JavaSE 8", "Java FP"}} + + Aplicar flatMap retorna: + Stream{ "Node.js", "JavaScript", "Android", "Kotlin", "JavaSE 8", "Java FP" } + */ + List nodeCourses = Utils.getListOf("Node.js", "Express.js", "Eventloop"); + List javaCourses = Utils.getListOf("Spring", "Maven", "Gradle", "Funtional"); + + Stream> coursesListStream = Stream.of(nodeCourses, javaCourses); + Stream coursesStream = coursesListStream.flatMap(Collection::stream); + + +// +// +// +// +// +// + //distinct + //Genera un Stream de elementos unicos tomando como fuente: Object.equals + Stream heroesNamesStream = Stream.of("Peter", "Logan", "Luisa", "Clark", "Gwen", "Logan", "Peter"); + Stream uniqueHeroesNamesStream = heroesNamesStream.distinct();// "Peter", "Logan", "Luisa", "Clark", "Gwen" + + +// +// +// +// +// + //limit + //Limita los elementos del Stream al numero indicado. + Stream justTwoHeroes = uniqueHeroesNamesStream.limit(2); + +// +// +// + //peek + /* + Recibe un Consumer para operar sobre cada elemento del Stream pero manteniendo el tipo del Stream + Es una manera sencilla de hacer una operacion intermedia sobre el Stream sin alterarlo (idealmente) + */ + Stream choosenTwoHeroes = justTwoHeroes.peek(heroe -> System.out.println("Un heroe ha sido elegido: " + heroe)); + + /* + Existen otras operaciones como sorted() que alteran el orden del Stream + o mas especificas como las operaciones mapTo… que convierten el Stream de un tipo a otro + + Es importante entender que estas operaciones solo generan nuevos Stream con cada invocacion. + */ + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/_19_final_ops/FinalOperations.java b/modules/src/main/java/com/platzi/functional_teacher_theory/_19_final_ops/FinalOperations.java new file mode 100644 index 0000000..7ed9af8 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/_19_final_ops/FinalOperations.java @@ -0,0 +1,130 @@ +package com.platzi.functional_teacher_theory._19_final_ops; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.platzi.functional_teacher_theory._17_operators_collectors.Operators.getCourses; + +public class FinalOperations { + static void operaciones() { + /* + Las Operaciones finales son, como se menciono antes, las operaciones que generan un valor + final despues de iterar los elementos del stream. + + Dichas operaciones son: + - anyMatch() + - allMatch() + - noneMatch() + - collect() + - count() + - findAny() + - findFirst() + - forEach() + - min() + - max() + - reduce() + - toArray() + */ + + //anyMatch + //Nos indica si un stream contiene un elemento segun el Predicate que le pasemos: + Stream numbersStream = Stream.of(1, 2, 3, 4, 5, 6, 7, 11); + boolean biggerThanTen = numbersStream.anyMatch(i -> i > 10); //true porque tenemos el 11 + + //allMatch + //Nos indica si todos los elementos de un Stream cumplen con un cierto Predicate: + Stream agesStream = Stream.of(19, 21, 35, 45, 12); + boolean allLegalDrinkingAge = agesStream.allMatch(age -> age > 18); //false, tenemos un menor + + //noneMatch + //Nos indica si todos los elementos de un Stream NO CUMPLEN un cierto Predicate: + Stream oddNumbers = Stream.of(1, 3, 5, 7, 9, 11); + boolean allAreOdd = oddNumbers.noneMatch(i -> i % 2 == 0); + + //collect + //Recibe un Collector para juntar todos los elementos del Stream en un cierto tipo: + Stream studentsStream = Stream.of("Tu", "Yo"); + List studentsList = studentsStream.collect(Collectors.toList()); + + //count + //Nos indica cuantos elementos tiene un Stream + Stream yearsStream = Stream.of(1990, 1991, 1994, 2000, 2010, 2019, 2020); + long yearsCount = yearsStream.count(); //7, solo nos dice cuantos datos tuvo el stream. + + + //findAny + /* + Nos retorna un Optional con un elemento, cualquiera del Stream, o Optional.empty() + si no hay elementos en el Stream. Esta operacion puede devolver cualquier elemento + del Stream. + */ + Stream> coursesStream = getCourses(); + Optional> coursesOptional = coursesStream.findAny(); + + //findFirst + //Retorna un Optional con el primer elemento del Stream o Optional.empty() si no hay elementos en el Stream + Stream> availableCourses = getCourses(); + Optional> firstCoursesOptional = coursesStream.findFirst(); + + + //forEach + //Itera cada elemento del Stream pasandolo al Consumer que se da como parametro: + Stream> courses = getCourses(); + courses.forEach(courseList -> System.out.println("Cursos disponibles: " + courseList)); + + //min + //Nos retorna un Optioanl con el elemento con valor minimo del Stream usando un Comparator para encontrarlo + // o Optional.empty() si el Stream no tenia elementos + Stream bigNumbers = Stream.of(100L, 200L, 1000L, 5L); + Optional minimumOptional = bigNumbers.min((numberX, numberY) -> (int) Math.min(numberX, numberY)); + + //max + //Escencialmente lo mismo que Stream.min, solo que retornando un Optional con el valor MAXIMO: + Stream bigNumbersAgain = Stream.of(100L, 200L, 1000L, 5L); + Optional maximumOptional = bigNumbers.min((numberX, numberY) -> (int) Math.max(numberX, numberY)); + + //reduce + /* + Existe en tres formas: + reduce(valorInicial, BinaryOperator) + reduce(BinaryAccumulator) + reduce(valorInicial, BinaryFunction, BinaryOperator) + + La diferencia entre los 3 tipos de invocacion: + + - reduce(BinaryAccumulator) + retorna un Optional del mismo tipo que el Stream, con un solo valor resultante de aplicar + el BinaryAccumulator sobre cada elemento O Optional.empty() si el stream estaba vacio. + Puede generar un NullPointerException en casos donde el resultado de BinaryAccumulator sea null + + - reduce(valorInicial, BinaryOperator) + retorna un valor del mismo tipo que el Stream, despues de aplicar BinaryOperator sobre cada + elemento del Stream, en caso de un Stream vacio, el valorInicial es retornado. + + Y el caso mas interesante… + + - reduce(valorInicial, BinaryFunction, BinaryOperator) + Genera un valor de tipo V, despues de aplicar BinaryFunction sobre cada elemento de tipo T en el Stream + y obtener un resultado V. Esta version de reduce usa el BinaryFunction como map + reduce. Es decir + por cada elemento en el Stream, se genera un valor V basado en el valorInical y el resultado anterior de + la BinaryFunction. BinaryOperator se utiliza en streams paralelos (stream.parallel()) para determinar el + valor que se debe mantener en cada iteracion. + + */ + + //reduce(BinaryAccumulator) + Stream aLongStoryStream = Stream.of("Cuando", "despertó,", "el", "dinosaurio", "todavía", "estaba", "allí."); + Optional longStoryOptional = aLongStoryStream.reduce((previousStory, nextPart) -> previousStory + " " + nextPart); + longStoryOptional.ifPresent(System.out::println); //"Cuando despertó, el dinosaurio todavía estaba allí." + + //reduce(valorInicial, BinaryOperator) + Stream firstTenNumbersStream = Stream.iterate(0, i -> i + 1).limit(10); + int sumOfFirstTen = firstTenNumbersStream.reduce(0, Integer::sum); //45 -> 0 + 1 + … + 9 + + //reduce(valorInicial, BinaryFunction acumulador, BinaryOperator combinador) + Stream aLongStoryStreamAgain = Stream.of("Cuando", "despertó,", "el", "dinosaurio", "todavía", "estaba", "allí."); + int charCount = aLongStoryStreamAgain.reduce(0, (count, word) -> count + word.length(), Integer::sum); + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/exercises/_11_Composition.java b/modules/src/main/java/com/platzi/functional_teacher_theory/exercises/_11_Composition.java new file mode 100644 index 0000000..c7a8915 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/exercises/_11_Composition.java @@ -0,0 +1,19 @@ +package com.platzi.functional_teacher_theory.exercises; + +import java.util.function.BiFunction; +import java.util.function.Function; + +public class _11_Composition { + //TODO: Crear una funcion o grupo de funciones que puedan calcular: x^2 + y^2 + 2xy + // usando unicamente los operadores: *, + una vez por funcion. + // Es decir, Math.pow no esta permitido. y funciones del estilo: a * b + c no estan permitidas. + public static BiFunction generateEquation() { + //Ejemplo: + Function toNegative = x -> -x; + + //Y aqui tienes un regalo para este ejercicio (tienes que usar esta funcion en el codigo final): + Function multiplyBy2 = x -> x * 2; + + return null; + } +} diff --git a/modules/src/main/java/com/platzi/functional_teacher_theory/util/Utils.java b/modules/src/main/java/com/platzi/functional_teacher_theory/util/Utils.java new file mode 100644 index 0000000..61e3fe9 --- /dev/null +++ b/modules/src/main/java/com/platzi/functional_teacher_theory/util/Utils.java @@ -0,0 +1,14 @@ +package com.platzi.functional_teacher_theory.util; + +import java.util.Arrays; +import java.util.List; + +public class Utils { + private Utils() { + } + + @SafeVarargs + public static List getListOf(T... items) { + return Arrays.asList(items); + } +} diff --git a/settings.gradle b/settings.gradle index 5f9de6f..1a6a0a7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ":modules" \ No newline at end of file +include "modules", "jobs-search-reporter" \ No newline at end of file