Java interjú: kérdések az OOP-ról
1. Mik a Java jellemzői?
Válasz:-
OOP fogalmak:
- tárgy orientáció
- öröklés
- Egységbezárás
- polimorfizmus
- absztrakció
-
Többplatformos: A Java program változtatás nélkül futtatható bármilyen platformon. Természetesen ehhez telepített JVM (Java virtuális gép) szükséges.
-
Nagy teljesítmény: A Just-In-Time (JIT) fordító nagy teljesítményt tesz lehetővé. A JIT fordító a bájtkódot gépi kóddá alakítja, majd a JVM elindítja a végrehajtást.
- Többszálú: A JVM létrehoz egy végrehajtási szálat, melynek neve
main thread
. A programozó több szálat is létrehozhat a Thread osztályból való származtatással vagy azRunnable
interfész megvalósításával.
2. Mi az öröklés?
Az öröklődés azt jelenti, hogy egy osztály örökölhet egy másik osztályt (az extends kulcsszó használatával). Ez azt jelenti, hogy újra felhasználhatja az örökölt osztály kódját. A meglévő osztály neve asuperclass
, az újonnan létrehozott osztály pedig a subclass
. Az emberek azt is mondják, hogy használják a szülő és a kifejezéseket child
.
public class Animal {
private int age;
}
public class Dog extends Animal {
}
hol Animal
van a parent
és Dog
a child
.
3. Mi az a kapszulázás?
Ezt a kérdést gyakran felteszik a Java fejlesztői pozíciókkal kapcsolatos interjúkban. A beágyazás elrejti a megvalósítást hozzáférésmódosítók, getterek és beállítók használatával. Ez azért történik, hogy megakadályozzák a külső hozzáférést mindenhol, ahol a fejlesztők szükségesnek tartják. Egy egyszerű példa a való életből az autó. Nincs közvetlen hozzáférésünk a motor működéséhez. Csak annyit kell tennünk, hogy a kulcsot a gyújtáskapcsolóba helyezzük, és beindítjuk a motort. A motorháztető alatt lezajló folyamatok nem a mi dolgunk. Sőt, ha beleavatkoznánk a motor működésébe, az előreláthatatlan helyzethez vezethet, esetleg az autó károsodásához és testi sérülésekhez vezethet. Pontosan ugyanez történik a programozásban is. Ezt jól leírják a Wikipédián. A CodeGym- en van egy cikk a kapszulázásról is .4. Mi a polimorfizmus?
A polimorfizmus egy program azon képessége, hogy azonos interfésszel rendelkező objektumokat ugyanúgy kezeljen, az objektum konkrét típusára vonatkozó információ nélkül. Ahogy a mondás tartja, "egy interfész - sok megvalósítás". A polimorfizmus segítségével különböző típusú objektumokat kombinálhat és használhat megosztott viselkedés alapján. Például van egy Animal osztályunk, amelynek két leszármazottja van: Kutya és Macska. Az általános Animal osztálynak mindenki által megosztott viselkedése van: hangot ad ki. Polimorf képességeket használunk, amikor össze kell gyűjtenünk mindent, ami az Animal osztályt örökli, és végre kell hajtanunk a "hangzás" metódust. Így néz ki:
List<Animal> animals = Arrays.asList(new Cat(), new Dog(), new Cat());
animals.forEach(animal -> animal.makeSound());
Más szóval, a polimorfizmus hasznos. És ez vonatkozik a polimorf (túlterhelt) módszerekre is. Hogyan használjuk a polimorfizmust
Interjúkérdések a Java szintaxissal kapcsolatban
5. Mi az a konstruktor a Java nyelven?
A kivitelezők a következő jellemzőkkel rendelkeznek:- Új objektum létrehozásakor a program a megfelelő konstruktort használja annak létrehozásához.
- A konstruktor olyan, mint egy metódus. Különlegessége abban rejlik, hogy nincs visszatérési érték (beleértve a void-ot is), és a neve megegyezik az osztály nevével.
- Ha nincs kifejezetten létrehozva konstruktor, akkor automatikusan létrejön egy üres konstruktor.
- Egy konstruktor felülbírálható.
- Ha deklarálunk egy konstruktort paraméterekkel, de szükségünk van egy paraméter nélkülire is, akkor azt külön kell létrehozni, mert nem jön létre automatikusan.
6. Melyik két osztály nem örökli az objektumot?
Ne tévesszen meg a trükkös kérdések – nincsenek ilyen órák. Minden osztály örökli az Object osztályt vagy közvetlenül, vagy ősökön keresztül!7. Mi az a lokális változó?
Ez egy másik népszerű interjúkérdés a Java fejlesztők számára. A lokális változó olyan változó, amely egy metóduson belül van definiálva, és mindaddig létezik, amíg a metódus fut. Amint a végrehajtás véget ér, a helyi változó megszűnik létezni. Itt van egy program, amely a helloMessage nevű helyi változót használja a main() metódusban:
public static void main(String[] args) {
String helloMessage;
helloMessage = "Hello, World!";
System.out.println(helloMessage);
}
8. Mi az a példányváltozó?
A példányváltozó egy osztályon belül deklarált változó. Addig létezik, amíg egy objektum létezik. Például van egy Bee osztályunk, amelynek két példányváltozója van - nectarLoad és maxNectarLoad:
public class Bee {
/**
* Current nectar load
*/
private double nectarLoad;
/**
* Maximum nectar that can the bee can collect.
*/
private double maxNectarLoad = 20.0;
...
}
9. Mik azok a hozzáférés-módosítók?
A hozzáférés-módosítók az osztályokhoz, metódusokhoz és változókhoz való hozzáférés testreszabására szolgáló mechanizmusok. A következő módosítók léteznek, a hozzáférés növelésének sorrendjében:private
— Ezt a hozzáférés-módosítót metódusokhoz, mezőkhöz és konstruktorokhoz használják. A hozzáférés arra az osztályra korlátozódik, amelyben deklarálva vannak.package-private (default)
— Ez az osztályok alapértelmezett hozzáférési szintje. A hozzáférés az adott csomagra korlátozódik, amelyben egy osztály, metódus, változó vagy konstruktor deklarálva van.protected
— Ez a hozzáférés-módosító ugyanazt a hozzáférési szintet kínálja, mintpackage-private
azokhoz az osztályokhoz való hozzáférés hozzáadásával, amelyek egy osztályt aprotected
módosítóval örökölnek.public
— Ezt a hozzáférési szintet az osztályokhoz is használják. Ez a hozzáférési szint azt jelenti, hogy az alkalmazás teljes körű hozzáférése van.
10. Mi az a módszer felülbírálása?
Felülbíráljuk a metódusokat, ha egy gyermekosztály meg akarja változtatni szülőosztálya viselkedését. Ha azt is meg kell tennünk, ami a szülő metódusban van, akkor a gyermekben használhatjuk a super.methodName()-t, amely végrehajtja a szülő metódust. Ezt követően hozzáadhatjuk a további logikánkat. Követelmények, amelyeket be kell tartani:- a metódus aláírásának meg kell egyeznie
- a visszatérési értéknek meg kell egyeznie
11. Mik azok a metódus aláírások?
A metódus aláírása a metódus nevének és a metódus által használt argumentumoknak a kombinációja. A metódus aláírása a metódus egyedi azonosítója a metódusok túlterhelése esetén.12. Mi az a módszer túlterhelés?
A metódus túlterhelése a polimorfizmus egyik jellemzője, amelyben megváltoztatjuk a metódus aláírását, hogy több metódust hozzunk létre, amelyek ugyanazt a műveletet hajtják végre:- ugyanaz a név
- különböző érvek
- különböző visszaküldési típusok lehetnek
ArrayList
osztály add()
metódusa túlterhelt lehet, így a bemeneti argumentumoktól függően különböző módon adhatunk hozzá:
add(Object o)
— Ez a módszer egyszerűen hozzáad egy objektumotadd(int index, Object o)
— Ez a módszer egy objektumot ad hozzá egy adott indexhezadd(Collection<Object> c)
— Ez a módszer objektumok listáját adja hozzáadd(int index, Collection<Object> c)
— Ez a módszer objektumok listáját adja hozzá egy adott indextől kezdve.
13. Mi az a felület?
A Java nem támogatja a többszörös öröklődést. Ennek a korlátnak a leküzdésére az általunk ismert és kedvelt interfészek kerültek beépítésre ;) Az interfészeknek sokáig csak metódusai voltak implementáció nélkül. Ennek a válasznak az összefüggésében beszéljünk róluk. Például:
public interface Animal {
void makeSound();
void eat();
void sleep();
}
Ebből következik néhány részlet:
- Az interfész összes metódusa nyilvános és absztrakt
- Minden változó nyilvános statikus végleges
- Az osztályok nem öröklik az interfészt (azaz nem használjuk az extends kulcsszót). Ehelyett az osztályok implementálják ezeket (azaz az implements kulcsszót használjuk). Sőt, tetszőleges számú interfészt implementálhat.
- Az interfészt megvalósító osztályoknak biztosítaniuk kell az interfészen található összes metódus megvalósítását.
public class Cat implements Animal {
public void makeSound() {
// Method implementation
}
public void eat() {
// Implementation
}
public void sleep() {
// Implementation
}
}
14. Mi az alapértelmezett metódus egy felületen?
Most beszéljünk az alapértelmezett módszerekről. Mire valók? Kinek valók? Ezeket a módszereket azért adták hozzá, hogy "két kezet" szolgáljanak. miről beszélek? Nos, egyrészt szükség volt új funkciók hozzáadására: lambdák és Stream API. Másrészt meg kellett tartani azt, amiről a Java híres – a visszafelé kompatibilitást. Ehhez az interfészeknek néhány új kész megoldásra volt szükségük. Így kerültek hozzánk az alapértelmezett metódusok. Az alapértelmezett metódus egy interfészen megvalósított metódus, amely kulcsszóval van megjelölvedefault
. Például a felületen jól ismert stream()
metódus Collection
. Higgye el, ez a felület nem olyan egyszerű, mint amilyennek látszik. Vagy az ugyanilyen híres forEach()
módszer aIterable
felület. Nem is létezett az alapértelmezett metódusok hozzáadása előtt. Egyébként itt olvashatsz róla a CodeGymen is .
15. Hogyan örökölhetünk két azonos alapértelmezett metódust?
Az előző válasz arra vonatkozóan, hogy mi az alapértelmezett módszer, egy másik kérdést vet fel. Ha interfészekben lehet metódusokat megvalósítani, akkor elméletileg két interfészt is megvalósíthat ugyanazzal a módszerrel. Hogyan csináljuk? Itt van két különböző interfész ugyanazzal a módszerrel:
interface A {
default void foo() {
System.out.println("Foo A");
}
}
interface B {
default void foo() {
System.out.println("Foo B");
}
}
És van egy osztályunk, amely megvalósítja ezt a két interfészt. De hogyan válasszunk egy adott módszert az A vagy B felületen? A következő speciális konstrukció ezt teszi lehetővé A.super.foo()
:
public class C implements A, B {
public void fooA() {
A.super.foo();
}
public void fooB() {
B.super.foo();
}
}
Így a metódus az interfész fooA()
alapértelmezett metódusát fogja használni , míg a metódus az interfész metódusát . foo()
A
fooB()
foo()
B
16. Mik azok az absztrakt metódusok és osztályok?
A Java nyelvenabstract
fenntartott szó. Absztrakt osztályok és metódusok jelölésére szolgál. Először is definíciókra van szükségünk. Az absztrakt metódus egy olyan metódus, amelyet a kulcsszó használatával deklarálunk abstract
implementáció nélkül egy absztrakt osztályban. Vagyis ez egy olyan módszer, mint egy felületen, de kulcsszó hozzáadásával, például:
public abstract void foo();
Az absztrakt osztály a következő kulcsszóval is megjelölt osztály abstract
:
public abstract class A {
}
Az absztrakt osztálynak számos jellemzője van:
- nem hozhat létre egy absztrakt osztály objektumát
- absztrakt módszerei lehetnek
- az is előfordulhat, hogy nincsenek elvont módszerei
17. Mi a különbség a String, a StringBuilder és a StringBuffer között?
String
az értékek egy állandó karakterlánc-készletben vannak tárolva. Amint létrejön egy karakterlánc, megjelenik ebben a készletben. És nem tudod törölni. Például:
String name = "book";
A változó a konstans karakterlánc-készletre fog mutatni Ha a névváltozót más értékre állítjuk, a következőket kapjuk:
name = "pen";
A konstans karakterlánc így néz ki: Más szóval mindkét érték ott marad. String puffer:
String
az értékek egy veremben vannak tárolva. Ha egy érték megváltozik, akkor az új érték felváltja a régit.String Buffer
szinkronizált, ezért szálbiztos.- A menetbiztonság miatt teljesítménye gyenge.
StringBuffer name = “book”;
Amint a névváltozó értéke megváltozik, a veremben lévő érték is megváltozik: A StringBuilder pontosan megegyezik a -val StringBuffer
, csak nem szálbiztos. Ennek eredményeként észrevehetően gyorsabb, mint StringBuffer
.
18. Mi a különbség az absztrakt osztály és az interfész között?
Absztrakt osztály:- Az absztrakt osztályoknak van alapértelmezett konstruktora. Minden alkalommal hívják, amikor az absztrakt osztály leszármazottja jön létre.
- Tartalmazhatnak elvont és nem absztrakt módszereket is. Általánosságban elmondható, hogy egy absztrakt osztálynak nem kell absztrakt metódusokkal rendelkeznie.
- Egy absztraktot öröklő osztálynak csak absztrakt metódusokat kell megvalósítania.
- Egy absztrakt osztálynak lehetnek példányváltozói (lásd az 5. kérdést).
- Egy interfésznek nincs konstruktora, és nem inicializálható.
- Csak absztrakt metódusok adhatók hozzá (kivéve az alapértelmezett módszereket).
- Az interfészt megvalósító osztályoknak minden metódust implementálniuk kell (az alapértelmezett metódusok kivételével).
- Az interfészeknek csak konstansai lehetnek.
19. Miért O(1) egy tömb elemének elérése?
Ezt a kérdést szó szerint feltették a legutóbbi interjúmban. Amint később megtudtam, ennek a kérdésnek az a célja, hogy megnézze, hogyan gondolkodik az ember. Nyilvánvaló, hogy ennek a tudásnak kevés gyakorlati értéke van. Pusztán annak ismerete elég. Először is tisztáznunk kell, hogy O(1) egy "állandó idejű" algoritmus időbonyolultságának jelölése. Más szavakkal, ez a megjelölés a leggyorsabb végrehajtási időt jelzi. A kérdés megválaszolásához mérlegelnünk kell, mit tudunk a tömbökről. Egy tömb létrehozásáhozint
a következőket kell írnunk:
int[] intArray = new int[100];
Ebből a szintaxisból több következtetés is levonható:
- Amikor egy tömb deklarálva van, a típusa ismert. Ha a típus ismert, akkor a tömb minden egyes cellájának mérete ismert.
- A teljes tömb mérete ismert.
Tehát hogyan juthatunk el az O(1)-hez, amikor egy ArrayList objektumaihoz férünk hozzá?
Ez a kérdés közvetlenül követi az előzőt. Az igazság az, hogy amikor egy primitíveket tartalmazó tömbbel dolgozunk, előre (a létrehozáskor) tudjuk az elemtípus méretét. De mit tegyünk, ha van ilyen öröklődési hierarchiánk, és szeretnénk létrehozni egy gyűjteményt az A típusú elemekhez, és különböző implementációkat (B, C és D) szeretnénk hozzáadni:
List<A> list = new ArrayList();
list.add(new B());
list.add(new C());
list.add(new D());
list.add(new B());
Ebben a helyzetben hogyan számítjuk ki az egyes cellák méretét? Végül is minden objektum más lesz, esetleg más kiegészítő mezőkkel. Mit kell tenni? Itt a kérdés úgy van feltéve, hogy az megzavarja Önt. Tudjuk, hogy a gyűjtemény nem tárol közvetlenül tárgyakat. Csak az objektumokra vonatkozó hivatkozásokat tárolja. És minden hivatkozás azonos méretű, és ez ismert. Ennek eredményeként itt is ugyanúgy számítunk címeket, mint az előző kérdésnél.
21. Autobox és unboxing
Történelmi háttér: az autoboxing és a unboxing a JDK 5 fő újításai közé tartozik. Az autoboxing egy primitív típusból a megfelelő wrapper osztályba történő automatikus átalakítás folyamata. Az unboxing pont az autoboxing ellentéte. Ez az a folyamat, amikor egy burkolóosztályt primitívvé alakítanak át. De ha egy wrapper értékenull
, akkor NullPointerException
a kidobás során a lesz dobva.
Primitívek és a hozzájuk tartozó burkolók
Primitív | Burkolat osztály |
---|---|
logikai érték | Boolean |
int | Egész szám |
byte | Byte |
char | karakter |
úszó | Úszó |
hosszú | Hosszú |
rövid | Rövid |
kettős | Kettős |
// Az automatikus boxolás megtörténik:
-
amikor primitívet rendelünk egy wrapper osztály hivatkozásához:
Java 5 ELŐTT:
// 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 }
-
amikor egy primitívet adunk át argumentumként egy olyan metódushoz, amely wrappert vár:
public void exampleOfAutoboxing() { long age = 3; setAge(age); } public void setAge(Long age) { this.age = age; }
// A kicsomagolás megtörténik:
-
amikor egy wrapper osztály egy példányát rendeljük hozzá egy primitív változóhoz:
// 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;
-
A számtani műveletek során. A műveletek csak a primitív típusokra vonatkoznak, ezért a primitívek kicsomagolása szükséges.
// 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
-
amikor egy burkolóosztály egy példányát adjuk át a megfelelő primitívet felvevő metódusnak:
public void exampleOfAutoboxing() { Long age = new Long(3); setAge(age); } public void setAge(long age) { this.age = age; }
22. Mi a végső kulcsszó, és hol használják?
Afinal
kulcsszó változókhoz, metódusokhoz és osztályokhoz használható.
- A végső változó értéke inicializálása után nem módosítható.
- Egy utolsó osztály steril :) Nem lehet gyereke.
- Egy végső módszert nem írhat felül egy leszármazott.
Végső változók
A Java két módot ad egy változó deklarálására és érték hozzárendelésére:- Deklarálhat egy változót, és később inicializálhatja.
- Deklarálhat egy változót és azonnal hozzárendelhet egy értéket.
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;
}
}
Tekinthető-e konstansnak egy végső változó?
Mivel a végső változókhoz nem tudunk új értékeket rendelni, úgy tűnik, hogy ezek állandó változók. De csak első pillantásra: Ha a változó adattípusaimmutable
, akkor igen, ez egy konstans. De ha az adattípus mutable
, azaz változtatható, akkor metódusok és változók segítségével módosíthatjuk a változó által hivatkozott objektum értékét final
. Emiatt nem nevezhető állandónak. A következő példa azt mutatja, hogy egyes végső változók valóban állandók, míg mások nem, mivel megváltoztathatók.
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?");
}
Lokális végső változók
Ha egyfinal
változót egy metóduson belül hozunk létre, azt változónak nevezzük 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);
}
}
}
A végső kulcsszót egy továbbfejlesztett ciklusban használhatjuk, mivel a ciklus minden iterációja után új változó jön létre. Ne feledje, hogy ez nem vonatkozik a normál for ciklusra, ezért fordítási idejű hibát kapunk.
// The final local j variable cannot be assigned
for (final int i = 0; i < args.length; i ++) {
System.out.println(args[i]);
}
Végső osztály
A minősített osztályfinal
nem bővíthető. Egyszerűbben fogalmazva, egyetlen más osztály sem örökölheti. A JDK osztályának kiváló példája final
a String. A megváltoztathatatlan osztály létrehozásának első lépése az, hogy jelölje meg final
, ezzel megakadályozva a kiterjesztését:
public final class FinalExample {
}
// Compilation error!
class WantsToInheritFinalClass extends FinalExample {
}
Végső módszerek
Ha egy metódust véglegesnek jelölünk, azt végső módszernek nevezzük (van értelme, igaz?). Egy utolsó metódust nem lehet felülírni egy gyermekosztályban. Egyébként az Object osztály wait() és notify() metódusai véglegesek, így nincs lehetőségünk felülbírálni őket.
public class FinalExample {
public final String generateAddress() {
return "Some address";
}
}
class ChildOfFinalExample extends FinalExample {
// Compilation error!
@Override
public String generateAddress() {
return "My OWN Address";
}
}
Hogyan és hol kell használni a final Java-ban
- Használja az utolsó kulcsszót néhány osztályszintű állandó meghatározásához;
- Hozzon létre végső változókat azokhoz az objektumokhoz, amelyeket nem szeretne módosítani. Például objektum-specifikus tulajdonságok, amelyeket naplózási célokra használhatunk.
- Ha nem szeretné, hogy egy osztályt meghosszabbítsunk, jelölje meg véglegesként.
- Ha egy megváltoztathatatlan osztályt kell létrehoznia, akkor azt véglegessé kell tennie.
- Ha azt szeretné, hogy egy metódus megvalósítása ne változzon a leszármazottaiban, akkor jelölje meg a metódust mint
final
. Ez nagyon fontos annak biztosítására, hogy a megvalósítás ne változzon.
23. Mik azok a változtatható és megváltoztathatatlan típusok?
Változékony
A változó objektumok olyan objektumok, amelyek állapota és változói a létrehozás után megváltoztathatók. A módosítható osztályok közé tartozik például a StringBuilder és a StringBuffer. Példa:
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());
}
}
Változhatatlan
A megváltoztathatatlan objektumok olyan objektumok, amelyek állapota és változói nem módosíthatók az objektum létrehozása után. Remek kulcs a HashMaphez, nem gondolod? :) Például String, Integer, Double stb. Példa:
// 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());
}
}
A következő részben a gyűjteményekkel kapcsolatos kérdéseket és válaszokat vizsgáljuk. Profilom a GitHubon Az 50 legjobb állásinterjú kérdése és válasza a Java Core-hoz. 2. rész
GO TO FULL VERSION