首页 > 编程语言 >Java的高级特性

Java的高级特性

时间:2024-09-01 21:22:25浏览次数:13  
标签:Java 高级 特性 Person 枚举 注解 方法 Lambda

Java的高级特性概述:

Lambda表达式

Lambda表达式是Java 8及更高版本中引入的一个重要特性,它提供了一种简洁的方式来表示匿名方法(即没有名称的方法)。Lambda表达式特别适用于实现仅有一个抽象方法的接口(这类接口被称为函数式接口)。Lambda表达式使得代码更加简洁、易于阅读,并且提高了编程效率。

Lambda表达式的基本语法

(参数列表) -> { 方法体 }

  • 参数列表:Lambda表达式接收的参数。如果参数列表为空,可以省略小括号()。如果只有一个参数,并且该参数的类型可以通过上下文推断出来,那么小括号和参数类型都可以省略。
  • ->:Lambda操作符,用于分隔参数列表和方法体。
  • 方法体: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等场景。

在集合操作中使用Lambda表达式进行过滤和映射示例

假设我们有一个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方法省略,但建议在实际类中实现以方便打印
}
其次,使用Lambda表达式实现过滤和打印的功能
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class LambdaExample {
    public static void main(String[] args) {
        // 创建一个Person对象的列表
        List<Person> people = Arrays.asList(
                new Person("Alice", 31),
                new Person("Bob", 25),
                new Person("Charlie", 35),
                new Person("David", 29)
        );

        // 使用Lambda表达式和stream API来过滤年龄大于30的人,并收集他们的姓名
        List<String> 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

流(Streams)API是Java 8中引入的一个关键特性,它提供了一种高效且表达力强的方式来处理集合(如List、Set)以及数组等数据源。流API的设计初衷是为了让集合操作更加灵活、易于理解,并且可以利用多核处理器的优势进行并行处理。

流的基本概念

  • 流(Stream):流是数据源到数据汇(比如另一个集合、累加器或终端操作)的序列。流操作可以是中间操作(如过滤、映射)或终端操作(如收集、归约)。
  • 数据源:流的数据源可以是集合、数组、生成器函数等。
  • 中间操作:中间操作会返回一个新的流,并且可以被链式调用。中间操作包括过滤(filter)、映射(map)、排序(sorted)等。
  • 终端操作:终端操作会处理流中的元素,并产生一个结果,比如集合(collect)、值(reduce)、无结果(forEach)等。终端操作会触发流的执行。

流的特点

  • 惰性求值:流操作是惰性的,即中间操作不会立即执行,而是会等到终端操作时才会实际处理流中的元素。
  • 不可变性:流操作不会修改数据源,而是返回一个新的流。
  • 函数式编程:流API的设计符合函数式编程的原则,允许你以声明式的方式处理数据集合。

流的常见操作

  • 过滤(Filter):通过给定的条件过滤流中的元素。
  • 映射(Map):将流中的每个元素转换成另一种形式。
  • 排序(Sorted):对流中的元素进行排序。
  • 收集(Collect):将流中的元素收集到集合中,如List、Set等。
  • 归约(Reduce):通过某种操作将流中的元素归约成一个单一的值。
  • 匹配(Match):检查流中的元素是否满足某些条件,如anyMatchallMatchnoneMatch

示例

假设我们有一个Person对象的列表,我们想要找出所有年龄大于30岁的人的姓名列表:

List<Person> people = // 假设这是我们的Person对象列表
List<String> 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";
Consumer<String> greeting = str::length; // 注意:这里实际上是不常见的用法,因为greeting没有使用到外部定义的str对象,更常见的是下面的形式
// 更常见的实例方法引用形式是在流操作中使用,比如list.forEach(System.out::println);


特定类型的任意对象的实例方法引用

使用类名来引用其任意对象的实例方法。这要求Lambda表达式中的参数是类类型的一个实例,并且该实例方法没有修改除参数以外的对象状态。

List<String> list = Arrays.asList("apple", "banana", "cherry");
list.forEach(String::toUpperCase); // 相当于 list.forEach(s -> s.toUpperCase());


构造器引用

使用类名来引用其构造器。

Supplier<List<String>> listSupplier = ArrayList::new; // 相当于 Supplier<List<String>> 


注解

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框架中,注解被用来实现依赖注入等功能。

反射(Reflection)

在Java中,反射(Reflection)是一种强大的机制(特性),它允许程序在运行时检查和操作类的行为和对象的属性。通过反射,你可以在运行时获取类的信息(如类的字段、方法、构造函数等),并动态地创建对象、调用方法或访问字段。这种机制为Java语言提供了高度的灵活性和动态性。然而,反射也会降低程序的性能,并可能破坏封装性,因此在使用时需要谨慎。

反射的主要用途包括:

  1. 动态创建对象:使用Class.forName()加载类,然后使用Class对象的newInstance()方法创建该类的实例。

  2. 动态调用方法:通过Method类的实例,可以调用类中的任何方法,包括私有方法。

  3. 动态访问字段:通过Field类的实例,可以访问类的私有字段,并对其进行修改。

  4. 获取类的信息:使用反射可以获取类的名称、父类、实现的接口、构造函数、方法和字段等信息。

反射的常用类和方法:

  • 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中,泛型(Generics)是Java 5引入的一个特性,泛型是一种强大的特性,它提供了编译时类型安全检测机制,允许程序员在类、接口、方法创建时定义一个或多个类型参数(这些类型参数在声明时指定,并在使用时具体化)。通过使用泛型,你可以编写更加灵活、可重用的代码,同时避免了在运行时出现ClassCastException

泛型的主要优点包括:

  1. 类型安全:通过泛型,你可以在编译时期就检查到类型错误,而不是在运行时。
  2. 消除强制类型转换:在使用泛型之前,我们经常需要对集合中的元素进行强制类型转换,而使用泛型后,你可以在获取元素时自动获得正确的类型。
  3. 提高代码的重用性:你可以编写一套泛型代码来操作多种类型的数据,提高了代码的重用性。

泛型的使用场景:

  • 集合类(如ListSetMap等)的泛型化,使得你可以在编译时期就指定集合中元素的类型。
  • 泛型方法,允许你定义在方法内部操作的类型。
  • 泛型接口,可以定义一个接口,其中的类型参数在实现类或子类中被具体化。
  • 泛型类,可以定义一个类,其中的类型参数在创建类的实例时被具体化。

示例代码

// 泛型集合示例
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
// stringList.add(123); // 编译错误,因为泛型集合指定了元素类型为String

// 泛型方法示例
public static <T> T getFirstElement(List<T> list) {
    if (list != null && !list.isEmpty()) {
        return list.get(0);
    }
    return null;
}

// 使用泛型方法
List<Integer> intList = new ArrayList<>();
intList.add(1);
Integer firstInt = getFirstElement(intList);

异常处理

Java的异常处理机制是一种结构化的错误处理方式。通过try-catch-finally语句块,你可以捕获并处理在程序执行过程中发生的异常。Java还提供了自定义异常的功能,允许你根据实际需要定义新的异常类。

异常处理的基本结构

Java中的异常处理是通过trycatchfinally(可选)块来实现的。

  • try块:包含可能引发异常的代码。
  • catch块:紧跟在try块之后,用于捕获并处理try块中抛出的异常。可以有多个catch块来处理不同类型的异常。
  • finally块(可选):无论是否发生异常,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("执行清理操作");
}

注意事项

  • 异常类型:在catch块中,你应该尽可能具体地指定异常类型,而不是简单地使用Exception类。这有助于你更精确地了解异常的原因,并编写更有针对性的处理代码。
  • 避免过度使用异常:异常处理虽然强大,但过度使用会使代码变得难以理解和维护。在可能的情况下,应该通过其他方式(如返回值、断言等)来处理错误情况。
  • 资源泄露:在使用finally块时,要确保即使在发生异常的情况下也能正确释放资源。

枚举(Enumerations)

枚举(Enumerations)在Java中是一种特殊的类,它用于表示一组常量。通过枚举,你可以定义一组命名的整型常量,使得代码更加清晰易读。枚举在Java 5(JDK 1.5)中被引入,自那以来,它们已经成为了处理固定集合的常用工具。枚举不仅比传统的常量定义方式更加类型安全,还提供了丰富的功能,如枚举方法、枚举构造函数、枚举集合等。

枚举的基本用法:

  1. 定义枚举:使用enum关键字来定义一个枚举。枚举中可以直接定义常量,这些常量默认是public static final的。

    enum Color {
        RED, GREEN, BLUE;
    }
  2. 枚举的构造函数:虽然枚举中的常量看起来像是静态字段,但实际上它们是通过枚举类型的构造函数创建的实例。枚举可以有构造函数,但构造函数必须是私有的,以防止外部代码创建枚举的实例。

    enum Color {
        RED("红色"), GREEN("绿色"), BLUE("蓝色");
    
        private final String description;
    
        Color(String description) {
            this.description = description;
        }
    
        public String getDescription() {
            return description;
        }
    }
  3. 实现接口:枚举类型可以实现接口,并且每个枚举常量都可以有自己的实现。

    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() {
            // 默认实现(可选)
        }
    }
  4. 枚举的方法:枚举可以包含抽象方法和具体方法,使得每个枚举常量可以有自己的行为。

  5. 枚举的遍历:可以使用values()方法遍历枚举的所有常量,或者使用EnumSetEnumMap等集合类来操作枚举。

    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编程、事件监听器等场景。

使用内部类的优点

  • 更好的封装:内部类可以隐藏起来,不被外部类以外的其他类访问。
  • 方便访问外部类的成员:内部类可以直接访问外部类的所有成员(包括私有成员),而不需要使用外部类的公共getter和setter方法。
  • 实现多重继承:通过内部类,Java可以间接地实现多重继承(因为Java本身不支持多重继承)。外部类可以继承一个类,而内部类可以实现多个接口。

自动装箱与拆箱

Java 5引入了自动装箱与拆箱机制,它允许自动地将基本数据类型与其对应的包装类进行转换。这简化了代码编写,但也需要注意自动装箱与拆箱可能会导致的性能问题。

自动装箱(Autoboxing)

自动装箱是指将基本数据类型(如int、double等)自动转换为它们对应的包装类(如Integer、Double等)的过程。这个过程是自动完成的,你不需要显式地调用包装类的构造函数。

示例

int i = 5;
Integer integer = i; // 自动装箱

在上面的例子中,int类型的变量i被自动装箱成了Integer类型的对象integer

拆箱(Unboxing)

拆箱则是自动装箱的逆过程,即将包装类对象自动转换为它们对应的基本数据类型。这个过程同样是自动完成的,你不需要显式地调用包装类的方法(如intValue())来获取基本数据类型的值。

示例

Integer integer = 10;
int i = integer; // 拆箱

在上面的例子中,Integer类型的对象integer被拆箱成了int类型的变量i

注意事项

  • 自动装箱和拆箱虽然简化了编程,但也可能导致性能问题,因为涉及到对象的创建和销毁。
  • 在进行拆箱时,如果包装类对象为null,则会抛出NullPointerException
  • 在进行大量数值计算时,建议使用基本数据类型以提高性能。

标签:Java,高级,特性,Person,枚举,注解,方法,Lambda
From: https://blog.csdn.net/WZX17307935391/article/details/141760378

相关文章

  • java开发学习资料集合
    针对java的学习,不同阶段采用的方式是不一样的。本文把java的学习分为入门、实战、进阶三个阶段。下面分开来说技术社区1、 CSDNCSDN在线学习平台,集合了各领域资深技术专家.覆盖领域:人工智能、大数据、区块链、数据库、大学课程、认证考试、系统/网络、游戏开发、Web开发......
  • java入门:安装开发环境
    安装JDK1:百度搜索JDK8,找到下载地址2:登录并同意协议3:下载电脑对应的版本4:双击安装JDK5:记住安装的路径(重要)6:配置环境变量我的电脑>右键>属性找到高级系统设置>环境变量>在系统设置下找到新建>变量名是JAVA_HOME>变量值填安装的路径7:配置path变量在系统变量中找到path......
  • 【JavaScript】LeetCode:6-10
    文章目录6轮转数组7买卖股票的最佳时机Ⅰ8买卖股票的最佳时机Ⅱ9两数之和10字母异位词分组6轮转数组数组题目要求最终结果返回nums。方法1:拼接数组,n=nums.concat(nums);。方法2:数组直接截取,这里提供方法2的代码。/***@param{number[]}nums*@param......
  • ecmascript和javascript的区别
    1.简介1.1.概述1.1.1.ecmascriptECMAScript(简称ES)是JavaScript编程语言的一个标准化版本。它是为网络开发设计的一种轻量级的脚本语言,主要用于在网页上实现交互性和动态效果。ECMAScript是该语言的标准名称,而JavaScript是其最知名和广泛使用的实现。1.1.2.javascrip......
  • 【JAVA系列】java命令注入科普
    名词科普原理科普注入科普原创medi0cr1tyMedi0cr1ty这里只讨论使用java执行命令的情况(Runtime/ProcessBuilder),结合之前挖过过的一些case或者群里见到过的case来讲。名词科普命令解释器shell:是一种软件程序(可视作一门编程语言的代码解释器),它接收用户在命令行界面......
  • 章10——面向对象编程(高级部分)——类名类方法
    类变量/静态(static)变量/静态属性特点:由该类的全体对象实例共享,该变量只占同一块内存空间。其标志是关键字static修饰。在类加载的时候便生成了。类加载的时机如下图:publicstaticintcount=0;类变量可以由类名来访问。System.out.println("共有"+Child.count+"......
  • JavaSE-递归法解决二分查找、快速排序
    704.二分查找https://leetcode.cn/problems/binary-search/packagedemo;publicclassBinarySearch{publicstaticvoidmain(String[]args){BinarySearchbr=newBinarySearch();System.out.println(br.search(newint[]{1,2,3,4,5,6,7......
  • 为什么Java仍旧生机盎然——对“为什么Java正在消亡”的回应
    [图片上传失败...(image-599293-1649288200226)]0.阅读完本文你将会了解Java作为热门语言之一所面临的争议了解Java的生态环境和未来1.前言原文标题:WhyJavaIsPerfectlyAlive——Aresponseto"WhyJavaIsDying"原文地址:https://betterprogramming.pub/why-......
  • 互联网 Java 工程师面试题(Java 面试题四)
    下面列出这份Java面试问题列表包含的主题多线程,并发及线程基础数据类型转换的基本原则垃圾回收(GC)Java集合框架数组字符串GOF设计模式SOLID抽象类与接口Java基础,如equals和hashcode泛型与枚举JavaIO与NIO常用网络协议Java中的数据结构和算法正则表达式JVM底......
  • 学JAVA的第七周
    变量和方法成员变量与局部变量的区别有哪些变量:在程序执行的过程中,在某个范围内其值可以发生改变的量。从本质上讲,变量其实是内存中的一小块区域成员变量:方法外部,类内部定义的变量局部变量:类的方法中的变量。成员变量和局部变量的区别作用域成员变量:针对整个类有效。局部......