JAVA基础

JAVA语言特点

  • 面向对象(封装,继承,多态)
  • 跨平台(原理通过java虚拟机运行程序,配套有多种系统的虚拟机)
  • 支持多线程
  • 编译与解释并存

JVM、JRE、JDK的区别

JVM: JAVA虚拟机,Java程序运行在Java虚拟机上。针对不同 系统的实现(Windows,Linux,macOS)不同的JVM,因此Java语言可以实现跨平 台
JRE:JAVA运行环境
JDK: JAVA开发工具包

之间的关系:JDK中包含JRE,JRE又包含JVM
image.png

JAVA的字节码

字节码,就是Java程序经过编译之类产生的.class文件,字节码能够被虚拟机识别,从而实现Java程序的跨平台性。

JAVA的编译过程

编译 :将我们的代码(.java)编译成虚拟机可以识别理解的字节码(.class)
解释 :虚拟机执行Java字节码,将字节码翻译成机器能识别的机器码
执行 :对应的机器执行二进制机器码

image.png

编译型语言与解释型语言

编译型:编译器会对源代码一次性翻译成可以被机器识别的机器码再执行
解释型:解释器会对源代码逐行解释成机器码并立即执行

JAVA是编译与解析并存,从.java源文件到.class字节码处是编译,然后交给JVM进行解释并运行。

JAVA数据类型

java语言数据类型分为两种:基本数据类型引用数据类型

image.png

JAVA类型转换

Java 所有的数值型变量可以相互转换,当把一个表数范围小的数值或变量直接赋给 另一个表数范围大的变量时,可以进行自动类型转换;反之,需要强制转换。
image.png
在Java中,自动类型转换(Automatic Type Casting)是指编译器会自动将一种数据类型转换为另一种数据类型,而不需要程序员显式地执行转换操作。这种类型转换通常发生在表达式中,涉及到的数据类型之间存在兼容性关系。
以下是一些常见的自动类型转换情况:

  1. 从小数类型向整数类型转换:
double d = 3.14;  
int i = (int) d; // 自动将double转换为int,高精度转为低精度必须强制转换
  1. 从无符号整数类型向有符号整数类型转换:
int i = 256;  
long l = i; // 自动将int转换为long
  1. 从较低的封装类型向较高的封装类型转换:
Integer i1 = 100;  
Number n = i1; // 自动将Integer转换为Number
  1. 从父类向子类转换:
Animal animal = new Dog();  
Dog dog = (Dog) animal; // 自动将Animal转换为Dog

需要注意的是,虽然自动类型转换可以简化代码编写,但有时也可能会导致数据丢失或类型转换异常。因此,在进行自动类型转换时,需要确保转换的数据类型之间是兼容的,并且转换后的数据不会失去精度或导致异常。

自动拆箱/封箱

  • 装箱:将基本类型用对应的引用类型包装起来
  • 拆箱:将包装类型转换为基本类型

例如,对于int类型,有一个对应的包装类Integer。当我们将一个Integer对象赋值给一个int类型的变量时,Java会自动将Integer对象拆箱成int值,这就是自动拆箱。相反,当我们从一个int类型的变量赋值给Integer对象时,Java会自动将int值封箱成Integer对象,这就是自动封箱。
下面是一些自动拆箱与封箱的示例:

java复制代码



// 自动拆箱  
Integer integer = 100; // Integer对象自动拆箱成int值  
int num = integer; // num的值是100  
  
// 自动封箱  
int num2 = 200;  
Integer integer2 = num2; // int值自动封箱成Integer对象

需要注意的是,自动拆箱和自动封箱功能只适用于赋值操作,不能用于方法参数传递或返回值类型等场景,因为在这些场景中需要明确数据类型和对应的包装类之间的转换。

Java中常见的封箱类型有以下几种:

  1. Integer:对应基本数据类型int,占4个字节,用于存储整数值。
  2. Double:对应基本数据类型double,占8个字节,用于存储双精度浮点数值。
  3. Long:对应基本数据类型long,占8个字节,用于存储长整数值。
  4. Float:对应基本数据类型float,占4个字节,用于存储单精度浮点数值。
  5. Short:对应基本数据类型short,占2个字节,用于存储短整数值。
  6. Byte:对应基本数据类型byte,占1个字节,用于存储字节值。
  7. Character:对应基本数据类型char,占2个字节,用于存储字符值。
  8. Boolean:对应基本数据类型boolean,占1个字节,用于存储布尔值。

这些包装类型都是不可变的(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的使用

Java的switch语句是一种用于执行多个条件分支的控制结构。它允许您根据一个变量的值来选择不同的代码块执行。下面是switch语句的基本语法:

switch (expression) {  
    case value1:  
        // 执行相应的代码块  
        break;  
    case value2:  
        // 执行相应的代码块  
        break;  
    ...  
    default:  
        // 当没有匹配的case时执行的代码块  
}
  • expression是一个变量,它的值将与每个case语句后面的值进行比较。
  • case用于指定一个值,当expression的值与该值相匹配时,相应的代码块会被执行。
  • break用于结束当前case代码块的执行,并跳出switch语句。
  • default是可选的,当expression的值与所有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面向对象的三大特征包括:封装、继承和多态。

  1. 封装(Encapsulation):封装是把抽象的数据(变量)和实现的方法(函数)封装在一起,形成一个对象。在Java中,可以通过类(class)和接口(interface)来实现封装。封装的优点包括:保护数据的安全性、简化代码、提高代码的可读性和可维护性。
  2. 继承(Inheritance):继承是一个类(子类)可以使用另一个类(父类)的属性和方法。子类可以继承父类的所有非私有属性和方法,并且可以在此基础上添加自己特有的属性和方法。继承可以减少代码的重复性,提高代码的可维护性和可重用性。
  3. 多态(Polymorphism):多态是指一个引用类型变量在运行时可以指向多种实际类型。在Java中,多态可以通过方法重载(Overloading)和方法重写(Overriding)来实现。多态可以使代码更加灵活、可扩展,并且可以提高代码的可读性和可维护性。

重载(overload)和重写(override)

重载(Overload)和重写(Override)是Java中两个重要的概念,都是面向对象编程的一部分,但它们之间存在明显的区别。

  • 重载(Overload): 是在同一个类中定义多个同名的方法,但是这些方法的参数类型或参数数量必须不同。在调用这些方法时,会根据传递给它们的参数类型和数量来决定具体使用哪个方法,这就是多态性的一种表现。
  • 重写(Override): 发生在子类和父类之间,子类中定义一个与父类同名、返回类型、参数类型均相同的方法。在重写中,方法的实现过程是被重新编写的,返回值和形参都不能改变。重写是子类对父类中允许访问的方法实现过程的再次编写,是实现多态性的一种方式。

简单来说,重载是同一个类中的方法名相同但参数不同,而重写是子类中的方法覆盖了父类的方法。

以下是方法重载的规则:

  1. 方法名必须相同。
  2. 参数列表必须不同,包括参数的类型、个数或排列顺序。
  3. 方法的返回类型可以相同也可以不相同。
  4. 仅仅是返回类型不同不足以成为方法的重载。

在调用方法时,会根据传递给方法的参数的类型、个数和排列顺序来决定具体使用哪个方法。这有助于简化代码并提高代码的可读性和可维护性。

以下是重载和重写的例子:
重载例子:

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表示不可变的意思,可用于修饰类、属性和方法:

  • 被final修饰的类不可以被继承
  • 被final修饰的方法不可以被重写
  • 被final修饰的变量不可变,被final修饰的变量必须被显式第指定初始值,

还得注意的是,这里的不可变指的是变量的引用不可变,不是引用指向的内容的不可变。

final StringBuilder sb = new StringBuilder("1234");
sb.append("5");
System.out.println(sb); //12345

== 和 equals 的区别

在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中的主要区别是:

  • ==操作符用于比较两个对象的引用是否相同,即它们是否指向内存中的同一个对象。
  • equals()方法默认行为与==相同,但可以被重写来比较对象的值。在很多情况下,如String、Integer等类,它被重写为比较对象的值而不是引用。

Java 中异常处理体系

Java 中所有的异常都是 Throwable 类的子类,Throwable 类又可以分为 Error 和 Exception 两个子类。

  • Error 表示系统错误,通常不需要处理;
  • Exception 表示程序可以处理的异常,是异常处理的主要对象。

Exception 类又可以分为运行时异常(RuntimeException)和非运行时异常(CheckedException)。

异常处理:

  1. 捕获异常(Try-Catch Blocks)

可以使用 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结构。

  1. 声明异常(Declare Exceptions)

Java 中的方法可以声明它们可能会抛出的异常。这样,调用该方法的代码就需要进行相应的异常处理。声明异常的关键字是 throws。

  1. 抛出异常(Throw Exceptions)

在 Java 中,可以使用 throw 关键字手动抛出异常。这通常用于在程序中的特定条件下触发异常。

  1. 自定义异常(Custom Exceptions)

Java 中可以创建自定义异常类,以便更好地描述程序中可能出现的异常情况。自定义异常类应该是 Exception 或其子类的子类。

finally的执行顺序

在Java中,finally块的执行顺序是在try块之后,无论是否发生异常。当try块中的代码执行完成后,不管是否发生异常,finally块中的代码都会被执行(return 之前会先执行finally)。
当出现异常时,try块中的代码将不会继续执行,而是跳转到相应的catch块中处理异常。一旦catch块中的代码执行完毕,控制流将跳转回finally块中执行清理操作。
如果finally块中的代码执行期间没有发生异常,那么finally块的代码将正常执行并结束。如果finally块中的代码发生异常,那么该异常将会覆盖try块或catch块中引发的异常。
总之,finally块的执行顺序始终在try块之后,无论是否发生异常。它通常用于执行清理操作,例如关闭文件或数据库连接等资源。

Java 中 IO 流分为几种?

流按照不同的特点,有很多种划分方式。

  • 按照流的流向分,可以分为 输入流 和 输出流 ;
  • 按照操作单元划分,可以划分为 字节流 和 字符流 ;
  • 按照流的角色划分为 节点流 和 处理流

Java IO流共涉及40多个类,看上去杂乱,其实都存在一定的关联, Java I0流的40多 个类都是从如下4个抽象类基类中派生出来的。

  • InputStream / Reader : 所有的输入流的基类,前者是字节输入流,后者是字符输 流。
  • OutputStream / Writer : 所有输出流的基类,前者是字节输出流,后者是字符输出流

Java 泛型了解么?什么是类型擦除?介绍一下常用的通配符

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的一个实例,无法在运行时区分它们实际持有的元素类型。这就是类型擦除的效果。
常用的通配符有:

  1. ?:表示任意类型。例如List<?>表示一个包含任意类型的列表。你不能往这样的列表中添加新的元素(除非你将其转换为特定的类型),但你可以获取和删除元素。
  2. ? extends T:表示T或者T的任意子类型。例如,List<? extends Number>表示一个包含Number或者Number的子类的列表。你可以往这样的列表中添加元素(只要它们是Number或者Number的子类),也可以获取和删除元素。
  3. ? super T:表示T或者T的任意超类型。例如,List<? super Number>表示一个包含Number或者Number的超类的列表。你可以往这样的列表中添加元素(只要它们是Number或者Number的超类),也可以获取和删除元素。

注解

Java 注解(Annotation)是 JDK 5.0 引入的一种元数据,用于将某些信息与代码关联起来。注解可以应用于类、方法、变量、参数和包等元素上,用于提供额外的信息或元数据,以便于编译器、开发工具和运行时环境使用。
Java 注解的基本语法如下:

@annotationName(value1, value2, ..., valueN)

其中,annotationName 是注解的名称,value1, value2, …, valueN 是注解的属性值,可以有多个属性值。
Java 中内置了一些注解,如 @Override、@Deprecated 等。同时,也可以自定义注解。自定义注解需要使用 @interface 关键字来定义,可以定义注解的名称、属性和属性值类型等信息。

注解生命周期有三大类,分别是:

  • RetentionPolicy.SOURCE:给编译器用的,不会写入 class 文件
  • RetentionPolicy.CLASS:会写入 class 文件,在类加载阶段丢弃,也就是运行的 时候就没这个信息
  • RetentionPolicy.RUNTIME:会写入 class 文件,永久保存,可以通过反射获取注 解信息

以下是一个简单的自定义注解示例:

@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 。
image.png

反射

Java反射(Reflection)是在运行时对于任意一个类,都能够了解这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。

Java反射机制主要提供了以下功能:

  1. 在运行时判断任意一个对象所属的类;
  2. 在运行时构造任意一个类的对象;
  3. 在运行时判断任意一个类所具有的成员变量和方法;
  4. 在运行时调用任意一个对象的方法。

Java反射机制的相关类都位于java.lang.reflect包中,主要有以下类:
image.png

Java反射机制的使用步骤如下:

  1. 获取想要操作的类的 Class 对象。
  2. 调用 Class 对象的 newInstance() 方法创建该类的对象。
  3. 获取该类的属性、方法、构造方法等信息。
  4. 调用对象的属性或方法。

以下是一个简单的例子说明如何使用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反射需要注意以下几点:

  1. 反射操作效率低,应尽量避免在热点代码中使用。
  2. 反射操作会破坏封装性,应谨慎使用。
  3. 反射操作可能会引发安全问题,应谨慎使用。

反射实现原理

Java反射的原理是Java虚拟机在运行时可以通过字节码文件找到对应的类、方法以及属性等。
具体来说,当Java程序加载一个类时,Java虚拟机会为这个类创建一个Class对象,通过这个Class对象可以获取这个类的所有属性和方法。同时,Java虚拟机还会将这个类的所有属性和方法存储在一个方法区中,以便在运行时可以通过反射来访问这些属性和方法。
总的来说,Java反射机制是在Java虚拟机的层面上实现的,它使得Java程序可以在运行时动态地获取类的信息并调用类的方法和属性,从而实现更加灵活和动态的编程方式。

JDK1.8都有哪些新特性

image.png

Stream API

Stream是一个能描述某些指令序列的新抽象概念。这个序列可以来自于各种数据源,例如集合、数组、I/O通道等。Stream API支持串行和并行操作,并且支持延迟操作,这意味着只有在需要结果时才会执行操作。
以下是一些基本的Stream操作:

  1. filter:过滤出满足条件的元素。
List<String> names = Arrays.asList("Peter", "Anna", "Mike", "Xenia");  
List<String> filtered = names.stream().filter(name -> name.startsWith("P")).collect(Collectors.toList());
  1. map:将每个元素转换为其他对象。
List<String> names = Arrays.asList("Peter", "Anna", "Mike", "Xenia");  
List<String> mapped = names.stream().map(String::toUpperCase).collect(Collectors.toList());
  1. reduce:将流中所有元素结合起来,得到一个值。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);  
int sum = numbers.stream().reduce(0, Integer::sum);
  1. collect:将流转换为其他形式。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);  
Set<Integer> set = numbers.stream().collect(Collectors.toSet());
  1. forEach:对流中每个元素执行操作。
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"开头的名字,然后将这些名字转换为大写,最后打印出来。

Lambda表达式

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修饰的方法和抽象方法,就会执行父类中的方法  
}

新日期时间API

从Java 8开始,Oracle引入了一个全新的日期和时间API,这个API位于java.time包中,这个API提供了更加易于使用、更加直观、更加强大的日期和时间处理能力。以下是一些主要的类和功能:

  1. LocalDate:这是一个不可变的日期类,表示一个日期,通常用于生日、纪念日等。这个类提供了许多方法,可以轻松地获取、比较、调整日期。
  2. LocalTime:这是一个不可变的时间类,表示一个时间,通常用于一天中的某个时间点,比如会议时间。和LocalDate类似,这个类也提供了许多方法,可以轻松地获取、比较、调整时间。
  3. LocalDateTime:这是一个不可变的日期时间类,表示一个日期和时间。这个类同样提供了许多方法,可以轻松地获取、比较、调整日期时间。
  4. ZonedDateTime:这是一个不可变的日期时间类,表示一个带有时区信息的日期和时间。这个类提供了许多方法,可以轻松地获取、比较、调整日期时间,以及进行时区转换。
  5. Duration:这是一个不可变的时间间隔类,表示两个日期时间之间的间隔。这个类提供了许多方法,可以轻松地获取、比较、调整时间间隔。
  6. Period:这是一个不可变的时间间隔类,表示两个日期之间的间隔。这个类提供了许多方法,可以轻松地获取、比较、调整时间间隔。

这些类都是不可变的,每个实例都表示一个特定的日期、时间或间隔。这些类的方法通常都会返回一个新的实例,表示经过调整后的日期、时间或间隔。
新的日期和时间API还提供了许多格式化和解析的方法,可以将日期、时间、日期时间、时间间隔等转换为字符串,或者将字符串解析为这些类型。这些方法通常都定义在DateTimeFormatter类中。
以下是一些使用Java日期和时间API的示例:

  1. 使用LocalDate类表示日期:
java复制代码



// 获取当前日期  
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);
  1. 使用LocalTime类表示时间:
java复制代码



// 获取当前时间  
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);
  1. 使用LocalDateTime类表示日期时间:
java复制代码



// 获取当前日期时间  
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);
  1. 使用ZonedDateTime类表示带有时区信息的日期时间:
java复制代码



// 获取当前日期时间带有时区信息  
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);

Java的Optional类

是一个用于解决null安全问题的工具类。在Java 8中引入,Optional类提供了一种更优雅、更安全的方式来处理可能为null的值。
Optional类是一个容器类,它可以保存类型T的值,或者仅仅保存null。Optional提供了许多有用的方法来操作和处理可能为空的值,避免了传统的null检查。

以下是Optional类的一些常用方法:

  1. Optional.of(T value): 创建一个Optional实例,如果value为null,则抛出NullPointerException。
  2. Optional.ofNullable(T value): 创建一个Optional实例,如果value为null,则返回一个空的Optional。
  3. Optional.empty(): 返回一个空的Optional实例。
  4. Optional.isPresent(): 判断Optional是否为空,如果为空返回false,否则返回true。
  5. Optional.get(): 如果Optional不为空,返回其值,否则抛出NoSuchElementException。
  6. Optional.orElse(T other): 如果Optional不为空,返回其值,否则返回other。
  7. Optional.orElseGet(Supplier<? extends T> other): 如果Optional不为空,返回其值,否则调用other的get()方法并返回结果。
  8. Optional.orElseThrow(Supplier<? extends X> exceptionSupplier): 如果Optional不为空,返回其值,否则抛出异常,异常由exceptionSupplier提供。
  9. Optional.map(Function<? super T, ? extends U> mapper): 如果Optional不为空,对其值进行映射,并返回一个新的Optional。
  10. Optional.flatMap(Function<? super T, Optional<U>> 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
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值