【Java】类的访问修饰符(Access Qualifier)

Posted by 西维蜀黍 on 2019-04-01, Last Modified on 2021-10-02

abstract - 抽象类

凡是用修饰符 abstract修饰的类,被称为抽象类。

抽象类与抽象方法

在了解抽象类之前,先来了解一下抽象方法。抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。抽象方法的声明格式为:

abstract void fun();

抽象方法必须用abstract关键字进行修饰。

如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。

因为抽象类中含有无具体实现的方法,所以不能将抽象类创建对象实例。


下面要注意一个问题:在《Java编程思想》一书中,将抽象类定义为“包含抽象方法的类”,但是后面发现如果一个类不包含抽象方法,只是用abstract修饰的话也是抽象类。也就是说抽象类不一定必须含有抽象方法

个人觉得这个属于钻牛角尖的问题吧,因为如果一个抽象类不包含任何抽象方法,为何还要设计为抽象类?所以暂且记住这个概念吧,不必去深究为什么。

abstract class ClassName {
    abstract void fun();
}

从这里可以看出,抽象类就是为了继承而存在的

如果你定义了一个抽象类,却不去继承它,那么等于白白创建了这个抽象类,因为你不能用它来做任何事情。

对于一个父类,如果它的某个方法在父类中实现出来没有任何意义,必须根据子类的实际需求来进行不同的实现,那么就可以将这个方法声明为abstract方法,此时这个类也就成为abstract类了。

包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。

注意,抽象类和普通类的主要有三点区别:

  • 抽象方法必须为public或者protected,缺省情况下默认为public(因为如果为private,则不能被子类继承,子类便无法实现该方法)。
  • 不能创建抽象类的实例对象;
  • 如果一个类继承于一个抽象类,则子类必须实现父类的所有抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。

final - 最终类

当一个类被final修饰时,该类不能被继承

子类继承往往可以重写父类的方法和改变父类属性,会带来一定的安全隐患。因此,当一个类不希望被继承时就,可以使用final修饰。

被定义为 final 类通常是一些有固定作用、用来完成某种标准功能的类。如Java系统定义好的用来实现网络功能的InetAddress、Socket等类都是 final类。

注意修饰符 abstract 和修饰符 final 不能同时修饰同一个类,因为abstract类是没有具体对象的类,它必须有子类,即就是是用来被继承的;而 final类是不可能有子类的类,所以用abstract和final修饰同一个类是无意义的。

最终类

最终类的意思是创建该类的实例后,该实例变量是不可改变的。

最终类有很多好处,譬如它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销等等。

条件

声明一个最终类,需要满足以下条件:

  1. 使用private和final修饰符来修饰该类的所有成员变量
  2. 提供带参的构造器用于初始化类的成员变量;
  3. 仅为该类的成员变量提供getter方法,不提供setter方法,因为普通方法无法修改fina修饰的成员变量;
  4. 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝;
  5. 通过构造器初始化所有成员时,进行深拷贝(deep copy);
  6. 如果有必要就重写Object类的hashCode()和equals()方法,应该保证用equals()判断相同的两个对象其Hashcode值也是相等的。

例子

public final class FinalClassExample {
    private final int id;
    private final String name;
    private final HashMap testMap;
 
    public int getId() {
        return id;
    }
    public String getName() {
        return name;
    }
 
    /**
     * 可变对象的访问方法
     */
    public HashMap getTestMap() {
        //return testMap;
        return (HashMap) testMap.clone();
    }
 
    /**
     * 实现深拷贝(deep copy)的构造器
     */
    public FinalClassExample(int i, String n, HashMap hm){
        System.out.println("Performing Deep Copy for Object initialization");
        this.id=i;
        this.name=n;
        HashMap tempMap=new HashMap();
        String key;
        Iterator it = hm.keySet().iterator();
        while(it.hasNext()){
            key=it.next();
            tempMap.put(key, hm.get(key));
        }
        this.testMap=tempMap;
    }
 
    /**
     * 实现浅拷贝(shallow copy)的构造器
     */
    /**
    public FinalClassExample(int i, String n, HashMap hm){
        System.out.println("Performing Shallow Copy for Object initialization");
        this.id=i;
        this.name=n;
        this.testMap=hm;
    }
    */
 
    /**
     * 测试浅拷贝的结果
     * 为了创建最终类,要使用深拷贝
     * @param args
     */
    public static void main(String[] args) {
        HashMap h1 = new HashMap();
        h1.put("1", "first");
        h1.put("2", "second");
        String s = "original";
        int i=10;
 
        FinalClassExample ce = new FinalClassExample(i,s,h1);
 
        //Lets see whether its copy by field or reference
        System.out.println(s==ce.getName());
        System.out.println(h1 == ce.getTestMap());
        //print the ce values
        System.out.println("ce id:"+ce.getId());
        System.out.println("ce name:"+ce.getName());
        System.out.println("ce testMap:"+ce.getTestMap());
        
        //change the local variable values
        i=20;
        s="modified";
        h1.put("3", "third");
        
        //print the values again
        System.out.println("ce id after local variable change:"+ce.getId());
        System.out.println("ce name after local variable change:"+ce.getName());
        System.out.println("ce testMap after local variable change:"+ce.getTestMap());
 
        HashMap hmTest = ce.getTestMap();
        hmTest.put("4", "new");
 
        System.out.println("ce testMap after changing variable from accessor methods:"+ce.getTestMap());
    }
}

输出

Performing Deep Copy for Object initialization
true
false
ce id:10
ce name:original
ce testMap:{2=second, 1=first}
ce id after local variable change:10
ce name after local variable change:original
ce testMap after local variable change:{2=second, 1=first}
ce testMap after changing variable from accessor methods:{2=second, 1=first}

现在我们注释掉深拷贝的构造器,取消对浅拷贝构造器的注释。也对getTestMap()方法中的返回语句取消注释,返回实际的对象引用。然后再一次执行代码。

Performing Shallow Copy for Object initialization
true
true
ce id:10
ce name:original
ce testMap:{2=second, 1=first}
ce id after local variable change:10
ce name after local variable change:original
ce testMap after local variable change:{3=third, 2=second, 1=first}
ce testMap after changing variable from accessor methods:{3=third, 2=second, 1=first, 4=new}

从输出可以看出,HashMap的值被更改了,因为构造器实现的是浅拷贝,而且在getter方法中返回的是原本对象的引用。


JDK中提供的八个包装类和String类都是最终类,我们来看看String的实现。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

    /**
     * Class String is special cased within the Serialization Stream Protocol.
     *
     * A String instance is written into an ObjectOutputStream according to
     * <a href="{@docRoot}/../platform/serialization/spec/output.html">
     * Object Serialization Specification, Section 6.2, "Stream Elements"</a>
     */
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];
    ...
}

可以看出String的value就是final修饰的,上述其他几条性质也是吻合的。

Reference