Lambda表达式是Java 8及更高版本中引入的一个重要特性,它提供了一种简洁的方式来表示匿名方法(即没有名称的方法)。Lambda表达式特别适用于实现仅有一个抽象方法的接口(这类接口被称为函数式接口)。Lambda表达式使得代码更加简洁、易于阅读,并且提高了编程效率。
(参数列表) -> { 方法体 }
()
。如果只有一个参数,并且该参数的类型可以通过上下文推断出来,那么小括号和参数类型都可以省略。{}
可以省略,同时该语句的结束分号;
也可以省略。但是,如果方法体包含多条语句,或者需要显式地返回结果,那么大括号{}
和必要的分号;
就不能省略。使用Lambda表达式实现打印字符串的Runnable接口
Runnable runnable = () -> System.out.println("Hello, Lambda!"); new Thread(runnable).start();
在这个例子中,
Runnable
是一个函数式接口,它有一个无参数、无返回值的run
方法。我们使用Lambda表达式() -> System.out.println("Hello, Lambda!")
来实现了这个run
方法,从而创建了Runnable
接口的一个匿名实现。
Lambda表达式在Java中广泛应用于集合的遍历、筛选、映射等操作,以及并发编程中的线程池、CompletableFuture等场景。
假设我们有一个Person
类,它有两个属性:name
(姓名)和age
(年龄),并且我们有一个Person
对象的列表。我们的目标是找出列表中所有年龄大于30岁的人,并打印出他们的姓名。
Person
类:public class Person {
private String name;
private int age;
// 构造函数、getter和setter省略
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
// toString方法省略,但建议在实际类中实现以方便打印
}
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class LambdaExample {
public static void main(String[] args) {
// 创建一个Person对象的列表
List people = Arrays.asList(
new Person("Alice", 31),
new Person("Bob", 25),
new Person("Charlie", 35),
new Person("David", 29)
);
// 使用Lambda表达式和stream API来过滤年龄大于30的人,并收集他们的姓名
List namesOfOldPeople = people.stream()
.filter(person -> person.getAge() > 30) // 过滤操作
.map(Person::getName) // 映射操作,使用方法引用
.collect(Collectors.toList()); // 收集结果
// 打印结果
namesOfOldPeople.forEach(System.out::println);
}
}
在这个例子中,我们首先通过
Arrays.asList
创建了一个Person
对象的列表。然后,我们使用stream()
方法将列表转换为流,以便进行链式操作。通过filter()
方法,我们传入了一个Lambda表达式person -> person.getAge() > 30
来过滤出年龄大于30的Person
对象。接着,我们使用map()
方法,并通过方法引用Person::getName
将过滤后的Person
对象映射为他们的姓名字符串。最后,我们使用collect(Collectors.toList())
将映射后的流收集到一个新的列表中,并通过forEach()
方法和System.out::println
来打印出这些姓名。这个例子展示了Lambda表达式在集合操作中的强大功能,以及如何与Java 8引入的流API结合使用来简化代码。
流(Streams)API是Java 8中引入的一个关键特性,它提供了一种高效且表达力强的方式来处理集合(如List、Set)以及数组等数据源。流API的设计初衷是为了让集合操作更加灵活、易于理解,并且可以利用多核处理器的优势进行并行处理。
filter
)、映射(map
)、排序(sorted
)等。collect
)、值(reduce
)、无结果(forEach
)等。终端操作会触发流的执行。anyMatch
、allMatch
、noneMatch
。假设我们有一个Person
对象的列表,我们想要找出所有年龄大于30岁的人的姓名列表:
List people = // 假设这是我们的Person对象列表
List namesOfOldPeople = people.stream()
.filter(p -> p.getAge() > 30) // 过滤操作
.map(Person::getName) // 映射操作,使用方法引用
.collect(Collectors.toList()); // 收集结果
// 现在namesOfOldPeople包含了所有年龄大于30岁的人的姓名
在这个例子中,我们首先通过
stream()
方法将列表转换为流,然后链式调用了filter()
和map()
两个中间操作,最后通过collect()
终端操作将结果收集到一个新的列表中。
与Lambda表达式紧密相关的是方法引用,它是对Lambda表达式的一种更简洁的写法,是Lambda表达式的一个简洁表示形式,它允许你直接引用已存在的方法或构造函数。当Lambda表达式的主体只是调用一个已存在的方法时,你可以使用方法引用来代替Lambda表达式。
使用类名来引用静态方法。
Integer::parseInt // 相当于 x -> Integer.parseInt(x)
使用特定对象来引用其实例方法。
String str = "Hello";
Consumergreeting = str::length; // 注意:这里实际上是不常见的用法,因为greeting没有使用到外部定义的str对象,更常见的是下面的形式
// 更常见的实例方法引用形式是在流操作中使用,比如list.forEach(System.out::println);
使用类名来引用其任意对象的实例方法。这要求Lambda表达式中的参数是类类型的一个实例,并且该实例方法没有修改除参数以外的对象状态。
List
list = Arrays.asList("apple", "banana", "cherry");
list.forEach(String::toUpperCase); // 相当于 list.forEach(s -> s.toUpperCase());
使用类名来引用其构造器。
Supplier
> listSupplier = ArrayList::new; // 相当于 Supplier
>
Java从1.5版本开始引入了注解,注解是Java中的一个重要特性,它为代码提供了元数据。这些元数据可以在编译时、加载时或运行时被读取,以执行各种任务,如自动生成代码、生成文档、进行编译时检查、在运行时处理类等。Java提供了内置的注解,如@Override
、@Deprecated
等,同时也允许你定义自己的注解。
在Java中,注解是通过@interface
关键字定义的,它看起来很像接口,但实际上是一种特殊的类型。注解可以附加在类、方法、参数、变量、包等程序元素上,以提供关于这些元素的额外信息。
Java标准库提供了许多内置的注解,比如@Override
(表示某个方法是重写了父类中的方法)、@Deprecated
(表示某个程序元素(类、方法等)已过时,不建议使用)、@SuppressWarnings
(指示编译器忽略特定的警告)等。
除了内置注解外,Java还允许开发者定义自己的注解。自定义注解时,可以通过元注解(如@Target
、@Retention
、@Inherited
等)来指定注解的适用范围、保留策略等。
@Target
:用于指定注解可以应用的Java元素类型(如类、方法、参数等)。@Retention
:用于指定注解的保留策略,即注解在何时生效。常见的保留策略有SOURCE
(仅在源码中保留,编译时丢弃)、CLASS
(在源码和class文件中保留,但运行时不可见)和RUNTIME
(在源码、class文件和运行时都保留,因此可以通过反射读取)。@Inherited
:表示注解类型会被自动继承。如果在一个类上使用了一个被@Inherited
注解的注解类型,那么这个类的子类也会继承这个注解。注解本身不直接影响代码的执行,但它们可以被编译器或运行时环境读取,以执行各种任务。例如,在Java的持久化框架(如JPA)中,注解被用来描述实体类与数据库表之间的映射关系;在Spring框架中,注解被用来实现依赖注入等功能。
在Java中,反射(Reflection)是一种强大的机制(特性),它允许程序在运行时检查和操作类的行为和对象的属性。通过反射,你可以在运行时获取类的信息(如类的字段、方法、构造函数等),并动态地创建对象、调用方法或访问字段。这种机制为Java语言提供了高度的灵活性和动态性。然而,反射也会降低程序的性能,并可能破坏封装性,因此在使用时需要谨慎。
动态创建对象:使用Class.forName()
加载类,然后使用Class
对象的newInstance()
方法创建该类的实例。
动态调用方法:通过Method
类的实例,可以调用类中的任何方法,包括私有方法。
动态访问字段:通过Field
类的实例,可以访问类的私有字段,并对其进行修改。
获取类的信息:使用反射可以获取类的名称、父类、实现的接口、构造函数、方法和字段等信息。
Class
:代表类的本身,包含创建对象、获取方法、字段等信息的方法。Method
:表示类和接口中的方法,可以动态调用方法。Field
:表示类和接口中的字段,可以访问和修改字段的值。Constructor
:表示类的构造函数,可以用来动态创建对象。// 动态加载类并创建对象
try {
Class> clazz = Class.forName("com.example.MyClass");
Object obj = clazz.newInstance();
// 调用方法
Method method = clazz.getMethod("myMethod", String.class);
method.invoke(obj, "Hello, Reflection!");
// 访问字段
Field field = clazz.getDeclaredField("myField");
field.setAccessible(true); // 如果字段是私有的,需要设置为可访问
Object fieldValue = field.get(obj);
System.out.println(fieldValue);
} catch (Exception e) {
e.printStackTrace();
}
在Java中,泛型(Generics)是Java 5引入的一个特性,泛型是一种强大的特性,它提供了编译时类型安全检测机制,允许程序员在类、接口、方法创建时定义一个或多个类型参数(这些类型参数在声明时指定,并在使用时具体化)。通过使用泛型,你可以编写更加灵活、可重用的代码,同时避免了在运行时出现ClassCastException
。
List
、Set
、Map
等)的泛型化,使得你可以在编译时期就指定集合中元素的类型。// 泛型集合示例
List stringList = new ArrayList<>();
stringList.add("Hello");
// stringList.add(123); // 编译错误,因为泛型集合指定了元素类型为String
// 泛型方法示例
public static T getFirstElement(List list) {
if (list != null && !list.isEmpty()) {
return list.get(0);
}
return null;
}
// 使用泛型方法
List intList = new ArrayList<>();
intList.add(1);
Integer firstInt = getFirstElement(intList);
Java的异常处理机制是一种结构化的错误处理方式。通过try-catch-finally语句块,你可以捕获并处理在程序执行过程中发生的异常。Java还提供了自定义异常的功能,允许你根据实际需要定义新的异常类。
Java中的异常处理是通过try
、catch
和finally
(可选)块来实现的。
try {
// 尝试执行的代码,可能引发异常
int result = 10 / 0; // 这里会抛出ArithmeticException
} catch (ArithmeticException e) {
// 处理ArithmeticException
System.out.println("发生了算术异常: " + e.getMessage());
} catch (Exception e) {
// 处理其他类型的异常(可选)
System.out.println("发生了异常: " + e.getMessage());
} finally {
// 清理代码,无论是否发生异常都会执行
System.out.println("执行清理操作");
}
Exception
类。这有助于你更精确地了解异常的原因,并编写更有针对性的处理代码。枚举(Enumerations)在Java中是一种特殊的类,它用于表示一组常量。通过枚举,你可以定义一组命名的整型常量,使得代码更加清晰易读。枚举在Java 5(JDK 1.5)中被引入,自那以来,它们已经成为了处理固定集合的常用工具。枚举不仅比传统的常量定义方式更加类型安全,还提供了丰富的功能,如枚举方法、枚举构造函数、枚举集合等。
定义枚举:使用enum
关键字来定义一个枚举。枚举中可以直接定义常量,这些常量默认是public static final
的。
enum Color {
RED, GREEN, BLUE;
}
枚举的构造函数:虽然枚举中的常量看起来像是静态字段,但实际上它们是通过枚举类型的构造函数创建的实例。枚举可以有构造函数,但构造函数必须是私有的,以防止外部代码创建枚举的实例。
enum Color {
RED("红色"), GREEN("绿色"), BLUE("蓝色");
private final String description;
Color(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
实现接口:枚举类型可以实现接口,并且每个枚举常量都可以有自己的实现。
interface Printable {
void print();
}
enum Color implements Printable {
RED {
@Override
public void print() {
System.out.println("红色");
}
},
GREEN {
@Override
public void print() {
System.out.println("绿色");
}
},
BLUE {
@Override
public void print() {
System.out.println("蓝色");
}
};
@Override
public void print() {
// 默认实现(可选)
}
}
枚举的方法:枚举可以包含抽象方法和具体方法,使得每个枚举常量可以有自己的行为。
枚举的遍历:可以使用values()
方法遍历枚举的所有常量,或者使用EnumSet
和EnumMap
等集合类来操作枚举。
for (Color c : Color.values()) {
System.out.println(c + " - " + c.getDescription());
}
内部类(Inner Class)是Java编程语言中一个非常重要的概念,它允许你定义在另一个类(称为外部类)里面的类。内部类可以是静态的(Static Inner Class)或非静态的(Non-static Inner Class,也称为实例内部类 Instance Inner Class)。内部类提供了更好的封装性,并且可以方便地访问外部类的成员(包括私有成员)。
非静态内部类会隐式地持有其外部类的一个引用。因此,你不能在没有外部类实例的情况下创建非静态内部类的实例。创建非静态内部类实例的通常语法是:
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();
静态内部类不会持有外部类的引用,因此它可以像其他任何类一样被创建,而不需要外部类的实例。创建静态内部类实例的语法类似于其他类的实例化:
OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();
局部内部类是在方法内部定义的类。它的作用域被限定在定义它的方法或代码块中。局部内部类同样可以访问外部类的成员,但不能访问外部类的非final局部变量(在Java 8及以后版本中,可以使用effectively final
的局部变量)。
匿名内部类是没有名字的内部类,它通常用于实现简单的接口或继承一个类,并在需要时立即使用其实例。匿名内部类常用于GUI编程、事件监听器等场景。
Java 5引入了自动装箱与拆箱机制,它允许自动地将基本数据类型与其对应的包装类进行转换。这简化了代码编写,但也需要注意自动装箱与拆箱可能会导致的性能问题。
自动装箱是指将基本数据类型(如int、double等)自动转换为它们对应的包装类(如Integer、Double等)的过程。这个过程是自动完成的,你不需要显式地调用包装类的构造函数。
示例:
int i = 5;
Integer integer = i; // 自动装箱
在上面的例子中,int
类型的变量i
被自动装箱成了Integer
类型的对象integer
。
拆箱则是自动装箱的逆过程,即将包装类对象自动转换为它们对应的基本数据类型。这个过程同样是自动完成的,你不需要显式地调用包装类的方法(如intValue()
)来获取基本数据类型的值。
示例:
Integer integer = 10;
int i = integer; // 拆箱
在上面的例子中,Integer
类型的对象integer
被拆箱成了int
类型的变量i
。
null
,则会抛出NullPointerException
。