Artikel ini awalnya diposting di blog saya.
Anda juga harus membaca Tutorial Java 11 saya (termasuk bahasa baru dan fitur API dari Java 9, 10 dan 11).
Selamat datang di perkenalan saya tentang Java 8. Tutorial ini memandu Anda langkah demi langkah melalui semua fitur bahasa baru. Didukung oleh contoh kode singkat dan sederhana, Anda akan mempelajari cara menggunakan metode antarmuka default, ekspresi lambda, referensi metode, dan anotasi berulang. Di akhir artikel, Anda akan memahami perubahan API terkini seperti aliran, antarmuka fungsional, ekstensi peta, dan API Tanggal baru. Tidak ada dinding teks, hanya sekumpulan cuplikan kode yang dikomentari. Menikmati!
★★★ Suka proyek ini? Tinggalkan bintang, ikuti di Twitter atau berdonasi untuk mendukung pekerjaan saya. Terima kasih! ★★★
Java 8 memungkinkan kita menambahkan implementasi metode non-abstrak ke antarmuka dengan menggunakan kata kunci default
. Fitur ini juga dikenal sebagai metode ekstensi virtual.
Inilah contoh pertama kami:
interface Formula {
double calculate ( int a );
default double sqrt ( int a ) {
return Math . sqrt ( a );
}
}
Selain metode abstrak calculate
antarmuka Formula
juga mendefinisikan metode default sqrt
. Kelas konkrit hanya perlu mengimplementasikan metode abstrak calculate
. Metode default sqrt
dapat digunakan secara langsung.
Formula formula = new Formula () {
@ Override
public double calculate ( int a ) {
return sqrt ( a * 100 );
}
};
formula . calculate ( 100 ); // 100.0
formula . sqrt ( 16 ); // 4.0
Rumusnya diimplementasikan sebagai objek anonim. Kode ini cukup bertele-tele: 6 baris kode untuk perhitungan sederhana sqrt(a * 100)
. Seperti yang akan kita lihat di bagian berikutnya, ada cara yang lebih baik dalam mengimplementasikan objek metode tunggal di Java 8.
Mari kita mulai dengan contoh sederhana tentang cara mengurutkan daftar string di Java versi sebelumnya:
List < String > names = Arrays . asList ( "peter" , "anna" , "mike" , "xenia" );
Collections . sort ( names , new Comparator < String >() {
@ Override
public int compare ( String a , String b ) {
return b . compareTo ( a );
}
});
Metode utilitas statis Collections.sort
menerima daftar dan pembanding untuk mengurutkan elemen dari daftar yang diberikan. Anda sering menemukan diri Anda membuat pembanding anonim dan meneruskannya ke metode pengurutan.
Daripada membuat objek anonim sepanjang hari, Java 8 hadir dengan sintaksis yang jauh lebih pendek, ekspresi lambda :
Collections . sort ( names , ( String a , String b ) -> {
return b . compareTo ( a );
});
Seperti yang Anda lihat, kodenya jauh lebih pendek dan lebih mudah dibaca. Tapi itu menjadi lebih pendek lagi:
Collections . sort ( names , ( String a , String b ) -> b . compareTo ( a ));
Untuk badan metode satu baris, Anda bisa melewati tanda kurung kurawal {}
dan kata kunci return
. Tapi itu menjadi lebih pendek lagi:
names . sort (( a , b ) -> b . compareTo ( a ));
Daftar sekarang memiliki metode sort
. Kompiler Java juga mengetahui tipe parameter sehingga Anda dapat melewatinya juga. Mari selami lebih dalam bagaimana ekspresi lambda dapat digunakan di alam liar.
Bagaimana ekspresi lambda cocok dengan sistem tipe Java? Setiap lambda sesuai dengan tipe tertentu, ditentukan oleh antarmuka. Antarmuka fungsional harus berisi tepat satu deklarasi metode abstrak . Setiap ekspresi lambda dari tipe tersebut akan dicocokkan dengan metode abstrak ini. Karena metode default tidak abstrak, Anda bebas menambahkan metode default ke antarmuka fungsional Anda.
Kita dapat menggunakan antarmuka arbitrer sebagai ekspresi lambda selama antarmuka tersebut hanya berisi satu metode abstrak. Untuk memastikan antarmuka Anda memenuhi persyaratan, Anda harus menambahkan anotasi @FunctionalInterface
. Kompiler mengetahui anotasi ini dan memunculkan kesalahan kompiler segera setelah Anda mencoba menambahkan deklarasi metode abstrak kedua ke antarmuka.
Contoh:
@ FunctionalInterface
interface Converter < F , T > {
T convert ( F from );
}
Converter < String , Integer > converter = ( from ) -> Integer . valueOf ( from );
Integer converted = converter . convert ( "123" );
System . out . println ( converted ); // 123
Perlu diingat bahwa kode ini juga valid jika anotasi @FunctionalInterface
dihilangkan.
Contoh kode di atas dapat disederhanakan lebih lanjut dengan memanfaatkan referensi metode statis:
Converter < String , Integer > converter = Integer :: valueOf ;
Integer converted = converter . convert ( "123" );
System . out . println ( converted ); // 123
Java 8 memungkinkan Anda meneruskan referensi metode atau konstruktor melalui kata kunci ::
. Contoh di atas menunjukkan cara mereferensikan metode statis. Tapi kita juga bisa mereferensikan metode objek:
class Something {
String startsWith ( String s ) {
return String . valueOf ( s . charAt ( 0 ));
}
}
Something something = new Something ();
Converter < String , String > converter = something :: startsWith ;
String converted = converter . convert ( "Java" );
System . out . println ( converted ); // "J"
Mari kita lihat cara kerja kata kunci ::
untuk konstruktor. Pertama kita mendefinisikan contoh kelas dengan konstruktor yang berbeda:
class Person {
String firstName ;
String lastName ;
Person () {}
Person ( String firstName , String lastName ) {
this . firstName = firstName ;
this . lastName = lastName ;
}
}
Selanjutnya kita tentukan antarmuka pabrik orang yang akan digunakan untuk membuat orang baru:
interface PersonFactory < P extends Person > {
P create ( String firstName , String lastName );
}
Daripada menerapkan pabrik secara manual, kami merekatkan semuanya melalui referensi konstruktor:
PersonFactory < Person > personFactory = Person :: new ;
Person person = personFactory . create ( "Peter" , "Parker" );
Kami membuat referensi ke konstruktor Person melalui Person::new
. Kompiler Java secara otomatis memilih konstruktor yang tepat dengan mencocokkan tanda tangan PersonFactory.create
.
Mengakses variabel lingkup luar dari ekspresi lambda sangat mirip dengan objek anonim. Anda dapat mengakses variabel akhir dari lingkup luar lokal serta bidang instan dan variabel statis.
Kita dapat membaca variabel lokal akhir dari lingkup luar ekspresi lambda:
final int num = 1 ;
Converter < Integer , String > stringConverter =
( from ) -> String . valueOf ( from + num );
stringConverter . convert ( 2 ); // 3
Namun berbeda dengan objek anonim, variabel num
tidak harus dinyatakan final. Kode ini juga valid:
int num = 1 ;
Converter < Integer , String > stringConverter =
( from ) -> String . valueOf ( from + num );
stringConverter . convert ( 2 ); // 3
Namun num
harus bersifat final secara implisit agar kode dapat dikompilasi. Kode berikut tidak dapat dikompilasi:
int num = 1 ;
Converter < Integer , String > stringConverter =
( from ) -> String . valueOf ( from + num );
num = 3 ;
Menulis ke num
dari dalam ekspresi lambda juga dilarang.
Berbeda dengan variabel lokal, kami memiliki akses baca dan tulis ke kolom instan dan variabel statis dari dalam ekspresi lambda. Perilaku ini diketahui dari objek anonim.
class Lambda4 {
static int outerStaticNum ;
int outerNum ;
void testScopes () {
Converter < Integer , String > stringConverter1 = ( from ) -> {
outerNum = 23 ;
return String . valueOf ( from );
};
Converter < Integer , String > stringConverter2 = ( from ) -> {
outerStaticNum = 72 ;
return String . valueOf ( from );
};
}
}
Ingat contoh rumus dari bagian pertama? Antarmuka Formula
mendefinisikan metode default sqrt
yang dapat diakses dari setiap contoh rumus termasuk objek anonim. Ini tidak berfungsi dengan ekspresi lambda.
Metode default tidak dapat diakses dari dalam ekspresi lambda. Kode berikut tidak dapat dikompilasi:
Formula formula = ( a ) -> sqrt ( a * 100 );
API JDK 1.8 berisi banyak antarmuka fungsional bawaan. Beberapa di antaranya terkenal dari versi Java yang lebih lama seperti Comparator
atau Runnable
. Antarmuka yang ada diperluas untuk mengaktifkan dukungan Lambda melalui anotasi @FunctionalInterface
.
Namun Java 8 API juga dilengkapi dengan antarmuka fungsional baru untuk membuat hidup Anda lebih mudah. Beberapa antarmuka baru tersebut terkenal dari perpustakaan Google Guava. Bahkan jika Anda familiar dengan perpustakaan ini, Anda harus tetap memperhatikan bagaimana antarmuka tersebut diperluas dengan beberapa ekstensi metode yang berguna.
Predikat adalah fungsi bernilai boolean dari satu argumen. Antarmuka berisi berbagai metode default untuk menyusun predikat ke istilah logis yang kompleks (dan, atau, meniadakan)
Predicate < String > predicate = ( s ) -> s . length () > 0 ;
predicate . test ( "foo" ); // true
predicate . negate (). test ( "foo" ); // false
Predicate < Boolean > nonNull = Objects :: nonNull ;
Predicate < Boolean > isNull = Objects :: isNull ;
Predicate < String > isEmpty = String :: isEmpty ;
Predicate < String > isNotEmpty = isEmpty . negate ();
Fungsi menerima satu argumen dan menghasilkan suatu hasil. Metode default dapat digunakan untuk menyatukan beberapa fungsi (menulis, dan Kemudian).
Function < String , Integer > toInteger = Integer :: valueOf ;
Function < String , String > backToString = toInteger . andThen ( String :: valueOf );
backToString . apply ( "123" ); // "123"
Pemasok menghasilkan hasil dari tipe generik tertentu. Berbeda dengan Fungsi, Pemasok tidak menerima argumen.
Supplier < Person > personSupplier = Person :: new ;
personSupplier . get (); // new Person
Konsumen mewakili operasi yang akan dilakukan pada satu argumen masukan.
Consumer < Person > greeter = ( p ) -> System . out . println ( "Hello, " + p . firstName );
greeter . accept ( new Person ( "Luke" , "Skywalker" ));
Pembanding terkenal dari versi Java yang lebih lama. Java 8 menambahkan berbagai metode default ke antarmuka.
Comparator < Person > comparator = ( p1 , p2 ) -> p1 . firstName . compareTo ( p2 . firstName );
Person p1 = new Person ( "John" , "Doe" );
Person p2 = new Person ( "Alice" , "Wonderland" );
comparator . compare ( p1 , p2 ); // > 0
comparator . reversed (). compare ( p1 , p2 ); // < 0
Opsional bukanlah antarmuka fungsional, tetapi utilitas bagus untuk mencegah NullPointerException
. Ini adalah konsep penting untuk bagian selanjutnya, jadi mari kita lihat sekilas cara kerja Opsional.
Opsional adalah wadah sederhana untuk nilai yang mungkin nol atau bukan nol. Pikirkan sebuah metode yang dapat mengembalikan hasil bukan nol tetapi terkadang tidak menghasilkan apa pun. Alih-alih mengembalikan null
Anda mengembalikan Optional
di Java 8.
Optional < String > optional = Optional . of ( "bam" );
optional . isPresent (); // true
optional . get (); // "bam"
optional . orElse ( "fallback" ); // "bam"
optional . ifPresent (( s ) -> System . out . println ( s . charAt ( 0 ))); // "b"
java.util.Stream
mewakili urutan elemen di mana satu atau lebih operasi dapat dilakukan. Operasi aliran bersifat perantara atau terminal . Sementara operasi terminal mengembalikan hasil dari tipe tertentu, operasi perantara mengembalikan aliran itu sendiri sehingga Anda dapat merangkai beberapa pemanggilan metode secara berurutan. Aliran dibuat pada sumber, misalnya daftar atau kumpulan seperti java.util.Collection
(peta tidak didukung). Operasi aliran dapat dijalankan secara berurutan atau paralel.
Streaming sangat kuat, jadi saya menulis Tutorial Java 8 Streams terpisah. Anda juga harus memeriksa Sequency sebagai perpustakaan serupa untuk web.
Pertama-tama mari kita lihat cara kerja aliran sekuensial. Pertama kita membuat sumber sampel dalam bentuk daftar string:
List < String > stringCollection = new ArrayList <>();
stringCollection . add ( "ddd2" );
stringCollection . add ( "aaa2" );
stringCollection . add ( "bbb1" );
stringCollection . add ( "aaa1" );
stringCollection . add ( "bbb3" );
stringCollection . add ( "ccc" );
stringCollection . add ( "bbb2" );
stringCollection . add ( "ddd1" );
Koleksi di Java 8 diperluas sehingga Anda cukup membuat aliran dengan memanggil Collection.stream()
atau Collection.parallelStream()
. Bagian berikut menjelaskan operasi aliran yang paling umum.
Filter menerima predikat untuk memfilter semua elemen aliran. Operasi ini bersifat perantara yang memungkinkan kita memanggil operasi aliran lain ( forEach
) pada hasilnya. ForEach menerima konsumen untuk dieksekusi untuk setiap elemen dalam aliran yang difilter. ForEach adalah operasi terminal. Ini void
, jadi kami tidak dapat memanggil operasi aliran lainnya.
stringCollection
. stream ()
. filter (( s ) -> s . startsWith ( "a" ))
. forEach ( System . out :: println );
// "aaa2", "aaa1"
Diurutkan adalah operasi perantara yang mengembalikan tampilan aliran yang diurutkan. Elemen diurutkan dalam urutan alami kecuali Anda meneruskan Comparator
khusus.
stringCollection
. stream ()
. sorted ()
. filter (( s ) -> s . startsWith ( "a" ))
. forEach ( System . out :: println );
// "aaa1", "aaa2"
Ingatlah bahwa sorted
hanya membuat tampilan aliran yang diurutkan tanpa memanipulasi urutan koleksi yang didukung. Urutan stringCollection
tidak tersentuh:
System . out . println ( stringCollection );
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
map
operasi perantara mengubah setiap elemen menjadi objek lain melalui fungsi yang diberikan. Contoh berikut mengubah setiap string menjadi string dengan huruf besar. Tapi Anda juga bisa menggunakan map
untuk mengubah setiap objek menjadi tipe lain. Tipe umum dari aliran yang dihasilkan bergantung pada tipe umum fungsi yang Anda teruskan ke map
.
stringCollection
. stream ()
. map ( String :: toUpperCase )
. sorted (( a , b ) -> b . compareTo ( a ))
. forEach ( System . out :: println );
// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Berbagai operasi pencocokan dapat digunakan untuk memeriksa apakah predikat tertentu cocok dengan aliran. Semua operasi tersebut bersifat terminal dan mengembalikan hasil boolean.
boolean anyStartsWithA =
stringCollection
. stream ()
. anyMatch (( s ) -> s . startsWith ( "a" ));
System . out . println ( anyStartsWithA ); // true
boolean allStartsWithA =
stringCollection
. stream ()
. allMatch (( s ) -> s . startsWith ( "a" ));
System . out . println ( allStartsWithA ); // false
boolean noneStartsWithZ =
stringCollection
. stream ()
. noneMatch (( s ) -> s . startsWith ( "z" ));
System . out . println ( noneStartsWithZ ); // true
Hitungan adalah operasi terminal yang mengembalikan jumlah elemen dalam aliran sebagai long
.
long startsWithB =
stringCollection
. stream ()
. filter (( s ) -> s . startsWith ( "b" ))
. count ();
System . out . println ( startsWithB ); // 3
Operasi terminal ini melakukan reduksi pada elemen aliran dengan fungsi yang diberikan. Hasilnya adalah Optional
yang menahan nilai yang dikurangi.
Optional < String > reduced =
stringCollection
. stream ()
. sorted ()
. reduce (( s1 , s2 ) -> s1 + "#" + s2 );
reduced . ifPresent ( System . out :: println );
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
Seperti disebutkan di atas, aliran dapat berurutan atau paralel. Operasi pada aliran sekuensial dilakukan pada satu thread sedangkan operasi pada aliran paralel dilakukan secara bersamaan pada beberapa thread.
Contoh berikut menunjukkan betapa mudahnya meningkatkan kinerja dengan menggunakan aliran paralel.
Pertama kita membuat daftar besar elemen unik:
int max = 1000000 ;
List < String > values = new ArrayList <>( max );
for ( int i = 0 ; i < max ; i ++) {
UUID uuid = UUID . randomUUID ();
values . add ( uuid . toString ());
}
Sekarang kami mengukur waktu yang diperlukan untuk mengurutkan aliran koleksi ini.
long t0 = System . nanoTime ();
long count = values . stream (). sorted (). count ();
System . out . println ( count );
long t1 = System . nanoTime ();
long millis = TimeUnit . NANOSECONDS . toMillis ( t1 - t0 );
System . out . println ( String . format ( "sequential sort took: %d ms" , millis ));
// sequential sort took: 899 ms
long t0 = System . nanoTime ();
long count = values . parallelStream (). sorted (). count ();
System . out . println ( count );
long t1 = System . nanoTime ();
long millis = TimeUnit . NANOSECONDS . toMillis ( t1 - t0 );
System . out . println ( String . format ( "parallel sort took: %d ms" , millis ));
// parallel sort took: 472 ms
Seperti yang Anda lihat, kedua cuplikan kode hampir identik tetapi pengurutan paralel kira-kira 50% lebih cepat. Yang harus Anda lakukan adalah mengubah stream()
menjadi parallelStream()
.
Seperti yang telah disebutkan, peta tidak secara langsung mendukung aliran. Tidak ada metode stream()
yang tersedia pada antarmuka Map
itu sendiri, namun Anda dapat membuat aliran khusus pada kunci, nilai, atau entri peta melalui map.keySet().stream()
, map.values().stream()
dan map.entrySet().stream()
.
Selain itu, peta mendukung berbagai metode baru dan berguna untuk melakukan tugas-tugas umum.
Map < Integer , String > map = new HashMap <>();
for ( int i = 0 ; i < 10 ; i ++) {
map . putIfAbsent ( i , "val" + i );
}
map . forEach (( id , val ) -> System . out . println ( val ));
Kode di atas seharusnya dapat menjelaskan dengan sendirinya: putIfAbsent
mencegah kita menulis pemeriksaan if null tambahan; forEach
menerima konsumen untuk melakukan operasi untuk setiap nilai peta.
Contoh ini menunjukkan cara menghitung kode pada peta dengan memanfaatkan fungsi:
map . computeIfPresent ( 3 , ( num , val ) -> val + num );
map . get ( 3 ); // val33
map . computeIfPresent ( 9 , ( num , val ) -> null );
map . containsKey ( 9 ); // false
map . computeIfAbsent ( 23 , num -> "val" + num );
map . containsKey ( 23 ); // true
map . computeIfAbsent ( 3 , num -> "bam" );
map . get ( 3 ); // val33
Selanjutnya, kita mempelajari cara menghapus entri untuk kunci tertentu, hanya jika kunci tersebut saat ini dipetakan ke nilai tertentu:
map . remove ( 3 , "val3" );
map . get ( 3 ); // val33
map . remove ( 3 , "val33" );
map . get ( 3 ); // null
Metode bermanfaat lainnya:
map . getOrDefault ( 42 , "not found" ); // not found
Menggabungkan entri peta cukup mudah:
map . merge ( 9 , "val9" , ( value , newValue ) -> value . concat ( newValue ));
map . get ( 9 ); // val9
map . merge ( 9 , "concat" , ( value , newValue ) -> value . concat ( newValue ));
map . get ( 9 ); // val9concat
Gabungkan dengan memasukkan kunci/nilai ke dalam peta jika tidak ada entri untuk kunci tersebut, atau fungsi penggabungan akan dipanggil untuk mengubah nilai yang ada.
Java 8 berisi API tanggal dan waktu baru di bawah paket java.time
. Date API baru sebanding dengan perpustakaan Joda-Time, namun tidak sama. Contoh berikut mencakup bagian terpenting dari API baru ini.
Jam menyediakan akses ke tanggal dan waktu saat ini. Jam mengetahui zona waktu dan dapat digunakan sebagai pengganti System.currentTimeMillis()
untuk mengambil waktu saat ini dalam milidetik sejak Unix EPOCH. Titik sesaat pada garis waktu juga diwakili oleh kelas Instant
. Instan dapat digunakan untuk membuat objek java.util.Date
lama.
Clock clock = Clock . systemDefaultZone ();
long millis = clock . millis ();
Instant instant = clock . instant ();
Date legacyDate = Date . from ( instant ); // legacy java.util.Date
Zona waktu diwakili oleh ZoneId
. Mereka dapat dengan mudah diakses melalui metode pabrik statis. Zona waktu menentukan offset yang penting untuk mengkonversi antara tanggal dan waktu instan dan lokal.
System . out . println ( ZoneId . getAvailableZoneIds ());
// prints all available timezone ids
ZoneId zone1 = ZoneId . of ( "Europe/Berlin" );
ZoneId zone2 = ZoneId . of ( "Brazil/East" );
System . out . println ( zone1 . getRules ());
System . out . println ( zone2 . getRules ());
// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
LocalTime mewakili waktu tanpa zona waktu, misalnya 22:00 atau 17:30:15. Contoh berikut membuat dua waktu lokal untuk zona waktu yang ditentukan di atas. Kemudian kita bandingkan kedua waktu tersebut dan hitung selisih jam dan menit antara kedua waktu tersebut.
LocalTime now1 = LocalTime . now ( zone1 );
LocalTime now2 = LocalTime . now ( zone2 );
System . out . println ( now1 . isBefore ( now2 )); // false
long hoursBetween = ChronoUnit . HOURS . between ( now1 , now2 );
long minutesBetween = ChronoUnit . MINUTES . between ( now1 , now2 );
System . out . println ( hoursBetween ); // -3
System . out . println ( minutesBetween ); // -239
LocalTime hadir dengan berbagai metode pabrik untuk menyederhanakan pembuatan instance baru, termasuk penguraian string waktu.
LocalTime late = LocalTime . of ( 23 , 59 , 59 );
System . out . println ( late ); // 23:59:59
DateTimeFormatter germanFormatter =
DateTimeFormatter
. ofLocalizedTime ( FormatStyle . SHORT )
. withLocale ( Locale . GERMAN );
LocalTime leetTime = LocalTime . parse ( "13:37" , germanFormatter );
System . out . println ( leetTime ); // 13:37
LocalDate mewakili tanggal yang berbeda, misalnya 11-03-2014. Ini tidak dapat diubah dan berfungsi persis analog dengan LocalTime. Sampel menunjukkan cara menghitung tanggal baru dengan menambahkan atau mengurangi hari, bulan, atau tahun. Ingatlah bahwa setiap manipulasi mengembalikan instance baru.
LocalDate today = LocalDate . now ();
LocalDate tomorrow = today . plus ( 1 , ChronoUnit . DAYS );
LocalDate yesterday = tomorrow . minusDays ( 2 );
LocalDate independenceDay = LocalDate . of ( 2014 , Month . JULY , 4 );
DayOfWeek dayOfWeek = independenceDay . getDayOfWeek ();
System . out . println ( dayOfWeek ); // FRIDAY
Mengurai LocalDate dari sebuah string sama mudahnya dengan mengurai LocalTime:
DateTimeFormatter germanFormatter =
DateTimeFormatter
. ofLocalizedDate ( FormatStyle . MEDIUM )
. withLocale ( Locale . GERMAN );
LocalDate xmas = LocalDate . parse ( "24.12.2014" , germanFormatter );
System . out . println ( xmas ); // 2014-12-24
LocalDateTime mewakili tanggal-waktu. Ini menggabungkan tanggal dan waktu seperti yang terlihat pada bagian di atas menjadi satu contoh. LocalDateTime
tidak dapat diubah dan berfungsi mirip dengan LocalTime dan LocalDate. Kita dapat menggunakan metode untuk mengambil bidang tertentu dari tanggal-waktu:
LocalDateTime sylvester = LocalDateTime . of ( 2014 , Month . DECEMBER , 31 , 23 , 59 , 59 );
DayOfWeek dayOfWeek = sylvester . getDayOfWeek ();
System . out . println ( dayOfWeek ); // WEDNESDAY
Month month = sylvester . getMonth ();
System . out . println ( month ); // DECEMBER
long minuteOfDay = sylvester . getLong ( ChronoField . MINUTE_OF_DAY );
System . out . println ( minuteOfDay ); // 1439
Dengan informasi tambahan zona waktu, informasi tersebut dapat diubah menjadi instan. Instan dapat dengan mudah dikonversi ke tanggal lama bertipe java.util.Date
.
Instant instant = sylvester
. atZone ( ZoneId . systemDefault ())
. toInstant ();
Date legacyDate = Date . from ( instant );
System . out . println ( legacyDate ); // Wed Dec 31 23:59:59 CET 2014
Memformat tanggal-waktu berfungsi seperti memformat tanggal atau waktu. Daripada menggunakan format yang telah ditentukan sebelumnya, kita dapat membuat pemformat dari pola khusus.
DateTimeFormatter formatter =
DateTimeFormatter
. ofPattern ( "MMM dd, yyyy - HH:mm" );
LocalDateTime parsed = LocalDateTime . parse ( "Nov 03, 2014 - 07:13" , formatter );
String string = formatter . format ( parsed );
System . out . println ( string ); // Nov 03, 2014 - 07:13
Tidak seperti java.text.NumberFormat
DateTimeFormatter
yang baru tidak dapat diubah dan aman untuk thread .
Untuk detail tentang sintaks pola, baca di sini.
Anotasi di Java 8 dapat diulang. Mari selami contoh secara langsung untuk memahaminya.
Pertama, kita mendefinisikan anotasi wrapper yang menyimpan array anotasi aktual:
@interface Hints {
Hint [] value ();
}
@ Repeatable ( Hints . class )
@interface Hint {
String value ();
}
Java 8 memungkinkan kita menggunakan beberapa anotasi berjenis sama dengan mendeklarasikan anotasi @Repeatable
.
@ Hints ({ @ Hint ( "hint1" ), @ Hint ( "hint2" )})
class Person {}
@ Hint ( "hint1" )
@ Hint ( "hint2" )
class Person {}
Menggunakan varian 2, kompiler Java secara implisit menyiapkan anotasi @Hints
di balik terpal. Itu penting untuk membaca informasi anotasi melalui refleksi.
Hint hint = Person . class . getAnnotation ( Hint . class );
System . out . println ( hint ); // null
Hints hints1 = Person . class . getAnnotation ( Hints . class );
System . out . println ( hints1 . value (). length ); // 2
Hint [] hints2 = Person . class . getAnnotationsByType ( Hint . class );
System . out . println ( hints2 . length ); // 2
Meskipun kami tidak pernah mendeklarasikan anotasi @Hints
pada kelas Person
, anotasi tersebut masih dapat dibaca melalui getAnnotation(Hints.class)
. Namun, metode yang lebih mudah adalah getAnnotationsByType
yang memberikan akses langsung ke semua anotasi @Hint
yang dianotasi.
Selanjutnya penggunaan anotasi di Java 8 diperluas ke dua target baru:
@ Target ({ ElementType . TYPE_PARAMETER , ElementType . TYPE_USE })
@interface MyAnnotation {}
Panduan pemrograman saya untuk Java 8 berakhir di sini. Jika Anda ingin mempelajari lebih lanjut tentang semua kelas dan fitur baru dari JDK 8 API, lihat JDK8 API Explorer saya. Ini membantu Anda mengetahui semua kelas baru dan permata tersembunyi JDK 8, seperti Arrays.parallelSort
, StampedLock
dan CompletableFuture
- hanyalah beberapa di antaranya.
Saya juga telah menerbitkan banyak artikel lanjutan di blog saya yang mungkin menarik bagi Anda:
Anda harus mengikuti saya di Twitter. Terima kasih telah membaca!