写此篇的缘由是由于在一次定义全局变量时,没有意识的去为其添加了static修饰符,以前对于static的理解仅仅停留在“类名.静态方法/变量”的调用上,那么为什么可以如此调用呢,今天就来深入学习一下这个特别的修饰符。
目录
-
介绍
首先一句话概括:方便在没有创建对象的情况下来进行调用
类名.静态方法
类名.静态变量
而其核心原因是:静态在类定义的时候已经被装载和分配,并占用内存,而非静态只有在创建对象后才会被分配内存,其原因与内存相关,内存方面之后会推出详细的推文暂时不做介绍。
-
静态变量与非静态变量的区别
静态变量 | 非静态变量 | |
所属 | 属于类 | 属于对象 |
内存分配时间 | 类加载时 | 对象创建时 |
调用方式 | 类名调用、对象调用 | 对象调用 |
生命周期 | 类加载-类消失 | 对象创建-对象被回收 |
别名 | 类变量 | 实例变量 |
数据存储位置 | 方法区(共享数据区) 对象的共享数据 | 堆内存的对象中 对象的特有数据 |
-
静态使用时的注意事项
-
静态方法只能访问静态成员(非静态则都可以访问)
-
静态方法中不可以使用this或super关键字
-
Static的四大应用场景
-
修饰成员变量和成员方法
-
静态代码块
-
静态成员类
-
静态导包(用来到入类中的静态资源,1.5后的新特性)
一、修饰成员变量和成员方法
被static修饰的成员属于类,不属于单个这个类的某个对象,被类中的所有对象共有。
-
修饰成员变量——静态变量
被static修饰的变量成为静态变量,其所属于类,被所有对象共享,当且仅当在类初次加载时会被初始化。
Static String TAG="...";
代码举例如下:
先创建一个Test类
public class Test {
static int mAge;//将年龄被static修饰符修饰
String mName; //将名字只设为普通的全局变量
Test(String name,int age){ //创建一个有参的构造函数作为一个对象
mName=name;
mAge=age;
}
Test(){} //创造一个无参的构造函数作为另一个对象
public String getName() {
return mName;
}
public int getAge() {
return mAge;
}
public void setName(String name) {
mName=name;
}
public void setAge(int age) {
mAge=age;
}
}
再由主函数去调用这个Test类
public class Main {
public static void main(String[] args) {
//为无参test对象设置姓名和年龄
Test test=new Test();
test.setAge(15);
test.setName("Tom");
//为有参test对象设置姓名和年龄
Test test1=new Test("Amy",6);
//输出两个对象的姓名和年龄
System.out.println("name:"+test.getName()+" age:"+test.getAge());
System.out.println("name:"+test1.getName()+" age:"+test1.getAge());
}
}
输出可以看到虽然年龄对于不同对象设置的不同,但获取到的年龄是相同的
name:Tom age:6
name:Amy age:6
这就是static修饰的主要作用,这个变量所属于类,对于不同对象获取到的值都是相同的。
-
常见的静态变量static final
static final int MESSAGE_AGE=1;
常见用途:为了别的类可以直接使用这个类的这个常量,final一般用来修饰不能再去修改的常量。若只被static修饰其他类就可以直接去使用并修改从而让这个类中所有对象获取此变量的值都发生变化。
-
修饰成员方法——静态方法
同样独立于对象的存在,直接通过类名调用,但要强调的是静态方法中只能使用静态变量和静态方法,原因是由于非静态成员变量/方法都必须要依赖于对象才能被调用(静态在类创建时已经被创建则已经占用内存,而非静态需要创建对象来实例化从而创建新内存),所以非静态可以使用静态或非静态。其结构如下:
Static void test(){}
代码举例如下:
同样创建一个Test类
public class Test {
static int mAge;//静态方法只能获取到静态变量
String mName="Lydia";//给全局变量赋值
Test(String name,int age){
mName=name;
mAge=age;
}
Test(){}
public String getName() {
return mName;
}
public int getAge() {
return mAge;
}
public void setName(String name) {
mName=name;
}
static void setAge(int age) { //创建一个静态方法,只有它能获取静态变量
mAge=age;
}
}
再用主函数去调用这个类
public classMmain {
public static void main(String[] args) {
// TODO Auto-generated method stub
Test test=new Test();//创建该对象为了去调用方法
Test test1=new Test("Amy",6);
Test.setAge(15);//调用静态方法去修改静态变量
System.out.println("name:"+test.getName()+" age:"+test.getAge());
System.out.println("name:"+test1.getName()+" age:"+test1.getAge());
}
}
此时输出可以看到test对象获取到的是Test类中定义的全局变量,而test1对象则是重新赋值,但Test.setAge直接调用静态方法却将静态变量统一修改
name:Lydia age:15
name:Amy age:15
常见用途:静态方法主要是用于获取或使用其他静态变量/方法,常用于工具类如Math.java中有很多静态工具方法。
二、静态代码块
静态代码块定义在类中方法外,在非静态代码块之前执行(静态代码块>非静态代码块>构造方法)当有多个时,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。其结构如下
static{
doSomething
}
注意静态代码块只会执行一次,代码举例如下:
创建Test类
public class Test {
static int mAge;
String mName="Lydia";
static {
System.out.print("这是静态代码块-->");
}
Test(){
System.out.print("这是构造方法-->");
}
{
System.out.print("这是非静态代码块-->");
}
public void test() {
System.out.println("这是普通方法中的代码块-->");
}
}
用主函数去调用这个类,为了直观感受静态代码块只会执行一次,那么来创建两次对象
public class main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Test test=new Test();//
test.test();
Test test1=new Test();
test1.test();
}
}
输出,可以看到静态代码块虽然创建了两次对像但却只执行了一次,而非静态代码块会每次都执行,但却在构造方法之前,而普通方法只会在调用时执行
这是静态代码块-->这是非静态代码块-->这是构造方法-->这是普通方法中的代码块-->
这是非静态代码块-->这是构造方法-->这是普通方法中的代码块-->
常见用途:初始化一些参数,启动时且只需要执行一次,提前加载又不需要多次加载的东西,所以它具有唯一性,在代码设计中常用来创建可以单例的对象,不用每次调用一个相同的对象都要重新new,因此来提高性能。
-
执行顺序
没有父类
1)类的静态属性
2)类的静态代码块
3)类的非静态属性
4)类的非静态代码块
5)构造方法
有父类的情况
1)父类的静态属性
2)父类的静态代码块
3)子类的静态属性
4)子类的静态代码块
5)父类的非静态属性
6)父类的非静态代码块
7)父类构造方法
8)子类非静态属性
9)子类非静态代码块
10)子类构造方法
-
静态代码块与静态方法和非静态代码块的区别
它与静态方法的区别就在于它是自动执行的,而静态方法则是被调用的时候执行的,而它与非静态代码块的区别在于它在它之前执行,并且非静态代码块会在每次对象创建时执行而静态代码块所属于类所以只会在第一次类加载时执行一次,同时非静态代码块可以在普通方法中定义,而静态代码块不行。
三、静态内部类
Java中静态类只有静态内部类,顶级类不能用static修饰,静态内部类属于外部类,而不属于外部类的对象,特点在于只能访问外部类的静态成员变量和静态方法,其结构如下
public class Outter {
static class Inner {
}
}
常见用途:作为共有类的辅助类,当它和外部类一起使用才有意义,举例如Android中VibrationEffect有三种振动形式OneShot、Waveform、Prebaked、而这三种振动形式就是作为静态成员类使用,VibrationEffect.OneShot等来引用这些振动形式。
public abstract class VibrationEffect implements Parcelable {
...
public static class OneShot extends VibrationEffect implements Parcelable {
...
}
public static class Waveform extends VibrationEffect implements Parcelable {
...
}
}
-
嵌套类
静态成员类(static member class)、非静态成员类(nonstatic member class)、匿名类(anonymous class)和局部类(local class) 并称为嵌套类(nested class),是指被定义在另一个类的内部类,如果嵌套类将来可能会被用于其他的某个环境中,它就应该是顶层类(top-level class),静态成员类是最简单的一种嵌套类,最好看作普通类,只是碰巧声明在另一个内部而已。
-
为什么要优先考虑静态内部类
如果嵌套类的实例可以在它的外围类的实例之外独立存在,这个嵌套类就必须是静态成员类,其他内部类必须在外围类创建实例后才能使用。所以如果声明的成员类不需要访问外围实例,就始终要把static修饰符放在它的声明中,使它成为静态成员类,而不是非静态成员类,如果省略了static修饰符,则每个实例都将包含一个额外的指向外围对象的引用。保存这份引用要消耗时间和空间,并且会导致外围实例在符合垃圾回收时却依然保留。所以在使用内部类时尽量优先考虑静态成员类。
四、静态导包
使用较少,但使用很简单,调用方法时会更方便,如常见的Log打印
创建一个要调用的PrintHelper.java文件
/*PrintHelper.java文件*/
package com.example.test;
public class PrintHelper{
public static void print(Object o){
System.out.println(o);
}
}
随后在导入该文件并直接调用print方法
/*Test.java文件*/
import static com.example.test.PrintHelper.*;
public class Test{
public static void main(String[] args){
print("Hello World!");
}
}
输出
Hello World!
上述代码来自于两个java文件,而PrintHelper很简单,只是包含了一个用于打印的static方法,随后导入到Test.java中无需再通过“类名.方法名”去调用,直接采用“方法名”就可以调用,仿佛在用自己的方法一样。
此内容均来自本小编公众号,此文章可回复“static”搜索,主要介绍Java、Android方面知识,欢迎关注一起探讨技术问题
IT女小白的成长史