• JAVA基础关键字小结一


    基础部分总是看起来简单,若要明白原理和使用场景,需要慢慢的体会。

    本文对常用的关键字如final,transient,volatile,static以及foreach循环的原理重新梳理了一遍。

    一、final的作用?
    1.final class
    当一个类被定义成final class,表示该类的不能被其他类继承,即不能用在extends之后。否则在编译期间就会得到错误。
    final方法可以保证其执行的确定性,从而确保了方法调用的稳定性。在一些框架设计中就会经常见到抽象类的一些已实现方法的方法被限制成final,因为在框架中一些驱动代码会依赖这些方法的实现了完成既定的目标,所以不希望有子类对它进行覆盖。
    final方法为何会高效:
    final方法会在编译的过程中利用内嵌机制进行inline优化。inline优化是指:在编译的时候直接调用函数代码替换,而不是在运行时调用函数。inline需要在编译的时候就知道最后要用哪个函数, 显然,非final是不行的。非final方法可能在子类中被重写,由于可能出现多态的情况,编译器在编译阶段并不能确定将来调用方法的对象的真正类型,也就无法确定到底调用哪个方法。
    final Variable
    简单说,Java里的final变量只能且必须被初始化一次,之后该变量就与该值绑定。但该次赋值不一定要在变量被定义时被立刻初始化,Java也支持通过条件语句给final变量不同的结果,只是无论如何该变量都只能变赋值一次。
    不过Java的final变量并非绝对的常量,因为Java的对象变量只是引用值,所以final只是表示该引用不能改变,而对象的内容依然可以修改。
    当final用来修饰类(Class) 和方法(Method)时,它主要影响面向对象的继承性,没有了继承性就没有了子类对父类的代码依赖,所以在维护时修改代码就不用考虑会不会破坏子类的实现,就显得更加方便。而当它用在变量(Variable)上时,Java保证了变量值不会修改,更进一步设计保证类的成员也不能修改的话,那么整个变量就可以变成常量使用,对于多线程编程是非常有利的。所以final对于代码维护有非常好的作用。

    二、transient的作用?
    transient有“临时的”,"短暂的"含义,我们了解过Serializable,Java序列化,当对某些变量我们不想对它进行序列化的时候就可以将此变量设置为transient,transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。transient说明一个属性是临时的,不会被序列化。
    1、transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。
    2、被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
    3、一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。也可以认为在将持久化的对象反序列化后,被transient修饰的变量将按照普通类成员变量一样被初始化。
    public class Main implements Serializable

    public class ExternalizableTest implements Externalizable
    private transient String content = "哈哈~我将会被序列化,不管我是是否被transient关键字修饰";
    我们知道在Java中,对象的序列化可以通过实现两种接口来实现,若操作的是一个Serializable对象,则所有的序列化将会自动进行,若操作的是 一个Externalizable对象,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量,这与是否被transient修饰无关。因此第二个例子输出的是变量content初始化的内容,而不是null。

    三、Java 中的volatile关键字?
    1:为什么会产生错误的数据?
    多线程引起的,因为对于多线程同时操作一个整型变量在大并发操作的情况下无法做到同步,而Atom提供了很多针对此类线程安全问题的解决方案,因此解决了同时读写操作的问题。


    2:为什么会造成同步问题?
    Java多线程在对变量进行操作的时候,实际上是每个线程会单独分配一个针对i值的拷贝(独立内存区域),但是申明的i值确是在主内存区域中,当对i值修改完毕后,线程会将自己内存区域块中的i值拷贝到主内存区域中,因此有可能每个线程拿到的i值是不一样的,从而出现了同步问题。


    3:为什么使用volatile修饰integer变量后,还是不行?
    因为volatile仅仅只是解决了存储的问题,即i值只是保留在了一个内存区域中,但是i++这个操作,涉及到获取i值、修改i值、存储i值(i=i+1),这里的volatile只是解决了存储i值得问题,至于获取和修改i值,确是没有做到同步。


    4:既然不能做到同步,那为什么还要用volatile这种修饰符?
    主要的一个原因是方便,因为只需添加一个修饰符即可,而无需做对象加锁、解锁这么麻烦的操作。但是本人不推荐使用这种机制,因为比较容易出问题(脏数据),而且也保证不了同步。


    5:那到底如何解决这样的问题?
    第一种:采用同步synchronized解决,这样虽然解决了问题,但是也降低了系统的性能。
    第二种:采用原子性数据Atomic变量,这是从JDK1.5开始才存在的针对原子性的解决方案,这种方案也是目前比较好的解决方案了。


    6:Atomic的实现基本原理?
    首先Atomic中的变量是申明为了volatile变量的,这样就保证的变量的存储和读取是一致的,都是来自同一个内存块,然后Atomic提供了getAndIncrement方法,该方法对变量的++操作进行了封装,并提供了compareAndSet方法,来完成对单个变量的加锁和解锁操作,方法中用到了一个UnSafe的对象,现在还不知道这个UnSafe的工作原理(似乎没有公开源代码)。Atomic虽然解决了同步的问题,但是性能上面还是会有所损失,不过影响不大,网上有针对这方面的测试,大概50million的操作对比是250ms : 850ms,对于大部分的高性能应用,应该还是够的了。

    四、foreach循环的原理?
    在编译的时候编译器会自动将对for这个关键字的使用转化为对目标的迭代器的使用,这就是foreach循环的原理。进而,我们再得出两个结论:

    1、ArrayList之所以能使用foreach循环遍历,是因为ArrayList所有的List都是Collection的子接口,而Collection是Iterable的子接口,ArrayList的父类AbstractList正确地实现了Iterable接口的iterator方法。之前我自己写的ArrayList用foreach循环直接报空指针异常是因为我自己写的ArrayList并没有实现Iterable接口

    2、任何一个集合,无论是JDK提供的还是自己写的,只要想使用foreach循环遍历,就必须正确地实现Iterable接口

    实际上,这种做法就是23中设计模式中的迭代器模式。
    上面的讲完了,好理解,但是不知道大家有没有疑问,至少我是有一个疑问的:数组并没有实现Iterable接口啊,为什么数组也可以用foreach循环遍历呢?
    Java将对于数组的foreach循环转换为对于这个数组每一个的循环引用。

    五、static关键字有哪些作 用?
    static关键字有哪些作 用,如果你答出static修饰变量、修饰方法我会认为你合格,答出静态块,我会认为你不错,答出静态内部类我会认为你很好,答出静态导包我会对你很满 意,因为能看出你非常热衷研究技术。
    使用静态导入
      静态导入的语法是:

      import static 包名.类名.静态成员变量;

      import static 包名.类名.静态成员函数;

    7、static可以被继承吗?什么时候用到?
    static定义的方法和变量可以被继承,但是不能被重写,无法实现多态。

    7.1. 静态方法 
    通常,在一个类中定义一个方法为static,那就是说,无需本类的对象即可调用此方法

    声明为static的方法有以下几条限制: 
    · 它们仅能调用其他的static 方法。 
    · 它们只能访问static数据。 
    · 它们不能以任何方式引用this 或super。
    7.2. 静态变量

    声明为static的变量实质上就是全局变量。当声明一个对象时,并不产生static变量的拷贝,而是该类所有的实例变量共用同一个static变量。静态变量与静态方法类似。所有此类实例共享此静态变量,也就是说在类装载时,只分配一块存储空间,所有此类的对象都可以操控此块存储空间,当然对于final则另当别论了 
    static和final一块用表示什么 
    static final用来修饰成员变量和成员方法,可简单理解为“全局常量”! 
    对于变量,表示一旦给值就不可修改,并且通过类名可以访问。 
    对于方法,表示不可覆盖,并且可以通过类名直接访问。
    7.3static 块初始化加载一次
    7.4 静态类

    通常一个普通类不允许声明为静态的,只有一个内部类才可以。这时这个声明为静态的内部类可以直接作为一个普通类来使用,而不需实例一个外部类。
    static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。

    7.5静态导包

    静态导入的语法是:

      import static 包名.类名.静态成员变量;

      import static 包名.类名.静态成员函数;

      注意导入的是成员变量和方法名。

    结论:java中静态属性和静态方法可以被继承,但是没有被重写(overwrite)而是被隐藏. 
    原因: 
    1). 静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成对,不需要继承机制及可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在"隐藏"的这种情况。 
    2). 多态之所以能够实现依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。重写的功能是:"重写"后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。 
    3). 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态。

  • 相关阅读:
    svn服务器的搭建和使用以及git服务器的搭建和使用
    MySQL Performance Schema详解
    Lua集成Redis及Nginx
    分布式系统下的CAP定理
    分布式事务一站式解决方案与实现
    Zookeeper集群搭建及原理
    Redis主从复制搭建及原理
    vue中给img的src添加token
    调度器34—RT负载均衡 Hello
    tracer ftrace笔记(5)—— 使用笔记汇总 Hello
  • 原文地址:https://www.cnblogs.com/lq147760524/p/7045393.html
Copyright © 2020-2023  润新知