zoukankan      html  css  js  c++  java
  • 2018年3月31日JAVA

    一.final关键字

    (1)final修饰类,这个类不能被继承,但还是个类,可以继承其他类
    (2)在测试类中实例化对象,用对象调用方法都可以
    (3)当final修饰父类中的方法时,子类不可以重写此方法
    (4)final修饰变量就变成了常量,只能被赋值一次,终身不变
    (5)final修饰引用类型的变脸时,只能被赋值一个地址一次,终身不变
    (6)final修饰成员变量时,只能在创建对象以前给他赋值
    (7)可以直接在类里面赋值,也可以用构造方法给他赋值,但是不能用set方法给赋值

    final关键字的四种用法

    在java中,final的含义在不同的场景下有细微的差别,但总体上来说,它指的是“这是不可变的”。下面,我们来讲final的四种主要用法。

    1.修饰数据

    在编写程序时,我们经常需要说明一个数据是不可变的,我们成为常量。在java中,用final关键字修饰的变量,只能进行一次赋值操作,并且在生存期内不可以改变它的值。更重要的是,final会告诉编译器,这个数据是不会修改的,那么编译器就可能会在编译时期就对该数据进行替换甚至执行计算,这样可以对我们的程序起到一点优化。不过在针对基本类型和引用类型时,final关键字的效果存在细微差别。我们来看下面的例子:

    复制代码
     1 class Value {
     2     int v;
     3     public Value(int v) {
     4         this.v = v;
     5     }
     6 }
     7 
     8 public class FinalTest {
     9     
    10     final int f1 = 1;
    11     final int f2;
    12     public FinalTest() {
    13         f2 = 2;
    14     }
    15 
    16     public static void main(String[] args) {
    17         final int value1 = 1;
    18         // value1 = 4;
    19         final double value2;
    20         value2 = 2.0;
    21         final Value value3 = new Value(1);
    22         value3.v = 4;
    23     }
    24 }
    复制代码

    上面的例子中,我们先来看一下main方法中的几个final修饰的数据,在给value1赋初始值之后,我们无法再对value1的值进行修改,final关键字起到了常量的作用。从value2我们可以看到,final修饰的变量可以不在声明时赋值,即可以先声明,后赋值。value3时一个引用变量,这里我们可以看到final修饰引用变量时,只是限定了引用变量的引用不可改变,即不能将value3再次引用另一个Value对象,但是引用的对象的值是可以改变的,从内存模型中我们看的更加清晰:

    上图中,final修饰的值用粗线条的边框表示它的值是不可改变的,我们知道引用变量的值实际上是它所引用的对象的地址,也就是说该地址的值是不可改变的,从而说明了为什么引用变量不可以改变引用对象。而实际引用的对象实际上是不受final关键字的影响的,所以它的值是可以改变的。

    另一方面,我们看到了用final修饰成员变量时的细微差别,因为final修饰的数据的值是不可改变的,所以我们必须确保在使用前就已经对成员变量赋值了。因此对于final修饰的成员变量,我们有且只有两个地方可以给它赋值,一个是声明该成员时赋值,另一个是在构造方法中赋值,在这两个地方我们必须给它们赋初始值。

    最后我们需要注意的一点是,同时使用static和final修饰的成员在内存中只占据一段不能改变的存储空间。

    2.修饰方法参数

    前面我们可以看到,如果变量是我们自己创建的,那么使用final修饰表示我们只会给它赋值一次且不会改变变量的值。那么如果变量是作为参数传入的,我们怎么保证它的值不会改变呢?这就用到了final的第二种用法,即在我们编写方法时,可以在参数前面添加final关键字,它表示在整个方法中,我们不会(实际上是不能)改变参数的值:

    复制代码
    public class FinalTest {
    
        /* ... */
    
        public void finalFunc(final int i, final Value value) {
            // i = 5; 不能改变i的值
            // v = new Value(); 不能改变v的值
            value.v = 5; // 可以改变引用对象的值
        }
    }
    复制代码

    3.修饰方法

    第三种方式,即用final关键字修饰方法,它表示该方法不能被覆盖。这种使用方式主要是从设计的角度考虑,即明确告诉其他可能会继承该类的程序员,不希望他们去覆盖这个方法。这种方式我们很容易理解,然而,关于private和final关键字还有一点联系,这就是类中所有的private方法都隐式地指定为是final的,由于无法在类外使用private方法,所以也就无法覆盖它。

    4.修饰类

    了解了final关键字的其他用法,我们很容易可以想到使用final关键字修饰类的作用,那就是用final修饰的类是无法被继承的。

    上面我们讲解了final的四种用法,然而,对于第三种和第四种用法,我们却甚少使用。这不是没有道理的,从final的设计来讲,这两种用法甚至可以说是鸡肋,因为对于开发人员来讲,如果我们写的类被继承的越多,就说明我们写的类越有价值,越成功。即使是从设计的角度来讲,也没有必要将一个类设计为不可继承的。Java标准库就是一个很好的反例,特别是Java 1.0/1.1中Vector类被如此广泛的运用,如果所有的方法均未被指定为final的话,它可能会更加有用。如此有用的类,我们很容易想到去继承和重写他们,然而,由于final的作用,导致我们对Vector类的扩展受到了一些阻碍,导致了Vector并没有完全发挥它应有的全部价值。

    总结

    final关键字是我们经常使用的关键字之一,它的用法有很多,但是并不是每一种用法都值得我们去广泛使用。它的主要用法有以下四种:

    1. 用来修饰数据,包括成员变量和局部变量,该变量只能被赋值一次且它的值无法被改变。对于成员变量来讲,我们必须在声明时或者构造方法中对它赋值;
    2. 用来修饰方法参数,表示在变量的生存期中它的值不能被改变;
    3. 修饰方法,表示该方法无法被重写;
    4. 修饰类,表示该类无法被继承。

    上面的四种方法中,第三种和第四种方法需要谨慎使用,因为在大多数情况下,如果是仅仅为了一点设计上的考虑,我们并不需要使用final来修饰方法和类。

    二.static关键字

    1.修饰成员变量

    在我们平时的使用当中,static最常用的功能就是修饰类的属性和方法,让他们成为类的成员属性和方法,我们通常将用static修饰的成员称为类成员或者静态成员,这句话挺起来都点奇怪,其实这是相对于对象的属性和方法来说的。请看下面的例子:(未避免程序太过臃肿,暂时不管访问控制)

    复制代码
    public class Person {
        String name;
        int age;
        
        public String toString() {
            return "Name:" + name + ", Age:" + age;
        }
        
        public static void main(String[] args) {
            Person p1 = new Person();
            p1.name = "zhangsan";
            p1.age = 10;
            Person p2 = new Person();
            p2.name = "lisi";
            p2.age = 12;
            System.out.println(p1);
            System.out.println(p2);
        }
        /**Output
         * Name:zhangsan, Age:10
         * Name:lisi, Age:12
         *///~
    }
    复制代码

    上面的代码我们很熟悉,根据Person构造出的每一个对象都是独立存在的,保存有自己独立的成员变量,相互不会影响,他们在内存中的示意如下:

     

    从上图中可以看出,p1和p2两个变量引用的对象分别存储在内存中堆区域的不同地址中,所以他们之间相互不会干扰。但其实,在这当中,我们省略了一些重要信息,相信大家也都会想到,对象的成员属性都在这了,由每个对象自己保存,那么他们的方法呢?实际上,不论一个类创建了几个对象,他们的方法都是一样的:

    从上面的图中我们可以看到,两个Person对象的方法实际上只是指向了同一个方法定义。这个方法定义是位于内存中的一块不变区域(由jvm划分),我们暂称它为静态存储区。这一块存储区不仅存放了方法的定义,实际上从更大的角度而言,它存放的是各种类的定义,当我们通过new来生成对象时,会根据这里定义的类的定义去创建对象。多个对象仅会对应同一个方法,这里有一个让我们充分信服的理由,那就是不管多少的对象,他们的方法总是相同的,尽管最后的输出会有所不同,但是方法总是会按照我们预想的结果去操作,即不同的对象去调用同一个方法,结果会不尽相同。

    我们知道,static关键字可以修饰成员变量和方法,来让它们变成类的所属,而不是对象的所属,比如我们将Person的age属性用static进行修饰,结果会是什么样呢?请看下面的例子:

    复制代码
    public class Person {
        String name;
        static int age;
        
        /* 其余代码不变... */
    
        /**Output
         * Name:zhangsan, Age:12
         * Name:lisi, Age:12
         *///~
    }
    复制代码

    我们发现,结果发生了一点变化,在给p2的age属性赋值时,干扰了p1的age属性,这是为什么呢?我们还是来看他们在内存中的示意:

    我们发现,给age属性加了static关键字之后,Person对象就不再拥有age属性了,age属性会统一交给Person类去管理,即多个Person对象只会对应一个age属性,一个对象如果对age属性做了改变,其他的对象都会受到影响。我们看到此时的age和toString()方法一样,都是交由类去管理。

    虽然我们看到static可以让对象共享属性,但是实际中我们很少这么用,也不推荐这么使用。因为这样会让该属性变得难以控制,因为它在任何地方都有可能被改变。如果我们想共享属性,一般我们会采用其他的办法:

    复制代码
    public class Person {
        private static int count = 0;
        int id;
        String name;
        int age;
        
        public Person() {
            id = ++count;
        }
        
        public String toString() {
            return "Id:" + id + ", Name:" + name + ", Age:" + age;
        }
        
        public static void main(String[] args) {
            Person p1 = new Person();
            p1.name = "zhangsan";
            p1.age = 10;
            Person p2 = new Person();
            p2.name = "lisi";
            p2.age = 12;
            System.out.println(p1);
            System.out.println(p2);
        }
        /**Output
         * Id:1, Name:zhangsan, Age:10
         * Id:2, Name:lisi, Age:12
         *///~
    }
    复制代码

    上面的代码起到了给Person的对象创建一个唯一id以及记录总数的作用,其中count由static修饰,是Person类的成员属性,每次创建一个Person对象,就会使该属性自加1然后赋给对象的id属性,这样,count属性记录了创建Person对象的总数,由于count使用了private修饰,所以从类外面无法随意改变。

    2.修饰成员方法

    static的另一个作用,就是修饰成员方法。相比于修饰成员属性,修饰成员方法对于数据的存储上面并没有多大的变化,因为我们从上面可以看出,方法本来就是存放在类的定义当中的。static修饰成员方法最大的作用,就是可以使用"类名.方法名"的方式操作方法,避免了先要new出对象的繁琐和资源消耗,我们可能会经常在帮助类中看到它的使用:

    复制代码
    public class PrintHelper {
    
        public static void print(Object o){
            System.out.println(o);
        }
        
        public static void main(String[] args) {
            PrintHelper.print("Hello world");
        }
    }
    复制代码

    上面便是一个例子(现在还不太实用),但是我们可以看到它的作用,使得static修饰的方法成为类的方法,使用时通过“类名.方法名”的方式就可以方便的使用了,相当于定义了一个全局的函数(只要导入该类所在的包即可)。不过它也有使用的局限,一个static修饰的类中,不能使用非static修饰的成员变量和方法,这很好理解,因为static修饰的方法是属于类的,如果去直接使用对象的成员变量,它会不知所措(不知该使用哪一个对象的属性)。

    3.静态块

    在说明static关键字的第三个用法时,我们有必要重新梳理一下一个对象的初始化过程。以下面的代码为例:

    复制代码
    package com.dotgua.study;
    
    class Book{
        public Book(String msg) {
            System.out.println(msg);
        }
    }
    
    public class Person {
    
        Book book1 = new Book("book1成员变量初始化");
        static Book book2 = new Book("static成员book2成员变量初始化");
        
        public Person(String msg) {
            System.out.println(msg);
        }
        
        Book book3 = new Book("book3成员变量初始化");
        static Book book4 = new Book("static成员book4成员变量初始化");
        
        public static void main(String[] args) {
            Person p1 = new Person("p1初始化");
        }
        /**Output
         * static成员book2成员变量初始化
         * static成员book4成员变量初始化
         * book1成员变量初始化
         * book3成员变量初始化
         * p1初始化
         *///~
    }
    复制代码

     上面的例子中,Person类中组合了四个Book成员变量,两个是普通成员,两个是static修饰的类成员。我们可以看到,当我们new一个Person对象时,static修饰的成员变量首先被初始化,随后是普通成员,最后调用Person类的构造方法完成初始化。也就是说,在创建对象时,static修饰的成员会首先被初始化,而且我们还可以看到,如果有多个static修饰的成员,那么会按照他们的先后位置进行初始化。

    实际上,static修饰的成员的初始化可以更早的进行,请看下面的例子:

    复制代码
    class Book{
        public Book(String msg) {
            System.out.println(msg);
        }
    }
    
    public class Person {
    
        Book book1 = new Book("book1成员变量初始化");
        static Book book2 = new Book("static成员book2成员变量初始化");
        
        public Person(String msg) {
            System.out.println(msg);
        }
        
        Book book3 = new Book("book3成员变量初始化");
        static Book book4 = new Book("static成员book4成员变量初始化");
        
        public static void funStatic() {
            System.out.println("static修饰的funStatic方法");
        }
        
        public static void main(String[] args) {
            Person.funStatic();
            System.out.println("****************");
            Person p1 = new Person("p1初始化");
        }
        /**Output
         * static成员book2成员变量初始化
         * static成员book4成员变量初始化
         * static修饰的funStatic方法
         * ***************
         * book1成员变量初始化
         * book3成员变量初始化
         * p1初始化
         *///~
    }
    复制代码

    在上面的例子中我们可以发现两个有意思的地方,第一个是当我们没有创建对象,而是通过类去调用类方法时,尽管该方法没有使用到任何的类成员,类成员还是在方法调用之前就初始化了,这说明,当我们第一次去使用一个类时,就会触发该类的成员初始化。第二个是当我们使用了类方法,完成类的成员的初始化后,再new该类的对象时,static修饰的类成员没有再次初始化,这说明,static修饰的类成员,在程序运行过程中,只需要初始化一次即可,不会进行多次的初始化。

    回顾了对象的初始化以后,我们再来看static的第三个作用就非常简单了,那就是当我们初始化static修饰的成员时,可以将他们统一放在一个以static开始,用花括号包裹起来的块状语句中:

    复制代码
    class Book{
        public Book(String msg) {
            System.out.println(msg);
        }
    }
    
    public class Person {
    
        Book book1 = new Book("book1成员变量初始化");
        static Book book2;
        
        static {
            book2 = new Book("static成员book2成员变量初始化");
            book4 = new Book("static成员book4成员变量初始化");
        }
        
        public Person(String msg) {
            System.out.println(msg);
        }
        
        Book book3 = new Book("book3成员变量初始化");
        static Book book4;
        
        public static void funStatic() {
            System.out.println("static修饰的funStatic方法");
        }
        
        public static void main(String[] args) {
            Person.funStatic();
            System.out.println("****************");
            Person p1 = new Person("p1初始化");
        }
        /**Output
         * static成员book2成员变量初始化
         * static成员book4成员变量初始化
         * static修饰的funStatic方法
         * ***************
         * book1成员变量初始化
         * book3成员变量初始化
         * p1初始化
         *///~
    }
    复制代码

    我们将上一个例子稍微做了一下修改,可以看到,结果没有二致。

    4.静态导包

     相比于上面的三种用途,第四种用途可能了解的人就比较少了,但是实际上它很简单,而且在调用类方法时会更方便。以上面的“PrintHelper”的例子为例,做一下稍微的变化,即可使用静态导包带给我们的方便:

    复制代码
    /* PrintHelper.java文件 */
    package com.dotgua.study;
    
    public class PrintHelper {
    
        public static void print(Object o){
            System.out.println(o);
        }
    }
    复制代码
    复制代码
    /* App.java文件 */

    import static com.dotgua.study.PrintHelper.*; public class App { public static void main( String[] args ) { print("Hello World!"); } /**Output * Hello World! *///~ }
    复制代码

    上面的代码来自于两个java文件,其中的PrintHelper很简单,包含了一个用于打印的static方法。而在App.java文件中,我们首先将PrintHelper类导入,这里在导入时,我们使用了static关键字,而且在引入类的最后还加上了“.*”,它的作用就是将PrintHelper类中的所有类方法直接导入。不同于非static导入,采用static导入包后,在不与当前类的方法名冲突的情况下,无需使用“类名.方法名”的方法去调用类方法了,直接可以采用"方法名"去调用类方法,就好像是该类自己的方法一样使用即可。

    总结

    static是java中非常重要的一个关键字,而且它的用法也很丰富,主要有四种用法:

    1. 用来修饰成员变量,将其变为类的成员,从而实现所有对象对于该成员的共享;
    2. 用来修饰成员方法,将其变为类方法,可以直接使用“类名.方法名”的方式调用,常用于工具类;
    3. 静态块用法,将多个类成员放在一起初始化,使得程序更加规整,其中理解对象的初始化过程非常关键;
    4. 静态导包用法,将类的方法直接导入到当前类中,从而直接使用“方法名”即可调用类方法,更加

    三.匿名对象

    使用方法:

    1 当对象对方法仅进行一次调用的时候,就可以简化成匿名对象。

    如一个 对象需要进行调用方法2次,用匿名对象的

    new Car().run()

    new Car().run()

    这是2个对象分别调用了run(),不是一个对象调用了多方法。

    2 匿名对象可以作为实际参数进行传递。

    public static void show(Car c)
    {
    //......
    }
    show(new Car());
     

    匿名对象的内存分析:

  • 相关阅读:
    JQuery操作DOM
    JQuery事件和动画
    Jquery选择器
    初学JQuery
    JavaScript对象及面向对象
    JavaScript操作DOM
    JavaScript操作BOM
    JavaScript基础
    网络流之最大流Dinic算法模版
    杭电1532----Drainage Ditches『最大流』
  • 原文地址:https://www.cnblogs.com/jason111/p/8681924.html
Copyright © 2011-2022 走看看