江苏省建设厅的官方网站,吉林市网站制作哪家好,wxr wordpress,怎么做直播网站超管Java的特点
Java是一门面向对象的编程语言。面向对象和面向过程的区别参考下一个问题。
Java具有平台独立性和移植性。
Java有一句口号#xff1a;Write once, run anywhere#xff0c;一次编写、到处运行。这也是Java的魅力所在。而实现这种特性的正是Java虚拟机JVM。已编…Java的特点
Java是一门面向对象的编程语言。面向对象和面向过程的区别参考下一个问题。
Java具有平台独立性和移植性。
Java有一句口号Write once, run anywhere一次编写、到处运行。这也是Java的魅力所在。而实现这种特性的正是Java虚拟机JVM。已编译的Java程序可以在任何带有JVM的平台上运行。你可以在windows平台编写代码然后拿到linux上运行。只要你在编写完代码后将代码编译成.class文件再把class文件打成Java包这个jar包就可以在不同的平台上运行了。
Java具有稳健性。
Java是一个强类型语言它允许扩展编译时检查潜在类型不匹配问题的功能。Java要求显式的方法声明它不支持C风格的隐式声明。这些严格的要求保证编译程序能捕捉调用错误这就导致更可靠的程序。异常处理是Java中使得程序更稳健的另一个特征。异常是某种类似于错误的异常条件出现的信号。使用try/catch/finally语句程序员可以找到出错的处理代码这就简化了出错处理和恢复的任务。
Java是如何实现跨平台的
Java是通过JVMJava虚拟机实现跨平台的。
JVM可以理解成一个软件不同的平台有不同的版本。我们编写的Java代码编译后会生成.class 文件字节码文件。Java虚拟机就是负责将字节码文件翻译成特定平台下的机器码通过JVM翻译成机器码之后才能运行。不同平台下编译生成的字节码是一样的但是由JVM翻译成的机器码却不一样。
只要在不同平台上安装对应的JVM就可以运行字节码文件运行我们编写的Java程序。
因此运行Java程序必须有JVM的支持因为编译的结果不是机器码必须要经过JVM的翻译才能执行。
Java 与 C 的区别
Java 是纯粹的面向对象语言所有的对象都继承自 java.lang.ObjectC 兼容 C 不但支持面向对象也支持面向过程。Java 通过虚拟机从而实现跨平台特性 C 依赖于特定的平台。Java 没有指针它的引用可以理解为安全指针而 C 具有和 C 一样的指针。Java 支持自动垃圾回收而 C 需要手动回收。Java 不支持多重继承只能通过实现多个接口来达到相同目的而 C 支持多重继承。
JDK/JRE/JVM三者的关系
JVM
英文名称Java Virtual Machine就是我们耳熟能详的 Java 虚拟机。Java 能够跨平台运行的核心在于 JVM 。 本文已经收录到Github仓库该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点欢迎star~ Github地址https://github.com/Tyson0314/Java-learning 所有的java程序会首先被编译为.class的类文件这种类文件可以在虚拟机上执行。也就是说class文件并不直接与机器的操作系统交互而是经过虚拟机间接与操作系统交互由虚拟机将程序解释给本地系统执行。
针对不同的系统有不同的 jvm 实现有 Linux 版本的 jvm 实现也有Windows 版本的 jvm 实现但是同一段代码在编译后的字节码是一样的。这就是Java能够跨平台实现一次编写多处运行的原因所在。
JRE
英文名称Java Runtime Environment就是Java 运行时环境。我们编写的Java程序必须要在JRE才能运行。它主要包含两个部分JVM 和 Java 核心类库。 JRE是Java的运行环境并不是一个开发环境所以没有包含任何开发工具如编译器和调试器等。
如果你只是想运行Java程序而不是开发Java程序的话那么你只需要安装JRE即可。
JDK
英文名称Java Development Kit就是 Java 开发工具包
学过Java的同学都应该安装过JDK。当我们安装完JDK之后目录结构是这样的 可以看到JDK目录下有个JRE也就是JDK中已经集成了 JRE不用单独安装JRE。
另外JDK中还有一些好用的工具如jinfojpsjstack等。 最后总结一下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将字节码转换成机器码执行即解释运行 和编译运行配合使用所以可以称为混合型或者半编译型。 最全面的Java面试网站 面向对象和面向过程的区别
面向对象和面向过程是一种软件开发思想。 面向过程就是分析出解决问题所需要的步骤然后用函数按这些步骤实现使用的时候依次调用就可以了。 面向对象是把构成问题事务分解成各个对象分别设计这些对象然后将他们组装成有完整功能的系统。面向过程只用函数实现面向对象是用类实现各个功能模块。
以五子棋为例面向过程的设计思路就是首先分析问题的步骤
1、开始游戏2、黑子先走3、绘制画面4、判断输赢5、轮到白子6、绘制画面7、判断输赢8、返回步骤29、输出最后结果。 把上面每个步骤用分别的函数来实现问题就解决了。
而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为
黑白双方棋盘系统负责绘制画面规则系统负责判定诸如犯规、输赢等。
黑白双方负责接受用户的输入并告知棋盘系统棋子布局发生变化棋盘系统接收到了棋子的变化的信息就负责在屏幕上面显示出这种变化同时利用规则系统来对棋局进行判定。
面向对象有哪些特性
面向对象四大特性封装继承多态抽象
1、封装就是将类的信息隐藏在类内部不允许外部程序直接访问而是通过该类的方法实现对隐藏信息的操作和访问。 良好的封装能够减少耦合。
2、继承是从已有的类中派生出新的类新的类继承父类的属性和行为并能扩展新的能力大大增加程序的重用性和易维护性。在Java中是单继承的也就是说一个子类只有一个父类。
3、多态是同一个行为具有多个不同表现形式的能力。在不修改程序代码的情况下改变程序运行时绑定的代码。实现多态的三要素继承、重写、父类引用指向子类对象。
静态多态性通过重载实现相同的方法有不同的參数列表可以根据参数的不同做出不同的处理。动态多态性在子类中重写父类的方法。运行期间判断所引用对象的实际类型根据其实际类型调用相应的方法。
4、抽象。把客观事物用代码抽象出来。
面向对象编程的六大原则?
对象单一职责我们设计创建的对象必须职责明确比如商品类里面相关的属性和方法都必须跟商品相关不能出现订单等不相关的内容。这里的类可以是模块、类库、程序集而不单单指类。里式替换原则子类能够完全替代父类反之则不行。通常用于实现接口时运用。因为子类能够完全替代基父类那么这样父类就拥有很多子类在后续的程序扩展中就很容易进行扩展程序完全不需要进行修改即可进行扩展。比如IA的实现为A因为项目需求变更现在需要新的实现直接在容器注入处更换接口即可.迪米特法则也叫最小原则或者说最小耦合。通常在设计程序或开发程序的时候尽量要高内聚低耦合。当两个类进行交互的时候会产生依赖。而迪米特法则就是建议这种依赖越少越好。就像构造函数注入父类对象时一样当需要依赖某个对象时并不在意其内部是怎么实现的而是在容器中注入相应的实现既符合里式替换原则又起到了解耦的作用。开闭原则开放扩展封闭修改。当项目需求发生变更时要尽可能的不去对原有的代码进行修改而在原有的基础上进行扩展。依赖倒置原则高层模块不应该直接依赖于底层模块的具体实现而应该依赖于底层的抽象。接口和抽象类不应该依赖于实现类而实现类依赖接口或抽象类。接口隔离原则一个对象和另外一个对象交互的过程中依赖的内容最小。也就是说在接口设计的时候在遵循对象单一职责的情况下尽量减少接口的内容。
简洁版
单一职责对象设计要求独立不能设计万能对象。开闭原则对象修改最小化。里式替换程序扩展中抽象被具体可以替换接口、父类、可以被实现类对象、子类替换对象迪米特高内聚低耦合。尽量不要依赖细节。依赖倒置面向抽象编程。也就是参数传递或者返回值可以使用父类类型或者接口类型。从广义上讲基于接口编程提前设计好接口框架。接口隔离接口设计大小要适中。过大导致污染过小导致调用麻烦。
数组到底是不是对象
先说说对象的概念。对象是根据某个类创建出来的一个实例表示某类事物中一个具体的个体。
对象具有各种属性并且具有一些特定的行为。站在计算机的角度对象就是内存中的一个内存块在这个内存块封装了一些数据也就是类中定义的各个属性。
所以对象是用来封装数据的。
java中的数组具有java中其他对象的一些基本特点。比如封装了一些数据可以访问属性也可以调用方法。
因此可以说数组是对象。
也可以通过代码验证数组是对象的事实。比如以下的代码输出结果为java.lang.Object。
Class clz int[].class;
System.out.println(clz.getSuperclass().getName());由此可以看出数组类的父类就是Object类那么可以推断出数组就是对象。
Java的基本数据类型有哪些
byte8bitchar16bitshort16bitint32bitfloat32bitlong64bitdouble64bitboolean只有两个值true、false可以使⽤用 1 bit 来存储
简单类型booleanbytecharshortIntlongfloatdouble二进制位数18161632643264包装类BooleanByteCharacterShortIntegerLongFloatDouble
在Java规范中没有明确指出boolean的大小。在《Java虚拟机规范》给出了单个boolean占4个字节和boolean数组1个字节的定义具体 还要看虚拟机实现是否按照规范来因此boolean占用1个字节或者4个字节都是有可能的。
为什么不能用浮点型表示金额
由于计算机中保存的小数其实是十进制的小数的近似值并不是准确值所以千万不要在代码中使用浮点数来表示金额等重要的指标。
建议使用BigDecimal或者Long来表示金额。
什么是值传递和引用传递
值传递是对基本型变量而言的传递的是该变量的一个副本改变副本不影响原变量。引用传递一般是对于对象型变量而言的传递的是该对象地址的一个副本并不是原对象本身两者指向同一片内存空间。所以对引用对象进行操作会同时改变原对象。
java中不存在引用传递只有值传递。即不存在变量a指向变量b变量b指向对象的这种情况。
了解Java的包装类型吗为什么需要包装类
Java 是一种面向对象语言很多地方都需要使用对象而不是基本数据类型。比如在集合类中我们是无法将 int 、double 等类型放进去的。因为集合的容器要求元素是 Object 类型。
为了让基本类型也具有对象的特征就出现了包装类型。相当于将基本类型包装起来使得它具有了对象的性质并且为其添加了属性和方法丰富了基本类型的操作。 给大家分享一个Github仓库上面有大彬整理的300多本经典的计算机书籍PDF包括C语言、C、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等可以star一下下次找书直接在上面搜索仓库持续更新中~ Github地址https://github.com/Tyson0314/java-books 自动装箱和拆箱
Java中基础数据类型与它们对应的包装类见下表
原始类型包装类型booleanBooleanbyteBytecharCharacterfloatFloatintIntegerlongLongshortShortdoubleDouble
装箱将基础类型转化为包装类型。
拆箱将包装类型转化为基础类型。
当基础类型与它们的包装类有如下几种情况时编译器会自动帮我们进行装箱或拆箱
赋值操作装箱或拆箱进行加减乘除混合运算 拆箱进行,,比较运算拆箱调用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 propertyint 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_VALUEh 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 Stringimplements java.io.Serializable, ComparableString, 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 进行同步 最全面的Java面试网站 什么是StringJoiner
StringJoiner是 Java 8 新增的一个 API它基于 StringBuilder 实现用于实现对字符串之间通过分隔符拼接的场景。
StringJoiner 有两个构造方法第一个构造要求依次传入分隔符、前缀和后缀。第二个构造则只要求传入分隔符即可前缀和后缀默认为空字符串。
StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
StringJoiner(CharSequence delimiter)有些字符串拼接场景使用 StringBuffer 或 StringBuilder 则显得比较繁琐。
比如下面的例子
ListInteger 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的各个元素代码看起来更加简洁。
ListInteger values Arrays.asList(1, 3, 5);
StringJoiner sj new StringJoiner(,, (, ));for (Integer value : values) {sj.add(value.toString());
}另外像平时经常使用的Collectors.joining(“,”)底层就是通过StringJoiner实现的。
源码如下
public static CollectorCharSequence, ?, 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的类型是u2u2是无符号的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.Person4554617c
}可以重写toString方法按照重写逻辑输出对象值。
public class Person {private int age;private String name;public Person(int age, String name) {this.age age;this.name name;}Overridepublic 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;}Overridepublic 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;Overrideprotected 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;Overrideprotected 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;Overrideprotected 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 {Overridepublic 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 {Overridepublic 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的区别
ErrorJVM 无法解决的严重问题如栈溢出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分钟5131时间。一个小时60分钟能处理6个顾客一共10个职员那就是只能处理60个顾客。
可以看到银行职员的工作状态并不饱和比如在第1步其实是处于等待中。
这种工作其实就是BIO每次来一个请求顾客就分配到线程池中由一个线程职员处理如果超出了线程池的最大上限10个就扔到队列等待 。
那么如何提高银行的吞吐量呢
思路就是分而治之将任务拆分开来由专门的人负责专门的任务。
具体来讲银行专门指派一名职员AA的工作就是每当有顾客到银行他就递上表格让顾客填写。每当有顾客填好表后A就将其随机指派给剩余的9名职员完成后续步骤。
这种方式下假设顾客非常多职员A的工作处于饱和中他不断的将填好表的顾客带到柜台处理。
柜台一个职员5分钟能处理完一个顾客一个小时9名职员能处理9*60/5108。
可见工作方式的转变能带来效率的极大提升。
这种工作方式其实就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 APIjava.util.stream 把真正的函数式编程风格引入到Java中默认方法默认方法就是一个在接口里面有了一个实现的方法。Optional 类 Optional 类已经成为 Java 8 类库的一部分用来解决空指针异常。Date Time API 加强对日期与时间的处理。 Java8 新特性总结 序列化和反序列化
序列化把对象转换为字节序列的过程称为对象的序列化.反序列化把字节序列恢复为对象的过程称为对象的反序列化.
什么时候需要用到序列化和反序列化呢?
当我们只在本地 JVM 里运行下 Java 实例这个时候是不需要什么序列化和反序列化的但当我们需要将内存中的对象持久化到磁盘数据库中时当我们需要与浏览器进行交互时当我们需要实现 RPC 时这个时候就需要序列化和反序列化了.
前两个需要用到序列化和反序列化的场景是不是让我们有一个很大的疑问? 我们在与浏览器交互时还有将内存中的对象持久化到数据库中时好像都没有去进行序列化和反序列化因为我们都没有实现 Serializable 接口但一直正常运行.
下面先给出结论:
只要我们对内存中的对象进行持久化或网络传输这个时候都需要序列化和反序列化.
理由:
服务器与浏览器交互时真的没有用到 Serializable 接口吗? JSON 格式实际上就是将一个对象转化为字符串所以服务器与浏览器交互时的数据格式其实是字符串我们来看来 String 类型的源码:
public final class Stringimplements java.io.SerializableComparableStringCharSequence {/\*\* 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 idinsertUser parameterTypeorg.tyshawn.bean.UserINSERT INTO t\_user(nameage) 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 的值?
如果不显示指定 serialVersionUIDJVM 在序列化时会根据属性自动生成一个 serialVersionUID然后与属性一起序列化再进行持久化或网络传输. 在反序列化时JVM 会再根据属性自动生成一个新版 serialVersionUID然后将这个新版 serialVersionUID 与序列化时生成的旧版 serialVersionUID 进行比较如果相同则反序列化成功否则报错.
如果显示指定了 serialVersionUIDJVM 在序列化和反序列化时仍然都会生成一个 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跨域资源共享
CORSCross-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、cssimg等静态资源分离到另一台独立域名的服务器上在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是falsebyte、short、int、long是0float是0.0f等。
举一个例子比如有一个扣费系统扣费时需要从外部的定价系统中读取一个费率的值我们预期该接口的返回值中会包含一个浮点型的费率字段。当我们取到这个值得时候就使用公式金额*费率费用 进行计算计算结果进行划扣。
如果由于计费系统异常他可能会返回个默认值如果这个字段是Double类型的话该默认值为null如果该字段是double类型的话该默认值为0.0。
如果扣费系统对于该费率返回值没做特殊处理的话拿到null值进行计算会直接报错阻断程序。拿到0.0可能就直接进行计算得出接口为0后进行扣费了。这种异常情况就无法被感知。
那我可以对0.0做特殊判断如果是0就阻断报错这样是否可以呢
不对这时候就会产生一个问题如果允许费率是0的场景又怎么处理呢
使用基本数据类型只会让方案越来越复杂坑越来越多。
这种使用包装类型定义变量的方式通过异常来阻断程序进而可以被识别到这种线上问题。如果使用基本数据类型的话系统可能不会报错进而认为无异常。
因此建议在POJO和RPC的返回值中使用包装类型。
8招让接口性能提升100倍
池化思想
如果你每次需要用到线程都去创建就会有增加一定的耗时而线程池可以重复利用线程避免不必要的耗时。
比如TCP三次握手它为了减少性能损耗引入了Keep-Alive长连接避免频繁的创建和销毁连接。
拒绝阻塞等待
如果你调用一个系统B的接口但是它处理业务逻辑耗时需要10s甚至更多。然后你是一直阻塞等待直到系统B的下游接口返回再继续你的下一步操作吗这样显然不合理。
参考IO多路复用模型。即我们不用阻塞等待系统B的接口而是先去做别的操作。等系统B的接口处理完通过事件回调通知我们接口收到通知再进行对应的业务操作即可。
远程调用由串行改为并行
比如设计一个商城首页接口需要查商品信息、营销信息、用户信息等等。如果是串行一个一个查那耗时就比较大了。这种场景是可以改为并行调用的降低接口耗时。
锁粒度避免过粗
在高并发场景为了防止超卖等情况我们经常需要加锁来保护共享资源。但是如果加锁的粒度过粗是很影响接口性能的。
不管你是synchronized加锁还是redis分布式锁只需要在共享临界资源加锁即可不涉及共享资源的就不必要加锁。
耗时操作考虑放到异步执行
耗时操作考虑用异步处理这样可以降低接口耗时。比如用户注册成功后短信邮件通知是可以异步处理的。
使用缓存
把要查的数据提前放好到缓存里面需要时直接查缓存而避免去查数据库或者计算的过程。
提前初始化到缓存
预取思想很容易理解就是提前把要计算查询的数据初始化到缓存。如果你在未来某个时间需要用到某个经过复杂计算的数据才实时去计算的话可能耗时比较大。这时候我们可以采取预取思想提前把将来可能需要的数据计算好放到缓存中等需要的时候去缓存取就行。这将大幅度提高接口性能。
压缩传输内容
压缩传输内容传输报文变得更小因此传输会更快。