往期:
JAVA 修炼秘籍第一章:《痛苦的折磨》
JAVA 修炼秘籍第二章:《逐渐魔化》
JAVA 修炼秘籍第三章:《绝地反击》
JAVA 修炼秘籍第四章:《闭关修炼》
JAVA 修炼秘籍第五章:《卧薪尝胆》
JAVA 修炼秘籍第六章:《鏖战》
JAVA修炼秘籍(番外篇)第一章:《这四道代码题,你真的会吗?》
——————————————————————生活以痛吻我,我却报之以歌。
文章目录
前言:什么是面向对象编程
在前面的章节中我们有介绍过什么是面向对象,了解了面向对象与面向过程的差别。下面让我们再次回顾一下什么是面向对象,什么是面向过程,这次没有举例说明。
理解了什么是面向对象,接下来看看如何进行面向对象编程,下面会进行详细解答。
一、包
介绍1: 把功能类似的或相关的类或接口组织到一个包中,方便查找。
介绍2: 同一包中的两个类不可以重名,不同包中的类的名字是可以相同的。
(就好比,张三有个大儿子叫王大力,不能给二儿子起名也叫王大力。但是隔壁王五家的儿子可以叫王大力,因为这是两家人,你也管不到人家儿子叫啥,你和一个陌生人重名,大部分人都有)。
1.导入包中的类
java给我们提供了一些已经被开发人员写好了的类提供给我们使用。比如:
import java.util.Scanner;
通过上面的代码,我们引用了java.util包中的Scanner类,接下来就可以进行使用了,如果你还想使用util中别的类,这时候我们可以这样写,大大节省时间。(JAVA中不会因为你导入了一个包而只用了一个其中的类而提高多余的空间需求,而是引入那个类就用那个类)。
import java.util.*
还有很多,可以直接引入使用的类,给大家贴上一个软件。
可以在这里面查找。
如果出现下面代码的情况这样怎么办。
util包 和 sql包 中都存在一个 Date 类, 此时就会编译出错,系统也不知道你是引用的那个包中的Date类,在这种情况下,还是要使用完整的类名。
2.类如何放入包
- 在文件的最上方加上一个 package 语句指定该代码在哪个包中。
package com.zhanghao;
public class project {
public static void main(String[] args) {
System.out.println();
}
}
- 包名尽量唯一,不要有重复包名的现象,一般都是公司的域名倒写,个人可以将自己名字倒写:zhanghao.com ——> com.zhanghao。
- 包名要和代码路径相匹配. 例如创建 com.zhanghao 的包, 那么会存在一个对应的路径 com/zhanghao 来存储代码。
- 如果一个类最上方没有加上 package 语句, 此类会被放到一个默认包中。
创建步骤:
就会弹出一个窗口让你输入包名。
此时左侧就会创建出这个包
创建类
输入类名后,类创建成功,最上方便会自动形成package语句。
二、继承
1.什么是继承
继承指可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过 “继承”和“组合”来实 父类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力。
知道了什么是继承,我们通过下面的代码与图片来加深印象。
图片中代码有些看不清,源代码复制到下方。
package com.zhanghao;
class Animal{//创建动物这个类
public String name;//动物有名字
public int age;//有年龄
public Animal(String name,int age) {//构造方法
this.name=name;
this.age=age;
}
public void eat(){//动物有吃这个行为,我们写成一个方法
System.out.println(age+"岁的"+name+"正在吃");
}
public void run(){//动物有跑这个行为,我们写成一个方法
System.out.println(age+"岁的"+name+"正在跑");
}
}
class Sheep{//创建羊这个类
public String name;//羊有名字
public int age;//羊有年龄
public Sheep(String name,int age){
this.name=name;
this.age=age;
}
public void eat(){//羊有吃这个行为,我们写成一个方法
System.out.println(age+"岁的"+name+"正在吃");
}
public void run(){//羊有跑这个行为,我们写成一个方法
System.out.println(age+"岁的"+name+"正在跑");
}
}
class Tiger{
public String name;//老虎有名字
public int age;//老虎有年龄
public Tiger(String name,int age){
this.name=name;
this.age=age;
}
public void eat(){//老虎有吃这个行为,我们写成一个方法
System.out.println(age+"岁的"+name+"正在吃");
}
public void run(){//老虎有跑这个行为,我们写成一个方法
System.out.println(age+"岁的"+name+"正在跑");
}
}
public class project {
public static void main(String[] args) {
Tiger tiger=new Tiger("虎霸王",20);
tiger.eat();
tiger.run();
}
public static void main2(String[] args) {
Sheep sheep=new Sheep("肖恩",5);
sheep.eat();
sheep.run();
}
public static void main1(String[] args) {
Animal animal=new Animal("动物",4);//实例化对象
animal.eat();//让动物有动作
animal.run();
}
}
- 通过上面的代码与图片我们可以看出,有很多重复代码,甚至相同代码,这三个类中的属性与方法完全相同,而且行为与意义完全一直。
- 此时我们就可以使用继承,因为老虎与羊都属于动物。让老虎与羊都继承于动物这个类。
3. 代码进行这样的更改,大大减少了代码的重复性,变得很清晰。
package com.zhanghao;
class Animal{//创建动物这个类
public String name;
public int age;
public Animal(String name,int age) {//构造方法
this.name=name;
this.age=age;
}
public void eat(){
System.out.println(age+"岁的"+name+"正在吃");
}
public void run(){
System.out.println(age+"岁的"+name+"正在跑");
}
}
class Sheep extends Animal{//创建羊这个类
public Sheep(String name,int age) {
super(name, age);
}
}
class Tiger extends Animal{
public Tiger(String name,int age){
super(name,age);
}
public void bite(){
System.out.println(this.name+"正在咬");
}
}
public class project {
public static void main(String[] args) {
Tiger tiger=new Tiger("虎霸王",20);
tiger.eat();
tiger.run();
tiger.bite();
Sheep sheep=new Sheep("肖恩",5);
sheep.run();
sheep.eat();
}
}
- 使用关键字extends来达到继承:class 子类 extends 父类
- JAVA中一个子类只能有一个父类,也就是说,一个孩子只能有一个爹,但是一个爹可以有好几个孩子。
- 所有子类都可以继承父类的public属性与方法,就像基因一样,孩子可以继承爹的基因,就像长相,身高这些基因问题。
- 但是父类中private的方法与属性,子类中是无法访问的,也就是爹的运气好,儿子是没有办法使用爹的运气来办自己的事。
- 子类的实例中,也包含父类的实例,可以使用super关键字得到父类实例的引用。
- extends 英文原意指 “扩展”。 而我们所写的类的继承, 也可以理解成基于父类进行代码上的 “扩展”。例如我们写的 Tiger 类, 就是在 Animal 的基础上扩展出了 fly 方法。
- 继承的子类还可以派生出新的子类,比如老虎继承于动物,那么老虎还可以派生出东北虎,白虎,孟加拉虎等等,东北虎又可以派生出成年虎,幼崽,母后等等。
- 层次多了,你真的能记住吗,所以在编程中要避免过多层次的继承,一般不要超过3层。
三、封装
封装对于属性与方法有4种访问权限。
- private——》私有的
- default——》 默认的
- protected——》受保护的
- public——》公共的
这四种的访问权限等级可以理解为:
public > protected > default > private.
有一个经典表格可以更好的理解。
这就是各自的可以使用的范围。 什么时候需要对方法或属性什么访问权限,需要程序员自己来进行设置,尽可能的使用比较严格的访问权限。
final也可以修饰类:
final修饰过的类表示不可以被继承,final 关键字的功能是 限制 类被继承
“限制” 这件事情意味着 “不灵活”. 在编程中, 灵活往往不见得是一件好事. 灵活可能意味着更容易出错.使用 final 修饰的类被继承的时候, 就会编译报错, 此时就可以提示我们这样的继承是有悖这个类设计的初衷的。
四、组合
- 在一个类中,用另一个类的对象作为数据属性,称为类的组合。
- 当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好。
- 举个例子:当一个类为医生,一个类为护士还有一些类似的类,那么可以组合为一个医院的类来使用,他们的关系是has-a的关系,也就是”医院has-a医生“,“医院has-a护士等等”。
- 而继承是is-a的关系,例如子类is-a父类,”老虎is-a动物“,“羊is-a动物”等等。
五、多态
1.什么是多态
是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
2.向上转型
通过上面的代码我们实现了继承的操作。
Tiger tiger=new Tiger("虎霸王",20);
这个代码也可以写成下面这种形式。
Animal animal=new Tiger("虎霸王",20);
- 此时 animal 是一个Animal 的引用, 指向一个Tiger 的实例. 这种写法称为向上转型。
- 这种发生向上转型的情况是直接赋值。
- 方法传参会有向上转型的出现。
public class project {
public static void main(String[] args) {
Tiger tiger=new Tiger("虎霸王",20);
feed(tiger);
}
public static void feed(Animal animal){
animal.eat("野鸡");
}
}
- 方法返回也会有向上转型的出现。
public class project {
public static void main(String[] args) {
Animal animal=getname();
}
public static Animal getname(){
Tiger tiger=new Tiger("虎霸王",20);
return tiger;
}
}
3.动态绑定
如果这个时候我们给子类Tiger中也添加一个eat方法,当我们再次调用eat方法时是调用子类中的eat方法,还是父类中的eat方法?
class Animal{//创建动物这个类
public String name;
public Animal(String name) {//构造方法
this.name = name;
}
public void eat(String food){
System.out.println("动物行动");
System.out.println(name+"正在吃"+food);
}
}
class Sheep extends Animal{//创建羊这个类
public Sheep(String name) {
super(name);
}
public void eat(String food){
System.out.println("羊行动");
System.out.println(name+"正在吃"+food);
}
}
public class project {
public static void main(String[] args) {
Animal animal=new Animal("动物");
animal.eat("肉");
Animal animal1=new Sheep("羊");
animal1.eat("草");
}
}
执行后的输出结果。
这时候可以看出来谁调用的谁,谁引用的谁,虽然两个都是Animal类型的引用,但是调用eat函数时,都是按照各自指向的实例来进行调用的,要看究竟这个引
用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为动态绑定
4.方法重写
- 上一段中我们其实就用到了重写的功能,重写还可以叫做,覆写,覆盖。
- 上一段中的父类拥有eat方法,子类也有一个返回值参数相同的eat方法,这就是重写。
- 重写与重载是完全不同的,他们两个的规则是完全不搭边的,前几章中有讲过重载的要求。
- 普通方法可以重写, static 修饰的静态方法不能重写.
- 重写中子类的方法的访问权限不能低于父类的方法访问权限.
- 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外)
public > protected > default > private.
记住这个使用范围。
假设:
class Animal{//创建动物这个类
public String name;
public Animal(String name) {//构造方法
this.name = name;
}
public void eat(String food){
System.out.println("动物行动");
System.out.println(name+"正在吃"+food);
}
}
class Sheep extends Animal{//创建羊这个类
public Sheep(String name) {
super(name);
}
private void eat(String food){
System.out.println("羊行动");
System.out.println(name+"正在吃"+food);
}
}
这个时候代码会出现编译出错,也就是,子类重写父类的方法时,子类的方法使用范围不可以低于父类方法的使用范围。
我们可以在重写的方法上加上”@Override“,这样如果方法出现一些书写错误等情况时,代码会编译报错,提醒我们无法构成重写。
5.理解多态
什么是多态我们上面已经有所介绍,接下来我们就可以利用重写,动态绑定等来使用多态的形式设计程序了
- 此时我们可以把上面这段代码中, project类上方的代码是类的实现者编写的, 分割线下方的代码是类的调用者编写的。
- 当类的调用者在编写 conduct 这个方法的时候, 参数类型为 Animal (父类),
- 此时在该方法内部并不知道, 也不关注当前的 food 引用指向的是哪个类型的实例.
- 此时 food 这个引用调用 eat 方法可能会有多种不同的表现,和 food 对应的实例相关,这种行为就称为多态
在Head First java中文版中有这样一个故事,可以让你知道,面向对象的好处,为什么使用面向对象的方式编程。
6.向下转型
有的时候我们也会用到向下转型,但是没有向上转型常见,向下转型为父类对象转换为子类对象,向上转型可以理解的话,这个也非常好理解。
Animal animal=new Tiger("虎霸王",20);
animal.eat();
上面这种代码是无法编译通过的,应该进行强制类型转换。
Animal animal=new Tiger("虎霸王",20);
Tiger tiger=(Tiger) animal;
向下转型存在一定风险,使用需谨慎。
7.super关键字
前面我们在方法重写中的代码有用过这个关键字,但是这个关键字的作用是什么我们没有介绍,现在来单独介绍一下这个关键字
- 使用super来调用父类的构造器。
class Sheep extends Animal{//创建羊这个类
public Sheep(String name) {
super(name);
}
}
- 使用super来调用父类的普通方法
class Tiger extends Animal{
public Tiger(String name){
super(name);
}
@Override
public void eat(String food){
super.eat(food);
System.out.println(this.name+"正在咬");
}
}
在这个代码中, 如果在子类的 eat 方法中直接调用 eat (不加super), 那么此时就认为是调用子类自己的 eat (也就是递归了). 而加上 super 关键字, 才是调用父类的方法
super与this的区别:
- super()主要是对父类构造函数的调用,this()是对重载构造函数的调用
- super()主要是在继承了父类的子类的构造函数中使用,是在不同类中的使用;this()主要是在同一类的不同构造函数中的使用
相同点:
- super()和this()都必须在构造函数的第一行进行调用,否则就是错误的
如果出现下列代码情况与super无关:
class Animal{//创建动物这个类
public String name;
public Animal() {//构造方法
run();
}
public void run(){
System.out.println("Animal");
}
}
class Tiger extends Animal{
private int sum=1;
@Override
public void run(){
System.out.println("tiger "+sum);
}
}
public class project {
public static void main(String[] args) {
Tiger tiger=new Tiger();
}
}
输出结果为:
原因:
用尽量简单的方式使对象进入可工作状态", 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题
六、抽象类
- 没有实际工作的方法, 我们可以把它设计成一个 抽象方法,包含抽象方法的类我们称为 抽象类。
- 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体代码)
- 对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类
- 抽象类不可以直接进行实例化操作(下面为错误示范)。
abstract class Tiger{
abstract public void run();
}
public class project {
public static void main(String[] args) {
Tiger tiger=new Tiger();
}
}
- 抽象方法不能是 private 的(下面为错误示范)。
abstract class Tiger{
abstract private void run();
}
- 抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写,也可以被子类直接调用。(下面代码输出结果为food)。
abstract class Animal{
abstract public void run();
void food(){
System.out.println("food");
}
}
class Tiger extends Animal{
public void run(){
System.out.println("tiger ");
}
}
public class project {
public static void main(String[] args) {
Animal animal=new Tiger();
animal.food();
}
}
- 抽象类最大的作用就是用来被继承。
- 抽象类是用来捕捉子类的通用特性的,是被用来创建继承层级里子类的模板。
- 现实中有些父类中的方法确实没有必要写,因为各个子类中的这个方法肯定会有不同。
- 而写成抽象类,这样看代码时,就知道这是抽象方法,而知道这个方法是在子类中实现的,所以有提示作用。
七、接口
interface IShape {
void draw();
}
class Cycle implements IShape {
public static final int num=10;
@Override
public void draw() {
System.out.println("○");
}
}
- Java中接口使用interface关键字修饰。
- 接口可以包含变量、方法;变量被隐士指定为public static final,方法被隐士指定为public abstract(可以将上面的代码进行简化)。
interface IShape {
void draw();
int num = 10;
}
- 接口无法被单独实例化。
- 接口支持多继承,即一个接口可以extends多个接口,间接的解决了Java中类的单继承问题。
- 一个类可以实现多个接口;
- 接口的实现类只有实现了接口中的方法后才能实例化。
- 接口只有定义,不能有方法的实现
- 接口之间也可以继承,使用extends
interface IFlying {
void fly();
}
interface ISwimming extends IFlying {
void swim();
}
实现多个接口
有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果.
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
interface IFlying {
void fly();
}
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
class Dog extends Animal implements IRunning {
public Dog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在用四条腿跑");
}
}
class Fish extends Animal implements ISwimming {
public Fish(String name) {
super(name);
}
@Override
public void swim() {
System.out.println(this.name + "正在用尾巴游泳");
}
}
class Bird extends Animal implements IFlying {
public Bird(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name + "正在飞");
}
}
class Wildgoose extends Animal implements IRunning, ISwimming, IFlying {
public Wildgoose(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name + "正在用翅膀飞");
}
@Override
public void run() {
System.out.println(this.name + "正在用两条腿跑");
}
@Override
public void swim() {
System.out.println(this.name + "正漂在水上");
}
}
总结: 上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口,这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力。
结束语: 多多联系使用面向对象的形式编程,这样有锻炼个人的抽象能力,多加练习,就会熟能生巧。再见,谢谢观看。