简体   繁体   中英

Casting a base class onto a derived class in Java

Currently in my Java program, I have a base Parent class with several methods which return a new instance of itself. For example:

public BasePage fill(String input, By locator) {
    driver.findElement(locator).clear();
    driver.findElement(locator).sendKeys(input);

    return new BasePage(driver);
}

As well, I have several classes which extend this base class with their own methods. My question is this: is there a way in my main program to call these Parent methods, but assign the returned value to a child class without using casts? For example, if I had a loginPage Child class which needed to use this method in a main program, how would I turn this:

loginPage = (LoginPage) loginPage.fill("username", usernameLocator);

into this:

loginPage = loginPage.fill("username", usernameLocator);

where I initialize the class:

public class LoginPage extends BasePage {
}

While not mentioned in this example, is it possible to allow it to return any of the child classes, such that it is not restricted to just one class.

Yes, there is a way. In the subclass (eg LoginPage ), override the fill method. With Java's covariant return types (scroll to the bottom), you can specify LoginPage as the return type.

public LoginPage fill(String input, By locator) {
    driver.findElement(locator).clear();
    driver.findElement(locator).sendKeys(input);

    return new LoginPage(driver);
}

That assumes that you can create a LoginPage with a driver , assumed to be of a Driver class, which means you need a subclass constructor that takes a Driver .

public LoginPage(Driver driver)
{
    super(driver);
    // Anything else LoginPage-specific goes here
}

There several ways to proceed here, but if the new instance if pretty much the same as the old one with perhaps some modifications I think you should consider to make your hierarchy clone-able.

public class BasePage implements Cloneable {

    public BasePage fill(String input, By locator) {
        driver.findElement(locator).clear();
        driver.findElement(locator).sendKeys(input);

        return (BasePage) clone();
    }

    @Override
    protected Object clone() {
      try {  
        return super.clone();
      } catch ( CloneNotSupportedException ex) {
        throw new RuntimeException("this can't ever happen!!!",ex);
      } 
    }
...
}

Then the child classes need to override the parent's fill with the appropriate cast

public class LoginPage extends BasePage {

    @Override
    public LoginPage fill(String input, By locator) {
        return (LoginPage) super.fill(input,locator);
    }
}

Notice that clone will always return an instance of the original invoking instance... in this case LoginPage.

This approach may not work or would need refinement depending on what are the other member fields in the hierarchy and how these are supposed to be shared between clones.

A different approach that works as long as you keep constant the signature of the constructor of all child classes, in this case (Driver), is to keep the logic of the method fill in the parent and select the appropriate child class by passing the class object...

public class BasePage {

  public <T extends BasePage> T fill(String input, By locator, Class<T> clazz) {
    driver.findElement(locator).clear();
    driver.findElement(locator).sendKeys(input);
    // not sure if the cast is even needed.
    try {
      return (T) clazz.getConstructor(Driver.class).newInstance(driver);
    } catch (Exception ex) {
      throw new RuntimeException("whoops! something went wrong",ex);
    } 
  }
}

Then to get a LoginPage...

 LoginPage lp = page.fill(input,loc,LoginPage.class);

You can overload fill in the children classes to avoid passing the class but then we would be doing as much as work as with the clone solution above.

No, not as such. If BasePage is the formal type of the reference on which you invoke the given fill() method, then the formal type of the return value is BasePage . You cannot treat that value as a subtype of BasePage without a cast.

In your particular example, even a cast should fail with a ClassCastException because the base class's method, which the subclass is not shown to override, returns an object whose class is BasePage , and such an object is not an instance of LoginPage .

However, a subclass could override that method, employing a covariant return type:

public class LoginPage extends BasePage {
    @Override
    public LoginPage fill(String input, Locator by) {
        return new LoginPage(/* ... */);
    }
}

In that case, if you invoke that method on a reference to a LoginPage (or one of its subclasses) then the return value is an instance of LoginPage and can be treated as such:

LoginPage page1 = new LoginPage();
LoginPage page2 = page1.fill("username", usernameLocator);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM