JavaSE-6 类和对象(二)

一、java中三种对象初始化的方法

1. 构造函数初始化

构造函数是一种特殊的方法,用于在创建对象时对对象的成员变量进行初始化。它的名称必须与类名相同,并且没有返回类型(包括 void)。根据参数的不同,构造函数可以分为无参构造函数和有参构造函数。

class Person {
    private String name;
    private int age;

    // 无参构造函数
    public Person() {
        name = "Unknown";
        age = 0;
    }

    // 有参构造函数
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void displayInfo() {
        System.out.println("Name: " + name + ", Age: " + age);
    }
}

public class Main {
    public static void main(String[] args) {
        // 使用无参构造函数创建对象
        Person person1 = new Person();
        person1.displayInfo();

        // 使用有参构造函数创建对象
        Person person2 = new Person("Alice", 25);
        person2.displayInfo();
    }
}
代码解释
  • Person 类中定义了一个无参构造函数和一个有参构造函数。无参构造函数将 name 初始化为 "Unknown"age 初始化为 0;有参构造函数接收 name 和 age 作为参数,并将其赋值给对应的成员变量。
  • 在 Main 类的 main 方法中,分别使用无参构造函数和有参构造函数创建了 Person 对象,并调用 displayInfo 方法显示对象信息。

2. 初始化块初始化

初始化块分为实例初始化块和静态初始化块。实例初始化块在每次创建对象时都会执行,并且在构造函数之前执行;静态初始化块在类加载时执行,并且只执行一次,用于初始化静态成员变量。

class Student {
    private String name;
    private static int studentCount;

    // 实例初始化块
    {
        name = "New Student";
        System.out.println("实例初始化块执行");
    }

    // 静态初始化块
    static {
        studentCount = 0;
        System.out.println("静态初始化块执行");
    }

    public Student() {
        studentCount++;
        System.out.println("构造函数执行");
    }

    public static void displayStudentCount() {
        System.out.println("Student count: " + studentCount);
    }

    public void displayName() {
        System.out.println("Name: " + name);
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建第一个对象
        Student student1 = new Student();
        student1.displayName();
        Student.displayStudentCount();

        // 创建第二个对象
        Student student2 = new Student();
        student2.displayName();
        Student.displayStudentCount();
    }
}
代码解释
  • Student 类中定义了一个实例初始化块和一个静态初始化块。实例初始化块将 name 初始化为 "New Student",并输出一条信息;静态初始化块将 studentCount 初始化为 0,并输出一条信息。
  • 在 Main 类的 main 方法中,创建了两个 Student 对象。可以看到,静态初始化块只在类加载时执行一次,而实例初始化块在每次创建对象时都会执行,并且在构造函数之前执行。

3. 静态工厂方法初始化

静态工厂方法是一种静态方法,用于创建并返回类的对象。与构造函数相比,静态工厂方法具有更灵活的命名和返回类型,可以根据不同的需求创建不同类型的对象。

class Rectangle {
    private double length;
    private double width;

    private Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    // 静态工厂方法
    public static Rectangle createSquare(double side) {
        return new Rectangle(side, side);
    }

    public static Rectangle createRectangle(double length, double width) {
        return new Rectangle(length, width);
    }

    public double getArea() {
        return length * width;
    }
}

public class Main {
    public static void main(String[] args) {
        // 使用静态工厂方法创建正方形对象
        Rectangle square = Rectangle.createSquare(5);
        System.out.println("Square area: " + square.getArea());

        // 使用静态工厂方法创建矩形对象
        Rectangle rectangle = Rectangle.createRectangle(4, 6);
        System.out.println("Rectangle area: " + rectangle.getArea());
    }
}

二、包

含义

package 语句用于声明当前 Java 源文件中定义的类所属的包。包在 Java 里是一种组织代码的机制,类似于计算机文件系统中的文件夹,能将相关的类和接口组织在一起,方便管理和维护代码。package 语句的格式如下:

package 包名;

其中,“包名” 是一个由点号(.)分隔的标识符序列,通常采用公司域名的倒序来命名,以确保包名的唯一性。例如:

package com.example.project;

这表明该 Java 源文件中定义的类属于 com.example.project 这个包。

示例

假设你创建了一个简单的自定义类 Person,并且想把它放在自定义的包 com.example.demo 中,那么 Person.java 文件的内容可能如下:

// 声明该类属于 com.example.demo 包
package com.example.demo;

// 定义 Person 类
public class Person {
    private String name;
    private int age;

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

    public void displayInfo() {
        System.out.println("Name: " + name + ", Age: " + age);
    }
}

注意事项

  • package 语句位置package 语句必须是 Java 源文件中的第一条非注释语句,即源文件中不能有任何可执行代码或其他声明在 package 语句之前(除了注释和空行)。
  • 文件目录结构:Java 源文件的目录结构必须与包名对应。例如,package com.example.demo; 的 Person.java 文件应该存放在 com/example/demo 目录下,通常是在项目的 src 目录下开始的相对路径。

 

三、构造函数的相互调用

1)有参数的构造方法调用无参数的构造方法

class Person {
    private String name;
    private int age;

    // 无参数的构造方法
    public Person() {
        this.name = "未知";
        this.age = 0;
        System.out.println("无参数构造方法被调用");
    }

    // 有参数的构造方法
    public Person(String name, int age) {
        // 调用无参数的构造方法
        this();
        this.name = name;
        this.age = age;
        System.out.println("有参数构造方法被调用");
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建 Person 对象,调用有参数的构造方法
        Person person = new Person("张三", 20);
        System.out.println("姓名: " + person.getName());
        System.out.println("年龄: " + person.getAge());
    }
}

输出结果:张三,20 

注意事项

  • 调用位置this() 调用必须放在构造方法的第一行,否则会导致编译错误,因为要保证无参数构造方法的初始化逻辑优先执行。
  • 避免循环调用:要避免构造方法之间的循环调用,例如有参数构造方法调用无参数构造方法,而无参数构造方法又调用有参数构造方法,这样会导致栈溢出错误。
  • 存在性检查:如果要使用 this() 调用无参数构造方法,确保类中确实定义了无参数构造方法,否则会出现编译错误。

2) 无参数的构造方法调用有参数的构造方法

class Book {
    private String title;
    private double price;

    // 有参数的构造方法
    public Book(String title, double price) {
        this.title = title;
        this.price = price;
        System.out.println("有参数构造方法被调用");
    }

    // 无参数的构造方法
    public Book() {
        // 调用有参数的构造方法
        this("默认书名", 0.0);
        System.out.println("无参数构造方法被调用");
    }

    public String getTitle() {
        return title;
    }

    public double getPrice() {
        return price;
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建 Book 对象,调用无参数的构造方法
        Book book = new Book();
        System.out.println("书名: " + book.getTitle());
        System.out.println("价格: " + book.getPrice());
    }
}

输出结果:默认,0.0 

  • 调用位置:和有参数构造方法调用无参数构造方法一样,this() 调用必须放在无参数构造方法的第一行,否则会导致编译错误。
  • 避免循环调用:要防止构造方法之间的循环调用,例如无参数构造方法调用有参数构造方法,有参数构造方法又调用无参数构造方法,这会引发栈溢出错误。
  • 参数匹配:使用 this() 调用有参数构造方法时,传入的参数类型和数量必须与有参数构造方法的参数列表相匹配,否则会出现编译错误。

 

 

四、内部类

(1)实例内部类(成员内部类)

成员内部类是定义在另一个类的内部,但在方法之外的类。它可以访问外部类的所有成员,包括私有成员。

  • 访问权限:成员内部类可以访问外部类的所有成员,包括私有成员和静态成员。而外部类要访问成员内部类的成员,需要先创建内部类的对象。
class Outer {
    private int outerVar = 10;

    class Inner {
        void display() {
            System.out.println(outerVar); 
        }
    }

    public void useInner() {
        Inner inner = new Inner();
        inner.display();
    }
}

1. 如何获取实例内部类的对象

  • 创建对象:要创建成员内部类的对象,必须先创建外部类的对象,通过外部类对象来创建内部类对象。格式为 OuterClass.InnerClass inner = outerObject.new InnerClass();
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
  • 生命周期:成员内部类的对象依赖于外部类的对象存在,外部类对象销毁时,内部类对象也无法单独存在。

2.实例内部类的非静态方法中包含了一个指向外部类对象的引用

实际上,在这段代码里,内部类 Inner 的 display 方法虽然没有显式地写出指向外部类对象的引用,但它其实是隐式地使用了该引用。

隐式引用机制

在 Java 中,实例内部类(像代码里的 Inner 类)的对象会隐式地持有一个对外部类对象的引用。当在实例内部类的方法中访问外部类的成员时,Java 编译器会自动在背后添加对外部类对象的引用。

 System.out.println(outerVar); 看似直接访问了 outerVar,但实际上 Java 编译器会把它处理成类似于 System.out.println(Outer.this.outerVar); 的形式。这里的 Outer.this 就是指向外部类对象的引用,只不过编译器帮我们隐藏了这个引用的显式书写。

显式引用的情况
class Outer {
    private int outerVar = 10;

    class Inner {
        void display() {
            // 显式使用指向外部类对象的引用
            System.out.println(Outer.this.outerVar); 
        }
    }

    public void useInner() {
        Inner inner = new Inner();
        inner.display();
    }
}

这种显式引用的方式在一些特殊场景下很有用,比如当内部类和外部类存在同名成员时,为了明确指定要访问的是外部类的成员,就需要使用 外部类名.this.成员名 的形式。

实例内部类是依赖于外部类的实例而存在的,也就是说,必须先创建外部类的对象,才能创建实例内部类的对象。

因此,实例内部类的对象会隐式地持有一个对外部类对象的引用,通过这个引用,实例内部类可以访问外部类的所有成员(包括私有成员)。

在实例内部类的非静态方法中,这个引用是默认存在的,可用于调用外部类的成员。

// 外部类
class OuterClass {
    private int outerValue = 10;

    // 实例内部类
    class InnerClass {
        public void printOuterValue() {
            // 通过外部类类名.this 访问外部类对象的成员
            System.out.println("外部类的 outerValue 值为: " + OuterClass.this.outerValue);
        }
    }

    public void createInnerAndCallMethod() {
        // 创建实例内部类的对象
        InnerClass inner = new InnerClass();
        // 调用实例内部类的方法
        inner.printOuterValue();
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建外部类的对象
        OuterClass outer = new OuterClass();
        // 调用外部类的方法,在该方法中创建内部类对象并调用其方法
        outer.createInnerAndCallMethod();
    }
}
  1. 外部类 OuterClass:包含一个私有成员变量 outerValue,其值为 10。
  2. 实例内部类 InnerClass:在 InnerClass 的非静态方法 printOuterValue 中,使用 OuterClass.this.outerValue 来访问外部类对象的 outerValue 成员。这里的 OuterClass.this 就是指向外部类对象的引用,通过它可以明确地引用外部类的成员

3. 实例内部类当中不能有静态的成员变量,非要定义只能被static final修饰

不能有普通静态成员变量
  • 实例内部类的特性:实例内部类是依赖于外部类的实例而存在的,也就是说,必须先创建外部类的对象,才能创建实例内部类的对象。而静态成员变量属于类本身,在类加载时就会被分配内存空间,不依赖于任何对象。
  • 逻辑冲突:如果允许实例内部类有普通的静态成员变量,就会出现逻辑上的冲突。因为静态成员变量是类级别的,应该独立于任何实例,但实例内部类又依赖于外部类的实例,这两者之间的依赖关系不匹配。
可以使用 static final 修饰的成员变量
  • 常量性质:被 static final 修饰的成员变量是常量,在编译时就会被确定值,并且在整个程序运行过程中不会改变。由于它的值是固定的,不需要依赖于任何实例来初始化,所以可以在实例内部类中定义。
// 外部类
class Outer {
    // 实例内部类
    class Inner {
        // 编译错误,不能有普通的静态成员变量
        // static int normalStaticVar = 10; 

        // 可以使用 static final 修饰的常量
        static final int CONSTANT = 20;

        public void display() {
            System.out.println("常量值: " + CONSTANT);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建外部类对象
        Outer outer = new Outer();
        // 创建实例内部类对象
        Outer.Inner inner = outer.new Inner();
        // 调用内部类方法
        inner.display();
    }
}

(2)静态内部类

这个是为了不创建外部类对象而直接创建内部类对象。

class Outer {
    private static int outerStaticVar = 20;

    static class StaticInner {
        void display() {
            System.out.println(outerStaticVar); 
        }
    }
}
  • 创建对象:创建静态内部类的对象不需要先创建外部类的对象,可以直接通过 OuterClass.InnerClass inner = new OuterClass.InnerClass(); 来创建。
Outer.StaticInner staticInner = new Outer.StaticInner();
  • 静态特性:静态内部类中可以定义静态成员和非静态成员,静态成员可以直接通过类名访问。 
  • 静态内部类当中,不能访问外部类的非静态成员。外部类的非静态成员,需要通过外部类对象的引用才能访问。非要访问:
    OuterClass outerClass = new OuterClass();
    System.out.println(outerClass.data1);

     

(3)局部内部类

不咋用 也不好

局部内部类是定义在方法或代码块内部的类,它的作用域仅限于定义它的方法或代码块。

  • 访问权限:局部内部类可以访问外部类的成员,以及定义它的方法中的 final 或 effectively final(Java 8 及以后,未被声明为 final 但实际上不会被修改的变量)局部变量。
class Outer {
    public void method() {
        final int localVar = 30; 
        class LocalInner {
            void display() {
                System.out.println(localVar); 
            }
        }
        LocalInner localInner = new LocalInner();
        localInner.display();
    }
}
  • 作用域:局部内部类只能在定义它的方法或代码块内部使用,出了这个范围就无法访问。所以不需要使用public private等修饰限定。
  • 生命周期:局部内部类的生命周期与定义它的方法或代码块的执行周期相关,方法或代码块执行结束后,局部内部类对象也无法再被使用。

(4)匿名内部类

这个等讲了接口再说,

匿名内部类是一种没有名字的局部内部类,通常用于创建只需要使用一次的类的对象。

  • 定义和使用:匿名内部类通常用于实现接口或继承抽象类,在创建对象的同时实现接口或抽象类的方法。
interface MyInterface {
    void doSomething();
}

class Outer {
    public void useAnonymous() {
        MyInterface obj = new MyInterface() {
            @Override
            public void doSomething() {
                System.out.println("匿名内部类实现接口方法");
            }
        };
        obj.doSomething();
    }
}
  • 访问权限:和局部内部类一样,匿名内部类可以访问外部类的成员和定义它的方法中的 final 或 effectively final 局部变量。
  • 无构造方法:由于匿名内部类没有类名,所以不能定义构造方法,但可以使用实例代码块来进行初始化操作。

五、对象的打印 toString()

在 Java 中,toString() 方法是 Object 类的一个重要方法,所有的类都直接或间接地继承自 Object 类,因此所有类都拥有 toString() 方法。下面从 toString() 方法的默认实现、重写该方法的作用、示例代码以及使用场景等方面详细介绍。

默认实现

Object 类中的 toString() 方法的默认实现返回一个包含类名、@ 符号以及对象哈希码的十六进制表示的字符串。其格式通常为 类名@十六进制哈希码。例如:

public class DefaultToStringExample {
    public static void main(String[] args) {
        Object obj = new Object();
        System.out.println(obj.toString());
    }
}

上述代码运行后,输出结果可能类似于 java.lang.Object@1b6d3586,这种默认输出对于大多数实际应用来说,提供的信息非常有限,不能很好地反映对象的具体状态。

 

重写 toString() 方法的作用

重写 toString() 方法可以让我们根据类的具体需求,自定义对象的字符串表示形式,从而方便调试、日志记录以及向用户展示对象的信息。通过重写该方法,我们可以将对象的重要属性以可读性强的方式输出。

示例代码

class Person {
    private String name;
    private int age;

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

    // 重写 toString() 方法
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

public class OverrideToStringExample {
    public static void main(String[] args) {
        Person person = new Person("张三", 20);
        System.out.println(person); 
        // 这里直接打印 person 对象,实际上会自动调用 person.toString() 方法
    }
}

在上述代码中,Person 类重写了 toString() 方法,将 name 和 age 属性组合成一个更具可读性的字符串。在 main 方法中,当直接打印 person 对象时,会自动调用重写后的 toString() 方法,输出结果为 Person{name='张三', age=20}

使用场景

  • 调试和日志记录:在开发过程中,当需要查看对象的状态时,重写 toString() 方法可以方便地输出对象的属性值,帮助我们快速定位问题。
  • 用户界面展示:在将对象信息展示给用户时,重写 toString() 方法可以提供更友好的输出格式。
  • 集合元素输出:当将对象存储在集合中并打印集合时,重写 toString() 方法可以让集合元素的输出更具可读性。例如:
import java.util.ArrayList;
import java.util.List;

public class CollectionToStringExample {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<>();
        personList.add(new Person("李四", 25));
        personList.add(new Person("王五", 30));
        System.out.println(personList); 
        // 由于 Person 类重写了 toString() 方法,这里输出的集合元素信息更清晰
    }
}

注意事项

  • 遵循契约:重写 toString() 方法时,应尽量遵循 toString() 方法的契约,即返回的字符串应该简洁且包含对象的重要信息。
  • 性能考虑:如果对象的属性较多或复杂,重写 toString() 方法时要注意性能问题,避免进行过于复杂的计算。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值