قابلیت Lambdas بزرگترین تغییر در جاوا 8 است. این قابلیت به ما اجازه میدهد تا توابع را بصورت یک آرگومان متود (با ارسال به یک تابع) یا کد را بصورت داده ببینیم. در زیر مثالهای استفاده از این قابلیت را میبینیم.
مثال 1:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
مثال 2:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
مثال 3:
Arrays.asList( "a", "b", "d" ).forEach( e -> {
System.out.print( e );
System.out.print( e );
} );
مثال 4:
String separator = ","
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );
مثال 5:
final String separator = ","
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );
مثال 6:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
مثال 7:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );
جاوا 8 در extend کردن از interfaceدو مفهوم جدید تعریف کرده است: متودهای default و static. متودهای default امکان اضافه کردن متودهای جدید در interface های موجود را بدون شکستن سازگاری باینری با کدهای نوشته شده برای ورژنهای قدیمی تر آن interfaceها را می دهد.
تفاوت بین متودهای default و متودهای abstractاین است که متودهای abstract نیازمند پیاده سازی شدن هستند اما متودهای default نه. در ادامه مثالی در این رابطه را میبینیم:
private interface Defaulable {
// Interfaces now allow default methods, the implementer may or
// may not implement (override) them.
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"
}
}
رابط Defaulable یک متود defaultبه نام notRequired با استفاده از کلمه کلیدی default تعریف میکنیم. کلاس DefaultableImplرابط Defaulable را بدون overrideکردن متود default پیاده سازی میکند. اما کلاس OverridableImpl متود defaultرا override میکند.
امکان جذاب دیگر در جاوا 8 این است که در interfaceمیتوان متودهای static را تعریف کرد. مثال زیر را ببنید:
private interface DefaulableFactory {
// Interfaces now allow static methods
static Defaulable create( Supplier< Defaulable > supplier ) {
return supplier.get();
}
}
حالا در متود main زیر نحوه استفاده از آنرا میبینیم.
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() );
}
و خروجی console:
Default implementation
Overridden implementation
این قابلیت برای اشاره مستقیم به متودها یا سازندهای کلاسها یا آبجکتهای(نمونه های) جاوا استفاده میشود. Reference های متود با پیوستگی به Lambdas نمای زبان را فشرده و خلاصه میکند.
در مثال زیر ابتدا کلاس کار را تعریف میکنیم و سپس نحوه استفاده از این قابلیت را روی آن نمایش میدهیم.
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() );
}
}
نوع اول reference های متود، referenceسازنده بصورت Class::new یا برای داده های عمومی Class<T>::newاست. دقت کنید که سازنده آرگومانی ندارد.
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );
نوع دوم referenceهای متود، reference متود staticبصورت Class::static_methodاست. دقت کنید که متود دقیقا یک پارامتر از نوع Carدریافت میکند.
cars.forEach( Car::collide );
نوع سوم referenceهای متود، reference به متود یک آبجکت مشخص شده با نوع بصورت Class::method است. دقت کنید که توسط متود پارامتری دریافت نمیشود.
cars.forEach( Car::repair );
در نهایت نوع چهارم refrenceبه متود نمونه کلاس خاص نمونه بصورت instance::method است.
دقت کنید که متود دقیقا یک پارامتر از نوع Car می پذیرد.
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
از جاوا 5 پشتیبانی از annotationها معرفی شد، این امکان بسیار مشهور شد و بصورت گسترده استفاده شد. به هر حال یکی از محدودیت های استفاده از annotationدر واقع این است که نمیتوان بیش از یکی را در یک مکان تعریف کرد. جاوا 8 این قانون را شکسته و جایگزینی annotationها را معرفی کرده است. این امکان اجازه میدهد تا annotationچندین بار در همان مکان تعریف شده تکرار شود.
جایگزینی annotationها با استفاده از @Repeatable annotationانجام میشود. در ادامه مثالی از آنرا میبینیم:
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() );
}
}
}
خروجی برنامه بشکل زیر است:
filter1
filter2
برای سالها توسعه دهندگان جاوا راههای متفاوتی را برای نگهداشتن نام پارامترهای متود در بایت کد جاوا و آنها را در زمان اجرا ایجاد میکردند (برای مثال Paranamer library). در نهایت جاوا 8 به این نیاز پاسخ داد و امکانی با استفاده از Reflection API و متود Parameter.getName()و بایت کد با استفاده از کامپایلر 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() );
}
}
}
اگر این کلاس را بدون استفاده از آرگومان –parametersکامپایل و اجرا کنید، خروجی زیر را خواهید دید:
Parameter: arg0
اما با آرگومان –parametersخواهید دید:
Parameter: args
به تازگی Stream API(java.util.stream) به جاوا اضافه شده است. Stream API پردازشهای روی collectionsها را بصورت قابل توجه ای ساده میکند (البته تنها به collectionsمحدود نمیشود).
اینجانب یک بررسی ساده روی سورس کدهای مربوط به Stream ها انجام دادم. دیدم که Stream یک interface است و در نتیجه برای آن کدی نوشته نشده است و در عوض تعداد زیادی از کلاسها این interface را implement کرده اند. در نتیجه هر کلاس یا Collection با استفاده از متودهای درون خود اینکار را انجام داده است. پس Stream در صورتی که بصورت parallel استفاده نشود، هیچ تاثیر منفی یا مثبتی روی کارایی و مصرف حافظه کدها ندارد و تنها کد نویسی را راحت میکند.
با یک مثال ساده کار را شروع میکنیم.
public class Streams {
private enum Status {
OPEN, CLOSED
};
private static final class Task {
private final Status status;
private final Integer points;
Task( final Status status, final Integer points ) {
this.status = status;
this.points = points;
}
public Integer getPoints() {
return points;
}
public Status getStatus() {
return status;
}
@Override
public String toString() {
return String.format( "[%s, %d]", status, points );
}
}
}
اکنون از کلاس taskیک مجموعه میسازیم.
حالا می خواهیم با استفاده از Stream مجموعه pointهای مربوط به taskهای در حالت OPEN را محاسبه کنیم.
// Calculate total points of all active tasks using sum()
final long totalPointsOfOpenTasks = tasks
.stream()
.filter( task -> task.getStatus() == Status.OPEN )
.mapToInt( Task::getPoints )
.sum();
System.out.println( "Total points: " + totalPointsOfOpenTasks );
و خروجی بصورت زیر است:
Total points: 18
حال می خواهیم مجموع pointهای تمام task ها را بصورت موازی (parallel) محاسبه کنیم.
// Calculate total points of all tasks
final double totalPoints = tasks
.stream()
.parallel()
.map( task -> task.getPoints() ) // or map( Task::getPoints )
.reduce( 0, Integer::sum );
System.out.println( "Total points (all tasks): " + totalPoints );
و خروجی بصورت زیر است:
Total points: 26
حال میخواهیم taskها را به توجه به OPENیا CLOSE بودنشان دسته بندی کنیم.
// Group tasks by their status
final Map< Status, List< Task > > map = tasks
.stream()
.collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );
و خروجی بصورت زیر است:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
حال میخواهیم درصد وزنی هر taskرا نسبت به کل taskها بر روی pointهای آن محاسبه کنیم.
// Calculate the weight of each tasks (as percent of total points)
final Collection< String > result = tasks
.stream() // Stream< String >
.mapToInt( Task::getPoints ) // IntStream
.asLongStream() // LongStream
.mapToDouble( points -> points / totalPoints ) // DoubleStream
.boxed() // Stream< Double >
.mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
.mapToObj( percentage -> percentage + "%" ) // Stream< String>
.collect( Collectors.toList() ); // List< String >
System.out.println( result );
و خروجی بصورت زیر است:
[19%, 50%, 30%]
در ادامه مثالی می آوریم که از Stream API در collectionsاستفاده نمیشود و در کار با فایل استفاده کرده ایم.
final Path path = new File( filename ).toPath();
try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
lines.( () -> System.out.println("Done!") ).forEach( System.out::println );
}
کد بالا تمام خطهای فایل را در خروجی چاپ میکند و در انتها عبارت "Done!" را چاپ میکند.
جاوا 8 امکان توسعه و اجرای برنامه های جاوا اسکریپ در JVM را از طریق موتور جاوا اسکریپت Nashorn فراهم کرده است. موتور جاوا اسکریپت Nashorn تنها پیاده سازی دیگری از javas.script.ScriptEngine است و همان مجموعه قوانین پیروی میکند که اجازه قابلیت همکاری جاوا و جاوا اسکریپت را میدهد. در اینجا یک مثال کوچک را میبینیم.
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName( "JavaScript" );
System.out.println( engine.getClass().getName() );
System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
و خروجی بصورت زیر است:
jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2
سرانجام پشتیبانی از کدگزاری Base64در کتابخانه های استاندارد جاوا قرار گرفت. در ادامه با مثالی نحوه استفاده از آنرا نمایش میدهیم.
package com.javacodegeeks.java8.base64;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Base64s {
public static void main(String[] args) {
final String text = "Base64 finally in Java 8!"
final String encoded = Base64
.getEncoder()
.encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
System.out.println( encoded );
final String decoded = new String(
Base64.getDecoder().decode( encoded ),
StandardCharsets.UTF_8 );
System.out.println( decoded );
}
}
و خروجی بصورت زیر است:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
همچنین انواع دیگر از کدگزاری مخصوص URLو MIME نیز توسط همین کلاس پشتیبانی میشود.
جاوا 8 تعداد بسیار زیادی از متودها را برای پردازش موازی آرایه ها اضافه کرده است. مسلما یکی از مهمترین آنها parallelSort()است که میتواند به میزان قابل توجه ای از طریق مرتب سازی در ماشینهای چند هسته ای سرعت را بالا ببرد. مثال کوچکی که در ادامه می آید متودهای جدید خانواده parallelXxxرا در عمل اثبات میکند.
package com.javacodegeeks.java8.parallel.arrays;
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
public class ParallelArrays {
public static void main( String[] args ) {
long[] arrayOfLong = new long [ 20000 ];
Arrays.parallelSetAll( arrayOfLong,
index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
i -> System.out.print( i + " " ) );
System.out.println();
Arrays.parallelSort( arrayOfLong );
Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
i -> System.out.print( i + " " ) );
System.out.println();
}
}
از طریق کد کوچکی با استفاده از متود parallelSetAllاستفاده میکند تا آرایه ای را با 20000 مقدار تصادفی پر کند. سپس با استفاده از parallelSortآنها را مرتب میکند و سپس ده مقدار کوچکتر آنرا چاپ میکند. خروجی مشابه زیر است:
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793
[1] http://www.javacodegeeks.com/2014/05/java-8-features-tutorial.html.
[2] http://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html
[3] http://docs.oracle.com/javase/tutorial/
[4]http://blog.arungupta.me/2014/03/wildfly8-jdk8-netbeans8-javaee7-excellent-combo-enterprise-java/
[5] http://winterbe.com/posts/2014/03/16/java-8-tutorial/
https://teaching.iranmodares.com/teaching-index.php?metaword=%D9%85%D8%AD%D9%85%D8%AF+%D8%AD%D8%B3%DB%8C%D9%86+%D8%AC%D9%84%DB%8C%D9%84%DB%8C