干货——详解Java中的关键字
在平时编码中,我们可能只注意了这些static,final,volatile等关键字的使用,忽略了他们的细节,更深层次的意义。
本文总结了Java中所有常见的关键字以及一些例子。
static 关键字
概述:
当static修饰类的属性或者方法时,那么就可以在没有创建对象的情况下使用该属性或方法。
静态块也是static的一个应用,用于初始化类时的一些操作。
静态方法和静态变量
划重点
被static修饰后的属性或者方法,使用时不需要new 一个类,用类.属性名或方法名访问.
比如java.lang.Math就存放了很多静态资源,可以直接使用Math.random()来获取随机数.
一些需要注意的地方
非静态方法是可以访问静态资源的,
静态方法是不能引用非静态资源的。
来看一个代码实例:
- public class TestStatic {
- protected int i = 100;
- public static void main(String args[]){
- System.out.println(i);
- }
- }
在以上代码,编译的时候会出错,main方法是静态方法,变量i是非静态的。
解决办法是,将变量i加上static修饰。
不经就要提出一个问题,
为什么非静态方法可以访问静态资源,而静态方法不能访问非静态资源呢?
从类加载机制上讲,静态资源是类初始化的时候加载的,然后非静态资源是new一个该类的对象的时候加载的。
这就带来一个问题:
加载类时默认先加载静态资源的,当new一个对象之后,才会加载其他资源,所以在new对象之前,静态资源是不知道类有哪些非静态资源的,
但是当对象new出来之后,该类的所有属性和方法都知道。
还有需要注意的是:
1.静态属性和方法可以通过类.属性名或方法名,而且,该类的对象也是访问静态属性和变量的。
2.Java的语法规定,static不能修饰局部变量。没有为什么,这就是规定。
静态块
静态块和静态变量、静态方法是没什么区别的,也是在类加载的时候执行,而且只执行一次。
关于静态块有两点需要注意:
1.静态资源的加载顺序严格按照静态资源的定义顺序加载的
2.静态块,对于定义在它之后的静态变量,可以赋值但不能访问。
static的题目
下面main()方法的输出结果是什么:
- public class InstanceClass extends ParentClass{
- public static String subStaticField = "子类静态变量";
- public String subField = "子类非静态变量";
- public static StaticClass staticClass = new StaticClass("子类");
- static {
- System.out.println("子类 静态块初始化");
- }
- {
- System.out.println("子类 [非]静态块初始化");
- }
- public InstanceClass(){
- System.out.println("子类构造器初始化");
- }
- public static void main(String args[]) throws InterruptedException {
- new InstanceClass();
- }
- }
- class ParentClass{
- public static String parentStaticField = "父类静态变量";
- public String parentField = "父类[非]静态变量";
- public static StaticClass staticClass = new StaticClass("父类");
- static {
- System.out.println("父类 静态块初始化");
- }
- {
- System.out.println("父类 [非]静态块初始化");
- }
- public ParentClass(){
- System.out.println("父类 构造器初始化");
- }
- }
- class StaticClass{
- public StaticClass(String name){
- System.out.println(name+" 静态变量加载");
- }
- }
输出结果:
- 父类 静态变量加载
- 父类 静态块初始化
- 子类 静态变量加载
- 子类 静态块初始化
- 父类 [非]静态块初始化
- 父类 构造器初始化
- 子类 [非]静态块初始化
- 子类构造器初始化
下面是我总结类加载流程,可以对照着这个流程,可以再重新看一下上面的例子,会有新的理解。
- 1. 加载父类静态
- 1.1 为静态属性分配存储空间并赋初始值
- 1.2 执行静态初始化块和静态初始化语句(从上至下)
- 2. 加载子类静态
- 2.1 为静态属性分配存储空间
- 2.2 执行静态初始化块和静态初始化语句(从上至下)
- 3. 加载父类非静态
- 3.1 为非静态块分配空间
- 3.2 执行非静态块
- 4. 加载子类非静态
- 4.1 为非静态块分配空间
- 4.2 执行非静态块
- 5. 加载父类构造器
- 5.1 为实例属性分配存数空间并赋初始值
- 5.2 执行实例初始化块和实例初始化语句
- 5.3 执行构造器内容
- 6. 加载子类构造器
- 6.1 为实例属性分配存数空间并赋初始值
- 6.2 执行实例初始化块和实例初始化语句
- 6.3 执行构造器内容
对照着刚才的规则,再看一下这个例子:
- public class TestStaticLoad {
- Person person = new Person("TestStaticLoad");
- static{
- System.out.println("TestStaticLoad static");
- }
- public TestStaticLoad() {
- System.out.println("TestStaticLoad constructor");
- }
- public static void main(String[] args) {
- new God();
- }
- }
- class Person{
- static{
- System.out.println("person static");
- }
- public Person(String str) {
- System.out.println("person "+str);
- }
- }
- class God extends TestStaticLoad {
- Person person = new Person("God");
- static{
- System.out.println("God static");
- }
- public God() {
- System.out.println("God constructor");
- }
- }
输出结果:
- TestStaticLoad static
- God static
- person static
- person TestStaticLoad
- TestStaticLoad constructor
- person God
- God constructor
一步一步地解析:
- 在TestStaticLoad 的main方法中,执行了new God(),那就就会去加载God类,在这之前会先加载它的父类:TestStaticLoad
- 第一步:加载父类静态,执行System.out.println("TestStaticLoad static"); 输出:TestStaticLoad static,
- 第二步:加载子类静态,执行System.out.println("God static");,输出God static
- 第三步:加载父类非静态,Person person = new Person("TestStaticLoad");,这里实例化了Person 对象,那就会去加载Person类。
- 第四步:加载Person类,首先看有没有父类,没有。好,加载静态块,执行System.out.println("person static");输出person static
- 第五步:Pernson类静态块加载完毕,加载构造器,new一个Person对象,输出person TestStaticLoad。这时TestStaticLoad 类非静态块加载完毕
- 第六步:加载God 父类(TestStaticLoad )构造器,输出TestStaticLoad constructor
- 第七步:God父类全部加载完毕,加载God的非静态块,Person person = new Person("God");这时又会去加载Person类,需要注意的是,static块只加载一次,因为之前在父类已经加载过了,这时只加载构造器,输出person God
- 最后一步:加载本类God 的构造器,输出God constructor。
static关键字的总结:
- static关键字 可以再没有创建对象的时候进行调用类的元素
- static 可以修饰类的方法 以及类的变量, 以及静态代码块
- 被static修饰的成为静态方法,静态方法是没有this的,静态方法不能访问同一个类中的非静态方法和静态变量,但是非静态方法 可以可以访问静态变量
- 类的构造器 也是静态的
- 静态变量被所有的内存所有的对象共享,在内存中只有一个副本。非静态变量是是在创建对象的时候初始化的,存在多个副本,每个副本不受影响。
- static 静态代码块,static 代码块可以放在类中的任何地方,类加载的时候会按照static代码块的顺序来加载代码块,并且只会执行一次。
- 枚举类和静态代码块 赋值静态代码块的变量
- 非静态方法能够通过this访问静态变量
- 静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问。
- static不可以修饰局部变量(java语法规定)
没想到static能有这么多需要注意的,可以说Java中的语法还是有很多可以深究的.
final 关键字
概述:
final关键字,在平时的过程中也是很常见的,在这里进行一下深入的学习,加深对final关键字的理解。
使用注意点:
1.在java中final可以用来修饰类、方法、和变量(包括成员变量和局部变量)
2.final修饰类的时候,这个类将永远不会被继承,类中的成员方法也会被隐式的修饰为final(尽量不要用final修饰类)
3.如果不想方法被继承,可以用final修饰,private也会隐式的将方法指定为final
4.final修饰变量的时候,如果是基本类型的变量,那么他的值在初始化之后就不能更改
5.final在修饰对象的时候,在其初始化之后就不能指向其他对象
6.被static和final修饰的变量,将会占据一段不能改变的存储空间,将会被看做编译期常量
7.不可变的是变量的引用而非引用指向对象的内容。
几个例子:
1.final变量和普通变量的区别
- public class TestFinal {
- public static void main(String args[]){
- String a = "test1";
- final String b = "test";
- String d = "test";
- String c = b + 1;
- String e = d + 1;
- System.out.println((a == c));
- System.out.println((a.equals(e)));
- }
- }
- true
- true
因为final变量是基本类型以及String时,在编译期的时候就把它当做常量来使用,不需要在运行时候使用。“==”是对比两个对象基于内存引用,如果两个对象的引用完全相同,则返回true,所以这里b是用访问常量的方式去访问,d是链接的方式,所以a的内存引用和c的内存引用是相等的,所以结果为true,a和e两个对象的值是相等的,所以结果为true
2.final在修饰对象的时候
- public class TestFinal {
- public static void main(String args[]){
- final TestFinal obj1 = new TestFinal();
- final TestFinal obj2 = new TestFinal();
- obj1 = obj2;
- }
- }
在编译的时候,或报错, 不能指向一个final对象。
volatile关键字
缓存一致性:
首先来看看线程的内存模型图:
当执行代码:
- i = i + 1;
- 首先从主存中读取i的值,
- 然后复制I到Cache中,
- CPU执行指令对i进行加1
- 将加1后的值写入到Cache中
- 最后将Cache中i的值刷新到主存中
这个在单线程的环境中是没有问题的,但是运行到多线程中就存在问题了。
问题出在主存中的变量,因为有可能其他线程读的值,线程的Cache还没有同步到主存中,每个线程中的Cahe中的值副本不一样,可能会造成"脏读"。
缓存一致性协议解决了这样的问题,它规定每个线程中的Cache使用的共享变量副本是一样的。
核心内容是当CPU写数据时,如果发现操作的变量式共享变量,它将通知其他CPU该变量的缓存行为无效,
所以当其他CPU需要读取这个变量的时候,发现自己的缓存行为无效,那么就会从主存中重新获取。
三个概念
Jvm定义了内存规范,试图做到各个平台对内存访问的差异,但是依旧会发生缓存一致性的问题。
首先了解三个概念,原子性,可见性,有序性。
原子性:指某个操作,一个或者多个,要么全部执行并且执行的过程中不会被任何因素打断,要么都不执行。
在JVM中,只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。看一个例子:
- x = 70; //语句1
- y = x; //语句2
- y++; //语句3
- y = x + 1; //语句4
上面四个语句中,只有语句1是原子性,其他都不是。
可见性:当多个线程访问一个变量时,一个线程修改了这个变量的值,其他线程能够看得到。
未加volatile变量修饰的变量,在被修改之后,什么时候写入到主存是不确定的,因此其他线程读取该变量的值可能还是未被修改的值。
如果改变了被volatile关键字修饰了,那么JVM将会标记它为共享变量,共享变量一经修改,就会立即同步到主存中,并且通知其他线程(CPU缓存)中值生效,请去主存中读取该值。
有序性:程序的执行顺序按照代码的先后顺序执行。但是JVM在执行语句的过程会对代码进行重排序(重排序:CPU为了提高程序运行效率,可能会对输入代码进行优化,但是不保证程序的执行先后顺序和代码中的顺序一致,但是会保证程序最终执行结果和代码顺序执行的结果是一致的)。
在多线程的环境下,原有的顺序执行会发生错误。
在JVM中保证了一定的有序性,比如被volatile修饰后的变量,那么该变量的写操作先行发生于后面对这个变量的读操作。
所以要想程序在多线程环境下正确运行,必须保证原子性,可见性,有序性。
volatile的作用
当一个变量(类的普通变量,静态变量)被volatile修饰之后,那么将具备两个属性:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序
下面来看看线程池中一些变量的定义:
- private volatile ThreadFactory threadFactory;
- private volatile RejectedExecutionHandler handler;
- private volatile long keepAliveTime;
- private volatile boolean allowCoreThreadTimeOut;
- private volatile int corePoolSize;
- private volatile int maximumPoolSize;
可以看到线程工厂threadFactory,拒绝策略handler,没有任务时的活跃时间keepAliveTime,keepAliveTime的开关allowCoreThreadTimeOut,核心池大小corePoolSize,最大线程数maximumPoolSize
都是被volatile修饰中,因为在线程池中有若干个线程,这些变量必需保持对线程可见性,不然会引起线程池运行不正确。
volatile不能保证原子性
i++;
它是非原子性的,当变量i被volatile修饰时,是否能保证原子性呢?
做个试验:
- public class TestAtomVolatile {
- public volatile int i = 0;
- public void increase() {
- i++;
- }
- public static void main(String[] args) throws InterruptedException {
- final TestAtomVolatile test = new TestAtomVolatile();
- for(int i=0;i<10;i++){
- new Thread(){
- public void run() {
- for(int j=0;j<1000;j++)
- test.increase();
- System.out.println(test.i);
- };
- }.start();
- }
- }
- }
以上代码就是10个线程,分别对变量i进行自增操作,预期结果应该是10000,但是总会存在着小于10000的情况。输出结果如下:
对于这种情况,可以使用锁,synchronize,Lock,也可以使用原子变量。
原子变量的例子:
volatile的原理
- 下面这段话摘自《深入理解Java虚拟机》:
- “”观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
- lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
- 1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
- 2)它会强制将对缓存的修改操作立即写入主存;
- 3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
assert关键字
assert断言
在目前的java编码中,是不推荐使用的,这里只是稍微了解一下:
使用方式:
- public class LearnAssert {
- public static void main(String args[]){
- assert true;
- System.out.println("断言1成功执行");
- System.out.println("-----------");
- assert false:"error";
- System.out.println("断言2成功执行");
- }
- }
assert是为了在调试程序时候使用的,默认不推荐使用,测试程序可以使用junit。
synchronized关键字
关于锁关键字,有以下几个总结:
- 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
- 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
- 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
下面介绍一个锁的实例:
- public class ManyThread {
- int count = 0;
- public synchronized void autoIncrement() {
- count++;
- }
- public static void main(String args[]) {
- ManyThread manyThread = new ManyThread();
- Runnable runnable = new MyRunnable2(manyThread);
- new Thread(runnable, "a").start();
- new Thread(runnable, "b").start();
- new Thread(runnable, "c").start();
- new Thread(runnable, "d").start();
- }
- }
- class MyRunnable2 implements Runnable {
- private ManyThread manyThread;
- public MyRunnable2(ManyThread manyThread) {
- this.manyThread = manyThread;
- }
- @Override
- public void run() {
- for (int i = 0; i < 10000; i++) {
- manyThread.autoIncrement();
- System.out.println(Thread.currentThread().getName() + " 执行中 " + "count:" + manyThread.count);
- }
- }
- }
用synchronized修饰后的autoIncrement()方法,会被加锁,确保它每次执行的时候都能保证只有一个线程在运行。
transient关键字
Java中,一个类想要序列化,可以通过实现Serilizable接口的方式来实现,实现该接口之后,该类所有属性和方法都会自动序列化。
但是如果属性或方法被transient修饰,那么将不会被序列化。
干货——详解Java中的关键字的更多相关文章
- 详解java中staitc关键字
一.static定义 static是静态修饰符意思,什么叫静态修饰符呢?大家都知道,在程序中任何变量或者代码都是在编译时由系统自动分配内存来存储的,而所谓静态就是指在编译后所分配的内存会一直存在,直到 ...
- 详解详解Java中static关键字和final关键字的功能
摘要:static关键字和final关键字是Java语言的核心,深入理解他们的功能非常重要. 本文分享自华为云社区<Java: static关键字与final关键字>,原文作者:唐里 . ...
- 详解Java中的clone方法
详解Java中的clone方法 参考:http://blog.csdn.net/zhangjg_blog/article/details/18369201/ 所谓的复制对象,首先要分配一个和源对象同样 ...
- 详解Java中的final关键字
本文原文地址:https://jiang-hao.com/articles/2019/coding-java-final-keyword.html1 final 简介2 final关键字可用于多个场景 ...
- 从缓存入门到并发编程三要素详解 Java中 volatile 、final 等关键字解析案例
引入高速缓存概念 在计算机在执行程序时,以指令为单位来执行,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入. 由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这 ...
- 【Java学习笔记之三十三】详解Java中try,catch,finally的用法及分析
这一篇我们将会介绍java中try,catch,finally的用法 以下先给出try,catch用法: try { //需要被检测的异常代码 } catch(Exception e) { //异常处 ...
- 详解Java中的字符串
字符串常量池详解 在深入学习字符串类之前, 我们先搞懂JVM是怎样处理新生字符串的. 当你知道字符串的初始化细节后, 再去写String s = "hello"或String s ...
- 详解Java中的Object.getClass()方法
详解Object.getClass()方法,这个方法的返回值是Class类型,Class c = obj.getClass(); 通过对象c,我们可以获取该对象的所有成员方法,每个成员方法都是一个Me ...
- 详解Java中的访问控制修饰符(public, protected, default, private)
Java中的访问控制修饰符已经困惑笔者多时,其中较复杂的情况一直不能理解透彻.今天下定决心,系统.全面地研究Java中的访问控制修饰符的所有方面,并整理成这篇文章,希望有同样疑惑的读者读完后能有所收获 ...
随机推荐
- 关于HTML5中的sessionStorage的会话级缓存使用
sessionStorage作为HTML5的Web Storage的两种存储方式之一. 用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数 ...
- EasyUI ComboGrid 笔记(支持分页)
业务要求: 下拉框做选择时需要展现多个字段供用户参考,由于内容可能会很多,故还需要考虑分页. 解决方案: 由于项目整体已经采用了EasyUI,在浏览了demo以后,初步考虑使用EasyUI的combo ...
- Codeforces 1083C Max Mex
Description 一棵\(N\)个节点的树, 每个节点上都有 互不相同的 \([0, ~N-1]\) 的数. 定义一条路径上的数的集合为 \(S\), 求一条路径使得 \(Mex(S)\) 最大 ...
- Python用HTMLTestRunner生成html测试报告
小编的主机:mac 一.引入HTMLTestRunner包 1.下载HTMLTestRunner.py,已上传到网盘,点击下载 2.将HTMLTestRunner.py复制到python安装目录的Li ...
- shapefile添加字段 设置文件名为字段内容
转眼间,这一年又结束了,再记录一点知识吧 同事说他有好多shapefile,想给每个shapefile添加一字段,并设置该字段的内容为shapefile文件名,想着用arcpy实现,于是有了下面的代码 ...
- 数据流图——从软考真题中学画数据流图DFD
文章目录 题目 画顶层图 画0层图 解题技巧 题目 建议将题目复制到word后与此文分屏查看.后面需要多次查看题目. 某高校欲开发一个成绩管理系统,记录并管理所有选修课程的学生的平时成绩和考试成绩, ...
- squid 透明代理配置
阿铭在教程中已经介绍过squid的安装和配置,http://study.lishiming.net/chapter22.html 教程中只介绍了初级的正向代理和反向代理,这篇文档将要介绍透明代理如何配 ...
- python基础--------字符串的调用详解(2)
Python 字符串的的调用方法~~~@@@ 17. strip : 去除字符串左右两边指定的字符 18. rstrip : 去除字符串右边指定的字符 19 . lstrip : 去除 ...
- 01 C语言程序设计--01 C语言基础--第1章 C语言概述&第2章 GCC和GDB
走进嵌入式开发的世界,企业级项目课程让你达到企业嵌入式应用开发要求.名师在线答疑,解决疑难.科学评测体系,系统评估学习.核心项目实........ 30 门课程 241小时12分钟 824 人学习 学 ...
- Exp3 免杀原理与实践_05齐帅
Exp3 免杀原理与实践 20154305_齐帅 想要弄懂免杀,一定得先把基础问题弄明白啊~~ 一.基础问题回答 (1)杀软是如何检测出恶意代码的? - -检测特征码: 依靠分析总结出计算机病毒中常出 ...