【问题标题】:Create a neat method out of three similar ones using generics使用泛型从三个相似的方法中创建一个简洁的方法
【发布时间】:2020-11-18 07:17:50
【问题描述】:

我已经尝试过使用泛型做一些事情,但我个人似乎找不到任何简单的解决方案。尽管如此,我仍然认为将这 3 种类似的方法搁置一旁是一种罪过。

    public List<PassengerPlane> getPassengerPlanes() {
        List<PassengerPlane> passengerPlanes = new ArrayList<>();
        for (Plane plane : planes) {
            if (plane instanceof PassengerPlane) {
                passengerPlanes.add((PassengerPlane) plane);
            }
        }
        return passengerPlanes;
    }

    public List<MilitaryPlane> getMilitaryPlanes() {
        List<MilitaryPlane> militaryPlanes = new ArrayList<>();
        for (Plane plane : planes) {
            if (plane instanceof MilitaryPlane) {
                militaryPlanes.add((MilitaryPlane) plane);
            }
        }
        return militaryPlanes;
    }

    public List<ExperimentalPlane> getExperimentalPlanes() {
        List<ExperimentalPlane> experimentalPlanes = new ArrayList<>();
        for (Plane plane : planes) {
            if (plane instanceof ExperimentalPlane) {
                experimentalPlanes.add((ExperimentalPlane) plane);
            }
        }
        return experimentalPlanes;
    }

【问题讨论】:

  • 如何引入作为Plane 抽象一部分的PlaneType 枚举。它使过滤就像比较所需的平面类型一样容易。当然,这仍然需要转换过滤后的实例。
  • 问题是你的“飞机”是如何形成的。当您创建“飞机”集合时,您是否已经将它们分类到地图或不同的列表中?
  • 您应该编辑您的问题以在标题中包含“通用”。这将使有类似问题的其他人更容易找到它。
  • @Glains 是的,我同意,我想过,但感觉好像还有另一种方法
  • 您在使用泛型时遇到了哪些具体问题?如果instanceof 是唯一的问题,那么这可能与Java: Instanceof and Generics 重复

标签: java generics


【解决方案1】:

您需要的是泛型方法,但问题是instanceof 无法检查类型参数(实际上在编译过程中已删除),它需要实际的类引用。因此,您可以明确地将其提供给该方法:

public <T extends Plane> List<T> getPlanes(Class<T> claz) {
  List<T> result = new ArrayList<>();
  for (Plane plane : planes) {
    if (claz.isInstance(plane)) {
      result.add(claz.cast(plane));
    }
  }
  return result;
}

注意instanceof 和显式转换如何更改为对.isInstance().cast() 的调用

像这样使用它

getPlanes(PassengerPlane.class)

【讨论】:

  • 反射当然是一种绕过显式转换的方法:)
  • 我只是好奇,你有没有理由避免显式转换? result.add((T) plane); 不会和result.add(claz.cast(plane)); 做同样的事情吗?我并不是想说 claz.cast() 有什么问题,我只是想知道这里是否有什么东西让我无法理解。 :)
  • 呃..,只是为了与isInstance() 保持一致,嘿,这是一个演示,为什么不尝试一些不同的东西呢?
  • 好的,谢谢。只要我没有遗漏一些关于 Java 的东西,哈哈
  • @CharlieArmstrong:claz.cast(plane) 的一个优点是它实际上验证了转换,而(T) plane 是未经检查的转换,即它只是告诉编译器假设我们是对的,但实际上并没有验证 plane 的类型是 T
【解决方案2】:

您可以使用 Streams 使事情变得更短,但我不确定是否有办法在此处使用 instanceof

public List<PassengerPlane> getPassengerPlanes() {
    return planes.stream().filter(t -> t instanceof PassengerPlane)
                 .map(t -> (PassengerPlane) t).collect(Collectors.toList());
}
public List<MilitaryPlane> getMilitaryPlanes() {
    return planes.stream().filter(t -> t instanceof MilitaryPlane)
                 .map(t -> (MilitaryPlane) t).collect(Collectors.toList());
}
public List<ExperimentalPlane> getExperimentalPlanes() {
    return planes.stream().filter(t -> t instanceof ExperimentalPlane)
                 .map(t -> (ExperimentalPlane) t).collect(Collectors.toList());
}

【讨论】:

  • return planes.stream().filter(cls::isInstance).map(cls::cast).collect(Collectors.toList()) 应该可以解决问题(使用Class&lt;T&gt; cls 参数)。
【解决方案3】:

以下是我使用泛型解决问题的方法:

public <T> List<T> getTPlanes(Class<T> clazz) { //declare the method to take a type generic
    List<T> tPlanes = new ArrayList<>(); //initialize an ArrayList of that type

    planes.stream() //stream the planes list
            .filter(clazz::isInstance) //filter it down to only planes of the type that we want
            .forEach((p) -> tPlanes.add((T) p)); //add each plane left in the stream to our new ArrayList, and cast it to the type generic

    return tPlanes; //return the ArrayList we just created and populated
}

【讨论】:

    【解决方案4】:

    您确实需要在某处进行强制转换:这是一个采用单一方法的解决方案,该方法采用子类型。

    import java.util.*;
    import java.util.stream.*;
    
    public class Example {
        public static class Plane { }
        public static class PassengerPlane extends Plane { }
        public static class MilitaryPlane extends Plane { }
        public static class ExperimentalPlane extends Plane { }
    
        private static List<Plane> planes =
            List.of(new PassengerPlane(),
                    new MilitaryPlane(),
                    new ExperimentalPlane());
    
        public static <T extends Plane> List<T> getPlanesOfType(Class<T> type, List<Plane> planes) {
            List<T> list =
                planes.stream()
                .filter(t -> type.isAssignableFrom(t.getClass()))
                .map(t -> type.cast(t))
                .collect(Collectors.toList());
    
            return list;
        }
    
        public static void main(String[] args) throws Exception {
            System.out.println(getPlanesOfType(PassengerPlane.class, planes));
            System.out.println(getPlanesOfType(MilitaryPlane.class, planes));
            System.out.println(getPlanesOfType(ExperimentalPlane.class, planes));
            System.out.println(getPlanesOfType(Plane.class, planes));
        }
    }
    
    [Example$PassengerPlane@7b227d8d]                                                                                                                                                                                                      
    [Example$MilitaryPlane@7219ec67]                                                                                                                                                                                                       
    [Example$ExperimentalPlane@45018215]                                                                                                                                                                                                   
    [Example$PassengerPlane@7b227d8d, Example$MilitaryPlane@7219ec67, Example$ExperimentalPlane@45018215]
    

    您可以使用单一方法来替换所有三个或使用它来实现。

    【讨论】:

      【解决方案5】:

      如果您的问题真的很短暂,那么可能不值得付出努力。但是,这是访问者模式的典型问题(尤其是如果您的重复代码较大)。

      步骤 1

      创建Visitor接口访问Plane的各个类型:

      interface Visitor {
          void visit(MilitaryPlane militaryPlane);
          void visit(ExperimentalPlane experimentalPlane);
          void visit(PassengerPlane passengerPlane);
      }
      

      ...并以List&lt;Plane&gt; 开头的方式实现它,每个.visit() 都可以丰富它:

      class PlaneVisitor implements Visitor {
      
          private final List<Plane> planes;
      
          PlaneVisitor(List<Plane> planes) {
              this.planes = requireNonNull(planes);
          }
      
          @Override
          public void visit(MilitaryPlane militaryPlane) {
              planes.add(militaryPlane);
          }
      
          @Override
          public void visit(ExperimentalPlane experimentalPlane) {
              planes.add(experimentalPlane);
          }
      
          @Override
          public void visit(PassengerPlane passengerPlane) {
              planes.add(passengerPlane);
          }
      
          public List<Plane> getPlanes() {
              return planes;
          }
      }
      

      第 2 步 - 在您的课程中启用访问者

      在您的基类Plane 中添加一个abstract 方法以接受访问者:

      public abstract class Plane {
          //...
          abstract void accept(Visitor visitor);
          //...
      }
      

      然后在每个子类中实现这个方法,让Visitor实例visit本身(this)。 PassengerPlane 的示例(所有其他类的逻辑相同):

      public class PassengerPlane extends Plane {
          //...
          @Override
          void accept(Visitor visitor) {
              visitor.visit(this);
          }
          //...
      }
      

      第 3 步 - 调整您的功能

      您的函数现在可以遍历planes 的列表,而不关心类型。将由visitor解决:

      public List<Plane> getPlanes() {
          PlaneVisitor planeVisitor = new PlaneVisitor(new ArrayList<>());
          for (Plane plane : planes) {
              plane.accept(planeVisitor);
          }
          return planeVisitor.getPlanes();
      }
      

      请注意,您需要添加一些方法/接口来执行此操作。由于您的代码非常小,即使它不是很“优雅”,您也可以想象它保持原样。但是,如果您的代码实际上应该比您向我们展示的更多,那么上面的示例可能会有所启发。

      【讨论】:

        【解决方案6】:

        可以通过引入一个做公共部分的方法来做到这一点:

        private static <T> List<T> createFilteredList(List<Plane> inList, Class<T> clazz) {
            List<T> outList = new ArrayList<>();
            for (Plane value : inList) {
                if (clazz.isInstance(value)) {
                    outList.add(clazz.cast(value));
                }
            }
            return outList;
        }
        

        那么就可以这样使用了:

        public List<PassengerPlane> getPassengerPlanes() {
            return createFilteredList(planes, PassengerPlane.class);
        }
        public List<MilitaryPlane> getPassengerPlanes() {
            return createFilteredList(planes, MilitaryPlane.class);
        }
        public List<ExperimentalPlane> getPassengerPlanes() {
            return createFilteredList(planes, ExperimentalPlane.class);
        }
        

        【讨论】:

          【解决方案7】:

          所以,您有一个可迭代的 Plane 作为输入。 飞机可以是PassengerPlane、MilitaryPlane 或ExperimentalPlane。

          您要做的是通过谓词过滤平面集合。谓词是一个函数,它接受一个平面并回答真或假。过滤器使用谓词来确定是在结果中包含给定项目还是跳过。

          如果您使用的是 Java 8 或更高版本,则可以使用 Streams API。 https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html

          1. 从可迭代平面生成流。
          2. 对其应用过滤器(中间操作)。
          3. 收集要列出的结果。

          使用 Stream API,您可以将上述方法转换为单行。像这样:

          planes.stream().filter(plane -&gt; plane instanceof MilitaryPlane).collect(toList());

          那么,您可能不需要单独的简洁方法。 但是如果你想要一些可重用的代码,那么你必须弄清楚这里的参数是什么。在上面的代码中,它是一个特定的平面实现:

          public List&lt;Plane&gt; filterPlanes(Iterable&lt;Plane&gt; planes, Class&lt;? extends Plane&gt; planeImplementation)

          因此,您可以使用此参数构建谓词:

          plane -&gt; planeImplementation.isInstance(plane)

          【讨论】:

            【解决方案8】:

            如果你有一个 Plane 超类型,你可以让子类继承 getPlanes() 方法。 this.getClass 将仅提取从列表中调用该方法的子类的平面。这样,您不必将类传递给方法,因为它可以从调用它的子类派生。

            public abstract class Plane {
            
                public Plane(){}
            
                public List<Plane> getPlanes() {
                    List<Plane> result = new ArrayList<>();
                    for (Plane plane : planes) {
                        if (this.getClass().isInstance(plane)) {
                            result.add(this.getClass().cast(plane));
                        }
                    }
                    return result;
                }
            }
            
            class PassengerPlane extends Plane {
            }
            class MilitaryPlane extends Plane {
            }
            class ExperimentalPlane extends Plane {
            }
            
            public class PlaneList {
                public String name;
                public static ArrayList<Plane> planes = new ArrayList<>();
            
                public PlaneList(){
                    planes.add(new MilitaryPlane());
                    planes.add(new MilitaryPlane());
                    planes.add(new MilitaryPlane());
                    planes.add(new PassengerPlane());
                    planes.add(new PassengerPlane());
                    planes.add(new ExperimentalPlane());
                }
            }
            

            我是这样测试的:

            public class Main {
            
                public static void main(String[] args) {
                    PlaneList list = new PlaneList();
                    Plane plane = new PassengerPlane();
            
                    for(Plane p : plane.getPlanes()){
                        System.out.println(p.toString());
                    }
                }
            }
            

            输出:

            com.company.PassengerPlane@7dc36524
            com.company.PassengerPlane@35bbe5e8
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-09-09
              • 2021-02-18
              相关资源
              最近更新 更多