软工计算1—Java篇1 20240513

1.Java中的函数重载

函数重载(Function Overloading)是面向对象编程中的一个概念,它允许在同一个类中定义多个同名函数,但这些函数的参数列表必须不同。参数列表的不同可以体现在参数的类型、数量或顺序上。函数重载使得程序设计更加灵活,可以针对不同的参数类型或数量提供不同的函数实现。
Java支持函数重载,下面是一个简单的例子:

public class OverloadExample {

    // 第一个版本的print方法,接受一个整数参数
    public void print(int a) {
        System.out.println("Integer: " + a);
    }

    // 第二个版本的print方法,接受一个双精度浮点数参数
    public void print(double a) {
        System.out.println("Double: " + a);
    }

    // 第三个版本的print方法,接受一个字符串参数
    public void print(String a) {
        System.out.println("String: " + a);
    }

    // 测试函数重载
    public static void main(String[] args) {
        OverloadExample example = new OverloadExample();
        example.print(10);    // 调用第一个版本的print方法
        example.print(20.5);  // 调用第二个版本的print方法
        example.print("Hello"); // 调用第三个版本的print方法
    }
}

在这个例子中,OverloadExample 类有三个名为 print 的方法,但它们接受不同类型的参数:一个接受 int 类型,一个接受 double 类型,另一个接受 String 类型。当我们在 main 方法中调用 print 方法时,编译器会根据提供的参数类型来决定调用哪个版本的 print 方法。

函数重载是编译时多态的一个例子,也称为静态多态或前绑定,因为在编译时就已经确定了调用哪个函数。与之相对的是运行时多态(动态多态或后绑定),它主要通过方法的动态绑定实现,常见于方法的继承和接口实现中。

2.java中的接口

在Java中,接口(Interface)是一种定义行为的方式,它描述了一组方法,但并不提供这些方法的具体实现。你可以将接口想象成一个合同或者协议,规定了实现这个接口的类必须遵循什么样的方法规则,但具体怎么去做,就留给实现这个接口的类自己去决定了。

简单例子

假设我们要设计一个“动物”世界,在这个世界里,动物可以有不同的行为,比如“叫”和“移动”。我们不关心每种动物是如何叫的或者如何移动的,我们只关心它们都必须具备“叫”和“移动”的能力。这时,就可以使用接口来定义这种行为规范。

// 定义一个接口 AnimalBehavior,规定动物需要有的行为
public interface AnimalBehavior {
    void makeSound(); // 定义叫的方法,但不提供实现
    void move();      // 定义移动的方法,也不提供实现
}

// 然后,我们定义具体的动物类,比如狗和鸟,让它们实现 AnimalBehavior 接口
public class Dog implements AnimalBehavior {
    @Override
    public void makeSound() {
        System.out.println("汪汪汪"); // 狗的叫声
    }

    @Override
    public void move() {
        System.out.println("狗在跑"); // 狗的移动方式
    }
}

public class Bird implements AnimalBehavior {
    @Override
    public void makeSound() {
        System.out.println("啾啾啾"); // 鸟的叫声
    }

    @Override
    public void move() {
        System.out.println("鸟在飞"); // 鸟的移动方式
    }
}

在这个例子中,AnimalBehavior接口就是一份协议,它声明了所有实现它的类都必须有makeSoundmove两个方法。DogBird类实现了这个接口,意味着它们承诺会提供这两个方法的具体实现。当我们创建DogBird的对象并调用这些方法时,就能看到不同的动物以它们特有的方式叫和移动。

接口的好处

  1. 多态性:通过接口,我们可以编写与具体实现分离的代码,使得程序更加灵活和可扩展。比如,我们可以编写一个函数,接收任何实现了AnimalBehavior接口的对象,而不用关心它是狗、鸟还是其他什么动物。
  2. 代码组织:接口帮助我们更好地组织代码,将行为的定义与实现分离开来,使得代码结构更清晰。
  3. 实现复用:多个类可以实现同一个接口,这样就可以在不同的类中重用相同的行为定义,同时也便于后期的维护和扩展。

总之,接口是Java中一种强大的抽象机制,它帮助我们设计出更加灵活、可扩展的系统。

3.java中的类和包(面向对象入门)

在Java中,类(Class) 是用来定义对象的蓝图,它描述了一组属性(数据成员)和方法(成员函数),用于描述具有相同特征和行为的一组对象。简单来说,类就像是制作具体物品的模具,而对象则是根据这个模具生产出来的一个个具体的物品。

简单例子

想象一下,我们要编写一个程序来模拟图书馆中的书籍管理。我们可以定义一个名为 Book 的类来描述书籍的共同特征和行为。

public class Book {
    String title;      // 书名
    String author;     // 作者
    int publicationYear; // 出版年份
    
    // 构造方法,用于创建Book对象时初始化其属性
    public Book(String title, String author, int publicationYear) {
        this.title = title;
        this.author = author;
        this.publicationYear = publicationYear;
    }
    
    // 方法:获取书籍信息
    public String getBookInfo() {
        return "书名: " + title + ", 作者: " + author + ", 出版年份: " + publicationYear;
    }
}

在这个例子中,Book 类定义了书籍的三个属性(titleauthorpublicationYear)以及两个行为(构造方法和getBookInfo方法)。我们可以根据这个类创建具体的书籍对象,比如《Java编程思想》这本书的实例。

包(Package)

包是Java中用于组织类和其他Java源文件的一种机制。它类似于文件系统的目录结构,有助于大型项目中类的管理和重用,同时避免命名冲突。

简单例子

继续上面的图书管理系统,随着系统规模的扩大,我们可能会定义很多类,比如Author(作者)、Library(图书馆)、Borrower(借阅者)等。为了更好地组织这些类,我们可以使用包。

例如,将上述的Book类放在一个名为com.library的包中,代码如下:

package com.library; // 这行声明了该类属于哪个包

public class Book {
    // ... 类的原有代码不变
}

通过这种方式,不同功能模块的类可以被分类到不同的包中,不仅使项目结构清晰,也方便了类的导入和使用。在其他类中要使用Book类时,就需要使用导入语句(如import com.library.Book;)来引入这个类。

总结来说,类是Java面向对象编程的基本单位,用于定义对象的结构和行为;而包则是一种组织类的方式,帮助我们更好地管理项目中的类和资源,促进代码的重用和维护。

4.java中的泛型与类型擦除

Java中的泛型(Generics) 是一种强大而灵活的工具,它允许你在编写代码时使用类型参数,从而让代码可以应用于多种数据类型,同时还能确保类型安全,避免运行时的类型转换错误。简单来说,泛型就是让代码变得更加通用和安全的一种方式。

简单例子

想象你正在设计一个简单的盒子(Box)类,用来存放任何类型的物品。没有泛型的情况下,你可能会这样写:

public class Box {
    Object item; // 使用Object类型来容纳任何类型的对象

    public void setItem(Object item) {
        this.item = item;
    }

    public Object getItem() {
        return item;
    }
}

这个Box类可以存放任何类型的对象,但是当你从盒子里取出物品时,由于getItem()方法返回的是Object类型,你需要强制类型转换才能得到实际的类型,这既不方便也不安全。

引入泛型

使用泛型改造后的Box类如下:

public class Box<T> {
    private T item; // T是一个类型参数,代表Box可以存储的任意类型

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

这里的T是一个类型参数(通常是一个大写字母表示),它代表一个未知的类型。当你创建Box对象时,可以指定这个T代表的具体类型:

Box<String> stringBox = new Box<>();
stringBox.setItem("Hello World");
String content = stringBox.getItem(); // 直接得到String类型,无需类型转换

Box<Integer> integerBox = new Box<>();
integerBox.setItem(123);
Integer number = integerBox.getItem(); // 同样直接得到Integer类型

通过泛型,你不需要进行类型转换,编译器会帮你检查类型安全,确保放入和取出的类型一致,提高了代码的可读性和安全性。泛型不仅可以用于类,还可以用于接口、方法等,使得Java的集合框架(如List、Map等)更加灵活和强大。

Java中的类型擦除(Type Erasure) 是指在编译阶段,Java编译器会移除掉泛型类型信息,将所有的泛型参数替换为它们的限定类型或者Object(如果没有限定类型的话)。换句话说,尽管我们在编写代码时使用了泛型来增加类型安全和代码的清晰度,但在字节码层面,这些类型信息是不复存在的。这一过程确保了泛型可以在Java早期版本(不支持泛型的Java 1.4及以前)的代码中向后兼容。

简单例子

考虑一个简单的泛型类Pair,它可以持有两个相同类型的数据:

public class Pair<T> {
    private T first;
    private T second;

    public Pair(T first, T second) {
        this.first = first;
        this.second = second;
    }

    public T getFirst() {
        return first;
    }

    public T getSecond() {
        return second;
    }
}

当你创建一个Pair<String>实例时,看起来像是这样的:

Pair<String> pair = new Pair<>("Hello", "World");

然而,在编译之后,所有的T都被擦除,变成了原始类型(即未指定泛型参数时的类型),上述代码实际上等同于:

Pair pair = new Pair("Hello", "World");

类型擦除的影响

  1. 无法直接获取泛型类型信息:在运行时,你不能直接通过反射获取到Pair<String>中的String类型信息,因为这个信息已经被擦除了。

  2. 类型转换的必要性:虽然在使用泛型时编译器会检查类型安全,但在某些情况下,例如从一个非泛型上下文中访问泛型对象时,可能需要进行类型转换。

  3. 桥接方法:为了保持多态性,编译器有时会生成桥接方法(bridge methods)。例如,如果你有一个泛型方法的重载,编译器会生成额外的方法来确保在类型擦除后,正确的重载方法仍能被正确调用。

总结

类型擦除是Java泛型实现的一个重要特性,它保证了泛型的兼容性,但也带来了一些限制,比如运行时类型信息的缺失。开发者在使用泛型时,应理解这一机制,合理设计代码,以充分利用泛型带来的类型安全优势,同时规避潜在的问题。

posted @   陆舟LandBoat  阅读(13)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示