Java特性

2021/10/2 22:10:02

本文主要是介绍Java特性,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

序言

Java起源于20世纪90年代,从1991年开始,Java语言持续发展,并主要应用于Web端的开发。JDK(Java Development Kit)版本也在不断更新。作为一种高级语言,为了让开发者能够写出可读性更强,且更安全简洁的代码,它在一个个版本里增加了新的特性。在阅读一些老代码时,确实也发现了各种各样不同的问题,把这些新特性用上可以让代码更简洁更清晰。下面就来从JDK 8开始总结一些新特性。

JDK 8

首先,介绍Java开发工具 8 提供的新特性。

接口的默认方法(Default Methods For Interfaces)

Interface中定义的方法默认修饰符是public abstract表示它是一个抽象方法,通过实现类来进行具体的实现,Java 8中可以使用default关键字向接口添加非抽象方法实现(虚拟扩展方法),如下。

public interface Formula {

    double caculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

在测试类中看到可以直接使用接口中的非抽象方法,

public class FormulaTest {
    public static void main(String[] args) {
        Formula formula = new Formula() {
            @Override
            public double caculate(int a) {
                return sqrt(a * 100);
            }
        };
        System.out.println(formula.caculate(100));  //100.0
        System.out.println(formula.sqrt(100));      //10.0
    }
}

Lambda表达式(Lambda Expressions)

lambda表达式应该是我们使用的最多的了,下面直接给出一个简单例子,按照字典序进行排序,将创建的一个匿名的比较器对象传入sort方法,让代码更加简洁易懂。

public class LambdaExpressions {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("john","peter","jerry","tom");
        //按字典序进行排序,下面也可以使用Comparator.reverseOrder()静态方法
        names.sort((o1, o2) -> o2.compareTo(o1));
        names.stream().forEach(System.out::println);
    }
}

函数式接口(Functional Interfaces)

为了让现有的函数友好地支持Lambda,增加了函数式接口的概念。“函数式接口”是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法的接口。像这样的接口,可以被隐式转换为lambda表达式。java.lang.Runnable和java.util.concurrent.Callable是函数式接口最典型的例子。Java 8还增加了特殊注解@FunctionalInterface进行声明函数式接口,编译器会进行静态代码检查。不过大部分的函数式接口都不需要我们写,java.util.function包里有现成的。

public class FunctionalInterfaceTest {
    @FunctionalInterface
    public interface Converter<F, T> {
        //仅包含一个抽象方法
        T convert(F from);
    }

    public static void main(String[] args) {
        Converter<String, Integer> converter = (from -> Integer.valueOf(from));
        Integer converted = converter.convert("123");
        System.out.println(converted.getClass());
    }
}

方法和构造函数引用(Method And Constructor References)

Java 8 允许通过::关键字传递方法或构造函数的引用,lambda表达式中的demo也展示了list集合通过stream流的forEach方法进行打印输出,这里打印输出就使用的是::关键字进行方法引用。

下面看一个引用对象方法的例子,

public class MethodReferences {
    @FunctionalInterface
    public interface Converter<F, T> {
        T convert(F from);
    }

    String startWith(String s){
        return String.valueOf(s.charAt(0));
    }

    public static void main(String[] args) {
        MethodReferences references = new MethodReferences();
        Converter<String,String> converter = references::startWith;
        String converted = converter.convert("Java");
        System.out.println(converted);  //J
    }
}

再看构造函数的引用的例子,

创建一个含有无参构造函数(可以使用@NoArgsConstructor注解)和有参构造函数(可以使用@AllArgsConstructor注解)的Person对象,

public class Person {
    String firstName;
    String lastName;

    Person() {
    }

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

创建一个Person对象的对象工厂接口,

public interface PersonFactory<T extends Person> {
    T create(String firstName, String lastName);
}

使用构造函数引用将它们关联起来,Person::new可以获取Person类构造函数的引用。

public class PersonCreateTest {
    public static void main(String[] args) {
        PersonFactory<Person> factory = Person::new;
        Person person = factory.create("John","Parker");
        System.out.println(person.firstName);
    }
}

Lambda表达式作用域(Lambda Scopes)

Lambda表达式其作用域主要有,

  1. 访问局部变量
  2. 访问字段和静态变量

可以直接在lambda表达式中访问外部的局部变量,但是外部局部变量必须是最终变量(不可被修改),

public class LambdaScopes {
    public static void main(String[] args) {
        final int num = 1;
        Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
        String res = stringConverter.convert(2);        //3
        System.out.println(res);
    }
}

num不加final关键字可以,但是修改num就会报错,错误信息如下,

 内置函数式接口(Built-In Functional Interface)

Java 1.8 API包含许多内置函数式接口,如Comparator或Runnable,有一些接口来自Google Guava库里。下面看一下这些接口如何扩展到Lambda上使用的。

Predicates

Predicate接口是只有一个参数的返回布尔类型值的断言型接口。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(与、或、非)。

@FunctionalInterface
public interface Predicate<T> {

    //通过参数评估断言
    boolean test(T t);

    //默认方法(非抽象方法) 与关系型运算符&&类似
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    //默认方法(非抽象方法) 与关系型运算符!类似
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    //默认方法(非抽象方法) 与关系型运算符||类似
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    //比较第一个test的方法和第二个test方法是否相同
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

Predicate使用示例如下,

public class PredicateTest {
    public static void main(String[] args) {
        Predicate<String> predicate = s -> s.length() > 0;
        System.out.println(predicate.test("foo"));              //true
        System.out.println(predicate.negate().test("foo"));     //false

        Predicate<Boolean> nonNull = Objects::nonNull;
        Predicate<Boolean> isNull = Objects::isNull;
        System.out.println(nonNull.test(false));            //true
        System.out.println(isNull.test(false));             //false

        Predicate<String> isEmpty = String::isEmpty;
        Predicate<String> isNotEmpty = isEmpty.negate();
        System.out.println(isNotEmpty.test("predicate"));       //true
    }
}

Function

Function接口接受一个参数并生成接口,默认方法可用于多个函数链接在一起(compose,andThen),java.util.function包下的接口源码大家可自行查阅,使用示例如下,

public class FunctionTest {
    public static void main(String[] args) {
        Function<String, Integer> toInteger = Integer::valueOf;
        Function<String, String> backToString = toInteger.andThen(String::valueOf);
        System.out.println(backToString.apply("111"));        //111
    }
}

Supplier

Supplier接口产生给定泛型类型的结果。与Function接口不同,Supplier接口不接受参数。

public class SupplierTest {
    public static void main(String[] args) {
        Supplier<Person> personSupplier = Person::new;
        Person person = personSupplier.get();   //new Person
        person.setFirstName("John");
        System.out.println(person.firstName);   //John
    }
}

Consumer

Consumer接口表示要对单个输入参数执行的操作。

public class ConsumerTest {
    public static void main(String[] args) {
        Consumer<Person> greeter = (p) -> System.out.println("Hello," + p.firstName);
        greeter.accept(new Person("John", "Peter"));    //Hello,John
    }
}

Comparator

Comparator是Java中经典老接口,Java 8在上面添加了多种默认方法。

public class ComparatorTest {
    public static void main(String[] args) {
        //下面等价于Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
        Comparator<Person> comparator = Comparator.comparing(p -> p.firstName);
        Person p1 = new Person("John", "Peter");
        Person p2 = new Person("Alice", "Bob");
        System.out.println(comparator.compare(p1, p2));             //9
        System.out.println(comparator.reversed().compare(p1, p2));  //-9
    }
}

Optional

Optional是为了解决空指针异常而产生的,虽然不能完全解决,但是非常有效的防止NullPointException的产生。它是一个简单的容器,其值可能是null或者不是null。在Java 8中方法的返回尽量使用Optional替代null。

public class OptionalTest {
    public static void main(String[] args) {
        Optional<Person> result = searchById();
        Person person = result.orElseThrow(() -> new BusinessException("待查对象未找到!"));
        System.out.println(person.firstName);           //John
    }

    private static Optional<Person> searchById() {
//        return Optional.of(new Person("John", "Peter"));
        return Optional.empty();
    }
}

Stream

Stream流应该是我们在Java 8中用的最多的了。Java.util.Collection的子类中List或Set都可以用Stream流进行操作,可以是并行执行或串行执行,默认是串行执行。Map不支持。

public class StreamTest {
    public static void main(String[] args) {
        Stream<Integer> stream = Sets.newSet(1, 2, 3, 4, 5, 6, 7, 8).stream();
        //设置为并行流
        stream.parallel();
        System.out.println(stream.isParallel());        //true
        stream.forEach(System.out::println);            //并行输出
    }
}

Filter

过滤通过一个Predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来完成其他操作。还是用上面的例子,过滤出上面集合中3的倍数。

public class FilterTest {
    public static void main(String[] args) {
        Stream<Integer> stream = Sets.newSet(1, 2, 3, 4, 5, 6, 7, 8).stream();
        stream.filter(n -> n % 3 == 0).forEach(System.out::println);    //3  6
    }
}

Sorted

排序也是一个中间操作,返回排序好后的Stream。sorted()方法里可以指定自定义的Comparator。

下面的例子先按字典序进行排序,然后过滤出第三个字符为c开头的字符串。

public class SortedTest {
    public static void main(String[] args) {
        Stream<String> stream = Lists.newArrayList("cac", "bew", "weee", "zzz", "abc").stream();
        //字符串先按字典序排序,再过滤出字符串第三个字符为c开头
        stream.sorted().filter(s -> s.startsWith("c", 2)).forEach(System.out::println);        //abc  cac
    }
}

Map

map也是一个中间操作,这个我们用的非常多,比如对外暴露的接口的对象集合数据流映射成待请求的对象集合。下面举个简单的字符串映射输出例子,集合中字符串全大写输出,

public class MapTest {
    public static void main(String[] args) {
        Stream<String> stream = Lists.newArrayList("cac", "bew", "weee", "zzz", "abc").stream();
        stream.sorted().map(String::toUpperCase).forEach(System.out::println);
    }
}

Match

Stream提供的匹配操作,允许检测制定的Predicate是否匹配整个Stream。所有匹配操作都是最终操作,并返回一个布尔类型的值。下面举的例子是集合中没有以m开头的字符串,除了noneMatch,还有anyMatch和allMatch,anyMatch表示有任意一个匹配,allMatch表示全部匹配。

public class MatchTest {
    public static void main(String[] args) {
        Stream<String> stream = Lists.newArrayList("cac", "bew", "weee", "zzz", "abc").stream();
        boolean result = stream.noneMatch(s -> s.startsWith("m"));
        System.out.println(result);     //true
    }
}

Count

计数也是一个最终操作,返回Stream中元素的个数,返回值类型为long。

public class CountTest {
    public static void main(String[] args) {
        Stream<String> stream = Lists.newArrayList("cac", "abc", "aaa", "awf").stream();
        long count = stream.filter(s -> s.startsWith("a")).count();
        System.out.println("集合元素个数为:" + count);     //3
    }
}

Reduce

Reduce规约也是一个最终操作,允许通过指定的函数来将Stream中的多个元素规约为一个元素,规约后的结果是通过Optional接口表示的。

public class ReduceTest {
    public static void main(String[] args) {
        Stream<String> stream = Lists.newArrayList("cac", "abc", "aaa", "awf").stream();
        Optional<String> reduced = stream.reduce((s1, s2) -> s1 + "#" + s2);
        reduced.ifPresent(System.out::println);         //cac#abc#aaa#awf

        Stream<Integer> integerStream = Sets.newSet(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).stream();
        int sum = integerStream.reduce(Integer::sum).get();         //这里隐式进行了拆包
        System.out.println(sum);                                    //55

        String concat = Stream.of("R", "e", "d", "u", "c", "e").reduce("", String::concat);
        System.out.println(concat);                             //Reduce
    }
}

Parallel Stream

前面提到过Stream的串行流和并行流,默认是串行,可以通过Stream.parallel()方法让其成为一个并行流。通过默认的ForkJoinPool提高多线程任务的速度,ForkJoinPool线程池类似于ThreadPoolExecutor线程池都继承了抽象类AbstractExecutorService。ForkJoinPool的线程数量默认是CPU数,主要通过分治法(Divide-and-Conquer)来解决问题,它适用于计算密集型任务。

它用的好可以提高执行效率,用的不好适得其反。《Java编程规范》以及《Effective-Java》都提及请谨慎使用并行流。

public class ParallelStreamTest {
    private static long calculatePI(long n) {
        return LongStream.rangeClosed(2, n)                     //2到n的Long数值流
//                .parallel()
                .mapToObj(BigInteger::valueOf)                  //转换为BigInteger
                .filter(i -> i.isProbablePrime(50))     //isProbablePrime:如果BigInteger可能是素数
                .count();
    }

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        calculatePI(10000000L);
        System.out.println("计算Pi(n)耗时:" + (System.currentTimeMillis() - startTime));
    }
}

Sequential Sort/Parallel Sort

串行排序即上面Stream流的排序,并行排序则是将Collection接口中的非抽象方法stream()改为parallelStream()。

Map

Map类型不支持Stream流,但可以在键值上创建专门的流,如map.keySet().stream()或者map.values().stream()以及map.entrySet().stream()。

public class MapStreamTest {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        for (int i = 0; i < 3; i++) {
            map.putIfAbsent(i, "val" + i);      //不存在则存入
        }
        map.forEach((id, val) -> System.out.println(val));
        
        String value = map.getOrDefault(5, "not found number");
        System.out.println(value);
    }
}

Data API

Java 8在java.time包下包含一个全新的日期和时间API。

public class DataAPITest {
    public static void main(String[] args) {
        ZoneId zoneId = ZoneId.systemDefault();
        System.out.println("当前系统时区:" + zoneId);

        LocalTime localTime = LocalTime.now(zoneId);
        System.out.println("本地时间:" + localTime);

        LocalDate localDate = LocalDate.now();
        System.out.println("本地日期:" + localDate);

        LocalDateTime currentTime = Instant.now()
                .atZone(ZoneId.of("Asia/Shanghai"))
                .toLocalDateTime();
        System.out.println("本地日期时间:" + currentTime);

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
        String date = currentTime.format(formatter);
        System.out.println("格式化后的时间:" + date);
    }
}

Clock

Clock类提供了访问当前日期和时间的方法,它是时区敏感的。

public class ClockTest {
    public static void main(String[] args) {
        Clock clock = Clock.systemDefaultZone();
        long mills = clock.millis();
        System.out.println(mills);
        Instant instant = clock.instant();
        System.out.println(instant);
    }
}

Annotation

Java 8中支持多重注解,如下,通过@Repeatable隐性定义好集合。

@interface Elements {
    Element[] value();
}

@Repeatable(Elements.class)
@interface Element {
    String value();
}

//old
@Elements({@Element("circle"), @Element("square")})
class Shape {
}

//new
@Element("brush")
@Element("cup")
class Product {
}

public class MultipleAnnotation {
    public static void main(String[] args) {
        Element element = Product.class.getAnnotation(Element.class);
        System.out.println(element);
        Elements elements = Product.class.getAnnotation(Elements.class);
        System.out.println(elements.value().length);
        Element[] elementArray = Product.class.getAnnotationsByType(Element.class);
        System.out.println(elementArray.length);
    }
}

Nashorn

javascript 引擎,从JDK1.8开始,Nashorn取代Rhino(JDK 1.6, JDK1.7)成为Java的嵌入式JavaScript引擎,性能提升了2到10倍。

public class NashornTest {
    public static void main(String[] args) {
        ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
        ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");
        String name = "Runoob";
        Integer result = null;
        try {
            nashorn.eval("print('" + name + "')");
            result = (Integer) nashorn.eval("10 + 2");

        } catch (ScriptException e) {
            System.out.println("执行脚本错误: " + e.getMessage());
        }
        System.out.println(result.toString());
    }
}

Base64

内置了 Base64 编码的编码器和解码器。

public class Base64Test {
    public static void main(String[] args) {
        try {
            // 编码
            String base64encodedString = Base64.getEncoder().encodeToString("runoob?java8".getBytes("utf-8"));
            System.out.println("Base64 编码字符串 (基本) :" + base64encodedString);
            // 解码
            byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);
            System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8"));
        } catch (UnsupportedEncodingException e) {
            System.out.println("不支持编码解码异常,异常信息:" + e);
        }
    }
}

总结

Java 8的特性除了上述所述还有其他特性。上面包含大部分的特性,具体其他特性可以参看官网。



这篇关于Java特性的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程