0

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!

Batou
  • 98
  • 1
  • 8
  • Annoying this is closed to redirect to some illegible answers from a decade ago that will doubtless dissuade you from trying. This is **perfectly doable** in your case. Hints: create an anon subclass for each button (`new Button<>(){}`) to lock-in the Generics, use Guava `TypeToken` in the constructor to easily extract `Class` – drekbour Jul 29 '20 at 18:01

2 Answers2

1

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);
}
VGR
  • 40,506
  • 4
  • 48
  • 63
  • Nothing wrong with this solution **but** Generics are NOT erased at runtime where a concrete subtype locks them in. See TypeToken for how to leverage this fact. https://stackoverflow.com/questions/38046880/how-to-use-typetoken-to-get-type-parameter – drekbour Jul 29 '20 at 18:05
  • @drekbour Notice that a TypeToken subclass is a non-generic class. – VGR Jul 29 '20 at 18:21
  • Agree but you made it feel clear that there's no solution to resolve T when there is a perfectly decent one (providing design is ok to subclass). – drekbour Jul 29 '20 at 18:32
  • @drekbour You may want to write your own answer. – VGR Jul 29 '20 at 18:40
  • Working like a charm :) – Batou Jul 31 '20 at 06:56
0

It's not possible to create an instance out of generic type you should look for these workarounds instead: Instantiating object of type parameter