非访问修饰符

非访问修饰符

为了实现一些其他的功能,Java 也提供了许多非访问修饰符。

  • static 修饰符,用来修饰类方法和类变量。
  • final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
  • abstract 修饰符,用来创建抽象类和抽象方法。
  • synchronized 和 volatile 修饰符,主要用于线程的编程。

final 修饰符

final 修饰符通常和 static 修饰符一起使用来创建类常量。用 final 修饰的成员变量表示常量,值一旦给定就无法改变。final 修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。final 变量定义的时候,可以先声明,而不给初值,这中变量也称为 final 空白,无论什么情况,编译器都确保空白 final 在使用之前必须被初始化。但是,final 空白在 final 关键字 final 的使用上提供了更大的灵活性,为此,一个类中的 final 数据成员就可以实现依对象而有所不同,却有保持其恒定不变的特征。

//-----------------成员变量------------------//
//初始化方式一,在定义变量时直接赋值
private final int i = 3;

//初始化方式二,声明完变量后在构造方法中为其赋值
//如果采用用这种方式,那么每个构造方法中都要有j赋值的语句
private final int j;

public FinalTest() {
   j = 3;
}

//如果取消该构造方法的注释,程序就会报错,因此它没有为j赋值
/*public FinalTest1(String str) {

}*/

//为了方便我们可以这样写
public FinalTest(String str) {
   this();  //调用无参构造器
}

//下面的代码同样会报错,因为对j重复赋值
/*public FinalTest1(String str1, String str2) {
   this();
   j = 3;
}*/


//初始化方式三,声明完变量后在构造代码块中为其赋值
//如果采用此方式,就不能在构造方法中再次为其赋值
//构造代码块中的代码会在构造函数之前执行,如果在构造函数中再次赋值,
//就会造成final变量的重复赋值
private final int k;

{
   k = 4;
}

//-----------------类变量(静态变量)------------------//
//初始化方式一,在定义类变量时直接赋值
public final static int p = 3;

//初始化方式二,在静态代码块中赋值
//成员变量可以在构造函数中赋值,但是类变量却不可以。
//因此成员变量属于对象独有,每个对象创建时只会调用一次构造函数,
//因此可以保证该成员变量只被初始化一次;
//而类变量是该类的所有对象共有,每个对象创建时都会对该变量赋值
//这样就会造成变量的重复赋值。
public final static int q;

static {
   q = 3;
}

final 变量

final 表示"最后的、最终的"含义,变量一旦赋值后,不能被重新赋值。被 final 修饰的实例变量必须显式指定初始值。final 修饰符通常和 static 修饰符一起使用来创建类常量。

public class Test{
  final int value = 10;
  // 下面是声明常量的实例
  public static final int BOXWIDTH = 6;
  static final String TITLE = "Manager";

  public void changeValue(){
     value = 12; //将输出一个错误
  }
}

final 方法

父类中的 final 方法可以被子类继承,但是不能被子类重写;声明 final 方法的主要目的是防止该方法的内容被修改。如下所示,使用 final 修饰符声明方法。

public class Test{
    public final void changeName(){
       // 方法体
    }
}

final 类

final 类不能被继承,没有类能够继承 final 类的任何特性。

public final class Test {
   // 类体
}

abstract 修饰符

抽象类

抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。抽象类可以包含抽象方法和非抽象方法。

abstract class Caravan{
   private double price;
   private String model;
   private String year;
   public abstract void goFast(); //抽象方法
   public abstract void changeColor();
}

抽象方法

抽象方法是一种没有任何实现的方法,该方法的的具体实现由子类提供。抽象方法不能被声明成 final 和 static。任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。

如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。抽象方法的声明以分号结尾,例如:public abstract sample();。

public abstract class SuperClass{
    abstract void m(); //抽象方法
}

class SubClass extends SuperClass{
     //实现抽象方法
      void m(){
          .........
      }
}

synchronized 修饰符

synchronized 关键字声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。

public synchronized void showDetails(){
    // .......
}

transient 修饰符

序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。

public transient int limit = 55;   // 不会持久化
public int b; // 持久化

当对象被序列化时(写入字节序列到目标文件)时,transient 阻止实例中那些用此关键字声明的变量持久化;当对象被反序列化时(从源文件读取字节序列进行重构),这样的实例变量值不会被持久化和恢复。

//定义一个需要序列化的类

class People implements Serializable{
    String name; //姓名
    transient Integer age; //年龄
    public People(String name,int age){
        this.name = name;
        this.age = age;
    }

    public String toString(){
        return "姓名 = "+name+" ,年龄 = "+age;
    }

}

public class TransientPeople {
    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        People a = new People("李雷",30);
        System.out.println(a); //打印对象的值
        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("d://people.txt"));
        os.writeObject(a);//写入文件(序列化)
        os.close();
        ObjectInputStream is = new ObjectInputStream(new FileInputStream("d://people.txt"));
        a = (People)is.readObject();//将文件数据转换为对象(反序列化)
        System.out.println(a); // 年龄 数据未定义
        is.close();
    }
}

volatile 修饰符

volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。一个 volatile 对象引用可能是 null。

public class MyRunnable implements Runnable
{
    private volatile boolean active;
    public void run()
    {
        active = true;
        while (active) // 第一行
        {
            // 代码
        }
    }
    public void stop()
    {
        active = false; // 第二行
    }
}

通常情况下,在一个线程调用 run() 方法(在 Runnable 开启的线程),在另一个线程调用 stop() 方法如果 第一行 中缓冲区的 active 值被使用,那么在 第二行 的 active 值为 false 时循环不会停止。但是以上代码中我们使用了 volatile 修饰 active,所以该循环会停止。

static 修饰符

静态变量并不是说其就不能改变值,不能改变值的量叫常量。其拥有的值是可变的,而且它会保持最新的值。说其静态,是因为它不会随着函数的调用和退出而发生变化。即上次调用函数的时候,如果我们给静态变量赋予某个值的话,下次函数调用时,这个值保持不变。

  • 静态变量:static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝静态变量也被称为类变量。局部变量不能被声明为 static 变量。

  • 静态方法:static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。

对类变量和方法的访问可以直接使用 classname.variablename 和 classname.methodname 的方式访问。如下例所示,static 修饰符用来创建类方法和类变量。

public class InstanceCounter {
   private static int numInstances = 0;
   protected static int getCount() {
      return numInstances;
   }

   private static void addInstance() {
      numInstances++;
   }

   InstanceCounter() {
      InstanceCounter.addInstance();
   }

   public static void main(String[] arguments) {
      System.out.println("Starting with " +
      InstanceCounter.getCount() + " instances");
      for (int i = 0; i < 500; ++i){
         new InstanceCounter();
          }
      System.out.println("Created " +
      InstanceCounter.getCount() + " instances");
   }
}

/**
Starting with 0 instances
Created 500 instances
**/

类的静态成员属于整个类 而不是某个对象,可以被类的所有方法访问,子类当然可以父类静态成员;静态方法属于整个类,在对象创建之前就已经分配空间,类的非静态成员要在对象创建后才有内存,所有静态方法只能访问静态成员,不能访问非静态成员;静态成员可以被任一对象修改,修改后的值可以被所有对象共享。

成员变量与类变量的比较

由 static 修饰的变量称为静态变量,其实质上就是一个全局变量。如果某个内容是被所有对象所共享,那么该内容就应该用静态修饰;没有被静态修饰的内容,其实是属于对象的特殊描述。不同的对象的实例变量将被分配不同的内存空间,如果类中的成员变量有类变量,那么所有对象的这个类变量都分配给相同的一处内存,改变其中一个对象的这个类变量会影响其他对象的这个类变量,也就是说对象共享类变量。

成员变量和类变量的区别:

  • 两个变量的生命周期不同:成员变量随着对象的创建而存在,随着对象的回收而释放。静态变量随着类的加载而存在,随着类的消失而消失。

  • 调用方式不同:成员变量只能被对象调用,静态变量可以被对象调用,还可以被类名调用。

  • 别名不同:成员变量也称为实例变量,静态变量也称为类变量。

  • 数据存储位置不同:成员变量存储在堆内存的对象中,所以也叫对象的特有数据;静态变量数据存储在方法区(共享数据区)的静态区,所以也叫对象的共享数据。

类变量的弊端在于,有些数据是对象特有的数据,是不可以被静态修饰的。因为那样的话,特有数据会变成对象的共享数据。这样对事物的描述就出了问题。所以,在定义静态时,必须要明确,这个数据是否是被对象所共享的。静态方法只能访问静态成员,不可以访问非静态成员;因为静态方法加载时,优先于对象存在,所以没有办法访问对象中的成员。静态方法中不能使用 this,super 关键字,因为 this 代表对象,而静态在时,有可能没有对象,所以 this 无法使用。

什么时候定义静态成员呢?或者说:定义成员时,到底需不需要被静态修饰呢?

  • 成员变量(数据共享时静态化),该成员变量的数据是否是所有对象都一样:如果是,那么该变量需要被静态修饰,因为是共享的数据。如果不是,那么就说这是对象的特有数据,要存储到对象中。

2、成员函数(方法中没有调用特有数据时就定义成静态):如果判断成员函数是否需要被静态修饰呢?只要参考,该函数内是否访问了对象中的特有数据:如果有访问特有数据,那方法不能被静态修饰。如果没有访问过特有数据,那么这个方法需要被静态修饰。

static 对 非 static 方法调用

public class Xix {
    // 静态成员
    public static String string="static成员";
    // 普通成员
    public String string2="非static成员";
    // 静态方法
    public static void method(){
        string="sss";
        //string2="sss";编译报错,因为静态方法里面只能调用静态方法或静态成员
        //method2();
        System.out.println("这是static方法,static方法与对象无关");
    }

    // 普通方法
    public void method2(){
        string ="string1";
        string2="string2";
        method(); //非静态方法里面可以发出对static方法的调用
        System.out.println("这是非static方法,此方法必须和指定的对象关联起来才起作用");
    }
    public static void main(String[] args) {
        Xix x=new Xix();
        x.method2();// 引用调用普通方法
        x.method();// 引用调用静态方法
    }
}

/**
这是static方法,static方法与对象无关
这是非static方法,此方法必须和指定的对象关联起来才起作用
这是static方法,static方法与对象无关
**/

因为非 static 方法是要与对象关联在一起的,必须创建一个对象后,才可以在该对象上进行方法调用,而 static 方法调用时不需要创建对象,可以直接调用。也就是说,当一个 static 方法被调用时,可能还没有创建任何实例对象,如果从一个 static 方法中发出对非 static 方法的调用,那个非 static 方法是关联到哪个对象上的呢?这个逻辑无法成立,所以,一个 static 方法内部无法对非 static 方法的调用。

上一页
下一页