一、高级类特性修饰符
1.1 static 修饰符
1.2 final 修饰符
1.3 abstract 修饰符
1.4 synchronized 修饰符
二、static 修饰符
作用域
- 修饰属性
- 方法
- 块
- 内部类
2.1 static 定义
- static 被称为静态,可以用来修饰类的 属性 和 方法
- 如果类的某个属性,不管创建多少个对象,属性的存储空间只有一个,那么这个属性就应该用 static 修饰,被 static 修饰的属性被称为静态属性
- static 属性可以使用对象调用,也可以通过类名直接调用
- 静态属性是类的所有对象共享的,即不管创建了多少个对象,静态属性在内存中只有一个。
2.2 static 属性
public class Person{
// 属于实例变量
private String name;
private int age;
// 属于静态变量,属于整个类的,共用一块内存空间
private static int count;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
count++;
}
public static void main(String[] args) {
Person p1 = new Person("A",18);
Person p2 = new Person("B",20);
System.out.println(p1.name+" "+p1.age+" "+p1.count);
System.out.println(p2.name+" "+p2.age+" "+p2.count);
}
}
我们发现两个 count 都是 2,由此可以得出结论,两个 对象调用的 count 是同一个内存空间的值,而 name 和 age分别是各自初始化赋的值
2.3 static 方法的定义
- 如果某个方法不需要与某个特定的对象绑定,那么该方法可以使用static修饰,被 static 修饰的方法称为静态方法。
- 基本结构如下
访问权限 修饰符 返回值类型 方法名称() {
方法体
}
// 给方法加上 static 修饰即可
public static void Test() {
xxx
}
2.4 static 方法的作用
- static 方法可以使用对象调用,也可以通过类名直接调用,更推荐使用类名调用
2.5 static 块特点
- static 块和 static 的属性以及方法的性质是相同的,用 static 修饰的代码块表示静态代码块,当Java虚拟机加载类时,就会执行该代码块。 (声明周期)
- 区别在于语法不同
作用:
- 静态块只有在类加载的时候被执行一次,不管创建多少个对象,都不会再执行。
- 如果一个类加载的时候,总要预先做- -些事情,则可以放在静态块中,例如,读取- -个属性文件,进行一些常规配置,写一些日志等。
- 一个类中可以有多个静态块,按照顺序执行。
static {
System.out.println("Hello World");
}
2.6 补充
- 本来的方法之间的调用
- 任何方法都可以直接调用静态方法
- 静态方法不能直接调用非静态方法,需要创建对象,用对象名调用非静态方法
何时使用 static 方法
- 如果某个方法与实例无关,也就是说不管哪个对象调用这个方法,都执行相同的操作,与对象没有关系,则应该定义为静态方法。不需要创建对象后再使用该方法。
- 例如: API 中的Math类,都是静态方法,因为进行数学运算时,与Math对象本身无关,使用类直接调用即可。
与该类对象本身无关时,定义为静态方法使用
public class Test {
public static int t = 1;
public static void a() {
System.out.println(t);
t = t + 1;
System.out.println("Hello World");
System.out.println(t);
}
public static void main(String[] args) {
// static 修饰的方法,可以直接通过 类名调用
a();
}
}
三、final 修饰符
作用域:
- 修饰类
- 常量属性
- 方法
- 局部常量
3.1 final 定义
final 用于声明属性,方法和类
- 属性:定义就必须直接赋值或者在构造方法中进行赋值,并且后期都不可以修改
- 方法:定义必须由实现代码,并且子类不刻意被覆盖
- 类:不能被定义为抽象类或是接口,不可以被继承
3.2 final 修饰属性
- 当 final 修饰属性时,基本数据类型的属性将成为常量,不能被修改
- 比如数学中的常量经常使用 final 修饰,比如 PI = 3.1415926 E = 2.71828
3.3 final 示例
public class Test {
private int index;
private static final double PI = 3.1415; // 在声明时同时赋值,往往与 static 一起使用
private final int level; // 声明时不赋值
public Test() {
// 在构造方法中逐一赋值
level = 0;
}
public Test(int index) {
this.index = index;
level = 1;
}
public void w(final int a) {
a = 12;// 这样是错误的,因为在方法参数前面加了 fianl 关键字,为了防止数据在方法体内被修改
}
// 总的原则:保证创建的每一个对象的时候, final 属性的值是确定的
}
3.4 final 总结
- final 修饰属性、局部变量,值不能被修改
- 被定义为静态常量的数据,常常以大写字母定义
- final 修饰类,不能被继承 (顶级类)
- final 修饰方法,不能被子类覆盖
- 不能和 abstract 一起使用
四、abstract 修饰符
作用域:
- 修饰类(如果一个类中没有包含足够的信息来秒回一个具体的对象,这样的类就是 抽象类)
- 方法 (被 abstract 修饰的方法)
abstract表示抽象,它和前面两个有些不同。它只能修饰类和方法。
4.1 abstract 修饰类和方法
abstract修饰的类被叫做抽象类,你可以把它看成一个类的半成品,所以它是无法被直接实例化的。abstract修饰的方法是抽象方法。
它必须要一个子类来继承。同时抽象类中定义的方法不一定是抽象方法,也可以是具体的方法
abstract class school{ //抽象类
public abstract void show();//抽象方法
public void say() {
System.out.println("我来自xx学校");
}
}
class teacher extends school{//继承school抽象类
public void show() {//实现show抽象方法
System.out.println("我是teacher!");
}
}
public class test{
public static void main(String[] args) {
school a = new teacher();
a.show();
a.say();
}
}
运行结果:
4.2 补充
这里有一个小小点要注意下,就是abstract不能和final一起使用,原因很简单,一个逻辑问题:abstract修饰的类必须要有子类来继承,final修饰的类不能有子类。他们俩从定义上就冲突了。
五、synchronized 修饰符
作用域:
- 修饰方法
- 同步代码块
synchronized 修饰符是一个在多线程中经常要用到的东西,它表示同步的。因为我们在使用多线程的时候经常需要多个线程来处理同一个东西,所以 synchronized用在这里就非常的nice。
synchronized同步代码块的用法:
synchronized(同步对象){
}
5.1 使用同步锁
如果我现在需要一个shop类来卖票,而且商店的票都要是通用的。而且我这个商店是很大的商店,它不可能只有一个窗口来卖票。所以这个时候我们要用多线程来解决它。
import java.lang.Thread;
public class test{
public static void main(String[] args) {
shop a = new shop();//实例化shop
Thread t1 = new Thread(a,"窗口一");//创建两个线程,分别是窗口1和2
Thread t2 = new Thread(a,"窗口二");
t1.start();//运行
t2.start();
}
}
class shop extends Thread{
public int number;//定义number表示票的数目
shop(){
number = 100;//默认为100张
}
@Override
public void run() {
while(true) {//一直卖票,直到票卖光了
System.out.println(Thread.currentThread().getName()+"正在卖票:"+number--);
if(number<=0) {
break;
}
}
}
}
运行效果:
可以发现,非常明显的是它没有达到我们想要的效果,似乎是票数冲突了。
这个时候,如果我们用上了synchronized
就不会出现这种情况了:
import java.lang.Thread;
public class SellTicket implements Runnable{
private static int ticket = 100; // 3个窗口共同拥有 100 张票
private Object obj = new Object();
// 使用同步会降低并发的效率
@Override
public void run() {
while (true) {
synchronized (obj) // 使用同步锁防止出现负数的情况
{
if (this.ticket > 0) {
try {
Thread.sleep(200);
System.out.println(Thread.currentThread().getName()+"正在卖票:"+ticket--);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
System.out.println(Thread.currentThread().getName()+"票卖完了!!!");
}
public static void main(String[] args) {
SellTicket st = new SellTicket(); // 实例化票类,共用一个主类,保证有100张票
Thread t1 = new Thread(st,"窗口一");
Thread t2 = new Thread(st,"窗口二");
Thread t3 = new Thread(st,"窗口三");
t1.start();
t2.start();
t3.start();
}
}
运行效果:
在上面的代码中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;
}
}
}
}
运行结果: