Java基础知识笔记-5-Java语言中的修饰符

Java基础知识笔记-5-Java语言中的修饰符

5 Java语言中的修饰符

表中的类仅限于顶层类,而不包括内部类。内部类是指定义在类或方法中的类。

修饰符 成员方法 构造方法 成员变量 局部变量
abstract Y Y - - -
static - Y - Y -
pubic Y Y Y Y -
protected - Y Y Y -
private - Y Y Y -
final Y Y - Y Y

从上表可以看出:修饰顶层类的修饰符包括:abstract,public,final,而static,protected,private不能修饰顶层类。成员方法和成员变量可以有多种修饰符,而局部变量只能用final来修饰。

1 访问控制修饰符

面向对象的基本思想之一是封装实现细节并且公开接口。java语言采用访问控制符来控制类,以及类的方法和访问权限,从而向使用者只暴露接口,但隐藏实现细节,访问控制分四种级别:

  • 公开级别:用public修饰,对外公开
  • 受保护级别:用protected修饰,向子类及同一个包中的类公开
  • 默认级别:没有访问控制修饰符,向同一个包中的类公开
  • 私有级别:用Private修饰,只有类本身可以访问,不对外公开
访问级别 访问控制修饰符 同类 同包 子类 不同的包
公开 public Y Y Y Y
受保护 protected Y - Y -
默认 Y Y - -
私有 Private Y - - -

成员变量,成员方法和构造方法可以处于4个访问级别中的一个:公开,受保护,默认或者私有。顶层类只可以处于公开或默认访问级别,因此顶层类不能用private和protected来修饰,以下代码会导致编译错误:

private class Sample{...};

2 abstract 修饰符

如果自下而上在类的继承层次结构中上移,位于上层的类更具有通用性,甚至可能更加抽象。从某种角度看,祖先类更加通用,人们只将它作为派生其他类的基类,而不作为想使用的特定的实例类。例如,考虑一下对Employee类层次的扩展。一名雇员是一个人,一名学生也是一个人。下面将类Person和类Student添加到类的层次结构中。

为什么要花费精力进行这样高层次的抽象呢?每个人都有一些诸如姓名这样的属性。学生与雇员都有姓名属性, 因此可以将getName法放置在位于继承关系较高层次的通用超类中。

现在,再增加一个getDescription方法,它可以返回对一个人的简短描述。例如:

an employee with a salary of $50,000.00
a student majoring in computer science

在Employee类和Student类中实现这个方法很容易。但是在Person类中应该提供什么内容呢?除了姓名之外,Person类一无所知。当然,可以让Person.getDescription()返回一个空字符串。然而,还有一个更好的方法,就是使用abstract关键字,这样就完全不需要实现这个方法了。

public abstract String getDescription();
// no implementation required

为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。

public abstract class Person {
    public abstract String getDescription();
}

除了抽象方法之外,抽象类还可以包含具体数据和具体方法。例如,Person类还保存着姓名和一个返回姓名的具体方法。

public abstract class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
    public abstract String getDescription();
        public String getName() {
            return name;
    }
}

抽象方法充当着占位的角色,它们的具体实现在子类中。扩展抽象类可以有两种选择。一种是在抽象类中定义部分抽象类方法或不定义抽象类方法,这样就必须将子类也标记为抽象类;另一种是定义全部的抽象方法,这样一来,子类就不是抽象的了。

例如,通过扩展抽象Person类,并实现getDescription方法来定义Student类。由于在Student类中不再含有抽象方法,所以不必将这个类声明为抽象的。

类即使不含抽象方法,也可以将类声明为抽象类。

抽象类不能被实例化。也就是说,如果将一个类声明为abstract,就不能创建这个类的对象。例如,表达式

new Person("Vinee Vu")

是错误的,但可以创建一个具体子类的对象。

需要注意,可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象。例如,

Person p = new Student("Vinee Vu", "Economics");

这里的p是一个抽象类Person的变量,Person引用了一个非抽象子类Student的实例

abstract修饰符可用来修饰类和成员方法;

  • 用abstract修饰的类表示抽象类,抽象类位于继承树的抽象层,抽象类不能被实例化,既不允许创建抽象类本身的实例。
  • 用abstract修饰的方法表示抽象方法,抽象方法没有方法体,抽象方法用来描述系统具有什么功能,但不提供具体的实现,没有用abstract修饰的方法称为具体方法,具体方法具有方法体。

abstract类和abstract方法

用关键字abstract修饰的类称为abstract类(抽象类)

如:

abstract class A {
}

用关键字abstract修饰的方法称为abstract方法(抽象方法),例如

abstract int min(int x,int y);

对于abstract方法,只允许声明,不允许实现(没有方法体),而且不允许使用abstract和final同时修饰一个方法和类。

使用abstract修饰符需要遵守以下语法规则

1.抽象类中可以没有抽象方法,但包含了抽象方法的类必须被定义为抽象类。如果子类没有实现父类中所有的抽象方法,那么子类也必须定义为抽象类,否则编译会出错。

2.没有抽象静态方法,static和abstract关键字不可在一起使用。

3.抽象类中可以有非抽象的构造方法,创建子类的实例可能会调用这些构造方法。抽象类不能被实例化,然而可以创建一个引用变量,其类型是一个抽象类,并让他引用非抽象的子类的一个实例。

4.抽象类及抽象方法不能被final修饰符修饰,abstract修饰符与final修饰符不能连用,因为抽象类只允许创建其子类,他的抽象方法才能被实现,并且只有他的具体子类才能被实例化,而用final修饰的类不允许拥有子类,用final修饰的方法不允许被子类方法覆盖,因此两者连用,会自相矛盾。

抽象类的一个重要特征是不允许实例化,比如苹果香蕉是具体类,而水果是抽象类一样。在自然界中并不存在水果类本身的实例,只存在它的具体子类的实例:

Fruit fruit=new Apple();

在继承树上,总可以把子类的对象看作父类的对象。当父类是具体类,父类的对象包括父类本身的对象,以及所有具体子类的对象;当父类是抽象类,父类的对象包括所有具体子类的对象,因此,所谓的抽象类不能实例化,是指不能创建抽象类本身的实例,尽管如此,可以创建一苹果对象,并把它看作是水果对象。

5.抽象方法不能被private修饰符修饰,这是因为如果方法是抽象的,表示父类只申明具备某种功能,但没有提供实现,这种方法有待于某个子类去实现它,父类中的abstract方法必须让子类是可见的。否则,在父类中声明一个永远无法实现的方法是无意义的,假如java允许把父类的方法同时用abstract和private修饰,那就意味着在父类中声明一个永远无法实现的方法,所以在java中不允许出现这一情况。

注意:abstract类也可以没有abstract方法。

如果一个abstract类是abstract类的子类,它可以重写父类的abstract方法,也可以继承这个abstract方法。

abstract类的子类如果不是abstract的,那么它就可以被实例化,这会导致执行该abstract类的构造器,进而执行其用于实例变量的域初始化器。


3 final修饰符

final具有不可改变的含义,它可以用来修饰非抽象类,非抽象对象成员,和变量。

  • 用final修饰的类不能被继承,没有子类
  • 用final修饰的方法不能被子类的方法覆盖
  • 用final修饰的变量表示常量,只能被赋一次值

final不能用来修饰构造方法,因为“方法覆盖”这一概念仅适用于类的成员方法,而不适用于类的构造方法,父类的构造方法和子类的构造方法之间不存在覆盖关系,因此用final修饰构造方法是无意义的。父类中的private修饰的方法不能被子类的方法覆盖,因此private类型的方法默认是final类型的。

将方法或类声明为final主要目的是:确保它们不会在子类中改变语义。例如,Calendar类中的getTime和setTime方法都声明为final。这表明Calendar类的设计者负责实现Date类与日历状态之间的转换,而不允许子类处理这些问题。同样地,String 类也是final类,这意味着不允许任何人定义String的子类。换言之,如果有一个String的引用,它引用的一定是一个String对象,而不可能是其他类的对象。

3.1 final类

继承关系的弱点是打破封装,子类能够访问父类的实现细节,而且能以方法覆盖的方式修改实现细节,在以下情况,可以考虑把类定义为final类型,使得这个类不能被继承。final类中的所有方法自动地成为final方法。

  • 不是专门为继承而设计的类,类本身的方法之间有复杂的调用关系,假如随意创建这些类的子类,子类有可能会错误的修改父类的实现细节。
  • 处于安全的原因,类的实现细节不允许有任何改动
  • 在创建对象模型时,确信这个类不会再被扩展

例如,JDK中的java.lang.String类被定义为final类:

public final class String {
    ···
}

如果其他类继承String类,就会导致编译错误

3.2 final方法

在某些情况下,处于安全的原因,子类不允许子类覆盖某个方法,此时可以把这个方法声明为final类型。例如在java.lang.Object类中,getClass()方法为final类型,而equals()方法不是final类型的:

public class Object {
    public final Class getClass() {
        ...
    }
    public boolean equal(Object o) {
        ...
    }
}

所有Object方法都可以覆盖equals()方法,但不能覆盖getClass()方法。否则就会导致编译错误。

3.3 final变量

用final修饰的变量表示取值不会改变的常量。

例如在java.lang.Integer类中定义了两个常量:

public static final int MAX_VALUE=2147483647;
public staric final int MIN_VALUE=-2147483647;

final变量具有以下特性:

1.final修饰符可以修饰静态变量,实例变量和局部变量,分别表示静态变量,实例变量和局部变量。

2.在成员变量的初始化那一节,曾经提到类的成员变量可以不必显式初始化,但是这不适用于final类型的成员变量。final类型的成员变量必须显式初始化,否则会导致编译错误。

例如:

public class Sample {
    static final int a=1;
    static final int b;
    static {
        b=1;//合法
    }
}

3.final变量只能赋一次值

例如:

public class Sample {
    private final int var1=1;
    public Sample() {
        var1=2;  //错误,不允许改变实例变量的值
    }
        public void method(final int param){
        final int var2=1;
        var2+=;  //编译错误
        param++;  //编译错误,不允许改变final类型参数的值
    }
}

以下Sample类的var1实例常量分别在Samlpe()和Sample(int x)这两个构造方法中初始化,这是合法的。因为在创建Sample对象时,只会执行一个构造方法,所以var1实例常量不会被初始化两次:

class Sample {
    final int var1;//定义实例常量
    final int var2=0;//定义并初始化var2实例常量
    Sample() {
        var1=1;//初始var1化实例常量
    }
    Sample(int x) {
        var1=x;//初始化var1实例常量
    }
}

4.如果将引用类型的变量用final修饰,那么该变量只能始终引用一个对象,但可以改变对象的内容,例如:

public class Sample {
    public int var;
    public Sample(int var) {
        this.var=var;
    }
    public static void main(String args[]){
        final Sample s=new Sample(1);
        s.var=2;//合法,修改引用变量s所引用的Sample对象的var属性
        s=new Sample(2);//编译出错,不能改变引用变量s所引用的Sample对象
    }
}

在程序中通过final修饰符来定义常量,具有以下作用:

  • 提高代码的安全性,禁止非法修改取值固定并且不允许改变的数据
  • 提高程序代码的可维护性
  • 提高程序代码的可读性

4 static修饰符

static修饰符可以用来修饰类的成员变量,成员方法和代码块:

  • 用static修饰的成员变量表示静态变量,可以直接通过类名来访问。
  • 用static修饰的成员方法表示静态方法,可以直接通过类名来访问。
  • 用static修饰的程序代码块表示静态代码块,当Java虚拟机加载类时,就会执行该代码块。

被static所修饰的成员变量和成员方法表明归某个类所有,它不依赖于类的特定实例,被类的所有实例共享,只要这个类被加载,java虚拟机就能根据类名在运行时数据区的方法区内定位到它们。

4.1 static变量

成员变量有两种,一种是被static修饰的变量叫类变量,或者静态变量,一种是没有被static修饰的变量,叫实例变量。

区别如下:

  • 静态变量在内存中只有一个备份,运行时java虚拟机只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配,可以直接通过类名访问静态变量。

  • 对于实例变量,没创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个备份,互不影响。

在类的内部,可以在任何方法内直接访问静态变量,在其他类中,可以通过某个类的类名来访它他的静态变量。

例如:

public class Sample{
    public static int number;//定义一个静态变量
    public void method(){
        int x=number;//在类的内部访问number静态变量
    }
}
public class Sample{
    public void method(){
        int x=Sample.number;//通过类名来访问number静态变量
    }
}

static变量在某种程度上与其他语言比如C语言中的全局变量相似,Java语言不支持不属于任何类的全局变量就,静态变量提供了这一功能,他有两个作用:

  • 能被类的所有实例共享,可作为实例之间进行交流的共享数据
  • 如果类的所有实例都包括一个相同的常量属性,可以把这个属性定义为静态常量类型,从而节省内存空间。

4.2 static方法

成员方法分为静态方法和实例方法,用static修饰的方法叫做静态方法或者类方法。静态方法和静态变量一样,不需要创建类的实例,可以直接通过类名来访问,例如:

public class Sample{
    public static int add(int x,int y){//静态方法
        return x+y;
    }
    public class Sample{
    public void Sample{
    public void method(){
            int result=Sample.add(1,2);//调用Sample类的add()静态
            System.out.println("result");
        }
    }
}

1.静态方法可访问的内容

因为静态方法不需要通过它所属的类的任何实例就会被调用,因此静态方法中不能使用this关键字,也不能直接访问所属类的实例变量和实例方法,但是可以直接访问所属类的静态变量和静态方法。

2.实例方法可以访问的内容

如果一个类没有被static修饰,那么它就是实例方法。在实例方法中可以直接访问所属类的静态变量,静态方法,实例变量,实例方法。

实例方法的例子多次已举,不必再举

3.静态方法必须被实现

静态方法用来表示某个类所特有的功能,这种功能的实现不依赖于类的具体实例,也不依赖于它的子类。既然如此,当前类必须为静态方法提供实现。换句话说,一个静态方法不能被定义为抽象方法。以下方法的定义是非法的:

static abstract void method();//编译出错,不能连用

如果一个方法是静态的,它就必须自己实现该方法;如果一个方法是抽象的,那么它就只表示类所具有的功能,但不会实现它,在子类中才会实现它。

4.作为程序入口的main()方法是静态方法

这样使得java虚拟机只要加载了main()方法所属的类,就能执行main方法,而无须先创建这个类的实例。

在main()静态方法中不能直接访问实例变量和实例方法。比如如下编译错误:

public class Sample{
    int x;
    void method(){
    }
    public static void main(String args[]){
        x=9;//编译错误
        this.x=9;//编译错误
        method();//编译错误
        this.method();//编译错误
    }
}

正确的做法是通过Sample实例的引用来访问实例方法和实例变量

public class Sample{
    int x;
    public static void main(String args[]){
        Sample s=new Sample();
        s.x=9;
        s.method();//合法
    }
}

5.方法的字节码都位于方法区

不管是实例方法,还是静态方法,他们的字节码都位于方方法区,Java编译器把Java方法的源程序代码编译成二进制的编码,成为字节码,Java虚拟机的解析器能够解析这种字节码。

注意:以下两点只需要了解,不需要掌握

4.3 static代码块

类中可以包含静态代码块,它不存在与任何方法体中,Java虚拟机加载时,会执行这些静态代码块,如果类中包含多个代码块,那么Java虚拟机按他们在类中出现的顺序依次执行他们,每个静态代码块只会被执行一次。

4.4 用static进行静态导入

从JDK5开始引入了静态导入语法,其目的是为了在需要经常访问同一个类的方法或成员变量的场合,简化程序代码。

猜你喜欢

转载自www.cnblogs.com/whatsabc/p/11489377.html