05 |「类、封装、继承、多态、接口 」

前言

类与接口

一、面向过程与面向对象

1、引例

  • 背景:做一个盒子;
  • 面向过程:开始先不去想做成一个什么样的盒子,随机选取工具制作,最后做成什么样就是什么样;
  • 面向对象:开始先预想好做成一个什么样的盒子,然后再去找对应的工具去做;

2、面向过程特征

  • 以过程为中心;
  • 将问题分解成详细的步骤,通过函数去实现每一个步骤,并依次调用;

3、面向对象特征

  • 封装性:对外部不可见;
  • 继承:扩展类的功能;
  • 多态:方法的重载、对象的多态性;

二、类与对象


1、类

  • 类的本质是一种数据类型,通过将变量函数打包在一起,组成一种新的数据类型;
  • 使用方式与基本数据类型相同;
  • Java 中所有的程序都需要以类的方式来编写和执行(在 Java 语言的世界里,所有的 Java 程序都必须要以类的形式存在;);

2、类与对象的关系

  • 类是对某一类事物的描述,是抽象的概念上的意义,无法具体化,没有可操作性,定义好类后并不能立即使用;

  • 对象是实际存在的该类事物的每一个个体,也被称为实例;

  • 类是一种数据类型;对象为类中的每一个变量;

    class Person {  类的命名:类的首字母大写;
    	String name;
    	int age;
    	public void tell() {
    		System.out.println("姓名:" + name + " " + "性别:" + age); 
    	}
    }
    
    public class Demo { 
    	public static void main(String[] args)
    	{
    		// 创建对象并为对象开辟内存空间;
    		// ①创建对象,此时P为Person的一个对象,此时并不能使用,需实例化
    		Person per = null;
    		per = new Person();  // 实例化,通过new开辟内存空间
    		
    		// 上面的两个操作可以合并,声明并实例化
    		Person per = new Person();
    	}
    }
    
  • 内存划分:Person p: 开辟栈内存空间,此时不能使用,需要通过 new 开辟堆内存空间后才可使用;

三、文件声明


  • 一个源文件中只能有一个public类,可以有多个非public类;
  • 源文件名应与public类名完全一致;
  • 包(package):即文件夹,后面跟着当前文件所在的文件夹路径;
  • 每个源文件中内容顺序:package xxx --> import xxx --> class
  • 在各个文件中写各种类,通过类之间的相互调用,共同实现复杂的项目逻辑;

四、修饰符


1、作用

  • 修饰符可以修饰类,也可修饰类中的元素;
  • 类中元素包括变量和函数
    • 变量称为成员变量,函数称为成员方法;

2、分类

  • 1)修饰成员变量/成员方法
    • public

      • 所有对象都可以访问,表示全局的,具有公共访问权限;
      • 使用 public 修饰变量/方法,则可以在当前项目中任何地方都可以访问到
    • private

      • 表示私有的,只有本类内部可以访问,离开本类之后,类外不能对其进行访问;
      • 将变量用 private 来修饰,表示该变量只能在所在的这个类中来使用,防止类外对其修改;
        class Student {
        	public int x;
        	private int y;
        }
        
        public class Main {
        	public static void main(String[] args) {
        		Student student = new Student();
        		student.x = 3;  // 不报错
        		student.y = 4;  // 报错
        	}
        }
        
    • protected:同一包或子类可以访问;对于类的外部是不可见的,只能在类的内部或继承的子类中访问;

    • 不加修饰符:在同一个包内(同一文件夹下)都可以访问;

    • static

      • static 修饰变量

        • 静态变量(公有)
          • 可以通过实例对象调用,最好通过 类名. 的方式来调用;
        • 静态变量与普通成员变量的区别
          • 静态成员变量为所有实例所共有的;
          • 普通成员变量为每个实例所独有的;
            class  Student {
            	public static int x = -1;
            	public int y = -1;
            }
            public class Main {
            	Student student1 = new Student(), student2 = new Student(), student3 = new Student();
            	student1.x = 2;  
            	System.out.printf("%d %d %d", student1.x, student2.x, student3.x);  // 均为2
            	Student.x;  // 通过类名来访问
            }
            
      • static 修饰函数

        • 静态方法(公有);可以通过实例对象调用,最好通过 类名. 的方式来调用;

        • 静态函数与普通成员函数的区别

          • 静态成员函数是所有对象所共有的;
          • 普通成员函数为每个对象所独有的;
        • 使用 static 修饰方法时,只能访问其它 static 声明的属性和方法,而非 static 声明的属性和方法是不能访问的(因为静态方法通过类名来访问,如果访问普通变量/方法无法得知访问的是哪个对象的变量和函数,不具有清晰的确定性));

          • 静态方法只能访问静态方法/静态变量;
          • 普通方法既可以访问静态方法/静态变量,也可以访问普通方法/普通变量;
        • 静态方法被 static 修饰,它们属于类而不是实例,因此在静态方法中只能访问类级别的静态成员,而不能直接访问实例级别的成员。这是因为静态方法在运行时并不知道具体的实例,因此无法访问实例级别的成员

          	public class MyClass {
              static int staticVar = 10;
              int instanceVar = 20;
          
              static void staticMethod() {
                  System.out.println(staticVar); // 可以访问静态变量
                  // System.out.println(instanceVar); // 错误!不能访问实例变量
              }
          }
          
        • 普通方法既可以访问静态方法/静态变量,也可以访问普通方法/普通变量:普通方法可以访问类级别的静态成员和实例级别的成员,因为普通方法在调用时会通过实例来确定具体的成员。不被 static 修饰的方法是属于对象的。

          public class MyClass {
              static int staticVar = 10;
              int instanceVar = 20;
          
              static void staticMethod() {
                  System.out.println(staticVar); // 可以访问静态变量
                  // System.out.println(instanceVar); // 错误!不能访问实例变量
              }
          
              void instanceMethod() {
                  System.out.println(staticVar); // 可以访问静态变量
                  System.out.println(instanceVar); // 可以访问实例变量
              }
          }
          
      • static 修饰类(静态内部类)
        静态内部类的实例:组织性和可读性: 将相关的类组织在一起,使得代码更加清晰和易于理解。静态内部类通常与外部类密切相关,因此将其定义为静态的可以减少类之间的耦合,提高代码的可读性和维护性。
        封装性: 静态内部类可以访问外部类的静态成员,但不能直接访问外部类的非静态成员。这种封装性可以帮助保持外部类的私有性,并防止不必要的访问。
        代码整洁性: 将相关的类放在一起,可以减少包的混乱性。静态内部类可以作为外部类的一部分,但不会增加包的大小或者混淆命名空间

        public class OuterClass {
            private static int outerStaticVar = 10;
            private int outerInstanceVar = 20;
        
            // 静态内部类
            public static class StaticInnerClass {
                void innerMethod() {
                    System.out.println("Accessing outer static variable: " + outerStaticVar);
                    // System.out.println("Accessing outer instance variable: " + outerInstanceVar); // 错误!无法访问非静态成员
                }
            }
        }
        

五、封装


1、目的

  • 使当前类的某些属性和方法在类外不可见;

2、实现

  • 将成员变量(属性)修饰为 private

  • 实现该属性的 getset 方法;

  • 类外访问被 private 修饰的变量时,通过 set 函数赋值,通过 get 函数取值;

    public class Point {
    	private int x;
    	private int y;
    	
    	// 构造方法
    	public Point(int x, int y) { 
    		this.x = x;  // this. :访问当前的成员变量,成员变量x
    		this.y = y;  // this.:访问当前成员变量,成员变量y
    	}
    
    	public void setX(int x) {
    		this.x = x;
    	}
    
    	public void setY(int y) {
    		this.y = y;
    	}
    
    	public int getX() {
    		return x;
    	}
    
    	public int getY() {
    		return y;
    	}
    
    	public String toString() {
    		return String.format("(%d, %d)", x, y);  // 将当前点转换成字符串
    	}
    }
    

六、构造方法


  • 作用:用于创建对象时初始化属性;

  • 构造方法无需调用,实例化过程中 new 时会自动调用构造方法;

  • 如果类中没有明显的构造方法,程序在编译时会自动创建一个无参且什么都不做的构造方法;

  • 构造方法也可以重载;

    public class Point {
    	private int x;
    	private int y;
    	
    	// 构造函数
    	public Point(int x, int y) {  // 局部变量 x,y,参数x,y
    		this.x = x;  // 将局部变量x赋值给成员变量x
    		this.y = y;  // 将局部变量y赋值给成员变量y
    	}
    }
    

七、继承


1、作用

  • 继承的逻辑常用,主要目的是为了扩展父类的功能,实现代码复用,减少代码量;
  • 关键字为 extends (扩展)

2、引例

  • 背景 1:一个游戏;

    • 所有英雄有共同的特点(血条、魔法值、技能),每个英雄又有各自不同的特点();
    • 为了减少代码量,将公共的特点变成一个基类,每个英雄角色将公共特点从基类中继承,然后在此基础上增加自己的特点;
  • 背景 2:点的基础上添加一个属性

    • 有一个 Point (点)类,想实现一个新的点类,含有 color 这个属性;
    • 新的点也属于点,新类可以将基类所有的内容继承过来,在基础上加一个新的参数;

3、注意点

1)每个类只能继承自一个类
  • 继承过程中,子类会继承基类中所有的成员变量和成员函数;
2)super 关键字
  • 作用:
    • 强行调用父类的方法
  • 使用
    • 在当前类调用基类(父类)时, 通过 super 关键字,super 代表父类;

    • 调用父类构造函数通过 super()

    • new 子类对象时,子类和父类的构造方法都会执行,编译器会自动加上 super(),自动调用父类的构造方法;

      class Father {
      	public Father() {
      		System.out.println("父类的构造方法");
      	}
      }
      
      class Son extends Father {
      	public Son() {
      		// 编译器会自动加上 super()
      		System.out.println("子类的构造方法");
      	}
      }
      
      public class Main
      {
      	public static void main(String[] args)
      	{
      		Son s = new Son();  // 输出:"父类的构造方法“ 与 ”子类的构造方法“,会先执行父类的构造方法
      	}
      }
      
      // 当子类和父类中有同名函数时,会优先使用子类的;
      // 父类
      class Point {
      	private int x;
      	private int y;
      	
      	// 构造函数
      	public Point(int x, int y) {  // 局部变量 x,y,参数x,y
      		this.x = x;  // this. :访问当前的成员变量,成员变量x
      		this.y = y;  // this.:访问当前成员变量,成员变量y
      	}
      
      	public void setX(int x) {
      		this.x = x;
      	}
      
      	public void setY(int y) {
      		this.y = y;
      	}
      
      	public int getX() {
      		return x;
      	}
      
      	public int getY() {
      		return y;
      	}
      
      	public String toString() {
      		return String.format("(%d, %d)", x, y);  // 将当前点转换成字符串
      	}
      }
      
      	// 子类
      	class ColorPoint extends Point {
      	    private String color;  // 当前类自己的属性
      		
      		// 当前类的构造函数
      	    public ColorPoint(int x, int y, String color) {
      	    	// 调用父类的构造函数 super()
      	        super(x, y);  // 将父类中的x和y赋成当前的局部变量亮x和y
      	        this.color = color;  // 赋值当前成员变量
      	    }
      		
      		// 实现当前类独有的函数
      	    public void setColor(String color) {  
      	        this.color = color;
      	    }
      	    
      		// 性质:当子类和父类中有同名函数时,会优先使用子类的
      	    public String toString() {  // 子类和父类中有同名的函数时会优先使用子类的
      	        return String.format("(%d, %d, %s)", super.getX(), super.getY(), this.color);  // 调用父类中的x和y
      	    }
      	}
      
3)重写与重载
  • 重写
    • 继承中有重写的概念,即子类定义了和父类同名的方法;
    • 定义:方法名称相同,返回值类型相同,参数相同;
    • 子类重写了父类的方法,如果不强行去调用父类方法是不会执行;
    class A {
    	public void tell() {
    		System.out.println("我是tell方法");
    	}
    }
    
    class B extends A {
    	// 重写
    	public void tell() {
    		System.out.println("我重写了tell方法");
    	}
    }
    
    public class Main
    {
    	public static void main(String[] args)
    	{
    		B b = new B();
    		b.tell();  // 只输出:我重写了tell方法,不会执行父类的tell方法(因为当子类和父类中有同名函数时,会优先使用子类的)
    	}
    }
    
    • 需求:必须执行父类中的 tell 方法,可以通过 super 关键字来调用;
    class A {
    	public void tell() {
    		System.out.println("我是tell方法");
    	}
    }
    
    class B extends A {
    	// 重写
    	public void tell() {
    		super.tell();  // 调用父类tell方法
    		System.out.println("我重写了tell方法");
    	}
    }
    
    public class Main
    {
    	public static void main(String[] args)
    	{
    		B b = new B();
    		b.tell();  // 输出:我是tell方法 与 我重写了tell方法,不会执行父类的tell方法
    	}
    }
    
4)子类对象实例化过程
  • 会自动先调用的父类构造方法,之后才调用子类的构造方法;

八、多态


1、引入

  • 因为子类继承了父亲的属性和方法,并且可以被视为父亲的一种特殊形式;

  • Java 中子类对象可以赋值给父类类型的变量,允许使用父类类型的变量来引用子类类型的对象;具体为,在 Java 中,如果一个类(子类)继承自另一个了类(父类),那么子类的对象可以被赋值给父亲类型的变量;

    // Dog 类继承自 Animal 类,并且重写了 sound() 方法。
    //在 Main 类的 main() 方法中,通过将 Dog 类的对象赋值给 Animal 类型的变量 animal,实现了多态性
    //尽管变量的类型是父亲类型,但是实际调用的是子类的方法,输出结果为 Dog barks
    class Animal {
        public void sound() {
            System.out.println("Animal makes a sound");
        }
    }
    
    class Dog extends Animal {
        @Override
        public void sound() {
            System.out.println("Dog barks");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Animal animal = new Dog();  // 子类对象赋值给父亲类型的变量
            animal.sound();  // 调用的是子类的方法
        }
    }
    

2、作用

  • 同一种类型的变量,调用同一个函数会有不同的行为;

  • 知识点:父类引用可以指向子类对象

  • Point2 虽然是父类类型,但是对象是子类对象;

  • Point1Point2 同为父类类型,调用 toString() 时结果不一样,Point1 调用父类的 toString()Point2 调用子类的 toString()

  • 一个游戏中几百个英雄,初始时每个英雄都会说一句话,如果不用多态的话,需要调用每一个英雄的 greet 方法,此时需要判断具体的哪个英雄,然后再执行相应的操作; 利用多态,将每一个英雄(对象)用同一个基类类型来定义即可,省去判断,每次调用 greet 函数时会根据具体对应的的对象来执行相应的 greet 函数。

  • 在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

九、接口


  • 接口和类的概念类似,但其主要是作为一个工具用来定义一个类中必须要包含哪些函数;
  • 定义:一个类实现一个接口,必须要实现这个接口中的所有函数;
  • 接口的主要作用作为一种规范程序员的工具,按照既定的要求去实现,方便协作;

1、接口的定义

  • 知识点:接口中的函数不添加修饰符,默认为 Public
    // 接口的定义
    public interface Role {  // 定义每个角色都需要包含的操作
        void greet();
        void move();
        int getSpeed();
    }
    

2、接口的继承

  • 知识点:一个接口可以继承自多个接口
public interface Hero extends Role {
	// 还包括Role接口中定义的函数
    void attack();
}

3、接口的实现

  • 接口的实现就是实现接口中定义的函数;

  • 实现的方式通过类来实现;

  • 角色定义成类,实现的功能定义成接口;

    // 定义一个宙斯类实现Hero接口
    class Zeus implements Hero {  // 角色1:宙斯
        private final String name = "Zeus";  // 当前英雄的名字
        public void attack() {
            System.out.println(name + ": Attack!");
        }
    	
    	// 实现Role中的函数:因为Hero继承自Role
        public void greet() {
            System.out.println(name + ": Hi!");
        }
    
        public void move() {
            System.out.println(name + ": Move!");
        }
    
        public int getSpeed() {
            return 10;
        }
    }
    
  • 一个类可以实现多个接口,每个接口用 , 隔开;定义两个接口,用一个类去实现;需要实现这两个类中所有的函数;

    // 此处的Role接口不继承自Hero接口,是两个独立的接口
    public class Zeus implements Hero, Role {
    	...
    }
    

4、接口的多态

  • 同一类型的变量执行同一操作,实现不同的功能;

  • 一个对象可以放到本类类型的变量上,也可以放到父类类型的变量上,也可以放到接口类型的变量上;

  • 背景:多个英雄(类)去实现同一个接口;英雄:宙斯、雅典娜;通过将对象全部定义成接口类型;访问接口类型变量的某一操作时,会根据不同的对象执行不同的操作;

    class Athena implements Hero {
        private final String name = "Athena";
        public void attack() {
            System.out.println(name + ": Attack!!!");
        }
    
        public void greet() {
            System.out.println(name + ": Hi!!!");
        }
    
        public void move() {
            System.out.println(name + ": Move!!!");
        }
    
        public int getSpeed() {
            return 10;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Hero[] heros = {new Zeus(), new Athena()};  // 将对象定义成接口类型
            for (Hero hero: heros) {  //  每一个对象都是同一种类型
            	// 同一种类型,执行同一种操作,实现不同的逻辑,第一个hero调用宙斯的greet(),第二个hero调用雅典娜的greet()
                hero.greet();
            }
        }
    }
    

  • 接口可以用来实现回调机制,将方法作为参数传递给其他方法使用

// 我们定义一个接口,这个接口包含了一个方法,作为回调函数
public interface Callback {  
    void onTaskCompleted(String result);  
}

// 然后,我们定义一个任务执行器,它接受一个Callback实例作为参数
public class TaskExecutor {  
    // 执行任务,并在完成后调用回调函数  
    public void executeTask(Callback callback) {  
        // 模拟任务执行...  
        String result = "任务完成";  
        // 假设任务执行完成,调用回调  
        callback.onTaskCompleted(result);  
    }  
}

// 最后,我们实现Callback接口,并在其中定义任务完成后的操作
public class TaskCompletionHandler implements Callback {  
    @Override  
    public void onTaskCompleted(String result) {  
        System.out.println("任务结果:" + result);  
    }  
  
    public static void main(String[] args) {  
        TaskExecutor executor = new TaskExecutor();  
        Callback callback = new TaskCompletionHandler();  
        executor.executeTask(callback);  
    }  
}

十、抽象类


  • 包含一个抽象方法的类就是抽象类;
  • 抽象方法
    • 声明未被实现的方法,必须使用 abstract 关键字声明;
  • 使用抽象类:抽象类不能直接进行实例化,必须通过其子类来实例化;通过子类(不是抽象类)实例化后,子类必须实现抽象类中的所有抽象方法;
    // 定义
    abstract class className {
    	// 下面的不是必须的,均为可选项
    	属性
    	方法
    	抽象方法
    }
    
    // 抽象类
    abstract class Abs {
    	private int age;
    	public void tell() {
    		
    	}
    	// 抽象方法
    	public abstract void say();
    	public abstract void print();
    }
    
    // 子类继承抽象类
    class AbsDemo extends Abs {
    	public void say() {
    	}
    	public void print() {
    	}
    }
    
    public class AbsDemo01 {
    	public static void main(String[] args) {
    		// 通过子类来使用抽象类
    		AbsDemo a = new AbsDemo();
    		a.say();
    		a.print();
    	}
    }
    

十一、补充


1、引用传递

2、this 关键字

  • 访问类中的属性

    • 用于和函数中同名的形参变量作区分;

  • 调用当前类中的方法

  • 通过 this 调用类中的构造方法,注意必须放在第一行

  • 表示当前对象;pthis 指同一个对象

3、匿名类

  • 作用

    • new 接口类型:是在创建对象时使用匿名类来实现某个接口
    • 利用此种方式,可以直接在对象创建的地方定义接口的实现逻辑,而不需要显示地定义一个具体的类名来实现接口;此种方式适用于临时需求或只需要使用一次的情况,可以简化代码;
    • 在这里插入图片描述
      在这里插入图片描述
  • 使用 { } 创建匿名内部类的好处是,我们可以在创建对象的同时对其进行定制化的扩展。
    在这里插入图片描述

首先,创建了一个 Handler 对象,并使用 new Handler(Looper.myLooper()) 进行初始化。这里的 Looper.myLooper() 返回当前线程的 Looper 对象,用于处理消息队列。
接着,使用 {} 创建了一个匿名内部类,并将其作为 Handler 对象的扩展。在这个匿名内部类中,重写了 handleMessage(Message msg) 方法,用于处理消息。在 handleMessage(Message msg) 方法中,我们首先通过 super.handleMessage(msg) 调用了父类 Handler 的 handleMessage() 方法,以确保父类的默认处理逻辑得到执行。然后,可以在该方法中添加自定义的消息处理逻辑。

  • 匿名对象
    • 指没有被命名或没有被赋予变量引用的对象,是一种临时创建和使用对象的方式,通常用于简化代码或在特定情况下临时执行某些操作。
    • 匿名对象的特点是在创建时不使用变量进行引用,而是直接在需要的地方创建对象,并在同一行代码中使用它。由于没有变量引用,匿名对象通常只能在创建的地方使用,无法在其他位置再次引用或重复使用。需要注意的是,由于匿名对象没有变量引用,一旦离开了创建的作用域或使用的代码块,该对象就会被垃圾回收机制回收。因此,在使用匿名对象时要注意对象的生命周期和作用域。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个写代码的修车工

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值