第7章 Java语言中的修饰符
一、访问控制修饰符
(1)public:公开级别,对同类、同包、子类和不同的包都公开;
(2)protected:保护级别,对同类、同包、子类公开;
(3)默认级别:对同类、同包公开;
(4)private:私有级别,只对同类公开。
//*包A:
public class Ca{
public int v1;
protected int v2;
int v3;
private int v4;
}
class Cb{
...
}
//*包B:
class Cc extends Ca{
public int v1;
protected int v2;
int v3;
private int v4;
}
class Cd{
...
}
对于上述代码
Ca当然可以访问其自身的所有成员,包括v1\v2\v3\v4;
Cb可以访问Ca的v1\v2\v3;
Cc可以访问Ca的v1\v2;
Cb可以访问Ca的v1。
二、abstract修饰符
abstract可以用来修饰类和成员方法。
(1)用abstract修饰的类称为抽象类,抽象类无法实例化。但是:用abstract修饰的类中可以没有抽象方法,但包含了抽象方法的类必须被修饰为抽象类。如果子类没有实现父类的所有抽象方法,则子类也必须被定义为抽象类。
(2)用abstract修饰的方法表示抽象方法,抽象方法没有方法体。一般用来描述系统具有的功能,但不提供具体实现。对于构造方法和静态成员方法而言,不能用abstract修饰。
(3)抽象类中可以有构造方法,创建子类实例时可能会调用这个构造方法。
(4)抽象类不能被实例化,但可以定义一个抽象类类型的引用变量,引用其非抽象子类的实例对象。
(5)抽象类和方法都不能用final修饰。abstract和final修饰符不可以同时使用。(*为什么这么设计?设想一下,如果可以同时使用,那么对于这个类来说,因为用final修饰了,所以其不能有子类;而其又是abstract类,必须通过子类去实现。那这里final和abstract同时修饰的类是毫无意义的,因为无法实现,无法实例化。)
(6)抽象类不能被实例化的深入:
a、从逻辑上,抽象类用来表示一些从具体类中抽象出来的类型,比如生活里的水果类,其子类包括苹果类、桔子类。如果:
Fruit fruit=new Fruit(); //现实是没有水果这个东西的
Fruit fruit=new Apple(); //但可以有苹果这个具体的水果
b、从语法上,抽象类可能包括抽象方法,如果实现了抽象类的实例对象,那这个实例对象无法实现这个抽象方法。
三、final修饰符
final具有“不可改变”的含义,可以修饰非抽象类、非抽象成员方法和变量。
(1)用final修饰的类不能被继承,没有子类。
(2)用final修饰的方法不能被重写。
(3)用final修饰的变量表示常量,只能被赋一次值。
(4)final不能修饰构造方法。因为方法重写这个概念只适用于类的成员方法,而不适用于构造方法,用final修饰构造方法是毫无意义的。父类中private修饰的方法是不能被子类的方法覆盖的,所以private类型的方法默认是final类型的。
1、final类
Java中的继承关系容易打破封装,可以通过final修饰类进行类的保护:
(1)明确不会继承的类。
(2)出于安全的原因,类的实现细节不允许被改动。
比如JDK中java.lang.String类被定义为final类型:
public final class String{..}
2、final方法
某些情况下,父类不允许子类重写其某个方法,这种情况就可以把这个方法声明为final类型,如java.lang.Object类中,getClass()方法为final类型,而equals()不是final类型。
3、final变量
final修饰的变量表示取值不会改变的常量,具有以下特征:
(1)final可以修饰静态变量、实例变量和局部变量,分别代表静态常量、实例常量和局部常量。例如某个学校招生,每个学生都有姓名年龄,其中假设姓名永远不会改变,则用实例常量描述,年龄每年都会变化,用一般实例变量描述。该学校只招收年龄在10-18岁的学生。那对于这个限制年龄,因为对于每个学生都是一致的,并且不会变化的,因此考虑用静态常量去描述。
package tsjava;
public class Student{
public static final int MAX_AGE=18; //静态常量
public static final int MIN_AGE=10; //静态常量
private final String name; //私有实例常量
private int age;
public Student(String name,int age){
this.name=name;
if(age>MAX_AGE||age<MIN_AGE)
System.out.println("不符合入学年龄条件!");
this.age=age;
}
public void setAge(int age){
this.age=age;
}
static{
System.out.println("招生开始----------");
}
public static void main(String[] args){
Student s1=new Student("GTS",15);
Student s2=new Student("ShuJing",19);
Student s3=new Student("QQ",10);
s1.setAge(17);
System.out.println("学生1:"+s1.name+" "+s1.age+"岁");
System.out.println("学生1:"+s2.name+" "+s2.age+"岁");
System.out.println("学生1:"+s3.name+" "+s3.age+"岁");
}
}
(2)对于final修饰的变量:静态常量必须在声明时赋值;实例常量必须在声明时赋值,或者在构造方法中初始化。
(3)final变量只能赋一次值。对于下个示例:
public class Sample{
static final int MAX_LENGTH=10; //静态常量
final int vmax; //实例常量
Sample(){ //通过构造函数给实例常量赋值,合法
vmax=1;
}
Sample(int x){ //通过构造函数给实例常量赋值,合法
vmax=x;
}
/*虽然上述出现了两次给实例常量vmax赋值
*但创建对象时,只会执行一个构造方法
*所以上面代码是合法的
*/
(4)如果将引用类型的变量用final修饰,那么该变量只能引用一个对象,但可以改变对象的内容,示例:
public class Sample{
public int var;
public Sample(int var){this.var=var;}
public static void main(String[] args){
final Sample samp=new Sample();
samp.var=2; //合法,可以更改对象内容
samp=new Sample(); //编译出错,无法改变samp引用的对象(即指向地址不能变)
}
}
四、static修饰符
static可以修饰类的成员变量、成员方法和代码块。静态成员变量和静态成员方法可以通过类名来访问。对于静态代码块,当JVM加载类的时候,就会执行该代码块,并且只在加载时执行一次。
被static修饰的成员变量和方法表示归整个类及其所有实例对象所有。只要这个类被加载,JVM就可以根据类名在运行时数据区的方法区定位找到。
1、static变量
(1)静态变量在整个内存里只有一个拷贝,运行时JVM只为其分配一次内存。
(2)实例变量在创建其对应实例对象时候为该变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响。
示例:有一群选民进行投票,每个人只能投一次票,并且当投票达到100时就停止投票。针对这个例子可以抽象出Voter类,类的成员变量中包括选民的姓名(对应实例变量),累计投票次数每次都会被改变(对应静态变量),投票次数限制数100(对应静态常量)。
package tsjava;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
public class Voter {
private static final int MAX_COUNT=100; //最大投票数
private static int count; //累计投票数
private String name;
private static Set<Voter> voters=new HashSet<Voter>(); //投票人信息
Voter(String name){
this.name=name;
}
/*投票程序*/
public void voteFor(){
if(count==MAX_COUNT){
System.out.println("投票已经结束!");
return;
}
if(voters.contains(this)){
System.out.println("你不能重复投票!");
return;
}
count++;
voters.add(this);
System.out.println("感谢你的投票!");
}
/*打印投票结果*/
public static void printVoterResult(){
System.out.println("当前投票数为:"+count);
System.out.println("参与投票的选民如下:");
for(Voter v:voters){
System.out.println(v.name);
}
}
static{
System.out.println("-------------------------");
System.out.println("-----------开始投票---------");
System.out.println("-------------------------");
}
public static void main(String[] args){
Voter tom=new Voter("Tom");
Voter mike=new Voter("Mike");
Voter jack=new Voter("Jack");
tom.voteFor();
mike.voteFor();
tom.voteFor();
jack.voteFor();
Voter.printVoterResult();
}
}
2、static方法
(1)静态方法可访问的内容
因为静态方法不需要通过它所属的类的实例对象就可以调用,因此静态方法中不能使用this关键字,也不能直接访问所属类的实例变量和实例方法,但可以直接访问所属类的静态变量和静态方法。一般来说,需要创建一个针对整个类的方法则需要使用静态方法,如上例中的printVoteResult方法。
(2)实例方法可访问的内容。实例方法可以直接访问所属类的所有成员,不必显式地添加类名。
(3)静态方法必须被实现,即static和abstract不能同时使用。
(4)作为程序入口的main()方法是静态方法,具备前述静态方法的特点。将main()定义为static则在JVM加载其所属类的时候就可以执行main()方法,而无须先创建实例。
(5)方法的字节码都位于方法区:不管实例方法还是静态方法,在Java编译器把Java源程序编译成字节码后,存储在方法区。
3、static代码块
JVM加载类的时候会执行这些静态代码块,如果一个类包括多个静态代码块,则按其在代码中出现的次序依次执行。每个静态代码块只在类加载时候执行一次。静态代码块与静态方法一样,不能直接访问类的实例变量和实例方法,必须通过实例的引用去访问。