三个修饰符 | 我的日常分享

三个修饰符

三个修饰符

一、abstract

1.1 什么是抽象

似是而非,像却又不是;具备某种对象的特征,但又不完整。

image-20220206194819800

第一个图有的人说是旋涡,有的人说是龙卷风;第二个图有的人说是盘子,有的人说是一群鱼;第三个图有的人说是鲤鱼,有的人说不是。这种似是而非的,具备某种特征就是抽象

1.2 生活中的抽象

在百度上面搜索动物的图片,搜索结果都是动物的子类对象,比如猫、鹿、斑马等等,而没有动物这个对象。所以说动物这个对象是不存在的,它是抽象的。

image-20220206195208138

1.3 不该被创建的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TestAbstract {
public static void main(String[] args) {
/**
* Animal只是一种会吃会睡的对象,再无其他行为,
* 现实中也无动物这个具体的对象,存在的是它的子类,
* 狗、老虎、猫等,所以它不应该被独立创建成对象。
*/
Animal a = new Animal();
}
}
class Animal{
String breed;
int age;
String sex;

public Animal() {
}
public void eat(){
System.out.println("吃...");
}
public void sleep(){
System.out.println("睡...");
}
}

Animal只是一种会吃会睡的对象,再无其他行为,现实中也无动物这个具体的对象,存在的是它的子类,狗、老虎、猫等,所以它不应该被独立创建成对象。

那如何限制这种对象的创建呢?

答:使用抽象类。

1.4 抽象类

使用abstract修饰类,此类不能new对象。但是可以声明此类的变量。

Animal是抽象的,无法实例化

image-20220206201125196

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestAbstract {
public static void main(String[] args) {
Animal a;
}
}
abstract class Animal{
String breed;
int age;
String sex;

public Animal() {
}
public void eat(){
System.out.println("吃...");
}
public void sleep(){
System.out.println("睡...");
}
}

abstract修饰的类,被称为抽象类。抽象类意为不够完整的类、不够具体的类,抽象类对象无法独立存在,即不能new对象

1.5 抽象类的作用

  1. 可被子类继承,提供共性属性和方法
  2. 可声明为引用,更自然的使用多态。例,Animal a;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class TestAbstract {
public static void main(String[] args) {
Animal a1 = new Dog();
Animal a2 = new Cat();
}
}

abstract class Animal{
String breed;
int age;
String sex;

public Animal() {
}
public void eat(){
System.out.println("吃...");
}
public void sleep(){
System.out.println("睡...");
}
}

class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗狗吃饭...");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫猫吃饭...");
}
}

1.6 不该被实现的方法

Dog中的eat应该输出“狗狗在吃饭”,Cat中的eat应该输出“猫猫在吃饭”,但是父类中有方法eat输出的是“吃”,子类都会重写这个eat方法,父类实现这个方法就是多余的,但是声明是必要的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
abstract class Animal{
String breed;
int age;
String sex;

public Animal() {
}
public void eat(){
System.out.println("吃...");
}
public void sleep(){
System.out.println("睡...");
}
}

class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗狗吃饭...");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫猫吃饭...");
}
}

1.7 抽象方法

abstract修饰的方法,称为抽象方法,只有方法声明,没有方法实现,必须包含在抽象类中。

产生继承关系后,子类必须重写父类中所有的抽象方法,否则子类必须被定义为抽象类。这是因为抽象方法被继承了,而未被重写,由于抽象方法必须存在于抽象类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class TestAbstract {
public static void main(String[] args) {
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.eat();
a2.eat();
}
}

abstract class Animal{
String breed;
int age;
String sex;

public Animal() {
}
public abstract void eat();
public void sleep(){
System.out.println("睡...");
}
}

class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗狗吃饭...");
}
}

class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫猫吃饭...");
}
}

运行结果:

image-20220206202804664

如果Dog类中为重写eat方法,且Dog类未定义为抽象类。

image-20220206202912828

  1. abstract修饰类:不能new对象,但是可以声明引用
  2. abstract修饰方法:只用方法声明,没有方法实现(需包含在抽象类中)
  3. 抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类。
  4. 子类继承抽象类后,必须抽象父类中所有的抽象方法,否则子类还是抽象类。

二、static

什么是静态:

  • 静态(static)可以修饰属性和方法
  • 静态成员时全类所有对象共享的成员
  • 在全类中只有一份,不因创建多个对象而产生多份
  • 不必创建对象,也可通过类名直接访问

2.1 静态属性

静态属性是整个类共同持有的共享空间,任何对象对其修改,都会影响到其它对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestStaticField {
public static void main(String[] args) {
MyClass c1 = new MyClass();
c1.a = 10;
MyClass c2 = new MyClass();
c2.a = 20;
System.out.println("c1.a="+c1.a+" c2.a="+c2.a);
}
}

class MyClass{
public static int a;//静态属性
}

运行结果:

c1.a赋值为10,c2.a赋值为20,最后c1.ac2.a的值都变为了20。

image-20220209142404458

案例1:统计一个类的对象被创建过多少次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TestCase1 {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student("小明");
Student s3 = new Student("小红",18);
System.out.println("学生类被创建了"+Student.count+"次");
}
}
class Student{
private String name;
private int age;
public static int count=0;
public Student(){
count++;
}
public Student(String name){
this();//保证调用无参构造方法count++
this.name = name;
}
public Student(String name,int age){
this(name);//保证调用无参构造方法count++
this.age = age;
}
}

运行结果:

在每次创建中,都会执行到count++,变量count是静态属性,只有一份,所有对象共享这个属性。

image-20220209144345483

2.2 静态方法

可直接通过类名.静态方法名进行访问。

已知的静态方法有:Array.copyOf() Array.sort() Math.random() Math.sqrt() 等,均可只用类名直接调用。

1
2
3
4
5
6
7
8
9
10
public class TestStaticMethod {
public static void main(String[] args) {
MyClass1.testMedthod();
}
}
class MyClass1{
public static void testMedthod(){
System.out.println("我是静态方法");
}
}

运行结果:

静态方法testMedthod可直接通过类名进行访问调用。

image-20220209145033555

静态方法的特点:

  • 静态方法允许直接访问静态成员。
  • 静态方法不能直接访问非静态成员。
  • 静态方法中不允许使用thissuper关键字。
  • 静态方法可以继承,不能重写、没有多态。

案例1:静态方法允许直接访问静态成员。(非静态方法也可以直接访问静态成员)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class TestCase1 {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student("小明");
Student s3 = new Student("小红",18);
System.out.println("学生类被创建了"+Student.getCount()+"次");
}
}
class Student{
private String name;
private int age;
private static int count=0;
public Student(){
count++;
}
public Student(String name){
this();//保证调用无参构造方法count++
this.name = name;
}
public Student(String name,int age){
this(name);//保证调用无参构造方法count++
this.age = age;
}
public static int getCount(){
return count;
}
}

运行结果:

获取count被定为私有属性,通过静态方法进行访问。

image-20220209150008849

案例2:静态方法不能直接访问非静态成员。

1
2
3
4
5
6
7
class Student{
private String name;
private int age;
public static void testName(String name){
this.name = name;
}
}

运行结果:

image-20220209150314840

案例3:静态方法中不允许使用this或super关键字。

因为静态方法是可以通过类名进行调用,调用的时候对象有可能没有被创建,而this关键字是本对象的引用,super是父类的引用,所以都不可用。

image-20220209150933339

案例4:静态方法可以继承,不能重写、没有多态。

TestDog.java

1
2
3
4
5
6
7
8
public class TestDog {
public static void main(String[] args) {
//可以继承
Dog.testAnimal();
//不能重写,子类方法直接覆盖了父类的方法
Dog.testAnimal1();
}
}

Animal.java

1
2
3
4
5
6
7
8
9
10
public class Animal {
private int age;
private String sex;
public static void testAnimal(){
System.out.println("Animal的静态方法");
}
public static void testAnimal1(){
System.out.println("Animal的静态方法1");
}
}

Dog.java

1
2
3
4
5
6
7
public class Dog extends Animal{
private String breed;
public static void testAnimal1(){
//super.age;//不能使用super关键字
System.out.println("Dog中的静态方法");
}
}

运行结果:

image-20220209152042439

2.3 动态代码块

创建对象时,触发动态代码块的执行。

执行地位:初始化属性之后、构造方法之前。

作用:可为实例属性赋值,或必要的初始行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestDynamicBlock {
public static void main(String[] args) {
MyClass c1 = new MyClass();
}
}
class MyClass{
String field="字段";
//动态代码块
{
System.out.println("动态代码块");
System.out.println(field);
System.out.println(this.field);
field = "动态代码块中给field赋值";
System.out.println(field);
}
public MyClass(){
System.out.println("**构造方法**");
}
}

运行结果:

image-20220209152717883

2.4 类加载

  • JVM首次使用某个类时,需要通过CLASSPATH查找该类的.class文件
  • .class文件中对类的描述信息加载到内存中,进行保存,如包名、类名、父类、属性、方法、构造方法等..
  • 加载时机:以下操作都会执行类加载
    1. 创建对象
    2. 创建子类对象
    3. 访问静态属性
    4. 调用静态方法
    5. 主动加载,Class.forName("全限定名");

2.5 静态代码块

类加载时,触发静态代码块的执行(仅一次)。

执行地位:静态属性初始化之后。

作用:可为静态属性赋值,或必要的初始行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestStaticBlock {
public static void main(String[] args) {
MyClass c1 = new MyClass();
MyClass c2 = new MyClass();
}
}
class MyClass{
static String field = "字段";
String field1 = "非静态字段";
static {
System.out.println("静态代码块");
System.out.println(field);
//System.out.println(this.field);//无法从静态上下文中引用非静态 变量 this
//System.out.println(field1);//java: 无法从静态上下文中引用非静态 变量 field1
field = "静态方法中为field赋值";
System.out.println(field);
}
}

运行结果:

只执行了一次

image-20220209155045933

案例1:访问静态方法导致类加载从而执行静态代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestStaticBlock {
public static void main(String[] args) {
System.out.println("访问静态属性:"+MyClass.field);
System.out.println("访问静态属性:"+MyClass.field);
System.out.println("访问静态属性:"+MyClass.field);
}
}
class MyClass{
static String field = "字段";
String field1 = "非静态字段";
static {
System.out.println("静态代码块");
System.out.println(field);
field = "静态方法中为field赋值";
System.out.println(field);
}
}

运行结果:

访问多次仅执行一次静态代码块

image-20220209155600721

案例2:调用静态方法导致类加载从而执行静态代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class TestStaticBlock {
public static void main(String[] args) {
MyClass.testMethod();
MyClass.testMethod();
MyClass.testMethod();
}
}
class MyClass{
static String field = "字段";
String field1 = "非静态字段";
static {
System.out.println("静态代码块");
System.out.println(field);
//System.out.println(this.field);//无法从静态上下文中引用非静态 变量 this
//System.out.println(field1);//java: 无法从静态上下文中引用非静态 变量 field1
field = "静态方法中为field赋值";
System.out.println(field);
}
public static void testMethod(){
/**
* 无代码
* 只为触发静态属性的初始化
* 和静态代码的执行
*/
}
}

运行结果:

只会执行一次。

image-20220209155937248

案例3:主动加载类导致类加载从而执行静态代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestStaticBlock {
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("demo9.MyClass");
Class.forName("demo9.MyClass");
Class.forName("demo9.MyClass");
}
}
class MyClass{
static String field = "字段";
String field1 = "非静态字段";
static {
System.out.println("静态代码块");
System.out.println(field);
field = "静态方法中为field赋值";
System.out.println(field);
}
}

运行结果:

image-20220209160455084

2.6 对象的创建过程

静态属性初始化 --> 静态代码块 --> 实例属性初始化 --> 动态代码块 --> 构造方法

静态的属性以及代码块仅初始化执行一次。

image-20220209161528163

TestCreateObject.java

1
2
3
4
5
6
7
public class TestCreateObject {
public static void main(String[] args) {
new Super();
System.out.println("**********");
new Super();
}
}

Super.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Super {
static String superField = "父类静态属性";
static {
System.out.println("父类静态代码块");
System.out.println(superField);
}

String superField1 = "父类属性";
{
System.out.println("父类动态代码块");
System.out.println(superField1);
}

public Super(){
System.out.println("父类构造方法");
}
}

运行结果:

image-20220209161541232

2.7 带有继承的对象创建的过程

父类静态属性初始化 --> 父类静态代码块 --> 子类静态属性初始化 --> 子类静态代码块--> 父类实例属性初始化 --> 父类动态代码块 --> 父类构造方法 --> 子类实例属性初始化 --> 子类动态代码块 --> 子类构造方法

静态的属性以及代码块仅初始化执行一次。

image-20220209200307848

TestCreateObject.java

1
2
3
4
5
6
7
public class TestCreateObject {
public static void main(String[] args) {
new Sub();
System.out.println("**********");
new Sub();
}
}

Super.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Super {
static String superField = "父类静态属性";
static {
System.out.println("父类静态代码块");
System.out.println(superField);
}

String superField1 = "父类属性";
{
System.out.println("父类动态代码块");
System.out.println(superField1);
}

public Super(){
System.out.println("父类构造方法");
}
}

Sub.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Sub extends Super{
static String subField = "子类静态属性";
static {
System.out.println("子类静态代码块");
System.out.println(subField);
}

String subField1 = "子类实例属性";
{
System.out.println("子类动态代码块");
System.out.println(subField1);
}

public Sub(){
System.out.println("子类构造方法");
}
}

运行结果:

image-20220209200033475

2.8 总结

  • static修饰的成员为静态成员,无需创建对象,可直接通过类名访问。
  • static修饰的成员,虽然可通过对象进行访问,但是不推荐,这个就好比脱裤子放屁,还得创建一个对象。
  • 静态方法不能直接访问非静态成员。
  • 静态方法中不能使用this和super。
  • 静态方法可以被继承,但不能重写,没有多态。
  • 静态代码块在类加载时被执行,且只执行一次。

三、final

3.1 什么是最终

  • 概念:最后的,不可更改的
  • final可修饰的内容
    • 类 –> 最终类
    • 方法 –> 最终方法
    • 变量 –> 最终变量

3.2 final修饰类

final修饰的类不能被继承。

String Math System均为final修饰的类,不能被继承。

1
2
3
4
5
6
7
public final class Student {
String name;
}

class MyClass extends Student{

}

运行结果:

报错,无法被继承。

image-20220209204543464

image-20220209204520851

3.3 final修饰类中方法

此方法不能被子类重写。可以被继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Animal {
public final void sleep(){
System.out.println("睡觉...");
}
}

class Dog extends Animal{
//编译不通过
@Override
public final void sleep() {
super.sleep();
}

public static void main(String[] args) {
Dog dog1 = new Dog();
dog1.sleep();
}
}

运行结果:

image-20220209204929003

3.4 final修饰变量

1. 修饰局部变量

此变量不能被更改(常量)

1
2
3
4
5
6
public class TestFinal {
public static void main(String[] args) {
final int num = 10;
num = 20;//编译报错,java: 无法为最终变量num分配值
}
}

运行结果:

image-20220209205234749

2. 修饰类属性

被修饰的类属性称为实例常量,不能被更改。

实例常量不再提供默认值,必须手动赋予初始值。否则报错:变量 name 未在默认构造器中初始化

赋值时机:显示初始化 动态代码块 构造方法

注意:如果在构造方法里进行初始化,必须保证所有的构造方法都能对其正确赋值,且不能重复赋值。

案例1:显示初始化
1
2
3
public class Student {
final String name = "小明";
}
案例2:在动态代码块中初始化
1
2
3
4
5
6
public class Student {
final String name;
{
name = "小明";
}
}

如果已经初始化了,重复初始化将会报错

1
2
3
4
5
6
public class Student {
final String name = "小明";
{
name = "小明";
}
}

运行结果:

image-20220209205917008

案例3:在构造方法中初始化
1
2
3
4
5
6
public class Student {
final String name;
public Student() {
name = "小明";
}
}

使用构造方法进行初始化,如果存在多个构造方法,需要保证每个构造方法都正确的对实例常量进行初始化

1
2
3
4
5
6
7
8
9
10
11
12
public class Student {
final String name;
public Student() {
name = "小明";
}
public Student(String s){

}
public Student(int a){

}
}

运行结果:

image-20220209210228438

修改后:

1
2
3
4
5
6
7
8
9
10
11
12
public class Student {
final String name;
public Student() {
name = "小明";
}
public Student(String s){
this();
}
public Student(int a,String s){
name = s;
}
}

3. 静态常量(static+final)

静态常量不再提供默认值,与实例常量一样需要手动赋予初始值,二种初始化方式:显示初始化 静态代码块

可通过类名直接访问。

静态常量一般使用大写字母命名。

案例1:显示初始化

1
2
3
4
5
6
7
8
public class TestFinal {
public static void main(String[] args) {
System.out.println(Student.SCHOOL_NAME);
}
}
class Student{
static final String SCHOOL_NAME = "第一中学";
}

运行结果:

image-20220209210957320

案例2:静态代码块初始化

1
2
3
4
5
6
7
8
9
10
11
public class TestFinal {
public static void main(String[] args) {
System.out.println(Student.SCHOOL_NAME);
}
}
class Student{
static final String SCHOOL_NAME;
static {
SCHOOL_NAME = "第一中学";
}
}

运行结果:

image-20220209211205360

不能重复初始化,将报错。

1
2
3
4
5
6
class Student{
static final String SCHOOL_NAME = "第一中学";
static {
SCHOOL_NAME = "第一中学";
}
}

运行结果:

image-20220209211133048

4. 修饰引用类型变量

对象常量,被final修饰的引用类型变量,其引用地址不能被改变,引用指向的对象值可被改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TestFinal {
public static void main(String[] args) {
final Student s1 = new Student();
s1.name = "小明";
s1.name = "小红";
s1.setAge(18);
s1.setAge(19);
s1.show();
}
}
class Student{
String name;
private int age;
public void setAge(int age){
this.age = age;
}
public int getAge(){
return this.age;
}
public void show(){
System.out.println("name:"+name+" age:"+age);
}
}

运行结果:

image-20220209211927823

如果引用被改变将报错。

1
2
3
4
5
6
public class TestFinal {
public static void main(String[] args) {
final Student s1 = new Student();
s1 = new Student();
}
}

运行结果:

image-20220209212118089

3.5 总结

  • final修饰类:此类不能被继承
  • final修饰方法:此方法不能被覆盖
  • final修饰变量:此变量不能被更改(无默认初始值,需要手动初始化,只允许赋值一次)
    • 修饰局部变量:显示初始化
    • 修饰实例常量:显示初始化、动态代码块、构造方法
    • 修饰静态常量:显示初始化、静态代码块
    • 基本类型常量:值不能变
    • 引用类型变量:值(引用)不可变,引用对象的值可变