JavaSE复习

面向对象


  • 面向对象编程(Object - Oriented Programming / OOP)
  • 面向对象编程的本质:以类的方式组织代码,以对象的方式封装数据
  • 三大特性:封装、继承、多态

从认识论角度考虑是先有对象后有类。对象是具体的事物。类是抽象的。

从代码的角度来考虑是现有类再有对象,类是对象的模板。

面向过程 & 面向对象


  • 面像过程思想
    • 步骤清晰简单,第一步做什么,第二步做什么。。。
    • 面向过程适合处理一些较为简单的问题
  • 面向对象思想
    • 以 分类 的思维模式解决问题,先思考解决问题需要哪些分类,然后进行单独思考。最后才对某个分类下的细节进行面向过程的思考。
    • 面向对象适合处理复杂的问题,适合处理需要多人协作的问题

对于描述复杂的事物,从宏观、整体上合理分析,需要用到面向对象的思考方式来分析整个系统,但是具体到细节上,任然需要用到面向过程的思考方式来处理

类与对象


  • 类是一种抽象的东西,是定义某一类东西的东西,但并不代表某一个具体的事物
    • 例如:家具,食物,餐具......
    • 类更像是制造一个对象的图纸
  • 对象是类的具体实例
    • 例如:椅子,炸鸡,筷子......
    • 对象是一个具体的实例,有自己的特点,不是某一个种类,单独的一个个体,不是抽象的

创建与初始化对象


  • 类中的构造器\构造方法,构造器在创建对象时会调用。

    • 类中只有,属性方法
    • 特点一:必须和类的名字相同
    • 特点二:一定,必须没有返回值,但也不能用void。
    public class Student { // 属性 String name; // 未赋值默认为null int age; // 未赋值默认为0 //方法 public void study(){ System.out.println(this.name + "正在干饭......"); // this代表当前这个类 } }

对象

  • 使用 new关键字创建对象

    • 使用 new关键字创建对象的时候,会给这个对象分配空间
    • 创建好的对象会进行默认初始化,以及对类中构造器的调用
    • 实例化一个对象的过程就像,类是玩具的图纸,通过这个图纸(类)拼出来一个玩具(对象)
    public class Application { public static void main(String[] args) { // 实例化一个之前定义Student类的一个对象 Student xiaoming = new Student(); // xiaoming这个对象就是一个Student类具体的实例 xiaoming.name = "小明"; // 给xiaoming的name属性赋值 System.out.println(xiaoming.name); System.out.println(xiaoming.age); } }

构造器

什么是构造器?

构造器通常也叫构造方法、构造函数、缺省构造器,构造器在new对象的时候会被调用,可以完成对象的创建同时给变量赋值

使用new关键字,本质是在调用构造器

语法


public 类名 (参数列表,可以没有参数) { // 不能有return,返回值为空 }
  • 必须和类的名字相同
  • 一定,必须没有返回值,但也不能用void
  • 当一个类没有构造方法时,系统默认提供无参构造

例:

  1. 创建一个空的Person
public class Person { } // 里面什么都不定义
  1. 发现能实例化出来这个Person
public class Application { public static void main(String[] args) { Person person = new Person(); } }
  1. 说明 Person类里存在一个空的构造器(无参构造器)
public class Person { public Person() { // 空的构造器 } }
  1. 结论:一个类里面即使什么都不写,也会存在一个方法(无参构造器)

显示的定义一个无参构造器


public class Person { public Person() { // 无参构造器 } }
  • 无参构造器能实例化初始值

    public class Person { String name; public Person() { // 实例化name的初始值 this.name = "小明"; } }
    public class Application { public static void main(String[] args) { // 实例化一个类 Person person = new Person(); // 输出person类的name属性 System.out.printf("name的值为:" + person.name); } }

    结果:

    name的值为:小明

有参构造器


  • 一旦定义有参构造器就一定得显示的定义一个无参构造器
public class Person { String name; // 无参构造 public Person() { } // 有参构造 public Person(String name) { this.name = name; } }
  • 在使用Person这个类创建对象时会出现方法重载

    public class Application { public static void main(String[] args) { // 实例化对象的时候传入值 Person person = new Person("小红"); System.out.printf("值为:" + person.name); } }

    结果:w

    name的值为:小红

    因为使用这个类时有值传入,所以程序就往有参构造那走了

封装


在面向对象程式设计方法中,封装(Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。

也就是隐藏内部的实现细节,外部不能查看,通过提供对外的方法来调用与修改。

封装能让代码更易于理解与维护,同时也增强了代码的安全性。

  • 封装(数据的隐藏)
    • 程序设计追求 “高内聚,低耦合
    • 高内聚:类的内部数据操作细节自己完成,不允许外部干涉
    • 低耦合:仅暴露少量的方法给外部使用
  • 简单来说就两个东西: getset
  • 所有实例相关的东西都是用引用.来访问
  • 所有静态相关的东西都是用类名.来访问

实现封装


  • 通过 private 关键字设置为私有属性,再设置公共的 getset 方法方便外部取得、设置name的值。
public class Student { // 私有的名字变量 private String name; // 取得名字的值 public String getName() { return this.name; } // 设置名字的值 public void setName(String name) { this.name = name; } }
  • 封装还可以进行判断输入值是否合法,或者更多骚操作。

  • 实例:

    public class Student { // 私有的名字变量 private String name; // 私有的性别变量 private char sex; // 取得名字的值 public String getName() { return this.name; } // 设置名字的值 public void setName(String name) { this.name = name; } // 取得sex public char getSex() { return sex; } // 设置sex过程中加入判断,防止输入不合法的值 public void setSex(char sex) { if (sex == '男' || sex == '女') { this.sex = sex; }else { System.out.println("输入不合法!"); } } }

    调用:

    import com.wnaoii.oop.Demo03.Student; public class Application { public static void main(String[] args) { Student student = new Student(); student.setName("小明"); System.out.println("名字叫:" + student.getName()); System.out.println("--------------"); // 输入值不合法 student.setSex('爬'); System.out.println(student.getSex()); // 输入值合法 student.setSex('男'); System.out.println("性别为:" + student.getSex()); } }

    结果:

    名字叫:小明 -------------- 输入不合法! 性别为:男

继承


  • B类继承A类,则A类是B类的超类(superclass)、父类、基类,B类是A类的子类(subclass)、派生类、扩展类
  • 子类继承父类,且拥有父类的实例域和方法,但构造方法不继承,私有属性不能在子类中直接访问(通过super.关键字来访问)
  • 继承能增加代码的重复利用率
  • Java中只有单继承,没有多继承(一个孩子一个爹)
  • 所有类都继承至一个Object类,它是所有类的祖宗
  • 继承的缺点:耦合度高,父类一旦修改,所有的子类全都受到牵连

类的继承格式


Java中通过 extends 关键字声明

class 父类 { } class 子类 extends 父类 { }
  • Objext类是个特殊类,我称之为祖宗类,所有类都默认继承 Object类。
  • 子类具有父类所有字段,之后定义不能和父类重复。

super & this


  • super关键字:

    • 代表父类,子类调用父类的字段时,用super.fileldName调用。

    • 只能在有继承的情况下才能使用

    • 构造方法:this( ); 本类的构造器

  • this关键字:

    • 代表自己,指向自己的引用,用 this.fileldName调用。
    • 没有继承也可以使用
    • 构造方法:super( );父类的构造器

实例:

  • 定义一个 Person
package com.wnaoii.oop.Demo04; public class Person { protected String Name = "父类Name"; private int Age = 13 }
  • 再定义一个 Student 类继承 Person
package com.wnaoii.oop.Demo04; // 子类 public class Student extends Person { private String Name = "子类Name"; public void printName(String name) { System.out.println(name); // 这个 name 调的是形式参数 name System.out.println(this.Name); // this.name 调的是当前这个类的 Name System.out.println(super.Name); // super.name 调的是当前这个类父类的 Name } }
  • 实例化 student 对象查看效果
package com.wnaoii.oop.Demo04; public class Application { public static void main(String[] args) { Student student = new Student(); student.printName("参数name"); } }

结果:

参数name 子类Name 父类Name

super 注意点

  • super 调用父类构造方法,必须在构造方法的第一个。
  • super 必须只能出现在子类的方法或构造方法中。
  • super 和 this 不能同时调用构造方法。(因为两者都必须在第一行,有冲突)

protected

  • 在继承中,子类无法访问父类的private字段或者private方法。
  • protected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问。

无法调用:

// 父类 public class Person { // 定义一个 private name变量 private String name = "父类Name"; }
// 子类 public class Student extends Person { public void printName() { System.out.println(super.name); // 编译错误,无法调用 } }

成功调用:

  • 由于 private 关键字使得子类无法调用,则可以把 private 改为 protected,这样子类便能调用父类被 protected 修饰的字段。
// 父类 public class Person { // 定义一个 protected name变量 protected String name = "父类Name"; }
// 子类 public class Student extends Person { public void printName() { System.out.println(super.name); // 子类通过关键字 super 成功调用 } }

继承中的构造器


  • 子类并不继承父类的构造器(构造方法或者构造函数),只是调用(隐式或显式)。
// 父类 public class Person { // 一个简单的无参构造 public Person() { System.out.println("已执行Person无参构造!") } }
// 子类 public class Student extends Person { // 此处隐藏了一个调用父类的无参构造 // super(); public Student() { System.out.println("已执行Student无参构造!") } }
// 调用 public class Application { public static void main(String[] args) { // 只用 Student 类创建一个对象 Student student = new Student(); } }

结果:

已执行Person无参构造! 已执行Student无参构造!

显示调用父类无参构造则必须在子类构造器的第一行。(不写一般都默认调用父类无参构造器)

若父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。

若父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。

方法重写(override)


为什么要重写?

  • 父类的功能,子类不一定需要,或子类需要的更多!

特点:

  • 子类和父类需要有继承关系
  • 重写是对父类可访问的方法的重写,和属性无关
  • 声明为 final 的方法不能被重写
  • 声明为 static 的方法不能被重写,但是能够被再次声明
  • 修饰符的范围可以扩大但不能缩小。(public > protected > Default > private)
  • 抛出的异常范围可以被缩小,但不能扩大
  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类。(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)
  • 方法名、返回值、形参必须相同,方法体不同。(外壳不变,核心重写

例:

package com.wnaoii.oop.Demo05; public class Person { public void say() { System.out.println("Person会说话!"); } }
package com.wnaoii.oop.Demo05; public class A extends Person { @Override public void say() { System.out.println("A不但会说,还会唱、跳、Rep!"); } }
package com.wnaoii.oop; import com.wnaoii.oop.Demo05.Person; import com.wnaoii.oop.Demo05.A; public class Application { public static void main(String[] args) { Person person = new Person(); person.say(); A a = new A(); a.say(); } }

输出:

Person会说话! A不但会说,还会唱、跳、Rep!

多态


一个类的多种形态

父类型引用指向子类型对象

编译时一种形态,运行时一种形态,这就是多态

编译时多态:通过 overloading(重载) 实现

编译阶段绑定父类型的方法

运行时多态:通过 overriding(重写) 和 继承实现

运行阶段动态绑定子类型对象的方法

多态存在的三个条件


  • 拥有继承关系
  • 子类重写父类方法
  • 父类引用指向子类对象:Parent p = new Child( );
    • upcasting 向上转型(父-->子 自动类型转换):将子类对象直接赋值给父类引用
    • 只有向上转型过的对象才能向下转型
    • downcasting 向下转型(子-->父 强制类型转换):将指向子类对象的父类引用赋值给子类引用

实例:

package com.wnaoii.oop.Demo06; //父类 public class Person { public int age = 0; public void run() { System.out.println("run"); } }

和父类有继承关系的Student类:

package com.wnaoii.oop.Demo06; // 子类 public class Student extends Person { public int age = 19; // 重写父类的 run() 方法 @Override public void run() { System.out.println("子类run()方法被调用!"); } // 子类特有的方法 public void eat() { System.out.println("子类特有eat()方法被调用!"); } }

调用:

package com.wnaoii.oop; import com.wnaoii.oop.Demo06.Person; import com.wnaoii.oop.Demo06.Student; public class Application { public static void main(String[] args) { Student s1 = new Student(); // 父类的引用指向子类的对象(向上转型) Person s2 = new Student(); // 多态成员方法调用 s1.run(); // 能调用自己的和继承的方法 s1.eat(); // 类型是 Student 正常调用 System.out.println(); s2.run(); // 子类重写了父类的方法,所以优先执行子类的方法 // s2.eat(); s2的类型为 Person ,而Person中没有eat()方法,无法调用 System.out.println(); // 多态成员变量调用 System.out.println(s1.age); System.out.println(s2.age); } }

结果:

子类run()方法被调用! 子类特有eat()方法被调用! 子类run()方法被调用! 19 0

由此可以看出多态成员访问的特点:

成员变量:编译看父类(左边),运行看父类(左边)

在程序的编译阶段,根据父类中的变量进行编译,实际运行时也是根据父类的变量进行运行

成员方法:编译看父类(左边),运行看子类(右边)

在程序的编译阶段,根据父类的方法进行编译,父类有这个方法编译通过,没有则报 ClassCastException异常(类型转换异常),在实际运行时则运行的是子类中的方法

具体分析

例:有一个Animal的父类,有一个move()方法,一个Dog子类,重写了父类的move方法

Animal dog = new Dog();

编译阶段:

对于编译器来说,编译器只知道dog这个对象的类型是Animal,所以编译器在检查语法的时候只会去Animal.class字节码文件中找父类的move()方法,找到了就绑定上move()方法,编译通过,静态绑定成功!(编译阶段属于静态绑定)

运行阶段:

在运行阶段中,实际上堆内存中创建的java对象是Dog的对象,所以运行move()方法时,实际参与的是一只dog,是对Dog对象中重写的move()方法的调用。(运行阶段绑定属于动态绑定)

向上转型


向上转型(Upcasting)是指将子类的引用赋值给父类的变量的过程,又或者说是将一个对象的类型转换为它的父类型或接口类型。这个过程也称为向上转型。这种转型是自动的,并且在大多数情况下都是安全的。

特点:

  • 向上转型是安全的:由于父类包含了子类的所有方法和属性,所以向上转型后对象仍然可以使用所有的方法和属性
  • 向上转型会丢失子类特有的方法和属性:由于向上转型后的对象只能访问父类的方法和属性,所以子类特有的方法和属性将不再可用
  • 向上转型可以使用 instanceof 运算符进行检测:可以使用 instanceof 运算符来检测对象是否是特定类型的实例。这对于在向上转型后确定对象的类型很有用
  • 向上转型可以使用强制类型转换进行恢复:如果需要访问子类特有的方法和属性,则可以使用强制类型转换将对象转回原来的子类类型。但是,这需要进行类型检查,以确保转换是安全的

例:假设有一个类型为Dog的对象,它有一个父类型为Animal。在这种情况下,可以将Dog对象向上转型为Animal类型,如下所示:

Animal animal = new Dog();

在这种情况下,animal变量仍然指向原来的Dog对象,但是可以通过animal变量调用的方法受到限制。例如,如果Dog类中有一个名为run的方法,则无法通过animal变量调用这个方法,因为Animal类中没有这个方法,这个方法是子类Dog特有的方法。

向下转型

向下转型(downcasting)是指将一个对象的引用从父类型向子类型转型。因为它可以让我们在不改变对象本身的情况下改变对对象的引用。这种转型不是自动的,且不安全!

特点:

  • 只有向上转型过的对象才可以向下转型
  • 向下转型是不安全的:由于向下转型后的对象会访问子类特有的方法和属性,所以如果对象本身不是子类的实例,就会发生类型转换异常
  • 向下转型可以使用 instanceof 运算符进行检测:可以使用 instanceof 运算符来检测对象是否是特定类型的实例。这对于在向下转型前确定对象的类型很有用
  • 向下转型必须使用强制类型转换:必须使用强制类型转换将对象转换为子类类型。但是,这需要进行类型检查,以确保转换是安全的

例:假设有一个类Animal和它的子类Dog,并且有一个Animal类型的对象animal,我们可以将animal向下转型为Dog类型,如下所示:

Animal animal = new Animal(); Dog dog = (Dog)animal

但是,如果animal对象本身不是Dog类的实例,animal是其他类new实例化出来的对象,在这种情况下,尝试向下转型将会导致类型转换异常ClassCastException。如下所示:

Animal animal = new cat(); Dog dog = (Dog)animal;

因此,在进行向下转型之前,通常会使用instanceof运算符来检测对象是否是特定类型的实例,以确保转换是安全的。

instanceof运算符的使用


可以在运行阶段动态判断引用指向的对象的类型,instanceof运算符的运算结果只能是布尔类型

规范:任何时候,任何地点,对类型进行向下转型时,一定要使用instanceof运算符进行判断

语法

引用 instanceof 类型

例1: a 是一个引用, a 变量保存了内存地址指向了堆中的对象

  • a instanceof Animal运算结果为true

    a引用指向的堆内存中的java对象一个Animal

  • a instanceof Animal运算结果为true

  • a引用指向的堆内存中的java对象不是一个Animal

例2:父类Animal,子类1Cat,子类2Dog

Animal a = new cat(); // 将Animal类型的a引用向下转型,Dog类和Cat类都继承自Animal类,但Dog类和Cat类相互并没有继承关系,此时强转就会出现类型转换异常 Dog dog = (Dog)a;

使用instanceof运算符进行判断

// 向上转型 Animal a = new cat(); // 添加instanceof运算符判断 if (a instanceof Dog) { // 运算结果为true时才开始向下转型 Dog dog = (Dog)a; }

什么时候需要使用向下转型?


父类需要访问子类中特有的方法时需要向下转型

多态在开发中的实际应用


多态在开发中的作用:降低程序的耦合度,提高程序的扩展力

编写一个程序模拟主人喂养宠物的场景:

提示1:

  • 主人类:Master
  • 宠物类:Pet
  • 宠物类子类:Dog、Cat、fish

提示2:

  • 主人应该提供一个喂养的方法:feed()
  • 宠物应该有一个吃的方法:eat()

要求:主人类中只提供一个feet()方法,达到喂养各种类型宠物的目的

编写测试程序:

  • 创建主人对象
  • 创建各种宠物对象
  • 调用主人的feed()方法喂养不同的宠物,观察执行结果

编写过程:

  1. 创建主人类

    public class Master {}
  2. 创建宠物类,实现eat()方法

    public class Pet { public void eat() {} }
  3. 创建宠物类子类,重写父类的eat()方法

    1. Dog类

      public class Dog extends Pet{ public void eat() { System.out.println("小狗正在吃骨头!"); } }
    2. Cat类

      public class Cat extends Pet{ public void eat() { System.out.println("小猫正在吃鱼!"); } }
    3. Fish类

      public class Fish extends Pet{ public void eat() { System.out.println("小鱼正在吃小虾米!"); } }
  4. 在主人类中添加feed()方法实现喂养多种类型的宠物

    public class Master { // 形参使用各种宠物的父类pet,传参时自动向上转型 public void feed(Pet p){ p.eat(); } }
  5. 编写测试类开始测试

    public class Test { public static void main(String[] args) { // 创建主人对象 Master master = new Master(); // 创建各种宠物对象 Dog dog = new Dog(); Cat cat = new Cat(); Fish fish = new Fish(); // 调用主人的feed()方法喂养不同的宠物 master.feed(dog); master.feed(cat); master.feed(fish); } }
  6. 输出结果

    小狗正在吃骨头! 小猫正在吃鱼! 小鱼正在吃小虾米!

__EOF__

本文作者WNAOII
本文链接https://www.cnblogs.com/WNAOII/p/17028161.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   WNAOII  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」
点击右上角即可分享
微信分享提示