问:接口和抽象类的区别?
答:
接口:
1.接口使用interface来定义
2.Java中,接口和类是并列的两个结构
3.接口中的成员
JDK7及以前:只能定义全局常量和抽象方法
>全局常量:public static final的.但是书写时,可以省略不写
>抽象方法:public abstract的
JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法(略)(可以由实现类的对象直接调用(注意遵守类优先原则和解决接口冲突问题))(为了解决接口中增加一个抽象方法,实现类必须重写的问题)和静态方法(只能由接口直接调用)
JDK9:添加了(静态)私有方法(为了简化默认方法和静态方法中的重复代码)
4. 接口中不能定义构造器的!意味着接口不可以实例化
5. Java开发中,接口通过让类去实现(implements)的方式来使用.
如果实现类覆盖了接口中的所抽象方法,则此实现类就可以实例化
如果实现类没覆盖接口中所的抽象方法,则此实现类仍为一个抽象类
6. Java类可以实现多个接口 —>弥补了Java单继承性的局限性
格式:class AA extends BB implements CC,DD,EE
7. 接口与接口之间可以继承,而且可以多继承
8. 接口的具体使用,体现多态性
9. 接口,实际上可以看做是一种规范
10.接口:是自上而下设计,一般先设计好接口,然后在具体的业务中去实现的
抽象类说明:
1.此类不能实例化
2.抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
3.抽象方法是方法的声明,没方法体
4.包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
5.若子类重写了父类中的所有的抽象方法后,此子类方可实例化。
若子类没重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
6.抽象类:是自下而上的设计。类之间存在一定的关系,is-a,终极目标是实现代码的复用
角度:定义,继承和实现,设计理念,属性方法构造器
补充说明:
1.类优先原则
答:
如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没重写此方法的情况下,默认调用的是父类中的同名同参数的方法。
2.接口冲突
答:
如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,
那么在实现类没重写此方法的情况下,报错。
3.为什么abstract不能修饰静态方法
答:
因为静态方法是编译时的行为,而方法的覆盖操作发生在运行时,所以无法覆盖静态方法,失去了abstract的意义。
问:重载和重写的区别
答:
“两同”即方法名相同、形参列表相同;
“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
问:sleep() 的 wait()区别
答:
1.来自不同类
wait => Object
sleep => thread
2、关于锁的释放
3、使用的范围
wait() 在同步方法或同步代码块中
sleep() 可以在任意地方
4、是否需要捕获异常wait() 不需要捕获异常sleep() 必须捕获异常
问:面向对象的四个基本特征?
答:
抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面,抽象只关注对象的哪些属性和行为,并不关注这此行为的细节是什么
继承:目的:对父类和方法的复用
继承是从已有类得到继承信息创建新类的过程,继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段.子类继承父类属性(静态特征)和方法(动态特征),继承必须遵循封装当中的控制访问
封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口.面向对象的本质就是将现实世界描绘成一系列完全自治,封闭的对象,可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。封装给对象提供了隐藏内部特性和行为的能力,对象提供一些能这被其它对象访问的方法来改变它内部的数据。
多态:多态性是指允许相同或不同子类型的对象对同一消息作出不同响应
LRU 缓存淘汰算法
LRU 算法设计:
分析上面的操作过程,要让 put 和 get 方法的时间复杂度为 O(1),我们可以总结出 cache 这个数据结构必要的条件:
1、显然 cache 中的元素必须有时序,以区分最近使用的和久未使用的数据,当容量满了之后要删除最久未使用的那个元素腾位置。
2、我们要在 cache 中快速找某个 key 是否已存在并得到对应的 val;
3、每次访问 cache 中的某个 key,需要将这个元素变为最近使用的,也就是说 cache 要支持在任意位置快速插入和删除元素。
那么,什么数据结构同时符合上述条件呢?哈希表查找快,但是数据无固定顺序;链表有顺序之分,插入删除快,但是查找慢。所以结合一下,形成一种新的数据结构:哈希链表 LinkedHashMap。
LRU 缓存算法的核心数据结构就是哈希链表,双向链表和哈希表的结合体。这个数据结构长这样:
借助这个结构,我们来逐一分析上面的 3 个条件:
1、如果我们每次默认从链表尾部添加元素,那么显然越靠尾部的元素就是最近使用的,越靠头部的元素就是最久未使用的。
2、对于某一个 key,我们可以通过哈希表快速定位到链表中的节点,从而取得对应 val。
3、链表显然是支持在任意位置快速插入和删除的,改改指针就行。只不过传统的链表无法按照索引快速访问某一个位置的元素,而这里借助哈希表,可以通过 key 快速映射到任意一个链表节点,然后进行插入和删除。
class LRUCache {
int cap;
LinkedHashMap<Integer, Integer> cache = new LinkedHashMap<>();
public LRUCache(int capacity) {
this.cap = capacity;
}
public int get(int key) {
if (!cache.containsKey(key)) {
return -1;
}
// 将 key 变为最近使用
makeRecently(key);
return cache.get(key);
}
public void put(int key, int val) {
if (cache.containsKey(key)) {
// 修改 key 的值
cache.put(key, val);
// 将 key 变为最近使用
makeRecently(key);
return;
}
if (cache.size() >= this.cap) {
// 链表头部就是最久未使用的 key
int oldestKey = cache.keySet().iterator().next();
cache.remove(oldestKey);
}
// 将新的 key 添加链表尾部
cache.put(key, val);
}
private void makeRecently(int key) {
int val = cache.get(key);
// 删除 key,重新插入到队尾
cache.remove(key);
cache.put(key, val);
}
}
双指针技巧:
1.链表中的快慢指针
2.二分查找
3.两数之和
4.反转数组