Java技术——Java中的static关键字解析

0. 前言  

staticJava中的重要的一个点。也是面试的时候经常被问到的点,如果理解不够很容易给面试官语言基础不扎实的印象。本文从static方法、static内部类、static变量、以及static代码块四个角度分别解析static关键字。转载请注明出处为SEU_Calvin的博客

 

1.  static方法

Java编程思想》里有这么一句话——“在static方法中不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象时,通过类本身来调用static方法。这实际上正是static方法的主要用途。”

 

静态方法不依赖于任何对象、仅通过类名就可以进行访问,当然前提是类被加载。而非静态方法依赖具体的对象才能够被调用。所以静态方法中不能访问类的非静态成员方法/变量如果通过类名调用静态方法,而该方法内部有非静态变量,此时对象都还没有创建,就会产生错误)。

当然反过来,在非静态成员方法中是可以访问静态成员方法/变量的

 

我们最常见的static方法就是main方法,另外还有,即使没有显示地声明为static类的构造器实际上也是静态方法。

还有就是需要注意,如果你没必要访问对象外部,那么就把你的方法成为静态方法,因为它会比实例方法更快的调用。

(实例方法为了实现多态需维护一个虚拟函数导向表)

 

2.  static内部类

这里主要总结一下两者的区别,顺便提出在使用static内部类时需要注意的一些性质:

1静态内部类不需要有外部类的引用,但非静态内部类需要持有对外部类的引用。这也是很多非静态内部类经常默认Android Activity外部类的引用,从而间接导致内存泄漏的原因。

2)非静态内部类能够访问外部类的静态和非静态成员,而静态类不能访问外部类的非静态成员,它只能访问外部类的静态成员。这一点和上面static方法的性质类似。

 

3.  static变量

静态变量被所有的对象所共享,在内存中只有一个副本,仅在类初次加载时会被初始化。

而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。

(静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问。)

 

需要注意的是,不论是static方法还是static变量,通过类名直接调用时,也会判断该方法/变量是否被修饰为private,如果是,仍然是无法获取到的,这说明static关键字无法改变成员的访问权限

 

4.  static代码块

首先看看下面程序会输出什么呢?

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

虽然在main方法中没有任何语句,但是还是会输出"test static 1""test static 2"static块可以置于类中的任何地方,只要不是方法内部,类中也可以有多个static块。

在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

根据只会执行一次的特性,静态代码块可以用以优化程序性能。实例如下:

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

这个实例用于判断该Person是否是90后孤寡老人。每次isBirthdaySuitable()被调用的时候,都会生成startDateendDate两个对象,造成了空间浪费,使用static静态块优化如下:

class Person{
    private Date birthDate;
    private static Date startDate,endDate;
    //一次性的初始化操作放在static代码块中进行
    static{
        startDate = Date.valueOf("1990");
        endDate = Date.valueOf("1999");
    }
    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }
    boolean isBirthdaySuitable () {
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) <=0;
    }
}

5.  static代码块的执行顺序

先看看下面程序会输出什么?

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");
    }
}

输出结果为:

我们来分析一下这段代码的执行过程:

1首先执行Test类中的static

2)接着执行main函数中的newMyClass(),而MyClass类还没有被加载,因此需要加载MyClass

在加载MyClass类的时候,发现MyClass类继承自Test类,但是由于Test类已经被加载过了,接着执行MyClass类的中的static

3在加载完之后,就通过构造器来生成对象。而在生成对象的时候,必须先初始化父类的成员变量,因此会执行Test中的Person person = new Person(),而Person类还没有被加载,因此会先加载Person类并执行Person类中的static块,接着执行父类的构造器,完成了父类的初始化

4)最后初始化MyClass,因此会先接着执行MyClass中的Person person = new Person()最后执行MyClass的构造器

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值