一、什么是面向对象?
1. 把具体的事物抽象成类,封装事物的特征。
2. 面向对象的特征:封装、继承、多态
封装:外部调用者无需关注内部细节
继承:子类继承基类的共同属性,并作出个性化扩展
多态:外部调用一个方法时,可以有不同的执行逻辑和结果,继承、重写、父类引用指向子类对象
二、JDK、JRE、JVM
JDK:java开发环境
JRE:java运行环境
JVM:java虚拟机
三、==和equals
==是运算符,对比的是栈中的值,equals比较的是对中对象的内存地址
常见于与String相关的笔试题,Object类提供的equals方法底层使用的是==,而String类重写了equals方法,没重写就是比地址,重写了就是比值
例如
str1在常量池中指向“Hello”,str2在常量池和堆空间中都创建了对象,str3指向了str2的引用(内存地址),所以
str1 == str2 str1在常量池中,str2在堆空间中,==比地址,false
str1 == str3 str1在常量池中,str3在堆空间中,==比地址,false
str2 == str3 str2在堆空间中,str3在堆空间中,str2传递引用给str3,==比地址,true
str1.equals(str2) 已重写,equals比较值,str1=“Hello”,str2=“Hello”,true
str1.equals(str3) 已重写,equals比较值,str1=“Hello”,str3=str2,true
str2.equals(str3) 已重写,equals比较值,str3 = str2,true
四、final
1.用法
修饰类:这个类不能被继承
修饰方法:这个方法不能被重写,但能重载
修饰变量:修饰基本数据类型时,这个变量不能被修改;修饰引用类型时,不能更改引用,可以更改属性值
2.成员变量声明时要赋初值,局部变量使用前要赋初值
3.局部内部类和匿名内部类只能访问外部的final变量(lamda表达式),因为
五、String、StringBuffer、StringBuilder
String | StringBuffer | StringBuilder | |
有啥用 | 创建和操作字符串 | ||
有啥区别 | 创建的字符串不能修改,只会修改引用 | 可以修改字符串内容 | 单线程 |
线程安全 | 线程安全 | 线程不安全 | |
性能等级 | 3 | 2 | 1 |
使用场景 | 多线程共享变量使用 | 优先使用 |
六、重载和重写
重载:同一个类中,方法名必须相同
重写:父类和子类中,方法名、参数列表必须相同,访问修饰符和返回值类型要小于父类
private和protected不允许重写
七、接口和抽象类的区别
语法上:
1.接口中只能是抽象方法,抽象类可以存在普通成员函数
2.接口中的成员变量只能是public static final ,抽象类中随便
3.一个类只能继承一个抽象类,但可以实现多个接口
使用场景上,接口更偏向于做什么,抽象类偏向于事物的本质是什么
八、List和Set的区别
List:有序,可重复。
Set:无序,不可重复;只能用Iterator取出所有元素。
九、hashCode和equals
1.hashCode()获取到一个int型的hashcode,这个哈希码的作用是确定该对象在哈希表中的索引位置,哈希表存储的是散列结构即k-v,能够通过key快速检索对象在堆空间的位置。
2.在存储数据时,发生hash冲突时会调用equals检查两个对象是否相同。
十、ArrayList和LinkedList
ArrayList:数组队列,在内存中是连续的存储空间,随机访问速度较快,适合做查询操作。扩容时,新建一个数组,把老数组的数据拷贝过去
LinkedList:双向链表,可以在内存中分散存储,需要遍历,适合做插入和删除操作,遍历时使用迭代器Iterator,如果在循环中使用get(i)方法,每次循环都遍历一次,极其消耗性能
十一、HashMap和HashTable
HashMap | HashTable | |
线程安全 | 不安全 | 安全 |
key | 允许为null | 不允许为null |
底层结构 | 数组+链表+红黑树,jdk8中,当数组长度超过64,链表长度达到8,链表就会转化为红黑树 |
存储过程:
(1)计算key的hashcode
(2)根据hashcode计算元素在数组中的索引
(3)如果发生hash冲突,进行equals比较,相同取代,不同插入则链表
(4)如果key为null,,存在下标为0的位置
底层主数组如何扩容?
和ArrayList扩容机制一样,但加入了加载因子=0.75
十二、ConcurrenHashMap
线程安全的HashMap,替代HashTable,因为不仅线程安全,效率还更高
jdk7
底层结构:ReentranLock + Segment + HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表
Segment是分段锁。继承了ReentranLock,锁定操作的Segment,其他Segment不受影响,并发数为segment的个数,可以通过构造函数指定,数组扩容不会影响其他segment。
HashTable操作时会整个锁起来,ConcurrenHashMap里有许多的Segment,互不影响。
查询是会二次hash,第一次定位Segment位置,第二次定位到元素所在的链表头部。
jdk8
底层结构:synchronized+CAS(乐观锁)+Node+红黑树,Node的value和next都用volatile修饰。
替换、查找、赋值操作都是用CAS。读操作是无锁的。
十三、如何实现一个IOC容器(控制反转)
思路:
(1)配置文件配置包扫描路径
(2)定义一些注解,比如说@Controller、@Service、@Mapper、@Autowire等
(3)从配置文件中获取需要扫描的包路径,获取这个路径下面的文件信息,把已.class结尾的文件添加到Set集合中
(4)遍历这个Set集合,获取在类上有指定注解的类,并交给IOC容器,定义一个安全的Map来存储这些对象
(5)遍历这个IOC容器,获取到每个类的实例,判断是否有依赖其他类的实例,然后递归注入
十四、字节码
字节码是jvm能够识别解释的文件,任何操作系统上只要安装了jvm,就可以执行任何.class文件。使用字节码的好处是跨平台、可移植的优点。
十五、类加载器
JDK自带三种类加载器:bootStrap ClassLoader、ExtClassLoader、AppClassLoader
bootStrap ClassLoader负责加载%JAVA_HOME%lib下的jar包和类文件
ExtClassLoader负责加载%JAVA_HOME%lib/etc下的jar包和类文件
AppClassLoader负责加载classpath下的类文件,即程序员自定义的类库
继承ClassLoader可以实现自定义类加载器
十六、双亲委派模型
就是向上委派,向下查找
每一个类加载器执行后,都会将加载过的类放进缓存,子加载器在加载新类时,先向上查找缓存有没有被加载过,如果没有,就查找自己的加载路径,如果还没有就向下继续查找加载路径。
向上直到顶层为止,向下直到发起加载的加载器。
好处:
(1)安全,避免用户自己编写的类替换了java核心类,比如String
(2)防止类重复加载,jvm中区分不同类,不仅是根据全路径类名,相同的class文件背不同的classLoader加载就是两个不同的类
十七、JAVA异常体系
所有的异常都来自顶级父类:Throwable
Throwable下面有两个子类:Exception、Error
Error:程序无法理解的错误,发生这类错误程序立刻停止运行
Exception:分为两种,RunTimeException运行时异常,发生时线程停止运行;和CheckedException检查异常,发生时编译不通过