链接一下目录:
菜鸡的2021春招实习之旅(面经+自己总结的笔记)
Java基础
1. 面向对象和面向过程的区别
面向对象的设计思想是将需要解决的问题分解成一个个对象,这一个个对象可以用来描述在完成这件事中扮演的具体角色和行为。
面向过程的设计思想是将需要解决的问题拆分成一个个具体的步骤,然后设置这些步骤的实现方法,然后按照顺序一步步实现即可
优缺点:
面向对象相较于面向过程而言,有着易维护、易复用和易扩展的特点,由于面向对象有封装、继承和多态的特点,所以能够设计出低耦合的系统,易于去维护;而相对面向过程,面向对象性能较低
封装是将对象属性进行私有化,对外提供一个公共的访问方法
2.java面向对象编程的三大特性:封装、继承和多态
封装
封装是将一个对象的属性进行私有化,给外界访问提供一些方法来访问这些私有属性,如果不想外界访问也可以不提供这些方法,但这个类存在也没什么意义了。
继承
继承是使用已存在的类来建立新的类的技术,新的类可以定义新的属性和新的方法,能够使用父类的技术,但是必须完全继承自存父类,不能选择性的继承。通过使用继承我们可以很方便的复用以前的代码
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
多态
指在程序定义中引用变量所指向的具体类型和通过该引用变量发出的方法具体调用对象在编译的时候并不确定,即引用变量指向哪个类的实例对象,引用对象调用的方法到底是哪个类发出的是并不确定,只有在程序运行时才能确定。
Java实现多态可以是继承和实现
3.String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
String的不可变性:
String类是已经被声明为final的, 不可被继承
String代表不可变的字符序列(简称不可变性)
- 当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
StringBuilder 与 StringBuffer 的区别
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,都有相同的一些方法比如append,indexof等
从线程安全性上来说,String对象是不可变的,可以理解成常量,是线程安全的。StringBuffer对内部方法调用了同步锁,是线程安全的;而StringBuilder没有加锁,是线程不安全的。
从性能上来说,当然是加锁了StringBuffer性能比较慢,而没有加锁的StringBuilder比较快
对于三者使用的总结:
- 操作少量的数据: 适用 String
- 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
4.自动拆箱与自动装箱
装箱:将基本类型用它们对应的引用类型包装起来;比如Integer i = 20;//将20包装成Integer
拆箱:将包装类型转换为基本数据类型;比如int a = i;//将上面的包装类型进行拆箱
使用integer时,数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);//true,指向同一个对象
System.out.println(i3==i4);//false指向不同对象
}
}
public class Test1 {
public static void main(String[] args) {
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);//false,直接创建两个不同对象
System.out.println(i3==i4);//false,直接创建两个不同对象
}
}
注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。Double、Float的valueOf方法的实现是类似的。
5.在一个静态方法内调用一个非静态成员为什么是非法的?
由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
一个非静态成员在未实例化前是不存在的,静态方法是不用实例化就可以调用的,所以调用不到非静态变量、成员和方法。
6.在 Java 中定义一个不做事且没有参数的构造方法的作用
如果有该类有子类,就会在调用子类实例过程,在构造方法中默认调用super()。如果父类没有无参数构造方法,只有有参数构造方法,父类就不会默认创建一个无参构造方法,导致super()调用失败。
7.import java 和 javax 有什么区别?
java和javax都是Java的API包,java为baijava语言的核du心包,javax为java语言的扩展包。
java包是java基础zhi核心类库,也就是Java Development kit ,提供java语言编程核dao心包,如io、awt、集合库(如Collection、List、Map)等;
javax是java基础上的扩展包,如swing、servlet、jsp、xml等类库。
8.接口和抽象类的区别是什么?
抽象类中不可以定义静态的抽象方法可以定义静态的实例方法
- 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法。
- 接口中除了 static、final 变量,不能有其他变量,而抽象类中则不一定。
- 一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过 extends 关键字扩展多个接口。
- 接口方法默认修饰符是 public,抽象方法可以有 public、protected 和 default 这些修饰符(抽象方法就是为了被重写所以不能使用 private 关键字修饰!)。
- 从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。
总结一下 jdk7~jdk9 Java 中接口概念的变化:
- 在 jdk 7 或更早版本中,接口里面只能有常量变量和抽象方法。这些接口方法必须由选择实现接口的类实现。
- jdk8 的时候接口可以有默认方法和静态方法功能。
- Jdk 9 在接口中引入了私有方法和私有静态方法。
9.成员变量与局部变量的区别有哪些?
1.成员变量属于类,局部变量属于方法。成员变量可以使用访问权限符和static来修饰,而成员变量不行。他们都可以用final来修饰
2.存储方式也有区别。如果成员变量用static修饰,这个成员变量是属于类的,是类成员变量;如果没有用static,这个成员变量是属于实例的,只有实例对象可以使用。局部变量为基本数据类型,则存储在栈内存内,如果是引用数据类型,则存储堆中引用对象的地址或者是指向常量池的地址
3.从生命周期来看,成员变量和实例对象是一样的;局部变量随着方法的生命周期一致。
4.成员变量会被默认赋值,而局部变量不会默认赋值,必须手动赋值
10.创建一个对象用什么运算符?对象实体与对象引用有何不同?
new运算法。new的引用放在栈中,new的实体放在堆内存中。一个对象引用可以指向0个或者1个对象实例,一个对象实例可以被0-n个对象引用指向
11.什么是方法的返回值?返回值在类的方法里的作用是什么?
方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果。返回值就是一个值,可以用于操作运算和赋值
12.一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?
对一个类进行初始化。能。默认会创建一个无参构造方法
13.构造方法有哪些特性?
1.构造方法名字和类相同
2.没有返回值,但是不能用void
3.产生实例的时候自动调用
14.静态方法和实例方法有何不同
1.调用方式来说,实例方法的调用必须通过实例后的对象.方法进行调用,而静态方法无序实例化,直接类名.方法就可以调用,也可以通过对象.方法进行调用
2.静态方法只允许调用本类的静态方法和静态变量,因为执行前就已经初始化;而实例方法无限制
15.对象的相等与指向他们的引用相等,两者有什么不同?
对象的相等是内存中的内容是否相等,而引用相等是地址是否相等
16.在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?
帮助子类做初始化工作
17.== 与 equals
==:
如果==比较的是基本数据类型,那就是比较他们的值是否相等。
如果==比较是的引用数据类型,就是比较他们的地址是否相等。
equals():
比较两个对象是否相等。如果没有重写equals,作用和==类似。如果重写了equals,一般重写都是比较内容了
拿String来说:
- String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
18.hashCode 与 equals (重要)
面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode 方法?”
hashCode()介绍
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
为什么要有 hashCode
我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode: 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与该位置其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()
方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的 Java 启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
通过我们可以看出:hashCode()
的作用就是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()
在散列表中才有用,在其它情况下没用。在散列表中 hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
hashCode()与 equals()的相关规定
- 如果两个对象相等,则 hashcode 一定也是相同的
- 两个对象相等,对两个对象分别调用 equals 方法都返回 true
- 两个对象有相同的 hashcode 值,它们也不一定是相等的
- 因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
- hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
public class Student {
int age;
String name ;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(age, name);
}
}
19.为什么 Java 中只有值传递?
方法参数是基本数据类型的话,就是将数据进行拷贝后执行方法体;如果是引用数据类型,就可以改变指向的具体内存中的数据。
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
- 一个方法可以改变一个对象参数的状态。
- 一个方法不能让对象参数引用一个新的对象。
请你解释什么是值传递和引用传递?
值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。 所以对引用对象进行操作会同时改变原对象.一般认为java内的传递都是值传递.
20.简述线程、程序、进程的基本概念。以及他们之间关系是什么?
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
21.线程有哪些基本状态?
1.初始状态:线程被构建,未调用start()方法
2.运行状态:java将运行和就绪都成为运行状态
3.阻塞状态:表示线程被阻塞
4.等待状态:表示该线程进入等待,需要线程通知或中断
5.超时等待状态:不同于等待,可以在指定的时间自行返回
6.终止状态:表示当前线程已被执行完毕
当线程执行 wait()
方法之后,线程进入 **WAITING(等待)**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)
方法或 wait(long millis)
方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞) 状态。线程在执行 Runnable 的run()
方法之后将会进入到 TERMINATED(终止) 状态。
22.关于 final 关键字的一些总结
final主要用于变量、方法和类中
1.变量中:被final修饰的基本数据类型变量必须被显式地初始化,且初始化以后不能被改变;被final修饰的引用变量在初始化以后不能指向别的对象。
2.方法中:被final修饰的方法无法被其子类重写;同时能够在一定程度上提升方法执行效率。类中所有的private方法被隐式定义为了final方法
3.类中:被final修饰的类无法被继承,类中的所有成员方法都默认修饰final
23.Java中的异常处理
在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable: 有两个重要的子类:Exception(异常) 和 Error(错误) ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。
Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java 虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如 Java 虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java 中,错误通过 Error 的子类描述。
Exception(异常):是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。RuntimeException 异常由 Java 虚拟机抛出。NullPointerException(要访问的变量没有引用任何对象时,抛出该异常)、ArithmeticException(算术运算异常,一个整数除以 0 时,抛出该异常)和 ArrayIndexOutOfBoundsException (下标越界异常)。
注意:异常和错误的区别:异常能被程序本身处理,错误是无法处理。
Throwable 类常用方法
- public string getMessage():获取异常信息
- public string toString():获取异常详细信息
- public string getLocalizedMessage():返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
- public void printStackTrace():在控制台上打印 Throwable 对象封装的异常信息
异常处理总结
- try 块: 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
- catch 块: 用于处理 try 捕获到的异常。
- finally 块: 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。
在以下 4 种特殊情况下,finally 块不会被执行:
- 在 finally 语句块第一行发生了异常。 因为在其他行,finally 块还是会得到执行
- 在出现异常前中用了 System.exit(int)已退出程序。 exit 是带参函数 ;
- 程序所在的线程死亡。
- 关闭 CPU。
24.Java 序列化中如果有些字段不想进行序列化,怎么办?
序列化:将java对象转换成字节序列;反序列化:将字节序列恢复成java对象
对于不想进行序列化的变量,使用 transient 关键字修饰。
transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。
25.获取用键盘输入常用的两种方法
Scanner和BufferedReader都可以读入键盘输入
方法 1:通过 Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();Copy to clipboardErrorCopied
方法 2:通过 BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();
26.Java 中 IO 流详解
IO流分类
- 按流的流向分,可分为输入流和输出流
- 按操作单元可以分为字节流和字符流
- 按照流的角色可以分为节点流和处理流
基类是InputStream、Reader、OutputStream、Writer
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
既然有了字节流,为什么还要有字符流?
问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
回答:字符流是由 Java 虚拟机将字节转换得到,需要->字节流->另一端接收->转换成字符流读取。所以如果仅涉及文本内容的I/O流,直接使用字符流会更加方便,也不会产生乱码。
27.BIO,NIO,AIO 有什么区别?
同步与异步
- 同步: 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。
- 异步: 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。
同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。
阻塞和非阻塞
- 阻塞: 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。
- 非阻塞: 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。
BIO、NIO、AIO区别
- BIO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机 1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
- NIO (Non-blocking/New I/O): NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.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 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO 操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
28.深拷贝和浅拷贝
简单来说,如果是对于基本数据类型而言,深拷贝和浅拷贝都仅仅是对值进行复制;而对于引用类型,深拷贝是拷贝一个内容完全一样的对象,而浅拷贝仅仅是拷贝地址。比如一个Int类型数据,无论深浅拷贝,都是拷贝这个数具体值,而例如一个int数组{1,2,3},深拷贝拷贝一个{1,2,3}的数组,地址不一致,浅拷贝则是拷贝数组地址
这里其实有一个概念,就是引用拷贝,比如我直接用一个指针指向一个对象,那么其实他是引用拷贝
浅拷贝就已经是两个不同的指针了,但是其实指向的是同一个内存区域,但是指针是不同的
深拷贝就是具体将里面所有的东西都进行clone
拷贝是需要继承cloneable接口的
29.什么是java序列化?如何实现java序列化?
java序列化就是将对象转换成字节序列。实现方法就是让对应对象实现serialization接口,是一个标记性接口,不需要重写方法,然后构造一个ObjectOutPutStream对象输出流,使用writeObject方法将Obj写成字节序列
30.你知道java8的新特性吗,请简单介绍一下
Lambda 表达式 :Lambda允许把函数作为一个方法的参数
方法引用: 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
默认方法:默认方法就是一个在接口里面有了一个实现的方法。
新工具:新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
31.请你说说Lamda表达式的优缺点。
优点:
- 简洁
- 非常容易并行计算。
- 可能代表未来的编程趋势。
缺点:
- 若不用并行计算,很多时候计算速度没有比传统的 for 循环快。(并行计算有时需要预热才显示出效率优势)
- 不容易调试。
- 可读性比较差 若其他程序员没有学过 lambda 表达式,代码不容易让其他语言的程序员看懂。
32.java序列化
1.什么java序列化?
java序列化就是把对象改成二进制的过程,可以保存到磁盘或者网络发送
1.实现java.io.serializable
2.java.io.externalizable
3.ObjectInputStream
4.ObjectOutputStream
2.序列化有什么用
3.如果不想序列化,怎么办
1.变量声明为静态变量 static
2.变量声明为瞬态变量 transient
静态变量反序列化是直接从内存中拿值
4.serialVersionUID有什么用?
修改属性,会报异常InvalidClassException无效类,因为每次修改会使得serialVersionUID改变,这时的办法是自己定义
5.是否可以自定义序列化过程
33.Object类下面有的方法
- Object() :构造方法
- registerNative(): 使得JVM发现本机
- clone():拷贝方法,只有实现了Cloneable的接口才能调用该方法,在深拷贝的浅拷贝的时候需要用到
- getClass():获得运行时的类型,返回当前Object的类对象,效果等同于Object.class
- equals():比较两个对象的内容是否相等,如果没有重写的话,和==的效果是一样的
- hashCode():返回对象的物理地址,和equals一起重写,这里和equals共同引出hashmap的原理
- toString():返回该对象的字符串表示
- wait():使得当前线程进入等待,直到其他线程调用notify()和notifyAll();
- wait(long time):进入time waiting的状态
- notify():唤醒等待的某个线程
- notifyAll():唤醒等待的所有线程
- finalize():调用垃圾回收方法,但是并不是立即
34. ==与equals的区别
==比较的是值,如果是基本数据类型,就是比较具体的数值.
如果比较的是引用数据类型,那么比较的是地址值
equals是Object中的方法,默认也是比较地址,效果和==是一样的,需要进行重写
public static void main(String[] args) {
String s1 = new String("zs");
String s2 = new String("zs");
System.out.println(s1 == s2);
String s3 = "zs";
String s4 = "zs";
System.out.println(s3 == s4);
System.out.println(s3 == s1);
String s5 = "zszs";
String s6 = s3 + s4;
System.out.println(s5 == s6);
final String s7 = "zs";
final String s8 = "zs";
String s9 = s7 + s8;
System.out.println(s5 == s9);
final String s10 = s3 + s4;
System.out.println(s5 == s10);
}
@Test
public void test2(){
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";//编译期优化
//如果拼接符号的前后出现了变量,则相当于在堆空间中new String(),具体的内容为拼接的结果:javaEEhadoop
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
//intern():判断字符串常量池中是否存在javaEEhadoop值,如果存在,则返回常量池中javaEEhadoop的地址;
//如果字符串常量池中不存在javaEEhadoop,则在常量池中加载一份javaEEhadoop,并返回次对象的地址。
String s8 = s6.intern();
System.out.println(s3 == s8);//true
}
@Test
public void test3(){
String s1 = "a";
String s2 = "b";
String s3 = "ab";
/*
如下的s1 + s2 的执行细节:(变量s是我临时定义的)
① StringBuilder s = new StringBuilder();
② s.append("a")
③ s.append("b")
④ s.toString() --> 约等于 new String("ab")
补充:在jdk5.0之后使用的是StringBuilder,在jdk5.0之前使用的是StringBuffer
*/
String s4 = s1 + s2;//
System.out.println(s3 == s4);//false
}
/*
1. 字符串拼接操作不一定使用的是StringBuilder!
如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非StringBuilder的方式。
2. 针对于final修饰类、方法、基本数据类型、引用数据类型的量的结构时,能使用上final的时候建议使用上。
*/
@Test
public void test4(){
final String s1 = "a";
final String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
System.out.println(s3 == s4);//true
}
35. final关键字
final用来修饰类表示类不可变,不可以继承
final修饰方法,表示该方法不可以重写
final修饰变量,表示这个变量是常量
如果修饰的是基本数据类型,这个值本身不能改变;如果修饰的是引用类型,引用的指向不能修改
String 跟其他两个类的区别是
String是final类型,每次声明的都是不可变的对象,
所以每次操作都会产生新的String对象,然后将指针指向新的String对象。
StringBuffer,StringBuilder都是在原有对象上进行操作
所以,如果需要经常改变字符串内容,则建议采用这两者。
StringBuffer vs StringBuilder
前者是线程安全的,后者是线程不安全的。
线程不安全性能更高,所以在开发中,优先采用StringBuilder.
StringBuilder > StringBuffer > String
什么叫线程安全和不安全?
如果一个字符串,在多线程环境下对这个对象的访问不需要加入额外的同步控制,操作结果依然正确,说明是线程安全的.StringBuffer里面的方法都选了synchronized修饰
如果是在方法内使用字符拼接,如果这个字符串不需要做参数传递,也就是在之后不会被用到,就可以用builder加快速度.
36.抽象类和接口的区别
-
语法:
JDK1.8以前
- 抽象类:方法可以有抽象的,也可以有非抽象, 有构造器(非抽象比如一些crud代码抽出来base类)
- 接口:方法都是抽象,属性都是常量,默认有public static final修饰(分层开发,用接口来进行解耦)
JDK1.8以后
接口页可以有实现的方法,不过要在方法的声明上加上default或者是static
-
设计:
-
抽象类:同一类事物的抽取,比如针对Dao层操作的封装,如,BaseDao,BaseServiceImpl
-
接口:通常更像是一种标准的制定,定制系统之间对接的标准
-
-
多继承,多重继承,多实现
-
- 多重继承:A->B->C(爷孙三代的关系)
- 多实现:Person implements IRunable,IEatable(符合多项国际化标准)
- 多继承:接口可以多继承,类只支持单继承
总结:
- 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
- 抽象类要被子类继承,接口要被类实现。
- 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
- 抽象类里可以没有抽象方法。
- 接口可以被类多实现(被其他接口多继承),抽象类只能被单继承。
- 接口中没有
this
指针,没有构造函数,不能拥有实例字段(实例变量)或实例方法。- 抽象类不能在Java 8 的 lambda 表达式中使用
37.int和Integer的区别
Integer i1 = new Integer(12);
Integer i2 = new Integer(12);
System.out.println(i1 == i2);
Integer i3 = 126;
Integer i4 = 126;
int i5 = 126;
System.out.println(i3 == i4);
System.out.println(i3 == i5);
Integer i6 = 128;
Integer i7 = 128;
int i8 = 128;
System.out.println(i6 == i7);
System.out.println(i6 == i8);
- 都定义为Integer的比较:
new:一旦new,就是开辟一块新内存,结果肯定是false
不new:
看范围
Integer做了缓存,-128至127,当你取值在这个范围的时候,会采用缓存的对象,所以会相等,具体可以看下面的源码
当不在这个范围,内部创建新的对象,此时不相等
- Integer和int的比较:
实际比较的是数值,Integer会做拆箱的动作,来跟基本数据类型做比较
此时跟是否在缓存范围内或是否new都没关系
源码分析:
当我们写Integer i = 126,实际上做了自动装箱:Integer i = Integer.valueOf(126);
分析这段源码
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
//IntegerCache是Integer的内部类
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
38.方法的重写和重载的区别
重载:发生在一个类中,方法名相同,参数列表不同.(注意和返回类型是没有关系的)如果方法名相同,参数列表相同,返回值类型不同,则直接报错,因为会认为是统一方法.
重写:发生在父类与子类之间,方法名相同,参数列表相同.
39.serialVersionUID的作用是什么
当执行序列化时,我们写对象到磁盘中,会根据当前这个类的结构生成一个版本号ID,当反序列化时,程序会比较磁盘中的序列化版本号ID跟当前的类结构生成的版本号ID是否一致,如果一致则反序列化成功,否则,反序列化失败.
也就是说在版本升级的时候使用,能够用来确定类的唯一性,如果当前版本号不同,说明类发生改变,反序列化失败.
40.谈谈java的异常体系
异常体系分为Error和Exception
Error一般是JVM异常,比如OOM和StackOverflow
exception分为运行时异常和非运行时异常
运行时异常就是我们通常说的logic异常
就是在编写程序的时候会出现的异常,比如空指针异常,索引越界异常,这种不能进行trycatch处理,需要去修改程序.
算数异常,
空指针,
类型转换异常,
数组越界,
NumberFormateException(数字格式异常,转换失败,比如“a12”就会转换失败)
而非运行时异常是客观因素产生的,如果FileNotFound或者是一些别的客观异常,提前做好处理也是java程序健壮性的一种表现.
IOException,
SQLException,
FileNotFoundException,
NoSuchFileException,
NoSuchMethodException
41.谈谈throw和throws的区别
throw,作用于方法内,用于主动抛出异常
throws, 作用于方法声明上,声明该方法有可能会抛些某些异常
42.创建线程的方式
1.继承Thread
2.实现Runable接口
3.实现Callable接口(可以获取线程执行后的返回值)
run()并不是开启一个线程,而是一个方法
只有new Thread(? extend Runable) .start()才是建立一个线程
本质上都是继承Thread来new一个线程
在开发中使用线程池比较多
Runable接口和Callable最大区别是Callable可以有返回值,在Callable的泛型加上返回值类型
43.线程的生命周期及六大状态
new,runnable,blocked,waiting,timed waiting,terminated
**六大状态:**NEW、RUNNABLE、BLOCKED、WAITIING、TIME_WAITING、TERMINAED
1,当进入synchronized同步代码块或同步方法时,且没有获取到锁,线程就进入了blocked状态,直到锁被释放,重新进入runnable状态
2,当线程调用wait()或者join时,线程都会进入到waiting状态,当调用notify或notifyAll时,或者join的线程执行结束后,会进入runnable状态
3,当线程调用sleep(time),或者wait(time)时,进入timed waiting状态,
当休眠时间结束后,或者调用notify或notifyAll时会重新runnable状态。
4,程序执行结束,线程进入terminated状态
blocked,waiting,timed waiting 我们都称为阻塞状态
上述的就绪状态和运行状态,都表现为runnable状态
44.sleep和wait的区别
1,定义在不同的类上
sleep方法定义在Thread类中
wait方法定义在Object类中
为什么wait要定义在Object中,而不定义在Thread中?
在同步代码块中,我们说需要一个对象锁来实现多线程的互斥效果,也就是说,Java的锁是对象级别的,而不是线程级别的。
2.对锁资源的处理方式不同
sleep不会释放锁
wait会释放锁
3.写在不同的代码域
sleep可以使用在任何代码块
wait必须在同步方法或者同步代码块中执行
4.方法使用区别
sleep会进入time waiting状态,到时间解除sleep
wait会进入waiting状态,可以用时间,或者使用notify或者notifyAll方法进行唤醒
为什么wait必须写在同步代码块中?
原因是避免CPU切换到其他线程,而其他线程又提前执行了notify方法,那这样就达不到我们的预期(先wait再由其他线程来唤醒),所以需要一个同步锁来保护
45.类加载器与类的加载机制
- 引导类加载器
- 扩展类加载器
- 应用程序类加载器
- 用户自定义类加载器:继承java.lang.ClassLoader类,重写findClass方法
类加载机制:
双亲委派机制
优点:
- 避免类的重复加载
- 这样做明显提高了安全性,保证了核心代码的安全可用,防止api被随意篡改
沙箱安全机制
自定义String类是先使用引导类加载器进行加载,先加载自带的String,进而保护核心源代码,这就是沙箱安全机制。
46.并发与并行的区别
并发:
- 多个事件在同一时间段内发生
- 同一个CPU执行多个任务,按细分的时间片交替执行
- 并发的多个任务会相互抢占资源
并行:
- 多个事件在同一时间点上发生
- 在多个CPU上同时处理多个任务
- 并行的多个任务不会相互抢占资源