概述
本文围绕静态的变量、方法、代码块、导入展开,内部类先不做介绍,内部类需要单独出一篇去解释。
静态方法
静态方法最大的特点就是可以不依赖于任何对象就可以被进行访问,所以没有this方法。
就像例子中有一个静态方法print2(),它在main方法中可以像普通方法一样直接被调用输出。
public class StaticDemo {
private static String str1 = "this is static string";
public static void print2() {
System.out.println(str1);
}
public static void main(String[] args) {
print2();
}
}
输出:
this is static string
假如说有个Test类想要调用StaticDemo.java中的print2()方法。
public class Test {
public static void main(String[] args) {
StaticDemo.print2();
}
}
这样就可以调用了,输出相同,也不需要new出这个对象来,更证实了静态变量或者方法中“只要这个类被加载了,就可以通过类名去进行访问”。
但是,静态方法中不能访问类的非静态成员变量和非静态成员方法。因为非静态成员方法和变量都是必须依赖具体的对象才能被调用,而被static关键字修饰的静态方法在对象创建之前就被写入了内存,由于时序的关系,先加载的肯定不能调用还未加载的方法或者变量。
如果实在想调用str2,可以用对象去访问到,new出对象,让它先加载出来就可以了。
为什么静态方法不能调用非静态的方法?
1. 首先,静态方法属于类,随着类的加载而加载;而非静态方法属于对象,对象是在类加载之后创建的。
2. 静态方法属于类,它不依赖于对象的调用,它是通过“类名.静态方法名”的方式来调用的;非静态方法在对象创建的时候,才会为其分配内存,然后通过类的对象去访问非静态方法。
3. 当对象还未创建的时候,是不会为它分配内存的,非静态方法也不存在,所以静态方法自然不能调用一个不存在的方法。
静态变量
静态变量被所有对象所共享,在内存中只有一个副本,当且仅当在类初次加载的时候被初始化,调用方式:【类名.变量名】或【对象.变量名】;
而非静态变量在创建对象的时候被初始化,可以存在多个副本,并且各对象所拥有的副本互不影响,调用方式【对象.变量名】。
public class Test {
public static int staticValue = 0;
public int value = 0;
public static void main(String[] args) {
Test test1 = new Test();
System.out.println("staticValue = " + Test.staticValue);
System.out.println("value = " + test1.value);
//静态和非静态变量分别加一
staticValue ++;
test1.value ++;
Test test2 = new Test();
System.out.println("staticValue+ = " + Test.staticValue);
System.out.println("value+ = " + test2.value);
}
}
输出:
staticValue = 0
value = 0
staticValue+ = 1
value+ = 0
因为静态变量只有一个副本,所有的对象都使用这一个副本,所以test加一后,使用test2拿到的是加完1之后的;
而非静态变量有多个副本,与具体的对象相关。
静态变量与静态常量
静态变量和静态常量看起来好像除了可变与不可变的关系也没什么了,其实它们在加载的时候还是有差别的。
比如有一个静态变量:
public static int value = 1;
1. JVM类加载到准备阶段的时候,会初始化静态变量,并将变量赋值为0或者null。在此阶段,value的值为0。
2. JVM类加载到初始化阶段的时候,会将静态变量的值赋值为指定的值。此阶段,value的值为1。
还有一个静态常量:
public static final int value = 1;
在JVM加载类到准备阶段的时候,直接将指定的值赋给静态常量。
所以,静态常量的初始化要比静态变量完成的早。
静态代码块
静态代码块的存在,就是为了静态属性的初始化。
静态代码块可以存在多个,JVM会根据他们在类中出现的先后顺序来依次执行,且每个静态代码块只会被执行一次。
import java.util.ArrayList;
import java.util.List;
public class FileListView {
private static List<String> videoEnableList = new ArrayList<String>();
static {
videoEnableList.add(".mp4");
videoEnableList.add(".avi");
}
public List<String> getVieoEnable() {
return videoEnableList;
}
}
在另一头
import java.util.List;
public class Test {
public static void main(String[] args) {
FileListView mFileListView = new FileListView();
List<String> videoList = mFileListView.getVieoEnable();
System.out.println("videoList = " + videoList);
}
}
videoList = [.mp4, .avi]
由此可见,把只执行一次的初始化代码放到静态代码块即可。
静态导入
静态导入的作用就是把一个静态变量或者静态方法一次性导入,导入后可以直接使用该方法或者变量,而不再需要写对象名;
这样就可以当我们非常频繁的使用一个对象的变量和方法,就不用频繁的写这个类名了。
下面就来一个获取一个随机数,并把它转化为字符串的一个例子。
import static java.lang.Math.pow; //静态导入 包名.类名.方法名
import static java.lang.String.valueOf;
import java.util.Random;
public class ImportStatic {
public static void main(String[] args) {
Random rm = new Random();
// 获得随机数
double pross = (1 + rm.nextDouble()) * pow(10, 15); //这里无需写类名,直接写方法名即可
// 将获得的获得随机数转化为字符串
String fixLengthString = valueOf(pross);
System.out.println(fixLengthString);
}
}
通过我多次的实验得出:
1. 像getInstance这种方法有很多,如果同时导入两个对象的getInstance()方法,在代码里又只写方法名,这怎么区分呢?
拿例子中的pow()方法来说,我再在别的类中创建一个这样的方法:
public class KeyMap {
public static final double pow(double a, double b) {
return 0;
}
}
这个方法一定要保证传入参数相同,仿照出一个完完全全相同的方法。如果参数不同,它就不认为是两个方法,也不会产生冲突。
这时候前面就已经有语法错误了。
2. 是所有方法都可以被静态加载吗?
当然不是,只有静态的方法可以被加载。
当我去掉了方法中的static关键字:
public class KeyMap {
public double pow() {
return 0;
}
}
import的地方立马报了错,找不到。
我觉得这个加载不了道理与静态方法不能调用普通的成员方法一样吧,去导入一个还没有被加载的方法,肯定是调用不到的。
静态内部类
普通内部类需要在外部类实例化后才能实例化;
相比,静态内部类可以不依赖于外部类实例对象而被实例化。
还有,静态内部类不能与外部类有相同的名字,不能访问外部类的普通成员变量,只能访问内部类中的静态成员和静态方法。
用static实现单例
单例的特点是一个类只能有一个实例,而static有且只能被加载一次,利用这个特点,恰恰好;
只需把该对象的构造方法隐藏掉,防止被new出来,然后对外伸出一个静态方法将自身实例返回出去就实现了。
public class FullBase {
private static FullBase mFullBase = null;
private FullBase(){}
public static FullBase getInstance(){
if (mFullBase == null){
mFullBase = new FullBase();
}
return mFullBase;
}
}
这是用static实现的最简单的单例模式,如果想查看其他的单例实现,扔个链接。
执行顺序
有这么一个父类和子类,他们两个都有各自的静态代码块、构造代码块、构造函数,看看他们的执行顺序是怎样的。
public class Father {
static {
System.out.println("父类静态代码块");
}
{
System.out.println("父类构造代码块");
}
public Father(){
System.out.println("父类构造函数");
}
}
在子类的main函数里调用new一个子类。
public class Son extends Father {
static {
System.out.println("子类静态代码块");
}
{
System.out.println("子类构造代码块");
}
public Son(){
System.out.println("子类构造函数");
}
public static void main(String[] args) {
new Son();
}
}
执行结果是这样的
父类静态代码块
子类静态代码块
父类构造代码块
父类构造函数
子类构造代码块
子类构造函数
参考文献
https://blog.csdn.net/qq_28761767/article/details/80994445
https://blog.csdn.net/xv1356027897/article/details/79497057
https://blog.csdn.net/a5650892/article/details/78708343