نوع Void در جاوا — به زبان ساده

۶۰۷ بازدید
آخرین به‌روزرسانی: ۰۶ شهریور ۱۴۰۲
زمان مطالعه: ۴ دقیقه
نوع Void در جاوا — به زبان ساده

اگر با زبان جاوا آشنا باشید، حتماً تاکنون با نوع Void یا Void Type در بخشی از کدهای جاوا مواجه شده‌اید و شاید کنجکاو بوده‌اید که منظور از این نوع Void در جاوا چیست؟ در این راهنما با این کلاس خاص آشنا می‌شویم و موارد استفاده و چگونگی استفاده از آن را خواهیم دید. همچنین توضیح می‌دهیم که چرا باید تا حد امکان از این نوع استفاده نکنیم.

نوع Void در جاوا چیست؟

نوع Void از JDK نسخه 1.1 به بعد عرضه شده است. هدف از این نوع این است که مقدار بازگشتی void را به صورت یک کلاس نمایندگی کند و شامل یک مقدار عمومی Class<Void>‎ است. این کلاس قابل وهله‌سازی نیست، زیرا تنها سازنده آن خصوصی است.

بنابراین تنها مقداری که می‌توان به یک متغیر Void انتساب داد، مقدار تهی (null) است. در واقع ممکن است این کار کمی بیهوده به نظر برسد، اما در ادامه خواهید دید که چگونه می‌توان از این نوع استفاده کرد.

کاربردها

برخی راه‌حل‌ها وجود دارند که استفاده نوع Void می‌تواند در آن‌ها جالب باشد.

بازتاب

ابتدا می‌توانیم از نوع void در زمان بازتاب استفاده کنیم. در واقع نوع بازگشتی هر متد void با متغیر Void.TYPE که مقدار Class<Void>‎ پیش‌گفته را نگهداری می‌کند، مطابقت خواهد داشت. یک کلاس ماشین حساب ساده مانند زیر را در نظر بگیرید:

1public class Calculator {
2    private int result = 0;
3 
4    public int add(int number) {
5        return result += number;
6    }
7 
8    public int sub(int number) {
9        return result -= number;
10    }
11 
12    public void clear() {
13        result = 0;
14    }
15 
16    public void print() {
17        System.out.println(result);
18    }
19}

برخی متدها مقدار صحیح بازگشت می‌دهند، برخی دیگر چیزی بازگشت نمی‌دهند. اکنون تصور کنید بخواهیم به وسیله بازتاب همه متدهایی که هیچ نتیجه‌ای بازگشت نمی‌دهند را بازیابی کنیم. این کار با استفاده از متغیر Coid.TYPE ممکن است:

1@Test
2void givenCalculator_whenGettingVoidMethodsByReflection_thenOnlyClearAndPrint() {
3    Method[] calculatorMethods = Calculator.class.getDeclaredMethods();
4    List<Method> calculatorVoidMethods = Arrays.stream(calculatorMethods)
5      .filter(method -> method.getReturnType().equals(Void.TYPE))
6      .collect(Collectors.toList());
7 
8    assertThat(calculatorVoidMethods)
9      .allMatch(method -> Arrays.asList("clear", "print").contains(method.getName()));
10}

چنان که می‌بینید، تنها متدهای clear()‎ و ()print بازیابی می‌شوند.

ژنریک‌ها

کاربرد دیگر نوع Void در کلاس‌های ژنریک است. فرض کنید یک متد را فراخوانی می‌کنیم که نیازمند یک پارامتر Callable است:

1public class Defer {
2    public static <V> V defer(Callable<V> callable) throws Exception {
3        return callable.call();
4    }
5}

اما Callable که می‌خواهیم ارسال کنیم چیزی بازگشت نمی‌دهد. از این رو می‌توانیم یک Callable<Void>‎ ارسال کنیم:

1@Test
2void givenVoidCallable_whenDiffer_thenReturnNull() throws Exception {
3    Callable<Void> callable = new Callable<Void>() {
4        @Override
5        public Void call() {
6            System.out.println("Hello!");
7            return null;
8        }
9    };
10 
11    assertThat(Defer.defer(callable)).isNull();
12}

ما می‌توانستیم یا از یک نوع تصادفی (مانند <Callable<Integer) استفاده کنیم و مقدار null بازگشت دهیم و یا کلاً از هیچ نوعی استفاده نکنیم (Callable)، اما استفاده از Void مقصود ما را به روشنی بیان می‌کند. همچنین می‌توانیم این متد را روی لامبداها نیز اعمال کنیم. در واقع Callable ما می‌توانست به صورت یک لامبدا نیز نوشته شود. متدی را تصور کنید که نیازمند یک تابع (Function) است، اما می‌خواهیم از یک تابع استفاده کنیم که چیزی بازگشت نمی‌دهد. در این حالت می‌توانیم کاری کنیم که مقدار Void بازگشت دهد:

1public static <T, R> R defer(Function<T, R> function, T arg) {
2    return function.apply(arg);
3}
1@Test
2void givenVoidFunction_whenDiffer_thenReturnNull() {
3    Function<String, Void> function = s -> {
4        System.out.println("Hello " + s + "!");
5        return null;
6    };
7 
8    assertThat(Defer.defer(function, "World")).isNull();
9}

نوع void در جاوا

چگونه از Void استفاده نکنیم؟

اکنون که با کاربردهای نوع Void آشنا شدیم، باید اعلام کنیم که گرچه کاربرد اول آن کاملاً مناسب است، اما در حد امکان باید از کاربرد نوع Void در ژنریک‌ها خودداری کنیم. در واقع مواجه شدن با یک نوع بازگشتی که نماینده نبودِ نتیجه است و تنها می‌تواند شامل null باشد، کار باطلی محسوب می‌شود.

در ادامه روش جلوگیری از بروز چنین حالت‌هایی را بررسی می‌کنیم. ابتدا متد با پارامتر Callable را تصور کنید. برای جلوگیری از استفاده از Callable<Void>‎ می‌توانیم متد دیگری ارائه کنیم که به جای آن یک پارامتر Runnable می‌گیرد:

1public static void defer(Runnable runnable) {
2    runnable.run();
3}

بنابراین می‌توانیم یک Runnable به آن ارسال کنیم که هیچ مقداری بازگشت نمی‌دهد و از این رو از شر بازگشتی null بی‌استفاده رها می‌شویم:

1Runnable runnable = new Runnable() {
2    @Override
3    public void run() {
4        System.out.println("Hello!");
5    }
6};
7 
8Defer.defer(runnable);

اما در این صورت، اگر کلاس Defer در اختیار ما قرار نداشته باشد تا ویرایش کنیم، باید چه کار کنیم؟ در این حالت یا می‌توانیم همچنان از گزینه <Callable<Void استفاده کنیم و یا کلاس دیگری ایجاد کنیم که یک Runnable می‌گیرد و فراخوانی به کلاس Defer را به تأخیر بیاندازیم:

1public class MyOwnDefer {
2    public static void defer(Runnable runnable) throws Exception {
3        Defer.defer(new Callable<Void>() {
4            @Override
5            public Void call() {
6                runnable.run();
7                return null;
8            }
9        });
10    }
11}

به این ترتیب بخش مشکل‌دار را یک بار برای همیشه کپسوله‌سازی می‌کنیم و به توسعه‌دهندگان بعدی اجازه می‌دهیم که از API ساده‌تری بهره بگیرند. البته همان نتیجه با استفاده از یک تابع نیز قابل حصول است. در مثال مورد بررسی Function هیچ چیزی بازگشت نمی‌دهد و از این رو می‌توانیم متد دیگری به جای آن ارائه کنیم که یک Consumer می‌گیرد:

1public static <T> void defer(Consumer<T> consumer, T arg) {
2    consumer.accept(arg);
3}

در این حالت اگر تابع هیچ پارامتری نگیرد، می‌توانیم یا از یک Runnable استفاده کنیم و یا در صورتی که مناسب باشد، اینترفیس تابعی خودمان را بسازیم:

1public interface Action {
2    void execute();
3}

سپس متد ()defet را بار دیگر overload می‌کنیم.

1public static void defer(Action action) {
2    action.execute();
3}
1Action action = () -> System.out.println("Hello!");
2 
3Defer.defer(action);

سخن پایانی

در این مقاله کوتاه به بررسی کلاس Void در جاوا پرداختیم. دیدیم که هدف از این کلاس چیست و شیوه استفاده از آن چگونه است. همچنین با برخی روش‌های جایگزینی آن نیز آشنا شدیم. کد کامل موارد مطرح‌شده در این مقاله را می‌توانید در این صفحه (+) مشاهده کنید.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۳ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
baeldung
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *