java --- 第三节课:类与对象

79 阅读17分钟

类和对象的基础知识

类的定义

  • 类是事物的属性(外在特征)和行为(具备的功能)的集合

  • 把具有相同特征和行为的一组对象抽象为类,类是抽象概念,如人类、车类等,无法具体到每个实体。

对象的定义

  • 对象是类的具体的体现

  • 某个类的一个实体,当有了对象后,这些特征便有了相应的值,行为也就有了相应的意义。

类是描述某一对象的统称,对象是这个类的一个实例而已。有类之后就能根据这个类来产生具体的对象。一类对象所具备的共同特征和行为(方法)都在类中定义。

image.png

类的格式

格式:

class  类名{
  类的属性 — 成员变量;
  类的行为 — 成员方法;
}

案例:

class Person{
       //类的属性
       String name;
       int age;
       String sex;
       //类的方法
       public void sayHello(){
              System.out.println(“Hello”);
        }
}

类的属性

  1. 属性是对一个类的描述,属性的值是这个类中不同对象的区分

  2. 属性是直接定义在类中的变量,它还有一个名字叫做成员变量,它的定义和“普通变量”的定义基本一致。

    • 格式:[修饰符] 属性类型 属性名 [ = 默认值];

    • 修饰符可以省略,可以是 public、protected、private、static、final等,也可以相互组合起来修饰属性。

  3. 属性类型:可以是 Java 语言允许的任何数据类型,包括基本数据类型和引用数据类型。

  4. 属性名:和普通变量的命名原则一致,但若可读性角度来说:属性名应该是由一个或多个有意义的单词连缀而成,第一个单词首字母小写,后面每个单词首字母大写,其他字母全部小写,单词与单词之间不需使用任何分隔符。

  5. 默认值:定义属性还可以定义一个默认值,如果未赋值,系统会赋予合适的初值。基本数据类型是 0,引用数据类型是 null。

public class Person{
    private int id;
    public String name;
    char gender;
}

类的方法

  1. 格式: image.png

    • 修饰符可以省略,也可以是 public、protected、private、static、final、abstract等, 其中 public、protected、private 三个最多只能出现一个,abstract 和 final 最多只能出现其中之一,它们可以与 static 组合起来修饰属性。
  2. 方法返回值类型:返回值类型可以是 Java 语言允许的任何数据类型,包括基本类型和引用类型。若声明了返回值类型,则方法体内必须有一个有效的 return 语句,该语句返回一个变量或一个表达式(变量或表达式类型必须与此处声明的类型匹配)。若方法没有返回值,必须使用 void 来声明。

  3. 方法名:与属性名规则基本相同,通常建议方法名以英文字的动词开头。

  4. 形参列表:用于定义该方法可以接受的参数,由零组或多组“参数类型 形参名”组合而成,多组参数之间以英文逗号(,)隔开,形参类型和形参名之间英文空格隔开。

注意:方法体里多条可执行性语句之间有严格的执行顺序,排在方法体前面的语句总是先执行,排在方法体后面的语句总是后执行。

public class Student{
    private String name;
    private int age = 33;
    
    public int getAge(){
        System.out.println("获取当前对象的年龄");
        return age;
    }
    
    public void readBook(String bookName){
        System.out.println(name + "正在读:" + bookName);
    }
}

创建对象

格式:类名  对象名  =   new   类名();

Person  me  =   new   Person();

使用对象中的属性

  1. 在本类中的方法内,可以直接调用本类定义的属性,无需额外操作。
  2. 对象使用:对象名.属性名 = 属性值
public class Student{
    String name;
    int age = 33;
    public int getAge(){
        System.out.println("获取当前对象的年龄");
        return age; 
    }
   
    public void readBook(String bookName){
        System.out.println(name + "正在读:" + bookName);
    }
}
Student me = new Student();
//赋值
me.name = “water”;
me.age = 25;
//输出
System.out.println(me.name + “…” + me.age + “…” + me.sex);

使用对象中的行为

  1. 在本类中的方法内,可以直接调用本类定义的其他,无需额外操作。
  2. 对象名.方法名();

eg: me.sayHello();

如果一个类包含了属性与方法,那么该类的每一个对象都具有自己的属性,但无论一个类有多少个对象,所有对象共享一个方法

对象的内存模型分析:

  1. javac  Demo04.java   ——   生成Demo04.class  和    Dog.class两个字节码文件
  2. Demo04.class 和  Dog.class 被放入方法区
  3. 永远第一个执行main方法,main方法被放进栈区
  4. new Dog() – 在堆内存里分配内存将Dog.class里的内容复制后放入,并将内存的地址存给dog
  5. 更改dog对象的属性值
  6. 执行dog对象的方法,执行新的方法时会将新方法压入栈中,直到执行到return时才会被释放出去,前提是此方法前面没有其他的方法在执行(栈的特性,先进后出)。
  7. 最后main方法执行完毕,被释放,堆内存中的内容失去被指的指针,等待垃圾回收器回收内存被释放

image.png

image.png

image.png

说明:eat()执行完成后,遇到return(由于方法类型是void,return被隐藏),eat()被移除栈,sleep()方法亦如此。

image.png

image.png

构造方法

构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时由编译器自动调用,并且在整个对象的生命周期内只调用一次。

定义

  1. 方法名称与类名称完全相同。
  2. 构造方法没有返回值声明(不是void)。
  3. 一个类中至少存在一个构造方法,若没有显示定义,编译器会生成一个默认的无参构造。
public class Person{
    // 构造方法
    public Person(){ // 无参构造函数
        // 构造方法的实现
    }
    public Person(int a){ // 有参构造函数
        // 构造方法的实现
    }
    public Person(int a,String b){ // 有参构造函数
        // 构造方法的实现
    }
    public Person(String a){ // 有参构造函数
        // 构造方法的实现
    }
    // ....
}

分类

  1. 显式构造器

    显式构造器就是程序员自己书写的构造器,这些构造器的名称必须和类名一致。通过形式参数列表的不同来区分这些构造器。

    • 无参数构造器

    • 有参数构造器

  2. 隐式构造器

    当一个类中没有提供任何构造方法,系统默认提供一个无参数的构造方法。这个无参数的构造方法叫做隐式构造器,也叫做缺省构造器。

作用

构造方法的作用只有一个:初始化对象,会在产生对象时,给对象未赋值的属性赋值。

调用

使用 new 运算符来调用构造方法,构造器会在产生对象时被调用。

new 构造方法名(实际参数列表);

案例:

public class Person{
    int id;
    String name;
    char gender;
    public Person(){
        id = 1;
        name = "张三";
        gender = '男';
    }
    public Person(int id1,String name1,char gender1){
        id = id1;
        name = name1;
        gender = gender1;
    }
}
// 其他类的main方法
// 调用了 Person 类的无参数构造器给属性赋值
Person p1 = new Person();
// 调用了 Person 类的有参数构造器给属性赋值
Person p2 = new Person(3,"关为",'男');

注意事项

this 关键字表示对当前对象的引用,一般在重名时使用

public class Person{
    int id;
    String name;
    char gender;
    public Person(){
        id = 1;
        name = "张三";
        gender = '男';
    }
    // ❌的写法
    public Person(int id,String name,char gender){
        // 这里定义的形式参数id、name和gender和成员变量重名了,
        // 如果还是这样赋值是错误的
        id = id; // ❌
        name = name; // ❌
        gender = gender; // ❌
    }
    // ✔的写法
    public Person(int id,String name,char gender){
        // 这里我们要使用到 this 关键字,
        this.id = id; // 将参数id值赋予成员变量id
        this.name = name; 
        this.gender = gender; 
    }
}

隐式构造器会给未赋值的属性赋予合适的初值(原始类型都是 0,引用类型是 null)。

public class Person{
    int id;
    String name;
    char gender;
    public void show(){
        System.out.println("id:" + id);
        System.out.println("name:" + name);
        System.out.println("gender:" + gender);
    }
}
// 其他类的main方法
Person person = new Person();
person.show(); // id:0   name:NULL   gender:NULL

当书写了显式构造器后,系统的无参数隐式构造器就无法被程序员调用了。

public class Person{
    int id;
    String name;
    char gender;
    public Person(int id){
        // ...
    }
}
// 其他类的main方法
Person person = new Person(); // ❌ 没有定义无参数构造器

如果定义的构造器并没有给所有属性赋值,那这些属性的值就是默认值

public class Person{
    int id;
    String name;
    char gender;
    public Person(int id){
        this.id = id;
    }
    public void show(){
        System.out.println("id:" + id);
        System.out.println("name:" + name);
        System.out.println("gender:" + gender);
    }
}
// 其他类的main方法
Person person = new Person(2);
person.show();// id:2   name:NULL   gender:NULL

匿名对象

  1. 定义:没有名字的对象就是匿名对象

  2. 格式:new  类名();

  3. 使用方式:

       new  类名().属性名;

       new  类名().方法名();

  1. 匿名对象的特点

       一次性,只能使用一次

  1. 使用范围

       (1)只需要使用一次对象的属性或者方法的时候,就可以选择使用匿名对象

       (2)匿名对象可以作为实际参数进行传递

class Person{
    public void eat(Pig pig){
       System.out.println(“我喜欢吃” + pig.name);
    }
}

class Pig{
       String name = “烤乳猪”;
}

class Demo{
       public static void main(String[] args){
              /*
                     Person person = new Person();
                     Pig pig = new Pig();
                     person.eat(pig)
              */
              new Person().eat(new Pig());
        }
}

image.png

抽象类

定义

抽象类:被abstract关键字所修饰的类就是抽象类

抽象方法:没有方法体的方法就是抽象方法

有抽象方法的类就是抽象类

特点

  1. 抽象类和抽象方法都要由abstract关键字来修饰
// class Person{
//    public void eat(){
//        System.out.println("吃"); // 在后续中并不会使用该方法,所以可以省略
//    }
// }
// 省略了执行语句的eat就没有内容,{}也可以省略,所以eat也就没有了方法体,
// 也就成为了抽象方法,而拥有抽象方法的类也就成为了抽象类
abstract class Person{
    public abstract void eat();
}
class Student extends Person{
    public void eat(){
        System.out.println("吃肉");
    }
}
class Teacher extends Person{
    public void eat(){
        System.out.println("吃米饭");
    }
}
class Demo{
    public static void main(String[] args){
        Student s = new Student();
        
        Teacher t = new Teacher();
    }
}
  1. 抽象类可以没有抽象方法,但是有抽象方法的类一定是抽象类

  2. 抽象类不能创建对象

个人理解:由于抽象类里有抽象方法,抽象方法没有方法特是不能调用的,为了防止调用抽象方法的情况,所以规定抽象类不能拥有对象,也就没有了会调用到抽象方法的情况。

  1. 一个类想要继承一个抽象类,要么该类也是一个抽象类,要么该类是一个普通类,但是必须重写所有的抽象方法

个人理解:必须重写抽象方法是为了防止抽象方法被调用

abstract class Person{
    public abstract void eat();
    public abstract void play();
    public void sleep(){
    
    }
    public void drink(){
    
    }
}
// 普通类
class Student extends Person{
    public abstract void eat(){
        
    }
    public abstract void play(){
        
    }
}

// 抽象类
abstract class Student extends Person{
    // 可以不用重写抽象方法
}

抽象类的成员特点

  1. 成员变量
  • 里面可以有静态的成员变量
  • 里面可以有非静态的成员变量
  • 里面可以自定义常量
  1. 成员方法
  • 里面可以有抽象方法
  • 里面可以有非抽象方法
  1. 构造方法
  • 有构造方法,即使不写,也有默认无参构造方法
  • 抽象类的构造方法是用来让子类调用的,然后给抽象类中的成变量进行初始化,方便提供给子类使用

abstract关键字不能哪些关键字共存

  1. abstract不和private关键字共存:

       因为被abstract关键字修饰的方法是抽象方法,子类是一定要重写的,如果该方法同时又被private修饰的话,子类还是重写不了,所以冲突

  1. abstract不能和static关键字共存

       因为被static关键字所修饰的方法可以通过类名直接调用,但是同时被abstract关键字修饰的方法,没有方法体的,调用一个没有方法的方法是没有意义的

  1. abstract不能和final关键字共存

       因为被final关键字所修饰的方法是不能被重写的,但是同时被abstract关键字修饰的方法,没有方法体的,所以冲突

内部类

定义在另一个类内部的类,它允许在一个类的内部定义另一个类。内部类可以访问外部类的成员变量和方法,并且可以用来实现一些特定的功能。

分类

• 成员内部类:定义在外部类的成员变量位置,并且可以访问外部类的成员变量和方法。

• 静态内部类:定义在外部类的静态变量位置,并且可以拥有自己的静态变量和方法。

• 局部内部类:定义在外部类的方法体内,作用域通常被限制在该方法中。

• 匿名内部类:没有名称的内部类,通常用来简化代码,在创建线程、事件处理等方面被广泛使用。

注意:在创建成员内部类对象时,必须先创建外部类对象,再使用外部类对象来创建成员内部类对象。因为成员内部类的存在依赖于外部类对象。

成员内部类

成员内部类是定义在另一个类中的类。它可以访问该外部类的所有成员变量和方法,即使它们被声明为私有。可通过以下步骤创建成员内部类:

  1. 在外部类中使用关键字 class 创建一个成员内部类。

  2. 调用外部类的构造函数或方法来创建成员内部类对象。

  3. 可以使用 new 运算符创建成员内部类对象。

public class Demo{
    public static void main(String[] args){
        Outer.Inner oi = new Outer().new Inner();
        System.out.println(oi.i);
        oi.print();

        Outer1 outer1 = new Outer1();
        outer1.show();
    }
}
class Outer{
    class Inner{ // 内部类
        int i = 1;
        
        public void print(){
            System.out.println("约吗");
        }
    }
}

class Outer1{
    private class Inner1{ // 内部类
        int i = 1;
        
        public void print(){
            System.out.println("约吗");
        }
    }
    public void shoe(){
        Inner inner = new Inner();
        System.out.println(inner.i);
        inner.println();
    }
}

注意事项:

• 内部类可以使用外部类的 private 成员(包括私有方法和变量)。

• 外部类不能直接访问内部类的成员。在外部类中,必须先创建内部类对象,然后才能访问内部类的成员。

• 使用内部类时应仔细考虑垃圾回收问题。如果内部类对象在外部类对象之外引用,则可能会导致内存泄漏。

静态内部类(Static Nested Classes)

静态内部类是静态声明的类,与外部类实例无关。它与其他静态成员类似,可以通过类名引用。

可通过以下步骤创建静态内部类:

  1. 在外部类中使用关键字 static 和 class 创建一个静态内部类。

  2. 使用外部类的名称和点运算符来访问静态内部类。

public class Demo{
    public static void main(String[] args){
        Outer.Inner oi = new Outer.Inner();
        System.out.println(oi.i);
        oi.print();

        /*
            以下方法需要导包: import com.inner,Outer,Inner;
        */
        Inner inner = new Inner();
        System.out.println(inner.i);
        inner.print();
    }
}
class Outer{
    static class Inner{ // 内部类
        int i = 1;
        
        public void print(){
            System.out.println("约吗");
        }
    }
}

注意事项:

• 静态内部类不能直接访问外部类的非静态成员。要访问外部类的非静态成员,需要首先创建外部类对象。

• 可以将静态内部类用作外部类的辅助类,但是,如果静态内部类不依赖于外部类实例,则最好将其声明为静态成员。

• 因为静态内部类不引用外部类实例,因此它可以防止内存泄漏等问题。

局部内部类(Local Inner Classes)

局部内部类是在代码块、方法或构造函数中定义的类。它只在该块中可见,并且通常用于解决特定问题。

可通过以下步骤创建局部内部类:

  1. 在代码块、方法或构造函数中使用关键字 class 创建一个局部内部类。

  2. 调用该块的方法或构造函数来创建局部内部类对象。

class Outer{
    public void print(){
        class Inner{
            int i = 2;
            public void show(){
                System.out.println("再见");
            }
        }
        Inner inner = new Inner();
        System.out.println(inner.i);
        inner.show();
    }
}
public class Demo{
    public static void main(String[] args){
        Outer outer = new Outer();
        outer.print();
    }
}

注意事项:

• 局部内部类不能用 public、protected 或 private 修饰。

• 局部内部类只能在声明它的块中实例化。

• 可以访问外部类的所有成员,包括私有成员。

匿名内部类(Anonymous Inner Classes)

匿名内部类是没有名称的局部内部类。它通常在创建对象时使用,并且只能实例化一次。

可通过以下步骤创建匿名内部类:

  1. 使用关键字 new 和要实现的接口或继承的基类来创建一个对象。

  2. 在大括号中编写代码块实现接口或继承的基类的方法。

interface Inter{
    public abstract void print();
}
class InterImpl implements Inter{
    public void print(){
        System.out.println("约吗");
    }
    public void show(){
        System.out.println("在吗");
    }
}
public Demo{
    public static void main(String[] args){
        InterImpl interImpl = new InterImpl();
        interImpl.print();
        interImpl.show();
        
        Inter inter = new InterImpl();
        inter.print();
        // inter.show();
        InterImpl interImpl = (InterImpl)inter;// 多态向下转型
        InterImpl.show();
    }
}
interface Inter{
    public abstract void print();
}

public Demo{
    public static void main(String[] args){
        //way-1
        new Inter(){
            public void print(){
                System.out.println("约吗");
            }
        }.print();
        
        //way-2
        Inter inter = new Inter(){
            public void print(){
                System.out.println("约吗");
            }
            public void show(){
                System.out.println("在吗");
            }
        }
        inter.print();
        // inter.show(); // 报错,因为多态的低端的不能使用子类特有的
    }
}

abstract class Father {
    int num;
 
    public Father(int num) {
        this.num = num;
    }
 
    public abstract void method();
}
 
class Niming {
    public void show() {
        Father father = new Father(10) { // 定义 Father 类的匿名子类
            @Override
            public void method() { // 重写 Father 类的 method() 方法
                System.out.println("Father 类的匿名子类重写了 method() 方法");
                System.out.println("num 的值为:" + num);
            }
        };
        father.method(); // 在合适的位置调用 method() 方法
    }
}
 
public class InnerClasssDemo5 {
    public static void main(String[] args) {
        Niming niming = new Niming();
        niming.show();
    }
}
interface InterFace1 {
    int num = 100;
    void method();
}
 
class Niming2 {
    public void show() {
        InterFace1 interFace1 = new InterFace1() { // 定义实现了 InterFace1 接口的匿名内部类
            @Override
            public void method() { // 重写 InterFace1 接口中的 method() 方法
                System.out.println("InterFace1 接口的匿名内部类重写了 method() 方法");
                System.out.println("num 的值为:" + num);
            }
        };
        interFace1.method(); // 在合适的位置调用 method() 方法
    }
}
 
public class InnerClassDemo6 {
    public static void main(String[] args) {
        Niming2 niming2 = new Niming2();
        niming2.show();
    }
}

注意事项:

• 匿名内部类通常用于事件处理程序和回调函数中。

• 不能为匿名内部类定义构造函数。

• 可以访问外部类的成员变量和方法,但只能访问最终变量(也就是值不会改变的变量)。

• 匿名内部类可以实现接口或继承一个类,但不能同时实现接口和继承一个类。

• 如果要在匿名内部类中使用一个局部变量,则该变量必须声明为 final。

类的初始化顺序

class Fu{
    static {
        System.out.println("fu...静态代码块");
    }
    {
        System.out.println("fu...构造方法代码块");
    }
    public Fu(){
        System.out.println("fu...构造方法");
    }
}
class Zi extends Fu{
    static {
        System.out.println("zi...静态代码块");
    }
    {
        System.out.println("zi...构造方法代码块");
    }
    public Zi(){
        // 1.super();
        // 2.执行构造方法体中构造代码块
        // 3.执行构造方法体中的内容
        System.out.println("zi...构造方法");
    }
}

image.png

成员变量和局部变量

局部变量:定义在方法中,或者方法声明上(形参)的变量就是局部变量

成员变量:定义在类中,方法外的变量就是成员变量(即类的属性)

image.png

局部变量和成员变量的区别:

  1. 定义外置不同

       局部变量:定义在方法中或方法声明上

       成员变量:定义在类中方法外

  1. 内存位置不同

       局部变量:存储在栈中方法中

       成员变量:存储在堆中的对象中

  1. 初始值不同

       局部变量:没有默认值初始值,想要使用一定要先赋值在使用

       成员变量:有默认初始化值,如果不赋值也可以使用

       String – null、int – 0、boolean — false、double – 0.0、char – ‘\u0000’

  1. 生命周期

       局部变量:因为是存储在方法中,所以是随着方法的存在而存在,随着方法的消失而消失

       成员变量:因为是存储在对象中,所以是随着对象的存在而存在,随着对象的消失而消失

  1. 作用域范围不同

       局部变量:出了方法就用不了了

       成员变量:在本类中的方法都可以使用