当前位置:课程学习>>第四章 面向对象基础>>文本学习>>知识点四
一、静态修饰符static
static被称为静态修饰符,它可以用来修饰类中的成员和类中的方法。
1. 类变量和实例变量
被static修饰的成员变量称为静态变量,也叫类变量(class variable)。没有被static修饰的成员变量叫实例变量(instance variable)。
类变量是所有该类对象的一个公共存储区,因此不需要创建对象就可以访问类变量。访问和使用类变量时,可以使用对象名来引用,也可以直接使用类名来引用。
类变量的初始化优先于任何其它非static变量,如果某个变量被声明为static,意味着在类初始化时就必须为它分配内存空间,而实例变量则是在创建实例后才可以用。
【例4.16】类变量和实例变量的用法。
1 //StaticVariable.java
2 public class StaticVariable
3 {
4 static int sa=3;
5 int b=5;
6 public static void main(String[] args)
7 {
8 System.out.println("Static variable sa ="+sa);
9 StaticVariable one =new StaticVariable();
10 System.out.println(one.sa);
11 System.out.println(one.b);
12 }
13}
二、访问控制符
Java中的访问权限即访问控制符,它是用来限定外界对其的访问存取程度,访问控制符可以修饰类、也可以修饰类中的成员和方法。一共有四种级别:缺省为空(包可访问)、public、protected、private。
1. 缺省情况(包可访问)
类、类中成员和方法的修饰符中若没有访问控制符修饰,那么就称为默认(或缺省)访问控制,也称包可访问,即该类、成员以及方法只能被与它同处一个包内的其他类所访问和引用,而不能被包外其他的类访问,即使其子类也不可以。由于包实际上就是目录,因此同一个包的含义也就是在同一个目录下或者就是同一个文件中。
【例4.17】缺省访问控制符的使用。
1 //PackageAccess.java
2 //package packageA;
3 class PackageAccess
4 {
5 int a=10;
6 void prt()
7 {
8 System.out.println("In the PackageAccess class");
9 }
三、继承extends
继承性是面向对象程序设计语言的一个基本特征,通过继承可以实现代码的复用。继承而得到的类称为子类(subclass),被继承的类称为父类(或叫超类superclass)。子类可以直接继承父类中访问控制符为public、protected的成员和方法,如果和父类在同一个包内,还可以继承缺省访问控制符的成员和方法。但是子类不能继承父类中访问控制符为private的成员和方法。
Java中是通过关键字extends来声明类的继承。此时需要注意的是父类必须是存在而且是可访问的,另外在编译子类时会自动编译父类。
继承的时候,如果子类中声明了和父类同名的成员变量时,父类的成员变量就不会被继承,而是被子类的成员所隐藏,称为成员变量的隐藏(overriding),同样如果子类中定义了和父类相同的方法(方法名相同、参数列表相同而且返回值类型也相同),这就是方法的重写(overriding)。
对于方法的重写需要注意的是子类中不能重写父类中final修饰的方法,如果不是抽象(abstract修饰的)类,子类就必须重写父类中的abstract修饰的(抽象)方法。此外重写后的方法不能比父类中的方法有更严格的访问控制符。
【例4.18】继承的实现、成员变量的隐藏以及方法的重写。
1 //ClassExtends.java
2 class SuperClass
3 {
4 int a=3;
5 String name="super class";
6 private int b;
7 void f1()
8 {
9 System.out.println("super class f1()");
10 }
11 public void f2()
12 {
13 b=3;
14 System.out.println("super class f2()");
15 }
16 }
17 public class ClassExtends extends SuperClass
18 {
19 String name="sub class";
20 public void f2()
21 {
22 a=4;
23 System.out.println("sub class f2()");
24 }
25 public static void main(String[] args)
26 {
27 ClassExtends ce=new ClassExtends();
28 ce.f1();
29 ce.f2();
30 System.out.println(ce.a);
31 System.out.println(ce.name);
32 }
33 }
程序运行结果为:
super class f1()
sub class f2()
4
sub class
这里有成员的隐藏(第19行),也有方法的重写(第20行的f2()),子类对象在访问成员和调用方法时都是子类中重写后的成员和方法,而不会访问和调用父类的成员和方法。需要注意的是第20行与第11行都有public修饰,如果去掉第20行的public,编译的时候就会出现图4.10所示的错误提示:
图4.10 重写后的方法不能比原来方法的访问权限更小
子类在继承父类后,子类不能继承父类的构造方法,但是用子类创建对象时却会自动调用父类中相应的构造方法。不管子类在创建对象时使用哪种形式(有参、无参)构造方法,都只会自动调用父类的默认构造方法(无参数的构造方法),此外如果父类中没有定义默认构造方法,那么在子类中也不能定义它的默认构造方法。当然子类中可以通过super关键字来调用父类有参数的构造方法(下一小节内容)。
【例4.19】继承时构造方法的调用。
1 class A
2 {
3 A()
4 {
5 System.out.println("in the A construction");
6 }
7 A(int a)
8 {
9 System.out.println("in the A construction,a="+a);
10 }
11 }
12 class B extends A
13 {
14 B()
15 {
16 System.out.println("in the B construction");
17 }
18 B(int b)
19 {
20 System.out.println("in the B construction,b="+b);
21 }
22 }
23 public class ConstructionExtends extends B
24 {
25 ConstructionExtends()
26 {
27 System.out.println("in the ConstructionExtends construction");
28 }
29 ConstructionExtends(int c)
30 {
31 System.out.println("in the ConstructionExtends construction,c="+c);
32 }
33 public static void main(String[] args)
34 {
35 System.out.println("create one object");
36 ConstructionExtends one=new ConstructionExtends();
37 System.out.println("create two object");
38 ConstructionExtends two=new ConstructionExtends(3);
39 System.out.println("main end");
40 }
41 }
程序运行结果如图4.11所示。
图4.11 继承时创建对象会依次调用父类的构造方法
如果把父类A或B中的默认构造方法删除掉,那么编译源程序时就会出现图4.12所示的错误(这里假设去掉B中的默认构造方法B())。
图4.12 父类没有默认构造方法时的错误提示
在一个子类中,既有类变量成员,又有实例成员,那么用这个子类创建对象时它们被初始化是有先后顺序的。首先是类中的类变量最先被初始化,然后是该类的父类的构造方法从高到低被依次调用,然后才是实例成员的初始化,最后才调用该类本身的构造方法,完成对象的创建工作。
【例4.20】类成员、实例成员以及构造方法的调用顺序。
1 //CallOrder.java
2 class TestClass
3 {
4 TestClass(int a)
5 {
6 System.out.println("in the TestClass,a="+a);
7 }
8 }
9 class SuperParentClass
10 {
11 SuperParentClass()
12 {
13 System.out.println("in the SuperParentClass");
14 }
15 }
16 class SuperClass extends SuperParentClass
17 {
18 SuperClass()
19 {
20 System.out.println("in the SuperClass");
21 }
22 }
23 public class CallOrder
24 {
25 TestClass tc1=new TestClass(1);
26 static TestClass tc2=new TestClass(2);
27 CallOrder(int a)
28 {
29 System.out.println("in the CallOrder,a="+a);
30 }
31 public static void main(String[] args)
32 {
33 System.out.println("main begin");
34 CallOrder one=new CallOrder(3);
35 System.out.println("create the second object");
36 CallOrder two=new CallOrder(4);
37 }
38 }
程序的运行结果如图4.13所示,注意类变量、实例变量、父类的构造方法、类本身的构造方法的执行顺序。
图4.13 类变量、实例变量、父类构造方法、本身构造方法的调用顺序
四、this和super
1. this
this关键字表示当前类或当前对象,可通过this来引用类中被隐藏的成员变量,也可以利用this在一个构造方法中调用另外一个构造方法。
【例4.21】this的用法。
1 //ThisUsage.java
2 public class ThisUsage
3 {
4 int a;
5 int b;
6 ThisUsage()
7 {
8 a=0;
9 b=0;
10 }
11 ThisUsage(int a,int b)
12 {
13 this.a=a;
14 this.b=b;
15 //a=a;
16 //b=b;
17 }
18 ThisUsage(int a)
19 {
20 //System.out.println("a="+a);
21 this(a,10);
22 }
23 void prt()
24 {
25 System.out.println("a="+a+",b="+b);
26 }
27 public static void main(String[] args)
28 {
29 ThisUsage tu1=new ThisUsage(3,4);
30 tu1.prt();
31 ThisUsage tu2=new ThisUsage(5);
32 tu2.prt();
33 }
34 }
编译运行该程序后,其结果为:
a=3,b=4
a=5,b=10
如果第13行、第14行注释掉更换为第15行、第16行,编译运行后其结果为:
a=0,b=0
a=0,b=0
这是由于在ThisUsage(int a,int b)方法中的参数和成员变量名完全相同,无法区分,因此必须使用this。此外如果把第20行的注释符去掉,编译时就会出现如下提示:对this的调用必须是构造函数中的第一个语句。因此不能看出调用其他构造方法的语句必须是第一条语句,这样在一个构造方法中最多只能调用一次其他的构造方法,而且this和稍后的super一样都只能用在非静态方法中,而不能用在static修饰的方法中。
2. super
super关键字表示的是当前类的父类,super的使用有两种用法。
(1) 用super来访问父类中被隐藏的成员变量和父类中被重写的方法,格式为:
super.成员名 super.方法名( [ 实参列表 ] )
【例4.22】super引用父类中被隐藏的成员和方法。
1 //SuperUsage1.java
2 class SuperClass
3 {
4 int x=3;
5 void prt()
6 {
7 System.out.println("in SuperClass.prt()");
8 }
9 };
10 class SuperUsage1 extends SuperClass
11 {
12 int x=10;
13 void prt()
14 {
15 super.prt();
16 System.out.println("in SubClasss.prt()");
17 System.out.println("super.x = "+super.x+", sub.x = "+x);
18 }
19 public static void main(String[] args)
20 {
21 SuperUsage1 su=new SuperUsage1();
22 su.prt();
23 }
24 };
里第15行是调用父类中被重写的prt()方法,在第17行访问了父类中被隐藏的成员变量x。程序运行结果为:
in SuperClass.prt()
in SubClass.prt()
super.x = 3,sub.x= 10
(2) 用super调用父类的构造方法,格式为: super( [ 实参列表 ] )
用super调用父类构造方法的语句和this用法相同也必须是构造方法的第一条语句,如果没有Java编译器会自动调用一个super()语句,而调用父类默认构造方法。
【例4.23】super调用父类构造方法。
1 //SuperUsage2.java
2 class SuperClass
3 {
4 int a;
5 SuperClass()
6 {
7 System.out.println("in the SuperClass");
8 }
9 SuperClass(int a)
10 {
11 this.a=a;
12 System.out.println("in the SuperClass,a="+a);
13 }
14 }
15 public class SuperUsage2 extends SuperClass
16 {
17 String str;
18 SuperUsage2()
19 {
20 //super();
21 System.out.println("in the SubClass");
22 }
23 SuperUsage2(int a,String str)
24 {
25 super(a);
26 this.str=str;
27 }
28 public static void main(String[] args)
29 {
30 SuperUsage2 one=new SuperUsage2();
31 SuperUsage2 two=new SuperUsage2(3,"test");
32 System.out.println(two.str);
33 }
34 }
程序运行结果为:
in the SuperClass
in the SubClass
in the SuperClass,a=3
test
这里如果把父类SuperClass中的默认构造方法去掉,即第5行到第8行去掉,那么在编译的时候就会出现错误,这是因为默认情况下,如果没有使用super语句,Java都会自动调用一个super()语句,即第20行有或没有的效果都一样。
五、常量final
final关键字表示最终或常量的含义,它可以用来修饰成员变量和类中的方法,也可以修饰类。
用final修饰的成员,不管其数据类型是基本数据类型还是引用类型,它的值一旦赋值后就不能被改变(即常量)。格式为:final 数据类型 成员名=初始化值;
【例4.24】常量final的用法。
1 //FinalTest.java
2 class FClass
3 {
4 int i=1;
5 };
6 public class FinalTest
7 {
8 final int fi=1;
9 FClass fc1=new FClass();
10 final FClass fc2=new FClass();
11 public static void main(String[] args)
12 {
13 FinalTest test=new FinalTest();
14 FClass fc3=new FClass();
15 test.fc1=fc3;
16 test.fi=4;
17 test.fc2=fc3;
18 test.fc1.i=10;
19 test.fc2.i=20;
20 System.out.println("test over");
21 }
22 };
编译该程序就会出现如图4.14所示。
图4.14 不能更改常量的值
这里是因为第16行对常量fi重新赋值了,第17行对常量fc2重新赋值了,只要把这两行注释掉,程序就可以正常运行了(至于第19行对fc2.i进行重新赋值是可以的,尽管fc2是常量,但它的成员i不是常量)。运行结果为:test over。
对于类中的常量成员,如果在声明时没有赋值,那么在创建对象时Java系统不会对它进行默认赋值(赋值为对应的数据类型的默认值),必须在使用前进行赋值,而且只能在构造方法中进行赋值。
【例4.25】利用构造方法对常量进行赋值。
1 //FinalVariable.java
2 class FinalVariable
3 {
4 int a;
5 final int fi1=1;
6 //final int fi1;
7 final int fi2;
8 FinalVariable()
9 {
10 fi2=3;
11 }
12 FinalVariable(int x)
13 {
14 fi2=x;
15 }
16 /*
17 void init(int x)
18 {
19 fi2=x;
20 }
21 */
22 public static void main(String[] args)
23 {
24 FinalVariable one=new FinalVariable();
25 FinalVariable two=new FinalVariable(10);
26 System.out.println("final variable one.a="+one.a);
27 System.out.println("final variable one.fi1="+one.fi1);
28 System.out.println("final variable one.fi2="+one.fi2);
29 System.out.println("final variable two.fi2="+two.fi2);
30 }
31 };
程序结果如图4.15所示。如果把第5行换为第6行或者把第16行到第21行的注释去掉,编译的时候都会出现错误。
图4.15 程序运行结果
用final修饰的方法是不能在它的子类中被重写,用final修饰的类是不能被继承。此外final修饰的类中所有的方法均为final方法(不管有无final关键字声明)。
六、抽象abstract
abstract表示抽象,可以用来修饰类和类中的方法。用abstract修饰的类叫抽象类,抽象类是不能直接用来创建对象,抽象类必须被继承,通过它的子类来访问抽象类中的成员和方法。
用abstract修饰的方法叫抽象方法,定义抽象方法只需方法的声明,不需要方法体(也不能有{}括号)。
【例4.26】抽象方法的定义。
1 //AbstractMethod.java
2 public abstract class AbstractMethod
3 {
4 abstract void f1();
5 abstract void f2()
6 {
7
8 }
9 }
编译该程序就会提示抽象方法不能有方法体,如图4.16所示。图4.16 抽象方法不能有方法体
图4.16 抽象方法不能有方法体
抽象类中不一定有抽象方法,但若有抽象方法,该类必须声明为抽象类。
【例4.27】抽象类中可以没有抽象方法。
1 //AbstractMethod.java
2 public abstract class AbstractMethod
3 {
4 void f1()
5 {
6
7 }
8 void f2()
9 {
10
11 }
12 }
抽象类中可以没有抽象方法,编译以上程序没有任何问题。
【例4.28】抽象方法必须在抽象类中。
1 //AbstractMethod.java
2 public class AbstractMethod
3 {
4 abstract void f1();
5 }
编译以上程序,会出现错误提示,如图4.17所示的错误。
图4.17 抽象方法在非抽象类中的编译错误
对于抽象类的子类(直接子类)必须在类中重写父类中所有的抽象方法(给出方法体),否则除非该子类也是抽象类。
【例4.29】抽象类的子类。
1 //ExtendsAbstractClass.java
2 abstract class AbstractClass
3 {
4 abstract void f1();
5 abstract void f2();
6 }
7 public class ExtendsAbstractClass extends AbstractClass
8 {
9 void f1()
10 {
11
12 }
13 void f2()
14 {
15 System.out.println("test");
16 }
17 }
编译该程序没有任何问题,但是如果把第9行到第12行或第13行到第16行注释掉,这里假设注释掉第9行到第12行,保存后重新编译,就会出现图4.18所示的错误。
图4.18 子类中未全部重写父类中的抽象方法出现的编译错误
更改的方法有两种:一是在子类中重写f1()方法,二是把当前类也声明为abstract类。