Запитання:
- Що таке сировинні Java, і чому я часто чую, що їх не слід використовувати в новому коді?
- Яка альтернатива, якщо ми не можемо використовувати сировинні типи, і як це краще?
Відповіді:
Специфікація мови Java визначає необроблений тип наступним чином:
Сирий тип визначається як один із:
Тип посилання, який формується шляхом взяття назви загального декларації типу без супровідного списку аргументів типу.
Тип масиву, тип елемента якого є необробленим.
Нечлений
static
тип необробленого типу,R
який не успадковується від суперкласу або надінтерфейсуR
.
Ось приклад для ілюстрації:
public class MyType<E> {
class Inner { }
static class Nested { }
public static void main(String[] args) {
MyType mt; // warning: MyType is a raw type
MyType.Inner inn; // warning: MyType.Inner is a raw type
MyType.Nested nest; // no warning: not parameterized type
MyType<Object> mt1; // no warning: type parameter given
MyType<?> mt2; // no warning: type parameter given (wildcard OK!)
}
}
Тут, MyType<E>
є параметризованим типом ( JLS 4.5 ). Це звичайно розмовно називати цей тип просто MyType
коротким, але технічно це ім'я MyType<E>
.
mt
має необроблений тип (і генерує попередження про компіляцію) першою точкою кулі у наведеному вище визначенні; inn
також має необроблений тип по третій кулі.
MyType.Nested
не є параметризованим типом, хоча це тип типу параметризованого типу MyType<E>
, тому що це static
.
mt1
, і mt2
вони оголошені з фактичними параметрами типу, тому вони не є необробленими.
По суті, сировинні типи поводяться так, як були до введення дженериків. Тобто, наступне є повністю законним під час компіляції.
List names = new ArrayList(); // warning: raw type!
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // not a compilation error!
Вищеописаний код працює чудово, але припустимо, у вас є також наступне:
for (Object o : names) {
String name = (String) o;
System.out.println(name);
} // throws ClassCastException!
// java.lang.Boolean cannot be cast to java.lang.String
Тепер ми стикаємося з проблемою під час виконання, оскільки names
містить щось, що не є instanceof String
.
Імовірно, якщо ви хочете , names
щоб утримувати тільки String
, ви могли б , можливо , по- , як і раніше використовувати необроблений тип і вручну перевірити всі add
самостійно, а потім вручну відливати до String
кожного елементу з names
. Ще краще , хоча НЕ використовувати сирий тип і дозволити компілятору виконати всю роботу за вас , використовуючи потужність Java generics.
List<String> names = new ArrayList<String>();
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // compilation error!
Звичайно, якщо ви дійсно хочете names
дозволити a Boolean
, ви можете оголосити це якList<Object> names
і наведений вище код компілюватиметься.
<Object>
в якості параметрів типу?Нижче наведено цитату з Ефективного другого видання Java, пункт 23: Не використовуйте необроблені типи в новому коді :
Тільки в чому різниця між необробленим типом
List
і типовим параметромList<Object>
? Якщо говорити вільно, перший відмовився від загальної перевірки типів, а другий чітко сказав компілятору, що він здатний містити об'єкти будь-якого типу. Хоча ви можете передатиList<String>
параметр типуList
, ви не можете передати його параметру типуList<Object>
. Існують правилаList<String>
підтипу для дженериків і є підтипом необробленого типуList
, але не параметризованого типуList<Object>
. Як наслідок, ви втрачаєте безпеку типу, якщо використовуєте сировинний тип типуList
, але не, якщо ви використовуєте параметризований тип типуList<Object>
.
Для ілюстрації пункту розглянемо наступний метод, який приймає a List<Object>
та додає a new Object()
.
void appendNewObject(List<Object> list) {
list.add(new Object());
}
Генеріки на Яві інваріантні. A List<String>
не є List<Object>
, тому наступне створює попередження компілятора:
List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!
Якби ви оголосили appendNewObject
взяти необроблений тип List
як параметр, це буде компілюватися, і ви втратили б безпеку типу, яку ви отримуєте з дженериків.
<?>
в якості параметра типу?List<Object>
, List<String>
і т. д. все List<?>
, тому може бути спокусливо просто сказати, що вони просто List
замість цього. Однак є основна відмінність: оскільки List<E>
визначає лише те add(E)
, ви не можете додати будь-який довільний об'єкт до List<?>
. З іншого боку, оскільки необроблений тип List
не має безпеки типу, ви можете add
майже все, що завгодноList
.
Розглянемо наступну варіацію попереднього фрагмента:
static void appendNewObject(List<?> list) {
list.add(new Object()); // compilation error!
}
//...
List<String> names = new ArrayList<String>();
appendNewObject(names); // this part is fine!
Компілятор зробив чудову роботу, захистивши вас від потенційного порушення типової інваріантності List<?>
! Якби ви оголосили параметр як необроблений тип List list
, код буде компілюватися, і ви порушили б інваріант типу List<String> names
.
Повернутися до JLS 4.8:
Можна використовувати як тип стирання параметризованого типу або стирання типу масиву, тип елемента якого є параметризованим типом. Такий тип називається сировинним типом .
[...]
Надкласи (відповідно, суперінтерфейси) необробленого типу - це стирання надкласів (суперінтерфейсів) будь-якого з параметрів параметрів загального типу.
Тип конструктора, методу екземпляра або неполевого
static
типу необробленого типу,C
який не успадковується від його суперклассів або надінтерфейсів, є типовим типом, який відповідає стирання його типу в загальній декларації, що відповідаєC
.
Простіше кажучи, при використанні необробленого типу конструктори, методи екземплярів і не static
поля також стираються .
Візьмемо такий приклад:
class MyType<E> {
List<String> getNames() {
return Arrays.asList("John", "Mary");
}
public static void main(String[] args) {
MyType rawType = new MyType();
// unchecked warning!
// required: List<String> found: List
List<String> names = rawType.getNames();
// compilation error!
// incompatible types: Object cannot be converted to String
for (String str : rawType.getNames())
System.out.print(str);
}
}
Коли ми використовуємо сире MyType
, воно також getNames
стирається, так що воно повертає сировину List
!
JLS 4.6 продовжує пояснювати наступне:
Стирання типу також відображає підпис конструктора або методу до підпису, який не має параметризованих типів або змінних типів. Стирання підпису конструктора або методу
s
- це підпис, що складається з однойменної назвиs
та стирання всіх формальних типів параметрів, наведених уs
.Тип повернення методу та параметри типу загального методу чи конструктора також зазнають стирання, якщо метод або підпис конструктора стираються.
Видалення підпису загального методу не має параметрів типу.
Наступний звіт про помилки містить деякі думки Мауріціо Цімадамора, розробника компілятора, та Алекса Баклі, одного з авторів JLS, про те, чому має відбуватися така поведінка: https://bugs.openjdk.java.net/browse / JDK-6400189 . (Коротше кажучи, це робить специфікацію простішою.)
Ось ще одна цитата JLS 4.8:
Використання сировинних типів допускається лише як поступка сумісності застарілого коду. Використання необроблених типів у коді, написаному після введення загальної мови в мову програмування Java, сильно не рекомендується. Можливо, що майбутні версії мови програмування Java не дозволять використовувати необроблені типи.
Ефективне Java 2nd Edition також має додати:
Зважаючи на те, що ви не повинні використовувати необроблені типи, чому мовні дизайнери дозволили їх? Для забезпечення сумісності.
Платформа Java збиралася вступити у своє друге десятиліття, коли були представлені дженерики, і існувала величезна кількість коду Java, який не використовував дженерики. Вважалося критичним, що весь цей код залишається законним та сумісним із новим кодом, який використовує дженерики. Потрібно було легально передавати екземпляри параметризованих типів методам, призначеним для використання із звичайними типами, і навпаки. Ця вимога, відома як сумісність з міграцією , зумовила рішення щодо підтримки сировинних типів.
Підводячи підсумок, в новому коді НІКОЛИ не слід використовувати сирі типи. Ви завжди повинні використовувати параметризовані типи .
На жаль, оскільки дженерики Java не повторно змінені, є два винятки, коли в новому коді повинні використовуватися необроблені типи:
List.class
, ніList<String>.class
instanceof
операнд, наприклад o instanceof Set
, ніo instanceof Set<String>
o instanceof Set<?>
також може уникати необробленого типу (хоча в цьому випадку він є лише поверхневим).
n
віддалених бобів для кожного класу реалізації з однаковим кодом.
TypeName.class
, де TypeName
простий ідентифікатор ( jls ). Якщо говорити гіпотетично, то, мабуть, це справді могло бути і те, і інше. Можливо, як підказка List<String>.class
є варіант, який JLS спеціально викликає помилку компілятора, тож якщо вони коли-небудь додадуть його до мови, я б очікував, що це саме те, що вони використовують.
Що таке сировинні Java, і чому я часто чую, що їх не слід використовувати в новому коді?
Типи сировини - це давня історія мови Java. На початку були Collections
і вони Objects
не тримали нічого більше і нічого менше. Кожна операція на Collections
необхідних ролях від Object
потрібного типу.
List aList = new ArrayList();
String s = "Hello World!";
aList.add(s);
String c = (String)aList.get(0);
Хоча це працювало більшу частину часу, помилки траплялися
List aNumberList = new ArrayList();
String one = "1";//Number one
aNumberList.add(one);
Integer iOne = (Integer)aNumberList.get(0);//Insert ClassCastException here
Старі типові колекції не могли забезпечити безпеку типу, тому програмісту довелося запам’ятати, що він зберігав у колекції.
У дженериках, де винайдено, щоб обійти це обмеження, розробник оголосив би збережений тип один раз, а компілятор зробив би це замість цього.
List<String> aNumberList = new ArrayList<String>();
aNumberList.add("one");
Integer iOne = aNumberList.get(0);//Compile time error
String sOne = aNumberList.get(0);//works fine
Для порівняння:
// Old style collections now known as raw types
List aList = new ArrayList(); //Could contain anything
// New style collections with Generics
List<String> aList = new ArrayList<String>(); //Contains only Strings
Більш складний порівняльний інтерфейс:
//raw, not type save can compare with Other classes
class MyCompareAble implements CompareAble
{
int id;
public int compareTo(Object other)
{return this.id - ((MyCompareAble)other).id;}
}
//Generic
class MyCompareAble implements CompareAble<MyCompareAble>
{
int id;
public int compareTo(MyCompareAble other)
{return this.id - other.id;}
}
Зауважте, що неможливо реалізувати CompareAble
інтерфейс compareTo(MyCompareAble)
із типовими компонентами. Чому не слід їх використовувати:
Object
збережене у файлі Collection
має бути передано перед тим, як його можна використовуватиObject
Що робить компілятор: Generics є зворотною сумісністю, вони використовують ті ж класи Java, що і сировинні типи. Магія трапляється здебільшого під час компіляції.
List<String> someStrings = new ArrayList<String>();
someStrings.add("one");
String one = someStrings.get(0);
Складається як:
List someStrings = new ArrayList();
someStrings.add("one");
String one = (String)someStrings.get(0);
Це той самий код, який ви б написали, якби безпосередньо використовували вихідні типи. Думав, що я не впевнений, що відбувається з CompareAble
інтерфейсом, я думаю, що він створює дві compareTo
функції, одна з яких - aMyCompareAble
а друга приймає Object
та передає її першій після того, як її буде передано.
Які альтернативи сировинним типам: Використовуйте дженерики
Сирий тип - це назва загального класу чи інтерфейсу без аргументів типу. Наприклад, враховуючи загальний клас Box:
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
Щоб створити параметризований тип Box<T>
, ви надаєте фактичний аргумент типу для параметра формального типу T
:
Box<Integer> intBox = new Box<>();
Якщо аргумент фактичного типу пропущено, ви створюєте необроблений тип Box<T>
:
Box rawBox = new Box();
Тому Box
є сировинним типом родового типуBox<T>
. Однак некласовий клас або тип інтерфейсу не є необробленим типом.
Сировинні типи відображаються в застарілому коді, оскільки багато класів API (наприклад, класи Collections) не були загальними до JDK 5.0. Використовуючи необроблені типи, ви по суті отримуєте поведінку перед генериками - це Box
дає вам Object
s. Для зворотної сумісності дозволяється присвоювати параметризований тип його сировинному типу:
Box<String> stringBox = new Box<>();
Box rawBox = stringBox; // OK
Але якщо призначити сировинний тип параметризованому типу, ви отримаєте попередження:
Box rawBox = new Box(); // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox; // warning: unchecked conversion
Ви також отримуєте попередження, якщо використовуєте необроблений тип для виклику загальних методів, визначених у відповідному загальному типі:
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8); // warning: unchecked invocation to set(T)
Попередження показує, що необроблені типи обходять перевірку загального типу, відкладаючи вилов небезпечного коду на час виконання. Тому слід уникати використання сировинних типів.
У розділі Type Erasure є додаткова інформація про те, як компілятор Java використовує необроблені типи.
Як згадувалося раніше, при змішуванні застарілого коду з загальним кодом, ви можете зіткнутися з попереджувальними повідомленнями, подібними до таких:
Примітка: Example.java використовує неперевірені або небезпечні операції.
Примітка. Перекомпілюйте з -Xlint: не встановлено прапорці для деталей.
Це може статися при використанні більш старого API, який працює на вихідних типах, як показано в наступному прикладі:
public class WarningDemo {
public static void main(String[] args){
Box<Integer> bi;
bi = createBox();
}
static Box createBox(){
return new Box();
}
}
Термін "неперевірений" означає, що у компілятора недостатньо інформації про тип для виконання всіх перевірок типів, необхідних для забезпечення безпеки типу. Попередження "не перевірено" вимкнено за замовчуванням, хоча компілятор дає підказку. Щоб побачити всі "неперевірені" попередження, перекомпілюйте з -Xlint: невірно.
Перекомпіляція попереднього прикладу за допомогою -Xlint: без галочки виявляється наступна додаткова інформація:
WarningDemo.java:4: warning: [unchecked] unchecked conversion
found : Box
required: Box<java.lang.Integer>
bi = createBox();
^
1 warning
Щоб повністю вимкнути неперевірені попередження, використовуйте прапорець -Xlint: -контрольований. @SuppressWarnings("unchecked")
Анотацію пригнічує неперевірені попередження. Якщо ви не знайомі з@SuppressWarnings
синтаксисом, див. Анотації.
Оригінальне джерело: Підручники Java
"Сирий" тип на Java - це клас, який не є загальним і має справу з "необробленими" Об'єктами, а не безпечними для типу параметрами загального типу.
Наприклад, перш ніж доступні дженерики Java, ви б використовували такий клас колекції:
LinkedList list = new LinkedList();
list.add(new MyObject());
MyObject myObject = (MyObject)list.get(0);
Коли ви додаєте свій об'єкт до списку, це не має значення для якого типу об'єкта він є, і коли ви отримуєте його зі списку, ви повинні чітко віддати його до типу, який ви очікуєте.
Використовуючи дженерики, ви видаляєте фактор "невідомий", оскільки ви повинні чітко вказати, який тип об'єктів може перейти до списку:
LinkedList<MyObject> list = new LinkedList<MyObject>();
list.add(new MyObject());
MyObject myObject = list.get(0);
Зауважте, що за допомогою дженерики вам не потрібно викидати об'єкт, що надходить з виклику get, колекція заздалегідь визначена для роботи лише з MyObject. Саме цей факт є головним рушійним фактором для генериків. Це змінює джерело помилок виконання на щось, що можна перевірити під час компіляції.
?
все ще пропонує тип безпеки. Я висвітлював це у своїй відповіді.
private static List<String> list = new ArrayList<String>();
Вам слід вказати тип-параметр.
Попередження радить, що типи, визначені для підтримки generic, повинні бути параметризовані, а не використовувати їх необроблену форму.
List
визначається підтримки дженериків: public class List<E>
. Це дозволяє безліч безпечних операцій, які перевіряються під час компіляції.
private static List<String> list = new ArrayList<>();
Що таке сировина, і чому я часто чую, що їх не слід використовувати в новому коді?
"Сирий тип" - це використання загального класу без вказівки аргументів типу для його параметризованих типів, наприклад, List
замість використання List<String>
. Коли вводили дженерики в Java, було оновлено кілька класів для використання дженериків. Використання цих класів як "необробленого типу" (без вказівки аргументу типу) дозволило застарілому коду все-таки компілювати.
"Сирі типи" використовуються для зворотної сумісності. Їх використання в новому коді не рекомендується, оскільки використання загального класу з аргументом типу дозволяє посилити введення тексту, що, в свою чергу, може покращити зрозумілість коду та призвести до попереднього пошуку потенційних проблем.
Яка альтернатива, якщо ми не можемо використовувати сировинні типи, і як це краще?
Кращою альтернативою є використання загальних класів за призначенням - з відповідним аргументом типу (наприклад, List<String>
). Це дозволяє програмісту конкретизувати типи, передає більше значення майбутнім обслуговуючим особам щодо передбачуваного використання змінної або структури даних, а також дозволяє компілятору забезпечити кращу безпеку типу. Ці переваги разом можуть покращити якість коду та запобігти введенню деяких помилок кодування.
Наприклад, для методу, коли програміст хоче забезпечити змінну списку під назвою "імена", містить лише рядки:
List<String> names = new ArrayList<String>();
names.add("John"); // OK
names.add(new Integer(1)); // compile error
polygenelubricants
посилання "необробленого типу" із stackoverflow.com/questions/2770111/… у свою власну відповідь, але я вважаю, що залишу їх для використання у власній відповіді.
Компілятор хоче, щоб ви написали це:
private static List<String> list = new ArrayList<String>();
бо в іншому випадку ви можете додати будь-який тип, який вам подобається list
, зробивши цю інстанцію new ArrayList<String>()
безглуздою. Java-дженерики - це лише функція часу компіляції, тому об’єкт, створений за допомогою, new ArrayList<String>()
буде радісно приймати Integer
або JFrame
елементи, якщо йому присвоєно посилання "необробленого типу" List
- сам об'єкт нічого не знає про те, які типи він повинен містити, лише компілятор.
Тут я розглядаю кілька випадків, за допомогою яких можна прояснити концепцію
1. ArrayList<String> arr = new ArrayList<String>();
2. ArrayList<String> arr = new ArrayList();
3. ArrayList arr = new ArrayList<String>();
ArrayList<String> arr
це ArrayList
посилальна змінна з типом, на String
яку посилається ArralyList
об'єкт типу String
. Це означає, що він може містити лише об'єкт типу String.
Це суворий, а String
не сирий тип, тому він ніколи не підніме попередження.
arr.add("hello");// alone statement will compile successfully and no warning.
arr.add(23); //prone to compile time error.
//error: no suitable method found for add(int)
У цьому випадку ArrayList<String> arr
це суворий тип, але ваш Об'єкт new ArrayList();
є необробленим.
arr.add("hello"); //alone this compile but raise the warning.
arr.add(23); //again prone to compile time error.
//error: no suitable method found for add(int)
тут arr
строгий тип. Отже, це додасть помилку часу компіляції при додаванні integer
.
Попередження : -
Raw
Об'єкт типу посилається наStrict
тип змінної змінноїArrayList
.
У цьому випадку ArrayList arr
це необроблений тип, але ваш об'єкт new ArrayList<String>();
- строгий тип.
arr.add("hello");
arr.add(23); //compiles fine but raise the warning.
Він додасть до нього будь-який тип Об'єкту, оскільки arr
це Сирий тип.
Попередження : -
Strict
Об'єктraw
типу посилається на тип змінної змінної.
Сировини типу є відсутність в параметрі типу при використанні універсального типу.
Сировина типу не слід використовувати , оскільки це може привести до помилок у час виконання, як і вставки double
в те , що повинно було бути Set
в int
с.
Set set = new HashSet();
set.add(3.45); //ok
Отримуючи речі з програми Set
, ви не знаєте, що виходить. Припустимо, що ви очікуєте, що це все буде int
, ви кидаєте це Integer
; виняток під час виконання, колиdouble
йде 3.45.
Якщо параметр типу доданий до вашого Set
, ви отримаєте помилку компіляції відразу. Ця попередня помилка дозволяє вирішити проблему, перш ніж щось підірветься під час виконання (таким чином заощаджуючи час та зусилля).
Set<Integer> set = new HashSet<Integer>();
set.add(3.45); //NOT ok.
Ось ще один випадок, коли сирі типи будуть кусати вас:
public class StrangeClass<T> {
@SuppressWarnings("unchecked")
public <X> X getSomethingElse() {
return (X)"Testing something else!";
}
public static void main(String[] args) {
final StrangeClass<String> withGeneric = new StrangeClass<>();
final StrangeClass withoutGeneric = new StrangeClass();
final String value1,
value2;
// Compiles
value1 = withGeneric.getSomethingElse();
// Produces compile error:
// incompatible types: java.lang.Object cannot be converted to java.lang.String
value2 = withoutGeneric.getSomethingElse();
}
}
Як було сказано у прийнятій відповіді, ви втрачаєте будь-яку підтримку для дженериків у коді необробленого типу. Кожен параметр типу перетворюється на його стирання (що у наведеному вище прикладі справедливо Object
).
Що говорить, що ваш list
єList
визначені об'єкти. Тобто Java не знає, які об’єкти знаходяться всередині списку. Тоді, коли ви хочете повторити список, вам потрібно передати кожен елемент, щоб мати доступ до властивостей цього елемента (у цьому випадку String).
Загалом, краща ідея параметризувати колекції, тому у вас не виникнуть проблеми з перетворенням, ви зможете лише додати елементи параметризованого типу, і ваш редактор запропонує вам вибрати відповідні методи.
private static List<String> list = new ArrayList<String>();
Сирий тип - це назва загального класу чи інтерфейсу без аргументів типу. Наприклад, враховуючи загальний клас Box:
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
Щоб створити параметризований тип Box, ви подаєте аргумент фактичного типу для формального параметра типу T:
Box<Integer> intBox = new Box<>();
Якщо аргумент фактичного типу пропущено, ви створюєте необроблений тип поля:
Box rawBox = new Box();
Уникайте сирих видів
Сирі типи відносяться до використання загального типу, не вказуючи параметр типу.
Наприклад ,
Список - це вихідний тип, поки List<String>
параметризований тип.
Коли дженерики були введені в JDK 1.5, вихідні типи зберігалися лише для підтримки зворотної сумісності зі старими версіями Java. Хоча використання сировинних типів все ще можливо,
Їх слід уникати :
Вони менш виразні і не самодокументують так само, як параметризовані типи Приклад
import java.util.*;
public final class AvoidRawTypes {
void withRawType() {
//Raw List doesn't self-document,
//doesn't state explicitly what it can contain
List stars = Arrays.asList("Arcturus", "Vega", "Altair");
Iterator iter = stars.iterator();
while (iter.hasNext()) {
String star = (String) iter.next(); //cast needed
log(star);
}
}
void withParameterizedType() {
List < String > stars = Arrays.asList("Spica", "Regulus", "Antares");
for (String star: stars) {
log(star);
}
}
private void log(Object message) {
System.out.println(Objects.toString(message));
}
}
Довідково : https://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html
Я знайшов цю сторінку, зробивши кілька зразків вправ і маючи таку саму загадку.
============== Я пішов з цього коду, як надає зразок ================
public static void main(String[] args) throws IOException {
Map wordMap = new HashMap();
if (args.length > 0) {
for (int i = 0; i < args.length; i++) {
countWord(wordMap, args[i]);
}
} else {
getWordFrequency(System.in, wordMap);
}
for (Iterator i = wordMap.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry) i.next();
System.out.println(entry.getKey() + " :\t" + entry.getValue());
}
====================== До цього коду =========================
public static void main(String[] args) throws IOException {
// replace with TreeMap to get them sorted by name
Map<String, Integer> wordMap = new HashMap<String, Integer>();
if (args.length > 0) {
for (int i = 0; i < args.length; i++) {
countWord(wordMap, args[i]);
}
} else {
getWordFrequency(System.in, wordMap);
}
for (Iterator<Entry<String, Integer>> i = wordMap.entrySet().iterator(); i.hasNext();) {
Entry<String, Integer> entry = i.next();
System.out.println(entry.getKey() + " :\t" + entry.getValue());
}
}
===================================================== ==============================
Це може бути безпечнішим, але для того, щоб занепокоїти філософію, знадобилося 4 години ...
Сирі типи добре, коли вони виражають те, що ви хочете висловити.
Наприклад, функція десеріалізації може повернути a List
, але вона не знає тип елемента списку. Тож List
відповідний тип повернення тут.