lunes, 29 de noviembre de 2021

Java: utilizar el valor de una property en una clase.

En .yml:

application.yml
sirec:
    kill-list-enabled: ${KILL_LIST_ENABLED:false}

O en .properties:

application.properties
sirec.kill-list-enabled: ${KILL_LIST_ENABLED:false}

Y en Java:

ApiTareasService.java
@Value("#{new Boolean('${sirec.kill-list-enabled:false}')}")
private Boolean killListEnabled;

if (killListEnabled) {
    ...
}

Java: sumar días o meses a una fecha en formato String

public static final String FECHA_FORMATO_ENTRADA = "yyyy-MM-dd HH:mm:ss.S";
public static final String FECHA_FORMATO_SALIDA = "dd/MM/yyyy";

SimpleDateFormat formatIn = new SimpleDateFormat(Constants.FECHA_FORMATO_ENTRADA);
SimpleDateFormat formatOut = new SimpleDateFormat(Constants.FECHA_FORMATO_SALIDA);

String tmpFecha = null;

try {
    Date dateSel = formatIn.parse(valoracionesBienInner.getFecha());
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(dateSel);
    calendar.add(Calendar.MONTH, Integer.valueOf(valoracionesBienInner.getPeriodoValidez()));
    Date salida = calendar.getTime();

    tmpFecha = formatOut.format(salida);

} catch (ParseException e) {
    throw new SirecApplicationException(0, "Error parseo fecha validez en valoraciones", e);
}

aElementoListaValoraciones.setFechaValidez(getNullHandlingValue(tmpFecha));

public static String getNullHandlingValue(String valor) {
    return valor == null ? "" : valor;
}

miércoles, 20 de octubre de 2021

Java RestCall (TODO)

 // Requires:
//<!-- https://mvnrepository.com/artifact/net.minidev/json-smart -->
//<dependency>
//    <groupId>net.minidev</groupId>
//    <artifactId>json-smart</artifactId>
//    <version>2.4.7</version>
//</dependency>

package com.thymewizards.util;

import java.util.Map;
import net.minidev.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

public class RestCall {
    private static final Logger logger = LoggerFactory.getLogger(RestCall.class);
    private RestTemplate restTemplate = new RestTemplate();

    public ResponseEntity<Object> executePOST(String url, HttpHeaders header, Map<String, String> urlParams, MultiValueMap<String, String> queryParams, Object obj) throws Exception {
        ResponseEntity<Object> resp = null;
        UriComponents builder = null;
        try {
            if (obj != null) {
                logger.info(String.format("{Body} : %s", obj.toString()));
            }
            builder = UriComponentsBuilder.fromHttpUrl(url).buildAndExpand(urlParams);
            if (queryParams != null) {
                builder = UriComponentsBuilder.fromUriString(url).queryParams(queryParams).buildAndExpand(urlParams);
            }
            HttpEntity<Object> httpEntity = new HttpEntity(obj, header);
            logger.info(String.format("{Header} : %s", httpEntity.getHeaders()));
            logger.info(String.format("Accediendo a la URL %s via POST", builder.toUriString()));
            resp = this.restTemplate.postForEntity(builder.toUriString(), httpEntity, Object.class, new Object[1]);
            if (resp != null) {
                logger.info(String.format("{Response} : %s", resp));
            }
            logger.info("OK.!");
        } catch (HttpStatusCodeException var9) {
            logger.error("KO.! Error!!", var9);
            throw var9;
        } catch (Exception var10) {
            logger.error(String.format("Accediendo a la URL {%s} via POST KO.! Error!!", url), var10);
            throw new Exception(var10);
            return resp;
        }
    }

    public ResponseEntity<Object> executePUT(String url, HttpHeaders header, Map<String, String> urlParams, MultiValueMap<String, String> queryParams, Object obj) throws Exception {
        ResponseEntity<Object> resp = null;
        UriComponents builder = null;
        try {
            if (obj != null) {
                logger.info(String.format("{Body} : %s", obj.toString()));
            }
            builder = UriComponentsBuilder.fromHttpUrl(url).buildAndExpand(urlParams);
            if (queryParams != null) {
                builder = UriComponentsBuilder.fromUriString(url).queryParams(queryParams).buildAndExpand(urlParams);
            }
            HttpEntity<Object> httpEntity = new HttpEntity(obj, header);
            logger.info(String.format("{Header} : %s", httpEntity.getHeaders()));
            logger.info(String.format("Accediendo a la URL %s via PUT", builder.toUriString()));
            resp = this.restTemplate.exchange(builder.toUriString(), HttpMethod.PUT, httpEntity, Object.class, new Object[0]);
            if (resp != null) {
                logger.info(String.format("{Response} : %s", resp));
            }
            logger.info("OK.!");
        } catch (HttpStatusCodeException var9) {
            logger.error("KO.! Error!!", var9);
            throw var9;
        } catch (Exception var10) {
            logger.error(String.format("Accediendo a la URL {%s} via PUT KO.! Error!!", builder), var10);
            throw new Exception(var10);
            return resp;
        }
    }

    public ResponseEntity<Object> executeDELETE(String url, HttpHeaders header, Map<String, String> urlParams, MultiValueMap<String, String> queryParams) throws Exception {
        ResponseEntity<Object> resp = null;
        UriComponents builder = null;
        try {
            builder = UriComponentsBuilder.fromHttpUrl(url).buildAndExpand(urlParams);
            if (queryParams != null) {
                builder = UriComponentsBuilder.fromUriString(url).queryParams(queryParams).buildAndExpand(urlParams);
            }
            HttpEntity<Object> httpEntity = new HttpEntity(header);
            logger.info(String.format("{Header} : %s", httpEntity.getHeaders()));
            logger.info(String.format("Accediendo a la URL %s via DELETE", builder.toUriString()));
            resp = this.restTemplate.exchange(builder.toUriString(), HttpMethod.DELETE, httpEntity, Object.class, new Object[0]);
            logger.info(String.format("{Response} : %s", resp));
            logger.info("OK.!");
        } catch (HttpStatusCodeException var8) {
            logger.error("KO.! Error!!", var8);
            throw var8;
        } catch (Exception var9) {
            logger.error(String.format("Accediendo a la URL {%s} via DELETE KO.! Error!!", url), var9);
            throw new Exception(var9);
            return resp;
        }
    }

    public ResponseEntity<Object> executeGET(String url, HttpHeaders header, Map<String, String> urlParams, MultiValueMap<String, String> queryParams, Object obj) throws Exception {
        ResponseEntity<Object> resp = null;
        UriComponents builder = null;
        try {
            if (obj != null) {
                logger.info(String.format("{Body} : %s", obj.toString()));
            }
            builder = UriComponentsBuilder.fromHttpUrl(url).buildAndExpand(urlParams);
            if (queryParams != null) {
                builder = UriComponentsBuilder.fromUriString(url).queryParams(queryParams).buildAndExpand(urlParams);
            }
            HttpEntity<Object> httpEntity = new HttpEntity(obj, header);
            logger.info(String.format("{Header} : %s", header));
            logger.info(String.format("Accediendo a la URL %s via GET", builder.toUriString()));
            resp = this.restTemplate.exchange(builder.toUriString(), HttpMethod.GET, httpEntity, Object.class, new Object[0]);
            if (resp != null) {
                logger.info(String.format("{Response} : %s", resp));
            }
            logger.info("OK.!");
        } catch (HttpStatusCodeException var9) {
            logger.error("KO.! Error!!", var9);
            throw var9;
        } catch (Exception var10) {
            logger.error(String.format("Accediendo a la URL {%s} via GET KO.! Error!!", url), var10);
            throw new Exception(var10);
            return resp;
        }
    }

    public Object rest2Object(ResponseEntity<Object> resp, Object obj) throws Exception {
        if (obj == null) {
            throw new Exception(String.format("Error el objecto <%s> es nulo", obj));
        } else {
            try {
                if (!resp.getStatusCode().equals(HttpStatus.OK) && !resp.getStatusCode().equals(HttpStatus.CREATED)) {
                } else {
                    JSONObject jsonBody = new JSONObject((Map) resp.getBody());
                    return ObjectMapperFactory.defaultOne().readValue(jsonBody.toJSONString(), obj.getClass());
                }
            } catch (Exception var4) {
                logger.error(String.format("Error al convertir <%s>", resp, obj.getClass().getSimpleName()), var4);
                throw new Exception(var4);
                return obj;
            }
        }
    }


@Slf4j
@RequiredArgsConstructor
public class ApiCuotasOnlineService extends ApiDelegateBaseHandler implements IApiCuotasOnlineService {
    private final ICuentaService cuentaService;
    private final HttpServletRequest request;

    @Value("${sirec.restcall.host:http://www.xyzxyz.es/}")
    private String host;

    @Override
    @SuppressWarnings("checkstyle:ParameterNumber")
    public CuotasContrato obtenerCuotasProducto(final String agrupacionAdn,
            final String numeroContrato, final String situacion, final String fechaDesde, final String fechaHasta,
            final String date, final String number, final String currency, final String balancePosition) {
        CuotasContrato cuotasContrato = new CuotasContrato();
        Tabla2Respuesta respuesta = new Tabla2Respuesta();

        List<TablaRespuestaColumnas> columnas = obtenerColumnasCuotasOnline();
        respuesta.setColumnas(columnas);

        if (columnas != null && !columnas.isEmpty()) {
            MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();
            queryParams.add("agrupacionAdn", agrupacionAdn);
            queryParams.add("numeroContrato", numeroContrato);
            queryParams.add("situacion", situacion);
            queryParams.add("fechaDesde", fechaDesde);
            queryParams.add("fechaHasta", fechaHasta);
            queryParams.add("date", date);
            queryParams.add("number", number);
            queryParams.add("currency", currency);
            queryParams.add("balancePosition", balancePosition);
            List<Map<String, String>> datos = obtenerDatosCuotasProducto(queryParams, columnas);
            respuesta.setDatos(datos);
        }

        cuotasContrato.setRespuesta(respuesta);
        return cuotasContrato;
    }

    private List<Map<String, String>> obtenerDatosCuotasProducto(final MultiValueMap<String, String> queryParams,
            final List<TablaRespuestaColumnas> columnas) {

        RestCall restCall = new RestCall();
        List<Map<String, String>> datos = new ArrayList<>();
        ListaConsultaOnline detalle = new ListaConsultaOnline();
        List<Map<String, String>> datosFiltrados = new ArrayList<>();
        HttpHeaders headers = createDefaultHttpHeaders();
        ResponseEntity<Object> resp;
        try {
            resp = (ResponseEntity<Object>) restCall.executeGET(
                host + "api/core/v1/consultasOnline/cuotas", headers, new HashMap<String, String>(),
                queryParams, null);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new SirecApplicationException(0, e.getMessage(), e);
        }
        try {
            detalle = (ListaConsultaOnline) restCall.rest2Object(resp, (Object) detalle);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        datos = detalle.getRespuesta().getDatos();
        // Se filtran los datos que coinciden con las columnas
        for (Map<String, String> dato : datos) {
            Map<String, String> map = new HashMap();
            for (TablaRespuestaColumnas columna : columnas) {
                map.put(columna.getId(), dato.get(columna.getId()));
            }
            datosFiltrados.add(map);
        }

        return datosFiltrados;
    }

    private List<TablaRespuestaColumnas> obtenerColumnasCuotasOnline() {
        log.info("ApiCuotasOnlineService.obtenerColumnasCuotasOnline");
        CuentaCamposDTO cuentaCamposDTO = new CuentaCamposDTO();
        cuentaCamposDTO = cuentaService.obtenerCamposDetalleCuotasOnlinePrestamosImpagados();
        return obtenerColumnasPreparadasCuotasOnline(cuentaCamposDTO);
    }

    private List<TablaRespuestaColumnas> obtenerColumnasPreparadasCuotasOnline(final CuentaCamposDTO cuentaCamposDTO) {
        // Campos configurables
        List<TablaRespuestaColumnas> columnas = new ArrayList<TablaRespuestaColumnas>();
        // Columnas
        if (cuentaCamposDTO != null && cuentaCamposDTO.getCampos() != null) {
            List<CampoConfiguracionInner> listCamposMostrar = new ArrayList<>();
            for (CampoConfiguracionInner campo : cuentaCamposDTO.getCampos()) {
                if (campo.getOrden() != null && campo.getOrden() > 0) {
                    listCamposMostrar.add(campo);
                }
            }
            listCamposMostrar.sort(Comparator.comparing(CampoConfiguracionInner::getOrden));

            // Columnas dinamicas
            columnas = listCamposMostrar.stream().map(campos -> new TablaRespuestaColumnas()
                    .id(campos.getNombreTecnicoCampo()).label(campos.getCampo()).tipo(campos.getTipoDato()))
                    .collect(Collectors.toList());
        } else {
            columnas.add(new TablaRespuestaColumnas());
        }
        return columnas;
    }

    private HttpHeaders createDefaultHttpHeaders() {
        HttpHeaders headers = new HttpHeaders();
        Enumeration<?> names = request.getHeaderNames();
        while (names.hasMoreElements()) {
            String name = (String) names.nextElement();
            Enumeration<?> values = request.getHeaders(name);
            while (values.hasMoreElements()) {
                headers.add(name, (String) values.nextElement());
            }
        }
        return headers;
    }

}


@Slf4j
@RequiredArgsConstructor
public class ApiPaygoldService extends ApiDelegateBaseHandler implements IApiPaygoldService {

    private final HttpServletRequest request;

    @Value("${sirec.restcall.host:http://www.xyzxyz.es/}")
    private String host;

    @Override
    public DetalleConsultaOnline paygold(final PaygoldEntrada paygoldEntrada) {
        DetalleConsultaOnline detalleConsultaOnline = new DetalleConsultaOnline();
        DetalleConsultaOnlineRespuesta respuesta = new DetalleConsultaOnlineRespuesta();

        MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();

        Map<String, String> datos = obtenerDatosRespuestaPaygold(queryParams, paygoldEntrada);
        respuesta.setDatos(datos);

        detalleConsultaOnline.setRespuesta(respuesta);
        return detalleConsultaOnline;
    }

    private Map<String, String> obtenerDatosRespuestaPaygold(final MultiValueMap<String, String> queryParams,
            final PaygoldEntrada paygoldEntrada) {

        RestCall restCall = new RestCall();
        DetalleConsultaOnline detalle = new DetalleConsultaOnline();
        Map<String, String> datos = new HashMap<String, String>();
        HttpHeaders headers = createDefaultHttpHeaders();
        ResponseEntity<Object> resp;
        try {
            resp = (ResponseEntity<Object>) restCall.executePOST(
                    host + "api/core/v1/consultasOnline/paygold", headers, new HashMap<String, String>(),
                    queryParams, paygoldEntrada);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new SirecApplicationException(0, e.getMessage(), e);
        }
        try {
            detalle = (DetalleConsultaOnline) restCall.rest2Object(resp, (Object) detalle);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        datos = detalle.getRespuesta().getDatos();

        return datos;
    }

    private HttpHeaders createDefaultHttpHeaders() {
        HttpHeaders headers = new HttpHeaders();
        Enumeration<?> names = request.getHeaderNames();
        while (names.hasMoreElements()) {
            String name = (String) names.nextElement();
            Enumeration<?> values = request.getHeaders(name);
            while (values.hasMoreElements()) {
                headers.add(name, (String) values.nextElement());
            }
        }
        return headers;
    }

}

Convertir importes de String a Bigdecimal

     /**
     * Convierte un importe de String con . para miles y , para decimales a BigDecimal
     * @param valor   Valor a formatear
     * @return
     */
    public static BigDecimal stringToBigDecimal(final String valor) {
        BigDecimal valorDecimal = null;

        try {
            valorDecimal = new BigDecimal(valor);
        } catch (NumberFormatException e) {
            String valorReplace = valor.replace(".", "").replace(",", ".");
            try {
                valorDecimal = new BigDecimal(valorReplace);
            } catch (NumberFormatException e2) {
                log.error(e2.getMessage());
            }
        }

        return valorDecimal;
    }

lunes, 4 de octubre de 2021

Java Formatea una fecha String de yyyy-MM-dd a dd/MM/yyyy

     /**
     * Formatea una fecha String de yyyy-MM-dd a dd/MM/yyyy
     * @param fechaOrigen
     * @return
     */
    public static String formateoFechaString(final String fechaOrigen) {
        String fechaDestino = "";
        if (null != fechaOrigen && !fechaOrigen.isEmpty()) {
            DateTimeFormatter formatoOrigen = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            DateTimeFormatter formatoDestino = DateTimeFormatter.ofPattern("dd/MM/yyyy");
            fechaDestino = LocalDate.parse(fechaOrigen, formatoOrigen).format(formatoDestino);
        }
        return fechaDestino;
    }

Java: Cambiar formato de importe String a coma para decimales y puntos para miles

 private static final String PATTERN = "#,##0.00";

/**
     * Formatea un importe a String en formato numerico
     * @param valor   Valor a formatear
     * @param entrada Indica true para entrada (hacia tx) o false para salida (hacia
     *                front)
     * @return
     */
    public static String formatImporte(final String valor, final boolean entrada) {
        DecimalFormat decimalFormat = (DecimalFormat) NumberFormat.getNumberInstance(Locale.GERMAN);
        decimalFormat.applyPattern(PATTERN);

        if (valor != null && !"".equals(valor.trim())) {
            double conValor = 0;
            try {
                conValor = Double.parseDouble(valor);
            } catch (NumberFormatException e) {
                String valorReplace = valor.replace(".", "").replace(",", ".");
                conValor = Double.parseDouble(valorReplace);
            }

            if (entrada) {
                return Double.toString(conValor);
            } else {
                return decimalFormat.format(conValor);
            }
        }

        return valor;
    }

Java: Sumar, redondear BigDecimal, y convertir a String

map.put("balance", refinancingLoanItem.getBalances().getTotalUnpaidAmount().getAmount().add(refinancingLoanItem.getBalances().getOutstandingCapital().getAmount()).setScale(2, BigDecimal.ROUND_CEILING).toPlainString());

domingo, 23 de mayo de 2021

Java: Gettear y setear campos a objetos genéricos mediante Reflection

import java.lang.reflect.Field;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;

 

/**
     * Método de reflexión que busca por nombre de atributo en un objeto ese atributo
     * y devuelve su valor
     *
     * @param nombreAtributo
     * @param objetoDondeBuscar
     * @return
     */
    public static String buscaParametroPorNombre(final String nombreAtributo, final Object objetoDondeBuscar) {
        Class<?> c = objetoDondeBuscar.getClass();
        Field f = null;
        String result = null;
        do {
            try {
                f = c.getDeclaredField(nombreAtributo);
            } catch (NoSuchFieldException e) {
                log.debug("No se encuentra el campo: " + nombreAtributo);
            }

            if (f != null) {
                try {
                    f.setAccessible(true);
                    result = f.get(objetoDondeBuscar) != null ? String.valueOf(f.get(objetoDondeBuscar)) : null;
                } catch (IllegalAccessException e) {
                    log.debug("No se encuentra el campo: " + nombreAtributo, e);
                }
            }
            c = c.getSuperclass();
        } while ((c != null) && (c != Object.class));

        return result;
    }

    /**
     * Método de reflexión que setea el valor de un atributo en un objeto
     *
     * @param nombreAtributo
     * @param objetoDondeBuscar
     * @param valorAtributo
     * @return
     */
    public static String setParametroPorNombre(final String nombreAtributo, final Object objetoDondeBuscar,
                                               final String valorAtributo) {
        Class<?> c = objetoDondeBuscar.getClass();
        Field f = null;

        try {
            f = c.getDeclaredField(nombreAtributo);
        } catch (NoSuchFieldException e) {
            log.debug("No se encuentra el campo: " + nombreAtributo);
        }
        if (f != null) {
            try {
                f.setAccessible(true);
                f.set(objetoDondeBuscar, valorAtributo);
            } catch (IllegalAccessException e) {
                log.debug("Error al setear el atributo: " + nombreAtributo + "con valor: " + valorAtributo, e);
            }
        }
        return "OK";
    }

martes, 18 de mayo de 2021

Ordenar una lista de objetos en Java

lstPersonasCuentaDo.sort(Comparator.comparing(Persona::getNombreCompleto, Comparator.nullsLast(Comparator.naturalOrder())));

jueves, 18 de marzo de 2021

Identificarse en GitHub con token de acceso en lugar de contraseña (Eclipse y STS)

If you're using Two Factor Authentication on GitHub, the "not authorized" error can be returned even if you are using the correct username and password. This can be resolved by generating a personal access token.

After generating the secure access token, we'll use this instead of a password. Make sure not to leave the page before you're done, because once you leave the page, you'll never see it again (thankfully it can be regenerated, but anything using the previously generated token will cease to authenticate).

This assumes that you've successfully installed EGit and that you've successfully cloned a repository.

  1. Go to your GitHub.com settings, and in the left hand pane click Personal access tokens.
  2. Click Generate new token. Select the scopes that you'd like this token to be able to use, and generate it.
  3. Copy the token. It should look something like this: 9731f5cf519e9abb53e6ba9f5134075438944888 (don't worry, this is invalid).
  4. Back in Eclipse (Juno, since that's OP's version), click Window > Show View > Other.... Under Git, select Git Repositories.
  5. A new pane appears, from which you can open (repository name) > Remotes > origin.
  6. Right click on a node and choose Change Credentials.... Enter your username for User, and your secure access token for the Password.

Tabla de conversión de símbolos UTF-8 a ISO-8859-1 (ANSI)

Latin1 UTF-1 UTF-8 UTF-7,5 UTF-7 JAVA HTML


 ¢à +AKA- \u00a0 &#160;
¡  ¡ ¡ ¢á +AKE- \u00a1 &#161;
¢  ¢ ¢ ¢â +AKI- \u00a2 &#162;
£  £ £ ¢ã +AKM- \u00a3 &#163;
¤  ¤ ¤ ¢ä +AKQ- \u00a4 &#164;
¥  ¥ Â¥ ¢å +AKU- \u00a5 &#165;
¦  ¦ ¦ ¢æ +AKY- \u00a6 &#166;
§  § § ¢ç +AKc- \u00a7 &#167;
¨  ¨ ¨ ¢è +AKg- \u00a8 &#168;
©  © © ¢é +AKk- \u00a9 &#169;
ª  ª ª ¢ê +AKo- \u00aa &#170;
«  « « ¢ë +AKs- \u00ab &#171;


 ¢ì +AKw- \u00ac &#172;
­  ­ ­ ¢í +AK0- \u00ad &#173;
®  ® ® ¢î +AK4- \u00ae &#174;
¯  ¯ ¯ ¢ï +AK8- \u00af &#175;
°  ° ° ¢ð +ALA- \u00b0 &#176;
±  ± ± ¢ñ +ALE- \u00b1 &#177;
²  ² ² ¢ò +ALI- \u00b2 &#178;
³  ³ ³ ¢ó +ALM- \u00b3 &#179;
´  ´ ´ ¢ô +ALQ- \u00b4 &#180;
µ  µ µ ¢õ +ALU- \u00b5 &#181;


 ¢ö +ALY- \u00b6 &#182;
·  · · ¢÷ +ALc- \u00b7 &#183;
¸  ¸ ¸ ¢ø +ALg- \u00b8 &#184;
¹  ¹ ¹ ¢ù +ALk- \u00b9 &#185;
º  º º ¢ú +ALo- \u00ba &#186;
»  » » ¢û +ALs- \u00bb &#187;
¼  ¼ ¼ ¢ü +ALw- \u00bc &#188;
½  ½ ½ ¢ý +AL0- \u00bd &#189;
¾  ¾ ¾ ¢þ +AL4- \u00be &#190;
¿  ¿ ¿ ¢ÿ +AL8- \u00bf &#191;
À  À À £À +AMA- \u00c0 &#192;
Á  Á Á £Á +AME- \u00c1 &#193;
  Â  £Â +AMI- \u00c2 &#194;
à  Ã Ã £Ã +AMM- \u00c3 &#195;
Ä  Ä Ä £Ä +AMQ- \u00c4 &#196;
Å  Å Ã… £Å +AMU- \u00c5 &#197;
Æ  Æ Æ £Æ +AMY- \u00c6 &#198;
Ç  Ç Ç £Ç +AMc- \u00c7 &#199;
È  È È £È +AMg- \u00c8 &#200;
É  É É £É +AMk- \u00c9 &#201;
Ê  Ê Ê £Ê +AMo- \u00ca &#202;
Ë  Ë Ë £Ë +AMs- \u00cb &#203;
Ì  Ì ÃŒ £Ì +AMw- \u00cc &#204;
Í  Í Í £Í +AM0- \u00cd &#205;
Î  Î ÃŽ £Î +AM4- \u00ce &#206;
Ï  Ï Ï £Ï +AM8- \u00cf &#207;
Ð  Ð Ð £Ð +ANA- \u00d0 &#208;
Ñ  Ñ Ñ £Ñ +ANE- \u00d1 &#209;
Ò  Ò Ã’ £Ò +ANI- \u00d2 &#210;
Ó  Ó Ó £Ó +ANM- \u00d3 &#211;
Ô  Ô Ô £Ô +ANQ- \u00d4 &#212;
Õ  Õ Õ £Õ +ANU- \u00d5 &#213;
Ö  Ö Ö £Ö +ANY- \u00d6 &#214;
×  × × £× +ANc- \u00d7 &#215;
Ø  Ø Ø £Ø +ANg- \u00d8 &#216;
Ù  Ù Ù £Ù +ANk- \u00d9 &#217;
Ú  Ú Ú £Ú +ANo- \u00da &#218;
Û  Û Û £Û +ANs- \u00db &#219;
Ü  Ü Ãœ £Ü +ANw- \u00dc &#220;
Ý  Ý Ý £Ý +AN0- \u00dd &#221;
Þ  Þ Þ £Þ +AN4- \u00de &#222;
ß  ß ß £ß +AN8- \u00df &#223;
à  à à £à +AOA- \u00e0 &#224;
á  á á £á +AOE- \u00e1 &#225;
â  â â £â +AOI- \u00e2 &#226;
ã  ã ã £ã +AOM- \u00e3 &#227;
ä  ä ä £ä +AOQ- \u00e4 &#228;
å  å Ã¥ £å +AOU- \u00e5 &#229;
æ  æ æ £æ +AOY- \u00e6 &#230;
ç  ç ç £ç +AOc- \u00e7 &#231;
è  è è £è +AOg- \u00e8 &#232;
é  é é £é +AOk- \u00e9 &#233;
ê  ê ê £ê +AOo- \u00ea &#234;
ë  ë ë £ë +AOs- \u00eb &#235;
ì  ì à £ì +AOw- \u00ec &#236;
í  í í £í +AO0- \u00ed &#237;
î  î î £î +AO4- \u00ee &#238;
ï  ï ï £ï +AO8- \u00ef &#239;
ð  ð ð £ð +APA- \u00f0 &#240;
ñ  ñ ñ £ñ +APE- \u00f1 &#241;
ò  ò ò £ò +API- \u00f2 &#242;
ó  ó ó £ó +APM- \u00f3 &#243;
ô  ô ô £ô +APQ- \u00f4 &#244;
õ  õ õ £õ +APU- \u00f5 &#245;
ö  ö Ã £ö +APY- \u00f6 &#246;
÷  ÷ ÷ £÷ +APc- \u00f7 &#247;
ø  ø ø £ø +APg- \u00f8 &#248;
ù  ù ù £ù +APk- \u00f9 &#249;
ú  ú ú £ú +APo- \u00fa &#250;
û  û û £û +APs- \u00fb &#251;
ü  ü ü £ü +APw- \u00fc &#252;
ý  ý ý £ý +AP0- \u00fd &#253;
þ  þ þ £þ +AP4- \u00fe &#254;
ÿ  ÿ ÿ £ÿ +AP8- \u00ff &#255;

jueves, 4 de marzo de 2021

Java Reflection

1. Overview 

In this article, we will be exploring Java reflection, which allows us to inspect or/and modify runtime attributes of classes, interfaces, fields, and methods. This particularly comes in handy when we don't know their names at compile time.

Additionally, we can instantiate new objects, invoke methods, and get or set field values using reflection. 

2. Project Setup

To use Java reflection, we do not need to include any special jars, any special configuration, or Maven dependencies. The JDK ships with a group of classes that are bundled in the java.lang.reflect package specifically for this purpose.

So all we need to do is to make the following import into our code:

import java.lang.reflect.*;

and we are good to go.

To get access to the class, method, and field information of an instance we call the getClass method which returns the runtime class representation of the object. The returned class object provides methods for accessing information about a class. 

3. Simple Example

To get our feet wet, we are going to take a look at a very basic example that inspects the fields of a simple Java object at runtime.

Let's create a simple Person class with only name and age fields and no methods at all. Here is the Person class:

public class Person {
    private String name;
    private int age;
}

We will now use Java reflection to discover the names of all fields of this class. To appreciate the power of reflection, we will construct a Person object and use Object as the reference type:

@Test
public void givenObject_whenGetsFieldNamesAtRuntime_thenCorrect() {
    Object person = new Person();
    Field[] fields = person.getClass().getDeclaredFields();

    List<String> actualFieldNames = getFieldNames(fields);

    assertTrue(Arrays.asList("name", "age")
      .containsAll(actualFieldNames));
}

This test shows us that we are able to get an array of Field objects from our person object, even if the reference to the object is a parent type of that object.

In the above example, we were only interested in the names of those fields, but there is much more that can be done and we will see further examples of this in the subsequent sections.

Notice how we use a helper method to extract the actual field names, it's a very basic code:

private static List<String> getFieldNames(Field[] fields) { List<String> fieldNames = new ArrayList<>(); for (Field field : fields) fieldNames.add(field.getName()); return fieldNames; } 

4. Java Reflection Use Cases

Before we proceed to the different features of Java reflection, we will discuss some of the common uses we may find for it. Java reflection is extremely powerful and can come in very handy in a number of ways.

For instance, in many cases, we have a naming convention for database tables. We may choose to add consistency by pre-fixing our table names with tbl_, such that a table with student data is called tbl_student_data.

In such cases, we may name the Java object holding student data as Student or StudentData. Then using the CRUD paradigm, we have one entry point for each operation such that Create operations only receive an Object parameter.

We then use reflection to retrieve the object name and field names. At this point, we can map this data to a DB table and assign the object field values to the appropriate DB field names. 

5. Inspecting Java Classes

In this section, we will explore the most fundamental component in the Java reflection API. Java class objects, as we mentioned earlier, give us access to the internal details of any object.

We are going to examine internal details such as an object's class name, modifiers, fields, methods, implemented interfaces, etc. 

5.1. Getting Ready

To get a firm grip on the reflection API, as applied to Java classes and have examples with variety, we will create an abstract Animal class which implements the Eating interface. This interface defines the eating behavior of any concrete Animal object we create.

So firstly, here is the Eating interface:

public interface Eating {
    String eats();
}

and then the concrete Animal implementation of the Eating interface:

public abstract class Animal implements Eating {

    public static String CATEGORY = "domestic";
    private String name;

    protected abstract String getSound();

    // constructor, standard getters and setters omitted 
}

Let's also create another interface called Locomotion which describes how an animal moves:

public interface Locomotion {
    String getLocomotion();
}

We will now create a concrete class called Goat which extends Animal and implements Locomotion. Since the superclass implements Eating, Goat will have to implement that interface's methods as well:

public class Goat extends Animal implements Locomotion {

    @Override
    protected String getSound() {
        return "bleat";
    }

    @Override
    public String getLocomotion() {
        return "walks";
    }

    @Override
    public String eats() {
        return "grass";
    }

    // constructor omitted
}

From this point onward, we will use Java reflection to inspect aspects of Java objects that appear in the classes and interfaces above. 

5.2. Class Names

Let's start by getting the name of an object from the Class:

@Test
public void givenObject_whenGetsClassName_thenCorrect() {
    Object goat = new Goat("goat");
    Class<?> clazz = goat.getClass();

    assertEquals("Goat", clazz.getSimpleName());
    assertEquals("com.baeldung.reflection.Goat", clazz.getName());
    assertEquals("com.baeldung.reflection.Goat", clazz.getCanonicalName());
}

Note that the getSimpleName method of Class returns the basic name of the object as it would appear in its declaration. Then the other two methods return the fully qualified class name including the package declaration.

Let's also see how we can create an object of the Goat class if we only know its fully qualified class name:

@Test
public void givenClassName_whenCreatesObject_thenCorrect(){
    Class<?> clazz = Class.forName("com.baeldung.reflection.Goat");

    assertEquals("Goat", clazz.getSimpleName());
    assertEquals("com.baeldung.reflection.Goat", clazz.getName());
    assertEquals("com.baeldung.reflection.Goat", clazz.getCanonicalName()); 
}

Notice that the name we pass to the static forName method should include the package information. Otherwise, we will get a ClassNotFoundException. 

5.3. Class Modifiers

We can determine the modifiers used in a class by calling the getModifiers method which returns an Integer. Each modifier is a flag bit which is either set or cleared.

The java.lang.reflect.Modifier class offers static methods that analyze the returned Integer for the presence or absence of a specific modifier.

Let's confirm the modifiers of some of the classes we defined above:

@Test
public void givenClass_whenRecognisesModifiers_thenCorrect() {
    Class<?> goatClass = Class.forName("com.baeldung.reflection.Goat");
    Class<?> animalClass = Class.forName("com.baeldung.reflection.Animal");

    int goatMods = goatClass.getModifiers();
    int animalMods = animalClass.getModifiers();

    assertTrue(Modifier.isPublic(goatMods));
    assertTrue(Modifier.isAbstract(animalMods));
    assertTrue(Modifier.isPublic(animalMods));
}

We are able to inspect modifiers of any class located in a library jar that we are importing into our project.

In most cases, we may need to use the forName approach rather than the full-blown instantiation since that would be an expensive process in the case of memory heavy classes. 

5.4. Package Information

By using Java reflection we are also able to get information about the package of any class or object. This data is bundled inside the Package class which is returned by a call to getPackage method on the class object.

Let's run a test to retrieve the package name:

@Test
public void givenClass_whenGetsPackageInfo_thenCorrect() {
    Goat goat = new Goat("goat");
    Class<?> goatClass = goat.getClass();
    Package pkg = goatClass.getPackage();

    assertEquals("com.baeldung.reflection", pkg.getName());
} 

5.5. Super Class

We are also able to obtain the superclass of any Java class by using Java reflection.

In many cases, especially while using library classes or Java's builtin classes, we may not know beforehand the superclass of an object we are using, this subsection will show how to obtain this information.

So let's go ahead and determine the superclass of Goat. Additionally, we also show that java.lang.String class is a subclass of java.lang.Object class:

@Test
public void givenClass_whenGetsSuperClass_thenCorrect() {
    Goat goat = new Goat("goat");
    String str = "any string";

    Class<?> goatClass = goat.getClass();
    Class<?> goatSuperClass = goatClass.getSuperclass();

    assertEquals("Animal", goatSuperClass.getSimpleName());
    assertEquals("Object", str.getClass().getSuperclass().getSimpleName());
}

5.6. Implemented Interfaces

Using Java reflection, we are also able to get the list of interfaces implemented by a given class.

Let's retrieve the class types of the interfaces implemented by the Goat class and the Animal abstract class:

@Test
public void givenClass_whenGetsImplementedInterfaces_thenCorrect(){
    Class<?> goatClass = Class.forName("com.baeldung.reflection.Goat");
    Class<?> animalClass = Class.forName("com.baeldung.reflection.Animal");

    Class<?>[] goatInterfaces = goatClass.getInterfaces();
    Class<?>[] animalInterfaces = animalClass.getInterfaces();

    assertEquals(1, goatInterfaces.length);
    assertEquals(1, animalInterfaces.length);
    assertEquals("Locomotion", goatInterfaces[0].getSimpleName());
    assertEquals("Eating", animalInterfaces[0].getSimpleName());
}

Notice from the assertions that each class implements only a single interface. Inspecting the names of these interfaces, we find that Goat implements Locomotion and Animal implements Eating, just as it appears in our code.

You may have observed that Goat is a subclass of the abstract class Animal and implements the interface method eats(), then, Goat also implements the Eating interface.

It is therefore worth noting that only those interfaces that a class explicitly declares as implemented with the implements keyword appear in the returned array.

So even if a class implements interface methods because its superclass implements that interface, but the subclass does not directly declare that interface with the implements keyword, then that interface will not appear in the array of interfaces. 

5.7. Constructors, Methods, and Fields

With Java reflection, we are able to inspect the constructors of any object's class as well as methods and fields.

We will, later on, be able to see deeper inspections on each of these components of a class but for now, it suffices to just get their names and compare them with what we expect.

Let's see how to get the constructor of the Goat class:

@Test
public void givenClass_whenGetsConstructor_thenCorrect(){
    Class<?> goatClass = Class.forName("com.baeldung.reflection.Goat");

    Constructor<?>[] constructors = goatClass.getConstructors();

    assertEquals(1, constructors.length);
    assertEquals("com.baeldung.reflection.Goat", constructors[0].getName());
}

We can also inspect the fields of the Animal class like so:

@Test
public void givenClass_whenGetsFields_thenCorrect(){
    Class<?> animalClass = Class.forName("com.baeldung.reflection.Animal");
    Field[] fields = animalClass.getDeclaredFields();

    List<String> actualFields = getFieldNames(fields);

    assertEquals(2, actualFields.size());
    assertTrue(actualFields.containsAll(Arrays.asList("name", "CATEGORY")));
}

Just like we can inspect the methods of the Animal class:

@Test
public void givenClass_whenGetsMethods_thenCorrect(){
    Class<?> animalClass = Class.forName("com.baeldung.reflection.Animal");
    Method[] methods = animalClass.getDeclaredMethods();
    List<String> actualMethods = getMethodNames(methods);

    assertEquals(4, actualMethods.size());
    assertTrue(actualMethods.containsAll(Arrays.asList("getName",
      "setName", "getSound")));
}

Just like getFieldNames, we have added a helper method to retrieve method names from an array of Method objects:

private static List<String> getMethodNames(Method[] methods) {
    List<String> methodNames = new ArrayList<>();
    for (Method method : methods)
      methodNames.add(method.getName());
    return methodNames;
}

6. Inspecting Constructors

With Java reflection, we can inspect constructors of any class and even create class objects at runtime. This is made possible by the java.lang.reflect.Constructor class.

Earlier on, we only looked at how to get the array of Constructor objects, from which we were able to get the names of the constructors.

In this section, we will focus on how to retrieve specific constructors. In Java, as we know, no two constructors of a class share exactly the same method signature. So we will use this uniqueness to get one constructor from many.

To appreciate the features of this class, we will create a Bird subclass of Animal with three constructors. We will not implement Locomotion so that we can specify that behavior using a constructor argument, to add still more variety:

public class Bird extends Animal {
    private boolean walks;

    public Bird() {
        super("bird");
    }

    public Bird(String name, boolean walks) {
        super(name);
        setWalks(walks);
    }

    public Bird(String name) {
        super(name);
    }

    public boolean walks() {
        return walks;
    }

    // standard setters and overridden methods
}

Let's confirm by using reflection that this class has three constructors:

@Test
public void givenClass_whenGetsAllConstructors_thenCorrect() {
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Constructor<?>[] constructors = birdClass.getConstructors();

    assertEquals(3, constructors.length);
}

Next, we will retrieve each constructor for the Bird class by passing the constructor's parameter class types in declared order:

@Test
public void givenClass_whenGetsEachConstructorByParamTypes_thenCorrect(){
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");

    Constructor<?> cons1 = birdClass.getConstructor();
    Constructor<?> cons2 = birdClass.getConstructor(String.class);
    Constructor<?> cons3 = birdClass.getConstructor(String.class, boolean.class);
}

There is no need for assertion since when a constructor with given parameter types in the given order does not exist, we will get a NoSuchMethodException and the test will automatically fail.

In the last test, we will see how to instantiate objects at runtime while supplying their parameters:

@Test
public void givenClass_whenInstantiatesObjectsAtRuntime_thenCorrect() {
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Constructor<?> cons1 = birdClass.getConstructor();
    Constructor<?> cons2 = birdClass.getConstructor(String.class);
    Constructor<?> cons3 = birdClass.getConstructor(String.class,
      boolean.class);

    Bird bird1 = (Bird) cons1.newInstance();
    Bird bird2 = (Bird) cons2.newInstance("Weaver bird");
    Bird bird3 = (Bird) cons3.newInstance("dove", true);

    assertEquals("bird", bird1.getName());
    assertEquals("Weaver bird", bird2.getName());
    assertEquals("dove", bird3.getName());

    assertFalse(bird1.walks());
    assertTrue(bird3.walks());
}

We instantiate class objects by calling the newInstance method of Constructor class and passing the required parameters in declared order. We then cast the result to the required type.

It's also possible to call the default constructor using the Class.newInstance() method. However, this method has been deprecated since Java 9, and we shouldn't use it in modern Java projects.

For bird1, we use the default constructor which from our Bird code, automatically sets the name to bird and we confirm that with a test.

We then instantiate bird2 with only a name and test as well, remember that when we don't set locomotion behavior as it defaults to false, as seen in the last two assertions. 

7. Inspecting Fields

Previously, we only inspected the names of fields, in this section, we will show how to get and set their values at runtime.

There are two main methods used to inspect fields of a class at runtime: getFields() and getField(fieldName).

The getFields() method returns all accessible public fields of the class in question. It will return all the public fields in both the class and all superclasses.

For instance, when we call this method on the Bird class, we will only get the CATEGORY field of its superclass, Animal, since Bird itself does not declare any public fields:

@Test
public void givenClass_whenGetsPublicFields_thenCorrect() {
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Field[] fields = birdClass.getFields();

    assertEquals(1, fields.length);
    assertEquals("CATEGORY", fields[0].getName());
}

This method also has a variant called getField which returns only one Field object by taking the name of the field:

@Test
public void givenClass_whenGetsPublicFieldByName_thenCorrect() {
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Field field = birdClass.getField("CATEGORY");

    assertEquals("CATEGORY", field.getName());
}

We are not able to access private fields declared in superclasses and not declared in the child class. This is why we are not able to access the name field.

However, we can inspect private fields declared in the class we are dealing with by calling the getDeclaredFields method:

@Test
public void givenClass_whenGetsDeclaredFields_thenCorrect(){
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Field[] fields = birdClass.getDeclaredFields();

    assertEquals(1, fields.length);
    assertEquals("walks", fields[0].getName());
}

We can also use its other variant in case we know the name of the field:

@Test
public void givenClass_whenGetsFieldsByName_thenCorrect() {
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Field field = birdClass.getDeclaredField("walks");

    assertEquals("walks", field.getName());
}

If we get the name of the field wrong or type in an in-existent field, we will get a NoSuchFieldException.

We get the field type as follows:

@Test
public void givenClassField_whenGetsType_thenCorrect() {
    Field field = Class.forName("com.baeldung.reflection.Bird")
      .getDeclaredField("walks");
    Class<?> fieldClass = field.getType();

    assertEquals("boolean", fieldClass.getSimpleName());
}

Next, we will look at how to access field values and modify them. To be able to get the value of a field, let alone setting it, we must first set it's accessible by calling setAccessible method on the Field object and pass boolean true to it:

@Test
public void givenClassField_whenSetsAndGetsValue_thenCorrect() {
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Bird bird = (Bird) birdClass.getConstructor().newInstance();
    Field field = birdClass.getDeclaredField("walks");
    field.setAccessible(true);

    assertFalse(field.getBoolean(bird));
    assertFalse(bird.walks());
    
    field.set(bird, true);
    
    assertTrue(field.getBoolean(bird));
    assertTrue(bird.walks());
}

In the above test, we ascertain that indeed the value of the walks field is false before setting it to true.

Notice how we use the Field object to set and get values by passing it the instance of the class we are dealing with and possibly the new value we want the field to have in that object.

One important thing to note about Field objects is that when it is declared as public static, then we don't need an instance of the class containing them, we can just pass null in its place and still obtain the default value of the field, like so:

@Test
public void givenClassField_whenGetsAndSetsWithNull_thenCorrect(){
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Field field = birdClass.getField("CATEGORY");
    field.setAccessible(true);

    assertEquals("domestic", field.get(null));
}

8. Inspecting Methods

In a previous example, we used reflection only to inspect method names. However, Java reflection is more powerful than that.

With Java reflection, we can invoke methods at runtime and pass them their required parameters, just like we did for constructors. Similarly, we can also invoke overloaded methods by specifying parameter types of each.

Just like fields, there are two main methods that we use for retrieving class methods. The getMethods method returns an array of all public methods of the class and superclasses.

This means that with this method, we can get public methods of the java.lang.Object class like toString, hashCode, and notifyAll:

@Test
public void givenClass_whenGetsAllPublicMethods_thenCorrect(){
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Method[] methods = birdClass.getMethods();
    List<String> methodNames = getMethodNames(methods);

    assertTrue(methodNames.containsAll(Arrays
      .asList("equals", "notifyAll", "hashCode",
        "walks", "eats", "toString")));
}

To get only public methods of the class we are interested in, we have to use getDeclaredMethods method:

@Test
public void givenClass_whenGetsOnlyDeclaredMethods_thenCorrect(){
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    List<String> actualMethodNames
      = getMethodNames(birdClass.getDeclaredMethods());

    List<String> expectedMethodNames = Arrays
      .asList("setWalks", "walks", "getSound", "eats");

    assertEquals(expectedMethodNames.size(), actualMethodNames.size());
    assertTrue(expectedMethodNames.containsAll(actualMethodNames));
    assertTrue(actualMethodNames.containsAll(expectedMethodNames));
}

Each of these methods has the singular variation which returns a single Method object whose name we know:

@Test
public void givenMethodName_whenGetsMethod_thenCorrect() throws Exception {
    Bird bird = new Bird();
    Method walksMethod = bird.getClass().getDeclaredMethod("walks");
    Method setWalksMethod = bird.getClass().getDeclaredMethod("setWalks", boolean.class);

    assertTrue(walksMethod.canAccess(bird));
    assertTrue(setWalksMethod.canAccess(bird));
}

Notice how we retrieve individual methods and specify what parameter types they take. Those that don't take parameter types are retrieved with an empty variable argument, leaving us with only a single argument, the method name.

Next, we will show how to invoke a method at runtime. We know by default that the walks attribute of the Bird class is false, we want to call its setWalks method and set it to true:

@Test
public void givenMethod_whenInvokes_thenCorrect() {
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Bird bird = (Bird) birdClass.getConstructor().newInstance();
    Method setWalksMethod = birdClass.getDeclaredMethod("setWalks", boolean.class);
    Method walksMethod = birdClass.getDeclaredMethod("walks");
    boolean walks = (boolean) walksMethod.invoke(bird);

    assertFalse(walks);
    assertFalse(bird.walks());

    setWalksMethod.invoke(bird, true);

    boolean walks2 = (boolean) walksMethod.invoke(bird);
    assertTrue(walks2);
    assertTrue(bird.walks());
}

Notice how we first invoke the walks method and cast the return type to the appropriate data type and then check its value. We then later invoke the setWalks method to change that value and test again. 

9. Conclusion

In this tutorial, we have covered the Java Reflection API and looked at how to use it to inspect classes, interfaces, fields, and methods at runtime without prior knowledge of their internals by compile time.

martes, 23 de febrero de 2021

SOLID (D): Dependency Inversion Principle

Dependency Inversion Principle

As a Java programmer, you’ve likely heard about code coupling and have been told to avoid tightly coupled code. Ignorance of writing “good code” is the main reason of tightly coupled code existing in applications. As an example, creating an object of a class using the new operator results in a class being tightly coupled to another class. Such coupling appears harmless and does not disrupt small programs. But, as you move into enterprise application development, tightly coupled code can lead to serious adverse consequences.

When one class knows explicitly about the design and implementation of another class, changes to one class raise the risk of breaking the other class. Such changes can have rippling effects across the application making the application fragile. To avoid such problems, you should write “good code” that is loosely coupled, and to support this you can turn to the Dependency Inversion Principle.

The Dependency Inversion Principle represents the last “D” of the five SOLID principles of object-oriented programming. Robert C. Martin first postulated the Dependency Inversion Principle and published it in 1996. The principle states:

“A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend on details. Details should depend on abstractions.”

Conventional application architecture follows a top-down design approach where a high-level problem is broken into smaller parts. In other words, the high-level design is described in terms of these smaller parts. As a result, high-level modules that gets written directly depends on the smaller (low-level) modules.

What Dependency Inversion Principle says is that instead of a high-level module depending on a low-level module, both should depend on an abstraction. Let us look at it in the context of Java through this figure.

In the figure above, without Dependency Inversion Principle, Object A in Package A refers Object B in Package B. With Dependency Inversion Principle, an Interface A is introduced as an abstraction in Package A. Object A now refers Interface A and Object B inherits from Interface A. What the principle has done is:

  1. Both Object A and Object B now depends on Interface A, the abstraction.

  2. It inverted the dependency that existed from Object A to Object B into Object B being dependent on the abstraction (Interface A).

Before we write code that follows the Dependency Inversion Principle, let’s examine a typical violating of the principle.

Dependency Inversion Principle Violation (Bad Example)

Consider the example of an electric switch that turns a light bulb on or off. We can model this requirement by creating two classes: ElectricPowerSwitch and LightBulb. Let’s write the LightBulb class first.

LightBulb.java

In the LightBulb class above, we wrote the turnOn() and turnOff() methods to turn a bulb on and off.

Next, we will write the ElectricPowerSwitch class.

ElectricPowerSwitch.java

In the example above, we wrote the ElectricPowerSwitch class with a field referencing LightBulb. In the constructor, we created a LightBulb object and assigned it to the field. We then wrote a isOn() method that returns the state of ElectricPowerSwitch as a boolean value. In the press() method, based on the state, we called the turnOn() and turnOff() methods.

Our switch is now ready for use to turn on and off the light bulb. But the mistake we did is apparent. Our high-level ElectricPowerSwitch class is directly dependent on the low-level LightBulb class. if you see in the code, the LightBulb class is hardcoded in ElectricPowerSwitch. But, a switch should not be tied to a bulb. It should be able to turn on and off other appliances and devices too, say a fan, an AC, or the entire lightning system of an amusement park. Now, imagine the modifications we will require in the ElectricPowerSwitch class each time we add a new appliance or device. We can conclude that our design is flawed and we need to revisit it by following the Dependency Inversion Principle.

Following the Dependency Inversion Principle

To follow the Dependency Inversion Principle in our example, we will need an abstraction that both the ElectricPowerSwitch and LightBulb classes will depend on. But, before creating it, let’s create an interface for switches.

Switch.java

We wrote an interface for switches with the isOn() and press() methods. This interface will give us the flexibility to plug in other types of switches, say a remote control switch later on, if required. Next, we will write the abstraction in the form of an interface, which we will call Switchable.

Switchable.java

In the example above, we wrote the Switchable interface with the turnOn() and turnoff() methods. From now on, any switchable devices in the application can implement this interface and provide their own functionality. Our ElectricPowerSwitch class will also depend on this interface, as shown below:

ElectricPowerSwitch.java

In the ElectricPowerSwitch class we implemented the Switch interface and referred the Switchable interface instead of any concrete class in a field. We then called the turnOn() and turnoff() methods on the interface, which at run time will get invoked on the object passed to the constructor. Now, we can add low-level switchable classes without worrying about modifying the ElectricPowerSwitch class. We will add two such classes: LightBulb and Fan.

LightBulb.java

Fan.java

In both the LightBulb and Fan classes that we wrote, we implemented the Switchable interface to provide their own functionality for turning on and off. While writing the classes, if you have missed how we arranged them in packages, notice that we kept the Switchable interface in a different package from the low-level electric device classes. Although, this did not make any difference from coding perspective, except for an import statement, by doing so we have made our intentions clear- We want the low-level classes to depend (inversely) on our abstraction. This will also help us if we later decide to release the high-level package as a public API that other applications can use for their devices. To test our example, let’s write this unit test.

ElectricPowerSwitchTest.java

The output is:

Summary of the Dependency Inversion Principle

Robert Martin equated the Dependency Inversion Principle, as a first-class combination of the Open Closed Principle and the Liskov Substitution Principle, and found it important enough to give its own name. While using the Dependency Inversion Principle comes with the overhead of writing additional code, the advantages that it provides outweigh the extra effort. Therefore, from now whenever you start writing code, consider the possibility of dependencies breaking your code, and if so, add abstractions to make your code resilient to changes.

Dependency Inversion Principle and the Spring Framework

You may think the Dependency Inversion Principle is related to Dependency Injection as it applies to the Spring Framework, and you would be correct. Uncle Bob Martin coined this concept of Dependency Inversion before Martin Fowler coined the term Dependency Injection. The two concepts are highly related. Dependency Inversion is more focused on the structure of your code, its focus is keeping your code loosely coupled. On the other hand, Dependency Injection is how the code functionally works. When programming with the Spring Framework, Spring is using Dependency Injection to assemble your application. Dependency Inversion is what decouples your code so Spring can use Dependency Injection at run time.