【问题标题】:Casting a base class onto a derived class in Java在 Java 中将基类强制转换为派生类
【发布时间】:2015-03-27 19:20:51
【问题描述】:

目前在我的 Java 程序中,我有一个基础 Parent 类,其中包含几个返回自身新实例的方法。例如:

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

    return new BasePage(driver);
}

另外,我有几个类用自己的方法扩展了这个基类。我的问题是:在我的主程序中有没有一种方法可以调用这些 Parent 方法,但不使用强制转换将返回的值分配给子类?例如,如果我有一个loginPage Child 类需要在主程序中使用此方法,我将如何转:

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

进入这个:

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

我在哪里初始化类:

public class LoginPage extends BasePage {
}

虽然在这个例子中没有提到,但是否可以允许它返回任何子类,这样它就不仅限于一个类。

【问题讨论】:

  • 你熟悉Java Polymorphism吗?
  • 我已经阅读了关于 Java Polymorphism 的页面,但它没有任何关于返回父类并将该值分配给子类而不进行强制转换的信息

标签: java inheritance


【解决方案1】:

是的,有办法。在子类中(例如LoginPage),覆盖fill 方法。使用 Java 的 covariant return types(滚动到底部),您可以指定 LoginPage 作为返回类型。

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

    return new LoginPage(driver);
}

假设您可以创建一个带有driverLoginPage,假设它属于Driver 类,这意味着您需要一个采用Driver 的子类构造函数。

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

【讨论】:

  • 这里唯一的问题是每个子类都必须通过提供超类实现的精确副本来覆盖一个方法,除了 new Thing() 位。
  • 我希望有一种方法可以让我返回 BasePage 类的任何孩子。不过,这确实对我的情况有所帮助。
【解决方案2】:

不,不是这样。如果BasePage 是调用给定fill() 方法的引用的正式类型,则返回值的正式类型是BasePage。如果没有强制转换,您不能将该值视为 BasePage 的子类型。

在您的特定示例中,即使强制转换也应该失败并显示 ClassCastException,因为未显示子类覆盖的基类方法返回一个类为 BasePage 的对象,而这样的对象不是LoginPage 的实例。

但是,子类可以使用协变返回类型覆盖该方法:

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

在这种情况下,如果您在对 LoginPage(或 子类之一)的引用上调用该方法,则返回值是 LoginPage 的一个实例,可以被视为比如:

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

【讨论】:

    【解决方案3】:

    这里有几种方法可以继续,但如果新实例与旧实例几乎相同,并且可能进行了一些修改,我认为您应该考虑使您的层次结构可克隆。

    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);
          } 
        }
    ...
    }
    

    然后子类需要用适当的强制转换覆盖父类的填充

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

    请注意,clone 将始终返回原始调用实例的实例...在本例中为 LoginPage。

    根据层次结构中的其他成员字段是什么以及应该如何在克隆之间共享这些字段,此方法可能不起作用或需要改进。

    只要您保持所有子类的构造函数的签名不变,在这种情况下(驱动程序),另一种方法是保持方法的逻辑填充父类并通过以下方式选择适当的子类传递类对象...

    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);
        } 
      }
    }
    

    然后去获取一个LoginPage...

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

    您可以重载填充子类以避免传递该类,但是我们将做与上面克隆解决方案一样多的工作。

    【讨论】:

    • 关于您的最后一段代码,newInstance() 方法似乎没有任何参数。应该将其留空并继承构造函数,还是我需要从包中导入此方法以获取您使用的方法?
    • @bagelmakers 对我的错误感到抱歉,因为我正在盲目编码(没有检查 API)...您必须使用 getConstructor(Class ... x) 获取适当的构造函数,并使用相应的调用 newInstance论据。我已更改答案以反映这一点。
    猜你喜欢
    • 2017-01-08
    • 2013-11-17
    • 2011-07-15
    • 1970-01-01
    • 2013-09-18
    • 2021-02-18
    • 2017-07-18
    相关资源
    最近更新 更多