《Java 核心技术 卷一》 -笔记

  • 代理
    • 利用代理可以在运行时创建一个实现了一组给定接口的新类。
    • 在编译时无法确定需要实现哪个接口时才有必要使用。
    • 代理类可以在运行时创建全新的类
    • 代理类能够实现指定的接口
      • 指定接口所需要的全部方法
      • Object类中的全部方法(除了clone和getClass),toString, equals...
    • 不能定义这些方法新代码,而是要提供一个调用处理器,实现InvocationHandler接口的类对象。
    • 创建代理对象
      • 只能使用Proxy类的newProxyInstance方法,三个参数
        • 类加载器,用null表示使用默认类加载器
        • Class对象数组,每个元素都是需要实现的接口,被代理的接口
        • 一个调用处理器
    • 调用代理对象时,调用会被重定向到调用处理器上。
    • 调用处理器中:invoke方法中调用的invoker(target, args)为actual method;可在结尾返回。
    • 代理类特性
      • 一个代理类只有一个实例域(调用处理器),所有的附件数据都必须存储在调用处理器上。
      • 如果使用同一个类加载器和接口数组调用两次newProxyInstance,那么只能够得到同一个类的两个对象。
    • Proxy
      • static Class<?> getProxyClass(ClassLoader, Class<?>),返回实现指定接口的代理类
      • static boolean isProxyClass(Class<?> ), 是否为代理类
  • 内部类
    • 1. 内部类
      • 为什么需要使用内部类
        • 1. 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
        • 2. 内部类可以对同一个包中的其他类隐藏起来。
        • 3. 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷(可被lambda替代了)
      • 内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。
      • 内部类的对象总有一个隐式引用,指向了创建它的外部类对象。
        • 编译器修改了所有的内部类的构造器,添加了一个外围类引用参数。
      • 只有内部类可以是私有类,而常规类只可以具有包可见性,或公有可见性。
      • 内部类特殊语法
        • OuterClass..this: 表示外围类引用
        • outerObject.new InnerClass(): 内部对象的构造器
        • 内部类中声明的所有静态域都必须是final
          • 每个外部对象,会分别有一个单独的内部类实例
        • 内部类可以允许有静态方法,但只能访问外围类的静态域和方法。
      • 内部类是一种编译器现象,与虚拟机无关,编译器将会把内部类翻译成用$分隔外部类名与内部类名的常规类文件。
      • 由于内部类拥有访问特权,所以与常规类比较起来功能更加强大。
      • 编译器在外围类添加静态方法(access$0)供内部类引用实例域。
        • OuterClass.access$0(outerObject)
      • 由于隐秘的访问方法需要拥有包可见性,所以攻击代码需要与被攻击类放在同个包下。
      • 在虚拟机中不存在私有类,因此编译器将会利用私有构造器生成一个包可见的类。
    • 2. 局部内部类
      • 在方法中定义局部类
      • 局部类不能用public或private进行声明,它的作用域被限定在声明这个局部类的块中。
      • 局部类的优势,对外部世界可以完成地隐藏起来。
      • 可以访问方法参数,JDK8-必须为final
    • 3. 匿名内部类 anonymous inner class
      • 创建类的对象
        • new SuperType() { inner class}
      • 匿名类不能有构造器,将构造器参数传递给超类构造器。
      • 可用lambda替代。
      • 双括号初始化
        • new ArrayList<String>() {{add(""}}
        • 外层括号建立了ArrayList的一个匿名子类,内层括号则是一个对象构造块。
      • new Object(){} 创建一个匿名对象, getClass().getEnclosingClass(); 获得外围类
    • 4. 静态内部类
      • 不需要内部类引用外围类时,可使用静态内部类取消引用。
      • 只有内部类可以声明为static
      • 静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样。
      • 静态内部类可以有静态域和方法。
  • 泛型
    • 使用泛型机制编写的程序代码,要比那些杂乱的使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。
    • Collection中的类型参数
      • JDK7+,构造函数可省略类型参数 List<String> list = new ArrayList<>();
    • 通配符类型(wildcard type)
      • 泛型类可以引入多个不同类型 Pair<T, U>
      • 使用E表示集合的元素类型,K和V分别表示表的关键字与值的类型,T(U或S)表示任意类型。
    • 泛型类可看作普通类的工厂。精妙
    • 泛型方法
      • 带有类型参数的简单方法,类型变量放在修饰符的后面,返回类型前面
        • public static <T> T getI(T t)
      • 调用方法时,Class.<String>getI("i"), 可省略<String>, Class.getI("i");
    • 类型变量的限定
      • <T extends Comparable>
      • 可以有多个限定
        • T extends Comparable & Serializable
        • 限定中至多有一个类,单继承,如果用类做限定,则必须是列表中的第一个。
    • 虚拟机没有泛型类型对象,所有对象都属于普通类
    • 类型擦除
      • 原始类型的名字就是删去类型参数后的泛型类型名,无限定的变量,直接用Object替换。
  • 异常
    • 异常处理的任务:将控制权从错误产生的地方转移给能够处理这种情况的错误处理器。
    • 异常层次结构
      • Error:运行时系统内部错误和资源耗尽错误,除了尽力使程序安全终止之外,再无能为力。
      • 如果出现RuntimeException,就一定是你的问题。
      • 派生于Error类和RuntimeException类的所有异常称为非受查异常(unchecked),所有其他异常称为受查(checked)异常。
    • 受查异常告诉编译器可能发生什么错误,提前准备,体面退出。
    • 如果调用一个抛出受查异常的方法(throws),就必须对它进行处理,或者继续传递。
    • 将异常直接交给能够胜任的处理器进行处理,要比压制对它的处理更好。
    • 子类方法中声明的受查异常(throws)不能比超类方法中的更通用。
    • Throwable
      • String getMessage(), 详细描述信息
      • Throwable getCause(), 获得原始异常
      • printStackTrace(), 获得堆栈情况(或通过Thread.dumpStack()就可不捕获异常,直接获得堆栈轨迹)
      • StackTraceElement[] getStackTrace(), 获得构造这个对象时调用堆栈的跟踪信息。
    • 捕获异常
      • jdk7+,可在catch中捕获多个异常(catch(FileNotFoundException | UnknownHostException e) ), 此时异常变量为final。
      • throwable.initCause(e), 原始异常e可设置为新异常的原因,不会丢失原始异常细节。
      • jdk7+,编译器会跟踪到异常来自try块内的哪个具体异常,就算方法throws SQLException, catch(Exception e){throw e;}只要e在catch中未改变,方法就是合法的。
      • finally
        • 建议解耦try/catch和try/finally语句块,职责单一,提高代码清晰度。
        • 若finally中有return语句,该值将覆盖try中return值。
        • finally语句块中,也可能抛出异常(in.close()),
          • 若要关闭的资源,实现了AutoCloseable接口,就可使用带资源的try语句(try-with-resources), try () {}
            • close抛出的异常将被抑制,可调用getSuppressed()得到被抑制的异常列表。
    • 堆栈轨迹(stack trace)
      • Thread.getAllStackTraces(),获得所有线程的堆栈轨迹
    • 异常机制使用技巧
      • 异常处理不能代替简单判断 e.g. 空值的判断
      • 不要过分细化异常 e.g.每条语句一个异常
      • 受查异常本来就很庞大,不要为逻辑错误抛出这些异常(反射库做法就不正确,需要经常捕获那些早已知道不可能发生的异常??)。
      • 不要压制异常。e.g. 要么抛出,要么关闭
      • 苛刻比放任要好,返回null(放任)还是抛异常(苛刻)。
      • 早抛出,晚捕获(精髓)
  • 集合
    • Java集合类库将接口与实现分离
    • 迭代器
      • for each是带有迭代器的循环,可以与任何实现Iterable接口的对象一起工作。
      • JDK8+ , iterator.forEachRemaining(e -> {});
      • remove会将上次调用的next方法时返回的元素,删除掉
        • 调用remove之前没调用next,将会抛出IllegalStateException
      • 如果迭代器发现它的集合被另一个迭代器修改了,或被该集合自身方法修改了,就会抛出ConcurrentModificationException
        • 每个迭代器都维护一个独立计数值。
    • Collection
      • removeIf(Predicate)
    • Java中,所有链表实际上都是双向链接的。
    • Collection
      • List
        • ArrayList
          • 默认大小为10
          • 扩容, JDK1.6(*1.5+1), JDK6+(*1.5) int newCapacity = oldCapacity + (oldCapacity >> 1);
        • Vector
          • 默认大小10
          • 扩容(*2),int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
          • 使用方法上synchronized
        • LinkedList
          • jdk1.6,双向循环链表; jdk1.7,无循环链表
          • ArrayList的增删未必就是比LinkedList要慢,末尾操作.
      • Set
        • 底层为HashMap
        • HashSet,先判断hash值(哈希值相同的在一个桶里),再进行equals.
        • TreeSet,使用二叉树,自定义对象要实现Comparable接口,才能使用排序.
        • LinkHashSet
    • Map
      • HashMap
        • 初始容量:1<<4, 16
        • capacity: 当前数组容量,保持在2^n,扩容后,数据*2
        • loadFactor,负载因子,0.75
          • 如果内存空间很多而又对时间效率要求很高,可以降低负载因子Load factor的值;相反,如果内存空间紧张而对时间效率要求不高,可以增加负载因子loadFactor的值,这个值可以大于1。
        • threshold,扩容阀值, = capacity * loadFactor
        • modCount字段主要用来记录HashMap内部结构发生变化的次数,主要用于迭代的快速失败。
        • 实现
          • JDK8之前,数组中每个元素为一个单向链表
          • 数组+链表+红黑树(链表元素超过8后)
        • 源码
          • Node[] table; 哈希桶数组
          • 使用链地址法解决冲突
          • 控制Hash碰撞的概率
            • 好的Hash算法和扩容机制
          • hashCode()32位 + 高位或运算 + 取模与运算
            • 取模:return h & (length-1);
              • 模运算的消耗还是比较大的,这个方法非常巧妙,当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。
          • 在HashMap中,哈希桶数组table的长度length大小必须为2的n次方(一定是合数),这是一种非常规的设计,常规的设计是把桶的大小设计为素数。相对来说素数导致冲突的概率要小于合数,HashMap采用这种非常规设计,主要是为了在取模和扩容时做优化.
          • 插入成功后,判断实际存在的键值对数量size是否超出了最大容量threshold,如果超过,进行扩容。 
          • JDK1.8我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”.
          • JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是从上图可以看出,JDK1.8不会倒置。
          • 线程不安全发生在扩容是,可能导致循环链表Infinite Loop.或者多线程更新同一hash值,可能导致覆盖。
            • 在JDK1.7中并发情况下HashMap在扩容时会出现死环现象(原因在于扩容之后链表会倒置呈现);
            • 在JDK1.8中扩容之后链表顺序保持不变,避免了死环现象的出现,
      • ConcurrentHashMap
        • 由多个Segment(类似HashMap)组成,Segment通过继承ReentrantLock进行加锁.分段锁
        • concurrencyLevel: Segment数量,默认16
      • TreeMap
      • LinkHashMap
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值