Qouson's blog Qouson's blog
首页
  • Java 基础

    • 基础
    • String
  • Java 中级

    • 网络编程
  • Java 高级

    • JVM
    • 多线程
  • Spring
  • SpringMVC
  • SpringBoot
  • MySQL
  • Redis
  • MQ
  • ZooKeeper
  • git
  • linux
  • 设计模式
  • 数据结构与算法
  • 计算机基础
  • Java相关框架
  • 分布式
  • DDD领域驱动设计
  • 系统设计
  • 杂乱无章
Java知识图谱
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

qouson

Java界的小学生
首页
  • Java 基础

    • 基础
    • String
  • Java 中级

    • 网络编程
  • Java 高级

    • JVM
    • 多线程
  • Spring
  • SpringMVC
  • SpringBoot
  • MySQL
  • Redis
  • MQ
  • ZooKeeper
  • git
  • linux
  • 设计模式
  • 数据结构与算法
  • 计算机基础
  • Java相关框架
  • 分布式
  • DDD领域驱动设计
  • 系统设计
  • 杂乱无章
Java知识图谱
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础

    • 基础
    • 集合
    • 基础-大彬
      • Java的特点
      • Java是如何实现跨平台的
      • Java与C++的却别
      • JDK、JRE和JVM三者关系
      • Java程序是编译执行还是解释执行
      • 面向对象和面向过程的区别
      • 面向对象有哪些特性
      • 面向对象编程的六大原则
      • 数组到底是不是对象
      • Java的基本数据类型有哪些
      • 为什么不能用浮点型表示金额
      • 什么是值传递和引用传递
      • 了解Java的包装类型吗?为什么需要包装类
      • 自动装箱和拆箱
      • 两个Integer用==比较不相等的原因
      • String为什么不可变
      • 为何JDK9要将String的底层实现由char[]改成byte[]
      • String,StringBuffer和StringBuilder区别
      • 什么是StringJoiner
      • String类的常用方法有哪些
      • new String("dabin")会创建几个对象
      • 什么是字符串常量池
      • String最大长度是多少
      • Object常用方法有哪些
      • 讲讲深拷贝和浅拷贝
      • 两个对象的hashCode()相同,则equals()是否也一定为true
      • 为什么重写equals时一定要重写hashCode
      • Java创建对象有几种方式
      • 说说类实例化的顺序
      • equals和==的区别
      • 常见的关键字有哪些
      • Java中finally一定会被执行吗
      • final关键字作用
      • 方法重载和重写的区别
      • 抽象类和接口的区别
      • 常见的Exception
      • Error和Exception的区别
      • 运行时异常和非运行时异常(checked)的区别
      • throw和throws的区别
      • BIO、NIO和AIO的区别
      • 守护线程是什么
      • Java支持多继承吗
      • 如何实现对象克隆
      • 同步和异步的区别
      • 阻塞和非阻塞的区别
      • Java8的新特性有哪些
      • 序列化和反序列化
      • 什么时候需要用到序列化和反序列化
      • 实现序列化和反序列化为什么要实现Serializable接口
      • 实现Serializable接口之后,为什么还要显示指定SerialVersionUID的值
      • static属性为什么不会被序列化
      • transient关键字的作用
      • 什么是反射
      • 反射有哪些应用场景呢
      • 讲讲什么是泛型
      • 如何停止一个正在运行的线程
      • 什么是跨域
      • 为什么要有同源策略
      • 跨域问题怎么解决
      • 设计接口需要注意什么
      • 过滤器和拦截器有什么却别
      • 对接第三方接口需要考虑什么
      • 后按接口性能优化有哪些方法
      • 为什么阿里巴巴Java开发手册中强制要求使用包装类型定义属性
      • 8招让接口性能提升100倍
    • 集合-大彬
  • String

  • 网络编程

  • JVM

  • 多线程

  • JavaSE
  • 基础
qouson
2024-11-14
目录

基础-大彬

# Java基础

# 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是通过JVM实现跨平台的。 JVM可以理解成一个软件,不同的平台有不同的版本。我们编写的Java代码,编译后会生成.class问及那(字节码文件)。Java虚拟机就是负责将字节码文件翻译成特定平台下的机器码,通过JVM翻译成机器码之后才能运行。不同平台下编译生成的字节码是一样的,但是由JVM翻译成的机器码却不一样。 只要在不同平台上安装对应的JVM,就可以运行字节码文件了,运行我们编写的Java程序。 因此,运行Java程序必须由JVM的支持,因为编译的结果不是机器码,必须要经过JVM的翻译才能执行。

# Java与C++的却别

  • Java是纯粹的面向对象语言,所有的对象都继承自java.lang.Object,C++兼容C,不但支持面向对象也支持面向过程。
  • Java通过虚拟机从而实现跨平台特性,C++依赖于特定的平台。
  • Java没有指针,它的引用可以理解为安全指针,而C++具有和C一样的指针。
  • Java支持自动垃圾回收,而C++需要手动回收。
  • Java不支持多重集成,只能通过实现多个接口来达到相同目的,而C++支持多重继承。

# JDK、JRE和JVM三者关系

JVM 就是我们耳熟能详的Java虚拟机。Java能够跨平台运行的核心在于JVM。 所有的java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行。也就是说class文件并不直接与机器的操作系统交互,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释过本地系统执行。 针对不同的系统由不同的jvm实现,由Linux版本的jvm实现,也有Weindos版本的jvm实现,但是同一段代码在编译后的字节码是一样的。这就是Java能够跨平台,实现一次编写,多处运行的原因所在。

JRE Java运行时环境。我们编写的Java程序必须要在JRE才能运行。它主要包含两个部分,JVM和Java核心类库。 JRE是Java运行环境,并不是一个开发环境,所有没有包含任何开发工具,如编译器和调试器等。 如果你只是想运行Java程序,而不是开发Java程序的化,那么你只需要安装JRE即可。

JDK Java开发工具包 JDK下有个JRE,除此之外,JDK还有一些好用的工具,如jinfo,jps,jstack等。

总结: JRE = JVM + Java核心类库 JDK = JRE + Java工具 + 编译器 + 调试器

# Java程序是编译执行还是解释执行

编译型语言 在程序运行之前,通过编译将源程序编译成机器码可以运行的二进制,以后执行这个程序时,就不用再进行编译了。 优点:编译器一般会有预编译的过程对代码进行优化。因为编译只做一次,运行时不需要编译,所以编译型语言的程序执行效率搞。可以脱离语言环境独立运行。 缺点:编译之后如果需要修改就需要整个模块重新编译。编译的时候根据对应的运行环境生成机器码,不同的操作系统之间移植就会有问题,需要根据运行的操作系统环境编译不同的可执行文件。 总结:执行速度快,效率高;依靠编译器,跨平台性差些。 代表语言:C,C++,Pascal,Object-C以及Swift。

解释型语言 定义:解释型语言的源代码不是直接翻译成机器码,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。在运行的时候才将源程序翻译成机器码,翻译一句,然后执行一句,直至结束。 优点: 1.由良好的平台兼容性,在任何环境中都可以运行,前提是安装了解释器 2.灵活,修改代码的时候直接修改就可以,可以快速部署,不用停机维护。 缺点:每次运行的时候都要解释一遍,性能上不如编译型语言。 总结:解释型语言执行速度慢,效率低;依靠解释器,跨平台性好。 代表语言:JavaScript,Python,Erlang,PHP,Perl,Ruby。 对于Java这种语言,它的源代码会先通过javac编译成字节码,在通过jvm将字节码转换成机器码执行,即解释运行和编译运行配合使用,所以可以称为混合型或半编译型。

# 面向对象和面向过程的区别

面向对象和面向过程是一种软件开发思想。

  • 面向过程就是分析出解决问题所需要的步骤,然后用函数按这些步骤实现,使用的时候依次调用就可以了。
  • 面向对象是把构成问题事务分解成各个对象,分别涉及这些对象,然后将他们组装成有完整功能的系统。面向过程只用函数实现,面向对象使用类实现各个功能模块。 以五子棋为例,面向过程的涉及思路就是首先分析问题的步骤: 1.开始游戏 2.黑子先走 3.绘制画面 4.判断输赢 5.轮到白子 6.绘制画面 7.判断输赢 8.返回步骤2 9.输出最后结果。 把上面每个步骤用分别的函数来实现,问题就解决了。

而面向对象的涉及则是从另外的思路来解决问题。整个五子棋可以分为: 1.黑白双方 2.棋盘系统,负责绘制画面 3.规则系统,负责判定诸如犯规输赢等

黑白双方负责接受用户的输入,并告知棋盘系统棋子布局发生变化,棋盘系统接收到了棋子变化的信息就负责在屏幕上显示这种变化,同时利用规则系统来对棋局进行判定。

# 面向对象有哪些特性

面向对象有三大特性:封装、继承和多态。抽象???

1.封装就是将类的信息隐藏在内部,不允许外部程序直接访问,而是通过该类的方法实现对隐藏信息的操作和访问。良好的封装能够减少耦合。 2.继承是从已有的类中派生出新的类,新的类继承父类的属性和行为,并能扩展新的能力,大大增加程序的重用性和易维护性。在Java中是单继承的,也就是说要给子类只有一个父类。 3.多态是同一个行为具有不同表现得能力。在不修改程序代码得情况下改变程序运行时绑定得代码。实现多态得三要素:继承,重写,父类引用指向子类对象。 静态多态性:通过重载实现,相同得方法有不同得参数列表,可以根据参数得不同,做出不同得处理。 动态多态性:在子类中重写父类得方法,运行期间判断引用对象得实际类型,根据其实际类型调用响应方法。 4.抽象,把客观事物用代码抽象出来。

# 面向对象编程的六大原则

单一职责原则:我们设计创建的对象,必须职责明确,比如商品类,里面的相关属性和方法都必须跟商品有关,不能出现订单等不相关的内容。这里的类可以是模块类库,程序集,而不单单指类。 里氏替换原则:子类能够完全替代父类,反之则不行。通常用于实现接口时运用。因为子类能够完全替代基类,那么这样父类就拥有很多子类,在后续的程序扩展中就很容易进行扩展,程序完全不需要进行修改即可进行扩展。比如IA的实现为A,因为项目需求变更,现在需要新的实现,直接在容器注入处更换接口即可。 迪米特法则:页角最小原则,或者说最小耦合。通常在设计程序或开发程序的时候,尽量要高内聚,低耦合。当两个类进行交互的受,会产生依赖。而迪米特法则就是建议这种依赖越少越好。就像构造函数注入父类对象时一样,当需要依赖某个对象时,并不在意其内部是怎么实现的,而是在容器中注入响应的实现,既符合里氏替换原则,又起到解耦作用。 开闭原则:开发扩展,封闭修改。当项目需求发生变更时候,要尽可能地不去对原有地代码进行修改,而在原有地基础上进行扩展。 依赖倒置原则:高层模块不应该直接依赖于底层模块的具体实现,而应该依赖于底层的抽象。接口和抽象类不应该依赖于实现类,而实现类依赖接口或抽象类。 接口隔离原则:一个对象和另一个对象交互的过程中,依赖的内容最小。也就是说在接口设计的时候,在遵循对象单一职责的情况下,尽量较少接口的内容。

简洁版:

单一职责:对象设计要求独立,不能设计万能对象 开闭原则:对象修改最小化 里氏替换:程序扩展中抽象被具体可以替换(接口,父类,可以被实现类对象,子类对象替换) 迪米特:高内聚,低耦合,尽量不要依赖细节。 依赖倒置:面向抽象编程。也就是参数传递,或者返回值,可以使用父类型或接口类型。从广义上讲:基于接口编程,提前设计好接口框架 接口隔离:接口设计大小要始终。过大倒置污染,过小,导致调用麻烦。

# 数组到底是不是对象

对象是根据某个类创建出来的一个实例。表示某类事务中一个具体的个体

对象具有各种属性,并且具有一些特定的行为。站在计算机的角度,对象就是内存中的一个内存块,在这个内存块封装了一些数据,也就是类中定义的各个属性。 所以,对象是用来封装数据的。 java中的数据具有java中其他对象的一些基本特点。比如封装了一些数据,可以访问属性,也可以调用方法 因此,可以说,数组是对象。 也可以通过代码验证数组是对象的事实。

# Java的基本数据类型有哪些

byte,8bit char,16bit short,16bit int,32bit float,32bit long,64bit double,64bit boolean,只有两个值,true和false,可以用1bit来存储

# 为什么不能用浮点型表示金额

由于计算中保存的小数其实是十进制的小数的近似值,并不是准确值,所以,千万不要在代码中使用浮点数来表示金额等重要指标。

建议用BigDecimal或Long

# 什么是值传递和引用传递

  • 值传递是对基本类型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
  • 引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身,两者指向同一片内存空间,所以引用对象进行操作会同时改变原对象。 java中不存在引用传递,只有值传递。即不存在变量a指向变量b,变量b指向对象这种情况

# 了解Java的包装类型吗?为什么需要包装类

Java是一种面向对象语言,很多地方需要使用独享而不是基本数据类型。 比如,在集合类中,我们是无法将int,double等类型放进去的。因为集合的容器要求元素是Object类型。

为了让基本类型也具有对象的特这个,就出现了包装类型。相当于将基本类型包装起来,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

# 自动装箱和拆箱

装箱:将基础类型转化为包装类型 拆箱:将包装类型转化为基础类型

当基础类型与它们的包装类有如下几种情况时,编译器会自动帮我们进行装箱或拆箱:

  • 赋值操作(装箱或拆箱)
  • 进行加减乘除混合运算(拆箱)
  • 进行><==比较运算(拆箱)
  • 调用equals方法比较(装箱) ArrayList,HashMap等集合类型添加基础类型数据时(装箱)

# 两个Integer用==比较不相等的原因

Integer c = 200,会调用Integer.valueOf(200) 而Integer.valueOf()源码可以看到,这里的实现并不是简单的new Integer,而是用IntegerCache做个cache。 默认Integercache下限是-128,上限是127.当赋值100给Integer,刚好在这个范围内,所以从cache去对应的Integer返回,所以a和b返回的是同一个对象,所以==比较是相等的,当赋值200给Integer时候,不在cache范围,所以会new Integer并返回,当然==比较的结果是不相等的。

# 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
}
1
2
3
4
5
6
7
8

从源码可以看出,String对象其实在内部就是一个个字符,存储在这个value数组里面的。

value数组用final修饰,final修饰的变量,值不能被修改。因此value不可以指向其他对象。

String类内部所有字段都是私有的,也就是被private修饰。而且String没有对外提供修改内部状态的方法,因此value数组不能改变。

所以String是不可变的。

那为什么String要设计成不可变的?

主要有以下几点原因:

1.线程安全。同一个字符串实例可以被多个县城共享,因为字符惨不可变,本身就是县城安全的。 2.支持hash映射和缓存。因为String的hash值经常会使用到,比如作为Map的键,不可变的特性使得hash值也不会变,不需要重新计算。 3.出于安全考虑。网络地址URL,文件路径path,密码通常情况下都是以String类型保存,假若String不是固定不变的,将会引起各种安全隐患。比如将密码用String的类型保存,那么它将一直留在内存中,知道垃圾收集器把它清除。加入String类不是固定不变的,那么这个密码可能会被改变,导致出现安全隐患。 4.字符串常量池优化。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不可变,因此是线程安全的。
  • StringBuffer不是线程安全的
  • StringBuffer是线程安全的,内部使用synchronized进行同步

# 什么是StringJoiner

StringJoiner是Java 8新增的一个API,它基于StringBuilder实现,用于实现对字符串之间通过分隔符拼接的场景。

StringJoiner有两个构造方法,第一个构造要求以此传入分隔符,前缀和后缀。第二个构造则只要求传入分割如既可(前缀和后缀默认为空字符串)

StringJoiner(CharSequence delimiter,CharSequence prefix,CharSequence suffix) StringJoiner(CharSequence delimiter)

有些字符串拼接场景,使用StringBuffer或StringBuilder则显得比较繁琐。

# String类的常用方法有哪些

indexOf():返回指定字符的索引 charAt():返回指定索引处的字符 replace():字符串替换 trim():去除字符串两端的空格 split():分割字符串,返回一个分割后的字符串数组 getBytes():获得字节数组 length():获得字符串长度 toLowerCase():字符串转小写 toUpperCase():字符串转大写 sustring():字符串截取 equals():字符串比较

# new String("dabin")会创建几个对象

使用这种方式会创建两个字符串对象(前提是字符串常量池中没有"dabin"这个字符串对象)

  • "dabin"属于字符串字面量,因此编译时期会在字符串常量池中创建一个字符串对象,指向这个"dabin"字符串字面量
  • 使用new的方式会在堆中创建一个字符串对象 String str1 = new String("123") String str2 = "123"

str1 == str2 返回false str1.equals(str2) 返回true

# 什么是字符串常量池

字符串常量池(String Pool)保存着所有字符串字面量,这些字面量在编译时期就确定。字符串常量池位于堆内存中,专门用来存储字符串常量。在创建字符串时,JVM首先会检查字符串常量池,如果该字符串已经存在池中,则返回其引用,如果不存在,则创建此字符串并放入池中,并返回其引用。

# String最大长度是多少

String类提供了一个length方法,返回值为int类型,而int的取值上限为2^31-1,也就是2147483647。 所以理论上String的最大长度为2^31-1 达到这个长度的话需要多大的内存

String内部是使用一个char数组来维护字符序列的,一个char占用两个字节。 如果说String最大长度是2^31-1那么最大的字符串占用内存空间约等于4GB。 也就是说,我们需要有大于4GB的JVM运行内存才行。 那String一般都存储在JVM的哪块区域呢 字符串在JVM中存储分两种情况,一种是String对象,存储在JVM的堆栈中,一种是字符串常量,存储在常量池里面。

什么情况下字符串会存储在常量池呢

当通过字面量进行字符串声明时,比如String s = "123",这个字符串在编译之后会以常量的形式进入常量池

那常量池中的字符串最大长度是2^31-1吗 不是的,字符串常量池对String的长度是有另外的限制的。Java中UTF-8编码的Unicode字符串在常量池中以CONSTANT_Utf8类型表示。 length在这里就是代表字符串的长度,length的类型是u2,u2是无符号的1位证书,也就是说最大长度可以做到2^16-1,即65535

不过javac编译器做了限制,需要length < 65535。所以字符串常量在常量池中的最大长度是65535 - 1 = 65534

String在不同的状态下,具有不同长度限制

  • 字符串常量长度不能超过65534
  • 对内字符串的长度不能超过2^31 - 1

# Object常用方法有哪些

toString():默认输出对象地址 equals():默认比较两个引用变量是否指向同一个对象(内存地址) hashCode():将与对象相关的信息映射成一个哈希值,默认的实现是根据内存地址换算出来的。 clone():Java赋值时复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的。Object对象有个clone()方法,实现了对象中各个属性的复制,但它的可见范围是protected

# 讲讲深拷贝和浅拷贝

浅拷贝:拷贝对象和原始对象的引用类型用同一个对象

以下例子,Cat对象里面有个Person对象,调用clone之后,克隆对象和原始对象Person引用的是同一个对象,这就是浅拷贝。

深拷贝:拷贝对象和原始对象的引用类型引用不同的对象

以下例子,在clone函数中不仅调用了super.clone,而且调用Person对象的clone方法(Person也要实现Cloneable接口并重写clone方法),从而实现了深拷贝。可以看到拷贝对象的值不会受到原对象的影响。

# 两个对象的hashCode()相同,则equals()是否也一定为true

equals与hashcode的关系: 1.如果两个对象调用equals比较返回true,那么他们的hashCode值一定相同 2.如果两个对象的hashCode相同,他们并不一定相同

hashCode方法主要用来提升对象比较的效率,先进行hashCode()的比较,如果不相同,那就不必再进行equals比较,这样就大大减少了equals比较的次数,当比较对象的数量很大的时候能提升效率。

# 为什么重写equals时一定要重写hashCode

之所以重写equals()要重写hashCode(),是为了保证equals()方法返回true的情况下hashCode值也要一致,如果重写了equals()没有重写hashCode(),就会出现两个对象相等但hashCode()不相等的情况。这样,当用其中一个对象作为键保存到HashMap,HashTable或HashSet中,再以两外一个对象作为键值去查他们的时候,则会查找不到。

# Java创建对象有几种方式

  • new关键字
  • 反射,Class.newInstace()
  • 调用对象clone()
  • 运用反序列化手段,调用java.io.ObjectInputStream的readObject()方法

# 说说类实例化的顺序

1.静态属性,静态代码块 2.普通属性,普通代码块 3.构造方法

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();
    }

    /**
     *      静态属性初始化
     *      静态代码块初始化
     *      普通属性初始化
     *      普通代码块初始化
     *      构造方法初始化
     */
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# equals和==的区别

  • 对于基本数据类型,==比较的是值是否相同。基本数据类型没有equals方法
  • 对于符合数据类型,==比较的是它们的存放地址(是否是同一个对象)。 equals默认比较地址值,重写的话按照重写逻辑去比较。

# 常见的关键字有哪些

static static可以用来修饰类的成员方法,类的成员变量 static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量是被所有对象所共享的,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。 静态内部类 在静态方法里,使用非静态内部类依赖外部类的实例,也就是说要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。

final 1.基本数据类型用finnal修饰,则不能修改,是常量;对象引用用final修饰,则引用只能指向该对象,不能指向别的对象,但是独享本身可以修改。 2.finnal修饰的方法不能被子类重写 3.finnal修饰的类不能被继承

this this.属性名称指访问类中的成员变量,可以用来区分成员变量和局部变量。 this.方法名称用来访问本类的方法。

supper supper关键字用于在子类中访问父类的变量和方法

final,finally,finalize区别 final:用于修饰属性,方法和类,分表示属性不能被重新赋值,方法不可被覆盖,类不可被继承。 finally是异常处理语句结构的一部分,一般以try-catch-finally出现,finally代码块表示总被执行 finalize是Object类的一个方法,该方法一般由垃圾回收器来调用,当我们调用System.gc()方法的时候,由垃圾回收器调用finalize()方法回收垃圾,JVM并不保证此方法总被调用

# Java中finally一定会被执行吗

答案是不一定 有以下两种情况finally不会被执行 1.程序未执行到try代码块 2.如果当一个线程在执行try语句块或者catch语句时被打断或终止,与其相对应finally语句块可能不会执行。还有更极端的情况,就是在线程运行try或cache语句块,突然死机或断电,finally语句块同样也不会执行。

# final关键字作用

final修饰的类不能被继承 final修饰的方法不能被重写 final修饰的变量叫常量,常量必须初始化,初始化之后不能被修改。

# 方法重载和重写的区别

同个类中多个方法可以有相同的方法名称,但是有不同的参数列表,这就称为方法重载。参数列表又叫参数签名,包括参数类型,参数个数,参数顺序,只要有一个不同就叫做参数列表不同

方法重写描述的是父类和子类之间的。当父类的功能无法满足子类的需求,可以在子类对方法进行重写。方法重写时,方法名与形参列表必须一致。

# 抽象类和接口的区别

1.语法层面区别 抽象类可以有方法实现,而接口中方法只能是抽象方法(Java8之后接口方法可以有默认实现) 抽象类中的成员变量可以是各种类型,接口中的成员变量只能是public static final类型 接口中不能含有静态代码块和静态方法,而抽象类可以有静态代码块和静态方法(Java8之后接口可以有静态方法)

2.设计层面区别 抽象层次不同。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口只是对行为进行抽象。继承抽象类是一种"是不是"的关系,而接口实现则是"有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是具不具备的关系,比如鸟是否能飞。 继承抽象类的是具有相似特点的类,而实现接口却可以不同的类

# 常见的Exception

RuntimeException: 1.ClassCastException 2.IndexOutOfBoundsException 3.NullPointerException 4.ArrayStoreException 5.NumberFormatException 6.ArithmeticException

checked Exception: 1.NoSuchFielException 2.ClassNotFoundException 3.IlleagalArgumentException

# Error和Exception的区别

Error:JVM无法解决的严重问题,如栈溢出StackOverFlowError,系统崩溃OutOfMemoryError Exception:其他因编程错误或偶然的外在因素导致的一般性问题。可以在代码中进行处理:如:空指针异常,数组下标越界等。

# 运行时异常和非运行时异常(checked)的区别

unchecked exception包括RuntimeException和Error,其他所有异常称为检查异常 1.RuntimeException由程序错误导致,应该修正程序避免这类异常发生 2.checkedException由具体的环境(读取的文件不存在或文件为空或sql异常)导致的异常。必须进行处理,不然编译不通过,可以catch或throws

# throw和throws的区别

throw:抛出异常,一般用于程序出现错误时,主动抛出异常。 throws:声明异常,一般用于方法出现错误时,通知调用者。

# 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允许把函数作为一个方法的参数
  • StreamAPI:新添加的Stream API把真正的函数式编程风格引入到Java中。
  • 默认方法:默认方法就是一个在接口里面有了一个实现的方法
  • Optional类:Optional类已经成为Java类库的一部分,用来解决空指针异常
  • Date Time API:加强对日期与时间的处理。

# 序列化和反序列化

序列化:把对象转换为字节序列的过程称为对象的序列化 反序列化:把字节序列恢复为对象的过程称为对象的反序列化

# 什么时候需要用到序列化和反序列化

当我们只在本地JVM运行Java实例,这个时候是不需要什么序列化和反序列化的,但我们需要将内存的对象持久化到磁盘,数据库中时,当我们需要与浏览器交互时,当我们需要实现RPC时,这个时候就需要序列化和反序列化了。

前两个需要用到序列化和反序列化的场景,是不是有很大的疑问? 我们在与浏览器交互时,还有将内存中的对象持久化到数据库中,好像没有去进行序列化和反序列化,因为我们都没有实现Serializable接口,但正常运行。

只要我们对内存中的对象进行持久化或网络传输,这个时候都需要序列化和反序列化

理由: 服务器与浏览器交互时真的没有用到Serializable接口吗?JSON格式实际上就是将一个对象转化为字符串,所以服务器与浏览器交互时,的数据格式其实时字符串,我们来看String类型源码 String实现了Serializable接口,并显示指定serialVersionUID。 对象持久化到数据库中的时候。 实际上我们并不是将整个对象持久化到数据库中,而是将对象中的属性持久化到数据库中,而这些属性(如Date/String)都实现了Serializable接口。

# 实现序列化和反序列化为什么要实现Serializable接口

在Java中实现了Serializable接口后,JVM在类加载的时候就会发现我们实现了整个接口,然后在初始化实例对象的时候就会在底层帮我们实现序列化和反序列化

如果被写对象类型不是String,数组,Enum,并且没有实现Serializable接口,那么在进行序列化的时候,将抛出NotSerializableException异常

# 实现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语言的反射机制

在运行状态中,对于任意一个类,能够知道这个类所有属性和方法。对于任意一个对象,能够调用它的任意一个方法和属性。

# 反射有哪些应用场景呢

1.JDBC连接数据库时用Class.forName()通过反射加载数据库的驱动程序 2.Eclispe,IDEA等开发工具利用反射动态解析对象的类型与结构,动态提示对象的属性和方法 3.Web服务器中利用反射调用了Servlet的service方法 4.JDK动态代理底层依赖反射实现

# 讲讲什么是泛型

Java泛型是JDK5中引入的新特性,允许在定义类和接口的时候使用类型参数。声明的类型参数在使用时用具体的类型来替换。 泛型最大的好处就是提高代码的复用性。以List接口为例,我们可以将String,Integer类型放入List,如不用泛型,存放String类型要写一个List接口,存放Integer要写另一个List接口,泛型可以很好的解决这个问题。

# 如何停止一个正在运行的线程

有几种方式 1.使用线程的stop方法 使用stop方法可以强制终止线程,不过stop是一个被废弃掉的方法,不推荐使用 使用stop方法,会一直向上传播ThreadDeath异常,从而使得目标线程解锁有锁住的监视器,即释放掉所有的对象锁。使得之前被锁住的对象得不到同步处理,因此可能会造成数据不一致问题。 2.使用interrupt方法 该方法知识告诉线程要终止,但最终何时终止取决于计算机。调用intterrupt方法仅仅是在当前线程中打了个停止的标记,并不是真的停止线程。 接着调用Thread.currentThread().isInterrupted()方法,可以用来判断当前线程是否被终止,通过这个判断我们可以做一些业务逻辑处理,通常如果isInterrupted返回true的话,会抛一个中断异常,然后通过try-catch捕获 3.设置标志位 设置标志位,当表示位位某个值时,是线程正常退出。设置标志位是用到了共享变量的凡是,为了保证共享变量在内存中的可见性,可以使用volatile修饰它,这样的话,变量取值始终会从主存中获取最新值。 但是这种volatile标记共享变量的方式,在线程发生阻塞时是无法完成响应的。比如调用了Thread.sleep()方法之后,线程处于不可运行状态,即便是主线程修改了共享变量的值,该线程此时根本无法检查循环标志,所以也就无法实现线程中断。 因此,interrupt()加上手动抛异常的方式是目前中断一个正在运行的线程最为正确的方式了。

# 什么是跨域

简单来讲,跨域是指从一个昱茗的网页去请求另一个昱茗的资源。由于有同源策略的关系,一般是不允许这么直接访问的。但是,很多场景经常会有跨域访问的需求,比如在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域问题。

那什么是同源策略呢

所谓同源是指“协议+域名+端口”三者相同,即便两个不同的昱茗指向同一个ip地址,也非同源

同源策略限制以下几种行为: 1.Cookie,LocalStorage和IndexDB无法读取 2.DOM和JS对象无法获得 3.AJAX请求不能发送

# 为什么要有同源策略

举个例子,加入你刚刚在网银输入账号密码,查看了自己的余额,然后再去访问其他带颜色的网站,这个网站可以访问刚刚的网银站点,并且获取账号密码,那后果可想而知。因此,从安全的角度来讲,同源策略是有利于保护网站信息的。

# 跨域问题怎么解决

CORS(Cross-origin resource sharing),跨域资源共享。CORS其实是浏览器制定的一个规范,浏览器会自动进行CORS通信,它的实现主要在服务端,通过一些HTTP Header来限制可以访问的域,例如页面A需要访问B服务器上的数据,如果B服务器上声明了允许A的域名访问,那么从A到B的跨域请求就可以完成。

@CrossOrigin注解 如果项目使用的是SpringBoot,可以在Controller类上添加一个@CrossOrigin注解,这样就可以解决跨域问题。当然这个标签也可以加到方法上,或者直接加到入口类上对所有接口进行跨域处理。注意S平日那个MVC版本要在4.2或以上

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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

通过jsonp跨域 通常为了减轻web服务器的负载,我们把js,css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过乡音的标签从不同域名下加载静态资源,这是浏览器允许的操作,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。

# 设计接口需要注意什么

1.接口参数校验。接口必须校验参数,比如入参是否允许为空,入参长度是否符合预期 2.设计接口时,充分考虑接口的可扩展性。思考接口是否可以复用,怎样保持接口的可扩展性。 3.串行调用考虑改用并行调用。比如设计一个商城首页接口,需要查商品信息,营销信息,用户信息等等。如果是串行一个个查,那耗时就比较大。这种场景是可以改为并行调用的,降低接口耗时。 4.接口是否需要防重处理。涉及到数据库修改的,要考虑防重处理,可以使用数据库防重表,以唯一流水号作为唯一索引。 5.日志打印全面,入参出参,接口耗时,记录好日志,方便甩锅。 6.修改旧接口时,注意兼容设计 7.异常处理得当,使用finally关闭流资源,使用log打印而不是e.printStackTrace(),不要吞异常等等。 8.是否需要考虑限流。限流为了保护系统,防止流量洪峰超过系统的承载能力。

# 过滤器和拦截器有什么却别

1.实现原理不同 过滤器和拦截器底层实现不同。过滤器是基于函数回调的,拦截器是基于Java的反射机制(动态代理)实现的。一般自定义的过滤器中都会实现一个doFilter()方法,这个方法有个FilterChain参数,而实际上它是一个回调接口。

2.使用范围不同 过滤器实现的是javax.servlet.Filter接口,而这个接口是Servlet规范中定义的,也就是说过滤器Filter的使用要依赖于tomcat等容器,导致它只能在web程序中使用。而拦截器是Spring组件,并由Spring容器管理,并不依赖Tomcat容器,是可以单独使用的。拦截器不仅能应用在web程序中,也可以用于Application,Swing中

3.使用的场景不同 因为拦截器更接近业务系统,所以拦截器主要用来实现项目中的业务判断,比如:日志记录,权限判断等业务。而过滤器通常是用来实现通用功能过滤的,比如:敏感词过滤,响应数压缩等功能。

4.触发时机不同 过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。 拦截器Intercepter是在请求进入servlet后,在进入Controller之前进行预处理的,Controller中渲染了对应的视图之后请求结束。

5.拦截的请求范围不同 请求的执行顺序是:请求进入容器-》进入过滤器-》进入Servlet-》进入拦截器-》执行控制器,可以看到过滤器和拦截器的执行时机不同的,过滤器会先执行,然后才会执行拦截器,最后才会进入真正要调用的方法。

# 对接第三方接口需要考虑什么

1.确认接口对接的网络协议,是https/http或者自定义的私有协议 2.约定好数据传参,响应格式(application/json),弱类型对接强类型语言要特别注意 3.接口安全方面,要确定身份校验方式,使用token,证书校验 4.确认是否需要接口调用失败后的重试机制,保证数据传输的最终一致性。 5.日志记录要全面。接口入参数,以及解析之后的参数值,都要用日志记录下来,方便定位问题

# 后按接口性能优化有哪些方法

1.优化索引。给where条件关键字段,或者order by后面的排序字段,加索引 2.优化sql语句,比如避免使用select *,批量操作,避免深分页,提升group by效率 3.避免大事务,使用@Transaction注解这种声明式事务的方式提供事务功能,容易造成大事务,引发其他问题。应该避免在事务中一次性处理太多数据,将一些跟事务无关的逻辑放到事务外面执行。 4.异步处理。剥离主逻辑和副逻辑,副逻辑可以异步执行,异步写库。比如用户购买的商品发货了,需要发短信通知,短信通知是副流程,可以异步执行,以免影响主流程执行。 5.降低锁粒度。在并发场景下,多个线程同时修改数据,造成数据不一致的情况。这种情况下,一般会加锁解决。但如果锁加的不好,导致锁的粒度太大,也会非常影响接口性能。 6.加缓存。如果表数据量非常大的话,直接从数据库查询数据,性能会非常差。可以使用Redis和memcached提升查询新跟那个,从而提高接口性能。 7.分库分表。当系统发展到一定阶段,用户并发量大,会有大量的数据库请求,需要占用大量的数据库连接,同时会带来磁盘IO的性能瓶颈问题,或者数据库表数据非常大,SQL查询即使走了索引,也很好使。这时,可以通过分库分表解决。分库用于解决数据库连接资源不足问题,和磁盘IO的性能瓶颈问题。分表用于解决单表数据量太大,sql语句查询数据时,即使走了索引页非常耗时问题。 8.避免在循环中查询数据库。循环查询数据库,非常耗时,最好能在一次查询中获取所有需要的数据。

# 为什么阿里巴巴Java开发手册中强制要求使用包装类型定义属性

# 8招让接口性能提升100倍

池化思想 如果你每次需要用到线程,都去创建,就会有增加一定的耗时,而线程池可以重复利用线程,避免不必要的耗时。 比如TCP三次握手,它为了减少性能损耗,引入了Keep-Alive长连接,避免频繁的创建和销毁连接。

拒绝阻塞等待 如果你调用一个系统B的接口,但是它处理业务逻辑,耗时需要10s甚至更多。然后你是一直阻塞等待,知道系统B的下游接口返回,再继续你的下一步操作吗?这样显然不合理。 参考IO多路复用模型。即我们不用阻塞等待系统B的接口,而是先去做别的操作。等系统B的接口处理完,通过事件回调通知,我们接口收到通知再进行对应的业务操作即可。

远程调用由串行改为并行 比如设计一个商城首页接口,需要查商品信息,营销信息,用户信息等等。如果是串行一个一个查,那耗时就比较大了。这种场景是可以改为并行调用的,降低接口耗时。

锁粒度避免过粗 再高并发场景,为了防止超卖等情况,我们经常需要加锁来保护共享资源。但是如果加锁的粒度过粗,是影响接口性能的。 不管事synchronized还是redis,只需要再共享临界资源加锁接口,不涉及共享资源的,就不必加锁。

耗时操作,考虑放到异步执行 耗时操作,考虑异步处理,这样可以降低接口耗时。比如用户注册成功后,短信邮件通知,是可以异步处理的。

使用缓存 把要查的数据,提前放好到缓存里面,需要时,直接查缓存,避免去查数据库或计算的过程。

提前初始化到缓存 预取思想很容易理解,就是提前把要计算查询的数据库,初始化到缓存。如果你在未来某个事件需要用到某个经过复杂计算的数据库,才实时去计算的话,可能耗时比较大。这时候,我们可以采用预取思想,提前把将来可能需要的数据计算好,放到缓存中,等需要的时候,去缓存取就行。这将大幅度提高接口性能。

压缩传输内容 压缩传输内容,传输报文变得更小,因此传输会更快。

编辑 (opens new window)
上次更新: 2024/11/14, 16:34:50
集合
集合-大彬

← 集合 集合-大彬→

最近更新
01
杂乱无章
12-25
02
集合-大彬
11-14
03
MySQL_xiaolingcode
11-14
更多文章>
Theme by Vdoing | Copyright © 2023-2025 qouson
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式