Java核心36讲笔记整理

@(Java基础)[学习笔记|]

一、谈谈你对Java体系的理解?“Java是解释执行”,这句话是正确的吗?


  • write once run anywhere——一次编写、到处运行
  • 虚拟机的回收机制
关于Java的解释执行与编译执行

首先java先经历过javac第一次编译成字节码,也就是二进制的.class文件,在运行的时候,通过jvm内嵌的解释器将字节码转换成机器码。现在常见的jvm都提供了JIT(just in time)编译器,动态编译器,具有缓存功能,会将编译过的代码放在缓存区。能够在运行时将热点代码(常见代码)编译成机器代码,这部分热点代码就属于编译执行,而不是解释执行。

二、对比Exception和Error——运行时异常与一般异常的区别


  1. Exception 和 Error 都是继承了 Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。
  2. Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理
  3. Error 是指在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM 自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如 OutOfMemoryError 之类,都是 Error 的子类。
  4. Exception 又分为可检查(checked)异常和不检查(unchecked)异常,可检查异常在源代码里必须显式地进行捕获处理,这是编译期检查的一部分。不检查异常就是所谓的运行时异常,类似 NullPointerException、ArrayIndexOutOfBoundsException 之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。
  5. 常见的ERROR:NoClassDefFoundError、VirtualMachineError、OutOfMemoryError、StackOverflowError 常见的Exception:IOException(Checked Exception) RuntimeException、NullPointerException、ClassCastException、SecurityException/ClassNotFoundException
  6. 从性能角度来审视一下 Java 的异常处理机制,这里有两个可能会相对昂贵的地方:

try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响 JVM 对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码;与此同时,利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句(if/else、switch)要低效。

Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作。如果发生的非常频繁,这个开销可就不能被忽略了。

注意: (1) 一个函数尽管抛出了多个异常,但是只有一个异常可被传播到调用端。最后被抛出的异常时唯一被调用端接收的异常,其他异常都会被吞没掩盖。 (2) 不要在finally代码块中处理返回值。 (3)请勿在try代码块中调用return、break或continue语句。万一无法避免,一定要确保finally的存在不会改变函数的返回值。

三、强引用、软引用、弱引用、幻象引用有什么区别?具体使用场景是什么?



不同的引用类型,主要体现的是对象不同的可达性(reachable)状态和对垃圾收集的影响。

所谓强引用("Strong" Reference),就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,当然具体回收时机还是要看垃圾收集策略。

软引用(SoftReference),是一种相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象。JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。

弱引用(WeakReference)并不能使对象豁免垃圾收集,仅仅是提供一种访问在弱引用状态下对象的途径。这就可以用来构建一种没有特定约束的关系,比如,维护一种非强制性的映射关系,如果试图获取时对象还在,就使用它,否则重现实例化。它同样是很多缓存实现的选择。

对于幻象引用,有时候也翻译成虚引用,你不能通过它访问对象。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制,比如,通常用来做所谓的 Post-Mortem 清理机制,我在专栏上一讲中介绍的 Java 平台自身 Cleaner 机制等,也有人利用幻象引用监控对象的创建和销毁。
复制代码

四、理解Java的字符串,String、StringBuffer、StringBuilder有什么区别?


String 字符串常量 StringBuffer 字符串变量(线程安全) StringBuilder 字符串变量(非线程安全)

StringBuffer

简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。

而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。

Java.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。

可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。 StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。 例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。

StringBuilder

java.lang.StringBuilder一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。 简单说来,StringBuilder就是去掉了StringBuffer的synchronized关键字

五、谈谈Java反射机制,动态代理是基于什么原理?


深入Java机制(一)--反射机制和动态代理机制

六、int 和 integer 有什么区别 integer的值缓存范围?


  • int与integer的基本使用对比

(1)Integer是int的包装类;int是基本数据类型; (2)Integer变量必须实例化后才能使用;int变量不需要; (3)Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ; (4)Integer的默认值是null;int的默认值是0。

  • int与Integer的深入对比
    • 新(new)对象与新(new)对象的比较(Integer == Integer)
    • 对象与值的比较(Integer == int)
    • 装箱对象与新对象的比较(Integer i = new Integer(100) == Integer j = 100)
    • 装箱对象与装箱对象的比较(Integer源码中的缓存)
  • 数据类型

原始数据类型(基本数据类型):boolean,char,byte,short,int,long,float,double 封装类类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double 引用数据类型:数组,类,接口

  • 基本解析

自动装箱:将基本数据类型重新转化为对象 自动拆箱:将对象重新转化为基本数据类型 对象时不能直接进行运算的,而是要转化为基本数据类型后才能进行加减乘除。

  • 深入解析Integer的-128——127的常量池缓存

    • 情景重现:
 public class Test {  
        public static void main(String[] args) {  
            //在-128~127 之外的数
            Integer num1 = 128;   Integer num2 = 128;           
            System.out.println(num1==num2);   //false

            // 在-128~127 之内的数 
            Integer num3 = 9;   Integer num4 = 9;   
            System.out.println(num3==num4);   //true
        }  
    }  
复制代码
  • Integer源码解析(给一个Integer对象赋一个int值的时候,会调用Integer类的静态方法valueOf,源码如下:)
public static Integer valueOf(String s, int radix) throws NumberFormatException {
      return Integer.valueOf(parseInt(s,radix));
  }
  
  
  public static Integer valueOf(int i) {
      assert IntegerCache.high >= 127;
      if (i >= IntegerCache.low && i <= IntegerCache.high)
          return IntegerCache.cache[i + (-IntegerCache.low)];
      return new Integer(i);
  }
  
  
复制代码
  • IntegerCache是Integer的内部类,源码如下:

    /**
      * 缓存支持自动装箱的对象标识语义
      * -128和127(含)。
      *
      * 缓存在第一次使用时初始化。 缓存的大小
      * 可以由-XX:AutoBoxCacheMax = <size>选项控制。
      * 在VM初始化期间,java.lang.Integer.IntegerCache.high属性
      * 可以设置并保存在私有系统属性中
     */
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];
    
        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            }
            high = h;
    
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
        }
    
        private IntegerCache() {}
    }
    复制代码
引用:厉害了,我的JAVA基础(4)

七、对比Vector、ArrayList、LinkedList有什么区别?

  • 综述:
  • ArrayList概述:
  1. ArrayList实现了List接口,即ArrayList实现了可变大小的数组。它允许所有元素,包括null。
  2. ArrayList是为可变数组实现的,当更多的元素添加到ArrayList的时候,它的大小会动态增大。它的元素可以通过get/set方法直接访问,因为ArrayList本质上是一个数组。
  3. 每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
  4. 注意ArrayList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:

List list = Collections.synchronizedList(new ArrayList(...));

  • LinkedList概述:
  1. List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。LinkedList是为双向链表实现的,添加、删除元素的性能比ArrayList好,但是get/set元素的性能较差。
  2. 除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。此类实现 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。
  3. 所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。
  4. 注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:

List list = Collections.synchronizedList(new LinkedList(...));

  • Vector概述:
  1. Vector和ArrayList几乎是一样的,区别在于Vector是线程安全的,因为这个原因,它的性能较ArrayList差。通常情况下,大部分程序员都使用ArrayList,而不是Vector,因为他们可以自己做出明确的同步操作。
  2. Vector 类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector 的大小可以根据需要增大或缩小,以适应创建 Vector 后进行添加或移除项的操作。
  3. 每个向量会试图通过维护 capacity 和 capacityIncrement 来优化存储管理。capacity 始终至少应与向量的大小相等;这个值通常比后者大些,因为随着将组件添加到向量中,其存储将按 capacityIncrement 的大小增加存储块。应用程序可以在插入大量组件前增加向量的容量;这样就减少了增加的重分配的量。
  4. Vector是线程安全的。
  • 结论:
  1. ArrayList 本质上是一个可改变大小的数组.当元素加入时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问.元素顺序存储 ,随机访问很快,删除非头尾元素慢,新增元素慢而且费资源 ,较适用于无频繁增删的情况 ,比数组效率低,如果不是需要可变数组,可考虑使用数组 ,非线程安全.
  2. LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList. 适用于 :没有大规模的随机读取,有大量的增加/删除操作.随机访问很慢,增删操作很快,不耗费多余资源 ,允许null元素,非线程安全.
  3. Vector (类似于ArrayList)但其是同步的,开销就比ArrayList要大。如果你的程序本身是线程安全的,那么使用ArrayList是更好的选择。 Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%.
引用:

Java ArrayList、LinkedList、Vector的区别


八、对比Hashtable、HashMap、TreeMap,谈谈你对HashMap的掌握?

(1) Map 整体结构

概括的说,HashMap 是一个关联数组、哈希表,它是线程不安全的,允许key为null,value为null。遍历时无序。 其底层数据结构是数组称之为哈希桶,每个桶里面放的是链表,链表中的每个节点,就是哈希表中的每个元素。 在JDK8中,当链表长度达到8,会转化成红黑树,以提升它的查询、插入效率,它实现了Map<K,V>, Cloneable, Serializable接口。 因其底层哈希桶的数据结构是数组,所以也会涉及到扩容的问题。

HashMap 的性能表现非常依赖于哈希码的有效性,请务必掌握 hashCode 和 equals 的一些基本约定:

  • equals 相等,hashCode 一定要相等。
  • 重写了 hashCode 也要重写 equals。
  • hashCode 需要保持一致性,状态改变返回的哈希值仍然要一致。
  • equals 的对称、反射、传递等特性:
    • 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
    • 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
    • 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
    • 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
  • equals 注意事项:
  • 对象域,使用equals方法 。
  • 类型安全的枚举,使用equals或== 。
  • 可能为null的对象域 : 使用 == 和 equals 。
  • 数组域 : 使用 Arrays.equals 。
  • LinkedHashMap 通常提供的是遍历顺序符合插入顺序,它的实现是通过为条目(键值对)维护一个双向链表。注意,通过特定构造函数,我们可以创建反映访问顺序的实例,所谓的 put、get、compute 等,都算作“访问”。
  • 对于 TreeMap,它的整体顺序是由键的顺序关系决定的,通过 Comparator 或 Comparable(自然顺序)来决定。
(2) HashMap 源码分析
  • HashMap 内部实现基本点分析。

首先,我们来一起看看 HashMap 内部的结构,它可以看作是数组(Node[] table)和链表结合组成的复合结构,数组被分为一个个桶(bucket),通过哈希值决定了键值对在这个数组的寻址;哈希值相同的键值对,则以链表形式存储,你可以参考下面的示意图。这里需要注意的是,如果链表大小超过阈值(TREEIFY_THRESHOLD, 8),图中的链表就会被改造为树形结构。

  • 容量(capcity)和负载系数(load factor)。

负载因子 * 容量 > 元素数量

  1. 如果没有特别需求,不要轻易进行更改,因为 JDK 自身的默认负载因子是非常符合通用场景的需求的。
  2. 如果确实需要调整,建议不要设置超过 0.75 的数值,因为会显著增加冲突,降低 HashMap 的性能。
  3. 如果使用太小的负载因子,按照上面的公式,预设容量值也进行调整,否则可能会导致更加频繁的扩容,增加无谓的开销,本身访问性能也会受影响。
  • 树化 。
(3) HashMap常量释义:

  • size

HashMap使用空间(占了多少个 key:Value)

记录了Map中KV对的个数

  • loadFactor

装载印子,用来衡量HashMap满的程度

  • threshold

临界值,当实际KV个数超过threshold时,HashMap会将容量扩容,threshold=容量*加载因子

  • capacity

容量,如果不指定,默认容量是16(static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;)

  • size 和 capacity的区别

HashMap就像一个“桶”,那么capacity就是这个桶“当前”最多可以装多少元素,而size表示这个桶已经装了多少元素。即已用空间与所有空间的区别

PS:文中分析基于JDK1.8.0_73
参考:

HashMap源码解析(JDK8) (重点推荐)

Hashtable、HashMap、TreeMap有什么不同

equals()方法总结

HashMap中傻傻分不清楚的那些概念


九、如何保证集合是线程安全的?ConcurrentHashMap 如何实现高效的线程安全?
涉及知识点:
  • HashMap 在多线程中的死循环异常
  • 集合线程安全的方法
  • Hashtable
  • Collections.SynchronizedMap
  • 线程安全容器类
  • 各种并发容器:比如 ConcurrentHashMap、CopyOnWriteArrayList
  • 各种线程安全队列(Queue/Deque),如 ArrayBlockingQueue、synchronousQueue
  • 各种有序容器的线程安全版本
  • 同步方法:乐观锁、悲观锁和分离锁
  • ConcurrentHashMap 实现原理

(使用得不多,故走马观花了!)

参考:

浅析高并发情况下的HashMap

实现线程安全的HashMap

如何保证集合是线程安全的?


十、Java NIO提供了哪些IO方式?看过NIO的源码吗?如果让你来改进IO,会做什么改进?
  1. NIO简介

从JDK1.4开始,Java提供了一系列改进的输入/输出处理的新特性,被统称为NIO(即New I/O)。新增了许多用于处理输入输出的类,这些类都被放在java.nio包及子包下,并且对原java.io包中的很多类进行改写,新增了满足NIO的功能,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同, NIO支持面向缓冲区的、基于通道的IO操作。 NIO将以更加高效的方式进行文件的读写操作。

  1. 关键知识点
  • 缓冲区Buffer

缓冲区实际上是一个容器对象,更直接的说,其实就是一个数组,在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,它也是写入到缓冲区中的;任何时候访问 NIO 中的数据,都是将它放到缓冲区中。而在面向流I/O系统中,所有数据都是直接写入或者直接将数据读取到Stream对象中。具体看下面这张图就理解了:

Buffer继承关系如图:

  • 通道Channel

Channel和传统IO中的Stream很相似。虽然很相似,但是有很大的区别,主要区别为:通道是双向的,通过一个Channel既可以进行读,也可以进行写;而Stream只能进行单向操作,通过一个Stream只能进行读或者写,比如InputStream只能进行读取操作,OutputStream只能进行写操作;

通道是一个对象,通过它可以读取和写入数据,当然了所有数据都通过Buffer对象来处理。我们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。同样不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。

Channel继承关系如下图:

  • 选择器Selector

Selector类是NIO的核心类,Selector能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销。

与Selector有关的一个关键类是SelectionKey,一个SelectionKey表示一个到达的事件,这2个类构成了服务端处理业务的关键逻辑。

  1. NIO VS IO
  • IO是面向流的,NIO是面向缓冲区的
  • Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方;
  • NIO则能前后移动流中的数据,因为是面向缓冲区的
  • IO流是阻塞的,NIO流是不阻塞的
  • Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了
  • Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。
  • 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
  • 选择器
  • Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

  1. 点睛之笔
  1. BIO里用户最关心“我要读”,NIO里用户最关心"我可以读了",在 AIO(Async I/O) 模型里用户更需要关注的是“读完了”。
  2. NIO的主要事件有几个:读就绪、写就绪、有新连接到来
  3. 使用NIO != 高性能,当连接数<1000,并发程度不高或者局域网环境下NIO并没有显著的性能优势。
  4. 线程本身占用较大内存,像Java的线程栈,一般至少分配512K~1M的空间,如果系统中的线程数过千,恐怕整个JVM的内存都会被吃掉一半。
参考:

NIO简介、NIO&IO的主要区别 Java NIO浅析


十一、面向对象基础,抽象类、接口的区别是什么?

  • 抽象类

官方定义:如果一个类没有包含足够多的信息来描述一个具体的对象,这样的类就是抽象类。 接地气的说法:使用abstract修饰符修饰的类 注意:

  • 抽象类不一定有抽象方法,有抽象方法的类一定是抽象类
  • 接口

官方定义:接口在java中是一个抽象类型,是抽象方法的集合 接地气的说法:使用 interface 作为关键字构建的对象(大括号“{}”) 注意:

  • 接口没有构造方法
  • 接口中的方法必须是抽象的
  • 接口中的属性不能使用 private 权限修饰符修饰
  • 接口支持多继承
  • 抽象类和接口的区别
  1. 抽象类可以拥有实现的方法,接口不能有已实现的方法
  2. 类(抽象类)只能继承一个抽象类,接口可以继承(也是使用 extends 关键字)多个接口
  3. 抽象类有构造方法,接口没有构造方法
  4. 普通类只能继承一个抽象类(单继承),但是可以实现多个接口(多实现)
  5. 便于扩展,建议优先使用抽象类,因为当抽象类新增加实现方法(当抽象方法使用),在需要的地方重写该方法即可,如果是使用接口的话,则接口使用的每个地方都需要修改。

其实不是很明白此节点对于面向对象基础的设置,毕竟前面讲的内容已经是基础知识,且有Java源码分析,而此处偏于形而上的理论知识,但是温故而知新,时不时看看也能有点收获,虽然效率不高!

参考:

抽象类和接口的区别



十二、说说你知道的设计模式?请手动实现单例模式。Spring、Mybatis使用了哪些模式?

设计模式有如内功心法,说起来平淡无奇,使用起来却能犹如绝世神功一般化腐朽为神奇的功效。虽然大家都知道设计模式的招式,犹如大家都知道写字的横竖撇捺,但是真正写出一手好字却是不易的,设计模式的使用也是一样。平常见到比较多的有观察者模式、包装模式、策略模式、装饰模式、单例模式、工厂模式等等,但是自身用起来却比较少。因为平时遇到的项目大多都是小项目,使用了MVP或者MVC甚至更简单的项目连这些开发模式都用不上,设计模式就更加用不到了!

因此发了牢骚以后,我们还是要解决问题,就像我虽然不用英语过四级或者英语口语达到什么样的水平,但是起码我们得知道26个英文字母是什么样的吧?but,我现在还真不知道设计模式的“贰拾叁式”。。。。。

但是我可以讨论一点熟悉的单例,大家可以看看下面单例的八种写法:

  1. 饿汉式——静态常量
 public class Singleton {

    private final static Singleton INSTANCE = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){
        return INSTANCE;
    }
}
复制代码
  1. 饿汉式——静态代码块
 public class Singleton {

    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    private Singleton() {}

    public Singleton getInstance() {
        return instance;
    }
}
复制代码
  1. 懒汉式——线程不安全
public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
复制代码
  1. 懒汉式——线程安全 同步方法
public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

复制代码
  1. 懒汉式——线程安全 同步代码块
public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}
复制代码
  1. 双重检查
    public class Singleton {

    private static volatile Singleton singleton;

    private Singleton() {}

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

复制代码
  1. 静态内部类
    public class Singleton {

    private Singleton() {}

    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

复制代码
  1. 枚举
    public enum Singleton {
    INSTANCE;
    public void whateverMethod() {

    }
}

复制代码

最后,参考菜鸟的设计模式介绍吧! 菜鸟——设计模式

单例模式原文(建议不要参考文章中的是否推荐,而是测试以后决定具体情境,因为据我了解枚举如今都不推荐使用,因此怀疑其实用性)

最后唠叨两句废话


本文的初衷是看见张鸿洋在玩安卓网页上显示其本人参加学习了这门课程,于是自己打算跟跟风的,结果因为没钱,于是就打算通过找资料跟着课程资料自己学一遍,但是发现后面的内容对我这个做安卓的来讲太高深了,我现在的主要目的是扎实基础,因此将再次结束。虽然跟着学习的过程无非是看看别人的博客,虽然其他的内容自己忘得差不多了,但是HasmMap、Integer这些基础自己却重现过了一遍,虽然收获不算多,但是在看的过程中自己也慢慢的成长,这就足够了!

最后衷心感谢一直把废话看完的朋友,希望你能看得懂本文的内容,谢谢!复制代码
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值