javagenericsreturn-valuemethod-chainingfluent-interface

Instantiate a generic class T object and return it


I'm working on java selenium tests and I am trying to setup a fluent/method-chaining design code:

I have a generic button class that allows navigating from a page class to the other. Buttons are attributes of my pages classes. Method in the button class should be able to return generic page classes that were defined on button instantiation (the page you are on, the page the button leads to etc.). But I can't figure how to do it without breaking the fluent design in the main class:

Button.java

public class Button<T, U> {
    public T observe() {
        return new T(); // Does not work
    }

    public U click() {
        return new U(); // Does not work
    }
}

FirstPage.java

public class FirstPage {
    public Button<FirstPage, SecondPage> buttonOnFirstPage = new Button<>();
}

SecondPage.java

public class SecondPage {
    public Button<SecondPage, AnotherPage> buttonOnSecondPage = new Button<>();
}

And finally what I want to be able to do without casting or mentioning types, in order to benefit from autocompletion and other fancy IDE stuff: BrowsingTest.java

public class BrowsingTest {
    public static void main(String[] args) {
        new FirstPage()
            .buttonOnFirstPage.observe()
            .buttonOnFirstPage.click()
            .buttonOnSecondPage.observe()
            .buttonOnSecondPage.click(); //etc.
    }
}

Any idea on how I can build that button class? Thanks a lot!


Solution

  • Generic types are a compile-time notation for ensuring type safety. They are erased at runtime.

    This means T and U do not exist at runtime. Which is why you can’t instantiate them.

    You can, however, pass in the constructors yourself:

    public class Button<T, U> {
        private final Supplier<? extends T> tConstructor;
        private final Supplier<? extends U> uConstructor;
    
        public Button(Supplier<? extends T> tConstructor,
                      Supplier<? extends U> uConstructor) {
    
            this.tConstructor = tConstructor;
            this.uConstructor = uConstructor;
        }
    
        public T observe() {
            return tConstructor.get();
        }
    
        public U click() {
            return uConstructor.get();
        }
    }
    

    And you can pass those constructors as method references:

    public class FirstPage {
        public Button<FirstPage, SecondPage> buttonOnFirstPage =
            new Button<>(FirstPage::new, SecondPage::new);
    }