共计 17022 个字符,预计需要花费 43 分钟才能阅读完成。
面霸篇,从面试题作为切入点晋升大家的 Java 内功,所谓 根基不牢,地动山摇。只有扎实的根底,才是写出写好代码。
回绝常识碎片化
码哥在《Redis 系列》的开篇 Redis 为什么这么快中说过:学习一个技术,通常只接触了零散的技术点,没有在脑海里建设一个残缺的常识框架和架构体系,没有零碎观。这样会很吃力,而且会呈现一看如同本人会,过后就遗记,一脸懵逼。
咱们须要一个零碎观,清晰残缺的去学习技术,同时也不能埋头苦干,过于死磕某个细节。
零碎观其实是至关重要的,从某种程度上说,在解决问题时,领有了零碎观,就意味着你能有根据、有章法地定位和解决问题。
跟着「码哥」一起来提纲挈领,梳理一个绝对残缺的 Java 开发技术能力图谱,将根底夯实。
点击下方关注我
[toc]
Java 平台的了解
码老湿,你是怎么了解 Java 平台呢?
Java 是一种面向对象的语言,有两个显著个性:
- 跨平台能力:一次编写,到处运行(Write once,run anywhere);
- 垃圾收集:
Java 通过字节码和 Java 虚拟机(JVM)这种跨平台的形象,屏蔽了操作系统和硬件的细节,这也是实现「一次编译,到处执行」的根底。
Java 通过垃圾收集器(Garbage Collector)回收分配内存,大部分状况下,程序员不须要本人操心内存的调配和回收。
最常见的垃圾收集器,如 SerialGC、Parallel GC、CMS、G1 等,对于实用于什么样的工作负载最好也心里有数。
JVM、JRE、JDK 关系
码老湿,能说下 JVM、JRE 和 JDK 的关系么?
JVM Java Virtual Machine
是 Java 虚拟机,Java 程序须要运行在虚拟机上,不同的平台有本人的虚拟机,因而 Java 语言能够实现跨平台。
JRE Java Runtime Environment
包含 Java 虚拟机和 Java 程序所需的外围类库等。
外围类库次要是 java.lang
包:蕴含了运行 Java 程序必不可少的零碎类,如根本数据类型、根本数学函数、字符串解决、线程、异样解决类等,零碎缺省加载这个包
如果想要运行一个开发好的 Java 程序,计算机中只须要装置 JRE 即可。
JDK Java Development Kit
是提供给 Java 开发人员应用的,其中蕴含了 Java 的开发工具,也包含了 JRE。
所以装置了 JDK,就无需再独自装置 JRE 了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe) 等。
Java 是解释执行么?
码老湿,Java 是解释执行的么?
这个说法不太精确。
咱们开发的 Java 的源代码,首先通过 Javac 编译成为字节码(bytecode),在运行时,通过 Java 虚拟机(JVM)内嵌的解释器将字节码转换成为最终的机器码。
然而常见的 JVM,比方咱们大多数状况应用的 Oracle JDK 提供的 Hotspot JVM,都提供了 JIT(Just-In-Time)
编译器。
也就是通常说的动静编译器,JIT 可能在运行时将热点代码编译成机器码,这种状况下局部 热点代码 就属于编译执行,而不是解释执行了。
采纳字节码的益处
什么是字节码?采纳字节码的益处是什么?
字节码:Java 源代码通过虚拟机编译器编译后产生的文件(即扩大为.class 的文件),它不面向任何特定的处理器,只面向虚拟机。
采纳字节码的益处:
家喻户晓,咱们通常把 Java 分为编译期和运行时。这里说的 Java 的编译和 C/C++ 是有着不同的意义的,Javac 的编译,编译 Java 源码生成“.class”文件外面理论是字节码,而不是能够间接执行的机器码。Java 通过字节码和 Java 虚拟机(JVM)这种跨平台的形象,屏蔽了操作系统和硬件的细节,这也是实现“一次编译,到处执行”的根底。
根底语法
JDK 1.8 之后有哪些新个性
接口默认办法:Java8 容许咱们给接口增加一个非形象的办法实现,只须要应用 default 关键字即可。
Lambda 表达式和函数式接口:Lambda 表达式实质上是一段匿名外部类,也能够是一段能够传递的代码。
Lambda 容许把函数作为一个办法的参数(函数作为参数传递到办法中),应用 Lambda 表达式使代码更加简洁,然而也不要滥用,否则会有可读性等问题,《EffectiveJava》作者 JoshBloch 倡议应用 Lambda 表达式最好不要超过 3 行。
StreamAPI:用函数式编程形式在汇合类上进行简单操作的工具,配合 Lambda 表达式能够不便的对汇合进行解决。
Java8 中解决汇合的要害抽象概念,它能够指定你心愿对汇合进行的操作,能够执行非常复杂的查找、过滤和映射数据等操作。
应用 StreamAPI 对汇合数据进行操作,就相似于应用 SQL 执行的数据库查问。也能够应用 StreamAPI 来并行执行操作。
简而言之,StreamAPI 提供了一种高效且易于应用的解决数据的形式。
办法援用:办法援用提供了十分有用的语法,能够间接援用已有 Java 类或对象(实例)的办法或结构器。
与 lambda 联结应用,办法援用能够使语言的结构更紧凑简洁,缩小冗余代码。
日期工夫 API:Java8 引入了新的日期工夫 API 改良了日期工夫的治理。Optional 类:驰名的 NullPointerException 是引起零碎失败最常见的起因。
很久以前 GoogleGuava 我的项目引入了 Optional 作为解决空指针异样的一种形式,不赞成代码被 null 查看的代码净化,冀望程序员写整洁的代码。
受 GoogleGuava 的激励,Optional 当初是 Java8 库的一部分。
新工具:新的编译工具,如:Nashorn 引擎 jjs、类依赖分析器 jdeps。
结构器是否能够重写
Constructor 不能被 override(重写),然而能够 overload(重载),所以你能够看到⼀个类中有多个构造函数的状况。
wait() 和 sleep 区别
起源不同:sleep()来自 Thread 类,wait()来自 Object 类。
对于同步锁的影响不同:sleep()不会该表同步锁的行为,如果以后线程持有同步锁,那么 sleep 是不会让线程开释同步锁的。
wait()会开释同步锁,让其余线程进入 synchronized 代码块执行。
应用范畴不同:sleep()能够在任何中央应用。wait()只能在同步控制办法或者同步控制块外面应用,否则会抛 IllegalMonitorStateException。
复原形式不同:两者会暂停以后线程,然而在复原上不太一样。sleep()在工夫到了之后会从新复原;
wait()则须要其余线程调用同一对象的 notify()/nofityAll()能力从新复原。
& 和 && 的区别
&
运算符有两种用法:
- 按位与;
- 逻辑与。
&&
运算符是短路与运算。逻辑与跟短路与的差异是十分微小的,尽管二者都要求运算符左右两端的布尔值都是 true 整个表达式的值才是 true。
&&
之所以称为短路运算,是因为如果 && 右边的表达式的值是 false,左边的表达式会被间接短路掉,不会进行运算。
留神:逻辑或运算符(|)和短路或运算符(||)的差异也是如此。
Java 有哪些数据类型?
Java 语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中调配了不同大小的内存空间。
分类
-
根本数据类型
-
数值型
- 整数类型(byte,short,int,long)
- 浮点类型(float,double)
- 字符型(char)
- 布尔型(boolean)
-
-
援用数据类型
- 类(class)
- 接口(interface)
- 数组([])
this 关键字的用法
this 是本身的一个对象,代表对象自身,能够了解为:指向对象自身的一个指针。
this 的用法在 java 中大体能够分为 3 种:
- 一般的间接援用,this 相当于是指向以后对象自身。
- 形参加成员名字重名,用 this 来辨别:
public Person(String name, int age) {
this.name = name;
this.age = age;
}
- 援用本类的构造函数
class Person{
private String name;
private int age;
public Person() {}
public Person(String name) {this.name = name;}
public Person(String name, int age) {this(name);
this.age = age;
}
}
super 关键字的用法
super 能够了解为是指向本人超(父)类对象的一个指针,而这个超类指的是离本人最近的一个父类。
super 也有三种用法:
- 一般的间接援用:与 this 相似,super 相当于是指向以后对象的父类的援用,这样就能够用 super.xxx 来援用父类的成员。
-
子类中的成员变量或办法与父类中的成员变量或办法同名时,用 super 进行辨别
lass Person{ protected String name; public Person(String name) {this.name = name;} } class Student extends Person{ private String name; public Student(String name, String name1) {super(name); this.name = name1; } public void getInfo(){System.out.println(this.name); //Child System.out.println(super.name); //Father } } public class Test {public static void main(String[] args) {Student s1 = new Student("Father","Child"); s1.getInfo();} }
- 援用父类构造函数;
成员变量与局部变量的区别有哪些
变量:在程序执行的过程中,在某个范畴内其值能够产生扭转的量。从实质上讲,变量其实是内存中的一小块区域。
成员变量:办法内部,类外部定义的变量。
局部变量:类的办法中的变量。
区别如下:
作用域
成员变量:针对整个类无效。局部变量:只在某个范畴内无效。(个别指的就是办法, 语句体内)
存储地位
成员变量:随着对象的创立而存在,随着对象的隐没而隐没,存储在堆内存中。
局部变量:在办法被调用,或者语句被执行的时候存在,存储在栈内存中。当办法调用完,或者语句完结后,就主动开释。
生命周期
成员变量:随着对象的创立而存在,随着对象的隐没而隐没 局部变量:当办法调用完,或者语句完结后,就主动开释。
初始值
成员变量:有默认初始值。
局部变量:没有默认初始值,应用前必须赋值。
动静代理是基于什么原理
基于反射实现
反射机制是 Java 语言提供的一种根底性能,赋予程序在运行时自省(introspect,官网用语)的能力。通过反射咱们能够间接操作类或者对象,比方获取某个对象的类定义,获取类申明的属性和办法,调用办法或者结构对象,甚至能够运行时批改类定义。
码老湿,他的应用场景是什么?
AOP 通过(动静)代理机制能够让开发者从这些繁琐事项中抽身进去,大幅度提高了代码的形象水平和复用度。
包装 RPC 调用:通过代理能够让调用者与实现者之间解耦。比方进行 RPC 调用,框架外部的寻址、序列化、反序列化等,对于调用者往往是没有太大意义的,通过代理,能够提供更加友善的界面。
int 与 Integer 区别
Java 是一个近乎纯净的面向对象编程语言,然而为了编程的不便还是引入了根本数据类型,然而为了可能将这些根本数据类型当成对象操作,Java 为每一个根本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了主动装箱 / 拆箱机制,使得二者能够互相转换。
Java 为每个原始类型提供了包装类型:
- 原始类型: boolean,char,byte,short,int,long,float,double。
- 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double。
int 是咱们常说的整形数字,是 Java 的 8 个原始数据类型(Primitive Types,boolean、byte、short、char、int、float、double、long)之一。Java 语言尽管号称一切都是对象,但原始数据类型是例外。
Integer 是 int 对应的包装类,它有一个 int 类型的字段存储数据,并且提供了基本操作,比方数学运算、int 和字符串之间转换等。在 Java 5 中,引入了主动装箱和主动拆箱性能(boxing/unboxing),Java 能够依据上下文,主动进行转换,极大地简化了相干编程。
Integer a= 127 与 Integer b = 127 相等吗
对于对象援用类型:== 比拟的是对象的内存地址。对于根本数据类型:== 比拟的是值。
大部分数据操作都是集中在无限的、较小的数值范畴,因此,在 Java 5 中新增了动态工厂办法 valueOf,在调用它的时候会利用一个缓存机制,带来了显著的性能改良。依照 Javadoc,这个值默认缓存是 -128 到 127 之间。
如果整型字面量的值在 -128 到 127 之间,那么主动装箱时不会 new 新的 Integer 对象,而是间接援用常量池中的 Integer 对象,超过范畴 a1==b1 的后果是 false。
public static void main(String[] args) {Integer a = new Integer(3);
Integer b = 3; // 将 3 主动装箱成 Integer 类型
int c = 3;
System.out.println(a == b); // false 两个援用没有援用同一对象
System.out.println(a == c); // true a 主动拆箱成 int 类型再和 c 比拟
System.out.println(b == c); // true
Integer a1 = 128;
Integer b1 = 128;
System.out.println(a1 == b1); // false
Integer a2 = 127;
Integer b2 = 127;
System.out.println(a2 == b2); // true
}
面向对象
面向对象与面向过程的区别是什么?
面向过程
长处:性能比面向对象高,因为类调用时须要实例化,开销比拟大,比拟耗费资源; 比方单片机、嵌入式开发、Linux/Unix 等个别采纳面向过程开发,性能是最重要的因素。
毛病:没有面向对象易保护、易复用、易扩大
面向对象
长处:易保护、易复用、易扩大,因为面向对象有封装、继承、多态性的个性,能够设计出低耦合的零碎,使零碎更加灵便、更加易于保护
毛病:性能比面向过程低
面向过程是具体化的,流程化的,解决一个问题,你须要一步一步的剖析,一步一步的实现。
面向对象是模型化的,你只需形象出一个类,这是一个关闭的盒子,在这里你领有数据也领有解决问题的办法。须要什么性能间接应用就能够了,不用去一步一步的实现,至于这个性能是如何实现的,管咱们什么事?咱们会用就能够了。
面向对象的底层其实还是面向过程,把面向过程形象成类,而后封装,不便咱们应用的就是面向对象了。
面向对象编程因为其具备丰盛的个性(封装、形象、继承、多态),能够实现很多简单的设计思路,是很多设计准则、设计模式等编码实现的根底。
面向对象四大个性
码老湿,如何了解面向对象的四大个性?
形象
形象是将一类对象的独特特色总结进去结构类的过程,包含数据抽象和行为形象两方面。形象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
另外,形象是一个宽泛的设计思维,开发者能不能设计好代码,形象能力也至关重要。
很多设计准则都体现了形象这种设计思维,比方基于接口而非实现编程、开闭准则(对扩大凋谢、对批改敞开)、代码解耦(升高代码的耦合性)等。
在面对简单零碎的时候,人脑能接受的信息复杂程度是无限的,所以咱们必须疏忽掉一些非关键性的实现细节。
封装
把一个对象的属性私有化,同时提供一些能够被外界拜访的属性的办法,如果属性不想被外界拜访,咱们大可不必提供办法给外界拜访。
通过封装,只须要裸露必要的办法给调用者,调用者不用理解背地的业务细节,用错的概率就缩小。
继承
应用已存在的类的定义作为根底建设新类的技术,新类的定义能够减少新的数据或新的性能,也能够用父类的性能,但不能选择性地继承父类。
通过应用继承咱们可能十分不便地复用以前的代码,须要留神的是,适度应用继承,层级深就会导致代码可读性和可维护性变差。
对于继承如下 3 点请记住:
- 子类领有父类非 private 的属性和办法。
- 子类能够领有本人属性和办法,即子类能够对父类进行扩大。
- 子类能够用本人的形式实现父类的办法。(当前介绍)。
多态
所谓多态就是指程序中定义的援用变量所指向的具体类型和通过该援用变量收回的办法调用在编程时并不确定,而是在程序运行期间才确定。
即一个援用变量到底会指向哪个类的实例对象,该援用变量收回的办法调用到底是哪个类中实现的办法,必须在由程序运行期间能力决定。
在 Java 中有两种模式能够实现多态:继承(多个子类对同一办法的重写)和接口(实现接口并笼罩接口中同一办法)。
多态也是很多设计模式、设计准则、编程技巧的代码实现根底,比方策略模式、基于接口而非实现编程、依赖倒置准则、里式替换准则、利用多态去掉简短的 if-else 语句等等。
什么是多态机制?
所谓多态就是指程序中定义的援用变量所指向的具体类型和通过该援用变量收回的办法调用在编程时并不确定,而是在程序运行期间才确定,即一个援用变量倒底会指向哪个类的实例对象,该援用变量收回的办法调用到底是哪个类中实现的办法,必须在由程序运行期间能力决定。
因为在程序运行时才确定具体的类,这样,不必批改源程序代码,就能够让援用变量绑定到各种不同的类实现上,从而导致该援用调用的具体方法随之扭转,即不批改程序代码就能够扭转程序运行时所绑定的具体代码,让程序能够抉择多个运行状态,这就是多态性。
多态分为编译时多态和运行时多态。
其中编辑时多态是动态的,次要是指办法的重载,它是依据参数列表的不同来辨别不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。
而运行时多态是动静的,它是通过动静绑定来实现的,也就是咱们所说的多态性。
Java 语言是如何实现多态的?
Java 实现多态有三个必要条件:继承、重写、向上转型。
继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些办法进行从新定义,在调用这些办法时就会调用子类的办法。
向上转型:在多态中须要将子类的援用赋给父类对象,只有这样该援用才可能具备技能调用父类的办法和子类的办法。
只有满足了上述三个条件,咱们才可能在同一个继承构造中应用对立的逻辑实现代码解决不同的对象,从而达到执行不同的行为。
重载与重写
办法的重载和重写都是实现多态的形式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载:产生在同一个类中,办法名雷同参数列表不同(参数类型不同、个数不同、程序不同),与办法返回值和拜访修饰符无关,即重载的办法不能依据返回类型进行辨别。
重写:产生在父子类中,办法名、参数列表必须雷同,返回值小于等于父类,抛出的异样小于等于父类,拜访修饰符大于等于父类(里氏代换准则);如果父类办法拜访修饰符为 private 则子类中就不是重写。
== 和 equals 的区别是什么
== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(根本数据类型 == 比拟的是值,援用数据类型 == 比拟的是内存地址)。
equals() : 它的作用也是判断两个对象是否相等。但它个别有两种应用状况:
- 类没有笼罩 equals() 办法。则通过 equals() 比拟该类的两个对象时,等价于通过“==”比拟这两个对象。
- 类笼罩了 equals() 办法。个别,咱们都笼罩 equals() 办法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
为什么重写 equals 时必须重写 hashCode 办法?
如果两个对象相等,则 hashcode 肯定也是雷同的
两个对象相等,对两个对象别离调用 equals 办法都返回 true
两个对象有雷同的 hashcode 值,它们也不肯定是相等的.
因而,equals 办法被笼罩过,则 hashCode 办法也必须被笼罩
为什么要有 hashcode
咱们以“HashSet 如何查看反复”为例子来阐明为什么要有 hashCode:
当你把对象退出 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象退出的地位,同时也会与其余曾经退出的对象的 hashcode 值作比拟,如果没有相符的 hashcode,HashSet 会假如对象没有反复呈现。
然而如果发现有雷同 hashcode 值的对象,这时会调用 equals()办法来查看 hashcode 相等的对象是否真的雷同。
如果两者雷同,HashSet 就不会让其退出操作胜利。
如果不同的话,就会从新散列到其余地位。这样咱们就大大减少了 equals 的次数,相应就大大提高了执行速度。
面向对象的根本准则
码老湿,什么是 SOLID?
这是面向对象编程的一种设计准则,对于每一种设计准则,咱们须要把握它的设计初衷,能解决哪些编程问题,有哪些利用场景。
- 繁多职责准则 SRP(Single Responsibility Principle) 类的性能要繁多,不能无所不包,跟杂货铺似的。
- 凋谢关闭准则 OCP(Open-Close Principle) 一个模块对于拓展是凋谢的,对于批改是关闭的,想要减少性能热烈欢迎,想要批改,哼,一万个不乐意。
- 里式替换准则 LSP(the Liskov Substitution Principle LSP) 子类能够替换父类呈现在父类可能呈现的任何中央。比方你能代表你爸去你姥姥家干活。哈哈~~(其实多态就是一种这个准则的一种实现)。
- 接口拆散准则 ISP(the Interface Segregation Principle ISP) 设计时采纳多个与特定客户类无关的接口比采纳一个通用的接口要好。就比方一个手机领有打电话,看视频,玩游戏等性能,把这几个性能拆分成不同的接口,比在一个接口里要好的多。
-
依赖倒置准则 DIP(the Dependency Inversion Principle DIP):高层模块(high-level modules)不要依赖低层模块(low-level)。高层模块和低层模块应该通过形象(abstractions)来相互依赖。除此之外,形象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖形象(abstractions)。
- 形象不应该依赖于具体实现,具体实现应该依赖于形象。就是你出国要说你是中国人,而不能说你是哪个村子的。
- 比如说中国人是形象的,上面有具体的 xx 省,xx 市,xx 县。你要依赖的形象是中国人,而不是你是 xx 村的。
- 所谓高层模块和低层模块的划分,简略来说就是,在调用链上,调用者属于高层,被调用者属于低层。
- Tomcat 就是高层模块,咱们编写的 Web 利用程序代码就是低层模块。Tomcat 和利用程序代码之间并没有间接的依赖关系,两者都依赖同一个「形象」,也就是 Servlet 标准。
- Servlet 标准不依赖具体的 Tomcat 容器和应用程序的实现细节,而 Tomcat 容器和应用程序依赖 Servlet 标准。
码老湿,接口隔离与繁多职责有什么区别?
繁多职责侧重点是模块、类、接口的设计思维。
接口隔离准则侧重于接口设计,提供了一种判断接口职责是否繁多的规范。
说下 Exception 与 Error 区别?
码老湿,他们的相同点是什么呀?
Exception 和 Error 都是继承了 Throwable 类,在 Java 中只有 Throwable 类型的实例才能够被抛出(throw)或者捕捉(catch),它是异样解决机制的根本组成类型。
Exception 和 Error 体现了 Java 平台设计者对不同异常情况的分类。
异样应用标准:
- 尽量不要捕捉相似 Exception 这样的通用异样,而是应该捕捉特定异样
- 不要生吞(swallow)异样。这是异样解决中要特地留神的事件,因为很可能会导致十分难以诊断的诡异状况。
Exception
Exception 是程序失常运行中,能够意料的意外状况,可能并且应该被捕捉,进行相应解决。
就好比开车去洗桑拿,后方路线施工,禁止通行。然而咱们换条路就能够解决。
Exception 又分为可查看(checked)异样和不查看(unchecked)异样,可查看异样在源代码里必须显式地进行捕捉解决,这是编译期查看的一部分。
不查看异样就是所谓的运行时异样,相似 NullPointerException、ArrayIndexOutOfBoundsException 之类,通常是能够编码防止的逻辑谬误,具体依据须要来判断是否须要捕捉,并不会在编译期强制要求。
Checked Exception 的假如是咱们捕捉了异样,而后恢复程序。然而,其实咱们大多数状况下,基本就不可能复原。
Checked Exception 的应用,曾经大大偏离了最后的设计目标。Checked Exception 不兼容 functional 编程,如果你写过 Lambda/Stream 代码,置信深有体会。
Error
此类谬误个别示意代码运行时 JVM 呈现问题。通常有 Virtual MachineError(虚拟机运行谬误)、NoClassDefFoundError(类定义谬误)等。
比方 OutOfMemoryError:内存不足谬误;StackOverflowError:栈溢出谬误。此类谬误产生时,JVM 将终止线程。
绝大多数导致程序不可复原,这些谬误是不受检异样,非代码性谬误。因而,当此类谬误产生时,应用程序不应该去解决此类谬误。依照 Java 常规,咱们是不应该实现任何新的 Error 子类的!
比方开车去洗桑拿,老王出车祸了。无奈洗了,只能去医院。
JVM 如何解决异样?
在一个办法中如果产生异样,这个办法会创立一个异样对象,并转交给 JVM,该异样对象蕴含异样名称,异样形容以及异样产生时应用程序的状态。
创立异样对象并转交给 JVM 的过程称为抛出异样。可能有一系列的办法调用,最终才进入抛出异样的办法,这一系列办法调用的有序列表叫做调用栈。
JVM 会顺着调用栈去查找看是否有能够解决异样的代码,如果有,则调用异样解决代码。
当 JVM 发现能够解决异样的代码时,会把产生的异样传递给它。如果 JVM 没有找到能够解决该异样的代码块,JVM 就会将该异样转交给默认的异样处理器(默认处理器为 JVM 的一部分),默认异样处理器打印出异样信息并终止应用程序。
NoClassDefFoundError 和 ClassNotFoundException 区别?
NoClassDefFoundError 是一个 Error 类型的异样,是由 JVM 引起的,不应该尝试捕捉这个异样。
引起该异样的起因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义,该动作产生在运行期间,即编译时该类存在,然而在运行时却找不到了,可能是变异后被删除了等起因导致;
ClassNotFoundException 是一个受查异样,须要显式地应用 try-catch 对其进行捕捉和解决,或在办法签名中用 throws 关键字进行申明。
当应用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 动静加载类到内存的时候,通过传入的类门路参数没有找到该类,就会抛出该异样;
另一种抛出该异样的可能起因是某个类曾经由一个类加载器加载至内存中,另一个加载器又尝试去加载它。
Java 常见异样有哪些?
java.lang.IllegalAccessError:守法拜访谬误。当一个利用试图拜访、批改某个类的域(Field)或者调用其办法,然而又违反域或办法的可见性申明,则抛出该异样。
java.lang.InstantiationError:实例化谬误。当一个利用试图通过 Java 的 new 操作符结构一个抽象类或者接口时抛出该异样.
java.lang.OutOfMemoryError:内存不足谬误。当可用内存不足以让 Java 虚拟机调配给一个对象时抛出该谬误。
java.lang.StackOverflowError:堆栈溢出谬误。当一个利用递归调用的档次太深而导致堆栈溢出或者陷入死循环时抛出该谬误。
java.lang.ClassCastException:类造型异样。假如有类 A 和 B(A 不是 B 的父类或子类),O 是 A 的实例,那么当强制将 O 结构为类 B 的实例时抛出该异样。该异样常常被称为强制类型转换异样。
java.lang.ClassNotFoundException:找不到类异样。当利用试图依据字符串模式的类名结构类,而在遍历 CLASSPAH 之后找不到对应名称的 class 文件时,抛出该异样。
java.lang.ArithmeticException:算术条件异样。譬如:整数除零等。
java.lang.ArrayIndexOutOfBoundsException:数组索引越界异样。当对数组的索引值为正数或大于等于数组大小时抛出。
final、finally、finalize 有什么区别?
除了名字类似,他们毫无关系!!!
- final 能够润饰类、变量、办法,润饰类示意该类不能被继承、润饰办法示意该办法不能被重写、润饰变量示意该变量是一个常量不能被从新赋值。
- finally 个别作用在 try-catch 代码块中,在解决异样的时候,通常咱们将肯定要执行的代码办法 finally 代码块中,示意不论是否出现异常,该代码块都会执行,个别用来寄存一些敞开资源的代码。
- finalize 是一个办法,属于 Object 类的一个办法,而 Object 类是所有类的父类,Java 中容许应用 finalize()办法在垃圾收集器将对象从内存中革除进来之前做必要的清理工作。
final 有什么用?
用于润饰类、属性和办法;
- 被 final 润饰的类不能够被继承
- 被 final 润饰的办法不能够被重写
- 被 final 润饰的变量不能够被扭转,被 final 润饰不可变的是变量的援用,而不是援用指向的内容,援用指向的内容是能够扭转的。
try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
答:会执行,在 return 前执行。
留神:在 finally 中扭转返回值的做法是不好的,因为如果存在 finally 代码块,try 中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行结束之后再向调用者返回其值,而后如果在 finally 中批改了返回值,就会返回批改后的值。
显然,在 finally 中返回或者批改返回值会对程序造成很大的困扰,C# 中间接用编译谬误的形式来阻止程序员干这种肮脏的事件,Java 中也能够通过晋升编译器的语法查看级别来产生正告或谬误。
public static int getInt() {
int a = 10;
try {System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a;
/*
* return a 在程序执行到这一步的时候,这里不是 return a 而是 return 30;这个返回门路就造成了
* 然而呢,它发现前面还有 finally,所以继续执行 finally 的内容,a=40
* 再次回到以前的门路, 持续走 return 30,造成返回门路之后,这里的 a 就不是 a 变量了,而是常量 30
*/
} finally {a = 40;}
return a;
}
执行后果:30。
public static int getInt() {
int a = 10;
try {System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a;
} finally {
a = 40;
// 如果这样,就又从新造成了一条返回门路,因为只能通过 1 个 return 返回,所以这里间接返回 40
return a;
}
}
执行后果:40。
强援用、软援用、弱援用、虚援用
强援用、软援用、弱援用、幻象援用有什么区别?具体应用场景是什么?
不同的援用类型,次要体现的是对象不同的可达性(reachable)状态和对垃圾收集的影响。
强援用
通过 new 创立的对象就是强援用,强援用指向一个对象,就示意这个对象还活着,垃圾回收不会去收集。
软援用
是一种绝对强援用弱化一些的援用,只有当 JVM 认为内存不足时,才会去试图回收软援用指向的对象。
JVM 会确保在抛出 OutOfMemoryError 之前,清理软援用指向的对象。
软援用通常用来实现内存敏感的缓存,如果还有闲暇内存,就能够临时保留缓存,当内存不足时清理掉,这样就保障了应用缓存的同时,不会耗尽内存。
弱援用
ThreadlocalMap 中的 key 就是用了弱援用,因为 ThreadlocalMap 被 thread 对象持有,所以如果是强援用的话,只有当 thread 完结时能力被回收,而弱援用则能够在应用完后立刻回收,不用期待 thread 完结。
虚援用
“虚援用”顾名思义,就是形同虚设,与其余几种援用都不同,虚援用并不会决定对象的生命周期。如果一个对象仅持有虚援用,那么它就和没有任何援用一样,在任何时候都可能被垃圾回收器回收。
虚援用次要用来跟踪对象被垃圾回收器回收的流动。虚援用与软援用和弱援用的一个区别在于:虚援用必须和援用队列(ReferenceQueue)联结应用。
当垃圾回收器筹备回收一个对象时,如果发现它还有虚援用,就会在回收对象的内存之前,把这个虚援用退出到与之 关联的援用队列中。
String、StringBuilder、StringBuffer 有什么区别?
可变性
String 类中应用字符数组保留字符串,private final char value[]
,所以 string 对象是不可变的。StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是应用字符数组保留字符串,char[] value,这两种对象都是可变的。
线程安全性
String 中的对象是不可变的,也就能够了解为常量,线程平安。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共办法。
StringBuffer 对办法加了同步锁或者对调用的办法加了同步锁,所以是线程平安的。StringBuilder 并没有对办法进行加同步锁,所以是非线程平安的。
性能
每次对 String 类型进行扭转的时候,都会生成一个新的 String 对象,而后将指针指向新的 String 对象。
StringBuffer 每次都会对 StringBuffer 对象自身进行操作,而不是生成新的对象并扭转对象援用。雷同状况下应用 StirngBuilder 相比应用 StringBuffer 仅能取得 10%~15% 左右的性能晋升,但却要冒多线程不平安的危险。
对于三者应用的总结
如果要操作大量的数据用 = String
单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
String
String 是 Java 语言十分根底和重要的类,提供了结构和治理字符串的各种根本逻辑。它是典型的 Immutable 类,被申明成为 final class,所有属性也都是 final 的。
也因为它的不可变性,相似拼接、裁剪字符串等动作,都会产生新的 String 对象。
StringBuilder
StringBuilder 是 Java 1.5 中新增的,在能力上和 StringBuffer 没有本质区别,然而它去掉了线程平安的局部,无效减小了开销,是绝大部分状况下进行字符串拼接的首选。
StringBuffer
StringBuffer 是为解决下面提到拼接产生太多两头对象的问题而提供的一个类,咱们能够用 append 或者 add 办法,把字符串增加到已有序列的开端或者指定地位。StringBuffer 实质是一个线程平安的可批改字符序列,它保障了线程平安,也随之带来了额定的性能开销,所以除非有线程平安的须要,不然还是举荐应用它的后继者,也就是 StringBuilder。
HashMap 应用 String 作为 key 有什么益处
HashMap 外部实现是通过 key 的 hashcode 来确定 value 的存储地位,因为字符串是不可变的,所以当创立字符串时,它的 hashcode 被缓存下来,不须要再次计算,所以相比于其余对象更快。
接口和抽象类有什么区别?
抽象类是用来捕获子类的通用个性的。接口是形象办法的汇合。
接口和抽象类各有优缺点,在接口和抽象类的抉择上,必须恪守这样一个准则:
- 行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
- 抉择抽象类的时候通常是如下状况:须要定义子类的行为,又要为子类提供通用的性能。
相同点
- 接口和抽象类都不能实例化
- 都位于继承的顶端,用于被其余实现或继承
- 都蕴含形象办法,其子类都必须覆写这些形象办法
接口
接口定义了协定,是面向对象编程(封装、继承多态)根底,通过接口咱们能很好的实现繁多职责、接口隔离、内聚。
- 不能实例化;
- 不能蕴含任何十分量成员,任何 field 都是隐含着 public static final 的意义;
- 同时,没有非静态方法实现,也就是说要么是形象办法,要么是静态方法。
Java8 中接口中引入默认办法和静态方法,并且不必强制子类来实现它。以此来缩小抽象类和接口之间的差别。
抽象类
抽象类是不能实例化的类,用 abstract 关键字润饰 class,其目标次要是代码重用。
从设计层面来说,抽象类是对类的形象,是一种模板设计,接口是行为的形象,是一种行为的标准。
除了不能实例化,模式上和个别的 Java 类并没有太大区别。
能够有一个或者多个形象办法,也能够没有形象办法。抽象类大多用于抽取相干 Java 类的共用办法实现或者是独特成员变量,而后通过继承的形式达到代码复用的目标。
码老湿,抽象类能用 final 润饰么?
不能,定义抽象类就是让其余类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能润饰抽象类
值传递
当一个对象被当作参数传递到一个办法后,此办法可扭转这个对象的属性,并可返回变动后的后果,那么这里到底是值传递还是援用传递?
是值传递。
Java 语言的办法调用只反对参数的值传递。当一个对象实例作为一个参数被传递到办法中时,参数的值就是对该对象的援用。
对象的属性能够在被调用过程中被扭转,但对对象援用的扭转是不会影响到调用者的。
为什么 Java 只有值传递?
首先回顾一下在程序设计语言中无关将参数传递给办法(或函数)的一些专业术语。按值调用 (call by value) 示意办法接管的是调用者提供的值,而按援用调用(call by reference)示意办法接管的是调用者提供的变量地址。
一个办法能够批改传递援用所对应的变量值,而不能批改传递值调用所对应的变量值。
它用来形容各种程序设计语言(不只是 Java)中办法参数传递形式。
Java 程序设计语言总是采纳按值调用。也就是说,办法失去的是所有参数值的一个拷贝,也就是说,办法不能批改传递给它的任何参数变量的内容。
根本数据类型
例子如下:
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 =" + num1);
System.out.println("num2 =" + num2);
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a =" + a);
System.out.println("b =" + b);
}
执行后果:
a = 20
b = 10
num1 = 10
num2 = 20
解析:
在 swap 办法中,a、b 的值进行替换,并不会影响到 num1、num2。
因为,a、b 中的值,只是从 num1、num2 的复制过去的。
也就是说,a、b 相当于 num1、num2 的正本,正本的内容无论怎么批改,都不会影响到原件自身。
对象援用类型
public static void main(String[] args) {int[] arr = {1, 2, 3, 4, 5};
System.out.println(arr[0]);
change(arr);
System.out.println(arr[0]);
}
public static void change(int[] array) {
// 将数组的第一个元素变为 0
array[0] = 0;
}
后果:
1
0
解析:
array 被初始化 arr 的拷贝也就是一个对象的援用,也就是说 array 和 arr 指向的时同一个数组对象。因而,内部对援用对象的扭转会反映到所对应的对象上。
通过 example2 咱们曾经看到,实现一个扭转对象参数状态的办法并不是一件难事。理由很简略,办法失去的是对象援用的拷贝,对象援用及其他的拷贝同时援用同一个对象。
很多程序设计语言(特地是,C++ 和 Pascal)提供了两种参数传递的形式:值调用和援用调用。
有些程序员认为 Java 程序设计语言对对象采纳的是援用调用,实际上,这种了解是不对的。
值传递和援用传递有什么区别?
值传递:指的是在办法调用时,传递的参数是按值的拷贝传递,传递的是值的拷贝,也就是说传递后就互不相干了。
援用传递:指的是在办法调用时,传递的参数是按援用进行传递,其实传递的援用的地址,也就是变量所对应的内存空间的地址。传递的是值的援用,也就是说传递前和传递后都指向同一个援用(也就是同一个内存空间)。