1 接口的意义
规范:架构师写接口,开发人员写实现。
扩展:接口实现类可以更换,方便扩展
回调:耗时操作的回调,观察者的回调
降低耦合
安全:只描述接口提供的功能,而不涉及具体的实现
2 抽象类的意义
为子类提供一个公共的类,封装子类中重复的方法,定义抽象方法,利于代码维护和重用。
多态,根据传递参数的不同,返回不同的结果。Animal->move,Bird,Fish,Man
3 接口和抽象类的区别
本质区别:抽象类表示的是这个对象是什么(人),接口表示的是这个对象能做什么(吃饭、运动)。
具体区别:
- 接口是一种更高层面的抽象,是一种规范、功能定义的声明。
- 抽象类可以实现部分方法,接口不能。
- 接口的成员变量默认是public static final的。接口是对开闭原则(对扩展开放,对修改关闭)的体现,static:共享模板 final:所有可变的东西都应该放到实现类中。
- 接口可以多继承,抽象类不行。
4 内部类的作用
- 内部类可以很好的实现隐藏。 一般的非内部类,是不允许有 private 与protected权限的,但内部类可以
- 内部类拥有外围类的所有元素的访问权限
- 可是实现多重继承
- 可以避免修改接口而实现同一个类中两种同名方法的调用。
5 子类能否重写父类的静态方法
父类的静态方法可以被子类继承,但是不能重写。
6 列举java的集合和继承关系
List、Set、Map是这个集合体系中最主要的三个接口,其中List和Set继承自Collection接口。
Set不允许元素重复。HashSet和TreeSet是两个主要的实现类,其中HashSet无序。
List有序且允许元素重复。ArrayList、LinkedList和Vector是三个主要的实现类,其中Vector是线程安全的。
Map也属于集合系统,但和Collection接口不同。Map是key对value的映射集合,其中key列就是一个集合。key不能重复,value可以重复(可以为空)。HashMap(可以为空)、TreeMap和HashTable(key不可以为空)是三个主要的实现类,其中TreeMap是线程安全的。
SortedSet和sortedMap接口对元素按指定规则排序,SortedMap对key列进行排序。
7 HashMap的原理
HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
HashMap的数据结构: 在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。
有序数组存储数据,对数据的索引效率都很高,但是插入和删除就会有性能瓶颈(回忆ArrayList),
链表存储数据,要一次比较元素来检索出数据,所以索引效率低,但是插入和删除效率高(回忆LinkedList),
两者取长补短就产生了哈希散列这种存储方式,也就是HashMap的存储逻辑。
负载因子表示一个散列表的空间的使用程度,默认0.75,有这样一个公式:initailCapacity*loadFactor=HashMap的容量。
所以负载因子越大则散列表的装填程度越高,也就是能容纳更多的元素,元素多了,链表大了,所以此时索引效率就会降低。
反之,负载因子越小则链表中的数据量就越稀疏,此时会对空间造成浪费,但是此时索引效率高。
HashMap的内部存储结构其实是数组和链表的结合。当实例化一个HashMap时,系统会创建一个长度为Capacity的Entry数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。
每个bucket中存储一个元素,即一个Entry对象,但每一个Entry对象可以带一个引用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Entry链。
我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。
如果两个键的hashcode相同,你如何获取值对象?
>
1.若重写了equals(Object obj)方法,则有必要重写hashCode()方法。
2.若两个对象equals(Object obj)返回true,则hashCode()有必要也返回相同的int数。
3.若两个对象equals(Object obj)返回false,则hashCode()不一定返回不同的int数。
4.若两个对象hashCode()返回相同int数,则equals(Object obj)不一定返回true。
5.若两个对象hashCode()返回不同int数,则equals(Object obj)一定返回false。
6.同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题。
将集合分成若干个存储区域(可以看成一个个桶),每个对象可以计算出一个哈希码,可以根据哈希码分组,每组分别对应某个存储区域,这样一个对象根据它的哈希码就可以分到不同的存储区域(不同的桶中)。
当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置。
找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。
当两个对象的hashcode相同会发生什么?
它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。
因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中
如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。
你了解重新调整HashMap大小存在什么问题吗?
当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。这个时候,你可以质问面试官,为什么这么奇怪,要在多线程的环境下使用HashMap呢?
我们可以使用CocurrentHashMap来代替Hashtable吗?
Hashtable是synchronized的,但是ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap当然可以代替HashTable,但是HashTable提供更强的线程安全性。
8 String
在JAVA中,有六个不同的地方可以存储数据:
- 寄存器(register) 处理器内部 最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制
- 堆栈(stack) 通用RAM 堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些 内存。JAVA编译器必须知道存储在堆栈内所有数据的确切大小和生命周期,因为它必须生成 相应的代码,以便上下移动堆栈指针。
存放基本类型的变量数据和对象,数组的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中) - 堆(heap)一种通用性的内存池(也存在于RAM中),存放所有new出来的对象。 堆不同于堆栈的好处是:编译器不需要知道要从堆里分配多少存储区 域,也不必知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当你需要创建一个对象的时候,只需要new写一行简单的代码,当执行 这行代码时,会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代码。用堆进行存储分配比用堆栈进行存储存储需要更多的时间。
- 静态存储(static storage)“在固定的位置”。可用关键字static来标识一个对象的特定元素是静态的,但JAVA对象本身从来不会存放在静态存储空间里。
- 常量存储(constant storage)常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。存放字符串常量和基本类型常量(public static final)
- 非RAM存储 如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。 硬盘等永久存储空间
就速度来说,有如下关系:
寄存器 >堆栈 > 堆 > 其它
对于栈和常量池中的对象可以共享,对于堆中的对象不可以共享。
|
|
|
|
-128到127之 间的Integer缓存到一个Integer数组中去了,如果你要把一个int变成一个Integer对象,首先去缓存中找,找到的话直接返回引用给你就 行了,不必再新new一个,如果不在-128-127 时会返回一个新new出来的Integer对象。由于321超出了这个范围,所以e、f不相等。
String str = “abc”创建对象的过程
- 首先在常量池中查找是否存在内容为”abc”字符串对象
- 如果不存在则在常量池中创建”abc”,并让str引用该对象
- 如果存在则直接让str引用该对象
至 于”abc”是怎么保存,保存在哪?常量池属于类信息的一部分,而类信息反映到JVM内存模型中是对应存在于JVM内存模型的方法区,也就是说这个类信息 中的常量池概念是存在于在方法区中,而方法区是在JVM内存模型中的堆中由JVM来分配的,所以”abc”可以说存在于堆中(而有些资料,为了把方法区的 堆区别于JVM的堆,把方法区称为栈)。一般这种情况下,”abc”在编译时就被写入字节码中,所以class被加载时,JVM就为”abc”在常量池中 分配内存,所以和静态区差不多。
String str = new String(“abc”)创建实例的过程
- 首先在堆中(不是常量池)创建一个指定的对象”abc”,并让str引用指向该对象
- 在字符串常量池中查看,是否存在内容为”abc”字符串对象
- 若存在,则将new出来的字符串对象与字符串常量池中的对象联系起来
- 若不存在,则在字符串常量池中创建一个内容为”abc”的字符串对象,并将堆中的对象与之联系起来 intern 方法可以返回该字符串在常量池中的对象的引用
泛型中 extends 和 super 的区别?
什么是上界?
|
|
上界<? extends T>不能往里存,只能往外取
什么是下界?
|
|
下界<? super T>不影响往里存,但往外取只能放在Object对象里
java虚拟机的特性
Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用模式Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。
哪些情况下的对象会被垃圾回收机制处理掉
Java 垃圾回收机制最基本的做法是分代回收。内存中的区域被划分成不同的世代,对象根据其存活的时间被保存在对应世代的区域中。一般的实现是划分成3个世代:年轻、年老和永久。内存的分配是发生在年轻世代中的。当一个对象存活时间足够长的时候,它就会被复制到年老世代中。对于不同的世代可以使用不同的垃圾回收算法。进行世代划分的出发点是对应用中对象存活时间进行研究之后得出的统计规律。一般来说,一个应用中的大部分对象的存活时间都很短。比如局部变量的存活时间就只在方法的执行过程中。基于这一点,对于年轻世代的垃圾回收算法就可以很有针对性。
进程和线程的区别
简而言之,一个程序至少有一个进程,一个进程至少有一个线程。
线程的划分尺度小于进程,使得多线程程序的并发性高。
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。如果有兴趣深入的话,我建议你们看看《现代操作系统》或者《操作系统的设计与实现》。对就个问题说得比较清楚。