谈一谈Java中的“静态”

首先,我们需要用到我在博客:面向对象的内存分析 用到的一张关键的分析图

一、静态方法

1.静态方法与非静态方法的区别

我们首先看一看一个普通的类的调用:

public class Song {
    String title;
    public Song(String title) {
        this.title = title;
    }
    public void play(){
        SoundPlayer player = new SoundPlayer();
        player.playSound(title);
    }
}

很显然,一般情况下,我们调用另一个类,首先就要创建这个类的实例,然后以此调用类的方法。但是 事实上,Java有一些方法如实用方法(如Math),则不需要类的实例!而Java中,static这个关键词,可以标记出不需要实例的方法。这就是staic的作用。而static修饰的方法或者是变量就叫做静态方法或是静态变量。

我们可以系一行代码作为示例,看一下Math如果被实例化会出现什么情况:

错误提示:Math的构造函数被标记为私有的,因此,我们在使用Math类中的方法时,无法对其进行实例化我们可以去看一下它的部分源码:

public final class Math {

    /**
     * Don't let anyone instantiate this class.
     */
    private Math() {}

    /**
     * The {@code double} value that is closer than any other to
     * <i>e</i>, the base of the natural logarithms.
     */
    public static final double E = 2.7182818284590452354;

    /**
     * The {@code double} value that is closer than any other to
     * <i>pi</i>, the ratio of the circumference of a circle to its
     * diameter.
     */
    public static final double PI = 3.14159265358979323846;

所以说,我们无需对Math进行实例化,用到的只有它的类的本身。对于Math类中的方法,都是被static修饰的方法,这些方法我们称之为静态方法。也很容易看到,静态方法中不存在对象的实例化,而是对类名字的直接引用。

是不是所有的类只要拥有一个或者是多个静态方法的类就不能被初始化?

当然不是!我们知道main()方法本身就是静态的方法,只要有main的方法的类就算有静态方法的类,但是我们同样可以在这个类中对其他类进行实例化,因此我们可以在类中任意组合静态与非静态的方法,然而任何非静态的方法都必须一实例化来操作。取得对象的方法只有new()或者序列化

2.静态方法不能调用非静态的变量或方法

通过上文,我们清楚了:静态方法是通过类名称来调用的,所以说静态的方法无法引用到该类的任何实例变量。示例如下:

编译器告诉我们:我不知道你说的是哪个实例变量!  也就是说静态方法是不知道堆上有哪些实例的。

那调用方法呢?同样是不被允许的!

静态方法无法看到实例变量的状态!

二、静态变量

1.静态变量的初始化

静态变量是在类的任何对象创建之前就完成初始化。

静态变量会在该类的任何静态方法执行之前就初始化。

静态变量不赋值时,默认初始化为0;

class Player {
    static int playerCount = 0;
    private String name;

    public Player(String name){
        this.name=name;
        playerCount++;
    }
}
public class PlayerTestDrive{
    public static void main(String[] args) {
        System.out.println(Player.playerCount);
        Player player = new Player("Tiger Woods");
        System.out.println(Player.playerCount);
    }
}

输出:

2.静态变量的作用

如果我们需要计算一个对象被实例化建立出来的次数?我们是不是可以利用构造函数进行累加计算?

public class Duck {
    int duckCount;
    public Duck() {
       duckCount++;
    }
}

显然这是不行的,因为duckCount是一个实例变量,所以这样做是不会成功的。每个Duck在被初始化的时候,duckCount的值都是0;这里我们有一种简单的方法,就是用static去修饰,这就是静态变量作用:被同类所有实例共享的变量

public class Duck {
    private int size;
    //静态的duckCount只会在第一次载入时被初始化
    private static int duckCount = 0;
    //当构造函数被执行,duckCount就会递增
    public Duck(){
        duckCount++;
    }
    public void setSize(int s){
        size=s;
    }
    public int getSize(){
        return size;
    }
    public static void main(String[] args) {
        Duck duck = new Duck();
        //每个Duck对象都有自己的size变量,但是所有的Duck实例只有一份duckCount变量
        duck.setSize(2);
        System.out.println("duckCount: "+duckCount+" duckSize: "+duck.getSize());
        duck.setSize(3);
        System.out.println("duckCount: "+duckCount+" duckSize: "+duck.getSize());
        //每创建一次对象就会重新载入构造函数,duckCount就会递增
        Duck duck1 = new Duck();
        System.out.println("duckCount: "+duckCount);
    }
}

 输出:

1.静态变量在内存中只有一份拷贝,JVM只为其分配一次内存

2.对于实例变量,每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响

3.静态变量是共享的,同一类所有的实例共享一份静态变量。如果不写递增方法,所有被实例的对象的duckCount都是0;

4.实例变量每个实例一个;静态变量每个类一个;

三、final变量

一个被标记final的变量代表该变量一旦被初始化就不会改动,也就是说类加载之后的静态final就不会再改变,因此,在使用final修饰变量时,变量必须被初始化,不然会报错。

1.final的初始化

(1)声明的时候:(final声明变量时,一般变量名要大写)

public class testFinal {
    private static final double COUNT=10;
}

(2)在静态初始化程序中:

public class testFinal {
    private static final double COUNT;
    static {
        COUNT = (double) Math.random();
    }
}

2.final其他用法

fina也可以修饰非静态变量,同样地,代表该值不能改变

final也可以修饰方法,代表该方法不能被覆盖掉

final也可以修饰类,代表该类不能被继承

四、静态的import

这种方法只能减轻少数代码量,同时也会增加代码的阅读难度,一般情况下不推荐使用

常规:

import java.lang.Math;
public class testImport {
    public static void main(String[] args){
        System.out.println("sqrt"+Math.sqrt(2.56));
        System.out.println("tan"+Math.tan(60));
    }
}

静态import

import static java.lang.Math.*;
import static java.lang.System.out;
public class testImport {
    public static void main(String[] args){
        out.println("sqrt"+sqrt(2.56));
        out.println("tan"+tan(60));
    }
}

事实上第二分段代码敲得更多些,说是节省代码量,仅仅是多次重读调用该包的时候

发布了43 篇原创文章 · 获赞 80 · 访问量 8736

猜你喜欢

转载自blog.csdn.net/qq_44717317/article/details/103110730