Java 学习 - Day05

IDE

集成开发环境(编写 + 编译 + 运行),常用:IDEA、Eclipse,下载链接如下:

下载 IntelliJ IDEA – 领先的 Java 和 Kotlin IDE (jetbrains.com)

Eclipse 下载 |Eclipse 基金会

IDEA 设置中文方式:

一种逻辑上的代码组织方式,作用如下:

  1. 逻辑分组:将相关类或接口组织在一起,便于查找和管理
  2. 命名空间:避免类名冲突,不同包下允许同名类
  3. 访问控制:比如default 访问修饰符表示只能在同包下访问
  4. 导入声明:要使用其他包中的类时,需要使用import 关键字进行导入

尽管包不是物理上的项目结构,但在实际的项目文件系统中,通常会根据包名来组织类文件(包名即文件名)

包的命名规则如下:

  1. 只能包含数字、字母下划线和小圆点
  2. 不能使用数字开头
  3. 不能使用关键字/保留字
  4. 一般为域名反写 + 项目名 + 模块名

Java 中常被使用的包如下:

  • java.lang.*:基本包,默认引入
  • java.util.*:工具包
  • java.net.*:网络包
  • java.awt.*:图形界面包

访问修饰符

Java 提供了四种访问修饰符来控制方法或属性的访问范围

  1. public:公开
  2. protected:同包和子类公开
  3. default:同包公开
  4. private:本类访问,不公开

修饰符用于修饰类、方法和属性,但类只能使用public 和default 修饰

封装 - private

封装是对 对象内部实现机制的隐藏,只暴露出对外访问的接口,用户只需与其进行互动即可完成需求。封装的优点如下:

  1. 隐藏数据:类的内部状态通常是隐藏的,只能通过类提供的方法才能进行访问。这有助于保护数据的完整性,避免外部代码直接修改对象的状态
  2. 提供接口:通过公共方法对外提供接口,隐藏方法内部的实现细节
  3. 访问控制:配合访问修饰符可以对类成员访问的限制
  4. 安全性:通过封装可以实现对访问数据的控制,更容易地实现数据验证和其他安全措施
  5. 模块化:有助于建立更模块化的代码,使得单个组件可以独立于其他部分进行开发和测试
public class Test {
    // 私有化属性,避免外部代码直接访问
    private int age;
    private String name;

    // 提供公开的方法共外界与属性进行交互
    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;
    }
}

继承 - extends

在面向对象编程中,继承是一种让一个类(子类)具备另一个类(父类)属性和行为的机制。通过继承,可以实现父类代码的复用,并且使得代码更容易维护和扩展

子类虽然继承了父类的所有属性和行为,但是对于父类中私有的成员无法在子类中直接访问,需要通过父类提供的方法来完成访问

子类基于父类创建,因此在创建子类对象时,在子类构造器中会默认调用其父类的无参构造器。如果父类中没有无参构造器,则需要super 指定使用父类的有参构造器来完成对父类的初始化工作

如之前的this 指向当前对象,super 则是指向其父类对象。并且调用父类无参构造器的动作,即super() 会在构造器的第一行,而这和this 在构造器中的使用一致。正因如此,this 和super 在构造器中无法同时出现

使用super 的好处之一就是明确分工,子类和父类都通过各自的构造器来完成初始化;之二就是应对重名,当然在不重名的情况下,无论是使用super 还是this 都与直接访问类没有差别

Java 中所有的类都有一个共同的父类,即Object(祖宗类)。而一个类最多只能有一个父类(单继承),但是可以间接继承

需要注意,继承不能滥用,使用之前必须保证子类和父类之间是is - a 的关系,比如灰太狼是狼

创建子类对象流程的内存分析:

  1. 加载类信息:将当前类及其父类的类信息加载到方法区
  2. 分配内存:在堆中为对象分配内存,包括子类声明的实例变量和从父类中继承到的(除了私有成员变量)
  3. 初始化父类:子类构造器执行前,先调用父类构造器来初始化父类部分(隐式调用super() )
  4. 初始化子类:执行子类构造器,完成子类初始化
  5. 引用赋值:将对象实例的内存地址赋给引用变量

Override

即重写、也可以说是方法覆盖,也就是子类中存在除方法体外与父类完全相同的方法,就可以看作是子类重写了父类中的该方法

除了方法签名、返回值类型必须一致外,还需要保证子类重写方法的访问权限不能低于父类

与方法重载相同,重写也是对方法名的复用。它可以使子类以不同于父类的方式处理业务

属性不能被重写,如果在子类中定义了与父类相同的属性,则称其隐藏了父类中的对应属性

多态

多态是面向对象编程的一个核心概念,它允许同样的代码应用于不同类型的对象,以产生不同的行为。多态建立在封装和继承基础上(三大特征),本质上就是父类应用指向子类对象

多态可以分为以下两种:

  1. 编译时多态:通常通过方法重载(overloading)来实现,它允许在同一个类中定义多个同名方法,但要求这些方法具有不同的参数列表。选择哪个方法是在编译时决定的,基于调用时传递的参数类型和数量
  2. 运行时多态:最常见的一种多态形式,它主要通过继承和方法重写(覆盖)来实现。当子类继承自父类,并且重写了父类的方法时,就可以通过父类引用变量来调用子类的方法。具体调用哪个方法取决于运行时对象的实际类型,而不是引用变量的类型。这种特性使得程序可以根据实际对象的类型表现出不同的行为。
class Animal {
    public void makeSound() {
        System.out.println("Some generic sound");
    }
}

class Dog extends Animal {
    // 对Aninal 类方法的重写
    public void makeSound() {
        System.out.println("Woof!");
    }
}

class Cat extends Animal {
    // // 对Aninal 类方法的重写
    public void makeSound() {
        System.out.println("Meow!");
    }
}

public class Main {
    public static void main(String[] args) {
        // Animal 是父类引用 -> 指向Dog 这个子类对象
        // 实际运行时执行哪个方法取决于等号右边的对象类型,即运行时多态
        Animal myAnimal = new Dog();
        myAnimal.makeSound(); // 输出 "Woof!"
        
        Animal myOtherAnimal = new Cat();
        myOtherAnimal.makeSound(); // 输出 "Meow!"
    }
}

上下转型

向上转型(子类用父类)需要遵循以下规则:

  1. 可以调用父类中除私有的成员
  2. 不能调用子类特有成员
  3. 具体可以调用的属性或行为由编译类型决定
class Animal {
    private int age;
    char sex;

    public void makeSound() {
        System.out.println("Some generic sound");
    }
}

class Dog extends Animal {
    private String name;    

    public void makeSound() {
        System.out.println("Woof!");
    }
}

public class Test{
    public static void main(String[] args) {
        // Dog 向上转型为Animal
        Animal myAnimal = new Dog();

        // 无法访问父类中私有的age,以及子类中特有的name
        System.out.println("Dog sex is " + myAnimal.sex);
    }
}

向下转型(父类使用子类)需要遵循以下规则:

  1. 父类引用必须指向当前类型的对象
  2. 只能转父类引用而不能转对象
  3. 可以调用子类的所有成员
class Animal {
    private int age;
    char sex;

    public void makeSound() {
        System.out.println("Some generic sound");
    }
}

class Dog extends Animal {
    private String name;

    public void makeSound() {
        System.out.println("Woof!");
    }
}

public class Test {
    public static void main(String[] args) {
        // 注掉这一行就会报错,因为父类引用必须是当前类型对象,即Dog
        Animal myAnimal = new Dog();

        // Animal 向下转型为Dog
        Dog dog = (Dog) myAnimal;
        System.out.println(dog.sex);
    }
}

区分向上或向下转型的方式很简单:在继承树中,父类在上、子类在下,父转子 -> 向下转型;子转父 -> 向上转型

之前提到属性不能被重写,具体的属性值取决于其编译类型

instanceof

比较操作符,用于判断左侧对象的类型是否为右侧类型或其子类,并且判断的是运行时类型

class Animal {
    private int age;
    char sex;

    public void makeSound() {
        System.out.println("Some generic sound");
    }
}

class Dog extends Animal {
    private String name;

    public void makeSound() {
        System.out.println("Woof!");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal a = new Dog();
        System.out.println(a instanceof Animal); // true
        System.out.println(a instanceof Dog); // true
    }
}

动态绑定

动态绑定也是面向对象编程中的一个关键概念。它是指在程序运行过程中决定调用哪个方法的过程,而不是在编译阶段就确定下来,这通常与继承和方法重写相关。具体规则如下:

  1. 当调用对象方法的时候,该方法回合运行类型对应对象的内存地址进行绑定
  2. 当调用对象属性的时候,没有动态绑定机制,遵循就近原则
class Animal {
    public String name = "Generic Animal";

    public void makeSound() {
        System.out.println(name + ": Some generic sound");
        eat();
    }

    public void eat() {
        System.out.println("Eating ome generic food");
    }
}

class Dog extends Animal {
    public String name = "Dog";

    public void eat() {
        System.out.println(name + ": Eating dog food");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal a = new Dog();
        a.makeSound(); // Generic Animal: Some generic sound
                       // Dog: Eating dog food
    }
}

多态数组

即数组中元素为数组类型的子类,比如Animal[] arr = {cat, dog, bird...}

此时其编译类型为Animal,无法直接访问具体属性的属性或行为(Animal 有就访问Animal 的,没有就编译错误)。所以就需要向下转型,而为了保证类型转换正确,在转换之前需要使用instanceof 比较符进行类型判断

class Animal {
    public String name = "Generic Animal";

    public void makeSound() {
        System.out.println(name + ": Some generic sound");
    }
}

class Dog extends Animal {
    public String name = "Dog";

    public void eat() {
        System.out.println(name + ": Eating dog food");
    }
}

class Cat extends Animal {
    public String name = "Cat";
    public void eat() {
        System.out.println(name + ": Eating cat food");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal[] animals = { new Dog(), new Cat()};
        for (int i = 0; i < animals.length; i++) {
            if (animals[i] instanceof Dog) {
                ((Dog) animals[i]).eat(); // Dog: Eating dog food
            } else if (animals[i] instanceof Cat) {
                ((Cat) animals[i]).eat(); // Cat: Eating cat food
            } else {
                System.out.println("类型有误");
            }
        }
    }
}

多态参数

与多态数组类似,实参类型允许为形参的子类型

class Animal {
    public String name = "Generic Animal";

    public String getName() {
        return name;
    }
}

class Cat extends Animal {
    public String name = "Cat";

    public String getName() {
        return name;
    }
}

public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        test.walking(new Cat()); // Cat is walking
        test.walking(new Animal()); // Generic Animal is walking
    }

    public void walking(Animal animal) {
        System.out.println(animal.getName() + " is walking");
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值