[软件构造心得1] 实验中遇到的有关字符串的一些坑


在我写实验的时候,常常被字符串为空和字符串相等的判断给坑到,而在进行调试时,还遇上了一些难以理解的现象,于是我大致整理了下遇到的问题,写了这篇博客。

一、字符串为空

常见的判断字符串为空,主要有以下三种

String string;

string == null      //1
string == ""        //2
string.isEmpty();   //3

1、string == null

这条语句的意思是string指的对象为空,也就还没分配内存。

String string = null;
System.out.println(string == null);
string = "";
System.out.println(string == null);

得到的结果

true
false

可见一旦分配了对象,哪怕是空的字符串""string == null也会判断为false

值得注意的是,由于string还未分配内存,因此不能直接调用string.equals() string.isEmpty()等方法,否则会报空指针错误

2、string.equals("")string == ""

这条语句的意思是字符串的值为空,也就是分配了对象,但分配的对象是一个空的字符串""
也就是

String string = "";
System.out.println(string == "");

打印结果是

true

其实对于字符串内容的比较,java中更推荐使用string.equals("")来进行比较,尽管对于我们直接写的""字符串内容,java虚拟机是将其存在常量缓冲池中,不需要分配地址,所以使用==的实际效果是和equals()方法一样的,但是如果我们进行比较的内容是另一个字符串对象,那么==将直接比较两个对象的地址,这样即使两个内容相同的字符串,比较的结果也是不同的。
直接说可能有些抽象,下面通过一个例子展示。

String str1 = new String();
str1 = "hello";
String str2 = new String("hello");
System.out.println(str1 == "hello");
System.out.println(str1 == str2);
System.out.println(str2 == "hello");

结果是

true
false
false

对于str1,直接指向了缓冲池的"hello"字符串,而str2则被分配了新的对象,没有指向缓冲池,因此使用==进行比较会出错。

而使用equals进行比较,则是直接比较内容,就没有这方面的问题。

String str1 = new String();
str1 = "hello";
String str2 = new String("hello");
System.out.println(str1.equals("hello"));
System.out.println(str1.equals(str2));
System.out.println(str2.equals("hello"));

结果

true
true
true

对于这块想要更深入了解可以看对象等价性和引用等价性的区别。

3、string.isEmpty()

其实string.isEmpty()string.equals("")是等价的。
isEmpty的代码内部实现如下

 /**
     * Returns {@code true} if, and only if, {@link #length()} is {@code 0}.
     *
     * @return {@code true} if {@link #length()} is {@code 0}, otherwise
     * {@code false}
     *
     * @since 1.6
     */
    public boolean isEmpty() {
        return value.length == 0;
    }

只有当字符串为空串时,也就是"",字符串的长度为0。

二、空字符串的打印

对于下面的代码

String str = null;
System.out.println(str);

我本以为静态检查会报错,一个空的对象是没法打印的。
但实际上打印的结果是

null

我们先看println的源码

/**
     * Prints a String and then terminate the line.  This method behaves as
     * though it invokes {@link #print(String)} and then
     * {@link #println()}.
     *
     * @param x  The {@code String} to be printed.
     */
    public void println(String x) {
        if (getClass() == PrintStream.class) {
            writeln(String.valueOf(x));
        } else {
            synchronized (this) {
                print(x);
                newLine();
            }
        }
    }

接着看printstream的内部实现

/**
	* The following two methods exist because in, out, and err must be
	* initialized to null. The compiler, however, cannot be permitted to
	* inline access to them, since they are later set to more sensible values
	* by initializeSystemClass().
	*/
	private static InputStream nullInputStream() throws NullPointerException {
	    if (currentTimeMillis() > 0) {
	        return null;
	    }
	    throw new NullPointerException();
	}
	
	private static PrintStream nullPrintStream() throws NullPointerException {
	    if (currentTimeMillis() > 0) {
	        return null;
	    }
	    throw new NullPointerException();
	}

阅读spec可以大致知道, in、out和err初始都是null的,它们稍后会被initializeSystemClass()设置为更合理的值。
而这个initializeSystemClass()的spec是这样的

/**
* Initialize the system class. Called after thread initialization.
*/

initializeSystemClass()这个方法的具体细节不再赘述了,有兴趣可以去看system类的源码。
总而言之,println相当于实现了以下内容

public void println(String str) {
        if (str == null) {
            str = "null";
        }
        write(s);
    }

事实上,对于Object o = null;打印结果也仍然是null
而如果是运行下面内容

char[] ch = null;
System.out.println(ch);

则会抛出异常

Exception in thread "main" java.lang.NullPointerException
	at java.base/java.io.Writer.write(Writer.java:213)
	at java.base/java.io.PrintStream.writeln(PrintStream.java:608)
	at java.base/java.io.PrintStream.println(PrintStream.java:940)

如果是直接写System.out.println(null);,则在静态检查阶段就会报错。

三、空字符串的连接

在写实验3时,我曾利用string == null来判断计划项是否被分配资源,若分配了便采用字符串连接,试图将资源赋值给string(像高铁管理这种资源由多节车厢组成的,采用字符串连接可能会方便点),但却没达到我想要的结果。

实验的代码相当于以下代码

String str = null;
str = str + "hello";
System.out.println(str);

我本以为会打印

hello

但实际打印的结果却是

nullhello

这实际上是字符串连接导致的问题。
通过反编译Java的代码,+的实现实际上是通过StringBuilderappend方法来进行字符串连接,而append的实现大致如下

 public AbstractStringBuilder append(String str) {
        if (str == null) str = "null";
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

可见对于空对象str,它进行了特判,转为"null",然后进行连接。

四、窗体中的JTextField

在实验3中用户的所有操作都通过GUI实现,因此绕不开窗体,由于我想加入对于用户输入的判断,如果用户没有对窗体的每个文本框都输入内容,需要提示用户重新输入。因此我想知道:
对于窗体的JTextField,如果我们不输入内容,调用下面的代码

private JTextField textField = new JTextField();
String str = textField.getText();

str存的是空指针null还是空字符串""呢?
我们查看getText()的源码

/**
     * Returns the text contained in this <code>TextComponent</code>.
     * If the underlying document is <code>null</code>,
     * will give a <code>NullPointerException</code>.
     *
     * Note that text is not a bound property, so no <code>PropertyChangeEvent
     * </code> is fired when it changes. To listen for changes to the text,
     * use <code>DocumentListener</code>.
     *
     * @return the text
     * @exception NullPointerException if the document is <code>null</code>
     * @see #setText
     */
    public String getText() {
        Document doc = getDocument();
        String txt;
        try {
            txt = doc.getText(0, doc.getLength());
        } catch (BadLocationException e) {
            txt = null;
        }
        return txt;
    }

可见字符串只有遇到BadLocationException异常才会返回为null,而看BadLocationException的spec

/**
 * This exception is to report bad locations within a document model
 * (that is, attempts to reference a location that doesn't exist).
 * <p>
 * <strong>Warning:</strong>
 * Serialized objects of this class will not be compatible with
 * future Swing releases. The current serialization support is
 * appropriate for short term storage or RMI between applications running
 * the same version of Swing.  As of 1.4, support for long term storage
 * of all JavaBeans&trade;
 * has been added to the <code>java.beans</code> package.
 * Please see {@link java.beans.XMLEncoder}.
 *
 * @author  Timothy Prinzing
 */

只有尝试引用不存在的位置才会抛出此异常,我个人觉得这个引用不存在的位置语义不是很明确,因此继续看DocumentgetText方法何时抛出BadLocationException

/**
     * Gets a sequence of text from the document.
     *
     * @param offset the starting offset &gt;= 0
     * @param length the number of characters to retrieve &gt;= 0
     * @return the text
     * @exception BadLocationException  the range given includes a position
     *   that is not a valid position within the document
     * @see Document#getText
     */
    public String getText(int offset, int length) throws BadLocationException {
        if (length < 0) {
            throw new BadLocationException("Length must be positive", length);
        }
        String str = data.getString(offset, length);
        return str;
    }

可以看到是length < 0才抛出异常,而这个lengthJTextField类的getText()doc.getLength()传进来的,所以我们再看DocumentgetLength方法

/**
     * Returns the length of the data.  This is the number of
     * characters of content that represents the users data.
     *
     * @return the length &gt;= 0
     * @see Document#getLength
     */
    public int getLength() {
        return data.length() - 1;
    }

接着看这个data.length()的源代码

/**
     * Returns the length of the content.
     *
     * @return the length &gt;= 1
     * @see AbstractDocument.Content#length
     */
    public int length() {
        return count;
    }

可以看到返回的是count
问题就是count初始的值为什么?
向上翻到了如下的初始化代码

/**
     * Creates a new StringContent object, with the initial
     * size specified.  If the length is &lt; 1, a size of 1 is used.
     *
     * @param initialLength the initial size
     */
    public StringContent(int initialLength) {
        if (initialLength < 1) {
            initialLength = 1;
        }
        data = new char[initialLength];
        data[0] = '\n';
        count = 1;
    }

可见count初始化为1

因此我们不输入数据,data.length() - 1 = 0,不会抛出BadLocationException,故得到的字符串str就不会是空指针null,那么str很有可能是空字符串""

下面接着验证,通过上面DocumentgetText方法,我们知道返回的字符串的值是通过data.getString(offset, length);得到的,于是查看data.getString源代码

/**
     * Retrieves a portion of the content.  where + len must be &lt;= length().
     *
     * @param where the starting position &gt;= 0
     * @param len the length to retrieve &gt;= 0
     * @return a string representing the content; may be empty
     * @exception BadLocationException if the specified position is invalid
     * @see AbstractDocument.Content#getString
     */
    public String getString(int where, int len) throws BadLocationException {
        if (where + len > count) {
            throw new BadLocationException("Invalid range", count);
        }
        return new String(data, where, len);
    }

到这边就一清二楚了,由于我们不输入数据,所以传入的参数len0,于是返回的新String对象创建的长度是0,所以就是空字符串""

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值