Java基础知识(1)-- 关键字

目录:

1、foreach

2、static

3、final

4、synchronized

5、volatile

6、transient

7、switch-case

8、default

9、super、this

10、throw、throws

11、final、finally、finalize


一、关键字

1、foreach原理

为什么是数组或者实现了Iterable接口,就能实现遍历呢?

* 对于List,编译器会调用Iterable接口的 iterator方法来循环遍历数组的元素,iterator方法中是调用Iterator接口的的next()和hasNext()方法来做循环遍历。java中有一个叫做迭代器模式的设计模式,这个其实就是对迭代器模式的一个实现。

* 对于数组,就是转化为对数组中的每一个元素的循环引用。

2、static

1)静态内部类:

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法。

 Outter.Inner inner = new Outter.Inner();

2)静态方法,将其变为类方法,可以直接使用“类名.方法名”的方式调用,常用于工具类

3)静态变量,将其变为类的成员,从而实现所有对象对于该成员的共享

4)静态代码块,将多个类成员放在一起初始化,使得程序更加规整,其中理解对象的初始化过程非常关键:

              基类的static域加载

                           |

                           |

              子类的static域加载                    

                           |

                           |

基类的成员变量初始化(基本类型初始化为默认值,对象引用设为null,若有初值则初始化为初值)           

                            |

                            |

基类的构造器加载(若在构造器中调用的方法在子类中被覆盖过,则调用覆盖后的方法)

                           |

                           |

             子类的成员变量初始化                             

                           |

                           |

                子类的构造器加载

5)静态导包,将类的方法直接导入到当前类中,从而直接使用“方法名”即可调用类方法,更加方便

import static java.lang.System.out;

import static java.lang.Integer.*;

public class TestStaticImport

{

    public static void main(String[] args)

    {

         out.println(MAX_VALUE);

        out.println(toHexString(42));

    }

}


3、final

1)修饰类

   当用final修饰一个类时,表明这个类不能被继承。

2)修饰方法

   只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为final的。

3)修饰变量

   对于一个final变量,

   如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改,并且当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当作编译期常量使用;

   如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象,但是它指向的对象的内容是可变的。

4)局部内部类和匿名内部类中使用的外部局部变量只能是final变量

  public class Test {

    public static void main(String[] args)  {

    }

    public void test(final int b) {

        final int a = 10;

        new Thread(){

            public void run() {

                System.out.println(a);

                System.out.println(b);

            };

        }.start();

    }

   }

   当test方法执行完毕之后,变量a的生命周期就结束了,而此时Thread对象的生命周期很可能还没有结束,那么在Thread的run方法中继续访问变量a就变成不可能了,但是又要实现这样的效果,怎么办呢?Java采用了复制的手段来解决这个问题。

   如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类(局部内部类)的常量池中添加一个内容相等的字面量或直接将相应的字节码嵌入到执行字节码中。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等,因此和方法中的局部变量完全独立开。

   如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。

   从上面可以看出,run方法中访问的变量a根本就不是test方法中的局部变量a。这样一来就解决了前面所说的生命周期不一致的问题。但是新的问题又来了,既然在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?

   对,会造成数据不一致性,这样就达不到原本的意图和要求。为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。到这里,想必大家应该清楚为何方法中的局部变量和形参都必须用final进行限定了。

4、synchronized

   synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题。从语法上讲,Synchronized总共有三种用法:

 (1)修饰普通方法,锁是方法所在的实例对象;

 (2)修饰静态方法,锁是当前类的Class对象;

 (3)修饰代码块,锁是括号里面的对象。

   原理:

   代码块同步是使用monitorenter和monitorexit指令实现,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明,但是方法的同步同样可以使用这两个指令来实现。monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处, JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个 monitor 与之关联,当一个monitor 被持有后,它将处于锁定状态。线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获得对象的锁。

1)同步块

每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。

3、如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

执行monitorexit的线程必须是monitor的所有者:

指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor的所有权。

2)同步方法

方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其所在对象的常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取方法所在对象或类对象的monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

5、volatile

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1)实现可见性

线程本身并不直接与主内存进行数据的交互,而是通过线程的工作内存来完成相应的操作。这也是导致线程间数据不可见的本质原因。因此要实现volatile变量的可见性,直接从这方面入手即可。

对volatile变量的写操作与普通变量的主要区别有两点:

 (1)修改volatile变量时会强制将修改后的值刷新的主内存中。

 (2)修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。

通过这两个操作,就可以解决volatile变量的可见性问题。

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

volatile关键字禁止指令重排序有两层意思:

(1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

(2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

volatile 实现原理:

  下面这段话摘自《深入理解Java虚拟机》:

 “观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”

 lock前缀指令实际上相当于一个内存屏障(内存栅栏),内存屏障会提供3个功能,第一点保障禁止指令重排序,第二、三点实现可见性:

    (1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;

 (2)它会强制将对缓存的修改操作立即写入主存;

 (3)如果是写操作,它会导致其他CPU中对应的缓存行无效。


6、transient

   1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。

   2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。

   3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。

7、switch-case

   swtich()变量类型只能是int、short、char、byte和enum类型(JDK 1.7 之后,类型也可以是String了),不能是浮点数,如float、double等类型,因为浮点数不能使用==、!=等相等符号进行对比。switch 不支持 long,是因为 switch 的设计初衷是对那些只有少数的几个值进行等值判断,如果值过于复杂,那么还是用 if 比较合适。JDK13增加了yield关键字,可以作为switch的返回值,只能跳出switch块并返回一个值。


8、default

  (1)switch-case的默认分支  

  (2)default方法(java8新特性):

   本来写Java接口的时候,是不能有方法体的函数,就类似于C++中的虚函数,default关键字在接口中修饰方法时,方法可以有方法体。default关键字可以让接口中的方法可以有默认的函数体,当一个类实现这个接口时,可以不用去实现这个方法,当然,这个类若实现这个方法,就等于子类覆盖了这个方法,最终运行结果符合Java多态特性。


9、super、this

   this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。

   1.普通的直接引用

     这种就不用讲了,this相当于是指向当前对象本身。

   2.形参与成员名字重名,用this来区分:

   3.引用构造函数

     这个和super放在一起讲,见下面。


   super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。

   super也有三种用法:

   1.普通的直接引用

     与this类似,super相当于是指向当前对象的父类,这样就可以用super.xxx来引用父类的成员。

   2.子类中的成员变量或方法与父类中的成员变量或方法同名

   3.引用构造函数

     super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句),从而委托父类完成一些初始化工作。

     this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)  


10、throw、throws

    throw是语句抛出一个异常。

    语法:throw (异常对象);

          throw e;

    throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常)

    语法:[(修饰符)](返回值类型)(方法名)([参数列表])[throws(异常类)]{......}

          public voiddoA(int a) throws Exception1,Exception3{......}


11、final、finally、finalize

 finally:

    首先一个不容易理解的事实:在try块中即便有return,break,continue等改变执行流的语句,finally也会执行。

    也就是说:try…catch…finally中的return只要能执行,就都执行了,他们共同向同一个内存地址(假设地址是0×80)写入返回值,后执行的将覆盖先执行的数据,而真正被调用者取的返回值就是最后一次写入的。

 注意事项:

 (1)不要在fianlly中使用return。

 (2)不要在finally中抛出异常。

 (3)减轻finally的任务,不要在finally中做一些其它的事情,finally块仅仅用来释放资源是最合适的。

 (4)尽量将所有的return写在函数的最后面,而不是try … catch … finally中。

finalize:

类的finalize方法,可以告诉垃圾回收器应该执行的操作,该方法从Object类继承而来。在从堆中永久删除对象之前,垃圾回收器调用该对象的finalize方法。注意,无法确切地保证垃圾回收器何时调用该方法,也无法保证调用不同对象的方法的顺序。即使一个对象包含另一个对象的引用,或者在释放一个对象很久以前就释放了另一个对象,也可能会以任意的顺序调用这两个对象的finalize方法。如果必须保证采用特定的顺序,则必须提供自己的特有清理方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容