mail@pastecode.io avatar
2 months ago
31 kB



1. Configuration file
├── app
│   ├── build.gradle
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── org
│       │   │       └── hyperskill
│       │   │           └── gradleapp
│       │   │               └── App.java
│       │   └── resources
│       └── test
│           ├── java
│           │   └── org
│           │       └── hyperskill
│           │           └── gradleapp
│           │               └── AppTest.java
│           └── resources
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle

2. Plugins
The plugins section adds some plugins to extend the capabilities of the project, for example, to add new tasks or

    plugins {
        // Apply the application plugin to add support for building a CLI application

        // Apply the plugin which adds support for Java

        // Apply the plugin which adds support for Kotlin/JVM

Basically, plugins for Kotlin and Java know how to build, package, and run tests on the project. The application plugin
facilitates creating an executable JVM application.

There is an alternative way to use a plugin in the project. It's more like a legacy way of applying plugins, which is
not widely used now, but just in case you see it somewhere, here it is:
    apply plugin: "application"   // for Groovy DSL
    apply(plugin = "application") // for Kotlin DSL

3. Repositories and dependencies
Usually, you don't need to write your program from scratch – you use already written pieces of code, either yours or
other developers'. This is where the dependency system comes in handy.
The repositories section declares locations from which dependencies will be downloaded and added to the project.

    repositories {

There are plenty of public repositories: JCenter, Maven Central, Google, and others. Usually, a description of a
dependency says which repository contains it.

The dependencies section is used to add external libraries to the project. Gradle will automatically download them from
the repositories and put them in the archive with the application. Your dependencies section should contain at least a
testing library like JUnit or something else, depending on your choice of when the project was initialized.

    dependencies {
        // Use JUnit test framework.
        testImplementation 'junit:junit:4.13'

        // This dependency is used by the application.
        implementation 'com.google.guava:guava:29.0-jre'

4. Configurations for the application plugin
The auto-generated build.gradle(.kts) file has a section that configures the application plugin, thanks to which the
application runs with the gradle run command.

    application {
        // Defines the main class for the application
        mainClass = "org.hyperskill.gradleapp.App"

The mainClass property defines a class with the entry point of the application. It allows us to run the application
invoking the gradle run command.

5. Generating and running Jar archive
As you may be aware, the conventional method to run a JVM-based application is to use the java -jar command. This
command can be run without Gradle, you only need to have a JAR beforehand. So let's build the JAR file for our

    gradle jar

    2 actionable tasks: 2 executed

Now, the JAR file is in the app/build/libs directory. If you want to clean the project folder from all generated
artifacts, just run the gradle clean command.

However, if you now try to run our generated application using the classic approach, there will be a problem:

    java -jar app/build/libs/app.jar
    no main manifest attribute, in app/build/libs/app.jar

The thing is that the application does not contain the Main-Class attribute in the MANIFEST.MF file. So, the JVM does
not know the path to the application's entry point. To fix this, you need to add the required attribute when generating
an archive for the application. Just add the following declaration to the build.gradle(.kts) file:

    // for Groovy DSL
    jar {
        manifest {
            attributes("Main-Class": "org.hyperskill.gradleapp.App")

    // for Kotlin DSL
    tasks.jar {
        manifest {
            attributes("Main-Class" to "org.hyperskill.gradleapp.AppKt")

This code adds the Main-Class attribute to the manifest property of the jar task. See the manifest as a map of
properties where you put your pair Main-Class -> Main. So, now, when you execute gradle jar followed by
java -jar app/build/libs/app.jar, everything should work as planned, and you will see the output line Hello world!.




import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

 * The Java Collections Framework includes the utility class Collections, that contains a number of static methods for
 * creating and processing collections. Some of the methods represent generic algorithms, which means they can work with
 * different types of collections.
 * It is often the case that programmers forget about this class and reinvent its methods from scratch. Obviously, it's
 * better to remember about this class and check whether it contains the operations you need to perform with a collection.
 * Do not confuse the Collections class and the Collection interface. They both belong to the java.util package but
 * represent completely different things.

public class TheUtilityClassCollections {

    static void creatingImmutableCollections() {
        // The first group is a set of methods for creating empty and single-element immutable collections.
        List<String> emptyList = Collections.emptyList();
        Set<Integer> emptySet = Collections.emptySet();

        List<Integer> singletonList = Collections.singletonList(100);
        Set<String> singletonSet = Collections.singleton("Hello");

        // Using these methods look pretty straightforward. But why do we need empty and single element collections?
        // For example, empty collections are often used as the return values from methods instead of null to avoid NPE.

        // Singleton collections are extremely optimized to work with a single value. As an example, the class
        // SingletonList<E> looks like this:
        class SingletonList<E> {

            private final E element;  // storing a single element

            SingletonList(E obj) {
                element = obj;

            // some fields and methods

        // Apart from this, the class also provides methods to create immutable collections from other collections:
        List<Integer> numbers = new ArrayList<>();
        List<Integer> immutableList = Collections.unmodifiableList(numbers);


    static void processingLists() {
        // There are also some methods for performing list-specific operations: sorting, reversing, rotating, and
        // shuffling lists.
        var numbers = new ArrayList<>(List.of(1, 2, 3, 2, 3, 4)); // getting a mutable list

        Collections.sort(numbers);    // [1, 2, 2, 3, 3, 4]
        Collections.reverse(numbers); // [4, 3, 3, 2, 2, 1]
        Collections.shuffle(numbers); // randomly permutes the list
        System.out.println(numbers);  // a result can be any: [4, 2, 3, 2, 3, 1]

        // The rotate method shifts the elements in the specified list by the given distance.
        numbers = new ArrayList<>(List.of(1, 2, 3, 2, 3, 4));

        Collections.rotate(numbers, 1); // [4, 1, 2, 3, 2, 3]
        Collections.rotate(numbers, 2); // [2, 3, 4, 1, 2, 3]

    static void calculationsOnCollections() {
        // There are some methods that can be applied to any collections since the methods take a Collection interface
        // as an argument.
        // - frequency counts the number of elements equal to a specified object;
        // - min and max finds the minimum and maximum elements according to the natural order of elements;
        // - disjoint checks that the two collections do not contain common elements.

        List<Integer> numbers = List.of(1, 2, 3, 2, 3, 4);

        System.out.println(Collections.frequency(numbers, 3)); // 2
        System.out.println(Collections.min(numbers)); // 1
        System.out.println(Collections.max(numbers)); // 4

        System.out.println(Collections.disjoint(numbers, List.of(1, 2))); // false
        System.out.println(Collections.disjoint(numbers, List.of(5, 6))); // true

        // If the collection is empty, the methods finding min and max will throw NoSuchElementException. But the
        // frequency will just return 0.

    static void aTrickyExample() {
        List<Integer> singletonList = Collections.singletonList(1);

        Collections.sort(singletonList);    // it doesn't throw an exception
        Collections.shuffle(singletonList); // it doesn't throw an exception

        List<Integer> numbers = Collections.unmodifiableList(List.of(2, 1, 3));
        Collections.shuffle(numbers); // it throws UnsupportedOperationException

        // The first and second operations work without throwing an exception since a list containing only a single
        // element doesn't require any modifications to be sorted or shuffled unlike the list with three elements. But
        // if you replace Collections.singletonList(1) with List.of(1), the first and second operations will also fail.
        // Even immutable collections have behavioral peculiarities.

    public static void main(String[] args) {



1. TheOptional

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Scanner;

 * Like many programming languages, Java uses null to represent the absence of a value. Sometimes this approach leads
 * to exceptions like NPEs since non-null checks make code less readable. To avoid the issues associated with null, Java
 * provides the Optional class that is a safer alternative for standard null references.

public class TheOptional {

    static void optionalValue() {
        // The Optional<T> class represents the presence or absence of a value of the specified type T. An object of
        // this class can be either empty or non-empty.

        // The first object represents an empty value (such as null), and the second one keeps a real string value.
        Optional<String> absent = Optional.empty();
        Optional<String> present = Optional.of("Hello");

        // The isPresent method checks whether an object is empty or not:
        System.out.println(absent.isEmpty()); // true
        System.out.println(present.isPresent()); // true

        // If you pass the null object to the of method, it will cause NPE.

    static String getRandomMessage() {
        return null;

    static void optionalsAndNullableObjects() {
        // In a situation when you don't know whether a variable is null or not, you should pass it to the ofNullable
        // method instead of the of method. It creates an empty Optional if the passed value is null.

        // In the following example, the getRandomMessage method may return null or some string message. Depending on
        // what is returned, the result will be different.
        String message = getRandomMessage(); // it may be null
        Optional<String> optMessage = Optional.ofNullable(message);
        System.out.println(optMessage.isPresent()); // true or false

    static void gettingTheValueFromAnOptional() {
        // get returns the value if it's present, otherwise throws an exception;
        // orElse returns the value if it's present, otherwise returns other
        // orElseGet returns the value if it's present, otherwise invokes other and returns its result.

        // get method
        // Since Java 10, the preferred alternative to the get method is the orElseThrow method whose behavior is the
        // same, but the name describes it better.
        Optional<String> optName = Optional.of("John");
        String name = optName.get(); // "John"

        optName = Optional.ofNullable(null);
        name = optName.get(); // throws NoSuchElementException
        name = optName.orElseThrow();

        // orElse method
        String nullableName = null;
        name = Optional.ofNullable(nullableName).orElse("unknown");
        System.out.println(name); // unknown

        // orElseGet method is quite similar, but it takes a supplier function to produce a result instead of taking
        // some value to return:
        class SomeClass {
            static String getDefaultResult() {
                return "Unknown";
        name = Optional.ofNullable(nullableName).orElseGet(SomeClass::getDefaultResult);


    static void conditionalActions() {
        // There are also convenient methods that take functions as arguments and perform some actions on values wrapped
        // inside Optional:
        // ifPresent performs the given action with the value, otherwise does nothing;
        // ifPresentOrElse performs the given action with the value, otherwise performs the given empty-based action.
        Optional<String> companyName = Optional.of("Google");
        companyName.ifPresent(name -> System.out.println(name.length())); // 6

        Optional<String> noName = Optional.empty();
        noName.ifPresent((name) -> System.out.println(name.length()));

        // The "classic" equivalent of these two code snippets looks like the following:
        String name = "Thieu";
        if (name != null) {

        // The method ifPresentOrElse is a safer alternative to the whole if-else statement. It executes one of two
        // functions depending on whether the value is present in the Optional.
        Optional<String> optName = Optional.ofNullable(null);
                _name -> System.out.println(_name.length()),
                () -> System.out.println(0)

    public static void main(String[] args) {

class TheOptionalExercise {

    static class InputStringReader {
        public Optional<String> getValue() {
            // implement
            Scanner scanner = new Scanner(System.in);
            String input = scanner.nextLine();

            Optional<String> optInput = "empty".equals(input) ? Optional.empty() : Optional.of(input);
            return optInput;

    static void theAddressBook() {
        class AddressBook {
            private static Map<String, String> namesToAddresses = new HashMap<>();

            static {
                namesToAddresses.put("Pansy Barrows", "63 Shub Farm Drive, Cumberland, RI 02864");
                namesToAddresses.put("Kevin Bolyard", "9526 Front Court, Hartsville, SC 29550");
                namesToAddresses.put("Earl Riley", "9197 Helen Street, West Bloomfield, MI 48322");
                namesToAddresses.put("Christina Doss", "7 Lincoln St., Matawan, NJ 07747");

            static Optional<String> getAddressByName(String name) {
                return Optional.ofNullable(namesToAddresses.get(name));

        Scanner scanner = new Scanner(System.in);

        String name = scanner.nextLine();
        Optional<String> optAddress = AddressBook.getAddressByName(name);

        // write your code here
                (address) -> System.out.println(name + " lives at " + address),
                () -> System.out.println("Unknown")


2. Currying

import java.util.Arrays;
import java.util.List;
import java.util.function.*;

 * Since functions can be considered as objects, they can be returned as results from other functions. It allows us to
 * use a special style of programming where we convey arguments to a function one by one and obtain functions as
 * intermediate results. This is a fairly advanced technique, so you won't have to use it every time you can, but it is
 * useful to be aware of such an option

public class Currying {

    /* 1. Returning functions */
    public static IntBinaryOperator sumF(IntUnaryOperator f) {
        return (a, b) -> f.applyAsInt(a) + f.applyAsInt(b);

    static void returningFunctions() {
        // build a new sumOfSquares operator
        IntBinaryOperator sumOfSquares = sumF(x -> x * x);

        // the sum is equal to 125 (5 * 5 + 10 * 10)
        long sum = sumOfSquares.applyAsInt(5, 10);

        // sum of two identities: 0 + 10 = 10
        long sumOfIdentities = sumF(x -> x).applyAsInt(0, 10);

        // sum with coefficients: 10 * 2 + 11 * 2 = 42
        long sumWithCoefficient = sumF(x -> x * 2).applyAsInt(10, 11);

        // sum of two cubes: 3 * 3 * 3 + 8 * 8 * 8 = 539
        long sumOfCubes = sumF(x -> x * x * x).applyAsInt(3, 8);

    /* 2. Currying functions */
    // Currying is a technique for translating the evaluation of a function that takes multiple parameters into
    // evaluating a sequence of functions, each with a single argument.
    static void curryingFunctions() {
        IntBinaryOperator notCurriedFun = (x, y) -> x + y; // not a curried function
        IntFunction<IntUnaryOperator> curriedFun = x -> y -> x + y; // a curried function

        // curried function
        IntFunction<IntFunction<IntFunction<Integer>>> fff = x -> y -> z -> x * y + z;

        // fff returns a curried function y -> z -> 2 * y + z
        IntFunction<IntFunction<Integer>> ff = fff.apply(2);

        // ff returns a curried function z -> 2 * 3 + z
        IntFunction<Integer> f = ff.apply(3);

        // f returns 7
        int result = f.apply(1);

        // here the result is equal to 153
        int anotherResult = fff.apply(10).apply(15).apply(3);

        // Let's rewrite the sumF method from the earlier example. Instead of returning a function from it, we can write
        // a curried function and then use it in the same way:
        Function<IntUnaryOperator, IntBinaryOperator> sumF =
                (g) -> (a, b) -> g.applyAsInt(a) + g.applyAsInt(b);

        // build a new sumOfSquares operator in terms of sumF
        IntBinaryOperator sumOfSquares = sumF.apply(x -> x * x);

        // the sum is equal to 125 again
        long sum = sumOfSquares.applyAsInt(5, 10);

    /* 3. An example of currying */
    static void anExampleOfCurrying() {
        // Suppose we would like to say "Hi" to our friends and "Hello" to our business partners. We can create a
        // function that has two arguments: what and who. The function will apply what depending on the context.
        Function<String, Consumer<String>> say = what -> who -> System.out.println(what + ", " + who);

        // The friends' context:
        List<String> friends = Arrays.asList("John", "Neal", "Natasha");
        Consumer<String> sayHi = say.apply("Hi");

        // The partner's context:
        List<String> partners = Arrays.asList("Randolph Singleton", "Jessie James");
        Consumer<String> sayHello = say.apply("Hello");

        // The result:
        // Hi, John
        // Hi, Neal
        // Hi, Natasha
        // Hello, Randolph Singleton
        // Hello, Jessie James

    public static void main(String[] args) {



1. TypeErasure

 * Generics were introduced to implement generic programming and control type-safety at compile-time. The feature has
 * been available since Java 5. To support backward compatibility with previous Java versions, information about generic
 * types is erased by the compiler.
 * This means that at runtime all these objects will have the same type:
 *      Generic types:  List<Integer> integers = new List<>();
 *      Raw type:       List objects = new List();
 * The transformation process is called type erasure.

public class TypeErasure {

    static void genericsReplacement() {
        // Type erasure replaces parameters of generic types with their bounds. Unbounded types are replaced by Object
        // This means that information about types is erased when a program is translated into byte code. As a result,
        // byte code contains only ordinary non-generic classes and interfaces.

        // Let's consider the generic class Data:
        class Data<T> {
            private T data;

            public Data(T data) {
                this.data = data;

            public T get() {
                return data;

            public void set(T data) {
                this.data = data;

        // The Java compiler will replace the parameter T with Object, because T is unbounded. Below is code that is
        // effectively the same as Data<T> after compilation:
        class Data2 {
            private Object data;

            public Data2(Object data) {
                this.data = data;

            public Object get() {
                return data;

            public void set(Object data) {
                this.data = data;

        // Now suppose Data is parameterized by <T extends Number>. In this case, the transformed code will look
        // similar to the last code snippet with one difference: Object will be replaced by Number.

        // If a value is assigned, a generic replacement can affect the accuracy of the program. If it is necessary to
        // preserve type safety, the compiler inserts type casting.
        Data<String> data = new Data<>("stored value");
        String stored = data.get();
        // After type erasure is performed the code above is equivalent to:
        Data2 data2 = new Data2("stored value");
        String stored2 = (String) data2.get();

    static void bridgeMethods() {
        // In order to preserve polymorphism through type casting, sometimes the compiler has to generate synthetic
        // methods. Let's consider an extension of the Data class:
        class Data<T> {
            private T data;

            public Data(T data) {
                this.data = data;

            public T get() {
                return data;

            public void set(T data) {
                this.data = data;

        class NumberData extends Data<Number> {
            public NumberData(Number data) {

            public void set(Number number) {
                System.out.println("NumberData set");

        // After type erasure, the NumberData method remains set(Number number), while the original Data method is
        // set(Object obj). Because NumberData extends Data, it is possible to invoke set(Object obj) from an instance
        // of NumberData and set objects of arbitrary type. But we only want to set objects of the Number type.
        // To solve this problem and preserve the polymorphism of generic types after type erasure, the Java compiler
        // generates a so-called bridge method in the NumberData class. It overrides parameterized parent methods and
        // provides type casting to specific parameters:
        class Data2 {
            private Object data;

            public Data2(Object data) {
                this.data = data;

            public Object get() {
                return data;

            public void set(Object data) {
                this.data = data;

        class NumberData2 extends Data2 {

            public NumberData2(Object data) {

            // Bridge method generated by the compiler
            public void set(Object data) {
                super.set((Number) data);

            public void set(Number number) {

        // A bridge method is a synthetic method created by the compiler as part of the type erasure process. It exists
        // in byte code only and not available for direct usage from Java code. Normally you don't directly encounter
        // bridge methods, although sometimes they might appear in a stack trace.

    public static void main(String[] args) {



2. Reification

import java.util.ArrayList;
import java.util.Collection;

 * Type erasure only affects certain data types — other types are not affected and preserve their type data in byte code.
 * Types that save information about themselves during type erasure are called reifiable, while types whose information
 * is erased are called non-reifiable.
 * The term reification refers to the process of making certain type parameters available at runtime as well as at
 * compile-time.

public class Reification {

    static void twoGroups() {
        // Let's recall what types are replaced during type erasure and can be called non-reifiable.
        // Non-reifiable types include:
        // 1. parameterized types like <T>, which are replaced by Object.
        // 2. bounded generics or wildcards. For example, <T extends Number> and <? extends Number> are replaced by Number.

        // Reifiable types are more extensive. They include:
        // 1. primitive types like int and double.
        // 2. non-parameterized types such as String, Number and other non-generic classes.
        // 3. more complicated reifiable types, which are technically equivalent to Object. The first is a raw type. It
        //    is a type that can be parameterized but is not. For instance, if class Box<T> is declared as
        //    Box box = new Box() then it's a raw type. The second is an unbounded wildcard type, for example, Box<?>.
        //    It includes arrays whose component type is reifiable as well.

    static void nonReifiableLimitations() {
        // The fact that non-reifiable types are not present at runtime leads to some limitations:

        // 1. It is prohibited to create an instance of a non-reifiable type.
        class Box<T> {
            private T instance;

            public void init() {
                // instance = new T(); // compile-time error: Type parameter T cannot be instantiated directly

            public void isIntegerSuperType() {
                // System.out.println(Integer.valueOf(0) instanceof T); // compile-time error: Illegal generic type for instanceof
        // This limitation is reasonable since we have no way to guarantee that T will implement any particular
        // constructor.

        // 2. Another limitation for a non-reifiable type includes using instanceof operator.
        // This operation is prohibited since the run-time bytecode contains no information on non-reifiable types,
        // making it impossible to verify whether an object is an instance of such a type.

        // 3. Only reifiable types can extend java.lang.Throwable.

        // class MyException<T> extends Exception {} // Generic class may not extend java.lang.Throwable
        // To illustrate the problem, suppose that the compiler ignored this error and ran the following code:

//        try {
//        } catch (MyException<String> e) {
//            System.out.println("String");
//        } catch (MyException<Long> e) {
//            System.out.println("Long");
//        }

        // After type erasure, both caught types would be translated into a single parameterless MyException type. As a
        // result, we have a dilemma on how to handle MyException – the program would not know which exception message
        // to print.

        // 4. Creating an instance of an array requires a reifiable type. This limitation also relates to Varargs, which
        // translates parameters into an array.

        // Remember that due to type erasure, the code
        Collection<Integer> col = new ArrayList<Integer>();
        Integer[] array = col.toArray(new Integer[0]);

        // is equivalent to:
        Collection col2 = new ArrayList();
        // col has no type parameter information at runtime.
        // Which array type should we create inside toArray() method without a parameter?
        Integer[] array2 = (Integer[]) col2.toArray();

        // 5. Casting to non-reifiable types usually results in a warning notifying the programmer that this practice
        // may lead to exceptions.

    public static void main(String[] args) {


    private class Exercise {


class Multiplicator {

    public static <T extends Copy<T>> Folder<T>[] multiply(Folder<T> folder, int arraySize) {
        // Method to implement
        Folder[] arr = new Folder[arraySize];

        for (int i = 0; i < arraySize; i++) {
            var newFolder = new Folder<T>();
            arr[i] = newFolder;

        return arr;


interface Copy<T> {
    T copy();

class Folder<T> {

    private T item;

    public void put(T item) {
        this.item = item;

    public T get() {
        return this.item;




public class TheInterface {

    public static void main(String[] args) {
        Calculator calculator = new Calculator() {
            public int add(int x, int y) {
                return x + 2 * y;

            public int subtract(int x, int y) {
                return x - 3 * y;

        CalculatorService calculatorService = new CalculatorService(calculator);
        System.out.println(calculatorService.addAndSubtract(10, 2));

interface Calculator {

    int add(int x, int y);
    int subtract(int x, int y);

class CalculatorService {

    private Calculator calculator;

    public CalculatorService(Calculator calculator) {
        this.calculator = calculator;

    public int addAndSubtract(int x, int y) {
        int sum = calculator.add(x, y);
        int difference = calculator.subtract(x, y);

        return sum - difference;
Leave a Comment