如何告诉Kotlin数组或集合不能包含空值?

时间:2015-11-07 13:23:53

标签: kotlin

如果我创建一个数组,然后填充它,Kotlin认为数组中可能有空值,并迫使我考虑到这个

val strings = arrayOfNulls<String>(10000)
strings.fill("hello")
val upper = strings.map { it!!.toUpperCase() } // requires it!!
val lower = upper.map { it.toLowerCase() } // doesn't require !!

创建填充数组没有此问题

val strings = Array(10000, {"string"})
val upper = strings.map { it.toUpperCase() } // doesn't require !!

如何告诉编译器strings.fill("hello")的结果是NonNull数组?

3 个答案:

答案 0 :(得分:2)

经验法则:如果有疑问,请明确指定类型(对此有一种特殊的重构):

val strings1: Array<String?> = arrayOfNulls<String>(10000)
val strings2: Array<String>  = Array(10000, {"string"})

因此,您看到strings1包含可以为空的项目,而strings2则不包含。那只是决定如何使用这些数组:

// You can simply use nullability in you code:
strings2[0] = strings1[0]?.toUpperCase ?: "KOTLIN"

//Or you can ALWAYS cast the type, if you are confident:
val casted = strings1 as Array<String>

//But to be sure I'd transform the items of the array:
val asserted = strings1.map{it!!}
val defaults = strings1.map{it ?: "DEFAULT"}

答案 1 :(得分:1)

没有办法告诉编译器。变量的类型在声明时确定。在这种情况下,变量被声明为可以包含空值的数组。

fill()方法不声明新变量,它只修改现有变量的内容,因此不会导致变量类型发生变化。

答案 2 :(得分:1)

为什么填充数组工作正常

填充数组在用作第二个参数的lambda调用期间推断出数组的类型:

import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;

public class Test {

    private JFrame frame;
    private JTextField textField;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    Test window = new Test();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     */
    public Test() {
        initialize();
    }

    /**
     * Initialize the contents of the frame.
     */
    private void initialize() {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridwidth = GridBagConstraints.REMAINDER;

        JCheckBox chA = new JCheckBox("A");
        chA.setFont(new Font("Adobe Arabic", Font.PLAIN, 12));
        frame.getContentPane().add(chA, gbc);
//      chA.setEnabled(false);

        JCheckBox chB = new JCheckBox("B");
        chB.setFont(new Font("Adobe Arabic", Font.PLAIN, 12));
        frame.getContentPane().add(chB, gbc);
//      chB.setEnabled(false);

        JCheckBox chckbx1 = new JCheckBox("C");
        chckbx1.setFont(new Font("Adobe Arabic", Font.PLAIN, 12));
        frame.getContentPane().add(chckbx1, gbc);
        chckbx1.setEnabled(false);

        JPanel panel = new JPanel();
        frame.add(panel, gbc);

        textField = new JTextField();
        JScrollPane scrollPane = new JScrollPane(textField);
//      scrollPane.add(textField);
        textField.setColumns(6);
        panel.add(scrollPane);

        frame.pack();
        frame.setLocationRelativeTo(null);

        panel.setVisible(false);

        ActionListener al = (ActionEvent e) -> {
            if (chA.isSelected() || chB.isSelected()) {
                chckbx1.setEnabled(true);
            }
            //setting the panel to visible when box a or B and chckbx1 has been selected 
            if ((chA.isSelected() || chB.isSelected()) && chckbx1.isSelected()) {
                panel.setVisible(true);
                frame.revalidate();
                frame.repaint();
            }
        };
        chA.addActionListener(al);
        chB.addActionListener(al);
        chckbx1.addActionListener(al);

    }

}

生成<body> <section id="firstpage" data-role="page"> <div data-role="header"> <h1>Activity</h1> </div> <div class="ui-content"> <p>This is the content on page 1</p> <!-- put some kind of a table with cells here --> </div> <div data-role="footer"> <!-- Buttons go here --> </div> </section> <section id="secondpage" data-role="page"> <div data-role="header"> <h1>label2</h1> </div> <div class="ui-content"> <p>This is the content on page 1</p> <!-- put some kind of a table with cells here --> </div> <div data-role="footer"> <!-- Buttons go here --> </div> </section> </body>

val strings = Array(10000, {"string"})

生成Array<String>

因此,将val strings = Array(10000, { it -> if (it % 2 == 0) "string" else null }) 左侧的声明更改为与lambda不匹配的声明无效。如果存在冲突,则表示存在错误。

如何使arrayOfNulls正常工作

对于Array<String?>问题,他们键入您指定的调用=在函数签名中用作泛型类型arrayOfNulls,函数arrayOfNulls<String>返回{{1}这意味着可以为空。代码中没有任何内容更改该类型。 T方法仅将值设置为现有数组。

要将此nullable-element数组转换为非可空元素列表,请使用:

arrayOfNulls

这很好,因为您的Array<T?>调用无论如何转换为列表,所以为什么不事先转换。现在,根据数组的大小,这可能是高性能的,如果在CPU高速缓存中,副本可能很快。如果它很大并且没有表现,你可以使这个懒惰:

fill

或者您可以通过复制保留数组,但实际上这没有任何意义,因为您使用val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") } val strings = nullableStrings.filterNotNull() val upper = strings.map { it.toUpperCase() } // no !! needed 撤消它:

map

在Java或Kotlin代码(JetBrains研究统计数据)中,数组确实不常见,除非代码进行了非常低级别的优化。使用列表可能会更好。

鉴于你最终可能会以列表结束,也许从那里开始并放弃阵列。

val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") }
val strings = nullableStrings.asSequence().filterNotNull()
val upper = strings.map { it.toUpperCase() } // no !! needed

但是,如果你不能停止使用数组的任务,并且真的必须在没有副本的情况下施放...

您总是可以编写一个执行两项操作的函数:首先,检查所有值是否为null,如果是,则返回强制转换为非null的数组。这有点hacky,但只是因为差异是可空性的,所以是安全的。

首先,在map上创建一个扩展函数:

val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") }
val strings: Array<String> = Array(nullableStrings.size, { idx -> nullableStrings[idx]!! })

然后使用此函数new函数进行转换(元素检查为非null转换):

val nullableStrings = listOf("a","b",null,"c",null,"d")
val strings =  nullableStrings.filterNotNull()

但即使谈到最后一个选项,我也觉得很脏。