如何使我的方法根据列表的输入类型返回不同类型的列表?

How do I make my method return different types of list base on the input type of list?

提问人:Azn Stride 提问时间:3/16/2023 最后编辑:Azn Stride 更新时间:3/16/2023 访问量:359

问:

我想为名为 的方法创建从输入到输出的映射,但以“智能”方式。处理不同类型列表的方式在本质上非常相似,因为输入列表共享相同的属性,但来自不同的类。List<T>List<S>foo(List<T>)foo(List<T>)

我的目的是在返回输出之前,只需对输入类型进行少量检查,即可重用尽可能多的实现。foo()

一种方法是在foo

if (items.get(0) instanceOf Cat) {
    List<Kitten> kittens = items.stream().map(cat -> Kitten.builder().name(cat.getName()).build().toList();
    return kittens;
}
if (items.get(0) instanceOf Dog) {
    List<Puppy> puppies = items.stream().map(dog -> Puppy.builder().name(dog.getName()).build().toList();
    return puppies;
}

,但感觉不对,因为如果我添加另一种类型,我将不得不添加另一个if条件。Bird

我想,如果我想为不同类型的输入使用不同的返回类型,另一种实现此目的的方法是为特定类型的列表创建自定义类,即

class DogList {
    private List<Dog> dogs;
}

class CatList {
    private List<Cat> cats;
}

class KittenList {
    private List<Kitten> kittens;
}

class PuppyList {
    private List<Puppy> puppies;
}

// And creating method for each type that's sth like
public KittenList foo(CatList cats) {
    List<Kitten> kittens = cats.getCats().stream().map(cat -> 
    Kitten.builder().name(cat.getName()).build().toList();
    return kittens;
}
public PuppyList foo(DogList dogs) {
    List<Puppy> puppies = dogs.getCats().stream().map(cat -> 
    Puppy.builder().name(dogs.getName()).build().toList();
    return puppies;
}

但是这样做感觉很奇怪,因为我创建自定义类只是为了包装列表。我还复制了 99% 的 foo 实现。并且这里的实现几乎相同,所以我更愿意重用相同的方法。.

java 数组 oop 设计 访客模式

评论

2赞 John Bayko 3/16/2023
我认为您必须抽象出类或接口来定义可以应用于不同类型的常见操作。
0赞 Daniel Janz 3/16/2023
为什么不只使用 List<Object>而不是类?比如:或者这是为了你的目的而通用的?List<Object> animals = items.stream().map(animal -> Animal.builder().name(animal.getName()).build().toList();

答:

0赞 rioswarawan 3/16/2023 #1

Listtype 仅包含 1 个类,例如 us 表示字符串列表。与您的示例相同,表示 T 的列表。List<String>List<T>

我可以建议创建一个超类,然后向 和 添加继承。AnimalDogCat

public class Animal {
    Integer legCount;
    Integer age;
}

public class Dog extends Animal {

    String name;

    public Dog(String name, int legCount, int age) {
        super(legCount, age);
        this.name = name;
    }
}

public class Cat extends Animal {
    String name;

    public Cat(String name, int legCount, int age) {
        super(legCount, age);
        this.name = name;
    }
}

将狗和猫变量添加到动物列表中

Dog dog1 = new Dog("Joni", 4, 2);
Cat cat1 = new Cat("Ron", 4, 3);

List<Animal> animals = new ArrayList();
animals.add(dog1);
animals.add(cat1);

然后在 foo 函数中变为

List<Puppy> puppies = new ArrayList();
List<Kitten> kittens = new ArrayList();

public void foo(List<Animal> animals) {
    for(animal in animals) {
        if(animals insntanceOf Dog) {
            Puppy puppy = ...
            puppies.add(puppy);
        }

        if(animals insntanceOf Kitten) {
            Kitten kitten = ...
            kittens.add(kitten);
        }
    }
}

为所有动物做一个循环

评论

3赞 alalalala 3/16/2023
但是,如果添加 Bird 类型,则仍需要添加条件分支。这并没有解决问题
0赞 alalalala 3/16/2023 #2

首先,我们需要确保 , , 并且有一个共同的父类:CatDogKittenPuppyAnimal

class Animal {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
class Dog extends Animal {
    private String name;
    public Dog(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
class Cat extends Animal {
    private String name;
    public Cat(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
class Kitten extends Animal {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Kitten{" +
                "name='" + name + '\'' +
                '}';
    }
}
class Puppy extends Animal {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Puppy{" +
                "name='" + name + '\'' +
                '}';
    }
}

然后使用像这样的通用方法:

public static <R extends Animal, T extends Animal> List<T> foo(List<R> source, Class<T> target) {

    return source.stream().map(v -> {
        T t = null;
        try {
            t = target.newInstance();
            t.setName(v.getName());
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return t;
    }).collect(Collectors.toList());
}

指定两个参数:源列表和目标类型。List<R> sourceClass<T> target

我们需要约束类型和类型,因为在方法内部,我们需要调用相应的 , 方法,如果是任何类型,那么我们无法确定它们有 , 方法,如何保证这一点? 很简单,如果 和 被要求是 的子类型,可以保证它们有 和 方法。因此,我们向泛型方法添加约束。TRsetNamegetNamesetNamegetNameTRAnimalsetNamegetName<R extends Animal, T extends Animal>

在方法实现中,我们将源列表中的每个元素 (R) 转换为目标类型 (T)。

如果添加新类型,则只需添加相应的类型描述类即可。

以下是完整的测试代码,需要你自己了解和处理代码的细节:

public class StackOverflow {

    public static void main(String[] args) {
        List<Dog> dogs = new ArrayList<Dog>(){{
            add(new Dog("dog 1"));
            add(new Dog("dog 2"));
            add(new Dog("dog 3"));
            add(new Dog("dog 4"));
        }};
        List<Cat> cats = new ArrayList<Cat>(){{
            add(new Cat("cat 1"));
            add(new Cat("cat 2"));
            add(new Cat("cat 3"));
        }};
        DogList dogList = new DogList();
        dogList.setDogs(dogs);
        CatList catList = new CatList();
        catList.setCats(cats);

        List<Puppy> puppies = foo(dogList.getDogs(), Puppy.class);
        System.out.println(puppies);

        List<Kitten> kittens = foo(catList.getCats(), Kitten.class);
        System.out.println(kittens);
    }

    public static <R extends Animal, T extends Animal> List<T> foo(List<R> source, Class<T> target) {

        return source.stream().map(v -> {
            T t = null;
            try {
                t = target.newInstance();
                t.setName(v.getName());
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
            return t;
        }).collect(Collectors.toList());
    }
}

class Animal {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
class Dog extends Animal {
    private String name;
    public Dog(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
class Cat extends Animal {
    private String name;
    public Cat(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
class Kitten extends Animal {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Kitten{" +
                "name='" + name + '\'' +
                '}';
    }
}
class Puppy extends Animal {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Puppy{" +
                "name='" + name + '\'' +
                '}';
    }
}

class DogList {
    private List<Dog> dogs;

    public List<Dog> getDogs() {
        return dogs;
    }
    public void setDogs(List<Dog> dogs) {
        this.dogs = dogs;
    }
}

class CatList {
    public List<Cat> getCats() {
        return cats;
    }
    public void setCats(List<Cat> cats) {
        this.cats = cats;
    }
    private List<Cat> cats;
}
0赞 tgdavies 3/16/2023 #3

这是做类似事情的一种方法 - 以一种类型安全的方式将特定动物的列表转换为其幼崽的列表。

它要求您在声明类时将幼崽动物的类型指定为类型参数:


import java.util.List;
public class Main {
    public static void main(String[] args) {
        List<Dog> dogs = List.of(new Dog(), new Dog());
        List<Cat> cats = List.of(new Cat(), new Cat());
        List<Puppy> puppies = createYoungList(dogs);
        List<Kitten> kittens = createYoungList(cats);
        //List<Puppy> kittens = createYoung(cats); compilation error
    }

    static <P extends Animal<Y>,Y> List<Y> createYoungList(List<P> parents) {
        return parents.stream().map(Animal::createYoung).toList();
    }

}

interface Animal<Y> {
    Y createYoung();
}

class Puppy {}
class Kitten {}

class Dog implements Animal<Puppy> {

    @Override
    public Puppy createYoung() {
        return new Puppy();
    }
}

class Cat implements Animal<Kitten> {

    @Override
    public Kitten createYoung() {
        return new Kitten();
    }
}

添加 的新实现不需要对 进行任何更改。AnimalcreateYoungList

1赞 Phil Ku 3/16/2023 #4

我试图复制你的问题。这是一种可能的解决方案:

创建一个超类并声明类之间的映射:Animal

private Map<Class<Animal>, Class<Animal>> classMap = Map.ofEntries(
  new AbstractMap.SimpleEntry(Cat.class, Kitten.class),
  new AbstractMap.SimpleEntry(Dog.class, Puppy.class)
);

遍历映射条目以处理 :foo

for(Entry<Class<Animal>, Class<Animal>> entry : classMap.entrySet()){
    if (items.get(0).getClass() == entry.getKey()) {
        List list = items.stream().map(item -> {
            Animal animal = new Animal();
            try {
                // instantiate animal young
                animal = entry.getValue().getConstructor().newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
            animal.setName(item.getName());
            // mapping values ...
            return animal;
        }).collect(Collectors.toList());

        return list;
    }
}

每当您需要添加更多动物类时,只需将它们添加到类映射中即可:

private Map<Class<Animal>, Class<Animal>> classMap = Map.ofEntries(
  new AbstractMap.SimpleEntry(Cat.class, Kitten.class),
  new AbstractMap.SimpleEntry(Dog.class, Puppy.class),
  new AbstractMap.SimpleEntry(Cow.class, Calf.class)
);

希望对您有所帮助。

0赞 Stone 3/16/2023 #5

我有一个想法:首先通过源类注册映射器函数。对于新的源类,只需注册一个新的映射器函数。 一个缺点是:有一个显式的类转换来获取目标列表。我不知道如何删除它。

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Test {

    public static void main(String[] args) {
        List<Dog> dogs = Arrays.asList(new Dog("d1"), new Dog("d2"));
        Map<Class, Function> register = new HashMap();
        register.put(Dog.class, (Function<Dog, Puppy>) dog -> new Puppy(dog.getName()));
        List<Puppy> puppies = (List<Puppy>) dogs.stream().map(register.get(Dog.class)).collect(Collectors.toList());
        System.out.println("puppies:" + puppies);
    }
}

class Dog {
    private String name;

    public Dog(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

class Puppy {
    private String name;

    public Puppy(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Puppy{" +
                "name='" + name + '\'' +
                '}';
    }
}

0赞 Bohemian 3/16/2023 #6

我不确定你的意图是什么,但希望这能帮助你实现目标。

从类型化方法返回类型化列表的一种方法是传递类型标记

private List<Object> items;

public <T> List<T> getAll(Class<T> clazz) {
    List<T> result = new ArrayList<>();
    for (Object obj : items) {
        if (clazz.isInstance(obj)) {
            result.add((T)obj);
        }
    }
    return result;
}

调用时推断类型:

List<Dog> dogs = myClass.getAll(Dog.class);