多态性和向下转换问题

时间:2016-12-14 15:50:06

标签: java polymorphism downcast

我正在阅读一本关于Java的书,目前正在讨论Polymorphism主题,以及如何downcast一个参考变量。但是,我很难理解向下转换的概念。下面是我正在关注的例子的uml。

UML

对于BasePlusCommissionEmployee的所有对象,他们的基本工资增加10%。其他Employee子类与正常情况一样。 PayrollSystemTest包含运行应用程序的主要方法。

// Fig. 10.9: PayrollSystemTest.java
// Employee hierarchy test program.

public class PayrollSystemTest
{
    public static void main(String[] args)
    {
        // create subclass objects
        SalariedEmployee salariedEmployee =
            new SalariedEmployee("John", "Smith", "111-11-1111", 800.00);
        HourlyEmployee hourlyEmployee =
            new HourlyEmployee("Karen", "Price", "222-22-2222", 16.75, 40.0);
        CommissionEmployee commissionEmployee =
            new CommissionEmployee(
            "Sue", "Jones", "333-33-3333", 10000, .06);
        BasePlusCommissionEmployee basePlusCommissionEmployee =
            new BasePlusCommissionEmployee(
            "Bob", "Lewis", "444-44-4444", 5000, .04, 300);

        System.out.println("Employee processed individually:");

        System.out.printf("%n%s%n%s: $%,.2f%n%n",
            salariedEmployee, "earned", salariedEmployee.earnings());
        System.out.printf("%s%n%s: $%,.2f%n%n",
            hourlyEmployee, "earned", hourlyEmployee.earnings());
        System.out.printf("%s%n%s: $%,.2f%n%n",
            commissionEmployee, "earned", commissionEmployee.earnings());
        System.out.printf("%s%n%s: $%,.2f%n%n",
            basePlusCommissionEmployee,
            "earned", basePlusCommissionEmployee.earnings());

        // create four-element Employee array
        Employee[] employees = new Employee[4];

        // initialize array with Employees
        employees[0] = salariedEmployee;
        employees[1] = hourlyEmployee;
        employees[2] = commissionEmployee;
        employees[3] = basePlusCommissionEmployee;

        System.out.printf("Employees processed polymorphically:%n%n");

        // generically process each element in array employees
        for (Employee currentEmployee : employees)
        {
            System.out.println(currentEmployee); // invokes toString

            // determine whether element is a BasePlusCommissionEmployee
            if (currentEmployee instanceof BasePlusCommissionEmployee)
            {
                // downcast Employee reference to
                // BasePlusCommissionEmployee reference
                BasePlusCommissionEmployee employee =
                    (BasePlusCommissionEmployee) currentEmployee;

                employee.setBaseSalary(1.10 * employee.getBaseSalary());

                System.out.printf(
                    "new base salary with 10%% increase is: $%,.2f%n",
                    employee.getBaseSalary());
            } // end if

            System.out.printf(
                "earned $%,.2f%n%n", currentEmployee.earnings());
        }  // end for

        // get type name of each object in employees array
        for (int j = 0; j < employees.length; j++)
            System.out.printf("Employee %d is a %s%n", j,
                employees[j].getClass().getName());
    } // end main
} // end class PayrollSystemTest

本书进一步解释了增强的for循环迭代数组employees并使用toString变量earnings调用方法EmployeecurrentEmployee。在每次迭代时引用数组中的不同Employee。因此,输出说明了调用每个类的特定方法,并在执行时根据对象的类型解析。

为了在当前BasePlusCommissionEmployee对象上调用getBaseSalary的方法setBaseSalaryEmployee,条件语句用于检查对象引用是否为{ {1}}对象使用 instanceof 运算符,如果条件为true,则对象必须向下转换BasePlusCommissionEmployeeEmployee类型调用上面提到的方法。

这让我很困惑,因为我们能够访问子类'BasePlusCommissionEmployee方法但是必须向下转换对象才能使用其他方法toStringgetBaseSalary?为什么会这样?

3 个答案:

答案 0 :(得分:3)

因为toString()中定义了Object,因此每个班级都可以使用BasePlusCommissionEmployee。基本工资的getter和setter仅在Employee中可用,因此您无法通过private static void getSubjectHash( X509Certificate x509Cert ) { try { // get the subject principal X500Principal x500Princ = x509Cert.getSubjectX500Principal( ); // create a new principal using canonical name (order, spacing, etc.) and get it in ANS1 DER format byte[] newPrincEnc = new X500Principal( x500Princ.getName( X500Principal.CANONICAL ) ).getEncoded( ); // read it in as an ASN1 Sequence to avoid custom parsing ASN1InputStream aIn = new ASN1InputStream( newPrincEnc ); ASN1Sequence seq = (ASN1Sequence) aIn.readObject( ); List<byte[]> terms = new ArrayList<>( ); int finalLen = 0; int i = 0; // hash the encodables for each term individually and accumulate them in a list for ( ASN1Encodable asn1Set : seq.toArray( ) ) { byte[] term = ( (ASN1Set) asn1Set ).getEncoded( ); term[9] = 0x0c; // change tag from 0x13 (printable string) to 0x0c terms.add( term ); finalLen += term.length; // digest the term byte[] hashBytes = truncatedHash( getDigest( term ), 4 ); printByteArray( String.format( "hash of object at %d:", i++ ), hashBytes ); System.out.println( "" ); } // hash all terms together in order of appearance int j = 0; byte[] finalEncForw = new byte[finalLen]; for ( byte[] term : terms ) for ( byte b : term ) finalEncForw[j++] = b; // digest and truncate byte[] hashBytes = truncatedHash( getDigest( finalEncForw ), 4 ); printByteArray( "hash of all terms in forward order", hashBytes ); System.out.println( "" ); } catch ( Exception ex ) { throw new RuntimeException( "uh-oh" ); } } 引用调用它(如果引用不同类型会发生什么情况?)。

这个例子不是你在实际代码中看到的。使用instanceof确定要做什么是糟糕的风格。

答案 1 :(得分:2)

当你想在一个实例上调用一个方法时,你可以调用的方法取决于多个事物,包括实例的声明类型,方法的修饰符以及你调用它们的位置。
在你的例子中,它是您感兴趣的实例的声明类型。

例如,当您声明:

String s = new String("string");

您可以从String班级调用可访问的方法 例如:

s.toString();
s.trim();
etc...

在您的情况下,当您声明:

BasePlusCommissionEmployee basePlusCommissionEmployee =
            new BasePlusCommissionEmployee(
            "Bob", "Lewis", "444-44-4444", 5000, .04, 300);
Employee currentEmployee = basePlusCommissionEmployee;
你可以这样做: basePlusCommissionEmployee.getBaseSalary()因为BasePlusCommissionEmployee声明的类型为。

提供了方法

你也可以这样做: basePlusCommissionEmployee.toString()currentEmployee.toString(),因为这两种类型(EmployeeBasePlusCommissionEmployee)都为toString()提供了方法,因为该方法是来自Object类的公共方法并且所有类都继承自Object类,因此这些类具有toString()方法。

但你做不到: currentEmployee.getBaseSalary()因为Employee声明的类型不提供。

的方法

要绕过它,您可以从基类转发到目标子类:

Employee currentEmployee = basePlusCommissionEmployee;
((BasePlusCommissionEmployee)currentEmployee).getBaseSalary();

答案 2 :(得分:1)

值得加入我的眼中:这实际上是一个相当的例子。

downcast 经常表明设计不好;这很好地证明了这一规则。

薪资系统应该需要这样的“instanceof”检查;然后为特定的子类做一些特定的计算。

这是使用多态的整个点:那些不同的类BasePlusCommissionEmployee和CommissionEmployee应该包含正确计算正确工资的方法。

这里要坚持的是TDA(告诉不要问)原则。 好的OO是关于告诉某些对象“做那件事”;而不是向对象询问某事,然后根据该对象的内部状态(或本例中的性质)做出外部决定!

对于任何有兴趣了解如何真正解决此问题的人,我建议查看Robert Martin的“敏捷原则”。那本书描述了现实世界薪资系统的设计/实施......