Java面试 4.1基础知识

4.1.1 Java 语言有哪些优点

具体而言,Java 语言具有以下几个方面的优点:

1)Java 为纯面向对象的语言。《Java 编程思想》提到 Java 语言是一种「Everything is ob-ject」的语言,它能够直接反应现实生活中的对象,例如火车、动物等,因此通过它,开发人员编写程序更为容易。

2)平台无关性。Java 语言可以「一次编译,到处运行」。无论是在 Windows 平台还是在 Linux、MacOS 等其他平台上对 Java 程序进行编译,编译后的程序在其他平台上都可以运行。由于 Java 为解释型语言,编译器会把 Java 代码变成「中间代码」,然后在 Java 虚拟机(Java Virtual Machine,JVM)上解释执行。由于中间代码与平台无关,因此,Java 语言可以很好地跨平台执行,具有很好的可移植性。

3)Java 提供了很多内置的类库,通过这些类库,简化了开发人员的程序设计工作,同时缩短了项目的开发时间,例如,Java 语言提供了对多线程的支持,提供了对网络通信的支持,最重要的是提供了垃圾回收器,这使得开发人员从对内存的管理中解脱出来。

4)提供了对 Web 应用开发的支持,例如,Applet、Servlet 和 JSP 可以用来开发 Web 应用程序;Socket、RMI 可以用来开发分布式应用程序的类库。

5)具有较好的安全性和健壮性。Java 语言经常被用在网络环境中,为了增强程序的安全性,Java 语言提供了一个防止恶意代码攻击的安全机制(数组边界检测和 Bytecode 校验等)。Java 的强类型机制、垃圾回收器、异常处理和安全检查机制使得用 Java 语言编写的程序有很好的健壮性。

6)去除了 C++ 语言中难以理解、容易混淆的特性,例如头文件、指针、结构、单元、运算符重载、虚拟基础类、多重继承等,使得程序更加严谨、简洁。

4.1.2 Java 与 C/C++ 有什么异同

需要注意的是,二者并非完全一样,下面主要介绍它们的不同点:

1)Java 为解释性语言,其运行过程为:程序源代码经过 Java 编译器编译成字节码,然后由 JVM 解释执行。而 C/C++ 为编译型语言,源代码经过编译和链接后生成可执行的二进制代码。因此,Java 的执行速度比 C/C++ 慢,但是 Java 能够跨平台执行,而 C/C++ 不能。

2)Java 为纯面向对象语言,所有代码(包括函数、变量等)必须在类中实现,除基本数据类型(包括 int、float 等)外,所有类型都是类。此外,Java 语言中不存在全局变量或全局函数,而 C++ 兼具面向过程和面向过程编程的特点,可以定义全局变量和全局函数。

3)与 C/C++ 语言相比,Java 语言中没有指针的概念,这有效防止了 C/C++ 语言中操作指针可能引起的系统问题,从而使程序变得更加安全。

4)与 C++ 语言相比,Java 语言不支持多重继承,但是 Java 语言引入了接口的概念,可以同时实现多个接口。由于接口也具有多态特性,因此在 Java 语言中可以通过实现多个接口来实现与 C++ 语言中多重继承类似的目的。

5)在 C++ 语言中,需要开发人员去管理对内存的分配(包括申请与释放),而 Java 语言提供了垃圾回收器来实现垃圾的自动回收,不需要程序显式地管理内存的分配。在 C++ 语言中,通常都会把释放资源的代码放到析构函数中,Java 语言中虽然没有析构函数,但却引入了一个 finalize()方法,当垃圾回收器将要释放无用对象的内存时,会首先调用该对象的 finalize()方法,因此,开发人员不需要关心也不需要知道对象所占的内存空间何时会被释放。

C++ 语言支持运算符重载,而 Java 语言不支持运算符重载。C++ 语言支持预处理,而 Java 语言没有预处理器,虽然不支持预处理功能(包括头文件、宏定义等),但它提供的 im-port 机制与 C++ 中的预处理器功能类似。C++ 支持默认函数参数,而 Java 不支持默认函数参数。C/C++ 支持 goto 语句,而 Java 不提供 goto 语句(但 Java 中 goto 是保留关键字)。C/C++ 支持自动强制类型转换,这会导致程序的不安全;而 Java 不支持自动强制类型转换,必须由开发人员进行显式地强制类型转换。C/C++ 中,结构和联合的所有成员均为公有,这往往会导致安全性问题的发生,而 Java 根本就不包含结构和联合,所有内容都封装在类里面。

Java 具有平台无关性,即对每种数据类型都分配固定长度,例如,int 类型总是占据 32 位,而 C/C++ 却不然,同一个数据类型在不同的平台上会分配不同的字节数。

Java 提供对注释文档的内建支持,所以源码文件也可以包含它们自己的文档。通过一个单独的程序,这些文档信息可以提取出来,并重新格式化成 HTML。

Java 包含了一些标准库,用于完成特定的任务,同时这些库简单易用,能够大大缩短开发周期,例如,Java 提供了用于访问数据库的 JDBC 库,用于实现分布式对象的 RMI 等标准库。C++ 则依靠一些非标准的、由其他厂商提供的库。

4.1.3 为什么需要 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 是程序的入口方法,所以当程序运行时,第一个执行的方法就是 main()方法。通常来讲,要执行一个类的方法,先必须实例化一个类的对象,然后通过对象来调用这个方法。但由于 main 是程序的入口方法,此时还没有实例化对象,因此在编写 main()方法时就要求不需要实例化对象就可以调用这个方法,鉴于此,main()方法需要被定义成 public 与 static。

引申:

1.main()方法是否还有其他可用的定义格式?

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 关键字来修饰。

4.1.4 如何实现在 main()方法执行前输出「Hello World」

在 Java 语言中,main()方法是程序的入口方法,在程序运行时,最先加载的就是 main()方法,但这是否意味着 main()方法就是程序运行时第一个被执行的模块呢?答案是否定的。

在 Java 语言中,由于静态块在类被加载时就会被调用,因此可以在 main()方法执行前,利用静态块实现输出「Hello World」的功能,以如下代码为例。

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

在 Java 语言中,当实例化对象时,对象所在类的所有成员变量首先要进行初始化,只有当所有类成员完成初始化后,才会调用对象所在类的构造函数创建对象。

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

4.1.6 Java 中的作用域有哪些

在 Java 语言中,变量的类型主要有 3 种:成员变量、静态变量和局部变量。类的成员变量的作用范围与类的实例化对象的作用范围相同,当类被实例化时,成员变量就会在内存中分配空间并初始化,直到这个被实例化对象的生命周期结束时,成员变量的生命周期才结束。被 static 修饰的成员变量被称为静态变量或全局变量,与成员变量不同的是,静态变量不依赖于特定的实例,而是被所有实例所共享,也就是说,只要一个类被加载,JVM 就会给类的静态变量分配存储空间。因此,就可以通过类名和变量名来访问静态变量。局部变量的作用域与可见性为它所在的花括号内。

1)public。表明该成员变量或方法对所有类或对象都是可见的,所有类或对象都可以直接访问。

2)private。表明该成员变量或方法是私有的,只有当前类对其具有访问权限,除此之外的其他类或者对象都没有访问权限。

3)protected。表明成员变量或方法对该类自身,与它在同一个包中的其它类,在其它包中的该类的子类都可见。

4.1.8 什么是构造函数

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

1)构造函数必须与类的名字相同,并且不能有返回值(返回值也不能为 void)。

2)每个类可以有多个构造函数。当开发人员没有提供构造函数时,编译器在把源代码编译成字节码的过程中会提供一个没有参数默认的构造函数,但该构造函数不会执行任何代码。如果开发人员提供了构造函数,那么编译器就不会再创建默认的构造函数了。

3)构造函数可以有 0 个、1 个或 1 个以上的参数。

4)构造函数总是伴随着 new 操作一起调用,且不能由程序的编写者直接调用,必须要由系统调用。构造函数在对象实例化时会被自动调用,且只运行一次;而普通的方法是在程序执行到它时被调用,且可以被该对象调用多次。

5)构造函数的主要作用是完成对象的初始化工作。

6)构造函数不能被继承,因此,它不能被覆盖,但是构造函数能够被重载,可以使用不同的参数个数或参数类型来定义多个构造函数。

7)子类可以通过 super 关键字来显式地调用父类的构造函数,当父类没有提供无参数的构造函数时,子类的构造函数中必须显式地调用父类的构造函数。如果父类提供了无参数的构造函数,此时子类的构造函数就可以不显式地调用父类的构造函数,在这种情况下编译器会默认调用父类提供的无参数的构造函数。当有父类时,在实例化对象时会先执行父类的构造函数,然后执行子类的构造函数。

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

由于 Java 不支持多重继承,即一个类只能有一个父类,为了克服单继承的缺点,Java 语言引入了接口这一概念。接口是抽象方法定义的集合(接口中也可以定义一些常量值),是一种特殊的抽象类。接口中的常量值默认使用 public static final 修饰。由于一个类可以实现多个接口,因此通常可以采用实现多个接口的方式来间接达到多重继承的目的。

在 Java 语言中,有些接口内部没有声明任何方法,也就是说,实现这些接口的类不需要重写任何方法,这些没有任何方法声明的接口又被叫做标识接口,标识接口对实现它的类没有任何语义上的要求,它仅仅充当一个标识的作用,用来表明实现它的类属于一个特定的类型。这个标签类似于汽车的标志图标,每当人们看到一个汽车的标志图标时,就能知道这款汽车的品牌。Java 类库中已存在的标识接口有 Cloneable 和 Serializable 等。在使用时会经常用 instan-ceof 来判断实例对象的类型是否实现了一个给定的标识接口。

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

Java 语言取消了指针的概念,但这只是在 Java 语言中没有明确提供指针的概念与用法,而实质上每个 new 语句返回的都是一个指针的引用,只不过在大部分情况下开发人员不需要关心如何去操作这个指针而已。

程序运行结果为:

∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ 引用类型 ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗

调用 changeObj()前:default value

调用 changeObj()后:changed value

∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ 基本数据类型 ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗

调用 changeInt()前:0

调用 changeInt()后:0

上面两个看似类似的方法却有着不同的运行结果,主要原因是 Java 在处理基本数据类型(例如 int、char、double 等)时,都是采用按值传递(传递的是输入参数的复制)的方式执行,除此之外的其他类型都是按引用传递(传递的是对象的一个引用)的方式执行。对象除了在函数调用时是引用传递,在使用「=」赋值时也采用引用传递

在实际编程中,经常会遇到从某个已有的对象 A 创建出另外一个与 A 具有相同状态的对象 B,并且对 B 的修改不会影响到 A 的状态,例如,Prototype(原型)模式中,就需要 clone 一个对象实例。在 Java 语言中,仅仅通过简单的赋值操作显然无法达到这个目的,而 Java 提供了一个简单有效的 clone()方法来满足这个需求。

Java 中的所有类默认都继承自 Object 类,而 Object 类中提供了一个 clone()方法。这个方法的作用是返回一个 Object 对象的复制。这个复制函数返回的是一个新的对象而不是一个引用。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)把浅复制的引用指向原型对象新的克隆体。

当类中只有一些基本的数据类型时,采用上述方法就可以了,但是当类中包含了一些对象时,就需要用到深复制了,实现方法是在对对象调用 clone()方法完成复制后,接着对对象中的非基本类型的属性也调用 clone()方法完成深复制,示例如下。

程序运行结果为:

a=Sat Jul 1323:58:56 CST 2013

b=Mon May 1323:58:56 CST 2013


4.1.11 什么是反射机制

具体而言,反射机制提供的功能主要有:得到一个对象所属的类;获取一个类的所有成员变量和方法;在运行时创建对象;在运行时调用对象的方法。

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

1)Class.forName(「类的路径」),如上例所示。

2)类名.Class。

3)实例.getClass()。

常见笔试题:

Java 创建对象的方式有几种?

答案:共有 4 种创建对象的方法。

1)通过 new 语句实例化一个对象。2)通过反射机制创建对象,见上述讲解。3)通过 clone()方法创建一个对象,见 4.1.10 节。4)通过反序列化的方式创建对象,见 4.7.5 节。

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

函数指针一般作为函数的参数来使用,开发人员在使用时可以根据自己的需求传递自定义的函数来实现指定的功能,例如,在实现排序算法时,可以通过传递一个函数指针来决定两个数的先后顺序,从而最终决定该算法是按升序还是降序排列。

在 Java 语言中没有指针的概念,那么如何才能在 Java 语言中实现类似于函数指针的功能呢?可以利用接口与类来实现同样的效果。具体而言,应先定义一个接口,然后在接口中声明要调用的方法,接着实现这个接口,最后把这个实现类的一个对象作为参数传递给调用程序,调用程序通过这个参数来调用指定的函数,从而实现回调函数的功能,示例如下。

程序运行结果为:

升序排列:1 3 4 7 7 1 9 4 0

降序排列:40 19 7 7 4 3 1

上例定义了一个用来比较大小的接口 IntCompare,这个接口实际上充当了 C 语言中函数指针的功能,在使用时,开发人员可以根据实际需求传入自定义的类。在上例中分别有两个类 Cmp1 和 Cmp2 都实现了这个接口,分别用来在实现升序排序和降序排序时使用。其实这也是策略设计模式所用到的思想。

=======================================

end

发布了95 篇原创文章 · 获赞 20 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_40993412/article/details/104029808