Skip to content

Latest commit

 

History

History
235 lines (161 loc) · 8.48 KB

避免在组合框中使用空值.md

File metadata and controls

235 lines (161 loc) · 8.48 KB

避免在组合框中使用空值

要在 JavaFX 中使用组合框,声明一个项目列表并使用 setValue() 设置初始值。ComboBox 方法 getValue() 检索当前选择的值。如果未提供初始值,则控件默认为空值。

当 ComboBox 驱动其他逻辑(如大写转换或数据库记录查找)时,空值是一个问题。虽然通常使用空检查来防止这种类型的错误,但为了简化代码,首选空对象。组合框经常出现在集群中,空对象技术减少了相关组合框的交互以及保存和加载操作中的空检查。

本文介绍了一对相关的组合框。一个组合框中的国家选择将修改另一个组合框中的可用城市项目列表。这两种选择都不是必需的。用户可以在任何时候按 Save 按钮,如果没有选择任何一个 ComboBox,将返回一个空对象——在这种情况下是一个空字符串。

从一个空的初始值中选择 “Swiss” 将会在城市组合框中填充瑞士城市。选择城市 “Zurich” 并按 Save 键将检索这些值。

nonullcombo_screenshot

数据结构

支持该应用程序的数据结构是国家列表和城市地图。城市地图以国家为关键。

NoNullComboApp.class

public class NoNullComboApp extends Application {

    private List<String> countries = new ArrayList<>();

    private Map<String, List<String>> citiesMap = new LinkedHashMap<>();

    private void initData() {

        String COUNTRY_FR = "France";
        String COUNTRY_DE = "Germany";
        String COUNTRY_CH = "Switzerland";

        countries.add(COUNTRY_FR); countries.add(COUNTRY_DE); countries.add(COUNTRY_CH);

        List<String> frenchCities = new ArrayList<>();
        frenchCities.add("Paris");
        frenchCities.add("Strasbourg");

        List<String> germanCities = new ArrayList<>();
        germanCities.add("Berlin");
        germanCities.add("Cologne");
        germanCities.add("Munich");

        List<String> swissCities = new ArrayList<>();
        swissCities.add("Zurich");

        citiesMap.put(COUNTRY_FR, frenchCities );
        citiesMap.put(COUNTRY_DE, germanCities );
        citiesMap.put(COUNTRY_CH, swissCities );
    }

要检索给定国家的城市集,请使用 Map 的 get() 方法。containsKey() 方法可用于确定 Map 是否包含指定国家的值。在本例中,containsKey() 将用于处理空对象的情况。

UI

UI 是一对带有标签和保存按钮的组合框。控件放在 VBox 中并左对齐。VBox 被封装在 tile 窗格中并居中。使用 tile 窗格是因为它不会水平拉伸 VBox。

NoNullComboApp.class

    @Override
    public void start(Stage primaryStage) throws Exception {

        Label countryLabel = new Label("Country:");
        country.setPrefWidth(200.0d);
        Label cityLabel = new Label("City:");
        city.setPrefWidth(200.0d);
        Button saveButton = new Button("Save");

        VBox vbox = new VBox(
                countryLabel,
                country,
                cityLabel,
                city,
                saveButton
        );
        vbox.setAlignment(Pos.CENTER_LEFT );
        vbox.setSpacing( 10.0d );

        TilePane outerBox = new TilePane(vbox);
        outerBox.setAlignment(Pos.CENTER);

        Scene scene = new Scene(outerBox);

        initData();

初始值

如前所述,如果没有为 ComboBox 指定值,那么 getValue() 调用将返回 null。尽管有几种防御技术——if 检查、Commons StringUtils 方法——可以抵御 nullpointerexception,但最好完全避免它们。当交互变得复杂或存在多个允许空选择的 combobox 时尤其如此。

NoNullComboApp.class

        country.getItems().add("");
        country.getItems().addAll( countries );
        country.setValue( "" );  // empty selection is object and not null

        city.getItems().add("");
        city.setValue( "" );

在这个应用程序中,国家组合框不会被改变,所以它的项目被添加到 start() 方法中。国家和城市一样,初始选择为空。此时,City 只包含一个空项。

交互

当国家值改变时,应该替换城市组合框的内容。通常在支持列表中使用clear();然而,这将在 ComboBox 中产生一个空值(没有项目,没有值)。相反,使用带有子句的 removeIf() 来保留单个空项。清除列表中的所有数据(空项除外)后,可以使用 addAll() 添加新选择的内容。

NoNullComboApp.class

        country.setOnAction( (evt) -> {

            String cty = country.getValue();

            city.getItems().removeIf( (c) -> !c.isEmpty() );

            if( citiesMap.containsKey(cty) ) {  // not an empty key
                city.getItems().addAll( citiesMap.get(cty) );
            }
        });

        saveButton.setOnAction( (evt) -> {
           System.out.println("saving country='" + country.getValue() +
                                      "', city='" + city.getValue() + "'");
        });

Save Button 操作将打印出这些值。在任何情况下,getValue() 都不会返回空值。

如果您是一名 Java 开发人员,那么您已经编写了数千次 “If not null”。然而,在一个又一个项目中,我看到NullPointerExceptions 突出显示了错过的情况或出现的新情况。本文介绍了一种在 combobox 中保留空对象的技术,方法是设置一个初始值,并在更改列表时使用 removeIf() 而不是 clear()。虽然这个例子使用的是String对象,但它可以扩展为使用具有 hashCode/equals 实现、空对象表示和 cellFactory 或 toString() 以产生空视图的域对象。

完整代码

代码可以在单个 .java 文件中进行测试。

NoNullComboApp.class

public class NoNullComboApp extends Application {

    private final ComboBox<String> country = new ComboBox<>();
    private final ComboBox<String> city = new ComboBox<>();

    private List<String> countries = new ArrayList<>();

    private Map<String, List<String>> citiesMap = new LinkedHashMap<>();

    @Override
    public void start(Stage primaryStage) throws Exception {

        Label countryLabel = new Label("Country:");
        country.setPrefWidth(200.0d);
        Label cityLabel = new Label("City:");
        city.setPrefWidth(200.0d);
        Button saveButton = new Button("Save");

        VBox vbox = new VBox(
                countryLabel,
                country,
                cityLabel,
                city,
                saveButton
        );
        vbox.setAlignment(Pos.CENTER_LEFT );
        vbox.setSpacing( 10.0d );

        TilePane outerBox = new TilePane(vbox);
        outerBox.setAlignment(Pos.CENTER);

        Scene scene = new Scene(outerBox);

        initData();

        country.getItems().add("");
        country.getItems().addAll( countries );
        country.setValue( "" );  // empty selection is object and not null

        city.getItems().add("");
        city.setValue( "" );

        country.setOnAction( (evt) -> {

            String cty = country.getValue();

            city.getItems().removeIf( (c) -> !c.isEmpty() );

            if( citiesMap.containsKey(cty) ) {  // not an empty key
                city.getItems().addAll( citiesMap.get(cty) );
            }
        });

        saveButton.setOnAction( (evt) -> {
           System.out.println("saving country='" + country.getValue() +
                                      "', city='" + city.getValue() + "'");
        });

        primaryStage.setTitle("NoNullComboApp");
        primaryStage.setScene( scene );
        primaryStage.setWidth( 320 );
        primaryStage.setHeight( 480 );
        primaryStage.show();
    }

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

    private void initData() {

        String COUNTRY_FR = "France";
        String COUNTRY_DE = "Germany";
        String COUNTRY_CH = "Switzerland";

        countries.add(COUNTRY_FR); countries.add(COUNTRY_DE); countries.add(COUNTRY_CH);

        List<String> frenchCities = new ArrayList<>();
        frenchCities.add("Paris");
        frenchCities.add("Strasbourg");

        List<String> germanCities = new ArrayList<>();
        germanCities.add("Berlin");
        germanCities.add("Cologne");
        germanCities.add("Munich");

        List<String> swissCities = new ArrayList<>();
        swissCities.add("Zurich");

        citiesMap.put(COUNTRY_FR, frenchCities );
        citiesMap.put(COUNTRY_DE, germanCities );
        citiesMap.put(COUNTRY_CH, swissCities );
    }
}