Java基础知识最详细版(面试必备)

2020-11-16  本文已影响0人  dybaby

刚刚经历过秋招,看了大量的面经,顺便将常见的Java常考知识点总结了一下,并根据被问到的频率大致做了一个标注。一颗星表示知识点需要了解,被问到的频率不高,面试时起码能说个差不多。两颗星表示被问到的频率较高或对理解Java有着重要的作用,建议熟练掌握。三颗星表示被问到的频率非常高,建议深入理解并熟练掌握其相关知识,方便面试时拓展(方便装逼),给面试官留下个好印象。

微信搜索公众号路人zhang,回复面试手册,领取本文档PDF版及更多面试资料。

推荐阅读:一文搞懂所有HashMap面试题

JVM、JRE及JDK的关系 **

JDK(Java Development Kit)是针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。

Java Runtime Environment(JRE)是运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。

JVM是Java Virtual Machine(Java虚拟机)的缩写,是整个java实现跨平台的最核心的部分,能够运行以Java语言写作的软件程序。

**简单来说就是JDK是Java的开发工具,JRE是Java程序运行所需的环境,JVM是Java虚拟机.它们之间的关系是JDK包含JRE和JVM,JRE包含JVM.**

JAVA语言特点 **

JAVA和C++的区别 **

面试时记住前四个就行了

Java的基本数据类型  **

注意String不是基本数据类型

类型 关键字 包装器类型 占用内存(字节)(重要) 取值范围 默认值
字节型 byte Byte 1 -128(-2^7) ~ 127(2^7-1) 0
短整型 short Short 2 -2^15 ~ 2^15-1 0
整型 int Integer 4 -2^31 ~ 2^31-1 0
长整型 long Long 8 -2^63 ~ 2^63-1 0L
单精度浮点型 float Float 4 3.4e-45 ~ 1.4e38 0.0F
双精度浮点型 double Double 8 4.9e-324 ~ 1.8e308 0.0D
字符型 char Character 2 '\u0000'
布尔型 boolean Boolean 1 true/flase flase

隐式(自动)类型转换和显示(强制)类型转换 **

看一个经典的代码

short s = 1;
s = s + 1;

这是会报错的,因为1是int型,s+1会自动转换为int型,将int型直接赋值给short型会报错。

做一下修改即可避免报错

short s = 1;
s = (short)(s + 1);

或这样写,因为s += 1会自动进行强制类型转换

short s = 1;
s += 1;

自动装箱与拆箱 **

**说下结论 **:IntegerShortByteCharacterLong这几个类的valueOf方法的实现是类似的。DoubleFloatvalueOf方法的实现是类似的。然后是BooleanvalueOf方法是单独一组的。

String(不是基本数据类型)

String的不可变性 ***

在 Java 8 中,String 内部使用 char 数组存储数据。并且被声明为final,因此它不可被继承。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {   
    private final char value[];
}

为什么String`要设计成不可变的呢(不可变性的好处):

1.可以缓存 hash 值()

    因为 `String` 的` hash `值经常被使用,例如` String` 用做 `HashMap` 的 `key`。不可变的特性可以使得 `hash `值也不可变,

因此只需要进行一次计算。

2.常量池优化

    `String` 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。

3.线程安全

    `String` 不可变性天生具备线程安全,可以在多个线程中安全地使用。

字符型常量和字符串常量的区别 *

  1. 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符
  2. 含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)
  3. 占内存大小 字符常量占两个字节 字符串常量占若干个字节(至少一个字符结束标志)

什么是字符串常量池?*

字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。

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

面试时一般不会问,但面试或笔试写字符串相关的算法题经常会涉及到,还是得背一背(以下大致是按使用频率优先级排序)

String和StringBuffer、StringBuilder的区别是什么?***

1.可变性

    `String`不可变,`StringBuilder`和`StringBuffer`是可变的

2.线程安全性

    `String`由于是不可变的,所以线程安全。`StringBuffer`对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。    `StringBuilder`并没有对方法进行加同步锁,所以是非线程安全的。

3.性能

`StringBuilder` > `StringBuffer` > `String`

为了方便记忆,总结如下

是否可变 是否安全 性能
String 不可变 安全
StringBuilder 可变 不安全
StringBuffer 可变 安全 较高

switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上 *

`switch`可以作用于`char` `byte` `short` `int`及它们对应的包装类型,`switch`不可作用于`long` `double` `float` `boolean`及他们的包装类型。在 JDK1.5之后可以作用于枚举类型,在JDK1.7之后可作用于`String`类型。

Java语言采用何种编码方案?有何特点?*

Java语言采用Unicode编码标准,它为每个字符制订了一个唯一的数值,因此在任何的语言,平台,程序都可以放心的使用。

访问修饰符 **

在Java编程语言中有四种权限访问控制符,这四种访问权限的控制符能够控制类中成员的可见性。其中类有两种`public`、`default`。而方法和变量有 4 种:`public`、`default`、`protected`、`private`。

运算符 *

关键字

static关键字 ***

`static`关键字的主要用途**就是方便在没有创建对象时调用方法和变量和优化程序性能**

**1.static变量(静态变量)**

用`static`修饰的变量被称为静态变量,也被称为类变量,可以直接通过类名来访问它。静态变量被所有的对象共享,在内存中只有一个副本,仅当在类初次加载时会被初始化,而非静态变量在创建对象的时候被初始化,并且存在多个副本,各个对象拥有的副本互不影响。

**2.static方法(静态方法)**

`static`方法不依赖于任何对象就可以进行访问,在`static`方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用,但是在非静态成员方法中是可以访问静态成员方法/变量的。
public class Main {
    public static String s1 = "s1";//静态变量
    String s2  = "s2";
    public void fun1(){
        System.out.println(s1);
        System.out.println(s2);
    }
    
    public static void fun2(){
        System.out.println(s1);
        System.out.println(s2);//此处报错,静态方法不能调用非静态变量
    }
}
**3.static代码块(静态代码块)**

静态代码块的主要用途是可以用来优化程序的性能,因为它只会在类加载时加载一次,很多时候会将一些只需要进行一次的初始化操作都放在`static`代码块中进行。如果程序中有多个`static`块,在类初次被加载的时候,会按照`static`块的顺序来执行每个`static`块。
public class Main {
    static {
        System.out.println("hello,word");
    }
    public static void main(String[] args) {
        Main m = new Main();
    }
}
**4.可以通过this访问静态成员变量吗?(可以)**

`this`代表当前对象,可以访问静态变量,而静态方法中是不能访问非静态变量,也不能使用`this`引用。

**5.初始化顺序**

静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。如果存在继承关系的话,初始化顺序为**父类中的静态变量和静态代码块——子类中的静态变量和静态代码块——父类中的实例变量和普通代码块——父类的构造函数——子类的实例变量和普通代码块——子类的构造函数**

final 关键字 ***

`final`关键字主要用于修饰类,变量,方法。
  1. 类:被final修饰的类不可以被继承
  2. 方法:被final修饰的方法不可以被重写
  3. 变量:被final修饰的变量是基本类型,变量的数值不能改变;被修饰的变量是引用类型,变量便不能在引用其他对象,但是变量所引用的对象本身是可以改变的。
public class Main {
    int a = 1;
    public static void main(String[] args) {
        final int b = 1;
        b = 2;//报错
        final Main m = new Main();
        m.a = 2;//不报错,可以改变引用类型变量所指向的对象
    }
}   

final finally finalize区别 ***

this关键字 **

重点掌握前三种即可

1.`this`关键字可用来引用当前类的实例变量。主要用于形参与成员名字重名,用`this`来区分。
public Person(String name, int age) {
    this.name = name;
    this.age = age;
}
2.`this`关键字可用于调用当前类方法。
public class Main {
    public void fun1(){
        System.out.println("hello,word");
    }
    public void fun2(){
        this.fun1();//this可省略
    }

    public static void main(String[] args) {
        Main m = new Main();
        m.fun2();
    }
}
3.`this()`可以用来调用当前类的构造函数。(注意:`this()`一定要放在构造函数的第一行,否则编译不通过)
class Person{
    private String name;
    private int age;
    
    public Person() {
    }
 
    public Person(String name) {
        this.name = name;
    }
    public Person(String name, int age) {
        this(name);
        this.age = age;
    }
}
4.`this`关键字可作为调用方法中的参数传递。

5.`this`关键字可作为参数在构造函数调用中传递。

6.`this`关键字可用于从方法返回当前类的实例。super

super关键字 **

1.`super`可以用来引用直接父类的实例变量。和`this`类似,主要用于区分父类和子类中相同的字段

2.`super`可以用来调用直接父类构造函数。(注意:`super()`一定要放在构造函数的第一行,否则编译不通过)

3.`super`可以用来调用直接父类方法。
public class Main {
    public static void main(String[] args) {
        Child child = new Child("Father","Child");
        child.test();
    }
}

class Father{
    protected String name;

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

    public void Say(){
        System.out.println("hello,child");
    }

}

class Child extends Father{
    private String name;

    public Child(String name1, String name2) {
        super(name1);      //调用直接父类构造函数
        this.name = name2;
    }

    public void test(){
        System.out.println(this.name);
        System.out.println(super.name);  //引用直接父类的实例变量
        super.Say();      //调用直接父类方法
    }
}

this与super的区别 **

break ,continue ,return 的区别及作用 **

面向对象和面向过程的区别 **

面向对象三大特性(封装、继承、多态) ***

面向对象五大基本原则是什么 **

抽象类和接口的对比 ***

在Java语言中,`abstract class`和`interface`是支持抽象类定义的两种机制。抽象类:用来捕捉子类的通用特性的。接口:抽象方法的集合。

相同点:

不同点:

类型 抽象类 接口
定义 abstract class Interface
实现 extends(需要提供抽象类中所有声明的方法的实现) implements(需要提供接口中所有声明的方法的实现)
继承 抽象类可以继承一个类和实现多个接口;子类只可以继承一个抽象类 接口只可以继承接口(一个或多个);子类可以实现多个接口
访问修饰符 抽象方法可以有public、protected和default这些修饰符 接口方法默认修饰符是public。你不可以使用其它修饰符
构造器 抽象类可以有构造器 接口不能有构造器
字段声明 抽象类的字段声明可以是任意的 接口的字段默认都是 static 和 final 的

在Java中定义一个不做事且没有参数的构造方法的作用 *

Java程序存在继承,在执行子类的构造方法时,如果没有用`super()`来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。如果父类只定义了有参数的构造函数,而子类的构造函数没有用`super`调用父类那个特定的构造函数,就会出错。

在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是 *

帮助子类做初始化工作。

一个类的构造方法的作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么? *

主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。

构造方法有哪些特性? **

变量 **

内部类 **

内部类包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类

重写与重载 ***

重载和重写的区别

构造器(constructor)是否可被重写(override)

构造器可以被重载,不能被重写

重载的方法能否根据返回类型进行区分?为什么?

不能,因为调用时不能指定类型信息,编译器不知道你要调用哪个函数。

== 和 equals 的区别 ***

hashCode 与 equals(为什么重写equals方法后,hashCode方法也必须重写) ***

Java 中是值传递还是引用传递,还是两者共存 **

这是一个很容易搞混又很难解释清楚的问题,先说结论,Java中只有值传递

先看这样一段代码

public class Main{
    public static void main(String[] args) {
        int a = 1;
        printValue(a);
        System.out.println("a:" + a);
    }
    public static void printValue(int b){
        b = 2;
        System.out.println("b:"+ b);
    }
}

输出

b:2
a:1

可以看到将a的值传到printValue方法中,并将其值改为2。但方法调用结束后,a的值还是1,并未发生改变,所以这种情况下为值传递。

再看这段代码

public class Main{
    public static void main(String[] args) {
        Preson p = new Preson();
        p.name = "zhangsan";
        printValue(p);
        System.out.println("p.name: " + p.name);
    }
    public static void printValue(Preson q){
        q.name = "lisi";
        System.out.println("q.name: "+ q.name);
    }
}
class Preson{
    public String name;
}

输出结果

q.name: lisi
p.name: lisi

在将p传入printValue方法后,方法调用结束,pname属性竟然被改变了!所以得出结论,参数为基本类型为值传递,参数为引用类型为时为引用传递。这个结论是错误的,下面来看看判断是值传递还是值传递的关键是什么,先看定义

从定义中可以明显看出,区分是值传递还是引用传递主要是看向方法中传递的是实际参数的副本还是实际参数的地址。上面第一个例子很明显是值传递,其实第二个例子中向printValue方法中传递的是一个引用的副本,只是这个副本引用和原始的引用指向的同一个对象,所以副本引用修改过对象属性后,通过原始引用查看对象属性肯定也是被修改过的。换句话说,printValue方法中修改的是副本引用指向的对象的属性,不是引用本身,如果修改的是引用本身,那么原始引用肯定不受影响。看下面这个例子

public class Main{
    public static void main(String[] args) {
        Preson p = new Preson();
        p.name = "zhangsan";
        printValue(p);
        System.out.println("p.name: " + p.name);
    }
    public static void printValue(Preson q){
        q = new Preson();
        q.name = "lisi";
        System.out.println("q.name: "+ q.name);
    }
}
class Preson{
    public String name;
}

输出结果

q.name: lisi
p.name: zhangsan

可以看到将p传入printValue方法后,printValue方法调用结束后,p的属性name没有改变,这是因为printValue方法中并没有改变副本引用q所指向的对象,而是改变了副本引用q本身,将副本引用q指向了另一个对象并对这个对象的属性进行修改,所以原始引用p所指向的对象不受影响。所以证明Java中只存在值传递。

IO流 *

Java IO流主要可以分为输入流和输出流。按照照操作单元划分,可以划分为字节流和字符流。按照流的角色划分为节点流和处理流。

Java I0流的40多个类都是从4个抽象类基类中派生出来的。

BIO,NIO,AIO 有什么区别? **

反射 ***

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

Java获取Class对象的三种方式

class Person {
    public String name = "zhangsan";
    public Person() {
    }
}
public class Main{
    public static void main(String[] args) throws ClassNotFoundException {
    //方式1
     Person p1 = new Person();
     Class c1 = p1.getClass();
     //方式2
    Class c2 = Person.class;
    //方式3可能会抛出ClassNotFoundException异常
    Class c3 = Class.forName("com.company");
    }
}

因为在一个类在 JVM 中只会有一个 Class 实例,所以对c1c2c3进行equals比较时返回的都是true

反射优缺点:

反射应用场景:

  1. Java的很多框架都用到了反射,例如Spring中的xml的配置模式等
  2. 动态代理设计模式也采用了反射机制

JAVA异常 ***

异常主要分为ErrorException两种

异常框图


在这里插入图片描述

除了以上的分类,异常还能分为非检查异常和检查异常

下面来看下try{}catch(){}finally{}return之间的“恩恩怨怨”,这里有些乱,面试时问的也不是很多,实在记不住就算啦。还是先看代码猜结果。

public class Main{
    public static void main(String[] args) {
        int a = test1();
        System.out.println(a);
        int b = test2();
        System.out.println(b);
        int c = test3();
        System.out.println(c);
        int d = test4();
        System.out.println(d);
        int e = test5();
        System.out.println(e);
    }
    public static int test1(){
        int a = 1;
        try{
            a = 2;
            return a;
        }catch(Exception e){
            System.out.println("hello,test1");
            a = 3;
        }finally{
            a = 4;
        }
        return a;
    }
    //输出 2
    
    
    public static int test2(){
        int a = 1;
        try{
            a = 2;
            return a;
        }catch(Exception e){
            System.out.println("hello,test2");
            a = 3;
            return a;
        }finally{
            a = 4;
        }
    }
    //输出 2
    
    
    public static int test3(){
        int a = 1;
        try{
            a = 2/0;
            return a;
        }catch(Exception e){
            System.out.println("hello,test3");
            a = 3;
        }finally{
            a = 4;
        }
        return a;
    }
    //输出 hello,test3 
    // 4
    
    
    public static int test4(){
        int a = 1;
        try{
            a = 2/0;
            return a;
        }catch(Exception e){
            System.out.println("hello,test4");
            a = 3;
            return a;
        }finally{
            a = 4;
        }
    }
    //输出 hello,test4
    // 3
    
    public static int test5(){
        int a = 1;
        try{
            a = 2/0;
            return a;
        }catch(Exception e){
            a = 3;
            return a;
        }finally{
            a = 4;
            return a;
        }
    }
    
    //输出 4
}

如果没有仔细的研究过,应该好多会猜错,下面总结下规律。

  1. 从前三个例子可以看出如果try{}中的代码没有异常,catch(){}代码块中的代码不会执行。所以如果try{}catch(){}都含有return时,无异常执行try{}中的return,存在异常执行catch(){}return
  2. 不管任何情况,就算try{}catch(){}中含有returnfinally{}中的代码一定会执行,那么为什么test1test2test3中的结果不是4呢,因为虽然finally是在return后面的表达式运算之后执行的,但此时并没有返回运算之后的值,而是把值保存起来,不管finally对该值做任何的改变,返回的值都不会改变,依然返回保存起来的值。也就是说方法的返回值是在finally运算之前就确定了的。
  3. 如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。这个不理解可以看看上面提到的Java的值传递的问题。
  4. 如果finally{}中含有return,会导致程序提前退出,不在执行try{}catch(){}中的return。所以test5返回的值是4。

最后总计一下try{}catch(){}finally{}的执行顺序。

  1. 先执行try中的语句,包括return后面的表达式;
  2. 有异常时,执行catch中的语句,包括return后面的表达式,无异常跳过catch语句;
  3. 然后执行finally中的语句,如果finally里面有return语句,执行return语句,程序结束;
  4. finally{}中没有return时,无异常执行try中的return,如果有异常时则执行catch中的return。前两步执行的return只是确定返回的值,程序并未结束,finally{}执行之后,最后将前两步确定的return的返回值返回。

JAVA注解 **

面试问的不多,但是在使用框架开发时会经常使用,但东西太多了,这里只是简单介绍下概念。

Annotation注解可以看成是java中的一种标记记号,用来给java中的类,成员,方法,参数等任何程序元素添加一些额外的说明信息,同时不改变程序语义。注解可以分为三类:基本注解,元注解,自定义注解

JAVA泛型 ***

Java 泛型是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

JAVA序列化 **

深拷贝与浅拷贝 ***

常见的Object方法 ***

这些方法都很重要,面试经常会问到,要结合其他知识将这些方法理解透彻

上一篇下一篇

猜你喜欢

热点阅读