เหตุใดจึงใช้ตัวเลือกใน Java 8+ แทนการตรวจสอบตัวชี้โมฆะแบบดั้งเดิม


110

เราเพิ่งย้ายไปยัง Java 8 ตอนนี้ฉันเห็นแอปพลิเคชันที่เต็มไปด้วยOptionalวัตถุ

ก่อน Java 8 (สไตล์ 1)

Employee employee = employeeServive.getEmployee();

if(employee!=null){
    System.out.println(employee.getId());
}

หลังจาก Java 8 (สไตล์ 2)

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employee = employeeOptional.get();
    System.out.println(employee.getId());
}

ฉันไม่เห็นคุณค่าที่เพิ่มขึ้นของOptional<Employee> employeeOptional = employeeService.getEmployee();เวลาที่บริการกลับมาเป็นตัวเลือก:

มาจากพื้นหลัง Java 6 ฉันเห็นสไตล์ 1 ชัดเจนกว่าและมีโค้ดน้อยกว่า มีข้อได้เปรียบที่แท้จริงที่ฉันหายไปที่นี่หรือไม่?

รวบรวมจากความเข้าใจจากคำตอบทั้งหมดและการวิจัยเพิ่มเติมที่บล็อก


20
ออราเคิลมีบทความมากมายเกี่ยวกับการใช้งานของจำเป็น
Mike Partridge

28
yuck! employeeOptional.isPresent()ดูเหมือนว่าจะขาดประเภทตัวเลือกทั้งหมด ตามความคิดเห็นของ @ MikePartridge นี่ไม่แนะนำหรือเป็นไปในทางที่ผิด อย่างชัดเจนตรวจสอบว่าประเภทตัวเลือกบางสิ่งบางอย่างหรืออะไรคือไม่มีไม่มี คุณควรจะง่ายmapหรือflatMapมากกว่าพวกเขา
Andres F.

7
ดูกองมากเกินคำตอบบนโดยไบรอันเก๊ที่สถาปนิก Java Languageที่ Oracle Optional
Basil Bourque

6
นี่คือสิ่งที่เกิดขึ้นเมื่อคุณปิดสมองในชื่อ "แนวทางปฏิบัติที่ดีที่สุด"
immibis

9
นั่นเป็นการใช้ทางเลือกที่ไม่ดีนัก แต่ก็มีประโยชน์ ด้วย nulls ทุกอย่างอาจเป็นโมฆะดังนั้นคุณจำเป็นต้องตรวจสอบอย่างต่อเนื่องเลือกตัวเลือกว่าตัวแปรนี้น่าจะหายไปให้แน่ใจว่าคุณตรวจสอบมัน
Richard Tingle

คำตอบ:


108

สไตล์ 2 ไม่เพียงพอสำหรับ Java 8 เพื่อให้เห็นประโยชน์เต็มที่ คุณไม่ต้องการif ... useเลย ดูตัวอย่างของออราเคิล เราได้รับคำแนะนำจากพวกเขา

สไตล์ 3

// Changed EmployeeServive to return an optional, no more nulls!
Optional<Employee> employee = employeeServive.getEmployee();
employee.ifPresent(e -> System.out.println(e.getId()));

หรือตัวอย่างข้อมูลที่ยาวขึ้น

Optional<Employee> employee = employeeServive.getEmployee();
// Sometimes an Employee has forgotten to write an up-to-date timesheet
Optional<Timesheet> timesheet = employee.flatMap(Employee::askForCurrentTimesheet); 
// We don't want to do the heavyweight action of creating a new estimate if it will just be discarded
client.bill(timesheet.orElseGet(EstimatedTimesheet::new));

19
แน่นอนว่าเราห้ามการใช้งานส่วนใหญ่ของ isPresent (จับโดยการตรวจสอบรหัส)
jk

10
@jk จริง ๆ ถ้ามีมากเกินไปvoid ifPresent(Consumer<T>, Runnable)ฉันจะโต้เถียงสำหรับการห้ามทั้งหมดisPresentและget
Caleth

10
@Caleth: คุณอาจจะสนใจใน Java ifPresentOrElse(Consumer<? super T>, Runnable)9
wchargin

9
ฉันพบข้อเสียเปรียบหนึ่งในรูปแบบ java 8 นี้เปรียบเทียบกับ java 6 one: มันเป็นเครื่องมือครอบคลุมรหัสของคนโง่ ด้วย if statement จะแสดงเมื่อคุณพลาดสาขาไม่ใช่ที่นี่
ธีรี่ร์

14
@Aaron "ลดความสามารถในการอ่านรหัสลงอย่างมาก" ส่วนใดที่ลดความสามารถในการอ่านได้อย่างแน่นอน ฟังดูเหมือน "ฉันเคยทำมันแตกต่างกันไปและฉันไม่ต้องการเปลี่ยนนิสัยของฉัน" มากกว่าการให้เหตุผลอย่างมีเหตุผล
Voo

47

หากคุณใช้Optionalเป็นเลเยอร์ "ความเข้ากันได้" ระหว่าง API ที่เก่ากว่าซึ่งอาจยังส่งคืนnullอาจเป็นประโยชน์ในการสร้างตัวเลือก (ไม่ว่างเปล่า) ในขั้นตอนล่าสุดที่คุณมั่นใจว่ามี เช่นที่คุณเขียน:

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

ฉันเลือกที่จะ:

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

ประเด็นก็คือว่าคุณรู้ว่ามีพนักงานที่ไม่ใช่ null บริการเพื่อให้คุณสามารถห่อที่ขึ้นมาในด้วยOptional Optional.of()จากนั้นเมื่อคุณโทรหาgetEmployee()คุณอาจจะได้หรือไม่รับพนักงานก็ได้ พนักงานคนนั้นอาจ (หรืออาจจะไม่) มี ID จากนั้นหากคุณจบด้วย ID คุณต้องการพิมพ์

ไม่จำเป็นต้องตรวจสอบnull ใด ๆ การมีอยู่ ฯลฯ ในรหัสนี้อย่างชัดเจน


4
ประโยชน์ของการใช้(...).ifPresent(aMethod)เกินif((...).isPresent()) { aMethod(...); }คืออะไร? ดูเหมือนว่าจะเป็นเพียงอีกรูปแบบหนึ่งที่เกือบจะเหมือนกัน
Ruslan

2
@Ruslan เหตุใดจึงต้องใช้ไวยากรณ์พิเศษหนึ่งรายการสำหรับการโทรหนึ่งครั้งเมื่อคุณมีโซลูชันทั่วไปที่ทำงานได้ดีเท่ากันทุกกรณี เราแค่ใช้แนวคิดเดียวกันจากแผนที่และร่วมที่นี่เช่นกัน
Voo

1
@Ruslan ... API คืนค่า null แทนการรวบรวมหรืออาร์เรย์ว่างเปล่า เช่นคุณสามารถทำสิ่งที่ชอบ (สมมติว่าชั้นผู้ปกครองที่ getChildren () ผลตอบแทนชุดของเด็กหรือ null ถ้ามีเด็กไม่ได้): Set<Children> children = Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet); ตอนนี้ผมสามารถประมวลผลได้เหมือนกันเช่นchildren children.forEach(relative::sendGift);อาจรวมกันได้: Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet).forEach(relative::sendGift);. สำเร็จรูปทั้งหมดของการตรวจสอบค่า null ฯลฯ ...
Joshua Taylor

1
@Ruslan ... มอบสิทธิ์ให้ลองแล้วเป็นรหัสจริงในไลบรารีมาตรฐาน ที่ช่วยลดปริมาณของรหัสที่ผมเป็นโปรแกรมเมอร์ต้องเขียนทดสอบการแก้ปัญหา ฯลฯ
โจชัวเทย์เลอร์

1
ดีใจที่ฉันใช้สวิฟท์ ถ้าให้พนักงาน = employeeService.employee {พิมพ์ (employee.Id); }
gnasher729

23

ถัดจากไม่มีค่าเพิ่มในการOptionalมีค่าเดียว ดังที่คุณเห็นนั่นเป็นเพียงการแทนที่เช็คnullด้วยการตรวจสอบสถานะ

มีมูลค่าเพิ่มมากในการมีCollectionสิ่งนั้น ทุกวิธีการสตรีมมิ่งนำไปแทนที่แบบเก่าลูปในระดับต่ำเข้าใจOptionalสิ่งต่าง ๆ และทำในสิ่งที่ถูกต้องเกี่ยวกับพวกเขา (ไม่ประมวลผลพวกเขาหรือท้ายที่สุดกลับไม่มีการตั้งค่าอื่นOptional) หากคุณจัดการกับรายการnบ่อยกว่ารายการแต่ละรายการ (และ 99% ของรายการที่เหมือนจริงทั้งหมด) มันเป็นการปรับปรุงครั้งใหญ่ที่มีการสนับสนุนในตัวสำหรับการนำเสนอทางเลือก ในกรณีที่เหมาะสมที่สุดคุณไม่จำเป็นต้องตรวจสอบnull หรือแสดงตน


24
นอกเหนือจากคอลเลกชัน / ลำธารตัวเลือกยังเป็นที่ดีที่จะทำให้ APIs มากขึ้นเอกสารด้วยตนเองเช่นVSEmployee getEmployee() Optional<Employee> getEmployee()ในขณะที่ทั้งสองทางเทคนิคสามารถกลับมาnullหนึ่งในนั้นมีแนวโน้มที่จะก่อให้เกิดปัญหา
amon

16
มีค่ามากมายในตัวเลือกของค่าเดียว ความสามารถในการ.map()โดยไม่ต้องตรวจสอบ null ทั้งหมดตลอดทางดีมาก เช่นOptional.of(service).map(Service::getEmployee).map(Employee::getId).ifPresent(this::submitIdForReview);.
Joshua Taylor

9
ฉันไม่เห็นด้วยกับความคิดเห็นเริ่มต้นของคุณไม่มีค่าเพิ่ม ตัวเลือกให้บริบทกับรหัสของคุณ ภาพรวมอย่างรวดเร็วสามารถบอกคุณถูกต้องถึงความถูกต้องของฟังก์ชั่นและครอบคลุมทุกกรณี ในขณะที่มีการตรวจสอบว่างเปล่าคุณควรตรวจสอบ null ทุกประเภทอ้างอิงในทุกวิธี? กรณีที่คล้ายกันสามารถใช้กับคลาสและตัวดัดแปลงสาธารณะ / ส่วนตัว พวกเขาเพิ่มคุณค่าเป็นศูนย์ในรหัสของคุณใช่ไหม
ArTs

3
@JoshuaTaylor สุจริตเมื่อเทียบกับที่nullแสดงออกผมชอบการตรวจสอบที่ล้าสมัยสำหรับ
Kilian Foth

2
ข้อได้เปรียบที่สำคัญอีกข้อหนึ่งของทางเลือกแม้ว่าจะมีค่าเดียวก็คือคุณไม่สามารถลืมตรวจสอบค่าว่างเมื่อคุณควร มิฉะนั้นจะง่ายมากที่จะลืมเช็คและจบด้วย NullPointerException ...
Sean Burton

17

ตราบใดที่คุณใช้Optionalเช่นเดียวกับ API แฟนซีสำหรับisNotNull()แล้วใช่คุณจะไม่พบความแตกต่างใด ๆ nullมีเพียงการตรวจสอบ

สิ่งที่คุณไม่ควรใช้Optionalสำหรับ

การใช้Optionalเพียงเพื่อตรวจสอบว่ามีค่าอยู่หรือไม่นั่นคือ Bad Code ™:

// BAD CODE ™ --  just check getEmployee() != null  
Optional<Employee> employeeOptional =  Optional.ofNullable(employeeService.getEmployee());  
if(employeeOptional.isPresent()) {  
    Employee employee = employeeOptional.get();  
    System.out.println(employee.getId());
}  

สิ่งที่คุณควรใช้Optionalสำหรับ

หลีกเลี่ยงค่าที่ไม่มีอยู่โดยสร้างค่าเมื่อจำเป็นเมื่อใช้เมธอด null-return API:

Employee employee = Optional.ofNullable(employeeService.getEmployee())
                            .orElseGet(Employee::new);
System.out.println(employee.getId());

หรือถ้าการสร้างพนักงานใหม่มีค่าใช้จ่ายสูงเกินไป:

Optional<Employee> employee = Optional.ofNullable(employeeService.getEmployee());
System.out.println(employee.map(Employee::getId).orElse("No employee found"));

นอกจากนี้การทำให้ทุกคนทราบว่าวิธีการของคุณอาจไม่ส่งคืนค่า (ถ้าด้วยเหตุผลใดก็ตามคุณไม่สามารถส่งคืนค่าเริ่มต้นเหมือนด้านบน):

// Your code without Optional
public Employee getEmployee() {
    return someCondition ? null : someEmployee;
}
// Someone else's code
Employee employee = getEmployee(); // compiler doesn't complain
// employee.get...() -> NPE awaiting to happen, devs criticizing your code

// Your code with Optional
public Optional<Employee> getEmployee() {
    return someCondition ? Optional.empty() : Optional.of(someEmployee);
}
// Someone else's code
Employee employee = getEmployee(); // compiler complains about incompatible types
// Now devs either declare Optional<Employee>, or just do employee = getEmployee().get(), but do so consciously -- not your fault.

และในที่สุดก็มีทุกวิธีการสตรีมเช่นเดียวกับที่ได้รับการอธิบายในคำตอบอื่น ๆ แต่ผู้ที่ไม่ได้จริงๆคุณตัดสินใจที่จะใช้Optionalแต่ทำให้การใช้งานของOptionalการให้บริการโดยคนอื่น


1
@Kapep แน่นอน เรามีการถกเถียงกันเรื่องนี้ที่ / r / java และฉันพูดว่า : «ฉันเห็นOptionalว่าเป็นโอกาสที่ไม่ได้รับ "ที่นี่มีOptionalสิ่งใหม่ที่ฉันทำเพื่อให้คุณถูกบังคับให้ตรวจสอบค่าว่างโอ้และโดยวิธี ... ฉันเพิ่มget()วิธีการเพื่อให้คุณไม่ต้องตรวจสอบอะไรจริง ๆ ;);)" . ( ... ) Optionalเป็นสิ่งที่ดีเป็น "นี้อาจจะเป็นโมฆะ" ป้ายไฟนีออน แต่การดำรงอยู่ที่เปลือยเปล่าของget()วิธีอ่อนเปลี้ยมัน» แต่ OP ได้ขอเหตุผลที่จะใช้Optionalไม่ใช่เหตุผลที่ผิดพลาดหรือข้อบกพร่องในการออกแบบ
walen

1
Employee employee = Optional.ofNullable(employeeService.getEmployee());ในข้อมูลโค้ดที่สามของคุณประเภทนั้นควรเป็นOptional<Employee>อย่างไร
Charlie Harding

2
@immibis พิการอะไร? เหตุผลหลักที่ใช้getคือถ้าคุณต้องโต้ตอบกับรหัสดั้งเดิม getฉันไม่สามารถคิดเหตุผลที่ถูกต้องจำนวนมากในรหัสใหม่ที่เคยใช้ ที่กล่าวว่าเมื่อโต้ตอบกับรหัสดั้งเดิมมักจะไม่มีทางเลือกอื่น แต่ยังคง walen มีจุดที่มีอยู่ของgetสาเหตุเริ่มต้นในการเขียนรหัสไม่ดี
Voo

1
@immibis ฉันไม่ได้พูดถึงMapครั้งเดียวและฉันไม่ได้ตระหนักถึงใครก็ตามที่ทำการเปลี่ยนแปลงที่เข้ากันได้อย่างรุนแรงแบบไม่ถอยหลังดังนั้นคุณแน่ใจว่าคุณสามารถออกแบบ API ที่ไม่ดีด้วยความตั้งใจOptionalได้ - ไม่แน่ใจว่าอะไรเป็นข้อพิสูจน์ Optionalจะทำให้ความรู้สึกบางtryGetวิธีแม้ว่า
Voo

2
@Voo Mapเป็นตัวอย่างที่ทุกคนคุ้นเคย แน่นอนว่าพวกเขาไม่สามารถMap.getคืนค่าเป็นตัวเลือกได้เนื่องจากความเข้ากันได้แบบย้อนหลัง แต่คุณสามารถจินตนาการถึงคลาสที่คล้ายกับแผนที่ที่ไม่มีข้อ จำกัด ด้านความเข้ากันได้ดังกล่าว class UserRepository {Optional<User> getUserDetails(int userID) {...}}พูด ตอนนี้คุณต้องการรับรายละเอียดของผู้ใช้ที่เข้าสู่ระบบและคุณรู้ว่าพวกเขามีอยู่เพราะพวกเขาจัดการเพื่อเข้าสู่ระบบและระบบของคุณจะไม่ลบผู้ใช้ theUserRepository.getUserDetails(loggedInUserID).get()ดังนั้นคุณจะทำอย่างไร
immibis

12

จุดสำคัญสำหรับฉันในการใช้ตัวเลือกเป็นที่ชัดเจนเมื่อนักพัฒนาต้องการตรวจสอบว่าค่าส่งคืนฟังก์ชั่นหรือไม่

//service 1
Optional<Employee> employeeOptional = employeeService.getEmployee();
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

//service 2
Employee employee = employeeService.getEmployeeXTPO();
System.out.println(employee.getId());

8
มันทำให้ฉันงุนงงว่าสิ่งต่าง ๆ ที่ใช้งานได้ดีพร้อมตัวเลือกถึงแม้ว่าจะดีจริง ๆ ถือว่าสำคัญกว่าที่นี่ตรงนี้ ก่อนจาวา 8 คุณต้องขุดผ่าน Javadoc เพื่อทราบว่าคุณต้องตรวจสอบคำตอบจากการโทรหรือไม่ โพสต์ java 8, คุณรู้จากชนิดส่งคืนถ้าคุณได้รับ "nothing" back หรือไม่
Buhb

3
มีปัญหา "รหัสเก่า" ที่สิ่งต่าง ๆ ยังสามารถคืนค่าเป็นโมฆะ ... แต่ใช่การบอกจากลายเซ็นนั้นยอดเยี่ยม
Haakon Løtveit

5
ใช่มันทำงานได้ดีขึ้นในภาษาที่ตัวเลือก <T> เป็นวิธีเดียวที่จะแสดงว่าค่าอาจหายไปเช่นภาษาที่ไม่มี null ptr
dureuill

8

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

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

แน่นอนคำถามอย่างรวดเร็วกลายเป็น "ทำไมเป็นปัจจุบันและไปถึงที่นั่น?"

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

แต่ให้ประโยชน์สองสามอย่าง (สอง) ดียอดเยี่ยมและมีเสน่ห์ (?):

  • มันง่ายต่อการเปลี่ยนรหัสดั้งเดิมเพื่อใช้คุณสมบัติใหม่
  • มันลดความโค้งการเรียนรู้ของตัวเลือก

คนแรกค่อนข้างง่าย

ลองนึกภาพคุณมี API ที่มีลักษณะดังนี้:

public interface SnickersCounter {
  /** 
   * Provides a proper count of how many snickers have been consumed in total.
   */
  public SnickersCount howManySnickersHaveBeenEaten();

  /**
    * returns the last snickers eaten.<br>
    * If no snickers have been eaten null is returned for contrived reasons.
    */
  public Snickers lastConsumedSnickers();
}

และคุณมีชั้นเรียนดั้งเดิมที่ใช้สิ่งนี้ (กรอกข้อมูลในช่องว่าง):

Snickers lastSnickers = snickersCounter.lastConsumedSnickers();
if(null == lastSnickers) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers);
}

ตัวอย่างที่วางแผนไว้เพื่อให้แน่ใจ แต่ทนกับฉันที่นี่

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

นี่คือที่เป็นปัจจุบันส่องสว่าง

เพราะตอนนี้: Snickers lastSnickers = snickersCounter.lastConsumedSnickers (); if (null == lastSnickers) {โยน NoSuchSnickersException ใหม่ (); } else {consumer.giveDiabetes (lastSnickers); }

กลายเป็น:

Optional<Snickers> lastSnickers = snickersCounter.lastConsumedSnickers();
if(!lastSnickers.isPresent()) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers.get());
}

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

โปรดสังเกตว่านี่หมายความว่าวิธีที่คุณทำคือวิธีจัดการกับรหัสดั้งเดิมโดยไม่ต้องทำการเขียนใหม่ที่มีราคาแพง แล้วรหัสใหม่ล่ะ?

ในกรณีของคุณที่คุณต้องการพิมพ์บางสิ่งออกไปคุณก็ทำได้:

. snickersCounter.lastConsumedSnickers () ifPresent (System.out :: println);

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

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

หากคุณต้องการใช้ทางเลือกเป็นการตรวจสอบความปลอดภัยแบบโมฆะแบบง่าย ๆ สิ่งที่คุณควรทำคือ:

new Optional.ofNullable(employeeServive.getEmployee())
    .map(Employee::getId)
    .ifPresent(System.out::println);

แน่นอนว่าเวอร์ชั่นที่ดูดีในลักษณะนี้:

employeeService.getEmployee()
    .map(Employee::getId)
    .ifPresent(System.out::println);

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

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


1
ปัญหาที่นี่คือคุณพยายามทำให้มันดูเหมือนว่าฟังก์ชั่นการใช้งานเหล่านี้สามารถอ่านได้เหมือนกับเวอร์ชั่นที่เก่ากว่าในกรณีหนึ่งแม้กระทั่งการพูดถึงตัวอย่างคือ "ชัดเจนที่สุด" ไม่เป็นเช่นนั้น ตัวอย่างนั้นชัดเจน แต่ไม่สมบูรณ์นักเนื่องจากต้องใช้ความคิดมากกว่า map(x).ifPresent(f)ก็ไม่ได้ทำให้ชนิดเดียวกันของความรู้สึกที่ใช้งานง่ายที่if(o != null) f(x)จะ ifรุ่นเป็นที่ชัดเจนอย่างสมบูรณ์แบบ นี่เป็นอีกกรณีหนึ่งของคนที่กระโดดบนรถเกวียนที่มีชื่อเสียง* ไม่ใช่ *เป็นกรณีของความก้าวหน้าที่แท้จริง
แอรอน

1
Optionalโปรดทราบว่าผมไม่ได้บอกว่าเป็นศูนย์ได้รับประโยชน์ในการใช้งานของโปรแกรมการทำงานหรือของ ผมหมายเฉพาะกับการใช้ตัวเลือกในกรณีนี้โดยเฉพาะและอื่น ๆ ifเพื่อที่จะให้สามารถอ่านได้เนื่องจากหลายคนจะทำหน้าที่เหมือนรูปแบบนี้จะเท่าเทียมกันหรืออ่านได้มากขึ้นกว่า
แอรอน

1
มันขึ้นอยู่กับแนวคิดเรื่องความชัดเจนของเรา ในขณะที่คุณทำแต้มได้ดีมากฉันอยากจะชี้ให้เห็นว่าบางครั้งลูกแกะตัวใหม่และแปลกใหม่ในบางจุด ฉันอยากเดิมพันคุกกี้อินเทอร์เน็ตที่มีคนจำนวนน้อยที่พูดว่า isPresent และรับและรู้สึกผ่อนคลายที่น่ายินดี: ตอนนี้พวกเขาสามารถปรับปรุงรหัสดั้งเดิมได้แล้วเรียนรู้ไวยากรณ์แลมบ์ดาในภายหลัง ในทางกลับกันคนที่มี Lisp, Haskell หรือประสบการณ์ทางภาษาที่คล้ายคลึงกันมีความยินดีที่ตอนนี้พวกเขาสามารถใช้รูปแบบที่คุ้นเคยกับปัญหาของพวกเขาใน java ...
Haakon Løtveit

@Aaron - ความชัดเจนไม่ใช่จุดของประเภทตัวเลือก ฉันเองพบว่าพวกเขาไม่ลดความคมชัดและมีบางกรณี (แม้ว่าไม่ใช่กรณีนี้) ที่พวกเขาเพิ่มขึ้น แต่ผลประโยชน์ที่สำคัญคือ (ตราบเท่าที่คุณหลีกเลี่ยงการใช้isPresentและgetมากที่สุดเท่าที่เป็นไปได้) พวกเขา ปรับปรุงความปลอดภัย มันยากที่จะเขียนโค้ดไม่ถูกต้องที่ล้มเหลวในการตรวจสอบกรณีที่ไม่มีค่าถ้าประเภทคือOptional<Whatever>แทนที่จะใช้ Whatevernullable
จูลส์

แม้ว่าคุณจะนำค่าออกมาทันทีตราบใดที่คุณไม่ได้ใช้งานgetคุณก็จำเป็นต้องเลือกว่าจะทำอย่างไรเมื่อเกิดค่าที่ขาดหายไป: คุณอาจใช้orElse()(หรือorElseGet()) เพื่อกำหนดค่าเริ่มต้นหรือorElseThrow()เพื่อเลือก ข้อยกเว้นที่บันทึกลักษณะของปัญหาซึ่งดีกว่า NPE ทั่วไปที่ไม่มีความหมายจนกว่าคุณจะขุดลงในการติดตามสแต็ก
จูลส์

0

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

สไตล์ที่ดีกว่าก็คือถ้า employeeServive.getEmployee () จะส่งกลับทางเลือกและจากนั้นรหัสก็จะกลายเป็น:

Optional<Employee> employeeOptional = employeeServive.getEmployee();
if(employeeOptional.isPresent()){
    Employee employee= employeeOptional.get();
    System.out.println(employee.getId());
}

วิธีนี้คุณจะไม่สามารถ callemployeeServive.getEmployee (). getId () และคุณสังเกตเห็นว่าคุณควรจัดการกับตัวเลือก valoue อย่างใด และถ้าในวิธีการ codebase ทั้งหมดของคุณจะไม่ส่งคืนค่าว่าง (หรือเกือบจะไม่เคยเป็นที่รู้จักกันดีในทีมของคุณยกเว้นกฎนี้) มันจะเพิ่มความปลอดภัยจาก NPE


12
isPresent()ตัวเลือกจะกลายเป็นภาวะแทรกซ้อนที่ไม่มีจุดหมายช่วงเวลาที่คุณใช้ เราใช้ตัวเลือกเพื่อให้เราไม่ต้องทดสอบ
candied_orange

2
เช่นเดียวกับคนอื่น ๆ isPresent()ได้กล่าวว่าไม่ได้ใช้ นี่เป็นวิธีการที่ไม่ถูกต้องในการเลือกใช้ ใช้mapหรือflatMapแทน
Andres F.

1
@CandiedOrange และคำตอบที่ได้รับคะแนนสูงสุด (บอกว่าใช้ifPresent) เป็นเพียงอีกวิธีในการทดสอบ
immibis

1
@CandiedOrange เป็นเพียงเท่าของการทดสอบเป็นifPresent(x) if(present) {x}ค่าส่งคืนไม่เกี่ยวข้อง
immibis

1
@CandiedOrange อย่างไรก็ตามตอนแรกคุณพูดว่า "ดังนั้นเราไม่ต้องทดสอบ" คุณไม่สามารถพูดว่า "ดังนั้นเราไม่จำเป็นต้องทดสอบ" เมื่อคุณอยู่ในความเป็นจริงยังคงทดสอบ ... ที่คุณเป็น
แอรอน

0

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

ในรหัสที่ฉันเห็นมีการตรวจสอบโมฆะที่จะไม่ล้มเหลวเนื่องจากคนไม่ต้องการติดตามผ่านรหัสเพื่อตรวจสอบว่าเป็นโมฆะเป็นไปได้ดังนั้นพวกเขาโยนตรวจสอบโมฆะทุกที่ สิ่งนี้ทำให้โค้ดช้าลงเล็กน้อย แต่ที่สำคัญกว่านั้นคือทำให้โค้ดมีเสียงดังมากขึ้น

อย่างไรก็ตามเพื่อให้สามารถใช้งานได้คุณต้องใช้รูปแบบนี้อย่างสม่ำเสมอ


1
วิธีที่ง่ายกว่าในการบรรลุเป้าหมายนี้คือการใช้@Nullableและ@ReturnValuesAreNonnullByDefaultร่วมกับการวิเคราะห์แบบคงที่
maaartinus

@maaartinus การเพิ่มเครื่องมือเพิ่มเติมในรูปแบบของการวิเคราะห์แบบคงที่และเอกสารประกอบในคำอธิบายประกอบมัณฑนากรแล้วจะง่ายกว่าในการรวบรวมเวลา API สนับสนุน?
เลื่อน

การเพิ่มเครื่องมือเพิ่มเติมนั้นเป็นเรื่องง่ายกว่าเนื่องจากไม่ต้องมีการเปลี่ยนแปลงรหัส นอกจากนี้คุณยังต้องการที่จะมีข้อบกพร่องอื่น ๆ เช่นกัน +++ ผมเขียนสคริปต์เล็กน้อยเพิ่มpackage-info.javaกับ@ReturnValuesAreNonnullByDefaultแพคเกจของฉันทั้งหมดดังนั้นสิ่งที่ฉันต้องทำคือการเพิ่มที่คุณต้องเปลี่ยนไป@Nullable Optionalซึ่งหมายถึงตัวอักษรที่น้อยลงไม่มีการเพิ่มขยะและไม่มีค่าใช้จ่ายรันไทม์ ฉันไม่ต้องค้นหาเอกสารตามที่แสดงเมื่อวางเมาส์เหนือวิธีการ เครื่องมือวิเคราะห์แบบสแตติกจะตรวจจับข้อผิดพลาดและการเปลี่ยนแปลงความไม่แน่นอนในภายหลัง
maaartinus

ความกังวลที่ใหญ่ที่สุดของฉันOptionalคือมันเพิ่มทางเลือกในการจัดการกับความไม่แน่นอน ถ้ามันเป็นจาวาตั้งแต่เริ่มต้นมันอาจจะใช้ได้ แต่ตอนนี้มันแค่เจ็บปวด การไปตามทาง Kotlin จะดีกว่ามาก IMHO +++ โปรดทราบว่าList<@Nullable String>ยังคงเป็นรายการสตริงขณะที่List<Optional<String>>ไม่อยู่
maaartinus

0

คำตอบของฉันคือ: ไม่ได้ อย่างน้อยก็คิดให้ดีว่ามันเป็นการปรับปรุงหรือไม่

การแสดงออก (นำมาจากคำตอบอื่น) เช่น

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

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

มันดูดีกว่าแบบเก่า

Employee employee = employeeService.getEmployee();
if (employee != null) {
    ID id = employee.getId();
    if (id != null) {
        System.out.println(id);
    }
}

ซึ่งทำให้เห็นได้ชัดว่าอาจมีส่วนelseคำสั่ง

สไตล์การใช้งาน

  • ดูแตกต่างไปจากเดิมอย่างสิ้นเชิง (และไม่สามารถอ่านได้สำหรับคนที่ไม่ได้ใช้)
  • เป็นความเจ็บปวดในการแก้ปัญหา
  • ไม่สามารถขยายได้อย่างง่ายดายสำหรับการจัดการกรณี "ขาด" ( orElseไม่เพียงพอเสมอ)
  • ทำให้เข้าใจผิดในการเพิกเฉยกรณี "ขาด"

ไม่ควรใช้เป็นยาครอบจักรวาล ถ้ามันใช้ได้ในระดับสากลดังนั้นความหมายของ.ผู้ปฏิบัติงานควรถูกแทนที่ด้วยความหมายของ?.ผู้ปฏิบัติงาน NPE ที่ถูกลืมและปัญหาทั้งหมดถูกเพิกเฉย


ในขณะที่เป็นแฟนชวาตัวยงฉันต้องบอกว่าสไตล์การใช้งานเป็นเพียงคำสั่งที่ไม่ดีของชายชวา

employeeService
.getEmployee()
?.getId()
?.apply(id => System.out.println(id));

รู้จักกันดีจากภาษาอื่น ๆ เราเห็นด้วยไหมว่าข้อความนี้

  • มันใช้งานไม่ได้จริงเหรอ?
  • คล้ายกับรหัสแบบเก่าง่ายกว่านี้มากเพียงใด
  • สามารถอ่านได้มากกว่าสไตล์การใช้งานหรือไม่
  • เป็นมิตรกับการดีบั๊กหรือไม่

ดูเหมือนว่าคุณจะอธิบาย C # 's การดำเนินงานของแนวคิดนี้มีคุณสมบัติภาษาที่แตกต่างกันอย่างสิ้นเชิงของ Java การดำเนินงานที่มีระดับห้องสมุดหลัก พวกเขาดูเหมือนฉัน
Caleth

@Caleth ไม่มีทาง เราสามารถพูดเกี่ยวกับ "แนวคิดของการทำให้การประเมินเป็นโมฆะง่ายขึ้น" แต่ฉันจะไม่เรียกมันว่า "แนวคิด" เราสามารถพูดได้ว่า Java ใช้สิ่งเดียวกันโดยใช้คลาสไลบรารีหลัก แต่มันก็เป็นระเบียบ เพียงแค่เริ่มต้นด้วยemployeeService.getEmployee().getId().apply(id => System.out.println(id));และทำให้มัน null Optionalปลอดภัยครั้งเดียวโดยใช้ผู้ประกอบการนำทางที่ปลอดภัยและกันโดยใช้ แน่นอนว่าเราสามารถเรียกมันว่า "รายละเอียดการใช้งาน" แต่เป็นเรื่องของการใช้งาน มีหลายกรณีที่ lamdas สร้างรหัสให้ง่ายขึ้นและดีขึ้น แต่ที่นี่เป็นการละเมิดที่น่าเกลียด
maaartinus

ระดับห้องสมุดคุณลักษณะภาษา:Optional.of(employeeService).map(EmployeeService::getEmployee).map(Employee::getId).ifPresent(System.out::println); employeeService.getEmployee()?.getId()?.apply(id => System.out.println(id));ไวยากรณ์สองรายการ แต่ท้ายที่สุดแล้วพวกเขาก็ดูคล้ายกันมากกับฉัน และ "แนวคิด" นั้นเป็นที่Optionalรู้จักNullableกันดีMaybe
Caleth

ฉันไม่เข้าใจว่าทำไมคุณคิดว่าหนึ่งในนั้นคือ "การละเมิดที่น่าเกลียด" และสิ่งอื่น ๆ ฉันจะเข้าใจว่าคุณคิดทั้ง "การละเมิดน่าเกลียด" หรือทั้งสองอย่างดี
Caleth

@Caleth หนึ่งความแตกต่างคือแทนที่จะเพิ่มเครื่องหมายคำถามสองข้อคุณต้องเขียนใหม่ทั้งหมด ปัญหาไม่ใช่ว่าคุณสมบัติภาษาและรหัสคลาสห้องสมุดแตกต่างกันมาก ไม่เป็นไร. ปัญหาคือรหัสต้นฉบับและรหัสคลาสห้องสมุดแตกต่างกันมาก แนวคิดเดียวกันรหัสที่แตกต่าง มันแย่มาก +++สิ่งที่ถูกทารุณกรรมคือ lamdas สำหรับการจัดการกับความบกพร่อง ลำดาสเป็นเรื่องดี แต่Optionalไม่ใช่ Nullability นั้นต้องการการสนับสนุนทางภาษาที่เหมาะสมเช่นเดียวกับใน Kotlin ปัญหาอื่น: List<Optional<String>>ไม่เกี่ยวข้องกับList<String>ที่เราต้องการให้พวกเขาเกี่ยวข้อง
maaartinus

0

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.