本固枝荣 —— Java关键字之修饰符1

为了更好的理解Java修饰符,可大致分为两种类型:访问权限修饰符和非访问权限修饰符。修饰符可用来修饰类、接口、方法、变量,代码块等,表达和实现程序中不同的功能,有Java基础的人应该都知道它们的用法和含义,这里会全面的整理下修饰符的用法,一些细节,及使用场景,算是一个整理总结吧。

访问权限修饰符

访问权限修饰符,顾名思义就是用来控制其他对象访问该对象的权限范围,关键字有public、protected、private。

public:公有的,对所有的类可见,可用于修饰类、接口、枚举、方法、变量等;(注意:接口、枚举、抽象类等只能是public!

protected:受保护的,对同包下的所有类及其子类可见,可用于修饰内部类、方法、变量等;(注意:不能修饰外部类!

private:私有的,对同一类内可见,可用于修饰内部类、方法、变量等;(注意:不能修饰外部类!

关于忽略不写修饰符的理解:网上很多人说的默认,均指向了default 或者 friendly,这是不正确的。friendly不是Java关键字,default是Java关键字但没有访问权限控制的含义。因此忽略不写访问权限修饰符是当做一种方式而已,建议不要拿某些些关键字来混淆,而且忽略不写的话只能在同一包内可见 。

非访问权限修饰符

这类修饰符就多了,如static、final、abstract、native、synchronized、volatile、transient等等,以修饰方法为例,像我们常说的静态方法、最终方法、抽象方法、本地方法、同步方法等就是修饰符修饰方法的很形象描述。下面对这些修饰符做一个全面的总结:

static

含义:静态的,可用来修饰内部类、方法、变量、代码块等等。

静态方法与静态变量

通过动手写个测试来感受下静态方法和静态变量的用法:

public class StaticClass {
    //全局变量
    public static String hello = "Hello,World!"; //静态变量
    public String bye = "GoodBye!"; //实例变量

    // 静态方法
    public static void sayHello() {
        // 静态方法 对全局变量 和 普通方法的调用
        System.out.println(hello);
//        System.out.println(bye); //报错
        System.out.println(new StaticClass().bye);
        
        sayHello();
        new StaticClass().sayBye();
    }

    // 普通方法
    public void sayBye() {
        // 普通方法 对全局变量 和 静态方法的调用
        System.out.println(hello);
        System.out.println(bye);

        this.sayBye(); //this当前对象
        sayHello();
    }
}

// 其他类
class TestClass {
    public static void testType() {
        // 其他类 对StaticClass的 静态变量 和 静态方法的调用
        StaticClass.sayHello();
        System.out.println(StaticClass.hello);

        // 其他类 对StaticClass的 实例变量 和 普通方法的调用
        new StaticClass().sayBye();
        System.out.println(new StaticClass().bye);
    }
}

从测试的结果来看:

  • 一个类中的普通方法和静态方法均可直接调用静态变量;普通方法可以直接调用实例变量,而静态方法却不能,需要通过构造生成类的对象才能调用该实例变量;普通方法与静态方法同理。因此可以得出——> 结论1:static修饰变量/方法的作用是让一个类中的静态变量/静态方法成为所有对象共享的资源,而实例变量/实例方法则是所有对象各自独有的资源;
  • 其他类对StaticClass类非私有的静态变量/实例变量 和 静态方法/普通方法的调用,可以得出——> 结论2:其他类中对一个类非私有的静态变量和静态方法可以直接通过 类名.静态变量 和 类名.方法名 的方式调用,而对非私有的实例变量 或 普通方法的调用需要构造该类的对象才能调用。

静态内部类

接着上面的例子,继续测试下静态内部类:(这里把静态内部类StaticInner 所在的类StaticClass  叫做外部类,而TestClass叫做其他类,不要混淆了! )

//外部类
public class StaticClass {
    public static String hello = "Hello,World!"; //静态变量
    public String bye = "GoodBye!"; //实例变量

    // 静态方法
    public static void sayHello() {
        // 对静态内部类的变量 和 方法进行调用
        System.out.println(new StaticInner().bad);
        new StaticInner().sayBad();

        System.out.println(StaticInner.good);
        StaticInner.sayGood();
    }

    // 普通方法
    public void sayBye() {
        // 对静态内部类的变量 和 方法进行调用
        System.out.println(new StaticInner().bad);
        new StaticInner().sayBad();

        System.out.println(StaticInner.good);
        StaticInner.sayGood();
    }

    // 静态内部类
    public static class StaticInner {
        public static String good = "good!";
        public String bad = "bad!";

        //静态内部类的静态方法
        public static void sayGood() {
            System.out.println(hello); //调用外部类-静态变量
            System.out.println(new StaticClass().bye); //调用外部类-实例变量

            System.out.println(good); //调用静态内部类的静态变量
            System.out.println(new StaticInner().bad); //调用静态内部类的实例变量

            sayHello(); //调用外部类-静态方法
            new StaticClass().sayBye(); //调用外部类-实例方法
            sayGood();
            new StaticInner().sayBad();
        }

        // 静态内部类的普通方法
        public void sayBad() {
            System.out.println(hello); //调用外部类-静态变量
//            System.out.println(bye); //报错
            System.out.println(new StaticClass().bye); //调用外部类-实例变量

            System.out.println(good); //调用静态内部类的静态变量
            System.out.println(bad); //调用静态内部类的实例变量

            sayHello(); //调用外部类-静态方法
            new StaticClass().sayBye(); //调用外部类-实例方法
            sayGood();
            sayBad();
        }
    }
}

// 其他类
class TestClass {
    public static void testType() {
        // 其他类 对外部类的 静态内部类的静态属性调用
        StaticClass.StaticInner.sayGood();
        System.out.println(StaticClass.StaticInner.good);
        //其他类 对外部类的 静态内部类的非静态属性调用,无法调用
    }
}

从测试的结果来看:

  • 外部类 对静态内部类的静态属性可以直接通过 内部类名.静态属性 的方式调用,而调用非静态属性的话,则需要通过构造内部类的对象才能去调用;
  • 静态内部类 对外部类的静态属性和非静态属性的调用 可以类比 外部类的静态方法,调用方式一模一样,而静态内部类中对自己的静态属性和非静态属性的调用 可以类比 外部类对自己的静态属性和非静态属性,调用方式也一模一样;
  • 其他类 对外部类的 静态内部类的 静态属性也是通过 外部类名.内部类名.静态属性 的方式调用,而无法调用非静态属性。

静态块

Java类中除了可以有变量、方法(包括构造器)、内部类之外,还有有代码块{},而static{}则标识的是静态代码块。代码块和静态代码块在Java程序中有什么作用,举个继承的例子分析下:

public class TestStatic extends A {
    static {
        System.out.println("=========我是TestStatic1========static块");
    }

    static {
        System.out.println("=========我是TestStatic2========static块");
    }

    {
        System.out.println("=========TestStatic的非static块=========");
    }

    public TestStatic() {
        System.out.println("=========我是TestStatic的构造器=========");
    }

    public static void main(String[] args) {
        // 测试
        A a1 = new TestStatic();
        System.out.println("----------第一次创建对象--------------" + a1);
        A a2 = new TestStatic();
        System.out.println("----------第二次创建对象--------------" + a2);
    }
}

class A {
    static {
        System.out.println("--------A1-------->static块");
    }

    static {
        System.out.println("--------A2-------->static块");
    }

    static {
        System.out.println("--------A3-------->static块");
    }

    {
        System.out.println("----------A的非static块----------");
    }

    public A() {
        System.out.println("----------我是A的构造器----------");
    }
}

# 测试结果打印如下:
--------A1-------->static块
--------A2-------->static块
--------A3-------->static块
=========我是TestStatic1========static块
=========我是TestStatic2========static块
----------A的非static块----------
----------我是A的构造器----------
=========TestStatic的非static块=========
=========我是TestStatic的构造器=========
----------第一次创建对象--------------com.xxwei.demo.controller.TestStatic@682a0b20
----------A的非static块----------
----------我是A的构造器----------
=========TestStatic的非static块=========
=========我是TestStatic的构造器=========
----------第二次创建对象--------------com.xxwei.demo.controller.TestStatic@3d075dc0

从测试的结果来看:

  • Java程序启动时,类加载的顺序为:父类的static块 -- 子类的static块 -- 父类的非静态块和构造器 -- 子类的非静态块和构造器;当第二次创建对象时,按先加载父类非静态块和构造器 再加载父类非静态块和构造器的顺序执行程序;
  • static块在类加载时只执行一次初始化,与程序中对象创建的次数无关,因此实际应用中会将不需要反复加载的资源对象放到static块中进行初始化;
  • 同一个类中多个static块是按顺序执行的。

final

含义:最终的,不可变的。可用来修饰类、方法(包括方法参数)、变量。

final修饰类、修饰方法

修饰的类,表示该类不可被继承,像String类、基本类型对应的包装类等。

修饰的方法,表示子类不可覆盖父类的该方法。注意:如果子类想要命名与父类相同名称的方法而不使用重写的方式,父类的方法可以加上private修饰符限定,那么子类命名的方法与父类的同名方法是没有关系的。

修饰的方法参数,表示传入的是一个常量,不能被修改。如:public void setAge(final int age){ return ++age;},编译报错。

final修饰变量

修饰的变量,表示的是一个常量,赋值后不能再被修改了。一直很好奇一个普通变量与final修饰的变量,在编译和反编译时是什么区别,做一个简单测试看一下:

// Test.java源码
public class Test {
    public static void main(String[] args) {
        String s = "niceDay";

        final String s1 = "nice";
        final String s2 = getNice();
        String s3 = "nice";

        String s4 = s1 + "Day";
        String s5 = s2 + "Day";
        String s6 = s3 + "Day";

        System.out.println(s == s4); //true
        System.out.println(s == s5); //false
        System.out.println(s == s6); //false
    }

    public static String getNice() {
        return "nice";
    }
}

// 编译器编译生成的Test.class字节码 
public class Test {
    public Test() {
    }

    public static void main(String[] args) {
        String s = "niceDay";
        String s1 = "nice";
        String s2 = getNice();
        String s3 = "nice";
        String s4 = "niceDay"; //
        String s5 = s2 + "Day"; //
        String s6 = s3 + "Day";
        System.out.println(s == s4);
        System.out.println(s == s5);
        System.out.println(s == s6);
    }

    public static String getNice() {
        return "nice";
    }
}

代码中用final修饰了变量s1、变量s2,s1直接赋值字符串nice,s2通过getNice()方法赋值,而s3无final修饰但也赋值字符串nice进行初始化了。从字节码文件看出:s1和字符串Day直接拼接成了字符串niceDay,而s2和s3还是原来的+号连接。这说明编译器在编译期间可以明确s1的值,而s2和s3的值要在运行期才能明确,因此可以得到结论:编译器如果能在编译期间明确final修饰变量的值,则它会被当做编译器常量使用。所以final修饰的变量表示的是一个常量,从编译器的角度来看不一定成立,和编译期间是否能明确变量的值有关!!!

final修饰的变量,在进行初始化赋值后不能再被修改了,即使是引用变量也是不能再指向其他对象了。而引用变量指向的对象不能更换,那么该对象的属性是否可以修改呢,来探索一下:

public class Test {
    public static void main(String[] args) {
        final SubTest subTest = new SubTest();
        SubTest st = new SubTest();
//        subTest = st; //该变量引用不能再指向其他对象,否则报错
        System.out.println(subTest.a + 3); //4
    }
}

class SubTest {
    public int a = 1;
}

从结果可知:引用变量指向的对象不能重新赋值修改,但该对象中的属性可以被操作!!

abstract

含义:抽象的。用于修饰类,方法。

抽象类和抽象方法

抽象类以abstract关键字定义的类,修饰符必须是public,也可以省略不写,抽象类中的抽象方法是非私有化的,抽象方法不能有代码实现。举例说明:

// 注释的代码均报错,表示无法编译
public abstract class abstractTestController 
        extends BaseController implements Func1, Func2 {
    // 普通类型的变量
    private String name1;
    public String name2;
    // abstract class类型的变量
    private  abstractTestController a1;
    public abstractTestController a2;
    // abstract类型的变量
//    private  abstract String sex1;
//    protected abstract String sex2;
//    public abstract String sex3;
    
    static {
        System.out.println("允许有静态代码块");
    }
    
    // 普通类型的构造器
    public abstractTestController() { }

    protected abstract void MA();
    public abstract void MB();
    public void MC(){
        System.out.println("允许有普通方法");
    }
//    public abstract void MCC(){...};
//    private abstract void MD();
//    public abstract static void ME();
//    public abstract synchronized void MF();
//    public abstract native void MG();
}
  • 注意抽象类中不允许有:私有抽象方法、抽象变量、抽象静态方法、抽象同步方法、抽象本地方法、抽象最终方法等,但允许有静态块、构造器、普通变量、本身对象类型的变量、普通方法及非私有抽象方法;
  • 抽象类可以继承抽象类,但还是单继承,而抽象类可以实现多个接口;
  • 抽象类继承抽象父类时,可以不重写抽象父类的抽象方法,但普通类继承抽象类时必须要重写抽象类的所有抽象方法;
  • 谈一下多态(面向对象的特征之一):抽象类虽然有构造器,但不能被实例化,这是因为抽象类的含义代表了是一种抽象类型而不是具体的类型,实际使用中也是通过多态的方式来定义对象的引用,如:AbstractList list = new ArrayList();

要注意抽象类与接口的区别:

接口可以理解为抽象类的变体,以interface标识,修饰符必须是public,修饰符也可以省略不写;

接口可以实现多继承,一个接口继承多个接口,可以不重写父接口的抽象方法;

接口内可以定义变量、抽象方法、静态方法、默认方法(Java8新增特性)等等;

interface Func extends Func1, Func2 {
    // 定义变量
    Logger logger = LoggerFactory.getLogger(Func.class);
    String str = "";
    // 抽象方法
    void test(String s);
    // 默认方法
    default void testZero() {
        System.out.println("Java8之后实现了默认方法,不需要被重写");
    }
    // 静态方法
    static void testAA() {
        System.out.println("静态方法,不需要被重写");
    }
}

native

含义:本地的,修饰方法所用,表示本地方法。本地方法的逻辑是C/C++语言实现的,并且本地方法会被编译成了DLL供Java平台调用,无需Java自己实现,用到时调用就行。

小结一下

这里详细而全面的总结了访问权限修饰符和部分非访问权限修饰符,对于我认为重要的修饰符,通过亲自测试和问题思索的方式来搞清楚怎么用的,从而进行一些总结。篇幅有限,就先分析到这吧,下一篇将继续分析总结synchronized、volatile、transient等关键字修饰符。不积硅步无以至千里,点滴付出终将有所收获,共同进步 ~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值