“静态”的历史
静态最初是c语言为了表示退出一个块后依然存在的局部变量,这时候的”静态“是有实际意义的,也就是块中的变量一直保留,再次进入时变量依然存在。
之后的”静态“在C中有了第二种含义:表示不能从其他文件访问的全局变量和函数,为了不加入新得关键字,所以重用了static关键字
第三次是C++中重用了static,指示属于类而不属于类对象的变量和函数,也就是java后来定义的”静态“的作用
静态字段
static作用于类的字段,这个字段从此将变为类所拥有的,也就是说,这个类实例化的所有对象,都将共享这个字段。
- 实际上我们如果想要让字段对于对象共享,会将字段设置为
private static int count = 0;
然后在构造方法中添加id = count++
,这样就可以实现自动的对象id字段绑定,private
可以保证从类外无法改变这个字段。
在一些面向对象的编程语言中,静态字段被称为类字段,“静态”只是沿用了C++的用法
public class TestStatic {
public static int number;
public int id;
public void setId(){
id = number;
number++;
}
}
之后实例化的Student对象,只要调用setId()方法,就可以为对象id赋值。number由于是类所属的,与实例化的对象无关,所以会在最初的数值上加一。不会因为实例化的对象而初始为1。
- 由于number字段是类所属的,所以可以通过类名调用:
int studentID = TestStatic.number;nm tg j
- static不可以修饰局部变量,因为static修饰的变量会一直存在于方法区.而局部变量的生命周期仅在局部代码块中
观察下方代码,this代表当前对象,而t1就是当前的对象,相当于t1.number,静态字段被所有对象共享,所以自然结果是33。
public class TestStatic {
public static int number = 33;
public void setId() {
int number = 4;
System.out.println(this.number);
System.out.println(number);
}
public static void main(String[] args) {
TestStatic t1 = new TestStatic();
t1.setId();
}
}
结果:33
4
静态常量
- 由于静态字段可以被每个实例化的对象访问修改,所以是不安全的
- 而的静态常量由于有final关键字,一旦定义了数值,就只能访问,无法修改,所以是安全的
比如Math类中定义的PI常量
public class Math{
public static final double PI = 3.141592653589323846;
}
//System类中的out就是一个静态常量
public static final PrintStream out = null;
注意:System类中有一个setOut的方法可以设置System.out为不同的流,是因为他是一个原生native方法,不是在java语言中实现,所以可以绕过java的访问控制机
静态常量也可以通过类名调用
静态方法
静态方法也是独立于实例对象的存在,依附于类,比如Math.pow(x,a),它不需要任何Math对象的参数(非静态的字段),并且他也无法在对象上进行操作。但是由于静态字段也是依附于类的,所以静态方法可以调用静态字段。
可以通过类名调用静态方法
Math.pow(2,4);
当然也是可以通过对象调用静态方法的
- 但往往静态方法都是与实例对象无关的,通过对象调用实例方法会给人一种有关系的错觉,所以建议使用类名调用。
- 静态方法会在类加载时存入内存方法区,直到程序结束内存在释放
public class Test {
public static void main(String[] args) {
TestStatic t1 = new TestStatic();
t1.setId();
}
}
静态方法与非静态方法的区别:
- 静态方法只能访问静态字段,非静态方法则无限制。(因为实例字段属于具体的对象,静态字段属于类)
- 非静态方法只有实例化对象后才会存在于方法区,而静态方法在类加载时创建
- 如果方法是一个工具,不是某个对象特有的,就可以声明 为静态
一个有意思的问题:构造器是静态方法吗?
在Java编程思想中认为构造器是静态方法,但构造器实际上不符合java对方法的定义,并且构造器有一个隐式参数:this,静态方法是不能有this的。
需要注意的是:Java语言中,实例构造器只能在new表达式(或者其他构造器,如子类构造器)中被调用(super()会调用父类构造器)
当继承了父类的子类实例对象(main方法在子类中)时,在执行main方法前,会先进行父类的类加载,然后进行子类加载,然后进入main方法,实例对象时会进入子类构造器,由于子类构造器会隐式的在第一行添加super(),所以会进入父类,但父类匿名代码块优先级更好,如果父类中有成员变量,则需要先初始化成员变量,之后才是父类构造器,之后回到子类执行匿名代码,最后才是子类构造器。
以下是先后顺序:
父类静态代码块
子类静态代码块
父类匿名代码块
父类构造器
子类匿名代码
子类构造器
面试题:
public class Test {
Person person = new Person();
static{
System.out.println("test 静态代码块");
}
{
System.out.println("Test的匿名代码块");
}
public Test() {
System.out.println("test 构造器");
}
public static void main(String[] args) {
new MyClass();
}
}
class Person{
{
System.out.println("Person的匿名代码块");
}
static{
System.out.println("person 静态代码块");
}
public Person() {
System.out.println("person构造器");
}
}
class MyClass extends Test {
Person person = new Person();
{
System.out.println("MyClass的匿名代码块");
}
static{
System.out.println("MyClass 静态代码块");
}
public MyClass() {
System.out.println("MyClass 构造器");
}
}
/*
test 静态代码块
MyClass 静态代码块
person 静态代码块
Person的匿名代码块
person构造器
Test的匿名代码块
test 构造器
Person的匿名代码块
person构造器
MyClass的匿名代码块
MyClass 构造器*/
静态代码块
static代码块就是静态代码块,可以有多个,不在任何一个方法体内,会随着类加载时执行一次。
public class TestStatic {
public static int number;
public int id;
{
System.out.println("匿名代码块");
}
static {
System.out.println("静态代码块");
}
public TestStatic() {
System.out.println("构造器");
}
public static void main(String[] args) {
TestStatic t1 = new TestStatic();
}
}
//执行顺序,匿名代码块每次都会在实例对象时执行一次,匿名代码块在构造器之前执行
静态代码块
匿名代码块
构造器
静态代码块的作用:优化性能
class Person{
private Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
Date startDate = Date.valueOf("1946");
Date endDate = Date.valueOf("1964");
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
//分析以上代码,会发现isBornBoomer()方法每次都会生成俩个对象,造成了空间的浪费
class Person{
private Date birthDate;
static{
Date startDate = Date.valueOf("1946");
Date endDate = Date.valueOf("1964");
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
return birthDate.compareTo(startDate) >= 0&& birthDate.compareTo(endDate) < 0;
}
}
//优化后的代码可以让所有对象共享这两个对象
匿名代码块
匿名代码块每次都会在实例对象时执行一次,匿名代码块在构造器之前执行
静态导包
加了static的导包语句,并在最后加上**.*** ,会将包中的所有静态方法导入到当前包,与非static导入不同的是,在类名不冲突的情况下,无需使用类名.方法名的形式调用类方法。
/* PrintHelper.java文件 */
package com.dotgua.study;
public class PrintHelper {
public static void print(Object o){
System.out.println(o);
}
}
/* App.java文件 */
import static com.dotgua.study.PrintHelper.*;
public class App
{
public static void main( String[] args )
{
print("Hello World!");
}
/**Output
* Hello World!
*///~
}
总结
- 只要类被加载了,那么从静态的字段、方法、代码块都会被以一同加载,并且只加载一次。之后可以直接调用。
- static关键字的基本作用:方便在不创建对象的情况下调用字段、方法。
- 静态变量的基本作用:对象之间共享值,方便访问变量