Java面试笔记

站在巨人的肩膀上,好记性不如烂键盘。

All from java面试笔记

本开源书 forked from Java 面试笔记,作者为 DONGChuan,欢迎大家 star 原作者的 repo,以便获得最新更新,谢谢!

说一说Java

sun公司在1995创建。
特点:

  • 面向对象
  • 平台独立
  • 解释性语言
  • 多线程

Java最重要的特点就平台独立,平台独立意味着可以在一个系统编译它然后在另一个系统使用它。

Java为什么是高效的?

因为Java使用Just-In-Time(即时)编译器。
把Java字节码直接转换成可以直接发送给处理器的指令的程序。

列举出2个IDE

eclipse,NetBeans。

面向对象的特征有那些方面?

  • 封装

让变量和访问这个变量的方法放在一起,将一个类中的成员变量全部定义成私有的,只有这个类自己的方法才可以访问到这些成员变量。

  • 抽象

声明方法的存在而不去实现它的类叫做抽象类。

  • 继承

继承是子类自动共享父类数据和方法的机制。是类之间的一种关系,提高了软件的可重用性和可扩展性。

  • 多态

一个方法或者一个对象可以有不同的形式。

JDK JRE JVM

  • 解释它们的区别

JDK

Java Development Kit用作开发,包含JRE,编译器和其他的工具。可以让开发者开发,编译,执行Java应用程序。

JRE

Java运行时环境是将要执行Java虚拟机,可以想象成它是一个容器,JVM是它的内容。

JRE = JVM + Java Packages Classes(like util, math, lang, awt,swing etc)+runtime libraries.

JVM

Java virtual machine(Java虚拟机)是一个可以执行Java编译产生的Java class文件的虚拟机进程,是一个纯的运行环境。

JVM不是平台独立的

Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。

什么是对象?

  • 对象是程序运行时的实体
  • 它的状态存储在变量
  • 行为是通过方法实现的
  • 方法上操作对象的内部状态
  • 方法是对象对对象的通信的主要手段

一个类是由哪些变量构成的?

  • 本地变量
    在方法体,构造体内部定义的变量。
  • 实例变量
    在类里但是不在方法里。
    在类被载入的时候被实例化。
  • 类变量
    在类里但在方法外,加了static关键字,也叫做静态变量。

静态变量和实例变量的区别?

  • 语法上定义区别:静态变量前要加static关键字,而实例变量前则不加。
  • 在程序运行时的区别:
    实例变量属于某个对象的属性,必须创建了实例对象(new一个)其中的实例变量才会被分配空间,才能使用这个实例变量。

静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。

  • 总结:实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。

封装Encapsulation

  • 使一个类的变量私有化private
  • 提供public方法来调用这些变量。所以外部类是进不去的。这些变量被隐藏在类里。只能通过已经定义的public方法调用。

好处:
当我们修改我们的实现的代码时,不会破坏其他调用我们这部分代码的代码。可维护性,灵活性和可扩展。

多态Polymorphism

多态就是指一个变量,一个方法或者一个对象可以有不同的形式。

  • 重载overloading
    就是一个类里有两个或更多的函数,名字相同而他们的参数不同。

  • 覆盖overriding

是发生在子类中!也就是说必须有继承的情况下才有覆盖发生。当你继承父类的方法时,如果感到那个方法不爽,功能要变,就把那个函数在子类中重新实现一遍。

构造器是否可以被覆盖?

构造器不能被继承,所以不能被覆盖,但是可以被重载。

接口Interface

接口是抽象方法的集合。一个类实现一个或多个接口。因此继承了接口的抽象方法。

接口的特点

  • 不能实例化
  • 没有构造体
  • 所有方法都是抽象的(abstract)。同时也是隐式的public static。也就是说声明时,可以省略public static。
  • 只能含有声明为finalstatic的field。

接口和抽象的区别

  • 抽象类可以有构造方法,接口不行
  • 抽象类可以有普通成员变量,接口没有
  • 抽象类可以有非抽象的方法,接口必须全部抽象
  • 抽象类的访问类型都可以,接口只能是public abstract
    一个类可以实现多个接口,但只能继承一个抽象类

基础概念题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
下面哪一项说法是正确的
在一个子类里,一个方法不是 public 就不能重载
覆盖一个方法只需要满足相同的方法名和参数类型
覆盖一个方法必须方法名,参数和返回类型都相同
一个覆盖的方法必须有相同的方法名,参数名和参数类型
答案 3
覆盖函数与被覆盖函数只有函数体不同
下面哪一项说法是错误的
重载函数的函数名必须相同
重载函数必须在参数个数或类型上有所不同
重载函数的返回值必须相同
重载函数的函数体可以不同
答案 3
函数的重载与函数的返回值无关
下面哪一项说法是正确的
静态方法不能被覆盖
静态方法不能被声明称私有
私有方法不能被重载
一个重载的方法在基类中不通过检查不能抛出异常
答案 1

super关键字

  • 调用父类(superclass)的成员或者方法
  • 调用父类的构造函数
  • 调用父类的成员或者方法

如果你的方法复写一个父类成员的方法,可以通过super关键字调用父类的方法。考虑下面的父类:

1
2
3
4
5
public class Superclass{
pubulic void printMethod(){
System.out.println("Printed in Superclass");
}
}

下面一个子类叫做Subclass,覆写了printMethond():

1
2
3
4
5
6
7
8
9
10
11
public class Subclass extends Superclass{
//overrides printMethod in Superclass
public void printMethod(){
super.printMethod();
System.out.println("Printed in Subclass");
}
public static void main(String[] args){
Subclass s = new Subclass{};
s.printMethod();
}
}

输出

1
2
Printed Superclass.
Printed Subclsaa

  • 调用父类的构造函数

使用super关键字调用父类的构造函数,下面的MountainBike类是Bicycle类的子类,它调用了父类的构造方法并加入了自己的初始化代码:

1
2
3
4
public MountainBike(int startHeight,int startCadence,int startSpeed,int startGear){
super(startCadence,startSpeed,StartGear);
seatHeight = startHeight;
}

调用父类的构造体必须放在 第一行
使用:

1
super();

或者:

1
super(parameter list);

通过super(),父类的无参构造体会被调用。通过super(parameter list),父类对应参数的构造体会被调用。

注意:构造体如果没有显式的调用父类的构造体,Java编译器会自动调用父类的无参构造。如果父类没有无参构造,就会报错。

Super程序题

题目一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base{
Base(){
System.out.println(“Base”);
}
}
public class Checket extends Base{
Checket(){
System.out.println(“Checket”);
super();
}
public static void main(String argv[]){
Checket a = new Checket();
}
}

输出是什么? 是 compile time error. super() 必须放在前面.放在前面之后,输出为 Base Checket

题目二

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.Date;
public class Test extends Date{
public static void main(String[] args) {
new Test().test();
}
public void test(){
System.out.println(super.getClass().getName());
}
}

返回的结果是 Test
因为super.getClass().getName() 调用了父类的 getClass() 方法, 返回当前类
如果想得到父类的名称,应该用如下代码:
getClass().getSuperClass().getName()

题目三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public abstract class Car {
String name = “Car”;
public String getName(){
return name;
}
public abstract void demarre();
}
public class B extends Car{
String name = “B”;
public String getName(){
return name;
}
public void demarre() {
System.out.println(getName() + “ demarre”);
}
}
public class C extends B{
String name = “C”;
public String getName(){
return name;
}
public void demarreWithSuper() {
System.out.println(super.getName() + “ demarre”);
}
public void demarreNoSuper() {
System.out.println(getName() + “ demarre”);
}
}
public class D extends B{
public String getName(){
return name;
}
public void demarreNoSuper() {
System.out.println(getName() + “ demarre”);
}
}
public class Test {
public static void main(String[] args) {
B b = new B();
b.demarre();
Car bCar = new B();
bCar.demarre();
C c = new C();
c.demarre(); // c 里并没有定义这个函数
c.demarreWithSuper();
c.demarreNoSuper();
D d = new D();
d.demarre();
transfer(c); // TransferC
transfer((B)c); // TransferB
transfer(d); // TransferB
}
public static void transfer(B b){
System.out.println(“TransferB”);
b.demarre();
}
public static void transfer(C c){
System.out.println(“TransferC”);
c.demarre();
}
}
}

输出是

1
2
3
4
5
6
7
8
9
10
11
12
B demarre
B demarre
C demarre
B demarre
C demarre
B demarre
TransferC
C demarre
TransferB
C demarre
TransferB
B demarre

this程序题

题目一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Tester{
int var;
Tester(double var){this.var = (int)var};
Tester(int var){this(“hello”);
Tester(String s){
this();
System.out.println(s);
}
Tester(){ System.out.println(“good-bye”);}貌似和 this 无关但是很重要
public class Base {
int i;
Base(){
add(1);
System.out.println(i);
}
void add(int v){
i+=v;
System.out.println(i);
}
}
public class MyBase extends Base{
MyBase(){
System.out.println(“MyBase”);
add(2);
}
void add(int v){
System.out.println(“MyBase Add”);
i+=v*2;
System.out.println(i);
}
}
public class Test {
public static void main(String[] args) {
go(new MyBase());
}
static void go(Base b){
b.add(8);
}
}
输出的结果是 22
}
Tester t = new Tester(5) 的输出是什么?
good-bye
hello

题目二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
貌似和 this 无关但是很重要
public class Base {
int i;
Base(){
add(1);
System.out.println(i);
}
void add(int v){
i+=v;
System.out.println(i);
}
}
public class MyBase extends Base{
MyBase(){
System.out.println(“MyBase”);
add(2);
}
void add(int v){
System.out.println(“MyBase Add”);
i+=v*2;
System.out.println(i);
}
}
public class Test {
public static void main(String[] args) {
go(new MyBase());
}
static void go(Base b){
b.add(8);
}
}
输出的结果是 22

子类会首先调用父类的构造函数,在父类的构造函数 Base() 中执行 add() 方法. 但这个 add()方法由于是在新建MyBase对象时调用的. 所以是执行的MyBase中的add方法在Java中,子类的构造过程中,必须 调用其父类的构造函数,是因为有继承关系存在时,子类要把父类的内容继承下来,通过什么手段做到的?这样:当你new一个子类对象的时候,必须首先要new一个父类的对像出来,这个父类对象位于子类对象的内部,所以说,子类对象比父类对象大,子类对象里面包含了一个父类的对象,这是内存中真实的情况.构造方法是new一个对象的时候,必须要调的方法,这是规定,要new父类对象出来,那么肯定要调用其构造方法,所以
第一个规则:子类的构造过程中,必须 调用其父类的构造方法一个类,如果我们不写构造方法,那么编译器会帮我们加上一个默认的构造方法,所谓默认的构造方法,就是没有参数的构造方法,但是如果你自己写了构造方法,那么编译器就不会给你添加了所以有时候当你new一个子类对象的时候,肯定调用了子类的构造方法,但是在子类构造方法中我们并没有显示的调用基类的构造方法,就是没写,如:super(); 并没有这样写,但是 第二个规则:如果子类的构造方法中没有显示的调用基类构造方法,则系统默认调用基类无参数的构造方法.

注意:如果子类的构造方法中既没有显示的调用基类构造方法,而基类中又没有默认无参的构造方法,则编译出错,所以,通常我们需要显示的:super(参数列表),来调用父类有参数的构造函数

抽象abstract

Abstract类

  • 不能被实例化
    Abstract方法
  • 在父类里定义抽象方法,在子类里定义这个具体的方法,所以它是抽象的。

好处
减少复杂度和提高可维护性

abstract相关问题

题目一
什么是抽象类

  • A class with no methods
  • A class with no concrete subclasses
  • A class with at least one undefiend message
  • None of above

答案是第四个,可以定义一个抽象空类:
abstract class emptyAb{}

题目二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class Base{
abstract public void myfunc();
}
public class Abs extends Base{
public static void main(String argv[]){
Abs a = new Abs();
a.amethod();
}
public void amethod(){
System.out.println(“A method”);
}
}

运行的结果:

The compiler will complain errors in Abs class

this()和 super()在构造体里怎么用?

this()在同一个类的构造体被调用,this(“toto”,“tata”,1)相当于调用对应参数的构造体。
super()用来调用父类构造体。

Static关键字

Static关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例的情况下直接被访问。
声明为static的方法有一下几条限制:

  • 仅能调用其他的static方法
  • 只能访问static变量
  • 不能以任何方式引用this或super
  • 不能被覆盖

声明为static的变量实质上就是全局变量 (+final就是全局变量)
当声明一个对象时,并不产生static变量的拷贝,而是该类所有的实例变量共用同一个static变量。
对于静态类,只能用于嵌套类内部类中。

Static相关问题

Static关键字是什么意思?

Static关键字表明一个成员变量或者成员方法可以在没有所属的类的实例的情况下直接被访问。

是否可以override一个static的方法?

不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。

一个static方法内部调用非static方法?

不可以。因为非static方法是要与对象关联在一起的,须创建一个对象的实例后,才可以在该对象上进行方法调用。而static方法被调用时不需要创建对象,可以直接调用,也就是说,当一个static方法被调用时,可能还没有创建任何实例对象。如果从一个static方法中发出对非static方法的调用,那个非static方法是关联到哪个对象上的呢?这个逻辑无法成立。所以,一个static方法内部发出对非static方法的调用。

是否可以在static环境中访问非static变量

同上。

单例模式 Singleton

Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private Singleton() {
// do something
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

多选题注意

  • 一是单例模式的类只提供私有的构造函数
  • 二是类定义中含有一个该类的静态私有对象
  • 三是该类提供一个静态的公有的函数用于创建或获取它本身的静态私有对象

equals() 与 hashcode()

equals

如果需要比较对象的值,就要equal方法。JDK中equal方法的实现:

1
2
3
public boolean equals(Object obj){
return(this==obj);
}

也就是说,默认情况下比较的还是对象的地址,所以如果把对象放入Set中等操作,就要重写equal方法。重写之后的equals()比较的就是对象的内容了。

== 和equal的区别

  • ==比较引用的地址
  • equal比较引用的内容(Object类本身除外)
1
2
3
4
5
6
7
8
9
10
11
12
String obj1 = new String(“xyz”);
String obj2 = new String(“xyz”);
// If String obj2 = obj1, the output will be true
if(obj1 == obj2)
System.out.printlln(“obj1==obj2 is TRUE”);
else
System.out.println(“obj1==obj2 is FALSE”);
// It will print obj1==obj2 is False
// If String obj2 = obj1, the output will be true

默认的, equals() 方法实际上和 “==” 在 object 类里是一样的. 但是这个方法在每一个子类里都会被覆写用来比较引用的内容 (因为每个类都继承了 object 类并覆写了这个方法)

1
2
3
4
5
6
7
8
9
String obj1 = new String(“xyz”);
String obj2 = new String(“xyz”);
if(obj1.equals(obj2))
System.out.printlln(“obj1==obj2 is TRUE”);
else
System.out.println(“obj1==obj2 is FALSE”);
Resultat: obj1==obj2 is TRUE

所有类的基类是哪个类?

java.lang.Object

Java支持多继承吗?

Java不支持多继承。

Path与Classpath

Path和Classpath是操作系统的环境变量

  • Path定义了系统可以在哪里找到可执行文件(.exe)
  • calsspath定义了.class文件的位置

反射机制

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

主要作用

  • 运行时去得类的方法和字段的相关信息
  • 创建某个类的新实例(.newInstance())
  • 取得字段引用直接获取和设置对象字段,无论访问修饰符是什么

用处如下

  • 观察或操作应用程序的运行时行为
  • 调试或测试程序,因为可以直接访问方法、构造函数和成员字段
  • 通过名字调用不知道的方法并使用该信息来创建对象和调用方法

final关键字

final类是不能被继承的,这个类就是最终的了,不需要再被继承,比如很多Java标准库就是final类。
final方法不能被子方法重写,fianl+static变量表示常量

一个.Java源文件是否可以包含多个类

可以的,但只能有一个是public的类而且这个public类必须与文件名一样

&与&&

都可以表示逻辑与and,但是&&具有短路功能,第一个表达式错了,第二个就被忽略。&的表达式是先计算后求与。

除此之外&可以用作位运算。
| 也有类似差异。

int与integer

int是数据类型,integer是int的封装。

int默认值为0,integer默认值为null,所以integer可以用来判断变量是否赋值,即null和0的区别。

integer通过与==比较

1
2
3
4
5
6
7
8
9
10
11
12
Integer a=10;
Integer b=10;
Integer c=new Integer(10);
Integer d=new Integer(10);
System.out.println(a==b);
System.out.println(c==d);
System.out.println(a.equals(b));
System.out.println(c.equals(d));
System.out.println(a.equals(c));

结果:

1
2
3
4
5
true
false
true
true
true

==比较的是对象的引用
当且仅当比较的两个引用指向同一对象才返回true

1
2
3
4
5
6
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a == b);
System.out.println(c == d);

结果:

1
2
true
false

Integer i = XXX
看看Integer的源代码就知道了,其实就是Integer吧-128-127(一个字节的二进制补码)之间的每个值都建立了一个对应的Integer对象,类似一个缓存,由于Integer是不可变类,因此这些缓存的Integer对象可以安全的重复使用。

Integer i= XXX,就是Integer i=Integer.valueOf(XXX),首先判断XXX是否在-128-127之间,如果是直接return已经存在的对象,所以是同一个引用,否则就只能new一个了,那就是不同的引用了。

异常

异常是指Java程序运行时(非编译)所发生的非正常情况或错误。Java使用面向对象的方式来处理异常,它把程序中发生的每个异常也都分别封装到一个对象来表示的,该对象中包含有异常的信息。。Java对异常进行了分类,所有异常的根类为java.lang.Throwable,Throwable下面又派生了两个子类:Error和Exception。

error和exception

Error表示应用程序本身无法克服和恢复的一种严重问题,程序只有死的份。
例如,说内存溢出和线程死锁等系统问题。Exception表示程序还能够克服和恢复的问题,比如一个输入参数不对引起的异常,其中又分为系统异常和普通异常。

Checked异常与Runtime异常

  • Runtime exceptions是runtime阶段碰到的异常,在编译的时候不需要检查,例如:

数组脚本越界,空指针异常,类转换异常。

  • Checked exception是在编译阶段的异常,并且强制检查。

编译器强制checked异常必须try..catch处理或用throws声明继续抛给上层调用方法处理,这就是为什么叫checked异常,而runtime异常可以处理也可以不处理,所以,编译器不强制用try..catch处理或用throws声明,所以Runtime异常也称为unchecked异常。

把对象声明成异常

  • 如果想要一个对象作为一个异常对象被抛出,应该怎么做?

继承Exception类,或者继承exception类里面的子类,这样可以更加具体的表明哪一类异常。

  • 如果我的类已经继承了其他的类,那应该怎么做?

这样其实就没有办法,Java不支持多继承,目前版本的JDK没有相关的接口。

处理异常的方法

  • try catch
  • throws

这两种方法有什么区别:

第一种方法是自己处理异常。
第二种异常是把异常抛给调用这个方法的模块去处理。一般Java的库就是这么处理的。

每一个try都必须有一个catch吗?

不是必须的,至少要有一个catch或者finally块。

try模块里的return

  • 如果在try模块里最后加了个return,finally模块还会执行吗?

是的,finally模块会先执行再return。

  • 如果换成system.exit(0)?

那就不会了,System.exit(0)时。会立马跳出程序。

  • try catch finally的执行顺序

特殊情况就是里面加return。

举个例子理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int getNumber() {
int a = 0;
try {
String s = “t”; ————————(1)
a = Integer.parseInt(s);———–(2)
return a;
} catch (NumberFormatException e) {
a = 1;———————————–(3)
return a;——————————-(4)
} finally {
a = 2;———————————–(5)
}
}

1、程序中标记的代码的执行顺序?
2、改程序的最后返回值(外部调用时)?
程序按顺序从上到下执行到(2),字符”t”转换成整数失败,产生异常并被捕获,
于是对a赋值成1,并将此值作为此方法的返回值(可以这么认为,该方法有一个存放返回值的空间,此时将1放在此处)。由于存在finally块,在返回前将该方法的内部变量a修改成2。
所以程序将按标记的顺序执行,外部调用该方法时得到的结果是1

先执行try或catch里里面的代码,然后再执行finally,再执行try或catch里面的return.

final,finally,finalize的区别

  • final用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
  • finally是异常处理语句结构的一部分,表示总是在执行。
  • finalize是Object类的一个方法,在垃圾收集器执行的时候会被调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。JVM不保证此方法总被调用。

垃圾回收 Gabage Collection

什么是GC?

GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃。Java提供的GC功能可以自动检测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。

垃圾回收器的基本原理是什么?

当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。
通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是“可达的”,哪些对象是”不可达的”。当GC确定一些对象为“不可达”时(比如设置为null),GC就有责任回收这些内存空间。

有什么办法主动通知虚拟机进行垃圾回收?

可以,程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行,这个选择题的时候有考。

heap和stack

Java的内存分为两类:

  • 堆内存heap
  • 栈内存stack

stack是指程序进入一个方法时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量。当这个方法结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。

heap一般用于存放不放在当前方法栈中的那些数据。例如,使用new创建的对象都放在堆里,所以,它不会随方法的结束而消失。方法中的局部变量使用final修饰后,放在堆中,而不是栈。

GC就一定能保证内存不溢出吗?

No。程序员可能创建一个对象,以后一直不再使用这个对象,这个对象却一直被引用,这个对象无用但是却无法被垃圾回收器回收的。

字节流与字符流

  • 字节流继承于InputStream OutputStream
  • 字符流继承于InputStreamReader OutputStreamWriter

字符流使用了缓冲区(buffer),而字节流没有使用缓冲区。
底层设备永远只接受字节数据。
字符是字节通过不同的编码的包装。
字符向字节转换时,要注意编码的问题。

Collection

Collection的子类是List和Set。

ArrayList和Vector

这两个类都实现了List接口(List接口继承了Collection接口)
他们都是有序列集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组。并且其中的数据是允许重复的。
ArrayList和Vector的区别:

  • Vector是线程安全的,也就是线程同步的,而ArrayList是线程不安全的。对于Vector&ArrayList,Hashtable&HashMap,要记住线程安全的问题。记住Vector与HashMap是旧的,是Java一诞生就提供了的。它们是线程安全的,ArrayList与HashMap是Java2时才提供的,它们是线程不安全的。
  • ArrayList与Vector都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加ArrayList与Vector的存储空间。Vector默认增长为原来两倍,而ArrayList的增长策略在文档中没有明确规定。ArrayList与Vector都可以设置初始的空间大小,Vector还可以设置增长的空间大小,而ArrayList没有提供设置增长空间的方法。

**总结:即vector增长原来的一倍,ArrayList增加原来的0.5倍。Vector线程安全,ArrayList不是。

HashMap和Hashtable

Hashtable是基于陈旧的Dictionary类的,HashMap是Java1.2引进的Map接口的一个实现。

Hashtable是线程安全的,也就是说是同步的。而HashMap是线程不安全的,不是同步的。

只有HashMap可以让你将空值NULL作为一个表的条目的key或value。但是HashTable不允许。

HashMap HashTable LinkedHashMap TreeMap

不允许键重复,值可以重复。

HashMap是一个最常用的Map, 它根据键的hashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度.HashMap最多只允许一条记录的键为null,不允许多条记录的值为null.HashMap不支持线程的同步,如果需要同步,可以用Collections.synchronizedMap(HashMap map)方法使HashMap具有同步的能力.

Hashtable与HashMap类似,不同的是:它不允许记录的键或者值为空;它支持线程的同步.

LinkedHashMap保存了记录的插入顺序,在用Iteraor遍历LinkedHashMap时,先得到的记录肯定是先插入的.在遍历的时候会比HashMap慢.有HashMap的全部特性.

TreeMap能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器.当用Iteraor遍历TreeMap时,得到的记录是排过序的.TreeMap的键和值都不能为空.

Collection相关问题

题目一

1
2
3
4
5
6
7
8
题目一
You need to store elements in a collection that guarantees that no duplicates are stored and all elements can be access in nature order, which interface provies that capabiliy?
A. java.util.Map
B. java.util.Collection
C. java.util.List
D. java.util.Set
答案 D

题目二

  • List,Set,Map是否继承自Collection接口,它们有什么区别?

List,Set是,Map不是。

区别:
Set不允许有重复的元素,且没有顺路Set取元素时,没法说取第几个,只能以Iterator接口取得所有的元素,再逐一遍历各个元素.

List表示有先后顺序的集合并且允许重复。

Map与List和Set不同,存储一对key/value,不能存储重复的key.

题目三

1
2
3
4
5
6
7
8
9
10
public static void main(){
Map<String,String> map = new HashMap<String,String>();
map.out(String.valueOf(System.currentTimeMillis())+"a",1);
map.out(String.valueOf(System.currentTimeMillis())+"a",2);
map.out(String.valueOf(System.currentTimeMillis())+"a",3);
for(Map.Entry<String,String> entry : map.entrySet()){
System.out.printf(entry.getValue());
}
}
输出顺序是 123顺序无法确定. Map 中的键是 Set. Set 顺序是随机的.

Multi-Thread 多线程

sleep()和wait()的区别

举个例子:

1
sleep(1000)

会把线程放到一边,直到整整一秒之后才再次启动。

1
wait(1000)

则是把线程放到一边至多一秒。如果碰到notify()或者notifyAll()就会提前启动。
而且wait()方法是在Object类里。而sleep()是在Thread类里。

同步 synchronized

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Synchronized Counter {
private int c = 0;
public synchronized void increment() {
c++;
}
public synchronized void decrement() {
c–;
}
public synchronized int value() {
return c;
}
}

如果 count 是这个类的实例化将有两个效果:

  • 不可能同时调用同一个对象的同一个方法, 防止造成冲突.同一时间只有一个线程可以调用这对象的同步方法.比如在一个账户里同时存钱和转当一个同步方法退出时,账.
  • 它会和随后一个同步方法的调用自动建立happens-before关系. 这保证了所有线程都知道对象的状态改变了.

如何实现muliti-Thread?

  • 继承Thread类
  • 实现Runable接口

Thread与Runable?

实现Runable接口比继承Thread类所具有的优势:

  • 适合多个相同的程序代码的线程去处理同一个资源
  • 可以避免Java中的单继承的限制
  • 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

Transient 关键字

当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。

preemptive scheduling 和 time slicing?

preemptive scheduling,优先级别最高的任务会被执行,除非它进入等待状态或者死了或者一个更高优先权的任务进来.

一个线程的初始状态是什么?

一个线程被创建和开始之后是“Ready”状态。

守护线程daemon Thread

守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分.因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程.反过来说,只要任何非守护线程还在运行,程序就不会终止.

用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了.将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现.

所有的线程都必须实现那个方法?

run方法,不管是继承Thread还是实现Runable接口。

StringBuffer相关问题

题目一

StringBuffer和StringBuilder

  • StringBuilder比StringBuffer快
  • 当需要保证线程安全的时候用StringBuffer
  • StringBuffer是synchroinzed,StringBuilder不是

String类一般被认为是不可改变的。如果需要对一个string做许多修改就需要使用StringBuilder或StringBuffer。
另外需要注意,String类是final类不可以被继承,有时候会在考察final关键字的时候考这个。

序列化 serialization

Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。
将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。
整个过程都是 Java 虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。

类 ObjectInputStream 和 ObjectOutputStream 是高层次的数据流,它们包含反序列化和序列化对象的方法。
Java中实现serialization主要靠两个类:

  • ObjectOutputStream
  • ObjectInputStream

他们是Java IO系统里的OutputStream和InputStream的子类

如何序列化一个对象到一个文件?

要被序列化的实例所对应的类必须实现 Serializable接口。然后你可以把实例传递给ObjectOutputStream,同时ObjectOutputStream也必须连接至fileoutputstream。这样就会把一个对象存储到一个文件里。

必须实现Serializable接口的哪个方法?

Serializable 接口是一个空接口.所以我们不实现它的任何方法.

初始化和清理

构造器初始化

初始化顺序

类内部变量定义的先后顺序决定了其初始化的顺序,并且会在任何方法被调用之前也会得到初始化。对于静态对象与非静态对象:先初始话静态对象,然后是非静态对象。

静态数据的初始化

静态数据只占用一份存储区域,static关键字不能用于局部变量,因为它只能作用于域。如果一个域是静态的基本类型域且未对其进行初始化,那么它就会获得基本类型的标准初值;如果是一个对象引用,则初始化为null.

静态初始化只有在必要时才会进行,且只被初始化一次,即如果不创建相应的对象或是引用相应的静态对象,那么则不会被初始化。

对象创建的过程:

  • 构造器实际上也是静态方法,Java解释器首先查找类路径定位相应class文件。
  • 载入class文件,执行静态初始化,静态初始化只在类对象首次加载的时候进行一次。
  • 使用new创建对象时首先将在堆上为对象分配足够的存储空间。
  • 存储空间清零,故其所有基本类型数据置为默认值。
  • 执行所有定义处的初始化动作。
  • 执行构造器。

Java Data Type -Java数据类型

JVM可以操作的数据类型分为两类:primitive types和 reference types.类型检查通常在编译期完成,不同指令操作数的类型可以通过虚拟机的字节码指令本身确定。

Primitive type

JVM所支持的基本数据类型有:数值类型,布尔类型和returnAddress类型。其中数值类型又可以分为整型和浮点型两种。

  • 整型:byte(8 bit), short(16 bit), int(32 bit), long(64 bit), char(16 bit unsigned)
  • 浮点型:float(32 bit),double(64 bit)
  • 布尔型:boolean 通常用 int 型表示,Oracle 中用 byte 表示
  • returnAddress:一条字节码指令的操作码

Reference type

引用类型分为三种:Class Types, Array Types 和 Interface Types, 这些引用类型的值分别由类实例、数组实例和实现了某个接口的类实例或者数组实例动态创建。引用类型中有一特殊的值null, 引用类型的默认值就是 null.

形式参数传递

基本类型作为形式参数传递不会改变实际参数,引用类型作为形式参数传递会改变实际参数。JDK1.5之后含有基本类型的包装类型,即自动拆装箱的功能,故将基本类型的相应对象作为参数传递时会自动拆箱为基本类型,故也不改变实际参数的值。

Run-Time Data Area-运行时数据区域

JVM运行时会有几个运行时数据区域,如下图所示。
image.png

程序计数器

线程私有内存,保存 当前线程所执行的字节码的行号指示器,这里和计算机组成原理中的计数器不太一样,计组中的PC指的是下一条要执行的指令的地址。JVM中常有多个线程执行,故每条线程都需要有一个独立的程序计数器。
如果线程执行的是Java方法,哪儿计数器记录的就是正在执行的虚拟机字节码指令的地址;如果执行的是 Native 方法,这个计数器则为空。
P.S. 这块内存无OutOfMemoryError

虚拟机栈

线程私有,虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行时会创建一个栈帧,栈帧中保存有局部变量表、操作数栈、动态链接和方法出口。粗略来讲Java内存区分为堆和栈,实际上栈指的往往是虚拟机栈中的局部变量表部分。
局部变量表中存放了编译期可知的各种基本数据类型、对象引用类型和returnAddress类型。方法运行期间局部变量表大小不变。

本地方法栈

和虚拟机类似,不过区别在于虚拟机栈为Java方法(字节码)服务,而本地方法栈为Native方法服务。

堆是被所有线程共享的一块内存区域。一般来说所有的对象实例和数组都要在堆上分配,但一些优化技术导致不一定所有对象实例都在堆上分配。

方法区

各线程共享的一块内存区域,和操作系统中进程中的文本段有些类似,用于存储虚拟机加载的类信息、常量、静态常量和即时编译后的代码数据等。

运行时常量池

这一部分是方法区的一部分,用于保存Class文件中编译期生成的字面值和符号引用。

直接内存

这一部分并不是虚拟机运行时的数据区域,用于Native函数分配堆外内存,提高性能。

J2EE应用的四个部分

  • 客户端层
  • web层(Servlet and JSP)
  • 业务层(JavaBeans)
  • 企业信息系统层

什么是事务

事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所做的所有更改都会被撤销。

什么是servlet

Servlets是服务器端的部件,是纯的Java对象,设计用于多种协议,特别是HTTP。

创建servlet

Servlet在容器中运行时,其实例的创建以及销毁等是由容器进行控制。

Servlet的创建有两种方法。

  • 客户端请求对应的Servlet时,创建Servlet实例。大部分Servlet都是这种Servlet、
  • 通过在web.xml中设置loaf-on-startup来创建servlet实例,这种实例在Web应用启东时,立即创建Servlet实例。

Servlet必须实现什么接口?

Servlet Interface

servlet生命周期

  • 读取Servlet类
  • 创建Servlet实例
  • Web容器调用Servlet的init()方法
  • 响应客户端请求通过Servlet中service()方法中相应的doXXX()方法
  • 调用Servlet的destroy

什么是Spring

Spring是Java EE的一个轻量级的开源框架,使J2EE开发更容易。
通过实现基于POJO的编程模型,Spring的核心design pattern是IOC

什么是Spring的配置文件

Spring的配置文件是一个XML文件,这个文件包含类的或者说bean的信息以及它们是如何配置的。

IOC有什么好处

  • 减少代码
  • 适应于更容易测试

什么是Spring Beans

一个Bean是被实例化,组装,以及由Spring IoC容器管理的对象

Singleton bean 是线程安全吗

不是。

什么叫线程安全
一段代码,同时几个线程同时使用,结果都是正确的,就叫线程安全。
比如我们打开百度知道的首页,全世界很多人都在打开,都是正确的,证明百度知道首页的那段代码是线程安全的。

Spring MVC理解

Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。

View(视图)是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。

Controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

MVC处理过程:对于每一个用户输入的请求,先被控制器接收,并决定由哪个模型来进行处理,然后模型通过业务逻辑层处理用户的请求并返回数据,最后控制器用相应的视图格式化模型返回的数据,并通过显示页面呈现给用户。

谢谢你请我吃糖果!