Java基础
什么是面向对象
OOP是一种常见的编程范式,是一种编程模式的高级抽象,在面向对象编程中,所有数据结构都会被抽象成一个具体的对象,对象的特征被抽象成属性,对象行为被抽象成方法
java语言的特点(封装、继承、多态、抽象、平台独立性和移植性、强类型语言、异常处理)
- 面向对象(封装,继承,多态);
- 平台无关性,平台无关性的具体表现在于,Java 是“一次编写,到处运行(Write Once,Run any Where)”的语言,因此采用 Java 语言编写的程序具有很好的可移植性,而保证这一点的正是 Java 的虚拟机机制。在引入虚拟机之后,Java 语言在不同的平台上运行不需要重新编译。
- 支持多线程。C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持;
- 支持 JIT 编译。JIT 是 Just-In-Time 的缩写,指的是即时编译器,它可以在程序运行时将字节码转换为本地机器码来提高程序运行速度。
--------------------------------------------------------------------------------------------------------------------------
- Java具有稳健性:Java是一个强类型语言,它允许扩展编译时检查潜在类型不匹配问题的功能。Java要求显式的方法声明,它不支持C风格的隐式声明。这些严格的要求保证编译程序能捕捉调用错误,这就导致更可靠的程序。
- 异常处理是Java中使得程序更稳健的另一个特征。
- 垃圾回收机制
JVM、JDK 和 JRE 有什么区别?
- JVM:Java Virtual Machine,也就是 Java 虚拟机,是 Java 实现跨平台的关键所在,针对不同的操作系统,有不同的 JVM 实现。JVM 负责将 Java 字节码转换为特定平台的机器码,并执行。
- Java 程序是通过 Java 虚拟机在系统平台上运行的,只要该系统可以安装相应的 Java 虚拟机,该系统就可以运行 java 程序。
- JRE:Java Runtime Environment,也就是 Java 运行时环境,包含了运行 Java 程序所必需的库,以及 Java 虚拟机(JVM)。
- JDK:Java Development Kit,是一套完整的 Java SDK(软件开发工具包),包括了JRE 以及编译器(javac)、Java 文档生成工具(Javadoc)、Java 调试器等开发工具。为开发者提供了开发、编译、调试 Java 程序的一整套环境。
简单来说,JDK 包含 JRE,JRE 包含 JVM。
什么是字节码?采用字节码的好处是什么?
所谓的字节码,就是 Java 程序经过编译之后产生的.class 文件,字节码能够被虚拟机识别,从而实现 Java 程序的跨平台性。
Java 程序从源代码到运行主要有三步:
- 编译:将我们的代码(.java)编译成虚拟机可以识别理解的字节码(.class)
- 解释:虚拟机执行 Java 字节码,将字节码翻译成机器能识别的机器码
- 执行:对应的机器执行二进制机器码
----------------------------------------------------------------------------------------------------------------------
- 编译型语言是指编译器针对特定的操作系统将源代码一次性翻译成可被该平台执行的机器码;
- 解释型语言是指解释器对源程序逐行解释成特定平台的机器码并立即执行。
面向对象主要有什么特点
Java面向对象的三大特性包括:封装、继承、多态:
- 封装:封装是指将对象的属性(数据)和行为(方法)结合在一起,对外隐藏对象的内部细节,仅通过对象提供的接口与外界交互。封装的目的是增强安全性和简化编程,使得对象更加独立。
- 继承:继承允许一个类(子类)继承现有类(父类或者基类)的属性和方法。以提高代码的复用性,建立类之间的层次关系。 同时,子类还可以重写或者扩展从父类继承来的属性和方法,从而实现多态。
- 多态:多态允许不同类的对象对同一消息做出响应,但表现出不同的行为。多态性可以分为编译时多态(重载)和运行时多态(重写)。
-
- 多态的目的是为了提高代码的灵活性和可扩展性,使得代码更容易维护和扩展。比如说动态绑定,允许在程序在运行时再确定调用的是子类还是父类的方法。
- 多态的前置条件有三个:
-
-
- 子类继承父类
- 子类重写父类的方法
- 父类引用指向子类的对象
-
五大基本原则 (单一职责、开放封闭原则、里氏替换原则、依赖倒置原则、接口隔离原则)
单一职责原则,开放封闭原则,里氏替换原则,依赖倒置原则,接口隔离原则
- 单一职责原则(SRP):一个类,最好只做一件事,只有一个引起它的变化。
- 开放封闭原则(OCP):软件实体应该是可扩展的,而不可修改的。
- 里氏替换原则(LSP):子类必须能够替换其父类。(继承和多态的综合体现)
-
- 类A是类B的父类,在进行调用的时候,类A可以引用类B,但是反过来不行。类A就是对外提供一个接口,具体的实现在类B中,在子类B中通过覆写父类A的方法实现新的方式支持同样的职责。
- 依赖倒置原则(DIP):依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
- 接口隔离原则:使用多个小的专门的接口,而不要使用一个大的总接口。接口中定义属性和需要子类实现的方法,实现类必须完全实现接口的所有方法、属性。
目的:
- 避免引用接口的类,需要实现用不到的接口方法、属性。
- 避免当接口修改的时候,有一连串的实现类需要更改。
使用多重继承分离,通过接口多继承来实现客户的需求
什么是接口
接口(Interface)是一种定义了一组行为规范的抽象类型。它通常用于定义对象的行为,但不实现这些行为。接口为实现它的类提供了一个契约,确保这些类提供了接口声明的所有方法的具体实现。
自动类型转换、强制类型转换?看看这几行代码?
Java 所有的数值型变量可以相互转换,当把一个表数范围小的数值或变量直接赋给另一个表数范围大的变量时,可以进行自动类型转换;反之,需要强制转换。
short s1 = 1; s1 = s1 + 1;
对吗?short s1 = 1; s1 += 1;
对吗?
对于 short s1 = 1; s1 = s1 + 1;编译出错,由于 1 是 int 类型,因此 s1+1 运算结果也是 int 型,需要强制转换类型才能赋值给 short 型。
而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short(s1 + 1);其中有隐含的强制类型转换
向上转型,向下转型(存在强制类型转换异常) 引用数据类型
子类对象赋值给父类引用为向上转型,父类引用转化为子类引用为向下转型
如果父类引用对象是父类本身,那么在向下转型的过程中是不安全的,编译不会出错,但是运行时会出现我们开始提到的 Java 强制类型转换异常,一般使用instanceof 运算符来避免出此类错误
Animal animal = new Dog(); // 向上转型,子类对象赋值给父类引用Dog dog = (Dog) animal; // 向下转型,将父类引用转换为子类引用 这个地方不是新建new 是两个引用的指向相同
向上转型不用强转,但有类型丢失问题,向下转型要强转,但是有安全问题(将非子类实例转换为子类引用,导致ClassCastException异常,可能导致程序崩溃,或产生不可预料的行为)
分析:
父类引用指向子类对象为向上转型,向上转型就是把子类对象直接赋给父类引用,不用强制转换。使用向上转型可以调用父类类型中的所有成员,不能调用子类类型中特有成员, 最终运行效果看子类的具体实现
需要注意的是如果父类引用对象指向的是子类对象(实例),那么在向下转型的过程中是安全的,也就是编译是不会出错误。
但是如果父类引用对象是父类本身,那么在向下转型的过程中是不安全的,编译不会出错,但是运行时会出现我们开始提到的 Java 强制类型转换异常,一般使用instanceof 运算符来避免出此类错误
Animal animal = new Dog(); // 向上转型,子类对象赋值给父类引用
Dog dog = (Dog) animal; // 向下转型,将父类引用转换为子类引用 这个地方不是新建new 是两个引用的指向相等了
//animal是子类实例,向下转型安全
---------------------------------------------------------
Animal animal = new Animal(); // 父类引用指向父类对象
//animal是父类实例,向下转型不安全
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 向下转型,前提是animal确实是Dog类的实例
// 可以安全地使用子类引用来访问子类特有的属性和方法
} else {
// animal不是Dog类的实例,进行相应的处理
//执行该部分逻辑
}
java有几种基本数据类型
Java 中的基础数据类型有8 种,分别是 byte、short、int、long、float、double、boolean、 char
- 整数类型
byte: 占据 1 个字节,表示的范围是: -128~127
short 占据 2 个字节,表示的范围是: -32768 ~ 32767
int 占据 4 个字节,表示的范围是:-2147483648 ~ 2147483647
long 占据 8 个字节,表示的范围是:-9223372036854775808 ~ 9223372036854775807
- 浮点数类型
float占据 4 个字节
double占据 8 个字节
- 布尔类型
boolean 赋值时只能选择 true 或者 false,无法赋值其他的值。 1字节
- 字符类型
char: 占据 2 个字节 每个 char 只能存储一个字符,所以存储一些英文字符时会浪费一些空间。
在 Java8 之前,String 使用 char 数组来存储字符串,但是从 Java 9 以后,已经替换成 byte 数组了,因为更加灵活,而且存储的效率也更高。
------------------------------------------------------------------------------------------------
引用数据类型有哪些?
- 类(class)
- 接口(interface)
- 数组(
[]
)
自动拆箱(xxx.intValue()) 与 装箱(Integer.valueOf())
- 装箱:将基本类型用包装器类型包装起来 (Byte、Short、Integer、Long、Float、Double、Character、Boolean)。
int 转换为 Integer : Integer i=Integer.valueOf(10);
double 转换为 Double Double .valueOf()
- 拆箱:将包装器类型转换为基本类型
将包装类型(Integer)的对象转换为其对应的基础(int)值称为拆箱。
int n= i.intValue();
自动装箱和拆箱可以让开发人员编写更简洁的代码,使其更易于阅读
基本类型和包装类型的区别?
- 包装类型不赋值就是null,而基本类型有默认值且不是 是null.
- 包装类型可用于泛型,而基本类型不可以。
- 基本数据类型的局部变量存放在java虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被static修饰)存放在java虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有的对象实例(逃逸分析)都存在于堆中。
- 相比于对象类型,基本数据类型占用的空间非常小。
JIT 即时编译器 对象逃逸 将内存在栈上分配,随着方法的调用和结束,释放内存
包装类型的缓存机制了解么? [-128,127] Byte Short Integer Long
要比较Integer
对象的数值是否相等,应该使用equals
方法,而不是==
运算符
说一下 integer的缓存
Java的Integer类内部实现了一个静态缓存池,用于存储特定范围内的整数值对应的Integer对象。
默认情况下,这个范围是-128至127。当通过Integer.valueOf(int)方法创建一个在这个范围内的整数对象时,并不会每次都生成新的对象实例,而是复用缓存中的现有对象,会直接从内存中取出,不需要新建一个对象。
Integer 缓存的主要目的是优化性能和内存使用。对于小整数的频繁操作,使用缓存可以显著减少对象创建的数量。
new Integer(10) == new Integer(10) 相等吗
在 Java 中,使用new Integer(10) == new Integer(10)
进行比较时,结果是 false。
这是因为 new 关键字会在堆(Heap)上为每个 Integer 对象分配新的内存空间,所以这里创建了两个不同的 Integer 对象,它们有不同的内存地址。
当使用==运算符比较这两个对象时,实际上比较的是它们的内存地址,而不是它们的值,因此即使两个对象代表相同的数值(10),结果也是 false。
什么是重写(方法体具体实现不同,其他相同)和重载(方法名相同,参数数量、顺序不同)
(编译时多态)方法重载(Overloading)是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载
(运行时多态)方法重写(Overriding)是在子类存在方法与父类的方法的名字相同,而且参数的个数、顺序与类型一样,就称为重写 (两小一大:(返回值类型、抛出异常)、(访问权限))
--------------------------------------------------------------------------------------------------------------------------
- 方法重载发生在同一个类中,同名的方法如果有不同的参数(参数类型不同、参数个数不同或者二者都不同)。
- 方法重写发生在子类与父类之间,要求子类与父类具有相同的返回类型,方法名和参数列表,并且不能比父类的方法声明更多的异常,遵守里氏代换原则。
访问修饰符 public、private、protected、以及不写(默认)时的区别?
Java 中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
- default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。可以修饰在类、接口、变量、方法。
- private : 在同一类内可见。可以修饰变量、方法。注意:不能修饰类(外部类)
- public : 对所有类可见。可以修饰类、接口、变量、方法
- protected : 对同一包内的类和所有子类可见。可以修饰变量、方法。注意:不能修饰类(外部类)。
&和&&有什么区别 逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
&运算符有两种用法:短路与
、逻辑与
。
&&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是 true 整个表达式的值才是 true。
&&之所以称为短路运算是因为,如果&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&。
自增自减运算
符号在前就先加/减,符号在后就后加/减
int count = 0;
for(int i = 0;i < 100;i++)
{
count = count++; //临时变量存储
}
System.out.println("count = "+count);
输出 0
int autoAdd(int count){
int temp = count;
count = count + 1;
return temp;
}
float 是怎么表示小数的?
float
类型的小数在计算机中是通过 IEEE 754 标准的单精度浮点数格式来表示的
单精度浮点数占用 4 字节(32 位),这 32 位被分为三个部分:符号位、指数部分和尾数部分。
按照这个规则,将十进制数 25.125 转换为浮点数,转换过程是这样的:
- 整数部分:25 转换为二进制是 11001;
- 小数部分:0.125 转换为二进制是 0.001; 11001.001 1001001(除去隐含的
1.
) - 用二进制科学计数法表示:(25.125 = 1.001001 \times 2^4);
符号位 S 是 0,表示正数;指数部分 E 是 4,转换为二进制是 100;尾数部分 M 是 1.001001。
使用浮点数时需要注意,由于精度的限制,进行数学运算时可能会遇到舍入误差,特别是连续运算累积误差可能会变得显著。
对于需要高精度计算的场景(如金融计算),可能需要考虑使用BigDecimal
类来避免这种误差。
讲一下数据准确性高是怎么保证的?
在金融计算中,保证数据准确性有两种方案,一种使用 BigDecimal
,一种将浮点数转换为整数 int 进行计算。
肯定不能使用 float
和 double
类型,它们无法避免浮点数运算中常见的精度问题,因为这些数据类型采用二进制浮点数来表示,无法准确地表示,例如 0.1
。
在处理小额支付或计算时,通过转换为较小的货币单位(如分),这样不仅提高了运算速度,还保证了计算的准确性。
==与equals()的区别
==和equals() 最大的区别是,一个是运算符、一个是方法
- ==:
- 如果比较的对象是基本数据类型,则比较数值是否相等;
- 如果比较的是引用数据类型,则比较的是对象的地址是否相等,即它们是否指向同一个对象实例
- equals()不能用来判断基本数据类型的变量,可以用来比较两个对象的内容是否相等
- 默认情况下比较两个对象的引用是否相等,等效于==。
- 通常类中重写equals方法,对具体的对象内容进行比较。
String中的equals 方法是被重写过的,因为 object的equals() 方法是比较的对象的内存地址,而string的equals()方法比较的是对象的值
为什么重写 equals 时必须重写 hashCode ⽅法? hash 先使用hashcode找到下标,再使用Equals方法比较对象
获取哈希码,确定该对象在哈希表中的索引位置,能根据 键 快速检索出对应的值
基于哈希的集合类(如 HashSet、HashMap、Hashtable 等)依赖于这一点来正确存储和检索对象。
具体地说,这些集合通过对象的哈希码将其存储在不同的“桶”中(底层数据结构是数组,哈希码用来确定下标),当查找对象时,它们使用哈希码确定在哪个桶中搜索,然后通过 equals()
方法在桶中找到正确的对象。
如果重写了 equals()
方法而没有重写 hashCode()
方法,两个"相等"的对象可能会因为哈希码不同而被放在不同的 HashMap 或 HashSet 桶中,这会导致一些问题,比如数据查找不到等。
switch
- 只能对对象进行等值判断
Byte 、short 、char 、int、 enum、 string 不能使用long
- 基本数据类型:
-
byte
short
(从 Java 5 开始)char
int
- 枚举类型(enum):从 Java 5 开始,
switch
语句可以用于枚举类型的变量。 String
类型:从 Java 7 开始,switch
语句也可以处理String
类型的变量。
-------------------------------------------------------------------------------------------
switch
语句对于基本数据类型和 String
是通过使用 equals
方法进行比较的,这意味着字符串比较是区分大小写的
- 为什么不能用于long
因为 switch
语句的设计是基于通过将每个 case
标签的值与 switch
表达式的值进行整数比较来工作。由于 long
类型的数据无法直接转换为 int
类型而不丢失信息,因此 switch
语句不能直接用于 long
类型的变量。
接口和抽象类有什么共同点和区别?
共同点:
- 都不能被实例化
- 都可以包含抽象方法
- 都可以有默认实现的方法 (Java 8可以用 default 关键字在接口中定义默认方法,不因为修改接口而修改实现接口的类)
区别:
- 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
- 一个类只能继承一个类,但是可以实现多个接口
- 接口中的成员变量只能是 public static final 类型的不能被修改且必须有初始值,而抽象类的成员变量默认default,可在子类中被重新定义,也可被重新赋值
- 抽象类可以定义构造方法;接口不可以定义构造方法,接口主要用于定义一组方法规范,没有具体的实现细节
抽象类和普通类的区别?
抽象类使用 abstract 关键字定义,不能被实例化,只能作为其他类的父类。普通类没有 abstract 关键字,可以直接实例化。
抽象类可以包含抽象方法和非抽象方法。抽象方法没有方法体,必须由子类实现。普通类只能包含非抽象方法。
抽象类的作用
抽象类的作用主要是为子类提供一个共同的模板,定义了一些通用的方法和属性。子类可以继承抽象类拥有这些通用属性并按需实现或覆盖其中的方法。
抽象类是面向对象编程中重要的概念,能够提高代码的复用性和可读性,同时也能够对类的继承进行限制。
当继承抽象类时,要么重写抽象类中所有的抽象方法,要么子类为抽象类。
在什么情况下会使用接口或抽象类吗?
通常,如果我们要定义一组类型必须遵守的契约,而不关心具体实现,那么就使用接口。
如果我们既想定义契约又想提供一些方法的默认实现,那么可以使用抽象类
// 接口
public interface Chargeable {
void charge();
}
// 抽象类
public abstract class Animal {
public abstract void makeSound();
//提供一个模板
public void eat() {
System.out.println("Animal is eating.");
}
}
深拷贝和浅拷贝区别了解吗? 什么是引用拷贝? (是否拷贝引用数据类型堆中的对象)
- 浅拷贝:创建一个新对象,仅拷贝被拷贝对象的成员变量的值,也就是基本数据类型变量的值,和引用数据类型变量的地址值,而对于引用类型变量指向的堆中的对象不会拷贝。
- 深拷贝:完全拷贝一个对象,拷贝被拷贝对象的成员变量的值,堆中的对象也会拷贝一份
- 引用拷贝:两个不同的引用指向同一个对象,只是复制对象的地址,并不会创建一个新的对象(值传递就是这个类型)
浅拷贝:
- Object 类提供的 clone()方法可以非常简单地实现对象的浅拷贝。
- 修改浅拷贝后的对象内部的引用对象会影响到原始对象。
深拷贝:(需要重载clone)
- 重写克隆方法:重写clone()克隆方法,引用类型变量单独克隆,这里可能会涉及多层递归。
- 序列化:可以先将原对象序列化,再反序列化成拷贝对象。
- 修改深拷贝后的对象内部的引用对象不会影响原始对象。
Java 创建对象有哪几种方式?
- new 创建新对象
- 通过反射机制
- 采用 clone 机制
- 通过序列化机制
new子类的时候,子类和父类静态代码块,构造方法的执行顺序
在 Java 中,当创建一个子类对象时,子类和父类的静态代码块、构造方法的执行顺序遵循一定的规则。这些规则主要包括以下几个步骤:
- 首先执行父类的静态代码块(仅在类第一次加载时执行)。
- 接着执行子类的静态代码块(仅在类第一次加载时执行)。
- 再执行父类的构造方法。
- 最后执行子类的构造方法。
- 静态代码块:在类加载时执行,仅执行一次,按父类-子类的顺序执行。
- 构造方法:在每次创建对象时执行,按父类-子类的顺序执行,先初始化块后构造方法
java中是值传递还是引用传递,还是两者共存
java只有值传递 :
- 当你将一个变量作为参数传递给方法时,实际上是将该变量的值复制一份,然后将复制的值传递给方法。
- 对于基本类型,传递的是值的副本,修改副本不会影响原始值。对于对象类型,传递的是引用的副本,修改副本会影响原始对象。
什么是fail-fast,什么是fail-safe
fail-safe和 fail-fast 是多线程并发操作集合时的一种失败处理机制
- fail-fast 表示快速失败,在集合遍历过程中,一旦发现容器中的数据被修改了,会立刻抛出ConcurrentModificationException 异常,从而导致遍历失败
- fail-safe 表示失败安全,也就是在这种机制下,出现集合元素的修改,不会抛出ConcurrentModificationException, 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
谈谈final、finally、 finalize有什么不同?
- final可以用来修饰类、方法、变量,分别有不同的意义,final修饰的class代表不可以继承扩展,final的变量是不可以修改的(引用数组时,引用不可改,数组内容可改变),而final的方法也是不可以重写的
如果是基本数据类型的变量,其数值一旦在初始化之后就不能更改;如果是引用类型的变量,在对其初始化之后就不能再让其指向另一个对象。但是引用指向的对象内容可以改变。
- finally 是 Java 中异常处理的一部分,用来创建 try 块后面的 finally 块。无论 try 块中的代码是否抛出异常,finally 块中的代码总是会被执行。通常,finally 块被用来释放资源,如关闭文件、数据库连接等。
- finalize是基础类java.lang.Object的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize机制现在已经不推荐使用,并且在JDK 9开始被标记为deprecated。
数组和链表区别是什么?
- 访问效率:数组可以通过索引直接访问任何位置的元素,访问效率高,时间复杂度为O(1),而链表需要从头节点开始遍历到目标位置,访问效率较低,时间复杂度为O(n)。
- 插入和删除操作效率:数组插入和删除操作可能需要移动其他元素,时间复杂度为O(n),而链表只需要修改指针指向,时间复杂度为O(1)。
- 缓存命中率:由于数组元素在内存中连续存储,可以提高CPU缓存的命中率,而链表节点不连续存储,可能导致CPU缓存的命中率较低,频繁的缓存失效会影响性能。
- 应用场景:数组适合静态大小、频繁访问元素的场景,而链表适合动态大小、频繁插入、删除操作的场景
ArrayList和LinkedList的区别?
- 底层数据结构:ArrayList使用动态数组作为底层数据结构,而LinkedList使用双向链表作为底层数据结构
- 随机访问性能:ArrayList支持通过索引直接访问元素,因为底层数组的连续存储特性,所以时间复杂度为O(1)。而LinkedList需要从头或尾部开始遍历链表,时间复杂度为O(n)。
- 插入和删除操作:ArrayList在尾部插入和删除元素的时间复杂度为O(1),因为它只需要调整数组的长度即可。但在中间或头部插入和删除元素时,需要将后续元素进行移动,时间复杂度为O(n)。而LinkedList在任意位置插入和删除元素的时间复杂度为O(1),因为只需要调整节点的指针即可。
- 内存占用:ArrayList在每个元素中都存储了实际的数据,而LinkedList在每个节点中存储了数据和前后节点的指针。因此,相同数量的元素情况下,LinkedList通常比ArrayList占用更多的内存空间。
自己整理 ,借鉴很多博主,感谢他们
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » Java基础
发表评论 取消回复