static
一、什么是static
static:可以修饰 java类、方法、变量、代码块的方法
二、static的用途
1、static修饰类
static修饰类时,这个类不能是普通的类,只能是内部类。具体使用:
public class StaticDemo1 {
public static class InnerClass {
InnerClass() {
System.out.println("===========静态内部类=============");
}
public void InnerMethod() {
System.out.println("===========静态内部类的方法==========");
}
}
public static void main(String[] args) {
InnerClass innerClass = new InnerClass();
innerClass.InnerMethod();
}
}
/*
输出结果:
===========静态内部类=============
===========静态内部类的方法==========
*/
内部类不用static修饰时写法:
public class StaticDemo11 {
public class InnerClass {
InnerClass() {
System.out.println("===========内部类=============");
}
public void innnerMethod() {
System.out.println("===========内部类的方法=======");
}
}
public static void main(String[] args) {
InnerClass innerClass = new StaticDemo11().new InnerClass();
innerClass.innnerMethod();
}
}
/*
输出:
===========内部类=============
===========内部类的方法=======
*/
结论:
如果没有用static修饰InnerClass,则只能先new一个外部实例,再通过外部实例创建内部类对象。
2、static修饰方法
修饰方法的时候,其实跟类一样,可以直接通过类名来进行调用:
public class StaticDemo2 {
public static void staticMethod2() {
System.out.println("========静态方法========");
}
public static void main(String[] args) {
// 方式1、通过类名调用
// StaticDemo2.staticMethod();
// 方式2、创建对象调用
StaticDemo2 demo2 = new StaticDemo2();
demo2.staticMethod2();
}
}
/*
输出:
========静态方法========
*/
**结论:**调用有两种方式,一般选择方式一
注意:静态方法不能调用非静态成员。
3、static修饰变量
被static修饰的成员变量叫做静态变量,也叫做类变量,说明这个变量是属于这个类的,而不是属于是对象,没有被static修饰的成员变量叫做实例变量,说明这个变量是属于某个具体的对象的。
public class StaticDemo3 {
public static String string = "静态变量";
public static void main(String[] args) {
// 直接通过类名称调用
System.out.println(StaticDemo3.string);
}
}
/*
输出:
StaticDemo1
*/
**结论:**可以直接通过类名称调用
- 静态变量和非静态变量的区别:
- 静态变量被所有对象共享,在内存中只有一个副本,在类初次加载的时候才会初始化
- 非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响
- 能通过this访问静态成员变量吗 ?
public class StaticDemo5 {
static int value = 33;
public static void main(String[] args) {
new StaticDemo5().printValue();
}
private void printValue() {
int value = 3;
System.out.println(this.value);
}
}
大家猜猜这里输出会是多少呢?这里主要考察了this和static的理解。this代表的是当前对象, 那么通过new Main()来调用printValue的话,当前对象就是通过new Main()生成的对象 。 而static变量是被对象所享有的,因此在printValue中的this.value的值毫无疑问是33 。 在这里永远要记住一点:静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。
4、static修饰代码块
-
静态代码块在类第一次被加载的时候执行。
-
类的初始化顺序:先父类后子类
- 静态代码块
- 普通代码块
- 构造函数
详细过程:
父类静态变量、父类静态代码块
子类静态变量、子类静态代码块
父类普通变量、父类普通代码块、父类构造函数
子类普通变量、子类普通代码块、子类构造函数
-
静态块里面的代码只执行一次,且只在初始化类的时候执行 。
代码验证:
public class Fu {
//父类静态代码块
static {
System.out.println("=====父类静态代码块=====");
}
// 父类构造函数
Fu() {
System.out.println("=====父类构造函数=======");
}
public static void main(String[] args) {
new Zi();
new Zi();
}
}
class Zi extends Fu {
// 子类静态代码块
static {
System.out.println("=====子类静态代码块=====");
}
Zi() {
System.out.println("====子类构造函数=====");
}
}
/*
输出:
=====父类静态代码块=====
=====子类静态代码块=====
=====父类构造函数=======
====子类构造函数=====
=====父类构造函数=======
====子类构造函数=====
*/
静态代码块需要注意的问题:
- 静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问。
public class StaticDemo4 {
static {
c = 10;
System.out.println(c);
}
public StaticDemo4() {
System.out.println(c);
}
public static void main(String[] args) {
new StaticDemo4();
}
private static int c;
}
报错:
如果将private static int c;
这行代码放到static静态代码块之前就为正确的
5、总结
1、static是一个修饰符,用于修饰成员。(成员变量,成员函数)static修饰的成员变量 称之为静态变量或类变量。
2、static修饰的成员被所有的对象共享。
3、static优先于对象存在,因为static的成员随着类的加载就已经存在。
4、static修饰的成员多了一种调用方式,可以直接被类名所调用,(类名.静态成员)。
5、static修饰的数据是共享数据,对象中的存储的是特有的数据。
三、面试题
1、下面这段代码的输出结果是什么?
public class Test extends Base{
static{
System.out.println("test static");
}
public Test(){
System.out.println("test constructor");
}
public static void main(String[] args) {
new Test();
}
}
class Base{
static{
System.out.println("base static");
}
public Base(){
System.out.println("base constructor");
}
}
输出结果:
base static
test static
base constructor
test constructor
分析:
-
先找到main方法入口, main方法是程序入口,但在执行main方法之前,要先加载Test类
-
加载Test类的时候,发现Test类继承Base类,于是先去加载Base类
-
加载Base类的时候,发现Base类有static块,而是先执行static块,输出base static结果
-
Base类加载完成后,再去加载Test类,发现Test类也有static块,而是执行Test类中的static块,输出test static结果
-
Base类和Test类加载完成后,然后执行main方法中的new Test(),调用子类构造器之前会先调用父类构造器
调用父类构造器,输出base constructor结果然后再调用子类构造器,输出test constructor结果
-
然后再调用子类构造器,输出test constructor结果
2、 这段代码的输出结果是什么?
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");
}
}
输出结果:
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
分析:
- 找到main方法入口,main方法是程序入口,但在执行main方法之前,要先加载Test类
- 加载Test类的时候,发现Test类有static块,而是先执行static块,输出test static结果
- 然后执行
new MyClass()
,执行此代码之前,先加载MyClass类,发现MyClass类继承Test类,而是要先加载Test类,Test类之前已加载 - 然后加载MyClass类,发现MyClass类有static块,而是先执行static块,输出myclass static结果
- 然后调用MyClass类的构造器生成对象,在生成对象前,需要先初始化父类Test的成员变量,而是执行Person person = new Person(“Test”)代码,发现Person类没有加载
- 转去又加载Person类,发现Person类有static块,而是先执行static块,输出person static结果
- 接着刚才Test类的
Person person = new Person("Test")
执行Person构造器,输出person Test结果 - 然后调用父类Test构造器,输出test constructor结果,这样就完成了父类Test的初始化了
- 转回来再初始化MyClass类成员变量,执行Person构造器,输出person MyClass结果
- 最后调用MyClass类构造器,输出myclass constructor结果,这样就完成了MyClass类的初始化了
总结:
把握住一个思路:执行过程
父类静态变量、父类静态代码块
子类静态变量、子类静态代码块
父类普通变量、父类普通代码块、父类构造函数
子类普通变量、子类普通代码块、子类构造函数
3、这段代码的输出结果是什么?
public class Test {
static{
System.out.println("test static 1");
}
public static void main(String[] args) {
}
static{
System.out.println("test static 2");
}
}
结果:
test static 1
test static 2
分析:
- 先找到main方法入口,但在执行main方法之前需要先加载Test类
- 加载Test类的时候,发现有静态代码块,执行静态代码块,按照顺序执行。