Java部分重要关键字

196 阅读16分钟

extends,implements,final,native,static,transient,synchronized,volatile

1.extend

在 Java 中通过 extends 关键字可以声明一个类是从另外一个类继承而来的

class 父类{}//父类

class 子类  extend父类{}

当一个类通过extend声明其继承自一个类时,可以获得父类允许被继承变量和方法。一个类可以被多个子类继承,但不能继承自多个父类。

接口(interface)也可以用extend声明其继承自其他接口,接口允许一个接口继承自多个接口。

interface a{}

interface b{}

interface c extends a,b{}

2. Implements

接口中包括常量的声明(没有变量),和抽象方法。接口也只有抽象方法。常量一定是public和static的(可以省略public,final,static),抽象方法的权限一定是public的(可以省略public abstract)。

一个类用 implements实现接口

interface a{}

class A implements a{}

一个类同样允许其实现多个接口

interface a{}

interface b{}

class A implements a,b{}

对于非抽象类实现接口,必须重写接口中所有的方法,同时明显的标出该方法是public的。抽象类可以不实现方法。

3.final

final关键字可以修饰类,成员变量,方法中的局部变量

  1. final类

final类不允许有子类,出于安全考虑,可以将一些类用final修饰,如String类。

  1. 常量

成员变量,方法中的局部变量被final修饰,那它就是常量,常量在运行期间不允许修改,因此在声明常量是必须指定值。无法被继承。

  1. final方法

不允许子类重写,即不能隐藏

  1. 用来修饰方法参数,表示在变量的生存期中它的值不能被改变;

4. native

native主要用于方法上

1、一个native方法就是一个Java调用非Java代码的接口。一个native方法是指该方法的实现由非Java语言实现,比如用C或C++实现。

2、在定义一个native方法时,并不提供实现体(比较像定义一个Java Interface),因为其实现体是由非Java语言在外面实现的

主要是因为JAVA无法对操作系统底层进行操作,但是可以通过jni(java native interface)调用其他语言来实现底层的访问。

可以将native方法比作Java程序同C程序的接口,其实现步骤:

  1. 在Java中声明native()方法,然后编译。
  2. 用javah产生一个.h文件;
  3. 写一个.cpp文件实现native导出方法,其中需要包含第二步产生的.h文件(注意其中又包含了JDK带的jni.h文件)。
  4. 将第三步的.cpp文件编译成动态链接库文件。
  5. 在Java中用System.loadLibrary()方法加载第四步产生的动态链接库文件,这个native()方法就可以在Java中被访问了。

具体操作步骤:

blog.csdn.net/zmx729618/a…

5. static

static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法。被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享。只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。即可以不使用new去创建对象就可以t通过类名调用。

class A {

    static int a = 8;

    static void qq() {

        System.out.println("aaaa");

    }

}



public class test {

    public static void main(String[] args) {

        System.out.println(A.a);

        A.qq();

    }

}

static成员变量和成员方法本质是全局变量和全局方法,当声明它类的对象时,不生成static变量的副本,而是类的所有实例共享同一个static变量。类似于String常量池都指向同一个常量。

class A {

    static int a = 8;

    static void qq() {

        System.out.println("aaaa");

    }

}



public class test {

    public static void main(String[] args) {

        System.out.println(A.a);

        A.qq();

        A a1=new A();

        A a2=new A();

        a1.a=1;

        System.out.println(a1.a);

        System.out.println(A.a);

        a2.a=2;

        System.out.println(a1.a);

        System.out.println(A.a);

        System.out.println(a2.a);

    }

}

可以看到,当修改了一个对象的(类名调用同样)对应的static变量时,所有对象的static变量都同步变化,因为他们就是同一个。

static变量前可以有private修饰,表示这个变量可以在类的静态代码块中,或者类的其他静态成员方法中使用,就不能在其他类中通过类名调用。添加其他访问权限修饰符同理。

static代码块

也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。例如:



class Test5 {

    private static int a;

    private int b;



    static {

        Test5.a = 3;

        System.out.println(a);

    }



    static {

        Test5.a = 4;

        System.out.println(a);

    }

}



public class test {

    public static void main(String[] args) {

        Test5 qq = new Test5();

    }

}

对于上面只是创建了Test5类,在加载类的时候就执行了代码块里的内容,相对于方法要调用才会被执行是不同的。代码块里调用的变量只能是静态变量,因为:

static final用来修饰成员变量和成员方法,可简单理解为“全局常量”!

对于变量,表示一旦给值就不可修改,并且通过类名可以访问。对于方法,表示不可覆盖,并且可以通过类名直接访问。

这样就可以避免共用变量时意外修改造成的错误。

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

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

static{}语句块执行的时机,即类被加载准确含义

  1. 实例化一个类的时候


class Test5 {

    static int a = 3;

    static {

        System.out.println("我被执行了");

    }

}



public class test {

    public static void main(String[] args) {

        Test5 qq = new Test5();

        System.out.println();

    }

}

  1. 调用类的静态方法的时候
class Test5 {

    static int a = 3;

    static void aa(){

        System.out.println("我是静态方法");

    }

    static {

        System.out.println("我被执行了");

    }

}



public class test {

    public static void main(String[] args) {

        Test5.aa();

        System.out.println();

    }

}

可以看到先执行了代码块,再执行了静态方法。

  1. 调用类的静态变量的时候


class Test5 {

    static int a = 3;

    static {

        System.out.println("我被执行了");

    }

}



public class test {

    public static void main(String[] args) {

        System.out.println(Test5.a);

    }

}

同样先执行代码块

调用类的静态常量的时候,是不会加载类的,即不会执行static{}语句块



class Test5 {

    static final int a = 3;

    static {

        System.out.println("我被执行了");

    }

}



public class test {

    public static void main(String[] args) {

        System.out.println(Test5.a);

    }

}

(这是java虚拟机的规定,当访问类的静态常量时,如果编译器可以计算出常量的值,则不会加载类,否则会加载类)

  1. Class.forName()时

但可以通过修改参数不让加载类

class Test5 {

    static {

        System.out.println("被执行了");

    }

}



public class test {

    public static void main(String[] args) throws ClassNotFoundException {

        Class.forName("Test5");

    }

}

  1. 如果静态变量在定义的时候就赋给了初值(如 static int X=100),那么赋值操作也是在类加载的时候完成的,并且当一个类中既有static{}又有static变量的时候,同样遵循“先定义先执行”的原则;

类加载特性

1)在虚拟机的生命周期中一个类只被加载一次。

2)类加载的原则:延迟加载,能少加载就少加载,因为虚拟机的空间是有限的。

3)类加载的时机: - 第一次创建对象要加载类. - 调用静态方法时要加载类,访问静态属性时会加载类。 - 加载子类时必定会先加载父类。 - 创建对象引用不加载类. - 子类调用父类的静态方法时

(1)当子类没有覆盖父类的静态方法时,只加载父类,不加载子类

(2)当子类有覆盖父类的静态方法时,既加载父类,又加载子类
  • 访问静态常量,如果编译器可以计算出常量的值,则不会加载类,例如:public static final int a =123;否则会加载类,例如:public static final int a = math.PI

执行顺序

  • 父类的静态初始化块
  • 子类的静态初始化块
  • 父类的初始化块
  • 父类的构造函数
  • 子类的初始化块
  • 子类的构造函数

总结:

  • 静态初始化块的优先级最高,也就是最先执行,并且仅在类第一次被加载时执行;
  • 非静态初始化块和构造函数后执行,并且在每次生成对象时执行一次;
  • 非静态初始化块的代码会在类构造函数之前执行。因此若要使用,应当养成把初始化块写在构造函数之前的习惯,便于调试;
  • 静态初始化块既可以用于初始化静态成员变量,也可以执行初始化代码;
  • 非静态初始化块可以针对多个重载构造函数进行代码复用。

引用:

zhuanlan.zhihu.com/p/42961231

6. Transient

transient 关键字的作用是控制变量的序列化,只能用来修饰成员变量,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

序列化:JVM中的Java对象转化为字节序列。

反序列化:字节序列转化为JVM中的Java对象。

静态成员变量不加transient关键字也不能被序列化

7.synchronized

Java中使用Thread类及其子类创建线程,对于一个线程完整的生命周期有如下六种状态:

  1. 新建

当对象被声明并创建时,此时就有了相应的内存空间和其他资源

  1. 就绪

线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。

  1. 运行

通过调用start()方法(父类继承),通知JVM有新线程在排队等待切换,如果线程是Thread的子类创建的,那么run()方法会立即被执行,即线程自己的使命。由于Thread类run()方法没有具体内容,因此必须重写。在run()没有结束以前不可以再次调用start()方法。可以理解为运行了不能再次运行。

  1. 中断有四种情况
  • CPU资源被切换给其他线程。
  • 在使用CPU期间执行了sleep(int millsecond)方法,参数为毫秒。线程会立即退出CPU,等待指定时间后重新排队。可以使用interrupt()吵醒休眠的线程。会发生InterruptedException异常。
  • 在使用CPU期间执行了wait()方法,和上面类似会退出CPU,但是不会主动去排队,必须其他线程调用notify()方法才会重新排队。如果使用notifyAll()方法,会通知所有等待的线程结束等待,并且先中断先继续。
  • 线程的某个操作进入阻塞状态,只有阻塞消除后才会继续排队。
  1. 死亡
  • 正常运行完run()语句。
  • 被提前强制结束。
  1. NEW

Thread state for a thread which has not yet started.

尚未启动的线程的线程状态。没有运行,只是创建了。

  1. RUNNABLE

Thread state for a thread which has not yet started.Thread state for a runnable thread. A thread in the runnable state is executing in the Java virtual machine but it maybe waiting for other resources from the operating system such as processor.

已经启动,正等待着第一次给予资源的运行

  1. BLOCKED

Thread state for a thread blocked waiting for a monitor lock.

  • A thread in the blocked state is waiting for a monitor lock

  • to enter a synchronized block/method or

  • reenter a synchronized block/method after calling

等待一个被锁定的线程,如synchronized的线程同步,或被阻塞的线程

  1. WAITING


 * Thread state for a waiting thread.

 * A thread is in the waiting state due to calling one of the

 * following methods:

 * <ul>

 *   <li>{  @link Object#wait() Object.wait} with no timeout</li>

 *   <li>{  @link #join() Thread.join} with no timeout</li>

 *   <li>{  @link LockSupport#park() LockSupport.park}</li>

 * </ul>

 *

 * <p>A thread in the waiting state is waiting for another thread to

 * perform a particular action.

 *

 * For example, a thread that has called {  @code Object.wait()}

 * on an object is waiting for another thread to call

 * {  @code Object.notify()} or {  @code Object.notifyAll()} on

 * that object. A thread that has called {  @code Thread.join()}

 * is waiting for a specified thread to terminate.

线程处于等待状态,如调用wait方法,或者其他线程的加入等引起的等待

  1. TIMED_WAITING
 /**

         * Thread state for a waiting thread with a specified waiting time.

         * A thread is in the timed waiting state due to calling one of

         * the following methods with a specified positive waiting time:

         * <ul>

         *   <li>{@link #sleep Thread.sleep}</li>

         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>

         *   <li>{@link #join(long) Thread.join} with timeout</li>

         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>

         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>

         * </ul>

         */

定时等待,如sleep方法,wait和join的超时

6.TERMINATED

  • The thread has completed execution.
  • Thread state for a terminated thread.

线程被终止或者线程被完成执行

线程调度的优先级

调度器把优先级分为十个等级,用Thread的类常量表示,在1到10之间,即Thread.MIN_PRIORITY到Thread.MAX_PRIORITY之间,没有明确指出即为5。可以使用setPriority(int grade)方法调整,通过getPriority方法返回优先级(部分操作系统只有1,5,10三个优先级)

调度器的任务是让高优先级的线程始终运行,一旦时间片有空闲就使有同等优先级的线程以轮流的方式使用时间片。

使用Thread类直接创建线程对象

class CarTarget implements Runnable {

    public void run() {

        for (int i = 1; i <= 20; i++) {

            System.out.print("轿车" + i + "  ");

        }

    }

}



class ElephantTarget implements Runnable {//实现Runnable接口

    public void run() {

        for (int i = 1; i <= 20; i++) {

            System.out.print("大象" + i + "  ");

        }

    }

}

public class test {

    public static void main(String args[]) {

        Thread speakElephant;            //用Thread声明线程

        Thread speakCar;                 //用Thread声明线程

        ElephantTarget elephant;         //speakElephant线程的目标对象

        CarTarget car;                   //speakCar线程的目标对象

        elephant = new ElephantTarget();

        car = new CarTarget();

        speakElephant = new Thread(elephant);   //创建线程

        speakCar = new Thread(car);              //创建线程

        speakElephant.start();                    //启动线程

        speakCar.start();                        //启动线程

        for (int i = 1; i <= 15; i++) {

            System.out.println("主人" + i + "  ");

        }

    }

}

对于一个已经在运行并且没有进入死亡状态的线程,不要再给他分配实体,因为线程只能引用最后分配的实体,之前的实体会成为垃圾,并且不会被收集器收集。

  • currentThread()是Thread类中的方法。可以用类名调用,返回正在执行的线程

线程同步

当一个线程A使用synchronized方法时,其他线程想使用该方法必须等待A使用完。

class Bank implements Runnable {

    int money = 200;



    public void setMoney(int n) {

        money = n;

    }



    public void run() {

        if (Thread.currentThread().getName().equals("会计"))

            saveOrTake(300);

        else if (Thread.currentThread().getName().equals("出纳"))

            saveOrTake(150);

        ;

    }



    public synchronized void saveOrTake(int amount) { //存取方法

        if (Thread.currentThread().getName().equals("会计")) {

            for (int i = 1; i <= 3; i++) {

                money = money + amount / 3;      //每存入amount/3,稍歇一下

                System.out.println(Thread.currentThread().getName() +

                        "存入" + amount / 3 + ",帐上有" + money + "万,休息一会再存");

                try {

                    Thread.sleep(1000);  //这时出纳仍不能使用saveOrTake方法

                } catch (InterruptedException e) {

                }

            }

        } else if (Thread.currentThread().getName().equals("出纳")) {

            for (int i = 1; i <= 3; i++) { //出纳使用存取方法取出60

                money = money - amount / 3;   //每取出amount/3,稍歇一下

                System.out.println(Thread.currentThread().getName() +

                        "取出" + amount / 3 + "帐上有" + money + "万,休息一会再取");

                try {

                    Thread.sleep(1000);       //这时会计仍不能使用saveOrTake方法

                } catch (InterruptedException e) {

                }

            }

        }

    }

}





public class test {

    public static void main(String args[]) {

        Bank bank = new Bank();

        bank.setMoney(200);

        Thread accountant,    //会计

                cashier;      //出纳

        accountant = new Thread(bank);

        cashier = new Thread(bank);

        accountant.setName("会计");

        cashier.setName("出纳");

        accountant.start();

        cashier.start();

    }

}

如果去掉synchronized修饰

可以看到在初始有200的情况下,取出50尽然还有250,说明第一次运行的会计在刚存100的时候就被打断了。

Java中每个对象,都有把内置锁,当做一个同步锁来使用。当一个线程在进入到synchronized代码块前时,会自动获取到监视器锁,此时其他线程在访问synchronized代码块时,就会被堵塞挂起(被拒之门外的意思)。拿到锁的线程会在执行完成、或者抛出异常、或者调用wait系列方法时释放该锁。其他线程只能等待锁被释放后才能获取该锁。通俗的讲,synchronized关键字将代码块中代码由并行转变成了串行,这样就保证了代码被顺序执行。

synchronized在内存层面,是如何实现加锁和释放锁的?

进入synchronized代码块时,会将代码块内用到的变量从该线程的工作内存中清除,转而从主内存中获取。退出synchronized代码块时,会将代码块内用到的变量的修改,刷新到主内存中。这其实就是synchronized解决共享变量内存可见性的原理。

方式一:同步代码块锁:

synchronized (共享变量) {

    //需要同步的代码

}

方式二:方法锁:

        private synchronized void method() {

            //需要同步的代码

        }

8. volatile

由于JVM运行程序的实体是线程,而每一个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域,而java内存模型中规定所有的变量都存储在主内存,主内存是共享内存区域,所有的线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存完成.

Volatile可以看做是轻量级的 Synchronized,它只保证了共享变量的可见性。在线程 A 修改被 volatile 修饰的共享变量之后,线程 B 能够读取到正确的值。

(1)简易性:在某些需要同步的场景下使用volatile变量要比使用锁更加简单

(2)性能:在某些情况下使用volatile同步机制的性能要优于锁

(3)volatile操作不会像锁一样容易造成阻塞

volatile特性

(1)volatile 变量具有 synchronized 的可见性特性,及如果一个字段被声明为volatile,java线程内存模型确保所有的线程看到这个变量的值是一致的.即拷贝的主内存中的变量修改后立即存回主内存并通知其他线程该变量已经修该,需要更新。

(2)禁止进行指令重排序

(3)不保证原子性

① 重排序:重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段

② 原子性:不可中断的一个或一系列操作

③ 可见性:锁提供了两种主要特性:互斥和可见性,互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的。

The Java volatile keyword is intended to address variable visibility problems. By declaring the counter variable volatile all writes to the counter variable will be written back to main memory immediately. Also, all reads of the counter variable will be read directly from main memory.

被volatile修饰的变量会 立即写到 主内存中,读取也是从主内存中读取,保证了对变量修改后的一致性。

public class MyClass {

    private int years;

    private int months

    private volatile int days;





    public void update(int years, int months, int days){

        this.years  = years;

        this.months = months;

        this.days   = days;

    }

}

这个udpate()方法写入三个变量,其中仅包含三个变量。days是不稳定的。

全员volatile可见性保证是指将值写入days,则所有线程可见的变量也会写入主内存。这意味着,当一个值被写到days的价值yearsmonths也被写入主内存。

当读取years, monthsdays可以这样做:

public class MyClass {

    private int years;

    private int months

    private volatile int days;



    public int totalDays() {

 int total = this.days;

 total += months * 30;

 total += years * 365;

 return total;

 }



    public void update(int years, int months, int days){

        this.years  = years;

        this.months = months;

        this.days   = days;

    }

}

注意totalDays()方法从读取days进入total变量。当读取days的价值monthsyears也被读入主内存。因此,您可以看到days, monthsyears上面的读取序列。

计算机的有存储能力的有

硬盘,内存,CPU

大小依次急剧减小,存取速度也依次急剧加快

对于文件,很大的数据量一般存储在硬盘里。程序运行时,产生的实例,数据,线程的工作空间在内存里,也会从硬盘里读取数据到内存内供CPU使用。CPU的缓存仅能存储很少的数据,以减少对重复数据的读取,也可提前缓存数据。