JAVA面试—基础题(2021面试必备)(最全+详细答案)

JAVA面试—基础题+答案

JAVA:
JAVA面试—容器(2021面试必备)(最全+详细答案)学好容器一篇就够了!

1.JDK 和 JRE 有什么区别?

JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。
JDK 包含了 JRE和java开发工具包。所以说:如果你只是想要需要运行一个现有的 java 程序,只需安装 JRE 就可以了,如果你需要编写 并运行java 程序,需要安装 JDK。

2.Java和C++的区别

很多人都在学习java之前学习了c语言和c++语言,我们来说说c++和java的区别
Java和C++的区别:

  1. Java源码会先经过一次编译,成为中间码,中间码再被解释器解释成机器码。对于Java而言,中间码就是字节码(.class),而解释器在JVM中内置了。
  2. C++源码一次编译,直接在编译的过程中链接了,形成了机器码。
  3. C++比Java运行得更加快,但是Java可以利用JVM跨平台。
  4. Java是纯面向对象的语言,所有代码(包括函数、变量)都必须在类中定义。而C++中还有面向过程(兼容c语言)的东西,比如是全局变量和全局函数。
  5. C++中有指针,Java不提供指针来直接访问内存,程序内存更加安全。
  6. Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
  7. C++运算符可以重载,但是Java中不可以。
  8. Java有自动内存管理机制,不需要程序员手动释放无用内存,c++需要自己手动释放内存。

3.面向对象的三个基本特征是什么?

面向对象的三个基本特征是:封装、继承和多态。

继承:让某个类型的对象获得另一个类型的对象的属性的方法。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,使得子类具有父类相同的行为,子类能继承父类的方法,也可以重写父类中的方法。
比如苹果继承的水果的一些特征,但是苹果也有自己的一些独特的特征,这就是继承,我们可以理解为:什么是什么,这里就是苹果是水果。
代码如下:

public class fruit
{
	public String name="水果";
}
public class  apple extends fruit{//使用extends进行继承
       public String name="苹果";  
}

封装:隐藏部分对象的属性和实现细节,对数据的访问只能通过外公开的接口。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
比如我们把对象内部变量定义为私有类型,则我们只能调用对象内部对应的方法来对变量进行修改。

/*
 * public class fruit {
	private String Name="苹果";  
}
 */
public class  ceshi 
{
	public static void main(String[] arg)
	{
		fruit p=new fruit();
			p.Name="西瓜";//我们在测试类中企图修改fruit对象中的Name时会报错
    }   
}

多态:对于同一个行为,不同的子类对象具有不同的表现形式。多态存在的3个条件:1.继承;2.重写;3.父类地址指向子类对象。

/*
 * public class fruit {
	private String Name="苹果";  
}
 */
public class  pingguo extends fruit 
{
	public static void main(String[] arg)
	{
		fruit q=new pingguo();
    }   
}

4.访问修饰符public,private,protected,以及不写时的区别?

在这里插入图片描述

5.自动类型转换和强制类型转换。

自动类型转换如下:
在这里插入图片描述
自动类型转换不能变为比自己级别小的:

public static void main(String[] arg)
	{
		double a=9.9;
		int b=a;//int级别比double低无法自动转换
		int c=10;
		double d=c;//double级别比int高,可以自动转换
    }   

强制类型转换可以任意级别转换,但是有可能会损失精度,谨慎使用:

public static void main(String[] arg)
	{
		double a=9.9;
		int b=(int)a;//此时a会损失所有的小数位,变为9。
		System.out.println(b);
    }   

6.java中的位运算(移位、位与、或、异或、非)

我们的位运算都是基于二进制的!!!二进制!!二进制!!
移位:二进制位进行移动。语言不好讲,代码很清晰,我们直接看代码。

        int a=5<<2;//5向左移2位
		System.out.println(a);//输出20
		int b=5>>2;//5向右移2位
		System.out.println(b);

为什么5向左移2位输出20呢?
首先会将5转为2进制表示形式(整数默认就是int类型,也就是32位):
0000 0000 0000 0000 0000 0000 0000 0101 然后左移2位后,低位补0:
0000 0000 0000 0000 0000 0000 0001 0100 换算成10进制为20
右移同理。
相当于就是你需要移动的数字转成二进制然后移动对应的位数再换成10进制。左移一位相当于乘以2(在范围内),右移相当于除以2(在范围内),你不要开个int左移50位说为什么不是乘以2^50次方。
位与:&(位与)的性质是1&1=1,0&1=0,1&0=0,0&0=0,只有二者都为1时取1。

        int a=5&6;
        System.out.println(a);//输出4
		int b=5&3;
		System.out.println(b);//输出1

我们对5,6转成二进制每一位进行与运算即可得到答案。
5转换为二进制:0000 0000 0000 0000 0000 0000 0000 0101
6转换成二进制:0000 0000 0000 0000 0000 0000 0000 0110
4转换成二进制:0000 0000 0000 0000 0000 0000 0000 0100
5&3同理。
位或:|(位或)的性质是1|1=1,1|0=1,0|1=1,0|0=0,二者只要有1则为1。

        int a=5|6;
		System.out.println(a);//输出7
		int b=5|3;
		System.out.println(b);//输出7

我们对5,6转成二进制每一位进行或运算即可得到答案。
5转换为二进制:0000 0000 0000 0000 0000 0000 0000 0101
6转换成二进制:0000 0000 0000 0000 0000 0000 0000 0110
7转换成二进制:0000 0000 0000 0000 0000 0000 0000 0111
5|3同理
位异或^(异或)的性质是1异或1=0,0异或1=1,1异或0=1,0异或0=0,只有二者不同时才为1

        int a=5^6;
		System.out.println(a);//输出3
		int b=5^3;
		System.out.println(b);//输出6

我们对5,6转成二进制每一位进行异或运算即可得到答案。
5转换为二进制:0000 0000 0000 0000 0000 0000 0000 0101
6转换成二进制:0000 0000 0000 0000 0000 0000 0000 0110
3转换成二进制:0000 0000 0000 0000 0000 0000 0000 0011
5^3同理
:~(非)的性质是非0=1,非1=0.

        int a=~5;
		System.out.println(a);//输出-6
		int b=~-4;
		System.out.println(b);//输出3

我们对5转成二进制每一位进行取反运算即可得到答案。
5转换为二进制:0000 0000 0000 0000 0000 0000 0000 0101
-6转换为二进制:1111 1111 1111 1111 1111 1111 1111 1010(实在对不齐,可以自己画在纸上);
注意:第一位表示符号位,0为正,1为负,把5中的0全变为1,1全变为0可以得到-6。
对-4取反同理。

7.== 和 equals 的区别是什么?

== 解读
对于基本类型和引用类型 == 的作用效果是不同的:
基本类型:比较的是值是否相同;
引用类型:比较的是引用(地址)是否相同;
代码示例:

   int p1=5;
   int p2=5;
   int p3=6;
   String x = "string";
   String y = "string";
   String z = new String("string");
   System.out.println(x==y); // true
   System.out.println(x==z); // false
   System.out.println(p1==p2); // true
   System.out.println(p2==p3); // false
   //我们来看看他们分别对应的地址有什么区别(以自己电脑上的显示的值为准,这里只是参考)
   System.out.println(System.identityHashCode(p1));//1435804085
   System.out.println(System.identityHashCode(p2));//1435804085
   System.out.println(System.identityHashCode(p3));//1784662007
   System.out.println(System.identityHashCode(x));//997110508
   System.out.println(System.identityHashCode(y));//997110508
   System.out.println(System.identityHashCode(z));//509886383
   //当常量池中已经有对应的值,比如这里的p2=5,y = "string";
   //java将不会再开辟新的地址,而是直接将原有的地址赋值给当前的地址;
   //new是无条件开辟一个新地址用来存值;

代码解读:因为p1和p2的值相同,p2和p3的值不相同,所以p1== p2是true,p2== p3是false。因为 x 和 y 指向的是同一个引用,所以 x==y是 true,而 new String()方法则重写开辟了内存空间,所以 x == z 结果为 false,而 equals 比较的一直是值,所以结果都为 true。
equals解读
equals本质就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较。

这是默认情况下的 equals 比较一个有相同值的对象,代码如下:

 public person(String name) {
	        this.name = name;
	    }

	    private String name;

	    public String getName() {
	        return name;
	    }

	    public void setName(String name) {
	        this.name = name;
	    }
	    person p1 = new person("李四");
    	person p2 = new person("李四");
    	System.out.println(p1.equals(p2)); // false

为什么输出结果是false呢?这是怎么回事,看一下 equals 源码就知道了,源码如下:

public boolean equals(Object obj) {
		return (this == obj);
}

原来java中equals就是相当于==。
然后我们创建String类来测试一下,代码如下:

String s1 = new String("张三");
String s2 = new String("张三");
System.out.println(s1.equals(s2)); // true

根据上面的结论不应该是false吗?? 为什么输出了true。
不急,我们先去查看一下 String 的 equals 方法,就能够找到答案,代码如下:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        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;
}

原来是 String 重写了 Object 的 equals 方法,把引用比较改成了值比较。

总结 :== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类给我们重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。
注意:Integer在-128到+127之间会被当做基本类型处理。
代码如下

        Integer p1=128;
    	Integer p2=128;
    	System.out.println(p1==p2);//false
    	Integer p3=127;
    	Integer p4=127;
    	System.out.println(p3==p4);//true

8.两个对象的 hashCode()相同,则 equals()也一定为 true?

结论:

两个对象equals相等,则它们的hashcode必须相等,反之则不一定。
两个对象==相等,则其hashcode一定相等,反之不一定成立。
代码如下:

        String str1 = "通话";
    	String str2 = "重地";
    	System.out.println(String.format(str1+"  "+str1.hashCode()));
    	System.out.println(String.format( str2+"  "+str2.hashCode()));
    	System.out.println(str1.equals(str2));//两个字符串不相同equals明显为false

9.final 在 java 中有什么作用?

(1)修饰类:表示该类不能被继承;

public final class fu {	
}
public class zi extends fu {//报错zi无法继承带有final修饰的父类	
}

(2)修饰方法:表示方法不能被重写;

public  class fu {
	public final void show()
	{
		System.out.println(66);
	}
}
public class zi extends fu {
	public void show()//无法重写带有final修饰的方法
	{
		System.out.println(666);
	}
}

(3)修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。

final int a=100;
    	a=200;//无法修改带有final修饰的变量

10.String 属于基础的数据类型吗?

在Java中,数据类型分为引用类型和基本类型,基本类型分为八种

整型:byte,short,int, long

浮点型:float,double

字符型:char

Boolean型:boolean

String不是基本的数据类型,是final修饰的java类,是引用类型。

11.java中的一些math函数

        System.out.println(Math.abs(-3.0)); //3.0   绝对值
		System.out.println(Math.sqrt(36.0));  //6.0  开根号 
		System.out.println(Math.max(58,78)); //78   两者之间较大的 
		System.out.println(Math.min(51,78)); //51  两者之间较小的
		System.out.println(Math.random());  // [0,100) 半开半闭  随机数
		System.out.println(Math.pow(2,9));  // 512.0   幂
		System.out.println(Math.ceil(18.01));  // 19.0  向上取整
		System.out.println(Math.floor(18.99));  // 18.0  向下取整
		System.out.println(Math.round(10.5));  // 11   四舍五入
		System.out.println(Math.round(-11.2));  // -11  四舍五入

12.java 中操作字符串都有哪些类?有什么区别?

String
StringBuffer
StringBuilder
1:java中操作字符串有三个类,分别是String,StringBuffer和StringBuilder
2:.这三个类都是以字符数组char[]的形式保存的字符串,但是String类型的字符串是不可变的,对String类型做修改操作都是相当于重新创建对象.而对StringBuffer和StringBuilder进行增删操作都是对同一个对象做操作.
3:StringBuffer中的方法很多都使用synchronized关键字修饰,所以StringBuffer是线程安全的,StringBuilder中的方法则没有synchronized关键字,线程不安全,但是StringBuilder因为没有使用使用synchronized关键字修饰,所以性能更高,所以在单线程环境下选择使用StringBuilder,多线程环境下使用StringBuffer.
4:如果声明的这个字符串几乎不做修改操作,那么使用String,因为不调用new关键字声明String类型的变量的话它不会在堆内存中创建对象,直接指向String的常量池,效率很高;

13.String str="李四"与 String str=new String(“李四”)有什么区别?

内存的分配方式不一样。String str="李四"的方式,Java 虚拟机会将其分配到常量池中;而 String str=new String(“李四”)方式,则会被分到堆内存中。

    String str1 = "李四";
	String str2 = "李四";
	String str3 = new String("李四");
	System.out.println(str1 == str2);//ture
	System.out.println(str2 == str3);//false

代码解释:
1.Java 虚拟机会将其分配到常量池中:常量池不会重复创建对象。
在String str1="李四"中,把"李四"存在常量池,地址赋给str1。假设再写一个String str2=“李四”,则会把之前"李四"的地址赋给str2,所以他们引用的是同一个地址值,共享同一个内存。
2分到堆内存中:堆内存会创建一个新的对象(内容相同也会重新创建一个对象)。
写一个String str3=new String(“李四”),因为调用的是new关键字,所以会创建一个新的"李四"对象,然后将新对象的地址值赋给str3。虽然str3和str1的值相同但是地址值不同。
总结:
1.堆内存用来存放由new创建的对象和数组等。
2.常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。
3.尽量使用new从堆中创建对象,因为堆的内存很大。

14.如何反转字符串?

1.使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
2.使用循环进行反转
代码如下:

        StringBuffer str1 = new StringBuffer();
    	str1.append("123456");
    	System.out.println(str1.reverse()); // 654321
    	StringBuilder str2 = new StringBuilder();
    	str2.append("123456");
    	System.out.println(str2.reverse()); // 654321
    	String start="123456";
    	String end="";
    	for(int i=start.length()-1;i>=0;i--)
    	{
    		end+=start.charAt(i);
    	}
    	System.out.println(end);//654321

15.String 类的常用方法都有那些?

网上的大同小异基本都有一下这些:
indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。

16.抽象类必须要有抽象方法吗?

1.抽象类可以有抽象方法也可以没有抽象方法。
2.如果类中存在抽象方法,则该类必须为抽象类。
正确代码:

public abstract class fu {
	public  void show()
	{
		System.out.println(66);//抽象类里面可以没有抽象方法并且可以存在非抽象方法
	}
}

错误代码:

public class fu {
	public abstract void show()//错误!!一但类中存在抽象方法,类名一定要使用abstract来修饰
	{
		System.out.println(66);
	}
}

17.普通类和抽象类有哪些区别?

1.普通类不能包含抽象方法,抽象类可以包含抽象方法。
2.抽象类不能直接实例化,普通类可以直接实例化。

public abstract  class fu {
	public void show()
	{
		System.out.println(66);
	}
	public static void main(String[] args)
    {
    	fu p=new fu();//Cannot instantiate the type fu 不能实例化fu这个对象
    }
}

18.抽象类能使用 final 修饰吗?

不能,定义抽象类意义就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类。
抽象类的就是要子类继承然后实现内部方法的。但是final修饰的类是不能再被继承和修改的。所以不能用final修饰。
简单的说就是:用final修饰的类不能存在子类,相当于断子绝孙,而定义抽象类的意义就是为了让子类继承,这就是前后矛盾,当然是不可以的。

19.接口和抽象类有什么区别?

1.一个类只能继承一个抽象类,而一个类可以实现多个接口。(单继承,多实现)
2.抽象类可以有构造方法,接口中不能有构造方法。
3.抽象类中可以有成员变量,接口中没有成员变量。(被final修饰变成了常量)
3.抽象类中可以有普通方法,接口中所有方法都必须是抽象的。(1.8后允许接口定义非抽象方法)
4.抽象类中抽象方法的访问类型可以是public,protected,但接口中抽象方法的访问类型只能是public,并且默认为public abstract(省略则自动默认补全)。
5.抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的(省略则自动默认补全)。

20.java 中 IO 流分为几种?

1.按照流的流向分,可以分为输入流和输出流。
输入:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
输出:把程序(内存)中的内容输出到磁盘、光盘等存储设备中。
2.按处理数据单位不同分为字节流和字符流。
字节流:每次读取(写出)一个字节,当传输的资源文件有中文时,就会出现乱码。
字符流:每次读取(写出)两个字节,有中文时,使用该流就可以正确传输显示中文。
3.按照流的角色划分为节点流和处理流。
节点流:从或向一个特定的地方(节点)读写数据。如FileInputStream。
处理流(包装流):是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。

21.BIO、NIO、AIO 有什么区别?

1.BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
2.NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
3.AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。

22.Files的常用方法都有哪些?

Files.exists():检测文件路径是否存在。
Files.createFile():创建文件。
Files.createDirectory():创建文件夹。
Files.delete():删除一个文件或目录。
Files.copy():复制文件。
Files.move():移动文件。
Files.size():查看文件个数。
Files.read():读取文件。
Files.write():写入文件。

23.Java代码的加载和执行顺序

在java类new一个对象的过程中,它们的执行顺序如下:

(当java类没有被继承时)

  1. 实现自身的静态属性和静态代码块。
注意:它们两个的执行顺序根据自身出现的顺序决定
  1. 实现自身的非静态属性和非静态代码块。

  2. 执行自身的构造函数。

(当java类被继承时,其子类被new对象的执行过程,初始化顺序如下:)

1、初始化父类静态属性

2、执行父类静态代码块

3、初始化子类静态属性

4、执行子类静态代码块

5、初始化父类实例变量

6、执行父类动态代码块

7、执行父类构造方法

8、初始化子类实例变量

9、执行子类动态代码块

10、执行子类构造方法

静态代码块和非静态代码块的异同点如下:

相同点:都是JVM加载类时且在构造函数执行之前执行,在类中都可以定义多个,为静态属性赋值、执行一些必要的初始化操作

不同点:静态代码块在非静态代码块之前执行(静态代码块执行顺序 > 非静态代码块执行顺序)。但是静态代码块只在第一次new对象时执行一次,之后不再执行。而动态代码块每new一次就执行一次。

无继承关系时如下:

    public static void main(String[] args) throws Exception {
       new MyClass();
    }
    
class MyClass{
    static String str1 = "MyClass 静态属性";
    static{
        //静态代码块 类级别
        System.out.println(str1);
        System.out.println("MyClass  静态代码块");
    }
    
    String str2 = "MyClass 非静态属性";
    {
        //动态代码块   对象级别
        System.out.println(str2);
        System.out.println("MyClass  动态代码块");
    }
    public MyClass(){
        System.out.println("MyClass  构造方法执行");
    }
}

结果:
MyClass 静态属性
MyClass 静态代码块
MyClass 非静态属性
MyClass 动态代码块
MyClass 构造方法执行

当java类被继承时,其子类被new对象时,它们的执行顺序,如下:

(zi类为fu类的子类)

  public static void main(String[] args) throws Exception {
       new zi();
    }
    
class fu{
    static String str1 = "fu 静态属性";
    static{
        //静态代码块 类级别
        System.out.println(str1);
        System.out.println("fu  静态代码块");
    }
    String str2 = "fu 非静态属性";
    {
        //动态代码块   对象级别
        System.out.println(str2);
        System.out.println("fu  动态代码块");
    }
    public fu(){
        System.out.println("fu  构造方法执行");
    }
}

class zi extends fu{
    static String str3 = "zi 静态属性";
    static{
        //静态代码块 类级别
        System.out.println(str3);
        System.out.println("zi  静态代码块");
    }

    String str4 = "zi 非静态属性";
    {
        //动态代码块   对象级别
        System.out.println(str4);
        System.out.println("zi  动态代码块");
    }

    public zi(){
        System.out.println("zi  构造方法执行");
    }
}

结果:
fu 静态属性
fu 静态代码块
zi 静态属性
zi 静态代码块
fu 非静态属性
fu 动态代码块
fu 构造方法执行
zi 非静态属性
zi 动态代码块
zi 构造方法执行

当java类被继承时,其父类被new对象时,它们的执行顺序,如下:

(zi类为fu类的子类)

  public static void main(String[] args) throws Exception {
       new fu();
    }
    
class fu{
    static String str1 = "fu 静态属性";
    static{
        //静态代码块 类级别
        System.out.println(str1);
        System.out.println("fu  静态代码块");
    }
    String str2 = "fu 非静态属性";
    {
        //动态代码块   对象级别
        System.out.println(str2);
        System.out.println("fu  动态代码块");
    }
    public fu(){
        System.out.println("fu  构造方法执行");
    }
}

class zi extends fu{
    static String str3 = "zi 静态属性";
    static{
        //静态代码块 类级别
        System.out.println(str3);
        System.out.println("zi  静态代码块");
    }

    String str4 = "zi 非静态属性";
    {
        //动态代码块   对象级别
        System.out.println(str4);
        System.out.println("zi  动态代码块");
    }

    public zi(){
        System.out.println("zi  构造方法执行");
    }
}

结果:
fu 静态属性
fu 静态代码块
fu 非静态属性
fu 动态代码块
fu 构造方法执行

由此可见:
总的顺序是:先父类后子类,先静态后动态,属性和代码块的初始化遵循正常的出场顺序无论是静态还是动态,但是他们总是先于构造器执行。

持续更新中,可以关注我持续更新有关于java面试的内容,也能给我提出一些意见,一起进步。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值