صادق خانزادی
صادق خانزادی
خواندن ۱۴ دقیقه·۳ ماه پیش

Java Collections - Everything You MUST Know

در این مقاله فرض میکنیم که شما اطلاعات حدودی از Java Collections دارید.

این سلسله مقالات که برای تکمیل مقاله Java Collections - Everything You MUST Know نوشته شده برای بهبود و درک بهتر collectionها و مواردی که به طور روزمره دارید استفاده میکنید نوشته شده لطفا تمام این دسته بندی رو برای درک بهتر مطالعه کنید .

Mutable VS Immutable in java

StringBuilder vs StringBuffer vs String

Integer vs int

HashMap vs TreeMap vs Set

Heap And Stack

Memory Heap vs Memory Leak

Integer Constant Pool

Integer Pool vs String Pool

Ternary Operator

Autoboxing

توی این آموزش میخوایم راجب Collection ها در جاوا صحبت کنیم و تا جایی که میتونیم بررسیش کنیم و هرچی نکته بلدیم رو بیاریم .

چنتا سوال هم اون آخر میگم خیلی باحاله توی مصاحبه ها هم میاد.

همین اول کار بگم هرچی بلد بودم رو آوردم و اگر نکته جالبی میدونید بگید که اضافه کنم .


نمای کلی Java Collections
نمای کلی Java Collections

نگاه کلی :

مجموعه‌ها در جاوا (Java Collections) ساختارهای داده‌ای هستند که برای ذخیره، سازماندهی و مدیریت گروهی از اشیاء استفاده می‌شوند. این مجموعه‌ها به شما اجازه می‌دهند تا به جای کار با تک تک اشیاء، با کل مجموعه به عنوان یک واحد کار کنید.

چرا مجموعه‌ها مهم هستند؟

  • سادگی کار با داده‌ها: به جای مدیریت آرایه‌های ثابت، مجموعه‌ها امکان افزودن، حذف و جستجوی عناصر را به صورت دینامیک فراهم می‌کنند.
  • انواع مختلف داده‌ها: مجموعه‌های جاوا از انواع مختلف داده‌ها پشتیبانی می‌کنند، از جمله اعداد، رشته‌ها و اشیاء سفارشی.
  • الگوریتم‌های آماده: بسیاری از عملیات رایج مانند مرتب‌سازی، جستجو و فیلتر کردن بر روی مجموعه‌ها به صورت آماده در کتابخانه‌های جاوا وجود دارند.
  • انعطاف‌پذیری: مجموعه‌ها در اندازه و نوع داده‌ها انعطاف‌پذیر هستند و با رشد برنامه شما به راحتی قابل تغییر هستند.

انواع اصلی مجموعه‌ها در جاوا

  • List:

مجموعه‌ای مرتب از عناصر است که هر عنصر می‌تواند چندین بار تکرار شود.
ArrayList:پیاده‌سازی متداول List است که بر اساس آرایه‌ها ساخته شده است.
LinkedList: پیاده‌سازی دیگری از List است که بر اساس نودها ساخته شده و برای عملیات درج و حذف در وسط لیست بهینه شده است.

  • Set:

مجموعه‌ای از عناصر منحصر به فرد است. هر عنصر فقط یک بار می‌تواند در مجموعه وجود داشته باشد. HashSet: پیاده‌سازی متداول Set است که بر اساس جدول هش ساخته شده و برای جستجوی سریع عناصر بهینه شده است.
TreeSet: پیاده‌سازی دیگری از Set است که عناصر را به صورت مرتب شده ذخیره می‌کند.

  • Map:

مجموعه‌ای از جفت‌های کلید-مقدار است. هر کلید باید منحصر به فرد باشد. HashMap: پیاده‌سازی متداول Map است که بر اساس جدول هش ساخته شده است. TreeMap: پیاده‌سازی دیگری از Map است که کلیدها را به صورت مرتب شده ذخیره می‌کند.

عملیات رایج روی مجموعه‌ها

  • اضافه کردن عنصر: اضافه کردن یک عنصر جدید به مجموعه.
  • حذف عنصر: حذف یک عنصر از مجموعه.
  • جستجوی عنصر: پیدا کردن یک عنصر خاص در مجموعه.
  • ایجاد زیرمجموعه: ایجاد یک مجموعه جدید که شامل بخشی از عناصر مجموعه اصلی است.
  • مرتب‌سازی: مرتب کردن عناصر مجموعه بر اساس یک معیار خاص.
  • ایجاد تکرار کننده: ایجاد یک شیء تکرار کننده برای پیمایش عناصر مجموعه.

چه زمانی از چه مجموعه‌ای استفاده کنیم؟

  • List: زمانی که ترتیب عناصر مهم باشد و نیاز به دسترسی به عناصر با اندیس داشته باشید
  • Set: زمانی که نیاز به مجموعه ای از عناصر منحصر به فرد داشته باشید
  • Map: زمانی که نیاز به نگاشت بین کلیدها و مقادیر داشته باشید

یه مثال ساده :

import java.util.ArrayList; import java.util.List; public class Example { public static void main(String[] args) { List<String> fruits = new ArrayList<>(); fruits.add(&quotapple&quot); fruits.add(&quotbanana&quot); fruits.add(&quotorange&quot); System.out.println(fruits); // خروجی: [apple, banana, orange] } }


نکات مهم

  • Generic types:

مجموعه‌ها در جاوا از generic types پشتیبانی می‌کنند که به شما اجازه می‌دهد تا نوع عناصر ذخیره شده در مجموعه را مشخص کنید.

  • Iterators:

برای پیمایش عناصر یک مجموعه از iterators استفاده می‌شود.

  • Collections framework:

مجموعه‌های جاوا بخشی از Collections framework هستند که شامل کلاس‌ها و رابط‌های مختلفی برای کار با مجموعه‌ها است.




موارد اضافه شده در جاوا 8 برای کار با Collection ها :

جاوا 8 با معرفی ویژگی‌های جدیدی، کار با مجموعه‌ها را بسیار قدرتمندتر و انعطاف‌پذیرتر کرده است. برخی از مهم‌ترین این ویژگی‌ها عبارتند از:

1. Stream API:

  • پردازش داده‌های موازی و موازی‌نما: استریم‌ها به شما اجازه می‌دهند تا روی مجموعه‌ها عملیات موازی یا موازی‌نما را به صورت روان انجام دهید.
  • عملیات فیلتر کردن، نگاشت و کاهش: با استفاده از متدهایی مانند filter, map, reduce و ... می‌توانید به راحتی داده‌های خود را دستکاری کنید.
  • پایپلاینینگ: عملیات مختلف را می‌توانید به صورت زنجیره‌ای روی استریم‌ها اعمال کنید.

2. Lambda Expressions:

  • نوشتن کدهای کوتاه و خواناتر: لامبدا اکسپرشنز به شما اجازه می‌دهند تا به صورت مختصر و مفید کدهای تابعی بنویسید.
  • استفاده در متدهای تابعی: لامبدا اکسپرشنز به عنوان آرگومان متدهایی که یک رابط تابعی را انتظار دارند، استفاده می‌شوند.
  • تسهیل کار با Stream API: لامبدا اکسپرشنز به طور گسترده‌ای در Stream API استفاده می‌شوند.

3. Method References:

  • مراجعه به متدها به عنوان آرگومان: متد رفرنس‌ها به شما اجازه می‌دهند تا به جای نوشتن لامبدا اکسپرشن، مستقیماً به یک متد موجود ارجاع دهید.
  • کاهش حجم کد: متد رفرنس‌ها به شما کمک می‌کنند تا کدهای تکراری را کاهش دهید.

4. Default Methods in Interfaces:

  • اضافه کردن پیاده‌سازی پیش‌فرض به رابط‌ها: با استفاده از متدهای پیش‌فرض، می‌توانید به رابط‌های موجود متدهای جدید اضافه کنید بدون اینکه کلاس‌های پیاده‌ساز را تغییر دهید.
  • تسهیل تکامل API: متدهای پیش‌فرض به شما کمک می‌کنند تا API‌های خود را بدون شکستن سازگاری با نسخه‌های قبلی، توسعه دهید.
List<String> names = Arrays.asList(&quotAlice&quot, &quotBob&quot, &quotCharlie&quot); // قبل از جاوا 8 List<String> longNames = new ArrayList<>(); for (String name : names) { if (name.length() > 5) { longNames.add(name); } } // 8با استفاده از استریم و لامبدادر جاوا List<String> longNames = names.stream() .filter(name -> name.length() > 5) .collect(Collectors.toList());

همانطور که می‌بینید، کد جاوا 8 بسیار خواناتر و خلاصه‌تر است.

مزایای استفاده از این ویژگی‌ها:

  • کدهای کوتاه‌تر و خواناتر: استفاده از لامبدا اکسپرشنز و Stream API باعث می‌شود کدهای شما کوتاه‌تر و خواناتر شوند.
  • کارایی بهتر: Stream API امکان پردازش موازی داده‌ها را فراهم می‌کند که منجر به افزایش کارایی می‌شود.
  • انعطاف‌پذیری بیشتر: با استفاده از این ویژگی‌ها، می‌توانید عملیات پیچیده‌ای را بر روی مجموعه‌ها به صورت ساده‌تر انجام دهید.

در کل، جاوا 8 با معرفی این ویژگی‌ها، یک جهش بزرگ در زمینه برنامه‌نویسی تابعی و کار با مجموعه‌ها ایجاد کرده است.


موارد اضافه شده در جاوا 10 برای کار با Collection ها :

جاوا 10 تغییرات چشمگیری در زمینه کار با مجموعه‌ها ایجاد نکرد. تمرکز اصلی این نسخه بر روی بهبود عملکرد، قابلیت اطمینان و برخی ویژگی‌های زیرساختی بوده است.

با این حال، برخی از تغییرات غیر مستقیم می‌توانند بر روی نحوه کار با مجموعه‌ها تاثیر بگذارند:

  • بهبود عملکرد Garbage Collector: با بهبود عملکرد Garbage Collector، مدیریت حافظه در برنامه‌های جاوا به ویژه آن‌هایی که از مجموعه‌های بزرگ استفاده می‌کنند، بهبود یافته است. این امر می‌تواند به طور غیرمستقیم بر روی کارایی عملیات روی مجموعه‌ها تاثیر بگذارد.
  • اضافه شدن Var برای پارامترهای لامبدا: این ویژگی به شما اجازه می‌دهد تا نوع پارامترهای لامبدا را به کامپایلر واگذار کنید. این می‌تواند کد شما را کوتاه‌تر و خواناتر کند، اما به طور مستقیم بر روی عملکرد مجموعه‌ها تاثیر نمی‌گذارد.

موارد اضافه شده در جاوا 11 برای کار با Collection ها :

جاوا 11، گرچه تغییرات بنیادینی در کار با مجموعه‌ها ایجاد نکرد، اما با معرفی ویژگی‌های جدید و بهبودهای عملکردی، بهینه‌سازی‌هایی را در این حوزه ارائه داده است.

مهم‌ترین تغییراتی که در جاوا 11 در ارتباط با مجموعه‌ها رخ داده است، به شرح زیر است:

1. مجموعه‌های غیرقابل تغییر (Immutable Collections):

  • ایجاد مجموعه‌های خواندنی: یکی از مهم‌ترین ویژگی‌های اضافه شده در جاوا 11، امکان ایجاد مجموعه‌های غیرقابل تغییر است. این مجموعه‌ها پس از ایجاد، قابل تغییر نیستند و از ایجاد تغییرات ناخواسته در داده‌ها جلوگیری می‌کنند.
  • بهبود امنیت و قابلیت اطمینان: مجموعه‌های غیرقابل تغییر به ویژه در محیط‌های چند نخی بسیار مفید هستند، زیرا از بروز خطاهای ناشی از تغییرات همزمان در مجموعه‌ها جلوگیری می‌کنند.
  • بهبود عملکرد: در برخی موارد، استفاده از مجموعه‌های غیرقابل تغییر می‌تواند به بهبود عملکرد برنامه‌ها کمک کند.
List<String> immutableList = List.of(&quotapple&quot, &quotbanana&quot, &quotorange&quot); // immutableList.add(&quotgrape&quot); // این خط منجر به خطا می‌شود

2. بهبود عملکرد Garbage Collector:

  • مدیریت بهتر حافظه: با بهبود عملکرد Garbage Collector، مدیریت حافظه در برنامه‌های جاوا به ویژه آن‌هایی که از مجموعه‌های بزرگ استفاده می‌کنند، بهبود یافته است. این امر می‌تواند به طور غیرمستقیم بر روی کارایی عملیات روی مجموعه‌ها تاثیر بگذارد.

3. سایر تغییرات:

  • بهبودهای جزئی در API: برخی از متدهای مربوط به مجموعه‌ها در جاوا 11 بهبود یافته‌اند تا استفاده از آن‌ها آسان‌تر شود.
  • حذف برخی از متدهای منسوخ شده: برخی از متدهای منسوخ شده که با مجموعه‌ها مرتبط بودند، از جاوا 11 حذف شده‌اند.
  • متد toArray(IntFunction<T[]> generator) یکی از تغییرات مهمی است که در جاوا 11 به رابط Collection اضافه شده است. این متد به شما امکان می‌دهد تا آرایه‌ای با نوع دلخواه و اندازه مناسب از عناصر یک مجموعه ایجاد کنید.

چرا این متد مهم است؟

  • انعطاف‌پذیری بیشتر: قبل از جاوا 11، متد toArray() تنها امکان ایجاد آرایه‌ای از نوع مشخص شده در تعریف مجموعه را فراهم می‌کرد. اما متد جدید به شما اجازه می‌دهد تا نوع آرایه خروجی را به طور دلخواه مشخص کنید.
  • جلوگیری از هشدارهای کامپایلر: در برخی موارد، استفاده از متد قدیمی toArray() منجر به هشدارهای کامپایلر می‌شد. متد جدید این مشکل را برطرف کرده است.
  • بهبود کارایی: در برخی سناریوها، استفاده از متد جدید می‌تواند به بهبود کارایی کمک کند، زیرا از ایجاد آرایه‌های موقتی جلوگیری می‌کند.
List<String> fruits = List.of(&quotapple&quot, &quotbanana&quot, &quotorange&quot); String[] fruitArray = fruits.toArray(String[]::new);

در مثال بالا، متد toArray() یک آرایه از نوع String[] با اندازه مناسب ایجاد می‌کند و عناصر مجموعه fruits را در آن کپی می‌کند.

مزایای استفاده از این متد:

  • نوع آرایه خروجی قابل کنترل است.
  • خطاهای زمان کامپایل کاهش می‌یابد.
  • در برخی موارد، کارایی بهبود می‌یابد.

توجه:

  • این متد از یک IntFunction به عنوان ورودی استفاده می‌کند که وظیفه ایجاد آرایه خالی با اندازه مشخص را بر عهده دارد.
  • اندازه آرایه ایجاد شده برابر با تعداد عناصر مجموعه است.
  • اگر نوع آرایه‌ای که شما مشخص می‌کنید با نوع عناصر مجموعه مطابقت نداشته باشد، یک ArrayStoreException رخ می‌دهد.

در کل، متد toArray(IntFunction<T[]> generator) یک ابزار قدرتمند برای کار با مجموعه‌ها در جاوا 11 است و به شما امکان می‌دهد تا عملیات تبدیل مجموعه به آرایه را با انعطاف‌پذیری بیشتری انجام دهید.

در کل، جاوا 11 با معرفی مجموعه‌های غیرقابل تغییر و بهبود عملکرد Garbage Collector، گامی مهم در جهت بهبود کارایی و امنیت برنامه‌های جاوا برداشته است.

جمع‌بندی

در حالی که جاوا 11 تغییرات بنیادینی در نحوه کار با مجموعه‌ها ایجاد نکرده است، اما با معرفی ویژگی‌های جدید و بهبودهای عملکردی، بهینه‌سازی‌هایی را در این حوزه ارائه داده است. به ویژه، مجموعه‌های غیرقابل تغییر یک ابزار قدرتمند برای افزایش امنیت و قابلیت اطمینان برنامه‌ها هستند.



هنوز وقت نکردم موارد مربوط به جاوا 14 و 17 و 21 رو بخونم اونها رو بهم وقتی خوندم قرار میدم .


بیایم یه چنتا مثال برای حالت های مختلف بزنیم :

1- خوب اولین مثال ها برای ابزار iterator هست :

یک ابزار قدرتمند در جاوا برای پیمایش عناصر یک مجموعه (مانند List، Set، Map) است. این ابزار به شما اجازه می‌دهد تا بدون دانستن ساختار داخلی مجموعه، روی عناصر آن به صورت ترتیبی حرکت کنید.

پیمایش یک ArrayList:

import java.util.ArrayList; import java.util.Iterator; public class IteratorExample { public static void main(String[] args) { ArrayList<String> fruits = new ArrayList<>(); fruits.add(&quotApple&quot); fruits.add(&quotOrange&quot); Iterator<String> iterator = fruits.iterator(); while (iterator.hasNext()) { String fruit = iterator.next(); System.out.println(fruit); } } } //خروجی //Apple //Orange

حذف عناصر در حین پیمایش:

import java.util.ArrayList; import java.util.Iterator; public class IteratorExample { public static void main(String[] args) { ArrayList<String> numbers = new ArrayList<>(); numbers.add(&quotOne&quot); numbers.add(&quotTwo&quot); numbers.add(&quotThree&quot); Iterator<String> iterator = numbers.iterator(); while (iterator.hasNext()) { String number = iterator.next(); if (number.equals(&quotTwo&quot)) { iterator.remove(); } } // چاپ عناصر باقی مانده for (String number : numbers) { System.out.println(number); } } }

نکته مهم: هنگام حذف عنصری در حین پیمایش با Iterator، باید بلافاصله بعد از فراخوانی next() و قبل از فراخوانی next() بعدی، از متد remove() استفاده کنید. در غیر این صورت، یک IllegalStateException رخ خواهد داد.

پیمایش یک HashMap:

import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class IteratorExample { public static void main(String[] args) { Map<String, Integer> ages = new HashMap<>(); ages.put(&quotAlice&quot, 30); ages.put(&quotBob&quot, 25); ages.put(&quotCharlie&quot, 35); // پیمایش کلیدها Iterator<String> keys = ages.keySet().iterator(); while (keys.hasNext()) { String key = keys.next(); System.out.println(&quotKey: &quot + key + &quot, Value: &quot + ages.get(key)); } } }

مزایای استفاده از Iterator:

  • پیمایش ایمن: از تغییرات تصادفی در مجموعه در حین پیمایش جلوگیری می‌کند.
  • انعطاف‌پذیری: برای انواع مختلف مجموعه‌ها قابل استفاده است.
  • پشتیبانی از عملیات حذف: امکان حذف عناصر در حین پیمایش را فراهم می‌کند.

موارد استفاده از Iterator:

  • پیمایش عناصر در مجموعه‌های مختلف
  • حذف عناصر در حین پیمایش
  • ایجاد ساختارهای داده سفارشی

نکات مهم:

  • یادتون باشه Iterator فقط برای پیمایش رو به جلو استفاده می‌شود.
  • برای پیمایش به عقب یا دسترسی تصادفی به عناصر، از ساختارهای داده دیگری مانند List استفاده کنید.
  • همیشه قبل از فراخوانی next(), از hasNext() استفاده کنید تا از خطای NoSuchElementException جلوگیری شود.

با استفاده از Iterator، می‌توانید به صورت ایمن و انعطاف‌پذیر بر روی عناصر مجموعه‌های مختلف در جاوا پیمایش کنید.

2- حالا یه چنتا مثال برای Stream Api بزنیم :

یکی از قدرتمندترین ویژگی‌های جاوا 8 و نسخه‌های بعدی است که به شما اجازه می‌دهد روی مجموعه داده‌ها به صورت تابعی عمل کنید. با استفاده از Stream API می‌توانید عملیات فیلتر کردن، نگاشت، کاهش، مرتب‌سازی و بسیاری دیگر را به صورت زنجیره‌ای و خوانا اجرا کنید.

فیلتر کردن عناصر یک لیست:

import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); // فیلتر کردن اعداد زوج List<Integer> evenNumbers = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(evenNumbers); // خروجی: [2, 4, 6, 8] } }

نگاشت عناصر به یک نوع داده دیگر:

import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamExample { public static void main(String[] args) { List<String> names = Arrays.asList(&quotAlice&quot, &quotBob&quot, &quotCharlie&quot); // تبدیل هر نام به طول آن List<Integer> lengths = names.stream() .map(String::length) .collect(Collectors.toList()); System.out.println(lengths); // خروجی: [5, 3, 7] } }

مرتب سازی عناصر :

import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamExample { public static void main(String[] args) { List<String> names = Arrays.asList(&quotAli&quot, &quotBahram&quot, &quotChangiz&quot); // مرتب‌سازی بر اساس حروف الفبا List<String> sortedNames = names.stream() .sorted() .collect(Collectors.toList()); System.out.println(sortedNames); // خروجی: [Ali, Bahram, Changiz] } }

یه کار سخت تر انجام بدیم مثلا پیدا کردن طولانی ترین کلمه در بین کلمات یک لیست:

import java.util.List; import java.util.stream.Collectors; public class StreamExample { public static void main(String[] args) { List<String> words = Arrays.asList(&quotapple&quot, &quotorange&quot, &quotkiwi&quot); // پیدا کردن طولانی‌ترین کلمه String longestWord = words.stream() .max(Comparator.comparingInt(String::length)) .orElse(&quotNo words found&quot); System.out.println(longestWord); // خروجی: orange } }

مزایای استفاده از Stream API:

  • خوانایی بهتر کد: کدهای نوشته شده با Stream API معمولاً خواناتر و مختصرتر هستند.
  • کارایی بهتر: در بسیاری از موارد، Stream API از حلقه‌های سنتی کارآمدتر است.
  • تابعی بودن: Stream API از سبک برنامه‌نویسی تابعی پشتیبانی می‌کند که منجر به کدهای تمیزتر و قابل نگهداری‌تر می‌شود.
  • انعطاف‌پذیری بالا: با استفاده از Stream API می‌توانید عملیات پیچیده و متنوعی را روی مجموعه داده‌ها انجام دهید.

موارد استفاده رایج Stream API:

  • پردازش فایل‌ها
  • جمع‌آوری داده‌ها
  • ساختارهای داده پیچیده
  • محاسبات آماری
  • و ...
میخواستم راجب Queue بگم و یه چنتا مثال بزنم اما دیگه داره یکم طولانی میشه این موارد رو بعدا در یک مقاله دیگه میگم .




خوب بیایم یه چنتا سوال ساده ولی توی مصاحبه میاد بگیم :

سوال اول : فرض کنید هیچ کدوم از کالکشن های مربوط به map مثلا HashMap , Map , HashSet , TreeMap و امثال اینها رو نداشتیم

چطوری میتونیم خودمون یه Map یا HashMap بسازیم ؟ (راهنمایی مثلا بیایم با استفاده از list یا ArrayList و ... ایجادش کنیم )

سوال دوم : یه Itarator سفارشی ایجاد کنید

سوال سوم : چه زمانی استفاده از TreeMap نسبت به HashMap مناسب تر است؟

سوال چهارم : TreeMap چگونه عناصر را مرتب می‌کند؟ چه مقایسه‌کننده‌ای برای این کار استفاده می‌شود؟

سوال پنجم : چگونه می‌توان یک HashMap را به یک TreeMap تبدیل کرد؟

سوال ششم : اگر تعداد زیادی از عناصر با همان هش کد را به HashMap اضافه کنیم چه اتفاقی می‌افتد؟ چگونه می‌توان با این مشکل مقابله کرد؟

سوال هفتم : چه زمانی از HashMap استفاده می‌کنیم و چه زمانی از TreeMap؟

سوال هشتم : تفاوت بین intermediate operations و terminal operations در Stream API چیست؟

سوال نهم : تفاوت بین reduce و collect در Stream API چیست؟

سوال دهم : چگونه می‌توان از ایجاد intermediate collection ها در Stream API جلوگیری کرد؟

سوال یازدهم : چگونه می‌توان از ایجاد boxing و unboxing در Stream API جلوگیری کرد؟

سوال دوازدهم : functional interfaces چیست؟ و چه تاثیری توی Stream Api داره ؟

اینا سوالاتی بود که توی مصاحبه ازم پرسیده بودند و خیلی خوب و مفید و جالبه



منتظر نگاه های زیباتون هستم .

موفق و پیروز باشید.

java collectionsiteratorstreamapijavaسوالات مصاحبه استخدامی
Java Developer - Technical Team Lead At Dotin
شاید از این پست‌ها خوشتان بیاید