1.基本概念
- Java是纯面向对象语言,“Everything is Object”;所有代码(包括函数、变量等)必须在类中实现,除基本数据类型(int、float等)外,所有类型都是类。不存在全局变量和全局函数。
- 平台无关性;
- Java提供很多被指的类库,通过这些类库,简化了开发人员的程序设计工作,同时缩短了项目的开发时间,如,Java语言提供了对多线程编程的支持,提供了对网络通信的支持,最重要是提供了垃圾回收器,让开发人员从对内存的管理中解脱出来;
- 提供对Web的支持,如Servlet、Jsp可以用来开发Web应用程序,Socket、RMI可以用来开发分布式应用程序的类库;
- 具有较好的安全性和健壮性。
- Java为解释性语言,其运行过程是:程序源代码经过Java编译器编译成字节码,然后由JVM解释执行。
- 不支持多继承,但是Java语言引入了接口的概念,可以同时实现多个接口,由于接口也具有多态特性,因此在Java语言中可以通过实现多个接口来实现与C++语言中多重继承类似的目的;
- Java提供垃圾回收器来实现垃圾的自动回收,不需要程序显式地管理内存的分配。Java引入了finalize()方法,当垃圾回收器将要释放无用对象的内存时,会首先调用该对象的finalize()方法,因此开发人员不需要关心也不需要知道对象所占的内存何时会被释放。
2.为什么需要public static void main(String []args)这个方法
public static void main(String []args)为Java程序的入口方法,JVM在运行程序时,会首先查找main()方法。其中,public是权限修饰符,表明任何类或对象都可以访问这个方法,static表明这个main()方法是一个静态方法,即方法中的代码是存储在静态存储区的,只要类被加载后,就可以使用该方法而不需要通过实例化对象来访问,可以直接通过类名.main()直接访问,JVM在启动时就是按照上述方法的签名(必须有public和static修饰,返回值为void,且方法的参数为字符串数组)来查找方法的入口地址,若能找到,就执行;找不到就会报错。void表明该方法没有返回值,main是JVM识别的特殊方法名,是程序的入口方法。字符串数组参数args为开发人员在命令行状态下与程序交互提供了一种手段。
注:
- main()方法是否还有其他可用的定义格式?
- 由于public与static没有先后顺序关系,因此下面的定义也是合理的。
static public void main(String []args) - 也可以把main()方法定义问final。
public static final void main(String []args) - 也可以用synchronozed来修饰main()方法。
public static synchronized void main(String []args) - 不能用abstract关键字修饰。
- 由于public与static没有先后顺序关系,因此下面的定义也是合理的。
- 同一个.java文件中是否可以有多个main()方法?
虽然每个类中都可以定义main()方法,但是只有与方法名相同的用public修饰的类中的main()方法才能作为整个程序的入口方法。
3.如何实现在main()方法执行前输出“Hello World”
众所周知,在Java语言中,main()方法是程序的入口方法,在程序运行时,最先加载的就是main()方法,但main()方法不一定是程序运行时第一个被执行的模块。
在Java语言中,由于静态块在类被加载时就会被调用,因此可以在main()方法执行前,领用静态块实现输出“Hello World”的功能,如下代码:
public class Test {
static {
System.out.println("Hello World1");
}
public static void main(String args[]){
System.out.println("Hello World2");
}
}
输出结果是:
Hello World1
Hello World2
由于静态代码不管顺序如何,都会在main()方法执行之前执行,因此一下代码与上面的代码有同样的输出结果:
public class Test {
public static void main(String args[]){
System.out.println("Hello World2");
}
static {
System.out.println("Hello World1");
}
}
```
***
**4.Java程序初始化的顺序是怎样的**
在Java语言中,当实例化对象时,对象所在类的所有成员变量首先要进行初始化,只有当所有类成员都实例化后,才会调用对象所在类的构造函数创建对象。
在Java程序的初始化一般遵循3个原则(优先级依次递减):1.静态对象(变量)优先于非静态对象(变量)初始化,其中,静态对象(变量)只初始化一次,而非静态对象(变量)可能会初始化很多次。2.父类有限元子类进行初始化。3.按照成员变量的定义顺序进行初始化。即使变量散布在方法定义之中,他们依然在任何方法(包括构造函数)被调用前先初始化。
Java程序初始化工作可以在许多不同的代码块中来完成(例如静态代码块、构造函数等),他们的执行顺序如下:父类静态变量、父类静态代码块、子类静态变量、子类静态代码块、父类非静态变量、父类非静态代码块、父类构造函数、子类非静态变量、子类非静态代码块、子类构造函数。
***
**5.Java作用域有哪些**
在Java语言中,作用域是由花括号的位置决定的,它决定了其定义的变量名的可见性与生命周期。
在Java语言中,有3种变量的类型:成员变量、静态变量和局部变量。类的成员变量的作用范围与类的实例化对象的作用范围相同,当类被实例化时,成员变量就会在内存中分配空间和初始化,直到这个类的对象的生命周期结束时,成员变量的生命周期才结束。被static修饰的成员变量称为静态变量或全局变量,与成员变量不同的是,静态变量不依赖于特定的实例,而是被所有实例所共享,也就是说,只要一个类被加载,JVM就会给类的静态变量分配存储空间。因此,可以通过类名和变量名来访问静态变量。
此外,成员变量也有4种作用域:public、private、protected、default。
- public:表明该成员变量或方法对所有类或对象都是可见的,所有类或对象都可以直接访问。
- private:表明该成员变量或方法是私有的,只有当前类对其具有访问权限,除此之外的其他类或对象都没有访问权限。
- protected:表明该成员变量或方法对自己及其子类都是可见,即自己和子类具有权限访问。除此之外的其他类或对象都是没有访问权限。
- default:表明该成员变量或方法只有自己或其位于同一个包内的类可见。若父类与子类位于同一个包内,则子类对父类的default成员变量或方法有访问权限;若父类与子类位于不同的package内,则没有访问权限。
**注:**
这些修饰符只能修饰成员变量或方法,不能用了修饰局部变量。private与protected不能用来修饰类(只有public、abstract或final能用来修饰类)。
***
**6.一个Java文件中是否可以定义多个类**
一个Java文件中可以定义多个类,但是最多只能有一个类被public修饰,并且这个类的文件与文件名必须相同,若有这个文件中没有public的类,则文件名随便是一个类的名字即可。
***
**7.构造函数**
构造函数是一种特殊的函数,用来在对象实例化时初始化对象的成员变量。构造函数具有一下特点:
- 构造函数必须与类的名字相同,并且不能有返回值。
- 每个类可以有多个构造函数。如果没写构造函数,编译器在编译源代码时提供一个无参数的默认构造函数。
- 构造函数可以有0个,1个或以上的参数。
- 构造函数总是伴随着new操作一起调用,且不能由程序的编写者直接调用,有系统调用。构造函数在对象实例化时会被自动调用,且只运行一次;而普通的方法是在程序执行到它时调用,且可以被对象调用多次。
- 构造函数的主要作用是完成对象的初始化工作。
- 构造函数不能被继承,因此,它不能被覆盖,但是构造函数可以被重载,可以使用不同的参数个数或参数类型来定义多个构造函数。
- 子类可以通过super关键字来显式地调用父类的构造函数,当父类没有提供无参数的额构造函数时,子类的构造函数中必须显式地调用父类的构造函数。如果父类提供了无参数的构造函数,此时子类的构造函数就可以不显示的调用父类的构造函数,在这种情况下编译器会默认调用父类提供的无参数的构造函数,当有父类时,在实例化对象时会先执行父类的构造函数,然后执行子类的构造函数。
- 当父类和子类都没有定义构造函数时,编译器会为父类生成一个默认的无参数的构造函数,给子类也生成一个默认的无参数的构造函数。此外,默认构造器的修饰符只跟当前类的修饰符有关,例如,如果一个类被定义为public,那么它的构造函数也是public。
**注:普通方法可以与构造函数同名,但是实例化对象时就运行构造函数**
***
**8.为什么Java中有些接口没有任何方法**
接口是抽象方法定义的集合(接口中也可以定义一些常量值),是一种特殊的抽象类,接口中只包含方法的定义,没有方法的实现。接口中的所有方法都是抽象的。接口中成员的作用域修饰符都是public,接口中的常量值默认使用public static final修饰。由于一个类可以实现多个接口,因此通常可以采用实现多个接口的方式来间接达到多重继承的目的。
在Java语言中,有些接口内部没有声明任何方法,也就是说,实现这些接口的类不需要重写任何方法,这些没有任何方法声明的接口又被称为**标识接口**,**标识接口对实现它的类没有任何语义上的要求,他仅仅充当一个标识的作用,用来表明实现它的类属于一个特定的类型**。这个标签类似于汽车的标志图标,每当人们看到一个汽车车标时,就能知道这款汽车的品牌。Java类库中已存在的标识接口有Cloneable和Serializable等。在使用时会经常用instanceof来判断实例对象的类型是否实现了一个给定的标识接口。
***
**9.Java中clone方法有什么用**
实质上每个new语句返回的都是一个指针的引用,只不过在大部分情况下开发人员不需要关心如何去操作这个指针而已。
由于Java取消指针的概念,因此开发人员在编程中往往忽略对象和引用的区别。在Java中,处理基本数据类型(如int,char,double等)时,都是采用按值传递(传递的是输入参数的复制)的方式执行,除此之外的其他类型都是按引用传递(传递的是对象的一个引用)的方式执行。对象除了在函数调用时是引用传递,在使用“=”赋值时也是采用引用传递,(如:Object A = new Object(); Object A = new Object(); b = a;)
在实际编程中,经常会遇到从某个已有的对象A创建出另外一个与A具有相同状态的对象B,并且对B的修改不会影响到A的情况,如Prototype(原型)模式中,就需要clone一个对象实例。在Java语言中,仅仅通过简单的赋值操作显然无法达到这个目的,而Java提供了一个简单有效的clone()方法来满足需求。
Java中的所有类默认都继承自Object类,而Object类中提供了一个clone()方法。这个方法的作用是返回一个Object对象的复制。这个复制函数返回的是一个新的对象而不是也给引用。使用步骤:
- 实现clone的类首先需要继承Cloneable接口。Cloneable接口实质上是一个标识接口,没有任何接口方法;
- 在类中重写Object类中的clone()方法;
- 在clone方法中调用super.clone()。调用java.lang.Object类的clone()方法;
- 把浅复制的引用指向原型对象的新的克隆体。
Java语言在重载clone()方法时,当开发人员自定义复制构造函数时,也会存在浅复制和深复制之分,当类中只有一些基本的数据类型时,上述步骤就行。但是当类中包含了一些对象时,就需要用到深复制了,实现方法是在对对象调用clone()方法完成复制后,接着对对象中的非基本类型的属性也调用clone()方法完成深复制。
***
**10.反射机制**
它允许程序在运行时进行自我检查,同时也允许对其内部成员进行操作。由于反射机制能够实现在运行时对类进行装载,因此能增强程序的灵活性,但是不恰当地使用反射机制,也会严重影响系统的性能。
反射机制的提供的功能主要有:
- 得到一个对象所属的类;
- 获取一个类的所有成员变量和方法;
- 在运行时创建对象;
- 在运行时调用对象的方法。
反射机制非常重要的一个作用就是可以在运行时动态地创建类的对象,如下:
```
class Base{
public void f() {
System.out.println("Base");
}
}
class Sub extends Base {
public void f() {
System.out.println("Sub");
}
}
public class Test {
public static void main(String []args) {
try{ //使用反射机制加载类
Class c = Class.forName("Sub");
Base b = (Base) c.newInstance();
b.f();
}catch(Exception e){
e.printStackTrace();
}
}
}
```
程序运行结果为:
Sub
在反射机制中,class是一个非常重要的类,有3种方法可以获取到类:
- class.forName("类的路径"),如上例;
- 类名.class;
- 实例.getClass();
Java创建对象的方式有4种:
- 通过new语句实例化一个对象;
- 通过反射机制创建对象,如上例;
- 通过clone()方法创建一个对象;
- 通过反序列化的方式创建对象;
***
**11.回调函数**
回调函数,就是指函数先在某处注册,而它将在稍后某个需要的时候被调用。Java中没有只针对的概念,可以利用接口与类来实现类似于函数指针的功能。首先定义一个接口,然后在接口中声明要调用的方法,接口实现这个接口,最后把这个实现类的一个对象作为参数传递给调用程序,调用程序通过这个参数来调用指定的函数,从而实现回调函数的功能,如下:
```
//接口中定义了一个用来比较大小的方法
interface IntCompare{
public int cmp(int a, int b);
}
class Cmp1 implements IntCompare{
public int cmp(int a, int b) {
if(a > b) {
return 1;
} else if(a < b) {
return -1;
} else
return 0;
}
}
class Cmp2 implements IntCompare{
public int cmp(int a, int b) {
if(a < b) {
return 1;
} else if(a >b) {
return -1;
} else
return 0;
}
}
public clas Test {
public static void insertSort(int []a, IntCompare cmp) {
if(a != null) {
for(int i = 1; i < a.length; i++ ) {
int temp = a[i],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) {
int []array1 = {7,3,29,40,4,7,1};
insertSort(array1, new Cmp1());
System.out.println("升序排列:");
for(int i = 0;i<array1.length;i++){
System.out.println(array1[i]+"");
}
System.out.println();
int []array2 = {7,3,29,40,4,7,1};
insertSort(array2, new Cmp2());
System.out.println("降序排列:");
for(int i = 0;i<array1.length;i++){
System.out.println(array2[i]+"");
}
}
}
```
程序运行结果:
升序排列:1 3 4 7 7 29 40
降序排列: 40 29 7 7 4 3 2 1
定义了一个比较大小的接口,在使用时,开发人员可以根据实际需要传入自定义的类,这也是策略设计模式所用到的思想。
***
**参考:Java程序员面试笔试宝典------何昊**