返回动态类型对象的 Java 函数

时间:2021-02-16 19:24:10

标签: java

如果我有两个子类 EmployeeIntern,它们都扩展了类 Person。假设我有 List<Person> contacts 是一个列表,其中包含在公司工作的每个人,EmployeeIntern

现在我想写一个函数findPerson(String name)。这个函数假设通过在 contacts 中搜索他们的名字来返回这个人,而不知道他们是属于 Employee 还是 Intern。但是,如果找到该对象,我想返回该对象。

但是,我认为 Java 不能为函数返回动态类型。除了编写两个函数之外,还有什么我可以做的吗?

我试图让函数返回对象类型 Person。但是,这样我无法将它们转换为适当的类型,尤其是我无法知道它们是 Employee 还是 Intern

3 个答案:

答案 0 :(得分:2)

Contacts.findPerson 方法可以有一个通用的返回类型,例如:

    <T extends Person > T findPerson(String name)

这将为呼叫者提供他们正在寻找的特定类型的人员(我认为您的意思是人员,而不是公司,对吗?)。为了确保返回的类型 T 匹配特定的类,您可以说:

    <T extends Person > T findPerson(Class<T> type, name)

现在类型 T 将绑定到与传入的类相同的类型,因此用法如下:

    var intern = contacts.findPerson(Intern.class, "Elon Musk");

答案 1 :(得分:1)

好吧,您需要两个函数 - findEmployeefindIntern,并分别进行 instanceof 检查:

Optional<Employee> findEmployee(List<Company> list, String name) {
    for (Company c : list) {
        if (c instanceof Employee && c.getName().equals(name)) {
            return Optional.of((Employee) c);
        }
    }

    return Optional.empty();
}

但我会考虑如何修改 Company 类,因此不需要将其强制转换为特定的子类(使用多态)。

答案 2 :(得分:1)

<块引用>

我有两个子类 Employee 和 Intern 扩展了 Company 类

作为您mentioned in a Comment,这种命名没有意义。因此,让我们将其更改为 Person 作为父类,具有两个子类,EmployeeIntern。我们会忘记Company

密封类

如果在编译时知道所有可能的子类,那么我们应该使用在 Java 16 中预览的新 sealed classes 特性。

我们的超类,Person

package work.basil.example.extending;

import java.time.*;
import java.util.Objects;
import java.util.UUID;

public abstract sealed class Person
        permits Employee, Intern
{
    // Member fields
    UUID id;
    String name;
    LocalDate whenHired;

    // Constructors
    public Person ( final UUID id , final String name , final LocalDate whenHired )
    {
        this.id = Objects.requireNonNull( id );
        this.name = Objects.requireNonNull( name );
        this.whenHired = Objects.requireNonNull( whenHired );
    }

    // Logic
    Period getPeriodOfEmployment ( )
    {
        ZoneId z = ZoneId.systemDefault();
        LocalDate today = LocalDate.now( z );
        Period p = Period.between( this.whenHired , today );
        return p;
    }

    // Object overrides.

    @Override
    public String toString ( )
    {
        return "Person{ " +
                "id=" + id +
                " | name='" + name + '\'' +
                " | whenHired=" + whenHired +
                " }";
    }
}

两个具体的子类,EmployeeIntern

package work.basil.example.extending;

import java.time.LocalDate;
import java.util.UUID;

public final class Employee extends Person
{
    public Employee ( UUID id , String name , LocalDate whenHired )
    {
        super( id , name , whenHired );
    }
}
package work.basil.example.extending;

import java.time.LocalDate;
import java.util.UUID;

public final class Intern extends Person
{
    public Intern ( UUID id , String name , LocalDate whenHired )
    {
        super( id , name , whenHired );
    }
}

按名称查找 Person 个对象

你问:

<块引用>

写一个函数 findPerson(String name) ... 事先不知道他们是属于 Employee 还是 Intern

EmployeeIntern 都从它们的超类 name 继承了一个 Person 成员字段。所以我们不关心哪个子类型。只需询问名称并与所需名称进行比较。

当然,我们可能会发现带有该名称的零个、一个或多个 Person 对象。所以我们应该返回一个 ListSet 找到的对象。对于 Set,我们需要 equalshashCode 的实现。我们还没有写这些,所以让我们暂时坚持使用 List

    List < Person > fetchPeopleByName ( final String name , final List < Person > people )
    {
        Objects.requireNonNull( people );
        List < Person > peopleSharingName = new ArrayList <>( people.size() );
        for ( Person person : people )
        {
            if ( person.name.equalsIgnoreCase( name ) )
            {
                peopleSharingName.add( person );
            }
        }
        return List.copyOf( peopleSharingName );  // Return unmodifiable list.
    }

执行该方法的代码。

        List < Person > people = List.of(
                new Employee( UUID.fromString( "a4dcf239-305f-40f1-b6cc-32e3e6643451" ) , "Alice" , LocalDate.of( 2008 , Month.JANUARY , 23 ) ) ,
                new Intern( UUID.fromString( "e5ac2a48-f25a-42e1-af58-6ce597cc93ed" ) , "Bob" , LocalDate.of( 2020 , Month.DECEMBER , 7 ) ) ,
                new Intern( UUID.fromString( "2c24630b-b7e2-4e9f-87ab-7c996137894f" ) , "Carol" , LocalDate.of( 2021 , Month.JANUARY , 11 ) ) ,
                new Employee( UUID.fromString( "4b1f918d-c95d-44c5-a85d-d092e3b83c51" ) , "Demetri" , LocalDate.of( 2011 , Month.MARCH , 16 ) )
        );

        List < Person > bobs = this.fetchPeopleByName( "Bob" , people );
        System.out.println( "bobs = " + bobs );

运行时,我们找到一个名称与“Bob”匹配的 Person 对象。

bobs = [Person{ id=e5ac2a48-f25a-42e1-af58-6ce597cc93ed | name='Bob' | whenHired=2020-12-07 }]

你提到了你自己对最后一种方法的尝试:

<块引用>

我试图让函数返回对象类型 Company Person。

是的,我们可以返回 Person 对象,因为我们通过 Person 超类型上声明的成员字段搜索 Person 对象。所以我们不关心子类型,EmployeeIntern

您还谈到了您对这种方法的尝试:

<块引用>

但是,这样我无法将它们转换为适当的类型,尤其是我无法知道它们是 Employee 还是 Intern

无需铸造。当询问他们的名字时,超类 Person 的成员字段,子类 EmployeeIntern 的任何对象都将通过提供他们的名字来响应。因此,成为 EmployeeIntern 与获得他们的名字无关。

按子类型 Person 查找 Employee 对象

您可能在其他情况下确实关心他们的子类型。例如,假设我们需要向每位员工发送有关其就业福利计划的信息。实习生没有这样的福利。因此,我们只想发送到恰好属于 Person 子类的 Employee 对象的子集。这是一些代码。

让我们创建一个方法来遍历 Person 对象列表,以仅返回子类 Employee 的对象。

我们使用 instanceOf 测试具体类。在较旧的 Java 中,我们必须在通过 instanceOf 测试后进行转换:( Employee ) person。在最新的 Java 版本中,新语法使转换自动和隐式。

    List < Employee > fetchEmployees ( final List < Person > people )
    {
        Objects.requireNonNull( people );
        List < Employee > employees = new ArrayList <>( people.size() );
        for ( Person person : people )
        {
            if ( person instanceof Employee employee )   // New syntax eliminates need to cast explicitly.
            {
                employees.add( employee );
            }
        }
        return List.copyOf( employees );
    }

执行该方法的代码。

        List < Person > people = List.of(
                new Employee( UUID.fromString( "a4dcf239-305f-40f1-b6cc-32e3e6643451" ) , "Alice" , LocalDate.of( 2008 , Month.JANUARY , 23 ) ) ,
                new Intern( UUID.fromString( "e5ac2a48-f25a-42e1-af58-6ce597cc93ed" ) , "Bob" , LocalDate.of( 2020 , Month.DECEMBER , 7 ) ) ,
                new Intern( UUID.fromString( "2c24630b-b7e2-4e9f-87ab-7c996137894f" ) , "Carol" , LocalDate.of( 2021 , Month.JANUARY , 11 ) ) ,
                new Employee( UUID.fromString( "4b1f918d-c95d-44c5-a85d-d092e3b83c51" ) , "Demetri" , LocalDate.of( 2011 , Month.MARCH , 16 ) )
        );

        List < Employee > emps = this.fetchEmployees( people );
        System.out.println( "emps = " + emps );

运行时,我们找到了两名员工。一个名为“Alice”,另一个名为“Demetri”。

emps = [Person{ id=a4dcf239-305f-40f1-b6cc-32e3e6643451 | name='Alice' | whenHired=2008-01-23 }, Person{ id=4b1f918d-c95d-44c5-a85d-d092e3b83c51 | name='Demetri' | whenHired=2011-03-16 }]

按名称字段组合搜索子类型 Employee

我们可以结合两种方法来生成具有特定名称的 Employee 对象列表。

List < Person > people = List.of(
        new Employee( UUID.fromString( "a4dcf239-305f-40f1-b6cc-32e3e6643451" ) , "Alice" , LocalDate.of( 2008 , Month.JANUARY , 23 ) ) ,
        new Intern( UUID.fromString( "e5ac2a48-f25a-42e1-af58-6ce597cc93ed" ) , "Bob" , LocalDate.of( 2020 , Month.DECEMBER , 7 ) ) ,
        new Intern( UUID.fromString( "2c24630b-b7e2-4e9f-87ab-7c996137894f" ) , "Carol" , LocalDate.of( 2021 , Month.JANUARY , 11 ) ) ,
        new Employee( UUID.fromString( "4b1f918d-c95d-44c5-a85d-d092e3b83c51" ) , "Demetri" , LocalDate.of( 2011 , Month.MARCH , 16 ) )
);

// Empty list. Our only "Bob" is an intern, not an employee.
List < Employee > employeesNamedBob = this.fetchEmployees( this.fetchPeopleByName( "Bob" , people ) );  

// A list of one `Employee` object named "Demetri". 
List < Employee > employeesNamedDemetri = this.fetchEmployees( this.fetchPeopleByName( "Demetri" , people ) );