调用子类';方法取决于Java中的对象类型

调用子类';方法取决于Java中的对象类型,java,generics,types,javafx,casting,Java,Generics,Types,Javafx,Casting,问题:根据传递的参数类型,动态选择相应的子类并调用其方法 研究:努力应用于任务: public abstract class MetaContainer extends Node { public static abstract interface CommonContainer { ObservableList<Object> getChildren(Object container); } public abstract class AnchorPan

问题:根据传递的参数类型,动态选择相应的子类并调用其方法

研究:努力应用于任务:

public abstract class MetaContainer
  extends Node {

  public static abstract interface CommonContainer {
    ObservableList<Object> getChildren(Object container);
  }

  public abstract class AnchorPaneContainer
    extends AnchorPane
    implements CommonContainer {

    public ObservableList<Object> getChildren(Object container) {
      // Special approach for AnchorPanes.
    }
  }

  public abstract class TabPaneContainer
    extends TabPane
    implements CommonContainer {

    public ObservableList<Object> getChildren(Object container) {
      // Special approach for TabPanes.
    }
  }
}
公共抽象类元容器
扩展节点{
公共静态抽象接口CommonContainer{
ObservableList getChildren(对象容器);
}
公共抽象类主播容器
锚烷
实现公共容器{
公共可观察列表getChildren(对象容器){
//锚烷的特殊方法。
}
}
公共抽象类TabPaneContainer
扩展选项卡窗格
实现公共容器{
公共可观察列表getChildren(对象容器){
//选项卡面板的特殊方法。
}
}
}
我尝试像这样使用类(并得到错误,因为CommonContainer是一个接口,不能有静态方法):

private observeList getElements(节点容器)
抛出ClassNotFoundException{
ObservableList nodes=FXCollections.observableArrayList();
ObservableList objects=FXCollections.observableArrayList();
objects.addAll(
MetaContainer.CommonContainer。
得到孩子(
(对象)容器);
用于(对象:对象){
nodes.add(
(节点)对象);
}
返回节点;
}
问题:我如何调用
getChildren()
整个
MetaContainer
并将其传递给参数中任何类型的容器,等待它在容器类型的子类中寻址正确的
getChildren()


说明:简而言之,我需要浏览节点容器以搜索简单控件。因此,您事先不知道该节点是什么类型的,只是在迭代时动态地知道。有些子节点是也必须浏览的容器,但每种类型都需要特定的方法。我可以做一些类似于在类型上切换case的事情,但我觉得应该做一些更优雅的事情,比如为每个类型创建子类和一个公共接口。

好的,让我尝试一下,尽管我仍然不知道我是否真的理解这个问题。我认为您希望通过以不同的方式获取不同
父类
子类的子节点来获取场景图的子集(即,不一定只是通过调用)。因此,如果它是一个简单的
窗格
,您只需调用
getChildren()
,但是如果它是一个
TabPane
,您将获得每个
选项卡
,并获得每个选项卡的内容,从中形成一个集合。(类似地,对于其他“容器类型控件”,如
拆分窗格
,等等),如果它是一个“简单”控件,则不认为它有任何子节点(例如,即使在幕后,
按钮
包含
文本

因此,我认为您可以通过构建一个类型安全的异构容器(参见Josh Bloch的),将特定的节点类型
N
映射到
函数来实现这一点。该函数将定义如何检索该类型的子节点

这看起来像

public class ChildRetrievalMapping {

    public static final ChildRetrievalMapping DEFAULT_INSTANCE = new ChildRetrievalMapping() ;

    static {
        // note the order of insertion is important: start with the more specific type

        DEFAULT_INSTANCE.put(TabPane.class, tabPane -> 
                tabPane.getTabs().stream().map(Tab::getContent).collect(Collectors.toList()));
        DEFAULT_INSTANCE.put(SplitPane.class, SplitPane::getItems);
        // others...

        // default behavior for "simple" controls, just return empty list:
        DEFAULT_INSTANCE.put(Control.class, c -> Collections.emptyList());

        // default behavior for non-control parents, return getChildrenUnmodifiable:
        DEFAULT_INSTANCE.put(Parent.class, Parent::getChildrenUnmodifiable);


        // and for plain old node, just return empty list:
        DEFAULT_INSTANCE.put(Node.class, n -> Collections.emptyList());
    }

    private final Map<Class<?>, Function<? ,List<Node>>> map = new LinkedHashMap<>();

    public <N extends Node> void put(Class<N> nodeType, Function<N, List<Node>> childRetrieval) {
        map.put(nodeType, childRetrieval);
    }

    @SuppressWarnings("unchecked")
    public <N extends Node> Function<N, List<Node>> getChildRetrieval(Class<N> nodeType) {
        return (Function<N, List<Node>>) map.get(nodeType);
    }

    @SuppressWarnings("unchecked")
    public List<Node> firstMatchingList(Node n) {
        for (Class<?> type : map.keySet()) {
            if (type.isInstance(n)) {
                return getChildRetrieval((Class<Node>) type).apply(n);
            }
        }
        return Collections.emptyList();
    }
}

我不确定这是否是你想要做的,如果是的话,也许有更优雅的方式来实现它。但我认为,为特定类型声明这样的策略要比在类型上进行大切换要好得多,而且还可以选择将其配置为您想要的特定规则。

我不理解设计。对象容器的
参数是什么?为什么容器的getChildren()方法需要容器作为参数?您的问题很不清楚。也许您可以给出一个如何使用此设计的示例(即,您希望编写的使用这些类型的对象的代码示例)。简而言之,我需要浏览节点容器以搜索简单控件。因此,您事先不知道该节点是什么类型的,只是在迭代时动态地知道。有些子节点是也必须浏览的容器,但每种类型都需要特定的方法。我可以做一些类似于在类型上切换case的事情,但是我觉得应该做一些更优雅的事情,比如每个类型的子类和一个公共接口。感谢您提供详细的答案。你能不能也提供一个与Java7兼容的代码,因为我还没有尝试过Java8。如何将lambdas转换回抽象类,这在一定程度上是清楚的,但在Java7中仍然没有函数类。不,Java7版本会非常冗长。如果你真的想折磨自己,用
javafx.util.Callback
替换
Function
,并用匿名内部类替换所有lambda表达式。请注意,Java7不再受到公开支持(Java9将在几个月后推出)。
public class ChildRetrievalMapping {

    public static final ChildRetrievalMapping DEFAULT_INSTANCE = new ChildRetrievalMapping() ;

    static {
        // note the order of insertion is important: start with the more specific type

        DEFAULT_INSTANCE.put(TabPane.class, tabPane -> 
                tabPane.getTabs().stream().map(Tab::getContent).collect(Collectors.toList()));
        DEFAULT_INSTANCE.put(SplitPane.class, SplitPane::getItems);
        // others...

        // default behavior for "simple" controls, just return empty list:
        DEFAULT_INSTANCE.put(Control.class, c -> Collections.emptyList());

        // default behavior for non-control parents, return getChildrenUnmodifiable:
        DEFAULT_INSTANCE.put(Parent.class, Parent::getChildrenUnmodifiable);


        // and for plain old node, just return empty list:
        DEFAULT_INSTANCE.put(Node.class, n -> Collections.emptyList());
    }

    private final Map<Class<?>, Function<? ,List<Node>>> map = new LinkedHashMap<>();

    public <N extends Node> void put(Class<N> nodeType, Function<N, List<Node>> childRetrieval) {
        map.put(nodeType, childRetrieval);
    }

    @SuppressWarnings("unchecked")
    public <N extends Node> Function<N, List<Node>> getChildRetrieval(Class<N> nodeType) {
        return (Function<N, List<Node>>) map.get(nodeType);
    }

    @SuppressWarnings("unchecked")
    public List<Node> firstMatchingList(Node n) {
        for (Class<?> type : map.keySet()) {
            if (type.isInstance(n)) {
                return getChildRetrieval((Class<Node>) type).apply(n);
            }
        }
        return Collections.emptyList();
    }
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.Labeled;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class PerformActionOnNodeTypes extends Application {

    @Override
    public void start(Stage primaryStage) {
        VBox root = new VBox(5, 
                new Label("Label 1"),
                new HBox(5, new Label("Label 2"), new Button("Button 1")),
                new HBox(5, new TextField("Some text"), new ComboBox<String>()),
                new TabPane(new Tab("Tab 1", new VBox(new Label("Label in tab 1"))),
                        new Tab("Tab 2", new StackPane(new Button("Button in tab 2")))));

        Button button = new Button("Show labeled's texts");
        button.setOnAction(e -> {
            List<Node> allSimpleNodes = new ArrayList<>();
            findAllSimpleNodes(allSimpleNodes, root);
            doAction(allSimpleNodes, Labeled.class, (Labeled l) -> System.out.println(l.getText()));
        });

        root.setAlignment(Pos.CENTER);
        BorderPane.setAlignment(button, Pos.CENTER);
        Scene scene = new Scene(new BorderPane(root, null, null, button, null), 600, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void findAllSimpleNodes(List<Node> allSimpleNodes, Node n) {
        List<Node> children = ChildRetrievalMapping.DEFAULT_INSTANCE.firstMatchingList(n);
        allSimpleNodes.addAll(children);
        for (Node child : children) {
            findAllSimpleNodes(allSimpleNodes, child);
        }
    }

    private <T> void doAction(Collection<Node> nodes, Class<T> type, Consumer<T> action) {
        nodes.stream()
            .filter(type::isInstance)
            .map(type::cast)
            .forEach(action);
    }

    public static class ChildRetrievalMapping {

        public static final ChildRetrievalMapping DEFAULT_INSTANCE = new ChildRetrievalMapping() ;

        static {
            // note the order of insertion is important: start with the more specific type

            DEFAULT_INSTANCE.put(TabPane.class, tabPane -> 
                    tabPane.getTabs().stream().map(Tab::getContent).collect(Collectors.toList()));
            DEFAULT_INSTANCE.put(SplitPane.class, SplitPane::getItems);
            // others...

            // default behavior for "simple" controls, just return empty list:
            DEFAULT_INSTANCE.put(Control.class, c -> Collections.emptyList());

            // default behavior for non-control parents, return getChildrenUnmodifiable:
            DEFAULT_INSTANCE.put(Parent.class, Parent::getChildrenUnmodifiable);


            // and for plain old node, just return empty list:
            DEFAULT_INSTANCE.put(Node.class, n -> Collections.emptyList());
        }

        private final Map<Class<?>, Function<? ,List<Node>>> map = new LinkedHashMap<>();

        public <N extends Node> void put(Class<N> nodeType, Function<N, List<Node>> childRetrieval) {
            map.put(nodeType, childRetrieval);
        }

        @SuppressWarnings("unchecked")
        public <N extends Node> Function<N, List<Node>> getChildRetrieval(Class<N> nodeType) {
            return (Function<N, List<Node>>) map.get(nodeType);
        }

        @SuppressWarnings("unchecked")
        public List<Node> firstMatchingList(Node n) {
            for (Class<?> type : map.keySet()) {
                if (type.isInstance(n)) {
                    return getChildRetrieval((Class<Node>) type).apply(n);
                }
            }
            return Collections.emptyList();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}