02、Java常用关键字

static关键字

static意思是静态的、全局的,一旦被修饰,说明被修饰的东西在一定范围内是共享的,谁都可以访问,这时候需要注意并发读写的问题。

1、修饰的对象

static可以修饰成员变量、方法和代码块。

  • 修饰成员变量

    当static修饰成员变量时,如果该变量是public,表示该变量任何类都可以直接访问,无需初始化类,直接使用类名的方式就可以访问。这时候要特别注意线程安全的问题,当多个线程同时对共享变量进行读写时,很有可能出现并发问题,如我们定义了public static List<String> list = new ArrayList<>()这样的共享变量,这个list如果同时被多个线程访问,就会有线程安全问题,常见的解决办法有:把线程不安全的ArrayList换成线程安全的CopyOnWriteArrayList;每次访问时手动加锁(可以使用synchronized和ReentranctLock,不能使用volite,因为volite不是原子性的,无法保证线程安全)。

  • 修饰方法

    当static修饰方法时,代表该方法和当前类无关,任意类都可以直接访问(前提是方法是public的)。有一点需要注意,该方法内部只能调用同样被static修饰的方法和成员变量。static内部的方法在执行的时候是没有线程安全问题的,因为方法执行时,数据是运行在栈帧里面的,栈的数据每个线程都是隔离开的,所以不会有线程安全问题,所以我们在编写util类时,static方法是可以放心使用的。

  • 修饰代码块

    当static修饰代码块时(也叫静态块),常常用于在类启动之前,对类进行一些初始化的操作。需要注意的是,静态块只能调用同样被static修饰的变量,并且static变量要写在静态块前面,不然编译会报错

2、初始化的时机

对于被static修饰的类变量、方法块和静态方法的初始化时机

class ParentClass {
    public static List LIST = new ArrayList(){{
        System.out.println("父类静态变量初始化");
    }};

    static {
        System.out.println("父类静态块初始化");
    }

    public ParentClass() {
        System.out.println("父类构造方法初始化");
    }

    public static void testStatic() {
        System.out.println("父类静态方法初始化");
    }
}

public class ChildrenClass extends ParentClass {
    public static List LIST = new ArrayList(){{
        System.out.println("子类静态变量初始化");
    }};

    static {
        System.out.println("子类静态块初始化");
    }

    public ChildrenClass() {
        System.out.println("子类构造方法初始化");
    }

    public static void testStatic() {
        System.out.println("子类静态方法初始化");
    }

    public static void main(String[] args) {
        System.out.println("run main");
        new ChildrenClass();
        System.out.println("########");
        ChildrenClass.testStatic();
    }
}

/**
* 运行结果:
* 父类静态变量初始化
* 父类静态块初始化
* 子类静态变量初始化
* 子类静态块初始化
* run main
* 父类构造方法初始化
* 子类构造方法初始化
* ########
* 子类静态方法初始化
*/

从结果中可以看出:

  • 父类的静态变量和静态块比子类优先初始化
  • 静态变量和静态块比类构造器优先初始化
  • 被static修饰的方法,在类初始化时并不会初始化,只有当自己被调用时才会被执行

final关键字

final的意思是不变的,可以用来修饰类、方法、变量(成员变量、局部变量、形参)

  1. 被final修饰的类,表明该类无法被继承
  2. 被final修饰的方法,表明该方法无法被重写
  3. 被final修饰的变量,变量在声明的时候就必须进行初始化,而且以后也不能修改其内存地址

注意第三点,说的是无法修改内存地址,而不是无法修改其值,因为对于List、Map这些集合类来说,被final修饰后,是可以修改其内部值的,但是无法修改其初始化时的内存地址。

try、catch、finally关键字

try用来确定代码执行的范围,catch捕捉可能会发生的异常,finally用来执行一定要执行的代码块

public static void main(String[] args) {
  try {
    System.out.println("try is run");
    throw new RuntimeException("try exception");
  } catch (Exception e) {
    System.out.println("catch is run");
    throw new RuntimeException("catch exception");
  } finally {
    System.out.println("finally is run");
    // throw new RuntimeException("finally exception");
  }
}
/**
* 运行结果
* try is run
* catch is run
* finally is run
* Exception in thread "main" java.lang.RuntimeException: catch exception
* 	at io.renren.Test.main(Test.java:10)
*/

代码的执行顺序为:try -> catch -> finally

从执行结果可以看出:

  • finally先执行后,再打印catch的异常(如果finally中出现异常,就抛出finally中的异常,方法结束)
  • 最终捕获的是catch的异常(finally中没有抛出异常),try中抛出的异常会被catch捕获,所以我们可以在catch中封装异常信息

volatie关键字

volatile的意思是可见的,常用来修饰某个共享变量,意思是当共享变量的值被修改后,会及时通知到其他线程上,其他线程就能知道当前共享变量的值已经被修改了。

在多核CPU下,为了提高效率,线程在拿值时,是直接和CPU缓存打交道的,而不是内存,主要是因为CPU缓存执行速度更快,比如线程要拿值C,会直接从CPU缓存中拿,如果CPU缓存中没有,就会从内存中拿,然后放到CPU缓存中,所以线程读的操作永远都是拿的CPU缓存的值。这时候就会产生一个问题,CPU缓存中的值和内存中的值可能并不是时刻都同步的,导致线程计算的值可能不是最新的,共享变量的值有可能已经被其他线程所修改了,但此时修改的是机器内存的值,CPU缓存的值还是老的,导致计算会出现问题。这个时候有个机制,就是内存会主动通知CPU缓存,当前共享变量的值已经失效了,需要重写拉取一份,CPU缓存就会重新从内存中拿取一份最新的值,volatile关键字就会触发这种机制。加了volatile关键字的变量,就会被识别成共享变量,内存中值被修改后,会通知到各个CPU缓存,使CPU缓存中的值也对应被修改,从而保证线程从CPU缓存中拿取出来的值是最新的。如下图所示:

在这里插入图片描述

从图中可以看到,线程一和线程二一开始都读取了C值,CPU1和CPU2中也都缓存了C值,然后线程1把C值修改了,这时候内存的值和CPU2缓存中的值就不相等了,内存这时发现C值被volatile修饰了,发现其是共享变量,就会将CPU2缓存中的C值状态置为无效,当线程2从CPU2缓存中获取C值时,就会去内存中拉取最新的C值

transient关键字

transient的意思是短暂的,transient关键字只能用来修饰类的成员变量,不能修饰类和方法,作用是在对象序列化时忽略该变量。

要理解transient的作用,首先要理解序列化的含义,Java中对象的序列化指的是将对象转换成以字节序列的形式来表示,这些字节序列包含了对象的数据和信息,一个序列化后的对象可以被写到磁盘中(例如数据库或文件),也可用于网络传输。一般地,当我们使用缓存cache(内存空间不够有可能会本地存储到硬盘)或远程调用rpc(网络传输)的时候,经常需要让实体类实现Serializable接口,目的就是为了让其可序列化。当然,序列化后的最终目的是为了反序列化,恢复成原先的Java对象实例。所以序列化后的字节序列都是可以恢复成Java对象的,这个过程就是反序列化。

在持久化对象时,对于一些特殊的数据成员(如用户的密码,银行卡号等),为了安全起见,不想用序列化机制来保存它。为了在一个特定对象的一个成员变量上关闭序列化,可以在这个成员变量前加上关键字transient。

@Data
class User implements Serializable {
    private String a = "普通成员变量";
    private transient String b = "transient修饰的变量";
}

public class Test {
    public static void main(String[] args) {
        try(ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("c://test.txt"))) {
            os.writeObject(new User());
            os.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }

        try(ObjectInputStream is = new ObjectInputStream(new FileInputStream("c://test.txt"))) {
            User user = (User) is.readObject();
            System.out.println("a: " + user.getA());
            System.out.println("b: " + user.getC());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

/**
* 运行结果:
* a: 普通成员变量
* b: null
*/

注意:

  1. 如果对象需要被序列化,则类必须实现Serializable接口;
  2. 一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法被访问,值为null;
  3. 一个静态变量不管是否被transient修饰,均不能被序列化,因为序列化保存的是对象状态,而静态变量是类所有的,因此序列化不会保存静态变量;

default关键字

default 关键字一般会用在接口的方法上,意思是对于接口的该方法,子类是无需强制实现的,但自己必须有默认实现,例如:

interface Parent {
    default void sayHello() {
        System.out.println("parent: say hello");
    }

    void sayBye();
}

// 子类可以不实现接口中的被deafult关键字修饰的方法
class Children implements  Parent {
    @Override
    public void sayBye() {
        System.out.println("children: say bye");
    }
}

public class Test {
    public static void main(String[] args) {
        Children children = new Children();
        children.sayHello();
        children.sayBye();
    }
}

/**
* 运行结果:
* parent: say hello
* children: say bye
*/

native关键字

native关键字用于修饰方法,表明该方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。例如Java中的System.arrayCopy()、Object.hashCode()等

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值