1. JAVA中的几种基本数据类型是什么,各自占用多少字节。
类型 |
占位 |
取值范围 |
字节大小 |
byte |
8 |
-128 ~ 127 |
1 |
short |
16 |
-32768 ~ 32678 |
2 |
char |
16 |
-128~127 |
2 |
int |
32 |
-2,147,483,648 ~ 2,147,483,647 |
4 |
long |
64 |
-9,223,372,036,854,775,808~+9,223,372,036,854,775,807 |
8 |
double |
64 |
-1.79769313486231576E+308 ~ 1.79769313486231576E+308 |
8 |
float |
32 |
-3,40292347E+38 ~ +3,40292347E+38 |
4 |
boolean |
1 |
true/false |
1 |
==================================================================
2. String类能被继承吗,为什么。
public final class String implements java.io.Serializable, ComparableString 被final修饰了,所有不能被继承。, CharSequence
1.final修饰的对象不能被修改;
2.final修饰的类不能被继承;
3.final修饰的方法不能被重写;
==================================================================
3. String,Stringbuffer,StringBuilder的区别。
1.是否多线程安全public class Test{
javac Test.java
javap -verbose Test
0: ldc #2 // String ABC
2: astore_1
3: new #3 // classjava/lang/StringBuffer
6: dup
7: ldc #4 // String A
9: invokespecial #5 // Method java/lang/StringBuffer.
>":(Ljava/lang/String;)V
12: ldc #6 // String B
14: invokevirtual #7 // Method java/lang/StringBuffer.
:(Ljava/lang/String;)Ljava/lang/StringBuffer;
17: ldc #8 // String C
19: invokevirtual #7 // Method java/lang/StringBuffer.
:(Ljava/lang/String;)Ljava/lang/StringBuffer;
22: astore_2
23: new #9 // classjava/lang/StringBuilder
26: dup
27: ldc #4 // String A
29: invokespecial #10 // Method java/lang/StringBuilder
t>":(Ljava/lang/String;)V
32: ldc #6 // String B
34: invokevirtual #11 // Method java/lang/StringBuilder
d:(Ljava/lang/String;)Ljava/lang/StringBuilder;
37: ldc #8 // String C
39: invokevirtual #11 // Method java/lang/StringBuilder
d:(Ljava/lang/String;)Ljava/lang/StringBuilder;
42: astore_3
43: return
1.这里面创建一个String 对象,JVM在编译期进行了常量折叠,直接拼接了ABC,所以在运行的时候只进行了两步:从字符串常量池中获取ABC、将string指向ABC2.这里面创建一个StringBuffer和StringBuilder对象时,都经历了七步:
a.从字符串常量池里面取出A,
b.调用StringBuilder和StringBuilder的append方法,添加A;
c.从字符串常量池里面取出B,
d.调用StringBuilder和StringBuilder的append方法,添加B;
e.从字符串常量池里面取出C,
f.调用StringBuilder和StringBuilder的append方法,添加C;g.将 StringBuilder和StringBuilder指向 ABC
==================================================================
4. ArrayList和LinkedList有什么区别。
1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。
2.在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。
3.LinkedList不支持高效的随机元素访问。
4.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
可以这样说:当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。
==================================================================
5. 讲讲类的实例化顺序,比如父类静态数据,父类构造函数,父类字段,子类静态数据,子类构造函数,子类字段,当new的时候,他们的执行顺序。
类的实例化顺序:先静态再父子
父类静态数据->子类静态数据->父类字段->子类字段->父类构造函数->子类构造函数
==================================================================
6. 用过哪些Map类,都有什么区别,HashMap是线程安全的吗,并发下使用的Map是什么,他们内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等。
最常用的Map实现类有:HashMap,ConcurrentHashMap(jdk1.8),LinkedHashMap,TreeMap,HashTable;
其中最频繁的是HashMap和ConcurrentHashMap,他们的主要区别是HashMap是非线程安全的。ConcurrentHashMap是线程安全的。
并发下可以使用ConcurrentHashMap和HashTable,他们的主要区别是:
1.ConcurrentHashMap的hash计算公式:(key.hascode()^ (key.hascode()>>> 16)) & 0x7FFFFFFF
HashTable的hash计算公式:key.hascode()& 0x7FFFFFFF
2.HashTable存储方式都是链表+数组,数组里面放的是当前hash的第一个数据,链表里面放的是hash冲突的数据
ConcurrentHashMap是数组+链表+红黑树
3.默认容量都是16,负载因子是0.75。就是当hashmap填充了75%的busket是就会扩容,最小的可能性是(16*0.75),一般为原内存的2倍
4.线程安全的保证:HashTable是在每个操作方法上面加了synchronized来达到线程安全,ConcurrentHashMap线程是使用CAS(compore and swap)来保证线程安全的
==================================================================
7. JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。
jdk8 放弃了分段锁而是用了Node锁,减低锁的粒度,提高性能,并使用CAS操作来确保Node的一些操作的原子性,取代了锁。
但是ConcurrentHashMap的一些操作使用了synchronized锁,而不是ReentrantLock,虽然说jdk8的synchronized的性能进行了优化,但是我觉得还是使用ReentrantLock锁能更多的提高性能
==================================================================
8. 有没顺序的 Map 实现类,如果有,他们是怎么保证有序的 。
顺序的 Map 实现类:LinkedHashMap,TreeMap
LinkedHashMap 是基于元素进入集合的顺序或者被访问的先后顺序排序,TreeMap 则是基于元素的固有顺序 (由 Comparator 或者 Comparable 确定)。
==================================================================
9. 抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么,类可以实现多个接口么。
抽象类和接口的区别:
1.抽象类可以有自己的实现方法,接口在jdk8以后也可以有自己的实现方法(default)
2.抽象类的抽象方法是由非抽象类的子类实现,接口的抽象方法有接口的实现类实现
3.接口不能有私有的方法跟对象,抽象类可以有自己的私有的方法跟对象
类不可以继承多个类,接口可以继承多个接口,类可以实现多个接口
==================================================================
10. 继承和聚合的区别在哪。
继承:指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;在Java中此类关系通过关键字extends明确标识,在设计时一般没有争议性;
聚合:聚合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即has-a的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享;比如计算机与CPU、公司与员工的关系等;表现在代码层面,和关联关系是一致的,只能从语义级别来区分;
==================================================================
11. 讲讲你理解的nio。他和bio的区别是啥,谈谈reactor模型。
BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
reactor模型:反应器模式(事件驱动模式):当一个主体发生改变时,所有的属体都得到通知,类似于观察者模式。
==================================================================
12. 反射的原理,反射创建类实例的三种方式是什么。
反射的原理:如果知道一个类的名称/或者它的一个实例对象, 就能把这个类的所有方法和变量的信息(方法名,变量名,方法,修饰符,类型,方法参数等等所有信息)找出来。
反射创建类实例的三种方式:
1.Class.forName("com.A");
2.new A().getClass();
3.A.class;
==================================================================
13. 反射中,Class.forName和ClassLoader区别。
class.forName()除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
==================================================================
14. 描述动态代理的几种实现方式,分别说出相应的优缺点。
动态代理有两种实现方式,分别是:jdk动态代理和cglib动态代理
jdk动态代理的前提是目标类必须实现一个接口,代理对象跟目标类实现一个接口,从而避过虚拟机的校验。
cglib动态代理是继承并重写目标类,所以目标类和方法不能被声明成final。
==================================================================15. 动态代理与cglib实现的区别。
动态代理有两种实现方式,分别是:jdk动态代理和cglib动态代理
jdk动态代理的前提是目标类必须实现一个接口,代理对象跟目标类实现一个接口,从而避过虚拟机的校验。
cglib动态代理是继承并重写目标类,所以目标类和方法不能被声明成final。
==================================================================16. 为什么cglib方式可以对接口实现代理。
cglib动态代理是继承并重写目标类,所以目标类和方法不能被声明成final。而接口是可以被继承的。
==================================================================
17. final的用途。
1.final修饰的对象不能被修改;
2.final修饰的类不能被继承;
3.final修饰的方法不能被重写;
==================================================================18. 写出三种单例模式实现。
/** * 单例模式 */ public class Singleton { /** * 饿汉式 单例模式 * 类在加载时就实例化, * 提供一个公共的方法获取实例化的类 *==================================================================* 优点:线程安全 *
* 缺点:类被加载时就实例化, * 有可能在整个代码周期都没有使用 * 且不会被回收,会一直存在 */ private static class 饿汉式 { private static 饿汉式 饿汉式 = new 饿汉式(); private 饿汉式() { } static 饿汉式 get饿汉式() { return 饿汉式; } } /** * 懒汉式 单例模式 * 只有在真正使用的时候,才实例化 *
* 优点:只有在真正使用的时候才实例化 *
* 缺点:线程不安全 */ private static class 懒汉式 { private static 懒汉式 懒汉式; private 懒汉式() { } static synchronized 懒汉式 get懒汉式() { if (null == 懒汉式) { 懒汉式 = new 懒汉式(); } return 懒汉式; } } /** * 枚举式 单例模式 * 跟 饿汉式 一样 */ private enum 枚举式 { 枚举式; public 枚举式 get枚举式() { return 枚举式; } } public static void main(String[] args) { Singleton.懒汉式 懒汉式 = Singleton.懒汉式.get懒汉式(); if (懒汉式 == Singleton.懒汉式.get懒汉式()) { System.out.println("懒汉式--单例生效"); } Singleton.饿汉式 饿汉式 = Singleton.饿汉式.get饿汉式(); if (饿汉式 == Singleton.饿汉式.get饿汉式()) { System.out.println("饿汉式--单例生效"); } if (枚举式.枚举式.get枚举式() == 枚举式.枚举式.get枚举式()) { System.out.println("枚举式--单例生效"); } } }
19. 如何在父类中为子类自动完成所有的hashcode和equals实现?这么做有何优劣。
父类的equals不一定满足子类的equals需求。比如所有的对象都继承Object,默认使用的是Object的equals方法,在比较两个对象的时候,是看他们是否指向同一个地址。
但是我们的需求是对象的某个属性相同,就相等了,而默认的equals方法满足不了当前的需求,所以我们要重写equals方法。
如果重写了equals 方法就必须重写hashcode方法,否则就会降低map等集合的索引速度。
==================================================================
20. 请结合OO设计理念,谈谈访问修饰符public、private、protected、default在应用设计中的作用。
OO设计理念:封装、继承、多态
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。所以我们可以通过public、private、protected、default 来进行访问控制
关键字 |
类内部 |
本包 |
子类 |
外部包 |
public |
√ |
√ |
√ |
√ |
protected |
√ |
√ |
√ |
× |
default |
√ |
√ |
× |
× |
private |
√ |
× |
× |
× |
java访问控制符的含义和使用情况
==================================================================
21. 深拷贝和浅拷贝区别。
浅拷贝只拷贝指针,深拷贝就是拷贝他的值,重新生成的对像。
==================================================================
22. 数组和链表数据结构描述,各自的时间复杂度。
数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。
链表恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。
访问数组中第 n 个数据的时间花费是 O(1) 但是要在数组中查找一个指定的数据则是 O(N) 。当向数组中插入或者删除数据的时候,最好的情况是在数组的末尾进行操作,时间复杂度是 O(1) ,但是最坏情况是插入或者删除第一个数据,时间复杂度是 O(N) 。在数组的任意位置插入或者删除数据的时候,后面的数据全部需要移动,移动的数据还是和数据个数有关所以总体的时间复杂度仍然是 O(N) 。
在链表中查找第 n 个数据以及查找指定的数据的时间复杂度是 O(N) ,但是插入和删除数据的时间复杂度是 O(1)
==================================================================
23. error和exception的区别,CheckedException,RuntimeException的区别。
Error(错误)表示系统级的错误和程序不必处理的异常,是java运行环境中的内部错误或者硬件问题。比如:内存资源不足等。对于这种错误,程序基本无能为力,除了退出运行外别无选择,它是由Java虚拟机抛出的。
Exception(违例)表示需要捕捉或者需要程序进行处理的异常,它处理的是因为程序设计的瑕疵而引起的问题或者在外的输入等引起的一般性问题,是程序必须处理的。
Exception又分为运行时异常,受检查异常。
RuntimeException(运行时异常),表示无法让程序恢复的异常,导致的原因通常是因为执行了错误的操作,建议终止程序,因此,编译器不检查这些异常。
CheckedException(受检查异常),是表示程序可以处理的异常,也即表示程序可以修复(由程序自己接受异常并且做出处理), 所以称之为受检查异常。
==================================================================
24. 请列出5个运行时异常。
NullPointerException
IndexOutOfBoundsException
ClassCastException
ArrayStoreException
BufferOverflowException
==================================================================
25. 在自己的代码中,如果创建一个java.lang.String对象,这个对象是否可以被类加载器加载?为什么。
不可以,双亲委派模式会保证父类加载器先加载类,就是BootStrap(启动类)加载器加载jdk里面的java.lang.String类,而自定义的java.lang.String类永远不会被加载到
==================================================================
26. 说一说你对java.lang.Object对象中hashCode和equals方法的理解。在什么场景下需要重新实现这两个方法。
父类的equals不一定满足子类的equals需求。比如所有的对象都继承Object,默认使用的是Object的equals方法,在比较两个对象的时候,是看他们是否指向同一个地址。
但是我们的需求是对象的某个属性相同,就相等了,而默认的equals方法满足不了当前的需求,所以我们要重写equals方法。
如果重写了equals 方法就必须重写hashcode方法,否则就会降低map等集合的索引速度。
==================================================================27. 在jdk1.5中,引入了泛型,泛型的存在是用来解决什么问题。
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
==================================================================
28. 有没可能 2个不相等的对象有同hashcode。
有可能,最简单的方法,百分百实现的方式就是重写hascode();
==================================================================
29. Java中的HashSet内部是如何工作的。
public HashSet() { map = new HashMap<>(); }默认使用的是HaseMap;
==================================================================
30. 什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题,如何解决。
序列化是一种用来处理对象流的机制 ,所谓对象流就是将对象的内容进行流化。
序列化是为了解决在对对象流进行读写操作时所引发的问题。
序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流;