目录
四、String、StringBuilder、StringBuffer
一、面向对象的三大特征
封装,继承,多态,详见
Java基础知识速成笔记“超实用”学完就去实战!(一)-CSDN博客
二、接口和抽象类
- 抽象类可以有构造方法,接口不能有构造方法。
- 抽象类可以有成员变量,接口中只能包含常量。
- 类可以继承一个抽象类,但可以实现多个接口。
- 抽象类提供了一种代码重用的机制,而接口提供了一种规范的定义。
- 使用抽象类来表示一种通用的概念,并在子类中实现具体的行为。
- 使用接口来定义类的行为规范,实现类可以根据需要实现不同的接口来拥有不同的行为。
详见 Java基础知识速成笔记“超实用”学完就去实战!(一)-CSDN博客
三、深拷贝、浅拷贝和引用拷贝
-
浅拷贝(Shallow Copy):
- 浅拷贝是指将一个对象的字段复制到另一个对象中,但对于字段中的引用类型,只复制引用,而不复制引用指向的对象本身。因此,原始对象和拷贝对象共享引用类型字段指向的对象。
- 如果对原始对象中的引用类型字段所指向的对象进行修改,这些修改也会反映在拷贝对象中。
- 浅拷贝通常是通过复制对象的每个字段来实现,但对于引用类型字段,只复制引用而不复制引用指向的对象。
-
深拷贝(Deep Copy):
- 深拷贝是指将一个对象及其所有嵌套的对象的内容都复制到一个新对象中。这包括复制原始对象的所有字段以及它们所引用的对象,以及这些对象引用的对象,依此类推。
- 深拷贝生成的拷贝对象与原始对象完全独立,它们之间没有任何共享部分。因此,对深拷贝对象或其嵌套对象的修改不会影响原始对象或其他拷贝对象。
-
引用拷贝(Reference Copy):
- 引用拷贝实际上不是一种复制,而是简单地将一个引用指向的对象赋值给另一个引用。这意味着两个引用指向的是同一个对象,它们共享相同的内存空间。
- 对其中一个引用指向的对象进行修改会直接影响另一个引用指向的对象,因为它们实际上是同一个对象的两个引用。
总的来说,深拷贝会完全复制对象及其嵌套的所有对象,而浅拷贝只复制对象的字段而不复制字段引用的对象。引用拷贝则简单地创建了指向同一对象的新引用。
四、String、StringBuilder、StringBuffer
String长度不可变,相当于常量,StringBuilder线程不安全,StringBuffer线程安全。
在Java中,String的长度是不可变的。一旦一个String对象被创建,它的长度就固定了,不能再改变。这意味着你不能直接修改一个String对象的长度。当你对一个String进行操作时(例如连接、剪切等),实际上是创建了一个新的String对象,而原始的String对象保持不变。
如果你需要处理可变长度的字符串,可以使用Java中的其他数据结构,比如StringBuilder或StringBuffer。这些类允许你在字符串上进行修改操作,而不必创建新的对象
总结:
- 操作少量的数据: 适用
String
- 单线程操作字符串缓冲区下操作大量数据: 适用
StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用
StringBuffer
详见Java中的常见类“StringBuilder、StringBuffer类”(四)用法详解-CSDN博客
五、字符串常量池
在Java中,字符串常量池是一个特殊的内存区域,用于存储字符串字面量。当你创建一个字符串字面量时,Java会首先检查字符串常量池。如果已经存在相同内容的字符串,则返回该字符串的引用,而不会创建新的字符串对象。这种机制被称为字符串的“interning”(内部化)。
// 在堆中创建字符串对象”ab“
// 将字符串对象”ab“的引用保存在字符串常量池中
String aa = "ab";
// 直接返回字符串常量池中字符串对象”ab“的引用
String bb = "ab";
System.out.println(aa==bb);// true
六、Java中的异常
异常是一种对象,它包含有关错误状态的信息,并在异常发生时通过异常处理机制传播到调用栈上层。Java中的异常类都继承自 Throwable
类,它有两个主要的子类:Exception
和 Error
·Exception
类及其子类通常用于表示可以通过合适的处理措施来恢复程序正常执行的异常情况
·Error
类及其子类通常用于表示无法通过程序处理的严重异常情况,这些情况通常属于系统级别的问题,例如内存不足或虚拟机崩溃。与 Exception
不同,Error
类通常不被程序捕获和处理,而是由Java虚拟机或操作系统处理
Throwable
类常用方法
getMessage()
: 返回异常的详细描述信息字符串。通常,这个描述包含异常的原因或上下文信息。
try {
// 可能抛出异常的代码
} catch (Exception e) {
System.out.println(e.getMessage());
}
printStackTrace()
: 打印异常跟踪栈信息到标准错误流。这个方法会将异常发生的地点以及方法调用的堆栈信息打印出来,以帮助调试程序。
try {
// 可能抛出异常的代码
} catch (Exception e) {
e.printStackTrace();
}
getCause()
: 返回导致异常的原因(如果有的话)。通常在异常链中使用,如果异常是由另一个异常引起的,那么可以使用该方法获取引起异常的原因。
try {
// 可能抛出异常的代码
} catch (Exception e) {
Throwable cause = e.getCause();
// 处理异常的原因
}
toString()
: 返回包含异常类名和异常消息的字符串。通常,这个方法返回的是异常类的全限定名以及异常的详细描述信息。
try {
// 可能抛出异常的代码
} catch (Exception e) {
System.out.println(e.toString());
}
详见Java基础知识速成笔记“超实用”学完就去实战!(一)-CSDN博客
七、泛型
泛型是一种编程机制,允许在类或方法中使用参数化类型(Parameterized Type)
详见Java基础知识速成笔记“超实用”学完就去实战!(一)-CSDN博客
八、值传递&引用传递
- 值传递:方法接收的是实参值的拷贝,会创建副本。
- 如果参数是基本类型的话,很简单,传递的就是基本类型的字面量值的拷贝,会创建副本。
- 如果参数是引用类型,传递的就是实参所引用的对象在堆中地址值的拷贝,同样也会创建副本。
- 引用传递:方法接收的直接是实参所引用的对象在堆中的地址,不会创建副本,对形参的修改将影响到实参。
Java中只有值传递!!! C++中有引用传递。
1.传递基本数据类型参数
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
2.传递引用类型参数
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
Java 对引用类型的参数采用的不是引用传递。这里传递的还是值,这个值是实参的地址!
也就是说 change
方法的参数拷贝的是 arr
(实参)的地址,因此,它和 arr
指向的是同一个数组对象。这也就说明了为什么方法内部对形参的修改会影响到实参。
public class Person {
private String name;
// 省略构造函数、Getter&Setter方法
}
public static void main(String[] args) {
Person xiaoZhang = new Person("小张");
Person xiaoLi = new Person("小李");
swap(xiaoZhang, xiaoLi);
System.out.println("xiaoZhang:" + xiaoZhang.getName());
System.out.println("xiaoLi:" + xiaoLi.getName());
}
public static void swap(Person person1, Person person2) {
Person temp = person1;
person1 = person2;
person2 = temp;
System.out.println("person1:" + person1.getName());
System.out.println("person2:" + person2.getName());
}
person1:小李
person2:小张
xiaoZhang:小张
xiaoLi:小李
九、序列化和反序列化
在Java中,序列化是将对象转换为字节序列的过程,以便将其保存到文件、数据库或通过网络传输。而反序列化则是将字节序列重新转换为对象的过程。
- 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
- 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
- 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
- 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。
序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
https://www.corejavaguru.com/java/serialization/interview-questions-1 图片来源
OSI 七层协议模型中,表示层做的事情主要就是对应用层的用户数据进行处理转换为二进制流,就是序列化。OSI 七层协议模型中的应用层、表示层和会话层对应的都是 TCP/IP 四层模型中的应用层,所以序列化协议属于 TCP/IP 协议应用层的一部分
十、反射机制
当我们编写Java代码时,我们通常是在编写类和调用类的方法。编译器会在编译时检查代码,以确保它们的语法和类型是正确的。然而,反射是一种机制,允许我们在运行时动态地检查类的信息、调用类的方法和访问类的字段,而不需要在编译时知道这些信息。
通俗地说,反射就像是在运行程序的时候,程序可以自己“照镜子”,了解自己的组成和结构,然后根据这些信息来做出相应的动作。
具体来说,Java的反射API提供了一组类和接口,允许我们在运行时:
- 获取类的信息:可以获取类的构造方法、成员变量、方法等信息。
- 创建类的实例:可以根据类的信息动态地创建对象实例。
- 调用对象的方法:可以调用类的方法,甚至是私有方法。
- 访问和修改对象的字段:可以获取和设置对象的字段的值,即使它们是私有的。
反射的一些应用场景:
-
框架和库开发: 许多框架和库,如Spring框架、Hibernate ORM等,都广泛使用了反射。通过反射,这些框架可以在不知道类的具体信息的情况下,动态地加载类、调用类的方法和访问类的字段,从而实现了更高级别的抽象和灵活性。
-
注解处理: 注解处理器通常使用反射来检查、解析和处理源代码中的注解。例如,可以使用反射来动态地查找和处理被特定注解标记的类、方法或字段,并执行相应的操作。
-
配置文件解析: 许多配置文件格式,如XML、JSON等,可以通过反射来解析和映射到Java对象,从而实现配置文件和Java对象之间的转换。
了解反射如何使用:
- 获取类的
Class
对象:可以使用类名的.class
属性或者通过对象的.getClass()
方法来获取类的Class
对象。- 获取类的构造方法、字段和方法等信息:通过
Class
对象的方法来获取类的构造方法、字段和方法等信息。- 创建对象实例或调用方法:根据获取到的构造方法或方法对象,可以动态地创建对象实例或调用对象的方法。
下面是一个简单的示例,演示了如何使用反射来动态地创建对象实例并调用对象的方法:
import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class ReflectionExample { public static void main(String[] args) { try { // 获取类的 Class 对象 Class<?> clazz = Class.forName("com.example.MyClass"); // 获取类的构造方法 Constructor<?> constructor = clazz.getConstructor(); // 使用构造方法创建对象实例 Object obj = constructor.newInstance(); // 获取类的方法 Method method = clazz.getMethod("sayHello", String.class); // 调用对象的方法 method.invoke(obj, "World"); } catch (Exception e) { e.printStackTrace(); } } } class MyClass { public void sayHello(String name) { System.out.println("Hello, " + name + "!"); } }
在上面的示例中,我们首先通过
Class.forName()
方法获取了类MyClass
的Class
对象,然后使用getConstructor()
方法获取了类的无参数构造方法。接着,我们使用newInstance()
方法创建了MyClass
的对象实例。最后,使用getMethod()
方法获取了类的sayHello()
方法,并通过invoke()
方法调用了该方法。
十一、注解
在Java中,注解(Annotation)是一种特殊的标记,它可以用来为程序的元素(类、方法、字段等)提供元数据信息。通俗地说,注解就像是程序中的“标签”,用来给程序的各个部分打上特定的标记,以便编译器、解释器或其他工具在后续操作中可以识别和处理这些标记。
注解以 @
符号开头,紧跟着注解的名称和一对括号,括号中可以包含一些参数。例如:
@Author(name = "John", date = "2022-03-15")
public class MyClass {
// 类的成员和方法
}
在上面的示例中,@Author
就是一个自定义的注解,它为类 MyClass
添加了一个作者的信息。
Java提供了一些内置的注解,如 @Override
、@Deprecated
、@SuppressWarnings
等,它们可以用来标记方法的重写、类的废弃、关闭编译器警告等。
1.@Override
是一个内置的注解,用于在子类中标记方法覆盖了父类中的方法。在Java中,当我们重写(覆盖)父类中的方法时,为了确保我们的重写操作正确,我们可以使用 @Override
注解来帮助编译器检查是否正确地覆盖了父类中的方法。
具体来说,@Override
注解有以下几个作用:
-
编译时检查: 编译器会检查带有
@Override
注解的方法是否真正地覆盖了父类中的方法。如果子类中的方法没有正确地覆盖父类中的方法(例如方法名拼写错误、参数列表不匹配等),编译器会产生编译错误,从而帮助我们及时发现并修复错误。 -
代码可读性:
@Override
注解可以增强代码的可读性,使得代码更加清晰和易懂。通过这个注解,我们可以清晰地知道某个方法是重写了父类的方法,而不是一个新的方法。
使用 @Override
注解非常简单,只需在子类中重写父类的方法时,在方法声明前添加 @Override
注解即可。例如:
class Parent {
public void doSomething() {
System.out.println("Parent's method");
}
}
class Child extends Parent {
@Override
public void doSomething() {
System.out.println("Child's method");
}
}
在上面的示例中,Child
类重写了 Parent
类中的 doSomething()
方法,并使用了 @Override
注解来标记这个重写操作。如果 @Override
注解被添加到 doSomething()
方法上,但实际上并没有正确地覆盖父类中的方法,编译器将会报错,帮助我们及时发现并修复问题。
2.
@Deprecated
是一个Java内置的注解,用于标记某个类、方法、字段或其他元素已经被废弃(过时),不推荐再使用。当我们在编写Java程序时,如果发现某个类、方法或字段已经不建议再使用,但为了兼容性或其他原因暂时还不能删除,就可以使用 @Deprecated
注解来标记它们。
具体来说,@Deprecated
注解有以下几个作用:
-
警告开发者: 通过
@Deprecated
注解,开发者可以清楚地知道某个元素已经被废弃,不建议再使用。当其他开发者在使用这个元素时,编译器会产生警告,提醒他们尽量避免使用这个元素。 -
文档说明: 使用
@Deprecated
注解可以在API文档中明确地标记某个元素已经被废弃。这样,其他开发者在查阅文档时可以清楚地了解到哪些元素不建议再使用。 -
向后兼容性: 尽管某个元素已经被废弃,但为了保持向后兼容性,可能暂时不能立即删除。使用
@Deprecated
注解可以提醒开发者在适当的时候考虑替代方案,并逐步停止使用这个废弃的元素。
使用 @Deprecated
注解非常简单,只需在要标记为废弃的元素上添加这个注解即可。例如:
@Deprecated
public class OldClass {
// 类的成员和方法
}
public class Main {
@Deprecated
public void oldMethod() {
// 方法的实现
}
}
在上面的示例中,OldClass
类和 oldMethod()
方法被使用 @Deprecated
注解标记为废弃的。当其他开发者使用这些被标记为 @Deprecated
的元素时,编译器会产生警告,提醒他们尽量避免使用这些元素,以免造成不必要的问题。
十二、JavaBean
在Java编程中,JavaBean是一种符合特定约定的普通Java类,用于表示应用程序中的数据对象。JavaBean通常包含私有字段(称为属性)、公共的getter和setter方法以及无参构造方法。这些属性和方法遵循了一定的命名规范,以便可以被JavaBean工具和其他框架自动识别和操作。
通俗来说,JavaBean就像是一个简单的数据容器,用于封装应用程序中的数据,方便在不同的组件之间传递和操作数据。
以下是JavaBean的一些特征和约定:
-
私有字段(属性): JavaBean通常包含私有的字段,用于存储对象的状态或数据。
-
公共的getter和setter方法: JavaBean提供了公共的getter和setter方法,用于访问和修改对象的属性值。getter方法用于获取属性的值,setter方法用于设置属性的值。
-
无参构造方法: JavaBean通常包含一个无参构造方法,用于创建对象实例。这样可以确保在创建JavaBean对象时不会出现错误。
-
可序列化: JavaBean可以实现Serializable接口,使得它们可以被序列化和反序列化,以便在网络上进行传输或持久化到磁盘中。
例如,下面是一个简单的JavaBean示例:
public class Person {
private String name;
private int age;
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
在这个示例中,Person类是一个JavaBean,它包含了私有的name和age属性,以及对应的公共的getter和setter方法,同时还有一个无参构造方法。通过这些属性和方法,Person类可以方便地封装和操作个人信息。
十三、this关键字
在Java中,this
是一个关键字,用于引用当前对象的引用。它通常用于以下几个方面:
- 引用当前对象: 当在类的实例方法中使用
this
关键字时,它表示当前对象的引用。通过this
关键字,我们可以在实例方法中访问当前对象的成员变量和方法。
public class MyClass {
private int number;
public MyClass(int number) {
this.number = number; // 使用this关键字引用当前对象的成员变量
}
public void printNumber() {
System.out.println(this.number); // 使用this关键字引用当前对象的成员变量
}
}
- 区分局部变量和成员变量: 当方法的参数名或局部变量名与成员变量名相同时,可以使用
this
关键字来明确指示访问的是成员变量。
public class MyClass {
private int number;
public MyClass(int number) {
this.number = number; // 使用this关键字明确指示访问成员变量
}
public void setNumber(int number) {
this.number = number; // 使用this关键字明确指示访问成员变量
}
}
- 在构造方法中调用另一个构造方法: 在构造方法中使用
this
关键字可以调用同一个类中的另一个构造方法。这种方式称为构造方法的重载。
public class MyClass {
private int number;
public MyClass() {
this(0); // 调用另一个构造方法
}
public MyClass(int number) {
this.number = number;
}
}
总之,this
关键字用于引用当前对象,可以用来访问当前对象的成员变量和方法,明确指示访问成员变量,以及在构造方法中调用另一个构造方法。
十四、a=a+b与a+=b区别?
+= 隐式的将加操作的结果类型强制转换为持有结果的类型。如果两个整型相加,如 byte、short 或者 int,首先会将它们提升到 int 类型,然后在执行加法操作。
而 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错
byte a = 127;
byte b = 127;
b = a + b; // error : cannot convert from int to byte
b += a; // ok
十五、什么是Java语法糖?
Java语法糖(Syntactic Sugar)是一种语言特性,它并不改变语言的功能或语义,但是提供了更简洁、更方便的语法形式,使得编码更加容易和直观。语法糖使得代码更易读、易写,但不会引入新的概念或功能。
1、switch
语句支持 String
类型
在Java SE 7及以后的版本中,switch
语句是支持 String
类型的。这意味着你可以在 switch
语句中使用 String
对象作为判断条件。
在早期的Java版本中,switch
语句只支持整数类型(如 int
、short
、byte
、char
)和枚举类型。但是随着Java SE 7的引入,Java语言规范进行了更新,允许 switch
语句使用 String
类型作为判断条件。
例如,你可以这样使用 switch
语句处理不同的字符串情况:
public class SwitchExample {
public static void main(String[] args) {
String day = "Monday";
switch (day) {
case "Monday":
System.out.println("Today is Monday");
break;
case "Tuesday":
System.out.println("Today is Tuesday");
break;
case "Wednesday":
System.out.println("Today is Wednesday");
break;
default:
System.out.println("Unknown day");
break;
}
}
}
在上面的示例中,day
是一个 String
对象,在 switch
语句中根据不同的字符串值执行不同的代码块。
2、泛型
让我们来看一个通俗的例子来解释为什么泛型可以被称为语法糖。
假设你有一个水果篮子,你希望这个篮子可以放不同种类的水果。在不使用泛型的情况下,你可能会创建一个名为 Basket
的类,它可以存放 Apple
、Banana
、Orange
等水果。在这种情况下,你的代码可能如下所示:
// 不使用泛型的水果篮子
public class Basket {
private Object item;
public void put(Object item) {
this.item = item;
}
public Object get() {
return item;
}
}
// 使用水果篮子
public class Main {
public static void main(String[] args) {
Basket basket = new Basket();
basket.put(new Apple());
// 需要进行类型转换
Apple apple = (Apple) basket.get();
apple.eat();
}
}
在这个例子中,我们创建了一个名为 Basket
的类,它可以存放任何类型的对象。但是,当你从篮子中取出水果时,你必须进行类型转换,因为 get()
方法返回的是 Object
类型,而不是特定类型的水果。
现在,让我们看看使用泛型如何简化这个例子:
// 使用泛型的水果篮子
public class Basket<T> {
private T item;
public void put(T item) {
this.item = item;
}
public T get() {
return item;
}
}
// 使用水果篮子
public class Main {
public static void main(String[] args) {
Basket<Apple> basket = new Basket<>();
basket.put(new Apple());
// 不需要进行类型转换
Apple apple = basket.get();
apple.eat();
}
}
在这个例子中,我们使用了泛型类 Basket<T>
,其中 T
是一个类型参数,它代表了篮子中存放的对象类型。在使用泛型的水果篮子时,我们不再需要进行类型转换,因为 get()
方法返回的是特定类型的水果,而不是 Object
类型。
通过泛型,我们可以在编译时进行类型检查,并且不再需要手动进行类型转换。这样,我们的代码变得更加简洁清晰,并且更加安全可靠。因此,泛型可以被认为是一种语法糖,它提供了一种更加方便的语法形式,同时不改变Java语言的基本特性。
3、自动拆箱与装箱
自动装箱使得原始数据类型(如 int
、double
等)可以直接赋值给对应的包装类对象(如 Integer
、Double
等),而无需手动进行装箱操作。拆箱则是自动将包装类对象转换为对应的原始数据类型。例如,可以直接将 int
赋值给 Integer
对象,也可以直接将 Integer
对象赋值给 int
。
4、可变长参数
可变参数允许方法接受数量可变的参数,而无需显式地定义参数数组。这种语法形式使得调用方法时更加灵活,并且更容易理解。
详细可见Java面试八股文(自用)(一)-CSDN博客(第十七条)
5、枚举
枚举是一种特殊的数据类型,它是一组命名的常量集合。在Java中,枚举类型被用于定义一组有限的常量,这些常量通常表示一组相关联的值。枚举在Java中的语法相对简单,但在背后它们实际上是使用类来实现的。在编译时,Java编译器会将枚举类型转换为一个继承自 java.lang.Enum
的类,每个枚举常量都是这个类的实例。
枚举不能被继承。
当我们使用枚举时,通常是为了定义一组相关的常量,并且这些常量在程序中是固定且有限的。让我们来举一个简单的例子来说明如何使用枚举。
假设我们要表示一周中的每一天,我们可以使用枚举来定义这些天:
// 定义一个枚举类型表示一周中的每一天
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
现在我们可以在程序中使用这个枚举类型 Day
。例如,我们可以声明一个变量来表示今天是星期几:
public class EnumExample {
public static void main(String[] args) {
// 声明一个 Day 类型的变量表示今天是星期几
Day today = Day.MONDAY;
// 输出今天是星期几
System.out.println("Today is " + today);
}
}
在这个例子中,我们声明了一个 Day
类型的变量 today
,并将其初始化为 Day.MONDAY
。然后我们使用 System.out.println()
方法输出今天是星期几。
枚举类型在编译时会被转换为类,每个枚举常量都是该类的一个实例。因此,我们可以像使用普通类一样使用枚举,比如传递给方法、作为方法的返回值等。
6、增强的for-each循环
当我们使用增强的 for
循环时,我们不需要手动管理索引,而是直接遍历集合或数组中的元素。这样可以使代码更加简洁清晰,减少了编写循环的复杂性。
下面是一个使用增强的 for
循环遍历数组的简单例子:
public class EnhancedForLoopExample {
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
// 使用增强的 for 循环遍历数组
for (int number : numbers) {
System.out.println(number);
}
}
}
在这个例子中,我们有一个整型数组 numbers
,我们使用增强的 for
循环遍历该数组。在每次迭代中,number
变量将依次被赋值为数组中的元素,然后我们可以直接使用 number
变量来访问该元素的值。
类似地,我们也可以使用增强的 for
循环来遍历集合,例如 ArrayList
:
import java.util.ArrayList;
import java.util.List;
public class EnhancedForLoopExample {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
// 使用增强的 for 循环遍历集合
for (String fruit : fruits) {
System.out.println(fruit);
}
}
}
在这个例子中,我们有一个 ArrayList
集合 fruits
,我们使用增强的 for
循环遍历该集合。在每次迭代中,fruit
变量将依次被赋值为集合中的元素,然后我们可以直接使用 fruit
变量来访问该元素的值。
总的来说,增强的 for
循环使得遍历数组和集合变得更加简单和直观,减少了代码的复杂性
7.lambda表达式
十六、List、Set、Queue、Map
List有序可重复
Set无序不重复
Queue有先后顺序可重复
Map键值对key无序不重复,value无序可重复
十七、ArrayList
和 Array区别
ArrayList
和 Array
是 Java 中两种不同的数据结构,它们之间有以下几点区别:
-
动态性:
Array
是静态数据结构,一旦创建大小就固定,无法动态改变。如果需要更改数组的大小,必须创建一个新的数组,并将原始数据复制到新数组中。ArrayList
是动态数组,它可以根据需要动态增长或缩小。当元素数量超过当前容量时,ArrayList
会自动增加其容量。
-
大小:
Array
有固定的大小,一旦创建就无法改变。ArrayList
的大小是可变的,可以根据需要动态调整。
-
元素类型:
Array
可以存储基本数据类型(如 int、double 等)或引用数据类型(如对象)。ArrayList
只能存储对象,不能直接存储基本数据类型。如果需要存储基本数据类型,必须使用其对应的包装类(如Integer
、Double
等)。
-
性能:
Array
在访问元素时性能较好,因为它是连续的内存空间。ArrayList
在插入和删除元素时性能较好,因为它使用了动态数组,不需要移动大量的元素。
-
语法:
Array
的声明和初始化比较简单,直接使用[]
操作符即可。ArrayList
是java.util
包中的一个类,需要使用new
关键字进行实例化,并指定泛型参数。
// 初始化一个 String 类型的数组
String[] stringArr = new String[]{"hello", "world", "!"};
// 修改数组元素的值
stringArr[0] = "goodbye";
System.out.println(Arrays.toString(stringArr));// [goodbye, world, !]
// 删除数组中的元素,需要手动移动后面的元素
for (int i = 0; i < stringArr.length - 1; i++) {
stringArr[i] = stringArr[i + 1];
}
stringArr[stringArr.length - 1] = null;
System.out.println(Arrays.toString(stringArr));// [world, !, null]
// 初始化一个 String 类型的 ArrayList
ArrayList<String> stringList = new ArrayList<>(Arrays.asList("hello", "world", "!"));
// 添加元素到 ArrayList 中
stringList.add("goodbye");
System.out.println(stringList);// [hello, world, !, goodbye]
// 修改 ArrayList 中的元素
stringList.set(0, "hi");
System.out.println(stringList);// [hi, world, !, goodbye]
// 删除 ArrayList 中的元素
stringList.remove(0);
System.out.println(stringList); // [world, !, goodbye]
总的来说,ArrayList
是 Array
的一种动态扩展版本,它提供了更多的灵活性和方便性,但也会带来一些性能上的开销。在大多数情况下,ArrayList
是更常用的选择,特别是在需要频繁插入、删除或动态调整大小的情况下。
十八、断言
断言(Assertion)是一种开发过程中用来验证程序逻辑的机制。断言通常用于在代码中指定一个布尔表达式,该表达式应该为真。如果布尔表达式为假,则会触发断言异常。在Java中,使用assert
关键字来实现断言。
断言的语法如下所示:
assert booleanExpression;
当布尔表达式为假时,将抛出 AssertionError
异常。通常,在开发和测试阶段使用断言来检查代码中的假设是否成立,以帮助开发人员识别和解决问题。在生产环境中,默认情况下,断言通常是被禁用的。要启用断言,可以使用 -ea
或 -enableassertions
参数在命令行中运行 Java 程序。
编写一个程序,要求输入一个正整数,并检查输入是否为正数,可以使用断言来验证。以下是一个简单的示例:
import java.util.Scanner;
public class AssertionExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入一个正整数:");
int number = scanner.nextInt();
// 使用断言检查输入的数是否为正数
assert number > 0 : "输入的数不是正整数";
System.out.println("输入的数是正整数:" + number);
}
}
在这个示例中,用户被要求输入一个整数。程序使用断言来验证输入的数是否为正数。如果输入的数不是正数,则断言将会失败,抛出 AssertionError
异常,并且带有指定的错误消息 "输入的数不是正整数"。如果输入的数是正数,则程序继续执行并输出相应的消息。
十九、内部类
二十、匿名内部类
-
没有名称:匿名类没有类名,因此它们不能被其他代码引用或重复使用。
-
直接创建:匿名类通常是在使用的地方直接创建的,例如作为方法参数、变量初始化或者数组初始化的一部分。
-
实现接口或抽象类:匿名类通常用于实现接口或抽象类,但也可以扩展类并提供自己的方法实现。