<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Yuu on Medium]]></title>
        <description><![CDATA[Stories by Yuu on Medium]]></description>
        <link>https://medium.com/@withyuu?source=rss-4ad64945190a------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*HNvikpoxJ6O_rZcJXv1wUA.png</url>
            <title>Stories by Yuu on Medium</title>
            <link>https://medium.com/@withyuu?source=rss-4ad64945190a------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 13 Nov 2026 13:18:00 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@withyuu/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Feature ใหม่ใน Java 17: #6 Text Blocks]]></title>
            <link>https://medium.com/ascend-developers/feature-%E0%B9%83%E0%B8%AB%E0%B8%A1%E0%B9%88%E0%B9%83%E0%B8%99-java-17-6-text-blocks-66525b6e8225?source=rss-4ad64945190a------2</link>
            <guid isPermaLink="false">https://medium.com/p/66525b6e8225</guid>
            <category><![CDATA[java17]]></category>
            <category><![CDATA[java]]></category>
            <dc:creator><![CDATA[Yuu]]></dc:creator>
            <pubDate>Mon, 20 Dec 2021 02:03:22 GMT</pubDate>
            <atom:updated>2021-12-20T02:03:22.710Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*gvyQAnR7Hkhc0Jsq.jpg" /></figure><p>Text Block เป็น literal แบบใหม่ที่ช่วยให้การเขียนข้อความที่มีหลายบรรทัดและต้องมีการจัด format ง่ายขึ้น</p><p>หลายๆ ครั้งเราจำเป็นต้องเขียน code snippet เช่น XML, JSON, HTML เป็น String literal ใน Java ซึ่งเวลาเขียนทีก็ค่อนข้างลำบาก เช่น</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1279b61735642d8e4ad36a3db440728b/href">https://medium.com/media/1279b61735642d8e4ad36a3db440728b/href</a></iframe><p>เราต้องมาคอยใส่ \n และ + ทุกบรรทัด แต่ใน Java 17¹ เราสามารถเขียนโค้ดด้านบนด้วย Text Block ได้ดังนี้</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/073258608f44dbcbf9583d68c8854885/href">https://medium.com/media/073258608f44dbcbf9583d68c8854885/href</a></iframe><p>ชีวิตดีขึ้นมากเลยทีเดียว</p><p>การใช้งานทั่วไปของ Text Block อันที่จริงก็มีเท่านี้แหละ แต่ถ้าใครต้องการความเป๊ะและรายละเอียดยิบย่อยของ Text Block เราไปดูกันต่อเลย</p><p><em>Note: Text Block ของ Java ทำงานต่างกับ Raw String ของ Kotlin นะครับ เวลาสลับไปมา ระหว่าง 2 ภาษานี้ต้องระวังนิดนึง</em></p><h3>New Line ในบรรทัดสุดท้าย</h3><p>ถ้าสังเกตตัวอย่างด้านบนดีๆ จะเห็นว่า Text Block ด้านบนเมื่อแปลงเป็น String literal แล้ว ที่บรรทัดสุดท้ายจะมี \n ติดมาตรง &lt;/html&gt;\n</p><p>นั่นเป็นเพราะ closing delimiter &quot;&quot;&quot; ของเราอยู่คนละบรรทัดกับ &lt;/html&gt; ทำให้มี new line character ติดมานั่นเอง ถ้าไม่อยากให้มีติดมาเราก็แค่ขยับ &quot;&quot;&quot; ไปอยู่ท้ายบรรทัดสุดท้าย หรืออีกวิธีคือใช้ line terminator \ (จะพูดถึงอีกทีในหัวข้อ escape sequences)</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c74646480d709fa555cb6febf23b99f8/href">https://medium.com/media/c74646480d709fa555cb6febf23b99f8/href</a></iframe><h3>Leading White Space</h3><p>white space ด้านหน้าจะโดนเอาออกเท่ากันทุกบรรทัด โดยจะเอาออกจนเท่ากับตำแหน่งของตัวอักษรตัวแรกของบรรทัดใดบรรทัดหนึ่ง</p><p>งงมั้ยครับ?</p><p>งงเนอะ ดูตัวอย่างดีกว่า ตัวอย่างด้านล่างใช้ . แทน white space ที่ถูกเอาออกไป</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b3b26cf06a1cf58f46233b9039f4b519/href">https://medium.com/media/b3b26cf06a1cf58f46233b9039f4b519/href</a></iframe><p>ถ้าเราเลื่อน closing delimiter มาทางซ้ายก็จะได้ leading white space นำหน้าแต่ละบรรทัด</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/098814de956273b15b3f98a862de2933/href">https://medium.com/media/098814de956273b15b3f98a862de2933/href</a></iframe><h3>Trailing White Space</h3><p>white space ด้านหลังจะโดนเอาออกทั้งหมดทุกบรรทัดเลย เพราะโดยทั่วไป white space ด้านหลังมักจะมาโดยไม่ตั้งใจอยู่แล้ว อย่างไรก็ตาม ถ้าอยากให้มี trailing white space ก็สามารถทำได้ด้วยการใช้ escape sequence \s (จะพูดถึงอีกทีในหัวข้อ escape sequences)</p><h3>Escape Sequence</h3><p>ใน Java ตัว <strong>Text Block ไม่ใช่ Raw String ฉะนั้น escape sequence ต่างๆ ยังทำงานเหมือนเดิม</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ba48408f1b76215bc616ce3798c50a87/href">https://medium.com/media/ba48408f1b76215bc616ce3798c50a87/href</a></iframe><p>ตามตัวอย่างด้านบนเรายังใช้ \t \n และ escape sequence ทุกตัวได้เหมือนเดิม สำหรับ double quote เราไม่จำเป็นต้อง escape ก็ได้ ยกเว้นถ้าเราอยากพิมพ์ double quote ติดกัน 3 ตัว เราต้อง escape เป็น\&quot; ตัวนึงเพื่อไม่ให้กลายเป็น &quot;&quot;&quot; delimiter</p><p>การที่ Text Block escape sequence ได้นี่เองทำให้เราต้องระวังมากๆ เวลา copy text จากที่อื่นมาวางต้องคอยดูว่าใน text นั้นมี escape sequence อยู่หรือเปล่า เช่นใน JSON อาจจะมี \n หรือ sequence อื่นๆ อยู่ใน value ก็ได้ ซึ่งจะทำให้ alignment ของ text ที่เราตั้งใจไว้ผิดเพี้ยนไป :(</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c4c99c214e4268b3374eafd81c3b5f87/href">https://medium.com/media/c4c99c214e4268b3374eafd81c3b5f87/href</a></iframe><h3>Escape Sequence ใหม่</h3><p>Java ออก escape sequence ใหม่มาใช้คู่กับ Text Block ได้แก่</p><h4>\ (Line Terminator)</h4><p>บางครั้งเราต้องการพิมพ์ข้อความบรรทัดเดียวยาวๆ จะทำเป็น String literal ก็ไม่สะดวก จะทำเป็น Text Block ก็จะโดนบังคับขึ้นบรรทัดใหม่ ในกรณีนี้เราสามารถใช้ \ เป็นตัวอักษรสุดท้ายในบรรทัดเพื่อไม่ให้มีการขึ้นบรรทัดใหม่ได้ ตามตัวอย่างด้านล่าง</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5217b6571b155bc49687b052508b7791/href">https://medium.com/media/5217b6571b155bc49687b052508b7791/href</a></iframe><p><em>Note: line-terminator </em><em>\ใช้ได้กับเฉพาะ Text Block เท่านั้น ใช้กับ String literal ไม่ได้</em></p><h4>\s (Single Space)</h4><p>เนื่องจาก Text Block จะ trim trailing white space ออกเสมอ ถ้าเราอยากให้ text ของเรามี trailing white space สามารถทำได้โดยการใช้ \s เป็นรั้วกั้น</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f23447a13de73ccd8c1f7e9ba7c67f8c/href">https://medium.com/media/f23447a13de73ccd8c1f7e9ba7c67f8c/href</a></iframe><p><em>Note: </em><em>\s ใช้ได้กับทั้ง Text Block และ String literal</em></p><h3>การ Concat Text Block</h3><p>Text Block เป็น String ธรรมดา สามารถ concat กับ String อื่นๆ ด้วยเครื่องหมาย + ได้ตามปกติ แต่เนื่องจากการเขียน Text Block ต้องเขียนหลายบรรทัด เวลาเขียนจะดูไม่ค่อยสวยงามเท่าไหร่</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/aa373e70a2ba5149db3c92fbd0a7fa99/href">https://medium.com/media/aa373e70a2ba5149db3c92fbd0a7fa99/href</a></iframe><p>วิธีที่สวยกว่าคือใช้ class method String::format แทน</p><p>แต่วิธีที่สวยที่สุดคือใช้ instance method ใหม่ String::formatted</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b8f98b359ab565f0101adaa03b32a376/href">https://medium.com/media/b8f98b359ab565f0101adaa03b32a376/href</a></iframe><h3>สรุป</h3><ul><li>Text Block ช่วยในการเขียน multiline string เช่นพวก code snippet ต่างๆ</li><li>Text Block ไม่ใช่ raw string! ยังมีการใช้ escape sequence เหมือนเดิม</li><li>การจัด format ของ Text Block มีเรื่องยิบย่อยที่ต้องรู้เยอะ แนะนำให้ลองเล่นเองหรือตามไปดู <a href="https://github.com/withyuu/java-11-to-17">source code</a> ของบทความเพิ่มเติมครับ</li></ul><p>[1]: Text Block feature release ใน Java 15</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=66525b6e8225" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/feature-%E0%B9%83%E0%B8%AB%E0%B8%A1%E0%B9%88%E0%B9%83%E0%B8%99-java-17-6-text-blocks-66525b6e8225">Feature ใหม่ใน Java 17: #6 Text Blocks</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Feature ใหม่ใน Java 17: #5 Switch Expressions]]></title>
            <link>https://medium.com/ascend-developers/feature-%E0%B9%83%E0%B8%AB%E0%B8%A1%E0%B9%88%E0%B9%83%E0%B8%99-java-17-5-switch-expressions-d9acb9f8c908?source=rss-4ad64945190a------2</link>
            <guid isPermaLink="false">https://medium.com/p/d9acb9f8c908</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[java17]]></category>
            <dc:creator><![CDATA[Yuu]]></dc:creator>
            <pubDate>Thu, 16 Dec 2021 10:31:11 GMT</pubDate>
            <atom:updated>2021-12-16T10:31:11.017Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QJYTDq5FTMqgquLsbuwbuw.jpeg" /></figure><p>เชื่อว่าทุกคนคงเคยใช้ switch statement กันมาอยู่แล้ว เพราะฉะนั้นก็คงคุ้นเคยกับเหตุการณ์นี้กันดี</p><pre>Color color = Color.ULTRAMARINE;<br>String result;<br>switch (color) {<br>  case CRIMSON, VERMILION:<br>    result = &quot;Shades of Red&quot;;<br>  case HONEYDEW, EMERALD:<br>    result = &quot;Shades of Green&quot;;<br>  case ULTRAMARINE, NAVY_BLUE:<br>    result = &quot;Shades of Blue&quot;;<br>  default:<br>    result = &quot;Undefined Shade&quot;;<br>}</pre><p>พอเขียนเสร็จ ลองรันดู ก็จะพบว่า result เป็น &quot;Undefined Shade&quot; !?</p><p>เป็นไปได้ไง ทำไมไม่เป็น &quot;Shades of Blue&quot; ล่ะ ???</p><p>สักพักก็จะนึกขึ้นได้ เฮ้ย! switch case มัน fall through! ลืมใส่ break;! (ใครสนใจเรื่อง fall through เชิง <strong>ลึก</strong> อ่านเพิ่มเติมได้ที่ <a href="https://medium.com/swlh/understanding-switch-case-fall-through-in-java-70b448427b0a">Understanding switch-case fall-through in Java</a>)</p><p>ทางทีมงาน Java เองก็ไม่ได้นิ่งดูดาย ใน Java 17¹ เลยมีการเพิ่ม feature ให้กับ switch หลักๆ 2 เรื่อง</p><h3>1. Arrow Labels</h3><pre>@Test<br>void switchWithArrowLabel() {<br>  Color color = Color.ULTRAMARINE;<br>  String result;<br>  switch (color) {<br>    case CRIMSON, VERMILION -&gt; result = &quot;Shades of Red&quot;;<br>    case HONEYDEW, EMERALD -&gt; result = &quot;Shades of Green&quot;;<br>    case ULTRAMARINE, NAVY_BLUE -&gt; result = &quot;Shades of Blue&quot;;<br>    default -&gt; result = &quot;Undefined Shade&quot;;<br>  }<br>  assertThat(result).isEqualTo(&quot;Shades of Blue&quot;);<br>}</pre><p>จากการเขียน switch แบบเดิม case L : เราสามารถเขียนเป็น case L -&gt; แทนได้ ซึ่งถ้าใช้เป็น Arrow Label แทนก็จะไม่มีการ fall through แล้ว!!</p><h3>2. Switch Expressions</h3><p>เราสามารถใช้ switch เป็น expression ได้แล้ว หรือถ้าให้พูดง่ายๆ ก็คือเราได้ return value จากการเรียกใช้ switch โดยค่าที่ return ออกมาก็คือค่าทางด้านขวาของแต่ละ case นั่นเอง จากตัวอย่างด้านบนเราสามารถเขียนใหม่ได้เป็น</p><pre>@Test<br>void switchExpression() {<br>  Color color = Color.ULTRAMARINE;<br>  String result =<br>      switch (color) {<br>        case CRIMSON, VERMILION -&gt; &quot;Shades of Red&quot;;<br>        case HONEYDEW, EMERALD -&gt; &quot;Shades of Green&quot;;<br>        case ULTRAMARINE, NAVY_BLUE -&gt; &quot;Shades of Blue&quot;;<br>        default -&gt; &quot;Undefined Shade&quot;;<br>      };<br>  assertThat(result).isEqualTo(&quot;Shades of Blue&quot;);<br>}</pre><h3>Keyword yield</h3><p>ในกรณีที่ด้านขวาของ case L -&gt; มีมากกว่า 1 คำสั่ง เราสามารถเขียนเป็น block แล้วคืนค่าจาก block นั้นโดยใช้ keyword yield</p><pre>@Test<br>void switchExpressionWithFullBlockAndYieldValue() {<br>  Color color = Color.EBONY;<br>  String result =<br>      switch (color) {<br>        case CRIMSON, VERMILION -&gt; &quot;Shades of Red&quot;;<br>        case HONEYDEW, EMERALD -&gt; &quot;Shades of Green&quot;;<br>        case ULTRAMARINE, NAVY_BLUE -&gt; &quot;Shades of Blue&quot;;<br>        default -&gt; {<br>          System.out.println(&quot;Unhandled Color: &quot; + color);<br>          <strong>yield</strong> &quot;Undefined Shade&quot;;<br>        }<br>      };<br>  assertThat(result).isEqualTo(&quot;Undefined Shade&quot;);<br>}</pre><p>นอกจากนี้เรายังสามารถใช้ switch expression กับการเขียน switch แบบเก่าคู่กับ yield ได้ด้วย</p><pre>@Test<br>void switchExpressionWithTraditionalSwitchBlock() {<br>  Color color = Color.ULTRAMARINE;<br>  String result =<br>      switch (color) {<br>        case CRIMSON, VERMILION:<br>          <strong>yield</strong> &quot;Shades of Red&quot;;<br>        case HONEYDEW, EMERALD:<br>          <strong>yield</strong> &quot;Shades of Green&quot;;<br>        case ULTRAMARINE, NAVY_BLUE:<br>          <strong>yield</strong> &quot;Shades of Blue&quot;;<br>        default:<br>          System.out.println(&quot;Unhandled Color: &quot; + color);<br>          <strong>yield</strong> &quot;Undefined Shade&quot;;<br>      };<br>  assertThat(result).isEqualTo(&quot;Shades of Blue&quot;);<br>}</pre><h3>ข้อควรระวังในการใช้ Switch Expressions</h3><p>พอเราใช้ switch เป็น expression แล้วเราต้องมั่นใจว่าเราจะได้ return value จาก switch (หรือไม่ก็ throw Exception) ซึ่งอันที่จริงมันก็ตรงไปตรงมา เหมือนเวลาเราเขียน method ที่มีการ return value นั่นแล</p><pre><strong>// COMPILATION ERROR:<br>// the switch expression does not cover all possible input values</strong><br>@Test<br>void switchRulesMustCoverAllPossibleValues() {<br>  Color color = Color.ULTRAMARINE;<br>  String result =<br>      switch (color) {<br>        case CRIMSON, VERMILION -&gt; &quot;Shades of Red&quot;;<br>        case HONEYDEW, EMERALD -&gt; &quot;Shades of Green&quot;;<br>        case ULTRAMARINE, NAVY_BLUE -&gt; &quot;Shades of Blue&quot;;<br>      };<br>  assertThat(result).isEqualTo(&quot;Shades of Blue&quot;);<br>}</pre><p>ตัวอย่างด้านบนนี้จะ <strong>Compile ไม่ผ่าน</strong> เพราะสามารถมีกรณีที่ค่าของ color ไม่ตรงกับค่าที่มีในแต่ละ case เลย ซึ่งจะทำให้ไม่มีอะไร return ออกไปจาก switch expression นี้</p><p>ในทางปฏิบัติเราจึงควรห้อย default case ไว้เสมอเวลาใช้ switch expression ซึ่งเราจะ return default value อะไรไป หรือจะ throw Exception ก็แล้วแต่</p><h3>สรุป</h3><p>ใน Java 17 มี feature ใหม่เกี่ยวกับ switch ให้เราใช้ 2 อย่าง นั่นคือ</p><ol><li>ใช้ Arrow Label แทน เพื่อป้องกันการ fall through</li><li>ใช้ switch เป็น expressions เพื่อเอา result จาก switch ได้ โดยอาจต้องใช้ keyword yield ช่วยในการ return ค่า</li></ol><p>สำหรับใครที่ยังดูตัวอย่างในบทความไม่จุใจ ตามไปดู <a href="https://github.com/withyuu/java-11-to-17">Source Code</a> ต่อได้เลยคร้าบ</p><p>[1]: Switch Expressions release ตั้งแต่ Java version 14</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d9acb9f8c908" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/feature-%E0%B9%83%E0%B8%AB%E0%B8%A1%E0%B9%88%E0%B9%83%E0%B8%99-java-17-5-switch-expressions-d9acb9f8c908">Feature ใหม่ใน Java 17: #5 Switch Expressions</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Feature ใหม่ใน Java 17: #4 Sealed Classes]]></title>
            <link>https://medium.com/ascend-developers/feature-%E0%B9%83%E0%B8%AB%E0%B8%A1%E0%B9%88%E0%B9%83%E0%B8%99-java-17-4-sealed-classes-817d46764884?source=rss-4ad64945190a------2</link>
            <guid isPermaLink="false">https://medium.com/p/817d46764884</guid>
            <category><![CDATA[sealed-classes]]></category>
            <category><![CDATA[java17]]></category>
            <category><![CDATA[java]]></category>
            <dc:creator><![CDATA[Yuu]]></dc:creator>
            <pubDate>Thu, 02 Dec 2021 16:11:10 GMT</pubDate>
            <atom:updated>2021-12-02T16:11:10.834Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/737/1*-t2rErJmY_DnX67lSnBBsA.png" /></figure><p>Sealed Class หรือ Sealed Interface ช่วยให้เราระบุได้ว่า class ไหนที่จะมา extend class หรือ implement interface ของเราได้</p><pre>public sealed abstract class Pet permits Cat, Dog, Bird { ... }</pre><pre>public final class Cat extends Pet { ... }<br>public final class Dog extends Pet { ... }<br>public final class Bird extends Pet { ... }</pre><h3>ที่มาของปัญหา</h3><p>ที่มาของ Sealed Class คือเรา <strong>ต้องการสร้าง superclass ที่ใครๆ ก็สามารถเข้าถึงได้แต่ไม่สามารถ extend ได้</strong> เช่น</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/731/0*p7vikA4i13VgvCoZ.png" /></figure><p>จาก class diagram ด้านบน เราอยากให้ DessertRestaurant สามารถ access Dessert ซึ่งเป็น superclass ของ Cake และ IceCream ได้ แต่ไม่อยากให้ Chocolate มา extend Dessert ได้</p><p>ก่อนหน้า Java 17 ถ้าเราอยากไม่อยากให้ class จาก package อื่นมา extend Dessert เราจะประกาศให้ Dessert เป็น package-private ตามโค้ดด้านล่าง</p><pre>abstract class Dessert { ... }</pre><pre>public final class Cake extends Dessert { ... }<br>public final class IceCream extends Dessert { ... }</pre><p>วิธีนี้กัน class อื่นมา extend ได้จริงแต่ปัญหาคือเราจะไม่สามารถใช้งาน Dessert จาก package อื่นได้ (extend ไม่ได้ และ access ไม่ได้)</p><pre>// Compile Error: Cannot access Dessert from outside package<br>Dessert dessert = new Cake();</pre><p>และถ้าเราจะแก้ให้ Dessert สามารถเข้าถึงได้โดยการเปลี่ยน access เป็น public ก็จะกลายเป็นว่า class อื่นๆ สามารถ extend Dessert ของเราได้อีก (extend ได้ และ access ได้)</p><p>จะเห็นได้ว่าการ extend และ access มาพร้อมๆ กันเสมอ Sealed Class จึงเกิดมาเพื่อแก้ปัญหานี้</p><p><strong><em>Challenge for Java Geek</em></strong><em>: อันที่จริงเราพอมีหนทางแก้ปัญหานี้โดยไม่ใช้ Sealed Class อยู่นะครับ ลองคิดเล่นๆ ดูก่อน เดี๋ยวจะเฉลยด้านล่าง :)</em></p><h3>การใช้งาน Sealed Class</h3><pre>public <strong>sealed</strong> abstract class Pet <strong>permits</strong> Cat, Dog, Bird { ... }<br><br>public <strong>final</strong> class Cat extends Pet { ... }<br><br>public <strong>sealed</strong> class Dog extends Pet {<br>  public static <strong>final</strong> class Hound extends Dog {}<br>  public static <strong>final</strong> class Terrier extends Dog {}<br>}<br><br>public <strong>non-sealed</strong> class Bird extends Pet { ... }</pre><ul><li>ประกาศให้เป็น Sealed Class/Interface ด้วย keyword sealed</li><li>Sealed Class ต้องประกาศ class ที่อนุญาตให้ extend ได้หลัง keyword permits ในกรณีที่ permitted subclass อยู่ใน source file เดียวกันทั้งหมด ไม่ต้องใช้ permits ก็ได้ เช่น class Dog ในตัวอย่างด้านบน</li><li>Sealed Class กับ permitted subclass ต้องอยู่ package เดียวกัน หรือ module เดียวกัน</li><li>Subclass ของ Sealed Class จะต้องประกาศเป็น final, sealedหรือ non-sealed โดย class ที่ประกาศเป็น non-sealed จะสามารถถูก extend โดย class อื่นได้</li><li>เกร็ดเล็กเกร็ดน้อย: non-sealedเป็น keyword แรกใน Java ที่มี dash !!</li><li>ใช้ <a href="https://github.com/withyuu/java-11-to-17/blob/main/article/record.md">Record</a> เป็น subclass ได้ (Record เป็น final class by default)</li></ul><h3>สรุป</h3><p>Sealed Class เหมาะกับการนำไปใช้ทำ library มาก เพราะเข้ามาช่วยจำกัด subclass ให้อยู่ในขอบเขตที่เราต้องการ โดยที่ library user ยังสามารถเรียกใช้ superclass ได้เหมือนเดิม</p><p>สามารถดู source code ในบทความนี้ได้ <a href="https://github.com/withyuu/java-11-to-17">ที่นี่</a></p><h3>เฉลย Challenge for Java Geek</h3><p>ก่อนหน้า Java 17 เราก็พอจะทำให้ class อื่น access superclass โดยที่ไม่สามารถ extend ได้ ด้วยการใช้ non-public constructor</p><pre>public abstract class Dessert {<br>  // package-private constructor<br>  Dessert() {}<br>}</pre><pre>public final class Cake extends Dessert { ... }<br>public final class IceCream extends Dessert { ... }</pre><p>อย่างไรก็ตาม วิธีนี้ใช้กับ interface ไม่ได้อยู่ดีครับ</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=817d46764884" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/feature-%E0%B9%83%E0%B8%AB%E0%B8%A1%E0%B9%88%E0%B9%83%E0%B8%99-java-17-4-sealed-classes-817d46764884">Feature ใหม่ใน Java 17: #4 Sealed Classes</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Feature ใหม่ใน Java 17: #3 Restore Always-Strict Floating-Point Semantics]]></title>
            <link>https://medium.com/ascend-developers/feature-%E0%B9%83%E0%B8%AB%E0%B8%A1%E0%B9%88%E0%B9%83%E0%B8%99-java-17-3-restore-always-strict-floating-point-semantics-8b2e3425c69?source=rss-4ad64945190a------2</link>
            <guid isPermaLink="false">https://medium.com/p/8b2e3425c69</guid>
            <category><![CDATA[java17]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[floating-points]]></category>
            <dc:creator><![CDATA[Yuu]]></dc:creator>
            <pubDate>Mon, 29 Nov 2021 08:14:23 GMT</pubDate>
            <atom:updated>2021-11-29T08:14:23.181Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/640/0*Fgr8Ssa1DXM51zXH.jpg" /></figure><p><em>ก่อนจะเข้าเนื้อหาใดๆ ขอสารภาพก่อนว่าทำงานมาเกินสิบปีผมเพิ่งรู้จัก keyword </em><em>strictfp วันนี้นี่แหละครับ 555</em></p><p>สำหรับตัว feature <strong>Restore Always-Strict Floating-Point Semantics</strong> นี่คิดว่าคงน้อยคนมากที่จะได้ใช้ เอาเป็นว่าบทความนี้เรามาเรียนรู้ประวัติศาสตร์ของ strictfp กันดีกว่า</p><h3>ปัญหาของ Floating Point</h3><p>เป็นที่รู้กันดีว่าคอมพิวเตอร์ซึ่งมีหน่วยความจำจำกัดไม่สามารถแสดงค่าจำนวนจริงทั้งหมดได้ การเก็บทศนิยมในภาษาต่างๆ จึงใช้การประมาณค่าด้วย standard <a href="https://en.wikipedia.org/wiki/IEEE_754">IEEE 754</a></p><p>การประมาณค่านี้ทำให้เราไม่สามารถคำนวณค่าทศนิยมที่ถูกเป๊ะๆ ได้ดัง<a href="https://techtalkbook.com/why-0-1-0-2-does-not-equal-0-3/">ตัวอย่างด้านล่าง</a></p><p><strong>Java</strong></p><pre>$ jshell<br>|  Welcome to JShell -- Version 17<br>|  For an introduction type: /help intro</pre><pre>jshell&gt; 0.1 + 0.2;<br>$1 ==&gt; 0.30000000000000004</pre><p><strong>Node.js</strong></p><pre>$ node<br>Welcome to Node.js v16.13.0.<br>Type &quot;.help&quot; for more information.<br>&gt; 0.1 + 0.2<br>0.30000000000000004</pre><p><strong>Python</strong></p><pre>$ python3<br>Python 3.8.10 (default, Sep 28 2021, 16:10:42) <br>Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.<br>&gt;&gt;&gt; 0.1 + 0.2<br>0.30000000000000004</pre><p>ซึ่งนี่ก็ยัง <strong>ไม่ใช่สิ่งที่ strictfp จะเข้ามาแก้ไข</strong> แต่อย่างใด :P</p><p>แค่จะแสดงให้เห็นว่าการแสดง floating point ของแต่ละภาษาใช้ IEEE 754 เป็น standard</p><h3>ประวัติศาสตร์ของ strictfp</h3><p>การมาของ keyword strictfp นั้นจริงๆ แล้วมีความเกี่ยวเนื่องกับ CPU architecture ในอดีต เพราะฉะนั้นต้องย้อนไปดูประวัติศาสตร์กันนิดนึง</p><h3>ช่วงก่อน Java 1.2 — strictfp ยังไม่ถือกำเนิด</h3><p>Java ก่อน version 1.2 จะบังคับให้การคำนวณ floating point ทั้งหมดเป็นไปตาม standard IEEE 754 วิธีนี้ทำให้การคำนวณ floating point ไม่ว่าจะอยู่บน CPU architecture ไหนก็จะได้ผลการคำนวณเท่ากันเสมอ อย่างไรก็ตาม ณ เวลานั้นการคำนวณ floating point ด้วย standard IEEE 754 บน CPU architecture x86 ยุ่งยากและ performance ไม่ดีเท่าที่ควร</p><h3>Java 1.2 ถึง Java 16 — การมาของ strictfp</h3><p>จากเหตุผลด้านบนที่ว่าการคำนวณ floating point ทำได้ช้า Java จึงออก keyword ใหม่มานั่นก็คือ strictfp นั่นเอง การคำนวณ floating point ที่ระบุว่าเป็น strictfp จะบังคับให้การคำนวณเป็นไปตาม standard IEEE 754 (strict) ในทางตรงกันข้าม ถ้าไม่ระบุว่าเป็น strictfp การคำนวณ floating point ก็จะแล้วแต่ CPU architecture ของเครื่องที่ JVM run อยู่เลยซึ่งก็จะเร็วกว่าการคำนวณแบบ strict</p><p>Code snippet ของ <a href="https://github.com/openjdk/jdk16/blob/4de3a6be9e60b9676f2199cd18eadb54a9d6e3fe/src/java.base/share/classes/java/lang/StrictMath.java#L81">class StrictMath ใน Java 16</a></p><pre>public static strictfp double toRadians(double angdeg) {<br>  // Do not delegate to Math.toRadians(angdeg) because<br>  // this method has the strictfp modifier.<br>  return angdeg * DEGREES_TO_RADIANS;<br>}</pre><p>วิธีการใช้ strictfp สามารถดูได้จาก <a href="https://www.baeldung.com/java-strictfp">ที่นี่</a></p><h3>Java 17 — ลาก่อน strictfp</h3><p>เมื่อเวลาผ่านไป CPU ตั้งแต่ Pentium 4 เป็นต้นมามี SSE2 extension ซึ่งช่วยให้การคำนวณ floating point แบบ strict เร็วขึ้นมาก ซึ่งทั้ง Intel และ AMD ก็ support SSE2 มาระยะเวลาหนึ่งแล้ว เป็นเหตุผลให้ Java กลับมาบังคับใช้ strict mode กับทุกๆ การคำนวณ floating point ไปเลยเหมือนสมัยก่อน Java 1.2 และเราก็ไม่จำเป็นต้องใช้ keyword strictfp อีกต่อไป</p><p>Code snippet ของ <a href="https://github.com/openjdk/jdk17/blob/74007890bb9a3fa3a65683a3f480e399f2b1a0b6/src/java.base/share/classes/java/lang/StrictMath.java#L89">class StrictMath ใน Java 17</a></p><pre>public static double toRadians(double angdeg) {<br>  return Math.toRadians(angdeg);<br>}</pre><h3>สรุป</h3><p>ขอย้ำอีกครั้งนะครับว่า การมาของ strictfp ตั้งแต่ Java 1.2 จนกระทั่งจากไปใน Java 17 <strong>ไม่ได้เกี่ยวข้อง </strong>กับเรื่องความไม่แม่นยำในการคำนวณทศนิยมเลย แต่เป็นเพราะต้องการให้ทุกๆ CPU Architecture คำนวณออกมาเหมือนกันต่างหาก</p><p>อย่างไรก็แล้วแต่ คิดว่าคนส่วนใหญ่คงไม่ได้ยุ่งกับ keyword strictfp นี้สักเท่าไหร่ แต่สำหรับคนที่เคยใช้ strictfp มาก่อนหน้านี้ ถ้า upgrade มาเป็น Java 17 ก็ไม่ต้องใช้แล้วนะครับ :)</p><h3>หมายเหตุ</h3><p>ใครที่อยากได้การคำนวณทศนิยมเลขฐานสิบได้เป๊ะๆ ให้ไปใช้ class <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/math/BigDecimal.html">BigDecimal</a> แทนครับ</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8b2e3425c69" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/feature-%E0%B9%83%E0%B8%AB%E0%B8%A1%E0%B9%88%E0%B9%83%E0%B8%99-java-17-3-restore-always-strict-floating-point-semantics-8b2e3425c69">Feature ใหม่ใน Java 17: #3 Restore Always-Strict Floating-Point Semantics</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Feature ใหม่ใน Java 17: #2 Records]]></title>
            <link>https://medium.com/ascend-developers/feature-%E0%B9%83%E0%B8%AB%E0%B8%A1%E0%B9%88%E0%B9%83%E0%B8%99-java-17-2-records-1fd72be06a99?source=rss-4ad64945190a------2</link>
            <guid isPermaLink="false">https://medium.com/p/1fd72be06a99</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[java17]]></category>
            <dc:creator><![CDATA[Yuu]]></dc:creator>
            <pubDate>Wed, 24 Nov 2021 02:56:23 GMT</pubDate>
            <atom:updated>2021-11-24T02:56:23.398Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eRlDtNatHxBaWMicmdB48w.png" /></figure><p>Record คือ class ประเภทใหม่ที่หลายคนรอคอยให้ Java มีแบบนี้สักที¹</p><p>syntax การประกาศ Record ก็เหมือน class ทั่วไปเพียงแต่เปลี่ยนคำว่า class เป็น record แทน และต่อท้ายในวงเล็บด้วย component สำหรับ Record นั้น</p><pre>public record Rectangle(int width, int height) { }</pre><p>แค่เห็นก็ว้าวแล้วใช่มั้ย<br> เย่! ในที่สุด Java ก็มี data class เหมือน Kotlin แล้ว!! — <strong>ผิด</strong><br> เย่! ในที่สุดก็ไม่ต้องใช้ Lombok แล้ว!! — <strong>ผิด</strong></p><p>ถึงแม้หน้าตาจะคล้ายกับ data class ของ Kotlin และการเอาไปใช้งานบางจุดก็ใกล้เคียงกันมาก แต่ก็ไม่ได้เหมือนกันซะทีเดียว เพราะฉะนั้นเราไปดูรายละเอียดกันก่อนดีกว่า</p><h3>ที่มาของปัญหา</h3><p>แรงจูงใจที่ทำให้เกิด Record class ขึ้นมาคือประโยคที่เราเจอคนบ่นถึง Java กันเป็นประจำ “Java is too verbose” หรือ Java ใช้คำฟุ่มเฟือย เกินไปนั่นเอง โดยเฉพาะ class ที่ทำหน้าที่เป็นแค่ตัวส่งข้อมูลที่ไม่ได้มีการแก้ไขข้อมูลใดๆ (immutable data carrier) ซึ่งเวลาที่เราจะสร้าง class แบบที่ว่าขึ้นมามีสิ่งที่เราต้องทำเยอะมาก</p><ul><li>ประกาศ class</li><li>ประกาศ private final field</li><li>ประกาศ constructor ที่รับค่ามาใส่ field</li><li>ประกาศ getter method</li><li>สร้าง method equals, hashCode</li><li>และอาจจะสร้าง method toString ด้วยเพื่อความสะดวก</li></ul><p>บางคนอาจบอกว่าของแค่นี้ใช้ IDE generate ออกมาให้ก็ได้ วิธีนี้สะดวกคนเขียนแต่คนที่มาอ่านโค้ดเราเห็นแบบนี้ก็ขี้เกียจอ่านอยู่ดี Java ก็เลยสร้าง Record ขึ้นมาเพื่อเป็น immutable data carrier โดยเฉพาะ</p><h3>การทำงานของ Record</h3><p>อย่างที่บอกไว้ว่าเป้าหมายของ Record คือการสร้าง class ที่เป็น immutable data carrier เพราะฉะนั้นจากตัวอย่างของ Record ด้านบนจะเทียบเท่าได้กับ class ดังนี้²</p><pre>public record Rectangle(int width, int height) {</pre><pre>  private final int width;<br>  private final int height;</pre><pre>  public Rectangle(int width, int height) {<br>    this.width = width;<br>    this.height = height;<br>  }</pre><pre>  public int width() {<br>    return this.width;<br>  }</pre><pre>  public int height() {<br>    return this.height;<br>  }</pre><pre>  public boolean equals(Object o) {<br>    if (!(o instanceof Rectangle)) return false;<br>    Rectangle other = (Rectangle) o;<br>    return other.width == width &amp;&amp; other.height == height;<br>  }</pre><pre>  public int hashCode() {<br>    return Objects.hash(width, height);<br>  }</pre><pre>  public String toString() {<br>    return String.format(&quot;Rectangle[width=%s, y=%s]&quot;, width, height);<br>  }<br>}</pre><p>สิ่งที่ Record ทำให้เราโดยอัตโนมัติเลย ได้แก่</p><ul><li>private final field ซึ่งชื่อและ type เป็นตามที่เราระบุไว้ใน Record header</li><li>Record class จะไม่มี default constructor แต่จะสร้าง constructor ที่รับ parameter ตามที่เราระบุไว้ใน Record Header และทำการ assign ค่าให้กับ field ในข้อแรก</li><li>getter method ที่ชื่อ<strong>ไม่ได้ขึ้นต้นด้วย</strong> <strong>get</strong> <strong>!!</strong></li><li>method equals(), hashCode(), และ toString()</li></ul><p>ถือว่า Record ช่วยเราประหยัดเวลาสร้าง immutable data class ไปได้เยอะ</p><h3>Local Record Class</h3><p>บ่อยครั้งที่เราทำงานกับ stream แล้วอยากให้ map() return ค่าออกมามากกว่า 1 ตัว ซึ่งถ้าเป็นในอดีตเราก็จะมอบหน้าที่นี้ให้กับ Tuple class แต่ตอนนี้เรามีอีกหนึ่งทางเลือกนั่นก็คือการใช้ Local Record Class</p><p>เราสามารถประกาศ Record class ภายใน method แล้วใช้งานในนั้นได้เลยดังตัวอย่างด้านล่าง</p><pre>private List&lt;String&gt; findGradeAStudent() {<br>  // local record<br>  record StudentGrade(StudentScore result, String grade) {}</pre><pre>  return getExamResults() // List&lt;StudentScore&gt;<br>      .stream()<br>      .map(ss -&gt; new StudentGrade(ss, calculateGrade(ss.score())))<br>      .filter(g -&gt; g.grade().equals(&quot;A&quot;))<br>      .map(StudentGrade::result)<br>      .map(StudentScore::name)<br>      .collect(Collectors.toList());<br>  }<br>}</pre><h3>การ customize Record class</h3><p>ถ้าเราต้องการเพิ่ม logic หรือแก้ไข default behavior ที่ Record class มีให้ เราก็สามารถทำได้เช่นเดียวกับ class ปกติทั่วไป</p><h4><strong>Constructor</strong></h4><p>เราสามารถใส่ validation หรือดัดแปลงค่า parameter ใน constructor ได้ โดย Record สามารถเขียน constructor ในรูปแบบ compact canonical constructor ได้ด้วย</p><pre>record DoubleSizeRectangle(int width, int height) {<br>  DoubleSizeRectangle {<br>    if (width &lt;= 0 || height &lt;= 0) {<br>      throw new IllegalArgumentException(&quot;Invalid width or height&quot;);<br>    }<br>    width *= 2;<br>    height *= 2;<br>  }<br>}</pre><p>จะมีค่าเท่ากับ</p><pre>record DoubleSizeRectangle(int width, int height) {<br>  DoubleSizeRectangle<strong>(int width, int height)</strong> {<br>    if (width &lt;= 0 || height &lt;= 0) {<br>      throw new IllegalArgumentException(&quot;Invalid width or height&quot;);<br>    }<br>    width *= 2;<br>    height *= 2;</pre><pre><strong>    // Initialization<br>    this.width = width;<br>    this.height = height;</strong><br>  }<br>}</pre><h4><strong>Accessor methods, equals, hashCode, toString, and other methods</strong></h4><p>เราสามารถประกาศ accessors, equals, hashCode, และ toString เองเพื่อ customize behavior ของ method เหล่านี้ได้เหมือน class ทั่วๆ ไป สิ่งที่ควรระวังเช่นเรื่อง <a href="https://www.baeldung.com/java-equals-hashcode-contracts">contract ระหว่าง equals กับ hashCode</a> ก็ยังเหมือนเดิม หรือถ้าจะสร้าง instance method / class (static) method อื่นๆ ขึ้นมาก็ได้เหมือนกัน</p><h4><strong>Implement Interface</strong></h4><p>ถึงแม้ว่า Record จะไม่สามารถ extend class อื่นได้ (ดูคำอธิบายด้านล่าง) แต่สามารถ implement interface ได้ตามปกติ เช่น เราอาจจะอยากให้ Record ของเรา implement Comparable interface ได้เป็นต้น</p><h4><strong>Annotation</strong></h4><p>การใช้ Annotation กับ Record จะค่อนข้าง tricky นิดนึง เพราะ component ใน Record header อาจจะหมายถึง field, constructor parameter, หรือ accessor method ก็ได้<br>Annotation ที่วางไว้บน component จะไปโผล่ที่ไหนนั้นขึ้นอยู่กับ @Target ของตัว Annotation เช่นถ้าเป็น @Target(ElementType.FIELD) ตัว Annotation ก็จะไปโผล่บน private field ของ Record นั้น ตัวอย่างเช่น</p><pre>@<strong>OnType</strong><br>public record TestSubject(<br>    @<strong>OnParameter</strong> int p1,<br>    @<strong>OnMethod</strong> int p2,<br>    @<strong>OnField</strong> int p3,<br>    @<strong>OnFieldAndParameter</strong> int p4) {<br>}</pre><p>จะมีค่าเท่ากับ</p><pre>@<strong>OnType</strong><br>public record TestSubject(int p1, int p2, int p3, int p4) {</pre><pre>  ...<br>  @<strong>OnField</strong> private final int p3<br>  @<strong>OnFieldAndParameter</strong> private final int p4</pre><pre>  public TestSubject(<br>        @<strong>OnParameter</strong> int p1,<br>        int p2,<br>        int p3,<br>        @<strong>OnFieldAndParameter</strong> int p4) {<br>    ...<br>  }</pre><pre>  ...</pre><pre>  @<strong>OnMethod</strong><br>  public int p2() {<br>    return this.p2;<br>  }</pre><pre>  ...</pre><pre>}</pre><h3>สิ่งที่ Record class ทำไม่ได้</h3><p>ด้วยความที่ Record ถูกออกแบบมาให้ immutable และ state ของ Record instance ขึ้นอยู่กับ component ใน Record header เท่านั้น เพราะฉะนั้นอะไรก็ตามที่ขัดกับความตั้งใจนี้จะไม่สามารถทำได้</p><ul><li>Record class ไม่สามารถ extend class อื่นได้</li><li>Record class ถือเป็น final class และห้ามเป็น abstract</li><li>ไม่สามารถมี instance field อื่นๆ ใน Record class ได้เพราะจะทำให้ state ของ instance ไม่ขึ้นอยู่กับ Record component เพียงอย่างเดียว</li><li>ไม่สามารถมี native method ได้</li></ul><h3>สรุป</h3><p>Record class ช่วยเพิ่มความสะดวกในการสร้าง immutable data class และยังสามารถใช้แทน Tuple ภายใน method ได้โดยการสร้าง Local Record Class</p><p>ถึงแม้จะยังเทียบกับ data class ของ Kotlin และ @Data ของ Lombok ที่สามารถสร้าง mutable data class ได้ด้วยไม่ได้ แต่ก็ถือว่าเป็น feature ที่ใครที่ upgrade เป็น Java 17 แล้วน่าเอามาใช้ครับ</p><p>สำหรับใครที่อยากดูการใช้งานของ Record class เพิ่มเติมตามไปดูได้ <a href="https://github.com/withyuu/java-11-to-17">ที่นี่</a></p><p>[1]: feature นี้ release ใน Java 16<br>[2]: เป็นแค่ตัวอย่างสำหรับเปรียบเทียบนะครับ implementation จริงๆ ขึ้นอยู่กับ VM แต่ละตัวซึ่งอาจต่างจากที่เขียน</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1fd72be06a99" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/feature-%E0%B9%83%E0%B8%AB%E0%B8%A1%E0%B9%88%E0%B9%83%E0%B8%99-java-17-2-records-1fd72be06a99">Feature ใหม่ใน Java 17: #2 Records</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Feature ใหม่ใน Java 17: #1 Pattern Matching for instanceof]]></title>
            <link>https://medium.com/ascend-developers/feature-%E0%B9%83%E0%B8%AB%E0%B8%A1%E0%B9%88%E0%B9%83%E0%B8%99-java-17-1-pattern-matching-for-instanceof-515db1d6bed?source=rss-4ad64945190a------2</link>
            <guid isPermaLink="false">https://medium.com/p/515db1d6bed</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[java17]]></category>
            <dc:creator><![CDATA[Yuu]]></dc:creator>
            <pubDate>Thu, 18 Nov 2021 11:05:50 GMT</pubDate>
            <atom:updated>2021-11-18T11:05:50.924Z</atom:updated>
            <content:encoded><![CDATA[<p>Java 17 ถูกปล่อยออกมาอย่างเป็นทางการแล้วเมื่อวันที่ 14 กันยายน 2564 ซึ่งถ้าอยากจะเทียบว่ามีอะไรต่างจาก version 11 ที่เป็น Long Term Release (LTS) ตัวก่อนหน้าก็สามารถดูได้จากที่นี่เลย <a href="https://openjdk.java.net/projects/jdk/17/jeps-since-jdk-11">JEPs in JDK 17 integrated since JDK 11</a></p><p>สำหรับในบทความซีรี่ส์นี้เราจะมาดูในส่วนที่เป็นเฉพาะ Language Feature กันว่ามีอะไรใหม่ในตัวภาษาบ้าง ซึ่งสำหรับตอนแรกนี้ขอนำเสนอ Feature <strong>Pattern Matching for instanceof</strong></p><h3>ที่มาของปัญหา</h3><p>ปัญหาที่ Feature นี้จะเข้ามาแก้ก็คือเรื่องความเยิ่นเย้อในการตรวจสอบและ cast instance type</p><p>ด้านล่างนี่คือ code ในการตรวจสอบ instance type และ cast ที่หลายๆ คนคงคุ้นเคยกันดี</p><pre>if (obj instanceof Car) {<br>  Car c = (Car) obj; // +_+<br>  c.drift();<br>}</pre><p>จะเห็นได้ว่าบรรทัดที่ทำการ cast เป็นอะไรที่เยิ่นเย้อ boilerplate code มากๆ ยังไม่นับว่าทั้งหมดนี้เราต้องพิมพ์คำว่า Car ถึง 3 ครั้ง!</p><h3>Pattern Matching for instanceof</h3><p>ด้วย feature นี้ของ Java 17¹ code เราจะกระชับขึ้น กลายเป็น</p><pre>if (obj instanceof Car c) {<br>  c.drift();<br>}</pre><p>อาจจะไม่ได้เป็น feature ที่เปลี่ยนแปลงอะไรมากนัก แต่ก็ช่วยทำให้ code ของเรากระชับขึ้นและมีโอกาสมีข้อผิดพลาดได้น้อยลง</p><h3>ข้อควรระวัง</h3><p>จุดที่น่าสนใจจุดหนึ่งของ feature นี้ก็คือ scope ของตัว pattern variable</p><p>ก่อนที่จะอธิบายถึงเรื่อง scope ก็ขออธิบายคำศัพท์นิดนึง จากในภาพด้านล่างตรงส่วน String s เราเรียกว่า <em>Type Pattern</em> และตรงตัวแปร s เรียกว่า <em>Pattern Variable</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/330/0*pEbXXrqEyIu0zItP.png" /></figure><p>pattern variable ใช้ concept ที่เรียกว่า <em>flow scoping</em> ซึ่งหมายความว่า pattern variable จะอยู่ใน scope เฉพาะในจุดที่ compiler สามารถบอกได้ว่า pattern นี้ match</p><p>ถ้าจากตัวอย่างด้านบนก็หมายความว่า เราจะเรียกใช้ตัวแปร c ได้ก็ต่อเมื่อมันถูกเรียกใช้ในจุดที่ compiler มั่นใจว่า c คือ Car เท่านั้น</p><pre>if (obj instanceof Car c) {<br>  c.drift(); // เรียกใช้ c ตรงนี้ได้<br>}<br>c.drift(); // compile error เรียกใช้ c ตรงนี้ไม่ได้</pre><p>หรือถ้าเราเขียนอีกแบบ</p><pre>if ( !(obj instanceof Car c) ) {<br>  c.drift(); // เรียกใช้ c ตรงนี้ไม่ได้ เพราะถ้าเข้า block นี้แปลว่า c ไม่ใช่ Car<br>  throw new RuntimeException();<br>}<br>c.drift(); // เรียกใช้ c ตรงนี้ได้! เพราะ compiler มั่นใจว่าบรรทัดนี้ c คือ Car</pre><p>ซึ่งถ้าเราใช้ boolean operator ควบคู่ไปกับ instanceof ด้วยก็ยังใช้หลักการเดียวกัน</p><pre>if (obj instanceof String s &amp;&amp; s.length() &gt; 0) {<br>  // แบบนี้สามารถทำได้ เพราะด้านหลัง operator &amp;&amp; s เป็น String แน่ๆ <br>}</pre><pre>if (obj instanceof String s || s.length() &gt; 0) {<br>  // แต่แบบนี้ compile error เพราะหลัง operator || s อาจจะไม่ใช่ String ก็ได้<br>}</pre><h3>สรุป</h3><p>Java 17 เพิ่ม Feature <strong>Pattern Matching for instanceof</strong> เข้ามาเพื่อให้การเขียน check + cast type กระชับขึ้น โดยที่มีข้อควรระวังเรื่อง scope ของ pattern variable อยู่เล็กน้อย<br> หลักการในการจำง่ายๆ ก็คือ <strong>pattern variable จะอยู่ใน scope เฉพาะในจุดที่มัน match เท่านั้น</strong> ครับ</p><p>[1]: feature นี้เพิ่มเข้ามาตั้งแต่ Java 16</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=515db1d6bed" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/feature-%E0%B9%83%E0%B8%AB%E0%B8%A1%E0%B9%88%E0%B9%83%E0%B8%99-java-17-1-pattern-matching-for-instanceof-515db1d6bed">Feature ใหม่ใน Java 17: #1 Pattern Matching for instanceof</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Passing HTTP headers to another microservice and put them to the log programmatically using Spring…]]></title>
            <link>https://medium.com/ascend-developers/passing-http-headers-to-another-microservice-and-put-them-to-the-log-programmatically-using-spring-91ae60126c6f?source=rss-4ad64945190a------2</link>
            <guid isPermaLink="false">https://medium.com/p/91ae60126c6f</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[spring-cloud-sleuth]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[spring-boot]]></category>
            <dc:creator><![CDATA[Yuu]]></dc:creator>
            <pubDate>Sat, 04 Sep 2021 07:24:12 GMT</pubDate>
            <atom:updated>2021-09-04T07:24:12.476Z</atom:updated>
            <content:encoded><![CDATA[<h3>Passing HTTP headers to another microservice and put them to the log programmatically using Spring Cloud Sleuth</h3><p>In a microservice architecture, it is common to pass a custom HTTP header, let’s say: “Correlation-ID”, from one service to another. It might also be equally common to record that value in the log for debugging later.</p><p>If you are using Spring Cloud Sleuth, this task would be quite easy with Sleuth’s baggage field feature. You will just have to add a couple of properties to the configuration file and Voilà!</p><p>However, my situation is somewhat different in that I need to do it programmatically. Since I have spent a considerable amount of time on it, I decided to write an article about it for future reference.</p><h3>Setup</h3><p>The first step is to add the spring-cloud-sleuth dependency to your project.</p><pre>&lt;dependencies&gt;<br>  ...<br>  &lt;dependency&gt;<br>    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;<br>    &lt;artifactId&gt;spring-cloud-starter-sleuth&lt;/artifactId&gt;<br>  &lt;/dependency&gt;<br>  ...<br>&lt;/dependencies&gt;<br>&lt;dependencyManagement&gt;<br>  &lt;dependencies&gt;<br>    &lt;dependency&gt;<br>      &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;<br>      &lt;artifactId&gt;spring-cloud-dependencies&lt;/artifactId&gt;<br>      &lt;version&gt;${spring-cloud.version}&lt;/version&gt;<br>      &lt;type&gt;pom&lt;/type&gt;<br>      &lt;scope&gt;import&lt;/scope&gt;<br>    &lt;/dependency&gt;<br>  &lt;/dependencies&gt;<br>&lt;/dependencyManagement&gt;</pre><p><em>Note that I set </em>spring-cloud.version<em> to </em>2020.0.3<em> which resolves to Sleuth 3.0.3. If you are using Sleuth 2.x, the code here may not work for you.</em></p><p>For this article, I created two simple microservices, A and B, that talk to each other. The controller code of the service A looks like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d50121ee7df75de89c1189348bfcda95/href">https://medium.com/media/d50121ee7df75de89c1189348bfcda95/href</a></iframe><p>The code of B controller is pretty similar but without calling another service.</p><h3>Configure Sleuth Baggage Using Application Properties</h3><p>First let’s take a look on how to achieve this without any coding. Just add these two properties to application.properties of both services:</p><pre>spring.sleuth.baggage.remote-fields=Correlation-Id (1)<br>spring.sleuth.baggage.correlation-fields=Correlation-Id (2)</pre><p>(1) Tell Sleuth to get the value from this header name(s) and propagate to remote services<br>(2) Set the baggage value(s) to Slf4j’s MDC</p><p>Call the service A with curl -H “Correlation-Id: <strong>528491</strong>” <a href="http://localhost:8080/a/message">http://localhost:8080/a/message</a> and this is the result:</p><pre>// Service-A Log<br>2021-09-04 12:02:52,215|INFO|service-a|<strong>528491</strong>|Service-A is called with Correlation-Id: <strong>528491</strong></pre><pre>// Service-B Log<br>2021-09-04 12:02:52,321|INFO|service-b|<strong>528491</strong>|Service-B is called with Correlation-Id: <strong>528491</strong></pre><p>You can see that the Correlation-Id header value is put into the log and propagated from A to B.</p><h3>Configure Sleuth Baggage Programmatically</h3><p>Alternatively, if you want to (or have to) do it programmatically, Sleuth allows us to define Customizer beans that will be used to customize the Tracer object. We will use BaggagePropagationCustomizer to add a remote-field baggage and use CorrelationScopeCustomizer to put the value into MDC.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/97b2652f621475af3bee62b43a897111/href">https://medium.com/media/97b2652f621475af3bee62b43a897111/href</a></iframe><p><em>If you would like to know how this works behind the scenes, I suggest you dive into Sleuth’s </em><a href="https://github.com/spring-cloud/spring-cloud-sleuth/blob/8b61684168be90e06b861c26205d25f61ab3a0a2/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/brave/BraveBaggageConfiguration.java"><em>BraveBaggageConfiguration code</em></a><em>.</em></p><p>Then we test by calling the service A with curl -H “Correlation-Id: <strong>314159</strong>” <a href="http://localhost:8080/a/message">http://localhost:8080/a/message</a> and see the result:</p><pre>// Service-A Log<br>2021-09-04 13:58:58,010|INFO|service-a|<strong>314159</strong>|Service-A is called with Correlation-Id: <strong>314159</strong></pre><pre>// Service-B Log<br>2021-09-04 13:58:58,076|INFO|service-b|<strong>314159</strong>|Service-B is called with Correlation-Id: <strong>314159</strong></pre><h3>Summary</h3><p>In this article, we display 2 ways to propagate HTTP headers from one microservice to another. Normally, we would just use Sleuth provided configuration properties. In other cases or if you need advanced customization, you can utilize the Customizers to configure baggage fields to suit your needs.</p><p>The complete source code in this article can be found here: <a href="https://github.com/withyuu/sleuth-baggage-programmatically">https://github.com/withyuu/sleuth-baggage-programmatically</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=91ae60126c6f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/passing-http-headers-to-another-microservice-and-put-them-to-the-log-programmatically-using-spring-91ae60126c6f">Passing HTTP headers to another microservice and put them to the log programmatically using Spring…</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>