C++转Java日记(06)

今天算是早上晚上加班加点学爽了,希望能赶紧速通了吧,找个实习比啥都强。

继承、方法重写

extends表示继承,Java的类只能直接继承一个父类。

super(...)调用父类构造方法。this(...)调用本类另一个构造方法。

子类可以重写父类的方法,@override是给编译器看的标记,注意与重载的区别。

重写是子类重新实现父类中同签名的实例方法,重载是方法名相同但参数列表不同。

实例

java 复制代码
public class Main {
    public static void main(String[] args) {
        Student s=new Student("Alice",90);
        System.out.println(s.getName());
        s.introduce();
    }
}
class Person{
    private String name;
    Person(String name){
        this.name=name;
    }
    public String getName(){
        return name;
    }
    public void introduce(){
        System.out.println("My name is "+name);
    }
}
class Student extends Person{
    private int score;
    Student(String name,int score){
        super(name);
        this.score=score;
    }
    /* @Override
    public void introduce(){
        System.out.println("My name is "+getName()+" and my score is "+score);
    }
    @Override */
    public void introduce(){
        super.introduce();
        System.out.println("My score is "+score);
    }
}

多态

Person p = new Student("Alice",90);

p.introduce();

先看以上例子,这段代码的introduce执行父类还是子类?

答案是子类,因为要区分变量声明的类型和实际对象的类型。

Person p表示声明类型是Person,决定p能够调用哪些方法。new Student()表示实际对象是Student,决定被重写的实例方法执行哪个版本。

可以用Person类型的变量引用学生对象,这样不会把学生对象转换成一个普通人对象,也不会复制或者丢掉它的成绩。实际对象仍然是Student,只是你通过Person类的引用来引用它。

反过来则不能直接写:

Student s = new Person("Alice") //编译错误

同一个调用可以执行不同的实现,例如:

java 复制代码
public class Main {
    public static void main(String[] args) {
        Person p1=new Student("小明");
        Person p2=new Teacher("王老师");
        p1.introduce();
        p2.introduce();
    }
}
class Person{
    private String name;
    Person(String name){
        this.name=name;
    }
    public String getName(){
        return name;
    }
    public void introduce(){
        System.out.println("My name is "+name);
    }
}
class Student extends Person{
    Student(String name){
        super(name);
    }
    public void introduce(){
        System.out.println("我是学生:"+getName());
    }
    public void study(){
        System.out.println("我要学习");
    }
}
class Teacher extends Person{
    Teacher(String name){
        super(name);
    }
    @Override 
    public void introduce(){
        System.out.println("我是老师:"+getName());
    }
}

p1.study()不能成功调用。

因为编译器根据变量声明类型Person检查调用,Person声明了introduce,允许调用;没声明Study,不允许调用。因此要分两步看,编译时:根据声明类型检测这个方法能不能调用;运行时:对于重写的实例方法,根据实际对象选择执行哪个实现。

多态的用途

例如我们只写一个统一的介绍方法

static void showPerson(Person p){

p.introduce();

}

这一句话应该写在main()方法外面,和main()并列。

java 复制代码
import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<Person> people = new ArrayList<>();
        people.add(new Student("小明"));
        people.add(new Teacher("老王"));
        for(Person person:people){
            person.introduce();
        }
    }
    static void showPerson(Person p){
        p.introduce();
    }
}
class Person{
    private String name;
    Person(String name){
        this.name=name;
    }
    public String getName(){
        return name;
    }
    public void introduce(){
        System.out.println("My name is "+name);
    }
}
class Student extends Person{
    Student(String name){
        super(name);
    }
    public void introduce(){
        System.out.println("我是学生:"+getName());
    }
    public void study(){
        System.out.println("我要学习");
    }
}
class Teacher extends Person{
    Teacher(String name){
        super(name);
    }
    @Override 
    public void introduce(){
        System.out.println("我是老师:"+getName());
    }
}

练习

Person p = new Student("小明");

p.introduce(); // ① 执行 Student 重写的 introduce()

p.study(); // ② 编译报错:Person 类型中没有 study()

Student s = new Student("小红");

s.study(); // ③ 正常执行 Student 的 study()

向下转型

从父类类型转成子类类型,叫向下转型。

Person p = new Student("小明");

p.study();//错误

Student s = (Student) p;

s.study();//正常执行

注意:没有创建新对象,也没有改变原对象。s和p仍然引用同一个学生对象,只是变量的声明类型不同。System.out.println(p==s)的输出是true。

不过转型必须符合对象的真实类型。

Person p = new Teacher("王老师");

Student s = (Student) p;

这段话能够通过编译但是运行时会抛出ClassCastException,因为类型转换不能把老师变成学生。

所以需要引入一个词instanceof(instance 名词 例子、实例)。

java 复制代码
        Person p = new Student("小明");
        if(p instanceof Student){
            Student s = (Student) p;
            s.study();
        }

可以直接写成

java 复制代码
        Person p = new Student("小明");
        if(p instanceof Student s){
            s.study();
        }

这个叫做instanceof模式匹配,检查成功后,直接提供一个Student类型的变量s,省去手动转型。

所以分清来年规划总需求,调用父类已经声明、子类重写了的方法,如introduce(),直接调用,让多态选择实现即可;调用子类才有的方法,如study(),先检查类型,再向下转型。

接口

接口:规定对象能做什么,实现类提供具体做法。

class Student extends Person 表示学生是一种人,也就是人的扩展。

class Student implements Learnable 表示学生承诺提供Learnable接口要求的方法。

java 复制代码
public class Main{
    public static void main(String[] args){
        Learnable a = new Student();
        Learnable b = new Robot();
        startLearning(a);
        startLearning(b);
    }
    static void startLearning(Learnable obj){
        obj.learn();
    }
}
interface Learnable{
    void learn();
}
class Student implements Learnable{
    @Override 
    public void learn(){
        System.out.println("学生通过看书学习");
    }
}
class Robot implements Learnable{
    @Override 
    public void learn(){
        System.out.println("机器人通过数据训练学习");
    }
}

接口里的void learn();是什么?

是实现这个接口的具体类,必须提供一个无参数、无返回值的learn()方法,这种接口方法默认就是public abstract,所以这里等价于public abstract void learn();实现时也必须写成public。而如果一个普通具体类写了class xxx implements Learnable却没有实现learn()就会编译报错。

implements就是明确声明实现关系,如果只写class Student漏了implements Learnable,那么在

Learnable obj = new Student();

会出现报错。因为Student没有声明实现Learnable。

最后可以对比一下Learnable obj=new Student();和Person p = new Student("小明"),它们两个高度相似,声明类型规定能调用的方法,实际对象决定执行哪个实现。

但是------接口本身不能直接创建对象,比如new Learnable();就是错误的,要创建具体实现类。

继承与实现也可以同时使用,学生既可以继承Person也可以实现Learnable。例子如下。

java 复制代码
public class Main{
    public static void main(String[] args){
        Learnable a = new Student("孩子们");
        Learnable b = new Robot();
        startLearning(a);
        startLearning(b);
    }
    static void startLearning(Learnable obj){
        obj.learn();
    }
}
interface Learnable{
    void learn();
}
class Person{
    private String name;
    Person(String name){
        this.name=name;
    }
    public String getName(){
        return name;
    }
}
class Student extends Person implements Learnable{
    Student(String name){
        super(name);
    }
    @Override 
    public void learn(){
        System.out.println(getName()+"正在学习");
    }
}
class Robot implements Learnable{
    @Override 
    public void learn(){
        System.out.println("机器人通过数据训练学习");
    }
}

泛型

先看不使用泛型的类

java 复制代码
class Box {
    private String value;

    public void set(String value) {
        this.value = value;
    }

    public String get() {
        return value;
    }
}

它不能直接存学生对象或整数。如果每种类型都写一个盒子类,就会重复很多代码。可以把固定的String换成一个类型参数 T。

java 复制代码
class Box<T> {
    private T value;
    public void set(T value){
        this.value = value;
    }
    public T get(){
        return value;
    }
}

这里的T代表使用这个类时指定的类型。它不是字符串,也不是变量值,而是类型的占位符。

java 复制代码
public class Main{
    public static void main(String[] args){
        Box<String> textBox = new Box<>();
        textBox.set("hello");
        String text = textBox.get();

        Box<Integer> numberBox = new Box<>();
        numberBox.set(123);
        int number=numberBox.get();

        System.out.println(text);
        System.out.println(number);
    }
}
class Box<T> {
    private T value;
    public void set(T value){
        this.value = value;
    }
    public T get(){
        return value;
    }
}

不直接用Object作为父类存储所有东西的原因:取出时,返回类型只是Object,需要自己手动向下转型,而且容易出错。那么泛型就可以把这一类错误提前到编译阶段。

Box<String> box = new Box();

box.set(123); //直接编译报错

只要内部保持一致即可。常用命名习惯是:

名称 常见含义
T Type,某种类型
E Element,元素类型
K Key,键类型
V Value,值类型

用包装类的原因:Java泛型的类型参数必须是引用类型,所以使用时可以自动装箱拆箱。

练习

java 复制代码
Box<String> a = new Box<>();
a.set("Java");

Box<String> b = a;
b.set("Python");

System.out.println(a.get());

逐步看:

执行语句 发生什么
new Box<>() 创建一个盒子
a.set("Java") 把盒子的 value 设为 "Java"
b = a b 和 a 引用同一个盒子,没有创建新盒子
b.set("Python") 把这个共同盒子的 value 改为 "Python"
a.get() 从同一个盒子取出 value,得到 "Python"

这不违反字符串不可变的规则:我们没有把 "Java" 字符串对象改成 "Python",而是修改了盒子的 value 字段,让它引用另一个字符串。

现在学着越来越恶心我总结了一下我的问题:哦那我一直都理解错了,我一直纠结的他妈的这个叫包装类?

对,你一直想弄清楚的是 包装类------为什么有了 int,还要有 Integer。我之前把话题带到泛型类怎么定义上了,没对准你的疑问,抱歉。

这两个概念分开记:

  • 包装类 :`Integer` 把整数值表示为对象,和基本类型 int 对应。
  • 泛型 :`ArrayList<T>` 中的类型参数机制,使用时可以指定 String、`Integer`、`Student` 等类型。

它们在这句里碰到一起:

复制代码
ArrayList<Integer> nums = new ArrayList<>();

列表通过泛型指定元素类型;因为不能填基本类型 int,所以填包装类 Integer。

接下来应该把包装类、装箱和拆箱讲透,再继续其他内容。

包装类、装箱与拆箱

以下内容用int与Integer为例。

包装类:用对象和表示基本类型的值

int a=10;

Integer b = Integer.valueOf(10);

Integer.valueOf(10)是Integer类提供的静态方法,返回一个表示该数值的对象,可能复用已有对象。

装箱与拆箱:值和包装对象之间转换。

装箱是把int转成Integer,拆箱则是Integer取出int。

int a=10;

Integer b = Integer.valueOf(a);

Java支持自动装箱,所以可以简写Integer b=a;也可以自动拆箱int c=b。

Integer对象的数值不可修改,这点和String类似。

Integer用==比较容易出问题,因为Java缓存并复用一部分整数包装对象,不能据此认为这两个Integer的==比较的是数值,需要用a.equals(b)。

两个 Integer 之间的 == 比较对象身份;比较整数值,使用 equals(),或者明确拆箱后比较。

System.out.println(a.intValue() == b.intValue()); // true

如果其中一边本来就是 int,则会拆箱后比较:

Integer a = 1000;

int b = 1000;

System.out.println(a == b); // true:a 拆箱后比较数值

最后是null。

Integer a=null;

但这些操作需要拆箱,因此会抛出 NullPointerException:

复制代码
int b = a;
a++;
System.out.println(a + 1);

例题

java 复制代码
int x = 10;
Integer a = x;
Integer b = a;

x = 20;
b++;

System.out.println(x);
System.out.println(a);
System.out.println(b);

这次正确结果是 20、10、11。

复制代码
int x = 10;
Integer a = x;

这里把 x 当时的值 10 装箱,让 a 引用表示 10 的对象。a 不会和变量 x 建立持续关联。

复制代码
Integer b = a;

此时 a、`b` 引用同一个表示 10 的对象。

复制代码
x = 20;

只修改变量 x,不会影响 a、`b`。

复制代码
b++;

取出 b 的数值 10,加一,再让 b 引用表示 11 的对象。`a` 仍然表示 10。

相关推荐
金銀銅鐵5 小时前
带您看懂字节码指令(一):一帧里有什么
java·后端·ai编程
nice_lcj5207 小时前
【从零理解 Spring AI · 14】RAG 的四个环节:加载、切分、向量化、检索
java·spring boot·embedding·向量数据库·rag·spring ai·ai应用开发
SomeOtherTime7 小时前
Rust所有权(AI回答)
开发语言·后端·rust
Easyhood7 小时前
Android 开发入门实践(十四):Compose 副作用入门与四个常用 API
android·开发语言·kotlin·android jetpack·compose
白杨尚青8 小时前
C++进阶篇(六):map 和 set 的模拟实现——用红黑树封装 mymap/myset(迭代器 + operator[])
开发语言·c++·迭代器·stl·set·map·红黑树
学长毕设8 小时前
基于springboot科创竞赛信息平台-附源码
java·spring boot·后端
聪明的笨猪猪9 小时前
Java 工程案例:AI 生成 @Async 异步线程池踩坑
java·经验分享·面试·ai编程
学长毕设9 小时前
基于SpringBoot公务员考试交流平台-附源码
java·spring boot·后端
SL_staff11 小时前
私有化低代码:面向强监管行业的可控数字化实践
java·低代码