面向对象进阶(二)
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
类获得了Usb
和TypeC
接口的功能,并为这两个接口中的抽象方法提供了具体的实现。这样,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("正在传输数据...");
}
});
}
}
在以上示例中,首先定义了两个接口Usb
和TypeC
,它们分别包含了start()、end()和transfer()
方法。然后在Test
类的main
方法中,通过匿名内部类来实现这两个接口的具体逻辑,然后传递给f1()和f2()
方法进行调用。使用匿名内部类可以避免定义新的类来实现接口,特别适用于只需要简单的接口实现的场景。通过匿名内部类,可以在创建对象的同时提供接口方法的具体实现,从而使代码更加简洁和灵活。