【Java基础】Java基础 面试高频考点总结

总结的Java基础部分高频的知识(持续更新)

基础

📌什么是面向对象?有什么特性?

面向对象就是把事物给对象化,包括其属性和行为。面向对象又三大特征,分别是:封装、继承、多态。

  • 封装: 就是指将对象的实现细节隐藏起来,通过公共的方法向外暴露除该对象的功能。比如我们通过将成员变量设置为私有后通过提供的getter/setter方法来访问。 使用封装可以提高安全性,简化操作,实现代码的组件化。
  • 继承: 继承是一种提高代码复用的重要手段,当一个子类继承了父类时,就会拥有父类的成员和方法,同时子类还可以自己实现更多的功能。但继承也是一种强耦合关系,父类改变子类也要相应的改变。
  • 多态: 多态就是同一类型对象在执行相同行为的情况下会又不同表现形态。 实现多态就要首先实现继承和重写以及向上转型。把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的实现差异,可以降低类之间的耦合度,易于扩展。

📌面向对象和面向过程的区别?

  • 面向过程是对于一个问题的解决过程的实现,重点强调的是功能的行为和执行过程。而面向对象是站在某一个对象它可以实现的功能的角度,将不同对象可实现的功能组装起来解决问题。
  • 面向过程采用的是自顶向下的设计方式,在设计初期就要考虑到每个模块以及每个函数,所以它的系统的适应性和可扩展性较差。面向对象编程更加符合常规思维,可扩展性好,更加模块化,所以也就拥有更低的耦合。

📌 Java和C++的区别?

  • 都是面向对象的编程语言,支持封装、继承和多态
  • 指针:Java不提供指针来直接访问内存,程序更安全
  • 继承:Java是单继承,C++是多继承,但Java可以实现多个接口
  • 内存:Java有自动内存管理机制,不需要程序员手动释放内存

📌JDK、JRE、JVM分别是什么?

  • JVM:Java虚拟机,是运行Java字节码的虚拟机,JVM有对于不用系统和的特定实现,以保证可以使用相同的字节码得出相同的结果。Java虚拟机不受平台的约束,实现了“一次编译,到处运行”
  • JDK : Java开发工具包,包含了Java虚拟机、Java程序设计语言、Java API类库这三部分
  • JRE:Java运行时环境(Java Runtime Environment),包含了JavaSE API 子集以及Java虚拟机这两部分

📌Java有哪些数据类型?

Java中有两类数据类型,分别是:基本数据类型和引用数据类型

  • 基本数据类型:包括byte(1)、short(2)、int(4)、long(8)、double(8)、float(4)、char(2)、boolean(1)
  • 引用数据类型:包括类、接口、数组等

📌String类为什么设计为不可变的?怎么实现的?

  • 将String对象设置为不可变有很大的好处。
    (1)首先,由于设计为不可变,所以可以实现字符串常量池,就可以在运行时节约很多堆上的空间。
    (2)String设计为不可变也大大提高了安全性,由于很多重要信息比如数据库的用户名密码、网络中的URL和主机名等都是使用String存储,如果String可变,就会引起很多安全问题。
    (3)在多线程下时安全的,由于设计为不可变,所以即使多个线程共享一个字符串,也不会造成多线程不同步的问题
    (4)因为字符串时是不可变的,所以它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键
  • 在实现上,首先String被final关键字修饰,就不能被继承以至于修改。其次String的底层是使用一个char[]实现的,而这个数组也使用了final修饰保证了其不可变性,另外对于String的所有方法都没有将原有的String对象暴露出来,所以别人也就无从修改。至此就保证了String的不可变。

📌 字符串的拼接方式有哪些?它们有什么区别?

字符串的拼接方式有三种:

  • 使用+ 直接拼接,由于String是不可变的对象,所以不会被修改,每次使用+都会在堆中再创建一个新的对象,使用+ 是线程安全的方式,但效率较低
  • 使用StringBuilder 的append方法来拼接字符串,它的底层数组没有使用final修饰,所以是可变的。另外它是线程不安全的,但拼接效率高
  • 使用StringBuffer 的append方法来拼接字符串,StringBuilder的所以公开方法都是使用synchronized关键字修饰的,所以它是线程安全的。

📌 final,finally和finalize区别是什么?

它们各自有各自的用途:

  • final :是一个修饰类、方法、变量的关键字。使用final修饰的类是不可以被继承的,使用final修饰的方法是不可以被重写的,使用final修饰的变量引用不可以修改(引用对象的内容可以修改)
  • finally:是一个使用在异常机的try-catch代码块中的关键字。finally代码块中的代码无论是否发生异常都会执行,通常用在释放资源中
  • finalize:是Object类中的一个方法,每一个对象都有一个finalize方法,作用是在垃圾回收前调用一次,且只调用一次

📌 == 、equals 、hashCode三者区别和联系是什么?

  • == 是一个操作符,如果比较的是基本数据类型,那么就是比较两个数值是否相同;如果是引用数据类型,比较的就是两个变量是否是同一个对象
  • equals是Object类的一个方法,如果没有进行重写,那么比较规则与==相同,如果重写,那就按照重新写的方法比较。一般情况下是将其重写为比较两个对象的对应内容是否相同
  • hashCode也是Object类的一个方法,也是用于判断两个对象是否相等,对于每一个对象hashCode会返回一个其在内存中的地址转换的一个int值。如果没有进行重写,那么两个不同对象的hashCode一定是不相同的。一般在重写equals方法时就要重写hashCode方法以达到对于两个对象:
    (1)equals为真,hashCode一定相同
    (2)equals为假,hashCode 可能相同可能不同
    (3)hashCode相同,equals可能为真可能为假
    (4)hashCode不同,equals一定为假

📌 方法重载和重写有什么区别?

  • 方法重载是在同一个类中的具有不同的参数列表的同名方法(无关返回值类型);而方法重写是在父类和子类中,一个子类具有和父类相同的方法,包括返回值、参数类型
  • 重载的返回值类型和权限修饰符、异常抛出类型没有要求;而重写要求返回值类型要小于等于父类被重写方法的类型,修饰符权限大于等于父类被重写方法权限修饰符,抛出的异常类型小于等于父类被重写方法抛出的异常类型
  • 在方法重载关系中,根据调用时的实参列表与形参列表来选择方法体;而方法重写调用方法体是根据引用的类型来决定

📌 抽象类和接口有什么区别?

  • 接口只能定于public static final 修饰的变量;抽象类可以定义普通对象
  • 接口在JDK1.8前只能定义public abstract 修饰的方法,1.8可以定义默认方法和静态方法;抽象类没有限制
  • 接口中只能有方法的定义,不能有实现;抽象类可以有方法的实现。
  • 接口可以被多实现,抽象类只能被单继承

📌 什么时候使用抽象类?什么时候使用接口?

接口被运用于实现比较常用的功能便于日后的维护或者添加删除方法;而抽象类更倾向于充当公共类的角色,不适用于日后重新对里面的代码进行修改。如果知道某个类应该成为基类,那么第一选择应该是让它成为一个接口,只有在必须要有方法定义和成员变量的时候,才应该选择抽象类。

📌 static关键字的作用有哪些?

static作为关键字可以修饰成员变量、成员方法、代码块、内部类,主要的就是实现与对象的解绑

  • 修饰成员变量: 被static修饰的成员变量属于类,在内存中只有一份,所有对类的实例对象的该成员属性都指向一个内存空间,所以就可以使用 类名.成员变量 直接访问
  • 修饰成员方法:被static修饰的静态方法直属于类,不需要创建对象就可以直接调用。在static方法中不可使用this、super关键字,也不可以调用非静态成员变量和方法
  • 修饰代码块:static修饰的代码块即静态代码块,直属于类,在JVM加载类时就执行,如果有多个就按顺序执行,通常用来初始化静态变量,而且只会被执行一次
  • 修饰内部类:static修饰内部类即静态内部类,它可以不依赖外部类的实例对象而被实例化,不能访问外部类的普通成员变量,只能访问外部类中的静态成员变量和静态方法

📌 内部类有什么作用?有哪些分类?

内部类可以有更好的封装性,有更多的权限修饰符,封装性可以得到更多的控制。内部类有四种不同的内部类:静态内部类、成员内部类、局部内部类、匿名内部类

  • 静态内部类:由static修饰,属于外部类本身,只加载一次。不能访问外部类的普通成员变量,只能访问外部类中的静态成员变量和静态方法
  • 成员内部类:属于外部类的每个对象,随对象一起加载,不可以定义静态成员和方法,可以访问外部类的所有内容
  • 局部内部类:定义在方法、构造器、代码块、循环中。只能定义实例成员变量和实例方法,作用也是只在局部代码块中
  • 匿名内部类:没有名字的局部内部类,匿名内部类会立即创建一个匿名内部类的对象返回,对象类型相当于当前new的类的子类类型

📌 泛型和泛型擦除是什么?

泛型就是参数化类型,在创建对象或调用方法时才明确参数的具体类型。
泛型擦除就是在编译阶段采用泛型加上的类型参数,会被编译器在编译时去掉,这个过程就被称为类型擦除,因此泛型主要用于编译阶段,在编译后生成的Java字节代码文件中不包含泛型中的类型信息。

📌泛型标记的规范有什么?

  • E:值Element,在集合中使用,表示在集合中存放的元素。
  • T:指Type,表示Java类,包括基本的类以及自定义类。
  • K:指Key,表示键,例如Map集合中的Key。
  • V:指Value,表示值,例如Map集合中的Value。
  • N:指Number,表示数值类型。
  • ?:表示不确定的Java类型。

📌异常有哪些分类?出现的原因是什么?

  • Throwable 是所有错误和异常的父类,Throwable分为Error和Exception
  • Error 是指Java程序运行错误,出现Error通常是因为系统的内部错误或资源耗尽导致的,Error不能在运行过程中被动态处理,如果程序运行中出现了Error,系统只能记录错误原因并安全终止
  • Exception指Java程序运行异常,即运行中发生了不期望的情况,分为RuntimeException和checkedException。其中RuntimeException指在Java虚拟机正常运行期间抛出的异常,可以被捕获并处理,例如空指针异常,数组越界。
  • 其中RuntimeException和Error统称为非受查异常,checkedException为受查异常需要我们进行处理

📌异常的处理方式有哪些?

异常通常由两种处理方式:

  • 直接抛出异常给调用者,由调用者根据情况处理。可以使用throws作用在方法上,也可以throw作用在方法内
  • 也可以使用try/catch代码块进行异常捕获处理,try代码块中的代码发生异常会被catch代码块捕获进行处理,还可以添加finally代码块,无论是否发生异常都会执行,一般用于释放资源。

📌throw 和 throws 有什么区别?

  • throw关键字是用来明确处理程序的异常
  • throws是用于在方法上表明该异常不能被处理

集合

📌说说常见的集合有哪些?

Map接口和Collection接口是所有集合框架的父接口:

  • Collection 接口的子接口包含:List、Set和Queue
  • Map接口的实现类有:HashMap、TreeMap、ConcurrentHashMap等
  • Set 是唯一的且无序的,它的实现类有:HashSet、TreeSet、LinkedHashSet(有序的HashSet)
  • List 是有序的,它的实现类主要有:ArrayList、LinkedList、Stack以及Vector

📌Collection和Collections有什么区别?

  • Collection是一个集合接口,它包含List、Set、Queue等
  • Collections是一个类,它为Collection对象提供了很多便捷的方法,比如sort()

📌ArrayList 和 Vector 有什么区别?

首先它们两个都实现了List接口,都是有序集合,他们俩的区别主要有:

  • 同步性:Vector是线程安全的,它的实现方法中使用了synchronized关键字,而ArrayList是线程不安全的,它的方法并没有保证线程同步。
  • 数据增长:ArrayList和Vector 都有一个初始的容量大小,当插入元素的数量到达容量大小时就会进行扩容,ArrayList一次扩容为原来的1.5倍,Vector一次扩容为原来的2倍

📌ArrayList 和 LinkedList 有什么区别?

  • 底层实现:ArrayList底层是采用数组实现的,内存是连续的,需要考虑容量;LinkedList底层是双向链表实现的,内存不一定是连续的,不需要考虑容量
  • 效率:由于底层实现方式的不同,ArrayList的随机访问下标处元素效率更好,而LinkedList在插入和删除数据时效率更好

📌Array和ArrayList有什么区别?

  • Array 可以存储基本类型和引用类型,ArrayList只能存储引用数据类型
  • Array 的大小是固定不变的,ArrayList的大小是可以变化的
  • ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。

📌List、Set、Map有什么区别?

  • List是保证了有序,同时也是可以重复的有索引的集合,它继承了Collection集合的全部功能,可使用迭代器遍历,也可使用索引遍历
  • Set 是一个无序的,保证不可重复的结合。Set的实现类中LinkedHashSet和TreeSet是有序的
  • Map是一个无序的,以键值对的形式存储的集合

📌了解HashMap吗?

  • HashMap是实现了Map接口的一个键值对形式存储对象的一个集合,它采用散列方式根据元素的Key散列到不同位置存储,它允许键为null。
  • 在JDK1.8之前,HashMap的底层是由数组+链表实现的,在JDK1.8时Hash Map的底层变味了数组+链表/红黑树实现,加入红黑树结构提升了查询效率。链表形式和数型结构都有对应的改变的阈值
  • HashMap的默认初始容量为16,负载因子是0.75,默认扩容阈值是16*0.75,如果大于扩容阈值就会进行扩容再散列操作,每次扩容的大小是之前的2倍。HashMap的长度都是2的次幂。
  • 如果发生冲突,HashMap采用链地址将冲突的数据加入链表中,在JDK1.8以前采用的是头插的方式,但这样会导致发生死循环,所以在1.8时优化成了尾插的方式,解决了死循环的问题
  • 另外,Hash Map是线程不安全的,在多线程情况下我们可以使用Collections.synchronizedMap(Map)、Hashtable、ConcurrentHashMap

📌HashMap的put方法具体流程是怎么样的?

  • 首先再存放数据前将对象hashCode的高16位和低16位进行异或得到对象的hash值,再将hash值和Map的长度-1进行与运算得到要插入的下标index的值
  • 使用高16位和低16位的异或运算可以保证尽可能多的位数参与运算,可以使得键值尽可能分散降低hash冲突。使用hash值和Map长度-1进行与运算可以控制下标的范围在数组长度以内,这也是为什么Map长度是2的幂次方。
  • 算出索引后,如果该下标处没有元素,就直接插入。如果发生冲突,判断如果是树结构就调用红黑树的插入方式插入节点,如果是链表结构,遍历链表,如果找到Key 相同的元素就覆盖该节点的value值,如果没发现相同的Key就遍历到尾部插入该节点。
  • 插入后判断如果超过转化为树的阈值,那就把链表结构转化为树型结构。如果元素数量达到扩容阈值就进行扩容

📌Hash Map的resize方法具体是怎样的?

  • 当HashMap中以及使用的容量达到总容量*负载因子就会开始扩容,扩容时会将数组容量增加至之前的两倍,如果新的容量小于默认值16,就设置为16,还会将阈值设置为之前的俩倍。如果大于最大的整型,就设置为最大值并返回。
  • 然后对每一个节点在新的hashMap中再散列,遍历数组中的每一个节点,为null就跳过。否则节遍历每一个节点再次计算hash值和新的下标放入。如果是树型结构再散列后红黑树太小就退化为链表形式。

📌了解HashTable吗?

Hashtable是与HashMap一样的以键值对存储元素的集合,它不允许键和值为null。基本实现以及内部方法都与HashMap相同,但它每一个方法都添加了synchronized关键字,所以HashTable是线程安全的一个结合。

📌HashMap和HashTable的区别?

  • 线程安全性不同: HashMap不是线程安全的,Hashtable是线程安全的。
  • 为null的要求不同:HashMap 中null 可以作为Key,而Hashtable中不可以。
  • 实现方式不同:Hashtable 继承了 Dictionary类,而 HashMap 继承的是 AbstractMap 类。
  • 初始化容量不同:HashMap 的初始容量为:16,Hashtable 初始容量为:11,两者的负载因子默认都是:0.75。
  • 底层实现不同:HashMap底层采用 数组+链表/红黑树 来实现的,HashTable采用 数组+链表实现。
  • 扩容机制不同:HashMap 扩容规则为当前容量2倍,Hashtable 扩容规则为当前容量2倍 + 1。
  • 迭代器不同:HashMap 中的 Iterator 迭代器是 fail-fast 的,而 Hashtable 是fail—safe 的

📌了解ConcurrentHashMap吗?

  • ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。不仅是线程安全的,而且减少了锁的力度提升了整体的性能。
  • 在JDK1.7中,ConcurrentHashMap采用Segment分段锁 + HashEntry数组的方式进行实现,是基于减小锁粒度的思想来保证线程安全的。一个Segment就是一个锁,里面包含多个 HashEntry数组,Segment的数量就是锁的并发度,默认是16,当修改 HashEntry数组中的元素时,必须获取对应的Segment锁。
  • 在JDK1.8中,ConcurrentHashMap对其进行优化,将原本的数组+链表的形式改为数组+链表/红黑树的形式,提升了查询效率。同时启用分段锁机制而是用CAS机制和Synchronized关键字保证线程安全,因为Synchronized关键字经过优化后,性能已经得到了很大的改善
  • 不管是JDK1.7还是1.8,读操作都没有加锁,而是使用volatile关键字修饰每个节点的Value值和next值,达到多线程之间的可见性
  • 同时使用了fail-safe迭代器,创建迭代器后可对元素进行更新

📌HashSet是如何去重的?

  • 对于基本类型的包装类,可以按照值进行比较
  • 对于引用数据类型,会首先比较HashCode()返回值是都相同,如果不同再使用equals()进行比较,如果不同才进行插入

📌迭代器是什么?

迭代器是实现了Iterator接口的一个用于遍历集合元素的指针。主要有三个方法:

  • hasNext() : 判断集合中是都还有元素
  • next() : 获取集合的下一个元素
  • remove() : 删除集合中迭代器上一个返回的元素

📌什么是fail-fast?什么是fail-safe呢?

  • fail-fast是java集合的一种错误检测机制,不允许除了迭代器本身的操作外的其它操作对集合结构进行遍历时修改(增删元素),如果修改就会抛出异常。它是根据modCount 变量来实现的,只要对集合进行增加删除操作,modCount就会+1,而每一遍历时都会检测modCount的值,如果发现改变,就排除异常。java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改。
  • fail-safe 是就是安全的修改集合结构的操作,使用了fail-safe机制后,会拷贝一份新的集合数据来进行遍历修改,不会抛出异常。但它的缺点是会增加内存开销,以及不能确保遍历是最新内容。java.util.concurrent包下的容器都是安全失败
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章