JavaEE基础

Java中接口与抽象类的区别

抽象类用来捕捉子类的通用特性、不能被实例化,只能被用作子类的超类

接口是抽象方法的集合。

  1. 如果类实现了某个接口,就继承了该接口的抽象方法
  2. 实现了这个接口,必须确保使用这些方法
  3. 接口本身只能是public

何时使用:

  • 想有默认实现,抽象类
  • 基本功能在不断改变,使用抽象类。若使用接口,需要改变所有实现了该接口的类
  • 多重继承,必须使用接口。子类不能够继承多个类,但可以实现多个接口

Java并发包

  1. ConcurrentHashMap
  2. CopyOnWriteArrayList
    1. 线程安全版本的ArrayList,每次增加的时候,需要新创建一个比原来容量+1大小的数组
    2. 拷贝原来的元素到新的数组中,同时将新插入的元素放在最末端。
    3. 然后切换引用;
    4. 迭代时生成快照数组;适合读多写少
  3. CopyOnWriteArraySet
    1. 基于CopyOnWriteArrayList实现;
    2. 不能插入重复数据,每次add的时候都要遍历数据,性能略低于CopyOnWriteArrayList
  4. ArrayBlockingQueue 数组结构组成的有界阻塞队列
  5. Atomic类,如AtomicInteger、AtomicBoolean i++变成原子操作, 底层是CAS,别的线程自旋等到该方法执行完成

HashMap简介 非线程安全

JDK7-位桶+链表的方式

JDK8-位桶+链表/红黑树

链表的长度达到阀值 8 ,链表就将转换成红黑树;小于6,转链表

实现了Serializable接口,支持序列化,实现了Cloneable接口,能被克隆

 

HashMap底层维护一个数组,数组中的存储Entry对象组成的链表

Map中的key,value则以Entry的形式存放在数组中,通过key的hashCode计算,

PUT

当插入新元素时,对于红黑树的判断如下:

判断table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向下面;

遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;插入到前面;

遍历中若发现key已经存在直接覆盖value;

hash冲突(碰撞):HashMap解决hash冲突的方式是用链表; 先找到下标 i,KEY值找Entry对象

当发生hash冲突时,新值存放在数组中,旧值在新值的链表上,将存放在数组中的Entry设置为新值的next

GET

对key进行null检查。如果key是null,table[0]这个位置的元素将被返回。

key的hashcode()方法被调用,然后计算hash值。

indexFor(hash,table.length)用来计算要获取的Entry对象在table数组中的精确的位置,

遍历链表,调用equals()方法检查key的相等性,如果equals()方法返回true,get方法返回Entry对象的value,否则,返回null。

红黑树

红黑树是一种近似平衡的二叉查找树,它能够确保任何一个节点的左右子树的高度差不会超过二者中较低那个的一陪。具体来说,红黑树是满足如下条件的二叉查找树(binary search tree):

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点必须是黑色
  3. 红色节点不能连续(也即是,红色节点的孩子和父亲都不能是红色)。
  4. 对于每个节点,从该点至null(树尾端)的任何路径,都含有相同个数的黑色节点。

红-黑树会尽量保证树总是平衡,对一个要插入的数据项,插入例程要检查会不会破坏树的特征,如果破坏了,程序就会进行纠正,根据需要改变树的结构

能以较快的时间O(logN)来搜索一棵树,需要保证树总是平衡的(或者至少大部分是平衡的),这就是说对树中的每个节点在它左边的后代数目和在它右边的后代数目应该大致相等。

 

HashMap共有四个构造方法 构造方法中两个很重要的参数:初始容量16和加载因子0.75

这两个是影响HashMap性能的重要参数,初始容量表示哈希表的长度,初始是16

加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 resize 操作(即扩容) 默认0.75

加载因子越大,对空间的利用更充分,但是查找效率会降低(链表长度会越来越长);

加载因子太小,那么表中的数据将过于稀疏(很多空间还没用,就开始扩容了),对空间造成严重浪费

加入键值对时,先判断当前已用数组长度是否大于等于阀值(容量*加载因子),如果大于等于,则进行扩容,容量扩为原容量2倍

散列法 h&(length-1)

Hashtable:key的hash值对length取模(即除法散列法),基本能保证元素在哈希表中散列的比较均匀,但除法运算,效率很低;

HashMap改进,&操作:通过h&(length-1)的方法来代替取模,同样实现了均匀的散列,但效率要高很多,h = key的hash

h = 101100110,length = 1000,h%(length-1)就是保留h右边的3位二进制=110 (也是为什么数组从0开始)

为什么哈希表的容量一定要是2的整数次幂

h&(length-1)就相当于对length取模,这样便保证了散列的均匀,同时提升了效率

  • 2的整数次幂为偶数,length-1为奇数,最后一位是1,保证h&(length-1)的最后一位可能为0或1,保证散列的均匀性
  • length为奇数的话,length-1为偶数,最后一位是0,h&(length-1)的最后一位为0,浪费了近一半的空间

resize方法

新建一个HashMap的底层数组,而后调用transfer方法,将就HashMap的全部元素添加到新HashMap,

要重新计算元素在新的数组中的索引位置

containsKey方法和containsValue方法

前者直接可以通过key的哈希值将搜索范围定位到对应的链表;后者要对哈希数组的每个链表进行搜索

concurrenthashmap 源码 JDK1.8版 synchronizedMap

1.8抛弃Segment分段锁机制,利用CAS+Synchronized来保证并发更新的安全,底层依然采用数组+链表+红黑树的存储结构

table:默认为null,初始化发生在第一次put操作,默认大小为16的数组,存储Node节点数据,扩容时大小总是2的幂次方。

nextTable:默认为null,扩容时新生成的数组,其大小为原数组的两倍。

sizeCtl :默认为0,用来控制table的初始化和扩容操作,具体应用在后续会体现出来

  • -1 代表table正在初始化(保证只有一个put触发初始化)
  • -N 表示有N-1个线程正在进行扩容操作
  • 其余情况:
  • 如果table未初始化,表示table需要初始化的大小
  • 如果table初始化完成,默认是table大小的0.75倍

Node:保存key,value及key的hash值的数据结构 class Node<K,V> implements Map.Entry<K,V> ;value和next都用volatile修饰,保证并发的可见性

ForwardingNode:特殊的Node节点,hash值为-1,其中存储nextTable的引用,table发生扩容的时候,ForwardingNode作为一个占位符放在table尾端,表示当前节点为null

延迟初始化

延迟初始化:只会初始化sizeCtl值,并不会直接初始化table,而是延缓到第一次put操作

多线程下的初始化机制:执行第一次put操作的线程会CAS方法修改sizeCtl为-1,且只有一个线程能够修改成功,

put操作

假设table已经初始化完成,put操作采用CAS+synchronized实现并发插入或更新操作

  • hash计算,定位索引位置,index = hash &(lengh - 1)
  • 获取table中对应索引的对象f,node类型:Unsafe.getObjectVolatile来获取 tabAt[index],获取指定内存的数据,保证每次拿到数据都是最新;因为线程都有一个工作内存,里面存储着table的副本,虽然table是volatile修饰的,但不能保证线程每次都拿到table中的最新元素
  • 如果f为null,说明table中该位置第一次插入元素,利用Unsafe.compareAndSwapObject(CAS)方法插入Node节点
    • CAS成功,说明Node节点已经插入,随后检查是否需要进行扩容
    • CAS失败,说明有其它线程提前插入了节点,自旋重新尝试插入节点
    • 如果f的hash值为-1,说明当前f是ForwardingNode节点,意味有其它线程正在扩容,则一起进行扩容操作
  • f不为null的其余情况把新的Node节点按链表或红黑树的方式插入到合适的位置,这个过程采用同步内置锁实现并发(Synchronized锁住Node,减少了锁粒度)
    • 在节点f上进行同步,节点插入之前,再次利用tabAt(tab, i) == f判断,防止被其它线程修改
    • 如果f.hash >= 0,说明f是链表结构的头结点,遍历链表,如果找到对应的node节点,则修改value,否则在链表尾部加入节点
    • 如果f是TreeBin类型节点if(f instanceof TreeBin),说明f是红黑树根节点,则在树结构上遍历元素,更新或增加节点
    • 如果链表中节点数binCount >= TREEIFY_THRESHOLD(默认是8),则把链表转化为红黑树结构

 

get操作:

getObjectVolatile 保证可见性获取索引的对象f=tabAt[index],遍历key,找到相等的,cas来保证变量的原子性读取

table扩容

元素数量达到容量阈值sizeCtl(长度*0.75),扩容分为两部分:

构建一个nextTable,大小为table的两倍

Unsafe.compareAndSwapInt修改sizeCtl值-1,保证只有一个线程初始化,扩容后的数组长度为原来的两倍,但是容量是原来的1.5

把table的数据复制到nextTable中:扩容操作支持并发插入,支持节点的并发复制,性能提升,但实现的复杂度上升

大体思想是遍历、复制的过程

1.末尾放fwd,其他线程会帮助扩容

得到需要遍历的次数 i,在tab的 i 位置,初始化一个forwardingNode实例fwd

2.构造反序列表,放入nextTable的 i 和 i+n 的位置上(旧数据放在数组后面,即新扩容的地方)

如果f是链表的头节点,就构造一个反序链表,把他们分别放在nextTable的 i 和 i+n 的位置上,移动完成,采用Unsafe.putObjectVolatile方法给table原位置赋值fwd

3.遍历过所有的节点,把table指向nextTable,更新sizeCtl为新数组大小的0.75

 

synchronizedMap()

SynchronizedMap类是定义在Collections中的一个静态内部类。实现了Map接口,通过synchronized关键字对map实现同步控制

区别及应用场景

1.ConcurrentHashMap的实现更加精细,在性能以及安全性方面更优

它对map中的所有桶加了锁。只要要有一个线程访问map,其他线程就无法进入map

同步操作精确控制到桶,其他线程,仍然可以对桶执行某些操作

例如:即使在遍历map时,其他线程试图对map进行数据修改,不会抛出ConcurrentModificationException

2.ConcurrentHashMap只能是HashMap,而Collections.synchronizedMap()可以接收任意Map实例,实现Map的同步,如TreeMap实现排序

Map<String, Object> map2 = Collections.synchronizedMap(new TreeMap<String, Object>());

Map<String, Object> map3 = new ConcurrentHashMap<String, Object>();

 

CAS底层实现原理 

CAS:Compare and Swap, 翻译成比较并交换。 

CAS需要在:操作值的时候,检查值有没有发生变化,如果没有发生变化则更新

JVM中的CAS操作正是利用了处理器提供的CMPXCHG指令实现的

通过锁和循环CAS的方式来实现原子操作

 

java中创建子类实例时会创建父类实例吗    

  • 创建一个子类对象,不会创建父类对象
  • 通过调用父类的init 方法,可以加载父类的方法
  • 先调用的是父类的构造器初始化属性,再调用的子类的构造器

 

linkedHashMap,treemap(排序)

TreeMap会按照key值默认排序

LinkedHashMap是HashMap的一个子类,输出的顺序和输入的相同

 

用java实现生产者消费者的三种方法 

 

简述java Object类中的方法有哪些

Object类是Java中其他所有类的祖先,基类

Object类位于java.lang包中,没有定义属性,在编译时会自动导入。一共有13个方法

clone方法

实现对象的浅复制,需实现了Cloneable接口;(可重写实现字段深复制)

getClass方法 final方法,获得运行时类(class对象)

finalize() :当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法

public boolean equals(Object obj); 比较对象(内存地址)是否相同

public native int hashCode(); 返回一个整形数值,表示该对象的哈希码值。

public String toString(); toString()方法返回该对象的字符串表示

notify(),notifyAll():唤醒在此对象监视器上等待的单个(所有)线程,

方法调用后,线程不会立即释放所持有的锁,直到其所在同步代码块中的代码执行完毕,再释放锁

wait():调用此方法所在的当前线程等待,直到在其他线程上调用对象的notify(),notifyAll()方法

wait(long timeout):线程等待,直到notify() notifyAll() 方法,或超过指定的时间量

wait(long timeout, int nanos)

线程等待,notify() notifyAll() 方法,或其他某个线程中断当前线程,或超过时间量

简述java中内部类    

java中的内部类有四种:

1.静态内部类:作为类的静态成员,存在于某个类的内部。

2.成员内部类:作为类的成员,存在于某个类的内部。

  成员内部类可以调用外部类的所有成员,但只有在创建了外部类的对象后,才能调用外部的成员

3.局部内部类:存在于某个方法的内部。

  局部内部类只能在方法内部中使用,一旦方法执行完毕,局部内部类就会从内存中删除。

  必须注意:如果局部内部类中要使用他所在方法中的局部变量,那么就需要将这个局部变量定义为final的。

4.匿名内部类:存在于某个类的内部,但是无类名的类。

 

java中容器(集合)类

Java 的集合分为两种接口:Collection, Map

Collection : 是最基本的集合接口。只允许每个位置上放一个对象,List、Set和Queue接口的父接口

Map:(k,v)形式,sortedMap接口的父接口

Set集合不允许包含相同的元素

HashSet类: 是Set接口的一个子类,无序集合,采用散列存储,所以没有顺序

TreeSet类: TreeSet是SortedSet接口的唯一实现类,有序集合,TreeSet可以确保集合元素处于排序状态

List接口 List是有序,能够控制每个元素插入的位置

ArrayList类: 动态数组的数据结构,适合改查

LinkedList类:基于链表的数据结构,适合增删

Stack 类: Stack继承自Vector,实现一个后进先出的堆栈

Map提供key到value的映射,一个Map中不能包含相同的key,每个key只能映射一个value,每个键最多只能映射到一个值

Hashtable类: 同步的,线程安全,效率低。添加数据使用put(key, value),取出数据使用get(key)

ConcurrentHashMap 代替 Hashtable. 同步的,线程安全

HashMap类:非同步的,线程不安全,效率上比hashTable要高

LinkedHashMap是HashMap的子类,维护了插入的先后顺序,适合LRU算法做缓存(最近最少使用)

WeakHashMap是改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收

TreeMap实现了SortedMap接口,默认根据key值进行升序排序

Arraylist与LinkedList区别

ArrayList实现了基于动态数组的数据结构,而LinkedList是基于链表的数据结构;

  1. 对于随机访问get和set,ArrayList要优于LinkedList(O(1)时间复杂度随机访问),因为LinkedList要移动指针(复杂度是O(n)); 
  2. 对于新增和删除操作add和remove,LinedList比较占优势,(不需要重新计算大小或者更新索引)因为ArrayList要移动数据;
  3. LinkedList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。

由于LinkedList可以实现栈、队列以及双端队列等数据结构,所以当特定需要时候,使用LinkedList

CopyOnWriteArrayList 适合读多写少的并发场景。比如白名单,黑名单

Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet

通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想

读的时候不需要加锁,如果读的时候有多个线程正在向ArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的ArrayList。

适合读多写少的并发场景。比如白名单,黑名单

CopyOnWrite的缺点

内存占用问题 写操作的时候,内存里会同时驻扎两个对象的内存

数据一致性问题 CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性

 

简述java内存模型的happen before原则

happen-before原则是判断数据是否存在竞争、线程是否安全的主要依据,保证了多线程环境下的可见性

在JMM(内存模型)中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系

happens-before原则定义如下:

  1. 如果A操作happens-before另一个操作B,那么A操作的执行结果将对B操作可见,且A操作的执行顺序排在B操作之前
  2. 如果两个操作不存在happens-before规则,那么这两个操作就没有顺序的保障

hashmap保存自定义类 需要重写自定义类的方法  hashCode()和equals()

需要重写hashCode()和equals()方法才可以实现自定义键在HashMap中的查找

JDK1.8的新特性

接口改善 接口里已经完全可以定义静态方法了

函数式接口 声明

如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。比如,java.lang.Runnable就是一个函数式接口,因为它只顶一个一个抽象方法

Lambdas:一个函数式接口非常有价值的属性就是他们能够用lambdas来实例化

java.util.stream:新的java.util.stream包提供了“支持在流上的函数式风格的值操作”

对编译器判定泛型能力的努力改进

改进并发API

ConcurrentHashMap(v8)

hashmap

简述java中的深拷贝与浅拷贝

区别:

如果是引用类型,浅拷贝只复制引用,不复制值;值类型则都是复制值

浅拷贝:stu = (Student)super.clone();

    1. 创建一个新对象,然后将当前对象的非静态字段(变量)复制该新对象
    2. 如果字段是值类型的,那么对该字段执行复制;
    3. 如果该字段是引用类型的话,则复制引用但不复制引用的对象。原始对象及其副本引用同一个对象

深拷贝:stu.addr = (Address)addr.clone();

  1. 一直copy对象所有的内部元素, 最后只剩下原始的类型(int)以及“不可变对象(String)”
  2. 将对象序列化再读出来也是深拷贝

例子:

stu = (Student)super.clone(); //浅copy

stu.addr = (Address)addr.clone(); // 深copy:将(属性变量字段)单独clone一次

 

java开发过程中遇到过哪些exception

算术异常类:ArithmeticExecption

空指针异常类:NullPointerException

类型强制转换异常:ClassCastException

数组负下标异常:NegativeArrayException

数组下标越界异常:ArrayIndexOutOfBoundsException

违背安全原则异常:SecturityException

文件已结束异常:EOFException

文件未找到异常:FileNotFoundException

字符串转换为数字异常:NumberFormatException

操作数据库异常:SQLException

输入输出异常:IOException

方法未找到异常:NoSuchMethodException

散列表的冲突处理

散列表的冲突处理主要分为闭散列法(开放定址法)和开散列法

闭散列法(开放定址法)

  1. 冲突的元素没有开辟额外的存储空间,还是在原先hash表的空间范围之内
  2. 当插入元素发生了散列冲突,就逐个查找下一个空的散列地址供插入,直到查找失败

(1). 线性探测法:将散列表看作是一个循环向量,若初始地址是f(key)=d,则依照顺序d、d+1、d+2…的顺序取查找,即f(key)=(f(key)+1)mod N;

(2). 二次探测法:基本思路和线性探测法一致,只是搜索的步长和方向更加的多样,会交替以两个方向,步长为搜索次数的平方来查找;

(3). 双重散列法:通常双重散列法是开放地址中最好的方法,其通过提供hash()和rehash()两个函数,前者产生冲突的时候,定制化后者rehash()重新寻址,其机制比前面两种固定格式的要灵活的多;

开放定址法一般用于冲突极少的情况,同时因为没有用到指针,所以对于数据的传输是友好的。

开散列法

  1. 一般通过将冲突的元素组织在链表中,采用链表遍历的方式查找。
  2. 解决方法直观,实现起来简单,尤其在删除元素的时候此处只是简单的链表操作
  3. 开散列法可以存储超过散列表容量个数的元素

(4). 链地址法:相同散列值的记录放到同一个链表中,他们在同一个Bucket中;

(5). 公共溢出法:将所有的冲突都放到一个公共的溢出表中去,适用于冲突情况很小的时候。

 

FInal

关于final的重要知识点

  1. final关键字可以用于成员变量、本地变量、方法以及类
  2. final成员变量必须初始化或者在构造器中初始化,否则就会报编译错误。
  3. 你不能够对final变量再次赋值,final变量就是常量,常量名大写:

final变量,即常量

变量声明为final的都叫作final变量;final变量经常和static关键字一起使用,作为常量

final方法 -- 不能重写、快

方法前面加上final关键字,代表方法不能被子类的方法重写。

final方法比非final方法要快,因为在编译的时候已经静态绑定了,称为静态绑定(static binding)。不需要在运行时再动态绑定

final类 -- 不能继承

final类通常功能是完整的,不能被继承。如:String, Interger

final关键字的好处

  1. final变量可以安全的在多线程环境下进行共享,不需要额外的同步开销
  2. JVM会对final方法、变量、类进行优化,缓存final变量,提高了性能

String

String类是一个final类型的字符数组, 不允许继承,改变 private final char value[]

可序列化,可比较,实现了Serializable, Comparable, CharSequence接口

equals( )方法被重写,|| 运算 先判断两个String对象是否相等,如果不等再判断两个String对象的字面值是否相等;一个为真即可

String对象的三种比较方式:

== 内存地址比较:new 出现新地址 直接对比两个引用所指向的内存地址,精确简洁直接明了。

equals 字符串值比较:比较两个引用所指对象字面值是否相等;已对object的equals进行改写

hashCode字符串数值化比较:都不保证

 

String:适用于少量的字符串操作的情况

StringBuilder:适用于单线程 速度快

StringBuffer:适用多线程 线程安全 很多方法可以带有synchronized关键字

 

String对象本身放在堆里面,已被虚拟机加载的类

String常量放在方法区的常量池 String s1 = "abc";String s2 = "abc";Java底层会优先在常量池中查找是否存在"abc",并引用

基本类型的变量数据和对象的引用(实例)都是放在栈里面

Object类的equals方法的本质其实是和“==”一样的,都是对比两个引用指向的内存值;没什么用,需要改写

 

static

主要用途:可以在没有创建对象的前提下,通过类本身来调用static方法、变量

  1. static方法一般称作静态方法
    1. static方法不能调用非静态方法,反过来可以
  2. static变量
    1. 静态变量被所有的对象(实例)所共享,内存中只有一个副本
    2. 不允许修饰局部变量
  3. static静态代码块优化程序性能
    1. 可以置于类中的任何地方,可以有多个static块
    2. 只会在类加载时,按照static块的顺序执行,且只执行一次

 

JAVA值传递和引用传递:

值传递:基本型变量,传递变量副本,改变副本不影响原变量. 

引用传递【对象、数组】:对象型变量,传递对象地址副本,会改变对象

JAVA内存模型

所有变量都存储在主内存,每条线程有各自的工作内存

工作内存保存了被线程用到的变量主内存中的拷贝,对变量的所有操作必须在工作内存中进行

不同线程之间无法直接互相访问工作内存

 

面向对象

继承:一个类如果继承现有的类,则这个类将拥有被继承类的所有非私有特性(属性和操作)。这里指的继承包含:类的继承和接口的实现。

封装:使类具有独立性和隔离性;保证类的高内聚。只暴露必须的属性和操作给类外部或者子类。

多态:同一消息可以根据发送对象的不同而采用不同的行为;

多态的三个要素:继承、重写、父类引用指向子类对象

方式一:重写、重载:

重写(Override):重写发生在子类,子类重写父类相同名称的方法

重载(Overload):类中定义了一个以上相同名称,但参数不同的方法

方式二:接口

方式三:抽象类和抽象方法

好处:

  1. 可替换性(substitutability):对已存在代码具有可替换性
  2. 可扩充性(extensibility):对代码具有可扩充性
  3. 接口性(interface-ability):超类可向子类提供了一个共同接口
  4. 灵活性(flexibility):灵活多样的操作
  5. 简化性(simplicity):简化对代码编写和修改过程

 

简述分派

重载和重写的原理:确定执行哪个方法的过程就是方法分派

程序设计中,函数命名成名字相同,需要根据方法的参数、引用的类型等信息来确定到底应该执行哪个方法。

静态分派是在编译期完成的,方法重载相关,在编译期确定,引用的类型、参数不同 (方法重载)

动态分派发生在执行阶段,方法重写相关,虚拟机根据执行方法的实际类型来判断执行哪个目标方法(子类重写父类)

Human man = new Man() ; man = new Woman() -》 woman say hello

 

静态方法可以通过类名直接访问,子类会继承父类的静态方法,但是不会复写父类方法

如果子类声明了和父类一样的方法,子类方法隐藏,不是复写

 

equals、==、 hashcode 区别

默认的“equals()”方法,等价于“==”方法,Object.java中,通过判断两个对象的地址是否相等。

我们通常会重写equals()方法:若两个对象的内容相等,则equals()方法返回true;否则,返回fasle。  

 

hashCode() 定义在JDK的Object.java中,hashCode() 在散列表中才有用

1)、如果两个对象相等,那么它们的hashCode()值一定相同。

           这里的相等是指,通过equals()比较两个对象时返回true。

2)、如果两个对象hashCode()相等,它们并不一定相等。

           因为在散列表中,hashCode()相等,即两个键值对的哈希值相等,仍旧存在哈希冲突

 

 

java中的泛型擦除

Java中的泛型基本上都是在编译器这个层次来实现的。

在生成的Java字节码中是不包含泛型中的类型信息

类型擦除:使用泛型的时候加上的类型参数,在编译器在编译的时候去掉,成为原生类型

public class TestF { public static void main(String[] args) { Class a1 = new ArrayList<Integer>().getClass(); Class a2 = new ArrayList<String>().getClass(); System.out.println(a1 == a2); } }

运行时会输出 true,运行时会输出 true

由于擦除机制,泛型不能用于显示地引用运行时类型的操作之中,例如转型、new表达式和instanceof操作。

若在泛型内部必须使用类型操作时,可以在运行时采用反射的方法将正在运行的类信息添加到泛型内部,这种方法称为补偿

 

java是如何解决这个问题的呢?java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除

 

反射

动态获取的信息以及动态调用对象的方法的功能,在运行状态中,能知道类的所有属性和方法,并调用;

 

jdk动态代理 vs cglib动态代理 Spring AOP底层实现;

代理模式:为对象提供代理以控制对某个对象的访问。预处理消息、过滤消息、转发消息等

区别:

cglib动态代理:无需实现接口,针对类来实现代理,对指定目标 产生一个子类 通过方法拦截技术拦截所有父类方法的调用。

JDK动态代理:只能用于实现了接口的类

JDK动态代理:根据被代理的接口动态生成代理类的class文件,并加载运行 ;被代理对象每调用一次方法,则调用一次代理

public void helloTest1() {

// 调用代理对象的create方法,代理HelloService接口;因为肯定是实现该接口的对象,所以直接传接口 ;create名字自己定,可以是getProxy

HelloService helloService = rpcProxy.create(HelloService.class);

// 调用代理的方法,执行invoke

String result = helloService.hello("World");

System.out.println("服务端返回结果:"+ result);

}

  1. 代理类中:
    1. 通过反射获取(UUID,类名,方法名,参数类型,参数),封装成request对象
public <T> T create(Class<?> interfaceClass) {

    //返回代理对象
    return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),

    new Class<?>[] { interfaceClass }, new InvocationHandler() {

    public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {

        //正常执行方法
        //return method.invoke(T, args)
    
        //增强代码 数据库事务等
        RpcRequest request = new RpcRequest();
        request.setRequestId(UUID.randomUUID().toString());
        request.setClassName(method.getDeclaringClass().getName());
        request.setMethodName(method.getName());
        request.setParameterTypes(method.getParameterTypes());
        request.setParameters(args);

    });

}

 

cglib动态代理 spring core包已经引入

针对类来实现代理,对指定目标 产生一个子类 通过方法拦截技术拦截所有父类方法的调用。 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值