目录
1、java语言有哪些优点和缺点?
2、JVM 、 JDK 和 JRE的关系
3、为什么说 Java 语言“编译与解释并存”?
4、Java和c++的区别
5、基本数据类型
5.1、java的8种基本数据类型:
5.2、基本类型和包装类型的区别:
5.3、包装类型的缓存机制:
5.4、自动装箱和自动拆箱:
5.5、浮点数运算的精度丢失问题及解决办法:
6、变量
6.1、成员变量与局部变量的区别?
6.2、静态变量有什么作用?
6.3、字符型常量和字符串常量的区别?
7、方法
7.1、静态方法和实例方法的区别:
7.2、静态方法为什么不能调用非静态成员?
7.3、重载和重写的区别:
8、面向对象基础
8.1、面向对象和面向过程的区别:
8.2、创建一个对象用什么运算符?对象实体与对象引用有何不同?
8.3、对象的相等和引用相等的区别:
8.4、构造函数是什么?能否被override?类没有声明构造方法,程序还能正确执行吗?
8.5、面向对象三大特征
8.6、接口和抽象类有什么共同点和区别?
8.7、深拷贝和浅拷贝的区别?什么是引用拷贝?
9、Object类
9.1、Object 类的常见方法有哪些?
9.2、== 和 equals() 的区别:
9.3、为什么重写 equals() 时必须重写 hashCode() 方法?
10、String类
10.1、String 为什么是不可变的?
10.2、String、StringBuffer、StringBuilder 的区别:
10.3、String s1 = new String("abc");这句话创建了几个字符串对象?
10.4、String#intern 方法有什么作用?
10.5、String 类型的变量和常量做“+”运算时发生了什么?
11、异常
11.1、Exception 和 Error 的区别:
11.2、Checked Exception 和 Unchecked Exception的区别:
11.3、Throwable 类常用方法有哪些?
11.4、try-catch-finally 如何使用?
11.5、finally 中的代码一定会执行吗?
11.6、如何使用 try-with-resources 代替try-catch-finally?
12、泛型的使用方式有哪几种?
13、反射
13.1、反射的使用:
13.2、反射的应用场景:
14、注解的解析方法有哪几种?
15、什么是序列化?什么是反序列化?
16、I/O流
字节缓冲流:
Java 中 3 种常见 IO 模型:
优点:
缺点:
高级编程语言按照程序的执行方式分为两种:
Java 语言既具有编译型语言的特征,又有解释型语言的特征。因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(.class
文件),这种字节码必须由 Java 解释器来解释执行。
占用空间:相比于包装类型, 基本数据类型占用的空间比较小。
比较方式:对于基本数据类型来说,==
比较的是值。对于包装数据类型来说,==
比较的是对象的内存地址。整型包装类对象之间值的比较,需要使用 equals()
方法。
对于不同类型变量的存储问题,有两条黄金法则:
public class Test {
// 成员变量,存放在堆中
int a = 10;
// 被 static 修饰,也存放在堆中,但属于类,不属于对象
// JDK1.7 静态变量从永久代移动了 Java 堆中
static int b = 20;
public void method() {
// 局部变量,存放在栈中
int c = 30;
static int d = 40; // 编译错误,不能在方法中使用 static 修饰局部变量
}
}
Byte
,Short
,Integer
,Long
这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character
创建了数值在 [0,127] 范围的缓存数据,Boolean
直接返回 True
或 False,
两种浮点数类型的包装类 Float
,Double
没有实现缓存机制。
看如下代码:
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);
上述代码应输出false。首先第一行代码会发生自动装箱,因此变量i1使用的是缓存中的对象,而i2会新new一个对象出来,故两者不相等。
如果想比较包装类对象之间值,需要使用equals。
装箱其实就是调用了 包装类的valueOf()
方法,拆箱其实就是调用了 xxxValue()
方法。
因此,
Integer i = 10
等价于Integer i = Integer.valueOf(10)
int n = i
等价于int n = i.intValue()
;
由于底层保存浮点数的机制,会出现小数的精度发生损失的情况,可以使用 BigDecimal
对浮点数的运算,不会造成精度丢失。如:
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
System.out.println(x); /* 0.1 */
System.out.println(y); /* 0.1 */
System.out.println(Objects.equals(x, y)); /* true */
public
,private
,static
等修饰符所修饰,而局部变量不能被访问控制修饰符及 static
所修饰;final
所修饰。static
修饰的,那么这个成员变量是属于类的,如果没有使用 static
修饰,这个成员变量是属于类实例的。 静态变量也就是被 static
关键字修饰的变量。它可以被类的所有实例共享,无论一个类创建了多少个对象,它们都共享同一份静态变量。也就是说,静态变量只会被分配一次内存,即使创建了多个对象,这样可以节省内存。
通常情况下,静态变量会被 final
关键字修饰成为常量。
类名.方法名
的方式,也可以使用 对象.方法名
的方式,而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象 。用new 运算符,new 创建对象实例,对象实例在堆内存中;对象引用指向对象实例,存放在栈内存中。
例如:
String str1 = "hello";
String str2 = new String("hello");
String str3 = "hello";
// 使用 == 比较字符串的引用相等
System.out.println(str1 == str2);
System.out.println(str1 == str3);
// 使用 equals 方法比较字符串的相等
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));
上述代码执行结果为:
结果分析:由于==比较的是引用相等,str2字符串会重新new一个string对象,因此引用和str1不同;str3会优先去字符串常量池寻找有无“hello”这个字符串,有的话就直接引用它,所以str3和str1引用相等。而最后两个比较的是字符串内容是否相等,故str1、str2、str3都相等。
构造方法是一种特殊的方法,主要作用是完成对象的初始化工作。构造函数可以被重载(overload),但不能被重写(override)。
当一个类没有声明构造方法,程序也是可以正常执行的。 当没有显式声明构造方式时,类会默认声明一个无参构造函数。但如果我们自己添加了类的构造方法(无论是否有参),Java 就不会添加默认的无参构造方法了。
抽象类和普通类类似,只是无法实例化,存在的意义就是让其他类继承的。抽象类可以包含抽象方法和非抽象方法,其中抽象方法没有具体的实现,而非抽象方法有具体的实现代码。接口是一种定义了一组方法签名的集合,这些方法默认都是公共的抽象方法,不包含具体的实现代码,比抽象类更加抽象。
共同点:
不同点:
Object 类是一个特殊的类,是所有类的父类。它主要提供了以下 11 个方法:
/**
* native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
*/
public final native Class> getClass()
/**
* native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的HashMap。
*/
public native int hashCode()
/**
* 用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写以用于比较字符串的值是否相等。
*/
public boolean equals(Object obj)
/**
* native 方法,用于创建并返回当前对象的一份拷贝。
*/
protected native Object clone() throws CloneNotSupportedException
/**
* 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
*/
public String toString()
/**
* native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
*/
public final native void notify()
/**
* native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
*/
public final native void notifyAll()
/**
* native方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 ,timeout 是等待时间。
*/
public final native void wait(long timeout) throws InterruptedException
/**
* 多了 nanos 参数,这个参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒。。
*/
public final void wait(long timeout, int nanos) throws InterruptedException
/**
* 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
*/
public final void wait() throws InterruptedException
/**
* 实例被垃圾回收器回收的时候触发的操作
*/
protected void finalize() throws Throwable { }
如果不同时重写的话,可能会出现以下情况:set集合中出现两个相同的对象,即无法去重。
原因:set集合在进行去重操作时,会先判断两个对象的hashcode是否相等,如果不重写hashcode()方法,就会调用object中的hashcode()方法,返回的是对象内存地址的哈希码,这就意味着两个内容相同的对象,其哈希码会由于内存地址的不同而不同。 两个内容相同的对象哈希码不同,则默认这两个对象不同,都会插入到set集合中,导致set集合有重复对象。
分情况:
intern 是一个 native(本地)方法,其作用是将指定的字符串对象的引用保存在字符串常量池中,可以简单分为两种情况:
当字符串不加 final
关键字拼接时:
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";
String str4 = str1 + str2;
String str5 = "string";
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
执行结果分析:首先明确一点:
对于编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池。并且,字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化。
因此,上述代码中,除了str4的其余字符串都已经存入到字符串常量池中;而str4涉及到两个字符串的拼接,底层会在堆中创建一个新的对象,所以str4和其他字符串的比较都为false。
不过,当字符串使用 final
关键字声明之后,可以让编译器当做常量来处理:
final String str1 = "str";
final String str2 = "ing";
// 下面两个表达式其实是等价的
String c = "str" + "ing";// 常量池中的对象
String d = str1 + str2; // 常量池中的对象
System.out.println(c == d);// true
Java 异常类层次结构图概览:
catch
或者throws
关键字处理的话,就没办法通过编译。除了RuntimeException
及其子类以外,其他的Exception
类及其子类都属于受检查异常 。常见的受检查异常有:IO 相关的异常、ClassNotFoundException
、SQLException
...。RuntimeException
及其子类统称为不受检查异常,常见的有:
NullPointerException
(空指针错误)ArrayIndexOutOfBoundsException
(数组越界错误)ClassCastException
(类型转换错误)IllegalArgumentException
(参数错误比如方法入参类型错误)NumberFormatException
(字符串转换为数字格式错误)ArithmeticException
(算术错误)SecurityException
(安全错误比如权限不够)UnsupportedOperationException
(不支持的操作错误比如重复创建同一用户)String getMessage()
: 返回异常发生时的简要描述String toString()
: 返回异常发生时的详细信息String getLocalizedMessage()
: 返回异常对象的本地化信息。使用 Throwable
的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()
返回的结果相同void printStackTrace()
: 在控制台上打印 Throwable
对象封装的异常信息catch
块,如果没有 catch
块,则必须跟一个 finally
块。finally
块里的语句都会被执行。当在 try
块或 catch
块中遇到 return
语句时,finally
语句块将在方法返回之前被执行。不一定,在某些情况下,finally 中的代码不会被执行,如:
try-with-resources 是 Java 7 引入的一种语法结构,用于自动关闭实现了 AutoCloseable
接口的资源。它可以代替传统的 try-catch-finally 结构来处理资源的释放。
优点:
close()
方法进行关闭。使用泛型,可以增加代码的灵活性和安全性,使得代码更加通用和易于维护。
反射是java语言的一个特性,它允程序在运行时动态获取和调用类的属性和方法。
要使用反射,需要先获取类的class对象(字节码对象),有三种方式:
接下来就可以通过具体的API调用获取到详细的属性或者方法:
Spring 框架的 IOC(动态加载管理 Bean)、加载数据库驱动:
Class.forName("com.mysql.cj.jdbc.Driver");
、IDEA的方法提醒都需要用到反射机制。
注解只有被解析之后才会生效,常见的解析方法有两种:
@Override
注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。@Value
、@Component
)都是通过反射来进行处理的。如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。
简单来说:
序列化协议对应于OSI 七层协议模型中表示层。
对于不想进行序列化的变量,使用 transient
关键字修饰。
transient
只能修饰变量,不能修饰类和方法。static
变量不属于任何对象(Object),所以无论有无 transient
修饰,均不会被序列化。I/O即Input/Output,数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。数据传输过程类似于水流,因此称为 IO 流。IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流。
Java I/O流中有四个抽象类基类,其架构如下图:
字节流适合传输所有类型的文件,如音频、视频、图片、文本文件的复制转移等。字符流只适合传输纯文本文件,如txt、java文件等。
字节流在调用 write(int b)
和 read()
方法的时候,一次只能读取一个字节。由于字节缓冲流内部有缓冲区(字节数组),因此,字节缓冲流会先将读取到的字节存放在缓存区,大幅减少 IO 次数,提高读取效率。
BIO (Blocking I/O):同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。在高并发场景下效率较低。
NIO (Non-blocking I/O):同步非阻塞 IO 模型,应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。通过轮询操作,避免了一直阻塞,但是不断发起调用消耗大量cpu资源。
AIO (Asynchronous I/O):异步 IO 模型,应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。