JVM: JAVA虚拟机,Java程序运行在Java虚拟机上。针对不同 系统的实现(Windows,Linux,macOS)不同的JVM,因此Java语言可以实现跨平 台
JRE:JAVA运行环境
JDK: JAVA开发工具包
字节码,就是Java程序经过编译之类产生的.class文件,字节码能够被虚拟机识别,从而实现Java程序的跨平台性。
编译 :将我们的代码(.java)编译成虚拟机可以识别理解的字节码(.class)
解释 :虚拟机执行Java字节码,将字节码翻译成机器能识别的机器码
执行 :对应的机器执行二进制机器码
编译型:编译器会对源代码一次性翻译成可以被机器识别的机器码再执行
解释型:解释器会对源代码逐行解释成机器码并立即执行
JAVA是编译与解析并存,从.java源文件到.class字节码处是编译,然后交给JVM进行解释并运行。
java语言数据类型分为两种:基本数据类型
和 引用数据类型
Java 所有的数值型变量可以相互转换,当把一个表数范围小的数值或变量直接赋给 另一个表数范围大的变量时,可以进行自动类型转换;反之,需要强制转换。
在Java中,自动类型转换(Automatic Type Casting)是指编译器会自动将一种数据类型转换为另一种数据类型,而不需要程序员显式地执行转换操作。这种类型转换通常发生在表达式中,涉及到的数据类型之间存在兼容性关系。
以下是一些常见的自动类型转换情况:
double d = 3.14;
int i = (int) d; // 自动将double转换为int,高精度转为低精度必须强制转换
int i = 256;
long l = i; // 自动将int转换为long
Integer i1 = 100;
Number n = i1; // 自动将Integer转换为Number
Animal animal = new Dog();
Dog dog = (Dog) animal; // 自动将Animal转换为Dog
需要注意的是,虽然自动类型转换可以简化代码编写,但有时也可能会导致数据丢失或类型转换异常。因此,在进行自动类型转换时,需要确保转换的数据类型之间是兼容的,并且转换后的数据不会失去精度或导致异常。
例如,对于int类型,有一个对应的包装类Integer。当我们将一个Integer对象赋值给一个int类型的变量时,Java会自动将Integer对象拆箱成int值,这就是自动拆箱。相反,当我们从一个int类型的变量赋值给Integer对象时,Java会自动将int值封箱成Integer对象,这就是自动封箱。
下面是一些自动拆箱与封箱的示例:
// 自动拆箱
Integer integer = 100; // Integer对象自动拆箱成int值
int num = integer; // num的值是100
// 自动封箱
int num2 = 200;
Integer integer2 = num2; // int值自动封箱成Integer对象
需要注意的是,自动拆箱和自动封箱功能只适用于赋值操作,不能用于方法参数传递或返回值类型等场景,因为在这些场景中需要明确数据类型和对应的包装类之间的转换。
Java中常见的封箱类型有以下几种:
这些包装类型都是不可变的(immutable),即它们的值一旦被创建就无法更改。它们也都有一些常用的静态方法,例如parseXXX()可以将字符串转换成相应的类型,toString()可以将它们转换成字符串等。
在Java中,
&
和&&
都是逻辑运算符,但它们在操作方式和功能上有一些不同。
**&**
和**&&**
都可以用于表示逻辑与操作,即两个表达式都必须为真,整个表达式才为真。
例如,对于以下两个表达式:
a & b
a && b
只有当a和b都为真时,这两个表达式的值才为真。
然而,&
和&&
在处理表达式的顺序和短路方面有所不同。
&
运算符的特点是它不会进行短路操作。即使第一个表达式的值为false,&
仍然会执行两个表达式。例如:
boolean a = false;
boolean b = true;
if (a & b) {
System.out.println("Both are true");
}
这段代码将会执行并输出"Both are true",因为虽然a的值为false,但b的值为true,所以a & b的结果为true。
相比之下,&&
运算符具有短路功能。当第一个表达式的值为false时,则不再计算第二个表达式。例如:
boolean a = false;
boolean b = true;
if (a && b) {
System.out.println("Both are true");
}
这段代码将不会执行并输出"Both are true",因为a的值为false,所以当计算a && b
时,由于短路功能,b的值将不会被计算,因此整个表达式的值为false。需要注意的是,&&
运算符的短路功能不能被逆转。也就是说,如果需要两个表达式都为真才能返回真值,那么不能只写一个&&
运算符。
例如,以下代码是错误的:
if (username != null && !username.equals("")) { ... }
这里应该使用&
运算符:
if (username != null & !username.equals("")) { ... }
因为如果username为null,调用equals方法会抛出NullPointerException异常。而&&运算符的短路功能可以保证当username为null时,不会执行equals方法的调用。
Java的switch语句是一种用于执行多个条件分支的控制结构。它允许您根据一个变量的值来选择不同的代码块执行。下面是switch语句的基本语法:
switch (expression) {
case value1:
// 执行相应的代码块
break;
case value2:
// 执行相应的代码块
break;
...
default:
// 当没有匹配的case时执行的代码块
}
下面是一个使用switch语句的示例:
int day = 3;
switch (day) {
case 1:
System.out.println("Monday");
break;
case 2:
System.out.println("Tuesday");
break;
case 3:
System.out.println("Wednesday");
break;
case 4:
System.out.println("Thursday");
break;
case 5:
System.out.println("Friday");
break;
case 6:
System.out.println("Saturday");
break;
case 7:
System.out.println("Sunday");
break;
default:
System.out.println("Invalid day");
}
在这个示例中,根据变量day的值,switch语句选择相应的代码块执行。在这种情况下,day的值为3,因此会执行与case 3关联的代码块,输出为"Wednesday"。如果day的值不在1到7之间,将执行default代码块并输出"Invalid day"。
Java面向对象的三大特征包括:封装、继承和多态。
重载(Overload)和重写(Override)是Java中两个重要的概念,都是面向对象编程的一部分,但它们之间存在明显的区别。
简单来说,重载是同一个类中的方法名相同但参数不同,而重写是子类中的方法覆盖了父类的方法。
以下是方法重载的规则:
在调用方法时,会根据传递给方法的参数的类型、个数和排列顺序来决定具体使用哪个方法。这有助于简化代码并提高代码的可读性和可维护性。
以下是重载和重写的例子:
重载例子:
public class Example {
void funcA(int a){
System.out.println("功能A,参数是整型");
}
void funcA(String a){
System.out.println("功能A,参数是字符串");
}
}
在这个例子中,funcA被重载了两次。一次接受一个整数作为参数,另一次接受一个字符串作为参数。当你调用funcA时,会根据你提供的参数类型来决定调用哪个方法。
重写例子:
public class Parent {
void funcA(int a){
System.out.println("父类功能A,参数是整型");
}
}
public class Child extends Parent {
@Override
void funcA(int a){
System.out.println("子类功能A,参数是整型");
}
}
在这个例子中,子类Child重写了父类Parent的funcA方法。如果你创建一个Child类的对象并调用funcA,那么会调用子类中的方法。如果你想要调用父类中的方法,那么需要使用super.funcA(a);来显式调用。
final表示不可变的意思,可用于修饰类、属性和方法:
还得注意的是,这里的不可变指的是变量的引用不可变,不是引用指向的内容的不可变。
final StringBuilder sb = new StringBuilder("1234");
sb.append("5");
System.out.println(sb); //12345
在Java中,和equals()方法在比较对象时具有不同的用途和含义。
操作符用于比较两个对象的引用是否相等 ( 基本数据类型比较的是值,引⽤数据类型比较的是内存地址 )。也就是说,它检查两个对象是否指向内存中的同一个对象。
例如:
String s1 = new String("Hello");
String s2 = new String("Hello");
if (s1 == s2) {
System.out.println("s1 and s2 reference the same object");
}
在这个例子中,s1和s2是两个不同的对象,虽然它们的内容相同,但是它们在内存中的位置是不同的,所以s1 == s2返回false。
equals()方法用于比较两个对象的值是否相等。默认情况下,equals()方法的行为与==操作符相同,它比较两个对象的引用。然而,许多类(如String,Integer等)重写了equals()方法,使其比较对象的值而不是引用。例如:
String s1 = new String("Hello");
String s2 = new String("Hello");
if (s1.equals(s2)) {
System.out.println("s1 and s2 have the same value");
}
在这个例子中,虽然s1和s2不是同一个对象(它们的引用不同),但它们的值是相同的,所以s1.equals(s2)返回true。
总结一下,==和equals()在Java中的主要区别是:
Java 中所有的异常都是 Throwable 类的子类,Throwable 类又可以分为 Error 和 Exception 两个子类。
Exception 类又可以分为运行时异常(RuntimeException)和非运行时异常(CheckedException)。
异常处理:
可以使用 try-catch 块来捕获和处理异常。Try 块包含可能会抛出异常的代码,而 catch 块则用于捕获并处理异常。Catch 块可以指定要捕获的异常类型,并处理该类型的异常。
下面是一个基本的try-catch-finally结构的示例:
try {
// 尝试执行的代码
} catch (Exception e) {
// 处理异常的代码
} finally {
// 总是执行的代码,无论是否引发异常
}
无论try块中的代码是否成功执行,finally块中的代码都会执行。如果try块中的代码引发异常,那么catch块中的代码将处理该异常,然后执行finally块中的代码。如果try块中的代码没有引发异常,那么将跳过catch块,直接执行finally块中的代码。
在使用外部资源(如文件和数据库连接)时,使用try-with-resources语句可以自动关闭这些资源。但是,如果需要执行其他清理操作,或者需要在关闭资源之前执行其他操作,则仍然需要使用try-catch-finally结构。
Java 中的方法可以声明它们可能会抛出的异常。这样,调用该方法的代码就需要进行相应的异常处理。声明异常的关键字是 throws。
在 Java 中,可以使用 throw 关键字手动抛出异常。这通常用于在程序中的特定条件下触发异常。
Java 中可以创建自定义异常类,以便更好地描述程序中可能出现的异常情况。自定义异常类应该是 Exception 或其子类的子类。
在Java中,finally块的执行顺序是在try块之后,无论是否发生异常。当try块中的代码执行完成后,不管是否发生异常,finally块中的代码都会被执行(return 之前会先执行finally)。
当出现异常时,try块中的代码将不会继续执行,而是跳转到相应的catch块中处理异常。一旦catch块中的代码执行完毕,控制流将跳转回finally块中执行清理操作。
如果finally块中的代码执行期间没有发生异常,那么finally块的代码将正常执行并结束。如果finally块中的代码发生异常,那么该异常将会覆盖try块或catch块中引发的异常。
总之,finally块的执行顺序始终在try块之后,无论是否发生异常。它通常用于执行清理操作,例如关闭文件或数据库连接等资源。
流按照不同的特点,有很多种划分方式。
Java IO流共涉及40多个类,看上去杂乱,其实都存在一定的关联, Java I0流的40多 个类都是从如下4个抽象类基类中派生出来的。
Java泛型是JDK 5.0引入的一个新特性,用于在编译期间提供更强的类型检查。泛型允许程序员在类、接口和方法中使用类型参数。这意味着你可以创建一些能在多种数据类型上操作的代码,而这些代码在编译时仍然保持类型安全。
泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。
泛型的工作方式是通过在定义类、接口或方法时使用类型参数。类型参数在使用泛型的地方可以用实际的类型来替代,这样就可以在编译期间进行类型检查。
例如,你可以创建一个名为"List"的泛型接口,该接口使用一个类型参数T:
interface List<T> {
void add(T item);
T get(int index);
}
然后,你可以使用任何你需要的类型来实现这个接口,如:
class StringList implements List<String> {
// 实现方法...
}
类型擦除(Type Erasure)是Java泛型的一部分,这是Java泛型实现的一种方式。在Java中,泛型是通过类型擦除来实现的,这意味着在运行时,关于泛型类型的所有信息都会被擦除。擦除的主要原因是为了使新的Java版本兼容旧的Java代码。
例如,考虑以下代码:
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
在运行时,无论是stringList还是integerList,它们都是List的一个实例,无法在运行时区分它们实际持有的元素类型。这就是类型擦除的效果。
常用的通配符有:
Java 注解(Annotation)是 JDK 5.0 引入的一种元数据,用于将某些信息与代码关联起来。注解可以应用于类、方法、变量、参数和包等元素上,用于提供额外的信息或元数据,以便于编译器、开发工具和运行时环境使用。
Java 注解的基本语法如下:
@annotationName(value1, value2, ..., valueN)
其中,annotationName 是注解的名称,value1, value2, …, valueN 是注解的属性值,可以有多个属性值。
Java 中内置了一些注解,如 @Override、@Deprecated 等。同时,也可以自定义注解。自定义注解需要使用 @interface 关键字来定义,可以定义注解的名称、属性和属性值类型等信息。
注解生命周期有三大类,分别是:
以下是一个简单的自定义注解示例:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // 作用位置
public @interface MyAnnotation {
String value() default "";
int count() default 0;
}
这个注解可以用于方法上,具有两个属性 value 和 count,分别默认为空字符串和 0。使用 @Retention 和 @Target 注解可以指定注解的保留策略和作用目标。在这个示例中,@Retention 指定了注解的保留策略为 RUNTIME,表示注解在运行时仍然可用;@Target 指定了注解的作用目标为 METHOD,表示注解只能应用于方法上。
使用自定义注解时,可以在代码中使用 @annotationName 语法来应用注解,并在代码中使用注解的属性值来访问注解的属性。例如:
@MyAnnotation(value = "hello", count = 3)
public void myMethod() {
// do something
}
在这个示例中,@MyAnnotation 注解被应用于 myMethod 方法上,并设置了 value 属性为 “hello”,count 属性为 3。在代码中可以使用 MyAnnotation 注解的属性值来访问这些属性。
再比如 Spring 常见的 Autowired ,就是 RUNTIME 的,所以在运行的时候可以通 过反射得到注解的信息,还能拿到标记的值 required 。
Java反射(Reflection)是在运行时对于任意一个类,都能够了解这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
Java反射机制主要提供了以下功能:
Java反射机制的相关类都位于java.lang.reflect包中,主要有以下类:
Java反射机制的使用步骤如下:
以下是一个简单的例子说明如何使用Java反射:
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) {
try {
// 获取String类的Class对象
Class<?> c = Class.forName("java.lang.String");
// 创建String对象
String str = (String) c.newInstance();
str = "Hello World";
// 获取String类的length()方法
Method method = c.getMethod("length");
// 调用length()方法
int length = (Integer) method.invoke(str);
System.out.println("字符串长度: " + length);
} catch (Exception e) {
e.printStackTrace();
}
}
}
在这个例子中,
1、首先通过Class.forName()获取java.lang.String类的Class对象。
2、然后,我们使用Class对象的newInstance()方法创建一个新的String对象。
3、接着,我们使用getMethod()获取String类的length()方法,并使用invoke()方法调用length()方法获取字符串的长度。
这就是Java反射的基本使用方式,通过反射,我们可以在运行时动态地获取类的信息并操作类的属性和方法。
使用Java反射需要注意以下几点:
反射实现原理
Java反射的原理是Java虚拟机在运行时可以通过字节码文件找到对应的类、方法以及属性等。
具体来说,当Java程序加载一个类时,Java虚拟机会为这个类创建一个Class对象,通过这个Class对象可以获取这个类的所有属性和方法。同时,Java虚拟机还会将这个类的所有属性和方法存储在一个方法区中,以便在运行时可以通过反射来访问这些属性和方法。
总的来说,Java反射机制是在Java虚拟机的层面上实现的,它使得Java程序可以在运行时动态地获取类的信息并调用类的方法和属性,从而实现更加灵活和动态的编程方式。
Stream是一个能描述某些指令序列的新抽象概念。这个序列可以来自于各种数据源,例如集合、数组、I/O通道等。Stream API支持串行和并行操作,并且支持延迟操作,这意味着只有在需要结果时才会执行操作。
以下是一些基本的Stream操作:
List<String> names = Arrays.asList("Peter", "Anna", "Mike", "Xenia");
List<String> filtered = names.stream().filter(name -> name.startsWith("P")).collect(Collectors.toList());
List<String> names = Arrays.asList("Peter", "Anna", "Mike", "Xenia");
List<String> mapped = names.stream().map(String::toUpperCase).collect(Collectors.toList());
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, Integer::sum);
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Set<Integer> set = numbers.stream().collect(Collectors.toSet());
List<String> names = Arrays.asList("Peter", "Anna", "Mike", "Xenia");
names.stream().forEach(System.out::println);
这些操作可以链接在一起,形成一个复杂的操作链。例如:
List<String> names = Arrays.asList("Peter", "Anna", "Mike", "Xenia");
names.stream()
.filter(name -> name.startsWith("P"))
.map(String::toUpperCase)
.forEach(System.out::println);
以上代码首先过滤出以"P"开头的名字,然后将这些名字转换为大写,最后打印出来。
Java lambda 表达式是一种匿名函数,可以用来定义简洁、灵活的代码块,并且可以作为参数传递给其他函数或方法。Lambda 表达式在 Java 8 中被引入,并且经常用于简化集合操作和其他函数式编程任务。
Lambda 表达式的语法如下:
(parameter1, parameter2, ..., parameterN) -> {
// lambda expression body
};
其中,参数列表中的参数类型和返回类型都可以省略,也可以显式地指定。如果只有一个参数,那么参数列表的括号可以省略。如果 lambda 表达式的主体只有一行,那么花括号也可以省略。
下面是一个简单的例子,演示了如何使用 lambda 表达式来对一个整数列表进行过滤,只保留偶数:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
在这个例子中,filter 方法接受一个 lambda 表达式作为参数,该表达式接受一个整数 n 并返回一个布尔值。在这个表达式中,如果 n 是偶数,则返回 true,否则返回 false。filter 方法使用这个 lambda 表达式来过滤列表中的元素,只保留满足条件的元素。最后,collect 方法将过滤后的元素收集到一个新的列表中。
在JDK 1.8中,接口可以包含默认方法。默认方法是一种有方法体(具体实现)的方法,可以被实现类继承和覆盖。
在接口中,可以使用default关键字定义默认方法,方法签名与抽象方法相同,但没有分号结尾。例如:
public interface MyInterface {
void abstractMethod();
default void defaultMethod() {
System.out.println("This is a default method in MyInterface.");
}
}
在实现该接口时,可以根据需要重写default修饰的方法:
public class MyClass implements MyInterface {
@Override
public void abstractMethod() {
System.out.println("This is an implementation of abstractMethod in MyClass.");
}
// 可以选择不重写默认方法,保持原有实现不变
}
当实现类继承父类和实现接口中有相同签名的方法时,会优先使用类中的方法,即“类优先原则”:
public class MyParentClass {
public void sameMethod() {
System.out.println("This is an implementation of sameMethod in MyParentClass.");
}
}
public class MyChildClass extends MyParentClass implements MyInterface {
// MyChildClass可以不重写default修饰的方法和抽象方法,就会执行父类中的方法
}
从Java 8开始,Oracle引入了一个全新的日期和时间API,这个API位于java.time包中,这个API提供了更加易于使用、更加直观、更加强大的日期和时间处理能力。以下是一些主要的类和功能:
这些类都是不可变的,每个实例都表示一个特定的日期、时间或间隔。这些类的方法通常都会返回一个新的实例,表示经过调整后的日期、时间或间隔。
新的日期和时间API还提供了许多格式化和解析的方法,可以将日期、时间、日期时间、时间间隔等转换为字符串,或者将字符串解析为这些类型。这些方法通常都定义在DateTimeFormatter类中。
以下是一些使用Java日期和时间API的示例:
// 获取当前日期
LocalDate today = LocalDate.now();
System.out.println("Today's date: " + today);
// 创建一个指定日期的实例
LocalDate date = LocalDate.of(2023, Month.MARCH, 17);
System.out.println("Date: " + date);
// 获取日期的年、月、日
int year = date.getYear();
int month = date.getMonthValue();
int day = date.getDayOfMonth();
System.out.println("Year: " + year);
System.out.println("Month: " + month);
System.out.println("Day: " + day);
// 获取当前时间
LocalTime now = LocalTime.now();
System.out.println("Current time: " + now);
// 创建一个指定时间的实例
LocalTime time = LocalTime.of(13, 45, 30);
System.out.println("Time: " + time);
// 获取时间的小时、分钟、秒
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();
System.out.println("Hour: " + hour);
System.out.println("Minute: " + minute);
System.out.println("Second: " + second);
// 获取当前日期时间
LocalDateTime now = LocalDateTime.now();
System.out.println("Current date and time: " + now);
// 创建一个指定日期时间的实例
LocalDateTime dateTime = LocalDateTime.of(2023, Month.MARCH, 17, 13, 45, 30);
System.out.println("Date and time: " + dateTime);
// 获取日期时间的年、月、日、小时、分钟、秒
int year = dateTime.getYear();
int month = dateTime.getMonthValue();
int day = dateTime.getDayOfMonth();
int hour = dateTime.getHour();
int minute = dateTime.getMinute();
int second = dateTime.getSecond();
System.out.println("Year: " + year);
System.out.println("Month: " + month);
System.out.println("Day: " + day);
System.out.println("Hour: " + hour);
System.out.println("Minute: " + minute);
System.out.println("Second: " + second);
// 获取当前日期时间带有时区信息
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println("Current date and time with timezone: " + zonedDateTime);
// 创建一个指定日期时间带有时区信息的实例
ZonedDateTime zonedDateTime2 = ZonedDateTime.of(LocalDateTime.of(2023, Month.MARCH, 17, 13, 45, 30), ZoneId.of("Europe/Paris"));
System.out.println("Date and time with timezone: " + zonedDateTime2);
是一个用于解决null安全问题的工具类。在Java 8中引入,Optional类提供了一种更优雅、更安全的方式来处理可能为null的值。
Optional类是一个容器类,它可以保存类型T的值,或者仅仅保存null。Optional提供了许多有用的方法来操作和处理可能为空的值,避免了传统的null检查。
以下是Optional类的一些常用方法:
Optional.of(T value)
: 创建一个Optional实例,如果value为null,则抛出NullPointerException。Optional.ofNullable(T value)
: 创建一个Optional实例,如果value为null,则返回一个空的Optional。Optional.empty()
: 返回一个空的Optional实例。Optional.isPresent()
: 判断Optional是否为空,如果为空返回false,否则返回true。Optional.get()
: 如果Optional不为空,返回其值,否则抛出NoSuchElementException。Optional.orElse(T other)
: 如果Optional不为空,返回其值,否则返回other。Optional.orElseGet(Supplier extends T> other)
: 如果Optional不为空,返回其值,否则调用other的get()方法并返回结果。Optional.orElseThrow(Supplier extends X> exceptionSupplier)
: 如果Optional不为空,返回其值,否则抛出异常,异常由exceptionSupplier提供。Optional.map(Function super T, ? extends U> mapper)
: 如果Optional不为空,对其值进行映射,并返回一个新的Optional。Optional.flatMap(Function super T, Optional> mapper)
: 如果Optional不为空,对其值进行映射,并将映射结果包装为一个新的Optional。下面是一个简单的示例,展示了如何使用Optional类:
import java.util.Optional;
public class OptionalExample {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = null;
// 使用Optional.of()创建一个Optional实例,如果值为null,抛出NullPointerException
Optional<String> optional1 = Optional.of(str1);
System.out.println("optional1: " + optional1.get()); // 输出:optional1: Hello
// 使用Optional.ofNullable()创建一个Optional实例,如果值为null,返回一个空的Optional
Optional<String> optional2 = Optional.ofNullable(str2);
System.out.println("optional2: " + optional2.orElse("default")); // 输出:optional2: default
// 判断Optional是否为空
boolean isPresent1 = optional1.isPresent(); // true
boolean isPresent2 = optional2.isPresent(); // false
System.out.println("isPresent1: " + isPresent1); // 输出:isPresent1: true
System.out.println("isPresent2: " + isPresent2); // 输出:isPresent2: false
// 获取Optional的值,如果为空抛出NoSuchElementException
String value1 = optional1.get(); // Hello
// String value2 = optional2.get(); // 抛出NoSuchElementException
System.out.println("value1: " + value1); // 输出:value1: Hello
// System.out.println("value2: " + value2); // 抛出NoSuchElementException
// 获取Optional的值,如果为空返回指定的默认值
String orElse1 = optional1.orElse("default"); // Hello
String orElse2 = optional2.orElse("default"); // default
System.out.println("orElse1: " + orElse1); // 输出:orElse1: Hello
System.out.println("orElse2: " + orElse2); // 输出:orElse2: default
}
}