有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

在为Java Swing元素实现ActionListener时,如何解决静态引用错误?

我是一个相当新手的Java程序员,正在尝试使用JavaSwing元素创建一个简单的用户界面,该元素接受用户输入,并(最终)验证输入的日期的格式是否正确

我目前正在尝试使用一个主“createTextbox”类,该类生成表单,同时使用一个实现ActionListener的类来读取用户的输入,并在单击按钮时验证日期。我遇到的问题是,当我尝试在ActionListener类中从CreateTextbox引用我的字段时,我得到了一个错误,因为在静态上下文中引用我的字段是非静态的。但是,我不想将字段定义为静态字段,因为我希望用户能够更改输入并再次单击按钮

以下是我的CreateTextbox类:

import javax.swing.JButton;
import javax.swing.JFrame;

import javax.swing.JLabel;
import javax.swing.JTextField;


public class CreateTextbox extends JFrame{

  JTextField stringEntry, dateEntry;
  JLabel stringEntryLabel, dateEntryLabel;
  JButton print;


  public CreateTextbox(){

    setLayout(null);
    stringEntry = new JTextField(1);
    stringEntry.setBounds(100,20,100,20);
    add(stringEntry);

    stringEntryLabel = new JLabel("String Name:");
    stringEntryLabel.setBounds(10,20,100,20);
    add(stringEntryLabel);

    dateEntry = new JTextField(1);
    dateEntry.setBounds(100,50,100,20);
    add(dateEntry);

    dateEntryLabel = new JLabel("Date:");
    dateEntryLabel.setBounds(10,50,100,20);

    print = new JButton("Validate");
    print.setBounds(20,260,100,20);
    print.addActionListener(new MyAction());
    add(print);

  }

  public static void main (String[] args)
  {
    CreateTextbox me = new CreateTextbox();
    me.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    me.setVisible(true);
    me.setLocation(550, 500);
    me.setSize(700, 700);
    me. setTitle("Create Textbox");

  }

} 

以及ActionListener类:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.ParseException;

public class MyAction implements ActionListener {

  public void actionPerformed(ActionEvent e) {
    String strInput = CreateTextbox.stringEntry.getText();
    String strDate = CreateTextbox.dateEntry.getText();
    valiDate(strDate);
  }
  public void valiDate(String date) {
    //code to validate the date
  }
}

我猜这有一个我忽略的非常简单的解决方案。我看过几篇像Non-static variable cannot be referenced from a static context这样的帖子,但我仍然很难找到解决方案


共 (2) 个答案

  1. # 1 楼答案

    既然你们已经说你们正在学习,我将试着用“为什么和为什么不”来给你们一个教学上的答案,可能有点太长了,但希望它能帮助你们

    当您引用CreateTextbox.stringEntry时,您引用的是一个类变量,这是一个静态变量,因为您实际上在语句的第一部分告诉我们的是一个类,而不是一个对象,这是该类的所有实例所共有的

    另一方面,当您执行this.stringEntry操作时,您引用的是对象的变量,它不同于that.stringEntry,并且它们是其所有者对象的属性。必须指出的是,一般来说,让您的变量可见是一种不好的做法(当然有时不这样做是合理的),而是让其他人通过“getter”访问它们,即允许您访问内部变量的方法:getStringEntry():JTextFieldgetDateEntry():JTextField。但同样,您不想让其他人获得您的组件,因为当您只想公开其值时,可以从外部操纵它们,所以只需公开它们各自的文本:

    public String getNameText() {
        return this.stringEntry.getText();
    }
    
    public String getDateText() {
        return this.dateEntry.getText();
    }
    

    然后,在另一个类中有一个ActionListener。当ActionListener的事件被触发时,该方法接收一个ActionEvent,其中包含对发起事件的对象的引用,并且可以通过方法getSource()访问该对象。当您有一个外部类时,您希望使用该类“附加”到CreateTextbox,因此您希望在源中接收CreateTextbox,因此您可以像这样访问实例:

    @Override
    public void actionPerformed(ActionEvent e) {
        Object source = e.getSource();
        if (source instanceof CreateTextbox) {
            CreateTextbox instance = (CreateTextbox) source;
            String strInput = instance.getNameText();
            String strDate = instance.getDateText();
            valiDate(strDate);
        }
    }
    

    请注意,您现在正在访问对象(类的一个实例化),而不是静态地访问类(在所有实例中都是通用的)

    但是您将这个操作添加到一个按钮,因此您将收到一个JButton作为源,它是您正在实现的内容的一个内部组件,因此这应该会让您认为您最好使用一个内部类<请注意,这通常是不正确的,也不是必需的,我是从一个好的设计角度讲的,您确实可以在外部类中实现这一点让我添加一个建议,使用Action而不是ActionListener,这没有多大区别,但很有用,因为它封装了不同组件要使用的整个操作,并使其跨组件层次结构以及名称、图标等可用

    一旦移动到类内部,您就可以选择声明内部类static,并使其独立于实例;或者不,这样您就可以访问变量。显然你的是第二个案子

    @Override
    public void actionPerformed(ActionEvent e) {
        String strInput = CreateTextbox.this.stringEntry.getText();
        String strDate = CreateTextbox.this.dateEntry.getText();
        valiDate(strDate);
    }
    

    这是您的方法在内部类中的外观,还原为原始代码,但注意到原始的CreateTextbox.stringEntry(静态引用)现在是CreateTextbox.this.stringEntry(访问实例)

    最后,它可能是这样的:

    import java.awt.event.ActionEvent;
    import javax.swing.AbstractAction;
    import javax.swing.Action;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JTextField;
    import net.miginfocom.swing.MigLayout;
    
    @SuppressWarnings("serial")
    public class CreateTextbox extends JFrame {
    
        /* Make your inner components private, it's your responsibility to manage
         * them and so you'd not get undesired interactions */
        protected JTextField stringEntry, dateEntry;
        protected JLabel stringEntryLabel, dateEntryLabel;
        protected JButton print;
        protected Action validateAction;
    
        public CreateTextbox() {
            super("Create Textbox"); // The title makes more sense in the constructor
            /* Never use a null layout, is a bad practice, can work for a one-shot
             * but is in no way something you can manage or expand. Also the results
             * are usually clunky. I use MigLayout, it requires expertise but when
             * you master it it's very useful. For standard ones:
             * https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html */
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            setLayout(new MigLayout("ins 10, gap 5", // Margin around of 10 px
                                                     // 5px between components 
                    "[][grow]", // Fist column, minimum; second column, grow
                    "[][][]"));
    
            /* "this." is not necessary but it's good to realize we're talking about
             * the variable of this INSTANCE of the class */
            this.stringEntryLabel = new JLabel("Name:");
            add(this.stringEntryLabel, ""); // This string is the constraint for telling
                                       // MigLayout how to place this component,
                                       // nothing required in this case 
    
            this.stringEntry = new JTextField();
            add(this.stringEntry, "grow, wrap"); // Fill space, next line
    
            this.dateEntryLabel = new JLabel("Date:");
            add(this.dateEntryLabel, "");
    
            this.dateEntry = new JTextField();
            add(this.dateEntry, "grow, wrap");
    
            this.validateAction = new MyAction();
    
            this.print = new JButton(this.validateAction); // Button for the action
            add(this.print, "span 2, center"); // Use 2 cells, center the button
    
            /* Example of how it's useful to have an action, not just a listener */
            getRootPane().getActionMap().put("validate", this.validateAction);
        }
    
        /* Added originally for didactical purposes */
        @Deprecated
        public String getNameText() {
            return this.stringEntry.getText();
        }
    
        /* Added originally for didactical purposes */
        @Deprecated
        public String getDateText() {
            return this.dateEntry.getText();
        }
    
        public static void main(String... args) {
            CreateTextbox me = new CreateTextbox();
            me.pack(); // Since now you have a layout, let it do its thing
            me.setLocationByPlatform(true); // Let OS place the window
            me.setVisible(true);
        }
    
    
        /* MyAction is better an Action than an ActionListener to it can be used for
         * more purposes */
        public class MyAction extends AbstractAction {
    
            public MyAction() {
                super("Validate");
            }
    
            @Override
            public void actionPerformed(ActionEvent e) {
                String strInput = CreateTextbox.this.stringEntry.getText();
                String strDate = CreateTextbox.this.dateEntry.getText();
                valiDate(strDate);
            }
    
            public void valiDate(String date) {
                // TODO code to validate the date
                System.out.println("Validating date \"" + date + "\"...");
            }
    
        }
    
    }
    

    让我补充一点,您应该看看文章"How to Use Formatted Text Fields",因为Java已经有了自己的方法来验证文本字段中的日期和其他值

  2. # 2 楼答案

    我将内联listener类。这样您就可以访问实例变量,即使它们是私有的

    print.addActionListener(event -> {
      String strInput = CreateTextbox.stringEntry.getText();
      String strDate = CreateTextbox.dateEntry.getText();
      validate(strDate);
    });