Scala กับ Java: เหตุใดฉันจึงควรเรียนรู้ Scala

เผยแพร่แล้ว: 2022-03-11

มีความจริงบางอย่างที่ยอมรับในคำกล่าวที่ว่า "Scala ยาก" แต่เส้นโค้งการเรียนรู้นั้นคุ้มค่ากับการลงทุน คุณลักษณะบางอย่างที่ซับซ้อนมากขึ้นของภาษา (Tuples, Functions, Macros เป็นต้น) ทำให้นักพัฒนาสามารถเขียนโค้ดได้ดีขึ้นและเพิ่มประสิทธิภาพโดยการเขียนโปรแกรมใน Scala ได้ง่ายขึ้น ตรงไปตรงมา เราเป็นโปรแกรมเมอร์ และถ้าเราไม่ฉลาดพอที่จะเรียนรู้ภาษาที่มีความซับซ้อน แสดงว่าเราอยู่ในธุรกิจที่ไม่ถูกต้อง

Scala เป็นภาษา JVM ที่ปลอดภัยสำหรับการพิมพ์ ซึ่งรวมเอาการเขียนโปรแกรมเชิงวัตถุและเชิงฟังก์ชันเข้าไว้ด้วยกัน เป็นภาษาที่กระชับ มีเหตุผล และทรงพลังเป็นพิเศษ บางคนอาจแปลกใจที่รู้ว่า Scala ไม่ได้ใหม่อย่างที่พวกเขาคิด โดยเปิดตัวครั้งแรกในปี 2546 อย่างไรก็ตาม ในช่วงไม่กี่ปีที่ผ่านมา Scala ได้เริ่มมีผู้ติดตามจำนวนมากขึ้นเรื่อยๆ ซึ่งทำให้เกิดคำถามว่า “ทำไมต้องสกาล่า”

บทความนี้กล่าวถึงข้อดีของ Scala โดยเฉพาะอย่างยิ่งเมื่อเทียบกับ Java (เนื่องจาก Scala เขียนขึ้นเพื่อทำงานใน JVM) สกาล่าไม่ใช่ความพยายามเพียงอย่างเดียวในการสร้าง “จาวาที่ดีกว่า” ทางเลือกอื่นเช่น Kotlin และ Ceylon ได้ไปตามเส้นทางนั้นแล้ว แต่พวกเขาได้ตัดสินใจขั้นพื้นฐานที่จะคงไว้ซึ่งรูปแบบที่ใกล้เคียงกันมากในไวยากรณ์ของภาษา Java เพื่อลดเส้นโค้งการเรียนรู้ นี่อาจดูเหมือนเป็นความคิดที่ดี แต่สุดท้ายมันก็ค่อนข้างจะเอาชนะใจตัวเองได้เพราะบังคับให้คุณต้องอยู่ในกระบวนทัศน์ของ Java ที่เหมือนกันหลายๆ แบบซึ่งเป็นสาเหตุให้ต้องการสร้าง "Java ที่ดีกว่า" ตั้งแต่แรก

ในทางตรงกันข้าม Scala ถูกสร้างขึ้นโดยเฉพาะโดยมีเป้าหมายในการเป็น ภาษา ที่ดีขึ้น กำจัดลักษณะเหล่านั้นของ Java ซึ่งถือว่าจำกัด น่าเบื่อเกินไป หรือน่าหงุดหงิดสำหรับนักพัฒนา ด้วยเหตุนี้ จึงมีความแตกต่างของโค้ดและการเปลี่ยนกระบวนทัศน์ที่สามารถทำให้การเรียนรู้การเขียนโปรแกรม Scala ในช่วงต้นนั้นยากขึ้นเล็กน้อย แต่ผลลัพธ์ที่ได้คือภาษาที่สะอาดกว่าและมีการจัดระเบียบที่ดี ซึ่งท้ายที่สุดแล้วง่ายต่อการใช้งานและเพิ่มประสิทธิภาพการทำงาน

เมื่อเปรียบเทียบ Scala กับ Java เคียงข้างกัน ประสิทธิภาพของ Scala นั้นชัดเจน

Scala vs. Java: อันไหนซับซ้อนกว่ากันจริงๆ?

แม้ว่าความเรียบง่ายของภาษา Java นั้นเป็นส่วนหนึ่งของความสำเร็จ แต่น่าแปลกที่ภาษา Java นั้นมีส่วนทำให้เกิดความซับซ้อนด้วยเช่นกัน แน่นอน คุณสามารถเขียนเกือบทุกอย่างใน Java แต่บรรทัดของโค้ดที่จำเป็นในการเขียนนั้นอาจเป็นเรื่องยุ่งยาก ในทางกลับกัน การเขียนโปรแกรมใน Scala มีโครงสร้างที่ซับซ้อนกว่าเล็กน้อย แต่ถ้าคุณสามารถเขียนโค้ดบรรทัด เดียว ที่ซับซ้อนกว่าเล็กน้อยซึ่งแทนที่ Java ที่ "ง่ายกว่า" 20 บรรทัด อันไหนที่ซับซ้อนกว่าจริงๆ

ความจริงก็คือ Java มักจะใช้คำฟุ่มเฟือยเกินไป ใน Scala คอมไพเลอร์นั้นฉลาดอย่างเหลือเชื่อ ดังนั้นสิ่งนี้จึงทำให้นักพัฒนาไม่ต้องระบุสิ่งที่คอมไพเลอร์สามารถอนุมานได้อย่างชัดเจน เปรียบเทียบ ตัวอย่างเช่น "สวัสดีชาวโลก!" ง่ายๆ โปรแกรมใน Java กับ Scala:

สวัสดีชาวโลกใน Java:

 public class HelloJava { public static void main(String[] args) { System.out.println("Hello World!"); } }

สวัสดีชาวโลกในสกาล่า:

 object HelloScala { def main(args: Array[String]): Unit = { println("Hello World!") } }

แม้ว่าทั้งสองภาษาจะไม่มีความแตกต่างกันมากนัก แต่ Scala ก็ใช้รายละเอียดน้อยกว่าแม้ในตัวอย่างง่ายๆ นี้

สำหรับตัวอย่างที่ใช้งานได้จริง มาดูการสร้างรายการสตริงอย่างง่าย:

ชวา:

 List<String> list = new ArrayList<String>(); list.add("1"); list.add("2"); list.add("3");

สกาล่า:

 val list = List("1", "2", "3")

แน่นอนว่ามีเทคนิคบางอย่างใน Java ในการย่อโค้ดให้สั้นลงเล็กน้อย แต่ไม่ใช่ในการใช้งานมาตรฐาน

พิจารณากรณีที่เรามีรายการสตริงที่เป็นตัวเลข แต่เราต้องการแปลงรายการนั้นเป็นรายการของจำนวนเต็ม:

ชวา:

 List<Integer> ints = new ArrayList<Integer>(); for (String s : list) { ints.add(Integer.parseInt(s)); }

สกาล่า:

 val ints = list.map(s => s.toInt)

ด้วยคุณสมบัติเชิงฟังก์ชันของ Scala การแปลงนี้จึงกลายเป็นเรื่องง่ายมาก

ตัวอย่างคลาส: Java กับ Scala

มาทำขั้นตอนต่อไปและเปรียบเทียบการสร้างวัตถุ Java แบบเก่า / ธรรมดาของ Java (POJO) ใน Java และ Scala

อันดับแรก เวอร์ชัน Java:

 public class User { private String name; private List<Order> orders; public User() { orders = new ArrayList<Order>(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Order> getOrders() { return orders; } public void setOrders(List<Order> orders) { this.orders = orders; } } public class Order { private int id; private List<Product> products; public Order() { products = new ArrayList<Product>(); } public int getId() { return id; } public void setId(int id) { this.id = id; } public List<Product> getProducts() { return products; } public void setProducts(List<Product> products) { this.products = products; } } public class Product { private int id; private String category; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } }

วุ้ย. รหัสล็อตต้า.

ตอนนี้รุ่น Scala:

 class User { var name: String = _ var orders: List[Order] = Nil } class Order { var id: Int = _ var products: List[Product] = Nil } class Product { var id: Int = _ var category: String = _ }

เราว่าภาษาไหนซับซ้อนกว่ากัน!

เรามีความเป็นธรรมหรือไม่?

หากคุณมาไกลถึงขนาดนี้ และเป็นโปรแกรมเมอร์ Java คุณอาจกำลังคิดว่าฉันกำลังทำการเปรียบเทียบโค้ดอย่างไม่เป็นธรรม ท้ายที่สุด ไม่มีอะไรหยุดฉันจากการสร้างตัวแปรสาธารณะใน Java แล้วกำจัดตัวรับและตัวตั้งค่า

อย่างไรก็ตาม หากคุณนึกย้อนกลับไปถึงเหตุผลที่อยู่เบื้องหลัง getters และ setters ใน Java นั่นเป็นเหตุผลเฉพาะสำหรับการพิสูจน์อักษรในอนาคต นั่นคือ ถ้าภายหลังคุณจำเป็นต้องเพิ่มตรรกะบางอย่างให้กับการรับหรือการตั้งค่าของตัวแปร คุณจะต้องเขียนตัวแปรสาธารณะเหล่านั้นใหม่เพื่อใช้เมธอดแทน (ซึ่งเป็นสาเหตุที่แนะนำให้ใช้ getters และ setters เพื่อเริ่มต้นใน Java ). อย่างไรก็ตาม ในการเขียนโปรแกรม Scala นี่ไม่ใช่กรณี เนื่องจากการออกแบบภาษา สิ่งที่เป็นนามธรรมยังคงไม่เปลี่ยนแปลงโดยไม่จำเป็นต้องใช้ตัวรับและตัวตั้งค่า ตัวอย่างเช่น ลองพิจารณาคลาส User ที่แก้ไขใน Scala ที่ส่ง NullPointerException หากคุณพยายามตั้งชื่อเป็น null:

 class User { private var _name: String = _ var orders: List[Order] = Nil def name = _name def name_=(name: String) = { if (name == null) { throw new NullPointerException("User.name cannot be null!") } _name = name }

และคุณยังสามารถตั้งชื่อได้ดังนี้:

 user.name = "John Doe"

โปรดทราบว่าการดำเนินการนี้ช่วยขจัดความจำเป็นในการกำหนดค่าตัวเข้าถึงวิธีการล่วงหน้าโดยสิ้นเชิง

ยิ่งไปกว่านั้น เนื่องจาก Scala ชอบความไม่เปลี่ยนรูป ฉันสามารถเขียนสิ่งนี้ใน Scala ได้กระชับยิ่งขึ้นด้วย case class:

 case class User(name: String, orders: List[Order]) case class Order(id: Int, products: List[Product]) case class Product(id: Int, category: String)

ค่อนข้างบ้าที่ฉันต้องเขียนโค้ดน้อยลง

ดูตัวอย่างเพิ่มเติมอีกเล็กน้อย

ตอนนี้ให้พิจารณาสถานการณ์สมมติที่มีคลาสข้างต้นซึ่งฉันต้องการเพิ่มวิธีการเล็ก ๆ น้อย ๆ ที่ดีในคลาส User ที่ส่งคืนรายการ Products ทั้งหมดที่ User สั่งซื้อ:

ในโลกที่ละเอียดของ Java:

 public List<Product> getProducts() { List<Product> products = new ArrayList<Product>(); for (Order order : orders) { products.addAll(order.getProducts()); } return products; }

โชคดีที่ java.util.List มีเมธอด addAll หรือ getProducts() จะใช้ Java นานกว่านั้น

ใน Scala สิ่งที่เราต้องมีคือ:

 def products = orders.flatMap(o => o.products)

คุณสามารถดูได้ว่าการใช้งานภาษา Scala นั้นเล็กลงเพียงใด ใช่ อาจดูซับซ้อนกว่าสำหรับมือใหม่ Scala แต่เมื่อคุณเข้าใจแนวคิดเบื้องหลังอย่างถ่องแท้แล้ว โค้ด Scala จะดูเรียบง่ายกว่าโค้ด Java มาก

มาซับซ้อนกว่านี้อีกหน่อยที่นี่ จะเป็นอย่างไรถ้าเราต้องการรับ Products เฉพาะใน Category ที่กำหนด ?

ในกรณีนี้ เราไม่สามารถใช้ประโยชน์จากเมธอด addAll ใน java.util.List ได้ ดังนั้นสิ่งต่าง ๆ จึงดูน่าเกลียด ใน Java :

 public List<Product> getProductsByCategory(String category) { List<Product> products = new ArrayList<Product>(); for (Order order : orders) { for (Product product : order.getProducts()) { if (category.equals(product.getCategory())) { products.add(product); } } } return products; }

อย่างไรก็ตาม ใน Scala โค้ดยังคงตรงไปตรงมา เราเพียงแค่ใช้ flatMap เพื่อรวมรายการผลิตภัณฑ์จาก Order แต่ละรายการที่รวมเป็นรายการเดียว จากนั้นเราจะกรองเพื่อรวมเฉพาะรายการที่ตรงกับหมวดหมู่:

 def productsByCategory(category: String) = orders.flatMap(o => o.products).filter(p => p.category == category)

ไดนามิกกับสแตติก

แน่นอนว่าไม่มีการขาดแคลนภาษาใหม่ ๆ ในช่วงไม่กี่ปีที่ผ่านมา แต่ในขณะที่ภาษาอื่น ๆ เกือบทั้งหมดที่เพิ่งเกิดขึ้นนั้นเป็นแบบไดนามิก Scala นั้นถูกพิมพ์แบบคงที่

ในฐานะนักพัฒนามืออาชีพ แม้ว่าฉันจะรู้จักและใช้ภาษาไดนามิกหลายภาษา แต่ความเห็นของฉันว่าการตรวจสอบเวลาคอมไพล์มีความสำคัญอย่างเหลือเชื่อในการเขียนโค้ดที่เป็นของแข็ง ในภาษาไดนามิก คุณไม่สามารถแน่ใจได้เลยว่าโค้ดของคุณไม่มีข้อบกพร่องและมีประสิทธิภาพเพียงพอ จนกว่าคุณจะเรียกใช้จริงในสถานการณ์ที่หลากหลาย นี้อาจนำไปสู่ข้อบกพร่องร้ายแรงที่อาจเกิดขึ้นในโค้ดที่ไม่เคยได้รับรู้จนกว่าโค้ดจะถูกใช้งานจริง

สรุป

หวังว่าบทความนี้จะรวบรวม Java กับ Scala มากพอที่จะทำให้คุณเข้าใจถึงพลังและความสามารถของ Scala เบื้องต้น และกระตุ้นความอยากอาหารของคุณในการเรียนรู้ภาษา ไม่เพียงแต่เป็นภาษาที่ยอดเยี่ยมที่ทำให้การเขียนโปรแกรมน่าเบื่อน้อยลงและสนุกสนานมากขึ้น แต่ยังถูกใช้โดยบริษัทที่ใหญ่ที่สุดในโลกบางแห่ง (LinkedIn, Twitter, FourSquare, The Guardian เป็นต้น)

ความนิยมและการใช้งาน Scala เพิ่มขึ้นอย่างรวดเร็ว โดยเห็นได้จากจำนวนตำแหน่งที่เปิดเพิ่มขึ้นสำหรับนักพัฒนา Scala ถ้าคุณยังไม่ได้ทำ ตอนนี้เป็นเวลาที่ดีที่จะเริ่มขี่เวฟและหยุดถามว่า "ทำไมต้องเรียนสกาล่า"

ที่เกี่ยวข้อง: ลดรหัส Boilerplate ด้วย Scala Macros และ Quasiquotes