Java基础——常见面试题汇总

目录

1、Java语言有哪些优点

2、Java与C/C++的异同

3、面向对象有哪些特征

4、面向对象的开发方式有哪些优点

5、重载和覆盖

6、多态

7、抽象类(abstrackt class)与接口(interface)有什么异同

8、JVM、JRE、JDK

9、Java的跨平台性

10、==、equals的hashCode有什么区别

11、标记接口

12、clone方法

13、反射机制

14、final、finally和finalize

15、switch语句

16、volatile关键字的作用

17、instanceof关键字的作用

18、Math类中round、ceil和floor方法

19、String、StringBuilder和StringBuffer

20、finally代码块

21、try-catch-finally 中哪个部分可以省略?

22、异常处理机制

23、File和Files、Path和Paths

24、NIO


 

1、Java语言有哪些优点

  • Java为纯面向对象语言。Everything is ject.用对象直接反应现实生活,使编程变的更为容易。
  • 平台无关性。Java为跨平台语言,可实现“一次编译,到处运行”。
  • Java内置了很多优秀的类库,通过这些类库,可以简化开发。例如:Java提供了对多线程的支持,提供了对网络通信的支持。
  • 提供了对Web应用开发的支持,例如,Applet、Servlet和JSP可以用来开发Web应用。
  • 具有较好的安全性和健壮性。Java的强类型机制、垃圾回收、异常处理和安全检查机制都使得Java程序有很好的健壮性。
  • 与C++相比,去除了很多特性,像指针、头文件、结构等,使程序更加简洁。

2、Java与C/C++的异同

  • Java为解释性语言,而C/C++为编译性语言。
  • Java可以跨平台运行,而C/C++不能。
  • Java为纯面向对象语言,所有代码都必须实现在类中。
  • 与C/C++相比,Java没有指针的概念。
  • Java不支持多继承。
  • Java提供了垃圾回收器来实现垃圾的自动回收,不需要开发人员去管理对内存的分配。
  • Java提供对注释文档的内建支持。
  • Java维护了一些标准的库,而C++则依靠一些非标准的、第三方提供的库。

3、面向对象有哪些特征

  • 抽象。
  • 继承。一个类(子类)可以由另一个类(父类)派生而来,这个过程叫做类继承。子类拥有父类的特性。继承增加了类的复用性。
  • 封装。封装是指将客观事物抽象成类,每个类对自身的数据和方法实行保护。类中的数据和方法可以对外提供有限的访问权限。
  • 多态。多态是指允许不同类的对象对同一消息做出响应。多态增加了程序的灵活性。

4、面向对象的开发方式有哪些优点

  • 较高的开发效率。将现实事物抽象成对象,从而以接近人的思维过程的方式去解决问题,这种方式直观明了,阅读性也强。
  • 高可维护性。采用面向对象的开发方式,使得代码的可读性非常好,同时面向对象的设计模式也使得代码的结构更加清晰明了。同时针对面向对象的开发方式已有许多非常成熟的设计模式,这些设计模式在开发特定模块时中只要适当的修改就可以满足需求,因此维护起来会非常方便。

5、重载和覆盖

重载(overload)和覆盖(override)是Java多态性的不同表现方式。其中重载是在一个类中多态性的一种表现,是指在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型。

  • 重载
  1. 重载是通过不同的方法参数来区分的,例如不同的参数个数、不同的参数类型或不同的参数顺序。
  2. 不能通过方法的访问权限、返回值类型和抛出的异常类型来进行重载。

覆盖是指派生类函数覆盖基类函数。覆盖一个方法并对其重写,以达到不同的作用。

  • 覆盖
  1. 派生类中的覆盖方法必须要和基类中被覆盖的方法有相同的函数名和参数。
  2. 派生类中的覆盖方法的返回值必须和基类中被覆盖的方法的返回值相同。
  3. 派生类中的覆盖方法所抛出的异常必须和基类(或是其子类)中被覆盖的方法抛出的异常一致。
  4. 基类中被覆盖的方法不能为private,且派生类中的覆盖方法的权限必须大于等于被覆盖的方法。

6、多态

多态是面向对象程序设计中一个重要机制,它表示当同一个操作作用在不同的对象时,会有不同的效果。

在Java中,多态有两种主要的表现方式:

  • 方法的重载(overload)。重载是指同一个类中有多个同名的方法,但这些方法有着不同的参数,因此在编译时就可以确定到底调用到哪个方法,它是一种编译时多态。重载可以被看作一个类中的方法多态性。
  • 方法的覆盖(override)。在Java语言中,基类的引用可以指向基类的实例对象,也可以指向其子类的实例对象。同样,基类也可以用接口代替。而用引用调用的方法在运行期才动态的绑定,就是引用指向的实际在内存中正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。通过这种动态绑定实现了多态(即可看成不同的类可以对同一消息进行处理)。这种多态也可以被称为运行时多态。

7、抽象类(abstrackt class)与接口(interface)有什么异同

  • 抽象类

在Java中可以通过把类或者类中的某些方法声明为abstract来表示一个类或方法是抽象的。

  1. 如果一个类含有抽象方法,那么这个类必须声明为抽象类。
  2. 没有抽象方法的类,也可以将其声明为抽象类。
  3. 声明为抽象类的类不能被实例化,即使类中没有抽象方法。
  4. 子类继承抽象类用extends关键字,并且只有在实现了抽象类的抽象方法后才能实例化,否则,子类也应该声明为抽象类。
  5. abstract关键字不能与final关键字一起使用。
  • 接口

接口是指一个方法的集合,接口中的所有方法都没有方法体,在Java语言中,接口是通过关键字interface来实现的。

  1. 接口中所有方法都为抽象方法,其所有成员方法默认都是public abstract的,而且只能被这两个关键字修饰,且关键字可以适当省略
  2. 接口中可以不包含任何方法,这种接口被称为标记接口,像Cloneable、Serializeable都是标记接口;
  3. 接口中的成员变量默认为public static final的,关键字可以适当省略,但必须给变量赋初始值;
  4. 类可以用关键字implements实现接口,接口可以用extends继承接口。

8、JVM、JRE、JDK

  • JVM:英文名称(Java Virtual Machine),即Java虚拟机。它包含编译器、解释器、库管理器、类加载器、程序验证、等组件。在运行时,Java源文件(*.java),通过java编译器(javac)编译生成一个ByteCode字节码文件(*.class),虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的目标机器码,然后在特定的机器上运行。

编译器:标准Java编译器。运行CREATE JAVA SOURCE语句时,它会将Java源文件转换为体系结构中立的单字节指令,称为字节码。
解释器:为了运行Java程序,Oracle JVM包括一个标准的Java2字节码解释器。解释器和关联的Java运行时系统运行标准Java类文件。运行时系统支持本机方法以及来自主机环境的call-in和call-out。
库管理器:将java源,类和资源文件加载到数据库中
class loader:在行j时找到java类,并为Oracle JVM加载和初始化java类
验证程序:与oracle和java安全性一起使用以验证java类

  • JRE:英文名称(Java Runtime Environment),Java运行时环境。它主要包含两部分:jvm的标准实现和Java的一些基本类库。
  • JDK:英文名称(Java Development Kit),Java开发工具包。相对于JRE,JDK集成了JRE,并且多了一些开发类的工具像:javac.exe,jar.exe,javadoc.exe等。

显然,这三者之间有着嵌套的关系,可以用一张图来表示:

9、Java的跨平台性

由问题1中,我们了解到Java为解释型语言,并且知道了Java程序的执行过程:即,程序源代码经过Java编译器编译成字节码,然后由JVM解释为目标平台的机器码,在特定平台上运行。

目标平台指的是特定机器和特定的操作系统的总体。

 Java的跨平台性体现在JVM上,JVM有多个版本以适应不同的平台。对Java程序来说,不同操作系统写的Java程序经编译后都生成JVM能够识解释执行的字节码,然后拿着字节码在装有JVM的系统上就都可以运行这个Java程序了。

10、==、equals的hashCode有什么区别

  • ==运算符用来比较两个变量的值是否相等。也就是说,它用于比较变量对应的内存中所存储的值是否相等。例如:如果等号两边是同基本数据类型,则判断两个基本数据类型表示的数值是否相等,像1==1、1.0==1.0(浮点型一般不直接用==判断相等);如果等号两边是同类型应用变量,则判断其指向的是否为同一对象。

同基本数据类型:像1==true,一个为int型,一个为boolean型,编译报错。

同类型应用变量:相同类的对象,或有继承关系的两个对象。

  • equals是Object类提供的方法之一。每个Java类都继承自Object类,所以每个对象都具有equals方法。Object类中定义的equals(Object)方法是直接使用“==”运算符比较两个对象,所以在没有覆盖equals(Object)方法的情况下,equals(Object)与“==”运算符一样,比较的是引用。如果编写的类希望能够比较该类创建的两个实例对象的内容是否相同,那么必须覆盖equals()方法,并且编写判断两个类相同的条件。
  • hashCode用来计算类的hash值,如果元素在hashmap中充当键时,也用于求取键的存取位置。

 hashCode和equals的关系:①如果用equals判断两个对象返回true,则调用两个对象的hashCode方法返回的结果一定相同,即:如果x.equals(y)==true,则x.hashCode()==y.hashCode();②如果用equals判断两个对象返回false,调用两个对象的hashCode方法返回的结果可能相同;③反之,调用两个对象的hashCode方法返回相同的结果,而用equals判断两个对象可能返回false。④而,调用两个对象的hashCode方法返回不同的结果,用equals判断两个对象一定返回false。

11、标记接口

在Java语言中,有些接口内部没有声明任何方法,也就是说,实现这些接口的类不需要重写任何方法,这些没有任何方法声明的接口又被叫做标识接口。

标识接口对实现它的类没有任何语义上的要求,它仅仅充当一个标识作用,用来标识实现它的类是否属于一个特定的类型。Java类库中已存在的标识接口有CloneableSerializalble等。在使用时会经常用instanceof来判断实例对象的类型是否实现了一个给定的标识接口。

12、clone方法

  • clone()方法是Object类默认提供的protect权限的方法。这个方法的作用是返回一个Object对象的复制。这个复制的对象系一个新的对象。
  • clone()方法的使用
  1. 实现clone的类首先需要继承Cloneable接口。
  2. 在类中重写Object类中的clone()方法。
  3. 在clone()方法中调用super.clone()返回浅复制的对象。
  • 深复制浅复制的详解见下文

详解Java中的clone方法——张纪刚

  • clone方法的实现原理:
  1. Object中的clone执行的时候使用了RTTI(run-time type identification)的机制,动态的找到了调用clone方法的对象的类的信息。
  2. 在调用Object的clone()方法返回clone类时,要是clone类没有实现Cloneable接口,则会导致抛出CloneNotSupportedException 异常,所以,Object中的clone方法对动态获取的类信息进行了判断。

13、反射机制

  • 反射机制提供的功能有:
  1. 得到一个对象所属的类;
  2. 获取一个类的所有成员变量和方法;
  3. 在运行时创建对象;
  4. 在运行时调用对象的方法。
  • 获取一个类的Class对象方法:
  1. Class.forName("类的限定名称");
  2. 类名.Class;
  3. 实列.getClass()。

14、final、finally和finalize

  • final可以用于声明成员变量、方法和类
  1. 修饰成员变量时:若成员变量为非静态的,则可以在声明时、初始化块或者构造函数中进行初始化。若成员变量为静态的,则需要在声明时或者在静态初始化块中初始化赋值。且,成员变量一经初始化后就不能再重新赋值。
  2. 修饰方法时:当一个方法声明为final时,该方法不允许任何子类重写这个方法,但子类仍然可以使用这个方法。
  3. 修饰类时:表示该类不可以被继承。
  • finally是异常处理的一部分,他只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行。
  • finalize是Object类的一个protect权限的方法。此方法在垃圾回收器回收对象内存前调用,可以用来回收资源。(但一般不建议用其进行资源回收,使用try-finally或者其他方式可能更好)。

finalize存在的问题:java.lang.ref.FinalizerReference引发的内存泄漏

15、switch语句

switch语句的格式如下:

switch(expression){
    case value :
       //语句
       break; //可选
    case value :
       //语句
       break; //可选
    //你可以有任意数量的case语句
    default : //可选
       //语句
}

switch case 语句有如下规则:e

  • switch 语句中的变量类型可以是: byte、short、int 或者 char。从Java SE5开始,switch支持enum类型,从 Java SE 7 开始,switch 支持字符串 String 类型了,同时 case 标签必须为字符串常量或字面量。

  • switch 语句可以拥有多个 case 语句。每个 case 后面跟一个要比较的值和冒号。

  • case 语句中的值的数据类型必须与变量的数据类型相同,而且只能是常量或者字面常量。

  • 当变量的值与 case 语句的值相等时,那么 case 语句之后的语句开始执行,直到 break 语句出现才会跳出 switch 语句。

  • 当遇到 break 语句时,switch 语句终止。程序跳转到 switch 语句后面的语句执行。case 语句不必须要包含 break 语句。如果没有 break 语句出现,程序会继续执行下一条 case 语句,直到出现 break 语句。

  • switch 语句可以包含一个 default 分支,该分支一般是 switch 语句的最后一个分支(可以在任何位置,但建议在最后一个)。default 在没有 case 语句的值和变量值相等的时候执行。default 分支不需要 break 语句。

16、volatile关键字的作用

程序在执行时,可能会把经常被访问的变量缓存进CPU的高速缓存当中,程序在读取这个变量时就直接从他的高速缓存中读取、写入数据,当运算结束后再将高速缓存中的数据刷新到主存中去。在单线程中这样会提高程序的运行效率,但在多线程中,变量的值可能会因别的线程而改变,但缓存中的值却不会相应的改变,从而会造成应用程序读取的值和实际的变量值不一致。

volatile关键字修饰的变量,系统每次用到它时都是直接从对应的内存中提取,而不会利用缓存。它保证了如果有线程对变量进行了修改,其他线程对此次修改内容可见。

volatile关键字的详解可参考下文

Java并发编程:volatile关键字解析

17、instanceof关键字的作用

用法:boolean result=object instanceof class;

它用来判断一个引用类型的变量(object)所指向的对象是否是一个类(class)(或接口、抽象类、父类)的实例。

18、Math类中round、ceil和floor方法

  • round放法表示四舍五入。其实现方法为:在原来数字的基础上先增加0.5然后再向下取整。

Math.round(11.5)得12;Math.round(-11.5)得11。

  • ceil方法的功能是向上取整。若a为正数,则把小数“入”,若a为负数,则把小数“舍”。

Math.ceil(11.5)得12;Math.ceil(-11.5)得11。

  • floor方法是向下取整。若a为正数,则把小数“舍”,若a为负数,则把小数“入”。

Math.floor(11.5)得11;Math.floor(-11.5)得12。

注意:运算结果都为double类型。

19、String、StringBuilder和StringBuffer

  • String是不可变类,也就是说,String对象一旦被创建,其值将不能更改。而StringBuilder和StringBuffer是可变类,当对象被创建后仍然可以对其值进行修改。

当一个字符串经常需要被修改时,最好使用StringBuilder或StringBuffer来实现。如果用String来保存一个经常被修改的字符串时,在字符串被修改时会比StringBuilder和StringBuffer多很多附加操作,同时生成很多无用的对象,由于这些无用的对象会占据内存,并且会被垃圾回收器回收,因此会影响程序的性能。

例如如下程序:

public class Test {

	public static void main(String[] args) {
		testString();
		testStringBuilder();
	}
	public static void testString() {
		String s="Hello";
		String s1="world";
		for(int i=0;i<10000;i++) {
			s+=s1;
		}
		System.out.println(s);
	}
	public static void testStringBuilder() {
		StringBuilder sb=new StringBuilder("Hello");
		String s1="world";
		for(int i=0;i<10000;i++) {
			sb.append(s1);
		}
		System.out.println(sb.toString());
	}
}

testString()方法执行比testStringBuilder()要慢很多,因为testString每次+操作都会在内存中创建一个新的对象,相当于内存中存在Helloworld、Helloworldworld……而且,每次创建对象的过程也消耗了时间。反观testStringBuilder()方法就要块很多,它全程在内存中只生成了三个字符串对象(Hello、world和sb.toString()生成的字符串)。

用String类创建字符串对象和其在内存中的存储机制,可参考下文:

Java字符串的创建与存储机制

  • StringBuilder和StringBuffer都是字符串的缓冲区,但是StringBuilder不是线程安全的,如果只是在单线程中使用字符串缓冲区,那么StringBuilder的效率会高一些。当有多个线程访问时,最好使用StringBuffer来确保线程安全。

20、finally代码块

  • 一般情况下finally代码块会在return前执行。
    public abstract class Test {
    
    	public static int test() {
    		try {
    			return 1;
    		}catch (Exception e) {
    			return 0;
    		}finally {
    			System.out.println("exe...");
    		}
    	}
    	public static void main(String[] args) {
    		int result=test();
    		System.out.println(result);
    	}
    }

    程序运行结果为exe... 1。

  • finally代码块中的return会覆盖别处的return。

public abstract class Test {

	@SuppressWarnings("finally")
	public static int test() {
		try {
			return 1;
		}catch (Exception e) {
			return 0;
		}finally {
			System.out.println("exe...");
			return 3;
		}
	}
	public static void main(String[] args) {
		int result=test();
		System.out.println(result);
	}
}

运行结果为exe... 3。

  • finally代码块中改变返回值的情况。
public abstract class Test {

	public static int test1() {
		int result = 1;
		try {
			result = 2;
			return result;
		} catch (Exception e) {
			return 0;
		} finally {
			result = 3;
			System.out.println("exe...one");
		}
	}
	public static StringBuilder test2() {
		StringBuilder sb=new StringBuilder("Hello");
		try {
			return sb;
		} catch (Exception e) {
			return null;
		}finally {
			sb.append(" World");
			System.out.println("exe...two");
		}
	}
	public static void main(String[] args) {
		int result1 = test1();
		System.out.println(result1);
		StringBuilder result2 = test2();
		System.out.println(result2);
	}
}

运行结果为exe...one 2 exe...two Hello World。可以看出程序在返回前会将返回的变量存在一个副本中,当执行完finally块中的代码后再将存起来的值返回,如果finally块中改变的时变量的值,则不会对返回结果造成影响,但是如果finally块中改变的时变量指向的对象的内容时(前提时返回的是引用),则会影响返回的结果。

  • finally代码块不被执行的情况
  1. 当程序进入try语句之前就出现异常时,会直接结束,不会执行finally代码块。
public abstract class Test {

	public static int test1() {
		int result = 5/0;
		try {
			return result;
		} catch (Exception e) {
			return 0;
		} finally {
			System.out.println("exe...one");
		}
	}
	public static void main(String[] args) {
		int result1 = test1();
		System.out.println(result1);
	}
}

2.当程序在执行finally块之前强制退出时。

public abstract class Test {

	public static int test1() {
		try {
			System.out.println("try..");
			System.exit(0);
		} catch (Exception e) {
			System.out.println("catch...");
		} finally {
			System.out.println("finally...");
		}
		return -1;
	}
	public static void main(String[] args) {
		int result1 = test1();
		System.out.println(result1);
	}
}

21、try-catch-finally 中哪个部分可以省略?

try-catch-finally 其中 catch 和 finally 都可以被省略,但是不能同时省略,也就是说有 try 的时候,必须后面跟一个 catch 或者 finally。

22、异常处理机制

异常是指程序运行时(非编译时)所发生的非正常情况或错误,当程序违反了语义规则时,JVM就会将出现的错误表示为一个异常并抛出。这个异常可以在catch块中进行捕获,然后进行处理。

Java语言把异常当作对象来处理,并定义了一个基类(java.lang.Throwable)作为所有异常的父类。在Java API中,已经定义了许多异常类,这些异常类分为Error(错误)和Exception(异常)两大类。

  • Error表示程序在运行期间出现了非常严重的错误,并且该错误不可恢复,由于这属于JVM层次地严重错误,因此这种错误是会导致程序终止执行地。此外,编译器不会检查Error是否被处理,因此在程序中不用去捕获Error类型的异常。OutOfMemoryError、ThreadDeath等都属于错误,这些异常发生时,JVM一般会选择将线程终止。
  • Exception表示客恢复的异常,是编译器可以捕获到的。它包含两种类型:检查异常(checked exception)运行时异常(runtime excetpion)。

检查异常

所有继承自Exception并且不是运行时异常的异常都是检查异常,比如最常见的IO异常和SQL异常。这种都发生在编译阶段,Java编译器强制要求程序去捕获此类型的异常(用try catch块)或者向上级抛出(用throws关键字)。异常的捕获处理后程序可以继续执行后续操作。

运行时异常

运行时异常可以不用进行捕获和处理。如果不对这种异常进行处理,当出现这类异常时,会由JVM来处理,例如NullPointerException异常。常见的运行时异常包括NullPointerException(空指针异常)、ClassCastException(数据类型转换异常)、IndexOutOfBoundsException(数组下标越界异常)等。

出现运行时异常后,系统会把异常一直往上层抛出,直到遇到处理代码为止。若没有处理块,则抛到最上层;如果是多线程就用Thread.run()方法抛出,如果是单线程,就用main()方法抛出。抛出之后,如果时线程,则这个线程退出。如果是主程序,那么整个程序退出。

catch块

Java异常处理用到了多态的概念,如果在异常处理过程中,先捕获到了基类,然后再捕获到子类,那么捕获子类的代码块永远执行不到。因此,在进行异常捕获时,应先捕获子类,再捕获基类的异常信息。如下:

try{
    //code
}catch(SQLException e1){
    //deal code
}catch(Exception e2){
    //deal code
}

 

23、File和Files、Path和Paths

  • File类自JDK1.0开始就存在,通过File类不仅可以查看文件或目录的属性,而且可以实现对文件或目录的创建、删除与重命名等操作。下边是File类的一些方法:
  1. File(String pathname):构造函数,根据指定的路径(pathname)创建一个File对象。
  2. createNewFile():若目录或文件存在,则返回false,否则创建文件或文件夹。
  3. delete():删除文件或文件夹。
  4. isFile():判断这个对象表示的是否是文件。
  5. isDirectory():判断这个对象表示的是否是文件。
  6. listFiles():若对象代表目录,则返回目录中所有文件的File对象。
  7. mkdir():根据当前对象指定的路径创建目录。
  8. exists():判断对象对应的文件是否存在。
  • Files类是JDK1.7新增的类,该类完全由操作文件、目录或其他类型文件的静态方法组成。下边是Files类的一些方法:
  1. copy():实现将一个源里的数据复制到另一个源中。源可以是文件或者输入输出流。
  2. createDirectories()、createFile()、createDirectory():创建文件夹或文件。
  3. delete()、deleteIfExists():删除文件。
  4. exists():判断文件是否存在。
  5. isDirectory():判断是否是文件夹。
  6. isExecutable():判断文件是否可执行。
  • Path类也是JDK1.7新增功能,它是一个接口,该接口用于在文件系统中定位文件。它通常表示一个依赖于系统的文件路径。使用空路径访问文件等同于访问文件系统的默认目录。Path定义getFileName、getParent、getRoot和subpath方法来访问路径组件或其名称元素的子序列。toPath方法可用于从java.io.File对象表示的抽象路径名中获取Path。生成的Path可用于在与java.io.File对象相同的文件上运行。此外,toFile方法对于从Path的String表示构造File非常有用。
  • Paths类是和path类一起新添加的,该类完全由静态方法组成,这些方法通过转换路径字符串或URI返回路径(Path类)的实例。

24、NIO

JDK 1.4的java.nio.*包中引入了新的JavaI/O类库,其目的在于提高速度。速度的提高在文件I/O和网络I/O中都有体现。

NIO具体参考下文:

NIO相关基础篇

Java NIO?看这一篇就够了!

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值