1. Initialisering av variabler

Som du redan vet kan du deklarera flera variabler i din klass, och inte bara deklarera dem, utan också omedelbart initiera dem med deras initiala värden.

Och samma variabler kan också initieras i en konstruktor. Detta innebär att dessa variabler i teorin skulle kunna tilldelas värden två gånger. Exempel

Koda Notera
class Cat
{
   public String name;
   public int age = -1;

   public Cat(String name, int age)
   {
     this.name = name;
     this.age = age;
   }

   public Cat()
   {
     this.name = "Nameless";
   }
}



Variabeln agetilldelas ett initialvärde.




Initialvärdet skrivs över.


Åldervariabeln lagrar sitt initiala värde.
 Cat cat = new Cat("Whiskers", 2);
Detta är tillåtet: den första konstruktorn kommer att anropas
 Cat cat = new Cat();
Detta är tillåtet: den andra konstruktören kommer att anropas

Detta är vad som händer när Cat cat = new Cat("Whiskers", 2);det körs:

  • Ett Catobjekt skapas
  • Alla instansvariabler initieras med sina initiala värden
  • Konstruktorn anropas och dess kod exekveras.

Med andra ord, variablerna får först sina initiala värden, och först därefter exekveras konstruktorns kod.


2. Initieringsordning av variabler i en klass

Variabler initieras inte bara innan konstruktorn körs – de initieras i en väldefinierad ordning: den ordning i vilken de deklareras i klassen.

Låt oss titta på lite intressant kod:

Koda Notera
public class Solution
{
   public int a = b + c + 1;
   public int b = a + c + 2;
   public int c = a + b + 3;
}

 Den här koden kommer inte att kompileras, eftersom det inte finns några och  variabler ännu när avariabeln skapas . Men du kan skriva din kod på följande sätt - den här koden kommer att kompileras och fungerar bra.bc

Koda Notera
public class Solution
{
   public int a;
   public int b = a + 2;
   public int c = a + b + 3;
}


0
0+2
0+2+3

Men kom ihåg att din kod måste vara transparent för andra utvecklare. Det är bättre att inte använda sådana här tekniker, eftersom det försämrar kodens läsbarhet.

Här måste vi komma ihåg att innan variabler tilldelas ett värde har de ett standardvärde . För inttypen är detta noll.

När JVM initierar avariabeln tilldelar den helt enkelt standardvärdet för int-typen: 0.

När den når b, kommer a-variabeln redan att vara känd och ha ett värde, så JVM kommer att tilldela den värdet 2.

Och när den når cvariabeln kommer variablerna aoch bredan att initieras, så JVM kommer enkelt att beräkna det initiala värdet för c: 0+2+3.

Om du skapar en variabel i en metod kan du inte använda den om du inte tidigare har tilldelat den ett värde. Men detta är inte sant för variablerna i en klass! Om ett initialvärde inte tilldelas en variabel i en klass, tilldelas det ett standardvärde.


3. Konstanter

Medan vi analyserar hur objekt skapas är det värt att beröra initialiseringen av konstanter, dvs variabler med modifieraren final.

Om en variabel har modifieraren finalmåste den tilldelas ett initialt värde. Du vet redan detta, och det är inget förvånande med det.

Men vad du inte vet är att du inte behöver tilldela startvärdet direkt om du tilldelar det i konstruktorn. Detta kommer att fungera bra för en slutlig variabel. Det enda kravet är att om du har flera konstruktörer måste en slutlig variabel tilldelas ett värde i varje konstruktor.

Exempel:

public class Cat
{
   public final int maxAge = 25;
   public final int maxWeight;

   public Cat (int weight)
   {
     this.maxWeight = weight; // Assign an initial value to the constant
   }
}


4. Koda i en konstruktor

Och några fler viktiga anteckningar om konstruktörer. Senare, när du fortsätter att lära dig Java, kommer du att stöta på saker som arv, serialisering, undantag, etc. De påverkar alla konstruktörernas arbete i olika grad. Det är ingen mening att dyka djupt in i dessa ämnen nu, men vi är skyldiga att åtminstone beröra dem.

Till exempel, här är en viktig kommentar om konstruktörer. I teorin kan du skriva kod av vilken komplexitet som helst i en konstruktor. Men gör inte det här. Exempel:

class FilePrinter
{
   public String content;

   public FilePrinter(String filename) throws Exception
   {
      FileInputStream input = new FileInputStream(filename);
      byte[] buffer = input.readAllBytes();
      this.content = new String(buffer);
   }

   public void printFile()
   {
      System.out.println(content);
   }
}






Öppna en fil läs ström
Läs filen till en byte array
Spara byte arrayen som en sträng




Visa filens innehåll på skärmen

I FilePrinter-klasskonstruktorn öppnade vi omedelbart en byteström på en fil och läste dess innehåll. Detta är komplext beteende och kan resultera i fel.

Tänk om det inte fanns någon sådan fil? Vad händer om det uppstod problem med att läsa filen? Tänk om den var för stor?

Komplex logik innebär en hög sannolikhet för fel och det betyder att koden måste hantera undantag korrekt.

Exempel 1 — Serialisering

I ett standard Java-program finns det många situationer där du inte är den som skapar objekt i din klass. Anta till exempel att du bestämmer dig för att skicka ett objekt över nätverket: i det här fallet kommer Java-maskinen själv att konvertera ditt objekt till en uppsättning byte, skicka det och återskapa objektet från uppsättningen byte.

Men anta att din fil inte finns på den andra datorn. Det kommer att uppstå ett fel i konstruktorn, och ingen kommer att hantera det. Och det är ganska kapabelt att få programmet att avslutas.

Exempel 2 — Initiering av fält i en klass

Om din klasskonstruktor kan kasta markerade undantag, dvs är markerad med nyckelordet throws, måste du fånga de angivna undantagen i metoden som skapar ditt objekt.

Men vad händer om det inte finns någon sådan metod? Exempel:

Koda  Notera
class Solution
{
   public FilePrinter reader = new FilePrinter("c:\\readme.txt");
}
Den här koden kommer inte att kompileras.

Klasskonstruktorn FilePrinterkan kasta ett markerat undantag , vilket innebär att du inte kan skapa ett FilePrinterobjekt utan att linda in det i ett försök-fångst-block. Och ett försök-fångst-block kan bara skrivas i en metod



5. Basklasskonstruktör

På tidigare lektioner diskuterade vi arv lite. Tyvärr är vår fullständiga diskussion om arv och OOP reserverad för nivån dedikerad till OOP, och nedärvning av konstruktörer är redan relevant för oss.

Om din klass ärver en annan klass, kommer ett objekt från den överordnade klassen att bäddas in i ett objekt i din klass. Dessutom har den överordnade klassen sina egna variabler och sina egna konstruktorer.

Det betyder att det är mycket viktigt för dig att veta och förstå hur variabler initieras och konstruktörer anropas när din klass har en överordnad klass och du ärver dess variabler och metoder.

Klasser

Hur vet vi i vilken ordning variabler initieras och konstruktörer anropas? Låt oss börja med att skriva koden för två klasser. Den ena kommer att ärva den andra:

Koda Notera
class ParentClass
{
   public String a;
   public String b;

   public ParentClass()
   {
   }
}

class ChildClass extends ParentClass
{
   public String c;
   public String d;

   public ChildClass()
   {
   }
}










Klassen ChildClass ärver ParentClassklassen.

Vi måste bestämma i vilken ordning variabler initieras och konstruktörer anropas. Loggning hjälper oss att göra detta.

Skogsavverkning

Loggning är processen att registrera åtgärder som utförs av ett program när det körs, genom att skriva dem till konsolen eller en fil.

Det är ganska enkelt att fastställa att konstruktören har anropats: i konstruktörens kropp, skriv ett meddelande till konsolen. Men hur kan du se om en variabel har initierats?

Egentligen är detta inte särskilt svårt: skriv en speciell metod som kommer att returnera värdet som används för att initiera variabeln och logga initieringen. Så här kan koden se ut:

Slutlig kod

public class Main
{
   public static void main(String[] args)
   {
      ChildClass obj = new ChildClass();
   }

   public static String print(String text)
   {
      System.out.println(text);
      return text;
   }
}

class ParentClass
{
   public String a = Main.print("ParentClass.a");
   public String b = Main.print("ParentClass.b");

   public ParentClass()
   {
      Main.print("ParentClass.constructor");
   }
}

class ChildClass extends ParentClass
{
   public String c = Main.print("ChildClass.c");
   public String d = Main.print("ChildClass.d");

   public ChildClass()
   {
      Main.print("ChildClass.constructor");
   }
}




Skapa ett ChildClassobjekt


Den här metoden skriver den skickade texten till konsolen och returnerar den också.





Deklarera ParentClassklassen

Display text och initialisera även variablerna med den.




Skriv ett meddelande om att konstruktören har anropats. Ignorera returvärdet.


Deklarera ChildClassklassen

Display text och initialisera även variablerna med den.




Skriv ett meddelande om att konstruktören har anropats. Ignorera returvärdet.

Om du kör den här koden kommer text att visas på skärmen enligt följande:

Konsolutdata för metodenMain.print()
ParentClass.a
ParentClass.b
ParentClass.constructor
ChildClass.c
ChildClass.d
ChildClass.constructor

Så du kan alltid personligen se till att variablerna i en klass initieras innan konstruktorn anropas. En basklass initieras helt innan initieringen av den ärvda klassen.