1. Всички класове наследяватObject

Всички класове в Java имплицитно наследяват Objectкласа.

Ще анализираме Howво е наследяването и How работи в Java в търсенето на Java Core. Засега ще разгледаме един прост факт, който следва от това:

Обект от всеки клас може да бъде присвоен на Objectпроменлива. Пример:

Код Забележка
Object o = new Scanner(System.in);
Променливата oсъхранява препратка към Scannerобект
Object o = new String();
Променливата oсъхранява препратка към Stringобект
Object o = new Integer(15);
Променливата oсъхранява препратка към Integerобект
Object o = "Hello";
Променливата oсъхранява препратка към Stringобект

Тук добрите новини свършват. Компилаторът не следи оригиналния тип обект, записан в Objectпроменлива, така че няма да можете да извиквате методи на записания обект , различни от методите на Objectкласа.

Ако трябва да извикате методите, свързани с оригиналния тип на обекта, тогава трябва първо да запишете препратка към него в променлива от правилния тип и след това да извикате методите на тази променлива:

Код Забележка
Object o = new Scanner(System.in);
int x = o.nextInt();
Програмата няма да се компorра. Класът Objectняма nextInt()метод.
Object o = new Scanner(System.in);

Scanner console = (Scanner) o;

int x = console.nextInt();
Това ще свърши работа.

Тук запазваме препратка към Scannerобект в Scannerпроменлива, използвайки оператор за придаване на тип .

Не можете просто да отидете и да присвоите Objectпроменлива на променлива на Scanner, дори ако Objectпроменливата съхранява препратка към Scannerобект. Но можете да направите това, ако използвате оператора typecast , за който вече знаете. Това е общият му вид:

Type name1 = (Type) name2;

Къде name1е името на Typeпроменлива и name2е името на Objectпроменлива, която съхранява препратка към Typeобект.

Преобразуване на типове

Ако типът на променливата и типът на обекта не съвпадат, тогава ClassCastExceptionще бъде хвърлено a. Пример:

Код Забележка
Object o = new Integer(5);
String s = (String) o;
Ще възникне грешка по време на изпълнение: тук ще бъде изведено
aClassCastException

Има начин да избегнете тази грешка в Java: правим това, като проверяваме типа на обекта, съхранен в променлива :

name instanceof Type

Операторът instanceofпроверява дали nameпроменливата е Typeобект.

Като пример, нека намерим низ в масив от различни обекти:

Код Забележка
Object[] objects = {10, "Hello", 3.14};

for (int i = 0; i < objects.length; i++)
{
   if (objects[i] instanceof String)
   {
      String s = (String) objects[i];
      System.out.println(s);
   }
}
Autoboxing ще преобразува тези стойности съответно в Integer, Stringи Double.

Цикъл върху масива от обекти

Ако обектът е String

Запазете го в Stringпроменлива
Показване на променливата на екрана.


2. Защо се появиха генериците — колекции

Да се ​​върнем към колекциите.

Веднага след като Java разработчиците създадоха ArrayListкласа, те искаха да го направят универсален, така че да може да съхранява всяHowъв тип обект. Така че те използваха масив от Objects за съхраняване на елементите.

Силата на този подход е, че можете да добавите обект от всяHowъв тип към колекцията.

Разбира се, има няколко слабости.

Недостатък 1.

Винаги е било необходимо да се напише оператор за преобразуване на тип, когато се извличат елементи от колекция:

Код Забележка
ArrayList numbers = new ArrayList();


for (int i = 0; i < 10; i++)
   numbers.add(i * 10);


int sum = 0;
for (int i = 0; i < 10; i++)
{
   sum = sum + (Integer) numbers.get(i);
}
Създайте колекция за съхраняване на препратки към Objectобекти

. Попълнете колекцията с числа 10, 20, ... 100;



Обобщете елементите на колекцията.


Необходимо е преобразуване на типове

Недостатък 2.

Нямаше гаранция, че колекцията съдържа определен тип елемент

Код Забележка
ArrayList numbers = new ArrayList();


for (int i = 0; i < 10; i++)
   numbers.add(i * 2.5);


int sum = 0;
for (int i = 0; i < 10; i++)
{
   sum = sum + (Integer) numbers.get(i);
}
Създайте колекция за съхраняване на препратки към Objectобекти

Ние запълваме колекцията с числа, представени като Doubleобекти:
0.0, 2.5, 5.0, ...


Сумирайте елементите на колекцията


Ще има грешка: a Doubleне може да бъде преобразувано вInteger

Данните могат да бъдат поставени в колекцията навсякъде:

  • в друг метод
  • в друга програма
  • от файл
  • по мрежата

Недостатък 3.

Данните в колекцията могат да бъдат променени случайно.

Можете да предадете колекция, пълна с вашите данни, към няHowъв метод. Този метод, написан от друг програмист, добавя своите данни към вашата колекция.

Името на колекцията не показва ясно кои типове данни могат да се съхраняват в нея. И дори ако дадете ясно име на вашата променлива, препратка към нея може да бъде предадена на дузина методи и тези методи определено няма да знаят нищо за оригиналното име на променливата.


3. Генерични лекарства

Generics в Java

В Java всички тези проблеми се елиминират от това страхотно нещо, наречено генерични.

В Java генеричните средства означават възможността за добавяне на параметри на типове към типове. Резултатът е сложен композитен тип. Общият изглед на такъв съставен тип е следният:

ClassName<TypeParameter>

Това е общ клас. И може да се използва навсякъде, където обикновено използвате класове.

Код Описание
ArrayList<Integer> list;
Създаване на променливи
list = new ArrayList<Integer> ();
Създаване на обекти
ArrayList<Integer>[] array;
Създаване на масиви

IntegerВ такава колекция могат да се съхраняват само променливи:

Код Описание
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(new Integer(1));
list.add(2);
list.add("Hello");
ArrayListколекция с Integerелементи
Това е разрешено
И това също ще работи
Автобокс

Но това не е позволено: грешка при компorране

Ще научите How да създавате свои собствени класове с параметри на типа в търсенето на Java Collections. Засега ще разгледаме How да ги използваме и How работят.


4. Как работят генеричните лекарства

Всъщност генериците са ужасно примитивни.

Компилаторът просто заменя генеричните типове с обикновени типове. Но когато се използват методи от общ тип, компилаторът добавя оператор за преобразуване на типа за преобразуване на параметри към параметрите на типа:

Код Какво прави компилаторът
ArrayList<Integer> list = new ArrayList<Integer>();
ArrayList list = new ArrayList();
list.add(1);
list.add( (Integer) 1 );
int x = list.get(0);
int x = (Integer) list.get(0);
list.set(0, 10);
list.set(0, (Integer) 10);

Да предположим, че имаме метод, който сумира числата в колекция от цели числа:

Код Какво прави компилаторът
public int sum(ArrayList<Integer> numbers)
{
   int result = 0;

   for (int i = 0; i < numbers.size(); i++)
      result = result + numbers.get(i);

   return result;
}
public int sum(ArrayList numbers)
{
   int result = 0;

   for (int i = 0; i < numbers.size(); i++)
      result = result + (Integer) numbers.get(i);

   return result;
}

С други думи, генериците са вид синтактична захар, точно като autoboxing, но малко повече. С autoboxing компилаторът добавя методи за преобразуване на an intв an Integerи обратно, а за генеричните добавя оператори за придаване на тип.

След като компилаторът компorра вашите генерични класове с параметри на типа, те просто се преобразуват в обикновени класове и оператори за преобразуване на типа. Информацията за аргументите на типа, предадени на променливи от общи типове, се губи. Този ефект се нарича още изтриване на типа .

Понякога програмистите, които пишат генерични класове (класове с параметри на типа), наистина се нуждаят от информацията за типовете, предадени като аргументи. В търсенето на Java Collections ще научите How да се справите с това и Howво включва то.



5. Няколко факта за генериците

Ето още няколко интересни факта за генериците.

Класовете могат да имат няколко параметъра на типа. Изглежда нещо подобно:

ClassName<TypeParameter1, TypeParameter2, TypeParameter3>

Всъщност това не е изненадващо. Навсякъде, където компилаторът може да добави оператор за преобразуване към един тип, той може да добави множество оператори за преобразуване на типа.

Примери:

Код Забележка
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(7, "Hello");
map.put(-15, "Hello");
Първият putпараметър на метода е a Integer, а вторият е aString

Генеричните типове също могат да се използват като параметри . Изглежда нещо подобно:

ClassName<TypeParameter<TypeParameterParameter>>

Да предположим, че искаме да създадем списък, който ще съхранява списъци с низове. В този случай ще получим нещо подобно:

// List of greetings
ArrayList<String> listHello = new ArrayList<String>();
listHello.add ("Hello");
listHello.add ("Hi");

// List of goodbyes
ArrayList<String> listBye = new ArrayList<String>();
listBye.add("Bye");
listBye.add ("Goodbye");

// List of lists
ArrayList<ArrayList<String>> lists = new ArrayList<ArrayList<String>>();
lists.add(listHello);
lists.add(listBye);

Генеричните типове (типове с параметри на типа) също могат да се използват като типове масиви. Изглежда нещо подобно:

ClassName<TypeParameter>[] array = new ClassName<TypeParameter>[size];

Тук не се случва нищо магическо: ъгловите скоби просто показват името на типа:

Код Негенеричен аналог
ArrayList<String>[] list = new ArrayList<String>[10];
StringArrayList[] list = new StringArrayList[10];
ArrayList<Integer>[] list = new ArrayList<Integer>[10];
IntegerArrayList[] list = new IntegerArrayList[10];
ArrayList<Scanner>[] list = new ArrayList<Scanner>[10];
ScannerArrayList[] list = new ScannerArrayList[10];