ภาษา Java มีคุณสมบัติของผู้ร่วมประชุมคล้ายกับที่ C # มีการสนับสนุนสำหรับผู้ได้รับมอบหมายหรือไม่?
ภาษา Java มีคุณสมบัติของผู้ร่วมประชุมคล้ายกับที่ C # มีการสนับสนุนสำหรับผู้ได้รับมอบหมายหรือไม่?
คำตอบ:
ไม่จริงไม่
คุณอาจจะได้รับผลกระทบเดียวกันโดยใช้การสะท้อนเพื่อรับวัตถุวิธีที่คุณสามารถเรียกใช้และวิธีอื่นคือการสร้างอินเทอร์เฟซด้วยวิธีการ 'เรียกใช้' หรือ 'เรียกใช้' เดี่ยวจากนั้นสร้างอินสแตนซ์ให้เรียกวิธี ความสนใจของคุณใน (เช่นใช้คลาสภายในที่ไม่ระบุชื่อ)
คุณอาจพบว่าบทความนี้น่าสนใจ / มีประโยชน์: โปรแกรมเมอร์ Java ดูที่ C # Delegates (@ archive.org)
ขึ้นอยู่กับสิ่งที่คุณหมายถึงคุณสามารถบรรลุผลที่คล้ายกัน (ผ่านวิธีการ) โดยใช้รูปแบบกลยุทธ์
แทนที่จะเป็นบรรทัดเช่นนี้ประกาศลายเซ็นวิธีการที่มีชื่อ:
// C#
public delegate void SomeFunction();
ประกาศอินเตอร์เฟส:
// Java
public interface ISomeBehaviour {
void SomeFunction();
}
สำหรับการประยุกต์ใช้วิธีที่เป็นรูปธรรมให้กำหนดคลาสที่ใช้ลักษณะการทำงาน:
// Java
public class TypeABehaviour implements ISomeBehaviour {
public void SomeFunction() {
// TypeA behaviour
}
}
public class TypeBBehaviour implements ISomeBehaviour {
public void SomeFunction() {
// TypeB behaviour
}
}
เมื่อใดก็ตามที่คุณมีSomeFunction
ตัวแทนใน C # ให้ใช้การISomeBehaviour
อ้างอิงแทน:
// C#
SomeFunction doSomething = SomeMethod;
doSomething();
doSomething = SomeOtherMethod;
doSomething();
// Java
ISomeBehaviour someBehaviour = new TypeABehaviour();
someBehaviour.SomeFunction();
someBehaviour = new TypeBBehaviour();
someBehaviour.SomeFunction();
ด้วยคลาสภายในที่ไม่ระบุชื่อคุณสามารถหลีกเลี่ยงการประกาศคลาสที่มีชื่อแยกกันและเกือบจะปฏิบัติต่อพวกมันเหมือนฟังก์ชั่นตัวแทนที่แท้จริง
// Java
public void SomeMethod(ISomeBehaviour pSomeBehaviour) {
...
}
...
SomeMethod(new ISomeBehaviour() {
@Override
public void SomeFunction() {
// your implementation
}
});
สิ่งนี้ควรใช้เมื่อการดำเนินการเฉพาะเจาะจงมากกับบริบทปัจจุบันและจะไม่ได้รับประโยชน์จากการใช้ซ้ำ
และแน่นอนใน Java 8 สิ่งเหล่านี้จะกลายเป็นการแสดงออกแลมบ์ดาโดยทั่วไป:
// Java 8
SomeMethod(() -> { /* your implementation */ });
เรื่องย่อ: ไม่มี
บทนำ
รุ่นใหม่ล่าสุดของ J การพัฒนาสภาพแวดล้อม Microsoft Visual ++ สนับสนุนภาษาสร้างเรียกว่าได้รับมอบหมายหรืออ้างอิงวิธีการที่ถูกผูกไว้ โครงสร้างนี้และคำหลักใหม่
delegate
และmulticast
นำไปสนับสนุนมันไม่ได้เป็นส่วนหนึ่งของจาวาTM ภาษาการเขียนโปรแกรมซึ่งระบุโดยJava Language ข้อกำหนดและแก้ไขเพิ่มเติมโดยคลาสจำเพาะรวมอยู่ในเอกสารสำหรับซอฟต์แวร์ JDKTM 1.1ไม่น่าเป็นไปได้ที่ภาษาการเขียนโปรแกรม Java จะรวมโครงสร้างนี้ ซันได้พิจารณาอย่างรอบคอบแล้วว่าได้ยอมรับมันในปี 1996 จนถึงระดับของการสร้างและทิ้งต้นแบบการทำงาน ข้อสรุปของเราคือการอ้างอิงวิธีการผูกไว้ไม่จำเป็นและเป็นอันตรายต่อภาษา การตัดสินใจครั้งนี้ทำขึ้นโดยการปรึกษาหารือกับ Borland International ซึ่งเคยมีประสบการณ์มาก่อนด้วยการอ้างอิงวิธีการที่ถูกผูกมัดใน Delphi Object Pascal
เราเชื่อว่าการอ้างอิงเมธอดที่ถูกผูกไว้นั้นไม่จำเป็นเนื่องจากทางเลือกการออกแบบอื่น, คลาสภายใน , มีฟังก์ชันการทำงานที่เท่าเทียมกันหรือเหนือกว่า โดยเฉพาะอย่างยิ่งคลาสภายในสนับสนุนข้อกำหนดของการจัดการเหตุการณ์ส่วนติดต่อผู้ใช้อย่างสมบูรณ์และถูกนำมาใช้ในการใช้ API ส่วนต่อประสานผู้ใช้อย่างน้อยครอบคลุมในระดับ Windows Foundation Classes
เราเชื่อว่าการอ้างอิงเมธอดที่ถูกผูกไว้นั้นเป็นอันตรายเพราะมันเบี่ยงเบนไปจากความเรียบง่ายของภาษาการเขียนโปรแกรมจาวาและลักษณะเชิงวัตถุของ APIs ที่แพร่หลาย การอ้างอิงวิธีการที่ถูกผูกไว้ยังแนะนำความผิดปกติในไวยากรณ์ภาษาและกฎการกำหนดขอบเขต ท้ายที่สุดพวกเขาลดการลงทุนในเทคโนโลยี VM เนื่องจาก VMs จำเป็นต้องจัดการประเภทการอ้างอิงและวิธีการเชื่อมโยงวิธีเพิ่มเติมอย่างมีประสิทธิภาพ
คุณได้อ่านสิ่งนี้ :
ผู้รับมอบสิทธิ์เป็นโครงสร้างที่มีประโยชน์ในระบบที่อ้างอิงเหตุการณ์ ผู้ได้รับมอบหมายหลักคือวัตถุที่เข้ารหัสวิธีการจัดส่งบนวัตถุที่ระบุ เอกสารนี้แสดงให้เห็นว่าคลาสใน Java ให้วิธีแก้ปัญหาทั่วไปได้อย่างไร
ผู้ได้รับมอบหมายคืออะไร จริงๆแล้วมันคล้ายกับตัวชี้ไปยังฟังก์ชันสมาชิกที่ใช้ใน C ++ แต่ผู้รับมอบสิทธิ์มีวัตถุเป้าหมายพร้อมกับวิธีการที่จะเรียกใช้ เป็นการดีที่จะสามารถพูดว่า:
obj.registerHandler (ano.methodOne);
.. และว่าวิธีการวิธีหนึ่งจะถูกเรียกว่า ano เมื่อได้รับเหตุการณ์เฉพาะบางอย่าง
นี่คือความสำเร็จของโครงสร้างผู้ได้รับมอบหมาย
Java Inner Classes
มันเป็นที่ถกเถียงกันอยู่ว่า Java ให้ฟังก์ชั่นนี้ผ่านชั้นในที่ไม่ระบุชื่อและดังนั้นจึงไม่จำเป็นต้องสร้างที่ได้รับมอบหมายเพิ่มเติม
obj.registerHandler(new Handler() {
public void handleIt(Event ev) {
methodOne(ev);
}
} );
ได้อย่างรวดเร็วก่อนดูเหมือนนี้ถูกต้อง แต่ในเวลาเดียวกันความรำคาญ เนื่องจากตัวอย่างการประมวลผลเหตุการณ์จำนวนมากความเรียบง่ายของไวยากรณ์ของผู้รับมอบสิทธิ์นั้นน่าสนใจมาก
ผู้จัดการทั่วไป
อย่างไรก็ตามหากการเขียนโปรแกรมแบบอิงเหตุการณ์ถูกใช้ในลักษณะที่แพร่หลายมากขึ้นตัวอย่างเช่นในฐานะที่เป็นส่วนหนึ่งของสภาพแวดล้อมการเขียนโปรแกรมแบบอะซิงโครนัสทั่วไป
ในสถานการณ์ทั่วไปมันไม่เพียงพอที่จะรวมเฉพาะวิธีการเป้าหมายและอินสแตนซ์วัตถุเป้าหมาย โดยทั่วไปอาจมีพารามิเตอร์อื่น ๆ ที่ต้องการซึ่งจะถูกกำหนดภายในบริบทเมื่อมีการลงทะเบียนตัวจัดการเหตุการณ์
ในสถานการณ์ทั่วไปที่มากกว่านี้วิธีการของจาวาสามารถให้โซลูชันที่หรูหรามากโดยเฉพาะเมื่อรวมกับการใช้ตัวแปรขั้นสุดท้าย:
void processState(final T1 p1, final T2 dispatch) {
final int a1 = someCalculation();
m_obj.registerHandler(new Handler() {
public void handleIt(Event ev) {
dispatch.methodOne(a1, ev, p1);
}
} );
}
สุดท้าย * สุดท้าย * สุดท้าย
ได้รับความสนใจของคุณ?
โปรดทราบว่าตัวแปรสุดท้ายสามารถเข้าถึงได้จากภายในนิยามคลาสวิธีที่ไม่ระบุชื่อ อย่าลืมศึกษารหัสนี้อย่างละเอียดเพื่อทำความเข้าใจเรื่องการแตกสาขา นี่อาจเป็นเทคนิคที่ทรงพลังมาก ตัวอย่างเช่นมันสามารถใช้เพื่อผลดีเมื่อลงทะเบียนตัวจัดการใน MiniDOM และในสถานการณ์ทั่วไปมากขึ้น
ในทางตรงกันข้ามผู้รับมอบสิทธิ์สร้างไม่ได้ให้วิธีแก้ปัญหาสำหรับข้อกำหนดทั่วไปนี้เพิ่มเติมและเป็นเช่นนั้นควรถูกปฏิเสธเป็นสำนวนที่สามารถใช้การออกแบบ
ฉันรู้ว่าโพสต์นี้เก่า แต่ Java 8 ได้เพิ่มแลมบ์ดาและแนวคิดของส่วนต่อประสานการทำงานซึ่งเป็นส่วนต่อประสานใด ๆ ที่มีเพียงวิธีเดียว ร่วมกันนำเสนอฟังก์ชั่นที่คล้ายกันกับตัวแทน C # ดูที่นี่สำหรับข้อมูลเพิ่มเติมหรือเพียงแค่ Google Java Lambdas http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html
ไม่ แต่พวกเขาปลอมแปลงโดยใช้พร็อกซีและการสะท้อนภาพ:
public static class TestClass {
public String knockKnock() {
return "who's there?";
}
}
private final TestClass testInstance = new TestClass();
@Test public void
can_delegate_a_single_method_interface_to_an_instance() throws Exception {
Delegator<TestClass, Callable<String>> knockKnockDelegator = Delegator.ofMethod("knockKnock")
.of(TestClass.class)
.to(Callable.class);
Callable<String> callable = knockKnockDelegator.delegateTo(testInstance);
assertThat(callable.call(), is("who's there?"));
}
สิ่งที่ดีเกี่ยวกับสำนวนนี้คือคุณสามารถตรวจสอบได้ว่ามีวิธีการมอบอำนาจและมีลายเซ็นที่จำเป็น ณ จุดที่คุณสร้างผู้มอบหมาย (แม้ว่าจะไม่ใช่ในเวลารวบรวม แต่น่าเสียดายที่แม้ว่าปลั๊กอิน FindBugs อาจ ช่วยได้ที่นี่) จากนั้นใช้อย่างปลอดภัยเพื่อมอบหมายให้อินสแตนซ์ต่างๆ
ดูรหัส karg บน githubสำหรับการทดสอบและการใช้งานเพิ่มเติม
ฉันใช้งานการสนับสนุนการโทรกลับ / ผู้รับมอบสิทธิ์ใน Java โดยใช้การสะท้อนกลับ รายละเอียดและแหล่งที่มาทำงานอยู่ที่มีอยู่บนเว็บไซต์ของฉัน
มีคลาสหลักการชื่อ Callback ที่มีคลาสซ้อนกันชื่อ WithParms API ที่ต้องการการเรียกกลับจะใช้วัตถุโทรกลับเป็นพารามิเตอร์และถ้าจำเป็นให้สร้าง Callback.WithParms เป็นตัวแปรวิธี เนื่องจากแอพพลิเคชั่นมากมายของวัตถุนี้จะถูกเรียกซ้ำการทำงานนี้อย่างหมดจด
ด้วยประสิทธิภาพการทำงานยังคงมีความสำคัญสูงสำหรับฉันฉันไม่ต้องการให้สร้างอาร์เรย์วัตถุแบบโยนทิ้งเพื่อเก็บพารามิเตอร์สำหรับการเรียกใช้ทุกครั้ง - หลังจากทั้งหมดในโครงสร้างข้อมูลขนาดใหญ่อาจมีองค์ประกอบหลายพันรายการและในการประมวลผลข้อความ สถานการณ์สมมติเราสามารถจบการประมวลผลโครงสร้างข้อมูลนับพันได้ในหนึ่งวินาที
เพื่อให้เป็น threadsafe พารามิเตอร์อาร์เรย์จำเป็นต้องมีอยู่โดยเฉพาะสำหรับการเรียกใช้เมธอด API แต่ละครั้งและเพื่อประสิทธิภาพการทำงานควรใช้อันเดียวกันนี้สำหรับการเรียกกลับทุกครั้ง ฉันต้องการวัตถุที่สองซึ่งจะมีราคาถูกในการสร้างเพื่อผูกโทรกลับด้วยอาร์เรย์พารามิเตอร์สำหรับการเรียก แต่ในบางสถานการณ์ผู้เรียกจะมีอาเรย์พารามิเตอร์ด้วยเหตุผลอื่นอยู่แล้ว ด้วยเหตุผลสองประการนี้พารามิเตอร์อาร์เรย์ไม่ได้อยู่ในวัตถุโทรกลับ นอกจากนี้ตัวเลือกการเรียกใช้ (ผ่านพารามิเตอร์เป็นอาร์เรย์หรือเป็นวัตถุแต่ละรายการ) นั้นอยู่ในมือของ API โดยใช้การเรียกกลับเพื่อให้สามารถใช้การเรียกใช้งานที่เหมาะสมที่สุดกับการทำงานภายในของมัน
คลาสที่ซ้อนกัน WithParms นั้นเป็นทางเลือกและให้บริการสองวัตถุประสงค์มันมีอาร์เรย์วัตถุพารามิเตอร์ที่จำเป็นสำหรับการเรียกกลับและมันมี 10 วิธีการเรียกใช้ที่โอเวอร์โหลด () จาก 10 ถึง 10 พารามิเตอร์ซึ่งโหลดอาร์เรย์พารามิเตอร์จากนั้น เรียกใช้เป้าหมายการโทรกลับ
สิ่งต่อไปนี้เป็นตัวอย่างการใช้การเรียกกลับเพื่อประมวลผลไฟล์ในแผนผังไดเรกทอรี นี่เป็นการตรวจสอบความถูกต้องครั้งแรกซึ่งเพิ่งนับจำนวนไฟล์ที่จะดำเนินการและรับรองว่าไม่มีขนาดเกินขนาดสูงสุดที่กำหนดไว้ ในกรณีนี้เราเพิ่งสร้าง callback inline ด้วย API invocation อย่างไรก็ตามเราสะท้อนวิธีการเป้าหมายออกเป็นค่าคงที่เพื่อให้การสะท้อนนั้นไม่ได้ทำทุกครั้ง
static private final Method COUNT =Callback.getMethod(Xxx.class,"callback_count",true,File.class,File.class);
...
IoUtil.processDirectory(root,new Callback(this,COUNT),selector);
...
private void callback_count(File dir, File fil) {
if(fil!=null) { // file is null for processing a directory
fileTotal++;
if(fil.length()>fileSizeLimit) {
throw new Abort("Failed","File size exceeds maximum of "+TextUtil.formatNumber(fileSizeLimit)+" bytes: "+fil);
}
}
progress("Counting",dir,fileTotal);
}
IoUtil.processDirectory ():
/**
* Process a directory using callbacks. To interrupt, the callback must throw an (unchecked) exception.
* Subdirectories are processed only if the selector is null or selects the directories, and are done
* after the files in any given directory. When the callback is invoked for a directory, the file
* argument is null;
* <p>
* The callback signature is:
* <pre> void callback(File dir, File ent);</pre>
* <p>
* @return The number of files processed.
*/
static public int processDirectory(File dir, Callback cbk, FileSelector sel) {
return _processDirectory(dir,new Callback.WithParms(cbk,2),sel);
}
static private int _processDirectory(File dir, Callback.WithParms cbk, FileSelector sel) {
int cnt=0;
if(!dir.isDirectory()) {
if(sel==null || sel.accept(dir)) { cbk.invoke(dir.getParent(),dir); cnt++; }
}
else {
cbk.invoke(dir,(Object[])null);
File[] lst=(sel==null ? dir.listFiles() : dir.listFiles(sel));
if(lst!=null) {
for(int xa=0; xa<lst.length; xa++) {
File ent=lst[xa];
if(!ent.isDirectory()) {
cbk.invoke(dir,ent);
lst[xa]=null;
cnt++;
}
}
for(int xa=0; xa<lst.length; xa++) {
File ent=lst[xa];
if(ent!=null) { cnt+=_processDirectory(ent,cbk,sel); }
}
}
}
return cnt;
}
ตัวอย่างนี้แสดงให้เห็นถึงความสวยงามของวิธีการนี้ - ตรรกะเฉพาะของแอปพลิเคชันนั้นได้ถูกแยกออกเป็น callback และความน่าเบื่อหน่ายในการเดินทรีไดเรกทอรีซ้ำนั้นถูกซ่อนไว้อย่างดีในวิธีการใช้งานแบบคงที่ และเราไม่ต้องจ่ายราคาของการกำหนดและการนำอินเทอร์เฟซสำหรับการใช้งานใหม่ทุกครั้ง แน่นอนข้อโต้แย้งสำหรับอินเทอร์เฟซคือมันชัดเจนมากขึ้นเกี่ยวกับสิ่งที่จะนำไปใช้ (มันถูกบังคับใช้ไม่ใช่แค่เอกสาร) - แต่ในทางปฏิบัติฉันไม่พบว่ามันจะเป็นปัญหาในการทำให้คำจำกัดความการโทรกลับถูกต้อง
การกำหนดและการใช้อินเทอร์เฟซนั้นไม่เลวร้ายนัก (เว้นแต่คุณจะแจกจ่ายแอปเพล็ตอย่างที่ฉันเป็นซึ่งหลีกเลี่ยงการสร้างชั้นเรียนพิเศษจริง ๆ ) แต่ที่นี่ส่องสว่างจริงๆเมื่อคุณมีการโทรกลับหลายครั้ง ไม่เพียง แต่ถูกบังคับให้ผลักพวกเขาแต่ละคนเข้าในชั้นเรียนที่แยกจากกันเพิ่มค่าใช้จ่ายในแอปพลิเคชั่นที่นำไปใช้ แต่มันน่าเบื่ออย่างยิ่งที่จะตั้งโปรแกรมและรหัสหม้อไอน้ำที่เป็นเสียงรบกวน
ใช่ & ไม่ใช่ แต่รูปแบบของผู้ได้รับมอบอำนาจใน Java อาจจะเป็นอย่างนี้ วิดีโอการสอนนี้เกี่ยวกับการแลกเปลี่ยนข้อมูลระหว่างกิจกรรม - ชิ้นส่วนและมีสาระสำคัญของรูปแบบการมอบหมายผู้แทนโดยใช้ส่วนต่อประสาน
ไม่มีdelegate
คำหลักที่ชัดเจนว่า C # แต่คุณสามารถบรรลุคล้ายกันใน Java 8 โดยใช้อินเตอร์เฟซที่ใช้งานได้ (เช่นอินเทอร์เฟซที่มีวิธีการเดียว) และแลมบ์ดา:
private interface SingleFunc {
void printMe();
}
public static void main(String[] args) {
SingleFunc sf = () -> {
System.out.println("Hello, I am a simple single func.");
};
SingleFunc sfComplex = () -> {
System.out.println("Hello, I am a COMPLEX single func.");
};
delegate(sf);
delegate(sfComplex);
}
private static void delegate(SingleFunc f) {
f.printMe();
}
วัตถุประเภทใหม่ทุกประเภทSingleFunc
จะต้องนำมาใช้printMe()
ดังนั้นจึงปลอดภัยที่จะส่งต่อไปยังวิธีอื่น (เช่นdelegate(SingleFunc)
) เพื่อเรียกprintMe()
วิธีการนั้น
ไม่ แต่มันมีพฤติกรรมที่คล้ายกันภายใน
ในผู้รับมอบสิทธิ์ C # จะใช้ในการสร้างจุดเข้าใช้งานแยกจากกันและทำงานเหมือนกับตัวชี้ฟังก์ชัน
ในจาวาไม่มีสิ่งที่เป็นตัวชี้ฟังก์ชั่น (ในรูปลักษณ์บน) แต่ภายในจาวาจำเป็นต้องทำสิ่งเดียวกันเพื่อให้บรรลุวัตถุประสงค์เหล่านี้
ตัวอย่างเช่นการสร้างเธรดใน Java ต้องการคลาสที่ขยายเธรดหรือใช้งาน Runnable เนื่องจากตัวแปรคลาสอ็อบเจ็กต์สามารถใช้เป็นตัวชี้ตำแหน่งหน่วยความจำได้
ไม่จาวาไม่มีคุณสมบัติที่น่าทึ่ง แต่คุณสามารถสร้างมันเองได้โดยใช้รูปแบบผู้สังเกตการณ์ นี่คือตัวอย่าง: เขียนตัวแทน C # ใน java
รหัสที่อธิบายมีข้อได้เปรียบหลายอย่างของผู้ได้รับมอบหมาย C # วิธีการทั้งแบบคงที่หรือแบบไดนามิกสามารถได้รับการปฏิบัติในลักษณะที่สม่ำเสมอ ความซับซ้อนในวิธีการโทรผ่านการสะท้อนกลับลดลงและรหัสนี้สามารถนำมาใช้ซ้ำได้โดยไม่จำเป็นต้องมีคลาสเพิ่มเติมในรหัสผู้ใช้ หมายเหตุเรากำลังเรียกใช้เวอร์ชันที่สะดวกสบายอื่น ๆ ของ invoke โดยที่สามารถเรียกเมธอดที่มีพารามิเตอร์หนึ่งตัวได้โดยไม่ต้องสร้าง object array รหัส Java ด้านล่าง:
class Class1 {
public void show(String s) { System.out.println(s); }
}
class Class2 {
public void display(String s) { System.out.println(s); }
}
// allows static method as well
class Class3 {
public static void staticDisplay(String s) { System.out.println(s); }
}
public class TestDelegate {
public static final Class[] OUTPUT_ARGS = { String.class };
public final Delegator DO_SHOW = new Delegator(OUTPUT_ARGS,Void.TYPE);
public void main(String[] args) {
Delegate[] items = new Delegate[3];
items[0] = DO_SHOW .build(new Class1(),"show,);
items[1] = DO_SHOW.build (new Class2(),"display");
items[2] = DO_SHOW.build(Class3.class, "staticDisplay");
for(int i = 0; i < items.length; i++) {
items[i].invoke("Hello World");
}
}
}
Java ไม่มีตัวแทนและภูมิใจในมัน :) จากสิ่งที่ฉันอ่านที่นี่ฉันพบในสาระสำคัญ 2 วิธีในการปลอมตัวแทน: 1. การสะท้อนกลับ; 2. ชั้นใน
การไตร่ตรองเป็นสิ่งที่พูดเหลวไหล! คลาสภายในไม่ครอบคลุมฟังก์ชั่นการจัดเรียงที่ง่ายที่สุด: ไม่ต้องการที่จะลงรายละเอียด แต่การแก้ปัญหากับชั้นในโดยทั่วไปคือการสร้างคลาส wrapper สำหรับอาร์เรย์ของจำนวนเต็มที่จะเรียงลำดับจากน้อยไปมากและชั้นเรียนสำหรับอาร์เรย์ของจำนวนเต็มจะเรียงลำดับจากมากไปน้อย