Java面试宝典笔记录

时间:2023-03-09 17:38:08
Java面试宝典笔记录

1.一个.java文件中可以有多个类(不是内部类),但是只能有一个public类,且类名和文件同名。(一般不提倡这么写,一类一文件)

2.java保留字:goto, const.

3.访问权限控制

 访问权限   类   包  子类  其他包

public       ∨   ∨   ∨     ∨

protected ∨   ∨   ∨     ×

default     ∨   ∨    ×      ×

private    ∨    ×     ×      ×

4.&&有短路的作用,&没有;

&可以表示按位与操作的操作符。

5..java可以用标号跳出当前循环:

ok:

for ( …){

if(i==5){

break ok;

}

}

也可不用标号,直接break。

6.8种基本数据类型(8个二进制位构成一个字节即1B,一个字节可以存写一个英文字母或者两个汉字,字节是存储空间的基本计量单位)

整型:

byte 8位 1字节

short 16位 2字节

int 32位 4字节

long 64位 8字节 后必须加L或者l

浮点型:

float 32位 4字节 后必须加F或者f

double 64位  8字节 D d可加可不加

布尔型

boolean 只有true false两个值

字符型:

char 16位 2字节

基本数据类型的封装类都是final,不可被继承

String 不是基本数据类型 也是final类

数字的默认类型为int 小数默认为 double

两种不同类型的数据运算,结果为较大的

byte short char 三种类型是平级的,不能自动转换,需要强制转换

常量的表示:

十六进制数 开头需要加 0x或者0X

八进制数 必须以0开头

长整型 必须以L或l结尾

float 型必须以F或f结尾

字符型用单引号括起来,占用两字节 常用来表示一些转义字符

\r 回车

\n 换行

\t 制表符 相当于 table键

\b 退格键 相当于 back space键

\' 单引号

\“ 双引号

\\表示 \

基本数据类型的常量都存在于栈内存里,

对象,数组存于堆内存里

7.char 可以存储一个汉字

char 类型的变量是用来存储unicode 编码的字符的,unicode编码的字符包含了汉字,如果汉字包含在unicode编码中就可以存储,unicode编码占用两字节,所以char也是占用两字节。

10.使用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的。例如,对于如下语句:

final StringBuffer a=new StringBuffer("immutable");

执行如下语句将报告编译期错误:

a=new StringBuffer("");

但是,执行如下语句则可以通过编译:

a.append(" broken!");

有人在定义方法的参数时,可能想采用如下形式来阻止方法内部修改传进来的参数对象:

public void method(final  StringBuffer  param){

}

实际上,这是办不到的,在该方法内部仍然可以增加如下代码来修改参数对象:        param.append("a");

11.==操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作符。

如果一个变量指向的数据是对象类型的,那么,这时候涉及了两块内存,对象本身占用一块内存(堆内存),变量也占用一块内存,例如Objet obj = new Object();变量obj是一个内存,new Object()是另一个内存,此时,变量obj所对应的内存中存储的数值就是对象占用的那块内存的首地址。对于指向对象类型的变量,如果要比较两个变量是否指向同一个对象,即要看这两个变量所对应的内存中的数值是否相等,这时候就需要用==操作符进行比较。

equals方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的两个对象是独立的。例如,对于下面的代码:

String a=new String("foo");

String b=new String("foo");

两条new语句创建了两个对象,然后用a,b这两个变量分别指向了其中一个对象,这是两个不同的对象,它们的首地址是不同的,即a和b中存储的数值是不相同的,所以,表达式a==b将返回false,而这两个对象中的内容是相同的,所以,表达式a.equals(b)将返回true。

在实际开发中,我们经常要比较传递进行来的字符串内容是否等,例如,String input = …;input.equals(“quit”),许多人稍不注意就使用==进行比较了,这是错误的,随便从网上找几个项目实战的教学视频看看,里面就有大量这样的错误。记住,字符串的比较基本上都是使用equals方法。

如果一个类没有自己定义equals方法,那么它将继承Object类的equals方法,Object类的equals方法的实现代码如下:

boolean equals(Object o){

return this==o;

}

这说明,如果一个类没有自己定义equals方法,它默认的equals方法(从Object 类继承的)就是使用==操作符,也是在比较两个变量指向的对象是否是同一对象,这时候使用equals和使用==会得到同样的结果,如果比较的是两个独立的对象则总返回false。如果你编写的类希望能够比较该类创建的两个实例对象的的内容是否相同,那么你必须覆盖equals方法,由你自己写代码来决定在什么情况即可认为两个对象的内容是相同的。

12.在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。

在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。

例如,对于下面的程序,无论创建多少个实例对象,永远都只分配了一个staticVar变量,并且每创建一个实例对象,这个staticVar就会加1;但是,每创建一个实例对象,就会分配一个instanceVar,即可能分配多个instanceVar,并且每个instanceVar的值都只自加了1次。

public class VariantTest{

public static int staticVar = 0;

public int instanceVar = 0;

public VariantTest(){

staticVar++;

instanceVar++;

System.out.println(“staticVar=” + staticVar + ”,instanceVar=” + instanceVar);

}

}

13.静态方法只能调用静态变量,非静态方法可以调用静态变量和实例变量

14.int.和Integer的区别

前者 8大基本数据类型之一,默认值为0.

后者 int的封装类 默认值为null.

15.Math.ceil(-11.5)=-11    向上取整

Math.round(-11.5) =-11 四舍五入 相当于+0.5 再向下取整

Math.floor(-11.5) = -12 向下取整

16.重载和覆盖

重载 overload:在同一类中可以有多个名称相同的参数,他们的参数列表各不相同(参数的个数类型不同)

重写 覆盖 override 具有继承关系的类中,子类的方法可以和父类的某个方法的名称和参数完全相同,实现的过程不同。

Constructor 构造器只可被重载 不能被继承和覆盖

22.面向对象的特征

封装:访问权限的控制

继承:父类有的子类也有,父类没的子类也可以有,父类有的子类可以改变

抽象:实例化对象

多态:父类的引用指向子类实例化的对象,接口的引用指向接口的实现类。

23.接口和抽象类

接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用,可以实现接口的空实现。

含有抽象方法的类必须定义为抽象类,抽象类中可以没有抽象方法。

抽象类不能实例化对象,子类需要实现抽象类中所有的抽象方法,所以抽象类中不能有抽象的构造方法和抽象静态方法,可以有构造方法和静态方法。

如果子类没有实现抽象类中的抽象方法,则子类也要定以为抽象的。

如果抽象方法实现某个接口,则子类还要实现抽象类中没有实现的接口方法。

接口可以说是抽象类的特例,接口中所有的方法必须为抽象的即 public abstract 通常省略,所有的成员变量必须为静态的 public static final .

下面比较一下两者的语法区别:

1.抽象类可以有构造方法,接口中不能有构造方法。

2.抽象类中可以有普通成员变量,接口中没有普通成员变量

3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。

4. 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然

eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。

5. 抽象类中可以包含静态方法,接口中不能包含静态方法

6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。

7. 一个类可以实现多个接口,但只能继承一个抽象类。

25.native

native方法表示该方法要用另外一种依赖平台的编程语言实现的,不存在着被子类实现的问题,所以,它也不能是抽象的,不能与abstract混用。例如,FileOutputSteam类要硬件打交道,底层的实现用的是操作系统相关的api实现,例如,在windows用c语言实现的,所以,查看jdk 的源代码,可以发现FileOutputStream的open方法的定义如下:

private native void open(String name) throws FileNotFoundException;

如果我们要用java调用别人写的c语言函数,我们是无法直接调用的,我们需要按照java的要求写一个c语言的函数,又我们的这个c语言函数去调用别人的c语言函数。由于我们的c语言函数是按java的要求来写的,我们这个c语言函数就可以与java对接上,java那边的对接方式就是定义出与我们这个c函数相对应的方法,java中对应的方法不需要写具体的代码,但需要在前面声明native。

26.内部类就是在类的内部定义的类,不能有静态成员变量,可以定义在外部类的方法内和方法外,

内部类可以访问外部类中的成员变量,

定义在外部类方法外部的类访问类型可以是任意,

创建内部类的实例对象时一定要先创建外部类的实例化对象。

定义在方法内的内部类不能有任何访问修饰符,好比方法的局部变量一样,但这种内部类前面可以加final或abstract修饰符。这种内部类对其他类是不可见也无法引用的,但是可以在创建对象时传递给其他类访问,这种内部类必须先定义后使用。

在方法内还可以创建匿名内部类,即定义某一接口或类的子类的同时,还创建了子类的实例化对象,无需为该子类定义名称。

内部类可以引用它的包含类的成员,如果不是静态内部类没有限制,如果是静态的只能访问静态的外部成员变量。

31.String 被设计成不可改变的,所有的对象都是不可改变的,如果对字符串进行各种修改,使用String会性能低下,内存开销大,因为每个不同的字符串,都要一个String 对象来表示。可以用StringBuffer,允许修改。

如果使用相同的字符串可以直接 String s=“ab“,而不是String s=new String(“ab");

后者调用生成器,每次都新生成一个对象。

StringBuffer 和StringBuilder 都继承了AbstractStringBuilder,公共方法都一样,前者加了同步锁,所以线程是安全的,后者没,所以非线程安全的,如果不是多线程,后者效率更高。

如果一个字符串是在方法里定义的,这种情况下仅仅可能一个线程访问它,不存在不安全的因素,推荐用StringBuilder,如果实例变量是在类里定义的,并且在多线程下会访问,这时最好使用StringBuffer.

栈 由JVM分配,用于保存线程执行动作和数据引用,一般用来存储基本数据类型的局部变量。

堆 由JVM分配,用于存储对象等数据的区域,一般用来存储我们new出来的对象,

常量池 在堆中分配出的一块区域,用于存储显式的String,float或者interger例如String str="abc",abc这个字符串就是显示声明,所以存在常量池中。

String shift =“a”+“b”+“c”+“d”只创建了一个对象,java编译时会对字符串常量直接相加的表达式进行优化。

36.数组有length,String 有length方法。

39.try finally

try

{

return x;

}

finally

{

++x;

}

返回为1,先把1存起来,然后执行finally,然后返回

try

{

return 1 ;

}

finally

{

return 2 ;

}

返回为2,finally中的代码比return 和break语句后执行

40、final, finally, finalize的区别。

  final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。

内部类要访问局部变量,局部变量必须定义成final类型,例如,一段代码……

finally是异常处理语句结构的一部分,表示总是执行。

finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。JVM不保证此方法总被调用。

41、运行时异常与一般异常有何异同?

异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。

42、error和exception有什么区别?

error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出,线程死锁等系统问题。不可能指望程序能处理这样的情况。 exception 表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。

43.java异常处理

jajava.lang.Throwable 下派生了Error和Exception,前者表示用程序本身无法克服和恢复的一种严重问题,程序只有死了的份如,内存溢出,线程锁死,后者可以克服和恢复,又分为系统异常和普通异常。系统异常只软件本身缺陷所导致,也就是开发者考虑不周,用户无法克服,如空指针异常,数组脚本越界,类型转换异常,这种情况可以让程序继续运行或死掉。普通异常是环境变化或异常导致,是用户可以克服的,如断网,硬盘空间不足,程序不应该死掉。java为系统异常和普通异常提供了不同的解决方案,编译器强制普通异常必须try..catch处理或用throws声明继续抛给上层调用方法处理,所以普通异常也称为checked异常,而系统异常可以处理也可以不处理,所以,编译器不强制用try..catch处理或用throws声明,所以系统异常也称为unchecked异常。

46.多线程

启动多线程的两种方法:继承Thread类,实现Ruanable接口。

获得线程信息:

可以用this和Thread.currentThread调用

getName();线程的名字

getId();线程的Is

isAlive();是否存活

start之前线程是死的,执行run时是活的,执行之后,又被认为是不存活的。

停止线程:

1.等待等待run方法执行完

2.线程对象调用stop()

3.线程对象调用interrupt(),在该线程的run方法中判断是否终止,抛出一个终止异常终止。

4.线程对象调用interrupt(),在该线程的run方法中判断是否终止,以return语句结束。

2,stop() 方法已经被禁用了,会产生一下问题,

强制结束,该线程应该做的清理工作无法完成。

强制结束,该线程已经加锁的对象强制解锁,造成数据不一致。

第三种是目前推荐的方法:

调用interrupt,然后在run方法中判断是否终止。判断终止的方式有两种,一种是Thread类的静态方法interrupted(),另一种是Thread的成员方法isInterrupted()。这两个方法是有所区别的,第一个方法是会自动重置状态的,如果连续两次调用interrupted(),第一次如果是false,第二次一定是true。而isInterrupted()是不会的。

try{

for(int i = 0 ; i < 50000000 ; i++){

if (interrupted()){

System.out.println("已经是停止状态,我要退出了");

throw new InterruptedException("停止.......");

}

System.out.println("i=" + (i + 1));

}

}catch (InterruptedException e){

System.out.println("顺利停止");

}

暂停线程

suspend()暂停线程

resume() 恢复线程

也被废弃了,线程持有锁定的公共资源的情况下,一旦被暂停,则公共资源也无法被其他线程持有。

线程被强制暂停,导致该线程执行的操作没有完全执行,这时访问线程的数据会出现数据不一致。

多线程的几种状态

新生(New):这个状态下我们通过new关键字生成了新的线程Thread对象,为它分配了内存空间,但是这个线程并不是存活状态;

就绪(Runnable):线程对象调用start()方法,线程进入runnable状态,等待分配CPU资源。这个时候线程是存活的;

运行(Running):获取CPU资源后,线程开始进行,run()方法被执行,直到线程结束,或者因为资源耗尽等原因被中断或挂起;

阻塞(Blocked):线程调用join()、sleep()和wait()等方法而暂处阻塞状态;

死亡(Dead):线程结束,或者因为异常而*中断或异常退出,生命周期结束。

sleep方法和wait方法

1、sleep方法(只释放CPU,不释放锁),比如一个变量i,好几个线程都要用到,线程A调用sleep方法只释放了CPU,但是A没有释放i的锁,而那几个线程又刚好需要i达到一定条件才能继续运行,所以线程A就算释放了CPU也没用。

2、wait方法(释放CPU和锁),比如有两个进程,进程A调用wait方法之后,进程释放CPU和锁,要等待线程B调用notify方法或notifyAll方法来通知A,你可以来抢夺资源了,但是A不一定就能抢得到。

同步锁

即有synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

1、public/private/protected 函数返回类型 synchronized 函数名(形参)

{

//函数内容

}

例如:

public void synchronized FuncName()

{

//do something

}

2、public/private/protected 函数返回类型  函数名(形参)

{

synchronized(this)

//需要进行同步操作的函数内容

//其他操作

}

volatile关键字为域变量的访问提供了一种免锁机制;

• 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新;

• 因此每次使用该域就要重新计算,而不是使用寄存器中的值;

• volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。

4.在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。

ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用

class Bank {

private int account = 100;

//需要声明这个锁

private Lock lock = new ReentrantLock();

public int getAccount() {

return account;

}

//这里不再需要synchronized

public void save(int money) {

lock.lock();

try{

account += money;

}finally{

lock.unlock();

}

}

线程让步:

yield()方法暂停之后,优先级与当前线程相同或者更高的就绪状态下的线程更有可能获得执行的机会。

线程合并:

将几个并行线程的线程合并为一个单线程执行,应用场景就是当一个线程必须等到另一线程执行完毕才能执行时。

join();

join(10000L);等待该线程终止的最长时间为10,

线程设置优先级:

默认为5

getPrority();

setPrority();

虽然Java提供了10个优先级别,但这些优先级别需要操作系统的支持。不同的操作系统的优先级并不相同,而且也不能很好的和Java的10个优先级别对应。所以我们应该使用MAX_PRIORITY、MIN_PRIORITY和NORM_PRIORITY三个静态常量来设定优先级,这样才能保证程序最好的可移植性。

线程守护:

setDaemon(true);

守护线程的好处就是不需要你关心他的结束问题。

线程的通信:

A.借助于Object类的wait()、notify()和notifyAll()实现通信

线程执行wait()后,就放弃了运行资格,处于冻结状态;

线程运行时,内存中会建立一个线程池,冻结状态的线程都存在于线程池中,notify()执行时唤醒的也是线程池中的线程,线程池中有多个线程时唤醒第一个被冻结的线程。

notifyall(), 唤醒线程池中所有线程。

注: (1) wait(), notify(),notifyall()都用在同步里面,因为这3个函数是对持有锁的线程进行操作,而只有同步才有锁,所以要使用在同步中;

(2) wait(),notify(),notifyall(),  在使用时必须标识它们所操作的线程持有的锁,因为等待和唤醒必须是同一锁下的线程;而锁可以是任意对象,所以这3个方法都是Object类中的方法。

B. 使用Condition控制线程通信

jdk1.5中,提供了多线程的升级解决方案为:

(1)将同步synchronized替换为显式的Lock操作;

(2)将Object类中的wait(), notify(),notifyAll()替换成了Condition对象,该对象可以通过Lock锁对象获取;

(3)一个Lock对象上可以绑定多个Condition对象,这样实现了本方线程只唤醒对方线程,而jdk1.5之前,一个同步只能有一个锁,不同的同步

C.使用阻塞队列(BlockingQueue)控制线程通信

BlockingQueue是一个接口,也是Queue的子接口。BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则线程被阻塞;但消费者线程试图从BlockingQueue中取出元素时,如果队列已空,则该线程阻塞。程序的两个线程通过交替向BlockingQueue中放入元素、取出元素,即可很好地控制线程的通信。

BlockingQueue提供如下两个支持阻塞的方法:

(1)put(E e):尝试把Eu元素放如BlockingQueue中,如果该队列的元素已满,则阻塞该线程。

(2)take():尝试从BlockingQueue的头部取出元素,如果该队列的元素已空,则阻塞该线程。

BlockingQueue继承了Queue接口,当然也可以使用Queue接口中的方法,这些方法归纳起来可以分为如下三组:

(1)在队列尾部插入元素,包括add(E e)、offer(E e)、put(E e)方法,当该队列已满时,这三个方法分别会抛出异常、返回false、阻塞队列。

(2)在队列头部删除并返回删除的元素。包括remove()、poll()、和take()方法,当该队列已空时,这三个方法分别会抛出异常、返回false、阻塞队列。

(3)在队列头部取出但不删除元素。包括element()和peek()方法,当队列已空时,这两个方法分别抛出异常、返回false。

线程池:

合理利用线程池会带来三大好处,降低资源消耗,提高响应速度,提高线程的可管理性。

Executor线程池的最大优点是把任务的提交和执行解耦。

死锁:

产生死锁的四个必要条件如下。当下边的四个条件都满足时即产生死锁,即任意一个条件不满足既不会产生死锁。

(1)死锁的四个必要条件

互斥条件:资源不能被共享,只能被同一个进程使用

请求与保持条件:已经得到资源的进程可以申请新的资源

非剥夺条件:已经分配的资源不能从相应的进程中被强制剥夺

循环等待条件:系统中若干进程组成环路,该环路中每个进程都在等待相邻进程占用的资源

(2) 处理死锁的方法

忽略该问题,也即鸵鸟算法。当发生了什么问题时,不管他,直接跳过,无视它;

检测死锁并恢复;

资源进行动态分配;

线程的相关类:

ThreadLocal它并不是一个线程,而是一个可以在每个线程中存储数据的数据存储类,通过它可以在指定的线程中存储数据,数据存储之后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到该线程的数据。 即多个线程通过同一个ThreadLocal获取到的东西是不一样的,就算有的时候出现的结果是一样的(偶然性,两个线程里分别存了两份相同的东西),但他们获取的本质是不同的。使用这个工具类可以简化多线程编程时的并发访问,很简洁的隔离多线程程序的竞争资源。

48.同步和异步

如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。

当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

58.Collection框架要实现比较要实现 comparable/comparator接口。

59.ArrayList和 Vector

两者都实现了List接口(List接口继承了Collection接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,我们可以按照位置取出元素允许重复。

这是和HashSet之类集合的最大不同处,HashSet 之类不能按索引号去检索其中的元素,也不能有重复的。

Vector是线程安全的,ArrayList 是线程不安全的,如果只有一个线程访问会访问到集合那最好用ArrayList,不考虑线程安全,效率高。如果有多个线程会访问到,最好用Vector,不需要我们自己再去考虑编写安全性的代码。

Vector和HashTable是旧的,是java一诞生就提供的, ArrayList和HashMap是java2才有的,线程不安全。

ArrayList与Vector都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加ArrayList与Vector的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector默认增长为原来两倍,而ArrayList的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。ArrayList与Vector都可以设置初始的空间大小,Vector还可以设置增长的空间大小,而ArrayList没有提供设置增长空间的方法。

总结:即Vector增长原来的一倍,ArrayList增加原来的0.5倍。

60.HashMap是HashTable的轻量级实现(非线程安全的实现),他们都完成了Map接口,HashMap允许将null作为键值,HashTable不允许。

HashMap继承陈旧的Dictionary 类,HashTable是java1.2引进的一个Map接口的实现。

61、List 和 Map 区别?

一个是存储单列数据的集合,另一个是存储键和值这样的双列数据的集合,List中存储的数据是有顺序,并且允许重复;Map中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的。

62、List, Set, Map是否继承自Collection接口?

List,Set是,Map不是

63.List 和Set和Map

List和Set都是单列元素的集合,都继承了Collection父类

List有序 允许重复,Set无序,不能重复。Set的add方法有个Boolean的返回值,当已经存在时,存不进去,返回false.

List 按顺序存值,可以用add(2,"a")插队。

并不是把对象本身存进去,而是在集合中用一个索引指向这个对象。

Map双列的集合,put(obj key,obj,value),key不能重复,value可以重复。根据equals比较对等的。

64. ArrayList和Vector都是使用数组方式存储数据,插入元素涉及数组元素移动等内存操作,索引快插入慢。

LinkedList也是线程不安全的,使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。

LinkedList也是线程不安全的,LinkedList提供了一些方法,使得LinkedList可以被当作堆栈和队列来使用。

HashSet 无序

LinkedHashSet 以元素插入的顺序

TreeSet 按升序 存储,访问遍历快。

68. 集合

ArrayList Vector 继承了Collection父类 实现了List接口

HashSet TreeSet 都继承了AbstractSet<>(Collection父类的子类AbstractCollection的子类)的都实现了Set接口,

Properties 继承了HashTable实现了Map接口

HashMap TreeMap HashTable 都实现了map接口

前两者都继承了AbstractMap类,后者继承了Dictionary类。

69.a.equals(b)==true, 在HashSet和HashMap中,他们值相等,hashCode必须相等,如果不是存在这,而是存在ArrayList中就可以不等。