Category Archives: Java

Arrays parallelSort

O método Arrays.sort é bastante eficiente para a maioria dos casos, mas quando lidamos com arrays muito grandes, sua versão sequencial pode se tornar um gargalo, principalmente ao aproveitar apenas um único núcleo da CPU. Para arrays de tipos primitivos, o Java utiliza uma implementação de Dual-Pivot Quicksort, que tem, em média, complexidade de O(n log n). Entretanto, mesmo essa implementação bem otimizada pode enfrentar limitações em contextos de alto volume de dados, sobretudo se a ordenação for um dos pontos críticos de desempenho da aplicação.

Uma solução bastante interessante nesses cenários é utilizar o Arrays.parallelSort. Introduzido a partir do Java 8, esse método parte o array em segmentos menores e os classifica de forma concorrente, aproveitando o framework Fork/Join para distribuir as tarefas entre múltiplos núcleos. Essa abordagem pode reduzir significativamente o tempo total de ordenação em sistemas com múltiplos threads, especialmente em arrays de tamanho enorme. Além disso, para conjuntos de dados cuja chave seja numérica e limitada, algoritmos não-baseados em comparação, como Radix Sort ou Counting Sort, podem ser explorados, já que esses algoritmos podem apresentar desempenho linear sob determinadas condições.

Portanto, ao avaliar a performance do Arrays.sort com arrays grandes, a recomendação é considerar o uso de algoritmos paralelos, como o Arrays.parallelSort, que se aproveitam da arquitetura moderna multi-core. Essa solução não só melhora o tempo de execução, mas também distribui de forma mais equilibrada a carga de processamento, resultando em um desempenho superior para aplicações que lidam com volumes massivos de dados.

Você também pode explorar algoritmos específicos para tipos de dados particulares. Por exemplo, para ordenar strings ou outros objetos com um padrão de comparação complexo, uma combinação de particionamento eficiente e técnicas de otimização local pode trazer ganhos importantes. Essa abordagem pode ser ajustada conforme o perfil dos dados e o ambiente de execução, permitindo um balanceamento ideal entre uso de memória e velocidade de processamento.

Java Factory

O padrão de design de fábrica é usado quando temos uma superclasse com várias subclasses e, com base na entrada, precisamos retornar uma das subclasses. Esse padrão tira a responsabilidade da instanciação de uma classe do programa cliente para a classe de fábrica. Vamos primeiro aprender como implementar um padrão de design de fábrica em Java e, em seguida, veremos as vantagens do padrão de fábrica. Veremos alguns dos usos do padrão de design de fábrica no JDK. Observe que esse padrão também é conhecido como Padrão de Design de Método de Fábrica .

Padrão de Design de Fábrica Super Classe

A superclasse no padrão de design de fábrica pode ser uma interface, uma classe abstrata ou uma classe Java normal. Para nosso exemplo de padrão de design de fábrica, temos uma superclasse abstrata com um método sobrescrito toString() para fins de teste.

package com.journaldev.design.model;

public abstract class Computer {
	
	public abstract String getRAM();
	public abstract String getHDD();
	public abstract String getCPU();
	
	@Override
	public String toString(){
		return "RAM= "+this.getRAM()+", HDD="+this.getHDD()+", CPU="+this.getCPU();
	}
}

Subclasses de Padrão de Design de Fábrica

Digamos que temos duas subclasses, PC e Servidor, com a implementação abaixo.

package com.journaldev.design.model;

public class PC extends Computer {

	private String ram;
	private String hdd;
	private String cpu;
	
	public PC(String ram, String hdd, String cpu){
		this.ram=ram;
		this.hdd=hdd;
		this.cpu=cpu;
	}
	@Override
	public String getRAM() {
		return this.ram;
	}

	@Override
	public String getHDD() {
		return this.hdd;
	}

	@Override
	public String getCPU() {
		return this.cpu;
	}

}

Observe que ambas as classes estão estendendo Computera superclasse.

package com.journaldev.design.model;

public class Server extends Computer {

	private String ram;
	private String hdd;
	private String cpu;
	
	public Server(String ram, String hdd, String cpu){
		this.ram=ram;
		this.hdd=hdd;
		this.cpu=cpu;
	}
	@Override
	public String getRAM() {
		return this.ram;
	}

	@Override
	public String getHDD() {
		return this.hdd;
	}

	@Override
	public String getCPU() {
		return this.cpu;
	}

}

Classe de fábrica

Agora que temos superclasses e subclasses prontas, podemos escrever nossa classe de fábrica. Aqui está a implementação básica.

package com.journaldev.design.factory;

import com.journaldev.design.model.Computer;
import com.journaldev.design.model.PC;
import com.journaldev.design.model.Server;

public class ComputerFactory {

	public static Computer getComputer(String type, String ram, String hdd, String cpu){
		if("PC".equalsIgnoreCase(type)) return new PC(ram, hdd, cpu);
		else if("Server".equalsIgnoreCase(type)) return new Server(ram, hdd, cpu);
		
		return null;
	}
}

Alguns pontos importantes sobre o método Factory Design Pattern são:

  1. Podemos manter a classe Factory Singleton ou podemos manter o método que retorna a subclasse como estático .
  2. Observe que, com base no parâmetro de entrada, diferentes subclasses são criadas e retornadas. getComputeré o método de fábrica.

padrão de fábrica java, padrão de fábrica, padrão de projeto de fábrica, diagrama de classe de padrão de fábrica

Aqui está um programa cliente de teste simples que usa a implementação do padrão de design de fábrica acima.

package com.journaldev.design.test;

import com.journaldev.design.factory.ComputerFactory;
import com.journaldev.design.model.Computer;

public class TestFactory {

	public static void main(String[] args) {
		Computer pc = ComputerFactory.getComputer("pc","2 GB","500 GB","2.4 GHz");
		Computer server = ComputerFactory.getComputer("server","16 GB","1 TB","2.9 GHz");
		System.out.println("Factory PC Config::"+pc);
		System.out.println("Factory Server Config::"+server);
	}

}

A saída do programa acima é:

Factory PC Config::RAM= 2 GB, HDD=500 GB, CPU=2.4 GHz
Factory Server Config::RAM= 16 GB, HDD=1 TB, CPU=2.9 GHz

Vantagens do Padrão de Projeto de Fábrica

  1. O padrão de design de fábrica fornece uma abordagem ao código para interface em vez de implementação.
  2. O padrão Factory remove a instanciação de classes de implementação reais do código do cliente. O padrão Factory torna nosso código mais robusto, menos acoplado e fácil de estender. Por exemplo, podemos facilmente alterar a implementação da classe PC porque o programa cliente não tem conhecimento disso.
  3. O padrão de fábrica fornece abstração entre classes de implementação e cliente por meio de herança.

Exemplos de padrões de design de fábrica no JDK

  1. Os métodos java.util.Calendar, ResourceBundle e NumberFormat getInstance()usam o padrão Factory.
  2. valueOf()método em classes wrapper como Boolean, Integer etc.

Tutorial em vídeo do YouTube sobre o padrão de design de fábrica

Recentemente, carreguei um vídeo no YouTube para o padrão Factory Design, por favor, dê uma olhada. Por favor, curta e compartilhe o vídeo e inscreva-se no meu canal do YouTube. https://www.youtube.com/watch?v=J1QU\_R4MQQc

Você pode baixar o código de exemplo do meu Projeto GitHub .

Java Singleton

Implementação do Padrão Java Singleton

Para implementar um padrão singleton, temos diferentes abordagens, mas todas elas têm os seguintes conceitos comuns.

  • Construtor privado para restringir a instanciação da classe de outras classes.
  • Variável estática privada da mesma classe que é a única instância da classe.
  • Método estático público que retorna a instância da classe. Este é o ponto de acesso global para o mundo externo obter a instância da classe singleton.

Nas próximas seções, aprenderemos diferentes abordagens para implementação de padrões singleton e preocupações de design com a implementação.

1. Inicialização rápida

Na inicialização ansiosa, a instância da classe singleton é criada no momento do carregamento da classe. A desvantagem da inicialização ansiosa é que o método é criado mesmo que o aplicativo cliente não o esteja usando. Aqui está a implementação da classe singleton de inicialização estática:

package com.journaldev.singleton;

public class EagerInitializedSingleton {

    private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();

    // private constructor to avoid client applications using the constructor
    private EagerInitializedSingleton(){}

    public static EagerInitializedSingleton getInstance() {
        return instance;
    }
}

Se sua classe singleton não estiver usando muitos recursos, essa é a abordagem a ser usada. Mas na maioria dos cenários, classes singleton são criadas para recursos como Sistema de Arquivos, conexões de Banco de Dados, etc. Devemos evitar a instanciação, a menos que o cliente chame o getInstancemétodo. Além disso, esse método não fornece nenhuma opção para tratamento de exceções.

2. Inicialização de bloco estático

A implementação da inicialização de bloco estático é semelhante à inicialização ansiosa, exceto que a instância da classe é criada no bloco estático que fornece a opção de tratamento de exceções .

package com.journaldev.singleton;

public class StaticBlockSingleton {

    private static StaticBlockSingleton instance;

    private StaticBlockSingleton(){}

    // static block initialization for exception handling
    static {
        try {
            instance = new StaticBlockSingleton();
        } catch (Exception e) {
            throw new RuntimeException("Exception occurred in creating singleton instance");
        }
    }

    public static StaticBlockSingleton getInstance() {
        return instance;
    }
}

Tanto a inicialização rápida quanto a inicialização de bloco estático criam a instância antes mesmo que ela seja usada, e essa não é a melhor prática a ser usada.

3. Inicialização preguiçosa

O método de inicialização lazy para implementar o padrão singleton cria a instância no método de acesso global. Aqui está o código de exemplo para criar a classe singleton com esta abordagem:

package com.journaldev.singleton;

public class LazyInitializedSingleton {

    private static LazyInitializedSingleton instance;

    private LazyInitializedSingleton(){}

    public static LazyInitializedSingleton getInstance() {
        if (instance == null) {
            instance = new LazyInitializedSingleton();
        }
        return instance;
    }
}

A implementação anterior funciona bem no caso do ambiente single-threaded, mas quando se trata de sistemas multi-threaded, pode causar problemas se vários threads estiverem dentro da ifcondição ao mesmo tempo. Isso destruirá o padrão singleton e ambos os threads obterão instâncias diferentes da classe singleton. Na próxima seção, veremos diferentes maneiras de criar uma classe singleton thread-safe .

4. Singleton seguro para threads

Uma maneira simples de criar uma classe singleton thread-safe é fazer com que o método de acesso global seja sincronizado para que apenas uma thread possa executar esse método por vez. Aqui está uma implementação geral dessa abordagem:

package com.journaldev.singleton;

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton(){}

    public static synchronized ThreadSafeSingleton getInstance() {
        if (instance == null) {
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }

}

A implementação anterior funciona bem e fornece segurança de thread, mas reduz o desempenho devido ao custo associado ao método synchronized, embora precisemos dele apenas para os primeiros threads que podem criar instâncias separadas. Para evitar essa sobrecarga extra toda vez, o princípio de bloqueio verificado duas vezes é usado. Nessa abordagem, o bloco synchronized é usado dentro da ifcondição com uma verificação adicional para garantir que apenas uma instância de uma classe singleton seja criada. O seguinte trecho de código fornece a implementação de bloqueio verificado duas vezes:

public static ThreadSafeSingleton getInstanceUsingDoubleLocking() {
    if (instance == null) {
        synchronized (ThreadSafeSingleton.class) {
            if (instance == null) {
                instance = new ThreadSafeSingleton();
            }
        }
    }
    return instance;
}

Continue seu aprendizado com a classe Thread Safe Singleton .

5. Implementação de Bill Pugh Singleton

Antes do Java 5, o modelo de memória Java tinha muitos problemas, e as abordagens anteriores costumavam falhar em certos cenários em que muitos threads tentavam obter a instância da classe singleton simultaneamente. Então Bill Pugh surgiu com uma abordagem diferente para criar a classe singleton usando uma classe auxiliar estática interna . Aqui está um exemplo da implementação do Singleton de Bill Pugh:

package com.journaldev.singleton;

public class BillPughSingleton {

    private BillPughSingleton(){}

    private static class SingletonHelper {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

Observe a classe estática interna privada que contém a instância da classe singleton. Quando a classe singleton é carregada, SingletonHelpera classe não é carregada na memória e somente quando alguém chama o getInstance()método, essa classe é carregada e cria a instância da classe singleton. Essa é a abordagem mais amplamente usada para a classe singleton, pois não requer sincronização.

6. Usando Reflexão para destruir o Padrão Singleton

A reflexão pode ser usada para destruir todas as abordagens de implementação singleton anteriores. Aqui está uma classe de exemplo:

package com.journaldev.singleton;

import java.lang.reflect.Constructor;

public class ReflectionSingletonTest {

    public static void main(String[] args) {
        EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
        EagerInitializedSingleton instanceTwo = null;
        try {
            Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                // This code will destroy the singleton pattern
                constructor.setAccessible(true);
                instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(instanceOne.hashCode());
        System.out.println(instanceTwo.hashCode());
    }

}

Ao executar a classe de teste anterior, você notará que hashCodeambas as instâncias não são as mesmas, o que destrói o padrão singleton. O Reflection é muito poderoso e usado em muitos frameworks como Spring e Hibernate. Continue seu aprendizado com o Tutorial Java Reflection .

7. Enumeração Singleton

Para superar essa situação com o Reflection, Joshua Bloch sugere o uso de enumpara implementar o padrão de design singleton, já que Java garante que qualquer enumvalor seja instanciado apenas uma vez em um programa Java. Como os valores Java Enum são globalmente acessíveis, o singleton também é. A desvantagem é que o enumtipo é um tanto inflexível (por exemplo, ele não permite inicialização preguiçosa).

package com.journaldev.singleton;

public enum EnumSingleton {

    INSTANCE;

    public static void doSomething() {
        // do something
    }
}

8. Serialização e Singleton

Às vezes, em sistemas distribuídos, precisamos implementar Serializableinterface na classe singleton para que possamos armazenar seu estado no sistema de arquivos e recuperá-lo em um momento posterior. Aqui está uma pequena classe singleton que implementa Serializableinterface também:

package com.journaldev.singleton;

import java.io.Serializable;

public class SerializedSingleton implements Serializable {

    private static final long serialVersionUID = -7604766932017737115L;

    private SerializedSingleton(){}

    private static class SingletonHelper {
        private static final SerializedSingleton instance = new SerializedSingleton();
    }

    public static SerializedSingleton getInstance() {
        return SingletonHelper.instance;
    }

}

O problema com a classe singleton serializada é que sempre que a desserializamos, ela criará uma nova instância da classe. Aqui está um exemplo:

package com.journaldev.singleton;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class SingletonSerializedTest {

    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        SerializedSingleton instanceOne = SerializedSingleton.getInstance();
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                "filename.ser"));
        out.writeObject(instanceOne);
        out.close();

        // deserialize from file to object
        ObjectInput in = new ObjectInputStream(new FileInputStream(
                "filename.ser"));
        SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
        in.close();

        System.out.println("instanceOne hashCode="+instanceOne.hashCode());
        System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());

    }

}

Esse código produz esta saída:

Output
instanceOne hashCode=2011117821
instanceTwo hashCode=109647522

Então ele destrói o padrão singleton. Para superar esse cenário, tudo o que precisamos fazer é fornecer a implementação do readResolve()método.

protected Object readResolve() {
    return getInstance();
}

Depois disso, você notará que hashCodeambas as instâncias são iguais no programa de teste.

Fonte: https://www.digitalocean.com/community/tutorials/java-singleton-design-pattern-best-practices-examples

TestNG

A Comprehensive Guide to TestNG: Mastering Test Automation in Java

In the world of software testing and automation, TestNG has emerged as one of the most powerful and widely adopted testing frameworks for Java. Its versatility, rich features, and simplicity make it a go-to tool for developers and QA engineers alike. Whether you’re just starting out with automated testing or looking to optimize your current workflow, this in-depth post will help you master TestNG and use it effectively in your projects.


What is TestNG?

TestNG (Test Next Generation) is a popular Java testing framework inspired by JUnit but with enhanced functionalities. It was designed to address the shortcomings of older testing frameworks by providing additional features, such as:

  • Test annotations for more flexibility
  • Dependency testing
  • Test grouping
  • Parallel test execution
  • Parameterization of tests

TestNG is well-suited for unit testing, integration testing, functional testing, end-to-end testing, and even large-scale test automation suites.


Why Choose TestNG?

1. Rich Feature Set

TestNG offers a wide range of features, including:

  • Flexible test configuration
  • Annotations to manage tests easily
  • Ability to create test dependencies and priorities
  • Support for data-driven testing with parameters and data providers
  • Parallel execution of tests for improved performance

2. Customizable Reporting

With TestNG, you get built-in test execution reports that can be further customized to fit your needs. Integration with tools like ReportNG or ExtentReports takes reporting to the next level.

3. Scalability

TestNG’s ability to run tests in parallel and its suite-based execution model make it ideal for large-scale testing projects.

4. Integration Support

TestNG works seamlessly with:

  • Build tools like Maven and Gradle
  • CI/CD tools like Jenkins
  • Selenium WebDriver for web automation

Getting Started with TestNG

To begin using TestNG, you need to set up your project with the necessary dependencies and configurations.

Step 1: Add TestNG to Your Project

If you’re using Maven, add the following dependency to your pom.xml file:

<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>7.8.0</version> <!-- Use the latest version -->
    <scope>test</scope>
</dependency>

For Gradle, add the dependency in your build.gradle file:

testImplementation 'org.testng:testng:7.8.0'

Step 2: Create Your First TestNG Test

Here is a simple example of a TestNG test class:

import org.testng.annotations.Test;
import org.testng.Assert;

public class ExampleTest {
    
    @Test
    public void testAddition() {
        int result = 5 + 3;
        Assert.assertEquals(result, 8, "Addition result is incorrect");
    }
    
    @Test
    public void testSubtraction() {
        int result = 10 - 5;
        Assert.assertEquals(result, 5, "Subtraction result is incorrect");
    }
}

Step 3: Run the Tests

You can execute your TestNG tests using your IDE (Eclipse, IntelliJ, etc.) or build tools like Maven. TestNG generates a detailed HTML report after the execution.

To run the tests via Maven, use:

mvn test

Key TestNG Annotations

Annotations are the backbone of TestNG. They help manage the lifecycle and execution flow of tests. Below are the most commonly used annotations:

Annotation Description
@Test Marks a method as a test case.
@BeforeMethod Executes before each test method.
@AfterMethod Executes after each test method.
@BeforeClass Executes once before all test methods in a class.
@AfterClass Executes once after all test methods in a class.
@BeforeSuite Executes before the entire test suite runs.
@AfterSuite Executes after the entire test suite completes.
@DataProvider Supplies test data for data-driven testing.
@Parameters Passes parameters to test methods.

Example of @BeforeMethod and @AfterMethod

import org.testng.annotations.*;

public class TestLifecycle {

    @BeforeMethod
    public void beforeMethod() {
        System.out.println("Executing Before Method");
    }

    @Test
    public void testExample() {
        System.out.println("Executing Test Method");
    }

    @AfterMethod
    public void afterMethod() {
        System.out.println("Executing After Method");
    }
}

Output:

Executing Before Method
Executing Test Method
Executing After Method

Advanced Features in TestNG

1. Test Prioritization

TestNG allows you to prioritize test methods using the priority attribute in the @Test annotation.

@Test(priority = 1)
public void testLogin() {
    System.out.println("Login Test");
}

@Test(priority = 2)
public void testDashboard() {
    System.out.println("Dashboard Test");
}

2. Data-Driven Testing with @DataProvider

@Test(dataProvider = "loginData")
public void testLogin(String username, String password) {
    System.out.println("Username: " + username + ", Password: " + password);
}

@DataProvider(name = "loginData")
public Object[][] getData() {
    return new Object[][] {
        {"user1", "pass1"},
        {"user2", "pass2"}
    };
}

3. Parallel Execution

Parallel execution improves performance by running tests concurrently. Use the parallel attribute in the TestNG XML configuration file:

<suite name="ParallelSuite" parallel="methods" thread-count="2">
    <test name="Test1">
        <classes>
            <class name="com.example.ParallelTests"/>
        </classes>
    </test>
</suite>

Integrating TestNG with Selenium

TestNG integrates seamlessly with Selenium WebDriver for web automation testing. Here’s an example of running a Selenium test using TestNG:

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.Test;

public class SeleniumTest {
    
    @Test
    public void openGoogle() {
        System.setProperty("webdriver.chrome.driver", "path/to/chromedriver");
        WebDriver driver = new ChromeDriver();
        driver.get("https://www.google.com");
        System.out.println("Title: " + driver.getTitle());
        driver.quit();
    }
}

Conclusion

TestNG is a powerful and versatile testing framework that simplifies the testing process for Java applications. With its advanced features, annotations, and integration capabilities, it caters to the needs of both small projects and large-scale enterprise solutions. By leveraging TestNG’s full potential, you can write cleaner, more efficient, and maintainable test code.

Estruturas de Dados em Java

Introdução

Neste post, abordaremos as principais classes de mapas disponíveis na linguagem Java, detalhando suas características, vantagens e desvantagens, performance e melhores práticas. As classes discutidas incluem HashMapTreeMapLinkedHashMapHashtableConcurrentHashMap e EnumMap.

1. HashMap

Características

  • Armazena pares de chave-valor.
  • Permite chaves e valores nulos.
  • Não garante a ordem dos elementos.
  • Implementa a interface Map.

Vantagens

  • Desempenho: Operações de inserção, busca e remoção têm complexidade média O(1) devido ao uso de uma tabela hash.
  • Flexibilidade: Aceita qualquer tipo de objeto como chave, desde que implementem hashCode() corretamente

Desvantagens

  • Não sincronizado: Não é seguro para uso em ambientes multithreaded sem sincronização adicional
  • Colisões: O desempenho pode ser afetado por colisões se a função hash não for bem projetada

Performance

  • A capacidade inicial padrão é 16, com um fator de carga padrão de 0.75. Isso significa que o HashMap será redimensionado quando atingir 75% de sua capacidade

Melhores Práticas

  • Defina um tamanho inicial adequado para evitar redimensionamentos frequentes.
  • Utilize uma boa função hash para minimizar colisões.
  • Considere o uso de Collections.synchronizedMap() ou ConcurrentHashMap em ambientes multithreaded.

2. TreeMap

Características

  • Armazena pares de chave-valor.
  • Mantém a ordem dos elementos com base na ordem natural das chaves ou em um comparador fornecido.
  • Implementa a interface NavigableMap.

Vantagens

  • Ordenação: Mantém os elementos ordenados, permitindo operações como firstKey()lastKey(), etc.

Desvantagens

  • Desempenho: As operações têm complexidade O(log⁡n), o que é mais lento que o HashMap para inserções e buscas

Performance

  • Ideal para cenários onde a ordenação dos elementos é necessária.

Melhores Práticas

  • Use TreeMap quando a ordem dos elementos for importante.

3. LinkedHashMap

Características

  • Combina as funcionalidades do HashMap com a manutenção da ordem de inserção.

Vantagens

  • Manutenção da Ordem: Os elementos são iterados na ordem em que foram inseridos, o que pode ser útil em várias aplicações

Desvantagens

  • Desempenho: Um pouco mais lento que o HashMap devido à manutenção da lista duplamente ligada

Performance

  • A complexidade das operações é semelhante ao HashMap, mas com um custo adicional para manter a ordem.

Melhores Práticas

  • Use LinkedHashMap quando precisar de acesso rápido e também desejar manter a ordem de inserção.

4. Hashtable

Características

  • Semelhante ao HashMap, mas é sincronizado e não permite chaves ou valores nulos.

Vantagens

  • Sincronização: Seguro para uso em ambientes multithreaded sem necessidade de sincronização adicional

Desvantagens

  • Desempenho: Geralmente mais lento que o HashMap devido à sobrecarga da sincronização

Performance

  • As operações têm complexidade média O(1), mas a sincronização pode impactar negativamente o desempenho.

Melhores Práticas

  • Utilize Hashtable apenas quando precisar de uma implementação sincronizada e não puder usar ConcurrentHashMap.

5. ConcurrentHashMap

Características

  • Uma versão do HashMap que é segura para uso em ambientes multithreaded.

Vantagens

  • Desempenho em Concorrência: Permite múltiplas operações simultâneas sem bloquear toda a estrutura

Desvantagens

  • Complexidade: A estrutura interna é mais complexa do que um HashMap simples, o que pode aumentar a sobrecarga em algumas situações.

Performance

  • Oferece uma boa performance para operações em ambientes concorrentes, com complexidade média O(1) para operações básicas.

Melhores Práticas

  • Use ConcurrentHashMap quando precisar de uma estrutura segura para acesso concorrente.

6. EnumMap

Características

  • Um mapa cujas chaves são enumeradas (enum).

Vantagens

  • Eficiência: Mais eficiente do que outras implementações de Map quando as chaves são enums, pois utiliza arrays internamente

Desvantagens

  • Limitação: Só pode ser usado com chaves do tipo enum.

Performance

  • Oferece desempenho superior devido à sua implementação baseada em arrays.

Melhores Práticas

  • Utilize EnumMap sempre que suas chaves forem enums para obter melhor desempenho e eficiência.

Conclusão

As diferentes implementações da interface Map em Java oferecem flexibilidade e eficiência na manipulação de dados. A escolha entre elas deve ser baseada nas necessidades específicas da aplicação, considerando fatores como desempenho, segurança em ambientes multithreaded e requisitos de ordenação. Ao seguir as melhores práticas discutidas neste eBook, você poderá otimizar o uso dessas estruturas em seus projetos Java.

Padrões GoF

Padrões de Design do GoF

Categorias Principais

  1. Padrões Criacionais: Focam na criação de objetos
    • Singleton: Garante uma única instância de uma classe
    • Factory Method: Cria objetos sem especificar a classe exata
    • Abstract Factory: Cria famílias de objetos relacionados
    • Builder: Constrói objetos complexos passo a passo
    • Prototype: Cria novos objetos clonando existentes
  2. Padrões Estruturais: Lidam com composição de classes e objetos
    • Adapter: Converte interface de uma classe em outra esperada
    • Bridge: Separa uma abstração de sua implementação
    • Composite: Compõe objetos em estruturas de árvore
    • Decorator: Adiciona responsabilidades a objetos dinamicamente
    • Facade: Fornece interface simplificada para um subsistema
    • Flyweight: Compartilha objetos para economizar memória
    • Proxy: Fornece um substituto para outro objeto
  3. Padrões Comportamentais: Descrevem comunicação entre objetos
    • Chain of Responsibility: Passa uma solicitação ao longo de uma cadeia de handlers
    • Command: Transforma uma solicitação em um objeto independente
    • Interpreter: Implementa uma linguagem especializada
    • Iterator: Acessa elementos de uma coleção sequencialmente
    • Mediator: Reduz dependências diretas entre objetos
    • Memento: Captura e restaura o estado interno de um objeto
    • Observer: Define uma dependência um-para-muitos entre objetos
    • State: Permite que um objeto altere seu comportamento
    • Strategy: Define uma família de algoritmos intercambiáveis
    • Template Method: Define o esqueleto de um algoritmo
    • Visitor: Separa um algoritmo de uma estrutura de objetos

Referência

Livro: “Design Patterns: Elements of Reusable Object-Oriented Software” Autores: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides Publicado em 1994

Princípio Fundamental

Os padrões de design visam tornar o código mais flexível, reutilizável e manutenível, resolvendo problemas recorrentes no desenvolvimento de software orientado a objetos.

Plano Treinamento – Software Developer

Plano de Treinamento em Desenvolvimento de Software

Módulo 1: Princípios SOLID

  • Conteúdo: Estudo dos cinco princípios SOLID:
    • Single Responsibility Principle
    • Open-Closed Principle
    • Liskov Substitution Principle
    • Interface Segregation Principle
    • Dependency Inversion Principle
  • Livros:
  • Recursos:
    • Artigos e tutoriais sobre SOLID.

Módulo 2: Clean Code

Módulo 3: Separation of Concerns (SoC)

Módulo 4: Test-Driven Development (TDD)

Módulo 5: Design Patterns

Módulo 6: Refactoring

Módulo 7: DevOps e CI/CD

Módulo 8: Estratégias de Deployment

  • Conteúdo: Shadow, Canary, Rolling Update, Blue Green.
  • Recursos:
    • Artigos e estudos de caso sobre estratégias de deployment.

Módulo 9: Provas de Conceito (POCs) e Spikes

  • Conteúdo: Desenvolvimento de POCs para validação de tecnologias.
  • Recursos:
    • Workshops práticos.

Módulo 10: Transaction Stream / Data Stream

Módulo 11: OpenShift e AWS

Módulo 12: Segurança e Padrões de Logs

Módulo 13: Automação e RPA

  • Conteúdo: Criação de automações e pipelines.
  • Recursos:

Módulo 14: Arquitetura Avançada

  • Conteúdo: Micro serviços, DDD, Arquitetura Hexagonal, CQRS.
  • Livros:
  • Recursos:
    • Estudos de caso sobre arquiteturas modernas.

Módulo 15: Integração de APIs

Conclusão

Este plano de treinamento é uma base sólida para desenvolver as habilidades necessárias em desenvolvimento de software moderno. É importante adaptar o conteúdo e a abordagem de acordo com as necessidades específicas do grupo ou indivíduo em treinamento. Além disso, a prática contínua e a participação em projetos reais são essenciais para consolidar o aprendizado.

Clean Code

Escrever código limpo e sustentável é um dos objetivos fundamentais dos engenheiros de software. A longo prazo, código limpo pode economizar tempo e dinheiro, pois é mais simples de compreender, alterar e estender. Veremos algumas recomendações para escrever código limpo neste artigo.

  1. Use nomes descritivos:  Escolha nomes descritivos para suas variáveis, funções e classes. Isso torna mais fácil entender o propósito e o uso de cada componente.
  2. Escreva código legível:  Certifique-se de que seu código seja fácil de ler e entender. Use formatação consistente, recuo adequado e comentários quando necessário. Considere a legibilidade ao escolher seus nomes de variáveis ​​e assinaturas de métodos.
  3. Minimize a complexidade:  Mantenha seu código o mais simples possível. Use o algoritmo e a estrutura de dados mais simples possíveis para resolver o problema. Evite engenharia excessiva e otimização prematura.
  4. Evite duplicação de código:  Não se repita (DRY). Se você se pegar copiando e colando código, considere refatorá-lo em uma função ou classe reutilizável.
  5. Escreva código modular:  divida seu código em módulos pequenos e reutilizáveis ​​que podem ser facilmente testados e mantidos. Use interfaces e abstração para tornar seu código mais flexível e extensível.
  6. Use comentários significativos:  Use comentários para explicar seções complexas ou confusas do código. Evite comentários desnecessários que simplesmente reafirmam o que o código está fazendo.
  7. Escreva testes automatizados:  Testes automatizados ajudam a garantir que seu código esteja correto e seja sustentável. Escreva testes para cada módulo e função, e execute-os regularmente como parte do seu processo de desenvolvimento.
  8. Use controle de versão:  O controle de versão ajuda você a manter o controle das alterações no seu código e colaborar com outros. Use um sistema de controle de versão como o Git e faça commit das suas alterações regularmente.
  9. Refatorar continuamente : Refatorar é o processo de melhorar a qualidade e a manutenibilidade do seu código sem alterar seu comportamento. Refatorar continuamente seu código para melhorar sua legibilidade, manutenibilidade e desempenho.
  10. Siga os padrões de codificação:  Use padrões de codificação e guias de estilo para garantir consistência e legibilidade. Considere adotar um padrão de codificação como o guia de estilo do Google ou o guia de estilo PEP 8 para Python.

Ferramentas de Teste por domínio

1. Testes Unitários

  • JUnit: Framework de teste unitário para Java.
  • TestNG: Alternativa ao JUnit com suporte adicional para configurações avançadas.
  • Mockito: Framework para criação de mocks em testes unitários.

2. Testes de Integração

  • Testcontainers: Ferramenta para criar ambientes de teste utilizando contêineres Docker.
  • Spring Boot Test: Abordagens específicas do Spring Boot para testes de integração.

3. Automação de Testes de APIs

  • RestAssured: Framework para automação de testes em APIs RESTful com Java.
  • Postman: Ferramenta GUI para testes de APIs e automação.
  • Newman: CLI para executar coleções do Postman.
  • Karate: Framework para automação de testes de APIs, com suporte a DSL semelhante ao Gherkin.

4. Testes Funcionais e End-to-End (E2E)

  • Selenium: Automação de testes para navegadores web.
  • Cypress: Automação de testes E2E moderna, com foco em aplicações web.
  • Playwright: Alternativa ao Cypress, com suporte a múltiplos navegadores.
  • Robot Framework: Ferramenta de automação genérica, aplicável a E2E e outros tipos de testes.

5. Testes de Contrato

  • Pact: Framework para validação de contratos entre serviços usando Consumer-Driven Contracts.

6. Testes de Carga e Desempenho

  • JMeter: Ferramenta para testes de carga em aplicações e APIs.
  • k6: Framework moderno para testes de desempenho com scripts em JavaScript.
  • Gatling: Ferramenta de alto desempenho para testes de carga.

7. Testes de Segurança

  • OWASP ZAP (Zed Attack Proxy): Ferramenta de segurança para identificar vulnerabilidades em aplicações web.
  • Burp Suite: Conjunto de ferramentas para testes de segurança, amplamente utilizado em APIs e aplicações web.

8. Simulação de Dependências

  • WireMock: Ferramenta para criar mocks de APIs REST.
  • Mockoon: Ferramenta GUI para simulação de APIs.
  • Hoverfly: Alternativa ao WireMock, com suporte a gravação e reprodução de interações.

9. Observabilidade e Debug

  • Jaeger: Ferramenta para rastreamento distribuído.
  • Zipkin: Alternativa para rastreamento de dependências em microserviços.

10. Gestão de Testes

  • TestRail: Ferramenta para planejar, organizar e rastrear casos de teste.
  • Zephyr: Alternativa para gestão de testes, com integração ao Jira.

11. Integração com CI/CD

  • Jenkins: Ferramenta de automação popular para CI/CD.
  • GitHub Actions: Plataforma integrada ao GitHub para automação de pipelines.
  • GitLab CI: Ferramenta de CI/CD integrada ao GitLab.

Resumo Consolidado

Domínio Ferramentas
Testes Unitários JUnit, TestNG, Mockito
Testes de Integração Testcontainers, Spring Boot Test
Automação de APIs RestAssured, Postman, Newman, Karate
Testes Funcionais e E2E Selenium, Cypress, Playwright, Robot Framework
Testes de Contrato Pact
Testes de Carga e Desempenho JMeter, k6, Gatling
Testes de Segurança OWASP ZAP, Burp Suite
Simulação de Dependências WireMock, Mockoon, Hoverfly
Observabilidade e Debug Jaeger, Zipkin
Gestão de Testes TestRail, Zephyr
Integração com CI/CD Jenkins, GitHub Actions, GitLab CI

Top interview question Leetcode

Easy

Array

Strings

Linked List

Trees

Sorting and Searching

Dynamic Programming

Design

Math

 Fizz Buzz
 Count Primes
 Power of Three
 Roman to Integer

Others

 Number of 1 Bits
 Hamming Distance
 Reverse Bits
 Pascal’s Triangle
 Valid Parentheses
 Missing Number

Medium

Array and Strings

 3Sum
 Set Matrix Zeroes
 Group Anagrams
 Longest Substring Without Repeating Characters
 Longest Palindromic Substring
 Increasing Triplet Subsequence
 Missing Ranges
 Count and Say

 

Linked List

 Add Two Numbers
 Odd Even Linked List
 Intersection of Two Linked Lists

 

Trees and Graphs

 Binary Tree Inorder Traversal
 Binary Tree Zigzag Level Order Traversal
 Construct Binary Tree from Preorder and Inorder Traversal
 Populating Next Right Pointers in Each Node
 Kth Smallest Element in a BST
 Inorder Successor in BST
 Number of Islands

 

Backtracking

 Letter Combinations of a Phone Number
 Generate Parentheses
 Permutations
 Subsets
 Word Search

 

Sorting and Searching

 Sort Colors
 Top K Frequent Elements
 Kth Largest Element in an Array
 Find Peak Element
 Search for a Range
 Merge Intervals
 Search in Rotated Sorted Array
 Meeting Rooms II
 Search a 2D Matrix II

 

Dynamic Programming

 Jump Game
 Unique Paths
 Coin Change
 Longest Increasing Subsequence

 

Design

 Flatten 2D Vector
 Serialize and Deserialize Binary Tree
 Insert Delete GetRandom O(1)
 Design Tic-Tac-Toe

 

Math

 Happy Number
 Factorial Trailing Zeroes
 Excel Sheet Column Number
 Pow(x, n)
 Sqrt(x)
 Divide Two Integers
 Fraction to Recurring Decimal

 

Others

 Sum of Two Integers
 Evaluate Reverse Polish Notation
 Majority Element
 Find the Celebrity
 Task Scheduler

 Hard

Array and Strings

 Product of Array Except Self
 Spiral Matrix
 4Sum II
 Container With Most Water
 Game of Life
 First Missing Positive
 Longest Consecutive Sequence
 Find the Duplicate Number
 Longest Substring with At Most K Distinct Characters
 Basic Calculator II
 Sliding Window Maximum
 Minimum Window Substring

 

Linked List

 Merge k Sorted Lists
 Sort List
 Copy List with Random Pointer

 

Trees and Graphs

 Word Ladder
 Surrounded Regions
 Lowest Common Ancestor of a Binary Tree
 Binary Tree Maximum Path Sum
 Friend Circles
 Course Schedule
 Course Schedule II
 Longest Increasing Path in a Matrix
 Alien Dictionary
 Count of Smaller Numbers After Self

 

Backtracking

 Palindrome Partitioning
 Word Search II
 Remove Invalid Parentheses
 Wildcard Matching
 Regular Expression Matching

 

Sorting and Searching

 Wiggle Sort II
 Kth Smallest Element in a Sorted Matrix
 Median of Two Sorted Arrays

 

Dynamic Programming

 Maximum Product Subarray
 Decode Ways
 Best Time to Buy and Sell Stock with Cooldown
 Perfect Squares
 Word Break
 Word Break II
 Burst Balloons

 

Design

 LRU Cache
 Implement Trie (Prefix Tree)
 Flatten Nested List Iterator
 Find Median from Data Stream
 Range Sum Query 2D – Mutable

 

Math

 Largest Number
 Max Points on a Line

 

Others

 Queue Reconstruction by Height
 Trapping Rain Water
 The Skyline Problem
 Largest Rectangle in Histogram