第一步,慢慢来

![java logo](/assets/blogImg/java%20logo.png)

1.基本概念

1.1为什么需要public static void mian(String[] args)这个方法

该方法为java程序的入口方法,jvm运行时会首先找main()方法。按照必须有public 与static修饰,返回值为void且方法的参数为字符串数组来查找方法的入口地址。

1) 由于public与static没有先后顺序关系,因此下面的定义也是合理的。
static public void main (String[ ] args)
2) 也可以把main()方法定义为final。
public static final void main (String[ ] args)
3) 也可以用synchronized来修饰main()方法。
static public synchronized void main ( String[ ] args)

不管哪种定义方式,都必须保证main()方法的返回值为void,并有static与public关键字 修饰。同时由于main()方法为程序的人口方法,因此不能甩abstract关键字来修饰。
提问:同一个.java文件中是否可以有多个main()方法?
虽然每个类中都可以定义main()方法,但是只有与文件名相同的用public修饰的类中的main()方法才能作为整个程序的入口方法。

例题:Java程序中程序运行人口方法main的签名正确的有( )。
A. public static void main (String[ ] args)
B. public static final void main (String[ ] args)
C. static public void main (String[ ] args)
D. static public synchronized void main (String[ ] args)
E. static public abstract void main (String[ ] args)
答案:A、B、C、D。

1.2如何实现在main()方法执行前输出“Hello world”?

在Java语言中,由于静态块在类被加载时就会被调用,因此可以在main ()方法执行前,利用静态块实现输出“Hello World”的功能.

  public class Test {

static {
System.out.println("静态块中的Hello world!");
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
System.out.println("在main方法中");
}
}

程序运行结果为:
静态块中的Hello world!
在main方法中
由于静态块不管顺序如何,都会在main()方法执行之前执行。所以下面代码输出相同

public class Test {

public static void main(String[] args) {
// TODO 自动生成的方法存根
System.out.println("在main方法中");

}
static {
System.out.println("静态块中的Hello world!");
}
}

1.3Java程序初始化的顺序是怎样的

当实例化对象时,对象所在类的所有成员变量首先要进行初始化,只有 当所有类成员完成初始化后,才会调用对象所在类的构造函数创建对象。
Java程序的初始化一般遵循3个原则(优先级依次递减):
①静态对象(变量)优先于非静态对象(变量)初始化,其中,静态对象(变量)只初始化一次,而非静态对象(变量) 可能会初始化多次。
②父类优先于子类进行初始化。
③按照成员变量的定义顺序进行初始化。 即使变量定义散布于方法定义之中,它们依然在任何方法(包括构造函数)被调用之前先初始化。

Java程序初始化工作可以在许多不同的代码块中来完成(例如静态代码块、构造函数 等),它们执行的顺序如下:父类静态变量>父类静态代码块>子类静态变量>子类静态代码块>父类非静态变量>父类非静态代码块>父类构造函数>子类非静态变量>子类非静态代码块>子类构造函数
例:

	class Base {

static {
System.out.println("Base static block!");
}

{
System.out.println("Base block!");
}

public Base() {
System.out.println("Base constructor!");
}
}

public class Second extends Base{
static {
System.out.println("Second static block!");
}
{
System.out.println("Second block!");
}

public Second() {
System.out.println("Second constructor!");
}

public static void main(String[] args) {
// TODO 自动生成的方法存根
new Second();
}
}

输出结果为:
Base static block!
Second static block!
Base block!
Base constructor!
Second block!
Second constructor!

1.4Java中的作用域有哪些

在Java语言中,作用域是由花括号的位置决定的,它决定了其定义的变量名的可见性与 生命周期。
变量的类型主要有3种:成员变量、静态变量和局部变量。成员变量也有4种作用域。

作用域和可见性 当前类 同一包下 子类 其他包
public v v v v
protected v v v x
defaoult v v x x
private v x x x

需要注意的是,这些修饰符只能修饰成员变量,不能用来修饰局部变量。private与protec­ted 不能用来修饰类(只有public、abstract或final能用来修饰类)。
例题:
下列说法中,正确的是( )
A.实例方法可直接调用超类的实例方法 B.实例方法可直接调用超类的类方法
C.实例方法可直接调用其他类的实例方法 D.实例方法可直接调用本类的类方法

答案:D。当超类的实例方法或类方法为private时,是不能被子类调用的。同理,当其他类的实例方法为private时,也不能被直接调用。

1.5一个Java文件中是否可以定义多个类

一个Java文件中可以定义多个类,但是最多只能有一个类被public修饰,并且这个类的类名与文件名必须相同,若这个文件中没有public的类,则文件名随便是一个类的名字即可。

1.6什么是构造闲数

构造函数是一种特殊的函数,用来在对象实例化时初始化对象的成员变量。在Java语言中,构造函数具有以下特点。

1) 构造函数必须与类的名字相同,并且不能有返回值(返回值也不能为void)
2) 每个类可以有多个构造函数。会有一个默认的无参构造函数
3) 构造函数可以有0个、1个或1个以上的参数。
4) 构造函数总是伴随着new操作一起调用,且不能由程序的编写者直接调用,必须要由系统调用。
5)构造函数的主要作用是完成对象的初始化工作。
6) 构造函数不能被继承,因此,它不能被覆盖,但是构造函数能够被重载,可以使用不同的参数个数或参数类型来定义多个构造函数。
7) 子类可以通过super关键字来显式地调用父类的构造函数,当父类没有提供无参数的构造函数时,子类的构造函数中必须显式地调用父类的构造函数。
8) 当父类和子类都没有定义构造函数时,编译器会为父类生成一个默认的无参数的构造函数,给子类也生成一个默认的无参数的构造函数。此外,默认构造器的修饰符只跟当前类的修饰符有关(例如,如果一个类被定义为public,那么它的构造函数也是public)。

问:普通方法是否可以与构造函数有相同的方法名?
可以。
例如:

public class Test {

public Test() {
// TODO 自动生成的构造函数存根
System.out.println("Construct!");
}

public void Test() {
System.out.println("call Test");
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
Test a = new Test();
a.Test();
}
}

输出结果为:
Construct!
call Test

常见笔试题:

  1. 下列关于构造方法的叙述中,错误的是( )。
    A. Java语言规定构造方法名与类名必须相同
    B. Java语言规定构造方法没有返回值,但不用void声明
    C. Java语言规定构造方法不可以重载
    D. Java语言规定构造方法只能通过new自动调用
    答案:C。可以定义多个构造函数,只要不同的构造函数有不同的参数即可。
  2. 下列说法中,正确的是( )
    A. class中的constructor不可省略
    B. constructor必须与class同名,但方法不能与class同名
    C. constructor在一个对象被new时执行
    D. 一个class 只能定义一个 constructor
    答案:C

1.7为什么Java中有些接口没有任何方法

Java不支持多重继承,即一个类只能有一个父类,为了克服单继承的缺点,Java语言引人了接口这一概念。接口是抽象方法定义的集合(接口中也可以定义一些常量值),是一 种特殊的抽象类。
接口中只包含方法的定义,没有方法的实现。接口中的所有方法都是抽象的。接口中成员的作用域修饰符都是public,接口中的常量值默认使用public static final修饰。 一个类可以实现多个接口,间接实现多继承。
有些接口内部没有声明任何方法,也就是说,实现这些接口的类不需要重写任何方法,这些没有任何方法声明的接口又被叫做标识接口**,标识接口对实现它的类没有任何语义上的要求,它仅仅充当一个标识的作用,用来表明实现它的类属于一个特定的类型。Java类库中已存在的标识接口有Cloneable和Serializable等。

例题:
不能用来修饰interface的有( )
A. private B. public C.protected D.static
答案:A、C、D。

1.8Java中的clone方法有什么作用

Java中的所有类默认都继承自Object类,而Object类中提供了一个clone()方法。这个方法的作用是返回一个Object对象的复制。这个复制函数返回的是一个新的对象而不是一个引用。以下是使用clone()方法的步骤。

1) 实现clone的类首先需要继承Cloneable接口。Cloneable接口实质上是一个标识接口, 没有任何接口方法。
2)在类中重写Object类中的clone()方法。
3)在clone方法中调用super.clone()无论clone类的继承结构是什么,super. clone()都 会直接或间接调用java. lang. Object类的clone()方法。
4)把浅复制的引用指向原型对象新的克隆体。

示例代码:

class Object implements Cloneable{
private int aInt = 0;
public int getInt() {
return aInt;
}
public void setInt() {
aInt = 1;
}

public void changeInt() {
this.aInt = 1;
}

public Object clone() {
Object o = null;
try {
o = (Object)super.clone();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return o;
}
}
public class TestRef {

public static void main(String[] args) {
// TODO 自动生成的方法存根
Object a = new Object();
Object b = a.clone();
b.changeInt();
System.out.println("a:"+a.getInt());
System.out.println("b:"+b.getInt());
}
}

输出结果为:
a:0
b:1

在C++语言中,当开发人员自定义复制构造函数时,会存在浅复制与深复制之分。 Java 语言在重载clone()方法时也存在同样的问题,当类中只有一些基本的数据类型时,采用上述方法就可以了,但是当类中包含了一些对象时,就需要用到深复制了,实现方法是在对对象调用clone ()方法完成复制后,接着对对象中的非基本类型的属性也调clone()方法完成深复制。示例如下:

import java. util. Date;
class Object implements Cloneable{
private Date birth = new Date();

public Date getBirth() {
return birth;
}

public void setBirth(Date birth) {
this.birth = birth;
}

public void changeDate() {
this.birth.setMonth(4);
}

public Object clone() {
Object o = null;
try {
o = (Object) super.clone();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}

//实现深复制
o.birth = (Date)this.getBirth().clone();
return o;
}

}
public class TestRef{

public static void main(String[ ] args) {
Object a = new Object();
Object b = a.clone();
b.changeDate();
System.out.println("a:"+a.getBirth());
System.out.println("b:"+b.getBirth());
}
}

输出结果为:
a:Sat Mar 23 21:52:40 CST 2019
b:Thu May 23 21:52:40 CST 2019

总结:使用时,检查类有无非基本类型(即对象) 的数据成员。若没有,则返回super. clone()即可;若有,确保类中包含的所有非基本类型的成员变量都实现了深复制。
Object o = super, clone( ) ; //先执行浅复制
对每一个对象attr执行以下语句:
o. attr = this. getAttr( ). clone ();
最后返回o。
引申:浅复制和深复制有什么区别?
浅复制( Shallow Clone):被复制对象的所有变量都含有与原来对象相同的值,而所有对 其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所 引用的对象。
深复制( Deep Clone):被复制对象的所有变量 都含有与原来对象相同的值,除去那些引用其他对 象的变量。那些引用其他对象的变量将指向被复制 的新对象,而不再是原有的那些被引用的对象。换言之,深复制把复制的对象所引用的对象都复制了一遍。

对下列类进行复制图解

class Test{
public int i;
public StringBuffer s;
}

clone

1.9什么是反射机制

反射机制提供的功能主要有:得到一个对象所属的类;获取一个类的所有成员
变量和方法;在运行时创建对象;在运行时调用对象的方法。 其实,反射机制非常重要的一个作用就是可以在运行时动态地创建类的对象。如下:

class Base{
public void f() {
System.out.println("Base!");
}
}

class Sub extends Base{
public void f() {
System.out.println("Sub");
}
}

public class Reflect {

public static void main(String[] args) {
// TODO 自动生成的方法存根
try {
//使用反射机制加载类
Class c = Class.forName("Sub");
Base b =(Base) c.newInstance();
b.f();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}

}

输出结果为:
Sub

在反射机制中,class是一个非常重要的类,那么如何才能获取class类呢?总共有如下3 种方法可以获取到class类:

1) class. forName (“类的路径”),如上例所示。
2) 类名.class
3) 实例.getClass( )

例题:Java创建对象的方式有几种?
答案:共有4种创建对象的方法。
1 )通过new语句实例化一个对象。
2) 通过反射机制创建对象。
3) 通过clone()方法创建一个对象。
4) 通过反序列化的方式创建对象。

1.10package有什么作用

package主要有以下两个作用:
第一,提供多层命名空间,解决命名冲突,通 过使用package,使得处于不同package中的类可以存在相同的名字。
第二,对类按功能进行 分类,使项目的组织更加清晰。当幵发一个有非常多的类的项目时,如果不使用package对类 进行分类,而是把所有类都放在一个package下,这样的代码不仅可读性差,而且可维护性也 不好,会严重影响开发效率。

常见笔试题:
下列说法中,正确的是( )。
A. Java中包的主要作用是实现跨平台功能
B. package语句只能放在import语句后面
C.包(package)由一组类(class)和接口(interface)组成
D.可以用#indude关键字来表明来自其他包中的类
答案:C。D应该为import

1.11如何实现类似C语言中函数指针的功能

在C语言中,有一个非常重要的概念——函数指针,其最重要的功能是实现回调函数。所谓回调函数,就是指函数先在某处注册,而它将在稍后某个需要的时候 被调用。回调函数一般用于截获消息、获取系统信息或处理异步事件。
函数指针一般作为函数的参数来使用,开发人员在使用时可以根据自己的需求传递自定义的函数来实现指定的功能,例如,在实现排序算法时,可以通过传递一个函数指针来决定两个数的先后顺序,从而最终决定该算法是按升序还是降序排列
在Java语言中没有指针的概念,可以利用接口与类来实现同样的效果。
具体实现如下:
先定义一个接口,然后在接口中声明 要调用的方法,接着实现这个接口,最后把这个实现类的一个对象作为参数传递给调用程序, 调用程序通过这个参数来调用指定的函数,从而实现回调函数的功能

interface IntCompare{
public int cmp(int a,int b) ;
}

class Cmp1 implements IntCompare{

@Override
public int cmp(int a, int b) {
// TODO 自动生成的方法存根
if (a>b)
return 1;
else if (a<b)
return -1;
else
return 0;
}

}

class Cmp2 implements IntCompare{

@Override
public int cmp(int a, int b) {
// TODO 自动生成的方法存根
if (a>b)
return -1;
else if (a<b)
return 1;
else
return 0;
}

}
public class Compare {

public static void insertSort(int[]a,IntCompare cmp) {
if(a!=null) {
for(int i = 1;i<a.length;i++) {
int temp = a[i];
int j = i;
if(cmp.cmp(a[j-1], temp)==1) {
while(j>=1&&cmp.cmp(a[j-1], temp)==1) {
a[j]=a[j-1];
j--;
}
}
a[j]=temp;
}
}
}

public static void main(String[] args) {
// TODO 自动生成的方法存根
int[]array1 = {7,3,19,40,4,7,1};
insertSort(array1, new Cmp1());
System.out.println("升序排序");
for(int i=0;i<array1.length;i++) {
System.out.print(array1[i]+" ");
}
System.out.println();

int[]array2 = {7,3,19,40,4,7,1};
insertSort(array2, new Cmp2());
System.out.println("降序排序");
for(int i=0;i<array2.length;i++) {
System.out.print(array2[i]+" ");
}
}
}

运行结果为:
升序排序
1 3 4 7 7 19 40
降序排序
40 19 7 7 4 3 1
上例定义了一个用来比较大小的接口 IntCompare,这个接口实际上充当了 C语言中函数指 针的功能,在使用时,开发人员可以根据实际需求传入自定义的类。在上例中分别有两个类 Cmpl和CmP 2都实现了这个接口,分别用来在实现升序排序和降序排序时使用。

2.面向对象技术

2.1面向对象有哪些特征

面向对象的主要特征包括抽象、继承、封装和多态

1) 抽象。抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当 前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面:一是过程抽象;二是数据抽象。
2) 继承。继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确 表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了 原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且派生类可以修改或增加新的方法使之更适合特殊的需要。
3) 封装。封装是指将客观事物抽象成类,每个类对自身的数据和方法实行保护。类可以 把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
4) 多态。多态是指允许不同类的对象对同一消息作出响应。多态包括参数化多态和包含 多态。多态性语言具有灵活、抽象、行为共享、代码共享等优势,很好地解决了应用程序函数同名问题。

2.2什么是继承

通过继承,子类可以使用父类中的一些成员变 量与方法,从而能够提高代码的复用性,提高开发效率。在Java语言中,被继承的类叫基类 (superclass)或父类,继承基类或父类的类叫派生类或子类(subclass)。继承是通过extends 关键字来实现的,使用格式为:class子类名extends父类名。
继承主要有如下几个特性:

1) Java语言不支持多重继承,也就是说,子类至多只能有一个父类,但是可以通过实现 多个接口来达到多重继承的目的。
2) 子类只能继承父类的非私有(public与protected)成员变量与方法。
3) 当子类中定义的成员变量和父类中定义的成员变量同名时,子类中的成员变量会覆盖 父类的成员变量,而不会继承。
4) 当子类中的方法与父类中的方法有相同的函数签名(相同的方法名,相同的参数个数 与类型)时,子类将会覆盖父类的方法,而不会继承。

例题:
列有关继承的说法中,正确的是( )。
A.子类能继承父类的所有方法和状态 B.子类能继承父类的非私有方法和状态
C.子类只能继承父类public方法和状态 D.子类能继承父类的方法,而不是状态
答案:B。

2.3组合和继承有什么区別

组合和继承是面向对象中两种代码复用的方式。组合是指在新类里面创建原有类的对象, 重复利用已有类的功能。继承是面向对象的主要特性之一,它允许设计人员根据其他类的实现 来定义一个类的实现。组合和继承都允许在新的类中设置子对象(subobject),只是组合是显式的,而继承则是隐式的。组合和继承存在着对应关系:组合中的整体类和继承中的子类对应,组合中的局部类和继承中的父类对应。

遵循以下两点原则。

1) 除非两个类之间是“is-a”(继承)的关系,否则不要轻易地使用继承,不要单纯地为了实现代码的重用而使用继承,因为过多地使用继承会破坏代码的可维护性,当父类被修改时,会影响到所有继承自它的子类,从而增加程序的维护难度与成本。
2) 不要仅仅为了实现多态而使用继承,如果类之间没有“is-a”的关系,可以通过实现接口与组合的方式来达到相同的目的。设计模式中的策略模式可以很好地说明这一点,采用接口与组合的方式比采用继承的方式具有更好的可扩展性。

在Java语言中,能使用组合就尽量不要使用继承。

2.4多态的实现机制是什么

多态表示当同一个操作作用在不同对象时,会有不同的语义,从而会产生不同的结果,例如,同样是执行“+”操作,“3+4” 用 来实现整数相加,而 “3” + “4” 却实现了字符串的连接。
多态主要有以下两种表现方式:

1) 方法的重载(overload)重载是指同一个类中有多个同名的方法,但这些方法有着不
同的参数,因此在编译时就可以确定到底调用哪个方法,它是一种编译时多态。
2) 方法的覆盖(override)。子类可以覆盖父类的方法,因此同样的方法会在父类与子类 中有着不同的表现形式。通过方法覆盖实现的多态也可以被称为运行时多态。
示例:

class Base{
public Base() {
g();
}

public void f() {
System.out.println("Base f()");
}
public void g() {
System.out.println("Base g()");
}
}

class Der extends Base{
public void f() {
System.out.println("Der f()");
}

public void g() {
System.out.println("Der g()");
}
}
public class TestPol {

public static void main(String[] args) {
// TODO 自动生成的方法存根
Base b = new Der();
b.f();
b.g();
}
}

运行结果:
Der g()
Der f()
Der g()

由于子类Derived的f()方法和g()方法与父类Base的方法同名,因此Derived的 方法会覆盖Base的方法。在执行Base b = new Derived()语句时,会调用Base类的构造函数, 而在Base的构造函数中,执行了 g()方法,由于Java语言的多态特性,此时会调用子类De­ rived 的 g() 方法, 而非父类Base的g()方法,因此会输出Derived g()。由于实际创建的是 Derived类的对象,后面的方法调用都会调用子类Derived的方法。
此外,只有类中的方法才有多态的概念,类中成员变量没有多态的概念
例题:
Java中提供了哪两种用于多态的机制?
答案:编译时多态和运行时多态。编译时多态是通过方法的重载实现的,运行时多态是通 过方法的覆盖(子类覆盖父类方法)实现的。

2.5重载和覆盖有什么区别

重载与覆盖的区别主要有以下几个方面:

1) 覆盖是子类和父类之间的关系,是垂直关系;重载是同一个类中方法之间的关系,是 水平关系。
2) 覆盖只能由一个方法或只能由一对方法产生关系;重载是多个方法之间的关系。
3) 覆盖要求参数列表相同;重载要求参数列表不同。
4) 覆盖关系中,调用方法体是根据对象的类型(对象对应存储空间类型)来决定;而重 载关系是根据调用时的实参表与形参表来选择方法体的。

例题:如下代码的运行结果是什么?

class Super{
public int f() {
return 1;
}
}

public class SubClass extends Super {

public float f() {
return 2f;
}

public static void main(String[] args) {
// TODO 自动生成的方法存根
Super s = new Super();
System.out.println(s.f());
}
}

答案:编译错误。因为函数是不能以返回值来区分的,虽然父类与子类中的函数有着不同 的返回值,但是它们有着相同的函数名,因此,编译器无法区分。
(注:但是我在eclipse中编译的时候会提示float类型与Super.f()不兼容,强制运行后输出的结果是Super.f()返回的1,因该是进行了某些处理。)

2.6抽象类(abstract class)与接口 (interface)有什么异同

抽象类:
(1) 抽象方法只作声明,而不包含实现,可以看成是没有实现体的虚方法

(2) 抽象类不能被实例化

(3) 抽象类可以但不是必须有抽象属性和抽象方法,但是一旦有了抽象方法,就一定要把这个类声明为抽象类

(4) 具体派生类必须覆盖基类的抽象方法

(5) 抽象派生类可以覆盖基类的抽象方法,也可以不覆盖。如果不覆盖,则其具体派生类必须覆盖它们。

接口:
(1) 接口不能被实例化

(2) 接口只能包含方法声明

(3) 接口的成员包括方法、属性、索引器、事件

(4) 接口中不能包含常量、字段(域)、构造函数、析构函数、静态成员

相同点:
(1) 都可以被继承

(2) 都不能被实例化

(3) 都可以包含方法声明

(4) 派生类必须实现未实现的方法

区别:
(1)抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。

(2)抽象类中可以有普通成员变量,接口中没有普通成员变量(接口中成员变量只能是 public static final 类型)

(3)接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。

(4) 一个类可以实现多个接口,但只能继承一个抽象类。

(5)接口可以被多重实现,抽象类只能被单一继承

(6)如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法

简单点说,接口是一种特殊形式的抽象类,使用接口完全有可能实现与抽象类相同的操作,但一般而言,抽象类多用于在同类事物中有无法具体描述的方法的场景,所以当子类和父 类之间存在有逻辑上的层次结构时,推荐使用抽象类;而接口多用于不同类之间,定义不同类 之间的通信规则,所以当希望支持差别较大的两个或者更多对象之间的特定交互行为时,应该 使用接口。

接口可以继承接口,抽象类可以实现接口,抽象类也可以继承具体类。抽象类也可以有静态的main方法。

例题:
1.下列关于接口的定义中,正确的是( )
A. void melhoda( ) ;B.public double methoda ();
C. public final double methoda( ) ; D.static void methoda (double dl );
E. protected void methoda ( double dl ) ; F. int a;
G. int b = 1 ;

答案:A、B、G。接口中的方法只能用关键字public和abstract来修 饰,因此选项C、D、E都是错误的。接口中的属性默认都为public static final,由于属性被fi­nal 修饰,因此它是常量,常量在定义时就必须初始化,因此F是错误的。

2.下列说法中,正确的是( )。
A.声明抽象方法大括号可有可无 B.声明抽象方法不可写出大括号
C.抽象方法有方法体 D. abstract可修饰属性、方法和类

答案:B。抽象方法不能有方法体,同理也就不能有大括号。abstract只能用来修饰类与方法,不能用来修饰属性。

2.7内部类有哪些

把一个类定义到另外一个类的内部,在类里面的这个类就叫做内部 类,外面的类叫做外部类。这个内部类可以被看作外部类的一个成员(与类的属性和方法类似)。还有一种类被称为顶层(top-level)类,指的是类定义代码不嵌套在其 他类定义中的类。

内部类可以分为很多种,主要有以下4种:静态内部类( static inner class)、成员内部类( member inner class)、局部内部类( local inner class) 和匿名内部类( anonymous inner class)

class outerClass{
static class innerClass{} //静态内部类
}

class outerClass{
class innerClass{}//成员内部类(普通内部类)
}

class outerClass{
public void menberFunction() {
class innerClass{} //局部内部类
}
}
public class MyFrame extends Frame{


public MyFrame() {
addWindowListener(new WindowAdapter() {//匿名内部类
public void windowClosing(WindowEvent e) {
dispose();
System.exit(0);
}
});
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
}
}

1)静态内部类是指被声明为static的内部类,它可以不依赖于外部类实例而被实例化,而通常的内部类需要在外部类实例化后才能实例化。静态内部类不能与外部类有相同的名字,不能访问外部类的普通成员变量,只能访问外部类中的静态成员和静态方法(包括私有类型)。
2)一个静态内部类,如果去掉“static”关键字,就成为成员内部类。成员内部类为非静 态内部类,它可以自由地引用外部类的属性和方法,无论这些属性和方法是静态的还是非 静态的。但是它与一个实例绑定在了一起,不可以定义静态的属性和方法。只有在外部的 类被实例化后,这个内部类才能被实例化。需要注意的是,非静态内部类中不能有静态成员。
3)局部内部类指的是定义在一个代码块内的类,它的作用范围为其所在的代码块,是内部类 中最少使用到的一种类型。局部内部类像局部变量一样,不能被public,protected、private以 及static修饰,只能访问方法中定义为final类型的局部变量。
4)匿名内部类是一种没有类名的内部类,不使用关键字class、extends、implements,没有构造函数,它必须继承(extends)其他类或实现其他接口。匿名内部类的好处是代码更加简洁、 紧凑,但带来的问题是易读性下降。在使用匿名内部类时,需要牢记以下几个原则:

1) 匿名内部类不能有构造函数。
2) 匿名内部类不能定义静态成员、方法和类。
3) 匿名内部类不能是 public、protected、private、static。
4) 只能创建匿名内部类的一个实例。
5) 一个匿名内部类一定是在new的后面,这个匿名类必须继承一个父类或实现一个 接口。
6) 因为匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。

2.8如何获取父类的类名

Java语言提供了获取类名的方法:getClass( ). getName()
示例:

public class getClassName {
public void test() {
System.out.println(this.getClass().getName());
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
new getClassName().test();
}
}

输出结果为:getClassName

但是不能直接通过调用父类的getClass( ). getName ()
(super. getClass( ). getName ())方法来获取父类的类名,主要原因在于Java语言中任何类都继承自Object类,getClass()方法在Object类中被定义为final与native,子类不能覆盖该方法。因此 this. getClass()和 super. getClass()最终都调用的是Object 中的 getClass()方法。

可以通过Java的反射机制,使用getClass( ). getSuperclass( ). getName()
示例:

class Super{
}
public class getClassName extends Super{
public void test() {
System.out.println(this.getClass().getSuperclass().getName());
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
new getClassName().test();
}
}

输出结果为:Super

2.9 this与super有什么区別

this用来指向当前实例对象,它的一个重要的作用就是用来区分对象的成员变量与方法的形参(当一个方法的形参与成员变量的名字相同时,就会覆盖成员变量)。

class Super{
String name;

//正确的写法
public Super(String name) {
this.name = name;
}

//错误的写法
public Super(String name) {
name =name;
}
}

super可以用来访问父类的方法或成员变量。当子类的方法或成员变量与父类有相同名字 时也会覆盖父类的方法或成员变量,要想访问父类的方法或成员变量只能通过super关键字来访问。

class basef{
public void f() {
System.out.println("base:f()");
}
}

class Sub extends basef{
public void f() {
System.out.println("sub:f()");
}
public void subf() {
f();
}
public void basef() {
super.f();
}
}
public class TestSuper {
public static void main(String[] args) {
// TODO 自动生成的方法存根
Sub s = new Sub();
s.subf();
s.basef();
}
}

输出结果为:
sub:f()
base:f()

例题:下面的运行结果是什么?

class Base{
public Base(){
System.out.println("Base!");
}
}

class Sub extends Base{
public Sub() {
System.out.println("Sub!");
super();
}

}
public class TestSuper {

public static void main(String[] args) {
// TODO 自动生成的方法存根
Base b = new Sub();
}

}

答案:编译错误。当子类构造函数需要显示调用父类构造函数时,super()必须为构造函数中的第一句语句。

public Sub() {
super();
System.out.println("Sub!");
}

改后输出结果为:
Base!
Sub!

3.关键字

####3.1 变量命名有哪些规则

在Java语言中,变量名、函数名、数组名统称为标识符,Java语言规定标识符只能由字母( a z , A Z )、数字(0 9)、下画线(_)和$组成,并且标识符的第一个字符必须是字母、下画线或$。此外,标识符也不能包含空白字符(换行符、空格和制表符)。在Java语言中,变量名是区分大小写的,例如Count与count被认为是两 个不同的标识符,而非相同的标识符。

例题:
1.下列不属于Java标识符的是( )。
A. _HelloWorld B. 3Hello World
C. $HelloWorld D. HeUoWorld3
答案:B。

2.下列标识符不合法的有( )。
A. new B. $usdollars
C. 1234 D. car.taxi
答案:A、C、D。

3.2 break,continue以及return有什么区別

1) break用于直接强行跳出当前循环,不再执行剩余代码。当多层循环嵌套,并且break语句出现在嵌套循环中的内层循环时,它将仅仅只是终止了内层循环的执行,而不影响外层循环的执行。
2) continue用于停止当次循环,回到循环起始处,进入下一次循环操作。简单来说,continue只是中断一次循环的执行而已。
3) return语句是一个跳转语句,用来表示从一个方法返回(返回一个值或其他复杂类 型),可以使程序控制返回到调用该方法的地方。

问:break如何跳出多重循环?
可以在多重循环的外面定义一个标识,然后在循环体里使用带有标识的break语句,这样即可跳出多重循环。Java中的标签就是一个紧跟着“:”的标识符,java语言的标签必须放在循环前面才有作用。

public class Break {

public static void main(String[] args) {
// TODO 自动生成的方法存根
out:
for(int i=0;i<5;i++) {
for(int j=0;j<5;j++) {
if(j>=2)
break out;

System.out.println(j);
}
}
System.out.println("break!");
}
}

运行结果为:
0
1
break!
当内部循环执行到j等于2时,程序跳出双重循环,执行System, out.println(“ break”)语句。

3.3 final,finally和finalize有什么区別

1) final用于声明属性、方法和类,分别表示属性不可变、方法不可覆盖和类不可被继承 (不能再派生出新的子类)。
final属性:final指的是引用的不可变性,即它只能指向初始时指向的那个对象,而不关心指向对象内容的变化。被final修饰的变量必须被初始化。
final方法:当一个方:法声明为final时,该方法不允许f任何子类重写这个方法,但子类仍 然可以使用这个方法。
final参数:用来表示这个参数在这个函数内部不允许被修改。
final类:当一个类被声明为fmal时,此类不能被继承,所有方法都不能被重写。一个类不能既被声明为abstract,又被声明为final。

2) finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块, 表示这段语句最终一定被执行。

3) finalize是Object类的一个方法,在垃圾回收器执行时会调用被回收对象的finalize()方法,可以覆盖此方法来实现对其他资源的回收,例如关闭文件等。

例题:JDK中哪些类是不能继承的?
答案:不能继承的类是那些用final关键字修饰的类。一般比较基本的类型或防止扩展类无意间破坏原来方法的实现的类型都应该是final的,在JDK中, String、SlringBuffer等都是基本类型,所以,String、StringBuffer等类是不能继承的。

3.4 assert存什么作用

断言(assert)作为一种软件调试的方法,提供了一种在代码中进行正确性检查的机制, 目前很多开发语言都支持这种机制。它的主要作用是对一个boolean表达式进行检査,一个正确运行的程序必须保证这个boolean表达式的值为true,若boolean表达式的值为false,则说明 程序已经处于一种不正确的状态下,系统需要提供告警信息并且退出程序。
assert 包括两种表达式,分别为 assert expression 1 与 assert expression 1 : expression2,其中, expression1表示一个boolean表达式,expression2表示一个基本类型或者是一个对象,基本类 型包括boolean、char、double、float、 int和long。

assert的应用范围很多,主要包括①检査控制流;②检查输入参数是否有效;③检查函 数结果是否有效;④检查程序不变量。

3.5 static关键字有哪些作用

static关键字主要有两种作用:
第一,为某特定数据类型或对象分配单一的存储空间,而与创建对象的个数无关。
第二,实现某个方法或属性与类而不是对象关联在一起,在不创建对象的情况下就可以通过类来直接调用方法或使用类的属性。
在Java语 言中,static主要有4种使用情况:成员变量、成员方法、代码块和内部类。

java通过static关键字来达到全局的效果,Java类提供了两种类型的变量:用static关键字修饰的静态变量和不用static关键字修饰的实例变量。
静态变量属于类,在内存中只有一个复制(所有实例都指向同一个内存地址),对静态变量的引用有两种方式,分别为“类.静态变量”和“对象.静态变量”
实例变量属于对象,只有对象被创建后,实例变量才会被分配空间,才能被使用,它在内存中存在多个复制。只能用“对象.实例变量”的方式来引用。

public class TestAttribute {

public static int staticInt = 0;
public int nonstaticInt = 0;
public static void main(String[] args) {
// TODO 自动生成的方法存根
TestAttribute t = new TestAttribute();
System.out.println("t.staticInt:"+ t.staticInt);
System.out.println("TestAttribute.staticInt:"+ TestAttribute.staticInt);
System.out.println("t.nonStaticInt:"+ t.nonstaticInt);

System.out.println("对变量都加1");
t.staticInt++;
t.nonstaticInt++;

TestAttribute t2 = new TestAttribute();
System.out.println("t2.staticInt:"+ t2.staticInt);
System.out.println("TestAttribute.staticInt:"+ TestAttribute.staticInt);
System.out.println("t2.nonStaticInt:"+ t2.nonstaticInt);

}
}

输出结果为:
t.staticInt:0
TestAttribute.staticInt:0
t.nonStaticInt:0
对变量都加1
t2.staticInt:1
TestAttribute.staticInt:1
t2.nonStaticInt:0

(2) static成员方法
static方法是类的方法,不需要创建对象就可以被调用,而非static方法是对象的方法,只有对象被创建出来后才可以被使用。
static方法中不能使用this和super关键字,不能调用非static方法,只能访问所属类的静态成员变量和成员方法,因为当static方法被调用时,这个类的对象可能还没被创建,即使已经被创建了,也无法确定调用哪个对象的方法。同理,static方法也不能访问非static类型的 变量。
static一个很重要的用途是实现单例模式。

public class Singleton {
private static Singleton instance = null;

private Singleton() {}

public static Singleton getInstance() {
if(instance==null) {
instance = new Singleton();
}
return instance;
}
}

(3) static代码块
static代码块(静态代码块)在类中是独立于成员变量和成员函数的代码块的。它不在任 何一个方法体内,JVM在加载类时会执行static代码块,如果有多个static代码块,JVM将会按顺序来执行。static代码块经常被用来初始化静态变量。这些static代码块只会被执行一次。

(4) static内部类
static内部类是指被声明为static的内部类,它可以不依赖于外部类实例对象而被实例化, 而通常的内部类需要在外部类实例化后才能实例化。静态内部类不能与外部类有相同的名字, 不能访问外部类的普通成员变量,只能访问外部类中的静态成员和静态方法。

引申:
1.什么是实例变量?什么是局部变量?什么是类变量?什么是final变量?
实例变量:变量归对象所有(只有在实例化对象后才可以)。每当实例化一个对象时,会创建一个副本并初始化,如果没有显示初始化,那么会初始化一个默认值。各个对象中的实例变量互不影响。
局部变量:在方法中定义的变量,在使用前必须初始化。
类变量:用static可修饰的属性、变量归类所有,只要类被加载,这个变量就可以被使用 (类名.变量名)。所有实例化的对象共享类变量。
final变量:表示这个变量为常量,不能被修改。
2.static与final结合使用表示什么意思?
用来修饰成员变量与成员方法, 类似于C/C++语言中的“全局常量”。对于变量,若使用static final修饰,则表示一旦赋值,就不可修改,并且通过类名可以访问。对于方法,若使用static final修饰,则表示该方法不可覆盖,并且可以通过类名直接访问。

例题:下列运行结果是什么?

public class TestS {
public static int testStatic() {
static final int i = 0;
System.out.println(i++);
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
TestS t = new TestS();
t.testStatic();
}
}

A.0 B.1 C.2 D.编译错误

答案:D。在Java语言中,不能在成员函数内部定义static变量。

3.6 使用switch时有哪些注意事项

使用switch (expr)时,expr只能是一个枚举常量(内部 也是由整型或字符类型实现)或一个整数表达式,其中整数表达式可以是基本类型int或其对应的包装类Integer,当然也包括不同的长度整型,例如short。

由于byte、short和char类型的值都能够被隐式地转换为int类型,因此这些类型以及它们对应的包装类型都可以作为switch 的表达式。但是 long、 float、double、String类型不能够隐式地转换为int类型,因此它们不能被用作switch的表达式。

在Java7中,switch 开始支持String类型了。一般必须在case语句结尾添加break语句。

3.7 volatile有什么作用

volatile是一个类型修饰符(type specifier),它是被设计用来修饰被不同线程访问和修改的变量。被volatile类型定义的变量,系统每次用到它时都是直接从对应的内存当中提取,而不会利用缓存。在使用了 volatile修饰成员变量后,所有线程在任何时候所看到变量的值都是相同的。

public class MyThread implements Readable {
private volatile Boolean flag;
public void stop() {
flag = false;
}
public void run() {
while(flag) {

//do
}
}
}

一般情况下volatile不能代替 sychronized()此外,使用volatile会阻止编译器对代码的优化,因此会降低程序的执行效率。所以,除非迫不得已,否则,能不使用volatile就尽量不要使用volatile。

3.8 instanceof有什么作用

instanceof是一个二元运算符,它的作用是判断一个引用类型的变量所指向的对象是否是一个类(或接口、抽象类、父类)的实例,即它左边的对象是否是它右边的类的实例该运算符返回boolean类型的数据。
常见的用法为:result = object instanceof class

public class TestIns {

public static void main(String[] args) {
// TODO 自动生成的方法存根
String s = "Hello";
int[]a = {1,2};
if(s instanceof String)
System.out.println("true");
if(s instanceof Object)
System.out.println("true");
if(a instanceof int[])
System.out.println("true");
}
}

运行结果:
true
true
true

3.9 strictfp有什么作用

关键字strictfp是strict float point的缩写,指的是精确浮点,它用来确保浮点数运算的准确性。(依照IEEE二进制浮点数算术标准)
当一个类被strictfp修饰时,所有方法都会自动被strictfp修饰。因此,strictfp可以保证浮点数运算的精确性,而且在不同的硬件平台上会 有一致的运行结果。

public strictfp class TestStr {
public static void TestStr() {
float f = 0.12365f;
double d = 0.03496421d;
double sum = f+d;
System.out.println(sum);
}

public static void main(String[] args) {
// TODO 自动生成的方法存根
TestStr();
}
}

运行结果:0.15861420949932098

4.基本类型与运算

4.1Java提供了哪些基本数据类型

Java语言一共提供了 8种原始的数据类型(byte, short, int, long, float, double, char, boolean),这些数据类型不是对象,而是Java语言中不同于类的特殊类型,这些基本类型的数据变量在声明之后就会立刻在上被分配内存空间。

以上这些基本类型可以分为如下4种类型:

1) int 长度数据类型:byte (8 bit)、short (16bit),int (32 bit) ,long (64 bit)
2) float长度数据类型:单精度(32bit float),双精度(64 bit double)
3) boolean类型变量的取值:ture、false。
4) char 数据类 型:Unicode 字符(16 bit)

Java语言还提供了对这些原始数据类型的封装类(字符类型Character,布尔类型 Boolean,数值类型 Byte、Short、Integer、Long、Float、Double)
Java语言中,默认声明的小数是double类型的,因此在对float类型的变量进行初始化时需要进行类型转换。float类型的变量有两种初始化方法: float f=l.0f或float f= (float) 1.0。

引申:
1.在Java语言中null值是什么?在内存中null是什么?
null不是一个合法的Object实例,所以编译器并没有为其分配内存,它仅仅用于表明该引用目前没有指向任何对象。
2.如何理解赋值语句String x = null?
它定义了一个变量“x”,x中存放的是String引用,此处为null。

例题:
1.下列表达式中,正确的是( )。
A. byte b = 128 ; B. boolean flag = null;
C. float f = 0. 9239 ; D. long a =2147483648 L;

答案:D。 A中byte能表示的取值范围为[-128,127],B中boolean 的取值只能是true或false,不能为null, C中0. 9239为double类型,需要进行数据类型转换。

2.String是最基本的数据类型吗?
答案:不是。基本数据类型包括 byte、int、char、long、float、double、 boolean 和 short。

3.int和Integer有什么区别?
答案:Java语言提供两种不同的类型,即引用类型和原始类型(或内置类型)。int是Java 语言的原始数据类型,Integer是Java语言为int提供的封装类。Java为每个原始类型提供了封装类。

4.赋值语句float f = 3. 4是否正确?
答案:不正确。数据3. 4默认情况下是double类型,即双精度浮点数,赋值给float类型的变量,会造成精度损失,需要进行强制类型转换,float f= (float) 3. 4或者float f = 3. 4 F写法都是可以的。

4.2什么是不可变类

指当创建了这个类的实例后,就不允许修改它的值了,一个对象一旦被创建出来,在其整个生命周期中,它的成员变量就不能被修改了。类似于常量(const),即只允许别的程序读,不允许别的程序进行修改。

在Java类库中,所有基本类型的包装类都是不可变类,例如Integer、Float等。此外, String也是不可变类。

如何创建一个不可变类。

1) 类中所有成员变量被private所修饰。
2) 类中没有写或者修改成员变量的方法,例如set x x x ,只提供构造函数,一次生成,永不改变。
3) 确保类中所有方法不会被子类覆盖,可以通过把类定义为final或者把类中的方法定义为final来达到这个目的。
4) 如果一个类成员不是不可变量,那么在成员初始化或者使用get方法获取该成员变量时,需要通过clone方法来确保类的不可变性。
5) 如果有必要,可使用覆盖Object类的equals()方法和hashCode()方法。

4.3 值传递与引用传递有哪些区别

(1) 值传递
在方法调用中,实参会把它的值传递给形参,形参只是用实参的值初始化一个临时的存储单元,因此形参与实参虽然有着相同的值,但是却有着不同的存储单元,因此对形参的改变不会影响实参的值。
(2) 引用传递
在方法调用中,传递的是对象(也可以看作是对象的地址),这时形参与实参的对象指向同一块存储单元,因此对形参的修改就会影响实参的值。

public class Test {

public static void changeS(StringBuffer ssl,StringBuffer ssl2 ) {
ssl.append("World");
ssl2=ssl;
}

public static void main(String[] args) {
// TODO 自动生成的方法存根
Integer a = 1;
Integer b = a;
b++;
System.out.println(a);
System.out.println(b);

StringBuffer s1 = new StringBuffer("Hello");
StringBuffer s2 = new StringBuffer("Hello");
changeS(s1, s2);
System.out.println(s1);
System.out.println(s2);
}
}

输出结果为:
1
2
HelloWorld
Hello

例题:
下列说法中,正确的是( )。
A. call by value不会改变实际参数的值
B. call by reference能改变实际参数地址
C. call by reference不能改变实际参数的地址
D. call by reference能改变实际参数的内容

答案:A、C、D。

4.4不同数据类型的转换有哪些规则

当参与运算的两个变量的数据类型不同时,就需要进行隐式的数据类型转换,转换的规则为:从低精度向高精度转换,即优先级满足
*byte < short < char < int < long < float < double *,例如,不同数据类型的值在进行运算时,short类型数据能够自动转为int类型 ,int类 型数据能够自动转换为float类型等 。反之 ,则需要通过强制类型转换来实现。
(1)类型自动转换
低级数据类型可以自动转换为高级数据类型

1) cha类型的数据转换为高级类型(如int, long等),会转换为其对应的ASCII码。
2)byte, char、short 型的数据在参与运算时会自动转换为int型,但当使用“+= ”运 算时,就不会产生类型的转换。
3)基本数据类型与boolean类型是不能相互转换的。

(2)强制类型转换
当需要从高级数据类型转换为低级数据类型时,就需要进行强制类型转换。可能会损失精度
注意事项:
java语言在涉及byte、short和char类型的运算时,首先会把这些类型的变量值强制转换为int类型,然后对int类型的值进行计算,最后得到的值也是int 类型。
“+=”为Java语言规定的运算法,Java编译器会对其进行特殊处理。
例如:
对于short s1 = 1; s1 = s1 + 1; 由于s1+1运算时会自动提升表达式的类型,所以结果是int型,再赋值给short类型s1时,编译器将报告需要强制转换类型的错误。
对于short s1 = 1; s1 += 1;由于 += 是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。

例题:
1.下面程序的运行结果是什么

int i = 1;
if(i)
System.out.println("true");
else
System.out.println("false");

答案:编译错误。因为if条件只接受boolean类型的值(true或false),而i的类型为int , int类型不能被隐式地转换为boolean类型。

2.对于下述代码结果强制类型转换后,变量a和b的值分别为( );

short a = 128 ; 
byte b = ( byte) a

答案: a = 128, b = -1。short类型变量占两个字节,a 对应的二进制为: 00000000 10000000,由于byte只占一个字节,在强制转换为byte时只截取低字节:10000000,因此b 的值为-1。

4.5运算符优先级是什么

单目:单目运算符+ –(负数) ++ – 等
乘除:算数单目运算符* / % + -
为:位移单目运算符<< >>
关系:关系单目运算符> < >= <= == !=
逻辑:逻辑单目运算符&& || & | ^
三目:三目单目运算符A > B ? X : Y
后:无意义,仅仅为了凑字数
赋值:赋值=

例题:

byte a = 5;
int b = 10;
int c = a>>2+b>>2;
System.out.println(c);

答案: 0。由于“+”的优先级比“》”高,因此程序中的表达式等价于a» (2+b ) »2, 相当于a»12»2,因此运行结果为0。

4.6Math类中round、ceil和floor方法的功能各是什么

1) round方法表示四舍五入。实现原理是在原来数字的基础上先增加0.5然后再向下取整,它的返回值类型为int型。Math. round(11.5)的结果为12,Math. round( - 11. 5)的结果为 - 11。

2) ceil方法的功能是向上取整。他的返回值类型是double型

3) floor方法的功能是向下取整。Math.floor(a),就是取小于a的最大的整数值。它的返回值类型与ceil方法一样,也是double

4.7 如何实现无符号数的占移操作

Java提供了两种右移运算符:“>>”和“>>> ”。其中,“>>”被称为有符号右移运算 符,“ >>>”被称为无符号右移运算符,它们的功能是将参与运算的对象对应的二进制数右移指定的位数。二者的不同点在于“>>”在执行右移操作时,若参与运算的数字为正数,则在高位补0;若为负数,则在高位补1。而“ >>>”则不同,无论参与运算的数字为正数或为负数,在执行运算时,都会在高位补0。

4.8 char型变量中是否可以存储一个中文汉字

在Java语言中,默认使用的Unicode编码方式,即每个字符占用两个字节,因此可以用来存储中文。虽然String是由char所组成的,但是它采用了一种更加灵活的方式来存储,即英文占用一个字符,中文占用两个字符。

例题:
在Java语言中,下列关于字符集编码(Character Set Encoding)和国际化(il8n)的叙述, 哪些是正确的?( )
A. 每个中文字符占用2Byte,每个英文字符占用1 Byte
B. 假设数据库中的字符是以GBK编码的,那么现实数据库数据的网页也必须是GBK编码的
C. Java的char类型,以UTF - 16 Big Endian的方式保存一个字符
D.实现国际化应用常用的手段是利用ResourceBundle类

答案:A 、D 。从上面的介绍可以得出A 是正确的,C是错误的。对于B ,数据库与Web页面可以有各自的编码,二者没有必然的关系。对于D ,ResourceBundle是一个资源处理类, 可以经常在国际化应用中使用。

5.字符串与数组

5.1字符串创建与存储的机制是什么

1 )对于 String s1 = new String( “abc”)语句与 String s2 = new String( “abc”)语句,存在两个引用对象s 1、s 2,两个内容相同的字符串对象”a b c ”,它们在内存中的地址是不同的。只要用到new总会生成新的对象。
2)对于String s1 =’’abc”语句与String s2 = ’’abc”语句,在JVM中存在着一个字符串池, 其中保存着很多String对象,并且可以被共享使用,s 1 、s 2引用的是同一个常量池中的对象。

例题:
new String( “abc”)创建了几个对象?
答案:一个或两个。如果常量池中原来有“a b c ”,那么只创建一个对象;如果常量池中 原来没有字符串“abc”,那么就会创建两个对象。

5.2“==’’、 equals 和 hashCode 有什么区别

1) “ ==”运算符用来比较两个变量的值是否相等。

2) equals是Object类提供的方法之一。每一个Java类都继承自Object类,所以每一个对象都具有equals这个方法。Object类中定义的equals(Object)方法是直接使用“==“运算符比较的两个对象,所以在没有覆盖equals( Object)方法的情况下,equals(Object)与“==” 运算符一样,比较的是引用。

3) hashCode()方法是从Object类中继承过来的,它也用来鉴定两个对象是否相等。Object类中的hashCode()方法返回对象在内存中地址转换成的一个int 值,所以如果没有重写hashCode()方法,任何对象的hashCode()方法都是不相等的。在hashmap中,由于key是不可以重复的,它在判断key是否重复时就判断了hashCode()

例题:
1.假设有以下代码 String s = “hello” ; String t = “hello” ; char c [] = {‘h’ ,‘e’ ,‘l’,‘l’,‘o’},下列选项中返回false语句的是:()
A. s. equals(t) B. t. equals( c) C. s == t D. t. equals( new String( “hello”))

答案:B。由于t 与c 分别为字符串类型和数组类型,因此返回值为 false。

2.下面程序的输出结果是什么?
String s = “abc” ;
String s1 = “ab” +“c”;
System, out. println( s == s1);

答案:true,“ab” + “c”在编译器就被转换为“abc”,存放在常量区,因此输出结果为true。

  1. Set里的元素是不能重复的,那么用什么方法来区分是否重复呢?是用“==”还是equals()?它们有什么差别?
    答案:用iterator()方法来区分是否重复。

5.3 String、StringBuffer、 StringBuilder 和 StringTokenizer 什么区別

Java有4个类可以对字符或字符串进行操作,Character、String、StringBuffer和StringTokenizer,其中Character用于单个字符操作,String用于字符串操作,属于不可变类,而StringBuffer也是用于字符串操作,不同之处是StringBuffer属于可变类。
String是不可变类,因此适合在需要被共享的场合中使用,而当一个字符串经常需要被修改时,最好使用StringBuffer来实现。String与StringBuffer的另外一个区别在于当实例化时,String可以用构造函数(new String(“”)),也可以用赋值。StringBuffer 只能使用构造函数(StringBuffer s = new StringBuffer( “ Hello”))

StringBuilder也可以被修饰的字符串,它与StringBuffer类似,都是字符串缓冲区,但StringBuilder不是线程安全的,如果只是在单线程中使用字符串缓冲区,那么StringBuilder的效率会更高些。因此在只有单线程访问时可以使用StringBuilder,当有多个线程访问时,最好使用线程安全的StringBuffer

执行效率StringBuilder(单线程使用)>StringBuffer>String

StringTokenizer是用来分割字符串的工具类。
示例:

import java.util.StringTokenizer;
public class Testl {

public static void main(String[] args) {
// TODO 自动生成的方法存根
StringTokenizer st = new StringTokenizer("Hello World!");
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
}
}

运行结果:
Hello
World!

5.4 Java中数组是不是对象

数组不仅有其自己的属性(例如length属性),也有一些方法可以被调用(例clone方法)。由于对象的特点是封装了一些数据,同时提供了 一些属性和方法,从这个角度来讲,数组是对象。每个数组类型都有其对应的类型,可以通过instanceof来判断数据的类型

public class Testl {
public static void main(String[] args) {
// TODO 自动生成的方法存根
int []a = {1,2};
int [][]b = new int[2][4];
String []s = {"a","b"};

if(a instanceof int[]) {
System.out.println("the type for a is int []");
}
if(b instanceof int[][]) {
System.out.println("the type for b is int [][]");
}
if(s instanceof String[]) {
System.out.println("the type for s is String[]");
}
}
}

运行结果:
the type for a is int []
the type for b is int [][]
the type for s is String[]

5.5 数组的初始化方式有哪几种

一维数组的声明方式为 type arrayName[ ]或 type[ ] arrayName
Java数组在定义时,并不会给数组元素分配存储空间,因此 []中不需要指定数组的长度
分配方法为:
array Name = new type [ arraySize ] ; // arraySize 表示数组的长度

完成数组的声明后,需要对其进行初始化,下面介绍两种初始化方法

1) int[] a= new int[5]; //动态创建了一个包含5个整型值的数组,默认初始化为0
2) int[] a={1,2,3,4,5}; //声明一个数组类型变量并初始化

二维数组的声明与初始化的方 式,二维数组有3种声明的方法:
声明二维数组时,其中[]必须为空。

1) type array Name [][];
2) type [][] arrayName;
3) type [] arrayName[];
array
对二维数组的访问,通过下标。示例:

public class Testl {

public static void main(String[] args) {
// TODO 自动生成的方法存根
int [][]arr = {{1,2},{3,4,5}};
for(int i = 0;i<arr.length;i++) {
for(int j = 0;j<arr[i].length;j++) {
System.out.print(arr[i][j]+" ");
}
}
}
}

运行结果:
1 2 3 4 5

例题:
1.下列数组的定义中,哪3条是正确的?( )
A.public int a [ ] B. static int [ ] a C. public [ ] int a
D. private int a [3] E. private int [3] a [ ] F. public final int [ ] a

答案:A、B、F。

2.下列数组定义及赋值中,错误的是( )。
A. int intArray[];
B. intArray = new int[3] ;intArray[ 1 ] =1; intArray[2] =2; intArray[3] =3;
C. int a[ ] = {1,2,3,4,5} ;
D. int[ ] [ ] a = new int[2] [ ] ;a[0] = new int[3] ;a[ 1 ] = new int[3];

答案:B 。 越界

3.下列说法中,错误的有( )。
A.数组是一种对象 B.数组属于一种原生类
C. int number[ ] = {31,23,33,43, 35,63} D.数组的大小可以任意改变

答案:B、C、D。原生类指未被实例化的类,数组一般指实例化、被分配空间的类,所以不属于原生类。

4.下列语句中,创建了一个数组实例的是( )
A. int[ ] a = new int [ 15 ] ; B. float fa = new float [20];
C. char[ ] ca = “Some String” ; D. int a [ ] [ ] = {4, 5 , 6} {1, 2, 3 } ;

答案:A

5.6 length属性和length ()方法有什么区别

数组提供了 length属性来获取数组的长度。
length()方法是针对字符串而言的,String提供了 length()方法来计算字符串的长度

Java中还有一个计算对象大小的方法——–size()方法, 该方法是针对泛型集合而言的,用于查看泛型中有多少个元素。

6.异常处理

6.1finally块中的代码什么时候被执行

问题描述:try{}里有一个return语句,那么紧跟在这个try后的finally{}中的代码是否会被执行?如果会的话,什么时候被执行,在return之前还是return之后?

finally块里代码一定会被执行。由于程序执行return就意味着结束对当前函数的调用并跳出这个函数体,因此任何语句要执行都只能在return前执行(除非碰到exit函数),因此finally块里的代码也是在return前执行的。
如果try - finally或者catch - finally中都有return,那么finally块中的return语句将会覆盖别处的return语句,最终返回到调用者那里的是finally中return的值。

对于基本类型的数据,在finally块中改变return的值 对返回值没有任何影响,而对引用类型的数据会有影响。


public class Testl {
public static int testFinally() {
int result = 1;
try {
result = 2;
return result;
} catch (Exception e) {
// TODO: handle exception
return 0;
}finally {
result = 3;
System.out.println("finally!");
}
}

public static StringBuffer testFinally2() {
StringBuffer s = new StringBuffer("Hello");
try {
return s;
} catch (Exception e) {
// TODO: handle exception
return null;
}finally {
s.append("World!");
System.out.println("fianlly2!");
}
}

public static void main(String[] args) {
// TODO 自动生成的方法存根
int resultVal = testFinally();
System.out.println(resultVal);
StringBuffer resultRef = testFinally2();
System.out.println(resultRef);

}
}

运行结果:
finally!
2
fianlly2!
HelloWorld!

问:岀现在Java程序中的finally块是不是一定会被执行?
答案:不一定会被执行。
1)当程序在进人try语句块之前就出现异常时,会直接结束,不会执行finally块中的代码
2)当程序在try块中强制退出时(exit(0))也不会去执行finally块中的代码

例题:
下面程序的运行结果是什么?

public class Foo {

public static void main(String[] args) {
// TODO 自动生成的方法存根
try {
return;
}finally {
System.out.println("Finally");
}
}
}

答案:Finally

6.2异常处理的原理是什么

Java语言把异常当作对象来处理,并定义了一个基类(java. lang. Throwable)作为所有异常的父类。在Java API中,已经定义了许多异常类,这些异常类分为Error (错误)和Excep­tion (异常)两大类。
违反语义规则包括两种情况:一种是Java类库内置的语义检查,例如当数组下标越 界时,会引发 IndexOutOfBoundsException,当访问 null 的对象时,会引发 NullPointerException;另一种情况是Java允许开发人员扩展这种语义检查,开发人员可以创建自己的异常并自由选择在何时用throw关键字拋出异常。

例题:
下列异常中,能使用throw抛出的是( )。
A.Rrror B.Event C. Object D. Throwable E. Exception F. RuntimeException

答案:A、D、E、F。其中 Throwable 为异常处理的基类,Error、 Exception 和 RuntimeEx­ ception都是Throwable的子类,因此都能使用throw拋出。

6.3运行时异常和普通异常有什么区別

Error表示程序在运行期间出现了非常严重的错误,并且该错误是不可恢复的,由于这属于JVM层次的严重错误,因此这种错误是会导致程序终止执行的。
Exception表示可恢复的异常,是编译器可以捕捉到的。它包含两种类型:检查异常 (checked exception)和运行时异常( runtime exception)

(1) 检查异常
所有继承自Exception并且不是运行时异常的异常都是检查异常,比如最常见的IO异常和SQL异常。

1) 异常的发生并不会导致程序出错,进行处理后可以继续执行后续的操作,例如,当连接数据库失败后,可以重新连接后进行后续操作。
2) 程序依赖于不可靠的外部条件,例如系统IO。

(2) 运行时异常
编译器没有强制对其进行捕获并处理。如果不对这种异常进行处理,当出现这种异常时,会由JVM来处理。最常见的运行时异常包括NullPointerException (空指针异常)、 ClassCastException (类型转换异常)ArraylndexOutOfBoundsException (数组越界异常)、ArrayStoreException (数组存储异常) 、 BufferOverflowException (缓冲区溢出异常)、ArithmeticExcep-ion (算术异常等

例题:
1.下面程序能否编译通过?如果把ArithmeticException换成IOException呢?

public class ExceptionTest {
public void doSomething()throws ArithmeticException{
System.out.println("test");
}

public static void main(String[] args) {
// TODO 自动生成的方法存根
ExceptionTest et = new ExceptionTest();
et.doSomething();
}
}

答案:能。ArithmeticException属于运行时异常,编译器没有强制对其进行捕获并处理,编译可以通过。IOException属于检查异常,编译器强制去捕获此类型的异常,如果不对异常进行捕获将会有编译错误。(即对et.doSomething进行try catch 包裹)

2.异常包含下列哪些内容?( )
A.程序中的语法错误
B.程序的编译错误
C.程序执行过程中遇到的事先没有预料到的情况
D.程序事先定义好的可能出现的意外情况

答案:C。

3.下列关于异常的说法中,正确的是( )。
A. 一旦岀现异常,程序运行就终止了
B. 如果一个方法申明将拋出某个异常,它就必须真的拋出那个异常
C. 在catch子句中匹配异常是一种精确匹配
D. 可能拋出系统异常的方法是不需要申明异常的
答案:D

7.输入输出流

7.1Java IO 流的实现机制是什么

输人和输出都被称为抽象的流,流可以被看作一组有序的字节集合,即数据在两设备之间的传输。

流的本质是数据传输,根据处理数据类型的不同,流可以分为两大类:字节流和字符流。
字节流以字节(8 bit)为单位,包含两个抽象类: InputStream (输入流)和OutputStream (输 出流)。
字符流以字符(16 bit)为单位,根据码表映射字符,一次可以读多个字节,它包含两个抽象类: Reader (输入流)和Writer (输出流)。

字节流和字符流最主要的区别为:字节流在处理输入输出时不会用到缓存,而字符流用到了缓存。

例题:Java中有几种类型的流?
答案:常见的流有两种,分别为字节流与字符流。其中,字节流继承于InputStream与 OutputStream,字符流继承于Reader与Writer。在java, io包中还有许多其他的流,流的作用主要是为了改善程序性能并且使用方便。

7.2管理文件和目法的类是什么

类(File)来管理文件和文件夹,通过类不仅能够查看文件或目录的属性,而且还可以实现对 文件或目录的创建、删除与重命名等操作

File (Siring pathname) 根据指定的路径创建一个File对象
createNewFile() 若目录或文件存在,则返回false,否则创建文件或文件夹
delete () 删除文件或文件夹
isFile() 判断这个对象表示的是否是文件
isDirectory() 判断这个对象表示的是否是文件夹
listFiles() 若对象代表目录,则返回目录中所有文件的File对象
mkdir() 根据当前对象指定的路径创建目录
exists () 判断对象对应的文件是否存在

例题:
如何列出某个目录下的所有目录和文件?
答案:假设目录“ C : \testDir” 下有两个文件夹(dir1和dir2) 和一个文件filel.txt,实
现代码如下:

import java.io.File;
public class TestFile {

public static void main(String[] args) {
// TODO 自动生成的方法存根
File file = new File("C:\\testDir");
//判断目录是否存在
if(!file.exists()) {
System.out.println("directory is empty!");
return;
}

File[]fileList = file.listFiles();
for(int i = 0;i<fileList.length;i++) {
//判断是否为目录
if(fileList[i].isDirectory()) {
System.out.println("directory is:"+fileList[i]);
}else if (fileList[i].isFile()) {
System.out.println("file is :"+fileList[i]);
}
}
}
}

运行结果:
directory is:C:\testDir\dir1
directory is:C:\testDir\dir2
file is :C:\testDir\file.txt

7.3 Java Socket 是什么

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个双向链路的一端称为一个Socket。Socket也称为套接字,可以用来实现不同虚拟机或不同计算机之间的通信。

Java Socket可以分为两种类型:面向连接的Socket通信协议(TCP, Transmission Control Protocol,传输控制协议)和面向无连接的Socket通信协议(UDP, User Datagram Protocol,用户数据报协议)。任何一个Socket都是由IP地址和端口号唯一确定的。

基于TCP的通信过程如下:首先,Server (服务器端Listen (监听)指定的某个端口(建议使用大于1024的端口)是否有连接请求;其次Client (客户)端向 Server端发岀Connect (连接)请求;最后,Server端向 Client端发回Accept (接受)消息。一个连接就建立起来了,会话随即产生。Server端和Client端都可以通过Send、 Write等方法与对方通信。

Socket的生命周期可以分为3个阶段:打开Socket、使用Socket收发数据和关闭Socket。

例题:
用Socket实现客户端和服务器端的通信,要求客户发送数据后能够回显相同的数据。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

public static void main(String[] args) {
// TODO 自动生成的方法存根
BufferedReader br = null;
PrintWriter pw = null;

try {
ServerSocket serverSocket = new ServerSocket(4000);
Socket socket = serverSocket.accept();
//获取输入流
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//获取输出流
pw = new PrintWriter(socket.getOutputStream(),true);

String s = br.readLine();
pw.println(s);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally {
try {
br.close();
pw.close();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}

}
}
}

Client

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class Client {

public static void main(String[] args) {
// TODO 自动生成的方法存根
BufferedReader br = null;
PrintWriter pw = null;

try {
Socket socket = new Socket("localhost", 4000);
//获取输入流和输出流
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
pw = new PrintWriter(socket.getOutputStream(),true);

//向服务器发送数据
pw.println("客户端发来的请求Hello");

String s = null;
while(true) {
s =br.readLine();
if(s!=null)
break;
}
System.out.println(s);
} catch (Exception e) {
// TODO: handle exception
}finally {
try {
br.close();
pw.close();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}

先启动Server后运行Client,运行结果为:客户端发来的请求Hello

7.4 什么是Java序列化

Java提供了两种对象持久化的方式,分别为序列化和外部序列化。
(1 )序列化(Serialization)
在分布式环境下,当进行远程通信时,无论是何种类型的数据,都会以二进制序列的形式在网络上传送。序列化是一种将对象以一连串的字节描述的过程,用于解决在对对象流进行读写操作时所引发的问题。序列化可以将对象的状态写在流里进行网络传输,或者保存到文件、 数据库等系统里,并在需要时把该流读取出来重新构造一个相同的对象。

要实现序列化的类都必须实现 Serializable 接口,它里面没有包含任何方法。使用一个输出流(例如FileOntpiit- Stream)来构造一个 ObjectOutputStream (对象流)对象,紧接着,使用该对象的 writeObject (Object obj) 方法就可以将obj对象写岀(即保存其状态),要恢复时可以使用其对应的输入流。
序列化有以下两个特点:
1)如果一个类能被序列化,那么它的子类也能够被序列化。
2) 由于static (静态)代表类的成员, transient代表对象的临时数据,被声明为这两种类型的数据成员是不能够被序列化的。

(2)外部序列化
外部序列化与序列化主要的区别在于序列化是内置的API,只需要实现Serializable接口, 开发人员不需要编写任何代码就可以实现对象的序列化,而使用外部序列化时,Extemalizable 接口中的读写方法必须由开发人员来实现。

例题:

import java.io.Serializable;
public class TestSer implements Serializable{
private static int i = 0;
private String word = "";

public static void setI(int i) {
TestSer.i = i;
}
public void setWord(String word) {
this.word = word;
}
}

创建一 个如下方式的 TestSer: TestSer ts= new TestSer( ) ; ts.setWord (“123”);ts. setl(2);将此对象序列化文件,并在另一个JVM中读取文件,进行反序列化,请问此时读出的DataObject对象中的word和i的值分别是

答案:“123”,0。Java在序列化时不会实例化static变量,因此上述代码只实例化了 word,而没有实例化i。在反序列化时只能读取到word的值,i为默认值。

8.Java平台与内存管理

####8.1 为什么说Java是平台独化性语言

保证Java具有平台独立性的机制为“中间码”和“Java虚拟机( Java Virtual Machine, JVM)”。
Java程序被编译后生成了一个“中间码”。不同的硬件平台上会安装有不同的JVM,由JVM来负责把“中间码”翻译成硬件平台能执行的代码。JVM不具有平台独立性,而是与硬件平台相关的。

解释执行过程分三步进行:代码的装入、代码的校验和代码的执行。装入代码的工作由 “类装载器”完成。被装入的代码由字节码校验器进行检查。

Java字节码的执行也分为两种方式:即时编译方式与解释执行方式,即时编译方式指的是解释器先将字节码编译成机器码,然后再执行该机器码。解释执行方式指的是解释器通过每次 解释并执行一小段代码来完成Java字节码程序的所有操作。通常采用的是解释执行方式。

例题:
1.一个Java程序运行从上到下的环境次序是( )。
A.操作系统、Java程序、JRE/JVM、硬件 B. JRE/JVM、Java程序、硬件、操作系统
C. Java程序、JRE/JVM、操作系统、硬件 D. Java程序、操作系统、JRE/JVM,硬件

答案:C

2.下列说法中,正确的是( )。
A.Java程序经编译后会产生机器码 B.Java程序经编译后会产生字节码
C. Java程序经编译后会产生DLL D.以上都不正确

答案:B。 .java文件被javac指令编译为.class后缀的字节码文件,再由JVM执行。

8.2 JVM加载class文件的原理是什么

Java语言是一种具有动态性的解释型语言,类(class)只有被加载到JVM中后才能运行。 这个加载过程是由类加载器来完成的,具体来说,就是由ClassLoader和它的子类来实现的。类加载器本身也是一个类,其实质是把类文件从硬盘读 取到内存中。

类的加载方式分为隐式加载与显式加载两种。
隐式加载指的是程序在使用new等方式创建对象时,会隐式地调用类的加载器把对应的类加载到JVM中。
显式加载指的是通过直接调用class.forName()方法来把所需的类加载到JVM中。

在Java语言中,类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是 保证程序运行的基础类(例如基类)完全加载到JVM中,至于其他类,则在需要时才加载。 在Java语言中,可以把类分为3类:系统类、扩展类和自定义类。对应3种类型的加载器。
Bootstrap Loader -负责加载系统类(jre/lib/rt. jar 的类)
ExtClassLoader -负责加载扩展类(jar/lib/ext/ * . jar 的类〉
AppClassLoader -负责加载应用类 (classpath指定的目录或jar中的类)

加载通过委托的方式实现的。当有类需要被加载时,类加载器会请求父类来完成这个载入工作,父类会使用其自己的搜索路径来搜索需要被载入的类,如果搜索不到,才会由子类按照其搜索路径来搜索待加载的类。

public class TestLoader {

public static void main(String[] args)throws Exception {
// TODO 自动生成的方法存根
//调用class加载器
ClassLoader clApp = TestLoader.class.getClassLoader();
System.out.println(clApp);
//调用上一层Class加载器
ClassLoader clExt = clApp.getParent();
System.out.println(clExt);
//调用根部Class加载器
ClassLoader clBoot = clExt.getParent();
System.out.println(clBoot);
}
}

运行结果:
sun.misc.Launcher$AppClassLoader@4e25154f
sun.misc.Launcher$ExtClassLoader@33909752
null

可以看出,TestLoader类是由AppClassLoader来加载的。另外,由于Bootstrap Loade是用C++语言来实现的,因此,在Java语言中是看不到它的,所以此时程序会输岀null。

类加载的主要步骤分为以下3步:
1)装载。根据查找路径找到相对应的class文件,然后导入。
2)链接。链接又可以分为3个小的步骤,具体如下。
①检查。检查待加载的class文件的正确性。
②准备。给类中的静态变量分配存储空间。
③解析。将符号引用转换成直接引用(这一步是可选的)。
3)初始化。对静态变量和静态代码块执行初始化工作。

8.3什么是GC

垃圾回收(Garbage Collection, GC),主要作用是回收程序中不再使用的内存。
Java提供了 垃圾回收器来自动检测对象的作用域,可自动地把不再被使用的存储空间释放掉。具体而言, 垃圾回收器要负责完成3项任务:分配内存、确保被引用对象的内存不被错误地回收以及回收再被引用的对象的内存空间。

垃圾回收器使用有向图来记录和管理堆内存中的所有对象**,通过这个有向图就可以识别哪些对象是“可达的”(有引用变量引用它就是“可达的”),哪些对象是“不可达的”(没有引用变量引用它就是不可达的),所有“不可达”对象都是可被垃圾回收的。
例如:

public class Test(){
public static void main(String[] args) {
Integer i1 = new Integer(1);
Integer i2 = new Integer(2);
i2 = i1;
}
}

上述代码在执行到i2=il后,内存的引用关系如图所示。
ref
垃圾回收器正在进行垃圾回收操作,在遍历上述有向图时,资源2所占的内存是不可达的,垃圾回收器就会认为这块内存已经不会再被使用了,因此就会回收该块内存空间。

例题:
1.现有如下代码。

public Object m() {
Object o = new Float(3.14F);
Object[] oa = new Object[1];
oa[0] = o;
o = null;
oa[0] = null;
print 'return 0' ;
}

当Float对象在第2行被创建后,什么时候能够被垃圾回收?

答案:当oa[0] = null;以后不再有对象引用Float对象,因此能够被垃圾回收。

2.下列关于垃圾回收的说法中,正确的是( )。
A. 一旦一个对象成为垃圾,就立刻被回收掉
B. 对象空间被回收掉之后,会执行该对象的fmalize方法
C. finalize方法和C++的析构函数完全是一回事情
D. 一个对象成为垃圾是因为不再有引用指着它,但是线程并非如此

答案:D。成为垃圾的对象,只有在下次垃圾回收器运行时才会被回收,而不是马上被清 理,因此选项A错误。finalize方法是在对象空间被回收前调用的,因此选项B错误。在C+ + 语言中,调用了析构函数后,对象一定会被销毁,而Java语言调用了 fmalize方法,垃圾却不 一定会被回收,因此finalize方法与C++的析构函数是不同的,所以选项C也不正确。对于 D,当一个对象不再被引用后就成为垃圾可以被回收,但是线程就算没有被引用也可以独立运 行的,因此与对象不同。所以正确答案为D。

3.是否可以主动通知JVM进行垃圾回收?

答案:由于垃圾回收器的存在,开发人员不能实时地调用垃圾回收器对某个对象或所有对象进行垃圾回收。可以通过调用System. gc()方法来“通知”垃圾回收器运行,当然,JVM也并不会保证垃圾回收器马上就会运行。由于System. gc()方法的执行会停止所有响应(不推荐频繁使用)。

8.4Java是否存在内存泄露问题

内存泄露是指一个不再被程序使用的对象或变量还在内存中占有存储空间。
内存泄露主要有两种情况:
一是在堆中申请的空间没有被释放;(垃圾回收机制的引入有效地解决)
二是对象已不再被使用,但还仍然在内存中保留着。

Java语言中的内存泄露主要指的是第二种情况。例如:

Vector v = new Vector(10);
for(int i = 1;i<10;i++) {
Object o = new Object();
v.add(o);
}

上述循环中,不断创建新的对象加到Vecto 象中,当退出循环后,o的作用域将会结束,但是由于v在使用这些对象,因此垃圾回收器无法将其回收,此时就造成了内存泄露。只有将这些对象从Vector中删除才能释放创建的这些对象。

在Java语言中,容易引起内存泄露的原因很多,主要有以下几个方面的内容:

1) 静态集合类,例如HashMap和Vector。
2) 各种连接,例如数据库连接、网络联接以及0连接等。
3) 监听器。
4) 变量不合理的作用域。
5)单例模式可能会造成内存泄露

8.5Java中的堆和栈有什么区别

堆与栈都是内存中存放数据的地方。变量分为基本数据类型和引用类型。

栈:
基本数据类型的变量(例如int、short、long、byte、float、double、boolean以及char等)以及对象的引用变量,其内存都分配在栈上,变量出了作用域就会自动释放。

堆:引用类型的变量, 其内存分配在堆上或者常量池(例如字符串常量),需要通过new等方式进行创建。

从堆和栈的功能以及作用来比较,堆主要用来存放对象的,栈主要是用来执行程序的。相较于堆,栈的存取速度更快,但栈的大小和生存期必须是确定的,因此缺乏一定的灵活性。而堆却可以在运行时动态地分配内存,生存期不用提前告诉编译器,但这也导致了其存取速度的缓慢。

9.容器

9.1Java Collections框架是什么

Java Collections框架中包含了大量集合接口以及这些接口的实现类和操作它们的算(例 如排序、查找、反转、替换、复制、取最小元素、取最大元素等)。
主要提供了 List (列表) 、 Queue (队列) 、 Set (集合) 、 Stack (栈)和Map (映射表,用于存放键值对) 等数据结构。其中,List、Queue、Set、Stack都继承Collection接口。
下面分别介绍Set、List和Map3个接口。

1) Set表示数学意义上的集合概念。其最主要的特点是集合中的元素不能重复,因此存入Set的每个元素都必须定义equals()方法来确保对象的唯一性。该接口有两个实现类:HashSet和TreeSet。其中TreeSet实现了 SortedSet接口,因此TreeSet容器中的元素是有序的。

2) List又称为有序的Collection。它按对象进入的顺序保存对象,所以它能对列表中的每 个元素的插入和删除位置进行精确的控制。同时,它可以保存重复的对象。LinkedList、ArrayList 和 Vector都实现了List 接口。

3) Map提供了一个从键映射到值的数据结构。它用于保存键值对,其中值可以重复,但 键是唯一的,不能重复。Java类库中有多个实现该接口的类:HashMap、TreeMap、Linked- HashMap、WeakHashMap和IdentityHashMap。虽然它们都实现了相同的接口,但执行效率却不是完全相同的。具体而言,HashMap是基于散列表实现的,采用对象的HashCode可以进行快速查询。LinkedHashMap采用列表来维护内部的顺序。TreeMap基于红黑树的数据结构来实现的,内部元素是按需排列的。

例题:
下面哪种创建Map集合的方式是正确的?( )
A. Map m = new Map()
B. Map m = new Map( init capacity, increment capacity)
C. Map m = new Map( new Collection ())
D. Map是接口,所以不能实例化

答案:D。由于Map是一个接口,因此不能直接实例化Map的对象,但是可以实例化实现 Map接口的类的对象,例如Map m = new HashMap()。

9.2 什么是迭代器

迭代器(Itemtor)是一个对象,它的工作是遍历并选择序列中的对象,它提供了一种访 问一个容器(container)对象中的各个元素,而又不必暴露该对象内部细节的方法。
迭代器的使用主要有以下3个方面的注意事项:
1 )使用容器的iterator()方法返回一个Iterator,然后通过Iterator的next()方法返回第一 个元素。
2) 使用Iterator的hasNext()方法判断容器中是否还有元素,如果有,可以使用next()方 法获取下一个元素。
3)可以通过remove()方法删除迭代器返回的元素。
Listlterator只存在于List中,支持在迭代期间向List中添加 或删除元素,并且可以在List中双向滚动。

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class IteratorTest {
public static void main(String[] args) {
// TODO 自动生成的方法存根
List<String> ll = new LinkedList<String>();
ll.add("first");
ll.add("second");
ll.add("third");
ll.add("fourth");

for(Iterator<String>iter=ll.iterator();iter.hasNext();) {
String str = (String)iter.next();
System.out.println(str);
}
}
}

运行结果:
first
second
third
fourth

Iterator与Listiterator有什么区别?
Iterator只能正向遍历集合,适用于获取移除元素。Listlerator继承自Iterator,专门针对 List,可以从两个方向来遍历List,同时支持元素的修改。

9.3ArrayList,Vector和LinkedList有什么区别

ArrayList、Vector、LinkedList类均在java.util包中,均为可伸缩数组,即可以动态改变长度的数组。

ArrayList和Vector都是基于存储元素的Object[ ] array来实现的,它们会在内存中开辟一 块连续的空间来存储,由于数据存储是连续的,因此,它们支持用序号(下标)来访问元素, 同时索引数据的速度比较快。但是在插人元素时需要移动容器中的元素,所以对数据的插入操作执行得比较慢。
ArrayList与Vector最大的区别就是synchronization (同步)的使用,没有一个ArrayList的 方法是同步的,而Vector的绝大多数方法(例如add、insert、remove、set、equals、 hashcode 等)都是直接或者间接同步的,所以Vector是线程安全的,ArrayList不是线程安全的。
LinkedList是釆用双向列表来实现的,对数据的索引需要从列表头开始遍历,因此用于随 机访问则效率比较低,但是插入元素时不需要对数据进行移动,因此插人效率较高。同时, LinkedList是非线程安全的容器。

例题:
1.若线性表最常用的操作是存取第i个元素及其前趋的值,则采用( )存储方式节省时间。
A.单链表 B.双链表 C.单循环链表 D.顺序表

答案:D。顺序适合在随机访问的场合使用,访问时间复杂度为0(1),而列表的随机访问操作的时间复杂度为0( n)。

2.对于import java, util包,下列说法中,错误的是( )。
A. Vector 类属于 java* util 包 B. Vector 类放在 ./java/util/目录下
C. Vector类放在java, util文件中 D. Vector类是Sun公司的产品

答案:C。

9.4HashMap、HashTable、 TreeMap和WeakHashMap哪些区別

Java为数据结构中的映射定义了一个接口 javautil.Map,它包括3个实现类:*HashMap、 HashTable和TreeMap。 **
Map是用来存储键值对的数据结构,在数组中通过数组下标来对其内 容索引的,而在Map中,则是通过对象来进行索引,用来索引的对象叫做key,其对应的对象 叫做value。

HashMap是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取 它的值,具有很快的访问速度。
HashMap与HashTable都采用了hash法进行索引,他们区别如下:

1)HashMap是HashTable的轻量级实现(非线程安全的实现),它们都完成了 Map接口, 主要区别在于HashMap允许空(null)键值(key)(但需要注意,最多只允许一条记录的键 为null,不允许多条记录的值为null),而HashTable不允许。

2)HashMap把HashTable的contains方法去掉了,改成 containsvalue,containsKey,因为contains方法容易让人引起误解。HashTable继承自Dictionary类,而HashMap是Java 1. 2引入的Map interface的一个实现。

3)HashTable的方法是线程安全的,而HashMap不支持线程的同步,所以它不是线程安全的。在多个线程访问HashTable时,不需要开发人员对它进行同步,HashMap开发人员必须提供额外的同步机制。

4)HashTable使用Enumeration, HashMap使用Iterator。

5) HashTable和HashMap采用的hash/rehash算法都几乎一样,所以性能不会有很大的
差异。

6) 在HashTable中,hash数组默认大小是11,增加的方式是old x2 + 1。在HashMap中, hash数组的默认大小是16,而且一定是2的指数。

7) hash值的使用不同,HashTable直接使用对象的hashCode。

HashMap里面存入的键值对在取岀时没有固定的顺序,是随机的。
TreeMap实现了SortMap接口,能够把它保存的记录根据键排序,因此,取出来的是排序后的键值对,如果需要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。

Weak Hash Map与HashMap类似,二者的不同之处在于WeakHashMap中key采用的是“弱引用”的方式,只要WeakHashMap中的key不再被外部引用,它就可以被垃圾回收器回收。 而HashMap中key采用的是“强引用的方式”,当HashMap中的key没有被外部引用时,只有在这个key从HashMap中删除后,才可以被垃圾回收器回收。

例题:
1.在HashTable上下文中,同步指的是什么?

答案:同步意味着在一个时间点只能有一个线程可以修改hash表,任何线程在执行HashTable的更新操作前都需要获取对象锁,其他线程则等待锁的释放。

2.如何实现HashMap的同步?
答案:HashMap可以通过Map m = Collections. synchronizedMap( new HashMap())来达到同步的效果。具体而言,该方法返回一个同步的Map,该Map封装了底层的HashMap的所有方法,使得底层的HashMap即使是在多线程的环境中也是安全的。

9.5用自定义类作为HashMap或HashTable的key需要注意哪些问题

开发者在使用自定义类作为HashMap的key时,需要注意以下几个问题:

1)如果想根据对象的相关属性来自定义对象是否相等的逻辑,此时就需要重写equals()
方法,一旦重写了equals()方法,那么就必须重写hashCode()方法。
2)当自定义类的多项作为HaShMap(HaShTable)的key时,最好把这个类设计为不可
变类。
3)从HashMap的工作原理可以看出,如果两个对象相等,那么这两个对象有着相同的 hashCode,反之则不成立。

9.6 Collection和 Collections有什么区别

Collection是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。实现该 接口的类主要有List和Set,该接口的设计目标是为各种具体的集合提供最大化的统一的操作方式。
Collections是针对集合类的一个包装类,它提供一系列静态方法以实现对各种集合的搜索、排序、线程安全化等操作,其中大多数方法都是用来处理线性表。Collections类不能实例化,如同一个工具类,服务于Collection框架。若在使用Collections类的方法时,对应的collec­tion的对象为null, 则这些方法都会抛出NullPointerException。
使用Collections的示例:

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class CollectionsTest {
public static void main(String[] args) {
// TODO 自动生成的方法存根
List<Integer>list = new LinkedList<>();
int array[] = {1,7,3,2};
for(int i = 0;i<array.length;i++) {
list.add(new Integer(array[i]));
}
Collections.sort(list);
for(int i = 0;i<array.length;i++) {
System.out.println(list.get(i));
}
}
}

运行结果:
1
2
3
7

10.多线程

10.1 什么是线程?它与进程有什么区别?为什么要使用多线程

线程是指程序在执行过程中,能够执行程序代码的一个执行单元。java中线程有4种状态:运行、就绪、挂起和结束。
进程是指一段正在执行的程序。

好处:

1) 使用多线程可以减少程序的响应时间。
2) 与进程相比,线程的创建和切换开销更小。
3) 多CPU或多核计算机本身就具有执行多线程的能力,如果使用单个线程,将无法重复 利用计算机资源,造成资源的巨大浪费。
4) 使用多线程能简化程序的结构,使程序便于理解和维护。一个非常复杂的进程可以分 成多个线程来执行。

10.2 同步和异步有什么区别

当多个线程需要访问同一个资源时,需要以某种顺序来确保该资源在某一时刻只能被一个线程使用,否则,程序的运行结果将会是不可预料的,在这种情况下就必须对数据进行同步,例如多个线程同时对同一数据进行写操作,即当线程A需要使用某个资源时,如果这个资源正在被线程B使用,同步机制就会让线程A—直等待下去,直到线程B结束对该资源的使用后,线程A才能使用这个资源。
要想实现同步操作,必须要获得每一个线程对象的锁。获得它可以保证在同一时刻只有一个线程能够进入临界区(访问互斥资源的代码块),并且在这个锁被释放之前,其他线程就不能再进入这个临界区。如果还有其他线程想要获得该对象的锁,只能进入等待队列等待。只有当拥有该对象锁的线程退出临界区时,锁才会被释放,等待队列中优先级最高的线程才能获得该锁,从而进入共享代码区。
实现同步的方式有两种:一 种是利用同步代码块来实现同步;另一种是利用同步方法来实现同步。

异步与非阻塞类似,由于每个线程都包含了运行时自身所需要的数据或方法,因此,在进行输入输出处理时,不必关心其他线程的状态或行为,也不必等到输人输岀处理完毕才返回。 当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,异步能够提高程序的效率。

10.3 如何实现Java多线程

(1)继承Thread类,重写run()方法
Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()方法。调用start()方法后并不是立即执行多线程代码,而是使得该线程变为可运行态(Riirmable),什么时候运行多线程代码是由操作系统决定的。
示例:

class Mythread extends Thread {
public void run() {
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread()+"-->"+i);
}
}
}
public class TestThread{
public static void main(String[] args) {
// TODO 自动生成的方法存根
Mythread thread = new Mythread();
//启动线程
thread.start();
}
}

输出结果为:
Thread[Thread-0,5,main]–>0
Thread[Thread-0,5,main]–>1
Thread[Thread-0,5,main]–>2
Thread[Thread-0,5,main]–>3
Thread[Thread-0,5,main]–>4
Thread[Thread-0,5,main]–>5
Thread[Thread-0,5,main]–>6
Thread[Thread-0,5,main]–>7
Thread[Thread-0,5,main]–>8
Thread[Thread-0,5,main]–>9

(2)实现Runnable接口,并实现该接口的run()方法

1) 自定义类并实现Runnable接口,实现run()方法。
2) 创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象。
3) 调用 Thread 的 start()方法。

class Task implements Runnable {
public void run() {
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread()+"-->"+i);
}
}
}

public class TestThread{
public static void main(String[] args) {
// TODO 自动生成的方法存根
Thread thread = new Thread(new Task());
//启动线程
thread.start();
}
}

运行结果:
Thread[Thread-0,5,main]–>0
Thread[Thread-0,5,main]–>1
Thread[Thread-0,5,main]–>2
Thread[Thread-0,5,main]–>3
Thread[Thread-0,5,main]–>4
Thread[Thread-0,5,main]–>5
Thread[Thread-0,5,main]–>6
Thread[Thread-0,5,main]–>7
Thread[Thread-0,5,main]–>8
Thread[Thread-0,5,main]–>9

(3)实现Callable接口,重写call()方法
Callable接口实际是属于Executor框架中的功能类,Callable接口与Runnable接口的功能 类似,但提供了比Runnable更强大的功能,主要表现为以下3点:

1) Callable可以在任务结束后提供一个返回值,Rurmable无法提供这个功能。
2) Callable中的call()方法可以抛岀异常,而Rumiable的run()方法不能抛出异常。
3) 运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供了检查计算是否完成的方法。

引申:一个类是否可以同时继承Thread与实现Runnable接口?
答案:可以。

public class TestThread extends Thread implements Runnable{

public void run() {
System.out.println("Run!");
}

public static void main(String[] args) {
// TODO 自动生成的方法存根
Thread thread = new Thread(new TestThread());
thread.start();
}
}

运行结果:Run!

10.4 run()方法与start()方法有什么区別

系统通过调用线程类的Start()方法来启动一个线程,此时该线程处于就绪状态, 而非运行状态,也就意味着这个线程可以被JVM来调度执行。在调度过程中,JVM通过调用线程类的run()方法来完成实际的操作,当run()方法结束后,此线程就会终止。

如果直接调用线程类的run()方法,这会被当作一个普通的函数调用,程序中仍然只有主线程这一个线程,也就是说,start方法()能够异步地调用run()方法,但是直接调用run()方法却是同步的,因此也就无法达到多线程的目的。

class ThreadDemo extends Thread{
public void run() {
System.out.println("Demo:begin");
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println("Demo:end");
}
}
public class TestThread{
public static void test1() {
System.out.println("Test1:begin");
Thread t1 = new ThreadDemo();
t1.start();
System.out.println("Test1:end");
}
public static void test2() {
System.out.println("Test2:begin");
Thread t2 = new ThreadDemo();
t2.start();
System.out.println("Test2:end");
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
test1();
try {
Thread.sleep(5000);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println();
test2();
}
}

运行结果:
Test1:begin
Test1:end
Demo:begin
Demo:end

Test2:begin
Demo:begin
Demo:end
Test2:end

从test1的运行结果可以看出线程t1是在test1方法结束后才执行的System.out.println(“Test1:end”);语句不需要等待t1.start()运行结果就可以执行,因此,在test1中调用start()方法是异步的,main线程与tl线程是异步执行的。

从test2的运行结果可以看岀,调用t2.run()是同步的调用方法,因为System.out.println(“Test2:end”);只有等t2. run()调用结束后才能执行。

10.5多线程同步的实现方法有哪些

Java主要提供了3种实现同步机制的方法:
(1) synchronized关键字
每个对象都有一个对象锁与之相关联,该锁表明对象在任何时候只允许一个线程所拥有,当一个线程调用对象的一段synchronized代码时,需要先获取这个锁,然后去执行相应的代码,执行结束后,释放锁。

1) synchronized方法。在方法的声明前加人synchronized关键字,示例如下:
public synchronized void mutiThreadAccess ();
只要把多个线程对类需要被同步的资源的操作放到mutiThreadAccess()方法中,就能保证这个方法在同一时刻只能被一个线程访问,从而保证了多线程访问的安全性。
2) synchronized块。synchronized块既可以把任意的代码段声明为synchronized,也可以指定上锁的对象,有非常高的灵活性。其用法如下:
synchronized (syncObject){
//访问syncObject的代码
}

( 2 ) wait()方法与notify()方法
当使用synchronized来修饰某个共享资源时,如果线程A1在执行synchronized代码,另外 一个线程A2也要同时执行同一对象的同一synchronized代码时,线程A2将要等到线程A1执行完成后,才能继续执行。在这种情况下可以使用wait()方法和notify()方法。
在synchronized代码被执行期间,线程可以调用对象的wait()方法,释放对象锁,进人等 待状态,并且可以调用notify()方法或notifyAll()方法通知正在等待的其他线程。notify()方法仅唤醒一个线程(等待队列中的第一个线程)并允许它去获得锁,notifyAlI()方法唤醒所有等 待这个对象的线程并允许它们去获得锁(并不是让所有唤醒线程都获取到锁,而是让它们去竞争)。

(3) Lock

1) lock()以阻塞的方式获取锁,也就是说,如果获取到了锁,立即返回;如果别的线
程持有锁,当前线程等待,直到获取锁后返回。
2) tryLock()以非阻塞的方式获取锁。只是尝试性地去获取一下锁,如果获取到锁,立
即返回true,否则,立即返回false。
3) tryLock( long timeout, TimeUnit unit)。如果获取了锁,立即返回true,否则会等待参数给定的时间单元,在等待的过程中,如果获取了锁,就返回true,如果等待超时,返回false。
4) lockInterruptibly()。如果获取了锁,立即返回;如果没有获取锁,当前线程处于休眠 状态,直到获得锁,或者当前线程被别的线程中断(会收到IntermptedException异常)。

10.6sleep()方法与wait()方法有什么区別

sleep()是使线程暂停执行一段时间的方法。wait()也是一种使线程暂停执行的方法,例 如,当线程交互时,如果线程对一个同步对象x发出一个wait()调用请求,那么该线程会暂停执行,被调对象进人等待状态,直到被唤醒或等待时间超时。
区别如下:
1)原理不同。
sleep()方法是Thread类的静态方法,是线程用来控制自身流程的,它会使 此线程暂停执行一段时间,而把执行机会让给其他线程,等到计时时间一到,此线程会自动 “苏醒”。
而wait ()方法是Object类的方法,用于线程间的通信,这个方法会使当前拥有该对象锁的进程等待,直到其他线程调用notify()方法(或notifyAll()方法)时才“醒”来,

2) 对锁的处理机制不同。由于sleepO方法的主要作用是让线程暂停执行一段时间,时间 一到则自动恢复,不涉及线程间的通信,因此,调用sleep()方法并不会释放锁。而wait()方法则不同,当调用wait()方法后,线程会释放掉它所占用的锁,从而使线程所在对象中的其他 synchronized数据可被别的线程使用。

3) 使用区域不同。由于wait()方法的特殊意义,因此它必须放在同步控制方法或者同步 语句块中使用,而sleep()方法则可以放在任何地方使用。
sleep()方法必须捕获异常,而wait( )、 notify ()以及notifyall()不需要捕获异常。

例题:
1.利用Thread. wait()同步线程,可以设置超时时间吗?

答案:可以设置超时,函数原型为wait (long timeout)和 wait (long timeout, int nanos) timeout代表最长的等待时间,单位为ms; nanos代表额外的等待时间,单位为ns。

2.在一个线程中sleep()方法,将使该线程在多长时间后获得对CPU的控制(假设睡
眠过程中不会有其他事件唤醒该线程)?
A.正好1000 ms B.少于1000 ms C.大于等于1000 ms D.不一定

答案:C。Sleep()方法指定的时间为线程不会运行的最短时间。当睡眠时间结束后,线程会返回到可运行状态,不是运行状态,还需要等待CPU调度执行。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。

10.7终止线程的方法有哪些

可以使用stop()方法与suspend()方法来终止线程的执行。
当用 Thread. stop()来终止线程时,它会释放已经锁定的所有监视资源。
调用suspend()方法不会释放锁,容易发生死锁(死锁指的是两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,如果无外力作用,它们都将无法推进)。

建议采用的方法是让线程自行结束进人Dead状态。一个线程进入Dead状态,即执行完run()方法,如果想要停止一个线程的执行,就要提供某种方式让线程能够自动结束run()方法的执行。在实现时,可以通过设置一个flag标记来控制循环是否执行,通过这种方法来让线程离开run()方法从而终止线程。

public class MyThread extends Thread 
{
public volatile boolean exit = false;

public void run()
{
while (!exit) {
System.out.println("Do something!");
};
}
public static void main(String[] args) throws Exception
{
MyThread thread = new MyThread();
thread.start();
sleep(1000); // 主线程延迟1秒
thread.exit = true; // 终止线程thread
thread.join();
System.out.println("线程退出!");
}
}

当线程处于非运行状态时(当slee()方法被调用或当wait()方法被调用或当被I/O阻塞时),上面介绍的方法就不可用了。此时可以使用intemipt()方法来打破阻塞的情况,当interrupt()方法被调用时,会抛出IntemiptedException异常,可以通过在run()方法中捕获这个异常来让线程安全退出。

public class MyThread extends Thread 
{

public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {

public void run() {
System.out.println("thread go to sleep");
try {
//用休眠来模拟线程被阻塞
Thread.sleep(5000);
System.out.println("thread finish");
} catch (Exception e) {
// TODO: handle exception
System.out.println("thread is interupted!");
}
}
});
thread.start();
thread.interrupt();
}
}

程序运行结果为:
thread go to sleep
thread is interupted!

10.8synchronized 与Lock 有什么区别

Java语言提供了两种锁机制来实现对某个共享资源的同步:
synchronized使用Object对象本身的notify、wait、notityAll调度机制,而 Lock可以使用 Condi­tion进行线程之间的调度,完成synchronized实现的所有功能。
(省略一下,待补充)

10.9什么是守护线程

Java提供了两种线程:守护线程与用户线程。守护线程又被称为“服务进程” “精灵线 程”或“后台线程”,是指在程序运行时在后台提供一种通用服务的线程,这种线程并不属于程序中不可或缺的部分。
任何一个守护线程都是整个JVM中所有非守护线程的 “保姆”。
如果用户线程已经全部退出运行, 只剩下守护线程存在了,JVM也就退出了。因为当所有非守护线程结束时,没有了被守护者, 守护线程也就没有工作可做了,也就没有继续运行程序的必要了,程序也就终止了,同时会 “杀死”所有守护线程。

将一个用户线程设置为守护线程的方法就是在调用start()方法启动线程之前调用对象的setDaemon()方法,若将以上参数设置为false,则表示的是用户进程模式。需要注意的是,当在一个守护线程中产生了其他线程,那么这些新产生的线程默认还是守护线程,用户线程也是如此。

例题:
1.Java的Daemon线程,setDaemon 设置必须要()
A.在调用start()方法之前 B.在调用start()方法之后 C.在调用start()方法前后均可

答案:A

2.关于守护线程的说法,正确的是( )。
A.所有非守护线程终止,即使存在守护线程,进程运行终止
B.所有守护线程终止,即使存在非守护线程,进程运行终止
C.只要有守护线程或者非守护进程其中之一存在,进程就不会终止
D.只要所有守护线程和非守护线程终止运行之后,进程才会终止

答案:A

10.10join()方法的作用是什么

join()方法的作用是让调用该方法的线程在执行完run()方法后,再执行join方法后面的代码。简单点说,就是将两个线程合并,用于实现同步功能。
t.join()方法阻塞调用此方法的线程(calling thread),直到线程t完成,此线程再继续;通常用于在main()主线程内,等待其它线程完成再结束main()主线程。例如:

class MyThreads implements Runnable{
@Override
public void run() {
// TODO 自动生成的方法存根
try {
System.out.println("begin:mythread!");
Thread.sleep(3000);
System.out.println("end:mythread!");
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}

}
public class JoinTest {

public static void main(String[] args) {
// TODO 自动生成的方法存根
Thread thread = new Thread(new MyThreads());
thread.start();
try {
thread.join(1000);//主线程等待thread结束1秒
if(thread.isAlive())//thread已经结束
System.out.println("thread has not finished!");
else
System.out.println("thread has finished!");
System.out.println("jion Finish!");
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}

运行结果:
begin:mythread!
thread has not finished!
jion Finish!
end:mythread!

11. Java数据库操作

11.1如何通过JDBC连接数据庫

1)加载JDBC驱动器。将数据库的JDBC驱动加载到classpath中,在基于JavaEE的Web 应用开发过程中,通常要把目标数据库产品的JDBC驱动复制到WEB -INF/lib下。
2)加载JDBC驱动,并将其注册到DriverManager中。一般使用反射Class. forName ( String driveName ) 0
3)建立数据库连接,取得 Connection 对象。一般通过 DriverManager.getConnection ( url,username, passwd)方法实现,其中,url表示连接数据库的字符串,username表示连接数据库的用户名,passwd表示连接数据库的密码。
4)建立 Statement 对象或是 PreparedStatement 对象。
5)执行SQL语句。
6)访问结果集ResultSet对象。
7)依次将 ResultSet、Statement、PreparedStatement、 Connection 对象关闭,释放掉所占用 资源,例如rs.close(),con.close()等。

1.JDBC的主要功能有( )。
A.创建与数据库的连接 B.发送SQL语句到数据库中
C.处理数据并查询结果 D.以上都是

答案:D

2.提供Java存取数据库能力的包是?
答案:Java.sql

11.2JDBC处理事务采取什么方法

一个事务是由一条或多条对数据库操作的SQL语句所组成的一个不可分割的工作单元, 只有当事务中的所有操作都正常执行完了,整个事务才会被提交给数据库。在JDBC中,一般是通过commit()方法或rollback()方法来结束事务的操作。其中commit()方法表示完成对事务 的提交, rollback )方法表示完成事务回滚,多用于在处理事务的过程中出现了异常的情况, 这两种方法都位于java.ql.Connection类中。一般而言,事务默认操作是自动提交,即操作成功后,系统将自动调用commit()方法,否则将调用rollback()方法。

11.3Class.forName的作用是什么

任何类只有被装载到JVM上才能运行。Class.forName()方法的作用就是把类加载到JVM中,它会返回一个与带有给定字符串名的类或接口相关联的Class对象,并且JVM会加载这个类,同时JVM会执行该类的静态代码段。

JDBC规范中要求Driver类在使用前必须向DriverManager注册自己,所以,当执行
Class.forName(“com.mysql.jdbc.Driver”)时,JVM会加载名为’com.mysql.jdbc.Driver”对应的Driver类。

public class Driver extends NonRegisteringDriver implements Java. sql.Driver { 
static {
try {
java.sql.DriverManager.registerDriver( new Driver());
} catch (SQLException E) {
throw new RuntimeException( " Can't register driver!");
}
}
}

在调用Class. forName()方法时,这个Driver类被加载了,由于静态部分被执行,因此 Driver 也被注册到了 DriverManager 中。

11.4Statement,PreparedStatement和CallableStatement有什么区别

Statement用于执行不带参数的简单SQL语句,并返回它所生成结果的对象,每次执行SQL语句时,数据库都要编译该SQL语句。

PreparedStatement表示预编译的SQL语句的对象,用于执行带参数的预编译SQL语句。(执行插入、更新、删除等操作,最好使用PreparedStatement

CallableStatement则提供了用来调用数据库中存储过程的接口,如果有输出参数要注册,说明是输出参数。
CallableStatement由prepareCall()方法所创建,它为所有 DBMS ( Database Management Sys­ tem, 数据库管理系统)提供了一种以标准形式调用已储存过程的方法。

例题:
1.用于调用存储过程的对象是()。
A. ResultSet B. DriverManager C. CallableStatemet D. PreparedStatement

答案:C

11.5getString()方法和getObject()方法有什么区别

getStrin()或getlnt()等方法在被调用时,程序会一次性地把数据都放到内存中,然后通过调用ResultSet的next()和getString()等方法来获取数据。当数据量大到内存中放不下时就 会拋出异常,而使用getObject()方法就不会这种问题,因为数据不会一次性被读到内存中, 每次调用时会直接从数据库中去获取数据,因此使用这种方法不会因为数据量过大而出错。

11.6什么是JDO

Java数据对象(Java Data Object,JDO)是一个用于存取某种数据仓库中的对象的标准化 API,它使开发人员能够间接地访问数据库。
JDO是JDBC的一个补充,它提供了透明的对象存储,因此对开发人员来说,存储数据对 象完全不需要额外的代码(例如JDBC API的使用)。相较于JDBC, JDO更灵活、更通用,它提供了到任何数据底层的存储功能,例如关系数据库、文件、 XML 以及对象数据库管理系统( Object Database Management System,ODBMS)等,使得应用可移植性更强。