static是"静态的"的意思.
静态方法
首先看看<Java编程思想>对静态方法的解释:
static方法就是没有this的方法.在static方法内部不能调用非静态方法,反过来倒是可以的.而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法.这实际上正是static方法的主要用途.
关于静态方法内不能调用非静态方法,而反过来可以.经过上面的解释,我们知道静态方法的调用不需要对象,那么其中也就不存在this.而非静态方法是需要具体的对象调用的,那么也就无法被静态方法直接调用,需要创建对象后用对象调用.
那反过来为什么可以呢,因为static方法不需要创建对象就可以访问.
所以,静态成员可以直接在静态方法或非静态方法中访问,非静态成员必须创建对象才能在静态方法中访问.
public class Static {
private static int number = 1; //静态
private String name = "a"; //非静态
private void out(){ //非静态
test(); //可以直接访问静态方法
System.out.println(name + number); //也可以直接访问静态变量
}
private static void test(){ //静态方法
//out(); //这样是不可以的
//System.out.println(number + name); //这样是不可以的
Static s = new Static(); //需要创建对象后才可以访问非静态成员
s.out();
System.out.println(s.name + number);
}
}
main为什么是static的?
就比如我们运行程序所需要的main(),就是个典型的静态方法:
public static void main(String[] args){}
为什么main()要用static?试想一下,如果main()不是static的,那么JVM就需要使用如下形式调用main()(假设类名为A)
A a = new A();
a.main();
看起来没什么问题,但是你在哪里创建这个A对象呢?一样还是需要main(),这样就无法启动程序.所以作为程序的入口,必须可以直接用类名就调用得到:
A.main();
静态方法的具体应用
最常见的地方就是工具方法,比如Math类中的诸多方法,都是直接通过Math类来调用:
Math.add();
Math.abs();
Math.pow();
Math.max();
.
.
.
还有就是工厂方法:
工厂方法模式(英语:Factory method pattern)是一种实现了“工厂”概念的面向对象设计模式。就像其他创建型模式一样,它也是处理在不指定对象具体类型的情况下创建对象的问题。工厂方法模式的实质是“定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。” ---维基百科 工厂方法 词条.
class Point {
private int x;
private int y;
private int z;
private int length;
private Point(int x, int y, int z){
this.x = x;
this.y = y;
this.z = z;
length = 3;
}
private Point(int x, int y){
this.x = x;
this.y = y;
length = 2;
}
static Point createXYZPoint(int x, int y, int z){
return new Point(x, y , z);
}
static Point createXYPoint(int x, int y){
return new Point(x, y);
}
@Override
public String toString() {
if (length == 3){
return "(" + x + "," + y + "," + z + ")";
} else {
return "(" + x + "," + y + ")";
}
}
}
这个类中两个return Point对象的方法就是工厂方法,分别用来创建两种不同的点.
public class TestPoint{
public static void main(String[] args) {
Point p1 = Point.createXYPoint(1, 2);
Point p2 = Point.createXYZPoint(3, 4, 5);
System.out.println(p1);
System.out.println(p2);
}
}
运行:
(1,2)
(3,4,5)
可以看到测试类中并没有使用new关键字也创建了对象. 因为你不能在测试类使用new创建创建Point对象:所有的构造器都是私有的.
这样的工厂方法往往有可以展示自己可以创建什么对象的名字,而不像构造器一样只有类名一个名称,可能有多个构造器而造成混乱(方法重载),这也是一点优势.
静态变量
与静态方法一样无需使用对象调用.
静态变量只存在一份:被所有对象共享,仅在类初次加载时初始化一次.与此对比,非静态变量每个对象都可以持有一份,内存中可以存在多个.下面的代码展示了两者的区别:
class Count {
static int times = 0;
int number = 0;
public static int getTimes() { return times; }
public int getNumber() { return number; }
}
public class TestCount{
public static void main(String[] args) {
for (int i = 0; i < Byte.MAX_VALUE; i++) {
Count.times++;
}
System.out.println("静态变量自增127次I:" + Count.getTimes());
for (int i = 0; i < Byte.MAX_VALUE; i++) {
Count.times++;
}
System.out.println("静态变量自增127次II:" + Count.getTimes());
System.out.println();
Count c1 = new Count();
Count c2 = new Count();
for (int i = 0; i < Byte.MAX_VALUE; i++) {
c1.number++;
c2.number++;
}
System.out.println("非静态变量1自增127次I:" + c1.getNumber());
System.out.println("非静态变量2自增127次I:" + c2.getNumber());
System.out.println();
for (int i = 0; i < Byte.MAX_VALUE; i++) {
c1.number++;
c2.number++;
}
System.out.println("非静态变量1自增127次II:" + c1.getNumber());
System.out.println("非静态变量2自增127次II:" + c2.getNumber());
System.out.println();
c1 = new Count();
c2 = new Count();
for (int i = 0; i < Byte.MAX_VALUE; i++) {
c1.number++;
c2.number++;
}
System.out.println("非静态变量1自增127次III:" + c1.getNumber());
System.out.println("非静态变量2自增127次III:" + c2.getNumber());
}
}
运行结果:
静态变量自增127次I:127
静态变量自增127次II:254
非静态变量1自增127次I:127
非静态变量1自增127次I:127
非静态变量1自增127次I:254
非静态变量1自增127次I:254
非静态变量1自增127次II:127
非静态变量1自增127次II:127
这里非静态变量创建了两个,每个的值都是独立的,而静态变量的值是共享的,无论多少次操作都是那一个静态变量.所以静态变量的一个用途就是用作计数器.
常量
前面在数据类型中引用了下面两个数值:
Math.E;
Math.PI;
进入Math类源码,可以看到它们两个的定义略有不同,多了一点东西:
public static final double E = 2.7182818284590452354;
public static final double PI = 3.14159265358979323846;
多了一个final,意思是"最终的",也就是说,不能改变.这里先不说,下一篇里面再详细说说final.
我们应用最多的常量可能就是System中的out,它是PrintStream类型的:
public final static PrintStream out = null;
再来看看输出语句的含义:
System.out.println();
通过System.out来访问System类中的PrintStream类型的常量out,再通过out来调用PrintStream中的println().
我们自己写一个来看看:
import java.io.PrintStream;
public class Static {
public static void main(String[] args) {
Status.STATUS.showStatus(); //Status类中的showStatus方法.
Status.PRINT.println("通过Integer类型常量调用Integer中的floatValue():" + Status.INTEGER.floatValue());
//通过Integer类型常量调用Integer中的floatValue():1.0
Status.PRINT.println(Status.STR); //Status类中的String类型常量.
}
}
class Status{
static final Status STATUS = new Status();
static final Integer INTEGER = 1;
static final PrintStream PRINT = new PrintStream(System.out);
static final String STR = "Status类中的String类型常量.";
void showStatus(){
System.out.println("Status类中的showStatus方法.");
}
}
这里我通过Status类型常量调用到了Status类中的showStatus(),还通过PrintStream类型常量PRINT调用println()输出了以下信息:Integer类型常量INTEGER访问到了Integer中的floatValue(),又通过Status类调用到了其中String类型的常量STR.
当然通过本类型常量调用本类型方法,普通的静态变量也是可以的.比如我把上面Status类型常量的final去掉这些代码照样可以正常运行.
关于常量的命名,一般来说是全部使用大写字母,这不是硬性规定.
静态代码块
如果我们想预先加载一些资源,比如图片,就可以使用独立的static块,static块的特点是,先于类被执行,一般用来做准备活动.
static{}
比如:
public class Static {
Static(){System.out.println("构造器.");}
public static void main(String[] args) {
Static s = new Static();
s.out();
System.out.println("main方法.");
}
private void out(){
System.out.println("out方法");
}
static{System.out.println("static块");}
}
运行结果:
static块
构造器.
out方法
main方法.
可以看到static块的执行顺序先于对象的初始化. 当存在多个static块时,按从前到后的顺序依次执行.
由此可以一个经典的问题,看看以下代码的输出结果是什么:
public class Static {
Static(){
System.out.println("父类Static构造器."); //1
}
public static void main(String[] args) {
new StaticSubClass();
}
static {
System.out.println("父类Static的静态块1."); //2
}
A a = new A("Static"); //3
static {
System.out.println("父类Static的静态块2."); //4
}
}
class StaticSubClass extends Static{
A a = new A("StaticSubClass"); //5
StaticSubClass(){
System.out.println("子类StaticSubClass构造器."); //6
}
static {
System.out.println("子类StaticSubClass静态块."); //7
}
}
class A{
String string;
A(String s){
System.out.println( s + " A.");
}
static {
System.out.println("A类的静态块."); //8
}
}
运行结果(顺序:24783156):
父类Static的静态块1.
父类Static的静态块2.
子类StaticSubClass静态块.
A类的静态块.
Static A.
父类Static构造器.
StaticSubClass A.
子类StaticSubClass构造器.
由于main()在父类中,所以最先加载父类的静态块,但是父类有两个静态块,就按照从前到后的顺序执行(2→4).主方法中创建了一个子类对象,就执行子类中的静态块(7),然后父类中有A的对象创建,检查发现没有A的对象,就加载A对象,于是就执行A中的静态块(8),此时可以创建A对象,就调用了A的构造器(父类中)(3),再执行父类构造器(1),子类中再执行A的创建(5),最后执行子类自己的构造器(6).
static就说到这里,下一篇来说说final.