一、基础
1.Java的特点?
- java是一门面向对象的编程语言
- java具有平台的独立性和移植性
-
- 一次编写 到处运行。实现这种特性的是因为java虚拟机JVM。已经编译的java程序可以在带有jvm的平台上运行,将代码编译成.class文件
- java具有稳健性
-
- java是一个强类型的语言
- 异常处理是java中使得程序更稳健的一个特征。
2.java是如何实现跨平台的?
- java是通过jvm(Java虚拟机)实现跨平台的。
- JVM可以理解成一个软件,不同的平台有不同的版本。jvm就是负责将字节码文件翻译成特定平台下的机器码,生成的字节码是一样的,但是由不同的jvm生成的机器码确是不一样。
3.java和c++的区别
- Java是纯粹的面向对象的语言,所有的对象都继承自java.lang.Object;c++兼容c,不但支持面向对象也支持面向过程。
- java是通过jvm从而实现跨平台的,c++依赖于特定的平台
- java没有指针,而c++有指针
- java支持自动垃圾回收,c++需要手动回收
- java不支持多继承,只能通过接口达到目的,c++支持多继承。
4.jdk/jre/jvm的关系
- jvm是Java虚拟机。java程序会首先被编译成class文件,这种文件在虚拟机上执行。不同的系统有不同的jvm,可以实现一次编写多处运行的原因。
- jre是Java的运行环境。我们编写的java程序必须要在jre才能运行,它主要包括两个部分:jvm和java的核心类库。是Java的运行环境,但不是开发环境。
- jdk是java的开发工具包,jdk集成了jre
5.java程序是编译执行还是解释执行?
- 编译型语言:在程序运行之前,通过编译器将源程序编译成机器码可运行的二进制,以后执行这个程序,不用在编译了。
-
- 优点:编译器一般会有预编译的过程,可以脱离语言环境独立运行。
- 缺点:编译之后如果需要修改整个模块重新编译。
- 总结:执行速度快,效率高,依靠编译器,跨平台差。
- 代表语言:c c++
- 解释性语言:先翻译成中间码,再有解释器对中间代码进行解释进行。翻译一句,执行一句。
-
- 优点:有良好的平台兼容性,在任何环境都可以运行。灵活,修改代码的时候直接修改就可以,可以快速部署,不用停机维护。
- 缺点:每次运行都需要解释一编,性能不如编译型语言
- 总结:执行速度慢,效率低;依靠解释器,跨平台性好
- 代表语言:javascript python php
- Java语言的源代码会先编译成字节码,再通过jvm将字节码转换成机器码执行,即解释运行和编译运行配合使用
6.面向对象和面向过程的区别
- 面向过程是分析出解决问题所需要的步骤,然后用函数按这些步骤实现,使用的时候一次调用就可以了。
- 面向对象是把构成问题的事务分解成各个对象,分别设计这个对象,然后将他们组装成完整功能的系统,面向过程是只能用函数实现,面向对象是用类实现各个功能。
7.面向对象有哪些特性?
- 面向对象的四大特性:封装 继承 多态 抽象
- 封装是将类的信息隐藏在类的内部,不允许外部程序直接访问,而是通过该类的方法实现对隐藏信息的操作和访问。好的封装可以减少耦合。
- 继承是从已有的类中派生出新的类,新的类继承父类的属性和行为,并能扩展新的能力,在java中是单继承的,也就是一个子类只有一个父类。
- 多态是同一个行为具有不同的类型的表现形式。实现多态的三要素是:继承 重写 父类引用指向子类的对象。
-
- 静态多态:通过重载实现,相同的方法有不同的参数列表。
- 动态多态:在子类重写父类的方法。
- 抽象:把客观的事物用代码抽象出来
8.面向对象编程的六大原则
- 单一原则:对象设计要求独立,不能设计万能对象,对象的相关法和属性必须跟商品相关。
- 开闭原则:对象修改最小化,开放扩展,封闭修改。尽可能不去修改原有的代码
- 里式替换原则:子类可以完全代替父类,反之不行。
- 依赖倒置原则:面向抽象编程。也就是参数传递,或者返回值,可以使用父类类型或者接口类型。广义上将:基于接口编程。
- 接口隔离:接口设计大小要适中。过大导致污染,过小,导致调用麻烦。
- 迪米特:高内聚,低耦合。尽量不要依赖细节。
9.数组到底是不是对象
- 数组是对象,父类是Object类
10.Java的基本数据类型有哪些?
- byte,8bit
- char,16bit
- short,16bit
- int, 32bit
- float,32bit
- long,64.bit
- double,64bit
- boolen,true/false 1bit or 32bit
11.为什么不能用浮点型表示金额?
- 计算机保存的小数都是十进制的小数的近似值,并不是准确值,所以不能有浮点型表示金额 有一些十进制的小数无法转换成二进制数,只能得到近似值。
- 建议使用long
12.什么是值传递和引用传递?
- 值传递是针对基本类型变量而言,传递的是该变量的一个副本,改变副本不影响原变量。
- 引用传递是对于对象型变量而言,传递的是该对象地址的一个副本,并不是原对象本身,两者在同一片空间。
- java中不存在 引用传递,不存在变量a指向变量b,变量b指向对象
13.为什么需要包装类?
- Java是一种面向对象语言,很多地方需要对象而不是基本数据类型。比如在集合类中,我们无法将int等类型放进去,因为集合的容器需要的object对象
- 包装类将基本类型包装起来,使他具有了对象的性质,并且有相应的方法和属性。
14.自动装箱和拆箱
- 赋值操作:装箱和拆箱
- 进行加减乘除混合运算:拆箱
- 比较运算:拆箱
- equals进行比较:装箱
- arraylist hashmap集合添加基本数据类型:装箱
- 装箱:
-
- Integer.valueof
- 拆箱:
-
- x.intValue()
15.string为什么不可变?
- 不可变对象:如果一个对象,在创建完成之后,不能在改变他的状态,那么这个对象是不可变的。不可改变的状态是不能改变对象内的成员变量,包括基本数据类型的值不能改变。
- 原因:
-
- value数组用final修饰,不能被修改。因此值不能指向其他对象。
- 内部的所有字段都是私有的,string没有对外提供修改内部状态的方法。
- 既然我们的String是不可变的,它内部还有很多substring, replace, replaceAll这些操作的方法。这些方法好像会改变String对象?怎么解释呢?其实不是的,我们每次调用replace等方法,其实会在堆内存中创建了一个新的对象。然后其value数组引用指向不同的对象。
16.jdk9将string的底层实现由char[]改变成byte[]?
- 主要是为了节省空间
- 在java程序的堆内存中,string占用空间最大,并且大多数string只占一个字符。
17.String, StringBuffer 和 StringBuilder区别
- 可变性
-
- string不可变
- stringbuffer stringbuilder是可变的
- 线程安全
-
- string不可变,线程是安全的
- stringbuffer是线程安全的,内部使用synchronized进行同步
- stringbuilder不是线程的安全的
18.String 类的常用方法有哪些?
- indexOf():返回指定字符的索引
- charAt():返回指定索引处的字符
- replace():字符串替换
- trim:去除字符串的两端空白
- split():分割字符串,返回一个分割后的字符串数组
- getBytes():返回字符串的byte类型数组
- length():返回字符串长度
- toLowerCase():将字符串转成小写字母
- toUpperCase():将字符串转成大写字符。
- substring():截取字符串。
- equals():字符串比较。
19.new String("dabin")会创建几个对象?
- 创建两种字符串的对象
- “dabin"属于字符串字面量,因此编译的时候会在字符串常量池中创建一个字符串对象
- new方式会在堆中创建一个字符串对象
20.String最大长度是多少
- string提供了一个length方法,返回值类型是int类型,int的取值上限是2^31-1
- String一般都存在哪块区域?
-
- 字符串在JVM中存储分两种情况,一种是String对象。存储在JVM的堆栈中。一种是字符串常量,存储早常量池中。
- 什么情况下的字符串会存储在常量池呢?
-
- 当通过字面量进行字符串声明时,比如String s = "程序新大彬";,这个字符串在编译之后会以常量的形式进入到常量池
21.object常用方法有哪些?
- toString()
-
- 默认输出对象地址,可以重写toString方法,按照重写的逻辑输出对象
- equals()
-
- 默认比较两个引用变量是否指向同一个对象的内存地址
- 可以重写equals方法按照age和name是否相等来判断
- hashCode()
-
- 将与对象相关的信息映射成一个哈希值,默认的实现hashCode值是根据内存地址换算出来。
- clone()
-
- java赋值是复制对象的引用,如果我们想要得到一个对象,使用复制操作是无法达到的目的。Object对象有个clone()方法,实现了对象中各个属性的复制,但它的可见范围是protected的。
- wait()
-
- 当前线程调用对象的wait()方法之后,当前线程会释放对象锁,进入等待状态。
- notify
-
- obj.notify()唤醒在此对象上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象上等待的所有线程。
22.谈一谈深拷贝和浅拷贝?
- 浅拷贝:拷贝对象和原始对象的引用类型引用同一个对象
- 深拷贝:拷贝对象和原始对象的引用类型引用不同对象
23.两个对象的hashCode()相同,则 equals()是否也一定为 true?
- 如果两个对象调用equals比较返回true,那么他们的hashcode值一定相同
- 如果两个对象的hashcode相同,他们并不一定相同。
- hashcode方法主要是用来提升对象的比较效率,先进性hashcode()的比较,如果不相同,那就不必在进行equals的比较,这样就大大减少了equals比较的次数,当比较对象的数量很大的时候能提升效率。
- hashcode是通过hash函数获得的,hashcode比较的是哈希码,哈希码是有特定的哈希算法得出。对象的物理地址跟这个hashcode地址不一样,hashcode代表对象的地址指的是对象在hash表中的位置,物理地址说的是对象存放在内存中的地址。通过物理地址转换成一个整数,然后该整数通过hash函数的算法得到了hashcode。
23.为什么重写equals方法,建议也重写hashcode?
- 有个A类重写了equals方法,但是没有重写hashCode方法,看输出结果,对象a1和对象a2使用equals方法相等,按照上面的hashcode的用法,那么他们两个的hashcode肯定相等,但是这里由于没重写hashcode方法,他们两个hashcode并不一样,所以,我们在重写了equals方法后,尽量也重写了hashcode方法,通过一定的算法,使他们在equals相等时,也会有相同的hashcode值。
24.Java创建对象有几种方式?
- 使用new创建对象
- 使用反射,Class.newlnstance()
- 调用对象的clone()复制
- 使用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法,将序列化的原始数据恢复为对象,以流的形式读取。
25.类实例化的顺序
- 1.静态属性,静态代码块
- 2.普通属性,普通代码块
- 3.构造方法
26.equals和==的区别?
- 对于基本的数据类型,==是比较他们的值。基本数据类型没有equal方法
- 对于复合数据类型,==比较的是他们的地址。equals()默认比较的是地址,重写的话按照重写的逻辑比较。
27.常见的关键字有哪些?
static
- static可以用来修饰类的成员方法、类的成员变量。
- static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
- 静态变量、静态方法(不依赖于对象,直接访问)、静态代码块(在类加载的时候执行一次)、静态内部类(在静态方法里,使用⾮静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。⽽静态内部类不需要。)
final
- 基本数据类型用final修饰,则不能修改,是一个常量;对象引用使用final修饰,则引用只能指向该对象,不能指向别的对象,但是本身可以修改
- final修饰的方法不能够被子类重写
- final修饰的类不能够被继承
this
- 访问类中的成员变量,可以用来区分成员变量和局部变量。
- 访问类中的方法
super
- 子类访问父类的变量和方法
28.final,finally,finalize的区别
- final用于修饰属性、方法和类, 分别表示属性不能被重新赋值,方法不可被覆盖,类不可被继承。
- finally用于异常处理语句的一部分,finally修饰的代码块表示总被执行。
- finalize是object类中的一个方法,该方法一般由垃圾回收器来调用,当我们调用system.gc方法时,由垃圾回收器调用finalize方法回收垃圾
29.final关键字的作用
- final修饰的类不能够被继承
- final修饰的方法不能够被重写
- final修饰的变量叫做常量,常量必须初始化,初始化之后不能够被修改
30.方法重载和重写的区别
- 重载:同一个类中的多个方法可以有相同的名称,但是有不同的参数列表,这就成为重载
- 重写:父类的功能无法满足子类的需求,可以在子类对方法进行重写。重写时,方法名与形参列表必须一致。
31.接口与抽象类
1.语法层次上的区别
- 抽象类可以有方法实现,而接口的方法只能是抽象方法
- 抽象类中的成员变量可以是各种类型,接口中的成员变量只能是public static final
- 抽象类可以有静态代码块和静态方法,接口中不能含有
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口
2.设计层次
- 抽象层次不同,抽象类是对整个类的整体进行抽象,包括属性和行为;但是接口只是对类的行为进行抽象。
- 继承抽象类的是具有相似特点的类,而实现接口的却可以不同的类。
32.常见的exception
1.runtimeException
- ClassCastException //类型转换异常
- IndexOutOfBoundsException //数组越界异常
- NullPointerException //空指针
- ArrayStoreException //数组存储异常
- NumberFormatException //数字格式化异常
- ArithmeticException //数学运算异常
2.checked Exception:
- NoSuchFieldException //反射异常,没有对应的字段
- ClassNotFoundException //类没有找到异常
- IllegalAccessException //安全权限异常,可能是反射时调用了private方法
33.error和exception的区别
- error:是jvm无法解决的严重问题
- exception:是一般性的问题
34.运行时异常和非运行时异常的区别
- 运行时异常是程序错误导致的
- 非运行时异常时具体的环境导致的,必须进行处理,不然编译不通过,可以catch或者throws。
35.throw和throws的区别
- throw用于抛出一个具体异常的对象
- throws用于方法签名中,用于声明该方法可能抛出的异常。子类方法抛出的异常范围更加小,或者根本不抛异常。关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常)。
36.BIO\NIO\AIO区别的区别?
- 同步阻塞io:用户进程发起一个io操作后,必须等待io操作的真正完成后,才能够进行
- 同步非阻塞IO: NIO基本思想总结起来就是:分而治之,将任务拆分开来,由专门的人负责专门的任务客户端与服务器通过Channel连接,采用多路复用器轮询注册的Channel。提高吞吐量和可靠性。用户进程发起一个IO操作以后,可做其它事情,但用户进程需要轮询IO操作是否完成,这样造成不必要的CPU资源浪费。
- 异步非阻塞IO: 非阻塞异步通信模式,NIO的升级版,采用异步通道实现异步通信,其read和write方法均是异步方法。用户进程发起一个IO操作,然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知。类似Future模
37.守护线程是什么?
- 守护线程时运行在后台的一种特殊线程
- 它独立于控制终端,并且周期的执行某种任务或者等待处理某些发生的时间。
- Java中垃圾回收线程就是特殊的守护线程
- Java的线程分为守护线程和用户线程,让所有用户线程停止,进程会停掉所有守护线程,退出程序。
38.java支持多继承吗?
Java中,类不支持多继承,接口支持多继承。接口的作用是扩展对象的功能,当一个子接口继承了多个父接口,说明了扩展了多个功能。
Java不支持多继承的原因:
- 出于安全的考虑,如果子类继承的多个父类含有相同的方法和属性,子类不知道具体要继承哪一个。
- Java提供了接口内部类以达到实现多继承的功能,弥补单继承的缺陷。
39.如何实现对象克隆?
- 实现cloneable接口,重写clone()方法。这种方式是浅拷贝,即如果类中属性有自定义引用类型,只拷贝引用,不拷贝引用指向的对象。如果对象的属性的Class也实现 Cloneable 接口,那么在克隆对象时也会克隆属性,即深拷贝。
- 结合序列化,深拷贝
- 通过org.apache.commons中的工具类beanUtils和PropertyUtils进行对象的复制。
40.同步和异步的区别
- 同步:发出一个调用,没有得到结果之前,该调用就不返回
- 异步:发出调用后,被调用者返回结果之后会通知调用者,或通过回调函数处理这个调用。
41.阻塞和非阻塞的区别
阻塞和非阻塞关注的是线程的状态。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会恢复运行。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
举个例子,理解下同步、阻塞、异步、非阻塞的区别:
同步就是烧开水,要自己来看开没开;异步就是水开了,然后水壶响了通知你水开了(回调通知)。阻塞是烧开水的过程中,你不能干其他事情,必须在旁边等着;非阻塞是烧开水的过程里可以干其他事情。
42.java8的特性
- Lambda 表达式:Lambda允许把函数作为一个方法的参数
- Stream API :新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中
- 默认方法:默认方法就是一个在接口里面有了一个实现的方法。
- Optional 类 :Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Date Time API :加强对日期与时间的处理。
43.序列化和反序列化
- 序列化:把对象转化为字节序列的过程称为对象的序列化
- 反序列化:把字节序列恢复为对象的过程称为对象的反序列化
43.什么时候需要用到序列化和反序列化呢?
当我们只在本地 JVM 里运行下 Java 实例,这个时候是不需要什么序列化和反序列化的,但当我们需要将内存中的对象持久化到磁盘,数据库中时,当我们需要与浏览器进行交互时,当我们需要实现 RPC 时,这个时候就需要序列化和反序列化了.
- 只要我们对内存中的对象进行持久化或网络传输,这个时候都需要序列化和反序列化.
44.实现序列化和反序列化为什么要实现 Serializable 接口?
在 Java 中实现了 Serializable 接口后, JVM 在类加载的时候就会发现我们实现了这个接口,然后在初始化实例对象的时候就会在底层帮我们实现序列化和反序列化。
45.实现 Serializable 接口之后,为什么还要显示指定 serialVersionUID 的值?
如果不显示指定 serialVersionUID,JVM 在序列化时会根据属性自动生成一个 serialVersionUID,然后与属性一起序列化,再进行持久化或网络传输. 在反序列化时,JVM 会再根据属性自动生成一个新版 serialVersionUID,然后将这个新版 serialVersionUID 与序列化时生成的旧版 serialVersionUID 进行比较,如果相同则反序列化成功,否则报错.
如果显示指定了 serialVersionUID,JVM 在序列化和反序列化时仍然都会生成一个 serialVersionUID,但值为我们显示指定的值,这样在反序列化时新旧版本的 serialVersionUID 就一致了.
46.static 属性为什么不会被序列化?
因为序列化是针对对象而言的,而static属性优先于对象的存在,随着类的加载而加载,所以不会被序列化。
47.transient关键字的作用?
被transient修饰的成员变量,在序列化的时候其值会被忽略,在被反序列化后, transient 变量的值被设为初始值, 如 int 型的是 0,对象型的是 null。
48.什么时反射
动态的获取的信息以及动态的调用对象的方法称为Java语言的反射机制。
在运行状态中,对于任意一个类,能够直到这个类的所有属性和方法。对于任意一个对象,能够调用他的任意一个方法和属性。
49.反射的应用场景
- jdbc连接数据库时使用class.forname通过反射加载数据库的驱动程序
- idea利用反射的动态解析对象的类型与结构,动态的提示对象的属性和方法
- web服务器利用反射调用了sevlet的service方法
- jdk动态代理底层依赖反射实现
50.讲讲什么是泛型?
允许在定义类和接口的时候使⽤类型参数。声明的类型参数在使⽤时⽤具体的类型来替换。
51.如何停止一个正在运行的线程
1.使用线程stop方法
- 使用stop方法可以强制终止线程。不过stop是一个被废弃掉的方法,不被推荐使用。
2.使用interrupt方法中止线程
- 告诉线程要终止,但最终何时终止取决于计算机。调用interrupt方法仅仅是在当前线程打了一个标志,并不是真正的停止线程
- 接着调用 Thread.currentThread().isInterrupted()方法,可以用来判断当前线程是否被终止,通过这个判断我们可以做一些业务逻辑处理,通常如果isInterrupted返回true的话,会抛一个中断异常,然后通过try-catch捕获
3.设置标志位
52.什么是跨域
跨域是指从一个域名的网页去请求另一个域名的资源。由于有同源策略的关系,一般是不允许这么直接访问的。但是,很多场景经常会有跨域访问的需求,比如,在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域问题。
什么是同源策略?
所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
53.跨域问题怎么解决呢?
- 跨域资源共享:
- @CrossOrigin注解
- nginx反向代理接口跨域
- 通过jsonp跨域
54.设计接口需要注意什么
- 接口参数校验。接口必须校验参数,比如入参是否允许为空,入参长度是否符合预期。
- 设计接口时,充分考虑接口的可扩展性。思考接口是否可以复用,怎样保持接口的可扩展性。
- 串行调用考虑改并行调用。比如设计一个商城首页接口,需要查商品信息、营销信息、用户信息等等。如果是串行一个一个查,那耗时就比较大了。这种场景是可以改为并行调用的,降低接口耗时。
- 接口是否需要防重处理。涉及到数据库修改的,要考虑防重处理,可以使用数据库防重表,以唯一流水号作为唯一索引。
- 日志打印全面,入参出参,接口耗时,记录好日志,方便甩锅。
- 修改旧接口时,注意兼容性设计。
- 异常处理得当。使用finally关闭流资源、使用log打印而不是e.printStackTrace()、不要吞异常等等
- 是否需要考虑限流。限流为了保护系统,防止流量洪峰超过系统的承载能力。
55.过滤器和拦截器有什么区别?
1.实现的原理不同
过滤器和拦截器底层实现不同。过滤器是基于函数回调的,拦截器是基于Java的反射机制(动态代理)实现的。一般自定义的过滤器中都会实现一个doFilter()方法,这个方法有一个FilterChain参数,而实际上它是一个回调接口。
2.范围不同
过滤器实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。而拦截器是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。拦截器不仅能应用在web程序中,也可以用于Application、Swing等程序中。
3.场景不同
因为拦截器更接近业务系统,所以拦截器主要用来实现项目中的业务判断的,比如:日志记录、权限判断等业务。而过滤器通常是用来实现通用功能过滤的,比如:敏感词过滤、响应数据压缩等功能
4.触发时机不同
过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。
5.拦截的请求范围不同
过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。