CodeGym /Java блог /Случаен /Топ 50 въпроса и отговори за интервю за работа за Java Co...
John Squirrels
Ниво
San Francisco

Топ 50 въпроса и отговори за интервю за работа за Java Core. Част 1

Публикувано в групата
Здравейте на всички, дами и господа, софтуерни инженери! Нека поговорим за въпросите за интервюто. За Howво трябва да се подготвите и Howво трябва да знаете. Това е страхотен момент да прегледате or да проучите тези точки за първи път. Топ 50 въпроса и отговори за интервю за работа за Java Core.  Част 1 - 1 В крайна сметка получих доста обширна колекция от често задавани въпроси относно ООП, Java синтаксис, Java изключения, колекции и многопоточност, които ще разделя на няколко части за удобство. Трудно е да се обхване всичко наведнъж, но се надявам, че този материал ще осигури добра основа за онези, които се готвят да намерят първата си работа като програмист. За най-добро разбиране и задържане съветвам да прегледате и други източници. Можете да разберете по-дълбоко една концепция, като подходите към нея от няколко различни ъгъла. Важно:Ще говорим само за Java преди version 8. Всички иновации, дошли във версии 9, 10, 11, 12 и 13, няма да бъдат разглеждани тук. ВсяHowви идеи/коментари за това How да се подобрят отговорите са добре дошли . Приятно четене. Да тръгваме!

Java интервю: въпроси за ООП

1. Какви са характеристиките на Java?

Отговор:
  1. ООП концепции:

    1. обектна ориентация
    2. наследство
    3. капсулиране
    4. полиморфизъм
    5. абстракция
  2. Междуплатформен: Java програма може да се изпълнява на всяка платформа без ниHowви промени. Разбира се, това изисква инсталиран JVM (Java виртуална машина).

  3. Висока производителност: Компилаторът Just-In-Time (JIT) прави възможна висока производителност. JIT компилаторът преобразува byte codeа в машинен code и след това JVM започва изпълнението.

  4. Многопоточност: JVM създава нишка за изпълнение, наречена main thread. Програмистът може да създаде множество нишки, като извлече от класа Thread or имплементира Runnableинтерфейса.

2. Какво е наследство?

Наследяването означава, че един клас може да наследи друг клас (с помощта на ключовата дума extends ). Това означава, че можете да използвате повторно code от класа, който наследявате. Съществуващият клас е известен като, superclassа новосъздаденият клас е subclass. Хората също казват да използват термините родител и child.

public class Animal {
   private int age;
}

public class Dog extends Animal {

}
къде Animalе parentи Dogе child.

3. Какво е капсулиране?

Този въпрос често се задава в интервюта за позиции на Java разработчици. Капсулирането крие изпълнението чрез използване на модификатори за достъп, гетери и сетери. Това се прави, за да се предотврати външен достъп, където разработчиците смятат, че е необходимо. Прост пример от реалния живот е колата. Нямаме пряк достъп до работата на двигателя. Всичко, което трябва да направим, е да поставим ключа в запалването и да включим двигателя. Процесите, които протичат под капака, не са наша работа. Освен това, ако трябва да се намесим в дейността на двигателя, това може да доведе до непредсказуема ситуация, евентуално да повреди колата и да доведе до телесна повреда. Абсолютно същото се случва в програмирането. Това е добре описано в Уикипедия. Има и статия за капсулирането в CodeGym .

4. Какво е полиморфизъм?

Полиморфизмът е способността на програмата да третира обекти със същия интерфейс по един и същи начин, без информация за конкретния тип на обекта. Както се казва, "един интерфейс - много реализации". С полиморфизма можете да комбинирате и използвате различни типове обекти въз основа на споделено поведение. Например, имаме клас Animal, който има два наследника: Dog и Cat. Общият клас Animal има поведение, споделяно от всички, способността да издава звук. Ние използваме полиморфни възможности, когато трябва да съберем всичко, което наследява класа Animal и да изпълним метода „издаване на звук“. Ето How изглежда:

List<Animal> animals = Arrays.asList(new Cat(), new Dog(), new Cat());
animals.forEach(animal -> animal.makeSound());
С други думи, полиморфизмът е полезен. И това се отнася и за полиморфните (претоварени) методи. Как да използваме полиморфизъм

Въпроси за интервю за синтаксиса на Java

5. Какво е конструктор в Java?

Конструкторите имат следните характеристики:
  1. Когато се създаде нов обект, програмата използва подходящия конструктор, за да го създаде.
  2. Конструкторът е като метод. Неговите отличителни черти се състоят във факта, че няма върната стойност (включително void) и че името му е същото като името на класа.
  3. Ако не е създаден изрично конструктор, автоматично се създава празен конструктор.
  4. Конструкторът може да бъде заменен.
  5. Ако декларирате конструктор с параметри, но имате нужда и от такъв без параметри, тогава трябва да го създадете отделно, тъй като няма да бъде създаден автоматично.

6. Кои два класа не наследяват Object?

Не се заблуждавайте от трикови въпроси - няма такива класове. Всички класове наследяват класа Object директно or чрез предци!

7. Какво е локална променлива?

Това е друг популярен въпрос за интервю за Java разработчици. Локална променлива е променлива, която е дефинирана вътре в метод и съществува, докато методът се изпълнява. Веднага щом изпълнението приключи, локалната променлива престава да съществува. Ето една програма, която използва локална променлива с име helloMessage в метода main():

public static void main(String[] args) {
   String helloMessage;
   helloMessage = "Hello, World!";
   System.out.println(helloMessage);
}

8. Какво е променлива на екземпляр?

Променлива на екземпляр е променлива, която е декларирана вътре в клас. Съществува, докато съществува един обект. Например, имаме клас Bee, който има две екземплярни променливи — nectarLoad и maxNectarLoad:

public class Bee {

   /**
    * Current nectar load
    */
   private double nectarLoad;

   /**
    * Maximum nectar that can the bee can collect.
    */
   private double maxNectarLoad = 20.0;
 
  ...
}

9. Какво представляват модификаторите за достъп?

Модификаторите за достъп са механизъм за персонализиране на достъпа до класове, методи и променливи. Съществуват следните модификатори, изброени по ред на увеличаване на достъпа:
  1. private— Този модификатор за достъп се използва за методи, полета и конструктори. Достъпът е ограничен до класа, в който са декларирани.
  2. package-private (default)— Това е нивото на достъп по подразбиране за класове. Достъпът е ограничен до конкретния пакет, в който е деклариран клас, метод, променлива or конструктор.
  3. protected— Този модификатор на достъп предлага същото ниво на достъп, Howто package-privateпри добавянето на достъп за класове, които наследяват клас с protectedмодификатора.
  4. public— Това ниво на достъп се използва и за класове. Това ниво на достъп означава, че има пълен достъп в цялото приложение.
Топ 50 въпроса и отговори за интервю за работа за Java Core.  Част 1 - 2

10. Какво е отмяна на метода?

Ние отменяме методите, когато дъщерен клас иска да промени поведението на своя родителски клас. Ако също трябва да направим това, което е в родителския метод, можем да използваме super.methodName() в дъщерния, който ще изпълни родителския метод. След това можем да добавим нашата допълнителна логика. Изисквания, които трябва да се спазват:
  • сигнатурата на метода трябва да е същата
  • върнатата стойност трябва да е същата

11. Какво представляват сигнатурите на метода?

Топ 50 въпроса и отговори за интервю за работа за Java Core.  Част 1 - 3Сигнатурата на метода е комбинация от името на метода и аргументите, които методът приема. Сигнатурата на метода е уникалният идентификатор на метода при претоварване на методи.

12. Какво е претоварване на метода?

Претоварването на метода е характеристика на полиморфизма, при която променяме сигнатурата на метода, за да създадем множество методи, които изпълняват едно и също действие:
  • същото име
  • различни аргументи
  • може да има различни видове връщане
Например, методът ArrayListна класа add()може да бъде претоварен, което ни позволява да добавяме по различни начини в зависимост от входните аргументи:
  • add(Object o)— Този метод просто добавя обект
  • add(int index, Object o)— Този метод добавя обект към конкретен индекс
  • add(Collection<Object> c)— Този метод добавя списък с обекти
  • add(int index, Collection<Object> c)— Този метод добавя списък с обекти, започвайки от определен индекс.

13. Какво е интерфейс?

Java не поддържа множествено наследяване. За да се преодолее това ограничение, бяха добавени интерфейси във форма, която познаваме и обичаме ;) Дълго време интерфейсите имаха само методи без ниHowва реализация. В контекста на този отговор нека поговорим за тях. Например:


public interface Animal {
   void makeSound();
   void eat();
   void sleep();
}
Някои подробности следват от това:
  • Всички методи в интерфейса са публични и абстрактни
  • Всички променливи са публични статични финални
  • Класовете не наследяват интерфейси (т.е. не използваме ключовата дума extends). Вместо това класовете ги имплементират (т.е. използваме ключовата дума implements). Освен това можете да внедрите толкова интерфейси, колкото искате.
  • Класовете, които имплементират интерфейс, трябва да предоставят реализация на всички методи, които са в интерфейса.
Като този:

public class Cat implements Animal {
   public void makeSound() {
       // Method implementation
   }

   public void eat() {
       // Implementation
   }

   public void sleep() {
       // Implementation
   }
}

14. Какво е метод по подразбиране в интерфейс?

Сега нека поговорим за методите по подразбиране. За Howво са те? За кого са те? Тези методи бяха добавени, за да обслужват "и двете ръце". за Howво говоря Е, от една страна, имаше нужда от добавяне на нова функционалност: ламбда и API на потока. От друга страна, беше необходимо да се запази това, с което Java е известна - обратна съвместимост. За да направят това, интерфейсите се нуждаеха от някои нови готови решения. Ето How методите по подразбиране дойдоха при нас. Методът по подразбиране е внедрен метод в интерфейс, маркиран с defaultключовата дума. Например, добре познатият stream()метод в Collectionинтерфейса. Повярвайте ми, този интерфейс не е толкова прост, колкото изглежда. Или също толкова известния forEach()метод вIterableинтерфейс. Освен това не съществуваше, докато не бяха добавени методите по подразбиране. Между другото, можете също да прочетете за това в CodeGym тук .

15. Как тогава да наследим два идентични метода по подразбиране?

Предишният отговор за това Howво е метод по подразбиране повдига друг въпрос. Ако можете да имплементирате методи в интерфейси, тогава теоретично можете да имплементирате два интерфейса с един и същ метод. Как да направим това? Ето два различни интерфейса със същия метод:

interface A {
   default void foo() {
       System.out.println("Foo A");
   }
}

interface B {
   default void foo() {
       System.out.println("Foo B");
   }
}
И имаме клас, който имплементира тези два интерфейса. Но How точно да изберем конкретен метод в интерфейс A or B? Следната специална конструкция позволява това: A.super.foo():

public class C implements A, B {
   public void fooA() {
       A.super.foo();
   }

   public void fooB() {
       B.super.foo();
   }
}
По този начин fooA()методът ще използва foo()метода по подразбиране на Aинтерфейса, докато fooB()методът ще използва foo()метода на Bинтерфейса.

16. Какво представляват абстрактните методи и класове?

В Java abstractе запазена дума. Използва се за обозначаване на абстрактни класове и методи. Първо, имаме нужда от определения. Абстрактен метод е метод, който се декларира с помощта на abstractключовата дума без реализация в абстрактен клас. Тоест, това е метод като в интерфейс, но с добавяне на ключова дума, например:

public abstract void foo();
Абстрактен клас е клас, също маркиран с abstractключовата дума:

public abstract class A {

}
Абстрактният клас има няколко характеристики:
  • не можете да създадете обект от абстрактен клас
  • може да има абстрактни методи
  • може също да няма абстрактни методи
Абстрактните класове са необходими за абстракция (съжалявам за тавтологията), която има набор от общи поведения и състояния (т.е. методи и променливи). Реалният живот е пълен с примери. Всичко около нас. „Животно“, „Кола“, „Геометрична фигура“ и т.н.

17. Каква е разликата между String, StringBuilder и StringBuffer?

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

String name = "book";
Променливата ще сочи към постоянния набор от низове. Топ 50 въпроса и отговори за интервю за работа за Java Core.  Част 1 - 4Задавайки променливата име на различна стойност, имаме:

name = "pen";
Константният набор от низове изглежда така: Топ 50 въпроса и отговори за интервю за работа за Java Core.  Част 1 - 5С други думи, и двете стойности остават там. Буфер за низове:
  • Stringстойностите се съхраняват в стек. Ако дадена стойност бъде променена, тогава новата стойност ще замени старата.
  • String Bufferе синхронизиран и следователно е безопасен за нишки.
  • Поради безопасността на конеца, работата му е лоша.
Пример:

StringBuffer name = “book”;
Топ 50 въпроса и отговори за интервю за работа за Java Core.  Част 1 - 6Веднага след като стойността на променливата име се промени, стойността в стека се променя: Топ 50 въпроса и отговори за интервю за работа за Java Core.  Част 1 - 7StringBuilder е точно същият като StringBuffer, само че не е безопасен за нишки. В резултат на това е значително по-бърз от StringBuffer.

18. Каква е разликата между абстрактен клас и интерфейс?

Абстрактен клас:
  • Абстрактните класове имат конструктор по подразбиране. Извиква се всеки път, когато се създава наследник на абстрактния клас.
  • Те могат да включват Howто абстрактни методи, така и неабстрактни. Като цяло, абстрактен клас не трябва да има абстрактни методи.
  • Клас, който наследява абстрактен, трябва да прилага само абстрактни методи.
  • Абстрактен клас може да има променливи на екземпляр (вижте въпрос #5).
Интерфейс:
  • Интерфейсът няма конструктор и не може да бъде инициализиран.
  • Могат да се добавят само абстрактни методи (с изключение на методите по подразбиране).
  • Класовете, които имплементират интерфейса, трябва да имплементират всички методи (с изключение на методите по подразбиране).
  • Интерфейсите могат да имат само константи.

19. Защо достъпът до елемент в масив е O(1)?

Този въпрос беше буквално зададен в последното ми интервю. Както разбрах по-късно, целта на този въпрос е да се види How мисли човек. Ясно е, че има малка практическа стойност в това знание. Самото знание е достатъчно. Първо, трябва да изясним, че O(1) е нотация за времевата сложност на алгоритъм за "постоянно време". С други думи, това обоmeaning показва най-бързото време за изпълнение. За да отговорим на този въпрос, трябва да разгледаме Howво знаем за масивите. За да създадем intмасив, трябва да напишем следното:

int[] intArray = new int[100];
От този синтаксис могат да се направят няколко заключения:
  1. Когато масивът е деклариран, неговият тип е известен. Ако типът е известен, тогава е известен размерът на всяка клетка в масива.
  2. Размерът на целия масив е известен.
От това следва, че за да разберем в коя клетка да пишем, просто трябва да изчислим в коя област от паметта да пишем. За компютър това е лесно. Компютърът знае къде започва разпределената памет, броя на елементите и размера на всяка клетка. Всичко това означава, че мястото за запис ще бъде равно на началното място на масива + размера на всяка клетка, умножен по индекса.

И така, How да стигнем до O(1) при достъп до обекти в ArrayList?

Този въпрос следва непосредствено предишния. Истината е, че когато работим с масив, който съдържа примитиви, знаем предварително (в момента на създаване) размера на типа елемент. Но Howво да правим, ако имаме този вид йерархия на наследяване и Топ 50 въпроса и отговори за интервю за работа за Java Core.  Част 1 - 8искаме да създадем колекция за елементи от тип A и да добавим различни реализации (B, C и D):

List<A> list = new ArrayList();
list.add(new B());
list.add(new C());
list.add(new D());
list.add(new B());
В тази ситуация, How да изчислим размера на всяка клетка? В края на краищата всеки обект ще бъде различен, евентуално с различни допълнителни полета. Какво да правя? Тук въпросът е поставен по начин, който има за цел да ви обърка. Знаем, че колекцията не съхранява директно обекти. Той съхранява само препратки към обекти. И всички препратки имат еднакъв размер и това е известно. В резултат на това изчисляваме addressите тук по същия начин, Howто в предишния въпрос.

21. Автобоксиране и разопаковане

Исторически контекст: автоматичното поставяне в кутия и разопаковане са някои от основните нововъведения в JDK 5. Автоматичното поставяне в кутия е процесът на автоматично преобразуване от примитивен тип в съответен клас обвивка. Unboxing е точно обратното на autoboxing. Това е процес на преобразуване на обвиващ клас в примитивен. Но ако стойността на обвивката е null, тогава NullPointerExceptionще бъде хвърлено по време на разопаковане.

Примитиви и съответните им обвивки

Примитивен Клас обвивка
булево Булева стойност
вътр Цяло число
byte Байт
въглен Характер
плавам Поплавък
дълго Дълги
къс Къс
двойно Двойна

// Autoboxing се случва:

  • когато присвоявате примитив на препратка към клас обвивка:

    ПРЕДИ Java 5:

    
    // Manual boxing (the way it was BEFORE Java 5).
    public void boxingBeforeJava5() {
       Boolean booleanBox = new Boolean(true);
       Integer intBox = new Integer(3);
       // And so on for other types
    }
    
    After Java 5:
    // Automatic boxing (the way it became in Java 5).
    public void boxingJava5() {
       Boolean booleanBox = true;
       Integer intBox = 3;
       // And so on for other types
    }
    
  • когато примитив се предава като аргумент на метод, който очаква обвивка:

    
    public void exampleOfAutoboxing() {
       long age = 3;
       setAge(age);
    }
    
    public void setAge(Long age) {
       this.age = age;
    }
    

// Разопаковането се случва:

  • когато присвоим екземпляр на клас обвивка на примитивна променлива:

    
    // BEFORE Java 5:
    int intValue = new Integer(4).intValue();
    double doubleValue = new Double(2.3).doubleValue();
    char c = new Character((char) 3).charValue();
    boolean b = Boolean.TRUE.booleanValue();
    
    // And after JDK 5:
    int intValue = new Integer(4);
    double doubleValue = new Double(2.3);
    char c = new Character((char) 3);
    boolean b = Boolean.TRUE;
    
  • По време на аритметични операции. Операциите се прилагат само за примитивни типове, така че е необходимо разопаковане на примитиви.

    
    // BEFORE Java 5:
    Integer integerBox1 = new Integer(1);
    Integer integerBox2 = new Integer(2);
    
    // A comparison used to require this:
    integerBox1.intValue() > integerBox2.intValue()
          
    // In Java 5
    integerBox1 > integerBox2
    
  • при предаване на екземпляр на клас обвивка към метод, който приема съответния примитив:

    
    public void exampleOfAutoboxing() {
       Long age = new Long(3);
       setAge(age);
    }
    
    public void setAge(long age) {
       this.age = age;
    }
    

22. Коя е крайната ключова дума и къде се използва?

Ключовата finalдума може да се използва за променливи, методи и класове.
  1. Стойността на крайната променлива не може да бъде променена, след като е инициализирана.
  2. Последен клас е стерилен :) Не може да има деца.
  3. Последният метод не може да бъде заменен от наследник.
Покрихме нещата от високо ниво. Сега нека се гмурнем по-дълбоко.

Крайни променливи

Java ни дава два начина да декларираме променлива и да й присвоим стойност:
  1. Можете да декларирате променлива и да я инициализирате по-късно.
  2. Можете да декларирате променлива и да присвоите стойност веднага.
Ето един пример, който демонстрира тези употреби на крайни променливи:

public class FinalExample {

   // A static final variable that is immediately initialized:
   final static String FINAL_EXAMPLE_NAME = "I'm likely the final one";

   // A final variable that is not initialized, but will only work if you
   // initialize it in the constructor:
   final long creationTime;

   public FinalExample() {
       this.creationTime = System.currentTimeMillis();
   }

   public static void main(String[] args) {
       FinalExample finalExample = new FinalExample();
       System.out.println(finalExample.creationTime);

       // The final FinalExample.FINAL_EXAMPLE_NAME field cannot be accessed
//    FinalExample.FINAL_EXAMPLE_NAME = "Not you're not!";

       // The final Config.creationTime field cannot be accessed
//    finalExample.creationTime = 1L;
   }
}

Може ли крайната променлива да се счита за константа?

Тъй като не можем да присвоим нови стойности на крайните променливи, изглежда, че това са постоянни променливи. Но само на пръв поглед: ако типът данни на променливата е immutable, тогава, да, това е константа. Но ако типът данни е mutable, тоест променлив, тогава ще бъде възможно да се използват методи и променливи за промяна на стойността на обекта, към който се отнася променливата final. Поради това не може да се нарече константа. Следващият пример показва, че някои крайни променливи са наистина константи, докато други не са, тъй като могат да бъдат променяни.

public class FinalExample {

   // Immutable final variables
   final static String FINAL_EXAMPLE_NAME = "I'm likely the final one";
   final static Integer FINAL_EXAMPLE_COUNT  = 10;

   // Mutable final variables
   final List<String> addresses = new ArrayList();
   final StringBuilder finalStringBuilder = new StringBuilder("Constant?");
}

Локални крайни променливи

Когато finalпроменлива е създадена в рамките на метод, тя се нарича променлива local final:

public class FinalExample {

   public static void main(String[] args) {
       // You can do this
       final int minAgeForDriveCar = 18;

       // Or you can do this, in a for-each loop:
       for (final String arg : args) {
           System.out.println(arg);
       }
   }

}
Можем да използваме ключовата дума final в подобрен for цикъл, тъй като след всяка итерация на цикъла се създава нова променлива. Имайте предвид, че това не се отнася за нормален for цикъл, така че ще получим грешка по време на компorране.

// The final local j variable cannot be assigned
for (final int i = 0; i < args.length; i ++) {
   System.out.println(args[i]);
}

Заключителен клас

Клас, деклариран като, finalне може да бъде разширен. Казано по-просто, никой друг клас не може да го наследи. Отличен пример за finalклас в JDK е String. Първата стъпка към създаването на неизменен клас е да го маркирате като final, като по този начин предотвратите разширяването му:

public final class FinalExample {
}

// Compilation error!
class WantsToInheritFinalClass extends FinalExample {
}

Крайни методи

Когато даден метод е маркиран като окончателен, той се нарича окончателен метод (има смисъл, нали?). Краен метод не може да бъде заменен в дъщерен клас. Между другото, методите wait() и notify() на класа Object са окончателни, така че нямаме възможност да ги заменим.

public class FinalExample {
   public final String generateAddress() {
       return "Some address";
   }
}

class ChildOfFinalExample extends FinalExample {

   // Compilation error!
   @Override
   public String generateAddress() {
       return "My OWN Address";
   }
}

Как и къде да използвате final в Java

  • Използвайте ключовата дума final, за да дефинирате някои константи на ниво клас;
  • Създайте крайни променливи за обекти, които не искате да бъдат променяни. Например, специфични за обекта свойства, които можем да използваме за целите на регистриране.
  • Ако не искате класът да бъде удължен, тогава го маркирайте като окончателен.
  • Ако трябва да създадете неизменен клас, трябва да го направите окончателен.
  • Ако искате изпълнението на даден метод да не се променя в неговите наследници, тогава маркирайте метода като final. Това е много важно, за да сте сигурни, че изпълнението няма да се промени.

23. Какво представляват изменяемите и неизменяемите типове?

Променлив

Променливите обекти са обекти, чието състояние и променливи могат да бъдат променяни след създаването. Примери за променливи класове включват StringBuilder и StringBuffer. Пример:

public class MutableExample {

   private String address;

   public MutableExample(String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   // This setter can change the name field
   public void setAddress(String address) {
       this.address = address;
   }

   public static void main(String[] args) {

       MutableExample obj = new MutableExample("First address");
       System.out.println(obj.getAddress());

       // We are updating the name field, so this is a mutable object
       obj.setAddress("Updated address");
       System.out.println(obj.getAddress());
   }
}

Неизменна

Неизменните обекти са обекти, чието състояние и променливи не могат да бъдат променяни след създаването на обекта. Страхотен ключ за HashMap, не мислите ли? :) Например String, Integer, Double и т.н. Пример:

// We'll make this class final so no one can change it
public final class ImmutableExample {

   private String address;

   ImmutableExample(String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   // We remove the setter

   public static void main(String[] args) {

       ImmutableExample obj = new ImmutableExample("Old address");
       System.out.println(obj.getAddress());

       // There is no way to change this field, so it is an immutable object
       // obj.setName("new address");
       // System.out.println(obj.getName());

   }
}
В следващата част разглеждаме въпроси и отговори относно колекциите. Моят профил в GitHub Топ 50 въпроса и отговори за интервю за работа за Java Core. Част 2
Коментари
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION