Java基础知识整理(面试1)

编程入门 行业动态 更新时间:2024-10-11 17:20:46

Java<a href=https://www.elefans.com/category/jswz/34/1769428.html style=基础知识整理(面试1)"/>

Java基础知识整理(面试1)

JAVA语言的特性

Java是一门面向对象的编程语言,Write once, run anywhere,一次编写、到处运行。Java是通过JVM(Java虚拟机)实现跨平台的特性,Java代码通过编译后会生成.class 文件(即:字节码文件),不同平台下编译生成的字节码是一样的(实现经过编译的Java程序可以在任何带有JVM的平台上运行的核心原因之一),Java虚拟机负责将字节码文件翻译成对应特定平台下的机器码,通过JVM翻译成机器码之后才能被运行,不同平台由JVM翻译成的机器码是不一样的,是基于各平台的架构结构决定。Java是一个强类型语言,它允许扩展编译时检查潜在类型不匹配问题的功能,且配备有完善的异常处理机制,保障了对程序中隐含异常的稳健处理逻辑。

Java是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object。Java 没有指针,它的引用可以理解为安全指针(因此才有npe,且npe是RunTimeException)。Java 仅支持单继承,不支持多重继承,可以通过实现多个接口来实现多继承。Java 支持自动垃圾回收(JVM垃圾回收)。

Java是一种混合型或者半编译型语言(编译+解释),源代码到字节码的部分是通过编译,而JVM通过将字节码转换为机器码的阶段是解释运行。

JAVA三大件:JDK、JRE和JVM

JVM

Java Virtual Machine,即Java 虚拟机。Java 能够跨平台运行的核心在于 JVM。JAVA程序通过编译生成.class的类文件(字节码文件),这种字节码文件不能直接与操作系统进行交互,需要通过JVM将字节码文件翻译成对应机器环境的机器码进行执行。

JRE

Java Runtime Environment,即Java 运行时环境。由JVM 和 Java 核心类库组成(JVM在JRE中),所有的Java程序必须要在JRE才能运行,单纯运行JAVA程序只需要安装JRE即可。

JDK

Java Development Kit,即Java 开发工具包(JDK中集成了JRE,运行是开发的一部分)。包含了部分实用性的JAVA工具(jinfo,jps,jstack,jmap等)以及编译器(javac)和调试器。

JAVA三大特性:封装、继承和多态

封装

封装就是将类的成员/属性信息隐藏在类的内部,外部程序不允许直接对这部分信息进行访问,而是通过该类暴露的方法实现对隐藏信息的操作和访问(get/set方法)

继承

继承就是子类不仅可以继承父类的属性和行为,并能扩展新的能力,子类拥有父类非 private 的属性和方法,并可以拥有自己的属性和方法以及重新实现父类方法。

多态

同一个行为具有多个不同表现形式的能力

1:静态多态性:即重载方法实现,相同的方法有不同的参数,可以基于参数的不同,做出不同的处理决策。

2:动态多态性:即在子类中重写父类的方法,在运行期间判断所引用对象的实际类型,基于实际类型调用相应的实现方法。

备注:

1:构造器 Constructor 是否可被 override?

由于父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override(重写),但是可以 overload(重载)

2:重载和重写的区别?

重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。

重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。

3:调用子类构造方法之前先调用父类的无参构造,为了帮助子类做初始化工作

4:每个类即便没有显示声明构造方法也会隐式声明一个无参构造

面向对象编程的六大原则

1、单一对象职责:对象设计要求独立,不能设计万能对象(模块、类库、程序集)。

2、开闭原则:开放扩展,封闭修改,当项目需求发生变更时,要尽可能的不去对原有的代码进行修改,而在原有的基础上进行扩展。

3、里式替换原则:子类能够完全替代父类,反之则不行。比如IA的实现为A,因为项目需求变更,现在需要新的实现,直接在容器注入处更换接口即可。

4、迪米特法则:高内聚,低耦合。尽量不要依赖细节。也叫最小原则或最小耦合。当两个类进行交互的时候,会产生依赖。而迪米特法则就是建议这种依赖越少越好。

5、依赖倒置原则:高层模块不应该直接依赖于底层模块的具体实现,而应该依赖于底层的抽象。接口和抽象类不应该依赖于实现类,而实现类依赖接口或抽象类。

6、接口隔离原则:一个对象和另外一个对象交互的过程中,依赖的内容最小。也就是说接口设计大小要适中,当过大则导致污染,过小则导致调用麻烦。在遵循对象单一职责的情况下,尽量减少接口的内容。

Java的基本数据类型

基本数据类型

基本数据类型是CPU可以直接进行运算的类型

简单类型booleanbytecharshortintlongfloatdouble
数据类型布尔类型整数类型字符类型整数类型整数类型整数类型浮点数类型浮点数类型
包装类型BooleanByteCharShortIntegerLongFloatDouble
字节数41224848
数据范围false-128-32768-2147483648-9223372036854775808
true12732767214748364792233720368547758073.4E+381.79E+308
二进制位数18161632643264

备注:

1:一个字节就是一个8位二进制数,1byte = 8bit。它的二进制表示范围00000000~11111111,换算成十进制是0~255,换算成十六进制是00~ff。

2:Java语言对布尔类型的存储并没有做规定,因为理论上存储布尔类型只需要1 bit,但是通常JVM内部会把boolean表示为4字节整数,也有1个字节的实现,基于JVM。

3:计算机中保存的小数其实是十进制的小数的近似值并不是准确值,因此代码中不可以使用浮点数来表示金额等重要的指标,建议使用BigDecimal或者Long来表示金额。

4:由于集合容器操作的存在,基本类型也需要具有对象的特征。因此包装类型就是将基本类型包装起来,使得它具有对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

5:Integer中存在IntegerCache做一个cache,默认Integer cache 的下限是-128,上限默认127。在范围内的值会从cache中取对应的Integer返回。

自动装箱和拆箱

装箱

将基础类型转化为包装类型

拆箱

将包装类型转化为基础类型

编译器实现自动装箱和拆箱的场景如下

1:实现赋值操作(装箱或拆箱)

2:进行加减乘除混合运算 (拆箱)

3:进行>,<,==比较运算(拆箱)

4:调用equals进行比较(装箱)

5:集合类添加基础类型数据时(装箱)

JAVA中的数组

一组类型相同的变量的集合就是数组类型

特点:

1:Java中数组一旦创建后,大小就不可改变

2:Java创建的数组所有元素初始化为默认值,整型都是0,浮点型是0.0,布尔型是false

3:访问数组中的某一个元素,需要使用索引,数组索引从0开始,如果超过数组范围则会报错: java.lang.ArrayIndexOutOfBoundsException

4:数组类的父类就是Object类,因此数组是对象,所以数组是一种引用类型

JAVA中的final系列关键字

final

final 用于修饰属性、方法和类

1:final 修饰的类叫最终类,该类不能被继承

2:final 修饰的方法不能被重写

3:final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改

finally

finally 是异常处理语句结构的一部分,一般以try-catch-finally出现,finally代码块表示总是被执行。当然,并不是 finally 一定会执行

1:程序未执行到try代码块则不会执行到 finally

2:当一个线程在执行 try 语句块或者 catch 语句块时被打断(interrupted)或者被终止(killed),与其相对应的 finally 语句块可能不会执行。例如代码中调用了System.exit()进行了退出。

3:CPU关闭服务宕机

4:在finally语句块中发生了异常

finalize

finalize 是Object类的一个方法,该方法一般由垃圾回收器来调用,当我们调用System.gc()方法的时候,由垃圾回收器调用finalize()方法,回收垃圾,JVM并不保证此方法总被调用

JAVA中的万物皆可String

String为什么不可变

public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {/** JDK9+ */@Stableprivate final byte[] value;/** JDK9之前 */private final char value[];private final byte coder;/** Cache the hash code for the string */private int hash; // Default to 0......
}

String对象在内部就是一个个字符/字节存储在这个value数组里面(JDK9之前数组是Char,JDK9开始使用byte),value数组用final修饰,final 修饰的变量,值不能被修改。因此value不可以指向其他对象。并且String类内部所有的字段都是被private修饰私有的,且没有对外提供修改内部数据状态的方法,因此value数组值不能改变。因此可以见得,String是不可变的。且String类不能被继承重写,因为类上也有final关键字标识。

备注:JDK9+将String的底层实现由char[]改成byte[]的原因主要是为了节约String占用的内存,因为大部分String都是Latin-1字符,只需要1个字节,在JDK9之前,JVM中String使用char数组存储,每个char占2个字节,所以即使字符串只需要1字节,它也要按照2字节进行分配,浪费了一半的内存空间。在JDK9+的版本,会先判断字符串是不是Latin-1字符,如果是则安装1字节进行内存分配,如果不是则按照2字节分配。这样便于提高内存的使用率,减少GC的次数,提升运行效率。但是国内语言环境都是中文字符串居多,使用UTF16编码,因此默认开销规格是2字符,因此此处char[]和byte[]的实现是没有实质性性能提升影响的。

String设计成不可变的原因

1、线程安全:同一个字符串实例可以被多个线程共享,因此设计成不可变可以保障线程安全

2、支持hash映射和缓存:String不可变可以保证 hash 值固定,无需重新计算

3、安全性:由于String不可变可以保证在内存中的安全,否则会出现篡改导致安全隐患

4、字符串常量池优化:String对象创建之后,会缓存到字符串常量池中,下次需要创建同样的对象时,可以直接返回缓存的引用,节约开销无需重复创建对象

备注:substring, replace, replaceAll这些String类的操作的方法,会在堆内存中创建了一个新的对象,然后其value数组引用指向不同的对象。因此每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象并不会改变本身的String对象。

String, StringBuffer 和 StringBuilder对比

基础对比
-StringStringBufferStringBuilder
可变性不可变可变可变
线程安全性线程安全线程安全非线程安全
实现方式数组数组数组
StringBuffer与StringBuilder对比

StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,使用 char[]/byte[] 保存字符串,由于数组没有final 修饰,因此都是可变的。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,以此来保障线程安全。StringBuilder 并没有对方法进行加同步锁,因此是非线程安全的。

StringJoiner API的实现

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

共有两个构造方法

// 分隔符 前缀 尾缀
StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
// 分隔符
StringJoiner(CharSequence delimiter)

Collectors.joining(","),底层就是通过StringJoiner实现的

String的长度限制

String类提供了一个length方法,返回值为int类型,由于int的取值上限为2^31 -1,所以理论上String的最大长度为2^31 -1(堆内)。但是在堆内和常量池的存储有区别,在常量池中Java的字符串以CONSTANT_Utf8类型表示,length此时的类型是u2(无符号的16位整数),此时最大长度可以做到2^16-1 即 65535。由于javac编译器做了限制,需要length < 65535,因此字符串常量在常量池中的最大长度是65535 - 1 = 65534

备注:

1、Java字面量:指在代码中直接表示特定值的常量或值,这些值可以是整数、浮点数、布尔值、字符、字符串或空值,Java编译器在编译时将这些字面量直接嵌入到程序中。

2、什么情况下字符串会存储在常量池:当通过字面量进行字符串声明时,该字符串在编译之后会以常量的形式进入到常量池。

字符型常量和字符串常量与字符串常量池

字符型常量占用2个字节,由单引号引起的一个字符,等同于一个整型值(ASCII)可以参与到表达式运算

字符串常量占用若干字节,由双引号引起的若干字符,代表一个地址值(字符串在内存中存放的位置)

字符串常量池

JVM 为了提高性能和减少内存开销引入了字符串常量池(Constant Pool Table)的概念,其内部保存着所有字符串字面量(所有字面量在编译时期就确定),字符串常量池相当于给字符串开辟一个常量池空间类似于缓存区,专门用来存储字符串常量。在创建字符串时JVM首先会检查字符串常量池,如果该字符串已经存在池中则返回其引用,如果不存在则创建此字符串并放入池中并返回其引用。

常量池与JDK版本

字符串常量池会基于 JDK 版本的变化而产生一些内存布局上的变化

JDK版本方法区的实现运行时常量池所在的位置
JDK1.6&JDK1.6-PermGen space(永久代)PermGen space(永久代)
JDK1.7PermGen space(永久代)Heap(堆)
JDK1.8&JDK1.8+Metaspace(元空间)Heap(堆)

JDK1.7及之前运行时数据区的内存布局

(JDK1.7还没有完全弃用永久代,字符串常量池被单独拿到堆,其余运行时常量池的部分还在方法区)

JDK1.8及之后运行时数据区的内存布局

备注:

1:JVM内存模型在JDK1.8的调整:JDK 1.8 与 JDK 1.7 最大的区别是 JDK 1.8 将永久代取消,并设立了元空间。官方给的说明是由于永久代内存经常不够用或发生内存泄露,会爆出 java.lang.OutOfMemoryError: PermGen 的异常,且永久代会给GC带来不必要的复杂度降低GC效率。所以把将永久区废弃而改用元空间了,改为使用本地内存空间。

2:永久代与方法区:方法区是JVM的一个规范,主要用于存储类的信息、常量池、方法数据、方法代码等,逻辑上属于堆的一部分,但是为了区分通常叫非堆区。永久代(Permanent Generation space),通常称为 PermGen ,是指内存的永久保存区域,是JDK1.7及之前版本 HotSpot 虚拟机基于JVM规范对方法区的一个落地实现(JDK1.8后被移除)。

3:元空间(Metaspace):元空间是JDK1.8及之后,HotSpot虚拟机对方法区的新实现,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。

4:JVM常量池相关知识:

new String("hello world")与"hello world"的解析

1:String str = "hello world" 解析

JVM 先在常量池中查找有没有已经存在的 "hello world" 对象,如果有则就让变量 str 指向该 "hello world" 对象 (返回地址)。如果没有则在常量池中新建一个 "hello world" 对象,并让变量 str 指向在常量池中刚刚新建的这个 "hello world" 对象(返回地址)。

2:String str = new String("hello world") 解析

JVM 先在常量池中查找有没有已经存在的 "hello world" 对象,如果字符串常量池中当前没有 “hello world” 这个字符串对象,则会创建两个字符串对象。“hello world”属于字符串字面量,编译期间会在字符串常量池中创建一个字符串对象,指向 “hello world” 字符串字面量。同时 new 的方式会在堆中创建一个字符串对象 "hello world",最后将堆中的这个对象的地址返回赋给变量 str 。若常量池中存在 "hello world" 对象,则直接只在堆中创建 "hello world" 对象,然后将堆中的这个对象的地址返回赋给变量 str。

3:String str = "h"+"e"+"l"+"l"+"o"+" "+"w"+"o"+"r"+"l"+"d"解析

Java 编译器会对字符串常量直接相加的表达式进行优化,不等到运行期去进行加法运算,在编译时就去掉了加号,直接将其编译成一个这些常量相连的结果。所以当前操作相当于直接定义一个 "hello world" 字符串,创建一个对象,在常量池中,并让变量 str 指向在常量池中刚刚新建的这个 "hello world" 对象(返回地址)。

备注:

文章引用:别再问我 new 字符串创建了几个对象了!我来证明给你看!

JAVA中的Object详解

Object常用方法有:toString()、equals()、hashCode()、clone()等

toString()方法

默认toString()输出对象地址,可以通过重写来实现自定义的逻辑。

equals与==的区别

==

如果是基本类型,判断它们值是否相等
如果是引用对象,判断两个对象指向的内存地址是否相同

equals

如果是object对象,默认equals比较的是引用的内存地址值

如果类重写过equals方法,则基于重写方法判断两个对象是否相等,例如:字符串,表示判断字符串内容是否相同

hashCode()方法

hashCode方法是获取哈希码,也称为散列码,是把对象相关的信息映射成一个哈希值,默认的实现hashCode值是根据内存地址换算出来的。哈希码的作用是确定该对象在哈希表中的索引位置,散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。hashcode方法主要是用来提升对象比较的效率,先进行hashcode()的比较,如果不相同,那就不必在进行equals的比较,这样就大大减少了equals比较的次数,提升执行效率。在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数。因此,两个对象的equals()相等,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。

hashCode 与 equals

equals与hashcode的关系

1:两个对象的hashCode相同,这两个对象不一定相同

2:两个对象equals返回ture,则两个对象的hashCode值一定相同,两个对象一定相同

3:equals 方法被覆盖过,则 hashCode 方法也必须被覆盖

4:hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则同一个 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

为什么重写 equals 时必须重写 hashCode 方法

相等的对象必须具有相等的散列码,为了保证equals()方法返回true的情况下hashcode值也要一致,如果重写了equals()没有重写hashcode(),就会出现两个对象相等但hashcode()不相等的情况

clone

Java赋值是复制对象引用,如果想要得到一个对象的副本,使用赋值操作是无法达到目的的。Object对象有个clone()方法,实现了对象中各个属性的复制,可见范围是protected的。

使用方式

1:覆盖clone()方法,可见性提升为public

2:实现Cloneable接口,属于标记型约定接口。调用clone方法时,会判断类有没有实现Cloneable接口,没有实现Cloneable的话会抛异常CloneNotSupportedException

notify() 和 notifyAll() 以及 wait()

wait()

线程需要获取到对象锁之后,才可以调用wait()。当调用对象的wait()方法后,当前线程会释放对象锁,进入等待状态。直到其他线程调用该对象的notify()/notifyAll()唤醒,或者在调用wait()时传入等待超时时间,在超时时间后自动唤醒。

wait()和sleep()对比

1:sleep() 方法是Thread包下的方法,wait() 方法是Object包下的方法

2:sleep() 方法是主动唤醒,因为必须要传递一个超时时间的参数,wait() 方法既可以通过传入超时时间主动唤醒,也可以不传递参数被动唤醒。sleep() 方法线程会进入 TIMED_WAITING 有时限等待状态,而调用无参数的 wait() 方法,线程会进入 WAITING 无时限等待状态

3:wait() 方法会主动的释放锁资源,而 sleep() 方法则不会

4:sleep() 方法不依赖于同步器synchronized,但是wait() 需要依赖synchronized关键字,否则抛出 IllegalMonitorStateException 异常

notify()

唤醒在此对象上等待的单个线程,选择是任意性的,使它获得该对象上的锁。当这个线程运行完毕以后释放对象上的锁,如果没有再次使用 notify 语句,即便该对象已经空闲,其他 wait 状态等待的线程仍会处在wait状态

notifyAll()

唤醒所有正在等待该对象的线程,使所有原来在该对象上等待被 notify 的线程统统退出 wait 状态,转而变成等待该对象上的锁,一旦该对象被解锁这些线程就会去竞争

创建对象的几种方式

1:使用 new 语句创建对象

2:使用反射机制,利用诸如Class.newInstance() 创建对象 或 Constructor 对象的newInstance()

3:调用对象的clone()方法

4:利用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法

5:使用Unsafe

对象实体与对象引用

new 创建的对象实例会放在堆内存中,对象引用执行对象实例,对象引用在栈内存中。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球),一个对象实例则可以有n个引用指向它(可以用n条绳子系住一个气球)

备注:对象相等,比较的是内存中存放的内容是否相等,指向对象的引用相等,比较的是指向的内存地址是否相等

JAVA中的反射

Java 的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法,并且对于任意一个对象,都能够调用它的任意一个方法,这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制

Class 对象

Java在运行时识别对象和类的信息的方法

1:RTTI(Run-Time Type Identification)运行时类型识别,其作用是在运行时识别一个对象的类型和类的信息,在编译期明确类的信息

2:反射机制,在运行时发现和使用类的信息

Class类的对象作用是运行时提供或获得某个对象的类型信息,每个类都有一个Class对象,每当编译一个新类就产生一个Class对象(JVM动态加载,保存在一个同名的.class文件中)。

获取反射的三种方式

1:使用 Class.forName 静态方法

2:使用 类.class 方法

3:使用实例对象的 getClass() 方法

Class.forName和ClassLoader的区别

1:Class.forName和ClassLoader都可以对类进行加载

2:ClassLoader负责加载 Java 类的字节代码到 Java 虚拟机中

3:Class.forName其实是调用了ClassLoader,对加载的类进行初始化化,Class.forName有参数控制是否对类进行初始化

反射的应用

1:JDK动态代理底层就是依赖反射实现

2:JDBC连接数据库

3:Spring 通过 XML 配置模式装载 Bean

反射存在的问题

1:Java反射的性能并不好,原因主要是编译器没法对反射相关的代码做优化

2:反射可以获取类中的域、方法、构造器,修改访问权限,存在安全隐患

泛型和类型擦除

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,解决不确定具体对象类型的问题(JDK5中开始引入)。Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

泛型通配符

1:? 表示不确定的 java 类型

2:T (type) 表示具体的一个java类型

3:K V (key value) 分别代表java键值中的Key Value

4:E (element) 代表Element

5:无边界通配符  <?>

6:上边界限定通配符 <? extends E>

7:下边界限定通配符 <? super E>

类型擦除

Java泛型是使用擦除实现的,不管是 ArrayList<Integer>() 还是 ArrayList<String>(),在编译生成的字节码中都不包含泛型中的类型参数,即都擦除成了ArrayList,也就是被擦除成“原生类型”,这就是泛型擦除。

实现原理:Java泛型在编译期完成,它是依赖编译器实现的。编译器主要做了两件事,第一个set()方法的类型检验,第二个是get()方法的类型转换,编译器插入了一个checkcast语句

泛型的限制与局限

1:泛型不能用基本类型实例化类型化参数

2:运行时类型查询只适用于原始类型

3:不能创建参数化类型的数组也不能实例化类型变量

4:使用泛型接口时,需要避免重复实现同一个接口

5:受检查异常的消除需要使用`@SuppressWamings("unchecked")`

6:定义API返回报文时,尽量使用泛型

值传递和引用传递

Java中不存在引用传递,只有值传递,引用类型传递的是地址

值传递:值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量

引用传递:对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身,两者指向同一片内存空间,所以对引用对象进行操作会同时改变原对象

备注:String与8种基本数据类型的包装类,BigInteger、BigDecimal虽然都是引用类型,但是在函数调用的时候,操作形参不会影响实参,操作形参相当于重新创建对象不会影响原实参。原因是因为这些都是依赖final的不可变类,不可变的意思是每次更换值都会重新生成对象并赋给引用,也保证了天然的线程安全

深拷贝和浅拷贝

浅拷贝

复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,即拷⻉对象和原始对象的引⽤类型引用同⼀个对象

@Data
public class Person {private String name;
}
@Data
public class Merle implements Cloneable {private String name;private Person owner;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}public static void main(String[] args) throws CloneNotSupportedException {Merle merle = new Merle();merle.setName("陨石边牧");Person owner = new Person();owner.setName("边牧主人");merle.owner = owner;Merle cloneMerle = (Merle) merle.clone();owner.setName("clone边牧主人");System.out.println(cloneMerle.owner.getName());}
}

深拷贝

将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,拷贝对象和原始对象的引用类型引用不同的对象

@Data
public class Person implements Cloneable{private String name;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
@Data
public class Gold implements Cloneable {private String name;private Person owner;@Overrideprotected Object clone() throws CloneNotSupportedException {Gold gold = null;gold = (Gold) super.clone();gold.owner = (Person) owner.clone();return gold;}public static void main(String[] args) throws CloneNotSupportedException {Gold gold = new Gold();gold.setName("金边边牧");Person owner = new Person();owner.setName("边牧主人");gold.owner = owner;Gold cloneGold = (Gold) gold.clone();owner.setName("克隆边牧主人");System.out.println(cloneGold.owner.getName());}
}

对象克隆

1:实现 Cloneable 接口,重写 clone() 方法

2:Object 的 clone() 方法是浅拷贝,即类中属性有自定义引用类型,只拷贝引用,不拷贝引用指向的对象

3:对象的属性的Class 也实现 Cloneable 接口,在克隆对象时也手动克隆属性,完成深拷贝

4:结合序列化(JDK java.io.Serializable 接口、JSON格式、XML格式等),完成深拷贝

类的实例化顺序

1:父类静态属性/静态代码块

2:子类静态属性/静态代码块

3:父类非静态属性/非静态代码块

4:父类构造方法

5:子类非静态属性/非静态代码块

6:子类构造方法

Java的异常相关详解

阻止当前方法或作用域继续执行的情况就是异常

异常的结构层次

所有异常的父类都是Throwable

Error

编译时或者系统错误,error是无法处理的。即错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。

Exception

程序本身可以处理的异常,分为运行时异常和非运行时异常。

运行时异常,即RuntimeException类及其子类异常,Java编译器不会检查它,即便不做处理也可以编译通过,一般运行时异常都是有程序逻辑错误引起的

非运行时异常,是RuntimeException及其子类以外的异常,是必须进行处理的异常,如果不处理,程序就不能编译通过

检查异常(checked exceptions)和非检查的异常(unchecked exceptions)

检查异常就是编译器要求开发人员必须处置的异常(编译器要求必须要对代码执行try…catch,或者执行throws exception),对于检查异常是必须处理或者必须捕获或者必须抛出。除了RuntimeException 与其子类,以及错误(Error),其他的都是检查异常。

非检查异常,是编译器不要求强制处置的异常,对此类异常可以捕获或继续抛出也可以不处理。对于RuntimeException与其子类,以及错误(Error)都是非检查异常。

throws、throw的区别

throw

throw关键字作用是抛出一个 Throwable类型的异常,在函数体中使用

throws

throws用于声明一个方法可能抛出的异常,在方法签名中使用

接口和抽象类的区别

1:抽象类需要子类继承,接口需要子类实现

2:抽象类可以有构造方法,接口不存在构造方法

3:接口中的实例变量默认是public static final的,而抽象类中不一定

4:一个类仅可以继承一个抽象类,但是可以实现多个接口,实现接口的话要实现接口的所有方法,而抽象类不一定

5:抽象类的关键字是abstract,接口的关键字是interface

6:抽象级别(从高到低):接口>抽象类>实现类

7:接口只能做方法声明,抽象类中可以作方法声明,也可以做方法实现

8:接口的方法默认是 public,抽象类可以有非抽象的方法

9:抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范

JAVA中的注解

Annotation(注解)是 Java 提供的一种对元程序中元素关联信息和元数据(metadata)的途径和方法。Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的 Annotation 对象,然后通过该 Annotation 对象来获取注解中的元数据信息。

标准元注解

@Target

修饰的对象范围,可被用于 packages、types(类、接口、枚举、Annotation 类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在 Annotation 类型的声明中使用了 target 可更加明晰其修饰的目标

@Retention

用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

source::在源文件中有效

class:在class文件中有效

runtime:在运行时有效

@Documented

描述-javadoc

@Inherited

 阐述了某个被标注的类型是被继承的,如果一个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该class 的子类。 

JAVA中的序列化

序列化

把Java对象转换为字节序列的过程,在 Java 中只要一个类实现了 java.io.Serializable 接口,那么它就可以被序列化

反序列

把字节序列恢复为Java对象的过程

static静态变量不参与序列化

序列化是针对对象而言的,而 static 属性优先于对象存在,随着类的加载而加载,所以不会被序列化

transient关键字可以使变量不参与序列化

变量修饰符,可以阻止修饰的字段被序列化到文件中。如果用transient声明一个实例变量,当对象存储时,它的值不需要维持,因此在序列化的时候其值会被忽略,在被反序列化后, transient 变量的值被设为初始值

serialVersionUID的意义

序列化版本号ID,每一个实现 Serializable 接口的类,都有一个表示序列化版本标识符的静态变量或者默认等于1L或者等于对象的哈希码,JAVA序列化的机制是通过判断类的 serialVersionUID 来验证版本是否一致。

JVM会把传来的字节流中的 serialVersionUID 和本地相应实体类的serialVersionUID 进行比较,如果相同,反序列化成功,如果不相同,就会抛出InvalidClassException 异常。如果显示指定了 serialVersionUID,JVM 在序列化和反序列化时仍然都会生成一个 serialVersionUID,但值为类中显示指定的值,这样在反序列化时新旧版本的 serialVersionUID 是一致的。

JAVA中的动态代理

JDK动态代理

JDK原生的动态代理是不需要任何外部依赖,但是它只能基于接口进行代理而不能基于类,JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler实现处理

CGLIB动态代理

cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理,通过继承的方式进行代理(主要是对指定的类生成一个子类,覆盖其中的方法),因此无论目标对象没有没实现接口都可以代理,但是无法处理修饰符为final的情况

更多推荐

Java基础知识整理(面试1)

本文发布于:2024-03-15 01:10:56,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1737776.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:基础知识   Java

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!