【JAVA基础】
【问】构造器(Constructor)就等于构造函数:
(1)只能重载,不能重写。
(2)this(...)表示此类的构造函数,super(...)表示父类的构造函数。他们都是为了解决构造方法中的代码冗余。
【问】abstract关键字冲突:final、static、private:
(1)final和private修饰的方法不能被重写,抽象方法就是为了重写,因此冲突;
(2)static是为了外部直接访问,但是abstract修饰成员没有方法体,所以访问没有意义,冲突。
【问】普通类和抽象类的区别?
抽象类 | 普通类 | |
实例化 | 不能 | 能 |
方法 | 抽象方法(abstract)+ 实现方法 | 实现方法(无抽象方法) |
【问】接口和抽象类的区别?
接口 | 抽象类 | |
设计思想层面 | 行为抽象设计(像什么) | 模板抽象设计(是什么) |
成员属性 |
常量(默认为static+
final
,且只能通过接口访问)
| 不一定为常量,也不一定为静态。 |
成员方法 | 抽象方法(无修饰符,默认为public abstract) | 抽象方法+实现方法 |
继承与实现角度 | 可以多实现、多继承 | 单继承 |
【问】强制类型转换?
多态情形下的,父类型转子类型。
class Person {
public void eat() {
System.out.println("The people were eating");
}
}
class Boy extends Person {
public void eat() {
System.out.println("The boy were eating");
}
}
public class Test {
public static void main(String[] args) {
Person person = new Boy();
Boy boy = (Boy) person;
boy.eat();
}
}
【问】==、equals、hashCode?
(1)== :判断地址是否相同。
(2)equals:重写前,与“==”一样;重写后,判断内容(属性值)是否相同。
(3)hashCode:重写前,根据内存地址得到;重写后,根据内容(所有属性)得到。
【问】hashcode和equals的关系?为什么重写equals一定要重写hashcode?
涉及到这两个的未重写,都是在做自定义类、集合放置自定义对象的时候,一般java自带的类都是重写过的。
(1)
判断两个对象内容是否相等的过程:
中间多了一个hashcode的环节是为了减少equals的使用,hashcode比equals更快!
(2)
从两个点来回答这个问题:
(1)重写hashcode也是为了让hashcode派上用场,减少equals的使用。
(2)equals和hashcode不重写前都是根据内存地址得出的,重写后才能进行内容比较。如果不重写,hashcode就是根据内存地址得出的,当HashMap碰到两个地址不同,内容相同的对象,HashMap就会存入两个内容一样的键。
【问】什么是java中的值传递?java中方法对原始对象的影响总结?
(1)
值传递 | 引用传递(地址) | |
定义 | (java)将实际参数的地址复制一份,传递到函数中 | 将实际参数的地址直接传递到函数中 |
能否修改原始对象的内容? | 不能 | 能 |
(2)
- java方法不能修改一个基本数据类型的参数(boolean、byte、short、int、long;char;float、double;)。
- java方法可以改变一个对象或数组的状态。(数组就是一个对象)
- java方法不能更改原对象的引用。
【问】java中的异常了解哪些?Throwable类的方法?try-catch-finally?throw和throws?
(1)以下都是类,均代表异常的类型:
(2)
Throwable类的方法 | 含义 |
getMessage() | 返回异常发生时的简要信息 |
toString() | 详细信息 |
getLocalMessage() | 本地化信息 |
printStackTrace() | 对象封装的异常信息 |
(3)try-catch-finally
++
finally不执行的四种情况:
(1)退出JVM的语句 System.exit(int)
(2)finally代码块一进去就有异常
(3)线程死亡
(4)CPU关闭
(4)
throw:
是语句抛出异常,出现于函数内部,用来抛出一个具体异常实例;throw被执行后面的语句不起作用,直接转入异常处理阶段。
throws:
是函数方法抛出异常,一般写在方法的头部,用来抛出一些异常,本身不进行解决,抛给方法的调用者进行解决(try...catch...)。
【问】什么是序列化?Java 序列化中如果有些变量不想进行序列化,怎么办?
(1)
序列化:
将Java对象转换成字节流的过程。当 Java 对象需要在 网络上传输 或者 持久化存储 到文件中时,就需要对 Java 对象进行序列化处理。
反序列化:
将字节流转换成 Java 对象的过程。
(2)
【问】什么是IO流?java IO流的种类?有了字节流,为什么还要字符流?
(1)
java数据想传输到别的地方(比如网络传输、持久化存储时),必须以IO流的形式(非java的数据形式有字节和字符。);传入java的流叫输入流,传出java的流叫输出流。“流”=数据传输的管道。
(2)
按照不同角色对流进行划分:
-
按照流的流向分:输入流,输出流
-
按照操作单元分:字节流,字符流(字节B/byte<字符)
-
按照流的角色:节点流、处理流。(目标设备中的类>>节点流>>处理流)
Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的:
-
InputStream/Reader:所有输入流的基类(字节/字符)
-
OutputStream/Writer:所有输出流的基类(字节/字符)
按照操作方式分类结构图:
按操作对象分类结构图:
(3)
有了字节流,为什么还要字符流?(字符>字节)
虽然字节是传输的基本单位,但想要生成字节,需要通过JVM由字节转换,这个过程非常耗时,并且容易产生乱码;
所以,java的IO流直接设计出字符流,方便直接对字符进行流操作。
字节流 | 字符流 | |
适用场景 | 音频、图片等媒体文件 | 涉及到字符的文件 |
【问】NIO和传统IO的区别(网络通信)?
传统BIO | NIO | |
特点 | 同步阻塞 | 同步非阻塞(java4) |
性能及原因 | 性能较低;1个线程处理1个请求,线程在IO期间不能做别的事情(比如客户端没有数据发送给服务端,就一直等待) | 性能高;1个线程可以处理多个请求,线程在IO期间可以做别的事情(客户端没有数据发送过来,线程可以去做别的事情,性能高) |
实现 | 基于Stream; 套接字:Socket、ServerSocket | 基于Buffer+Channel,另外还有Selector选择器;套接字:SocketChannel、ServerSocketChannel |
NIO模型的三大特点:
(1)channel:双向的,既可以读,也可以写。
(2)buffer:缓冲,是一块内存。
(3)selector:selector监测管道是否有数据变化(事件),在别的未获取锁的且有数据变化的线程中,提前把数据写入buffer或把数据从buffer中取出。
【问】static,this,super关键字?
static:
- 修饰成员属性和成员方法: 被 static 修饰的成员属于类。可以被类直接调用,也可以通过对象调用(建议通过类调用)。
- 静态代码块: (执行顺序:静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
- 静态内部类(static修饰类的话只能修饰内部类): 静态内部类与非静态内部类的区别: 1. 静态内部类的创建不需要依赖外部类对象的创建。2. 它不能使用任何外部类的非静态成员。
- 静态导包(用来导入类中的静态资源,1.5之后的新特性):
import static
,可以直接使用类中静态成员变量和静态成员方法。
this:
this关键字用于引用类的当前实例。
class Manager {
Employees[] employees;
void manageEmployees() {
int totalEmp = this.employees.length;
System.out.println("Total employees: " + totalEmp);
this.report();
}
void report() { }
}
- this.employees.length:访问当前实例的变量。
- this.report():访问当前实例的方法。
super:
super关键字用于从子类访问父类的变量和方法。
【问】深拷贝和浅拷贝?
Student类三个属性:name、age、teacher。
-
浅拷贝一个对象时:创建一个新对象,并对对象内的非静态属性和方法进行复制。对象的属性分为基本数据类型和引用类型。对基本数据类型的属性,直接在栈区进行复制;对引用类型的属性,则只复制引用但不复制对象,共同引用同一个对象。
- 深拷贝一个对象时:创建一个新对象,并对对象内的非静态属性和方法进行复制。对基本数据类型的属性,直接在栈区进行复制;对引用类型的属性,复制引用也复制对象,分别引用不同的对象。
【JAVA集合】
【问】java集合总结?哪些是并发集合(做了线程安全处理的集合)?
(1)
(2)
【List】
Vector:
读、写都做了同步。
CopyOnWriteArrayList:
写做了同步,读没做同步。适用于读多写少。(在写的时候会复制一个副本,对副本写,写完用副本替换原值。)
【Set】
CopyOnWriteArraySet:
基于CopyOnWriteArrayList,依然是CopyOnWrtite的特性,但不允许重复元素。
【Queue】
ConcurrentLinkedQueue:
通过无锁的方式实现,通常性能优于阻塞队列BlockingQueue。
LinkedBlockingQueue:
阻塞队列的一种实现类,典型应用场景是“生产者-消费者”模式中,如果生产快于消费,生产队列装满时会阻塞,等待消费。进行了读、写锁的分离。
【Deque】
LinkedBlockingDueue:
没有进行读、写锁的分离,因此同一时间只能有一个线程对其操作,因此在高并发应用中,它的性能要远远低于LinkedBlockingQueue,更低于ConcurrentLinkedQueue。
并发性能:ConcurrentLinkedQueue>LinkedBlockingQueue>LinkedBlockingDueue
【Map】
HashTable:
全部进行同步。
ConcurrentHashMap:
(jdk7)分段锁,锁粒度为segment。具体做法是将数组分为n段segment,用n个锁分别锁住这n个segment,并发性能是HashTable的n倍。通过ReenTrantLock加锁。无红黑树。
(jdk8)摒弃了 Segment 的概念,数据结构类似于java8的hashmap。锁的粒度就是HashEntry(首节点),只要不hash冲突,就不会产生并发。通过CAS+Synchronized加锁。有红黑树。
【问】ConcurrentHashMap详解?
JDK1.7:
put:
经过一次hash定位到Segment的位置,然后再hash定位到指定的HashEntry
get:
经过一次hash定位到Segment的位置,然后再hash定位到指定的HashEntry
size:
- 第一种方案他会使用不加锁的模式去尝试多次计算ConcurrentHashMap的size,最多三次,比较前后两次计算的结果,结果一致就认为当前没有元素加入,计算的结果是准确的
- 第二种方案是如果第一种方案不符合,他就会给每个Segment加上锁,然后计算ConcurrentHashMap的size返回
JDK1.8:
put:
没有hash冲突就直接调用Unsafe的方法CAS插入该元素。
get:
与 jdk1.8 hashmap一样
size:
使用一个volatile类型的变量baseCount记录元素的个数,当插入新数据或则删除数据时,会通过addCount()方法更新baseCount
【问】RandomAccess接口?
【问】ArrayList的扩容机制?
扩容过程:
1.无参构造时,存储数组初始化容量为0;有参构造时,存储数组,初始化容量为传入参数。
2.第一次add时,若容量为0,发生扩容,扩为默认值10。
3.之后再调用add,且发现元素个数大于容量时,再次发生扩容:通过Arrays.copyOf()的方式将容量扩容为原来的1.5倍。
【问】HashMap和HashTable的区别?
HashMap | HashTable | |
线程安全性 | 不安全 | 安全 |
效率 | 高 | 低 |
键有无null | 允许有null,放在数组第0个位置 | 不允许有null |
红黑树机制 | jdk8以后有 | 没有 |
扩容 | 初始默认16,扩容乘以二; 大小始终保证为2的幂次方 | 初始默认11,扩容乘以2n+1 |
【问】HashMap的底层实现?
(1)jdk1.7
数组(默认16)、链表+头插法(扩容产生死循环)
用HashEntry内部类实现的对象表示键值对。属性:K,V,next。
put实现:根据key的hashcode+扰动函数等一系列处理得到数组的索引位置。若发生哈希碰撞(两个key值不同,但算出来的数组索引相同),则在该索引位置通过头插法形成链表。
(2)jdk1.8的变化
做了改进:红黑树+尾插法
为什么红黑树?查询速度比链表快。
为什么尾插法?头插法的缺陷:并发条件下扩容rehash产生死循环(链表头插法的扩容会颠倒链表的顺序。在并发的时候,线程a的rehash使链表颠倒了引用顺序,线程b使链表保持原有的引用顺序,造成两个节点互相引用,最终形成了死循环)。
【问】HashMap链表升级为红黑树?红黑树退化为链表?
避免频繁的树化和退化过程:
(1)数组长度到64+链表长度到8;
(2)红黑树节点数量小于6,退化为链表;
【问】HashMap的加载因子?为什么是0.75?
(1)
加载因子是确定数组扩容时机的。扩容条件:存入数据的数量>=数组长度*加载因子。(反应内存利用率)
(2)
空间(内存)与时间(冲突)的平衡,官方测出来0.75最优。
【问】HashMap的数组长度为什么是2的幂次方?
为了减少哈希碰撞。计算索引时要对数组长度取余,用2的幂次方可以使结果的散列性更好。
【问】LinkedHashSet 和 TreeSet 的区别?(均为有序Set)
-
LinkedHashSet:是 HashSet 的子类;能够按照添加的顺序遍历;
-
TreeSet:底层使用红黑树;能够按照添加元素的顺序进行遍历,排序的方式有自然排序和定制排序。(TreeMap也是红黑树)