1. 面向对象
什么是面向对象
什么是面向对象?
对比面向过程,是两种不同的处理问题的角度
面向过程更注重事情的每一个步骤及顺序,面向对象更注重事情有哪些参与者 (对象)、及各自需要做什么
封装、继承、多态
2. JDK,JRE,JVM
JDK:
Java Develpment Kit java 开发工具
JRE:
Java Runtime Environment java运行时环境
JVM:
java Virtual Machine java 虚拟机
3. ==和equals区别
3.1. String 中的 equals 是如何重写的
首先会判断比较两个字符串的引用是否相同,相同则直接返回true;
如果不相同再继续比较:
-
- 是否是String的实例,不是直接返回false
- 字符串的长度是否一致,不是直接返回false
- 字符串的每个字符,不是返回false
3.2. 为什么重写 equals 方法必须重写 hashcode 方法
equals 方法是用来比较对象大小是否相等的方法,hashcode 方法是用来判断每个对象 hash 值的一种方法。如果只重写 equals 方法而不重写 hashcode 方法,很可能会造成两个不同的对象,它们的 hashcode 也相等,造成冲突。
4. java的特点
5. 值传递和引用传递
值传递是:调用函数时,将实际的参数复制一份到函数中去,这样对函数传递进来的形式参数修改时,不会影响到原来的参数;
引用参数:调用参数时,将对象的地址直接传递到函数中去,这样函数修改时,将会影响到原来的参数;
6. String s1 = new String("abc") 在内存中创建了几个对象
一个或者两个,String s1 是声明了一个 String 类型的 s1 变量,它不是对象。使用 new
关键字会在堆中创建一个对象,另外一个对象是 abc
,它会在常量池中创建,所以一共创建了两个对象;如果 abc 在常量池中已经存在的话,那么就会创建一个对象。
7. String 为什么是不可变的、jdk 源码中的 String 如何定义的、为什么这么设计。
不可变对象需要遵守下面几条原则
- 不可变对象的内部属性都是 final 的
- 不可变对象的内部属性都是 private 的
- 不可变对象不能提供任何可以修改内部状态的方法、setter 方法也不行
- 不可变对象不能被继承和扩展
String 类是一种对象,它是独立于 Java 基本数据类型而存在的,String 你可以把它理解为字符串的集合,String 被设计为 final 的,表示 String 对象一经创建后,它的值就不能再被修改,任何对 String 值进行修改的方法就是重新创建一个字符串。String 对象创建后会存在于运行时常量池中,运行时常量池是属于方法区的一部分,JDK1.7 后把它移到了堆中。
不可变对象不是真的不可变,可以通过反射
来对其内部的属性和值进行修改,不过一般我们不这样做。
8. final关键字
8.1. 为什么局部内部类和匿名内部类只能访问局部final 变量?
在Java中,局部内部类(local inner class)和匿名内部类(anonymous inner class)可以访问它们被定义时所在的块中的变量。但是,这些变量必须是有效的,也就是说,对于局部内部类和匿名内部类来说,它们只能访问那些被声明为final
的局部变量。
这是因为Java需要确保当一个局部内部类或匿名内部类对象在其外部方法已经执行完毕之后仍然能够访问该方法中的局部变量。为了保证这一点,Java编译器要求这些变量必须是不可改变的,即它们必须是final
的。
这里有几个关键点需要注意:
- 不可变性:为了保证线程安全以及对象状态的一致性,局部变量必须是不可改变的。使用
final
关键字可以确保变量一旦被初始化就不能再被修改。 - 生命周期:局部内部类或匿名内部类的实例可能比创建它的方法的执行周期要长。这意味着即使该方法已经返回,内部类的实例仍然可能存在,并且可能还在使用该方法中的局部变量。
- 编译器检查:编译器会检查并确保任何被内部类访问的局部变量都被声明为
final
。如果没有这样做,编译器将报错。
9. static关键字
10. 抽象类和接口的区别是什么
- 抽象的等级不一样:接口>抽象类>类;接口是只允许对方法的定义,抽象类可以对方法定义和实现,类只允许对方法的实现;
- 使用的关键字不一样:抽象类是:abstract class;接口是:interface
- 变量:接口中定义的只能是公共的静态变量;抽象类中的变量是普通变量
11. 重写和重载的区别
- 子父级关系不同:重写主要是针对子类对父类的不同表现形式;重载是同一个类中的不同表现形式;
- 概念不同:重写的方法一般使用@ override来表示;重写后的方法的声明和参数类型、顺序必须与父类一致;重载则必须满足:方法的参数顺序、参数个数、类型任意一个保持不同即可;
12. HashMap 和 HashTable 的区别
相同点:
都是基于哈希表实现的,其中每一个元素都是key-value键值对,都实现了Map、Clonable、Serializable接口。
不同点:
- 父类不同:HashMap 继承了
AbstractMap
类,而 HashTable 继承了Dictionary
类 - 空值不同:HashMap 允许空的 key 和 value 值,HashTable 不允许空的 key 和 value 值。HashMap 会把 Null key 当做普通的 key 对待。不允许 null key 重复。
- 线程安全性:HashMap不是线程安全的;可以选择构造线程安全的 Map 比如
Collections.synchronizedMap
或者是ConcurrentHashMap
;而 HashTable 本身就是线程安全的容器。 - 性能方面:虽然 HashMap 和 HashTable 都是基于
单链表
的,但是 HashMap 进行 put 或者 get 操作,可以达到常数时间的性能;而 HashTable 的 put 和 get 操作都是加了synchronized
锁的,所以效率很差。 - 初始容量不同:HashTable 的初始长度是11,之后每次扩充容量变为之前的 2n+1(n为上一次的长度)而 HashMap 的初始长度为16,之后每次扩充变为原来的两倍。创建时,如果给定了容量初始值,那么HashTable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。
13. HashMap 和 HashSet 的区别
HashSet 继承于 AbstractSet 接口,实现了 Set、Cloneable,、java.io.Serializable 接口。HashSet 不允许集合中出现重复的值。HashSet 底层其实就是 HashMap,所有对 HashSet 的操作其实就是对 HashMap 的操作。所以 HashSet 也不保证集合的顺序,也不是线程安全的容器。
14. HashMap 的底层结构
HashMap 采用位桶 + 链表
的实现,即使用链表
来处理冲突,同一 hash 值的链表都存储在一个数组中;
JDK 1.8 当每个桶中元素大于 8 的时候,会转变为红黑树,目的就是优化查询效率。
15. Arrays.asList 获得的 List 应该注意什么
Arrays.asList
是 Array 中的一个静态方法,它能够实现把数组转换成为 List 序列
注意:
- Arrays.asList 转换完成后的 List 不能再进行结构化的修改,什么是结构化的修改?就是不能再进行任何 List 元素的增加或者减少的操作。
- Arrays.asList 不支持基础类型的转换:Java 中的基础数据类型(byte,short,int,long,float,double,boolean)是不支持使用 Arrays.asList 方法去转换的
16. Collection 和 Collections 的区别
Collection 是集合类的父类,它是一个顶级接口,大部分抽象类比如说 AbstractList
、AbstractSet
都继承了 Collection 类,Collection 类只定义一节标准方法比如说 add、remove、set、equals 等,具体的方法由抽象类或者实现类去实现。
Collections 是集合类的工具类,Collections 提供了一些工具类的基本使用
- sort 方法,对当前集合进行排序, 实现 Comparable 接口的类,只能使用一种排序方案,这种方案叫做自然比较
- 比如实现线程安全的容器
Collections.synchronizedList
、Collections.synchronizedMap
等 - reverse 反转,使用 reverse 方法可以根据元素的自然顺序 对指定列表按降序进行排序。
- fill,使用指定元素替换指定列表中的所有元素。
17. 你知道 fail-fast 和 fail-safe 吗
fail-fast
是 Java 中的一种快速失败
机制,fail-fast 你可以把它理解为一种快速检测机制,它只能用来检测错误,不会对错误进行恢复;
fail-safe
是 Java 中的一种 安全失败
机制,遍历时不是直接在原集合上进行访问,而是先复制原有集合内容,在拷贝的集合上进行遍历
18. ArrayList、LinkedList 和 Vector 的区别
它们都实现了 List 接口。
- ArrayList 的底层是动态数组,它是基于数组的特性而演变出来的,所以ArrayList 遍历访问非常快,但是增删比较慢,因为会涉及到数组的拷贝。ArrayList 是一个非线程安全的容器,在并发场景下会造成问题,如果想使用线程安全的容器的话,推荐使用
Collections.synchronizedList
;ArrayList 在扩容时会增加 50% 的容量。 - LinkedList 的底层是双向链表,所以 LinkedList 的增加和删除非常快,只需把元素删除,把各自的指针指向新的元素即可。但是 LinkedList 遍历比较慢,因为只有每次访问一个元素才能知道下一个元素的值。LinkedList 也是一个非线程安全的容器,推荐使用
Collections.synchronizedList
- Vector 向量是最早出现的集合容器,Vector 是一个线程安全的容器,它的每个方法都粗暴的加上了
synchronized
锁,所以它的增删、遍历效率都很低。Vector 在扩容时,它的容量会增加一倍。
19. Exception 和 Error 有什么区别
Exception 泛指的是 异常
,Exception 可以被捕获,Exception 主要分为两种异常,
一种是编译期出现的异常,称为 checkedException
,举例:IOException
一种是程序运行期间出现的异常,称为 uncheckedException
统称RuntimeException,举例:RuntimeException
Error 是指程序运行过程中出现的错误,通常情况下会造成程序的崩溃,Error 通常是不可恢复的,Error 不能被捕获。
20. String、StringBuilder 和 StringBuffer 有什么区别
String 特指的是 Java 中的字符串,String 类位于 java.lang
包下,String 类是由 final 修饰的,String 字符串一旦创建就不能被修改,任何对 String 进行修改的操作都相当于重新创建了一个字符串。String 字符串的底层使用 StringBuilder 来实现的
StringBuilder 位于 java.util
包下,StringBuilder 是一非线程安全的容器,StringBuilder 的 append 方法常用于字符串拼接,它的拼接效率要比 String 中 +
号的拼接效率高。StringBuilder 一般不用于并发环境
StringBuffer 位于 java.util
包下,StringBuffer 是一个线程安全的容器,多线程场景下一般使用 StringBuffer 用作字符串的拼接
21. Comparator 和 Comparable 有什么不同
- Comparable 更像是自然排序
- Comparator 更像是定制排序
同时存在时采用 Comparator(定制
22. 反射创建类实例的三种方式是什么
- 对象实例.getClass();
- 通过 Class.forName() 创建
- 对象实例.newInstance() 方法创建
23. 内部类有哪些分类,分别解释一下
内部类的分类一般主要有四种
- 成员内部类
- 局部内部类
- 匿名内部类
- 静态内部类
静态内部类
就是定义在类内部的静态类,静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;
成员内部类
就是定义在类内部,成员位置上的非静态类,就是成员内部类。成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。
定义在方法中的内部类,就是局部内部类
。定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。
匿名内部类
就是没有名字的内部类,除了没有名字,匿名内部类还有以下特点:
- 匿名内部类必须继承一个抽象类或者实现一个接口
- 匿名内部类不能定义任何静态成员和静态方法。
- 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
排序)的规则进行比较。