深入理解JavaGuide面试突击版

目录

前言

本文为《JavaGuide面试突击版》的阅读笔记,会带有笔者的理解和一些扩展的东西。

作者公众号:JavaGuide

原作链接:https://github.com/Snailclimb/JavaGuide

Java基础

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

  • 面向过程:没有面向对象易维护、易复用、易扩展。
  • 面向对象:易维护、易复用、易扩展。因为面向对象有封装、继承、多态的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。

补充:Java性能差的主要原因并不是它是面向对象语言,而是Java是半编译语言,最终的执行代码并不是可以直接被CPU执行的二进制机械码。
而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比Java好。

Java语言有哪些特点?

  1. 简单易学
  2. 面向对象(封装、继承、多态)
  3. 平台无关性
  4. 可靠性
  5. 安全性
  6. 支持多线程
  7. 支持网络编程且很方便(Java本身就是为简化网络编程设计的)
  8. 编译与解释并存

关于JVM、JDK和JRE最详细通俗的解答

Java程序从源代码到运行一般有下面三步:
在这里插入图片描述

JVM

Java虚拟机(JVM)是运行Java字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。

什么是字节码?采用字节码的好处是什么?

在Java中,JVM可以理解的代码就叫做字节码(即扩展名为.class的文件),他不面向任何特定的处理器,只面向虚拟机。Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java程序无需重新编译便可在多种不同操作系统的计算机上运行。

编译型语言和解释型语言

详见文章:编译型语言和解释型语言

对于上图,我们需要格外注意的是 .class->机器码 这一步。

在这一步JVM类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。

而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了JIT编译器,而JIT属于运行时编译。

当JIT编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。

而我们知道,机器码的运行效率肯定是高于Java解释器的。这也解释了我们为什么经常会说Java是编译与解释共存的语言。

JIT编译器,英文写作Just-In-Time Compiler,中文意思是即时编译器。
JIT是一种提高程序运行效率的方法。
通常,程序有两种运行方式:静态编译与动态解释。
静态编译的程序在执行前全部被翻译为机器码,而动态解释执行的则是一句一句边运行边翻译。
在这里插入图片描述
在这里插入图片描述

HotSpot 采用了惰性评估(Lazy Evaluation)的做法:如果一段代码频繁的调用方法,或是一个循环,也就是这段代码被多次执行,JIT 编译器会参与其中,而如果一个方法从来不被执行,则不会经过 JIT 编译。

根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是 JIT 所需要编译的部分。

二八定律是19世纪末20世纪初意大利经济学家帕累托发现的。
他认为,在任何一组东西中,最重要的只占其中一小部分,约20%,其余80%尽管是多数,却是次要的,因此又称二八定律。

JVM会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。

JDK 9 引入了一种新的编译模式 AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了 JIT 预热等各方面的开销。

JDK 支持分层编译和 AOT 协作使用。但是,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。

在这里插入图片描述

总结

字节码和不同系统的 JVM 实现是 Java 语言 “一次编译,随处可以运行” 的关键所在。

JDK 和 JRE的关系

简单来说:
在这里插入图片描述
具体来说:
在这里插入图片描述
一般情况下,如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。有时,即使你不打算在计算机上进行任何 Java 开发,仍需要安装 JDK。例如,如果要使用 JSP 部署 WEB 应用程序,应用程序服务器会将 JSP 转换成 Java servlet,servlet 需要使用 JDK 来编译。

Java 和 C++ 的区别

  • 都是面向对象的语言,都支持封装、继承和多态。
  • Java 不提供指针来直接访问内存,程序内存更加安全。
  • Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
  • Java 有自动内存管理机制,不需要程序员手动释放无用内存。
  • 在 C 语言中,字符串或字符数组最后都会有一个额外的字符 ‘\0’ 来表示结束。但是,Java 语言中没有结束符这一概念。

什么是主类?Java 应用程序和 Java 小程序的主类有何不同?

  • 主类是 Java 程序执行的入口点,一个程序只能有一个主类。
  • 在 Java 应用程序中,主类是指包含 main() 方法的类,而不一定要求该类是 public 类,但小程序的主类要求必须是 public 类。
  • 在 Java 小程序中,主类是一个继承自系统类 JApplet 或 Applet 的子类。

Java应用程序与小程序之间有哪些差别?

  • 应用程序从主线程启动(也就是 main() 方法)。
  • Applet 小程序没有 main() 方法,主要是嵌在浏览器页面上运行(调用 init() 或者 run() 来启动)。
    在这里插入图片描述

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

  • 形式上:字符型常量是单引号引起的一个字符;字符串常量是双引号引起的若干个字符。
  • 含义上:字符常量相当于一个整型值(ASCII 值),可以参加表达式运算;字符串常量代表一个地址值(该字符串在内存中存放位置)
  • 占内存大小:字符常量只占 2 个字节;字符串常量占若干个字节。
    在这里插入图片描述

构造器 Constructor 是否可被 override?

Constructor 不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。

重载和重写的区别

  • 重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。
  • 重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变。
区别点 重载方法 重写方法
发生范围 同一个类 子类中
参数列表 必须修改 一定不能修改
返回类型 可修改 一定不能修改
异常 可修改 可以减少或删除,一定不能抛出新的或者更广的异常
访问修饰符 可修改 一定不能做更严格的限制(可以降低限制)
发生阶段 编译期 运行期

编译期与运行期

参考:深入分析Java的编译期与运行期

在这里插入图片描述

编译期:
在这里插入图片描述
运行期:
在这里插入图片描述

继承

子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问的,仅仅只是拥有。

多态

在 Java 中有两种形式可以实现多态:

  • 继承(多个子类对同一方法的重写)
  • 接口(实现接口并覆盖接口中同一方法)

String StringBuffer 和 StringBuilder 的区别是什么?String 为什么是不可变的?

详见文章:谈谈String,StringBuffer和StringBuilder

自动装箱与拆箱

  • 装箱:将基本数据类型用它们对应的引用类型包装起来。
  • 拆箱:将包装类型转换为基本数据类型。

在一个静态方法内调用一个非静态成员为什么是非法的?

类的静态成员(变量或方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接访问。

非静态成员(变量或方法)属于类的对象,只有在类的对象产生(实例化)时才会分配内存,然后通过类的对象(实例)去访问。

所以,如果一个类的静态方法去调用非静态方法或变量的时候,因为类的静态方法存在的时候,类的非静态成员可能不存在,访问一个内存中不存在的东西当然会出错。

public class A {
    
    
    private static String staticStr = "I am static!";
    private  String commonStr = "I am not static!";
    public String getStaticStr(){
    
    //非静态方法调用静态变量 √
        return staticStr;
    }
    public String getCommonStr(){
    
    //非静态方法调用非静态变量 √
        return commonStr;
    }
    public static String staticGetStaticStr(){
    
    //静态方法调用静态变量 √
        return staticStr;
    }
    public static String staticGetCommonStr(){
    
    //静态方法调用非静态变量 ×
        return commonStr;
    }
}

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

当子类构造器执行体中既没有 super 调用,也没有 this 调用时,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器,如果父类中无此构造器,编译时将发生错误。

详见文章:谈谈Java中的构造方法

接口的演变

详见文章:谈谈Java接口的演变

成员变量与局部变量的区别有哪些

  • 从语法形式上看:成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
  • 从变量在内存中的存储方式来看:如果成员变量是使用 static 修饰的,那么这个成员变量是属于类的,如果没有使用 static 修饰,这个成员变量是属于实例的。对象存于堆内存,如果局部变量类型为基本数据类型,那么存储在栈内存,如果为引用数据类型,那存放的是指向堆内存对象的引用或者是指向常量池中的地址。
  • 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
  • 成员变量如果没有被赋初值:则会被自动赋予类型的默认值(一种情况例外:被 final 修饰的成员变量必须显式地赋值),而局部变量则不会被自动赋值。

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

帮助子类做初始化工作。

== 与 equals(重要)

详见文章:一张图带你了解字符串的初始化过程

  • ==:判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型比较的是值,引用数据类型比较的是内存地址)。
  • equals():它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
    • 类没有重写 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
          public boolean equals(Object obj) {
              
              
             return (this == obj);
         }
      
    • 类重写了 equals() 方法。一般,我们都重写 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true(即认为这两个对象相等)。
      详见文章:分析String的equals()方法

举个例子:

public class Test {
    
    
    public static void main(String[] args) {
    
    
        String a = new String("ab");//a为一个引用
        String b = new String("ab");//b为另一个引用,对象的内容一样
        String aa = "ab";//放在常量池中
        String bb = "ab";//从常量池中查找
        if (aa == bb)//true
            System.out.println("aa==bb");
        if (a == b)//false,非同一对象
            System.out.println("a==b");
        if (a.equals(b))//true
            System.out.println("aEQb");
        if (42 == 42.0)//true
            System.out.println("true");
    }
}

hashCode 与 equals(重要)

面试官可能会问你:“你重写过 hashCode 和 equals 么,为什么重写 equals 时必须重写 hashCode 方法?”

详见文章:谈谈hashCode()

为什么 Java 中只有值传递?

详见文章:深入理解Java中的值传递

简述线程、程序、进程的基本概念。以及他们之间关系是什么?

线程与进程相似,但线程是一个比进程更小的执行单位。

一个进程在其执行的过程中可以产生多个线程。

与进程不同的是,同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程或是在各个线程之间切换工作时,负担要比进程小得多,也正是因为如此,线程也被称为轻量级进程。

程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。

系统运行一个程序即是一个进程从创建,运行到消亡的过程。

简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间、内存空间、文件、输入输出设备的使用权等等。

换句话说,当程序在执行时,将会被操作系统载入内存中。

线程是进程划分成的更小的运行单位。

线程和进程最大的不同在于基本上各个进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会互相影响。

从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。

线程有哪些基本状态?

状态名称 说明
NEW 初始状态,线程被构建,但是还没有调用start()方法
RUNNABLE 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称作“运行中”
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)
TIME_WAITING 超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的
TERMINATED 终止状态,表示当前线程已经执行完毕

线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java线程状态变迁如下图所示(图源《Java并发编程艺术》4.1.4节)
在这里插入图片描述
更多细节请看:总算把线程六种状态的转换说清楚了!

关于final关键字的一些总结

详见:谈谈Java中的final关键字

Java中的异常处理

在这里插入图片描述
在 Java 中,所有的异常都有一个共同的祖先:java.lang 包中的 Throwable 类。

Throwable 类有两个 Java 异常处理相关的重要的子类:Exception(异常)和 Error(错误)。

粉红色的异常是受检查的异常(checked exceptions),其必须被 try…catch 语句块所捕获,或者在方法签名里通过 throws 子句声明。受检查的异常必须在编译时被捕捉处理,命名为 Checked Exception 是因为Java编译器要进行检查,Java虚拟机也要进行检查,以确保这个规则得到遵守。

绿色的异常是运行时异常(runtime exceptions),需要程序员自己分析代码决定是否捕获和处理。

而声明为 Error 的,则属于严重错误,如系统崩溃、虚拟机错误、动态链接失败等,这些错误无法恢复或者不可能捕捉,将导致应用程序中断,Error 不需要捕捉。

注意:当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值会覆盖原始的返回值。

public static int f(int value) {
    
    
	try {
    
    
		return value * value;
	} finally {
    
    
		if (value == 2) {
    
    
			return 0;
		}
	}
}

如果调用f(2),返回值将是0,因为 finally 语句的返回值覆盖了 try 语句块的返回值。

下面代码的输出结果是什么?

public class ZeroTest {
    
    
    public static void main(String[] args) {
    
    
     try{
    
    
       int i = 100 / 0;
       System.out.print(i);
  }catch(Exception e){
    
    
       System.out.print(1);
       throw new RuntimeException();
  }finally{
    
    
       System.out.print(2);
  }
      System.out.print(3);
 }
}

首先执行try,遇到算术异常,抛出,执行catch,打印1,然后抛出RuntimeException,缓存异常,执行finally,打印2,然后抛出RuntimeException。

如果catch中没有抛出RuntimeException,则执行结果为123。

Java序列化中如果有些字段不想进行序列化该怎么办?

使用 transient 关键字修饰。

详见:Java序列化

获取键盘输入常用的两种方法

方法一:通过 Scanner

Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();

方法二:通过BufferedReader

BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();

Java中IO流

参考:Java的I/O流

BIO,NIO,AIO有什么区别?

  • BIO(Blocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的I/O,并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的BIO模型是无能为力的。因此,我们需要一种更高效的I/O处理模型来应对更高的并发量。
  • NIO(Non-blocking/New I/O):NIO是一种同步非阻塞的I/O模型,在Java1.4中引入了NIO框架,对应Java.nio包,提供了Channel、Selector、Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。NIO提供了与传统BIO模型中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用NIO的非阻塞模式来开发。
  • AIO(Asynchronous I/O):AIO也就是NIO2。在Java7中引入了NIO的改进版NIO2,它是异步非阻塞的I/O模型。异步IO是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO是异步IO的缩写,虽然NIO在网络操作中,提供了非阻塞的方法,但是NIO的IO行为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程自行进行IO操作,IO操作是同步的。查阅网上相关资料,发现就目前来说AIO的应用还不是很广泛,Netty之前也尝试使用过AIO,不过又放弃了。

深拷贝vs浅拷贝

参考:一维数组的四种拷贝方式

Java集合

说说List、Set、Map三者的区别?

  • List(对付顺序的好帮手):List 接口存储一组不唯一的且有序的对象。
  • Set(注重独一无二的性质):不允许重复的集合。不会有多个元素引用相同的对象。
  • Map(用Key来搜索的专家):使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。

ArrayList 与 LinkedList 的区别?

参考:ArrayList与LinkedList区别

ArrayList 与 Vector的区别?为什么要用ArrayList取代Vector?

Vector类的所有方法都是同步的。可以用两个线程安全地访问一个Vector对象,但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。

ArrayList不是同步的,所以在不需要保证线程安全时建议使用ArrayList。

ArrayList扩容机制

参考文章:ArrayList扩容机制探究

猜你喜欢

转载自blog.csdn.net/qq_44491553/article/details/110526615