Java概述
何为编程
-
编程是指使用计算机语言编写代码,以实现计算机程序的过程。编程是一种创造性的活动,它要求程序员具备逻辑思维、分析问题和解决问题的能力。编程可以用于开发各种类型的软件,例如桌面应用程序、移动应用程序、网站、游戏等。
什么是Java
- Java是一门面向对象编程语言,它由 Sun Microsystems 公司于 1995 年推出。不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。Java 语言具有简单、安全、可移植、高效等特点,被广泛应用于开发各种类型的软件,例如桌面应用程序、移动应用程序、Web 应用程序、游戏等。
jdk1.5之后的三大版本
- Java SE(J2SE,Java 2 Platform Standard Edition,标准版)
Java SE 以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。Java SE 包含了支持 Java Web 服务开发的类,并为Java EE和Java ME提供基础。 - Java EE(J2EE,Java 2 Platform Enterprise Edition,企业版)
Java EE 以前称为 J2EE。企业版本帮助开发和部署可移植、健壮、可伸缩且安全的服务器端Java 应用程序。Java EE 是在 Java SE 的基础上构建的,它提供 Web 服务、组件模型、管理和通信 API,可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和 Web2.0应用程序。2018年2月,Eclipse 宣布正式将 JavaEE 更名为 JakartaEE - Java ME(J2ME,Java 2 Platform Micro Edition,微型版)
Java ME 以前称为 J2ME。Java ME 为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境。Java ME 包括灵活的用户界面、健壮的安全模型、许多内置的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持。基于 Java ME 规范的应用程序只需编写一次,就可以用于许多设备,而且可以利用每个设备的本机功能。
3 Jdk和Jre和JVM的区别
看Java官方的图片,Jdk中包括了Jre,Jre中包括了JVM
-
JDK :JDK 是 Java 开发工具包,包括了 Java 编译器、Java 虚拟机、开发工具和类库等组件,是 Java 程序员进行开发、编译和调试 Java 程序必须的工具。Jdk还包括了一些Jre之外的东西 ,就是这些东西帮我们编译Java代码的, 还有就是监控Jvm的一些工具 Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等
-
JRE :JRE 是 Java 运行时环境,包括了 Java 虚拟机和标准类库等组件,用于运行已经编译好的 Java 程序。Jre大部分都是 C 和 C++ 语言编写的,他是我们在编译java时所需要的基础的类库 Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库等。
如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。
-
JVM:JVM 是 Java 虚拟机,是 Java 程序运行的环境,它可以将字节码文件解释成机器码并执行。JVM 是 Java 语言实现跨平台的关键,因为它可以在不同的操作系统上运行相同的字节码文件。
Java的跨平台性?
-
跨平台性是指软件可以在多个不同的操作系统和硬件平台上运行,而不需要进行修改或编译。
-
Java 语言具有很好的跨平台性,这是因为 Java 代码是通过编译器编译成字节码,字节码可以在任何支持 Java 虚拟机(JVM)的平台上运行。
Java的跨平台性原理是什么
-
Java 编译器将源代码编译成字节码文件,字节码文件包含了 Java 虚拟机可以理解的指令集。
-
Java 虚拟机是一个软件,它可以在不同的操作系统和硬件平台上运行,它负责将字节码文件解释成机器码并执行。
-
Java 虚拟机提供了标准的类库,程序员可以使用这些类库进行开发,这些类库在不同的平台上都有相同的实现。
-
因为 Java 虚拟机和标准类库都是跨平台的,所以 Java 程序可以在任何支持 Java 虚拟机的平台上运行,而不需要进行修改或编译。
Java语言有哪些特点
-
简单易学:Java 语言的语法简单明了,易于学习和理解。
-
面向对象:Java 语言是一种纯粹的面向对象编程语言,支持封装、继承和多态等特性。
-
平台无关性:Java 语言的编译器将源代码编译成字节码,字节码可以在任何支持 Java 虚拟机(JVM)的平台上运行,因此具有很好的移植性。
-
安全性高:Java 语言提供了严格的安全机制,可以防止程序在运行时对系统造成损害。
-
高性能:Java 语言的虚拟机具有优秀的垃圾回收机制和即时编译技术,可以提高程序的执行效率。
-
多线程支持:Java 语言提供了多线程编程的支持,可以方便地实现多线程程序。
-
开源免费:Java 语言是一种开源免费的编程语言,可以免费使用、修改和分发。
-
丰富的类库:Java 语言提供了丰富的类库,包括了各种常用的数据结构、算法、网络、图形界面、数据库等组件,可以方便地进行程序开发。
什么是字节码?采用字节码的最大好处是什么
-
字节码:字节码是一种中间代码,是 Java 语言编译后的代码,它不是针对特定的硬件和操作系统,而是针对 Java 虚拟机(JVM)的。Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),字节码可以在任何支持 Java 虚拟机的平台上运行。
-
采用字节码的好处:
采用字节码的最大好处是可以实现平台无关性。因为 Java 代码是先编译成字节码,然后由 JVM 运行,所以 Java 代码可以在任何支持 JVM 的平台上运行。这种机制使得 Java 语言具有很好的移植性,可以方便地将 Java 程序部署到不同的操作系统和硬件平台上。
采用字节码还可以提高程序的安全性。因为字节码是一种中间代码,不是直接可执行的机器 码,所以可以防止程序被恶意修改或破解。同时,Java 虚拟机还提供了严格的安全机制,可 以防止程序在运行时对系统造成损害。
-
先看下java中的编译器和解释器:
Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行,这就是上面提到的Java的特点的编译与解释并存的解释。
Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。
什么是Java程序的主类?应用程序和小程序的主类有何不同?
- Java 程序的主类是指包含 main() 方法的类。在 Java 应用程序中,main() 方法是程序的入口点,程序从 main() 方法开始执行。主类必须包含如下方法签名的 main() 方法:
public static void main(String[] args)
其中,public 表示该方法是公共方法,可以从其他类中访问;static 表示该方法是静态方法,可以直接通过类名调用;void 表示该方法不返回任何值;main 是方法的名称;String[] args 表示传递给 main() 方法的参数,它是一个字符串数组。
主类是 Java 程序的重要组成部分,也是程序入口点的定义所在。
Java应用程序与小程序之间有那些差别?
-
运行环境不同:Java 应用程序是在 Java 虚拟机(JVM)上运行的,而小程序是在微信客户端上运行的。
-
开发语言不同:Java 应用程序使用 Java 语言开发,而小程序使用 WXML、WXSS 和 JavaScript 等语言开发。
-
应用场景不同:Java 应用程序通常用于开发大型应用程序,如企业级应用、Web 应用、移动应用等,而小程序通常用于开发轻量级应用,如小游戏、小工具等。
-
功能限制不同:小程序的功能相对较为简单,只能访问微信提供的 API 接口,而 Java 应用程序的功能相对较为丰富,可以访问各种系统资源和第三方库。
-
开发工具不同:Java 应用程序的开发工具通常是 Eclipse、IntelliJ IDEA 等集成开发环境(IDE),而小程序的开发工具是微信开发者工具。
Java和C++的区别
-
运行环境不同:Java 程序是在 Java 虚拟机(JVM)上运行的,而 C++ 程序是编译成本地机器码后直接运行的。
-
内存管理方式不同:Java 采用自动垃圾回收机制,程序员不需要手动管理内存,而 C++ 需要手动分配和释放内存。
-
语言特性不同:Java 是一种面向对象的编程语言,支持类、接口、继承、多态等特性,而 C++ 同样支持面向对象编程,但也支持过程式编程。
-
平台兼容性不同:Java 程序具有很好的平台无关性,可以在任何支持 Java 虚拟机的平台上运行,而 C++ 程序需要在不同平台上重新编译才能运行。
-
异常处理方式不同:Java 提供了强大的异常处理机制,可以方便地捕获和处理异常,而 C++ 的异常处理相对较为简单。
Oracle JDK 和 OpenJDK 的对比
-
许可证:Oracle JDK 使用的是 Oracle 公司的专有许可证,而 OpenJDK 使用的是 GPL 许可证。
-
支持和更新:Oracle JDK 提供商 Oracle 公司提供商业支持和更新,而 OpenJDK 由社区维护,更新较为频繁。
-
功能差异:Oracle JDK 包含一些商业特性,如 Java Flight Recorder、Java Mission Control 等,而 OpenJDK 不包含这些特性。
-
安全性:Oracle JDK 提供商 Oracle 公司对 JDK 进行安全性测试和修复,而 OpenJDK 的安全性由社区维护。
-
兼容性:Oracle JDK 与 Java SE 规范的兼容性更好,而 OpenJDK 的兼容性可能会受到社区开发人员的影响。
基础语法
Java的数据类型
Java有哪些数据类型
定义:Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同大小的内存空间。
分类
- 基本数据类型
- 数值型
- 整数类型(byte,short,int,long)
- 浮点类型(float,double)
- 字符型(char)
- 布尔型(boolean)
- 数值型
- 引用数据类型
- 类(class)
- 接口(interface)
- 数组([])
Java基本数据类型图
java 中的 Math.round(-1.5) 等于多少?
Math提供了三个与取整有关的方法:ceil、floor、round
1、ceil:向上取整;
Math.ceil(11.3) = 12;
Math.ceil(-11.3) = 11;
2、floor:向下取整;
Math.floor(11.3) = 11;
Math.floor(-11.3) = -12;
3、round:四舍五入;
加0.5然后向下取整。
Math.round(11.3) = 11;
Math.round(11.8) = 12;
Math.round(-11.3) = -11;
Math.round(-11.8) = -12;
float f=3.4;是否正确?
- 不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成 float f =3.4F;。
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语言采用何种编码方案?有何特点?
- Java语言采用Unicode编码标准,Unicode(标准码),它为每个字符制订了一个唯一的数值,因此在任何的语言,平台,程序都可以放心的使用。
注释
什么是Java注释
定义:Java 注释是在编写 Java 代码时添加的文本,用于解释代码的作用、功能、实现方式等信息。
分类
- 单行注释
// 这是一个单行注释 int a = 10; // 这是一行代码和一个单行注释
- 多行注释
/* 这是一个多行注释, 可以跨越多行。 */ int a = 10;
- 文档注释
/** * 这是一个文档注释,用于生成 API 文档。 * @param a 参数 a 的作用是什么 * @return 返回值的作用是什么 */ public int method(int a) { return a + 1; }
注意事项:多行和文档注释都不能嵌套使用。
访问修饰符
- 访问修饰符:用于控制元素的访问权限,包括 public、protected、private 和 default 四种访问修饰符。
- public:可以被任何类访问。
- protected:可以被同一包内的类和子类访问。
- private:只能被同一类内的其他方法访问。
- default:只能被同一包内的类访问。
运算符
Java 运算符是用于执行算术、逻辑、位、关系等运算的符号。
- 算术运算符:用于执行基本的数学运算,包括 +、-、*、/、% 等。
int a = 10;
int b = 3;
int c = a + b; // c 的值为 13
int d = a - b; // d 的值为 7
int e = a * b; // e 的值为 30
int f = a / b; // f 的值为 3
int g = a % b; // g 的值为 1
- 关系运算符:用于比较两个值之间的关系,包括 >、>=、<、<=、==、!= 等。
int a = 10;
int b = 3;
boolean c = a > b; // c 的值为 true
boolean d = a >= b; // d 的值为 true
boolean e = a < b; // e 的值为 false
boolean f = a <= b; // f 的值为 false
boolean g = a == b; // g 的值为 false
boolean h = a != b; // h 的值为 true
- 逻辑运算符:用于执行逻辑运算,包括 &&、||、! 等。
boolean a = true;
boolean b = false;
boolean c = a && b; // c 的值为 false
boolean d = a || b; // d 的值为 true
boolean e = !a; // e 的值为 false
- 位运算符:用于对二进制数进行操作,包括 &、|、^、~、<<、>>、>>> 等。
int a = 10; // 二进制表示为 1010
int b = 3; // 二进制表示为 0011
int c = a & b; // c 的值为 2,二进制表示为 0010
int d = a | b; // d 的值为 11,二进制表示为 1011
int e = a ^ b; // e 的值为 9,二进制表示为 1001
int f = ~a; // f 的值为 -11,二进制表示为 11111111111111111111111111110101
int g = a << 2; // g 的值为 40,二进制表示为 101000
int h = a >> 2; // h 的值为 2,二进制表示为 10
int i = a >>> 2; // i 的值为 2,二进制表示为 10
- 赋值运算符:用于将一个值赋给一个变量,包括 =、+=、-=、*=、/=、%= 等。
int a = 10;
a += 3; // 等价于 a = a + 3;
a -= 3; // 等价于 a = a - 3;
a *= 3; // 等价于 a = a * 3;
a /= 3; // 等价于 a = a / 3;
a %= 3; // 等价于 a = a % 3;
关键字
Java 关键字是指在 Java 语言中具有特殊含义和用途的保留字,不能用作标识符或变量名。
-
访问修饰符关键字:用于控制类、方法、变量等元素的访问权限,包括 public、private、protected 和 default。
-
类、方法、变量声明关键字:用于声明类、方法、变量等元素,包括 class、interface、enum、extends、implements、throws、void、return、new、this 等。
-
流程控制关键字:用于控制程序的流程和执行顺序,包括 if、else、switch、case、default、for、while、do、break、continue、return 等。
-
异常处理关键字:用于处理程序运行时出现的异常情况,包括 try、catch、finally、throw 和 throws。
-
其他关键字:包括 static、final、abstract、synchronized、volatile 等,用于修饰类、变量、方法等元素的特性和行为。
final 的作用?
final 是一个关键字,用于修饰变量、方法和类。
- final 修饰的变量表示常量,其值不能被修改,一旦被赋值就不能改变。
- final 修饰的方法表示该方法不能被子类重写。
- final 修饰的类表示该类不能被继承。
final finally finalize区别?
-
final:final 是一个关键字,用于修饰变量、方法和类。final 修饰的变量表示常量,其值不能被修改,一旦被赋值就不能改变。final 修饰的方法表示该方法不能被子类重写。final 修饰的类表示该类不能被继承。
-
finally:finally 是一个关键字,用于定义在 try-catch 语句块中的一个代码块,无论是否捕获异常,finally 中的代码都会被执行。finally 常用于资源释放、清理等操作。
-
finalize:finalize 是 Object 类中的一个方法,用于在对象被垃圾回收之前执行一些清理操作。finalize 方法是由垃圾回收器在回收对象时自动调用的,程序员不能直接调用。
this关键字的用法
-
this 是一个关键字,用于表示当前对象的引用。可以理解为:指向对象本身的一个指针。
-
this的用法在java中大体可以分为3种:
-
1.用于区分局部变量和成员变量:当成员变量和局部变量同名时,使用 this 关键字可以区分它们。
-
public class Person {
private String name; // 成员变量 name
public void setName(String name) { // 设置成员变量 name 的值
this.name = name; // 使用 this 关键字区分成员变量和局部变量
}
}
-
-
2.用于在构造方法中调用其他构造方法:当一个类有多个构造方法时,可以使用 this 关键字在一个构造方法中调用另一个构造方法。
public class Person { private String name; private int age; public Person() { // 无参构造方法 this("Unknown", 0); // 调用另一个构造方法 } public Person(String name, int age) { // 有参构造方法 this.name = name; this.age = age; } }
-
3.用于返回当前对象的引用:在某些情况下,需要返回当前对象的引用,可以使用 this 关键字。
public class Person { private String name; private int age; public Person setName(String name) { this.name = name; return this; // 返回当前对象的引用 } public Person setAge(int age) { this.age = age; return this; // 返回当前对象的引用 } }
-
super关键字的用法
-
super 是一个关键字,用于表示父类的引用。
-
super也有三种用法:
-
1.调用父类的构造方法:在子类的构造方法中,可以使用 super 关键字调用父类的构造方法。
public class Student extends Person { private String school; public Student(String name, int age, String school) { super(name, age); // 调用父类的构造方法 this.school = school; } }
-
2.调用父类的成员变量和成员方法:在子类中,可以使用 super 关键字调用父类的成员变量和成员方法。
public class Student extends Person { private String school; public Student(String name, int age, String school) { super(name, age); this.school = school; } public void printInfo() { System.out.println(super.getName()); // 调用父类的 getName 方法 System.out.println(super.age); // 访问父类的 age 成员变量 } }
-
3.在子类中访问父类的构造方法、成员变量和成员方法:在子类中,可以使用 super 关键字访问父类的构造方法、成员变量和成员方法。
public class Student extends Person { private String school; public Student(String name, int age, String school) { super(name, age); this.school = school; } public void printInfo() { System.out.println(super.getName()); // 调用父类的 getName 方法 System.out.println(super.age); // 访问父类的 age 成员变量 super.printInfo(); // 调用父类的 printInfo 方法 } }
-
this与super的区别
super 关键字用于表示父类的引用,可以用于以下几个方面:
this 和 super 的区别在于它们所表示的对象不同。this 表示当前对象,super 表示父类的对象。
此外,this 和 super 还有一些使用上的限制。例如,this 和 super 不能同时出现在同一个构造方法的第一行,因为它们都表示对构造方法的调用。另外,在静态方法中不能使用 this 和 super 关键字,因为静态方法属于类,而不属于对象。
-
this 关键字用于表示当前对象的引用,可以用于以下几个方面:
- 区分局部变量和成员变量
- 在构造方法中调用其他构造方法
- 返回当前对象的引用
- 调用父类的构造方法
- 调用父类的成员变量和成员方法
- 在子类中访问父类的构造方法、成员变量和成员方法
static存在的主要意义
-
共享内存空间:static 可以用来修饰成员变量和成员方法,被 static 修饰的成员变量和成员方法属于类,而不属于对象。这意味着,不同的对象共享同一个 static 成员变量和 static 成员方法,它们都存储在类的内存空间中。这样可以节省内存空间,提高程序的执行效率。
-
方便访问:被 static 修饰的成员变量和成员方法可以直接通过类名访问,而不需要创建对象。这样可以方便地访问类的静态成员,而不需要创建对象。
-
类型限定:被 static 修饰的成员变量和成员方法属于类,而不属于对象。这意味着,它们可以被类的所有对象共享。同时,static 成员变量和 static 成员方法也可以通过类名直接访问,这样可以对类进行类型限定,避免了对象的创建和类型转换的开销。
-
静态块初始化:static 还可以用来定义静态块,在类加载时执行。静态块可以用来初始化静态成员变量和执行一些静态操作。
static应用场景
-
共享数据:被 static 修饰的成员变量可以被所有对象共享,这样可以避免创建多个对象造成内存浪费。例如,在一个多线程的程序中,可以使用 static 成员变量来实现多个线程共享数据。
-
工具类:被 static 修饰的成员方法可以直接通过类名调用,而不需要创建对象。这样可以方便地定义一些工具类,例如 Math 类中的 abs() 方法就是一个静态方法。
-
常量定义:被 static 和 final 修饰的成员变量可以定义为常量,常量不会改变,可以提高程序的可读性和可维护性。例如,Java 中的 Math.PI 就是一个静态常量。
-
静态块初始化:静态块可以用来初始化静态成员变量和执行一些静态操作。例如,在一个多线程的程序中,可以使用静态块来初始化一些静态变量。
static注意事项
- 1、静态只能访问静态。
- 2、非静态既可以访问非静态的,也可以访问静态的。
流程控制语句
- if-else 语句:if-else 语句用于根据条件执行不同的代码块。if-else 语句的基本语法如下:
if (condition) {
// 如果条件为真,执行这里的代码
} else {
// 如果条件为假,执行这里的代码
}
- switch-case 语句:switch-case 语句用于根据不同的条件执行不同的代码块。switch-case 语句的基本语法如下:
switch (variable) {
case value1:
// 如果 variable 等于 value1,执行这里的代码
break;
case value2:
// 如果 variable 等于 value2,执行这里的代码
break;
default:
// 如果 variable 不等于任何一个值,执行这里的代码
break;
}
- for 循环:for 循环用于重复执行某段代码,可以指定循环次数或者循环条件。for 循环的基本语法如下:
for (initialization; condition; update) {
// 循环体
}
- while 循环:while 循环用于重复执行某段代码,只要指定的条件为真。while 循环的基本语法如下:
while (condition) {
// 循环体
}
- do-while 循环:do-while 循环与 while 循环类似,不同之处在于 do-while 循环至少会执行一次循环体。do-while 循环的基本语法如下:
do {
// 循环体
} while (condition);
- break 和 continue 语句:break 语句用于跳出循环,continue 语句用于跳过当前循环。break 和 continue 语句可以用于 for、while 和 do-while 循环中。
break ,continue ,return 的区别及作用
-
break 跳出总上一层循环,不再执行循环(结束当前的循环体)
-
continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)
-
return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)
在 Java 中,如何跳出当前的多重嵌套循环
-
在Java中,要想跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break 语句,即可跳出外层循环。例如:
outer: for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { if (i * j > 50) { break outer; // 跳出外层循环 } } }
面向对象
面向对象概述
面向对象和面向过程的区别
-
面向过程:
-
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
-
缺点:没有面向对象易维护、易复用、易扩展
-
-
面向对象:
-
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
-
缺点:性能比面向过程低
-
面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。
面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,管我们什么事?我们会用就可以了。
面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象了。
面向对象三大特性
面向对象的特征主要有以下几个方面:
-
封装(Encapsulation):将数据和行为封装在对象内部,通过接口来实现对外的访问。封装可以保护对象的内部状态,防止外部直接访问和修改,提高了程序的安全性和可靠性。
-
继承(Inheritance):通过继承来复用已有的代码,并在此基础上进行扩展。继承可以提高代码的复用性和可维护性,避免了重复编写相似的代码。
-
多态(Polymorphism):同一个方法可以根据不同的对象表现出不同的行为。多态可以提高程序的灵活性和可扩展性,使得程序可以适应不同的场景和需求。
这三大特性是面向对象编程的核心,也是面向对象编程与面向过程编程的重要区别。在实际开发中,应该充分利用这三大特性,以提高程序的可读性、可维护性和可扩展性。
什么是多态机制?
-
多态是面向对象编程中的一个重要特性,它可以让同一个方法在不同的对象上表现出不同的行为,从而提高程序的灵活性和可扩展性。
Java多态的实现
-
重载(Overloading):同一个类中可以定义多个同名的方法,但这些方法的参数类型、个数或顺序必须不同。编译器会根据调用时的参数类型、个数或顺序来选择相应的方法。这种多态称为编译时多态或静态多态。
-
重写(Overriding):子类可以重写父类中的方法,以实现自己的行为。在调用这个方法时,会根据对象的实际类型来选择相应的方法。这种多态称为运行时多态或动态多态。
通过多态机制,可以实现代码的复用和扩展,使得程序更加灵活和可维护。需要注意的是,要想使用多态机制,必须满足一些前提条件,比如必须有继承关系、方法名和参数列表必须相同等等。同时,在使用多态时也要注意避免一些常见的问题,比如类型转换异常、方法不存在等等。
abstract class Animal {
public abstract void speak();
}
class Cat extends Animal {
@Override
public void speak() {
System.out.println("Cat meows!");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Cat();
animal.speak(); // 输出 Cat meows!
}
}
面向对象五大基本原则是什么?
-
单一职责原则(Single Responsibility Principle,SRP):一个类应该只有一个职责,即一个类只负责一项功能。
-
开闭原则(Open-Closed Principle,OCP):一个类应该对扩展开放,对修改关闭。即当需要改变一个类的功能时,应该通过添加新的代码来扩展其功能,而不是修改原有的代码。
-
里氏替换原则(Liskov Substitution Principle,LSP):子类对象可以替换父类对象出现在程序中的任何地方,并且保证程序的逻辑不变。即子类应该可以完全替代父类并且不影响程序的正确性。
-
接口隔离原则(Interface Segregation Principle,ISP):客户端不应该依赖于它不需要的接口。即一个类不应该强制实现它不需要的接口,而应该将接口拆分成更小的和更具体的接口。
-
依赖倒置原则(Dependency Inversion Principle,DIP):高层模块不应该依赖于低层模块,它们都应该依赖于抽象。即抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
类与接口
抽象类和接口的对比
-
抽象类是用来捕捉子类的通用特性的。接口是抽象方法的集合。
-
从设计层面来说,抽象类是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
相同点
- 接口和抽象类都不能实例化
- 都位于继承的顶端,用于被其他实现或继承
- 都包含抽象方法,其子类都必须覆写这些抽象方法
不同点
参数 | 抽象类 | 接口 |
---|---|---|
声明 | 抽象类使用abstract关键字声明 | 接口使用interface关键字声明 |
实现 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现 | 子类使用implements关键字来实现接口。它需要提供接口中所有声明的方法的实现 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
访问修饰符 | 抽象类中的方法可以是任意访问修饰符 | 接口方法默认修饰符是public。并且不允许定义为 private 或者 protected |
多继承 | 一个类最多只能继承一个抽象类 | 一个类可以实现多个接口 |
字段声明 | 抽象类的字段声明可以是任意的 | 接口的字段默认都是 static 和 final 的 |
备注:Java8中接口中引入默认方法和静态方法,以此来减少抽象类和接口之间的差异。
现在,我们可以为接口提供默认实现的方法了,并且不用强制子类来实现它。
- 接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则:
- 行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
- 选择抽象类的时候通常是如下情况:需要定义子类的行为,又要为子类提供通用的功能。
普通类和抽象类有哪些区别?
- 普通类不能包含抽象方法,抽象类可以包含抽象方法。
- 抽象类不能直接实例化,普通类可以直接实例化。
抽象类能使用 final 修饰吗?
- 不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类
创建一个对象用什么关键字?对象实例与对象引用有何不同?
- new关键字,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)
变量与方法
成员变量与局部变量的区别有哪些
-
变量:在程序执行的过程中,在某个范围内其值可以发生改变的量。从本质上讲,变量其实是内存中的一小块区域
-
成员变量:方法外部,类内部定义的变量
-
局部变量:类的方法中的变量。
-
成员变量和局部变量的区别
作用域
- 成员变量:针对整个类有效。
- 局部变量:只在某个范围内有效。(一般指的就是方法,语句体内)
存储位置
- 成员变量:随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中。
- 局部变量:在方法被调用,或者语句被执行的时候存在,存储在栈内存中。当方法调用完,或者语句结束后,就自动释放。
生命周期
- 成员变量:随着对象的创建而存在,随着对象的消失而消失
- 局部变量:当方法调用完,或者语句结束后,就自动释放。
初始值
- 成员变量:有默认初始值。
- 局部变量:没有默认初始值,使用前必须赋值。
在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?
- 帮助子类做初始化工作。
一个类的构造方法的作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么?
- 主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。
构造方法有哪些特性?
-
名字与类名相同;
-
没有返回值,但不能用void声明构造函数;
-
生成类的对象时自动执行,无需调用。
静态变量和实例变量区别
-
静态变量: 静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。
-
实例变量: 每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。
静态变量与普通变量区别
-
static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
-
还有一点就是static成员变量的初始化顺序按照定义的顺序进行初始化。
静态方法和实例方法有何不同?
静态方法和实例方法的区别主要体现在两个方面:
-
在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
-
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制
在一个静态方法内调用一个非静态成员为什么是非法的?
- 由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
什么是方法的返回值?返回值的作用是什么?
- 方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!
内部类
什么是Java内部类?
- Java内部类是指在一个类的内部定义的另一个类。内部类可以访问外部类的私有成员,包括私有方法、私有属性和私有构造函数等,同时也可以被外部类访问。
内部类的分类有哪些
内部类可以分为四种:
成员内部类、
静态内部类、
局部内部类和
匿名内部类。
成员内部类
public class OuterClass {
private int outerField;
public class InnerClass {
private int innerField;
public InnerClass(int innerField) {
this.innerField = innerField;
}
public void printFields() {
System.out.println("outerField = " + outerField);
System.out.println("innerField = " + innerField);
}
}
public void testInnerClass() {
InnerClass innerClass = new InnerClass(10);
innerClass.printFields();
}
}
静态内部类
public class OuterClass {
private static int outerField;
public static class InnerClass {
private int innerField;
public InnerClass(int innerField) {
this.innerField = innerField;
}
public void printFields() {
System.out.println("outerField = " + outerField);
System.out.println("innerField = " + innerField);
}
}
public static void testInnerClass() {
InnerClass innerClass = new InnerClass(10);
innerClass.printFields();
}
}
局部内部类
public class OuterClass {
private int outerField;
public void testLocalInnerClass() {
final int localVariable = 10;
class LocalInnerClass {
private int innerField;
public LocalInnerClass(int innerField) {
this.innerField = innerField;
}
public void printFields() {
System.out.println("outerField = " + outerField);
System.out.println("localVariable = " + localVariable);
System.out.println("innerField = " + innerField);
}
}
LocalInnerClass localInnerClass = new LocalInnerClass(20);
localInnerClass.printFields();
}
}
匿名内部类
public interface MyInterface {
void doSomething();
}
public class OuterClass {
private int outerField;
public void testAnonymousInnerClass() {
MyInterface myInterface = new MyInterface() {
private int innerField;
@Override
public void doSomething() {
System.out.println("outerField = " + outerField);
System.out.println("innerField = " + innerField);
}
};
myInterface.doSomething();
}
}
内部类的优点
-
封装性:内部类可以访问外部类的私有成员,而外部类无法访问内部类的私有成员,从而实现了更好的封装性。
-
继承性:内部类可以继承其他类或者实现接口,从而实现更加灵活的程序设计。
-
回调函数:内部类可以作为回调函数的实现方式,从而实现更加灵活和复杂的事件处理。
-
代码组织:内部类可以将相关的代码组织在一起,从而提高代码的可读性和可维护性。
-
访问权限:内部类可以访问外部类的所有成员,包括私有成员,从而实现更加细粒度的访问权限控制。
内部类有哪些应用场景
- 一些多算法场合
- 解决一些非面向对象的语句块。
- 适当使用内部类,使得代码更加灵活和富有扩展性。
- 当某个类除了它的外部类,不再被其他的类使用时。
局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?
-
局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final呢?它内部原理是什么呢?先看这段代码:
public class Outer { void outMethod(){ final int a =10; class Inner { void innerMethod(){ System.out.println(a); } } } }
-
以上例子,为什么要加final呢?是因为生命周期不一致, 局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。
public class Outer {
private int age = 12;
class Inner {
private int age = 13;
public void print() {
int age = 14;
System.out.println("局部变量:" + age);
System.out.println("内部类变量:" + this.age);
System.out.println("外部类变量:" + Outer.this.age);
}
}
public static void main(String[] args) {
Outer.Inner in = new Outer().new Inner();
in.print();
}
}
运行结果:
局部变量:14
内部类变量:13
外部类变量:12
重写与重载
构造器(constructor)是否可被重写(override)
- 构造器不能被继承,因此不能被重写,但可以被重载。
重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
-
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
-
重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
-
重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。
重载是指在同一个类中定义多个方法,它们具有相同的名称但参数列表不同,可以根据传入的参数类型和数量来决定调用哪个方法。例如:
public void print(int num) {
System.out.println(num);
}
public void print(String str) {
System.out.println(str);
}
重写是指在子类中重新定义父类中已存在的方法,方法名、参数列表和返回类型都必须与父类中的方法相同,目的是为了实现多态。例如:
class Animal {
public void move() {
System.out.println("Animal is moving");
}
}
class Dog extends Animal {
@Override
public void move() {
System.out.println("Dog is moving");
}
}
对象相等判断
== 和 equals 的区别是什么
-
== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型 == 比较的是值,引用数据类型 == 比较的是内存地址)
-
equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
-
情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
-
情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
-
举个例子:
-
String str1 = "hello";
String str2 = "hello";
String str3 = new String("hello");
System.out.println(str1 == str2); // true,因为它们指向同一个字符串常量池中的对象
System.out.println(str1 == str3); // false,因为它们指向不同的对象
System.out.println(1 == 1.0); // true,因为它们的值相等
class Person {
private String name;
private int age;
@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Person other = (Person) obj;
return name.equals(other.name) && age == other.age;
}
}
-
说明:
- String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。
- 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。
hashCode 与 equals (重要)
-
HashSet如何检查重复
-
两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?
-
hashCode和equals方法的关系
-
面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”
hashCode()介绍
-
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode()函数。
-
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
为什么要有 hashCode
我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:
- 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
hashCode()与equals()的相关规定
-
如果两个对象相等,则hashcode一定也是相同的
-
两个对象相等,对两个对象分别调用equals方法都返回true
-
两个对象有相同的hashcode值,它们也不一定是相等的
因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
对象的相等与指向他们的引用相等,两者有什么不同?
- 对象的相等 比的是内存中存放的内容是否相等而 引用相等 比较的是他们指向的内存地址是否相等。
值传递
当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递
- 是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的
为什么 Java 中只有值传递
-
首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。
-
Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。
- 下面通过例子来给大家说明
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a);
System.out.println("b = " + b);
}
-
结果:
a = 20 b = 10 num1 = 10 num2 = 20
-
解析:
- 在swap方法中,a、b的值进行交换,并不会影响到 num1、num2。因为,a、b中的值,只是从 num1、num2 的复制过来的。也就是说,a、b相当于num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]);
change(arr);
System.out.println(arr[0]);
}
public static void change(int[] array) {
// 将数组的第一个元素变为0
array[0] = 0;
}
-
结果:
1 0
-
解析:
- array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的时同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。
通过示例我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。
-
总结
Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的。
-
下面再总结一下Java中方法参数的使用情况:
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)
- 一个方法可以改变一个对象参数的状态。
- 一个方法不能让对象参数引用一个新的对象。
值传递和引用传递有什么区别
-
值传递:指的是在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷贝,也就是说传递后就互不相关了。
-
引用传递:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。
Java包
JDK 中常用的包有哪些
java.lang
:提供 Java 语言的基本类和接口,如字符串、异常、线程等。java.util
:提供了 Java 中常用的工具类,如集合框架、日期和时间处理、随机数生成等。java.io
:提供了输入输出相关的类和接口,如文件读写、网络通信等。java.net
:提供了网络编程相关的类和接口,如 URL、Socket、ServerSocket 等。java.sql
:提供了 JDBC(Java 数据库连接)相关的类和接口,用于连接和操作数据库。java.awt
和javax.swing
:提供了图形用户界面(GUI)相关的类和接口,如窗口、按钮、文本框等。java.math
:提供了高精度数学运算相关的类和接口,如 BigInteger、BigDecimal 等。
IO流
java 中 IO 流分为几种?
-
字节流(Byte Stream):以字节为单位进行读写,通常用于读写二进制数据,如图像、音频、视频等。字节流的基类是
InputStream
和OutputStream
。 -
字符流(Character Stream):以字符为单位进行读写,通常用于读写文本数据。字符流的基类是
Reader
和Writer
。 -
字节缓冲流(Buffered Byte Stream):基于字节流,提供了缓冲功能,可以提高读写效率。字节缓冲流的基类是
BufferedInputStream
和BufferedOutputStream
。 -
字符缓冲流(Buffered Character Stream):基于字符流,提供了缓冲功能,可以提高读写效率。字符缓冲流的基类是
BufferedReader
和BufferedWriter
BIO,NIO,AIO 有什么区别?
-
简答
- BIO(Blocking IO)是传统的阻塞式 IO,即在进行 IO 操作时,当前线程会被阻塞,直到操作完成。BIO 适用于连接数较小且固定的情况,编程模型简单,易于理解和使用,但并发性能较差。
- NIO(Non-Blocking IO)是基于事件驱动的 IO,即在进行 IO 操作时,当前线程不会被阻塞,而是可以继续处理其他任务。NIO 适用于连接数较多且连接时间较短的情况,具有较高的并发性能,但编程模型相对复杂。
- AIO(Asynchronous IO)是基于回调的异步 IO,即在进行 IO 操作时,不需要通过轮询或者阻塞等方式等待操作完成,而是通过回调函数的方式来通知操作完成。AIO 适用于连接数较多且连接时间较长的情况,具有更好的并发性能和可扩展性,但编程模型相对复杂。
-
详细回答
- BIO (Blocking I/O): 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
- NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的
Socket
和ServerSocket
相对应的SocketChannel
和ServerSocketChannel
两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发 - AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
Files的常用方法都有哪些?
createDirectory(Path path)
:创建一个目录。createFile(Path path, FileAttribute<?>... attrs)
:创建一个文件。delete(Path path)
:删除一个文件或目录。exists(Path path, LinkOption... options)
:判断文件或目录是否存在。isDirectory(Path path, LinkOption... options)
:判断是否为目录。isExecutable(Path path)
:判断是否可执行。isHidden(Path path)
:判断是否为隐藏文件。isReadable(Path path)
:判断是否可读。isRegularFile(Path path, LinkOption... options)
:判断是否为普通文件。isSameFile(Path path, Path path2)
:判断两个路径是否指向同一文件。isWritable(Path path)
:判断是否可写。move(Path source, Path target, CopyOption... options)
:移动或重命名文件或目录。readAllBytes(Path path)
:读取文件的所有字节。readAllLines(Path path, Charset cs)
:读取文件的所有行。write(Path path, byte[] bytes, OpenOption... options)
:将字节数组写入文件。write(Path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption... options)
:将字符串序列写入文件。
反射
什么是反射机制?
-
Java反射机制是指在运行时动态地获取类的信息并操作类的属性、方法和构造函数等。它允许程序在运行时通过名称来访问类的属性和方法,而不需要提前知道它们的名称。Java反射机制可以让我们在运行时动态地创建对象、调用方法、获取成员变量等操作,是Java语言的一个重要特性。
-
静态编译和动态编译
-
静态编译:在编译时确定类型,绑定对象
-
动态编译:运行时确定类型,绑定对象
-
反射机制优缺点
Java反射机制的优点包括:
- 可以在运行时动态地获取类的信息和操作类的属性、方法和构造函数等,使程序更加灵活。
- 可以通过反射机制创建对象、调用方法、获取成员变量等,使得一些复杂的操作变得简单。
- 可以在运行时动态地加载类,从而实现插件式开发等功能。
Java反射机制的缺点包括:
- 反射机制的性能较差,因为它需要在程序运行时动态地获取信息,而不是在编译时就确定。
- 反射机制容易导致代码的不安全性,因为它可以绕过访问权限来操作类的属性和方法等。
- 反射机制使用起来较为复杂,需要较高的技能水平。
反射机制的应用场景有哪些?
-
反射是框架设计的灵魂。
- Spring框架中的自动化配置
-
Spring框架使用反射机制来实现自动化配置。在Spring容器启动时,它会扫描应用程序中的所有类,并使用反射机制读取和设置这些类的注解信息。然后,它会根据这些注解信息来自动化配置应用程序的各个组件,如数据源、事务管理器、Web MVC等。
- Hibernate框架中的ORM映射
-
Hibernate框架使用反射机制来实现ORM映射。在Hibernate中,每个实体类都对应着一个数据库表。当Hibernate需要将一个实体对象存储到数据库中时,它会使用反射机制动态地读取这个实体对象的属性,并将这些属性映射到数据库表的列上。
- Struts2框架中的AOP编程
-
Struts2框架使用反射机制来实现AOP编程。在Struts2中,开发人员可以通过注解或XML配置文件来定义拦截器,然后将这些拦截器应用到Action方法上。当请求到达Action方法时,Struts2会使用反射机制动态地代理这个方法,并将拦截器织入到这个方法中。
Java获取反射的三种方法
- 通过对象的getClass()方法获取Class对象
String str = "Hello, World!";
Class cls = str.getClass();
- 通过类名.class获取Class对象
Class cls = String.class;
- 通过Class.forName()方法获取Class对象
Class cls = Class.forName("java.lang.String");
常用API
String相关
字符型常量和字符串常量的区别
- 字符型常量是指用单引号括起来的一个字符,它的类型是char。例如:
char c = 'A';
- 字符串常量是指用双引号括起来的一串字符,它的类型是String。例如:
String str = "Hello, World!";
- 字符型常量只能表示一个字符,而字符串常量可以表示多个字符组成的字符串。另外,字符型常量在内存中只占用一个字节,而字符串常量在内存中占用多个字节,具体取决于字符串的长度。
什么是字符串常量池?
- 字符串常量池是Java中的一个特殊的内存区域,用于存储字符串常量。当程序中使用双引号创建字符串常量时,Java会先在字符串常量池中查找是否已经存在该字符串,如果存在,则直接返回该字符串的引用;否则,就在字符串常量池中创建一个新的字符串,并返回该字符串的引用。
String 是最基本的数据类型吗?
- 不是。Java 中的基本数据类型只有 8 个 :byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(referencetype),Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型。
String有哪些特性?
- 不可变性
-
String对象是不可变的,一旦创建了一个String对象,就不能再修改它的值。如果需要修改一个字符串,就必须创建一个新的String对象。
- 字符串常量池
-
Java中的字符串常量池是在堆内存中的一个特殊区域,用于存储字符串常量。由于String对象是不可变的,因此可以在字符串常量池中共享相同的字符串对象,从而节省内存空间。
- 比较性
-
String对象可以使用equals()方法来比较两个字符串的内容是否相同。另外,String还实现了Comparable接口,因此可以使用compareTo()方法来比较两个字符串的大小关系。
- 不可变序列
-
String对象实现了CharSequence接口,因此可以像使用数组一样访问字符串中的每个字符。但与数组不同的是,String对象是不可变的,因此不能通过下标来修改字符串中的字符。
- 高效性
-
由于String对象是不可变的,因此可以被安全地共享。这使得Java能够在多个线程之间共享字符串常量池中的字符串对象,从而提高了程序的执行效率。
String为什么是不可变的吗?
-
简单来说就是String类利用了final修饰的char类型数组存储字符,源码如下图所以:
/** The value is used for character storage. */ private final char value[];
String真的是不可变的吗?
- 我觉得如果别人问这个问题的话,回答不可变就可以了。 下面只是给大家看两个有代表性的例子:
1 String不可变但不代表引用不可以变
String str = "Hello";
str = str + " World";
System.out.println("str=" + str);
-
结果:
str=Hello World
-
解析:
-
实际上,原来String的内容是不变的,只是str由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串。
2.通过反射是可以修改所谓的“不可变”对象
// 创建字符串"Hello World", 并赋给引用s
String s = "Hello World";
System.out.println("s = " + s); // Hello World
// 获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
// 改变value属性的访问权限
valueFieldOfString.setAccessible(true);
// 获取s对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(s);
// 改变value所引用的数组中的第5个字符
value[5] = '_';
System.out.println("s = " + s); // Hello_World
-
结果:
s = Hello World s = Hello_World
-
解析:
-
用反射可以访问私有成员, 然后反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。但是一般我们不会这么做,这里只是简单提一下有这个东西。
是否可以继承 String 类?
- String 类是 final 类,不可以被继承。
String str="i"与 String str=new String(“i”)一样吗?
- 不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。
String s = new String(“xyz”);创建了几个字符串对象?
-
两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。
String str1 = "hello"; //str1指向静态区 String str2 = new String("hello"); //str2指向堆上的对象 String str3 = "hello"; String str4 = new String("hello"); System.out.println(str1.equals(str2)); //true System.out.println(str2.equals(str4)); //true System.out.println(str1 == str3); //true System.out.println(str1 == str2); //false System.out.println(str2 == str4); //false System.out.println(str2 == "hello"); //false str2 = str1; System.out.println(str2 == "hello"); //true
如何将字符串反转?
-
可以使用Java中的StringBuilder或StringBuffer类的reverse()方法来反转字符串。
-
示例代码:
String str = "Hello, World!"; StringBuilder sb = new StringBuilder(str); sb.reverse(); String reversedStr = sb.toString(); System.out.println(reversedStr);
数组有没有 length()方法?String 有没有 length()方法
- 数组没有 length()方法 ,有 length 的属性。String 有 length()方法。JavaScript中,获得字符串的长度是通过 length 属性得到的,这一点容易和 Java 混淆。
String 类的常用方法都有那些?
- indexOf():返回指定字符的索引。
- charAt():返回指定索引处的字符。
- replace():字符串替换。
- trim():去除字符串两端空白。
- split():分割字符串,返回一个分割后的字符串数组。
- getBytes():返回字符串的 byte 类型数组。
- length():返回字符串长度。
- toLowerCase():将字符串转成小写字母。
- toUpperCase():将字符串转成大写字符。
- substring():截取字符串。
- equals():字符串比较。
在使用 HashMap 的时候,用 String 做 key 有什么好处?
- HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。
String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?
可变性
- String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。
线程安全性
- String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
性能
- 每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结
-
如果要操作少量的数据用 = String
-
单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
-
多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
Date相关
包装类相关
自动装箱与拆箱
-
装箱:将基本类型用它们对应的引用类型包装起来;
-
拆箱:将包装类型转换为基本数据类型;
int 和 Integer 有什么区别?
-
Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
-
Java 为每个原始类型提供了包装类型:
-
原始类型: boolean,char,byte,short,int,long,float,double
-
包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
-
Integer a= 127 与 Integer b = 127相等吗?
- 对于对象引用类型:==比较的是对象的内存地址。
- 对于基本数据类型:==比较的是值。
如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false
public static void main(String[] args) {
Integer a = new Integer(3);
Integer b = 3; // 将3自动装箱成Integer类型
int c = 3;
System.out.println(a == b); // false 两个引用没有引用同一对象
System.out.println(a == c); // true a自动拆箱成int类型再和c比较
System.out.println(b == c); // true
Integer a1 = 128;
Integer b1 = 128;
System.out.println(a1 == b1); // false
Integer a2 = 127;
Integer b2 = 127;
System.out.println(a2 == b2); // true
}
博主总结不易 , 多点点关注和赞呀 !!!