面向对象进阶(二)

面向对象进阶(二)

1 静态

static关键字用于修饰类的成员,包括成员变量、成员方法和代码块。静态成员与类的实例对象无关,它们属于类本身,而不是类的某个特定实例。
1.静态变量

  • 静态变量是使用static关键字修饰的成员变量。
  • 它在类的所有实例之间共享,只有一份内存副本,不会随着对象的创建而改变。
  • 可以直接通过类名访问,无需创建对象。
  • 常用于表示类级别的属性或常量。
  • 示例:
public class MyClass {
    static int staticVar = 10; // 静态变量
    // 其他成员和方法...
}
// 访问静态变量
System.out.println(MyClass.staticVar); // 输出:10

2.静态方法

  • 静态方法是使用static关键字修饰的类的方法。
  • 静态方法不属于类的实例,不能直接访问实例变量或调用实例方法。
  • 可以直接通过类名调用,无需创建对象。
  • 静态方法中不能使用this关键字,因为它没有隶属于特定对象。
  • 通常用于提供与类相关的功能,例如工具方法、工厂方法等。
  • 示例:
public class MyClass {
    static void staticMethod() {
        System.out.println("This is a static method.");
    }
    // 其他成员和方法...
}

// 调用静态方法
MyClass.staticMethod(); // 输出:This is a static method.

3.静态代码块

  • 静态代码块是使用static关键字修饰的代码块,用于初始化静态成员或执行一些静态操作。
  • 它在类加载时执行,只执行一次。
  • 静态代码块在构造方法之前执行,用于为静态成员赋初始值或进行其他一次性的初始化操作。
  • 示例:
public class MyClass {
    static {
        System.out.println("This is a static code block.");
    }
    // 其他成员和方法...
}

// 类加载时会执行静态代码块
MyClass myClass = new MyClass(); // 输出:This is a static code block.

静态成员在类加载时就会被初始化,而非静态成员在对象创建时才会被初始化。因此,静态成员的生命周期与类的生命周期相同,而非静态成员的生命周期与对象的生命周期相同。静态成员的共享性使得它们适用于表示类级别的属性或方法,而非静态成员则适用于表示对象级别的属性或方法。

package j_static;

public class Chinese {
    private String name;
    private int age;

    public static String nation = "中国";

    public static void f1() {
        System.out.println("f1");
    }

    public static void show() {
        //在静态方法中,不能使用this,super
        //静态方法中,只能使用静态变量
        System.out.println("国家:" + nation);
        f1();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

  • JMM分析:
    在这里插入图片描述

2 单例设计模式

设计模式:程序在某一个场景的,设计和开发套路,比如围棋和象棋套路
单例:要求程序中某一个组件,在程序运行的整个生命周期中,只有一个实例。

  • 饿汉式(Eager Initialization)和懒汉式(Lazy Initialization)都是单例设计模式的实现方式,用于保证一个类只有一个实例对象。
  • 构造函数私有,外面的组件不能主动创建这个对象。
  • 提供静态方法返回对象实例,返回的是静态实例(静态的特性),所以只有一个实例。

1.饿汉式单例

  • 饿汉式在类加载时就创建了单例对象,并在类的整个生命周期中保持不变。
  • 在类加载的时候就创建实例,因此不存在多线程安全问题。
  • 优点是简单且线程安全,缺点是如果这个实例在程序运行期间未被使用,会造成内存浪费。
  • 示例代码:
public class Singleton1 {

    private Singleton1() {
        System.out.println("饿汉式单例实例化...");
    }

    private static Singleton1 instance = new Singleton1(); //因为静态的特性只有一份存在方法区,所以这个对象一定只有一个

    public static Singleton1 getInstance() {
        return instance;
    }
}

2.懒汉式单例

  • 懒汉式在首次使用时才创建实例对象。
  • 懒汉式在多线程环境下需要考虑线程安全问题,常见的实现方式是在getInstance( )方法上添加同步关键字synchronized,但这会影响性能。
  • 优点是在实例首次被使用前不会占用内存,缺点是在多线程环境下需要处理线程安全问题。
  • 示例代码(使用双重检查锁定):
public class Singleton2 {
    private Singleton2() {
        System.out.println("懒汉式单例实例化...");
    }

    private static Singleton2 instance = null;

    public static Singleton2 getInstance() {
        if (instance == null) {
            instance = new Singleton2();
        }
        return instance;
    }
}
  • JMM分析:
    在这里插入图片描述

3 代码块和初始化顺序

3.1 代码块

代码块(Code Block)是一个包含一组语句的代码段,它用于组织和限定变量的作用域,以及在特定时机执行一些特定的代码。
1.普通代码块

  • 普通代码块是包含在一对花括号 {} 中的一组代码。
  • 普通代码块没有名称,它位于方法内部,用于限定变量的作用域。
  • 普通代码块在方法被调用时会被执行,并且在执行完毕后会销毁。

2.构造代码块

  • 构造代码块是定义在类中,没有名称的代码块,用于初始化实例对象时执行一些操作。
  • 构造代码块在构造器之前执行,每次创建对象时都会执行一次。
  • 构造代码块可以用于统一初始化对象的属性或执行其他必要的操作。

3.静态代码块

  • 静态代码块是定义在类中,使用static关键字修饰的代码块。
  • 静态代码块在类加载时执行,只执行一次,用于初始化静态成员或执行一些静态操作。
  • 静态代码块在其他静态成员初始化之前执行。

示例:

public class Example {
    // 静态代码块
    static {
        System.out.println("This is a static code block.");
    }

    // 构造代码块
    {
        System.out.println("This is a constructor code block.");
    }

    // 构造方法
    public Example() {
        System.out.println("This is a constructor.");
    }

    public static void main(String[] args) {
        // 普通代码块
        {
            System.out.println("This is a normal code block.");
        }
        
        Example example1 = new Example();
        Example example2 = new Example();
    }
}

在上面的示例中,我们定义了一个包含静态代码块、构造代码块和普通代码块的类。在程序运行时,静态代码块首先被执行(在类加载时执行),然后创建对象example1时,构造代码块和构造方法会依次执行;接着创建对象example2时,同样会执行构造代码块和构造方法。普通代码块在main()方法中被执行。

3.2 初始化顺序

  • 1.静态初始化

  • 2.初始化

  • 3.构造函数

  • 父类先执行

示例:

  • 创建父类Parent
public class Parent {
    int i = 0; //初始化
    {
        System.out.println("Parent初始化代码块1");
    }
    {
        System.out.println("Parent初始化代码块2");
    }

    static int i2 = 0; //静态初始化
    static {
        System.out.println("Parent静态初始化代码块1");
    }
    static {
        System.out.println("Parent静态初始化代码块2");
    }

    public Parent() {
        System.out.println("Parent的构造函数。。。。");
    }
}
  • 创建子类Children
public class Children extends Parent{
    int i = 0; //初始化
    {
        System.out.println("Children初始化代码块1");
    }
    {
        System.out.println("Children初始化代码块2");
    }

    static int i2 = 0; //静态初始化
    static {
        System.out.println("Children静态初始化代码块1");
    }
    static {
        System.out.println("Children静态初始化代码块2");
    }

    public Children() {
        System.out.println("Children的构造函数。。。。");
    }
}
  • 测试类
public class Test {
    public static void main(String[] args) {
        Children c = new Children();
    }
}
  • 执行结果

Parent静态初始化代码块1
Parent静态初始化代码块2
Children静态初始化代码块1
Children静态初始化代码块2
Parent初始化代码块1
Parent初始化代码块2
Parent的构造函数…
Children初始化代码块1
Children初始化代码块2
Children的构造函数…

以上示例先执行父类的静态初始化,再执行子类的静态初始化,然后执行父类初始化和构造函数,最后执行子类初始化和构造函数。

4 final关键字

final关键字在Java中用于提供不可变性、安全性和性能优化。当你确定一个类、变量或方法不需要被修改时,可以使用final关键字来确保其不可变性,从而避免潜在的错误和安全问题。
final关键字,用于修饰类、变量和方法,具有不同的作用:

  • 修饰类:final修饰的类是不可继承的,即不能有子类继承该类。这样可以防止类的实现被修改或继承可能引发的问题。
final class MyClass {
    // 类定义...
}
  • 修饰变量:final修饰的变量表示一个常量,一旦赋值后就不能再被修改。被final修饰的变量必须在声明时或构造方法中进行初始化。
final int myConstant = 10;
  • 修饰方法:final修饰的方法不能被子类重写,即子类不能对final方法进行覆盖。
class Parent {
    final void f() {
        // 方法定义...
    }
}

class Child extends Parent {
    // 编译错误,不能重写final方法
    // void f() { ... }
}

5 抽象类

  • 抽象类使用abstract关键字进行声明,如果一个类包含抽象方法,那么该类必须被声明为抽象类。
  • 抽象类可以包含抽象方法,抽象方法没有具体的实现,只有方法名,以分号结束:比如: public abstract void talk();需要由子类去实现具体的逻辑。
  • 抽象类可以包含非抽象方法,非抽象方法有具体的实现,并可以被子类直接继承和使用。
  • 抽象类可以有构造方法,用于初始化抽象类的成员变量。
  • 由于抽象类不能被实例化,所以抽象类不能用new关键字来创建对象。
public abstract class A {

    public abstract void f1();

    public void f2() {
        System.out.println("f2");
    }
}


public class B extends A {
    @Override
    public void f1() {
        System.out.println("f1()");
    }
}


public class Test {
    public static void main(String[] args) {
//      new A();   // 抽象类不能被实例化
        new B();
    }
}

6 接口

  • 接口使用interface关键字进行声明。
  • 接口中的方法都是抽象方法,没有方法体,只有方法名。
  • 接口中的变量默认是public static final的,也就是常量。
  • 类通过implements关键字来实现一个接口,并提供接口中定义的方法的具体实现。
  • 一个类可以同时实现多个接口,但只能继承一个父类。

示例:

public interface Usb {
    //接口的方法默认public abstract
	void start();

    void end();

    //接口中不能有普通方法
}

public interface TypeC {
    void transfer();
}

public class Printer implements Usb,TypeC{
    @Override
    public void transfer() {
        System.out.println("typeC接口传输数据");
    }

    @Override
    public void start() {
        System.out.println("usb 启动");
    }

    @Override
    public void end() {
        System.out.println("usb 关闭");
    }
}

在上述示例中,通过实现接口,Printer类获得了UsbTypeC接口的功能,并为这两个接口中的抽象方法提供了具体的实现。这样,Printer类可以当作Usb接口和TypeC接口的实例对象使用,同时具备了两个接口的行为特性。这就是Java中实现多继承的一种方式。

接口和抽象的区别

  • 抽象类可以有非抽象方法,接口只能有抽象方法
  • 接口中方法默认public abstract
  • 接口可以实现多继承,抽象类不可以

7 内部类

内部类(Inner Class)是定义在另一个类内部的类,也就是一个类嵌套在另一个类中。内部类可以访问外部类的成员,包括私有成员,而外部类也可以访问内部类的成员。内部类提供了一种将逻辑相关的类组织在一起的方式,增强了代码的可读性和封装性。

内部类的分类:

  • 成员内部类
    成员内部类是定义在外部类的成员位置,不使用static修饰的内部类。可以直接访问外部类的成员,包括私有成员。
  • 静态内部类
    静态内部类是定义在外部类的成员位置,使用static修饰的内部类。不依赖于外部类的实例,可以直接通过外部类名访问,与外部类的对象没有关系。
  • 局部内部类
    局部内部类是定义在方法内部的类,它只在方法内部有效,作用范围被限定在方法内部。可以访问方法内的局部变量,但这些局部变量必须是final修饰的。
  • 匿名内部类
    匿名内部类是没有名字的内部类,通常用于实现接口或继承抽象类。定义匿名内部类时直接在创建对象时进行实现,可以简化代码。

语法

  • 修饰成员变量的所有的修饰符都可以修饰成员内部类
  • 内部类可以使用继承

示例:

public class Person {
    //内部类,如果一个类只给当前外部类使用,别的组件不用,比如Dog和Cat类只是在Person中使用
    //所有的成员变量的修饰符,都可以修饰内部类
    //静态成员内部类
    public static class Dog {
        public void eat() {
            System.out.println("狗吃骨头");
        }
    }
	//非静态成员内部类
    public class Cat {
        public void eat() {
            System.out.println("猫吃鱼");
        }
    }

    public void f1() {
    	//局部内部类
        class Bird {
            public void eat() {
                System.out.println("鸟吃虫子");
            }
        }
        Bird b = new Bird();
        b.eat();
    }
}

Person类中定义了三种类型的内部类:
Dog是静态成员内部类,使用public static class Dog来声明。
Cat是非静态成员内部类,使用public class Cat来声明。
f1()方法中定义了局部内部类Bird

public class Test {
    public static void main(String[] args) {
        //创建静态成员内部类的实例
        Person.Dog dog = new Person.Dog();
        dog.eat();
        //创建非静态成员内部类的实例
        Person p = new Person();
        Person.Cat cat = p.new Cat();
        cat.eat();
        p.f1();
    }
}

Test类的main方法中,分别通过以下方式创建内部类的实例:
创建静态成员内部类的实例:Person.Dog dog = new Person.Dog();
创建非静态成员内部类的实例:Person p = new Person(); Person.Cat cat = p.new Cat();
调用外部类方法,创建局部内部类的实例:p.f1();

通过不同的方式创建内部类的实例,可以看到内部类的访问方式和范围。静态成员内部类不依赖于外部类的实例,可以直接使用外部类名访问;非静态成员内部类需要依赖于外部类的实例,需要通过外部类的实例来创建;局部内部类的作用范围限定在外部方法内部,只能在方法内部创建。

8 匿名内部类

一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。

  • 特点
    匿名内部类必须继承父类或实现接口
    匿名内部类只能有一个对象
    匿名内部类对象只能使用多态形式引用

  • 语法:new 接口(类){实现};

示例:
定义两个接口

public interface Usb {

    void start();

    void end();
}

public interface TypeC {
    void transfer();
}

测试

public class Test {
    public static void f1(Usb usb) {
        usb.start();
        usb.end();
    }

    public static void f2(TypeC typeC){
        typeC.transfer();
    }

    public static void main(String[] args) {
    	//方式1
        /**
         * 1:多态性:编译类型是父类
         * 2:new 接口(类),后面跟一个实现
         * 3:有一个对象usb1,实现了Usb接口的一个没有名字的类的实例
         *
         * Test$1->实现了Usb接口->用这个类产生一个对象usb1
         */
        Usb usb1 = new Usb() {
            @Override
            public void start() {
                System.out.println("打印机usb启动");
            }

            @Override
            public void end() {
                System.out.println("打印机usb停止");
            }
        };
        f1(usb1);
		//方式2:匿名内部类直接作为方法参数
        f2(new TypeC() {
            @Override
            public void transfer() {
                System.out.println("正在传输数据...");
            }
        });
    }
}

在以上示例中,首先定义了两个接口UsbTypeC,它们分别包含了start()、end()和transfer()方法。然后在Test类的main方法中,通过匿名内部类来实现这两个接口的具体逻辑,然后传递给f1()和f2()方法进行调用。使用匿名内部类可以避免定义新的类来实现接口,特别适用于只需要简单的接口实现的场景。通过匿名内部类,可以在创建对象的同时提供接口方法的具体实现,从而使代码更加简洁和灵活。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值