前言
Java语言拾遗,高手如果感兴趣可跳过前两节。
类方法
方法被声明为static后,则称为类方法。类方法相对于实例方法,前者区别于后者的地方:前者为属于该类的所有实例对象共享,无须实例化对象,仅通过类名即可访问(当然,是否能够直接访问,还取决于所声明的访问权限)。
因为被static化后产生上述特殊性,所以static变量都会在类被加载时初始化,而类方法也同时随类加载而进驻内存。先来段代码热热身吧~
public
class
Test
...
{
public static void main(String[] args) ...{
A.print();
System.out.println(A.publicStr);
}
}
class
A
...
{
private static A a = new A();
private static String privateStr = null;
public static String publicStr = "A Class";
private A() ...{
privateStr = "A Calss";
}
public static void print() ...{
System.out.println(privateStr);
}
}
上段代码,输出结果为:
由结果可知,即字符串prvateStr的值为空。嘿,可别认为值应该是下面那样啊。那样的话,进行下去就太具挑战性了。
请记住一点,类变量初始化的顺序与其在类中的赋值顺序一致。
重写(覆盖)
或许大家对于面向对象编程语言最初的印象就是其语言单元是以父类、子类的关系存在着,而构建这一关系的就是继承机制了。子类可以继承父类一切非private的变量与方法,并且可以添加自己的变量与方法。在构建一个系统时,这机制让我们强烈地感觉到编程是一门优雅的艺术。
来段小小的代码简单地展示下:
public
class
Test
...
{
public static void main(String[] args) ...{
Programmer pro = new Programmer("Jack");
pro.printName();
pro.printProfession();
}
}
class
Man
...
{
private String name = null;
public final String characteristic = "I am a thinking animal";
public Man(String name) ...{
this.name = name;
}
public void printName() ...{
System.out.println(name);
}
}
class
Programmer
extends
Man
...
{
private String profession = "Programmer";
public Programmer(String name) ...{
super(name);
}
public void printProfession() ...{
System.out.println(characteristic + ", and a " + profession);
}
}
结果如下:
Jack
I am a thinking animal, and a Programmer
如上,子类Programmer中并没定义字符串characteristic,但我们却在其方法printProfession()中调用了;同样,我们正常使用了父类定义的printName()方法。而这就是继承的简单实现。
继承不仅仅带来以上特性。它还赋予子类重写(覆盖)父类方法的能力(因为旨在讲类方法的重写,所以这儿就不讲重载以及变量在继承机制中的问题了)。方法的重写(覆盖):继承父类的子类,可以通过拟具有相同方法名与参数组的方法来重写父类中对应的方法,从而让子类更个性化。又因为重写(覆盖)的出现,多态也随之产生。多态:通过父类变量可以引用其子类对象,从而调用子类中那些继承自自己并被重写(覆盖)的方法。
public
class
Test
...
{
public static void main(String[] args) ...{
Man manP = new Programmer("Jack");
Man manS = new Student("Tom", "MayBeHarvard");
manP.printName();
System.out.println();
manS.printName();
}
}
class
Man
...
{
private String name = null;
public Man(String name) ...{
this.name = name;
}
public void printName() ...{
System.out.println(name);
}
}
class
Programmer
extends
Man
...
{
private String profession = "Programmer";
public Programmer(String name) ...{
super(name);
}
public void printName() ...{
System.out.println("Hey, I am a " + profession + ". You know, that work is an art.");
System.out.print("My name is ");
super.printName();
}
}
class
Student
extends
Man
...
{
private String school = null;
public Student(String name, String school) ...{
super(name);
this.school = school;
}
public void printName() ...{
System.out.println("Hi, I am a student from " + school);
System.out.print("My name is ");
super.printName();
}
}
结果如下:
Hey, I am a Programmer. You know, that work is an art.
My name is Jack
Hi, I am a student from MayBeHarvard
My name is Tom
Man类型变量引用其子类对象,并成功调用对应方法打印出个性化的自我介绍。
类方法的重写?
进行到这儿,对类方法与继承、重写等概念应该有较清楚的认识了。如果您不是很清楚、或者我上面介绍得不够详细,请参考Java的圣经 《The Java Language Specification》
现在开始本文的问题吧:
public
class
Test
...
{
public static void main(String[] args) ...{
Man man = new Programmer();
Programmer pro = new Programmer();
man.printName("ManCallMe");
System.out.println();
pro.printName("CallMeByMyself");
}
}
class
Man
...
{
public static void printName(String name) ...{
System.out.println(name);
}
}
class
Programmer
extends
Man
...
{
public static void printName(String name) ...{
System.out.println("Hey, I am a Programmer. You know, that work is an art.");
System.out.print("My name is " + name);
}
}
结果如下:
Hey, I am a Programmer. You know, that work is an art.
ManCallMe
Hey, I am a Programmer. You know, that work is an art.
My name is CallMeByMyself
“结果绝对不是这样!”如果运行过这段代码,您一定会大声的对我说。的确,结果事实上是这样的:
ManCallMe
Hey, I am a Programmer. You know, that work is an art.
按照多态的介绍,结果应该是上一个啊!?为什么事实却是纠正的那个呢?难道重写(覆盖)没有成功?事实上,重写操作是成功的,因为第二个函数调用输出的结果证明了这点。那这一切的一切的问号是为何呢?
方法被加载的顺序是这一切的根本原因!
当一个方法被调用时,JVM首先检查其是不是类方法。如果是,则直接从调用该方法引用变量所属类中找到该方法并执行,而不再确定它是否被重写(覆盖)。如果不是,才会去进行其它操作(例如动态方法查询),具体请参考: 方法的加载
具体到上面那段代码,因为引用man是Man类型变量,所以JVM根据上述规定,调用的是Man中的printName(),而不是Programmer中printName();而pro正是Programmer,同理,其调用是自己的printName(),出现料想外的结果也就纯属正常了。
最后,以一段有趣的代码结束本文。它也是本文对于类方法理解的强有力证据.它正确输出了结果,呵呵~
public
class
Test
...
{
public static void main(String[] args) ...{
(Man.getMan()).printName();
}
}
class
Man
...
{
public static void printName() ...{
System.out.println("None");
}
public static Man getMan() ...{
return null;
}
}