整理Java中的关键字static、final、abstract、const

一、static

static的作用

static关键字作用:在没有创建对象的情况下来调用方法/变量
static修饰的方法或变量不需要依赖对象进行访问,只要类被加载了就可以
static可以修饰:属性,方法,代码段,内部类(静态内部类或嵌套内部类)

1.1 修饰类(静态内部类、嵌套内部类)

很少用?不太了解……

1.2 修饰成员变量

  • 用static修饰的成员称为类成员或者静态成员。
  • 静态变量可以被所有对象共享。
  • 对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
  • 对于实例变量,每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。
  • 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对象的方法实际上只是指向了同一个方法定义。这个方法定义是位于内存中的一块不变区域(由jvm划分),我们暂称它为静态存储区。这一块存储区不仅存放了方法的定义,实际上从更大的角度而言,它存放的是各种类的定义,当我们通过new来生成对象时,会根据这里定义的类的定义去创建对象。多个对象仅会对应同一个方法,这里有一个让我们充分信服的理由,那就是不管多少的对象,他们的方法总是相同的,尽管最后的输出会有所不同,但是方法总是会按照我们预想的结果去操作,即不同的对象去调用同一个方法,结果会不尽相同。

1.3 修饰成员方法

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

1.4 static 代码块

  • static关键字还有一个比较关键的作用就是用来形成静态代码块以优化程序性能。
  • 当我们初始化static修饰的成员时,可以将他们统一放在一个以static开始,用花括号包裹起来的块状语句中。
  • static块可以置于类中的任何地方,类中可以有多个static块。
  • 在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

为什么说static块可以用来优化程序性能?

  • 是因为它的特性:只会在类加载的时候执行一次。
  • 因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。

1.5 静态导包

下面列出代码来演示使用静态导包带给我们的方便:

/* 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中非常重要的一个关键字,而且它的用法也很丰富,主要有四种用法:

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

二、final

使用final方法的原因有两个。

  • 第一个原因是把方法锁定,以防任何继承类修改它的含义;
  • 第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。
    -------------------------------------------------------------------------------------------------------《Java编程思想》

2.1 修饰类

用final修饰类,表示这个类不能被继承。

2.2 修饰变量

用final关键字修饰的变量,只能进行一次赋值操作,并且在生存期内不可以改变它的值。更重要的是,final会告诉编译器,这个数据是不会修改的。

  • 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.3 修饰成员方法

  • 即父类的final方法是不可以被子类继承重写的。
  • 如果父类中的final方法是public修饰的,子类可以继承到此方法,子类会重写此方法,将会导致编译出错。
  • 如果父类中的final方法是private修饰的,子类继承不到此方法,这时子类可以定义一个和父类中的final方法相同的方法,因为这个方法是属于子类重新定义的,所以编译不会出错。

static 和final 以及 final static 和 static final 的区别

final:

  • final可以修饰:属性,方法,类,局部变量(方法中的变量)

  • final修饰的属性的初始化可以在编译期,也可以在运行期,初始化后不能被改变。

  • final修饰的属性跟具体对象有关,在运行期初始化的final属性,不同对象可以有不同的值。

  • final修饰的属性表明是一个常数(创建后不能被修改)。

  • final修饰的方法表示该方法在子类中不能被重写,final修饰的类表示该类不能被继承。

  • 对于基本类型数据,final会将值变为一个常数(创建后不能被修改);但是对于对象句柄(亦可称作引用或者指针),final会将句柄变为一个常数(进行声明时,必须将句柄初始化到一个具体的对象。而且不能再将句柄指向另一个对象。但是,对象的本身是可以修改的。这一限制也适用于数组,数组也属于对象,数组本身也是可以修改的。方法参数中的final句柄,意味着在该方法内部,我们不能改变参数句柄指向的实际东西,也就是说在方法内部不能给形参句柄再另外赋值)。

static:

  • static可以修饰:属性,方法,代码段,内部类(静态内部类或嵌套内部类)

  • static修饰的属性的初始化在编译期(类加载的时候),初始化后能改变。

  • static修饰的属性所有对象都只有一个值。

  • static修饰的属性强调它们只有一个。

  • static修饰的属性、方法、代码段跟该类的具体对象无关,不创建对象也能调用static修饰的属性、方法等

  • static和“this、super”势不两立,static跟具体对象无关,而this、super正好跟具体对象有关。

  • static不可以修饰局部变量。

static final和final static:

  • static final和final static没什么区别,一般static写在前面。

static final:

  • static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数(创建后不能被修改)。
  • static final修饰的属性表示一旦给值,就不可修改,并且可以通过类名访问。
  • static final也可以修饰方法,表示该方法不能重写,可以在不new对象的情况下调用

三、abstract

抽象类有什么好处呢?
1、由于抽象类不能被实例化,最大的好处就是通过方法的覆盖来实现多态的属性。也就是运行期绑定
2、抽象类将事物的共性的东西提取出来,由子类继承去实现,代码易扩展、易维护。

3.1 抽象方法

  • 抽象方法跟普通方法是有区别的,它没有自己的主体(没有{}包起来的
    业务逻辑),跟接口中的方法有点类似。所以我们没法直接调用抽象方法

  • 抽象方法不能用private修饰,因为抽象方法必须被子类实现(覆写),而private权限对于子类来
    说是不能访问的,所以就会产生矛盾

  • 抽象方法也不能用static修饰,试想一下,如果用static修饰了,那么我们可以直接通过类名调
    用,而抽象方法压根就没有主体,没有任何业务逻辑,这样就毫无意义了。

/**
 * 车子类
 */
public abstract class Car {
	
	public abstract void run();
}
/**
 * 自行车
 */
class Bicycle extends Car{

	@Override
	public void run() {
		System.out.println("人踩着跑。。。");
	}
	
}
/***
 * 汽车
 */
class Automobile extends Car{

	@Override
	public void run() {
		System.out.println("发动机驱动跑。。。");
	}
	
}

3.2 抽象类

  • 用abstract关键字来表达的类,其表达形式为:(public)abstract class 类名{}

  • 抽象类不能被实例化,也就是说我们没法直接new 一个抽象类。抽象类本身就代表了一个类型,无法
    确定为一个具体的对象,所以不能实例化就合乎情理了,只能有它的继承类实例化。

  • 抽象类虽然不能被实例化,但有自己的构造方法(这个后面再讨论)

  • 抽象类与接口(interface)有很大的不同之处,接口中不能有实例方法去实现业务逻辑,而抽象类
    中可以有实例方法,并实现业务逻辑,比如我们可以在抽象类中创建和销毁一个线程池。

  • 抽象类不能使用final关键字修饰,因为final修饰的类是无法被继承,而对于抽象类来说就是
    需要通过继承去实现抽象方法,这又会产生矛盾。

抽象类与抽象方法

  • 如果一个类中至少有一个抽象方法,那么这个类一定是抽象类,但反之则不然。也就是说一个抽象类中可以没有抽象方法。这样做的目的是为了此类不能被实例化。

  • 如果一个类继承了一个抽象类,那么它必须全部覆写抽象类中的抽象方法,当然也可以不全部覆写,如果

  • 不覆写全部抽象方法则这个子类也必须是抽象类(这样做就无意义了)

public abstract class Car {
	
	public void mothod1(){
		
	}
	public abstract void mothod2();
	
	public abstract void method3();
	
}
class Bicycle extends Car{
	
	@Override
	public void mothod2() {//需要覆写抽象方法mothod2
		
	}
	@Override
	public void method3() {//需要覆写抽象方法mothod3
		
	}
}

抽象类的构造器

public abstract class Car {
	
	Car(){
		System.out.println("抽象方法无参构造函数");
	}
	Car(String a){
		System.out.println("抽象有参构造方法");
	}
	public void mothod1(){
		System.out.println(this.getClass());
		System.out.println("抽象类的实例方法");
	}
	
	public abstract void mothod2();
	
}
/**
 * 自行车
 */
class Bicycle extends Car{
	
	Bicycle(){
		System.out.println("子类无参构造函数");
	}
	
	@Override
	public void mothod2() {//需要覆写抽象方法mothod2
		
	}
}
/**另一个包的测试类**/
public class Test {

	public static void main(String[] args) {
		Bicycle b = new Bicycle();
		b.mothod1();
	}
}
结果:
抽象方法无参构造函数
子类无参构造函数
class com.shaolin.service.impl.Bicycle
抽象类的实例方法

从上面的例子中可以看出:

1、抽象类是有构造方法的(当然如果我们不写,编译器会自动默认一个无参构造方法)。而且从结果来看,和普通的继承类一样,在new 一个子类对象时会优先调用父类(这里指的是抽象类Car)的构造器初始化,然后再调用子类的构造器。至此相信大家都会有这样一个疑问,为什么抽象方法不能实例化却有构造器呢? 对于这个问题网上也中说纷纭,没有确定答案。
我是这样想的:既然它也属于继承的范畴,那么当子类创建对象时必然要优先初始化父类的属性变量和实例方法,不然子类怎么继承和调用呢?而它本身不能实例化,因为它本身就是不确定的一个对象,如果它能被实例化,那么我们通过它的对象来调用它本身的抽象方法是不是有问题。所以不能实例化有在情理之中。因此大家只要记住这个规定就行。

2、对于抽象类中的非statci(静态)和非abstract(抽象)方法中的this关键字(静态方法中不能有关键字this之前已经讨论过可以参考 关于静态static那些事)代表的是它的继承类,而非抽象类本身,这个好理解,因为抽象类本身不能被实例化。如果有多个继承类,谁调用this就代表谁。

四、const

const是java中的预留关键字(java中预留关键字还有goto),现在没有作为关键字,以后的版本中可能扩展用,但现在常用于C,C++中。C中的const类似于final。

参考博客链接1
参考博客链接2
参考博客链接3
abstract参考链接

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页