Java部分
Java相关
一.基本语法
1.面向对象的特性
- 继承:从已有的类得到继承信息创建新类的过程。提供信息的是父类,得到继承信息的是子类
- 封装:隐藏一切可以隐藏的东西,只向外界提供最简单的编程接口
- 多态:允许不同的子类型的对象对同一个消息做出不同的响应。多态分为编译时多态(方法重载[前绑定])与运行时多态(方法重写override[后绑定])[继承,重写,向上转型]
- 抽象:将一类对象的共同特征总结出来构造类的过程,只关注属性和行为,不关注具体实现的细节
2.Public,private,protected,以及默认
3.如何理解clone?
(1)为什么要用clone?
A和B是两个独立的对象,但是B的初值是由A对象确定的,之后对B的任何改动都不影响A的值
(2)new一个对象和clone一个对象的区别?
- 程序执行到new操作符时,首先去看new后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后再调用构造函数,填充对象的各个域。构造函数返回之后,一个对象创建完成,可以将它的引用发布到外部,在外部就可以使用这个引用操纵这个对象
- clone第一步和new一样,都是分配内存,分配的内存和原对象相同,然后使用原对象中的各个域,填充新对象的域,填充之后clone方法返回,一个新对象被创建,同样可以将新对象的引用发布到外部
(3)clone对象的使用
Person p = new Person(23,"howie"); Person p1 = (Person)p.clone(); //两个对象的地址是不同的
- 如果两个Person对象的name地址值相同,说明两个对象的name都指向同一个String对象,也就是浅拷贝。如果两个对象的name的地址值不同,那么说明指向不同的String,也就是拷贝Person的时候,同时拷贝了name引用的String对象,也就是深拷贝
- 深拷贝(传递引用)和浅拷贝(拷贝对象)的区别就是在处理对象引用的时候,浅拷贝只复制引用(浅拷贝对于对象中的基本数据类型的属性是可以得到完全的拷贝,但是对于对象中的非基本数据类型的属性并没有进行克隆而是进行的赋值,也就是说克隆出来的对象与原对象有一部分内容地址是相同的),深拷贝会将引用对应的对象复制一份。深拷贝一般都对串行化和对象序列化来实现。因此对于浅拷贝来说原对象的值发生变化的时候,拷贝对象的值也会发生变化。而深拷贝则是拷贝了原对象的所有的值,所以当原对象的值发生变化,拷贝对象的值也不变
- Python中:如果用copy.copy,copy.deepcopy对一个全部都是不可变类型的数据进行拷贝,那么它们结果相同,都是引用指向;如果拷贝的是一个拥有不可变类型的数据,即使元祖的时候最顶层,那么deepcopy依然是深拷贝,而copy.copy还是指向
4.Java中有没有Goto?
goto为Java中的保留字,在目前版本的Java中没有使用
5.&和&&的区别
- &为按位与和逻辑与
- &&为短路与运算,被称为短路运算
6.在Java中,如何跳出当前的多重嵌套循环
在最外层循环之前加一个标记例如A,然后使用break A,可以跳出。效果与goto类似,不推荐使用
7.两个对象值相同(x.equals(y) == true),但是可以有不同的hashCode(X)
Java对于equals方法和hashCode方法的定义如下:
- 如果两个对象相同,那么它们的hashCode一定相同
- 如果两个对象的hashCode相同,那么他们并不一定相同
equals方法的官方定义:
- equals方法必须满足自反性(x.equals(x)必须返回true),对称性(x.equals(y)返回true的时候,y.equals(x)也必须返回true),传递性(x.equals(y)和y.equals(x=z)返回true的时候,x.equals(z)必须返回true),一致性(当x和y引用的对象没有被修改的时候,多次调用x.equals(y)应该得到同样的返回值;而且对于任何非null值得引用x.equals(null)必须返回false)
实现高质量的equals方法的诀窍在于:
- 使用==操作符检查“参数是否为这个对象的引用”
- 使用instanceof操作符检查“参数是否为正确的类型”
- 对于类中的关键属性,检查参数传入对象的属性与之是否匹配
- 编写完equals方法之后,检查对称性,传递性和一致性
- 重写equals方法必须重写hashCode
- 不要将equals方法参数中的object方法替换为其他类型,在重写的时候不要忘了@Override
8.String可以继承吗?
String是final类,不可以被继承,多线程并发访问不会有任何问题
9.重载和重写的区别?重载的方法能够根据返回类型进行区别?
重载和重写都是实现多态的方法,区别在于前者是编译时多态,后者是运行时多态。
方法重载的规则:
- 方法名一致,参数列表中参数的顺序,类型,个数不同
- 重载和方法的返回值无关,存在于父类和子类,同类中
- 可以抛出不同的异常,可以有不同的修饰符
方法重写的规则:
- 参数列表必须完全与重写方法一致,返回类型也需要一致
- 构造方法不能被重写,声明为final的方法不能被重写,声明为static的不能被重写
- 访问权限不能比父类中重写的方法的访问权限低
- 重写的方法可以抛出任何非强制异常,无论被重写的方法是否抛出异常
10.为什么函数不能根据返回类型来区分重载?
调用的时候不能制定类型信息,编译器不知道要调用哪个函数
11.char类型可以存储中文汉字吗?
可以,因为Java中使用的编码是Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号),一个char类型占2个字节(16比特),所以可以放一个中文。
- JVM内部都是Unicode,当字符被从JVM内部转移到外部的时候,需要进行编码转换。所以Java中有字节流和字符流,以及在字符流和字节流之间进行转换的转换流
12.抽象类和接口的异同*?
抽象类:
- 抽象类可以定义构造器
- 可以有抽象方法和具体方法
- 接口中可以定义成员变量
- 有抽象方法的类必须被声明为抽象类,抽象类不一定要有抽象方法
- 抽象类中可以有静态方法
- 一个类只能继承一个抽象类
接口:
- 接口中不能定义构造器
- 方法全都是抽象方法
- 抽象类中的成员可以使private,默认,protected,public
- 接口中定义的成员变量实际上都是常量
- 接口中不能有静态方法
- 一个类中可以实现多个接口
相同点:
- 不能被实例化
- 可以讲抽象类和抽象类型作为引用类型
- 一个类如果继承了某个抽象类或者是实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类
13.抽象的方法是否可以同时是静态的?是否可以同时是本地方法,是否可以同时被synchronized?
- 抽象方法需要子类的重写,但是静态方法是不能被重写的
- 本地方法是由本地代码实现的方法,而抽象方法是没有实现的
- synchronized和方法的实现细节有关,抽象方法不涉及实现细节
14.==和equals的区别?
- == 如果比较的是基本数据类型,则比较的是数值是否相等;如果比较的是引用数据类型,则比较的是对象的地址是否相同
- equals用来比较两个对象的内容是否相等,如果没有对它的重写,则比较的是地址
基本数据类型(在栈中进行分配,变量名指向具体的数值,创建和销毁很快):
- 整数:int,long,long long
- 浮点:float,double
- 字符:char
- 布尔:boolean
引用数据类型(在堆中进行分配,变量名指向数据对象的地址[变量的哈希值],需要在JVM中进行销毁):
- 类:class
- 接口:interface
- 数组:array
15.什么是OO思想?
面向过程是以数据为中心,考虑数据如何流转的问题
面向对象是将属性抽象出对象,考虑对象之间如何沟通交互的问题
16.什么是JRE,JDK?
- JRE是Java的运行时的环境
- JDK是Java的开发包
17.Java的特征
- 跨平台
- 可移植
18.如果方法final修饰,会怎么样?
子类不能复写父类的方法
19.Java中的异常处理
- 异常的定义:组织当前方法和作用域继续执行的问题
Throwable是Java语言中所有错误或异常的超类(一切皆可抛)
Error错误交给JVM进行处理(基本都是JVM虚拟机相关的问题)
对于可以恢复的条件使用被检查的异常(CheckedException)[编译时异常],对于程序错误(言外之意不可以被恢复,大错已经酿成)使用运行时异常(RuntimeException)
编写程序的时候捕捉的异常都是CheckedException
受检查的异常,要么用try-catch来进行捕获,要么使用throws抛出,交给它的父类进行处理
try&&catch&&finally
- 顺序为try-->catch-->finally
- 如果使用try,catch的时候,finally中的return会覆盖已有的返回值
throw和throws:
- throws表示一个方法声明可能抛出一个异常,由方法的调用者进行异常的处理
- throw表示此处抛出一个已定义的异常
编写异常类:
- 所有的异常都是Throwable的子类
- 如果希望写一个检查性异常类,需要继承Exception类
- 如果想写一个运行时异常类,需要继承RuntimeException类
20.)StringBuiler和StringBuffer和String
在Java中,无论使用何种方式进行字符串连接,实际上都是使用的StringBuilder
字符串的+操作本质就是创建了StringBuilder对象进行append方法,然后将拼接之后的StringBuilder对象用toString方法处理为String对象
三者的区别:
String:
被声明为final类,内部变量也被final修饰,不可以被继承
不变性:典型的Immutable类(不可变--设计模式),多线程共享并频繁访问的时候保证数据的一致性
字符串常量池缓存,下次再创建的时候直接返回缓存中的内容
为引用类型,底层为char数组实现的
String的不可变可以修改:通过反射获取value数组,通过修改value属性的访问权限,可以对字符串的值进行修改
字符串内容不长久发生变化的业务场景使用String类,例如:常量声明,少量的字符串拼接
String不是基本数据类型
String的实例化:
- 直接赋值
- 通过构造函数进行传递参数
String str = "Hello";//直接赋值,存储于字符串常量池中,常量池有优化 String str2 = new String("Hello");//通过构造函数来传递,存储于堆内存中 char[] chars = {'你',‘好’}; String str3 = new String(chars);
String对于equals方法进行了重写(父类不能满足,则子类进行重写)[转化为数组,首先比较length,之后挨个的比较byte数组中的字符]
String的intern()方***去字符串常量池中进行寻找,如果已经存在一个值相等的字符串对象的话,则直接返回该对象的引用 ,如果不存在则在常量池中创建对象并且返回
String str1 = "Hello World"; String str2 = "Hello" + " World"; System.out.println(str1 == str2); //true,对于常量进行拼接的话仍然是保存在常量池中
String str1 = "Hello World"; String str2 = "Hello"; str2 += " World"; System.out.println(str1 == str2); //false,通过间接的方式是存储于堆内存中的,变量+常量==堆内存中
String str1 = "Hello World"; final String str2 = " World"; String str3 = "Hello" + str2; System.out.println(str1 == str3); //true,因为final变为常量,放置于常量池中
String str1 = "Hello World"; final String str2 = new String(" World"); String str3 = "Hello" + str2; System.out.println(str1 == str3); //false,str2仍然是在堆内存中的
- 字符串常量池:位于堆内存中,避免重复的开辟空间,创建的时候JVM首先检查常量池中是否有string,如果存在,则直接引用
String是线程安全的
在使用HashMap的时候,使用String做key的时候什么好处?
- String中有缓存的机制,创建出hashcode值之后会一直保存
- 再次计算的时候不用重复的机型计算
StringBuilder&&StringBuffer:
- 实现原理基于可修改的char数组,默认的大小为16
- 都继承自AbstractStringBuilder
- StringBuffer线程安全,StringBuilder非线程安全
- StringBuilder在频繁的进行字符串的运算,且运行在单线程的环境之下 ,例如:SQL语句的拼接,JSON拼接
- StringBuilder所有的方法都没有被synchronized修饰,因此比StringBuffer要高
StringBuffer:
- stb为StringBuffer类型,底层为value数组,当append的时候,首先新建str,之后resize下,将原有的数据复制到新的stringbuffer中,然后在后面进行添加,添加结束之后将std的引用指向新的str
- StringBuffer在频繁进行字符串的运算(例如拼接,替换,删除),且运行在多线程的环境之下,例如:XML解析,HTTP参数解析与封装
21.final&&finally&&finalize
Final:
final修饰类,表示这个类不能被继承
final修饰方法,表示方法不能被重写
final修饰变量,表示这个变量不能被重新赋值
为什么使用final修饰符?
- 定义的功能和行为不希望被改变
- 保证系统的安全性,健壮性
Finally:
- try-catch语句中,一定会被执行(注意return)
- 关闭资源
- 释放锁
Finalize:
- 定义于java.lang.Object类中
- 垃圾回收之前进行调用,在对象回收之前释放资源
- 每一个对象的finalize()方法只能被GC调用一次
22.Integer的大小为-128~127
在-128到127的数是在缓存中,如果超过了之后会new一个新对象
23.反射的原理和创建类实例的方法
反射的概念:Java的反射机制是在运行状态中,对于任意一个类,都可以获得这个类的所有属性和方法,对于任意一个对象都能够调用它的任意一个属性和方法。这种在运行时动态的获取信息以及动态调用对象的方法的功能叫做Java的反射机制
反射机制的原理:
- 一切皆对象,类也是对象
- 类中的内容:constructor,field,method
- 加载:当student.class在硬盘中的时候是一个文件,当载入内存的时候,可以认为是一个对象
获取反射机制:
- 通过建立对象
- 通过路径
- 通过类名
(24)wait和sleep的区别:
- Sleep来自Thread类,wait来自Object类
- sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法
- 适用范围:wait,notify,notifyall只能在同步控制方法中使用,而sleep可以在任何地方使用
- sleep必须捕获异常,而notify,wait,waitall不需要捕获异常
二.Java的数据类型
1.字符流和字节流
- 字符流:输入流(Reader)和输出流(Writer)
- 字节流:输入流(InputStream)和输出流(OutputStream)
- 字节流到字符流:xxputStreamwriter/reader
- 字节流读到一个字节就返回一个字节;字符流使用了字节流读到一个或多个字节时,先去查指定的编码表,将查到的字符返回。如果是音频,图片歌曲,用字节流更好;如果关系到中文(文本)的,用字符流更好
2.List&&Map&&Set
List的三个子类的特点
- ArrayList底层结构是数组,底层的查询快,增删慢
- LinkedList底层结构是链表类型的,增删快,查询慢
- vector底层的结构是数组,线程安全的,增删慢,查询慢
Map的三个实现类:
- HashMap基于hash表的map接口实现,非线程安全,高效,支持null值和null键
- HashTable线程安全,低效,不支持null值和null键
- LinkedMap保存了记录的插入顺序
Set的两个实现类
- HashSet底层是由HashMap实现的,使用方法需要equals()和hashcode()方法
- LinkedHashSet继承于HashSet,同时又基于LinkedHashMap来进行实现
3.HashMap和HashTable
- HashMap是线程不安全的,HashMap是一个接口,是Map的子接口,不允许键值重复,允许空键和空值,效率比HashTable要高
- HashTable是线程安全的集合,不允许null值作为key或者value值,HashTable是synchronized,多个线程访问的时候不需要自己为它的方法实现同步,而HashMap需要
4.List a = new ArrayList();
定义之后有些ArrayList有的方法而List没有的属性和方法它就不能再用了
二.新增部分
1.JDK 13
- switch表达式:
int i = 8; String s = switch (i) { case 1 -> "one"; case 8 -> "eight"; default -> "zero"; }; System.out.println(s);//输出eight
- String的定义
String html = """ <html> <head></head> <title></title> </html> """; System.out.println(html);//类似于markdown的语法,可以直接输出
- 尚未出来的特性---纤程
Linux只是使用了两个级别:Ring 0---》内核态 Ring3---》用户态
线程:启动一个线程需要保存0x80中断+上下文,用户态-》内核态-》用户态
用户态系统调用到内核态:
1.app发出0x80中断 或 调用sysenter原语
2.os进入内核态
3.在中断向量表中查找处理例程
4.保存硬件现场 CS IP等寄存器的值
5.保存app现场 堆栈与寄存器的值
6.执行中断例程system_call
根据参数与编号寻找对应的例程
- 执行并返回
7.回复现场
8.返回用户态
9.app继续执行
线程:pc stack-->jvm-->os-->jvm-->pc stack
反射由ASM实现的
纤程(fiber)[目前的JVM不支持]:
//纤程(天生适合高并发) @SuppressWarnings("fiber") Fiber<void> fiber = new Fiber<Void>(new SuspendableRunnable() { public void run() throws SuspendExecution,InterruptedException { calc(); } }); fiber.start(); //线程 Thread thread = new Thread(new Runnable() { @Override public void run() { calc(); } });
三.JVM相关
1.GC相关
(1)请讲一下垃圾回收算法?
1)为什么要有垃圾回收?
- C语言和C++中需要手动的分配与回收内存
2)回收什么垃圾?
每一个对象都有自己的引用(引用链),使用可达性分析算法(如果没有被GC Root引用的对象可以被回收)
根对象:
- 虚拟机栈中引用的对象
- 方法去中的类静态属性引用的对象
- 方法区中的常量引用的对象
- 本地方法栈中JNI(native方法)引用的对象
3)如何进行回收
- Marking-sweep:标记整理法
- Marking-Compat:标记清除法
4)分代(对于内存进行管理)
- 年轻代
- 老年代
- 方法区
5)常见的回收器
- 单线程GC
- 多线程GC
- CMS Garbage Collector:多线程GC
- G1 Garbage Collector:jdk7引入GC,多线程,高并发,低暂停,逐步取代CMS GC
四.多线程
五.手写源码系列
六.设计模式
1.单例模式
核心思想:保证系统中一个类仅有一个实例,并且提供一个访问该实例的全局访问方法
单例模式的特点:
- 单例类只能有一个实例
- 单例类必须自己创建该唯一实例
- 单例类必须给其他对象提供访问该实例的方法
饿汉式:
// 饿汉式 public final class Singleton { // 定义静态实例对象,加载时初始化 private static Singleton instance = new Singleton(); // 私有构造方法,不允许外部直接new对象 private Singleton(){} // 提供唯一实例全局访问方法 public static Singleton getInstance() { return instance; } }
优点:
- getInstance访问,性能高
- 线程安全
缺点:
- 加载的时候就初始化,可能会造成资源的浪费
懒汉式:
// 懒汉式 public final class Singleton { // 定义静态实例对象,但是不初始化 private static Singleton instance = null; // 私有构造方法,不允许外部直接new对象 private Singleton(){} // 提供唯一实例全局访问方法 public static Singleton getInstance() { // 使用的是偶再初始化 if(instance == null) instance = new Singleton(); return instance; } }
优点:
- getInstance()访问性能高
- 延迟初始化:使用的时候才初始化,提高了资源的利用率
缺点:
- 非线程安全
- 多个线程同时创建不唯一
懒汉式+double check
// 懒汉式 public final class Singleton { // 定义静态实例对象,但是不初始化 private static Singleton instance = null; // 私有构造方法,不允许外部直接new对象 private Singleton(){} // 提供唯一实例全局访问方法 public static Singleton getInstance() { //如果instance为null,则进入同步块 if( instance == null ){ // 只有一个线程可以进入同步块 synchronized(Singleton.class) { // 判断如果instance为空,则继续 if( instance == null ) instance = new Singleton(); } } return instance; } }
优点:
- getInstance()访问性能高
- 延迟初始化
缺点:
- 线程
实例化对象的时候的步骤:
- 为对象分配内存空间
- 初始化默认值(区别于构造器方法的初始化)
- 执行构造器方法
- 将对象的指向刚分配的内存空间
懒汉式+double check+volatile
// 懒汉式 public final class Singleton { // 定义静态实例对象,但是不初始化,声明为volatile private static volatile Singleton instance = null; // 私有构造方法,不允许外部直接new对象 private Singleton(){} // 提供唯一实例全局访问方法 public static Singleton getInstance() { //如果instance为null,则进入同步块 if( instance == null ){ // 只有一个线程可以进入同步块 synchronized(Singleton.class) { // 判断如果instance为空,则继续 if( instance == null ) instance = new Singleton(); } } return instance; } }
volatile关键字:
- 原子性
- 可见性
- 有序性:解决上一种方法的重排序问题
内部类Holder
// 懒汉式 public final class Singleton { // 在静态内部类中持有singleton对象 private static class Holder { private static Singleton INSTANCE = new Singleton(); } // 私有构造方法,不允许外部直接new对象 private Singleton(){}; //提供唯一实例全局访问方法 public static Singleton getInstance() { return Holder.INSTANCE; } }
优点:
- getInstance()访问性能高
- 延迟初始化
- 线程安全
前面实现方式可能存在的问题:
- 需要额外的工作来实现序列化,否则每次反序列化一个序列化的对象的时候都会创建一个新的实例
- 可以使用反射强行调用私有构造器
- Effctive Java推荐使用ENUM来实现
枚举
public final class Singleton extends Enum { // 编译器生成静态values()方法 public static Singleton[] values() { return (Singleton)$VALUES.clone(); } // 编译器生成静态valueOf方法,注意间接调用了Enum类的valueOf方法 public static Singleton valueOf(String s){ return (Singleton)Enum.valueOf(com/howie/demo/Singleton,s); } // 私有构造函数 private Singleton(String s,int i){ super(s,i); } // 前面定义的枚举实例,JVM返回名字,通过名字构造,如果有了就不会重新构造了 public static final Singleton INSTANCE; static { // 实例化枚举实例,类加载的时候进行实例化 INSTANCE = new Singleton("INSTANCE",0); $VALUES = (new Singleton[] { INSTACE }); } }
单例模式的场景:
- Windows中的任务管理器
- 数据库连接池
- Java中的Runtime
- Spring中Bean的默认生命周期
单例模式的优点
- 提供了唯一实例的全局访问方法,可以优化共享资源的访问
- 避免对象的频繁创建和销毁,可以提高性能