澳门新浦京娱乐游戏深入理解Java中的final关键字

本文由码农网 –
栗子蜀黍原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!

1     深入理解Java中的final关键字

方法重载

在Java中,同一个类中的2个或2个以上的方法可以有同一个名字,只要它们的参数声明不同即可。在这种情况下,该方法就被称为重载(
overloaded),这个过程称为方法重载( method overloading)。
方法重载是Java实现多态性的一种方式。

当一个重载方法被调用时,Java用参数的类型和(或)数量来表明实际调用的重载方法的版本。所以,每个重载方法的参数的类型和(或)数量必须是不同的。返回类型并不足以区分所使用的是哪个方法。

当一个重载的方法被调用时,Java在调用方法的参数和方法的自变量之间寻找匹配。但是,这种匹配并不总是精确的。在找不到精确匹配时,Java的自动类型转换(注意,是支持自动类型转换的才行)也适用于重载方法的自变量。

方法重载支持多态性,因为它是Java实现
“一个接口,多个方法”范型的一种方式。如,Java的标准的类库包含一个绝对值方法,叫做abs
(
)。这个方法被Java的math类重载,用于处理数字类型。Java根据参数类型决定调用的abs()的版本。

当你重载一个方法时,该方法的每个版本都能够执行你想要的任何动作。但是,在实际的编程中,你应该只重载相互之间关系紧密的操作。

简介:本文主要介绍java中不可变类的相关知识,文章中大部分内容来自博客,博客地址见以下链接

1.1   final关键字的含义?

final在Java中是一个保留的关键字,可以声明成员变量、方法、类以及本地变量。一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如果你试图将变量再次初始化的话,编译器会报编译错误。

 

澳门新浦京娱乐游戏,构造函数重载

构造函数也能够重载,并且很常见。

Creating Immutable Classes in
Java

1.2   什么是final变量?

凡是对成员变量或者本地变量(在方法中的或者代码块中的变量称为本地变量)声明为final的都叫作final变量。final变量经常和static关键字一起使用,作为常量。下面是final变量的例子:

1

2

public static final String LOAN = "loan";

LOAN = new String("loan") //invalid compilation error

final变量是只读的。

把对象作为参数

给方法传递对象是正确的,也是常用的。
当你开始创建你自己的类的时候,为了方便高效的构造对象,必须为同一构造函数方法提供多种形式。

小编不去纠结原文中,为什么标题是immutable class,正文却上来便问what is
immutable
object。相信,学java的人,都应该知道class和object确切表示什么意思。

1.3    什么是final方法?

final也可以声明方法。方法前面加上final关键字,代表这个方法不可以被子类的方法重写。如果你认为一个方法的功能已经足够完整了,子类中不需要改变的话,你可以声明此方法为final。final方法比非final方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。下面是final方法的例子:

 

 

参数是如何传递的

计算机语言给子程序传递参数的方法有两种。
第一种方法是按值传递( call-by-value)。这种方法将一个参数值(
value)复制成为子程序的正式参数。这样,对子程序的参数的改变不影响调用它的参数。
第二种传递参数的方法是引用调用(
call-by-reference)。在这种方法中,参数的引用(而不是参数值)被传递给子程序参数。在子程序中,该引用用来访问调用中指定的实际参数。这样,对子程序参数的改变将会影响调用子程序的参数。

当你给方法传递一个简单类型时,它是按值传递的。接收参数的子程序参数的改变不会影响到该方法之外。
当你给方法传递一个对象时,对象是通过引用传递的。当你创建一个类类型的变量时,你仅仅创建了一个类的引用。当你将这个引用传递给一个方法时,接收它的参数将会指向该参数指向的同一个对象。

当一个对象引用被传递给方法时,引用本身使用按值调用被传递。但是,因为被传递的值指向一个对象,该值的拷贝仍然指向它相应的参数所指向的同一个对象。

class:java中class确切的表示为一个类

1.4    什么是final类?

使用final来修饰的类叫作final类。final类通常功能是完整的,它们不能被继承。Java中有许多类是final的,譬如String,
Interger以及其他包装类。下面是final类的实例:

 

返回对象

方法能够返回任何类型的数据,包括你创建的类的类型。

既然所有的对象用关键字new动态地分配内存,你不必担心一个对象会出范围,因为它被其创建的方法终止。只要你程序中有它的一个引用,该对象将会继续存在。当没有该对象的引用时,在下一次垃圾回收发生时该对象将被回收。

object:java中object确切的表示为一个对象,也称为类的实例

1.5    final关键字的好处

下面总结了一些使用final关键字的好处

  1. final关键字提高了性能。JVM和Java应用都会缓存final变量。
  2. final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
  3. 使用final关键字,JVM会对方法、变量及类进行优化。

递归

递归就是依照自身定义事物的过程。在Java编程中,递归是允许方法调用自身调用的属性。调用自身的方法称为是递归的(
recursive)。

当一个方法调用它自身的时候,堆栈就会给新的局部变量和自变量分配内存,方法代码就带着这些新的变量从头执行。递归调用并不产生方法新的拷贝。只有参数是新的。每当递归调用返回时,旧的局部变量和自变量就从堆栈中清除,运行从方法中的调用点重新开始。

许多子程序的递归版本执行时会比它们的迭代版本要慢一点,因为它们增加了额外的方法调用的消耗。对一个方法太多的递归调用会引起堆栈崩溃。因为自变量和局部变量的存储都在堆栈中,每次调用都创建这些变量新的拷贝,堆栈有可能被耗尽。

递归的主要优点在于:某些类型的算法采用递归比采用迭代算法要更加清晰和简单。

其实,如果一个类被设计成不可变的类,那么这个类的实例化对象也是不可变的。

1.5.1 不可变类

创建不可变类要使用final关键字。不可变类是指它的对象一旦被创建了就不能被更改了。String是不可变类的代表。不可变类有很多好处,譬如它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销等等。

介绍访问控制

封装也提供另一个重要属性:访问控制( access
control)。通过封装你可以控制程序的哪一部分可以访问类的成员。通过控制访问,可以阻止对象的滥用。

Java的访问指示符有public(公共的,全局的)、private(私有的,局部的)、和protected(受保护的)。Java也定义了一个默认访问级别,public。指示符protected仅用于继承情况中。

当一个类成员被public指示符修饰时,该成员可以被你的程序中的任何其他代码访问。当一个类成员被指定为private时,该成员只能被它的类中的其他成员访问。

成员声明语句必须以访问指示符开头。

在大多数实际应用的类中,你将有必要仅仅允许通过方法来对数据操作。

不可变类:当你获得这个类的一个实例引用时,你不可以改变这个实例的内容。

1.5.1.1  为什么String是不可变的

String是所有语言中最常用的一个类。我们知道在Java中,String是不可变的、final的。Java在运行时也保存了一个字符串池(String
pool),这使得String成为了一个特别的类。

String类不可变性的好处

  1. 只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String
    interning将不能实现(译者注:String
    interning是指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串。),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。
  2. 如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
  3. 因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
  4. 类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。
  5. 因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

以上就是我总结的字符串不可变性的好处。

理解static

如果一个成员被声明为static,它就能够在它的类的任何对象创建之前被访问,而不必引用任何对象。你可以将方法和变量都声明为static。

声明为static的变量实质上就是全局变量。当声明一个对象时,并不产生static变量的拷贝,而是该类所有的实例变量共用同一个static变量。

声明为static的方法有以下几条限制:

  • 它们仅能调用其他的static方法。
  • 它们只能访问static数据。
  • 它们不能以任何方式引用this或super

如果你需要通过计算来初始化你的static变量,你可以声明一个static块,Static块仅在该类被加载时执行一次。
如,

class UseStatic {
    static int a = 3;
    static int b;

    static void meth(int x) {
        System.out.println("x = " + x);
        System.out.println("a = " + a);
        System.out.println("b = " + b);
    }

    static {
        System.out.println("Static block initialized.");
        b = a * 4;
    }

    public static void main(String args[]) {
        meth(42);
    }
}

运行结果:
Static block initialized.
x = 42
a = 3
b = 12

一旦UseStatic类被装载,所有的static语句被运行。首先,a被设置为3,接着static块执行(打印一条消息),最后,b被初始化为a*4或12。然后调用main(),main()调用meth(),把值42传递给x。3个println
( )语句引用两个static变量a和b,以及局部变量x 。

一个static变量可以通过定义该static方法的类名加点号运算符的方法访问。

那么,什么是不可变对象?

一旦一个类的实例化对象被创建并初始化,那么它就不可以被改变。我们可以调用访问器方法(getter),复制对象,或者传递对象,但是不允许任何方法改变这个对象的状态。包装类(e.g.Integer或Float)和String类是不可变类的代表。

访问器方法(accessor
method):对成员变量做出访问的方法,e.g.getter()方法。

修改器方法(mutator method):对成员变量做出修改的方法,e.g.setter()方法。

1.5.1.2  如何写一个不可变类?

不可变对象对于缓存是非常好的选择,因为你不需要担心它的值会被更改。不可变类的另外一个好处是它自身是线程安全的,你不需要考虑多线程环境下的线程安全问题。

 

n  下面是创建不可变类的方法,我也给出了代码,加深理解。

 

  1. 将类声明为final,所以它不能被继承
  2. 将所有的成员声明为私有的,这样就不允许直接访问这些成员
  3. 对变量不要提供setter方法
  4. 将所有可变的成员声明为final,这样只能对它们赋值一次
  5. 通过构造器初始化所有成员,进行深拷贝(deep copy)
  6. 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝
  7. 为了理解第5和第6条,我将使用FinalClassExample来阐明。

 

FinalClassExample.java

package com.journaldev.java;

 

import java.util.HashMap;

import java.util.Iterator;

 

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)的构造器

     * @param i

     * @param n

     * @param hm

     */

 

    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)的构造器

     * @param i

     * @param n

     * @param hm

     */

    /**

    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方法中返回的是原本对象的引用。

介绍final

在声明final变量的时候,必须进行初始化。

final变量的所有的字符选择大写是一个普遍的编码约定。

声明为final的变量在实例中不占用内存。这样,一个final变量实质上是一个常数。

定义一个不可变类

如果我们要自己创建一个不可变类,需要遵守下面的规则:

将成员变量(field:在一些书中也翻译为域)声明成final并在构造器中初始化。

对于基本类型的成员变量,用final修饰,一旦它被初始化,就不能被改变了。而对于引用类型的成员变量,不能够改变它的引用。

成员变量如果被声明称final,那么构建对象时,必须要初始化这样的域

引用类型是可变的,我们需要采取一些措施来保证它的不可变性。

为什么?如果我们只是声明了一个final的可变引用类型,那么这个引用可以去引用外部的类,或者被其他外部类引用。在这种情况下,我们要做到:

1.这些方法不会改变这些可变对象中的内容

2.不要将这些引用分享到外部供其他类使用,例如,如果对成员变量的引用是可以被其他类改变的,那么这些外部类就可以改变这个类中的内容。

3.如果必须要返回一个引用,那么就返回一个对象的深度拷贝,这样尽管返回的对象内容改变了,但也保存着原始的内容。

只提供访问器方法(i.e. getter方法)不提供修改器方法(i.e.setter方法)

如果一定要改变这个对象的内容,那就创建一个新的不可变对象内容做相应的修改,返回修改后的对象的引用

声明类是final的。如果一个类可以被继承,那么它子类就可以重载它的方法,并且修改成员变量

1.5.2 关于final的重要知识点

  1. final关键字可以用于成员变量、本地变量、方法以及类。
  2. final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
  3. 你不能够对final变量再次赋值。
  4. 本地变量必须在声明时赋值。
  5. 在匿名类中所有变量都必须是final变量。
  6. final方法不能被重写。
  7. final类不能被继承。
  8. final关键字不同于finally关键字,后者用于异常处理。
  9. final关键字容易与finalize()方法搞混,后者是在Object类中定义的方法,是在垃圾回收之前被JVM调用的方法。
  10. 接口中声明的所有变量本身是final的。
  11. final和abstract这两个关键字是反相关的,final类就不可能是abstract的。
  12. final方法在编译阶段绑定,称为静态绑定(static binding)。
  13. 没有在声明时初始化final变量的称为空白final变量(blank final
    variable),它们必须在构造器中初始化,或者调用this()初始化。不这么做的话,编译器会报错“final变量(变量名)需要进行初始化”。
  14. 将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
  15. 按照Java代码惯例,final变量就是常量,而且通常常量名要大写:

private final int COUNT = ``10;

  1. 对于集合对象声明为final指的是引用不能被更改,但是你可以向其中增加,删除或者改变内容。譬如:

 

我们已经知道final变量、final方法以及final类是什么了。必要的时候使用final,能写出更快、更好的代码的。

 

介绍嵌套类和内部类

在另一个类中定义的类就是嵌套类( nested
classes)。嵌套类的范围由装入它的类的范围限制。

嵌套类可以访问嵌套它的类的成员,包括private成员。但是,包围类不能访问嵌套类的成员。
如,

class Outer {
    int outer_x = 100;

    void test() {
        Inner inner = new Inner();
        inner.display();
    }

    class Inner {
        int y = 10; // y is local to Inner

        void display() {
            System.out.println("display: outer_x = " + outer_x);
        }
    }

    void showy() {
        System.out.println(y); // error,y not known here!
    }
}

class InnerClassDemo {
    public static void main(String args[]) {
        Outer outer = new Outer();
        outer.test();
    }
}

嵌套类一般有2种类型:前面加static标识符的和不加static标识符的。
加static标识符的嵌套类,只能通过对象来访问它包围类的成员。所以static嵌套类很少使用。
非static的嵌套类又称为内部类( inner
class)。它可以访问它的外部类的所有变量和方法,它可以直接引用它们,就像外部类中的其他非static成员的功能一样。

内部类只有在外部类的范围内才是可知的。

尽管我们强调嵌套类在它的外部类的范围之内声明,但在几个程序块的范围之内定义内部类是可能的。如,在由方法定义的块中,或甚至在for循环体内部,你也可以定义嵌套类。

尽管嵌套类在日常的大多数编程中不使用,但当处理applet(小应用程序)时是特别有帮助的。

Java API中不可变类的例子

让我们来回顾一下String类,用它来理解上述的几个方面在String类实现中的体现:

所有在Stirng类中成员变量都被声明成private,这些成员变量都在构造器中在构建对象时被初始化。

trim concat substring
都可以改变String的对象,为了保证String的不可变性,这些方法都返回的是一个改变相应内容后新的对象。

string类被声明称final,所以任何类都不能继承,重载它的方法。

探索String类

有关字符串的最重要一点是,你创建的每一个字符串实际上都是String类型的一个对象,即使是字符串常量实际上也是String对象。

Java定义了一个和String类同等的类叫StringBuffer,它允许字符串改变。

Java定义了一个String对象的运算符:“ +”。它用来连接两个字符串。

你可以用equals()来检验两个字符串是否相等。你可以调用方法length()来获得一个字符串的长度。你可以调用charAt()来获得一个字符串指定索引的字符。这三个方法的通用格式如下所示:
boolean equals(String object)
int length( )
char charAt(int index)

如,

class Main{
    public static void main(String[] args){
        String art = "hello";
        String art2 = "hello";
        boolean boo = art.equals(art);
        System.out.println(boo);
        System.out.println(art.charAt(2));
        System.out.println(art.length());
    }
}

运行结果:
true
l
5

与其他对象类型一样,strings也可以组成数组,例如:

class Main {
    public static void main(String args[]) {
        String str[] = { "one","two","three" };
        for(int i=0; i<str.length; i++)
        System.out.println("str[" + i + "]: " +
        str[i]);
        }
}

运行结果:
str[0]: one
str[1]: two
str[2]: three

自己实现一个不可变类

接下来我们自己实现一个不可变类ImmutableCircle。

//ImmutableCircle.java
// Point is a mutable class
class Point {
    private int xPos, yPos;
    public Point(int x, int y) {
       xPos = x;
       yPos = y;
    }
    public String toString() {
        return "x = " + xPos + ", y = " + yPos;
    }
    int getX() { return xPos; }
    int getY() { return yPos; }
}
// ImmutableCircle is an immutable class – the state of its objects
// cannot be modified once the object is created
public final class ImmutableCircle {
private final Point center;
private final int radius;
public ImmutableCircle(int x, int y, int r) {
center = new Point(x, y);
radius = r;
}
public String toString() {
return "center: " + center + " and radius = " + radius;
}
public int getRadius() {
return radius;
}
public Point getCenter() {
// return a copy of the object to avoid
// the value of center changed from code outside the class
return new Point(center.getX(), center.getY());
}
public static void main(String []s) {
System.out.println(new ImmutableCircle(10, 10, 20));
}
// other members are elided ...
}

上面的程序运行之后,打印:

center: x = 10, y = 10 and radius = 20

上面的程序体现了不可变类的以下几点:

  • 这个类被声明成final,不可以被继承,也不可以重载它的方法
  • 这个类的成员变量都是final并且是私有的
  • 因为成员变量center是一个引用类型,是可变的,所以在他的getter方法中,返回的是对point对象的拷贝

设计一个不可变的类最关键的一点:

要注意引用类型的成员变量,如果成员变量的类型是可变的引用类型,就必须要采取必要的措施来保护这个成员变量不会被修改

不可变类不足的地方

不可变对象同样也有不足的地方。为了保证不可变性,不可变类中的方法会创建出一定量的对象的拷贝。例如,在上面的代码中,每次调用getcenter方法都会新建并返回一个point对象的拷贝。而假如我们只需要调用一次,返回一个point对象,就没必要费尽心神的去设计一个不可变类,仅仅只需要一个可变的immutablecircle类就可以了。

String类在很多应用场景中都会用到,如果我们调用String类中trim,concat,或者是在循环中调用substring方法,都会创建一个新的临时String对象。同时,java也提供了Stringbuffer和Stringbuilder的可变类。他们同String一样,但是却可以改变这个对象的内容。所以,我们可以根据不同的场景使用String类或者Stringbuffer/Stringbuilder类。

总结,文章的最后还是那句话,要根据自己的实际需要,去设计代码,而不要过度设计。

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图