一、高级类特性修饰符

1.1 static 修饰符

1.2 final 修饰符

1.3 abstract 修饰符

1.4 synchronized 修饰符

二、static 修饰符

作用域

  1. 修饰属性
  2. 方法
  3. 内部类

2.1 static 定义

  • static 被称为静态,可以用来修饰类的 属性方法
  • 如果类的某个属性,不管创建多少个对象,属性的存储空间只有一个,那么这个属性就应该用 static 修饰,被 static 修饰的属性被称为静态属性
  • static 属性可以使用对象调用,也可以通过类名直接调用
  • 静态属性是类的所有对象共享的,即不管创建了多少个对象,静态属性在内存中只有一个。

2.2 static 属性

  1. public class Person{
  2. // 属于实例变量
  3. private String name;
  4. private int age;
  5. // 属于静态变量,属于整个类的,共用一块内存空间
  6. private static int count;
  7. public Person(String name, int age) {
  8. super();
  9. this.name = name;
  10. this.age = age;
  11. count++;
  12. }
  13. public static void main(String[] args) {
  14. Person p1 = new Person("A",18);
  15. Person p2 = new Person("B",20);
  16. System.out.println(p1.name+" "+p1.age+" "+p1.count);
  17. System.out.println(p2.name+" "+p2.age+" "+p2.count);
  18. }
  19. }

image.png

我们发现两个 count 都是 2,由此可以得出结论,两个 对象调用的 count 是同一个内存空间的值,而 name 和 age分别是各自初始化赋的值

2.3 static 方法的定义

  • 如果某个方法不需要与某个特定的对象绑定,那么该方法可以使用static修饰,被 static 修饰的方法称为静态方法。
  • 基本结构如下
  1. 访问权限 修饰符 返回值类型 方法名称() {
  2. 方法体
  3. }
  4. // 给方法加上 static 修饰即可
  5. public static void Test() {
  6. xxx
  7. }

2.4 static 方法的作用

  • static 方法可以使用对象调用,也可以通过类名直接调用,更推荐使用类名调用

2.5 static 块特点

  • static 块和 static 的属性以及方法的性质是相同的,用 static 修饰的代码块表示静态代码块,当Java虚拟机加载类时,就会执行该代码块。 (声明周期)
  • 区别在于语法不同

作用:

  • 静态块只有在类加载的时候被执行一次,不管创建多少个对象,都不会再执行。
  • 如果一个类加载的时候,总要预先做- -些事情,则可以放在静态块中,例如,读取- -个属性文件,进行一些常规配置,写一些日志等。
  • 一个类中可以有多个静态块,按照顺序执行。
  1. static {
  2. System.out.println("Hello World");
  3. }

2.6 补充

  • 本来的方法之间的调用
    • 任何方法都可以直接调用静态方法
    • 静态方法不能直接调用非静态方法,需要创建对象,用对象名调用非静态方法

何时使用 static 方法
  • 如果某个方法与实例无关,也就是说不管哪个对象调用这个方法,都执行相同的操作,与对象没有关系,则应该定义为静态方法。不需要创建对象后再使用该方法。
  • 例如: API 中的Math类,都是静态方法,因为进行数学运算时,与Math对象本身无关,使用类直接调用即可。

与该类对象本身无关时,定义为静态方法使用

  1. public class Test {
  2. public static int t = 1;
  3. public static void a() {
  4. System.out.println(t);
  5. t = t + 1;
  6. System.out.println("Hello World");
  7. System.out.println(t);
  8. }
  9. public static void main(String[] args) {
  10. // static 修饰的方法,可以直接通过 类名调用
  11. a();
  12. }
  13. }

image.png

三、final 修饰符

作用域:

  1. 修饰类
  2. 常量属性
  3. 方法
  4. 局部常量

3.1 final 定义

final 用于声明属性,方法和类

  • 属性:定义就必须直接赋值或者在构造方法中进行赋值,并且后期都不可以修改
  • 方法:定义必须由实现代码,并且子类不刻意被覆盖
  • 类:不能被定义为抽象类或是接口,不可以被继承

3.2 final 修饰属性

  • 当 final 修饰属性时,基本数据类型的属性将成为常量,不能被修改
  • 比如数学中的常量经常使用 final 修饰,比如 PI = 3.1415926 E = 2.71828

3.3 final 示例

  1. public class Test {
  2. private int index;
  3. private static final double PI = 3.1415; // 在声明时同时赋值,往往与 static 一起使用
  4. private final int level; // 声明时不赋值
  5. public Test() {
  6. // 在构造方法中逐一赋值
  7. level = 0;
  8. }
  9. public Test(int index) {
  10. this.index = index;
  11. level = 1;
  12. }
  13. public void w(final int a) {
  14. a = 12;// 这样是错误的,因为在方法参数前面加了 fianl 关键字,为了防止数据在方法体内被修改
  15. }
  16. // 总的原则:保证创建的每一个对象的时候, final 属性的值是确定的
  17. }

3.4 final 总结

  1. final 修饰属性、局部变量,值不能被修改
  2. 被定义为静态常量的数据,常常以大写字母定义
  3. final 修饰类,不能被继承 (顶级类)
  4. final 修饰方法,不能被子类覆盖
  5. 不能和 abstract 一起使用

四、abstract 修饰符

作用域:

  1. 修饰类(如果一个类中没有包含足够的信息来秒回一个具体的对象,这样的类就是 抽象类)
  2. 方法 (被 abstract 修饰的方法)

abstract表示抽象,它和前面两个有些不同。它只能修饰类和方法。

4.1 abstract 修饰类和方法

abstract修饰的类被叫做抽象类,你可以把它看成一个类的半成品,所以它是无法被直接实例化的。abstract修饰的方法是抽象方法。

它必须要一个子类来继承。同时抽象类中定义的方法不一定是抽象方法,也可以是具体的方法

  1. abstract class school{ //抽象类
  2. public abstract void show();//抽象方法
  3. public void say() {
  4. System.out.println("我来自xx学校");
  5. }
  6. }
  7. class teacher extends school{//继承school抽象类
  8. public void show() {//实现show抽象方法
  9. System.out.println("我是teacher!");
  10. }
  11. }
  12. public class test{
  13. public static void main(String[] args) {
  14. school a = new teacher();
  15. a.show();
  16. a.say();
  17. }
  18. }

运行结果:

4.2 补充

这里有一个小小点要注意下,就是abstract不能和final一起使用,原因很简单,一个逻辑问题:abstract修饰的类必须要有子类来继承,final修饰的类不能有子类。他们俩从定义上就冲突了。

五、synchronized 修饰符

作用域:

  1. 修饰方法
  2. 同步代码块

synchronized 修饰符是一个在多线程中经常要用到的东西,它表示同步的。因为我们在使用多线程的时候经常需要多个线程来处理同一个东西,所以 synchronized用在这里就非常的nice。

synchronized同步代码块的用法:

  1. synchronized(同步对象){

5.1 使用同步锁

如果我现在需要一个shop类来卖票,而且商店的票都要是通用的。而且我这个商店是很大的商店,它不可能只有一个窗口来卖票。所以这个时候我们要用多线程来解决它。

  1. import java.lang.Thread;
  2. public class test{
  3. public static void main(String[] args) {
  4. shop a = new shop();//实例化shop
  5. Thread t1 = new Thread(a,"窗口一");//创建两个线程,分别是窗口1和2
  6. Thread t2 = new Thread(a,"窗口二");
  7. t1.start();//运行
  8. t2.start();
  9. }
  10. }
  11. class shop extends Thread{
  12. public int number;//定义number表示票的数目
  13. shop(){
  14. number = 100;//默认为100张
  15. }
  16. @Override
  17. public void run() {
  18. while(true) {//一直卖票,直到票卖光了
  19. System.out.println(Thread.currentThread().getName()+"正在卖票:"+number--);
  20. if(number<=0) {
  21. break;
  22. }
  23. }
  24. }
  25. }

运行效果:

可以发现,非常明显的是它没有达到我们想要的效果,似乎是票数冲突了。
这个时候,如果我们用上了synchronized就不会出现这种情况了:

  1. import java.lang.Thread;
  2. public class SellTicket implements Runnable{
  3. private static int ticket = 100; // 3个窗口共同拥有 100 张票
  4. private Object obj = new Object();
  5. // 使用同步会降低并发的效率
  6. @Override
  7. public void run() {
  8. while (true) {
  9. synchronized (obj) // 使用同步锁防止出现负数的情况
  10. {
  11. if (this.ticket > 0) {
  12. try {
  13. Thread.sleep(200);
  14. System.out.println(Thread.currentThread().getName()+"正在卖票:"+ticket--);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. } else {
  19. break;
  20. }
  21. }
  22. }
  23. System.out.println(Thread.currentThread().getName()+"票卖完了!!!");
  24. }
  25. public static void main(String[] args) {
  26. SellTicket st = new SellTicket(); // 实例化票类,共用一个主类,保证有100张票
  27. Thread t1 = new Thread(st,"窗口一");
  28. Thread t2 = new Thread(st,"窗口二");
  29. Thread t3 = new Thread(st,"窗口三");
  30. t1.start();
  31. t2.start();
  32. t3.start();
  33. }
  34. }

运行效果:
image.png

在上面的代码中synchronized(this)中的this表示锁定的是本对象,

5.2 补充

假如我们上面的例子中,我的商店不只有一家,而是需多家连锁店,每家商店都有多个窗口,这种情况怎么解决嘞?嘿嘿,我们可以使用static关键字来和它配合使用:

import java.lang.Thread;
public class test{ 
       public static void main(String[] args) {
             shop a = new shop("商店一");
             shop b = new shop("商店二");
             Thread t1 = new Thread(a,"窗口1");//商店1的窗口1和2
             Thread t2 = new Thread(a,"窗口2");

             Thread t3 = new Thread(b,"窗口1");//商店2的窗口1和2
             Thread t4 = new Thread(b,"窗口2");

             t1.start();
             t2.start();
             t3.start();
             t4.start();
       }
}
class shop extends Thread{
       public static int number;//这里定义了static之后,所有的shop类就共用同一个number了
       public String name;

       shop(String name){//构造函数
             this.name = name;
             number = 100;
       }
       @Override
       public void run() {
             while(true) {
                    synchronized(this) {
                           System.out.println(name+Thread.currentThread().getName()+"正在卖票:"+number--);
                           try {
                                 Thread.sleep(100);
                           } catch (InterruptedException e) {
                                 // TODO Auto-generated catch block
                                 e.printStackTrace();
                           }
                    }
                    if(number<=0) {
                           break;
                    }
             }
       }
}

运行结果: