Java关键字总结

final

final 在 Java 中是一个保留的关键字,可以声明成员变量、方法、类以及本地变量。

final 变量

对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象,但是可以改变实例的属性。
凡是对成员变量或者本地变量(在方法中的或者代码块中的变量称为本地变量)声明为 final 的都叫作 final 变量。final 变量经常和 static 关键字一起使用,作为常量。下面是 final 变量的例子:

public static final String NAME = "wupx";
NAME = new String("wupx"); //invalid compilation error

final 方法

final 也可以声明方法,Java 里用 final 修饰符去修饰一个方法的唯一正确用途就是表达:这个方法原本是一个虚方法,现在通过 final 来声明这个方法不允许在派生类中进一步被覆写(override)。
Java 中非私有的成员方法默认都是虚方法,而虚方法就可以在派生类中被覆写。为了保证某个类上的某个虚方法不在派生类中被进一步覆写,就需要使用 final 修饰符来声明,让编译器(例如 javac)与 JVM 共同检查并保证这个限制总是成立。

final 类

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

final class User{

}

class Reader extends User{  //compilation error: cannot inherit from final class

}

final 关键字的好处

  • final 关键字提高了性能,JVM 和 Java 应用都会缓存 final 变量
  • final 变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销

static

被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。static可以用来修饰类的成员方法、类的成员变量,另外也可以编写static代码块来优化程序性能。

static变量

  • 类内的static变量被所有实例共享。
  • static关键字只能用来修饰成员变量,不能用来修饰局部变量。

类的成员变量可以分为以下两种:

  1. 静态变量(或称为类变量),指被 static 修饰的成员变量。
  2. 实例变量,指没有被 static 修饰的成员变量。

静态变量与实例变量的区别如下:
1)静态变量

  • 运行时,Java 虚拟机只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配。
  • 在类的内部,可以在任何方法内直接访问静态变量。
  • 在其他类中,可以通过类名访问该类中的静态变量。

2)实例变量

  • 每创建一个实例,Java 虚拟机就会为实例变量分配一次内存。
  • 在类的内部,可以在非静态方法中直接访问实例变量。
  • 在本类的静态方法或其他类中则需要通过类的实例对象进行访问。

静态变量在类中的作用如下:

  • 静态变量可以被类的所有实例共享,因此静态变量可以作为实例之间的共享数据,增加实例之间的交互性。
  • 如果类的所有实例都包含一个相同的常量属性,则可以把这个属性定义为静态常量类型,从而节省内存空间。

static方法

static方法可以通过类名.方法名调用。
与成员变量类似,成员方法也可以分为以下两种:

  1. 静态方法(或称为类方法),指被 static 修饰的成员方法。
  2. 实例方法,指没有被 static 修饰的成员方法。

静态方法与实例方法的区别如下:

  • 静态方法不需要通过它所属的类的任何实例就可以被调用,因此在静态方法中不能使用 this 关键字(注意,不要搞混,非静态方法可以使用this调用静态属性static变量),也不能直接访问所属类的实例变量和实例方法,但是可以直接访问所属类的静态变量和静态方法。另外,和 this 关键字一样,super 关键字也与类的特定实例相关,所以在静态方法中也不能使用 super 关键字。
  • 在实例方法中可以直接访问所属类的静态变量、静态方法、实例变量和实例方法。
  • 一个静态方法不能直接调用同类下的非静态方法(也不能使用非静态属性),非静态方法可以调用同类下的静态方法

由于static方法在装载class时首先完成,比构造方法早,此时非static属性和方法还没有完成初始化。所以,在static方法内部无法直接调用非static方法(可以通过先实例化对象,再用该对象调用非static方法),也无法调用非static变量(属性)。

static代码块

静态代码块指 Java 类中的 static{ } 代码块,主要用于初始化类,为类的静态变量赋初始值,提升程序性能。
在静态初始化块中不能直接访问非staic成员。

class Person{
    private Date birthDate;

    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }

    boolean isBornBoomer() {
        Date startDate = Date.valueOf("1946");
        Date endDate = Date.valueOf("1964");
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}

isBornBoomer是用来这个人是否是1946-1964年出生的,而每次isBornBoomer被调用的时候,都会生成startDate和birthDate两个对象,造成了空间浪费,如果改成这样效率会更好:

class Person{
    private Date birthDate;
    private static Date startDate,endDate;
    static{
        startDate = Date.valueOf("1946");
        endDate = Date.valueOf("1964");
    }

    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }

    boolean isBornBoomer() {
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}

很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。
静态初始化块可以置于类中的任何地方,类中可以有多个静态初始化块。(所以如果main方法为空,static块中有打印语句,也能正常打印)
在类初次被加载时,会按照静态初始化块的顺序来执行每个块,并且只会执行一次。
执行顺序:先初始化父类的静态代码—>初始化子类的静态代码–>初始化父类的非静态代码—>初始化父类构造函数—>初始化子类非静态代码—>初始化子类构造函数

volatile

主要用于解决变量在多个线程之间的可见性

  • 保证线程之间变量的可见性
    JAVA 语言里的 volatile 关键字是用来修饰变量的,volatile 可以保证变量变化在多线程间的可见性。
    在一个多线程应用中,出于计算性能的考虑,每个线程默认是从主内存将该变量拷贝到线程所在CPU的缓存中,然后进行读写操作的。现在电脑基本都是多核CPU,不同的线程可能运行的不同的核上,而每个核都会有自己的缓存空间。这里存在一个问题,JVM 既不会保证什么时候把 CPU 缓存里的数据写到主内存,也不会保证什么时候从主内存读数据到 CPU 缓存。也就是说,不同 CPU 上的线程,对同一个变量可能读取到的值是不一致的,这也就是我们通常说的:线程间的不可见问题。
  • 不保证对数据操作的原子性
    例如inc++是一个复合操作:
  1. 读取inc的值
  2. 对inc加1
  3. 将inc的值写回内存
    volatile是无法保证这三个操作是具有原子性的,有可能导致下面这种情况出现:
    1、线程 1 对 inc 进行读取操作之后,还未对其进行修改。线程 2 又读取了 inc的值并对其进行修改(+1),再将inc 的值写回内存。
    2、线程 2 操作完毕后,线程 1 对 inc的值进行修改(+1),再将inc 的值写回内存。
    这也就导致两个线程分别对 inc 进行了一次自增操作后,inc 实际上只增加了 1。
  • 禁止指令重排
    双重校验锁实现对象单例(线程安全) :
public class Singleton {

   private volatile static Singleton uniqueInstance;

   private Singleton() {
    }

   public  static SingletongetUniqueInstance() {
      //先判断对象是否已经实例过,没有实例化过才进入加锁代码
       if (uniqueInstance == null) {
           //类对象加锁
           synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = newSingleton();
                }
           }
       }
       return uniqueInstance;
    }
}

uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance= new Singleton(); 这段代码其实是分为三步执行:

  1. 为 uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. 将 uniqueInstance 指向分配的内存地址
    但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。

synchronized

主要解决的是多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

  • 修饰实例方法 (锁当前对象实例)
    给当前对象实例加锁,进入同步代码前要获得 当前对象实例的锁 。
synchronized void method() {
   //业务代码
}
  • 修饰静态方法 (锁当前类)
    给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前 class 的锁。
    这是因为静态成员不属于任何一个实例对象,归整个类所有,不依赖于类的特定实例,被类的所有实例共享。
synchronized static void method() {
   //业务代码
}

静态 synchronized 方法和非静态 synchronized 方法之间的调用互斥么?不互斥!如果一个线程 A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized方法占用的锁是当前实例对象锁。

  • 修饰代码块 (锁指定对象/类)
    对括号里指定的对象/类加锁:
    1、synchronized(object) 表示进入同步代码库前要获得给定对象的锁。
    2、synchronized(类.class) 表示进入同步代码前要获得给定Class的锁。
synchronized(this) {
   //业务代码
}
  • synchronized 和 volatile比较
    synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在!
    1、volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好 。但是volatile 关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。
    2、volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。
    3、volatile关键字主要用于解决变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性。

abstract

abstract关键字可以修饰类和方法。

抽象类

抽象类不能实例化,也就是不能使用 new 关键字创建对象。但有自己的构造方法。不能实例化最大的好处就是通过方法的覆盖来实现多态的属性,也就是运行期绑定。(Map = new HashMap)
抽象类可以没有抽象方法,可以有具体方法
抽象类不能使用final关键字修饰,因为final修饰的类是无法被继承的,而对于抽象类来说就是需要通过继承去实现抽象方法的。
如果一个类继承了一个抽象类,必须实现所有抽象方法。如果子类没有覆写全部抽象方法,那么这个子类也必须是抽象类(这么做没意义)

抽象方法

抽象方法只能存在抽象类中
抽象方法没有方法体(没有大括号),也就是不实际实现方法
抽象方法不能用private修饰,因为抽象方法必须被子类实现(覆写),而private权限对于子类来说是不能访问的。
抽象方法不能用static修饰。抽象类是不能实例化的,即不能被分配内存。而static修饰的方法在类实例化之前就已经别分配了内存。抽象类不能被分配内存,而static方法必须被分配内存。所以抽象类中不能有静态的抽象方法。
构造方法不能用abstract修饰

interface

接口(interface)是抽象方法和常量值的定义的集合。
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。(跟抽象类有点像)
接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。接口中每一个方法也是隐式抽象的,声明时不必要abstract关键字。接口中的方法都是公有的。
接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误。JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。)。接口不能包含成员变量,除了 static 和 final 变量。
接口不是类,没有构造方法,不能实例化

接口和抽象类比较

接口强调的是具有某种行为(方法)has a,抽象类强调的是is a
飞机(抽象类)和鸟(抽象类)实现了飞(接口),战斗机和客机继承飞机,鹰和雀继承鸟
只能继承一个类,可以实现多个接口

参考

https://zhuanlan.zhihu.com/p/88775601
https://blog.csdn.net/kuangay/article/details/81485324
http://c.biancheng.net/view/6038.html
https://blog.csdn.net/qq_33685334/article/details/129774371
https://blog.csdn.net/playboy_lei/article/details/78965497
https://javaguide.cn/java/concurrent/java-concurrent-questions-02.html#volatile-关键字
https://cloud.tencent.com/developer/article/1894413
https://javaguide.cn/java/concurrent/java-concurrent-questions-02.html#synchronized-关键字
http://c.biancheng.net/view/1004.html
https://blog.csdn.net/weixin_40096176/article/details/79094991
https://www.runoob.com/java/java-interfaces.html

posted @ 2023-05-11 21:34  roadwide  阅读(25)  评论(0编辑  收藏  举报