1月29日  说说你对static关键字的理解

        如果与其它技术人员交流关于static关键字的理解的话,思路要清晰,从字段到类,再说说static的功能性作用。

        (1)static 修饰字段

                  static修饰的字段是属于类的,这个类的所有对象均共享此一个字段的值。可以直接使用类名调用。

        (2)static修饰方法

                  static修饰的方法也是可以直接使用类名调用。一般在单例模式中,可以将构造函数私有化,然后将方法直接设置为静态的,那么调用的时候,就不用需要new这个类的对象。

        (3)static修饰内部类

                  static修饰的内部类我见过的情况就是单例模式,由某个单例类的内部嵌套类来生成单例对象。

        (4)static静态代码块

                   静态代码块一般用于执行一些初始化的动作。

        (5)静态导包

                   static关键字还可以用于静态导包。使用 import static 包名.方法名; 

                   也能够在其它类中导入静态常量。

          其它:

                    其实main函数也是一种特殊的静态函数,加载某个类由触发这个类的静态函数触发。

1月30日  交流一下final关键字的用途

         (1)修饰变量。被final修饰的变量不能被修改。

         (2)修饰方法。被final修饰的方法不能被子类修改。一般在模版设计模式中,父类可以将模版方法使用final修饰。再提供一些抽象方法给子类实现。

         (3)修饰类。被final修饰的类不能被继承。

         (4)还有一些特殊的用法,比如某个字段被final修饰,那么必须在编译阶段就指明它的值。可以直接为final修饰的成员变量设置初始值,也可以为构造函数中为final修饰的变量设置初始值。

         (5)特殊用法就是,在并发编程中。因为JVM可能会出现重排序优化,在构造函数执行结束的时候, final域的变量一定会被初始化,而普通变量可能没有被初始化。

         (6)还有就是,一般为匿名内部类提供参数,这个参数需要使用final修饰。因为匿名类的生命周期有可能超过局部变量的声明周期,所以使用final来延长这个局部变量的生命周期。

2月11日  说说对关键字protected与default的作用范围理解

        如果某个字段或者方法被protected修饰,可以在本包中创建这个对象,可以调用这个对象的这个protected修饰字段或者方法。如果在另外的包中创建此对象,那么调用对象的protected修饰的字段或者方法是不行的。

        另外一个就是在子类中可以直接访问父类的protected修饰的方法或者字段。

权限修饰符,那么这就是包访问权限。在同一个包中,可以创建直接调用某对象的protected字段或者方法。

2月11日  交流一下序列化 Serializable [ sɪərɪrlaɪ'zəbl C瑞来紫波 ]相关的东西,transient[ˈtrænziənt trainZi 恩特]。

         [ 什么是序列化,序列化版本号,transient关键字,与static关键字,属性是对象 ]

         对象序列化就是把对象转换成字节数组,便于进行网络传输。反序列化也就是把字节数组重新恢复成对象。

         实现序列化接口Serializable接口会产生一个序列化版本ID。JVM会先把从网络环境中获取到的字节数组恢复成一个对象,然后把这个对象的序列化版本ID与本地实体对象中的序列化版本ID比较。如果比较一致,则可以成功的反序列化,将熟悉赋值到本地的实体对对象中。反之,则反序列化失败。

          用transient修饰的成员变量的值不会被反序列化。

静态区中。

          如果某个成员变量也是一个对象,那么这个成员变量也要实现序列化接口。

2月12日  谈谈对volatile关键字的理解

        volatile关键字的用途就是让变量在多个线程间可见,强制线程从公共堆栈中取得变量的值。

        volatile关键字还有一个作用就是禁止指令重排序优化。

        缺点就是但是它不保证原子性。

        volatile关键字原理:

        (1)将最先的变量的值写入到主内存中

        (2)使其它线程中缓存了该变量的内存地址失效

         JMM内存模型要求会画。线程 《===》 线程私有内存(共享变量副本) 《===》主存

         此处牵扯到单例模式的双重检验锁的懒汉式。

         下面重排序过程务必能够手写出来。

       instance = new Singleton(); 这一行代码可以拆分成三句。

       memory = allocate();  //1.分配对象的内存空间

       initInstance(memory); //2.初始化对象

       instance = memroy;    //3.设置instance引用指向这块内存

        上面的代码中,2和3可能会被重排序。

        另外一个并发线程B可能拿到的是没有被A线程初始化的对象。

        解决办法:禁止指令重排序。所以使用 volatile 关键字修饰instance对象

public static Single getInstance() {
if (instance == null) {
synchronized (Single.class) {
if (instance == null) {
instance = new Single();
}
}
}
return instance;
}

2月13日  谈谈对Java异常体系的理解

        常见的RuntimeException : 

        NullPointerException  ArrayIndexOutOfBoundExceptions  ClassCastException  NumberFormatException IllegalArgumentException

        空指角标内裤转换

        数字解析非法参数

        找不到类解析打断SQL

        SQLException ParseException InterruptException ClassNotFoundException  

2月14日  谈谈对synchronized关键字的理解

        一般如果交流这些略有复杂性的关键字的时候,可以先从使用角度(作用)来阐述,然后是阐述原理。

        synchronized有两个使用场景。synchronized关键字的基本用法就是修饰方法。修饰的方法就是一个同步方法。同一时刻,只能有一个线程访问这个方法,同步方法的同步锁是当前对象的字节码文件。另外一个使用场景就是修饰同步代码块,同一时刻使用有一个线程进入这个临界区,同步锁是任意指定一个对象。

监视器进入指令插入到同步代码的开始位置,将监视器退出指令加入到同步代码的结束或者异常位置。

        synchroized可重入性:当一个线程在得到对象锁,再次请求该对象锁是可以的。

        synchronized在内存中的作用。它能保证变量的可见性与排他性。

        可见性分析:在释放锁的时候,将线程私有变量刷新到主存。获取锁的时候,将私有内存中变量的值废弃,重新从主存中获取最新变量的值。排他性分析:在进入同步代码之前,使用监视器进入指令。在方法退出或者异常出,使用监视器退出指令。保证临界区内中有一个线程。

2月18日  面向对象的基本特征是什么?

        JAVA是一门面向对象的语言,它的基本特征是封装、继承、多态。

        封装也就是把客观事物封装成抽象的类,并且隐藏类的实现细节,仅仅对外提供公共的访问方式。生活中封装也处处都能体现出来,就像开车一样,只需要学习如何操作防线盘,刹车,油门,不需要知道汽车的内部构造,这样的话,可以降低我们学习的难度。编程也一样。

        继承。子类是从父类派生出来的类,子类拥有父类所有的属性与方法。并且子类还能够扩展。继承的好处之一就是增加代码的复用性。就像男人继承了人,也就有了人的一切性质。

        多态。我的理解就是同一种事物有不同的形态。比如我们的老师,他在生活中可能扮演着父亲一样的角色,在学习中扮演着老师的角色。从代码方面来实现的话,那么将老师这个人视为一个对象,他就实现了父亲接口与教学接口。在不同环境下,是扮演的角色不一样。

2月18日  聊聊多态

        多态的前提:类与类之间要有继承或者实现的关系。父类引用指向子类对象,子类重写父类的方法。

        多态的原理:动态绑定。绑定,在Thinking in Java 这本书里面的解释是:对象引用找到正确的方法体的过程。动态绑定也就是指:程序在运行阶段,动态的判断对象引用所指向的实际类型。这个时候,或许对象引用是一个父类型,但是执行的确是子类的方法体。

        多态的优势:对象复用性与灵活性。比如我们在某个方法上定义一个接口类型的参数,那么传递进来的实际对象可以是任何一个实现类。所以说,这么方法不用担心实际传递对象的问题,这体现了这个方法的服用性与灵活性。

2月18日 自动装箱与自动拆箱

        自动装箱与自动拆箱。自动装箱是把基本数据类型转换为包装类。自动拆箱是把包装类对象转为基本数据类型。

        Integer a = 10 ; 相当于调用了  Integer a = new Integer(10);

        Integer b = 10 ;  sout(a == b) 的结果是 true。 因为包装类常量 在 -128 到 127 之间。

        sout( a == 10) ,自动拆箱,所以结果是true。

2月18日 String、StringBuilder、StringBuffer

        String是字符串,它是一种不可变的对象。任何对String变量的修改实则是重新创建了一个新的String对象。

        StringBuffer是一个可变的字符序列,它线程安全。

        StringBuilder也是一个可变的字符序列,但是它线程不安全。 

        一般来说,从执行效率上来看,StringBuilder的效率最高。因为它用于单线程环境并且是一个可变对象,操作的是StringBuilder对象本身,并没有创建新的对象。StringBuffer的效率其次,它的也是可变对象,但是因为多线程的原因,需要判断同步锁,所以效率略低与StringBuilder。性能相对最差的是字符串,它是不可变对象,在操作字符串的过程中,有可能会产生很多新的对象,所以性能相对最差。

2月18日 字符串存放的位置

        String是存放在字符串常量池,在编译期已经被确定了。

        如果是通过构造函数创建的字符串,那么会存放在堆内存当中。

        对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象

INTERN方法

        直接使用双引号声明出来的String对象会直接存储在常量池中。
        如果是用new 声明的 String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中,并返回池中的引用。

String str = new String("abc");
//保存地址
Object a = str;
//返回字符串常量池中的"abc"内存地址
str = str.intern();
//保存地址
Object b = str;
//比较地址
System.out.println(a == b); //false

字符串倒序代码手写

private static String reverse(String str) {
if (StringUtils.isBlank(str)) {
throw new IllegalArgumentException("str should not be blank");
}
StringBuilder stringBuilder = new StringBuilder(str);
return stringBuilder.reverse().toString();
}

字符串设计为不可变类的优势

        线程安全

        因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

        加快字符串处理速度

        因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

2月20日  为什么要重写hashCode与equals方法       

        首先要明白的是hashCode与equals方法的基本作用。

        hashCode返回的是对象的内存地址,equals方法比较的是两个对象是否相等,在Object类中默认是比较的两个对象的内存地址。它们共同用于比较两个对象是否相等。

        我个人理解为什么要重写hashCode与equals方法。为了提升程序的效率才重写hashCode与equals方法。在比较对象的时候,先比较hashCode,如果hash值不一样,那么没必要在进行比较了。如果hash值一样,那么再比较equals方法。这样的好处就是可以大量减少比较equals方法的次数。常见的要重写hashCode与equals方法的例子就是在集合框架中。

         在集合框架中,Set集合是无序的,不可以重复的。为了保证元素的唯一性,新插入一个元素的时候,先计算它的hash值,然后通过这个哈希值定义到一个地址上。然后看看这个地址上有没有元素。如果没有的话,那么就直接插入。如果有的话,那么再比较equals方法。这样设计的好处就是可以减少equals方法的比较次数,从而提升Set集合的插入效率。

提升比较两个对象是否相同的效率。

2月20日  JVM的内存分布

       堆内存、JAVA栈内存、方法区、本地方法栈、程序计数器。

       堆内存使用NEW关键字创建的对象,都保存在堆内存中。这块内存不需要我们关心,会由垃圾回收器进行控制。堆内存被所有线程共享,并且在JVM中只有一个。另外,数组也存放在堆内存中。

       Java栈用于保存方法的临时变量,对象引用。

       方法区。用于存储已被虚拟机加载的类信息、常量、静态变量等数据。在方法区中有一个非常重要的部分就是常量池,比如字符串常量池。

       本地方法栈。 本地方法栈则是为虚拟机使用到的Native方法服务。而JAVA的栈内存是为了保存我们编写的临时变量与对象引用。

        程序计数器。JAVA其实说到底也就是一条一条的指令。程序计数器个人理解也就是用来指示执行哪条指令的。每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的。

2月26日  谈谈对ThreadLocal这个对象的理解


       定义:ThreadLocal可以视为线程局部变量。

       结构:一个Thread对象中持有一个的ThreadLocalMap。键为具体的ThreadLocal对象,值使用的是嵌套类Entry对象进行封装。

       Thread  =   ThreadLocalMap  =  Entry[]   ,  Entry =  ThreadLocal : value。

       作用:在线程的任务代码块中。调用ThreadLocal的set方法设值,调用ThreadLocal对象的get方法取值。其实内部使用的是嵌套类Entry对象进行封装键值对。键为ThreadLocal,值为我们存放的值。让后把这个Entry对象存放到当前线程的ThreadLocalMap中。

        因为Entry的key是一个弱引用的ThreadLocal对象,所以在 垃圾回收 之前,将会清除此Entry对象的key。那么, ThreadLocalMap 中就会出现 key 为 null 的 Entry,就没有办法访问这些 key 为 null 的 Entry 的 value。所以value所占内存不会被释放。若在指定的线程任务里面,调用ThreadLocal对象的get()、set()、remove()方法,可以避免出现内存泄露。

       内存泄露:为什么要将ThreadLocal设计为弱引用?

答:因为弱引用的对象的生命周期直到下一次垃圾回收之前被回收。弱引用的对象将会被置为null。我们可以通过判断弱引用对象是否已经为null,来进行相关的操作。在ThreadLocalMap中,如果键ThreadLocal已经被回收,说明ThreadLocal对象已经为null,所以其对应的值已经无法被访问到。这个时候,需要及时手动编写代码清理掉这个键值对,防止内存泄露导致的内存溢出。

        补充:ThreadLocal 的经典使用场景是数据库连接和 session 管理等。

ThreadLocal与Synchronized的区别

synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。

       一句话,ThreadLocal用于数据隔离,而Synchronized用于数据共享。