Java 之 基础知识

Java基础知识点

1、什么是面向对象(OOP)?

(1)面向过程(C语言):

理解
将解决这个问题的过程拆成一个个方法(是没有对象去调用的),通过一个个方法的执行来解决问题。
优点
性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素
缺点
没有面向对象易维护、易复用、易扩展

(2)面向对象(C++,Java)

理解
面向对象编程就是先抽象出对象,然后用对象执行方法的方式解决问题。
优点
易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
缺点
性能比面向过程低

2、Java特性是什么?实现多态的机制是什么?

封装
封装是给对象提供了隐藏内部属性和行为的能力。通过public,protected,private修饰符来给其他的位于同一个包或者不同包下面对象赋予了不同的访问权限。它可以通过隐藏对象的属性来保护对象内部的状态,并且可以防止对象之间的不良交互,提高安全性。
继承
继承就是子类继承父类的特征和行为,使得子类对象具有父类的属性和方法。子类可以通过继承父类的属性和方法,从而避免存在重复的代码,代码更加简洁,并提高代码的维护性、复用性
多态
多态是同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的对象而执行不同操作
多态机制
(1)编译时多态(静态绑定),如重载方法:
在程序执行前方法已经被绑定,java中的方法,只有final,static,private,重载方法和构造方法是静态绑定;所有的变量都是静态绑定。
(2)运行时多态(动态绑定),如重写方法:
编译时不确定具体调用哪个具体方法,直到运行时,根据传入的对象信息,才进行绑定相应的方法。发生在运行阶段,绑定的是对象信息。运行时多态通常有两种实现方法:1、子类继承父类(extends)2、类实现接口(implements)。
运行时多态的必要条件有:继承、重写、父类引用指向子类对象。

3、重写(Override)与重载(Overload)的区别?

重写
子类对父类的允许访问的方法的实现过程进行重新编写,返回值和形参不可改变。(Java5不可变,Java7可以是父类返回值的派生)
好处在于子类可以根据需要,定义自己行为

重载
一个类里面,方法名字相同,而参数不同。返回值类型可以相同可以不同

区别
(1)参数列表: 重写不可改变参数列表;重载参数列表必须不同
(2)返回值类型:重写不可改变;重载可变可不变
(3)抛出异常范围:重写可以减少或删除,但不能抛出新的或者更广的异常;重载可以修改
(4)访问权限:重写可以降低限制,但不可增加限制;重载可以改变。

4、静态属性和静态方法是否可以被继承?是否可以被重写?为什么?

子类可以继承父类的非静态方法与非静态属性。
子类可以继承父类的静态方法与静态属性。称为隐藏

子类不可以重写父类的静态/非静态属性。父类的同名属性会被隐藏。
子类可以重写覆盖父类的非静态方法但不可重写覆盖父类静态方法。

原理解析:
静态属性和方法属于一个类,而非对象,在内存中一般只保存一份。调用的时候直接通过类名.方法名即可,不需要继承机制也可以调用。

5、接口(Interface)与抽象类(Abstract Class)的区别?

接口
特点
(1)接口中的方法都是public abstract修饰,不能实现。属性都是public static final 修饰
(2)接口没有构造方法
(3)接口可以多实现implements
(4)是对行为功能的抽象,描述的是否具备某种行为特征,表示这个对象能做什么。

作用
提供一组抽象方法的集合,供子类使用。

抽象类
特点
(1)不可被实例化,含有无法具体实现的方法
(2)可以有成员变量、成员方法、构造方法
(3)只要包含一个抽象方法的类,一定是抽象类
(4)任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
(5)只能单继承(extends继承一个类)
(6)是对事物的一种抽象,描述的是某一类特性的事物,表示这个对象是什么

作用
抽象类本质上是为了继承而存在,为子类提供一个公共特性的通用模板,是子类的抽象

6、什么是内部类?内部类、静态内部类、局部内部类和匿名内部类的区别及作用?

内部类定义:
定义在类内部的类称作内部类

内部类分类与区别:
(1)静态内部类:

定义:
在类内部的静态类

特点:
1) 只能访问外部类静态的成员和方法
2)它的创建不依赖外部类
3)非静态内部类编译完成后保存着外部类的引用,静态内部类没有

应用场景:
与外部类关系密切且不依赖外部类实例。
Java集合类HashMap内部就有一个静态内部类Entry。Entry是HashMap存放元素的抽象,HashMap内部维护Entry数组用了存放元素,但是Entry对使用者是透明的。

(2)成员内部类

定义 :
定义在类内部的非静态类

特点
1)可以访问外部类所有的成员与方法
2)成员内部类实例依赖外部类的实例,其他类使用成员内部类必须先创建一个外部类的实例
3)成员内部类不能定义静态方法和变量
应用场景:

(3)匿名内部类:
定义:
内部类的定义和声明写在一起时,不用给这个类起名直接使用。

特点
1)可以访问外部类所有成员和方法
2)没有构造方法
3)不可以是抽象类,因为抽象类不能创建实例
4)匿名内部类常用于回调函数(绑定监听)
应用场景:

(4)局部内部类

定义
在外部类方法中的类

特点
1)定义在静态方法中的局部类只能访问外部类的静态变量和方法,定义在非静态方法中,可以访问外部类的所有变量和方法
2)局部内部类可以访问方法的参数和方法中的局部变量,这些参数和变量必须声明成final。

应用场景

为什么要设计内部类\内部类好处
(1)内部类是为了更好的封装,把内部类封装在外部类里,不允许同包其他类访问
(2)内部类中的属性和方法即使是外部类也不能直接访问,相反内部类可以直接访问外部类的属性和方法,即使private
(3)实现多继承:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
(4)匿名内部类用于实现回调
(5) 维持两者之间密切的联系,保持嵌套可读性

静态内部类设计意图
其他内部类依托于外部类,持有外部类的引用。
它的创建是不需要依赖于外围类的。
它不能使用任何外围类的非static成员变量和方法。

为什么局部内部类用final修饰
(1)外部类结束时,局部变量就会销毁,但内部类可能还会存在,并指向一个不存在的局部变量。
(2)把局部变量改成final,这样在编译时,把final修饰的局部变量复制作为局部内部类数据成员,使局部内部类不可改变这个变量。保证与原始变量一致。

7、== 和 equals() 和 hashCode() 的区别?

  • ==

基本数据类型用= =比较的是数据的值。
引用类型用= =进行比较的是他们在内存中的存放地址

  • equals()

默认Object的equals方法主要用于判断对象内存地址引用是不是同一个 地址(是不是同一个对象)
定义的equals与==等效

String类对equals方法进行重写,比较的是堆中的内容是否相等
判断步骤:
1.若A==B 即是同一个String对象 返回true
2.若对比对象是String类型则继续,否则返回false
3.判断A、B长度是否一样,不一样的话返回false
4.逐个字符比较,若有不相等字符,返回false

hashCode()
hashCode()方法返回的就是一个hash码(int类型)。
hash码的主要用途就是在对对象进行散列的时候作为key输入,我们需要每个对象的hash码尽可能不同,这样才能保证散列的存取性能。
hashCode只有在集合中用到,相当于集合的key,利用下标访问可以提高查找效率

  • hashCode()作用

要想保证元素不重复,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;(放入对象的hashcode与集合中任一元素的hashcode不相等)如果这个位置上已经有元素了(hashcode相等),就调用它的equals方法与新元素进行比较,相同的话就不存,不相同就散列其它的地址。
所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了

equals与hashCode

(1)如果a.equals(b),则一定有a.hashCode() 一定等于 b.hashCode()
(2)如果!a.equals(b),则a.hashCode() 不一定等于 b.hashCode()。
(3)a.hashCode()==b.hashCode() 则 a.equals(b)可真可假
(4)a.hashCode()!= b.hashCode() 则 a.equals(b)为假

8、Integer 和 int 之间的区别?

int属于基本数据类型,还包括byte(1) 、short(2) 、long(8)、int(4)、char(2)、float(4)、double(8)
Integer是int的包装类,必须实例化才能使用。
Integer默认值是null int 默认值是0

精度丢失
float:单精度 32位,符号位占用1位,指数位占用 8 位,小数位占用 23 位。
double:双精度,符号位占用 1 位,指数位占用 11 位,小数位占用 52 位
默认是十进制,转成二进制数时,小数位无法表示完全情况就会出现精度丢失。
解决
计算时使用BigDecimal

扩展
(1)两个通过new生成的Integer变量用不相等(内存地址不同)
(2)非new生成的Integer对象与new生成的Integer对象用不相同。前者指向常量池,后者指向新建对象,内存地址不同
(3)Integer与int比较时,Integer会拆箱,最终比较的是值。
(4)两个非new的Integer变量,俩值区间处于[-128,127]中,则从常量缓存中取值,比较的是值,否则比较的是内存地址。

9、String 转换成 Integer 的方式及原理?

Integer.parseInt(str) 或者 Integer.parseInt(str,radix) // 进制不传时,默认十进制
parseInt(str)方法过程:

(1) 参数校验,包括字符串和进制;字符串不为null,进制在[2,36]之间.
(2)字符串长度不为0,即不为空;否则抛出异常;
(3)开始处理字符串,取字符串第一位字符,根据字符的ASCII码与‘0’的ASCII码比较判断是否是’+'或者‘-’
(4)确定正负数后,逐位获得每位字符的int值
(5)通过*=和-=对各结果进行拼接
// 这个方法比较关键,主要作用是根据字符的ASCII码把字符转换成int类型,如字符‘2’,则返回int类型的
digit = Character.digit(s.charAt(i++),radix)

10、自动装箱实现原理?类型转换实现原理?

装箱原理 Integer i =10
执行了Integer.valueOf(int i) 方法,此方法对于[-128,127]中的值返回的是缓存中的Integer对象,其他值返回new Integer(i)一个新对象。
拆箱原理 int t = i ;
执行 i.intValue() 方法,return value值。

11、对 String 的了解,String 为什么要设计成不可变的?

String表示字符串常量,字符串长度不可变,线程安全。

怎么实现不可变:
(1)String类由final修饰,保证引用不可变.不可被继承
(2)对外部提供set方法
(3)值由private final修饰,初始化之后不可改变。

为什么设计成不可变
(1)满足字符串常量池的需要(有助于共享)如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。如果String对象是可变的,那就不能这样共享
(2)线程安全考虑
同一个字符串实例可以被多个线程共享。字符串的不变性保证字符串本身便是线程安全的
(3)使用广泛,网络地址,HashMap的key,等等。需要保证安全性。

12、String、StringBuffer、StringBuilder 之间的区别? String拼接方式的区别?

**String:**长度不可变,线程安全,执行效率高
**StringBuffer:**长度可变变量,Synchronized修饰,线程安全;拼接时都对本身进行操作,不会生成新对象,适用于多线程大量数据,性能低。
**StringBuilder **长度可变变量,线程不安全;拼接时都对本身进行操作,不会生成新对象,适用于单线程大量数据,性能高。

+号拼接: 每次使用 "+“拼接 都会新建一个StringBuilder对象,并且最后toString()方法还会生成一个String对象
concat拼接: concat其实就是申请一个char类型的buf数组,将需要拼接的字符串都放在这个数组里,最后再创建并返回一个新的String对象
append拼接: 实现append的方法都是调用父类AbstractStringBuilder的append方法,利用char数组保存字符,通过ensureCapacityInternal方法来保证数组容量可用。不可用时翻倍扩容。不生成新的String对象
String a = “a”+“b”+“c”;在内存中创建了几个对象?一个对象
一个对象 编译时类似于 String a = “abc”
只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中
String s=new String(“abc”)创建了几个对象?——2个对象
new String(“abc”)可看成"abc”(创建String对象)和new String(String original)(String构造器,创建String对象)2个对象。
String a=”ab”+”cd”; 创建一个对象

13、static 关键字有什么作用?

  • static修饰方法
    在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用
  • static修饰变量
    静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化
  • static修饰代码块
    在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次

14、列举 Java 的集合以及集合之间的继承关系?

在Java容器中一共定义了2种集合, 顶层接口分别是Collection和Map。但是这2个接口都不能直接被实现使用,分别代表两种不同类型的容器。
在这里插入图片描述

15、List、Set、Map 的区别?

List:
特点: List是一个允许重复元素的指定索引、有序集合,允许null
分类: ArrayList 与LinkedList

Set:
特点: Set是一种不包含重复的元素的Collectio,允许null
分类: HashSet (无序)与LinkedHashSet(可以保持顺序的Set集合) ,TreeSet(一组有次序的集合,如果没有指定排序规则Comparator,则会按照自然排序)

Map :
特点: 存储键值对映射的容器类。Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。
分类: HashMap、LinkedHashMap、HashTable、WeakHashMap
三种遍历方式:
(1)Set keySet() 获得所有key的集合然后通过key访问value

Set<String> keySet = map.keySet();//先获取map集合的所有键的Set集合
Iterator<String> it = keySet.iterator();//有了Set集合,就可以获取其迭代器。
while(it.hasNext()) {
       String key = it.next();
       String value = map.get(key);//有了键可以通过map集合的get方法获取其对应的值。
       System.out.println("key: "+key+"-->value: "+value);//获得key和value值
}

(2)Collection values() 获得value的集合

Collection<String> collection = map.values();//返回值是个值的Collection集合

(3)Set< Map.Entry< K, V>> entrySet() 获得key-value键值对的集合

//通过entrySet()方法将map集合中的映射关系取出(这个关系就是Map.Entry类型)
Set<Map.Entry<String, String>> entrySet = map.entrySet();
//将关系集合entrySet进行迭代,存放到迭代器中                
Iterator<Map.Entry<String, String>> it = entrySet.iterator();
while(it.hasNext()) {
       Map.Entry<String, String> me = it.next();//获取Map.Entry关系对象me
       String key = me.getKey();//通过关系对象获取key
       String value = me.getValue();//通过关系对象获取value
}

16、ArrayList、LinkedList 的区别 /数组与链表的区别?

ArrayList 数组 :
(1)它允许所有元素,包括null
(2)它的size, isEmpty, get, set, iterator,add这些方法的时间复杂度是O(1),如果add n个数据则时间复杂度是O(n)
(3)ArrayList没有同步方法
LinkedList链表
(1)允许null元素。
(2)LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部
(3)LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步
查找方面: 数组的效率更高,可以直接索引出查找,而链表必须从头查找。
插入删除方面 特别是在中间进行插入删除,这时候链表体现出了极大的便利性,只需要在插入或者删除的地方断掉链然后插入或者移除元素,然后再将前后链重新组装,但是数组必须重新复制一份将所有数据后移或者前移。
内存申请方面 当数组达到初始的申请长度后,需要重新申请一个更大的数组然后把数据迁移过去才行。而链表只需要动态创建即可。
所以:查找元素则使用ArrayList。插入/删除元素则使用LinkedList。

ArrayList add()方法 扩容规则
(1)添加元素前判断数组容量是否足够,若不够,则先扩容
(2)每次扩容都是按原容量的1.5倍进行扩容(新数组容量 = 原数组容量*1.5 + 1)
(3)原数组通过Arrays.copyOf()将原数据元素拷贝到心数组
Arraylist fail-fast机制实现原理
为了解决集合中结构发生改变的时候,快速失败的机制。数组有添加或删除的时候,全局变量modCount++,查询的时候判断请求时的modCount和全局modCount是否一致,不一致则直接抛出异常。

17、HashSet、LinkedHashSet与TreeSet

HashSet: 无序、无重复、允许null 、线程不安全,数据结构基于HashMap

LinkedHashSet 有序,无重复,允许null,线程不安全

**TreeSet ** TreeSet即是一组有次序的集合,如果没有指定排序规则Comparator,则会按照自然排序,reeSet内的元素必须实现Comparable接口。

18、HashMap,HashTable,ConcurrentHashMap 实现原理以及区别?

在这里插入图片描述
一、HashTab:

底层机构实现:
Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。底层使用Entry链表数组存储,Entry自身是单向链表。
初始化:
初始容量:11
负载因子:0.75f
阈值:(int)11*0.75=8

put()方法:
(1)先获取synchronized锁。put方法不允许null值,如果发现是null,则直接抛出异常。
(2)计算key的哈希值和index,遍历对应位置的链表,如果发现已经存在相同的hash和key,则更新value,并返回旧值。
(3)如果不存在相同的key的Entry节点,则调用addEntry方法增加节点。
(4)addEntry方法中,如果需要则进行扩容。
(5)扩容为数组长度增加一倍,更新哈希表的扩容门限值。遍历旧表中的节点,计算在新表中的index,插入到对应位置链表的头部

二、ConcurrentHashMap

ConcurrentHashMap是HashTable的扩展,解决了线程安全和多线程的效率问题,但是无法扩容。它其实是默认分成16个不同的小的hashTable,然后在通过一些计算方式在多线程的情况下让每个键值对到不同的hashTable存放,从而能够体现多线程的效率问题,也能够保证线程安全的问题;
也叫分段锁机制

三、HashMap:

定义与特点:
HashMap就是最基础最常用的一种Map,它无序,以散列表(数组+链表/红黑树)的方式进行存储,存储内容是键值对映射。是一种非同步的容器类,故它的线程不安全。

原理结构
通过hash的方法,通过put和get存储和获取对象。存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),且数组容量大于64,则使用红黑树来替换链表,从而提高速度。

常量:
DEFAULT_INITIAL_CAPACITY :默认容量: 16
MAXIMUM_CAPACITY :最大容量 :2的30次方
DEFAULT_LOAD_FACTOR :阈值 0.75
TREEIFY_THRESHOLD : 转成红黑树的条件 :链表长度大于8
UNTREEIFY_THRESHOLD :红黑树转成链表条件:红黑树个数小于6
MIN_TREEIFY_CAPACITY :数组容量 : 64
当数组容量大于64 且链表长度大于8时才会由链表转成红黑树

为什么加载因子是0.75 而不是0.8或1 呢?

加载因子越大,扩容的几率越小,空间的利用率会非常的高,但index下标冲突概率也就越大,每个下标里对应的链表数据会增多,查询效率会低下成本会增高。
加载因子越小,index下标冲突概率也就越小,每个下标里的链表数据少,查询效率会提高,但反复扩容会导致扩容成本增大,未利用的空间也很多,空间的利用率不高
因此,必须在 "冲突的机会"与"空间利用率"之间寻找一种平衡与折衷

put()操作流程
在这里插入图片描述

19、JDK1.7 & JDK1.8 中HashMap对比

(1)数据结构不一致,jdk7用的是数组+链表,jdk8用的是数组+链表+红黑树

(2)扩容的改进,jdk7在多线程情况下,采用头插入法,容易造成死循环,jdk8在多线程情况下,使用尾插入法,解决了死锁问题
(3)扩容后数据存储位置的计算方式不一样:在JDK1.7中扩容后需要重新计算hash值,JDK1.8 中不需要再次计算,只需要原哈希值与扩容新增出来的长度 ,进行 你、n & hash运算,如果值等于 0,则下标位置不变。如果不为 0,那么新的位置则是原来位置上加原来容量。(扩容后,二进制第五位为1,1&任何值都是1 就相当于多取HashCode一位,如果这一位是0,表示在原来位置,这一位是1,表示新位置=原位置+旧的容量)
(4)hash计算规则不一样 DK1.7用了9次扰动处理=4次位运算+5次异或;JDK1.8只用了2次扰动处理=1次位运算+1次异或
(5)扩容时1.8会保持原链表的顺序,而1.7会颠倒链表的顺序;而且1.8是在元素插入后检测是否需要扩容,1.7则是在元素插入前扩容

为什么引入红黑树:

可解决死锁问题,并极大的提高查询速度

头插法为什么会造成死循环:

当在多线程的情况下,同时对hashMap实现扩容,因为每次数组在扩容的时候,新的数组长度发生了变化,需要重新计算index值;需要将原来的table中的数据移动到新的table中,因为采用头插法,形成了循环链表。当直接get()操作,由于是环形链表无法结束。所以始终循环无结果。

20、ConcurrentHashMap在JDK1.8版本比1.7改进了什么?

Jdk1.7版本的ConcurrentHashMap:
(1)底层采用分段锁机制,分成16个不同的Segment,每个Segment独立table有独立是锁, Segment其实就是我们的hashTable.即:数组+Segments分段锁+HashEntry链表
(2)Segment底层继承ReentrantLock上锁,使用CAS做修改,最大支持64次自旋
(3)根据key计算index存放在Segment位置,index需要计算两次(第一次计算存放那个Segment对象中 ,第二次计算Segment对象中那个HashEntry<K,V>[] table)

Jdk1.8版本的ConcurrentHashMap
(1)删除了分段锁技术 ,改用CAS无锁机制+synchronized机制。即:数组+链表+红黑树(使用Node数组保存数据
(2)New Node 采用CAS乐观锁机制保证线程安全性的问题
(3).如果计算index(只计算一次)产生了冲突 ,使用synchronized上锁

CAS:
乐观锁(CAS):每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试(自旋),直到成功为止。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

悲观锁(synchronized):线程一旦得到锁,其他需要锁的线程就挂起的情况就是悲观锁

优势
jdk1.7 :Segment+ReentrantLock锁住的是单个Segment,也就是单个HashTable,
但CAS+Synchronized锁住的是节点,锁的粒度比jdk1.7更加精细

缺点
(1)在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
(2)CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性

21、HashSet 与 HashMap 怎么判断集合元素重复?

HashSet要求不能存储相同的对象,HashMap要求不能存储相同的键。

HashSet不能添加重复的元素,当调用add(Object)方法时候,
首先会调用Object的hashCode方法判hashCode是否已经存在,如不存在则直接插入元素
如果已存在则调用Object对象的equals方法判断是否返回true,如果为true则说明元素已经存在,如为false则插入元素

为了保证HashSet中的对象不会出现重复值,在被存放元素的类中必须要重写hashCode()和equals()这两个方法。

哈希值: hashCode获取的int值
哈希表/散列表 通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表
哈希函数: 对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址
**扰动函数:**把哈希值右移 16 位,也就正好是自己长度的一半,之后与原哈希值做异或运算,这样就混合了原哈希值中的高位和低位,增大了随机性。减少碰撞。
哈希冲突: 哈希函数将两个不同的键映射到同一个索引的情况。

解决方式
(1)拉链法 :哈希冲突后,用链表去延展来解决。将所有关键字为同义词的记录存储在同一线性链表中
(2)开地址法:哈希冲突后,并不会在本身之外开拓新的空间,而是继续顺延下去某个位置来存放。通过探测再散列

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表

22、final、finally 和 finalize 的区别 break ,continue ,return 的区别及作用?

  • break 跳出总上一层循环,不再执行循环(结束当前的循环体)
  • continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)
  • return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)?

final 修饰符:
(1)如果类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承
(2)将变量或者方法声明为final,可以保证他们在使用中不被改变

finally(用于异常处理)
finally在try,catch中可以有,可以没有。如果trycatch中有finally则必须执行finally块中的操作。一般情况下,用于关闭文件的读写操作,或者是关闭数据库的连接等等.

**finalize(用于垃圾回收) **
finalize这个是方法名。在java中,允许使用finalize()方法在垃圾收集器将对象从内存中清理出去之前做必要的清理工作.
finalize()不一定会被调用;如果一种未被捕获的异常在使用finalize方法时被抛出,这个异常不会被捕获;inalize()和垃圾回收器的运行本身就要耗费资源, 也许会导致程序的暂时停止.

23、什么是序列化?怎么实现?有哪些方式?

序列化定义与作用:
对象序列化:是一个用于将对象状态转换为二进制字节流的过程,
持久化:可以将其保存到磁盘文件中或通过网络发送到任何其他程序;
反序列化:从字节流创建对象的相反的过程称为反序列化。

作用:序列化的作用就是为了保存java的类对象的状态,并将对象转换成可存储或者可传输的状态(二进制字节流),用于不同jvm之间进行类实例间的共享,用于跨平台传输。

怎么实现:
(1)要传递的类实现Serializable接口传递对象
(2)要传递的类实现Parcelable接口传递对象

字段忽略序列化方式:

  • 使用关键字 transient
  • 使用注解 @jsonignore
    Serializable 和Parcelable的对比
    在这里插入图片描述

24、Json解析工具对比

  • Gson(Google)
序列化:Gson().toJson(object)
反序列化:Gson().fromJson(Json, Simple.class)

json转换复杂的bean,比如List,Set 需要使用TypeToken

Gson gson = new Gson();
String json = "[{\"id\":\"1\",\"name\":\"Json技术\"},{\"id\":\"2\",\"name\":\"java技术\"}]";
//将json转换成List
List list = gson.fromJson(json,new TypeToken<LIST>() {}.getType());
//将json转换成Set
Set set = gson.fromJson(json,new TypeToken<SET>() {}.getType());
  • FastJson(阿里巴巴)
// json转换bean
String json = "{\"id\":\"2\",\"name\":\"Json技术\"}";
Book book = JSON.parseObject(json, Book.class);

//bean转换json
JSON.toJSONString(simple)

String json = "[{\"id\":\"1\",\"name\":\"Json技术\"},{\"id\":\"2\",\"name\":\"java技术\"}]";
//将json转换成List
List list = JSON.parseObject(json,new TypeReference<ARRAYLIST>(){});
//将json转换成Set
Set set = JSON.parseObject(json,new TypeReference<HASHSET>(){});
  • JackSon

一些集合Map,List的转换出现问题

  • Moshi(Square)

25、对 Java 的异常体系的了解

在这里插入图片描述
Java异常以Throwable开始,扩展出Error和Exception。

  • Error:
    Error是程序代码无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止退出,其表示程序在运行期间出现了十分严重、不可恢复的错误,应用程序只能中止运行
  • Exception
    Exception分运行时异常和非运行时异常。
    运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,这些异常也是不检查异常,程序代码中自行选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序代码应该从逻辑角度尽可能避免这类异常的发生.
    非运行时异常所有继承Exception且不是RuntimeException的异常都是非运行时异常,也称检查异常,如上图中的IOException和ClassNotFoundException,编译器会对其作检查,故程序中一定会对该异常进行处理,处理方法要么在方法体中声明抛出checked Exception,要么使用catch语句捕获checked Exception进行处理,不然不能通过编译。

26、异常崩溃捕获

  • 继承系统默认的处理类Thread.UncaughtExceptionHandler。设置处理类为程序处理器。当发生崩溃时,回调uncaughtException(thread, ex) 方法。收集用户与设备信息,提交服务器。
  • 第三方Bugly收集
  • 第三方友盟手机

27、对反射的了解?

Java反射说的是在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。
每个类被加载之后,系统就会为该类生成一个对应的Class对象。通过该Class对象就可以访问到JVM中的这个类。

  • 获取Calss对象
//第一种方式 通过Class类的静态方法——forName()来实现
class1 = Class.forName("com.lvr.reflection.Person");
//第二种方式 通过类的class属性
class1 = Person.class;
//第三种方式 通过对象getClass方法
Person person = new Person();
Class<?> class1 = person.getClass();
  • 类成员变量的反射
Field[] allFields = class1.getDeclaredFields();//获取class对象的所有属性
Field[] publicFields = class1.getFields();//获取class对象的public属性
Field ageField = class1.getDeclaredField("age");//获取class指定属性
Field desField = class1.getField("des");//获取class指定的public属性
  • 类成员方法的反射
Method[] methods = class1.getDeclaredMethods();//获取class对象的所有声明方法
Method[] allMethods = class1.getMethods();//获取class对象的所有public方法 包括父类的方法
Method method = class1.getMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的public方法
Method declaredMethod = class1.getDeclaredMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的方法
  • 类构造函数的反射
Constructor<?>[] allConstructors = class1.getDeclaredConstructors();//获取class对象的所有声明构造函数
Constructor<?>[] publicConstructors = class1.getConstructors();//获取class对象public构造函数
Constructor<?> constructor = class1.getDeclaredConstructor(String.class);//获取指定声明构造函数
Constructor publicConstructor = class1.getConstructor(String.class);//获取指定声明的public构造函数
  • 应用
    逆向代码 ,例如反编译
    与注解相结合的框架 例如Retrofit
    单纯的反射机制应用框架 例如EventBus
    动态生成类框架 例如Gson

28、对注解(Annotation)的了解?

注解(Annotation)是插入代码中的元数据,一种代码级别的说明。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

  • 作用
    (1)编写文档:通过代码里标识的注解生成文档(生成java doc文档(api))
    (2)代码分析:通过代码里标识的注解对代码进行分析(使用反射)
    (3)编译检查:通过代码里标识的注解让编译器能实现基本的编译检查;
  • 常见
    @Override : 子类继承父类
    @Deprecated : 方法过时
    @SuppressWarnnings :编译警告
  • 元注解: 用来提供对其它 annotation类型作说明
    (1)@Target: 描述注解能够作用的位置 :
    取值ElementType:
    1.CONSTRUCTOR:用于描述构造器;
    2.FIELD:用于描述域;
    3.LOCAL_VARIABLE:用于描述局部变量;
    4.METHOD:用于描述方法;
    5.PACKAGE:用于描述包;
    6.PARAMETER:用于描述参数;
    7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

(2)@Retention: 描述该注解的生命周期
取值RetentionPoicy:
1.SOURCE:当前被描述的注解只在源文件中有效,不会保留到class字节码文件中,也不会被JVM读取到。
2.CLASS:当前被描述的注解,会保留到class字节码文件中,不会被JVM读取到。
3.RUNTIME:当前被描述的注解,会保留到class字节码文件中,并被JVM读取到。

  • 只有定义为RetentionPolicy.RUNTIME时,我们才能通过注解反射获取到注解

(3)@Documented:
用来描述注解是否被抽取到api文档中。在生成javadoc文档的时候将该Annotation也写入到文档中
(4)@Inherited:
元注解是一个标记注解,@Inherited用来描述注解是否被子类继承。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnno{}

29、自定义注解实现

  • 自定义
@Target(ElementType.FIELD)  //  注解用于字段上
@Retention(RetentionPolicy.RUNTIME)  // 保留到运行时,可通过注解获取
public @interface MyField {
    String description();
    int length();
}
  • 使用
public class MyFieldTest {

    //使用我们的自定义注解
    @MyField(description = "用户名", length = 12)
    private String username;

    @Test
    public void testMyField(){

        // 获取类模板
        Class c = MyFieldTest.class;

        // 获取所有字段
        for(Field f : c.getDeclaredFields()){
            // 判断这个字段是否有MyField注解
            if(f.isAnnotationPresent(MyField.class)){
                MyField annotation = f.getAnnotation(MyField.class);
                System.out.println("字段:[" + f.getName() + "], 描述:[" + annotation.description() + "], 长度:[" + annotation.length() +"]");
            }
        }

    }
}

30、依赖(Dependency)、依赖倒置(DIP)、依赖注入(DI)和控制反转(IOC)的了解?

  • 依赖:依赖是类与类之间的连接,依赖关系表示一个类依赖于另一个类的定义,通俗来讲就是需要
    Person依赖具体出行方式(Car,Bike等),
  • 依赖倒置:
    Person中使用接口Driveable来定义交通工具,出行依赖的是交通工具接口,而不是具体的交通工具。
    依赖倒置原则的本质就是通过抽象(接口或抽象类)使个各类或模块的实现彼此独立,互不影响,实现模块间的松耦合。
  • 控制反转:
    此时Person还需要指定具体的出行方式。还需要更改代码.那就通过依赖注入方式:直接把出行方式注入给Person,这样Person无论用什么出行方式都不再需要更改代码,只需要外部调用时注入具体出行方式。
  • 依赖注入:
    实现依赖注入有 3 种方式:
    (1)构造函数中注入
    (2)setter 方式注入
    (3)接口注入

(1)上层模块不应该依赖于底层模块,它们都应该依赖于抽象
(2)抽象不应该依赖于细节,细节应该依赖于抽象
(3)依赖倒置是面向对象开发领域中的软件设计原则,它倡导上层模块不依赖于底层模块,抽象不依赖细节。
(4)依赖反转是遵守依赖倒置这个原则而提出来的一种设计模式,它引入了 IoC 容器的概念。
(5)依赖注入是为了实现依赖反转的一种手段之一。
(6)它们的本质是为了代码更加的“高内聚,低耦合”

31、对泛型的了解?

定义:
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。实现参数的任意化。
规则:
(1)泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
(2)泛型的类型参数可以有多个。
(3)泛型的参数类型可以使用extends语句,例如。习惯上称为“有界类型
(4)泛型的参数类型还可以是通配符类型。例如Class<?> classType =Class.forName(“java.lang.String”);

  • 优点:
    (1)提供了编译期的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException。
    (2)消除了代码中许多的强制类型转换,增强了代码的可读性

通配符<?> 、<? extends T>、<? super T>:

泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。
在这里插入图片描述
类型擦除:
编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
这样做的目的,是确保能和Java 5之前的版本进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。

32、对枚举Enum的了解

枚举就是把一些可能出现的情况都给罗列出来,这里有个重要的前提就是这些个情况是你必须能全部罗列出来的,就是包含所有的情况。
定义的枚举值,会被默认修饰为 public static final ,从修饰关键字,即可看出枚举值本质上是静态常量。

enum ColorEn { RED, GREEN, BLUE }
  • 优点:可以将常量组织起来,统一进行管理。
  • 基本特性:
    (1)如果枚举中没有定义方法,也可以在最后一个实例后面加逗号、分号或什么都不加。
    (2)如果枚举中没有定义方法,枚举值默认为从 0 开始的有序数值
    (3)Java 语法中却不允许使用赋值符号 = 为枚举常量赋值
    (4)枚举可以添加普通方法、静态方法、抽象方法、构造方法
    (5)枚举可以实现接口,不可以继承类(因为 enum 实际上都继承自 java.lang.Enum 类,而 Java 不支持多重继承)
  • 应用:枚举实现单例类。

33、静态代理和动态代理的区别?有什么场景使用?

  • 定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用(类似中介)

  • 代理模式模型:在Java中主要是通过接口的方式来实现代理模式的
    在这里插入图片描述
    1、抽象角色:对应代理接口(<< interface >>Subject),用来定义代理类和委托类的公共对外方法/接口;
    2、真实角色:对应委托类(接口实现类RealSubject),真正实现业务逻辑的类,是代理角色所代表的真实对象,是最终要引用的对象,需要实现抽象角色接口;
    3、代理角色:对应代理类(Proxy),需要实现抽象角色接口。用来代理和封装真实角色。代理角色内部含有对真实对象的引用,从而可以操作真实对象。同时,代理对象可以在执行真实对象操作时,添加或去除其他操作,相当于对真实对象进行封装;
    4、客户角色:对应客户端,使用代理类和主题接口完成一些工作。

  • 优点:
    1) 通过引入代理对象来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性;
    2) 通过代理对象对访问进行控制

分类

  • 静态代理:在程序运行前就已经存在代理类的.class文件,代理类和委托类的关系在运行前就确定,存在于硬盘中。
    (1)首先定义一个抽象接口,也就是前面提到的 抽象角色
public interface Drive {
    //所需要的代理方法
    void drive();
}

(2)真实角色类,让他去实现刚才的接口,在接口中做这个角色所需要的任务

public class Daslen implements Drive{
    //真实角色实现代理接口,进行真实操作
    @Override
    public void drive() {
        System.out.println("达乐森——>开车很流弊");
    }
}
public class Eason implements Drive{
    @Override
    public void drive() {
        System.out.println("依乐森——>开车很奔放");
    }
}

(3)定义一个代理类,通过实现抽象接口来持有真实角色,同时在执行真实角色的动作 前/后 都可以添加自己所需要的操作

public class Agent implements Drive{

    private Drive drive;

    public Agent(Drive drive) {
        this.drive = drive;
    }
    //代理前动作
    private void beforeDrive(){
    }
    //代理后动作
    private void afterDrive(){
    }
    @Override
    public void drive() {
        //通过代理类可以在实际操作代理对象之前和之后进行操作
        beforeDrive();
        this.drive.drive();
        afterDrive();
    }
}

(4)进行调用

public class Client {

    //静态代理
    public static void main(String[] args) {

        //真实角色->达乐森
        Drive daslen = new Daslen();
        //真实角色->依乐森
 		Drive Eason = new Eason();
 		
        //代理角色->Daslen
        Agent agentDaslen = new Agent(daslen);
        //通过代理角色操作真实角色
        agentDaslen.drive();
    }
}
  • 动态代理:代理对象是在JVM编译时,通过反射,通过反射从内存中去生成一个我们所需要的代理对象
    类,存在于内存中。
    调用Proxy的静态方法newProxyInstance,提供ClassLoader、代理接口类型数组、回调处理器InvocationHandler。
    通过代理对象调用方法实际调用真实对象的方法

(1)再实现一个接口

public interface Message {
    //所需要的代理方法
    void message();
}

(2)调用

    //动态代理
    public static void main(String[] args) throws Exception {
        //真实角色
        final Daslen daslen = new Daslen();
 		final Eason eason = new Eason();
        //动态代理 ->通过动态获取实际代理对象
        Object o = Proxy.newProxyInstance(Client.class.getClassLoader(),
                new Class[]{Drive.class, Message.class},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        String name = method.getName();
                        if (name.equals("drive")) {
                            method.invoke(daslen, args);
                        } else if (name.equals("message")) {
                            method.invoke(Easlen, args);
                        }
                        return null;
                    }
                });

        //通过代理对象操作实际对象
        ((Drive) o).drive();

        ((Message) o).message();
    }
  • 静态代理与动态代理优缺点
    1、静态代理
    优点:
    业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
    缺点:
    (1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
    (2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
    (3)采用静态代理模式,那么真实角色(委托类)必须事先已经存在的,并将其作为代理对象代理对象内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀。
    2、动态代理
    优点
    1、动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。
    2、动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。
    缺点
    JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值