Java中的static——关于Eclipse的No enclosing instance of type ... 错误的理解和改正

本文链接地址:https://www.cnblogs.com/oberon-zjt0806/p/11632528.html

No enclosing instance of type SomeClass is accessible. Must qualify the allocation with an enclosing instance of type SomeClass (e.g. x.new A() where x is an instance of SomeClass).

这是怎么发现的??

拿Eclipse编写Java的AWT/Swing程序时,编写了一个public class MainFrame extends JFrame然后在其中放入一个主函数写入了如下内容:

public static void main(String args[])
{
	MainJFrame frmBase = new MainJFrame("Hello Swing");
	frmBase.setBackground(Color.white);
	frmBase.setVisible(true);
	frmBase.addWindowListener(new MainJWindowListener());    // <--- Error occured here !!!
}

也就是我在主函数创建了窗口之后想要继续在主函数里为frmBase这个JFrame窗口添加窗口侦听器(class MainJWindowListener implements WindowListener),其中MainJWindowListener作为MainFrame内类
然后错误就发生了……

为什么会这样呢??

如果已经了解了静态的内容,请自行跳转至“静态成员说完了,那跟这你遇到的这个问题有什么关系呢??”子节

好吧,其实说到底都是mainstatic修饰惹的祸。
我们知道static的成员也好,类也好,所谓的“静态”其实说的都是“为整个程序所共享”的意思,说的直白一点,static的东西都非常的“大公无私”
当然,其实我写C#写的比较多,对Java的规则还不是很了解,似乎Java并不像C#那样允许public static class SomeClass,所以对于Java而言更多的是针对静态成员讨论的。

作为一个大公无私的内容,static的成员里面除了自己再构造一些临时对象以外,直接援引的类、函数都不能为某一对象所特有的。

不能为某一对象所特有??但是既然是定义到类里的属性,那类的所有对象不是都有这个属性么??

不,这里说的“特有”并不是这个意思,或许我换个词语会更好一些——对象“个性”的属性。

个性化……但这是什……

打住,我知道你要问这件事,所以我这里举个糖炒栗子:
比如说类Gold被定义为:

public class Gold
{
    // Properties
    private static float xau = 1504.35f;  // 金单价,也就是每盎司黄金在国际市场的价格,我随便编的数请别在意……
    private float oz;    // 该黄金实例(理解成金块金锭金碎末金戒指金项链都可以)的质量(盎司数)
    
    // Constructor
    public Gold(float oz)
    {
        this.oz = oz;
    }
    // Methods ... 
}

我们看到金单价xau是静态的,因为无论那个金子多沉,长成什么样子,金单价对所有金子都是一样的,而且当xau变动的时候,所有Gold的价格也都会随着xau变动。所以这种属性就不具备“个性”,因为你用我用大家一起用,也就是说,这是全体的“共性”。

而重量oz就不一样了,一座金山(Kingsoft)可能有1e+xxx的oz,而一个金粒可能只有1e-xxx的oz,我可能用一个new Gold(1e+500)表示我构建了一个黄金星球,我也可能用new Gold(1e-500)表示我构造了一个金分子之类的。
但总之,他们都是Gold,他们都有oz,可是每个人的oz都不一样,这就是个性

你有Freestyle嘛

我们接下来再看看它的方法想定义些什么(但我现在暂时不定义):

public class Gold
{
    // Properties ...
    
    // Constructor ...

    // Methods
    // GetPrice() 能够获得该金块的具体价格,按金单价*盎司数计算
    // Rise(float price) 提升黄金单价,增加price元
    // Reduce(float price) 降低黄金单价,减少price元
    // ...
}

先看这些函数,很明显我们能看出一些规律:
GetPrice这种函数,它的结果是“个性”的,xau*oz当中oz是“个性”的,这个结果当然也就可能彼此不同。
RiseReduce这两个函数,它们要修改的xau是共性的,而且仅仅牵涉到“共性”,也就是说,它们是能够影响整个黄金国度每一个金子的命运的函数。

嗨,要是我嘛,全都定义成平常的就可以了吧

啊,确实可以,因为非静态的成员能够访问类内所有的其他成员,无所谓静态和非静态。
当然,对于GetPrice来说,这无所谓,而且其结果本身就是“个性”的:

public class Gold
{
    // Properties ...
    
    // Constructor ...

    // Methods
    public float GetPrice() // 能够获得该金块的具体价格,按金单价*盎司数计算
    {
        return xau*oz;
    }
    public void Rise(float price) //提升黄金单价,增加price元
    {
        xau += price;
    }
    public void Reduce(float price) // 降低黄金单价,减少price元
    {
        xau -= price;
    }
    // ...
}

这么写的话当然没有问题,不犯任何语法错误而且编译是通过的,而且你只要实例化一个金子你也确实能够正确地调用这些函数并得到正确的结果。
像这样:

public void main(String args[]) //主函数,在哪个类里就别管了
{
    Gold gNugget = new Gold(0.01f);
    System.out.println(String.format("%.2f", gNugget.GetPrice()));
    gNugget.Rise(10.0f);
    System.out.println(String.format("%.2f", gNugget.GetPrice()));
    gNugget.Reduce(20.0f);
    System.out.println(String.format("%.2f", gNugget.GetPrice()));
}

输出结果:

15.04
15.14
14.94

结果确实没问题

……当然你也可以弄好多个金子这么玩……

只不过……

gNugget.Rise()或者gMountain.Reduce()被调用的时候,所有金子(当然包含它们自身)都是受到影响的。
想像一下:

gNugget.Reduce(1000.0f);

结果一个小小的金粒导致了黄金市场遭受了惨重的金融危机,你不觉得这个小金粒的能耐忒大了点么??
当然,这也就同将Rise()/Reduce()改成public也差不多了。

好吧,是挺危险的,那我不希望这样,该怎么办呢

一个普通的,不是static的函数,意味着它是一种“个性”的行为,这也就是说:

这种行为谁都可以做,想来就来我行我秀u can u up

当然,如果一个函数被声明为static的,那么也就意味着这不是一种“个性”的行为,而是“共性”的行为,这也就是说:

这种行为并不能由某一个体单独执行,必须赌上全种族的命运,以种族的名义,去吧!!!

所以,如果不希望金价被某一个金块干扰的话,RiseReduce也得是static的:

    public static void Rise(float price) //提升黄金单价,增加price元
    {
        xau += price;
    }
    public static void Reduce(float price) // 降低黄金单价,减少price元
    {
        xau -= price;
    }

这样一来,RiseReduce就必须以Gold.Rise()Gold.Reduce()调用,而不能让Gold的实例来调用,也就是说,这一举措会导致下面的语句出错:

gNugget.Rise(10.0f);
gNugget.Reduce(20.0f);

因为这两个函数是“共性”的,想涨价降价你一个小小的gNugget说了不算的,必须是以Gold全体的名义来说:Gold.Rise()

当然,共性行为也会有这样的约束,比如,共性的行为不允许引入个性的内容(书面上来说就是静态方法中不允许调用非静态的属性和方法和内部类),因为如果一个行为牵扯到个性的因素,那么它就不能以这个类全体的名义去做,比如我把GetPrice改成static的就会出错:

    public static float GetPrice() // 不行!!!!
    {
        return xau*oz;
    }

因为并不存在属于Gold全体的oz,每个金子都有自己的oz,所以这个时候调用Gold.GetPrice(),程序自己也不知道该返回谁的oz,所以这样是不可以的。

静态成员说完了,那跟这你遇到的这个问题有什么关系呢??

拿Eclipse编写Java的AWT/Swing程序时,编写了一个public class MainFrame extends JFrame然后在其中放入一个主函数写入了如下内容:

public static void main(String args[])
{
	MainJFrame frmBase = new MainJFrame("Hello Swing");
	frmBase.setBackground(Color.white);
	frmBase.setVisible(true);
	frmBase.addWindowListener(new MainJWindowListener()); // <--- Error occured here !!!
}

也就是我在主函数创建了窗口之后想要继续在主函数里为frmBase这个JFrame窗口添加窗口侦听器(class MainJWindowListener implements WindowListener),其中MainJWindowListener作为MainFrame内类
然后错误就发生了……

请注意画线部分!!!

Java里面main函数强制具备这些修饰:public static void,也就是说main理所应当的是个静态成员。
但是,请注意,main里面试图引入了MainJWindowListener这个内类并试图调用构造函数进行构造,可是,MainJWindowListener只是个缺省访问级别的普通动态内类,这就是问题所在了。

那该怎么解决呢??

这里基于使用了Swing这个事实,解决方案有两种,其中一种是推荐的,而另外一种是通用的。

推荐的方法是用于根治在Swing下发生这种问题的一种重构代码的思路,这种方法规避了在静态成员中进行动态内类(这里面动态内类主要指事件侦听器)的引用。

通用的方法是针对我们遇到的这个问题,通过修改代码使得静态成员能够调用动态内类,这里时刻注意一点,内类因为不是静态的,这意味着内类的构造必须在一个外部类(一个非静态类)实例的范畴下进行。

推荐的方法

考虑到我这里的情景,我是要给窗口注册一下窗口关闭的动作,也就是为关闭按钮添加事件侦听器。
但是实际情况是,基本上只要是这个MainJFrame窗口,我都希望它注册这个关闭按钮的事件侦听器。所以这个过程完全可以不在主函数里进行。

而一个非静态类,无论长成什么样子,其构造函数永远也不会是static,所以事件侦听的注册完全可以放到构造函数里去做

public MainJFrame(String caption)
{
    super(caption);
    //....Some initial actions...//
    this.addWindowListener(new MainJWindowListener());
}

然后main函数只需要负责构造之就可以了:

public static void main(String args[])
{
    //..Others..//
    MainJFrame frmBase = new MainJFrame();
    //..Others..//
}

这是个非常一劳永逸且合乎逻辑的做法,因为就算有多个MainJFrame,我们一般都会希望对用户行为的反馈是一致的,也就是说实际上所有的MainJFrame都会被配备相同的事件侦听器,既然如此的话放到构造函数统一随着构造的时候执行是再好不过的了。

通用的方法

推荐的方法里面规避了在静态成员中调用这个内部类,转到一个非静态成员中进行,而且事实上证明“效果拔群”

但是说到底“效果拔群”也只是对这类情形“拔群”,假使我们非要这么做,比如说我非要在多个MainJFrame做不同的事件侦听处理,那就不应该写在构造函数当中,因为这不是统一的构造流程,没法写进一个函数里。

也就是说,我们非得要在main函数里做侦听绑定,因为main函数强制具有static性质,而MainJWindowListener不是static的所以像这样是不可以的:

public static void main(String args[])
{
	MainJFrame frmBase = new MainJFrame("Hello Swing");
	frmBase.setBackground(Color.white);
	frmBase.setVisible(true);
    
     // 不行!!!MainJWindowListener不是静态的!!!
	frmBase.addWindowListener(new MainJWindowListener()); 
}

也就是说,MainJWindowListener必须跟着一个MainJFrame的实例里面才可以构造,那么我们非要找一个MainJFrame实例mjfinstance不可,然后再new mjfinstance.MainJWindowListener()……

……巧了,这不是有么??frmBase不就是一个MainJFrame么??所以把这一句改成:

frmBase.addWindowListener(new frmBase.MainJWindowListener());

就可以了。

posted @ 2019-10-07 22:57  Oberon  阅读(1538)  评论(0编辑  收藏  举报