JavaRush /Blog Java /Random-VI /Các tính năng của Java 8 – Hướng dẫn cơ bản (Phần 1)
0xFF
Mức độ
Донецк

Các tính năng của Java 8 – Hướng dẫn cơ bản (Phần 1)

Xuất bản trong nhóm
Phần đầu tiên dịch của bài viết Tính năng Java 8 – Hướng dẫn TUYỆT VỜI . Phần thứ hai ở đây (liên kết có thể thay đổi). Các tính năng của Java 8 – Hướng dẫn cơ bản (Phần 1) - 1 Lưu ý của biên tập viên : Bài viết này được xuất bản trong khi Java 8 được cung cấp rộng rãi và tất cả các dấu hiệu đều cho thấy đây thực sự là một phiên bản chính. Tại đây, chúng tôi đã cung cấp rất nhiều hướng dẫn cho Java Code Geeks, chẳng hạn như Chơi với Java 8 – Lambdas và Concurrency , Hướng dẫn API ngày và giờ của Java 8: LocalDateTimeLớp trừu tượng so với Giao diện trong Kỷ nguyên Java 8 . Chúng tôi cũng liên kết tới 15 hướng dẫn Java 8 phải đọc từ các nguồn khác . Tất nhiên, chúng tôi xem xét một số nhược điểm, chẳng hạn như Mặt tối của Java 8 . Vì vậy, đã đến lúc thu thập tất cả các tính năng chính của Java 8 vào một nơi để thuận tiện cho bạn. Thưởng thức!

1. Giới thiệu

Không còn nghi ngờ gì nữa, việc phát hành Java 8 là sự kiện lớn nhất kể từ Java 5 (được phát hành cách đây khá lâu, vào năm 2004). Nó mang lại nhiều tính năng mới cho Java cả về ngôn ngữ, trình biên dịch, thư viện, công cụ và JVM (Máy ảo Java). Trong hướng dẫn này, chúng ta sẽ xem xét những thay đổi này và minh họa các trường hợp sử dụng khác nhau bằng các ví dụ thực tế. Hướng dẫn bao gồm một số phần, mỗi phần đề cập đến một khía cạnh cụ thể của nền tảng:
  • Ngôn ngữ
  • Trình biên dịch
  • Thư viện
  • Công cụ
  • Môi trường thời gian chạy (JVM)

2. Các tính năng mới trong Java 8

Trong mọi trường hợp, Java 8 là một bản phát hành chính. Có thể nói rằng việc này mất rất nhiều thời gian vì việc triển khai các tính năng mà mọi nhà phát triển Java đang tìm kiếm. Trong phần này chúng tôi sẽ đề cập đến hầu hết trong số họ.

2.1. Lambdas và giao diện chức năng

Lambdas (còn được gọi là phương thức riêng tư hoặc ẩn danh) là thay đổi ngôn ngữ lớn nhất và được mong đợi nhất trong toàn bộ bản phát hành Java 8. Chúng cho phép chúng ta chỉ định chức năng làm đối số phương thức (bằng cách khai báo một hàm xung quanh nó) hoặc chỉ định mã dưới dạng dữ liệu : các khái niệm mà mọi nhà phát triển chức năng đều quen thuộc. lập trình _ Nhiều ngôn ngữ trên nền tảng JVM (Groovy, Scala , ...) đã có lambda ngay từ ngày đầu, nhưng các nhà phát triển Java không có lựa chọn nào khác ngoài việc thể hiện lambda thông qua các lớp ẩn danh. Cuộc tranh luận về thiết kế lambda đã tiêu tốn rất nhiều thời gian và công sức của công chúng. Nhưng cuối cùng những thỏa hiệp đã được tìm thấy, dẫn đến sự xuất hiện của những thiết kế ngắn gọn mới. Ở dạng đơn giản nhất, lambda có thể được biểu diễn dưới dạng danh sách các tham số được phân tách bằng dấu phẩy, ký hiệu -> và phần thân. Ví dụ:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) )
Lưu ý rằng loại đối số e được xác định bởi trình biên dịch. Ngoài ra, bạn có thể chỉ định rõ ràng loại tham số bằng cách gói tham số đó trong dấu ngoặc đơn. Ví dụ:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
Trong trường hợp phần thân lambda phức tạp hơn, nó có thể được gói trong dấu ngoặc nhọn, tương tự như định nghĩa hàm thông thường trong Java. Ví dụ:
Arrays.asList( "a", "b", "d" ).forEach( e -< {
    System.out.print( e );
    System.out.print( e );
} );
Lambda có thể tham chiếu đến các thành viên của lớp và các biến cục bộ (ngầm làm cho cuộc gọi có hiệu lực bất kể finaltrường có được truy cập hay không). Ví dụ: 2 đoạn này tương đương nhau:
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
VÀ:
final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
Lambdas có thể trả về một giá trị. Kiểu trả về sẽ được xác định bởi trình biên dịch. Không cần phải khai báo returnnếu phần thân của lambda chỉ có một dòng. Hai đoạn mã dưới đây tương đương nhau:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
VÀ:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );
Các nhà phát triển ngôn ngữ đã suy nghĩ rất lâu về cách làm cho các hàm hiện có trở nên thân thiện với lambda. Kết quả là, khái niệm về giao diện chức năng đã xuất hiện. Giao diện chức năng là giao diện chỉ có một phương thức. Kết quả là nó có thể được chuyển đổi ngầm thành biểu thức lambda. java.lang.Runnablejava.util.concurrent.Callablehai ví dụ tuyệt vời về giao diện chức năng. Trong thực tế, các giao diện chức năng rất dễ vỡ: nếu ai đó thêm dù chỉ một phương thức khác vào định nghĩa giao diện, nó sẽ không còn hoạt động nữa và quá trình biên dịch sẽ không hoàn tất. Để tránh sự mong manh này và xác định rõ ràng mục đích của một giao diện là chức năng, một chú thích đặc biệt đã được thêm vào Java 8 @FunctionalInterface(tất cả các giao diện hiện có trong thư viện Java đều nhận được chú thích @FunctionalInterface). Hãy xem định nghĩa đơn giản này về giao diện chức năng:
@FunctionalInterface
public interface Functional {
    void method();
}
Có một điều cần lưu ý: các phương thức mặc định và phương thức tĩnh không vi phạm nguyên tắc của giao diện chức năng và có thể được khai báo:
@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();

    default void defaultMethod() {
    }
}
Lambdas là tính năng phổ biến nhất của Java 8. Chúng có tất cả tiềm năng để thu hút nhiều nhà phát triển hơn đến với nền tảng tuyệt vời này và cung cấp hỗ trợ thông minh cho các tính năng trong Java thuần túy. Để biết thêm thông tin chi tiết, vui lòng tham khảo tài liệu chính thức .

2.2. Giao diện mặc định và phương thức tĩnh

Java 8 đã mở rộng định nghĩa về giao diện với hai khái niệm mới: phương thức mặc định và phương thức tĩnh. Các phương thức mặc định làm cho giao diện có phần giống với các đặc điểm nhưng phục vụ mục đích hơi khác. Chúng cho phép bạn thêm các phương thức mới vào các giao diện hiện có mà không phá vỡ khả năng tương thích ngược đối với các phiên bản đã viết trước đó của các giao diện này. Sự khác biệt giữa các phương thức mặc định và các phương thức trừu tượng là các phương thức trừu tượng phải được triển khai trong khi các phương thức mặc định thì không. Thay vào đó, mỗi giao diện phải cung cấp cái gọi là triển khai mặc định và tất cả các con cháu sẽ nhận được nó theo mặc định (với khả năng ghi đè triển khai mặc định này nếu cần). Hãy xem ví dụ dưới đây.
private interface Defaulable {
    // Интерфейсы теперь разрешают методы по умолчанию,
    // клиент может реализовывать  (переопределять)
    // or не реализовывать его
    default String notRequired() {
        return "Default implementation";
    }
}

private static class DefaultableImpl implements Defaulable {
}

private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
}
Giao diện Defaulablekhai báo một phương thức mặc định notRequired()bằng cách sử dụng từ khóa defaultnhư một phần của định nghĩa phương thức. Một trong các lớp, DefaultableImpl, triển khai giao diện này, giữ nguyên phương thức mặc định. Một lớp khác, OverridableImpl, ghi đè việc triển khai mặc định và cung cấp lớp riêng của nó. Một tính năng thú vị khác được giới thiệu trong Java 8 là các giao diện có thể khai báo (và đưa ra cách triển khai) các phương thức tĩnh. Đây là một ví dụ:
private interface DefaulableFactory {
    // Interfaces now allow static methods
    static Defaulable create( Supplier<Defaulable> supplier ) {
        return supplier.get();
    }
}
Một đoạn mã nhỏ kết hợp phương thức mặc định và phương thức tĩnh từ ví dụ trên:
public static void main( String[] args ) {
    Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
    System.out.println( defaulable.notRequired() );

    defaulable = DefaulableFactory.create( OverridableImpl::new );
    System.out.println( defaulable.notRequired() );
}
Đầu ra giao diện điều khiển của chương trình này trông như thế này:
Default implementation
Overridden implementation
Việc triển khai phương thức mặc định trong JVM rất hiệu quả và việc gọi phương thức được hỗ trợ bởi các lệnh mã byte. Các phương thức mặc định cho phép các giao diện Java hiện có phát triển mà không làm gián đoạn quá trình biên dịch. Ví dụ điển hình là nhiều phương thức được thêm vào giao diện java.util.Collection: stream(), parallelStream(), forEach(), removeIf(), ... Mặc dù mạnh mẽ nhưng các phương thức mặc định nên được sử dụng một cách thận trọng: trước khi khai báo một phương thức mặc định, bạn nên suy nghĩ kỹ xem liệu điều này có thực sự cần thiết hay không vì điều này có thể dẫn đến để biên soạn sự mơ hồ và lỗi trong hệ thống phân cấp phức tạp. Để biết thêm thông tin chi tiết, vui lòng tham khảo tài liệu .

2.3. Phương pháp tham khảo

Các phương thức tham chiếu triển khai cú pháp hữu ích để tham chiếu các phương thức hoặc hàm tạo hiện có của các lớp hoặc đối tượng Java (thể hiện). Cùng với các biểu thức lambda, các phương thức tham chiếu làm cho cấu trúc ngôn ngữ trở nên nhỏ gọn và ngắn gọn, dựa trên mẫu. Dưới đây là một lớp Carlàm ví dụ về các định nghĩa phương thức khác nhau, hãy nêu bật bốn loại phương thức tham chiếu được hỗ trợ:
public static class Car {
    public static Car create( final Supplier<Car> supplier ) {
        return supplier.get();
    }

    public static void collide( final Car car ) {
        System.out.println( "Collided " + car.toString() );
    }

    public void follow( final Car another ) {
        System.out.println( "Following the " + another.toString() );
    }

    public void repair() {
        System.out.println( "Repaired " + this.toString() );
    }
}
Phương thức tham chiếu đầu tiên là tham chiếu đến một hàm tạo có cú pháp Class::newhoặc một phương pháp thay thế cho generics Class< T >::new. Lưu ý rằng hàm tạo không có đối số.
final Car car = Car.create( Car::new );
final List<Car> cars = Arrays.asList( car );
Tùy chọn thứ hai là tham chiếu đến một phương thức tĩnh có cú pháp Class::static_method. Lưu ý rằng phương thức này lấy chính xác một tham số loại Car.
cars.forEach( Car::collide );
Loại thứ ba là tham chiếu đến một phương thức của một thể hiện của một đối tượng tùy ý thuộc một loại nhất định với cú pháp Class::method. Lưu ý rằng không có đối số nào được phương thức này chấp nhận.
cars.forEach( Car::repair );
Và loại cuối cùng, thứ tư là tham chiếu đến một phương thức của một thể hiện của một lớp cụ thể với cú pháp instance::method. Xin lưu ý rằng phương thức này chỉ chấp nhận một tham số thuộc loại Car.
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
Việc chạy tất cả các ví dụ này dưới dạng chương trình Java sẽ tạo ra kết quả đầu ra của bảng điều khiển sau (tham chiếu lớp Carcó thể khác nhau):
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Để biết thêm thông tin chi tiết và chi tiết về các phương pháp tham khảo, vui lòng tham khảo tài liệu chính thức .

2.4. Chú thích trùng lặp

Kể từ khi Java 5 giới thiệu hỗ trợ cho chú thích , tính năng này đã trở nên rất phổ biến và được sử dụng rất rộng rãi. Tuy nhiên, một trong những hạn chế của việc sử dụng chú thích là thực tế là cùng một chú thích không thể được khai báo nhiều lần ở cùng một nơi. Java 8 phá vỡ quy tắc này và đưa ra các chú thích trùng lặp. Điều này cho phép các chú thích giống nhau được lặp lại nhiều lần tại vị trí chúng được khai báo. Các chú thích trùng lặp sẽ tự chú thích bằng cách sử dụng chú thích @Repeatable. Trên thực tế, đây không phải là một sự thay đổi nhiều về ngôn ngữ vì nó là một thủ thuật biên dịch, trong khi kỹ thuật vẫn giữ nguyên. Hãy xem một ví dụ đơn giản:
package com.javacodegeeks.java8.repeatable.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class RepeatingAnnotations {
    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface Filters {
        Filter[] value();
    }

    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    @Repeatable( Filters.class )
    public @interface Filter {
        String value();
    };

    @Filter( "filter1" )
    @Filter( "filter2" )
    public interface Filterable {
    }

    public static void main(String[] args) {
        for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
            System.out.println( filter.value() );
        }
    }
}
Như chúng ta có thể thấy, lớp này Filterđược chú thích bằng @Repeatable( Filters. class). Filterschỉ đơn giản là chủ sở hữu của các chú thích Filter, nhưng trình biên dịch Java cố gắng che giấu sự hiện diện của chúng với các nhà phát triển. Do đó, giao diện Filterablechứa các chú thích Filterđược khai báo hai lần (không đề cập đến Filters). API Reflection cũng cung cấp một phương thức mới getAnnotationsByType()để trả về một số loại chú thích trùng lặp (hãy nhớ rằng Filterable. class.getAnnotation( Filters. class) sẽ trả về một phiên bản được trình biên dịch đưa vào Filters). Đầu ra của chương trình sẽ trông như thế này:
filter1
filter2
Để biết thêm thông tin chi tiết, vui lòng tham khảo tài liệu chính thức .

2.5. Cải thiện suy luận kiểu

Trình biên dịch Java 8 đã nhận được nhiều cải tiến về suy luận kiểu. Trong nhiều trường hợp, các tham số kiểu rõ ràng có thể được xác định bởi trình biên dịch, do đó làm cho mã sạch hơn. Hãy xem một ví dụ:
package com.javacodegeeks.java8.type.inference;

public class Value<T> {
    public static<T> T defaultValue() {
        return null;
    }

    public T getOrDefault( T value, T defaultValue ) {
        return ( value != null ) ? value : defaultValue;
    }
}
Và đây là cách sử dụng với type Value<String>:
package com.javacodegeeks.java8.type.inference;

public class TypeInference {
    public static void main(String[] args) {
        final Value<String> value = new Value<>();
        value.getOrDefault( "22", Value.defaultValue() );
    }
}
Tham số loại Value.defaultValue()được xác định tự động và không cần phải cung cấp rõ ràng. Trong Java 7, ví dụ tương tự sẽ không được biên dịch và phải được viết lại thành <NOBR>Value.<String>defaultValue()</NOBR>.

2.6. Hỗ trợ chú thích mở rộng

Java 8 mở rộng bối cảnh có thể sử dụng các chú thích. Ngày nay, hầu hết mọi thứ đều có thể có chú thích: biến cục bộ, kiểu chung, siêu lớp và giao diện được triển khai, thậm chí cả các ngoại lệ của phương thức. Một vài ví dụ được trình bày dưới đây:
package com.javacodegeeks.java8.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;

public class Annotations {
    @Retention( RetentionPolicy.RUNTIME )
    @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
    public @interface NonEmpty {
    }

    public static class Holder<@NonEmpty T> extends @NonEmpty Object {
        public void method() throws @NonEmpty Exception {
        }
    }

    @SuppressWarnings( "unused" )
    public static void main(String[] args) {
        final Holder<String> holder = new @NonEmpty Holder<String>();
        @NonEmpty Collection<@NonEmpty String> strings = new ArrayList<>();
    }
}
ElementType.TYPE_USEElementType.TYPE_PARAMETERhai loại phần tử mới để mô tả bối cảnh chú thích có liên quan. Annotation Processing APIcũng đã trải qua những thay đổi nhỏ để nhận biết các loại chú thích mới trong Java.

3. Các tính năng mới trong trình biên dịch Java

3.1. Tên tham số

Trong suốt thời gian qua, các nhà phát triển Java đã phát minh ra nhiều cách khác nhau để lưu trữ tên tham số phương thức trong mã byte Java để cung cấp chúng khi chạy (ví dụ: thư viện Paranamer ). Cuối cùng, Java 8 tạo ra hàm khó này bằng ngôn ngữ (sử dụng API và phương thức Reflection Parameter.getName()) và mã byte (sử dụng đối số trình biên dịch mới javac:) –parameters.
package com.javacodegeeks.java8.parameter.names;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class ParameterNames {
    public static void main(String[] args) throws Exception {
        Method method = ParameterNames.class.getMethod( "main", String[].class );
        for( final Parameter parameter: method.getParameters() ) {
            System.out.println( "Parameter: " + parameter.getName() );
        }
    }
}
Nếu bạn biên dịch lớp này mà không sử dụng đối số –parametersrồi chạy chương trình, bạn sẽ thấy nội dung như sau:
Parameter: arg0
Với tham số –parametersđược truyền tới trình biên dịch, đầu ra của chương trình sẽ khác (tên thực tế của tham số sẽ được hiển thị):
Parameter: args
Đối với người dùng Maven nâng cao, đối số –parameters có thể được thêm vào phần biên dịch bằng cách sử dụng phần maven-compiler-plugin:
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
    <compilerArgument>-parameters</compilerArgument>
    <source>1.8</source>
    <target>1.8</target>
    </configuration>
</plugin>
Để kiểm tra tính khả dụng của tên tham số, có một isNamePresent()phương thức thuận tiện được cung cấp bởi lớp Parameter.

4. Công cụ Java mới

Java 8 đi kèm với một bộ công cụ dòng lệnh mới. Trong phần này chúng ta sẽ xem xét điều thú vị nhất trong số đó.

4.1. Động cơ Nashorn: jjs

jjs là một công cụ Nashorn độc lập dựa trên dòng lệnh. Nó lấy danh sách các tệp mã nguồn JavaScript và chạy chúng. Ví dụ: hãy tạo một tệp func.js có nội dung sau:
function f() {
     return 1;
};

print( f() + 1 );
Để chạy tệp này, hãy chuyển nó làm đối số cho jjs :
jjs func.js
Đầu ra của bàn điều khiển sẽ như thế này:
2
Để biết thêm chi tiết xem tài liệu .

4.2. Trình phân tích phụ thuộc lớp: jdeps

jdeps là một công cụ dòng lệnh thực sự tuyệt vời. Nó hiển thị các phụ thuộc cấp gói hoặc cấp lớp cho các lớp Java. Nó chấp nhận tệp .class , thư mục hoặc tệp JAR làm đầu vào. Theo mặc định , jdeps xuất các phần phụ thuộc vào đầu ra tiêu chuẩn (bảng điều khiển). Để làm ví dụ, hãy xem báo cáo phụ thuộc của thư viện Spring Framework phổ biến . Để giữ ví dụ ngắn gọn, chúng ta hãy xem xét các phần phụ thuộc chỉ dành cho tệp JAR org.springframework.core-3.0.5.RELEASE.jar.
jdeps org.springframework.core-3.0.5.RELEASE.jar
Lệnh này xuất ra khá nhiều nên chúng ta sẽ chỉ phân tích một phần đầu ra. Các phần phụ thuộc được nhóm theo gói. Nếu không có phụ thuộc, không tìm thấy sẽ được hiển thị .
org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
   org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.io
      -> java.lang
      -> java.lang.annotation
      -> java.lang.ref
      -> java.lang.reflect
      -> java.util
      -> java.util.concurrent
      -> org.apache.commons.logging                         not found
      -> org.springframework.asm                            not found
      -> org.springframework.asm.commons                    not found
   org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.lang
      -> java.lang.annotation
      -> java.lang.reflect
      -> java.util
Để biết thêm thông tin chi tiết, vui lòng tham khảo tài liệu chính thức .
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION