面向对象编程

我们都知道Java是一门面向对象编程的语言,而我们要面向对象来进行编程,首先需要具有面向对象编程的思想。我们以包,继承,组合,多态,抽象类,接口来作为突破点来认识面向对象进行编程,而同时OOP语言的三大特征为:继承,封装,多态。面对对象的程序是由对象组成的,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。而程序中的很多对象来自于标准库除此之外就是自定义的一些对象,而我们究竟是自己来构造对象还是从库中导入,取决于开发项目的具体要求,但是从根本上来说,只要对象能够满足要求,就不必要关心其功能的具体实现过程。

包 (package) 是组织类的一种方式,借助于包可以更方便的组织自己的代码,并将自己的代码与别人提供的代码库分开来进行管理,标准的Java类库分布于多个包中(java.lang java.util java.net等)我们也可以使用嵌套层次来组织包,所有标准的java包都处于java和javax包层次中。 使用包的主要目的是保证类名的唯一性。

  • 导入包中的类
    一个类可以使用所属包中的所有类,以及其他包中的公有类(public class)。我们可以用两种方式来访问另一个包中的公有类。第一种是在每个类名之前加完整的包名,另一种则是使用import语句。import语句是一种引用包含在包中的类的简明描述,利用该语句在使用类时,不必写出包的全名。
//第一种情况
 public class Test {
    public static void main(String[] args) {
        java.util.Date date = new java.util.Date();
        // 得到一个毫秒级别的时间戳
        System.out.println(date.getTime());
   }
}      
//利用import语句
import java.util.Date;
public class Test {
    public static void main(String[] args) {
        Date date = new Date();
        // 得到一个毫秒级别的时间戳
        System.out.println(date.getTime());
   }
}

补充:如果需要使用 java.util 中的其他类, 可以使用 import java.util.* ,但是我们更建议显式的指定要导入的类名.,否则还是容易出现冲突的情况。

import java.util.*;
import java.sql.*;
public class Test {
    public static void main(String[] args) {
        // util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
        Date date = new Date();
        System.out.println(date.getTime());
   }
}
/*编译出错
Error:(5, 9) java: 对Date的引用不明确
  java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配
  */

出现这种情况时则需要使用完整的类名

import java.util.*;
import java.sql.*;
public class Test {
    public static void main(String[] args) {
        java.util.Date date = new java.util.Date();
        System.out.println(date.getTime());
   }
}
  • 静态导入
    使用 import static 可以导入包中的静态的方法和字段。使用这种方式可以更方便的写一些代码,例如:
import static java.lang.Math.*;
public class Test {
    public static void main(String[] args) {
        double x = 30;
        double y = 40;
        // 静态导入的方式写起来更方便一些.
        // double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
        double result = sqrt(pow(x, 2) + pow(y, 2));
        System.out.println(result);
   }
}
  • 将类放到包中
    基本规则:
    1.在文件的最上方加上一个 package 语句指定该代码在哪个包中。
    2.包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 )。
    3.包名要和代码路径相匹配. 例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存储代码.
    4.如果一个类没有 package 语句, 则该类被放到一个默认包中.
    操作步骤:
  1. 在 IDEA 中先新建一个包: 右键 src -> 新建 -> 包
  2. 在弹出的对话框中输入包名, 例如 com.bit.demo1
  3. 在包中创建类, 右键包名 -> 新建 -> 类, 然后输入类名即可
  4. 此时可以看到我们的磁盘上的目录结构已经被 IDEA 自动创建出来了
    目录结构:(D:\Java\Test\src\com\bit\demo1)
  • 包的访问权限控制
    我们已经了解了访问修饰符 public 和 private。 标记为 public 的部分可以被任意的类使 用;标记为private的部分只能被定义它们的类使用。如果没有指定public或private, 这个部 分(类、 方法或变量)可以被同一个包中的所有方法访问。
  • 常见的系统包
    java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
    java.lang.reflect:java 反射编程包;
    java.net:进行网络编程开发包。
    java.sql:进行数据库开发的支持包。
    java.util:是java提供的工具程序包。(集合类等) 非常重要
    java.io:I/O编程开发包。

继承

利用继承,可以基于已存在的类构造一个新类(代码重用)。继承已存在的类就是复用(继承)这些类的方法和域。 在此基础上, 还可以添加一些新的方法和域。
被继承的类, 我们称为 父类 , 基类 或 超类,对于继承的类, 我们称为子类, 派生类,例如Animal里包括Cat,Dog…Animal就是父类,Cat,Dog…即为子类。
而父类与子类之间满足什么条件可以发生继承呢?(以Animal,Cat, Dog为例)
1.这三个类都具备一个相同的方法, 而且行为是完全一样的.
2.这三个类都具备一个相同的属性, 而且意义是完全一样的.
3.从逻辑上讲, Cat 和 Dog都是一种 Animal (is - a 语义).

  • 语法规则

class 子类 extends 父类 {

}
1.使用 extends 指定父类.
2.Java 中一个子类只能继承一个父类 (而C++/Python等语言支持多继承).
3.子类会继承父类的所有 public 的字段和方法.
4.对于父类的 private 的字段和方法, 子类中是无法访问的.
5.子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用

  • protected 关键字
    如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了我们 “封装” 的初衷.两全其美的办法就是 protected 关键字.
    对于类的调用者来说, protected 修饰的字段和方法是不能访问的对于类的 子类 和 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的。
    Java 中对于字段和方法共有四种访问权限
    1.private: 类内部能访问, 类外部不能访问
    2.默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
    3.protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
    4.public : 类内部和类的调用者都能访问.

  • 多层继承, 即子类还可以进一步的再派生出新的子类.
    我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多. 类之间的关系也会更加复杂.但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.如果想从语法上进行限制继承, 就可以 final 关键字.

  • final关键字
    修饰一个变量或者字段的时候, 表示 常量 (不能修改)final 关键字也能修饰类, 此时表示被修饰的类就不能被继承.

组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果.例如:

 public class Student {
 ...
}
public class Teacher {
 ...
}
public class School {
 public Student[] students;
 public Teacher[] teachers;
} 

组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段.这是我们设计类的一种常用方式之一.
组合表示 has - a 语义
在刚才的例子中, 我们可以理解成一个学校中 “包含” 若干学生和教师.
继承表示 is - a 语义
在上面的 “动物和猫” 的例子中, 我们可以理解成一只猫也 “是” 一种动物

多态

  • 向上转型
    在面向对象程序设计中, 针对一些复杂的场景(很多类, 很复杂的继承关系), 程序猿会画一种 UML 图的方式来表示类之间的关系. 此时父类通常画在子类的上方. 所以我们就称为 “向上转型” , )
    例如这个代码: Bird bird = new Bird(“啊哈”);
    这个代码也可以写成这个样子
Bird bird = new Bird("啊哈");
Animal bird2 = bird;
// 或者写成下面的方式
Animal bird2 = new Bird("啊哈");

此时 bird2 是一个父类 (Animal) 的引用, 指向一个子类 (Bird) 的实例.种写法称为向上转型.

  • 向上转型发生的时机:直接赋值 方法传参 方法返回。
  • 方法传参
 public class Test {
 public static void main(String[] args) {
 Bird bird = new Bird("啊哈");
 feed(bird);
 }
 public static void feed(Animal animal) {
 animal.eat("谷子");
 }
}

执行结果:啊哈正在吃谷子
此时形参 animal 的类型是 Animal (基类), 实际上对应到 Bird (父类) 的实例

  • 方法返回
public class Test {
 public static void main(String[] args) {
 Animal animal = findMyAnimal();
 }
 public static Animal findMyAnimal() {
 Bird bird = new Bird("啊哈");
 return bird;
 }
} 

此时方法 findMyAnimal 返回的是一个 Animal 类型的引用, 但是实际上对应到 Bird 的实例.

  • 动态绑定
    运行时绑定–》动态绑定(动多态)
    发生的条件:
    1.先要向上转型–》父类的引用 引用子类的对象
    2.父类和子类都有同名的覆盖方法
    在这里插入图片描述
    因此, 在 Java 中, 调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为 动态绑定.

  • 方法重写
    子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override).
    关于重写的注意事项

  • 重写和重载完全不一样. 不能混淆

  • 普通方法可以重写, static 修饰的静态方法不能重写

  • 重写中子类的方法的访问权限不能低于父类的方法访问权限.

  • 针对重写的方法, 可以使用 @Override 注解来显式指定
    重载和重写的区别:
    在这里插入图片描述

  • 理解多态
    使用多态的好处是什么?

  1. 类调用者对类的使用成本进一步降低.
    封装是让类的调用者不需要知道类的实现细节.
    多态能让类的调用者连这个类的类型是什么都不必知道
    只需要知道这个对象具有某个方法即可.
    因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低.这也贴合了 <<代码大全>> 中关于 “管理代码复杂程度” 的初衷
  2. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
  3. 可扩展能力更强
    代码示例: 打印多种形状
lass Shape {
 public void draw() {
 // 啥都不用干
 }
}
class Cycle extends Shape {
 @Override
 public void draw() {
 System.out.println("○");
 }
}
class Rect extends Shape {
 @Override
 public void draw() {
 System.out.println(" □"); 
 }
}
class Flower extends Shape {
 @Override
 public void draw() {
 System.out.println("♣");
 }
}
//--------------------------------------------
public class Test {
 public static void main(String[] args) {
 Shape shape1 = new Flower();
 Shape shape2 = new Cycle();
 Shape shape3 = new Rect();
 drawMap(shape1);
 drawMap(shape2);
 drawMap(shape3);
 }
 // 打印单个图形
 public static void drawShape(Shape shape) {
 shape.draw();
 }
}

在这个代码中, 分割线上方的代码是 类的实现者 编写的, 分割线下方的代码是 类的调用者 编写的.当类的调用者在编写 drawMap 这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例. 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现(和 shape 对应的实例相关), 这种行为就称为多态

  • 向下转型
    向上转型是子类对象转成父类对象, 向下转型就是父类对象转成子类对象. 相比于向上转型来说, 向下转型没那么常见,
    但是也有一定的用途
    向下转型的前提是有一次(之前)向上转型 。需要注意的是如果需要向下转换一种类型(子类),前提是向下转换的这种类型(子类)已经被父类引用。
    instanceof :用来判断内存中实际对象(实例)A是不是B类型(左边是对象(实例)右边是类),用来做类的类型判断。
    java.lang.ClassCastException: 类型转换异常

  • super 关键字
    用于在在子类内部调用父类方法
    super 表示获取到父类实例的引用. 涉及到两种常见用法

  1. 使用了 super 来调用父类的构造器(这个代码前面已经写过了)
public Bird(String name) {
 super(name);
} 
  1. 使用 super 来调用父类的普通方法
public class Bird extends Animal {
 public Bird(String name) {
 super(name);
 }
 @Override
 public void eat(String food) {
 // 修改代码, 让子调用父类的接口.
 super.eat(food);
 System.out.println("我是一只小鸟");
 System.out.println(this.name + "正在吃" + food);
 }
}

在这个代码中, 如果在子类的 eat 方法中直接调用 eat (不加super), 那么此时就认为是调用子类自己的 eat (也就是递归了). 而加上 super 关键字, 才是调用父类的方法
注意 super 和 this 功能有些相似, 但是还是要注意其中的区别

抽象类

例如没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstractmethod), 包含抽象方法的类我们称为 抽象类(abstract class).
语法规则:

//对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类.
 abstract class Shape {
 abstract public void draw();
} 

注意事项:

  1. 抽象类不能直接实例化.
  2. 抽象方法不能是 private 的.
  3. 抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写,也可以被子类直接调用.
    抽象类的作用:抽象类存在的最大意义就是为了被继承.
    在这里插入图片描述

接口

接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量
语法规则
在刚才的打印图形的示例中, 我们的父类 Shape 并没有包含别的非抽象方法, 也可以设计成一个接口

interface IShape {
 void draw();
}
class Cycle implements IShape {
 @Override
 public void draw() {
 System.out.println("○");
 }
}
public class Test {
 public static void main(String[] args) {
 IShape shape = new Rect();
 shape.draw();
 }
} 

在这里插入图片描述
提示:
1.我们创建接口的时候, 接口的命名一般以大写字母 I 开头.

  • 接口的命名一般使用 “形容词” 词性的单词.

  • 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性

  • 实现多个接口(提示, IDEA 中使用 ctrl + i 快速实现接口)
    有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果.
    Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口.继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性 .有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力

  • 接口使用实例
    给对象数组排序给定一个学生类

class Student {
 private String name;
 private int score;
 public Student(String name, int score) {
 this.name = name;
 this.score = score;
 }

 @Override
 public String toString() {
 return "[" + this.name + ":" + this.score + "]";
 }
} 

再给定一个学生对象数组, 对这个对象数组中的元素进行排序(按分数降序).

Student[] students = new Student[] {
 new Student("张三", 95),
 new Student("李四", 96),
 new Student("王五", 97),
 new Student("赵六", 92),
}; 

按照我们之前的理解, 数组我们有一个现成的 sort 方法, 能否直接使用这个方法呢?

Arrays.sort(students);
System.out.println(Arrays.toString(students));
// 运行出错, 抛出异常.
Exception in thread "main" java.lang.ClassCastException: Student cannot be cast to
java.lang.Comparable

不难发现, 和普通的整数不一样, 两个整数是可以直接比较的, 大小关系明确. 而两个学生对象的大小关系怎
么确定? 需要我们额外指定.让我们的 Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法

class Student implements Comparable {
 private String name;
 private int score;
 public Student(String name, int score) {
 this.name = name;
 this.score = score;
 }
 @Override
 public String toString() {
 return "[" + this.name + ":" + this.score + "]";
 }
 @Override
 public int compareTo(Object o) {
 Student s = (Student)o;
 if (this.score > s.score) {
 return -1;
 } else if (this.score < s.score) {
 return 1;
 } else {
 return 0;
 }
 }
} 

在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象.
然后比较当前对象和参数对象的大小关系(按分数来算).
如果当前对象应排在参数对象之前, 返回小于 0 的数字;
如果当前对象应排在参数对象之后, 返回大于 0 的数字;
如果当前对象和参数对象不分先后, 返回 0;再次执行程序, 结果就符合预期了.

// 执行结果
[[王五:97], [李四:96], [张三:95], [赵六:92]]

注意事项: 对于 sort 方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备 compareTo 这样的能力. 通过重写 compareTo 方法的方式, 就可以定义比较规则.

  • 接口间的继承

接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字.
抽象类和接口核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值