Java >> Java tutorial >  >> Java

Ikke-adgangsmodifikatorer i Java

Introduktion

Modifiers er nøgleord, der lader os finjustere adgangen til vores klasse og dens medlemmer, deres omfang og adfærd i visse situationer. For eksempel kan vi kontrollere, hvilke klasser/objekter der kan få adgang til bestemte medlemmer af vores klasse, om en klasse kan nedarves eller ej, om vi kan tilsidesætte en metode senere, om vi skal tilsidesætte en metode senere osv.

Modifier søgeord skrives før variabel/metode/klasse (retur) type og navn, f.eks. private int myVar eller public String toString() .

Modifikatorer i Java falder i en af ​​to grupper - adgang og ikke-adgang :

  • Adgang:public , private , protected .
  • Ikke-adgang:statisk, endelig, abstrakt, synkroniseret, flygtig, forbigående og native .

native er ikke dækket mere detaljeret nedenfor, da det er et simpelt nøgleord, der markerer en metode, der vil blive implementeret på andre sprog, ikke i Java. Det fungerer sammen med Java Native Interface (JNI). Det bruges, når vi vil skrive ydeevnekritiske sektioner af kode på mere ydeevnevenlige sprog (som C).

Vil du vide mere om adgang modifikatorer, i modsætning til ikke-adgang? Hvis ja, så tjek vores artikel Adgangsmodifikatorer i Java.

Ikke-adgangsmodifikatorer

Disse typer modifikatorer bruges til at kontrollere en række ting, såsom arveevner, om alle objekter i vores klasse deler den samme medlemsværdi eller har deres egne værdier for disse medlemmer, om en metode kan tilsidesættes i en underklasse osv.

En kort oversigt over disse modifikatorer kan findes i følgende tabel:

Ændringsnavn Oversigt
statisk Medlemmet tilhører klassen, ikke til objekter i den klasse.
final Variabelværdier kan ikke ændres, når de først er tildelt, metoder kan ikke tilsidesættes, klasser kan ikke nedarves.
abstrakt Hvis anvendt på en metode - skal implementeres i en underklasse, hvis den anvendes på en klasse - indeholder abstrakte metoder
synkroniseret Styrer trådadgang til en blok/metode.
flygtig Variabelværdien læses altid fra hovedhukommelsen, ikke fra en specifik tråds hukommelse.
transient Medlemmet springes over, når et objekt serialiseres.

Den statiske modifikator

static modifier gør et klassemedlem uafhængigt af ethvert objekt i den klasse. Der er et par funktioner, du skal huske på her:

  • Variabler erklæret static deles mellem alle objekter i en klasse (da variablen i det væsentlige tilhører selve klassen i dette tilfælde), dvs. objekter har ikke deres egne værdier for denne variabel, i stedet deler de alle en enkelt.
  • Variabler og metoder erklæret static kan tilgås via klassenavnet (i stedet for den sædvanlige objektreference, f.eks. MyClass.staticMethod() eller MyClass.staticVariable ), og de kan tilgås uden at klassen instansieres .
  • static metoder kan kun bruge static variabler og kalde andre static metoder og kan ikke henvise til this eller super på nogen måde (en objektforekomst eksisterer måske ikke engang, når vi kalder en static metode, så this ville ikke give mening).

Bemærk :Det er meget vigtigt at bemærke, at static variabler og metoder kan ikke adgang ikke-static (instans) variabler og metoder. På den anden side, ikke-static variabler og metoder kan få adgang til static variabler og metoder.

Dette er logisk, da static medlemmer eksisterer selv uden et objekt fra den klasse, hvorimod instans medlemmer eksisterer kun, efter at en klasse er blevet instansieret.

Statiske variable

For variabler bruger vi static hvis vi ønsker at variablen skal være fælles/delt for alle objekter.

Lad os tage et kig på hvordan static variabler opfører sig anderledes end almindelige instansvariabler:

class StaticExample {
    public static int staticInt = 0;
    public int normalInt = 0;
    
    // We'll use this example to show how we can keep track of how many objects
    // of our class were created, by changing the shared staticInt variable
    public StaticExample() {
        staticInt++;
        normalInt++;
    }
}
// No instances of StaticExample have been created yet
System.out.println(StaticExample.staticInt); // Prints: 0
// System.out.println(StaticExample.normalInt); // this won't work, obviously

// Let's create two instances of StaticExample
StaticExample object1 = new StaticExample();
// We can refer to static variables via an object reference as well, 
// however this is not common practice, we usually access them via class name
// to make it obvious that a variable/method is static
System.out.println(object1.staticInt); // Prints: 1
System.out.println(object1.normalInt); // Prints: 1

StaticExample object2 = new StaticExample();
System.out.println(object2.staticInt); // Prints: 2
System.out.println(object2.normalInt); // Prints: 1

// We can see that increasing object2's staticInt 
// increases it for object1 (and all current or future objects of that class)

object1.staticInt = 10;
object1.normalInt = 10;
System.out.println(object2.staticInt); // Prints: 10
System.out.println(object2.normalInt); // Prints: 1 (object2 retained its own value for normalInt as it depends on the class itself)

Statiske metoder

Det mest almindelige eksempel på brug af static er main() metode, er den erklæret som static fordi det skal kaldes før nogen objekter eksisterer. Et andet almindeligt eksempel er Math klasse, da vi bruger den pågældende klasses metoder uden at lave en forekomst af den først (som Math.abs() ).

En god måde at tænke static på metoder er "Giver det mening at bruge denne metode uden først at oprette et objekt af denne klasse?" (du behøver f.eks. ikke at instansiere Math klasse for at beregne den absolutte værdi af et tal).

Statiske metoder kan bruges til at få adgang til og ændre static medlemmer af en klasse. De bruges dog almindeligvis til at manipulere metodeparametre eller beregne noget og returnere en værdi.

Disse metoder omtales som utility metoder:

static int average(int num1, int num2) {
    return (num1+num2)/2;
}

Denne hjælpemetode kan bruges til at beregne gennemsnittet af to tal, for eksempel.

Som nævnt ovenfor er Math klasse bruges ofte til at kalde static metoder. Hvis vi ser på kildekoden, kan vi bemærke, at den for det meste tilbyder hjælpemetoder:

public static int abs(int i) {
    return (i < 0) ? -i : i;
}

public static int min(int a, int b) {
    return (a < b) ? a : b;
}

public static int max(int a, int b) {
    return (a > b) ? a : b;
}

Statiske blokke

Der er også en static blok. En static blok udføres kun én gang, når klassen første gang instansieres (eller en static medlem er blevet kaldt, selvom klassen ikke er instansieret), og før resten af ​​koden.

Lad os tilføje en static blokere til vores StaticExample klasse:

class StaticExample() {
    ...
    static {
        System.out.println("Static block");
    }
    ...
}
StaticExample object1 = new StaticExample(); // "Static block" is printed
StaticExample object2 = new StaticExample(); // Nothing is printed

Uanset deres placering i klassen, static blokke initialiseres før andre ikke-statiske blokke, inklusive konstruktører:

class StaticExample() {
    public StaticExample() {
        System.out.println("Hello from the constructor!");
    }

    static {
        System.out.println("Hello from a static block!");
    }
}

Instantiering af denne klasse vil udsende:

Hello from a static block!
Hello from the constructor!

Hvis flere static blokke er til stede, vil de køre i deres respektive rækkefølge:

public class StaticExample {
    
    static {
        System.out.println("Hello from the static block! 1");
    }
    
    public StaticExample() {
        System.out.println("Hello from the constructor!");
    }
    
    static {
        System.out.println("Hello from the static block! 2");
    }
}

Instantiering af denne klasse vil udsende:

Hello from the static block! 1
Hello from the static block! 2
Hello from the constructor!

Statisk import

Som allerede nævnt er det bedre at ringe til static medlemmer præfikset med klassenavnet i stedet for instansnavnet. I nogle tilfælde instansierer vi aldrig rigtig en klasse med static metoder, såsom Math klasse, som tilbyder adskillige hjælpemetoder vedrørende matematik.

Når det er sagt, hvis vi bruger en klasse' static medlemmer ofte, kan vi importere individuelle medlemmer eller dem alle ved hjælp af en static import . Dette giver os mulighed for at springe over deres opkald som præfiks med klassenavnet:

package packageOne;

public class ClassOne {
    static public int i;
    static public int j;

    static public void hello() {
        System.out.println("Hello World!");
    }
}
package packageTwo;

static import packageOne.ClassOne.i;

public class ClassTwo {
    public ClassTwo() {
        i = 20;
    }
}

Eller hvis vi gerne vil importere alle static medlemmer af ClassOne , vi kunne gøre det sådan:

package packageTwo;

import static packageOne.ClassOne.*;

public class ClassTwo {
    public ClassTwo() {
        i = 20;
        j = 10;
    }
}

Det samme gælder for metoder:

package packageTwo;

import static packageOne.ClassOne.*;

public class ClassTwo {
    public ClassTwo() {
        hello();
    }
}

Hvis du kører dette, udsendes:

Hello World!

Det virker måske ikke så vigtigt, men det hjælper, når vi kalder mange static medlemmer af en klasse:

public int someFormula(int num1, int num2, int num3) {
    return Math.ceil(Math.max(Math.abs(num1), Math.abs(num2))+Math.max(Math.abs(num2), Math.abs(num3)))/(Math.min(Math.abs(num1), Math.abs(num2))+Math.min(Math.abs(num2), Math.abs(num3)));
}

// Versus...
import static java.lang.Math.*;
public int someFormula(int num1, int num2, int num3) {
    return ceil(max(abs(num1), abs(num2))+max(abs(num2), abs(num3)))/(min(abs(num1), abs(num2))+min(abs(num2), abs(num3)));
}

Den endelige modifikator

Søgeordet final kan have en af ​​tre betydninger:

  • for at definere navngivne konstanter (variabler, hvis værdier ikke kan ændres efter initialisering)
  • for at forhindre en metode i at blive tilsidesat
  • for at forhindre en klasse i at blive nedarvet

Navngivne konstanter

Tilføjelse af final modifikator til en variabelerklæring gør den variabel uændrelig, når den først er initialiseret.

final modifikator bruges ofte sammen med static modifikator, hvis vi definerer konstanter. Hvis vi kun anvender static til en variabel, kan den stadig nemt ændres. Der er også en navnekonvention knyttet til dette:

static final double GRAVITATIONAL_ACCELERATION = 9.81;

Variabler som disse er ofte inkluderet i hjælpeklasser, såsom Math klasse, ledsaget af adskillige hjælpemetoder.

Selvom de i nogle tilfælde også berettiger deres egne klasser, såsom Constants.java :

public static final float LEARNING_RATE = 0.3f;
public static final float MOMENTUM = 0.6f;
public static final int ITERATIONS = 10000;

Bemærk :når du bruger final med objektreferencevariabler skal du være forsigtig med, hvilken type adfærd du forventer. Overvej følgende:

class MyClass {
    int a;
    int b;

    public MyClass() {
        a = 2;
        b = 3;
    }
}
    final MyClass object1 = new MyClass();
    MyClass object2 = new MyClass();

Referencevariablen object1 er faktisk final og dens værdi kan ikke ændres, men hvad betyder det alligevel for referencevariabler? Det betyder, at object1 kan ikke ændre, hvilket objekt det peger på længere, men vi kan ændre selve objektet. Dette er noget, der ofte forvirrer folk:

    // object1 = object2; // Illegal!
    object1.a = 5; // Perfectly fine

Metodeparametre kan også erklæres final . Dette bruges til at sikre, at vores metode ikke ændrer den parameter, den modtager, når den kaldes.

Lokale variabler kan også erklæres final . Dette bruges til at sikre, at variablen kun modtager en værdi én gang.

Forhindring af tilsidesættelse

Hvis du angiver final modifikator, mens en metode defineres, kan enhver fremtidig underklasse ikke tilsidesætte den.

class FinalExample {
    final void printSomething() {
        System.out.println("Something");
    }
}
class ExtendsFinalExample extends FinalExample {
    // This would cause a compile-time error
    //void printSomething() {
    //  System.out.println("Some other thing");
    //}
    
    // However, you are perfectly free to overload this method
    void printSomething(String something) {
        System.out.println(something);
    }
}

En lille bonus ved at erklære virkelig endelige metoder som final er et lille præstationsløft, når vi kalder denne metode. Normalt løser Java metodekald dynamisk under kørsel, men med metoder erklæret final , Java kan løse et kald til den på kompileringstidspunktet, eller hvis en metode er virkelig lille, kan den simpelthen inline kald til den metode, da den "ved", at den ikke vil blive tilsidesat. Dette eliminerer overhead forbundet med et metodekald.

Forebyggelse af arv

Denne brug af final er ret ligetil, en klasse defineret med final kan ikke arves. Dette erklærer naturligvis også implicit alle metoder i den pågældende klasses finale (de kan ikke tilsidesættes, hvis klassen ikke kan nedarves i første omgang).

final class FinalExample {...}

Den abstrakte modifikator

abstract modifier bruges til at definere metoder, der vil blive implementeret i en underklasse senere. Oftest bruges det til at foreslå, at nogle funktioner bør implementeres i en underklasse, eller (af en eller anden grund) kan den ikke implementeres i superklassen. Hvis en klasse indeholder en abstract metode, skal den også erklæres abstract .

Bemærk :Du kan ikke oprette et objekt af en abstract klasse. For at gøre det skal du levere en implementering for alle abstract metoder.

Et eksempel ville være, hvis vi havde en simpel klasse kaldet Employee der indkapsler data og metoder for en medarbejder. Lad os sige, at ikke alle medarbejdere bliver aflønnet på samme måde, nogle typer medarbejdere er timelønnede og nogle får en fast løn.

abstract class Employee {
    int totalHours; // In a month
    int perHour;    // Payment per hour
    int fixedRate;  // Fixed monthly rate
    ...
    abstract int salary();
    ...  
}
class Contractor extends Employee {
    ...
    // Must override salary if we wish to create an object of this class
    int salary() {
        return totalHours*perHour; 
    }
    ...
}
class FullTimeEmployee extends Employee {
    ...
    int salary() {
        return fixedRate; 
    }
    ...
}
class Intern extends Employee {
    ...
    int salary() {
        return 0; 
    }
    ...
}

Hvis en underklasse ikke giver en implementering til alle abstract metoder i superklassen, skal den erklæres som abstract også, og et objekt af den klasse kan ikke oprettes.

Bemærk :abstract bruges meget med polymorfi, f.eks. vi vil sige ArrayList<Employee> employees = new ArrayList(); , og tilføj Contractor , FullTimeEmployee og Intern gør indsigelse mod det. Selvom vi ikke kan oprette et objekt af Employee klasse, kan vi stadig bruge den som en referencevariabeltype.

Den synkroniserede modifikator

Når to eller flere tråde skal bruge den samme ressource, skal vi på en eller anden måde sikre os, at kun én af dem har adgang til den ad gangen, dvs. vi skal synkronisere dem.

Dette kan opnås på flere måder, og en enkel og læsbar måde (omend med noget begrænset brug) er ved at bruge synchronized søgeord.

Et vigtigt begreb at forstå, før du ser, hvordan du bruger dette søgeord, er begrebet en skærm. Hvert objekt i Java har sin egen implicitte skærm tilknyttet. En skærm er en "gensidigt udelukkende" lås, hvilket betyder, at kun én tråd kan "eje" en skærm ad gangen. Når en tråd kommer ind i monitoren, kan ingen anden tråd komme ind i den, før den første tråd går ud. Dette er hvad synchronized gør.

Tråde er uden for denne artikels omfang, så jeg vil fokusere på syntaksen for synchronized kun.

Vi kan synkronisere adgang til metoder og kodeblokke. Synkronisering af kodeblokke fungerer ved at give en objektforekomst, som vi ønsker at synkronisere adgang til, og den kode, som vi ønsker at udføre relateret til det objekt.

class SynchronizedExample {

    ...
    SomeClass object = new SomeClass();
    ....

    synchronized(object) {
         // Code that processes objects
         // only one thread at a time
    }
    
    // A synchronized method
    synchronized void doSomething() {
         ...
    }
    ...
}

Den flygtige modifikator

volatile modifier fortæller Java, at en variabel kan ændres uventet af en anden del af programmet (som i multithreaded programmering), og så denne variabels værdi altid læses fra hovedhukommelsen (og ikke fra CPU-cachen), og at hver ændring af volatile variabel gemmes i hovedhukommelsen (og ikke i CPU-cachen). Med dette i tankerne, volatile bør kun bruges, når det er nødvendigt, da læsning/skrivning til hukommelsen hver gang er dyrere end at gøre det med CPU-cache og kun læsning/skrivning til hukommelsen, når det er nødvendigt.

Forenklet sagt - når en tråd læser en volatile variabel værdi, er det garanteret, at den vil læse den senest skrevne værdi. Grundlæggende en volatile variabel gør det samme som synchronized metoder/blokke gør, vi kan bare ikke erklære en variabel som synchronized .

Den forbigående modifikator

Når en variabel er erklæret som transient , det betyder, at dets værdi ikke gemmes, når objektet er gemt i hukommelsen.
transient int a; betyder, at når vi skriver objektet til hukommelsen, vil indholdet af "a" ikke blive inkluderet. For eksempel bruges det til at sikre, at vi ikke gemmer private/fortrolige oplysninger i en fil.

Når vi forsøger at læse et objekt, der indeholder transient variabler, alle transient variabelværdier indstilles til null (eller standardværdier for primitive typer), uanset hvad de var, da vi skrev objektet til filen. Et andet eksempel på brug ville være, når en variabels værdi skal udledes baseret på andre data (såsom en persons nuværende alder), og ikke er en del af den vedvarende objekttilstand.

Bemærk :Der sker noget meget interessant, når vi bruger transient og final sammen. Hvis vi har en transient final variabel, der evalueres som et konstant udtryk (strenge eller primitive typer), vil JVM altid serialisere den og ignorere enhver potentiel transient modifikator. Når transient final bruges med referencevariabler, får vi den forventede standardadfærd for transient .

Konklusion

Modifikatorer er nøgleord, der lader os finjustere adgangen til vores klasse og dens medlemmer, deres omfang og adfærd i visse situationer. De giver grundlæggende træk til vores klasser og deres medlemmer. Enhver udvikler bør være grundigt bekendt med dem for at få den bedst mulige brug af dem.

Som at være opmærksom på, at protected adgangskontrol kan nemt omgås, eller transient final modifikator, når det kommer til konstante udtryk.


Java tag