Java基础语法 非访问修饰符的使用


Java基础语法 非访问修饰符的使用

除了前面文章所说到的访问修饰符外,Java 还提供了许多非访问修饰符,具体如下:

  • static 修饰符,用来修饰类方法和类变量。
  • final 修饰符,用来修饰类、方法和变量。final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量不可修改值。
  • abstract 修饰符,用来创建抽象类和抽象方法。
  • synchronized 和 volatile 修饰符,主要用于线程的编程。
  • transient 修饰符,序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。

根据现阶段大家的学习进度以及修饰符的使用频率,下面我将着重介绍static、final、abstract修饰符。至于线程相关修饰符,我将在大家学习到线程内容相关知识的时候为大家详细介绍。至于transient修饰符,大家知道即可,不用深入研究。

1、static修饰符

  • 静态变量:static修饰的类变量称为静态变量

    static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。

    static成员变量的初始化顺序按照定义的顺序进行初始化。

    静态变量也被称为类变量,在主方法里声明静态变量会报错。

    局部变量不能被声明为 static 变量,也就是说例如if语句、for循环里面定义的变量是不能用static修饰符修饰的。

  • 静态方法:static修饰的类方法称为静态方法

    在《Java编程思想》里面有这样一段话:

    $\textcolor{BlueViolet}{“static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。}$

    $\textcolor{BlueViolet}{而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。”}$

    这段话怎么去理解呢?这段话虽然只是说明了static方法的特殊之处,但是可以看出static关键字的基本作用,简而言之,一句话来描述就是:方便在没有创建对象的情况下来进行调用(方法/变量)

    也就是说被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。举个简单的例子:

​ 上面的代码中我并没有创建Demo对象,却可以直接使用Demo.age获取变量的值,通过Demo.printAge调用Demo的方法,这就是 static的特点之一。

​ static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附 于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法, 因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。

​ 但是要注意的是,虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/ 变量的。举个简单的例子:

​ 从上面的代码可以看到,我在静态方法中打印非静态变量height,还未运行代码编译就直接报红了,再看下面,我在主函数里面通过Domo.printSex()来调用Demo的非静态方法,发现出现同样的问题,未运行代码编译就直接报红。这就说明了静态方法中不能访问非静 态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的。

  • static代码块

    static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

    为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。下面看个例子:

    class Person{
        private Date birthDate;
         
        public Person(Date birthDate) {
            this.birthDate = birthDate;
        }
         
        boolean isLiulei() {
            Date startDate = Date.valueOf("1946");
            Date endDate = Date.valueOf("1964");
            return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
        }
    }

    isLiulei方法是用来说明这个人是否是1946-1964年出生的,而每次isLiulei被调用的时候,都会生成startDate和birthDate两个对象,造成了空间浪费,如果改成这样效率会更好:

    class Person{
        private Date birthDate;
        private static Date startDate,endDate;
        //静态代码块
        static{
            startDate = Date.valueOf("1946");
            endDate = Date.valueOf("1964");
        }
         
        public Person(Date birthDate) {
            this.birthDate = birthDate;
        }
         
        boolean isLiulei() {
            return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
        }
    }

    因此,我们在进行程序开发中,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。

  • static关键字注意点:

    1. 与C/C++中的static不同,Java中的static关键字不会影响到变量或者方法的作用域。在Java中能够影响到访问权限的只有private、public、protected、default这几个关键字。
    2. 静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。

2、final修饰符

final关键字可以用来修饰引用(变量)、方法和类。

  • final变量

    1. final 表示”最后的、最终的”含义,变量一旦赋值后,不能被重新赋值。
    2. 被 final 修饰的实例变量必须显式指定初始值。
    3. final 修饰符通常和 static 修饰符一起使用来创建类常量。
    4. 如果引用为基本数据类型,则该引用为常量,该值无法修改;
    5. 如果引用为引用数据类型,比如对象、数组,则该对象、数组本身可以修改,但指向该对象或数组的地址的引用不能修改。
    6. 如果引用为类的成员变量,则必须当场赋值,否则编译会报错。

    实例如下:

    final class Person {
        String name ="zs";    
        //3. 此处不赋值会报错
        //final int age;
        final int age = 10;  
    }
    public class Demo {
        public static void main(String[] args) {        
            //1. 基本数组类型为常量,无法修改
            final int i = 9;
            //i = 10;               
            
            //2. 地址不能修改,但是对象本身的属性可以修改
            Person p = new Person();
            p.name = "lisi";
            
            final int[] arr = {1,2,3,45};
            arr[3] = 999;
            //arr = new int[]{1,4,56,78};
        }
    }
  • final方法

    当使用final修饰方法时,这个方法将成为最终方法,无法被子类重写。但是,该方法仍然可以被继承。

    声明 final 方法的主要目的是防止该方法的内容被修改。

    class Person {
        public final void say() {
            System.out.println("说....");
        }
        public void eat() {
            System.out.println("吃...");
        }
    }
    class Teacher extends Person {
        //1. final修饰的方法不能被重写,但此方法仍然被继承
        /*@Override
        public void say() {
            System.out.println("老师在一本正经的说...");
        }*/
        
        public void eat() {
            System.out.println("老师在大口大口的吃...");
        }
    }
    public class Demo02 {
        public static void main(String[] args) {
            Teacher t = new Teacher();
            t.say();
        }    
    }

    上面的代码,我在主方法里创建的是Teacher对象,调用的是Person中定义的say()方法,说明final修饰的方法被继承了。

  • final类

    当用final修饰类时,该类成为最终类,无法被继承。简称为“断子绝孙类”。

    /***
     * final修饰类,则该类成为最终类,无法被继承
     * @author Administrator
     */
    final class Person{    }
    class Teacher extends Person { }//编译报错
    class MyString extends String{ }//String类为最终类无法被继承,编译报错

    3、abstract修饰符

abstract 修饰符,用来创建抽象类和抽象方法,也就是说abstract 修饰的类似抽象类,abstract 修饰的方法即抽象方法。

说到抽象,想必大家第一个联想到的就是抽象画,基本上看不懂画的是啥,只能说它在表达某个概念。在Java中我们使用abstract关键字来表达抽象。

学习abstract修饰的抽象之前,先举个例子:

我们说车子都可以跑(run)。但有几个轮子,怎么跑,对于不同的车有不同的结果。自行车需要人踩着跑,汽车发动机推动跑等等,那么我们可以车表达为抽象类。代码实现如下:

/**
 * 抽象类:abstract修饰车子类
 */
public abstract class Car {
    //抽象方法:abstract修饰run()方法
	public abstract void run();
}
/**
 * 自行车
 */
class Bicycle extends Car{
    //重写run()方法
	@Override
	public void run() {
		System.out.println("人踩着跑。。。");
	}
	
}
/***
 * 汽车
 */
class Automobile extends Car{
	//重写run()方法
	@Override
	public void run() {
		System.out.println("发动机驱动跑。。。");
	}
}

假如后面各种车,它们倒着跑、悬在空中跑,随它怎么跑,只需要继承抽象类实现自己的功能就行了。相信说到这里大家对抽象已有一个初步的印象了吧。

  • 抽象方法:

    1、从上面的例子中我们可以看到抽象方法跟普通方法是有区别的,它没有自己的方法主体(没有{ }包起来的业务逻辑),跟接口中的方法有点类似。所以我们没法直接调用抽象方法。

    2、抽象方法不能用private修饰,因为抽象方法必须被子类实现(覆写),而private权限对于子类来说是不能访问的,所以就会产生矛盾。

    3、抽象方法也不能用static修饰,试想一下,如果用static修饰了,那么我们可以直接通过类名调用,而抽象方法压根就没有主体,没有任何业务逻辑,这样就毫无意义了。

  • 抽象类:

    1、用abstract关键字来表达的类,其表达形式为:(public)abstract class 类名{ }。

    2、抽象类不能被实例化,也就是说我们没法直接new 一个抽象类。抽象类本身就代表了一个类型,无法确定为一个具体的对象,所以不能实例化就合乎情理了,只能用它的继承类实例化。

    3、抽象类虽然不能被实例化,但有自己的构造方法(这个后面再讨论)。

    4、抽象类与接口(interface)有很大的不同之处,接口中不能有实例方法去实现业务逻辑,而抽象类中可以有实例方法,并实现业务逻辑,比如我们可以在抽象类中创建和销毁一个线程池。

    5、抽象类不能使用final关键字修饰,因为final修饰的类无法被继承,而对于抽象类来说就是需要通过继承去实现抽象方法,这又会产生矛盾。

  • 抽象类与抽象方法的关联:

    1、如果一个类中至少有一个抽象方法,那么这个类一定是抽象类,但反之则不然。也就是说一个抽象类中可以没有抽象方法。这样做的目的是为了此类不能被实例化。

    2、如果一个类继承了一个抽象类,那么它必须全部覆写抽象类中的抽象方法,当然也可以不全部覆写,如果不覆写全部抽象方法则这个子类也必须是抽象类(这样做就无意义了)

    public abstract class Car {
    	public void mothod1(){
            
    	}
    public abstract void mothod2();
    
    public abstract void method3();
    }
    
    class Bicycle extends Car{
    	@Override
    	public void mothod2() {//需要覆写抽象方法mothod2
    	
    	}
    	@Override
    	public void method3() {//需要覆写抽象方法mothod3
    	
    	}
    }
  • 抽象类的构造器:

    先来看一个例子:

    public abstract class Car {
    	Car(){
    		System.out.println("抽象方法无参构造函数");
    	}
        
    	Car(String a){
    		System.out.println("抽象有参构造方法");
    	}
        
    	public void mothod1(){
    		System.out.println(this.getClass());
    		System.out.println("抽象类的实例方法");
    	}
    
    	public abstract void mothod2();
    }
    
    /**
     * 自行车
    */
    class Bicycle extends Car{
    	Bicycle(){
       		System.out.println("子类无参构造函数");
       }
    
       @Override
       public void mothod2() {//需要覆写抽象方法mothod2
       	
       }
    }
       
    /**另一个包的测试类**/
    public class Test {
    	public static void main(String[] args) {
       		Bicycle b = new Bicycle();
            b.mothod1();
        }
    }
    运行结果:
    抽象方法无参构造函数
    子类无参构造函数
    class com.wedu.Bicycle
    抽象类的实例方法

    从上面的例子中可以看出:

    1、抽象类是有构造方法的(当然如果我们不写,编译器会自动默认一个无参构造方法)。而且从结果来看,和普通的继承类一样,在new 一个子类对象时会优先调用父类(这里指的是抽象类Car)的构造器初始化,然后再调用子类的构造器。至此相信大家都会有这样一个疑问,为什么抽象方法不能实例化却有构造器呢? 对于这个问题网上也中说纷纭,没有确定答案。

    我是这样想的:既然它也属于继承的范畴,那么当子类创建对象时必然要优先初始化父类的属性变量和实例方法,不然子类怎么继承和调用呢?而它本身不能实例化,因为它本身就是不确定的一个对象,如果它能被实例化,那么我们通过它的对象来调用它本身的抽象方法是不是有问题。所以不能实例化有在情理之中。如果实在理解不了就记住这个规定就行。

    2、对于抽象类中的非statci(静态)和非abstract(抽象)方法中的this关键字(静态方法中不能有关键字this之前已经讨论过)代表的是它的继承类,而非抽象类本身,这个好理解,因为抽象类本身不能被实例化。如果有多个继承类,谁调用this就代表谁。

抽象类有什么好处呢?
1、由于抽象类不能被实例化,最大的好处就是通过方法的覆盖来实现多态的属性。
2、抽象类将事物的共性的东西提取出来,由子类继承去实现,代码易扩展、易维护。


文章作者: 刘磊
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 刘磊 !
  目录