同步操作将从 程序员大彬/Java-learning 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
Table of Contents generated with DocToc
大家好,我是大彬。最近在面试,看了很多面经,这段时间抽空将Java常见的面试题总结了一下,如果对你有帮助,可以收藏和点赞,后续还会继续更新新的面试题目哦!
文章目录
首先给大家分享一个github仓库,上面放了200多本经典的计算机书籍,包括C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,可以star一下,下次找书直接在上面搜索,仓库持续更新中~
github地址:https://github.com/Tyson0314/java-books
如果github访问不了,可以访问gitee仓库。
**Java是一门面向对象的编程语言。**面向对象和面向过程的区别参考下一个问题。
Java具有平台独立性和移植性。
Java具有稳健性。
面向对象和面向过程是一种软件开发思想。
面向过程就是分析出解决问题所需要的步骤,然后用函数按这些步骤实现,使用的时候依次调用就可以了。
面向对象是把构成问题事务分解成各个对象,分别设计这些对象,然后将他们组装成有完整功能的系统。面向过程只用函数实现,面向对象是用类实现各个功能模块。
以五子棋为例(来源网络),面向过程的设计思路就是首先分析问题的步骤: 1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。 把上面每个步骤用分别的函数来实现,问题就解决了。
而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为:
黑白双方负责接受用户的输入,并告知棋盘系统棋子布局发生变化,棋盘系统接收到了棋子的变化的信息就负责在屏幕上面显示出这种变化,同时利用规则系统来对棋局进行判定。
JDK和JRE是Java开发和运行工具,其中JDK包含了JRE,而JRE是可以独立安装的。
JDK:Java Development Kit,JAVA语言的软件工具开发包,是整个JAVA开发的核心,它包含了JAVA的运行(JVM+JAVA类库)环境和JAVA工具。
JRE:Java Runtime Environment,Java运行环境,包含JVM标准实现及Java核心类库。JRE是Java运行环境,并不是一个开发环境,所以没有包含任何开发工具(如编译器和调试器)。
JRE是运行基于Java语言编写的程序所不可缺少的运行环境。也是通过它,Java的开发者才得以将自己开发的程序发布到用户手中,让用户使用。
面向对象四大特性:封装,继承,多态,抽象
简单类型 | boolean | byte | char | short | Int | long | float | double |
---|---|---|---|---|---|---|---|---|
二进制位数 | 1 | 8 | 16 | 16 | 32 | 64 | 32 | 64 |
包装类 | Boolean | Byte | Character | Short | Integer | Long | Float | Double |
值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。所以对引用对象进行操作会同时改变原对象。
Java中基础数据类型与它们对应的包装类见下表:
原始类型 | 包装类型 |
---|---|
boolean | Boolean |
byte | Byte |
char | Character |
float | Float |
int | Integer |
long | Long |
short | Short |
double | Double |
装箱:将基础类型转化为包装类型。
拆箱:将包装类型转化为基础类型。
当基础类型与它们的包装类有如下几种情况时,编译器会自动帮我们进行装箱或拆箱:
示例代码:
Integer x = 1; // 装箱 调⽤ Integer.valueOf(1)
int y = x; // 拆箱 调⽤了 X.intValue()
下面看一道常见的面试题:
public void testAutoBox() {
int a = 100;
Integer b = 100;
System.out.println(a == b);
Integer c = 100;
Integer d = 100;
System.out.println(c == d);
Integer e = 200;
Integer f = 200;
System.out.println(e == f);
}
输出:
true
true
false
为什么第三个输出是false?看看 Integer 类的源码就知道啦。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
Integer e = 200;
会调用 调⽤Integer.valueOf(200)
。而从Integer的valueOf()源码可以看到,这里的实现并不是简单的new Integer,而是用IntegerCache做一个cache。
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;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
}
...
}
这是IntegerCache静态代码块中的一段,默认Integer cache 的下限是-128,上限默认127。当赋值100给Integer时,刚好在这个范围内,所以从cache中取对应的Integer并返回,所以二次返回的是同一个对象,所以==比较是相等的,当赋值200给Integer时,不在cache 的范围内,所以会new Integer并返回,当然==比较的结果是不相等的。
先看下Java8 String类的源码:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
}
String类是final的,它的所有成员变量也都是final的。为什么是final的?
1. 可变性
2. 线程安全
使用这种方式会创建两个字符串对象(前提是字符串常量池中没有 "dabin" 这个字符串对象)。
字符串常量池(String Pool)保存着所有字符串字面量,这些字面量在编译时期就确定。字符串常量池位于堆内存中,专门用来存储字符串常量。在创建字符串时,JVM首先会检查字符串常量池,如果该字符串已经存在池中,则返回其引用,如果不存在,则创建此字符串并放入池中,并返回其引用。
Java面试经常会出现的一道题目,Object的常用方法。下面给大家整理一下。
object常用方法有:toString()、equals()、hashCode()、clone()等。
toString
默认输出对象地址。
public class Person {
private int age;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public static void main(String[] args) {
System.out.println(new Person(18, "程序员大彬").toString());
}
//output
//me.tyson.java.core.Person@4554617c
}
可以重写toString方法,按照重写逻辑输出对象值。
public class Person {
private int age;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return name + ":" + age;
}
public static void main(String[] args) {
System.out.println(new Person(18, "程序员大彬").toString());
}
//output
//程序员大彬:18
}
equals
默认比较两个引用变量是否指向同一个对象(内存地址)。
public class Person {
private int age;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public static void main(String[] args) {
String name = "程序员大彬";
Person p1 = new Person(18, name);
Person p2 = new Person(18, name);
System.out.println(p1.equals(p2));
}
//output
//false
}
可以重写equals方法,按照age和name是否相等来判断:
public class Person {
private int age;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (o instanceof Person) {
Person p = (Person) o;
return age == p.age && name.equals(p.name);
}
return false;
}
public static void main(String[] args) {
String name = "程序员大彬";
Person p1 = new Person(18, name);
Person p2 = new Person(18, name);
System.out.println(p1.equals(p2));
}
//output
//true
}
hashCode
将与对象相关的信息映射成一个哈希值,默认的实现hashCode值是根据内存地址换算出来。
public class Cat {
public static void main(String[] args) {
System.out.println(new Cat().hashCode());
}
//out
//1349277854
}
clone
java赋值是复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的。Object对象有个clone()方法,实现了对
象中各个属性的复制,但它的可见范围是protected的。
protected native Object clone() throws CloneNotSupportedException;
所以实体类使用克隆的前提是:
public class Cat implements Cloneable {
private String name;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Cat c = new Cat();
c.name = "程序员大彬";
Cat cloneCat = (Cat) c.clone();
c.name = "大彬";
System.out.println(cloneCat.name);
}
//output
//程序员大彬
}
getClass
返回此 Object 的运行时类,常用于java反射机制。
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public static void main(String[] args) {
Person p = new Person("程序员大彬");
Class clz = p.getClass();
System.out.println(clz);
//获取类名
System.out.println(clz.getName());
}
/**
* class com.tyson.basic.Person
* com.tyson.basic.Person
*/
}
wait
当前线程调用对象的wait()方法之后,当前线程会释放对象锁,进入等待状态。等待其他线程调用此对象的notify()/notifyAll()唤醒或者等待超时时间wait(long timeout)自动唤醒。线程需要获取obj对象锁之后才能调用 obj.wait()。
notity
obj.notify()唤醒在此对象上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象上等待的所有线程。
浅拷贝:拷⻉对象和原始对象的引⽤类型引用同⼀个对象。
以下例子,Cat对象里面有个Person对象,调用clone之后,克隆对象和原对象的Person引用的是同一个对象,这就是浅拷贝。
public class Cat implements Cloneable {
private String name;
private Person owner;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Cat c = new Cat();
Person p = new Person(18, "程序员大彬");
c.owner = p;
Cat cloneCat = (Cat) c.clone();
p.setName("大彬");
System.out.println(cloneCat.owner.getName());
}
//output
//大彬
}
深拷贝:拷贝对象和原始对象的引用类型引用不同的对象。
以下例子,在clone函数中不仅调用了super.clone,而且调用Person对象的clone方法(Person也要实现Cloneable接口并重写clone方法),从而实现了深拷贝。可以看到,拷贝对象的值不会受到原对象的影响。
public class Cat implements Cloneable {
private String name;
private Person owner;
@Override
protected Object clone() throws CloneNotSupportedException {
Cat c = null;
c = (Cat) super.clone();
c.owner = (Person) owner.clone();//拷贝Person对象
return c;
}
public static void main(String[] args) throws CloneNotSupportedException {
Cat c = new Cat();
Person p = new Person(18, "程序员大彬");
c.owner = p;
Cat cloneCat = (Cat) c.clone();
p.setName("大彬");
System.out.println(cloneCat.owner.getName());
}
//output
//程序员大彬
}
equals与hashcode的关系:
hashcode方法主要是用来提升对象比较的效率,先进行hashcode()的比较,如果不相同,那就不必在进行equals的比较,这样就大大减少了equals比较的次数,当比较对象的数量很大的时候能提升效率。
之所以重写equals()要重写hashcode(),是为了保证equals()方法返回true的情况下hashcode值也要一致,如果重写了equals()没有重写hashcode(),就会出现两个对象相等但hashcode()不相等的情况。这样,当用其中的一个对象作为键保存到hashMap、hashTable或hashSet中,再以另一个对象作为键值去查找他们的时候,则会查找不到。
Java创建对象有以下几种方式:
Java中类实例化顺序:
public class LifeCycle {
// 静态属性
private static String staticField = getStaticField();
// 静态代码块
static {
System.out.println(staticField);
System.out.println("静态代码块初始化");
}
// 普通属性
private String field = getField();
// 普通代码块
{
System.out.println(field);
System.out.println("普通代码块初始化");
}
// 构造方法
public LifeCycle() {
System.out.println("构造方法初始化");
}
// 静态方法
public static String getStaticField() {
String statiFiled = "静态属性初始化";
return statiFiled;
}
// 普通方法
public String getField() {
String filed = "普通属性初始化";
return filed;
}
public static void main(String[] argc) {
new LifeCycle();
}
/**
* 静态属性初始化
* 静态代码块初始化
* 普通属性初始化
* 普通代码块初始化
* 构造方法初始化
*/
}
对于基本数据类型,==比较的是他们的值。基本数据类型没有equal方法;
对于复合数据类型,==比较的是它们的存放地址(是否是同一个对象)。equals()默认比较地址值,重写的话按照重写逻辑去比较。
static
static可以用来修饰类的成员方法、类的成员变量。
静态变量
static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
以下例子,age为非静态变量,则p1打印结果是:Name:zhangsan, Age:10
;若age使用static修饰,则p1打印结果是:Name:zhangsan, Age:12
,因为static变量在内存只有一个副本。
public class Person {
String name;
int age;
public String toString() {
return "Name:" + name + ", Age:" + age;
}
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "zhangsan";
p1.age = 10;
Person p2 = new Person();
p2.name = "lisi";
p2.age = 12;
System.out.println(p1);
System.out.println(p2);
}
/**Output
* Name:zhangsan, Age:10
* Name:lisi, Age:12
*///~
}
静态方法
static方法一般称作静态方法。静态方法不依赖于任何对象就可以进行访问,通过类名即可调用静态方法。
public class Utils {
public static void print(String s) {
System.out.println("hello world: " + s);
}
public static void main(String[] args) {
Utils.print("程序员大彬");
}
}
静态代码块
静态代码块只会在类加载的时候执行一次。以下例子,startDate和endDate在类加载的时候进行赋值。
class Person {
private Date birthDate;
private static Date startDate, endDate;
static{
startDate = Date.valueOf("2008");
endDate = Date.valueOf("2021");
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
}
静态内部类
在静态方法里,使用⾮静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。⽽静态内部类不需要。
public class OuterClass {
class InnerClass {
}
static class StaticInnerClass {
}
public static void main(String[] args) {
// 在静态方法里,不能直接使用OuterClass.this去创建InnerClass的实例
// 需要先创建OuterClass的实例o,然后通过o创建InnerClass的实例
// InnerClass innerClass = new InnerClass();
OuterClass outerClass = new OuterClass();
InnerClass innerClass = outerClass.new InnerClass();
StaticInnerClass staticInnerClass = new StaticInnerClass();
outerClass.test();
}
public void nonStaticMethod() {
InnerClass innerClass = new InnerClass();
System.out.println("nonStaticMethod...");
}
}
final
基本数据类型用final修饰,则不能修改,是常量;对象引用用final修饰,则引用只能指向该对象,不能指向别的对象,但是对象本身可以修改。
final修饰的方法不能被子类重写
final修饰的类不能被继承。
this
this.属性名称
指访问类中的成员变量,可以用来区分成员变量和局部变量。如下代码所示,this.name
访问类Person当前实例的变量。
/**
* @description:
* @author: 程序员大彬
* @time: 2021-08-17 00:29
*/
public class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
this.方法名称
用来访问本类的方法。以下代码中,this.born()
调用类 Person 的当前实例的方法。
/**
* @description:
* @author: 程序员大彬
* @time: 2021-08-17 00:29
*/
public class Person {
String name;
int age;
public Person(String name, int age) {
this.born();
this.name = name;
this.age = age;
}
void born() {
}
}
super
super 关键字用于在子类中访问父类的变量和方法。
class A {
protected String name = "大彬";
public void getName() {
System.out.println("父类:" + name);
}
}
public class B extends A {
@Override
public void getName() {
System.out.println(super.name);
super.getName();
}
public static void main(String[] args) {
B b = new B();
b.getName();
}
/**
* 大彬
* 父类:大彬
*/
}
在子类B中,我们重写了父类的getName()方法,如果在重写的getName()方法中我们要调用父类的相同方法,必须要通过super关键字显式指出。
**同个类中的多个方法可以有相同的方法名称,但是有不同的参数列表,这就称为方法重载。**参数列表又叫参数签名,包括参数的类型、参数的个数、参数的顺序,只要有一个不同就叫做参数列表不同。
重载是面向对象的一个基本特性。
public class OverrideTest {
void setPerson() { }
void setPerson(String name) {
//set name
}
void setPerson(String name, int age) {
//set name and age
}
}
**方法的重写描述的是父类和子类之间的。当父类的功能无法满足子类的需求,可以在子类对方法进行重写。**方法重写时, 方法名与形参列表必须一致。
如下代码,Person为父类,Student为子类,在Student中重写了dailyTask方法。
public class Person {
private String name;
public void dailyTask() {
System.out.println("work eat sleep");
}
}
public class Student extends Person {
@Override
public void dailyTask() {
System.out.println("study eat sleep");
}
}
语法层面上 1)抽象类可以有方法实现,而接口的方法中只能是抽象方法; 2)抽象类中的成员变量可以是各种类型的,接口中的成员变量只能是public static final类型; 3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法; 4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
设计层面上的区别 1)抽象层次不同。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口只是对类行为进行抽象。继承抽象类是一种"是不是"的关系,而接口实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是具备不具备的关系,比如鸟是否能飞。 2) 继承抽象类的是具有相似特点的类,而实现接口的却可以不同的类。
门和警报的例子:
class AlarmDoor extends Door implements Alarm {
//code
}
class BMWCar extends Car implements Alarm {
//code
}
常见的RuntimeException:
unchecked Exception:
Error:JVM 无法解决的严重问题,如栈溢出(StackOverflowError)、内存溢出(OOM)等。程序无法处理的错误。
Exception:其它因编程错误或偶然的外在因素导致的一般性问题。可以在代码中进行处理。如:空指针异常、数组下标越界等。
unchecked exception包括RuntimeException和Error类,其他所有异常称为检查(checked)异常。
throw:用于抛出一个具体的异常对象。
throws:用在方法签名中,用于声明该方法可能抛出的异常。子类方法抛出的异常范围更加小,或者根本不抛异常。
同步阻塞IO : 用户进程发起一个IO操作以后,必须等待IO操作的真正完成后,才能继续运行。
同步非阻塞IO: 客户端与服务器通过Channel连接,采用多路复用器轮询注册的Channel。提高吞吐量和可靠性。用户进程发起一个IO操作以后,可做其它事情,但用户进程需要轮询IO操作是否完成,这样造成不必要的CPU资源浪费。
异步非阻塞IO: 非阻塞异步通信模式,NIO的升级版,采用异步通道实现异步通信,其read和write方法均是异步方法。用户进程发起一个IO操作,然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知。类似Future模式。
每个线程都有一个ThreadLocalMap(ThreadLocal内部类),Map中元素的键为ThreadLocal,而值对应线程的变量副本。
调用threadLocal.set()-->调用getMap(Thread)-->返回当前线程的ThreadLocalMap<ThreadLocal, value>-->map.set(this, value),this是ThreadLocal
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
调用get()-->调用getMap(Thread)-->返回当前线程的ThreadLocalMap<ThreadLocal, value>-->map.getEntry(this),返回value
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
threadLocals的类型ThreadLocalMap的键为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,如longLocal和stringLocal。
public class ThreadLocalDemo {
ThreadLocal<Long> longLocal = new ThreadLocal<>();
public void set() {
longLocal.set(Thread.currentThread().getId());
}
public Long get() {
return longLocal.get();
}
public static void main(String[] args) throws InterruptedException {
ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
threadLocalDemo.set();
System.out.println(threadLocalDemo.get());
Thread thread = new Thread(() -> {
threadLocalDemo.set();
System.out.println(threadLocalDemo.get());
}
);
thread.start();
thread.join();
System.out.println(threadLocalDemo.get());
}
}
ThreadLocal 并不是用来解决共享资源的多线程访问的问题,因为每个线程中的资源只是副本,并不共享。因此ThreadLocal适合作为线程上下文变量,简化线程内传参。
fast-fail是Java集合的一种错误机制。当多个线程对同一个集合进行操作时,就有可能会产生fast-fail事件。 例如:当线程a正通过iterator遍历集合时,另一个线程b修改了集合的内容,此时modCount(记录集合操作过程的修改次数)会加1,不等于expectedModCount,那么线程a访问集合的时候,就会抛出ConcurrentModificationException,产生fast-fail事件。边遍历边修改集合也会产生fast-fail事件。
解决方法:
java中,类不支持多继承。接口才支持多继承。接口的作用是拓展对象功能。当一个子接口继承了多个父接口时,说明子接口拓展了多个功能。当一个类实现该接口时,就拓展了多个的功能。
Java不支持多继承的原因(来源网络):
org.apache.commons
中的工具类BeanUtils
和PropertyUtils
进行对象复制。同步:发出一个调用时,在没有得到结果之前,该调用就不返回。
异步:在调用发出后,被调用者返回结果之后会通知调用者,或通过回调函数处理这个调用。
阻塞和非阻塞关注的是线程的状态。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会恢复运行。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
举个例子,理解下同步、阻塞、异步、非阻塞的区别:
同步就是烧开水,要自己来看开没开;异步就是水开了,然后水壶响了通知你水开了(回调通知)。阻塞是烧开水的过程中,你不能干其他事情,必须在旁边等着;非阻塞是烧开水的过程里可以干其他事情。
[Java8 新特性总结](Java-learning/Java8.md at master · Tyson0314/Java-learning (github.com))
序列化:把内存中的对象转换为字节序列的过程。
反序列化:把字节序列恢复为Java对象的过程。
实现Serializable接口即可。序列化的时候(如objectOutputStream.writeObject(user)),会判断user是否实现了Serializable(obj instanceof Serializable),如果对象没有实现Serializable接口,在序列化的时候会抛出NotSerializableException异常。
本文已经收录到github仓库,此仓库用于分享Java相关知识总结,包括Java基础、MySQL、Spring Boot、MyBatis、Redis、RabbitMQ、计算机网络、数据结构与算法等等,欢迎大家提pr和star!
github地址:https://github.com/Tyson0314/Java-learning
如果github访问不了,可以访问gitee仓库。
我的其他文章推荐:
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。