Java 基础面试系列-02

2022年7月17日
大约 12 分钟

Java 基础面试系列-02

1. 什么是不可变对象?有什么好处?

不可变对象是指对象一旦被创建,状态就不能再改变,任何修改都会创建一个新的对象。

比如String、Integer及其它包装类。

不可变对象最大的好处是线程安全。

2. 静态变量和实例变量有什么区别?

静态变量:独立存在的变量,只是位置放在某个类下,可以直接类名加点调用静态变量名使用。并且是项目或程序一启动运行到该类时就直接常驻内存。不需要初始化类再调用该变量。用关键字static声明。静态方法也是同样,可以直接调用。

实例变量:相当于该类的属性,需要初始化这个类后才可以调用。如果这个类未被再次使用,垃圾回收器回收后这个实例也将不存在了,下次再使用时还需要初始化这个类才可以再次调用。

1)存储区域不同:静态变量存储在方法区属于类所有,实例变量存储在堆当中;

2)静态变量与类相关,普通变量则与实例相关;

3)内存分配方式不同。

4)生命周期不同。

需要注意的是从静态变量在jdk7以后和字符串常量池一起存储在了堆中,JDK1.8开始用于实现方法区的PermSpace被MetaSpace取代。

3. Object 类都有哪些公共方法?

1)equals(obj);

判断其他对象是否“等于”此对象。

2)toString();

表示返回对象的字符串。通常,ToString方法返回一个“以文本方式表示”此对象的字符串。结果应该是一个简洁但信息丰富的表达,很容易让人阅读。建议所有子类都重写此方法。

**3)getClass(); ** 返回此对象运行时的类型。

4)wait();

表示当前线程进入等待状态。

5)finalize();

用于释放资源。

6)notify();

唤醒在该对象上等待的某个线程。

7)notifyAll();

唤醒在该对象上等待的所有线程。

8)hashCode();

返回对象的哈希代码值。用于哈希查找,可以减少在查找中使用equals的次数,重写equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。

9)clone();

实现对象的浅复制,实现Cloneable接口才可以调用这个方法,否则抛出CloneNotSupportedException异常。

4. Java 创建对象有哪几种方式?

创建对象构造方法说明
使用new关键字调用构造方法
使用Class类的newInstance方法调用构造方法
使用Constructor类的newInstance方法调用构造方法
使用clone方法没有调用构造方法
使用反序列化没有调用构造方法

5. a==b 与 a.equals(b) 有什么区别?

假设a和b都是对象,则a==b是比较两个对象的引用,只有当a和b指向的是堆中的同一个对象才会返回 true,而a.equals(b)是进行逻辑比较,当内容相同时,返回true,所以通常需要重写该方法来提供逻辑一致性的比较。

多数情况下需要重写这个方法,如String类重写equals()用于比较两个不同对象,但是包含的字母相同的比较:

public boolean equals(Object obj) {
	if (this == obj) {// 相同对象直接返回true
		return true;
	}
	if (obj instanceof String) {
		String anotherString = (String)obj;
		int n = value.length;
		if (n == anotherString.value.length) {
			char v1[] = value;
			char v2[] = anotherString.value;
			int i = 0;
			while (n-- != 0) {
				if (v1[i] != v2[i])
					return false;
				i++;
			}
			return true;
		}
	}
	return false;
}

6. Object 中 equals() 和 hashcode() 有什么联系?

Java的基类Object提供了一些方法,其中equals()方法用于判断两个对象的地址是否相等(可以理解成是否是同一个对象),地址相等则认为是对象相等,hashCode()方法用于计算对象的哈希码。equals()和hashCode()都不是final方法,都可以被重写(overwrite)。

hashCode()方法是为对象产生整型的hash值,用作对象的唯一标识。

hashCode()方法常用于基于hash的集合类,如Hashtable、HashMap等,根据Java规范使用equal()方法来判断两个相等的对象,必须具有相同的hashcode。

将对象放入到集合中时,首先要判断放入对象的hashcode是否已经存在,不存在则直接放入集合。

如果hashcode相等,然后通过equal()方法判断要放入对象与集合中的其他对象是否相等,使用equal()判断不相等,则直接将该元素放入集合中,反之不放入集合中。

7. hashcode() 中可以使用随机数字吗?

hashcode()中不可以使用随机数字,这是因为对象的hashcode值必须是相同的。

8. Java 中 & 和 && 有什么区别?

&是位操作

&&是逻辑运算符

逻辑运算符具有短路特性,而&不具备短路特性。

来看一下代码执行结果:

public class Test{

    static String name;
 
    public static void main(String[] args){
        if(name!=null & name.equals("")){
            System.out.println("ok");
        }else{
            System.out.println("error");
        }
    }
}

执行结果:

Exception in thread "main" java.lang.NullPointerException
	at com.jingxuan.JingXuanApplication.main(JingXuanApplication.java:25)

上述代码执行时抛出空指针异常,若果&替换成&&,则输出日志是error。

9. 一个 .java 类文件中可以有多少个非内部类?

一个.java类文件中只能出现一个public公共类,但是可以有多个default修饰的类。如果存在两个public修饰的类时,会报如下错误:

The public type Test must be defined in its own file

10. Java 中如何正确退出多层嵌套循环?

1)使用lable标签和break方式;

lable是跳出循环标签。

break lable;是跳出循环语句。

当执行跳出循环语句时会跳出循环标签下方循环的末尾后面。

lable:
for(int i=0;i<3;i++){
	for(int j=0;j<3;j++){
		System.out.println(i);
		if(i == 2) {
			break lable;
		}
		
	}
}

上述代码在执行过程中,当i=2时,执行跳出循环语句,控制台只输出i=0和i=1的结果,执行继续for循环后面的代码。

0
0
0
1
1
1
2
执行for后面的程序代码

2)通过在外层循环中添加标识符,比如定义布尔类型bo = false,当bo=true跳出循环语句。

11. 浅拷贝和深拷贝有什么区别?

浅拷贝是指被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。

深拷贝是指被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,并且不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。

12. Java 中 final关键字有哪些用法?

Java代码中被final修饰的类不可以被继承。

Java代码中被final修饰的方法不可以被重写。

Java代码中被final修饰的变量不可以被改变,如果修饰引用类型,那么表示引用类型不可变,引用类型指向的内容可变。

Java代码中被final修饰的方法,JVM会尝试将其内联,以提高运行效率。

Java代码中被final修饰的常量,在编译阶段会存入常量池中。

13. String s = new String("abc"); 创建了几个String对象?

String s = new String("abc"); 创建了2个String对象。

一个是字符串字面常数,在字符串常量池中。

一个是new出来的字符串对象,在堆中。

14. String 和 StringBuffer 有什么区别?

String是不可变对象,每次对String类型进行操作都等同于产生了一个新的String对象,然后指向新的String对象。因此尽量避免对String进行大量拼接操作,否则会产生很多临时对象,导致GC开始工作,影响系统性能。

StringBuffer是对象本身操作,而不是产生新的对象。因此在有大量拼接的情况下,建议使用StringBuffer。

String是线程安全的,而StringBuffer也是线程安全的。

需要注意是Java从JDK5开始,在编译期间进行了优化。如果是无变量的字符串拼接时,那么在编译期间值都已经确定了的话,javac工具会直接把它编译成一个字符常量。比如:

String str = "关注微信公众号" + "“Java精选”" + ",面试经验、专业规划、技术文章等各类精品Java文章分享!";

在编译期间会直接被编译成如下:

String str = "关注微信公众号“Java精选”,面试经验、专业规划、技术文章等各类精品Java文章分享!";

15. Java 中 3*0.1 == 0.3 返回值是什么?

3*0.1==0.3返回值是false

这是由于在计算机中浮点数的表示是误差的。所以一般情况下不进行两个浮点数是否相同的比较。而是比较两个浮点数的差点绝对值,是否小于一个很小的正数。如果条件满足,就认为这两个浮点数是相同的。

System.out.println(3*0.1 == 0.3);
System.out.println(3*0.1);

System.out.println(4*0.1==0.4);
System.out.println(4*0.1);

执行结果如下:

false
0.30000000000000004
true
0.4

分析:3*0.1的结果是浮点型,值是0.30000000000000004,但是4*0.1结果值是0.4。这个是二进制浮点数算法的计算原因。

16. a=a+b 和 a+=b 有什么区别吗?

+=操作符会进行隐式自动类型转换,a+=b隐式的将相加操作结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换。

byte a = 127;
byte b = 127;
a = a + b;
a += b;

a = a + b; 编译报错:

Type mismatch: cannot convert from int to byte

17. Java 中 int a[] 和 int []a 有什么区别?

采用int a[]这种写法是为了沿袭C、C++的写法。

Java中为了说明所有东西都是对象常采用int[] a写法。

18. Java 中 Math. round(-1.5) 等于多少?

Math.round(-1.5)的返回值是-1。

public class Test {
	public static void main(String[] args){
		System.out.println(Math.round(1.3));   //1
		System.out.println(Math.round(1.4));   //1
		System.out.println(Math.round(1.5));   //2
		System.out.println(Math.round(1.6));   //2
		System.out.println(Math.round(1.7));   //2
		System.out.println(Math.round(-1.3));  //-1
		System.out.println(Math.round(-1.4));  //-1
		System.out.println(Math.round(-1.5));  //-1
		System.out.println(Math.round(-1.6));  //-2
		System.out.println(Math.round(-1.7));  //-2
	}
}

四舍五入的原理上参数加0.5,然后做向下取整。也可以按照如下方式:

1、小数点后第一位小于5,运算结果为参数整数部分。 2、小数点后第一位大于5,运算结果为参数整数部分绝对值+1,符号(即正负)不变。 3、小数点后第一位等于5,正数运算结果为整数部分+1,负数运算结果为整数部分。

19. String 类的常用方法都有哪些?

String 类型常用方法有很多,见表格内容:

返回类型方法名功能说明
intlength()得到字符串的字符个数
byte[]getByte()将字符串转换成字节数组
char[]toCharArray()将字符串转换成字符数组
Stringsplit(String)将字符串按照指定内容劈开
booleanequals()判断两个字符串的内容是否相同,可以重新equals
booleanequalsIgnoreCase(String)忽略太小写比较两个字符串的内容是否相同
booleancontains(String)判断字符串里面是否包含指定的内容
booleanstartsWith(String)判断字符串是否以指定的内容开头
booleanendsWith(String)判断字符串是否以指定的内容结尾
StringtoUpperCase()将字符串全部转换成大写
StringtoLowerCase()将字符串全部转换成小写
Stringreplace(String,String)将某个内容全部替换成指定内容
StringreplaceAll(String,String)将某个内容全部替换成指定内容,支持正则
StringrepalceFirst(String,String)将第一次出现的某个内容替换成指定的内容
Stringsubstring(int)从指定下标开始一直截取到字符串的最后
Stringsubstring(int,int)从下标x截取到下标y-1对应的元素
Stringtrim()去除字符串的前后空格
charcharAt(int)得到指定下标位置对应的字符
intindexOf(String)得到指定内容第一次出现的下标
intlastIndexOf(String)得到指定内容最后一次出现的下标

还包括valueOf()方法,由基本数据型态转换成String:String类别中已经提供了将基本数据型态转换成String的static方法 ,也就是String.valueOf()这个参数多载的方法,有以下几种:

1)String.valueOf(boolean b): 将boolean变量b转换成字符串

2)String.valueOf(char c): 将char变量c转换成字符串

3)String.valueOf(char[] data): 将char数组data转换成字符串

4)String.valueOf(char[] data, int offset, int count): 将char数组data中由data[offset]开始取count个元素转换成字符串

5)String.valueOf(double d): 将double变量d转换成字符串

6)String.valueOf(float f): 将float变量f转换成字符串

7)String.valueOf(int i): 将int变量i转换成字符串

20. Java 中 IO 流有哪几种?

IO流从功能角度可以分为输入流(input)、输出流(output)。

IO流从类型角度可以分为字节流和字符流。

字符流和字节流是根据处理数据的不同来区分的。字节流按照8位传输,字节流是最基本的,所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。

字节流按8位传输以字节为单位输入输出数据,字符流按16位传输以字符为单位输入输出数据。

字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串。

字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。

字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节, 操作字节和字节数组。因此字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,对多国语言支持性比较好。

字节流和字符流分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四个):InputStream、OutputStream、Reader、Writer。