• java中关键字static和final


    面向对象的不足

    凡是有利必有弊,强对象编程,使得语法简单统一,但也有其缺点,而且有很多。我们在接下来的课程里会一点点接触到。我们今天先看第一个。

    有些变量和函数确实没必要定义在一个类里。强行规定这些函数在类里,反而显得累赘。想一个例子,比如正弦函数sin,常数PI,这些函数或者常量值为什么要定义在类里呢?一定要定义的话,定义在哪个类里合适呢?

    Java的做法是把数学函数封装到一个叫做Math的类里。叹气...一个叫Math的类,太不直观了。再来思考一个问题,如果说,我们这样写

     
    class Math {
        public double sin(double x) {/*code goes here*/}
    }

    那么,我每次要调用sin函数都得写成

    new Math().sin(x)

    因为,我们只能通过Math类型的对象去调用定义在Math类中的函数。这太不科学了。为了调用一个本来可以全局存在的函数,我们却要新建一个对象?!语言的设计者肯定也不会这么傻。于是,他们引入了static这个关键字。

    Static关键字

    当我们把一个函数或者变量加上static限制以后,就可以在不创建一个对象的情况下,直接使用类里的函数或者变量了。

    class Math {
        public static double sin(double x) {/*code goes here*/}
    }
    Math.sin(x)
     

    编程语言的关键字是有它内在的逻辑的,不要去死记硬背,通过上面的分析,我们就能知道static关键字用于修饰变量和函数是不得不这样做,而不是大家闲得慌,去加这么一个关键字故意去难为新手们。

    好了,有了static关键字,世界好像变得合理了一点。但是做为语言设计者,还是不满意,如果我有一个程序,里面会用到大量的数学函数,然后我就看到了满屏幕的Math.xxx。要是能把这个Math去掉就好了。然后设计者们就把主意打到了import那里。我们能不能把这些本来就是全局函数,全局常量的值导入到当前文件中,好像他们本身就没有被封装到类里一样?于是这种语法就出现了:


    import static java.lang.Math.*;
    public class Main {
        public static void main(String args[]) {
            System.out.println(sin(PI / 2));
        }
    }

    好了,通过这种方法,就把Math中的所有static方法和常量都引入到当前文件了,我们再也不用使用Math开头去引用一个静态函数了。

    其实static的语义到这一步就基本说清楚了。如果我是Java的设计者,我不会再为static增加其他用法了。但是不幸的是,Java有一个坏品味,那就是重用关键字。这让一些本来简单的事情又变复杂了。

    static关键字还可以用来定义静态代码块。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

    这种与类加载有关的逻辑,显然应该甩锅给ClassLoader。比如 ClassLoader在加载一个类的时候,调用类中的static onLoad()方法,也比静态代码块这种引起混淆的语法好。这样的设计才会显得清晰。静态代码块是什么鬼!?这显然是一个关键字复用的错误例子。我们没办法改变这种设计,只能去适应。记住这个用法吧。

    static 函数里不能使用this, super?废话!你只要明白了static 函数其实是全局函数,它只是因为Java的强对象的要求,而不得不找个类“挂靠”,它本身与任何类都没有关系。所以在static 方法里,当然不能直接访问某一个类的成员变量和成员函数了。但是呢,一个类让一个static函数挂靠了,总得有点好处吧?要说好处,倒也有一个,那就是类的成员函数调用static方法不用带类名了。

    class Example {
        public static void sayHello() {
            System.out.println("Hello, everybody~");
            // 这个当然不能用。static函数与其挂靠的那个类的对象没有任何关系。
            // static函数是全局唯一的。
            // this.sayBye();
        }   
    
        public void sayBye() {
            System.out.println("Good Bye~");
        }   
    
        public void saySomething() {
            // 唯一的一点好处,大概就是成员函数里这样三种写法都是OK的。
            // 但这个没卵用。我更喜欢Java只保留第三种写法,免得大家误会。
            this.sayHello();
            sayHello();
            Example.sayHello();
            this.sayBye();
        }
    }

    final关键字

    final 用于修饰一个类,那么这个类就不能再被其他类继承。用于修饰一个方法,这个方法不能被覆写。这是非常好的一个东西。可以避免我们造出混乱的继承结构。比如Java中的String类就是final的,它是不能被继承的。 

    // 想创建一个自己的String类是不行的。因为String是final的。
    class MyString extends String {}

    再看修饰method的情况:

    class A { 
        public final void f() {
        }   
    
        // 这里是OK的,只是一次重载
        public final void f(int a) {
        }   
    }
    
    class B extends A { 
        // 会报错,说f是final的,不能覆写
        public void f() {
        }   
    }

    好。到此为止,final是如此地清晰。

    但不幸的是,Java的设计者不知道是出于什么考虑,把final也拿来定义变量。这就让人无语了。这是const关键字做的事情啊。重用关键字决不会让语法变得更简洁。

    对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

    而且,事实证明,Java的final和C++的const还真就是同样的。就连一些容易混淆的地方都原封不动地迁移过来。

    我们挨着看。先看基本用法。

    final int a = 1;
            a += 2; // 会报错,我们不能修改一个final变量

    再看一下容易引起混乱的地方。

    public class Hello {
        public static void main(String args[]) {
            String a = "go die, ";
            final String b = "final";
            String c = "go die, " + b;
            String d = a + b;
            String e = "go die, final";
            System.out.println(e == c); //true,比较两个变量是否指向同一个对象
            System.out.println(e == d); //false
            System.out.println(c.equals(d));//true,比较两个字符串的值是否相同
        }   
    }

    结果可能出乎你的意料。我来解释一下。在编译阶段,变量c其实已经是"go die, final"了,等到我们后面分析Java字节码文件的时候就会看到,c和e是指向了常量池中的同一个字符串,也就是“go die, final"。所以它们其实是同一个对象。但是d却是运行时生成的,并不引用常量池中的"go die, final"这个字符串,所以,e和d并不是同一个对象,虽然它们的值相同。这和C++中编译时const变量转成编译时常量如出一辙。究其根本原因,还是在于b 在编译阶段就已经被当作常量“final” 去做下面的编译了。

    final关键字被用来当做const用,实在不是个好的品味。当然,const这个关键字是保留字,就是说在Java中虽然现在没用,但不保证以后不会用。你是不能拿这个词来当变量名的。

    额外加一句,如果一个变量在整个执行阶段不会被修改,那么加上final进行修饰是一个好的编程习惯。

  • 相关阅读:
    python文件处理(对比和筛选)版本2
    python文件处理(对比和筛选)
    python 从2个文件中提取不相同的内容并输出到第三个文件中
    Unity学习(十三)场景优化之四叉树
    Android权限之三共享UID和签名
    > 软件编程 > 安卓开发 > Unity编译时找不到AndroidSDK的问题:Unable to list target pla
    Unity性能优化(4)-官方教程Optimizing graphics rendering in Unity games翻译
    Unity性能优化(3)-官方教程Optimizing garbage collection in Unity games翻译
    Unity性能优化(2)-官方教程Diagnosing performance problems using the Profiler window翻译
    Unity性能优化(1)-官方教程The Profiler window翻译
  • 原文地址:https://www.cnblogs.com/msymm/p/9395516.html
Copyright © 2020-2023  润新知