美文网首页
Java 为什么不让写 abstract static 方法

Java 为什么不让写 abstract static 方法

作者: zhzhzoo | 来源:发表于2014-08-08 01:39 被阅读2080次

最近在家里修习 Java 这项技能,Java 是面向对象的,是有丰富动态类型信息的,是到处都用引用的。这让我这个从来都用静态 C/C++ 的家伙很不适应呢。

嗯比如有一个问题,为什么 Java 里定义方法,abstractstatic 不能写在一起呢?想想看,人类是会有这么写的需求的,我想写个基类,它规定子类有哪些静态方法,多正常呀。

这个问题嘛,持 OO 思想者也许会解释说两个词矛盾,abstract 的意思是抽象,就是说这个方法是一些东西共有的,但每个东西不一样需要具体实现(刚学一个词儿叫 manifestation),至于 static 我也不知道 OO 应该怎么解释这个词儿。好吧这个“持 OO 思想者说”是我编的。
也有人说 static 的意思是可以在没有实例的情况下调用,但标记为 abstract 后就没有实现无法调用了,是个矛盾。但是我标记 abstract 本来也没想调用它,只想指出它的子类应该实现这个静态方法,没有矛盾呀。

真正的原因呢,是这么做没有必要。类型这个东西在 int, double, int*, int (int) 这些时候是用来处理底层软硬件的不同工作模式的,但到了 struct, class 什么之后就是为了抽象存在的。抽象可以简化思考并尽可能帮助检查错误。对于 Java,一个人不会凭空想出来 abstractstatic 同时修饰的方法,因为一个 static 方法必须通过类名调用。而如果像我上面说的那样,规定一个类的子类必须有某种 static 方法,那么那个方法也必须至少一次通过子类的类名调用(可以只通过实例调用但是就没有必要声明为 static 了),这样调用时编译器就可以发现该方法不存在,就不需要父类写个 abstract 方法规定子类必须有该方法来预防错误了。

但是我想这么写可不是凭空想出来的,我的想法是有理有据令人信服的。比如说

#include <stdio.h>

struct cat
{
    static void meow()
    {
        printf("meow\n");
    }
};

template <class T>
struct mammal
{
    void lol()
    {
        T::meow();
    }
};

int main()
{
    mammal<cat> KarenJi;
    KarenJi.lol();    // meow

    return 0;
}

嗯说到模版和泛型了。上面是一个用模版的 C++ 程序(好像生产生活中的教材里 mammalcat 会是继承关系,啊这是细节问题不要拍我),它定义了一个有 static 函数的结构体 cat 和一个结构体模版 mammal,其中 mammallol 函数通过模版类型名变量 T 调用了它的静态函数。在编译时刻,C++ 编译器会逐个把每个结构体模版和函数模版中的类型名变量用实际的类型代入,为每种类型生成不同的代码。在代入生成代码的过程中,编译器会检查这个结构体是不是定义了这个静态函数(如果用类型名变量调用了静态函数的话),从而给出错误提示。

但是 Java 对泛型的处理和 C++ 对模版的处理有所不同,在 Java 里这么做有两个问题。Java 的所有类型(除了基本类型)都是对象而且是 Object 的子类,这样 Java 没什么动力去做一个功能特别强的泛型了,毕竟 C++ 无模版就没法泛型,Java 大不了都搞成 Object 完了再动态检查类型。但是这样编译时刻对类型不做任何检查的话,太容易把编译时刻可能查出来的问题推迟到运行时刻才发现了(其实 C 的 void * 也有类似作用和问题,qsort 熟么的),Java 就也只好引入尖括号标记,加入了编译时刻的检查机制。Java 的泛型就是一编译的检查机制,实现起来如下:以 public class mammal<T> 为例,在编译检查后,把所有 T 都换成 Object,这样在泛型类型变量被赋值时对象会先被强制转换成 Object,而一个引用获得泛型类型变量的值时,指向的对象会由 Object 强制转为 T(没有考虑泛型方法和有(多个)界限的情况)。用类型名变量调用静态方法碰到问题都是从这种机制中生出来的。

问题一是这样子 Java 没法做检查,C++ 在编译的过程中有一个把类型代入的过程,Java 没有,Java 没有动机去弄明白这个类有没有这个静态方法。问题二是这样子运行时 Java 也根本没法知道你要调用哪个方法。在 C++ 里静态函数是用函数地址来调用的,类型不同,静态函数地址也不同,但 C++ 用为每个类型单独生成一份代码的方法解决了这个问题。在 Java 里,尽管不用函数地址,也得用 invokestatic 和要调用静态函数的类名,所有类用同样的代码,做不到调用不同的方法。你可以说那只有类静态函数不同,其它代码都一样就不用再生成一份了,我调用时编译器处理一下加个类型信息或者关于静态函数的信息,让这个调用可以调用就好了嘛,嗯有道理不过 Java 没有这么做。Java 的泛型机制没有任何可能传递类型信息,要传类型信息唯一的方法是传与该类有关的实例(该类的实例,有方法返回类型为该类的类的实例,etc)。

这时我就提出标题那个问题啦,我要是写了个父类叫 lolable,让 T extends lolable,我再写下 T.lol() 的时候,问题一不就解决了么?是这个样子的,不过问题二还是没有办法解决。我当时没有想到问题二,所以我想写 abstract static 方法的想法是应该给予理解的。

好了,那么为什么不让写 abstract static 方法这个问题回答好了,那想写怎么办?很简单呀,不要调用静态方法了,new 出个对象来,然后传入实例,调用实例方法就好了。如果你觉得为了调用方法传实例不值,你可以只传类型信息,就是要调用方法的类的 class,然后接收 class 的家伙再去反射调用静态方法,嗯可是用“反射”这么动态的东西去调用一个静态方法违和感满满。
话说回来,为什么非要写静态方法呢?有什么方法是非得没有实例不可的呢?工厂方法呀。既然只有工厂方法,那么调用的时候直接把方法传进去当参数就好了,但是 Java 没有高阶函数(真是汗颜,Java 要啥没啥,传的东西只有 int, double, Object,一看就没有 C/C++ 高大上,C/C++ 尽管不是函数式语言,可好歹还能传函数指针呐),得写个类把方法包起来传进去,就像这个样子:

public class MyParcelable implements Parcelable {
    private int mData;

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(mData);
    }

    public static final Parcelable.Creator<MyParcelable> CREATOR
            = new Parcelable.Creator<MyParcelable>() {
        public MyParcelable createFromParcel(Parcel in) {
            return new MyParcelable(in);
        }

        public MyParcelable[] newArray(int size) {
            return new MyParcelable[size];
        }
    };
   
    private MyParcelable(Parcel in) {
        mData = in.readInt();
    }
}

以及许许多多接收 Creator 的方法。

总之我要绝食静坐上街游行呼吁各方推动 Java 加入高阶函数的进程,你看传函数当参数或返回值就是语法糖嘛不需要做什么复杂的工作。有匿名类和 GC,lambda 表达式也就是个语法糖(尽管我知道有啦,可我还是要吐槽匿名类里提及的变量必须得是 final 的)。

相关文章

网友评论

      本文标题:Java 为什么不让写 abstract static 方法

      本文链接:https://www.haomeiwen.com/subject/fhretttx.html