簡體   English   中英

根據Java中的對象類型調用子類的方法

[英]Call Subclasses' Method Depending on Object Type in Java

問題:根據傳遞的參數類型,動態選擇相應的子類並調用其方法。

研究:努力將泛型方法應用於任務:

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是接口並且不能具有靜態方法):

  private ObservableList<Node> getElements(Node container)
    throws ClassNotFoundException {

    ObservableList<Node> nodes = FXCollections.observableArrayList();
    ObservableList<Object> objects = FXCollections.observableArrayList();

    objects.addAll(
      MetaContainer.CommonContainer.
      getChildren(
        (Object) container));

    for (Object object : objects) {
      nodes.add(
        (Node) object);
    }

    return nodes;
  }

問題:如何在整個MetaContainer上調用getChildren()並將其傳遞給參數中任何類型的容器,等待它在容器類型的子類中尋址正確的getChildren()

說明:簡而言之,我需要向下瀏覽節點容器以搜索簡單控件。 因此,您不知道事先是什么類型的節點-迭代時只能動態地知道。 一些子節點也是必須瀏覽的容器,但是每種類型都需要特定的方法。 我可以在類型上進行大小寫切換,但是覺得應該做得更優雅一些,例如每種類型的子類和一個公共接口。

好的,讓我a一下,盡管我仍然不知道我是否真的理解這個問題。 我認為您想通過以不同的方式獲取不同Parent子類的子節點來獲取場景圖的子集(即不一定通過調用Parent.getChildrenUnmodifiable() )。 因此,如果它是一個簡單的Pane ,則只需調用getChildren() ,但如果它是一個TabPane ,則將獲取每個Tab並獲取每個選項卡的內容,從而從中組成一個集合。 (和類似的其他“容器類型控件”,例如SplitPane等)。如果它是“簡單”控件,則不要將其視為具有任何子節點(即使在幕后, Button包含一個例如Text )。

因此,我認為您可以通過構建類型安全的異構容器(請參閱Josh Bloch的Effective Java )將特定的節點類型N映射到Function<N, List<Node>>來做到這一點。 該函數將定義如何檢索該類型的子節點。

這可能看起來像

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();
    }
}

現在您可以調用childRetrievalMapping.findFirstMatchingList(node); 並從與節點匹配的地圖中的第一種類型定義的意義上獲取子級列表。 因此,使用DEFAULT_INSTANCE ,如果將TabPane傳遞給它,它將獲取所有內容節點; 如果您將其傳遞給SplitPane ,它將獲得項目; 如果您將其傳遞給另一種類型的控件,它將返回一個空列表,依此類推。

這是使用此示例。 這只是建立一個場景圖,然后當您按下按鈕時,它會遍歷它,只得到上述類中的策略定義的“簡單”節點。 (然后,它選擇Labeled所有實例,並將getText()的結果傳遞到系統控制台。)請注意,它如何(故意)避免了作為選項卡本身實現的一部分的標簽,這是幼稚的root.lookupAll(".labeled")不會。

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);
    }
}

我不確定這是否是您要執行的操作,如果是,可能會有更優雅的方法來處理。 但是我認為,針對特定類型聲明這樣的策略要比對類型進行大量更改要好得多,並且可以將其配置為具有所需的特定規則。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM