Bootstrap

JAVA中的static和内部类超详解

目录

1.static的作用

为什么要有这个static的修饰呢?

静态方法

 静态代码块

2.内部类的概念

1.静态内部类

2.匿名内部类

实例1:不使用匿名内部类来实现抽象方法

实例2:匿名内部类的基本实现

实例3:在接口上使用匿名内部类


1.static的作用

static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但是Java语言中没有全局变量的概念。 被static修饰的成员变量和成员方法独立于该类的任何对象。 也就是说,它不依赖类特定的实例,被类的所有实例共享。

为什么要有这个static的修饰呢?

static是静态修饰符,什么叫静态修饰符呢?大家都知道,在程序中任何变量或者代码都是在编译时由系统自动分配内存来存储的,而所谓静态就是指在编译后所分配的内存会一直存在,直到程序退出内存才会释放这个空间,也就是只要程序在运行,那么这块内存就会一直存在。这样做有什么意义呢?在Java程序里面,所有的东西都是对象,而对象的抽象就是类,对于一个类而言,如果要使用他的成员,那么普通情况下必须先实例化对象后,通过对象的引用才能够访问这些成员,但是用static修饰的成员可以通过类名加“.”进行直接访问。

只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。

下面我们来举个例子感受一下:

下面是一个简单的例子,展示了 static 方法和非 static 方法的区别:

首先,我们创建一个 Car 类,其中包含一个非 static 方法 drive 和一个 static 方法 getBrand

public class Car {
    private String brand;

    // 非静态方法
    public void drive() {
        System.out.println("开着倾城璧的车");
    }

    // 静态方法
    public static String getBrand() {
        return "江西软件大学牌子";
    }
}

 drive 方法是一个非 static 方法,这意味着它必须与类的实例相关联。当你创建 Car 类的实例时,你可以调用这个方法。

public class Main {
    public static void main(String[] args) {
        Car myCar = new Car();
        myCar.drive(); // 输出:开着倾城璧的车
    }
}

另一方面,getBrand 方法是一个 static 方法,这意味着它不需要与类的实例相关联。你可以直接通过类名调用这个方法。 

public class Main {
    public static void main(String[] args) {
        System.out.println(Car.getBrand()); // 输出: 江西软件大学牌子
    }
}

按照是否静态的对类成员变量进行分类可分两种:

  1. 一种是被static修饰的变量,叫静态变量或类变量
  2. 另一种是没有被static修饰的变量,叫实例变量

两者的区别是:

对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。

对于实例变量,没创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。

下面再举一个例子:

来考虑这样一个 Student 类。

public class Student {
    String name;
    int age;
    String school = "江西软件大学";
}

假设江西软件大学录取了一万名新生,那么在创建一万个 Student 对象的时候,所有的字段(name、age 和 school)都会获取到一块内存。学生的姓名和年纪不尽相同,但都属于郑州大学,如果每创建一个对象,school 这个字段都要占用一块内存的话,就很浪费。 因此,最好将 school 这个字段设置为 static,这样就只会占用一块内存,而不是一万块、

public class Student {
    String name;
    int age;
    static String school = "江西软件大学";

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) {
        Student s1 = new Student("倾城璧", 18);
        Student s2 = new Student("倾城壁", 16);
    }
}

 s1 和 s2 这两个引用变量存放在栈区(stack),倾城璧+18 这个对象和倾城璧+16 这个对象存放在堆区(heap),school 这个静态变量存放在静态区。

public class Counter {
    int count = 0;

    Counter() {
        count++;
        System.out.println(count);
    }

    public static void main(String args[]) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();
        Counter c3 = new Counter();
    }
}

public class StaticCounter {
    static int count = 0;

    StaticCounter() {
        count++;
        System.out.println(count);
    }

    public static void main(String args[]) {
        StaticCounter c1 = new StaticCounter();
        StaticCounter c2 = new StaticCounter();
        StaticCounter c3 = new StaticCounter();
    }
}

运行结果:

1
1
1

1
2
3

 “我们创建一个成员变量 count,并且在构造函数中让它自增。因为成员变量会在创建对象的时候获取内存,因此每一个对象都会有一个 count 的副本, count 的值并不会随着对象的增多而递增。”

由于静态变量只会获取一次内存空间,所以任何对象对它的修改都会得到保留,所以每创建一个对象,count 的值就会加 1,所以最终的结果是 3

静态方法

  • 静态方法属于这个类而不是这个类的对象;
  • 调用静态方法的时候不需要创建这个类的对象;
  • 静态方法可以访问静态变量。

下面继续代码演示:

public class StaticMethodStudent {
    String name;
    int age;
    static String school = "江西软件大学";

    public StaticMethodStudent(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    static void change() {
        school = "南昌大学";
    }
    
    void out() {
        System.out.println(name + " " + age + " " + school);
    }

    public static void main(String[] args) {
        StaticMethodStudent.change();
        
        StaticMethodStudent s1 = new StaticMethodStudent("倾城璧", 18);
        StaticMethodStudent s2 = new StaticMethodStudent("倾城璧", 16);
        
        s1.out();
        s2.out();
    }
}

change() 方法就是一个静态方法,所以它可以直接访问静态变量 school,把它的值更改为河南大学;并且,可以通过类名直接调用 change() 方法,就像 StaticMethodStudent.change() 这样。

沉默王二 18 南昌大学
沉默王三 16 南昌大学

“需要注意的是,静态方法不能访问非静态变量和调用非静态方法。你看,三妹,我稍微改动一下代码,编译器就会报错。”

“先是在静态方法中访问非静态变量,编译器不允许。

 然后在静态方法中访问非静态方法,编译器同样不允许。”

 

 静态代码块

静态代码块通常用来初始化一些静态变量,它会优先于 main() 方法执行。

public class StaticBlock {
    static {
        System.out.println("静态代码块");
    }

    public static void main(String[] args) {
        System.out.println("main 方法");
    }
}
//输出:
静态代码块
main 方法

2.内部类的概念

在Java中,内部类(Inner Class)是一种类定义在另一个类内部的类。内部类可以访问外部类的成员变量和方法,包括私有成员。内部类可以提高代码的封装性,并有助于组织相关的类和功能。

 Java 一个类中可以嵌套另外一个类,语法格式如下:

class OuterClass {   // 外部类
    // ...
    class NestedClass { // 嵌套类,或称为内部类
        // ...
    }
}

内部类有4中形式:成员内部类,局部内部类,静态内部类和匿名内部类。

嵌套类有两种类型:

  • 非静态内部类
  • 静态内部类

1.静态内部类

静态内部类可以使用 static 关键字定义,静态内部类我们不需要创建外部类来访问,可以直接访问它

静态内部类的语法如下:

public class OuterClass {
    private static int outerField = 10;

    public static class InnerStaticClass {
        public void innerStaticMethod() {
            System.out.println("Inner static method: " + outerField);
        }
    }
}

 在这个例子中,InnerStaticClass 是 OuterClass 的一个静态内部类。它不能直接访问外部类的非静态成员,但它可以访问外部类的静态成员(如 outerField)。

class OuterClass {
  int x = 10;

  static class InnerClass {
    int y = 5;
  }
}

public class MyMainClass {
  public static void main(String[] args) {
    OuterClass.InnerClass myInner = new OuterClass.InnerClass();
    System.out.println(myInner.y);
  }
}
//输出结果为5

2.匿名内部类

匿名内部类(Anonymous Inner Class)是Java中的一种特殊的内部类,它没有明确的类名,通常用于实现接口或继承类。匿名内部类通常用于事件处理和GUI编程。

说明白了匿名内部类也就是没有名字的内部类 

下面是实现匿名内部类的表达格式

new 父类或接口() {
    // 方法覆盖或实现
};

为什么要有匿名内部类?下面是详细例子

实例1:不使用匿名内部类来实现抽象方法
abstract class Person {
    public abstract void eat();
}
 
class Child extends Person {
    public void eat() {
        System.out.println("eat something");
    }
}
 
public class Demo {
    public static void main(String[] args) {
        Person p = new Child();
        p.eat();
    }
}

运行结果:eat something

可以看到,我们用Child继承了Person类,然后实现了Child的一个实例,将其向上转型为Person类的引用

但是,如果此处的Child类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?

这个时候就引入了匿名内部类

实例2:匿名内部类的基本实现
abstract class Person {
    public abstract void eat();
}
 
public class Demo {
    public static void main(String[] args) {
        Person p = new Person() {
            public void eat() {
                System.out.println("eat something");
            }
        };
        p.eat();
    }
}

可以看到,我们直接将抽象类Person中的方法在大括号中实现了

这样便可以省略一个类的书写

并且,匿名内部类还能用于接口上

实例3:在接口上使用匿名内部类
interface Person {
    public void eat();
}
 
public class Demo {
    public static void main(String[] args) {
        Person p = new Person() {
            public void eat() {
                System.out.println("eat something");
            }
        };
        p.eat();
    }
}

运行结果:eat something

由上面的例子可以看出,只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现

最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是继承Runnable接口

工厂方法: 在某些情况下,你可能会使用匿名内部类作为工厂方法的一部分,以创建不同类型的对象。

interface Animal {
    void makeSound();
}

class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow!");
    }
}

public class AnimalFactory {
    public static Animal createAnimal(String type) {
        if ("dog".equals(type)) {
            return new Dog();
        } else if ("cat".equals(type)) {
            return new Cat();
        }
        return null;
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = AnimalFactory.createAnimal("dog");
        animal.makeSound(); // 输出: Woof!
    }
}

匿名内部类在Java编程中是一种非常有用的特性,它提供了一种简洁的方式来创建和实例化一个类或接口的实现。它通常用于事件处理、回调函数、单例模式和工厂方法等场景。

ps:今天就写到这里了,其他两种内部类感觉在实际开发中用不到,就先不写了,等以后沉淀一下知识,再来补充这篇文章的其他内容。

;