为什么JTextComponent.setText(String)不对行结尾进行规范化?

时间:2013-07-26 13:26:27

标签: java swing jtextcomponent

最近我注意到Java文本组件使用换行符(LF,\n,0x0A)来在内部表示和解释换行符。这让我感到非常惊讶并且我的假设是,在问号下使用System.getProperty('line.separator') 无处不在是一种很好的做法。

看起来无论何时处理文本组件,在使用提到的属性时都应该非常小心,因为如果使用JTextComponent.setText(String),最终可能会得到一个包含不可见换行符的组件(例如CR) )。除非可以将文本组件的内容保存到文件中,否则这可能看起来不那么重要。如果使用所有文本组件提供的方法将文本保存并打开到文件,则在重新打开文件时,隐藏的换行符会突然显示在组件中。原因似乎是JTextComponent.read(...)方法进行了规范化。

那么为什么JTextComponent.setText(String)没有标准化行结尾?或者是否允许在文本组件中修改文本的任何其他方法?在处理文本组件时使用System.getProperty('line.separator')是一种好的做法吗?这是一个好习惯吗?

一些代码可以提出这个问题:

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

public class TextAreaTest extends JFrame {

    private JTextArea jtaInput;
    private JScrollPane jscpInput;
    private JButton jbSaveAndReopen;

    public TextAreaTest() {
        super();
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setTitle("Text Area Test");
        GridBagLayout layout = new GridBagLayout();
        setLayout(layout);        

        jtaInput = new JTextArea();
        jtaInput.setText("Some text followed by a windows newline\r\n"
                + "and some more text.");
        jscpInput = new JScrollPane(jtaInput);
        GridBagConstraints constraints = new GridBagConstraints();
        constraints.gridx = 0; constraints.gridy = 0;
        constraints.gridwidth = 2;
        constraints.weightx = 1.0; constraints.weighty = 1.0;
        constraints.fill = GridBagConstraints.BOTH;
        add(jscpInput, constraints);

        jbSaveAndReopen = new JButton(new SaveAndReopenAction());
        constraints = new GridBagConstraints();
        constraints.gridx = 1; constraints.gridy = 1;
        constraints.anchor = GridBagConstraints.EAST;
        constraints.insets = new Insets(5, 0, 2, 2);
        add(jbSaveAndReopen, constraints);

        pack();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                TextAreaTest tat = new TextAreaTest();
                tat.setVisible(true);
            }
        });
    }

    private class SaveAndReopenAction extends AbstractAction {

        private File file = new File("text-area-test.txt");

        public SaveAndReopenAction() {
            super("Save and Re-open");
        }

        private void saveToFile() 
                throws UnsupportedEncodingException, FileNotFoundException,
                IOException {

            Writer writer = null;
            try {
                writer = new OutputStreamWriter(
                        new FileOutputStream(file), "UTF-8");
                TextAreaTest.this.jtaInput.write(writer);
            } finally {
                if (writer != null) {
                    try {
                        writer.close();
                    } catch (IOException ex) {
                    }
                }
            }
        }

        private void openFile() 
                throws UnsupportedEncodingException, IOException {
            Reader reader = null;
            try {
                reader = new InputStreamReader(
                        new FileInputStream(file), "UTF-8");
                TextAreaTest.this.jtaInput.read(reader, file);
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException ex) {
                    }
                }
            }
        }

        public void actionPerformed(ActionEvent e) {
            Throwable exc = null;
            try {
                saveToFile();
                openFile();
            } catch (UnsupportedEncodingException ex) {
                exc = ex;
            } catch (FileNotFoundException ex) {
                exc = ex;
            } catch (IOException ex) {
                exc = ex;
            }
            if (exc != null) {
                JOptionPane.showConfirmDialog(
                        TextAreaTest.this, exc.getMessage(), "An error occured",
                        JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE);
            }
        }        
    }
}

添加新文本后,此程序在我的Windows机器上保存的示例(为什么单个CR?o_O):

enter image description here

Edit01

我在Netbeans IDE中运行/调试了这个,它在Windows 7上使用JDK1.7u15 64位(C:\ Program Files \ Java \ jdk1.7.0_15)。

2 个答案:

答案 0 :(得分:2)

首先,真正的答案是设计师认为设计应该如何运作。你真的需要问他们才能得到真正的理由。

说完了:

  

那么为什么没有JTextComponent.setText(String)规范化行结尾?

我认为最可能的原因是:

  • 这将是出乎意料的行为。大多数程序员都希望 1 a' get'在文本字段上返回相同的字符串值,即#set;' ...或用户输入的内容。

  • 如果文本字段 进行了规范化,那么程序员很难在花瓶中保留原始文本的行结尾。

  • 设计师可能想要在某些时候改变主意(比较readwrite方法的报告行为)bur无法为兼容性的原因。

无论如何,如果你需要规范化,那么就没有什么能阻止你的代码对setter检索到的值这样做了。

  

或者是否允许在文本组件中修改文本的任何其他方法?

报告(见评论)read和/或write进行了规范化。

  

在处理文本组件时,使用System.getProperty(' line.separator')是一种很好的做法吗?这是一个好习惯吗?

这取决于具体情况。如果您知道自己正在阅读和编写要处理的文件,那么这就是"平台,这可能是一个好主意。如果要在不同的平台(使用不同的行分隔符)上读取文件,那么规范化以匹配当前机器的约定可能是一个坏主意。


1 - 其他方法(如{em} 他们不是" getters"和#34; setters"。我在谈论人们如何期待" getters"和#34; setters"表现......而不是别的什么。此外,人们不应该期望所有以相同的方式行事,除非指明他们这样做。但显然,问题的一部分是规范...... javadocs ......对这些问题保持沉默。

另一种可能性是@predi报告的规范化行为实际上发生在read / write个对象中......

答案 1 :(得分:1)

使用系统行分隔符是值得怀疑的。我使用它以平台特定格式编写文本文件。

阅读时,我总是只是扔掉任何'\ r',(CR)有效地将Windows / Mac / Unix转换为Unix风格的换行符。在内部,我永远不会使用普通的'\ n'(LF)以外的任何东西来表示换行 - 这会浪费内存并使处理文本更加痛苦。