面向对象2

一.this关键字

1.什么是this关键字

this就是一个变量,用在方法中,可以拿到当前类的对象。

2.为什么要使用this关键字

  1. 作用域: 当一个变量在较高级别的作用域中声明时,它可能会遮蔽在较低级别作用域中声明的具有相同名称的变量。这意味着在较高级别的作用域中,你将无法直接访问较低级别作用域中的变量,因为它被遮蔽了。
  2. 方法参数和实例变量: 在方法内部,方法参数的名称可能与类的实例变量名称相同。这种情况下,方法参数会遮蔽实例变量,从而在方法内部无法直接访问实例变量。
public class ShadowingExample {
    private int value = 10;
public void printValue(int value) {
    // 在方法内部,方法参数 value 遮蔽了实例变量 value
    System.out.println("Method parameter value: " + value);
}

public void printInstanceValue() {
    // 直接访问实例变量 value
    System.out.println("Instance variable value: " + this.value);
}

public static void main(String[] args) {
    ShadowingExample example = new ShadowingExample();
    example.printValue(5);
    example.printInstanceValue();
}
}

输出结果如下

Method parameter value: 5
Instance variable value: 10

当使用this关键字以后,可以区分成员变量和方法中的局部变量

二.访问修饰符

1.作用对象

  1. 成员变量(字段): 可以为类的字段(成员变量)指定访问修饰符,以控制它们的可见性。这些修饰符定义了哪些代码可以访问这些字段。
  2. 方法: 方法可以使用访问修饰符来指定谁可以调用这些方法。通过使用适当的修饰符,你可以限制或扩展方法的可见性。
  3. 构造方法: 构造方法也可以使用访问修饰符来指定构造方法的可见性。这决定了哪些代码可以创建类的实例。
  4. 嵌套类和接口: 嵌套类(内部类)和接口可以使用访问修饰符来限制它们的可见性范围。这有助于控制嵌套类和接口的外部可见性。

2.访问修饰符种类

  1. public(公共):

    • 可以在任何地方访问,没有访问限制。
    • 可以被类内部、同一包内其他类和不同包中的类访问。
  2. protected(受保护):

    • 可以在同一包内的其他类中访问。
    • 子类(无论是否在同一包内)可以访问父类中的 protected 成员。
    • 不同包内的子类可以访问父类中的 protected 成员,但前提是通过继承关系。
  3. 默认(或包私有,默认可见性):

    • 如果没有指定任何访问修饰符,默认为包私有。
    • 只能在同一包内的其他类中访问,无法在不同包中访问。
  4. private(私有):

    • 只能在声明成员的类内部访问。

    • 其他类无法访问私有成员,包括子类。

    注:一个类只能 有一个public类且与文件名和类名相同

三.封装

1.什么是封装

封装是将数据(属性、成员变量)和操作(方法、函数)捆绑在一起,形成一个独立的单元,并且对外部隐藏其内部实现的细节。通过封装,对象的内部状态和行为被保护,外部只能通过指定的接口来访问和操作对象,而不需要了解内部的具体实现。

2.使用封装的意义

  1. 信息隐藏: 封装使得对象的内部实现细节对于外部来说是不可见的。这可以防止外部代码直接访问对象的内部状态,从而减少了代码之间的耦合性。这种信息隐藏提供了更好的模块化,使代码更容易维护和修改。
  2. 安全性: 封装可以限制对数据的访问,通过提供有限的接口来控制数据的修改和读取。这有助于防止不合法或错误的操作,提高了代码的稳定性和安全性。
  3. 易于修改和维护: 由于封装将实现细节隐藏在内部,你可以更自由地修改类的内部实现而不影响外部使用。这使得在不改变外部接口的情况下进行优化、重构或修复问题变得更容易。
  4. 接口抽象: 封装通过定义明确的接口来表示对象的功能,而不必了解内部的具体实现。这种抽象性使得代码更易于理解和使用,特别是在团队开发中。
  5. 代码复用: 封装鼓励将通用的功能封装到独立的类中,这样可以在多个地方重用相同的实现逻辑,提高了代码的可维护性和可重用性。

四.包装类

1.什么是包装类

封装类(Wrapper Classes)是一组类,它们提供了一种方式将基本数据类型(如整数、字符、布尔等)包装成对象。每个基本数据类型都对应一个封装类,这些封装类提供了一些额外的功能,例如将基本数据类型转换为对象、进行类型转换、执行数学运算等

包装类对应基本数据类型表:

包装类基本数据类型
Integerint
Longlong
Floatfloat
Doubledouble
Characterchar
Booleanboolean
Bytebyte
Shortshort

2.为什么要使用引用类型

1.将基本数据类型转换为对象,例如在集合框架、泛型、反射和其他一些Java API中,这些情况需要使用对象而不是基本数据类型。

2.封装类还可以提供一些方法来处理基本数据类型的操作和转换。

3.默认值为null有时利于开发

3使用:

方法1:

Integer类的构造方法

int intValue = 42;
Integer integerObject = new Integer(intValue);

String stringValue = "123";
Integer integerObject = new Integer(stringValue);

虽然上述构造方法在较早的 Java 版本中可用,但从 Java 9 开始,推荐使用静态工厂方法 valueOf 来创建 Integer 对象,因为它会使用缓存以提高性能。

因此如下图会编译器会出现红色横线但是不会影响运行

image-20230816220427228

方法2:

Integer类内部的valueOf方法

Integer valueOf(int i)

Integer valueOf(String s) throws NumberFormatException

方法3:

自动装箱的方法:

自动装箱允许你将基本数据类型赋值给相应的封装类对象,而无需显式地进行构造

int intValue = 42;
Integer integerObject = intValue; // 自动装箱

4.例题

public static void main(String[] args){
	Integer n1 = new Integer(127);
	Integer n2 = new Integer(127);
	System.out.println(n1 == n2); //false(这里比较的是地址),n1和n2地址不相同
	System.out.println(n1.equals(n2)); //true(重写方法,这里比较的是值)
	
	Integer a1 = new Integer(128);
	Integer a2 = new Integer(128);
	System.out.println(a1 == a2);//false这里比较的是地址),n1和n2地址不相同
	System.out.println(a1.equals(a2));//true (这里比较的是值)

	Integer x1 = 128;
	Integer x2 = 128;
	System.out.println(x2 == x2); //false 
    //这里超过缓冲池的范围( -128 到 127),会新建地址,因此地址不同
	System.out.println(x1.equals(x2)); //true 依然是值的比较
	
	Integer num1 = 127;
	Integer num2 = 127;
	System.out.println(num1 == num2); //true 
    //两个值都为 127 的 Integer 对象,而且这2个值在缓冲池的范围内。因此,这两个对象会被认为是同一个对象,所以使用 == 运算符进行引用比较时会返回 
	System.out.println(num1.equals(num2)); //true

5.equals方法

默认的 equals 方法,它用于比较对象的引用是否相等。

public boolean equals(Object obj) {
    return (this == obj);
}

可以根据需要重写

五.继承

1.什么是继承

允许一个类(子类或派生类)从另一个类(父类或基类)继承属性和方法。继承使得一个类能够拥有另一个类的特性,从而实现代码重用和层次结构的构建。

2.为什么要继承

  1. 代码重用: 继承允许子类重用父类的代码,从而避免了重复编写相同的代码。
  2. 扩展功能: 子类可以在继承的基础上添加新的属性和方法,从而扩展父类的功能。
  3. 多态性: 继承和多态性一起使用,可以实现方法的动态绑定,使得同一个方法调用可以根据对象的实际类型执行不同的代码。
  4. 层次结构: 通过继承创建类的层次结构,使得类与类之间形成更具有逻辑关联的结构。

3.继承

  1. 属性(成员变量): 子类可以继承父类的非私有属性。这些属性在子类中具有相同的名称、类型和初始值。子类可以直接访问这些继承的属性。
  2. 方法: 子类可以继承父类的方法。子类可以直接调用继承的方法,也可以根据需要对其进行重写(覆盖)来定制自己的行为。
  3. 访问修饰符: 如果父类中的属性或方法具有非私有的访问修饰符(publicprotected 或默认访问级别),则子类可以继承这些属性和方法,并在子类中使用相同的访问修饰符进行访问。
  4. 构造方法: 子类会继承父类的构造方法,但子类不能直接调用父类的构造方法。子类的构造方法会调用父类的构造方法来初始化继承的属性。

需要注意的是,子类不会继承父类的私有属性和方法。私有属性和方法只能在父类内部访问,子类无法直接继承或调用。接口和抽象类: 子类可以继承父类的接口和抽象类。子类需要实现父类的抽象方法或接口定义的方法。

4.重写

子类(派生类)在继承父类(基类)的方法基础上,重新定义并实现该方法,以适应子类的特定需求。通过方法重写,子类可以定制继承的方法的行为,使其在子类中具有不同的实现。

1.重写的要求

  1. 方法名、参数列表和返回类型必须相同: 重写的方法必须与父类中的方法有相同的方法名、参数列表和返回类型。
  2. 访问修饰符: 重写的方法的访问修饰符不能比父类中的方法更严格。例如,如果父类方法是 public,那么子类中重写的方法可以是 publicprotected,但不能是 private
  3. 异常: 子类重写的方法不能抛出比父类方法更多的受检异常。但是,子类重写的方法可以抛出未受检异常。
  4. 返回类型: 重写方法的返回类型必须与父类方法的返回类型兼容。如果返回的是对象,则它们应该是同一个类或其子类。
  5. 调用父类方法: 在重写方法中,可以使用 super 关键字来调用父类的方法,以便在子类中扩展父类方法的行为

2.重写与重载的区别

1.方法签名:
  • 重写: 重写的方法必须具有与父类方法相同的方法名、参数列表(包括参数类型和参数顺序)以及返回类型。
  • 重载: 重载的方法必须具有不同的参数列表,可以是参数类型、参数个数或参数顺序的不同组合。
2.继承关系:

重写: 重写发生在父子类之间,子类可以重新定义父类的方法以适应自己的需求。

重载: 重载发生在同一个类中,用于在同一个类中定义多个具有相同方法名但不同参数列表的方法

3.实现机制:
  • 重写: 重写的方法在运行时(动态绑定时)根据对象的实际类型来决定调用哪个版本的方法,实现多态性。
  • 重载: 重载的方法在编译时根据参数的静态类型来决定调用哪个版本的方法。
4.目的和使用场景:
  • 重写: 主要用于实现多态性,通过在子类中定制方法行为,以便子类可以为继承的方法提供不同的实现。
  • 重载: 主要用于提供更灵活的方法调用方式,方便用户调用不同版本的方法。
5.访问修饰符和返回类型:
  • 重写: 子类重写的方法的访问修饰符不能比父类中的方法更严格,可以是相同或更宽松的访问修饰符。
  • 重载: 重载的方法可以具有相同或不同的访问修饰符,可以有不同的返回类型。
6.关键字:
  • 重写: 可以使用 @Override 注解来明确标识正在重写的方法,不是强制性的,但建议使用。
  • 重载: 没有特定的关键字来标识重载。
7.运行时和编译时:
  • 重写: 重写的方法在运行时动态绑定,实现动态多态性。
  • 重载: 重载的方法在编译时静态解析,实现静态多态性

六.instance of

1.语法

object instanceof Class

object 是要检查的对象,Class 是类名或接口名。如果 objectClass 的一个实例,或者实现了 Class 接口,那么表达式的值为 true,否则为 false

2.用例

  class Shape { }

class Circle extends Shape { }

class Square extends Shape { }

public class IfInstanceOfExample {
    public static void main(String[] args) {
        Shape shape1 = new Circle();
        Shape shape2 = new Square();
  if (shape1 instanceof Circle) {
        System.out.println("shape1 is a Circle");
    } else if (shape1 instanceof Square) {
        System.out.println("shape1 is a Square");
    } else if (shape1 instanceof Shape) {
        System.out.println("shape1 is a Shape");
    }

    if (shape2 instanceof Circle) {
        System.out.println("shape2 is a Circle");
    } else if (shape2 instanceof Square) {
        System.out.println("shape2 is a Square");
    } else if (shape2 instanceof Shape) {
        System.out.println("shape2 is a Shape");
    }
}
}

结果:

shape1 is a Circle
shape2 is a Square

七.final关键字

1.用法

  1. final 修饰类:

    • 如果一个类被声明为 final,则该类不能被继承。即,不能创建该类的子类。

    • 这在某些情况下用于防止类被继承或修改,或者是为了保证类的安全性和稳定性。

    • 示例:final class MyClass { ... }

    • final class Vehicle {
          // ...
      }
      
      // 无法继承 final 类
      // class Car extends Vehicle { ... }
      

      在这个示例中,Vehicle 类被声明为 final,因此不能创建 Vehicle 的子类。任何尝试继承 Vehicle 类的代码都会导致编译错误。

  2. final 修饰方法:

    • 如果一个方法被声明为 final,则该方法不能被子类重写(覆盖)。

    • 这通常用于确保方法的实现在子类中不被修改,以防止出现意外的重写。

    • 注:可以被继承

    • 示例:final void myMethod() { ... }

    • class Animal {
          final void makeSound() {
              // ...
          }
      }
      
      class Dog extends Animal {
          // 编译错误,无法重写 final 方法
          // void makeSound() { ... }
      }
      

      在这个示例中,Animal 类的 makeSound() 方法被声明为 final,因此无法在 Dog 类中重写这个方法。任何尝试重写这个方法的代码都会导致编译错误。

  3. final 修饰变量:

    • 对于变量,final 表示该变量的值一旦被初始化后,就不能再被修改,即变成了一个常量。

    • 对于基本数据类型,这意味着其值不可更改;对于引用类型,意味着不能再指向其他对象,但对象内部的状态可以被修改。

    • final 变量必须在声明时或构造方法中进行初始化。

    • 示例:final int myValue = 10;

    • class Constants {
          final int MAX_VALUE = 100;
      }
      
      public class FinalVariableExample {
          public static void main(String[] args) {
              Constants constants = new Constants();
             // 编译错误,无法修改 final 变量的值
             
              // constants.MAX_VALUE = 200;
             
              System.out.println(constants.MAX_VALUE); // 输出:100
          }
      }
      

八.关于一些例题

1.final关键字在方法参数中

public static void main(String[] args) {

    change(10);

    Student stu = new Student();

    change(stu);
    System.out.println(stu.getAge());
}

public static void change(final int num) {
    //num++;
}

public static void change(final Student stu) {
    stu.setAge(28);
}

这里方法参数定义后,num无法改变值,因此这里num++;会报错

2.final关键字在方法参数中(地址值)

public static void main(String[] args) {
    Student stu = new Student();
    change(stu);
}

public static void change(final Student stu) {
   // stu = new Student();

}

这里stu地址值无法更改因此无法编译

image-20230817001852717

3.final声明变量(方法与成员变量之间的关系)

 public static void main(String[] args) {
       final Student stu = new Student();
        change(stu);
    }

    public static void change( Student stu) {
        stu = new Student();
    }

虽然使用了 final 关键字声明了 stu 变量,但是仍然可以在方法参数 change(Student stu) 中将其传递,并在方法内部重新分配新的对象,而不会导致编译错误。

4.final修饰参数(和上一题相同)

按值传递没有接收时不会影响成员变量的值

 public static void main(String[] args) {
        final int n = 1;
        change(n);
        System.out.println(n);
    }

    public static void change(int num) {
        num++;
    }