อินเทอร์เฟซใน Java

อินเทอร์เฟซ Java แตกต่างจากคลาสและสิ่งสำคัญคือต้องรู้วิธีใช้คุณสมบัติพิเศษในโปรแกรม Java ของคุณ บทช่วยสอนนี้แนะนำความแตกต่างระหว่างคลาสและอินเทอร์เฟซจากนั้นจะแนะนำคุณผ่านตัวอย่างที่สาธิตวิธีการประกาศใช้งานและขยายอินเตอร์เฟส Java

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

ดาวน์โหลดรับโค้ดดาวน์โหลดซอร์สโค้ดสำหรับแอปพลิเคชันตัวอย่างในบทช่วยสอนนี้ สร้างโดย Jeff Friesen สำหรับ JavaWorld

Java interface คืออะไร?

อินเตอร์เฟซที่เป็นจุดที่สองระบบตอบสนองและโต้ตอบ ตัวอย่างเช่นคุณอาจใช้อินเทอร์เฟซของตู้หยอดเหรียญเพื่อเลือกรายการชำระเงินและรับรายการอาหารหรือเครื่องดื่ม จากมุมมองของการเขียนโปรแกรมอินเทอร์เฟซจะอยู่ระหว่างส่วนประกอบซอฟต์แวร์ พิจารณาว่าส่วนหัวของเมธอด (ชื่อเมธอดรายการพารามิเตอร์และอื่น ๆ ) อยู่ระหว่างโค้ดภายนอกที่เรียกใช้เมธอดและโค้ดภายในเมธอดที่จะดำเนินการอันเป็นผลมาจากการเรียก นี่คือตัวอย่าง:

System.out.println(average(10, 15)); double average(double x, double y) // interface between average(10, 15) call and return (x + y) / 2; { return (x + y) / 2; }

สิ่งที่มักสร้างความสับสนให้กับผู้เริ่มต้น Java คือคลาสนั้นมีอินเทอร์เฟซด้วย ดังที่ฉันได้อธิบายไว้ใน Java 101: คลาสและอ็อบเจ็กต์ใน Java อินเทอร์เฟซเป็นส่วนหนึ่งของคลาสที่สามารถเข้าถึงรหัสที่อยู่ภายนอกได้ อินเทอร์เฟซของคลาสประกอบด้วยการรวมกันของวิธีการฟิลด์ตัวสร้างและเอนทิตีอื่น ๆ พิจารณารายชื่อ 1.

รายการ 1. คลาสบัญชีและอินเทอร์เฟซ

class Account { private String name; private long amount; Account(String name, long amount) { this.name = name; setAmount(amount); } void deposit(long amount) { this.amount += amount; } String getName() { return name; } long getAmount() { return amount; } void setAmount(long amount) { this.amount = amount; } }

Account(String name, long amount)คอนสตรัคและvoid deposit(long amount), String getName(), long getAmount()และvoid setAmount(long amount)วิธีการในรูปแบบAccountอินเตอร์เฟซชั้นของพวกเขาสามารถเข้าถึงรหัสภายนอก private String name;และprivate long amount;สาขาที่ไม่สามารถเข้าถึงได้

เพิ่มเติมเกี่ยวกับอินเตอร์เฟส Java

คุณสามารถทำอะไรกับอินเทอร์เฟซในโปรแกรม Java ของคุณได้บ้าง? ดูภาพรวมเกี่ยวกับหกบทบาทของอินเทอร์เฟซ Java ของ Jeff

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

เมื่อมีการเปิดเผยการนำไปใช้งานอาจเกิดการพึ่งพาซึ่งกันและกันระหว่างส่วนประกอบซอฟต์แวร์ ตัวอย่างเช่นรหัสวิธีการอาจขึ้นอยู่กับตัวแปรภายนอกและผู้ใช้ของคลาสอาจขึ้นอยู่กับฟิลด์ที่ควรซ่อนไว้ การเชื่อมต่อนี้อาจนำไปสู่ปัญหาเมื่อการใช้งานต้องมีวิวัฒนาการ (อาจต้องลบช่องที่เปิดเผยออก)

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

List names = new ArrayList() void print(List names) { // ... }

ตัวอย่างนี้ประกาศและเริ่มต้นnamesฟิลด์ที่เก็บรายชื่อสตริง ตัวอย่างนี้ยังประกาศprint()วิธีการพิมพ์เนื้อหาของรายการสตริงซึ่งอาจเป็นหนึ่งสตริงต่อบรรทัด เพื่อความกะทัดรัดฉันได้ละเว้นการนำวิธีไปใช้

Listเป็นอินเทอร์เฟซ Java ที่อธิบายการรวบรวมวัตถุตามลำดับ ArrayListเป็นคลาสที่อธิบายการใช้งานListอินเตอร์เฟส Java บนอาร์เรย์ ตัวอย่างใหม่ของArrayListชั้นจะได้รับและได้รับมอบหมายให้ตัวแปรList names( ListและArrayListถูกเก็บไว้ในjava.utilแพ็คเกจของไลบรารีคลาสมาตรฐาน)

วงเล็บมุมและข้อมูลทั่วไป

วงเล็บมุม ( <และ>) เป็นส่วนหนึ่งของชุดคุณลักษณะทั่วไปของ Java ซึ่งระบุว่าnamesอธิบายรายการสตริง (สามารถจัดเก็บได้เฉพาะสตริงในรายการ) ฉันจะแนะนำ generics ในบทความ Java 101 ในอนาคต

เมื่อปฏิสัมพันธ์รหัสลูกค้าด้วยnamesก็จะเรียกวิธีการเหล่านั้นที่ได้รับการประกาศโดยและที่จะดำเนินการโดยList รหัสลูกค้าจะไม่ได้ติดต่อโดยตรงกับArrayList ArrayListด้วยเหตุนี้รหัสไคลเอ็นต์จะไม่หยุดทำงานเมื่อต้องใช้คลาสการนำไปใช้งานอื่นเช่นLinkedList:

List names = new LinkedList() // ... void print(List names) { // ... }

เนื่องจากprint()ประเภทพารามิเตอร์เมธอดคือListการใช้งานวิธีนี้จึงไม่จำเป็นต้องเปลี่ยนแปลง แต่ถ้าประเภทที่ได้รับการพิมพ์จะต้องมีการเปลี่ยนแปลงไปArrayList LinkedListหากทั้งสองคลาสต้องประกาศวิธีการเฉพาะของตนเองคุณอาจต้องเปลี่ยนprint()การนำไปใช้อย่างมีนัยสำคัญ

การแยกListจากArrayListและLinkedListช่วยให้คุณสามารถเขียนโค้ดที่ไม่มีผลต่อการเปลี่ยนแปลงการนำคลาสไปใช้ ด้วยการใช้อินเตอร์เฟส Java คุณสามารถหลีกเลี่ยงปัญหาที่อาจเกิดขึ้นจากการใช้คลาสการนำไปใช้งาน การแยกส่วนนี้เป็นเหตุผลหลักในการใช้อินเตอร์เฟส Java

การประกาศอินเตอร์เฟส Java

คุณประกาศอินเทอร์เฟซโดยยึดตามไวยากรณ์เหมือนคลาสที่ประกอบด้วยส่วนหัวตามด้วยเนื้อความ อย่างน้อยที่สุดส่วนหัวประกอบด้วยคีย์เวิร์ดinterfaceตามด้วยชื่อที่ระบุอินเทอร์เฟซ ร่างกายเริ่มต้นด้วยอักขระปีกกาเปิดและลงท้ายด้วยวงเล็บปีกกาปิด ระหว่างตัวคั่นเหล่านี้เป็นค่าคงที่และการประกาศส่วนหัวของเมธอด:

interface identifier { // interface body }

โดยการประชุมอักษรตัวแรกของชื่ออินเตอร์เฟซที่เป็น uppercased และตัวอักษรที่ตามมา lowercased (ตัวอย่างDrawable) หากชื่อประกอบด้วยคำหลายคำอักษรตัวแรกของแต่ละคำจะเป็นตัวพิมพ์ใหญ่ (เช่นDrawableAndFillable) หลักการตั้งชื่อนี้เรียกว่า CamelCasing

2 Drawableรายการประกาศอินเตอร์เฟซที่มีชื่อว่า

รายการ 2. ตัวอย่างอินเตอร์เฟส Java

interface Drawable { int RED = 1; int GREEN = 2; int BLUE = 3; int BLACK = 4; int WHITE = 5; void draw(int color); }

อินเทอร์เฟซในไลบรารีคลาสมาตรฐานของ Java

ตามหลักการตั้งชื่ออินเตอร์เฟสจำนวนมากในไลบรารีคลาสมาตรฐานของ Java จะลงท้ายด้วยคำต่อท้ายที่สามารถ ตัวอย่างเช่นCallable, Cloneable, Comparable, Formattable, Iterable, Runnable, และSerializable Transferableอย่างไรก็ตามคำต่อท้ายไม่ได้บังคับ ห้องสมุดระดับมาตรฐานรวมถึงอินเตอร์เฟซCharSequence, ClipboardOwner, Collection, Executor, Future, Iterator, List, Mapและอื่น ๆ อีกมากมาย

Drawableประกาศห้าฟิลด์ที่ระบุค่าคงที่ของสี อินเทอร์เฟซนี้ยังประกาศส่วนหัวสำหรับdraw()วิธีการที่ต้องเรียกใช้ด้วยค่าคงที่เหล่านี้เพื่อระบุสีที่ใช้ในการวาดโครงร่าง (การใช้ค่าคงที่จำนวนเต็มไม่ใช่ความคิดที่ดีเพราะสามารถส่งผ่านค่าจำนวนเต็มไปdraw()ได้อย่างไรก็ตามก็เพียงพอแล้วในตัวอย่างง่ายๆ)

ค่าเริ่มต้นของส่วนหัวของฟิลด์และเมธอด

public final staticเขตข้อมูลที่มีการประกาศในอินเตอร์เฟซที่มีโดยปริยาย public abstractอินเตอร์เฟซของหัววิธีการอยู่โดยปริยาย

Drawableระบุประเภทการอ้างอิงที่ระบุสิ่งที่ต้องทำ (วาดบางสิ่ง) แต่ไม่ใช่วิธีการทำ รายละเอียดการใช้งานถูกส่งไปยังคลาสที่ใช้อินเทอร์เฟซนี้ อินสแตนซ์ของคลาสดังกล่าวเรียกว่า drawables เนื่องจากพวกเขารู้วิธีวาดตัวเอง

อินเทอร์เฟซเครื่องหมายและการติดแท็ก

อินเตอร์เฟซที่มีร่างกายที่ว่างเปล่าเป็นที่รู้จักกันเป็นอินเตอร์เฟซที่เครื่องหมายหรืออินเตอร์เฟซที่ติดแท็ก อินเทอร์เฟซมีอยู่เพื่อเชื่อมโยงข้อมูลเมตากับคลาสเท่านั้น ตัวอย่างเช่นCloneable(ดูการสืบทอดใน Java ตอนที่ 2) บอกเป็นนัยว่าอินสแตนซ์ของคลาสการนำไปใช้สามารถโคลนแบบตื้น ๆ ได้ เมื่อObject's clone()ตรวจหาวิธี (ผ่านรันไทม์ประจำตัวชนิด) ที่อินสแตนซ์เรียกร้องของการดำเนินการระดับCloneableมันตื้นโคลนวัตถุ

การใช้งานอินเตอร์เฟส Java

คลาสใช้อินเทอร์เฟซโดยต่อท้ายimplementsคีย์เวิร์ดของ Java ตามด้วยรายการชื่ออินเทอร์เฟซที่คั่นด้วยเครื่องหมายจุลภาคเข้ากับส่วนหัวคลาสและโดยการเข้ารหัสแต่ละวิธีการเชื่อมต่อในคลาส Listing 3 แสดงคลาสที่ใช้Drawableอินเทอร์เฟซของ Listing 2

รายชื่อ 3. Circle ที่ใช้อินเทอร์เฟซ Drawable

class Circle implements Drawable { private double x, y, radius; Circle(double x, double y, double radius) { this.x = x; this.y = y; this.radius = radius; } @Override public void draw(int color) { System.out.println("Circle drawn at (" + x + ", " + y + "), with radius " + radius + ", and color " + color); } double getRadius() { return radius; } double getX() { return x; } double getY() { return y; } }

Circleคลาสของ Listing 3 อธิบายวงกลมเป็นจุดศูนย์กลางและรัศมี รวมทั้งให้ตัวสร้างและวิธีทะเยอทะยานที่เหมาะสมCircleดำเนินการDrawableอินเตอร์เฟซโดยการผนวกimplements DrawableกับCircleส่วนหัวและเอาชนะ (ตามที่ระบุโดย@Overrideคำอธิบายประกอบ) Drawable's draw()หัววิธี

รายการที่ 4 นำเสนอตัวอย่างที่สอง: Rectangleคลาสที่ใช้Drawableด้วย

การแสดงรายการ 4. การใช้อินเทอร์เฟซ Drawable ในบริบทสี่เหลี่ยมผืนผ้า

class Rectangle implements Drawable { private double x1, y1, x2, y2; Rectangle(double x1, double y1, double x2, double y2) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; } @Override public void draw(int color) { System.out.println("Rectangle drawn with upper-left corner at (" + x1 + ", " + y1 + ") and lower-right corner at (" + x2 + ", " + y2 + "), and color " + color); } double getX1() { return x1; } double getX2() { return x2; } double getY1() { return y1; } double getY2() { return y2; } }

Listing 4's Rectangle class describes a rectangle as a pair of points denoting the upper-left and lower-right corners of this shape. As with Circle, Rectangle provides a constructor and suitable getter methods, and also implements the Drawable interface.

Overriding interface method headers

The compiler reports an error when you attempt to compile a non-abstract class that includes an implements interface clause but doesn't override all of the interface's method headers.

An interface type's data values are the objects whose classes implement the interface and whose behaviors are those specified by the interface's method headers. This fact implies that you can assign an object's reference to a variable of the interface type, provided that the object's class implements the interface. Listing 5 demonstrates.

Listing 5. Aliasing Circle and Rectangle objects as Drawables

class Draw { public static void main(String[] args) { Drawable[] drawables = new Drawable[] { new Circle(10, 20, 15), new Circle(30, 20, 10), new Rectangle(5, 8, 8, 9) }; for (int i = 0; i < drawables.length; i++) drawables[i].draw(Drawable.RED); } }

Because Circle and Rectangle implement Drawable, Circle and Rectangle objects have Drawable type in addition to their class types. Therefore, it's legal to store each object's reference in an array of Drawables. A loop iterates over this array, invoking each Drawable object's draw() method to draw a circle or a rectangle.

Assuming that Listing 2 is stored in a Drawable.java source file, which is in the same directory as the Circle.java, Rectangle.java, and Draw.java source files (which respectively store Listing 3, Listing 4, and Listing 5), compile these source files via either of the following command lines:

javac Draw.java javac *.java

Run the Draw application as follows:

java Draw

You should observe the following output:

Circle drawn at (10.0, 20.0), with radius 15.0, and color 1 Circle drawn at (30.0, 20.0), with radius 10.0, and color 1 Rectangle drawn with upper-left corner at (5.0, 8.0) and lower-right corner at (8.0, 9.0), and color 1

Note that you could also generate the same output by specifying the following main() method:

public static void main(String[] args) { Circle c = new Circle(10, 20, 15); c.draw(Drawable.RED); c = new Circle(30, 20, 10); c.draw(Drawable.RED); Rectangle r = new Rectangle(5, 8, 8, 9); r.draw(Drawable.RED); }

อย่างที่คุณเห็นมันน่าเบื่อที่จะเรียกใช้draw()เมธอดของแต่ละวัตถุซ้ำ ๆ นอกจากนี้การทำเช่นนี้จะเพิ่ม bytecode พิเศษให้กับDrawไฟล์คลาสของ ด้วยการคิดCircleและRectangleเป็นDrawables คุณสามารถใช้ประโยชน์จากอาร์เรย์และลูปง่ายๆเพื่อลดความซับซ้อนของโค้ด นี่เป็นประโยชน์เพิ่มเติมจากการออกแบบโค้ดให้ชอบอินเทอร์เฟซมากกว่าคลาส

ข้อควรระวัง!