Java的特点
Java是一门面向对象的编程语言。面向对象和面向过程的区别参考下一个问题。
Java具备平台独立性和移植性。
- Java有一句口号:
Write once, run anywhere
,一次编写、到处运行。这也是Java的魅力所在。而实现这种个性的正是Java虚拟机JVM。已编译的Java程序能够在任何带有JVM的平台上运行。你能够在windows平台编写代码,而后拿到linux上运行。只有你在编写完代码后,将代码编译成.class文件,再把class文件打成Java包,这个jar包就能够在不同的平台上运行了。
Java具备稳健性。
- Java是一个强类型语言,它容许扩大编译时查看潜在类型不匹配问题的性能。Java要求显式的办法申明,它不反对C格调的隐式申明。这些严格的要求保障编译程序能捕获调用谬误,这就导致更牢靠的程序。最全面的Java面试网站
- 异样解决是Java中使得程序更持重的另一个特色。异样是某种相似于谬误的异样条件呈现的信号。应用
try/catch/finally
语句,程序员能够找到出错的解决代码,这就简化了出错解决和复原的工作。
Java是如何实现跨平台的?
Java是通过JVM(Java虚拟机)实现跨平台的。
JVM能够了解成一个软件,不同的平台有不同的版本。咱们编写的Java代码,编译后会生成.class 文件(字节码文件)。Java虚拟机就是负责将字节码文件翻译成特定平台下的机器码,通过JVM翻译成机器码之后能力运行。不同平台下编译生成的字节码是一样的,然而由JVM翻译成的机器码却不一样。
只有在不同平台上装置对应的JVM,就能够运行字节码文件,运行咱们编写的Java程序。
因而,运行Java程序必须有JVM的反对,因为编译的后果不是机器码,必须要通过JVM的翻译能力执行。
本文曾经收录到Github仓库,该仓库蕴含计算机根底、Java根底、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等外围知识点,欢送star~
Github地址
如果拜访不了Github,能够拜访码云地址。
码云地址
Java 与 C++ 的区别
- Java 是纯正的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 兼容 C ,岂但反对面向对象也反对面向过程。
- Java 通过虚拟机从而实现跨平台个性, C++ 依赖于特定的平台。
- Java 没有指针,它的援用能够了解为平安指针,而 C++ 具备和 C 一样的指针。
- Java 反对主动垃圾回收,而 C++ 须要手动回收。
- Java 不反对多重继承,只能通过实现多个接口来达到雷同目标,而 C++ 反对多重继承。
JDK/JRE/JVM三者的关系
JVM
英文名称(Java Virtual Machine),就是咱们耳熟能详的 Java 虚拟机。Java 可能跨平台运行的外围在于 JVM 。
所有的java程序会首先被编译为.class的类文件,这品种文件能够在虚拟机上执行。也就是说class文件并不间接与机器的操作系统交互,而是通过虚拟机间接与操作系统交互,由虚拟机将程序解释给本地零碎执行。
针对不同的零碎有不同的 jvm 实现,有 Linux 版本的 jvm 实现,也有Windows 版本的 jvm 实现,然而同一段代码在编译后的字节码是一样的。这就是Java可能跨平台,实现一次编写,多处运行的起因所在。
JRE
英文名称(Java Runtime Environment),就是Java 运行时环境。咱们编写的Java程序必须要在JRE能力运行。它次要蕴含两个局部,JVM 和 Java 外围类库。
JRE是Java的运行环境,并不是一个开发环境,所以没有蕴含任何开发工具,如编译器和调试器等。
如果你只是想运行Java程序,而不是开发Java程序的话,那么你只须要装置JRE即可。
最初给大家分享一个Github仓库,下面有大彬整顿的300多本经典的计算机书籍PDF,包含C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,能够star一下,下次找书间接在下面搜寻,仓库继续更新中~
Github地址
JDK
英文名称(Java Development Kit),就是 Java 开发工具包
学过Java的同学,都应该装置过JDK。当咱们装置完JDK之后,目录构造是这样的
能够看到,JDK目录下有个JRE,也就是JDK中曾经集成了 JRE,不必独自装置JRE。
另外,JDK中还有一些好用的工具,如jinfo,jps,jstack等。
最初,总结一下JDK/JRE/JVM,他们三者的关系
JRE = JVM + Java 外围类库
JDK = JRE + Java工具 + 编译器 + 调试器
Java程序是编译执行还是解释执行?
先看看什么是编译型语言和解释型语言。
编译型语言
在程序运行之前,通过编译器将源程序编译成机器码可运行的二进制,当前执行这个程序时,就不必再进行编译了。
长处:编译器个别会有预编译的过程对代码进行优化。因为编译只做一次,运行时不须要编译,所以编译型语言的程序执行效率高,能够脱离语言环境独立运行。
毛病:编译之后如果须要批改就须要整个模块从新编译。编译的时候依据对应的运行环境生成机器码,不同的操作系统之间移植就会有问题,须要依据运行的操作系统环境编译不同的可执行文件。
总结:执行速度快、效率高;依附编译器、跨平台性差些。
代表语言:C、C++、Pascal、Object-C以及Swift。
解释型语言
定义:解释型语言的源代码不是间接翻译成机器码,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。在运行的时候才将源程序翻译成机器码,翻译一句,而后执行一句,直至完结。
长处:
- 有良好的平台兼容性,在任何环境中都能够运行,前提是装置了解释器(如虚拟机)。
- 灵便,批改代码的时候间接批改就能够,能够疾速部署,不必停机保护。
毛病:每次运行的时候都要解释一遍,性能上不如编译型语言。
总结:解释型语言执行速度慢、效率低;依附解释器、跨平台性好。
代表语言:JavaScript、Python、Erlang、PHP、Perl、Ruby。
对于Java这种语言,它的源代码会先通过javac编译成字节码,再通过jvm将字节码转换成机器码执行,即解释运行 和编译运行配合应用,所以能够称为混合型或者半编译型。
面向对象和面向过程的区别?
面向对象和面向过程是一种软件开发思维。
- 面向过程就是剖析出解决问题所须要的步骤,而后用函数按这些步骤实现,应用的时候顺次调用就能够了。
- 面向对象是把形成问题事务分解成各个对象,别离设计这些对象,而后将他们组装成有残缺性能的零碎。面向过程只用函数实现,面向对象是用类实现各个功能模块。
以五子棋为例,面向过程的设计思路就是首先剖析问题的步骤:
1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输入最初后果。
把下面每个步骤用别离的函数来实现,问题就解决了。
而面向对象的设计则是从另外的思路来解决问题。整个五子棋能够分为:
- 黑白单方
- 棋盘零碎,负责绘制画面
- 规定零碎,负责断定诸如犯规、输赢等。
黑白单方负责承受用户的输出,并告知棋盘零碎棋子布局发生变化,棋盘零碎接管到了棋子的变动的信息就负责在屏幕下面显示出这种变动,同时利用规定零碎来对棋局进行断定。
面向对象有哪些个性?
面向对象四大个性:封装,继承,多态,形象
1、封装就是将类的信息暗藏在类外部,不容许内部程序间接拜访,而是通过该类的办法实现对暗藏信息的操作和拜访。 良好的封装可能缩小耦合。
2、继承是从已有的类中派生出新的类,新的类继承父类的属性和行为,并能扩大新的能力,大大增加程序的重用性和易维护性。在Java中是单继承的,也就是说一个子类只有一个父类。
3、多态是同一个行为具备多个不同表现形式的能力。在不批改程序代码的状况下扭转程序运行时绑定的代码。实现多态的三要素:继承、重写、父类援用指向子类对象。
- 动态多态性:通过重载实现,雷同的办法有不同的參数列表,能够依据参数的不同,做出不同的解决。
- 动静多态性:在子类中重写父类的办法。运行期间判断所援用对象的理论类型,依据其理论类型调用相应的办法。
4、形象。把客观事物用代码形象进去。
面向对象编程的六大准则?
- 对象繁多职责:咱们设计创立的对象,必须职责明确,比方商品类,外面相干的属性和办法都必须跟商品相干,不能呈现订单等不相干的内容。这里的类能够是模块、类库、程序集,而不单单指类。
- 里式替换准则:子类可能齐全代替父类,反之则不行。通常用于实现接口时使用。因为子类可能齐全代替基(父)类,那么这样父类就领有很多子类,在后续的程序扩大中就很容易进行扩大,程序齐全不须要进行批改即可进行扩大。比方IA的实现为A,因为我的项目需要变更,当初须要新的实现,间接在容器注入处更换接口即可.
- 迪米特法令,也叫最小准则,或者说最小耦合。通常在设计程序或开发程序的时候,尽量要高内聚,低耦合。当两个类进行交互的时候,会产生依赖。而迪米特法令就是倡议这种依赖越少越好。就像构造函数注入父类对象时一样,当须要依赖某个对象时,并不在意其外部是怎么实现的,而是在容器中注入相应的实现,既合乎里式替换准则,又起到理解耦的作用。
- 开闭准则:凋谢扩大,关闭批改。当我的项目需要产生变更时,要尽可能的不去对原有的代码进行批改,而在原有的根底上进行扩大。
- 依赖倒置准则:高层模块不应该间接依赖于底层模块的具体实现,而应该依赖于底层的形象。接口和抽象类不应该依赖于实现类,而实现类依赖接口或抽象类。
- 接口隔离准则:一个对象和另外一个对象交互的过程中,依赖的内容最小。也就是说在接口设计的时候,在遵循对象繁多职责的状况下,尽量减少接口的内容。
简洁版:
- 繁多职责:对象设计要求独立,不能设计万能对象。
- 开闭准则:对象批改最小化。
- 里式替换:程序扩大中形象被具体能够替换(接口、父类、能够被实现类对象、子类替换对象)
- 迪米特:高内聚,低耦合。尽量不要依赖细节。
- 依赖倒置:面向形象编程。也就是参数传递,或者返回值,能够应用父类类型或者接口类型。从狭义上讲:基于接口编程,提前设计好接口框架。
- 接口隔离:接口设计大小要适中。过大导致净化,过小,导致调用麻烦。
数组到底是不是对象?
先说说对象的概念。对象是依据某个类创立进去的一个实例,示意某类事物中一个具体的个体。
对象具备各种属性,并且具备一些特定的行为。站在计算机的角度,对象就是内存中的一个内存块,在这个内存块封装了一些数据,也就是类中定义的各个属性。
所以,对象是用来封装数据的。
java中的数组具备java中其余对象的一些根本特点。比方封装了一些数据,能够拜访属性,也能够调用办法。
因而,能够说,数组是对象。
也能够通过代码验证数组是对象的事实。比方以下的代码,输入后果为java.lang.Object。
Class clz = int[].class;
System.out.println(clz.getSuperclass().getName());
由此,能够看出,数组类的父类就是Object类,那么能够推断出数组就是对象。
Java的根本数据类型有哪些?
- byte,8bit
- char,16bit
- short,16bit
- int,32bit
- float,32bit
- long,64bit
- double,64bit
- boolean,只有两个值:true、false,能够使⽤用 1 bit 来存储
简略类型 | 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的大小。在《Java虚拟机标准》给出了单个boolean占4个字节,和boolean数组1个字节的定义,具体 还要看虚拟机实现是否依照标准来,因而boolean占用1个字节或者4个字节都是有可能的。
为什么不能用浮点型示意金额?
因为计算机中保留的小数其实是十进制的小数的近似值,并不是精确值,所以,千万不要在代码中应用浮点数来示意金额等重要的指标。
倡议应用BigDecimal或者Long来示意金额。
什么是值传递和援用传递?
- 值传递是对基本型变量而言的,传递的是该变量的一个正本,扭转正本不影响原变量。
- 援用传递个别是对于对象型变量而言的,传递的是该对象地址的一个正本,并不是原对象自身,两者指向同一片内存空间。所以对援用对象进行操作会同时扭转原对象。
java中不存在援用传递,只有值传递。即不存在变量a指向变量b,变量b指向对象的这种状况。
理解Java的包装类型吗?为什么须要包装类?
Java 是一种面向对象语言,很多中央都须要应用对象而不是根本数据类型。比方,在汇合类中,咱们是无奈将 int 、double 等类型放进去的。因为汇合的容器要求元素是 Object 类型。
为了让根本类型也具备对象的特色,就呈现了包装类型。相当于将根本类型包装起来,使得它具备了对象的性质,并且为其增加了属性和办法,丰盛了根本类型的操作。
主动装箱和拆箱
Java中根底数据类型与它们对应的包装类见下表:
原始类型 | 包装类型 |
---|---|
boolean | Boolean |
byte | Byte |
char | Character |
float | Float |
int | Integer |
long | Long |
short | Short |
double | Double |
装箱:将根底类型转化为包装类型。
拆箱:将包装类型转化为根底类型。
当根底类型与它们的包装类有如下几种状况时,编译器会主动帮咱们进行装箱或拆箱:
- 赋值操作(装箱或拆箱)
- 进行加减乘除混合运算 (拆箱)
- 进行>,<,==比拟运算(拆箱)
- 调用equals进行比拟(装箱)
- ArrayList、HashMap等汇合类增加根底类型数据时(装箱)
示例代码:
Integer x = 1; // 装箱 调⽤ Integer.valueOf(1)
int y = x; // 拆箱 调⽤了 X.intValue()
上面看一道常见的面试题:
Integer a = 100;
Integer b = 100;
System.out.println(a == b);
Integer c = 200;
Integer d = 200;
System.out.println(c == d);
输入:
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 c = 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并返回,所以a和b返回的是同一个对象,所以==
比拟是相等的,当赋值200给Integer时,不在cache 的范畴内,所以会new Integer并返回,当然==
比拟的后果是不相等的。
String 为什么不可变?
先看看什么是不可变的对象。
如果一个对象,在它创立实现之后,不能再扭转它的状态,那么这个对象就是不可变的。不能扭转状态的意思是,不能扭转对象内的成员变量,包含根本数据类型的值不能扭转,援用类型的变量不能指向其余的对象,援用类型指向的对象的状态也不能扭转。
接着来看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对象其实在外部就是一个个字符,存储在这个value数组外面的。
value数组用final润饰,final 润饰的变量,值不能被批改。因而value不能够指向其余对象。
String类外部所有的字段都是公有的,也就是被private润饰。而且String没有对外提供批改外部状态的办法,因而value数组不能扭转。
所以,String是不可变的。
那为什么String要设计成不可变的?
次要有以下几点起因:
- 线程平安。同一个字符串实例能够被多个线程共享,因为字符串不可变,自身就是线程平安的。
- 反对hash映射和缓存。因为String的hash值常常会应用到,比方作为 Map 的键,不可变的个性使得 hash 值也不会变,不须要从新计算。
- 出于平安思考。网络地址URL、文件门路path、明码通常状况下都是以String类型保留,假若String不是固定不变的,将会引起各种安全隐患。比方将明码用String的类型保留,那么它将始终留在内存中,直到垃圾收集器把它革除。如果String类不是固定不变的,那么这个明码可能会被扭转,导致呈现安全隐患。
- 字符串常量池优化。String对象创立之后,会缓存到字符串常量池中,下次须要创立同样的对象时,能够间接返回缓存的援用。
既然咱们的String是不可变的,它外部还有很多substring, replace, replaceAll这些操作的办法。这些办法如同会扭转String对象?怎么解释呢?
其实不是的,咱们每次调用replace等办法,其实会在堆内存中创立了一个新的对象。而后其value数组援用指向不同的对象。
为何JDK9要将String的底层实现由char[]改成byte[]?
次要是为了节约String占用的内存。
在大部分Java程序的堆内存中,String占用的空间最大,并且绝大多数String只有Latin-1字符,这些Latin-1字符只须要1个字节就够了。
而在JDK9之前,JVM因为String应用char数组存储,每个char占2个字节,所以即便字符串只须要1字节,它也要依照2字节进行调配,节约了一半的内存空间。
到了JDK9之后,对于每个字符串,会先判断它是不是只有Latin-1字符,如果是,就依照1字节的规格进行分配内存,如果不是,就依照2字节的规格进行调配,这样便进步了内存使用率,同时GC次数也会缩小,晋升效率。
不过Latin-1编码集反对的字符无限,比方不反对中文字符,因而对于中文字符串,用的是UTF16编码(两个字节),所以用byte[]和char[]实现没什么区别。
String, StringBuffer 和 StringBuilder区别
1. 可变性
- String 不可变
- StringBuffer 和 StringBuilder 可变
2. 线程平安
- String 不可变,因而是线程平安的
- StringBuilder 不是线程平安的
- StringBuffer 是线程平安的,外部应用 synchronized 进行同步
什么是StringJoiner?
StringJoiner是 Java 8 新增的一个 API,它基于 StringBuilder 实现,用于实现对字符串之间通过分隔符拼接的场景。
StringJoiner 有两个构造方法,第一个结构要求顺次传入分隔符、前缀和后缀。第二个结构则只要求传入分隔符即可(前缀和后缀默认为空字符串)。
StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
StringJoiner(CharSequence delimiter)
有些字符串拼接场景,应用 StringBuffer 或 StringBuilder 则显得比拟繁琐。
比方上面的例子:
List<Integer> values = Arrays.asList(1, 3, 5);
StringBuilder sb = new StringBuilder("(");
for (int i = 0; i < values.size(); i++) {
sb.append(values.get(i));
if (i != values.size() -1) {
sb.append(",");
}
}
sb.append(")");
而通过StringJoiner来实现拼接List的各个元素,代码看起来更加简洁。
List<Integer> values = Arrays.asList(1, 3, 5);
StringJoiner sj = new StringJoiner(",", "(", ")");
for (Integer value : values) {
sj.add(value.toString());
}
另外,像平时常常应用的Collectors.joining(“,”),底层就是通过StringJoiner实现的。
源码如下:
public static Collector<CharSequence, ?, String> joining(
CharSequence delimiter,CharSequence prefix,CharSequence suffix) {
return new CollectorImpl<>(
() -> new StringJoiner(delimiter, prefix, suffix),
StringJoiner::add, StringJoiner::merge,
StringJoiner::toString, CH_NOID);
}
String 类的罕用办法有哪些?
- indexOf():返回指定字符的索引。
- charAt():返回指定索引处的字符。
- replace():字符串替换。
- trim():去除字符串两端空白。
- split():宰割字符串,返回一个宰割后的字符串数组。
- getBytes():返回字符串的 byte 类型数组。
- length():返回字符串长度。
- toLowerCase():将字符串转成小写字母。
- toUpperCase():将字符串转成大写字符。
- substring():截取字符串。
- equals():字符串比拟。
new String(“dabin”)会创立几个对象?
应用这种形式会创立两个字符串对象(前提是字符串常量池中没有 “dabin” 这个字符串对象)。
- “dabin” 属于字符串字面量,因而编译期间会在字符串常量池中创立一个字符串对象,指向这个 “dabin” 字符串字面量;
- 应用 new 的形式会在堆中创立一个字符串对象。
什么是字符串常量池?
字符串常量池(String Pool)保留着所有字符串字面量,这些字面量在编译期间就确定。字符串常量池位于堆内存中,专门用来存储字符串常量。在创立字符串时,JVM首先会查看字符串常量池,如果该字符串曾经存在池中,则返回其援用,如果不存在,则创立此字符串并放入池中,并返回其援用。
String最大长度是多少?
String类提供了一个length办法,返回值为int类型,而int的取值下限为2^31 -1。
所以实践上String的最大长度为2^31 -1。
达到这个长度的话须要多大的内存吗?
String外部是应用一个char数组来保护字符序列的,一个char占用两个字节。如果说String最大长度是2^31 -1的话,那么最大的字符串占用内存空间约等于4GB。
也就是说,咱们须要有大于4GB的JVM运行内存才行。
那String个别都存储在JVM的哪块区域呢?
字符串在JVM中的存储分两种状况,一种是String对象,存储在JVM的堆栈中。一种是字符串常量,存储在常量池外面。
什么状况下字符串会存储在常量池呢?
当通过字面量进行字符串申明时,比方String s = “程序新大彬”;,这个字符串在编译之后会以常量的模式进入到常量池。
那常量池中的字符串最大长度是2^31-1吗?
不是的,常量池对String的长度是有另外限度的。。Java中的UTF-8编码的Unicode字符串在常量池中以CONSTANT_Utf8类型示意。
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
length在这里就是代表字符串的长度,length的类型是u2,u2是无符号的16位整数,也就是说最大长度能够做到2^16-1 即 65535。
不过javac编译器做了限度,须要length < 65535。所以字符串常量在常量池中的最大长度是65535 – 1 = 65534。
最初总结一下:
String在不同的状态下,具备不同的长度限度。
- 字符串常量长度不能超过65534
- 堆内字符串的长度不超过2^31-1
Object罕用办法有哪些?
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;
所以实体类应用克隆的前提是:
- 实现Cloneable接口,这是一个标记接口,本身没有办法,这应该是一种约定。调用clone办法时,会判断有没有实现Cloneable接口,没有实现Cloneable的话会抛异样CloneNotSupportedException。
- 笼罩clone()办法,可见性晋升为public。
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()。
notify
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
//程序员大彬
}
两个对象的hashCode()雷同,则 equals()是否也肯定为 true?
equals与hashcode的关系:
- 如果两个对象调用equals比拟返回true,那么它们的hashCode值肯定要雷同;
- 如果两个对象的hashCode雷同,它们并不一定雷同。
hashcode办法次要是用来晋升对象比拟的效率,先进行hashcode()的比拟,如果不雷同,那就不用在进行equals的比拟,这样就大大减少了equals比拟的次数,当比拟对象的数量很大的时候能晋升效率。
为什么重写 equals 时肯定要重写 hashCode?
之所以重写equals()
要重写hashcode()
,是为了保障equals()
办法返回true的状况下hashcode值也要统一,如果重写了equals()
没有重写hashcode()
,就会呈现两个对象相等但hashcode()
不相等的状况。这样,当用其中的一个对象作为键保留到hashMap、hashTable或hashSet中,再以另一个对象作为键值去查找他们的时候,则会查找不到。
Java创建对象有几种形式?
Java创建对象有以下几种形式:
- 用new语句创建对象。
- 应用反射,应用Class.newInstance()创建对象。
- 调用对象的clone()办法。
- 使用反序列化伎俩,调用java.io.ObjectInputStream对象的readObject()办法。
说说类实例化的程序
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();
}
/**
* 动态属性初始化
* 动态代码块初始化
* 一般属性初始化
* 一般代码块初始化
* 构造方法初始化
*/
}
equals和==有什么区别?
- 对于根本数据类型,==比拟的是他们的值。根本数据类型没有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关键字显式指出。
final, finally, finalize 的区别
- final 用于润饰属性、办法和类, 别离示意属性不能被从新赋值,办法不可被笼罩,类不可被继承。
- finally 是异样解决语句构造的一部分,个别以
try-catch-finally
呈现,finally
代码块示意总是被执行。 - finalize 是Object类的一个办法,该办法个别由垃圾回收器来调用,当咱们调用
System.gc()
办法的时候,由垃圾回收器调用finalize()
办法,回收垃圾,JVM并不保障此办法总被调用。
final关键字的作用?
- final 润饰的类不能被继承。
- final 润饰的办法不能被重写。
- final 润饰的变量叫常量,常量必须初始化,初始化之后值就不能被批改。
办法重载和重写的区别?
同个类中的多个办法能够有雷同的办法名称,然而有不同的参数列表,这就称为办法重载。参数列表又叫参数签名,包含参数的类型、参数的个数、参数的程序,只有有一个不同就叫做参数列表不同。
重载是面向对象的一个根本个性。
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、语法层面上的区别
- 抽象类能够有办法实现,而接口的办法中只能是形象办法(Java 8 之后接口办法能够有默认实现);
- 抽象类中的成员变量能够是各种类型的,接口中的成员变量只能是public static final类型;
- 接口中不能含有动态代码块以及静态方法,而抽象类能够有动态代码块和静态方法(Java 8之后接口能够有静态方法);
- 一个类只能继承一个抽象类,而一个类却能够实现多个接口。
2、设计层面上的区别
- 抽象层次不同。抽象类是对整个类整体进行形象,包含属性、行为,然而接口只是对类行为进行形象。继承抽象类是一种”是不是”的关系,而接口实现则是 “有没有”的关系。如果一个类继承了某个抽象类,则子类必然是抽象类的品种,而接口实现则是具备不具备的关系,比方鸟是否能飞。
- 继承抽象类的是具备类似特点的类,而实现接口的却能够不同的类。
门和警报的例子:
class AlarmDoor extends Door implements Alarm {
//code
}
class BMWCar extends Car implements Alarm {
//code
}
常见的Exception有哪些?
常见的RuntimeException:
ClassCastException
//类型转换异样IndexOutOfBoundsException
//数组越界异样NullPointerException
//空指针ArrayStoreException
//数组存储异样NumberFormatException
//数字格式化异样ArithmeticException
//数学运算异样
checked Exception:
NoSuchFieldException
//反射异样,没有对应的字段ClassNotFoundException
//类没有找到异样IllegalAccessException
//平安权限异样,可能是反射时调用了private办法
Error和Exception的区别?
Error:JVM 无奈解决的重大问题,如栈溢出StackOverflowError
、内存溢出OOM
等。程序无奈解决的谬误。
Exception:其它因编程谬误或偶尔的外在因素导致的一般性问题。能够在代码中进行解决。如:空指针异样、数组下标越界等。
运行时异样和非运行时异样(checked)的区别?
unchecked exception
包含RuntimeException
和Error
类,其余所有异样称为查看(checked)异样。
RuntimeException
由程序谬误导致,应该修改程序防止这类异样产生。checked Exception
由具体的环境(读取的文件不存在或文件为空或sql异样)导致的异样。必须进行解决,不然编译不通过,能够catch或者throws。
throw和throws的区别?
- throw:用于抛出一个具体的异样对象。
- throws:用在办法签名中,用于申明该办法可能抛出的异样。子类办法抛出的异样范畴更加小,或者基本不抛异样。
通过故事讲清楚NIO
上面通过一个例子来解说下。
假如某银行只有10个职员。该银行的业务流程分为以下4个步骤:
1) 顾客填申请表(5分钟);
2) 职员审核(1分钟);
3) 职员叫保安去金库取钱(3分钟);
4) 职员打印票据,并将钱和票据返回给顾客(1分钟)。
上面咱们看看银行不同的工作形式对其工作效率到底有何影响。
首先是BIO形式。
每来一个顾客,马上由一位职员来接待解决,并且这个职员须要负责以上4个残缺流程。当超过10个顾客时,残余的顾客须要排队等待。
一个职员解决一个顾客须要10分钟(5+1+3+1)工夫。一个小时(60分钟)能解决6个顾客,一共10个职员,那就是只能解决60个顾客。
能够看到银行职员的工作状态并不饱和,比方在第1步,其实是处于期待中。
这种工作其实就是BIO,每次来一个申请(顾客),就调配到线程池中由一个线程(职员)解决,如果超出了线程池的最大下限(10个),就扔到队列期待 。
那么如何进步银行的吞吐量呢?
思路就是:分而治之,将工作拆分开来,由专门的人负责专门的工作。
具体来讲,银行专门指派一名职员A,A的工作就是每当有顾客到银行,他就递上表格让顾客填写。每当有顾客填好表后,A就将其随机指派给残余的9名职员实现后续步骤。
这种形式下,假如顾客十分多,职员A的工作处于饱和中,他一直的将填好表的顾客带到柜台解决。
柜台一个职员5分钟能解决完一个顾客,一个小时9名职员能解决:9*(60/5)=108。
可见工作形式的转变能带来效率的极大晋升。
这种工作形式其实就NIO的思路。
下图是十分经典的NIO阐明图,mainReactor
线程负责监听server socket,接管新连贯,并将建设的socket分派给subReactor
subReactor
能够是一个线程,也能够是线程池,负责多路拆散已连贯的socket,读写网络数据。这里的读写网络数据可类比顾客填表这一耗时动作,对具体的业务解决性能,其扔给worker线程池实现
能够看到典型NIO有三类线程,别离是mainReactor
线程、subReactor
线程、work
线程。
不同的线程干业余的事件,最终每个线程都没空着,零碎的吞吐量天然就下来了。
那这个流程还有没有什么能够进步的中央呢?
能够看到,在这个业务流程里边第3个步骤,职员叫保安去金库取钱(3分钟)。这3分钟柜台职员是在期待中度过的,能够把这3分钟利用起来。
还是分而治之的思路,指派1个职员B来专门负责第3步骤。
每当柜台员工实现第2步时,就告诉职员B来负责与保安沟通取钱。这时候柜台员工能够持续解决下一个顾客。
当职员B拿到钱之后,告诉顾客钱曾经到柜台了,让顾客从新排队解决,当柜台职员再次服务该顾客时,发现该顾客前3步曾经实现,间接执行第4步即可。
在当今web服务中,常常须要通过RPC或者Http等形式调用第三方服务,这里对应的就是第3步,如果这步耗时较长,通过异步形式将能极大升高资源使用率。
NIO+异步的形式能让大量的线程做大量的事件。这实用于很多利用场景,比方代理服务、api服务、长连贯服务等等。这些利用如果用同步形式将消耗大量机器资源。
不过尽管NIO+异步能进步零碎吞吐量,但其并不能让一个申请的等待时间降落,相同可能会减少等待时间。
最初,NIO根本思维总结起来就是:分而治之,将工作拆分开来,由专门的人负责专门的工作
BIO/NIO/AIO区别的区别?
同步阻塞IO : 用户过程发动一个IO操作当前,必须期待IO操作的真正实现后,能力持续运行。
同步非阻塞IO: 客户端与服务器通过Channel连贯,采纳多路复用器轮询注册的Channel
。进步吞吐量和可靠性。用户过程发动一个IO操作当前,可做其它事件,但用户过程须要轮询IO操作是否实现,这样造成不必要的CPU资源节约。
异步非阻塞IO: 非阻塞异步通信模式,NIO的升级版,采纳异步通道实现异步通信,其read和write办法均是异步办法。用户过程发动一个IO操作,而后立刻返回,等IO操作真正的实现当前,应用程序会失去IO操作实现的告诉。相似Future模式。
守护线程是什么?
- 守护线程是运行在后盾的一种非凡过程。
- 它独立于管制终端并且周期性地执行某种工作或期待解决某些产生的事件。
- 在 Java 中垃圾回收线程就是非凡的守护线程。
Java反对多继承吗?
java中,类不反对多继承。接口才反对多继承。接口的作用是拓展对象性能。当一个子接口继承了多个父接口时,阐明子接口拓展了多个性能。当一个类实现该接口时,就拓展了多个的性能。
Java不反对多继承的起因:
- 出于安全性的思考,如果子类继承的多个父类外面有雷同的办法或者属性,子类将不晓得具体要继承哪个。
- Java提供了接口和外部类以达到实现多继承性能,补救单继承的缺点。
如何实现对象克隆?
- 实现
Cloneable
接口,重写clone()
办法。这种形式是浅拷贝,即如果类中属性有自定义援用类型,只拷贝援用,不拷贝援用指向的对象。如果对象的属性的Class也实现Cloneable
接口,那么在克隆对象时也会克隆属性,即深拷贝。 - 联合序列化,深拷贝。
- 通过
org.apache.commons
中的工具类BeanUtils
和PropertyUtils
进行对象复制。
同步和异步的区别?
同步:收回一个调用时,在没有失去后果之前,该调用就不返回。
异步:在调用收回后,被调用者返回后果之后会告诉调用者,或通过回调函数解决这个调用。
阻塞和非阻塞的区别?
阻塞和非阻塞关注的是线程的状态。
阻塞调用是指调用后果返回之前,以后线程会被挂起。调用线程只有在失去后果之后才会复原运行。
非阻塞调用指在不能立即失去后果之前,该调用不会阻塞以后线程。
举个例子,了解下同步、阻塞、异步、非阻塞的区别:
同步就是烧开水,要本人来看开没开;异步就是水开了,而后水壶响了告诉你水开了(回调告诉)。阻塞是烧开水的过程中,你不能干其余事件,必须在旁边等着;非阻塞是烧开水的过程里能够干其余事件。
Java8的新个性有哪些?
- Lambda 表达式:Lambda容许把函数作为一个办法的参数
- Stream API :新增加的Stream API(java.util.stream) 把真正的函数式编程格调引入到Java中
- 默认办法:默认办法就是一个在接口外面有了一个实现的办法。
- Optional 类 :Optional 类曾经成为 Java 8 类库的一部分,用来解决空指针异样。
- Date Time API :增强对日期与工夫的解决。
Java8 新个性总结
序列化和反序列化
- 序列化:把对象转换为字节序列的过程称为对象的序列化.
- 反序列化:把字节序列复原为对象的过程称为对象的反序列化.
什么时候须要用到序列化和反序列化呢?
当咱们只在本地 JVM 里运行下 Java 实例,这个时候是不须要什么序列化和反序列化的,但当咱们须要将内存中的对象长久化到磁盘,数据库中时,当咱们须要与浏览器进行交互时,当咱们须要实现 RPC 时,这个时候就须要序列化和反序列化了.
前两个须要用到序列化和反序列化的场景,是不是让咱们有一个很大的疑难? 咱们在与浏览器交互时,还有将内存中的对象长久化到数据库中时,如同都没有去进行序列化和反序列化,因为咱们都没有实现 Serializable 接口,但始终失常运行.
上面先给出论断:
只有咱们对内存中的对象进行长久化或网络传输,这个时候都须要序列化和反序列化.
理由:
服务器与浏览器交互时真的没有用到 Serializable 接口吗? JSON 格局实际上就是将一个对象转化为字符串,所以服务器与浏览器交互时的数据格式其实是字符串,咱们来看来 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
/\*\* use serialVersionUID from JDK 1.0.2 for interoperability \*/
private static final long serialVersionUID = -6849794470754667710L;
......
}
String 类型实现了 Serializable 接口,并显示指定 serialVersionUID 的值.
而后咱们再来看对象长久化到数据库中时的状况,Mybatis 数据库映射文件里的 insert 代码:
<insert id="insertUser" parameterType="org.tyshawn.bean.User">
INSERT INTO t\_user(name,age) VALUES (#{name},#{age})
</insert>
实际上咱们并不是将整个对象长久化到数据库中,而是将对象中的属性长久化到数据库中,而这些属性(如Date/String)都实现了 Serializable 接口。
实现序列化和反序列化为什么要实现 Serializable 接口?
在 Java 中实现了 Serializable 接口后, JVM 在类加载的时候就会发现咱们实现了这个接口,而后在初始化实例对象的时候就会在底层帮咱们实现序列化和反序列化。
如果被写对象类型不是String、数组、Enum,并且没有实现Serializable接口,那么在进行序列化的时候,将抛出NotSerializableException。源码如下:
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
实现 Serializable 接口之后,为什么还要显示指定 serialVersionUID 的值?
如果不显示指定 serialVersionUID,JVM 在序列化时会依据属性主动生成一个 serialVersionUID,而后与属性一起序列化,再进行长久化或网络传输. 在反序列化时,JVM 会再依据属性主动生成一个新版 serialVersionUID,而后将这个新版 serialVersionUID 与序列化时生成的旧版 serialVersionUID 进行比拟,如果雷同则反序列化胜利,否则报错.
如果显示指定了 serialVersionUID,JVM 在序列化和反序列化时依然都会生成一个 serialVersionUID,但值为咱们显示指定的值,这样在反序列化时新旧版本的 serialVersionUID 就统一了.
如果咱们的类写完后不再批改,那么不指定serialVersionUID,不会有问题,但这在理论开发中是不可能的,咱们的类会一直迭代,一旦类被批改了,那旧对象反序列化就会报错。 所以在理论开发中,咱们都会显示指定一个 serialVersionUID。
static 属性为什么不会被序列化?
因为序列化是针对对象而言的,而 static 属性优先于对象存在,随着类的加载而加载,所以不会被序列化.
看到这个论断,是不是有人会问,serialVersionUID 也被 static 润饰,为什么 serialVersionUID 会被序列化? 其实 serialVersionUID 属性并没有被序列化,JVM 在序列化对象时会主动生成一个 serialVersionUID,而后将咱们显示指定的 serialVersionUID 属性值赋给主动生成的 serialVersionUID.
transient关键字的作用?
Java语言的关键字,变量修饰符,如果用transient申明一个实例变量,当对象存储时,它的值不须要维持。
也就是说被transient润饰的成员变量,在序列化的时候其值会被疏忽,在被反序列化后, transient 变量的值被设为初始值, 如 int 型的是 0,对象型的是 null。
什么是反射?
动静获取的信息以及动静调用对象的办法的性能称为Java语言的反射机制。
在运行状态中,对于任意一个类,可能晓得这个类的所有属性和办法。对于任意一个对象,可能调用它的任意一个办法和属性。
反射有哪些利用场景呢?
- JDBC连贯数据库时应用
Class.forName()
通过反射加载数据库的驱动程序 - Eclispe、IDEA等开发工具利用反射动静解析对象的类型与构造,动静提醒对象的属性和办法
- Web服务器中利用反射调用了Sevlet的
service
办法 - JDK动静代理底层依赖反射实现
讲讲什么是泛型?
Java泛型是JDK 5中引⼊的⼀个新个性, 容许在定义类和接口的时候使⽤类型参数。申明的类型参数在使⽤时⽤具体的类型来替换。
泛型最⼤的益处是能够提⾼代码的复⽤性。以List接口为例,咱们能够将String、 Integer等类型放⼊List中, 如不⽤泛型, 寄存String类型要写⼀个List接口, 寄存Integer要写另外⼀个List接口, 泛型能够很好的解决这个问题。
如何进行一个正在运行的线程?
有几种形式。
1、应用线程的stop办法。
应用stop()办法能够强制终止线程。不过stop是一个被废除掉的办法,不举荐应用。
应用Stop办法,会始终向上流传ThreadDeath异样,从而使得指标线程解锁所有锁住的监视器,即开释掉所有的对象锁。使得之前被锁住的对象得不到同步的解决,因而可能会造成数据不统一的问题。
2、应用interrupt办法中断线程,该办法只是通知线程要终止,但最终何时终止取决于计算机。调用interrupt办法仅仅是在以后线程中打了一个进行的标记,并不是真的进行线程。
接着调用 Thread.currentThread().isInterrupted()办法,能够用来判断以后线程是否被终止,通过这个判断咱们能够做一些业务逻辑解决,通常如果isInterrupted返回true的话,会抛一个中断异样,而后通过try-catch捕捉。
3、设置标记位
设置标记位,当标识位为某个值时,使线程失常退出。设置标记位是用到了共享变量的形式,为了保障共享变量在内存中的可见性,能够应用volatile润饰它,这样的话,变量取值始终会从主存中获取最新值。
然而这种volatile标记共享变量的形式,在线程产生阻塞时是无奈实现响应的。比方调用Thread.sleep() 办法之后,线程处于不可运行状态,即使是主线程批改了共享变量的值,该线程此时根本无法查看循环标记,所以也就无奈实现线程中断。
因而,interrupt() 加上手动抛异样的形式是目前中断一个正在运行的线程最为正确的形式了。
什么是跨域?
简略来讲,跨域是指从一个域名的网页去申请另一个域名的资源。因为有同源策略的关系,个别是不容许这么间接拜访的。然而,很多场景常常会有跨域拜访的需要,比方,在前后端拆散的模式下,前后端的域名是不统一的,此时就会产生跨域问题。
那什么是同源策略呢?
所谓同源是指”协定+域名+端口”三者雷同,即使两个不同的域名指向同一个ip地址,也非同源。
同源策略限度以下几种行为:
1. Cookie、LocalStorage 和 IndexDB 无奈读取
2. DOM 和 Js对象无奈取得
3. AJAX 申请不能发送
为什么要有同源策略?
举个例子,如果你刚刚在网银输出账号密码,查看了本人的余额,而后再去拜访其余带色彩的网站,这个网站能够拜访刚刚的网银站点,并且获取账号密码,那结果可想而知。因而,从平安的角度来讲,同源策略是有利于爱护网站信息的。
跨域问题怎么解决呢?
嗯,有以下几种办法:
CORS,跨域资源共享
CORS(Cross-origin resource sharing),跨域资源共享。CORS 其实是浏览器制订的一个标准,浏览器会主动进行 CORS 通信,它的实现次要在服务端,通过一些 HTTP Header 来限度能够拜访的域,例如页面 A 须要拜访 B 服务器上的数据,如果 B 服务器 上申明了容许 A 的域名拜访,那么从 A 到 B 的跨域申请就能够实现。
@CrossOrigin注解
如果我的项目应用的是Springboot,能够在Controller类上增加一个 @CrossOrigin(origins =”*”) 注解就能够实现对以后controller 的跨域拜访了,当然这个标签也能够加到办法上,或者间接加到入口类上对所有接口进行跨域解决。留神SpringMVC的版本要在4.2或以上版本才反对@CrossOrigin。
nginx反向代理接口跨域
nginx反向代理跨域原理如下: 首先同源策略是浏览器的安全策略,不是HTTP协定的一部分。服务器端调用HTTP接口只是应用HTTP协定,不会执行JS脚本,不须要同源策略,也就不存在逾越问题。
nginx反向代理接口跨域实现思路如下:通过nginx配置一个代理服务器(域名与domain1雷同,端口不同)做跳板机,反向代理拜访domain2接口,并且能够顺便批改cookie中domain信息,不便以后域cookie写入,实现跨域登录。
// proxy服务器
server {
listen 81;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #批改cookie里域名
index index.html index.htm;
add_header Access-Control-Allow-Origin http://www.domain1.com;
}
}
这样咱们的前端代理只有拜访 http:www.domain1.com:81/*就能够了。
通过jsonp跨域
通常为了加重web服务器的负载,咱们把js、css,img等动态资源拆散到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载动态资源,这是浏览器容许的操作,基于此原理,咱们能够通过动态创建script,再申请一个带参网址实现跨域通信。
设计接口要留神什么?
- 接口参数校验。接口必须校验参数,比方入参是否容许为空,入参长度是否合乎预期。
- 设计接口时,充分考虑接口的可扩展性。思考接口是否能够复用,怎么放弃接口的可扩展性。
- 串行调用思考改并行调用。比方设计一个商城首页接口,须要查商品信息、营销信息、用户信息等等。如果是串行一个一个查,那耗时就比拟大了。这种场景是能够改为并行调用的,升高接口耗时。
- 接口是否须要防重解决。波及到数据库批改的,要思考防重解决,能够应用数据库防重表,以惟一流水号作为惟一索引。
- 日志打印全面,入参出参,接口耗时,记录好日志,不便甩锅。
- 批改旧接口时,留神兼容性设计。
- 异样解决切当。应用finally敞开流资源、应用log打印而不是e.printStackTrace()、不要吞异样等等
- 是否须要思考限流。限流为了爱护零碎,避免流量洪峰超过零碎的承载能力。
过滤器和拦截器有什么区别?
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、拦挡的申请范畴不同。
申请的执行程序是:申请进入容器 -> 进入过滤器 -> 进入 Servlet -> 进入拦截器 -> 执行控制器。能够看到过滤器和拦截器的执行机会也是不同的,过滤器会先执行,而后才会执行拦截器,最初才会进入真正的要调用的办法。
参考链接:https://segmentfault.com/a/1190000022833940
对接第三方接口要思考什么?
嗯,须要思考以下几点:
- 确认接口对接的网络协议,是https/http或者自定义的公有协定等。
- 约定好数据传参、响应格局(如application/json),弱类型对接强类型语言时要特地留神
- 接口平安方面,要确定身份校验形式,应用token、证书校验等
- 确认是否须要接口调用失败后的重试机制,保障数据传输的最终一致性。
- 日志记录要全面。接口出入参数,以及解析之后的参数值,都要用日志记录下来,不便定位问题(甩锅)。
参考:https://blog.csdn.net/gzt19881123/article/details/108791034
后端接口性能优化有哪些办法?
有以下这些办法:
1、优化索引。给where条件的关键字段,或者order by
前面的排序字段,加索引。
2、优化sql语句。比方防止应用select *、批量操作、防止深分页、晋升group by的效率等
3、防止大事务。应用@Transactional注解这种申明式事务的形式提供事务性能,容易造成大事务,引发其余的问题。应该防止在事务中一次性解决太多数据,将一些跟事务无关的逻辑放到事务里面执行。
4、异步解决。剥离主逻辑和副逻辑,副逻辑能够异步执行,异步写库。比方用户购买的商品发货了,须要发短信告诉,短信告诉是副流程,能够异步执行,免得影响主流程的执行。
5、升高锁粒度。在并发场景下,多个线程同时批改数据,造成数据不统一的状况。这种状况下,个别会加锁解决。但如果锁加得不好,导致锁的粒度太粗,也会十分影响接口性能。
6、加缓存。如果表数据量十分大的话,间接从数据库查问数据,性能会十分差。能够应用Redis和
memcached晋升查问性能,从而进步接口性能。
7、分库分表。当零碎倒退到肯定的阶段,用户并发量大,会有大量的数据库申请,须要占用大量的数据库连贯,同时会带来磁盘IO的性能瓶颈问题。或者数据库表数据十分大,SQL查问即便走了索引,也很耗时。这时,能够通过分库分表解决。分库用于解决数据库连贯资源有余问题,和磁盘IO的性能瓶颈问题。分表用于解决单表数据量太大,sql语句查问数据时,即便走了索引也十分耗时问题。
8、防止在循环中查询数据库。循环查询数据库,十分耗时,最好能在一次查问中获取所有须要的数据。
为什么在阿里巴巴Java开发手册中强制要求应用包装类型定义属性呢?
嗯,以布尔字段为例,当咱们没有设置对象的字段的值的时候,Boolean类型的变量会设置默认值为null
,而boolean类型的变量会设置默认值为false
。
也就是说,包装类型的默认值都是null,而根本数据类型的默认值是一个固定值,如boolean是false,byte、short、int、long是0,float是0.0f等。
举一个例子,比方有一个扣费零碎,扣费时须要从内部的定价零碎中读取一个费率的值,咱们预期该接口的返回值中会蕴含一个浮点型的费率字段。当咱们取到这个值得时候就应用公式:金额*费率=费用 进行计算,计算结果进行划扣。
如果因为计费零碎异样,他可能会返回个默认值,如果这个字段是Double类型的话,该默认值为null,如果该字段是double类型的话,该默认值为0.0。
如果扣费零碎对于该费率返回值没做非凡解决的话,拿到null值进行计算会间接报错,阻断程序。拿到0.0可能就间接进行计算,得出接口为0后进行扣费了。这种异常情况就无奈被感知。
那我能够对0.0做非凡判断,如果是0就阻断报错,这样是否能够呢?
不对,这时候就会产生一个问题,如果容许费率是0的场景又怎么解决呢?
应用根本数据类型只会让计划越来越简单,坑越来越多。
这种应用包装类型定义变量的形式,通过异样来阻断程序,进而能够被辨认到这种线上问题。如果应用根本数据类型的话,零碎可能不会报错,进而认为无异样。
因而,倡议在POJO和RPC的返回值中应用包装类型。
参考链接:https://mp.weixin.qq.com/s/O_jCxZWtTTkFZ9FlaZgOCg
8招让接口性能晋升100倍
池化思维
如果你每次须要用到线程,都去创立,就会有减少肯定的耗时,而线程池能够反复利用线程,防止不必要的耗时。
比方TCP
三次握手,它为了缩小性能损耗,引入了Keep-Alive长连贯
,防止频繁的创立和销毁连贯。
回绝阻塞期待
如果你调用一个零碎B
的接口,然而它解决业务逻辑,耗时须要10s
甚至更多。而后你是始终阻塞期待,直到零碎B的上游接口返回,再持续你的下一步操作吗?这样显然不合理。
参考IO多路复用模型。即咱们不必阻塞期待零碎B
的接口,而是先去做别的操作。等零碎B
的接口解决完,通过事件回调告诉,咱们接口收到告诉再进行对应的业务操作即可。
近程调用由串行改为并行
比方设计一个商城首页接口,须要查商品信息、营销信息、用户信息等等。如果是串行一个一个查,那耗时就比拟大了。这种场景是能够改为并行调用的,升高接口耗时。
锁粒度防止过粗
在高并发场景,为了避免超卖等状况,咱们常常须要加锁来爱护共享资源。然而,如果加锁的粒度过粗,是很影响接口性能的。
不论你是synchronized
加锁还是redis
分布式锁,只须要在共享临界资源加锁即可,不波及共享资源的,就不必要加锁。
耗时操作,思考放到异步执行
耗时操作,思考用异步解决,这样能够升高接口耗时。比方用户注册胜利后,短信邮件告诉,是能够异步解决的。
应用缓存
把要查的数据,提前放好到缓存外面,须要时,间接查缓存,而防止去查数据库或者计算的过程。
提前初始化到缓存
预取思维很容易了解,就是提前把要计算查问的数据,初始化到缓存。如果你在将来某个工夫须要用到某个通过简单计算的数据,才实时去计算的话,可能耗时比拟大。这时候,咱们能够采取预取思维,提前把未来可能须要的数据计算好,放到缓存中,等须要的时候,去缓存取就行。这将大幅度提高接口性能。
压缩传输内容
压缩传输内容,传输报文变得更小,因而传输会更快。
最初给大家分享一个Github仓库,下面有大彬整顿的300多本经典的计算机书籍PDF,包含C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,能够star一下,下次找书间接在下面搜寻,仓库继续更新中~
Github地址