جاوا ۹ و Jshell (جشنواره عیدتاعید)

(این مقاله از طرف جناب آقای محمد حسین ریماز برای جشنواره عید تا عید جاواکاپ ارسال شده است و محتوای این مطلب لزوماً موردتأیید جاواکاپ نیست).
ورژن جدید جاوا یعنی جاوا ۹ قرار بود که امسال در کنفرانس 2016 JavaOne معرفی شود و به صورت رسمی منتشر شود اما بخاطر بررسی بیشتر پروژه Jigsaw ،جاوا ۹ باز هم قرار است با تأخیر منتشر شود. اما تقریباً بقیه قابلیتهای جاوا ۹ نهایی شدهاند و امکانات متعدد و API های جدیدی به همراه بهینه سازی های فراوانی که در جاوا ۹ اضافه شدهاند هماکنون به صورت آزمایشی در اختیار علاقهمندان قرار گرفته است.1
Jshell یکی از این امکانات جدید است که در جاوا ۹ معرفی شده و قرار است در این مقاله این ابزار کاربردی جدید را بررسی کنیم و سپس نشان دهیم چگونه میتوانیم با استفاده از کتابخانه JSoup یک صفحه وب را تحلیل یا اصطلاحاً Scrape کنیم. هرچند که قرار نیست کار با کتابخانه JSoup را به شما نشان دهیم اما برای جذابتر کردن کار و نشان دادن قدرت جدیدی که در اختیار ما قرار گرفته لیست ۲۵۰ فیلم برتر IMDb را استخراج خواهیم کرد.
Jshell اصطلاحاً یک REPL است. درواقع یک ابزار تحت کنسول است که به طور مداوم و در حلقه ای بی پایان دستورات وارد شده را میخواند و آنها را ارزیابی میکند و نتایج این ارزیابی ها را به ما نشان میدهد و در نهایت دوباره اینکار را تکرار می کند. چنین مفهومی پیش از این در بسیاری از زبانها مانند Python , Groovy و Scala و دیگر زبانها وجود داشته و این امکان را به توسعهدهنده میدهد تا بدون باز کردن یک IDE و ساختن پروژه و سختیهای اینچنینی مستقیماً بتواند کد های خود را در یک محیط تعاملی در درون کنسول بنویسد و با سرعت بیشتری بتواند این کد ها را اجرا و تست کند.
برای بهره مندی از این قابلیت جدید شما نیاز به این دارید که JDK 9 را دانلود کنید برای اینکار میتوانید به سایت۱ OPEN JDK مراجعه کنید و همانطوری که جاوا را قبلاً نصب نمودهاید، جاوا ۹ را هم به همان شکل نصب کنید.
Hello World in Jshell
برای اینکه بتوانیم وارد jShell بشویم باید کامند jshell را در کنسول اجرا کنیم. حال میتوانیم بدون هیچ دردسری به دنیا سلام کنیم!
jshell> System.out.println("Hello JShell World") Hello JShell World
به همین سادگی به دنیا سلام کردیم!
عبارات کنترلی
برای شروع احتمالاً فیبوناچی نوشتن شروع کلیشه ای خوبی باشد! برای اینکار باید یک آرایه تعریف کنیم:
jshell> int[] a = new int[10] a ==> [I@7d9d1a19
حال که آرایه را ساختهایم کافیست دو مقدار اولیه آن را مقدار دهی کنیم و به همان روش Dynamic Programming شروع به پر کردن خانههای بعدی این آرایه کنیم تا سری فیبوناچی را تشکیل دهیم.
jshell> a[0] = 0 $4 ==> 0 jshell> a[1] = 1 $5 ==> 1 jshell> for(int i=2;i<a.length;i++){ ...> a[i] = a[i-2] + a[i-1]; ...> } jshell> System.out.println(Arrays.toString(a)) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
در همینجا دو نکته وجود دارد که دانستنش خالی از لطف نیست:
- میتوانید با فشردن کلید TAB دستوری که در حال وارد کردن آن هستید را به صورت خودکار کامل کنید یا لیستی از دستورات ممکن را دریافت کنید.
- در دستورات یک خطی نیازی به وارد کردن سمیکالن (;) وجود ندارد اما اگر دستورات چند خطی باشد لازم است که سمیکالن گذاشته شود.
متد ها
ساختن متد های در Jshell کار چندان سختی نیست و همانگونه که قبلاً متد ها را تعریف میکنیم در اینجا نیز به همان شکل اینکار را انجام می دهیم:
jshell> int[] produceFibonacci(int size){ ...> int[] fibonacci = new int[size]; ...> fibonacci[0] = 0; ...> fibonacci[1] = 1; ...> for(int i=2;i<fibonacci.length;i++){ ...> fibonacci[i] = fibonacci[i-1] + fibonacci[i-2]; ...> } ...> return fibonacci; ...> } | created method produceFibonacci(int)
حال متد ما به درستی تعریف شده و میتوانیم از آن استفاده کنیم. با استفاده از دستور methods/ میتوانیم لیستی از متد های تعریف شده را مشاهده کنیم:
jshell> /methods | printf (String,Object...)void | produceFibonacci (int)int[]
پس متد ما به درستی تعریف شده و میتوانیم آن را به سادگی فراخوانی کنیم:
jshell> produceFibonacci(10) $2 ==> [I@4883b407 jshell> System.out.println(Arrays.toString($2)) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
متد ما به درستی اجرا شد. همانطوری که میبینید، در دستورات فوق خروجی متد در جایی ریخته نشده در اینصورت خود Jshell آن را در 2$ ذخیره کرده و ما هم میتوانیم به آن دسترسی داشته باشیم. همینطور با استفاده از دستور vars/ میتوانیم لیستی از متغیر های تعریف شده را ببینیم:
jshell> /vars | int[] $2 = [I@4883b407
همینطور برای پاک کردن تمام متد ها و متغیر هایی که تعریف شده میتوانیم از دستور reset/ استفاده کنیم:
jshell> /reset | Resetting state. jshell> /vars
کلاسها
تعریف کلاسها هم کار آسانی است:
jshell> class Person{ ...> private String name; ...> private int age; ...> public Person(String name, int age){ ...> this.name = name; ...> this.age = age; ...> } ...> public String toString(){ ...> return name + " has " + age + " years old"; ...> } ...> } | created class Person
حال میتوانیم از کلاس تعریف شده شی بسازیم:
jshell> new Person("Hossein" , 20) $3 ==> Hossein has 20 years old
امکان استفاده از Abstract Class و اینترفیس و دیگر قابلیتهای شی گرایی در Jshell هرچند به صورت محدود تر وجود دارد. اما باید توجه کرد که اصولاً در چنین محیط هایی امکان نوشتن برنامههای خیلی بزرگ وجود ندارد.
کلاس Student را از کلاس Person ارث بری می کنیم:
jshell> class Student extends Person{ ...> private String fieldOfStudy; ...> public Student(String name, int age, String fieldOfStudy){ ...> super(name,age); ...> this.fieldOfStudy = fieldOfStudy; ...> } ...> public String toString(){ ...> return super.toString() + " Studying " + fieldOfStudy ; ...> } ...> } | created class Student
کلاس Student نیز به درستی تعریف شده و میتوانیم از آن استفاده کنیم. برای دیدن لیستی از کلاسهای تعریف شده میتوانیم از دستور types/ استفاده کنیم.
jshell> /types | class Test | class Person | class Student
ساختن یک شی از Student:
2 jshell> new Student("Hossein" , 20 , "Computer Engineering") $5 ==> Hossein has 20 years old Studying Computer Engineering
Imports و Classpath
برای استفاده از متد ها و کلاسهای تعریف شده در پکیچ های دیگر نیاز دارید که آنها را import کنید.به صورت پیشفرض تعدادی از این پکیج های پراستفاده تعریف شده اند. میتوانید لیست پیکج های import شده را با استفاده از دستور imports/ ببینید:
jshell> /imports | import java.util.* | import java.io.* | import java.math.* | import java.net.* | import java.util.concurrent.* | import java.util.prefs.* | import java.util.regex.*
برای اینکه بتوانید از کتابخانههای دیگر استفاده کنید باید آدرس فایل jar آنها را به classpath اضافه کنید و پکیج های مربوطه آنها را import کنید. ما میخواهیم از کتابخانه Jsoup برای خزش بر صفحات وب و استخراج محتویات آنها استفاده کنیم. میتوانید این کتابخانه را از سایت آن دانلود کنید.
برای اضافه کردن به classpath باید از دستور classpath/ استفاده کنید و در ادامه آن آدرس فایل jar را مشخص کنید.
jshell> /classpath Desktop/jshell/jsoup-1.9.2.jar | Path 'Desktop/jshell/jsoup-1.9.2.jar' added to classpath jshell> import org.jsoup.nodes. Attribute Attributes BooleanAttribute Comment DataNode Document DocumentType Element Entities FormElement Node TextNode XmlDeclaration jshell> import org.jsoup.nodes.
همانطور که میبینید ما فایل jsoup-1.9.2.jar را به classpath اضافه کردیم و سعی کردیم که یک کلاس از پکیج org.jsoup.nodes را import کنیم تا از آن استفاده کنیم.
۲۵۰ فیلم برتر IMDb
تا اینجا تمام چیزی که لازم بود از Jshell بدانیم را یاد گرفتیم. حال میخواهیم کمی تفریح کنیم و لیست 250 فیلم برتر ۲سایت IMDb را استخراج کنیم و در فایلی ذخیره کنیم تا در ایام فراغتمان این فیلمها را ببینیم.
Jsoup یکی از معروف ترین و قویترین کتابخانهها برای انجام اینکار است. در این مقاله قرار نیست که این کتابخانه را کامل بررسی کنیم و حتی اجزا آن را معرفی کنیم و کار با آن را یاد بدهیم. برای یادگرفتن آن میتوانید به مثالها و داکیومنت های موجود در این سایت مراجعه کنید.۳
کار چندان سختی در پیش نداریم، اول از همه نیاز به ساختن یک Document داریم اما قبل از آن باید آن را import کنیم:
jshell> import org.jsoup.* jshell> import org.jsoup.nodes.Document jshell> Document imdbTop = Jsoup.connect("http://www.imdb.com/chart/top").get(); imdbTop ==> <!doctype html> <html xmlns:og="http://ogp.me/ns#" xmlns:fb="http://www.face …
در اینجا ما از لینک مربوط به ۲۵۰ فیلم برتر یک Document ساختیم. همانطور که مشاهده میکنید اطلاعات موجود در آن صفحه به صورت خودکار توسط کتابخانه دانلود می شود.
الان همه چیز برای پارس کردن محتویات این صفحه و استخراج اطلاعات دلخواه ما آماده است. کافی است که ما بر روی آلمان (Element) های این داکیومنت گذر کنیم و از Selector ها و قابلیتهای کتابخانه برای استخراج اطلاعاتمان استفاده کنیم:
jshell> for(Element row : imdbTop.select("table.chart.full-width tr")){ ...> String title = row.select(".titleColumn a").text(); ...> String rating = row.select(".imdbRating").text(); ...> System.out.println(title + "\t" + rating); ...> } The Shawshank Redemption 9.2 The Godfather 9.2 The Godfather: Part II 9.0 The Dark Knight 8.9 12 Angry Men 8.9 Schindler's List 8.9 Pulp Fiction 8.9 The Lord of the Rings: The Return of the King 8.9 The Good, the Bad and the Ugly 8.9 Fight Club 8.8 …
همانطور که دیدید اطلاعات را به سادگی و فقط با یک حلقه ساده استخراج کردیم و کار ما تقریباً تمام شده و تنها کافیست این اطلاعات استخراج شده را در فایلی ذخیره کنیم.
میتوانیم این اطلاعات را از کنسول کپی کنیم و در یک فایل ذخیره کنیم اما طبیعتاً خیلی بهتر است که مستقیماً این کار را انجام دهیم.
برای اینکار نیاز داریم که این رشتهها را در جایی ذخیره کنیم:
jshell> List<String> topMovies = new ArrayList<>(250); topMovieList ==> [] jshell> for(Element row : imdbTop.select("table.chart.full-width tr")){ ...> String title = row.select(".titleColumn a").text(); ...> String rating = row.select(".imdbRating").text(); ...> topMovies.add(title + "\t" + rating); ...> }
حال با استفاده از امکانات موجود در Java NIO میتوانیم با یک خط به سادگی محتویات این لیست را ذخیره کنیم.
jshell> import java.nio.charset.StandardCharsets jshell> import java.nio.file.Files; jshell> import java.nio.file.Paths; Files.write(Paths.get("Desktop/Top250.txt"),topMovies,StandardCharsets.UTF_8)
و تمام! از تماشای این فیلمها لذت ببرید!
مراجع:
1- http://openjdk.java.net/projects/jdk9/
2- http://www.imdb.com/chart/top
3- https://jsoup.org/cookbook/
آقا محمد حسین
خیلی مقاله ی عالی ای بود
واقعا لذت بردم.
قبلا یه بار تو یکی از فایل های ویدئویی جادی کاری شبیه اینو با یه زبون دیگه دیده بودم. خیلی خوبه که حالا تو جاوا هم می تونیم این کارو انجام بدیم.
من فکر می کردم این حالت کار کردن توی فضای shell مانند، بدون نیاز به این که یک کلاس و یک متد main داشته باشیم و build کنیم ویژگی انحصاری زبون های اسکریپتی مثل پایتون باشه.
در ضمن من جایی که باید ستاره بدیم و پیدا نمی کنم!! چرا؟؟
نکنه فقط به پست های خانوم مهرعلیان می شه ستاره داد؟ (D-:)