一文看懂java-static关键字

2018年7月6日
java多线程
2018年7月3日
[springmvc.xml] cannot be opened
2018年7月13日

一文看懂java-static关键字

带有静态方法的类通常是不打算要被初始化的。我们都知道抽象类是不能被初始化的。但当你想让一些非抽象类也不能被初始化,你可以使用私有的构造函数加以限制。Math类就是这样防止自己被初始化的。它让构造函数标记为私有,所以你无法创建Math的实例。

但这并不能说有静态方法的类就不能被初始化了。事实上,只要有main()的类都算是有静态的方法的!因此你可以随意在类中组合静态和非静态方法。

静态的方法不能调用非静态的变量和方法。

比如下面的代码:

public class StaticTest {
  int i=0;
  public int geti(){
    return i;
  }
  public static void main(String[] args) {
    System.out.println(i);//错,
    int o=geti();//错,
  }
}

 

静态变量被同类的所有实例共享。

我们来看下面这个例子:

public class StaticTest {
  int i=0;
  public int geti(){
    return i;
  }
  public static void main(String[] args) {
    Song song01=new Song("lalala");
    Song song02=new Song("hahaha");
    Song song03=new Song("wawawa");
    System.out.println(song01.toString()+"\n"+song02.toString()+"\n"+song03.toString());
  }
}
 class  Song {
  private String name;
  private int num=0;
  Song(String name) {
    this.name=name;
    num++;
  }
  @Override
  public String toString() {
    return name+" "+num;
  }
}

 

输出结果为:

lalala 1

hahaha 1

wawawa 1

我们可以发现song01、song02、song03各自拥有一个name,但这些实例只有一个num变量。所以说,静态变量是共享的。同一类的所有实例共享一份静态变量。

静态变量在类被加载时就会被初始化。类似下面的代码:

public class StaticTest {
  public static void main(String[] args) {
    //静态变量会在楼类的任何对象刨应立前就完成初始化
    //份态变量会在民类的任何 静态方法执行之前就初始化
    System.out.println(Song.num);
    Song song01=new Song("lalala");
    System.out.println(Song.num);
  }
}
 class  Song {
  private String name;
  public static int num=0;//song类被载入时num被初始化为0
  Song(String name) {
    this.name=name;
    num++;
  }
}

 

程序运行后输出:

0

1

类属性中被static所引用的变量,会被作为GC的root根节点。作为根节点就意味着,这一类变量是基本上不会被回收的。因此,static很容易引入内存泄漏的风险。

当一个面试官让你解释static关键字,如果你说static可以修饰属性、方法和内部类,以及修饰之后又有什么效果的话,那么面试官基本上不会记住你这个回答,整个印象就是普通。

但是如果你说完以后,补充一下说道,你曾经遇到过一个内存泄漏的问题,就是因为static修饰的一个Map类型的变量导致的,最后排查了堆栈信息找到了问题的所在,并且解决了这个问题。那么,面试官这个时候内心中对你的印象,就会不自然的提升几分。

对于static,更深入的理解是,static会将所引用的属性、方法以及内部类,与类直接产生引用关系,而非与类的实例。这就是为什么,你可以使用类名.属性、类名.方法以及类名.内部类名,来直接引用一个被static所修饰的属性、方法或者内部类。如果你没有用static修饰,那么你就必须使用实例才能引用这些方法、属性或者是内部类,最典型的就是内部类。

我们来看下下面这段简短的代码:

public class StaticTest {

  public static void main(String[] args) {
      Song.get();
      //Song.set();
      Song song=new Song();
      song.set("lili");
  }
}
 class Song {
  private String name;
  public void set(String name){
    this.name=name;
  }
  public static void get(){
    System.out.println("name");
  }
}

 

我们定义了一个Song类,并在其中定义了一个非静态方法set()和一个静态方法get(),当我们想要执行static修饰的静态方法的时候我们可以直接使用[类.方法名]来调用这个方法,而当我们想要调用set()方法时,我们必须要new一个Song的实例,然后用[实例名.方法名]来调用这个方法,这就是static的基本作用。

 

让我们简单修改下代码:

public class StaticTest {

  public static void main(String[] args) {
      Song.get();
      //Song.set();
      Song song=new Song();
      song.set("lili");
      
      Song.Singer singer=new Song().new Singer();
      singer.set("fengfeng");
  }
}
 class Song {
  private String name;
  public void set(String name){
    this.name=name;
  }
  public static void get(){
    System.out.println("name");
  }
  class Singer{
    private String singername;
    public void set(String singername){
      this.singername=singername;
    }
  }
}

 

相信很多程序员都好奇过,为什么一个没有被static修饰的内部类,必须要这么声明:

OutterClass.InnerClass innerClass = new OutterClass().new InnerClass();

也就是我们上面程序的:

Song.Singer singer=new Song().new Singer();

解释一下,这是因为你没有使用static修饰InnerClass,所以你必须new出来一个OutterClass的实例,才能在此基础上new出内部类的实例,因为内部类只能通过外部类的实例才能引用。如果你使用了static修饰,那么你就可以这样使用内部类。

OutterClass.StaticInnerClass staticInnerClass = new OutterClass.StaticInnerClass();

类似这样:

public class StaticTest {

  public static void main(String[] args) {
      Song.get();
      //Song.set();
      Song song=new Song();
      song.set("lili");
      
      //Song.Singer singer=new Song().new Singer();
      //singer.set("fengfeng");
      Song.Singer singer=new Song.Singer();
  }
}
 class  Song {
  private String name;
  public void set(String name){
    this.name=name;
  }
  public static void get(){
    System.out.println("name");
  }
  static class Singer{
    private String singername;
    public void setSinger(String singername){
      this.singername=singername;
    }
  }
}

 

这两种方式最大的区别就是,第一种方式,如果你想要获得InnerClass的实例,你必须有一个OutterClass的实例,

其实这种方式你创建了两个实例,所以有两个new关键字。而第二种方式就好理解一些,静态内部类不依赖于外部类的实例存在,因此只需要直接创建内部类的实例就可以了,所以只有一个new关键字。

 

带有静态方法的类通常是不打算要被初始化的。我们都知道抽象类是不能被初始化的。但当你想让一些非抽象类也不能被初始化,你可以使用私有的构造函数加以限制。Math类就是这样防止自己被初始化的。它让构造函数标记为私有,所以你无法创建Math的实例。

但这并不能说有静态方法的类就不能被初始化了。事实上,只要有main()的类都算是有静态的方法的!因此你可以随意在类中组合静态和非静态方法。

静态的方法不能调用非静态的变量和方法。

发表评论

电子邮件地址不会被公开。 必填项已用*标注