java final static

参考android listview 声明ViewHolder内部类时,为什么建议使用static关键字
这个问题也是我每次面试别人必问的问题之一。其实这个是考静态内部类和非静态内部类的主要区别之一。非静态内部类会隐式持有外部类的引用,就像大家经常将自定义的adapter在Activity类里,然后在adapter类里面是可以随意调用外部activity的方法的。当你将内部类定义为static时,你就调用不了外部类的实例方法了,因为这时候静态内部类是不持有外部类的引用的。声明ViewHolder静态内部类,可以将ViewHolder和外部类解引用。大家会说一般ViewHolder都很简单,不定义为static也没事吧。确实如此,但是如果你将它定义为static的,说明你懂这些含义。万一有一天你在这个ViewHolder加入一些复杂逻辑,做了一些耗时工作,那么如果ViewHolder是非静态内部类的话,就很容易出现内存泄露。如果是静态的话,你就不能直接引用外部类,迫使你关注如何避免相互引用。 所以将 ViewHolder内部类 定义为静态的,是一种好习惯.

本文参考
Java中的static关键字解析
Java关键字之final和static

一、final

Java语言里,final关键字有多种用途,其主题都表示“不可变”,但背后的具体内容并不一样。当final关键字用于修饰类时表示该类不允许被继承;当它用于修饰方法时表示该方法在派生类里不允许被覆写(override)。当final关键字用于修饰变量时表示该变量的值不可变;静态变量、实例成员变量、形式参数和局部变量都可以被final修饰。

1.final类
当一个类声明为final类,也就证明这个类是不能够被继承的,即禁止继承,因此final类的成员方法是没有机会被覆盖的,这个final类的功能是完整的。在Java中有很多类是final的,如String、Interger以及其他包装类。
final类的好处:不可变类有很多的好处,它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的开销。
下面是final类的实例:

final class PersonalLoan{
}
 
class CheapPersonalLoan extends PersonalLoan{ //compilation error: cannot inherit from final class
}

2.final方法
如果一个类不允许其子类覆盖某个方法,即不能被重写,则可以把这个方法声明为final方法。(类中所有的private方法都隐式的指定为final)。
使用final方法的原因:

  • 方法锁定,防止任何继承类修改它的含义,确保在继承中使方法行为保持不变且不被覆盖;
  • 效率,将一个方法指明为final,就是同意编译器将针对该方法的所有调用都转化为内嵌调用(相当于在编译的时候已经静态绑定,不需要在运行时再动态绑定)。

下面是final方法的实例:

public class Test1 {
   public static void main(String[] args) { 
   }
   public void f1() { 
      System.out.println("f1"); 
   } 
   //final方法
   public final void f2() { 
      System.out.println("f2"); 
   }
}
public class Test2 extends Test1 { 
   public void f1(){ 
      System.out.println("Test1父类方法f1被覆盖!"); 
   } 
   public static void main(String[] args) { 
      Test2 t=new Test2(); 
      t.f1(); //子类重写父类的方法 
      t.f2(); //调用从父类继承过来的final方法 
   }
}

3.final变量
程序中有些数据的恒定不变是很有必要的,比如:

  • 一个永不改变的编译时常量;
  • 一个在运行时被初始化的值,而在程序的后面不希望它被改变。
    这种类型的变量只能被赋值一次,一旦被赋值之后,就不能够再更改了。

有几点要注意的:

  • 一个既是static又是final的域只占据一段不能改变的存储空间,一般用大写来表示;
  • final使数值恒定不变,而当用于对象时,final使引用恒定不变(一旦引用指向一个对象,就无法再把它改为指向另一个对象);

final变量的好处:

  • 提高性能,JVM和Java应用程序都会缓存final变量;
  • final变量可以在安全的在多线程环境下进行共享,而不需要额外的开销。
public class Test { 
    public static final int PI = 3.14;//这个变量是只读的
    public final int INIT; //final空白,必须在初始化对象的时候赋初值 
    public Test(int x) { 
        INIT = x; 
    } 
    public static void main(String[] args) { 
        Test t = new Test(2); 
        //t.PI=3.1415;//出错,final变量的值一旦给定就无法改变 
        System.out.println(t.INIT); 
    }
}

4.总结

  • 本地变量必须在声明的时候赋值;
  • 在匿名类中所有变量都必须是final变量;
  • final方法不能被重写;
  • final类不能被继承;
  • final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误;
  • 接口中声明的所有变量本身是final的;
  • final方法在编译阶段绑定,称为静态绑定(static binding);
  • 对于集合对象声明为final指的是引用不能被更改,但是你可以向其中增加,删除或者改变内容;
二、static

1.static方法
Math类中有很多计算方法,不需要初始化对象,就能直接调用,这正是静态方法的主要用途。静态方法由于不依赖于任何对象,也就没有this,当然也不能访问类的非静态变量和方法,因为它们需要依赖具体的对象才能调用。
如果一个方法和他所在类的实例对象无关,那么它就应该是静态的,反之他就应该是非静态的。如果我们确实应该使用非静态的方法,但是在创建类时又确实只需要维护一份实例时,就需要用单例模式了。
比如说我们在系统运行时候,就需要加载一些配置和属性,这些配置和属性是一定存在了,又是公共的,同时需要在整个生命周期中都存在,所以只需要一份就行,这个时候如果需要我再需要的时候new一个,再给他分配值,显然是浪费内存并且再赋值没什么意义,所以这个时候我们就需要单例模式或静态方法去维持一份且仅这一份拷贝,但此时这些配置和属性又是通过面向对象的编码方式得到的,我们就应该使用单例模式,或者不是面向对象的,但他本身的属性应该是面对对象的,我们使用静态方法虽然能同样解决问题,但是最好的解决方案也应该是使用单例模式。

2.static类
参考知乎-为什么Java内部类要设计成静态和非静态两种?
从字面上看,一个被称为静态嵌套类,一个被称为内部类。
从字面的角度解释是这样的:
什么是嵌套?嵌套就是我跟你没关系,自己可以完全独立存在,但是我就想借你的壳用一下,来隐藏一下我自己(真TM猥琐)。
什么是内部?内部就是我是你的一部分,我了解你,我知道你的全部,没有你就没有我。(所以内部类对象是以外部类对象存在为前提的)

注意android listview 声明ViewHolder内部类:

    // ViewHolder静态类  
    static class ViewHolder {  
        public ImageView img;  
        public TextView title;  
        public TextView content;  
    } 

参考android listview 声明ViewHolder内部类时,为什么建议使用static关键字
这个问题也是我每次面试别人必问的问题之一。其实这个是考静态内部类和非静态内部类的主要区别之一。非静态内部类会隐式持有外部类的引用,就像大家经常将自定义的adapter在Activity类里,然后在adapter类里面是可以随意调用外部activity的方法的。当你将内部类定义为static时,你就调用不了外部类的实例方法了,因为这时候静态内部类是不持有外部类的引用的。声明ViewHolder静态内部类,可以将ViewHolder和外部类解引用。大家会说一般ViewHolder都很简单,不定义为static也没事吧。确实如此,但是如果你将它定义为static的,说明你懂这些含义。万一有一天你在这个ViewHolder加入一些复杂逻辑,做了一些耗时工作,那么如果ViewHolder是非静态内部类的话,就很容易出现内存泄露。如果是静态的话,你就不能直接引用外部类,迫使你关注如何避免相互引用。 所以将 ViewHolder内部类 定义为静态的,是一种好习惯.

另外,使用使用静态内部类实现单例模式,这种方法也是《Effective Java》上所推荐的

public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){}
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

3.static变量
参考Java中静态变量的适用场景
静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
另外,注意在C/C++中static是可以作用域局部变量的,但是在Java中切记:static是不允许用来修饰局部变量。不要问为什么,这是Java语法的规定。

public class WeekB{

      static class Data { 

             private int week;
             private String name;
             Data(int i, String s) {
                   week= i;
                   name = s;
            }

      }

      static Data weeks[] = {
             new Data(1, "Monday"),
             new Data(2, "Tuesay"),
             new Data(3, "Wednesday"),
             new Data(4, "Thursday"),
             new Data(5, "Friday"),
             new Data(6, "Saturday"),
             new Data(7, "Sunday")
      };

      public static void main(String args[]) {

             final int N = 10000;
             WeekB weekinstance;
             for (int i = 1; i <= N; i++){
                   weekinstance = new WeekB ();
             }
     }

}

在类WeekB中,在Data weeks[]之前添加了static关键字,将该对象变量声明为静态的,因此当你创建10000个WeekB对象时系统中只保存着该对象的一份拷贝,而且该类的所有对象实例共享这份拷贝,这无疑节约了大量的不必要的内存开销.
那么是不是我们应该尽量地多使用静态变量呢?其实不是这样的,因为静态变量生命周期较长,而且不易被系统回收,因此如果不能合理地使用静态变量,就会适得其反,造成大量的内存浪费,所谓过犹不及。因此,建议在具备下列全部条件的情况下,尽量使用静态变量:
(1)变量所包含的对象体积较大,占用内存较多。
(2)变量所包含的对象生命周期较长。
(3)变量所包含的对象数据稳定。
(4)该类的对象实例有对该变量所包含的对象的共享需求。
如果变量不具备上述特点建议你不要轻易地使用静态变量,以免弄巧成拙。

4.static代码块
static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

class Person{
    private Date birthDate;
     
    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }
     
    boolean isBornBoomer() {
        Date startDate = Date.valueOf("1946");
        Date endDate = Date.valueOf("1964");
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}

isBornBoomer是用来这个人是否是1946-1964年出生的,而每次isBornBoomer被调用的时候,都会生成startDate和birthDate两个对象,造成了空间浪费,如果改成这样效率会更好:

class Person{
    private Date birthDate;
    private static Date startDate,endDate;
    static{
        startDate = Date.valueOf("1946");
        endDate = Date.valueOf("1964");
    }
     
    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }
     
    boolean isBornBoomer() {
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}

因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。

三、笔试题

1.下面这段代码的输出结果是什么?

public class Test extends Base{
 
    static{
        System.out.println("test static");
    }
     
    public Test(){
        System.out.println("test constructor");
    }
     
    public static void main(String[] args) {
        new Test();
    }
}
 
class Base{
     
    static{
        System.out.println("base static");
    }
     
    public Base(){
        System.out.println("base constructor");
    }
}
base static
test static
base constructor
test constructor

在执行开始,先要寻找到main方法,因为main方法是程序的入口,但是在执行main方法之前,必须先加载Test类,而在加载Test类的时候发现Test类继承自Base类,因此会转去先加载Base类,在加载Base类的时候,发现有static块,便执行了static块。在Base类加载完成之后,便继续加载Test类,然后发现Test类中也有static块,便执行static块。在加载完所需的类之后,便开始执行main方法。在main方法中执行new Test()的时候会先调用父类的构造器,然后再调用自身的构造器。
2.这段代码的输出结果是什么?

public class Test {
    Person person = new Person("Test");
    static{
        System.out.println("test static");
    }
     
    public Test() {
        System.out.println("test constructor");
    }
     
    public static void main(String[] args) {
        new MyClass();
    }
}
 
class Person{
    static{
        System.out.println("person static");
    }
    public Person(String str) {
        System.out.println("person "+str);
    }
}
 
 
class MyClass extends Test {
    Person person = new Person("MyClass");
    static{
        System.out.println("myclass static");
    }
     
    public MyClass() {
        System.out.println("myclass constructor");
    }
}
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor

首先加载Test类,因此会执行Test类中的static块。接着执行new MyClass(),而MyClass类还没有被加载,因此需要加载MyClass类。在加载MyClass类的时候,发现MyClass类继承自Test类,但是由于Test类已经被加载了,所以只需要加载MyClass类,那么就会执行MyClass类的中的static块。在加载完之后,就通过构造器来生成对象。而在生成对象的时候,必须先初始化父类的成员变量,因此会执行Test中的Person person = new Person(),而Person类还没有被加载过,因此会先加载Person类并执行Person类中的static块,接着执行父类的构造器,完成了父类的初始化,然后就来初始化自身了,因此会接着执行MyClass中的Person person = new Person(),最后执行MyClass的构造器。
3.这段代码的输出结果是什么?

public class Test {
     
    static{
        System.out.println("test static 1");
    }
    public static void main(String[] args) {
         
    }
     
    static{
        System.out.println("test static 2");
    }
}
test static 1
test static 2

虽然在main方法中没有任何语句,但是还是会输出,原因上面已经讲述过了。另外,static块可以出现类中的任何地方(只要不是方法内部,记住,任何方法内部都不行),并且执行是按照static块的顺序执行的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 问:谈谈 Java 中 final、finally、finalize 的区别? 答: final 是一个修饰符,如...
    Little丶Jerry阅读 122评论 0 0
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,577评论 18 399
  • 一:java概述:1,JDK:Java Development Kit,java的开发和运行环境,java的开发工...
    ZaneInTheSun阅读 2,629评论 0 11
  • Java关键字final 在设计程序时,出于效率或者设计的原因,有时候希望某些数据是不可改变的。这时候可以使用fi...
    狮_子歌歌阅读 734评论 1 4
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 7,066评论 0 62