【Java】面向对象编程

目录

导入包中的类

静态导入

将类放到包中

包的访问控制权限

继承

final关键字

super关键字

protected 关键字

组合

多态

向上转型

动态绑定

方法重写

理解多态

抽象类

接口

小结


什么是包?

(package) 是组织类的一种方式 。使用包的主要目的是保证类的唯一性。
其实就相当于我们C语言中的库函数,里面放了已经封装好的类,当我们需要用到里面的字段和方法时,就可以通过包来导入。

导入包中的类

Java 中已经提供了很多现成的类供我们使用:例如下面这个打印数组的函数
public class Test {
    public static void main(String[] args) {
        int[] arr={1,2,3,4,5};
        System.out.println(Arrays.toString(arr));
    }
}

【Java】面向对象编程_第1张图片

 这个就是导入包中的类,我们在在写代码的时候,要用一些已经写好的函数,我们就需要导入它,就是导入包中的类。我们需要哪个方法或者字段就引用哪个方法或者字段。

我们可以通过下图方式看到until这个包中的Arrays这个类。

【Java】面向对象编程_第2张图片

那么package和import有什么区别呢?

package:“包”,指类所在的包

import:“引入”,引入类中所需要的类

import只能导入一个具体的类,不能导入一个包。

对于上面那种导入包中的类,我们还可以用*代替这个包中所有的类,until中有很多类,Java处理的时候会拿到自己想要的,并不是所有类都拿出来。

import java.util.*;

public class Test {
    public static void main(String[] args) {
        int[] arr={1,2,3,4,5,6};
        System.out.println(Arrays.toString(arr));
    }
}

 但是这种方法也有不好的地方,例如:

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 static 可以导入包中的静态的方法和字段
import static java.lang.System.*;
public class Test {
    public static void main(String[] args) {
        out.println("hello");
   }
}

【Java】面向对象编程_第3张图片

 静态导入的好处:

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.caicai.demo1 )。
3、包名要和代码路径相匹配 . 例如创建 com.bit.demo1 的包 , 那么会存在一个对应的路 com/caicai/demo1 来存储代码
4、如果一个类没有 package 语句 , 则该类被放到一个默认包中。
接下来看个例子:

【Java】面向对象编程_第4张图片

 接下来我们可以看我们的磁盘目录结构已经被IDEA自动创建出来了:

 【Java】面向对象编程_第5张图片

 同时我们也可以看到,在新创建的TestDemo.java文件的最上方出现了一个package语句。该package 语句指定该代码在哪个包中。

包的访问控制权限

我们已经了解了类中的 public private,private 中的成员只能被类的内部使用。(如果对private不懂得可以去我这篇文章看看)

如果某个成员不包含 public private 关键字 , 此时这个成员可以在包内部的其他类使用 , 但是不能在包外部的类使用
下面的代码给了一个示例 , Demo1 Demo2 是同一个包中 , Test 是其他包中。
Demo1.java:
package com.caicai.demo1;
public class Demo1 {
    int value = 0; 
}

Demo2.java:

package com.caicai.demo1; 

public class Demo2 { 
 public static void main(String[] args) { 
 Demo1 demo = new Demo1(); 
 System.out.println(demo.value); 
 } 
} 
// 执行结果, 能够访问到 value 变量
10

Test.java:

import com.caicai.demo2; 
public class Test { 
 public static void main(String[] args) { 
 Demo1 demo = new Demo1(); 
 System.out.println(demo.value); 
 } 
} 
// 编译出错
Error:(6, 32) java: value在com.caicai.demo2中不是公共的; 无法从外部程序包中对其进行访问

总结:包的访问控制权限就是说如果某个成员不含关键字public或者private,则该成员只能在该包内的其他类中使用,不能在其他包中使用。

继承

什么是继承:

代码中创建的类 , 主要是为了抽象现实中的一些事物 ( 包含属性和方法 )。
有的时候客观事物之间就存在一些关联 , 那么在表示类和对象的时候也会存在一定的关联

(简单来说就是几个类中有完全相同的成员或者方法,我们可以把这些相同成员或者方法放在一个类(假设放在A)中,其他类(B,C……)可以通过继承来使用。A就是父类,B,C……就是子类。

继承的意义是为了代码的复用。

此时我们举个例子来看:

// Animal.java 
public class Animal { 
 public String name; 
 public Animal(String name) { 
 this.name = name; 
 } 
 
 public void eat(String food) { 
 System.out.println(this.name + "正在吃" + food); 
 } 
} 


// Cat.java 
class Cat { 
 public String name; 
 
 public Cat(String name) { 
 this.name = name; 
 } 
 
 public void eat(String food) { 
 System.out.println(this.name + "正在吃" + food); 
 } 
} 


// Bird.java 
class Bird { 
 public String name; 
 
 public Bird(String name) { 
 this.name = name; 
 } 
 
 public void eat(String food) { 
 System.out.println(this.name + "正在吃" + food); 
 } 
 
 public void fly() { 
 System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿"); 
 } 
}

【Java】面向对象编程_第6张图片 所以我们可以知道:

这三个类都具备一个相同的 eat 方法 , 而且行为是完全一样的
这三个类都具备一个相同的 name 属性 , 而且意义是完全一样的
从逻辑上讲 , Cat Bird 都是一种 Animal (is - a 语义 )。

此时我们就可以让 Cat Bird 分别继承 Animal , 来达到代码重用的效果

总结:

此时 , Animal 这样被继承的类 , 我们称为 父类 , 基类 超类 , 对于像 Cat Bird 这样的类 , 我们称为 子类 , 派生类 。和现实中的儿子继承父亲的财产类似 , 子类也会继承父类的字段和方法 , 以达到代码重用的效果。
 
基本语法
class 子类 extends 父类 {
}

对于上面的代码, 可以使用继承进行改进. 此时我们让 Cat 和 Bird 继承自 Animal 类, 那么 Cat 在定义的时候就不必再写 name 字段和 eat 方法.

class Animal { 
 public String name; 
 public Animal(String name) { 
 this.name = name; 
 } 
 public void eat(String food) { 
 System.out.println(this.name + "正在吃" + food); 
 } 
} 

class Cat extends Animal { 
 public Cat(String name) { 
 // 使用 super 调用父类的构造方法. 
 super(name); 
 } 
} 

class Bird extends Animal { 
 public Bird(String name) {
super(name); 
 } 
 public void fly() { 
 System.out.println(this.name + "正在飞"); 
 } 
} 

public class Test { 
 public static void main(String[] args) { 
 Cat cat = new Cat("小黑"); 
 cat.eat("猫粮"); 
 Bird bird = new Bird("圆圆"); 
 bird.fly(); 
 } 
}

【Java】面向对象编程_第7张图片

【Java】面向对象编程_第8张图片

extends 英文原意指 "扩展". 而我们所写的类的继承, 也可以理解成基于父类进行代码上的扩展。

如果我们把 name 改成 private, 那么此时子类就不能访问了:
class Animal { 
 private String name; 
 public Animal(String name) { 
 this.name = name; 
 } 
 public void eat(String food) { 
 System.out.println(this.name + "正在吃" + food); 
 } 
} 

class Bird extends Animal { 
 public Bird(String name) { 
 super(name); 
 } 
 public void fly() { 
 System.out.println(this.name + "正在飞"); 
 } 
} 
// 编译出错
Error:(19, 32) java: name 在 Animal 中是 private 访问控制

注意:

1、使用 extends 指定父类

2、Java 中一个子类只能继承一个父类 (C++/Python等语言支持多继承)。

3、子类会继承父类的所有 public 的字段和方法对于父类的 private 的字段和方法 , 子类中是无法访问的
4、子类的实例中 , 也包含着父类的实例 可以使用 super 关键字得到父类实例的引用。

final关键字

建议一个父类最多被继承3层,如果一个类不想被继承,可以用final修饰。
final int a=10;表示被修饰之后是常量,不能被改变。
【Java】面向对象编程_第9张图片

final class A;类不能被继承:
【Java】面向对象编程_第10张图片

super关键字

不出现在静态方法中!!!(因为他是父类对象的引用,依赖对象,而被static修饰之后以后的方法和成员依赖类,不依赖对象。)

1、super();//调用父类的构造方法。//必须放第一行

2、super.func();//调用父类的方法。

3、super.data;//调用父类的成员变量。

这时候我们可以知道super跟我们之前学到的this关键字很像:

1、this()-->调用当前对象的其他构造方法。//必须放第一行

2、this.func()-->调用当前对象的方法。

3、this.date-->调用当前对象的成员变量。

他们都是用来调用,只是调用的对象不同,用法是相似的。

protected 关键字

刚才我们发现 , 如果把字段设为 private, 子类不能访问 . 但是设成 public, 又违背了我们 " 封装 " 的初衷 。两全其美的办法就是 protected 关键字对于类的调用者来说 , protected 修饰的字段和方法是不能访问的 。对于类的 子类 同一个包的其他类 来说 , protected 修饰的字段和方法是可以访问的 。
// Animal.java 
 public class Animal { 
 protected String name; 
 public Animal(String name) {
   this.name = name; 
 } 
 public void eat(String food) { 
 System.out.println(this.name + "正在吃" + food); 
 } 
} 

// Bird.java 
public class Bird extends Animal { 
 public Bird(String name) { 
 super(name); 
 } 
 public void fly() { 
 // 对于父类的 protected 字段, 子类可以正确访问
 System.out.println(this.name + "正在飞"); 
 } 
} 

// Test.java 和 Animal.java 不在同一个包之中了. 
public class Test { 
 public static void main(String[] args) { 
 Animal animal = new Animal("小动物"); 
 System.out.println(animal.name); // 此时编译出错, 无法访问 name 
 } 
}
小结 : Java 中对于字段和方法共有四种访问权限
private: 类内部能访问 , 类外部不能访问
默认 ( 也叫包访问权限 ): 类内部能访问 , 同一个包中的类可以访问 , 其他类不能访问 .
protected: 类内部能访问 , 子类和同一个包中的类可以访问 , 其他类不能访问 .
public : 类内部和类的调用者都能访问
【Java】面向对象编程_第11张图片

组合

和继承类似 , 组合也是一种表达类之间关系的方式 , 也是能够达到代码重用的效果
例如表示一个学校:
public class Student { 
 ... 
} 
public class Teacher { 
 ... 
} 
public class School { 
 public Student[] students; 
 public Teacher[] teachers; 
}
组合并没有涉及到特殊的语法 ( 诸如 extends 这样的关键字 ), 仅仅是将一个类的实例作为另外一个类的字段 ,这是我们设计类的一种常用方式之一
组合表示 has - a 语义
在刚才的例子中 , 我们可以理解成一个学校中 " 包含 " 若干学生和教师 .
继承表示 is - a 语义
在上面的 " 动物和猫 " 的例子中 , 我们可以理解成一只猫也 " " 一种动物 .

多态

向上转型

在上面的例子中 , 我们写了形如下面的代码:
//1、
Bird bird = new Bird("圆圆");

//或者如下:
//2、
Animal bird = new Bird("圆圆");
此时 2中的bird 是一个父类 (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 (子类) 的实例。

动态绑定

当子类和父类中出现同名方法的时候 , 再去调用会出现什么情况呢 ?
对前面的代码稍加修改 , Bird 类也加上同名的 eat 方法 , 并且在两个 eat 中分别加上不同的日志
// Animal.java 
public class Animal { 
 protected String name; 
 public Animal(String name) { 
 this.name = name; 
 } 
 public void eat(String food) { 
 System.out.println("我是一只小动物"); 
 System.out.println(this.name + "正在吃" + food); 
 }
} 
// Bird.java 
public class Bird extends Animal { 
 public Bird(String name) { 
 super(name); 
 } 
 public void eat(String food) { 
 System.out.println("我是一只小鸟"); 
 System.out.println(this.name + "正在吃" + food); 
 } 
} 
// Test.java 
public class Test { 
 public static void main(String[] args) { 
 Animal animal1 = new Animal("圆圆"); 
 animal1.eat("谷子"); 
 Animal animal2 = new Bird("扁扁"); 
 animal2.eat("谷子"); 
 } 
} 
// 执行结果
我是一只小动物
圆圆正在吃谷子
我是一只小鸟
扁扁正在吃谷子

【Java】面向对象编程_第12张图片

此时, 我们发现:

animal1 animal2 虽然都是 Animal 类型的引用 , 但是 animal1 指向 Animal 类型的实例 , animal2 指向Bird 类型的实例
针对 animal1 animal2 分别调用 eat 方法 , 发现 animal1.eat() 实际调用了父类的方法 ,
animal2.eat() 实际调用了子类的方法
因此 , Java , 调用某个类的方法 , 究竟执行了哪段代码 ( 是父类方法的代码还是子类方法的代码 ) , 要看究竟这个引用指向的是父类对象还是子类对象 . 这个过程是程序运行时决定的 ( 而不是编译期 ), 因此称为 动态绑定

简而言之动态绑定也称运行时绑定:通过父类引用调用父类和子类同名的覆盖方法。

编译时绑定:通过函数的重载实现的。编译的时候,会根据你给的参数的个数和类型,在编译期,确定你最终调用的一个方法。

方法重写

针对刚才的 eat 方法来说 :
子类实现父类的同名方法 , 并且参数的类型和个数完全相同 , 这种情况称为 覆写 / 重写 / 覆盖 (Override)
重写:
1、方法名相同
2、参数列表相同(个数+类型)
3、父子类的情况下
4、返回值一般相同(协变类型返回值不相同)
协变类型如下图所示:
【Java】面向对象编程_第13张图片

关于重写的注意事项
1. 重写和重载完全不一样 不要混淆。
2. 普通方法可以重写 , static 修饰的静态方法不能重写,final、private修饰的方法不能被重写。
3. 重写中子类的方法的访问权限不能低于父类的方法访问权限
4. 重写的方法返回值类型不一定和父类的方法相同 ( 但是建议最好写成相同 , 特殊情况除外 )
另外 , 针对重写的方法 , 可以使用 @Override 注解来显式指定

// Bird.java 
public class Bird extends Animal { 
 @Override 
 private void eat(String food) { 
 ... 
 } 
}
有了这个注解能帮我们进行一些合法性校验 . 例如不小心将方法名字拼写错了 ( 比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法 , 就会编译报错 , 提示无法构成重写
我们推荐在代码中进行重写方法时 显式加上 @Override 注解

理解多态

有了面的向上转型 , 动态绑定 , 方法重写之后 , 我们就可以使用 多态 (polypeptide) 的形式来设计程序了 。我们可以写一些只关注父类的代码 , 就能够同时兼容各种子类的情况。
例如:
class 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("♣"); 
 } 
} 
/我是分割线// 
// Test.java 
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 对应的实例相关 ), 这种行为就称为 多态。

抽象类

在刚才的打印图形例子中 , 我们发现 , 父类 Shape 中的 draw 方法好像并没有什么实际工作 , 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的 . 像这种没有实际工作的方法 , 我们可以把它设计成一个 抽象方法 (abstract class)。
abstract class Shape { 
 abstract public void draw(); 
}
注意:
1、在 draw 方法前加上 abstract 关键字 , 表示这是一个抽象方法 . 同时抽象方法没有方法体 { }, 不能执行具体 代码
2、对于包含抽象方法的类 , 必须加上 abstract 关键字表示这是一个抽象类。
3、抽象类不能直接实例化,只能被继承。
4、一个普通类如果继承了抽象类,要重写抽象类中所有抽象方法。
5、一个抽象类A如果继承了另一个抽象类B,那么A可以不实现B中的抽象方法。
6、结合上一点(第五点),如果A再次被一个普通类C继承之后,C中需要重写A,B中所有抽象方法。
7、抽象类和抽象方法都不可以被final修饰。
Shape shape = new Shape(); 
// 编译出错
Error:(30, 23) java: Shape是抽象的; 无法实例化
抽象方法不能是 private
abstract class Shape { 
 abstract private void draw(); 
} 
// 编译出错
Error:(4, 27) java: 非法的修饰符组合: abstract和private
抽象类中可以包含其他的非抽象方法 , 也可以包含字段 这个非抽象方法和普通方法的规则都是一样的 , 可以被重写 ,也可以被子类直接调用
abstract class Shape { 
 abstract public void draw(); 
 void func() { 
 System.out.println("func"); 
 } 
} 
class Rect extends Shape { 
 ... 
} 
public class Test { 
 public static void main(String[] args) { 
 Shape shape = new Rect(); 
 shape.func(); 
 } 
} 
// 执行结果
func

接口

接口是抽象类的更进一步 抽象类中还可以包含非抽象方法 和字段接口中包含的方法都是抽象方法 , 字段只能包含静态常量。
在刚才的打印图形的示例中 , 我们的父类 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、使用 interface 定义一个接口
2、接口中的方法一定是抽象方法 , 因此可以省略 abstract
3、接口中的方法一定是 public, 因此可以省略 public (接口当中的成员变量默认是public static final修饰的)
4、Cycle 使用 implements 继承接口此时表达的含义不再是 " 扩展 ", 而是 " 实现 "
5、在调用的时候同样可以创建一个接口的引用 , 对应到一个子类的实例
6、接口不能单独被实例化
7、接口中只能包含抽象方法 对于字段来说 , 接口中只能包含静态常量 (fifinal static)。
interface IShape { 
 void draw(); 
 public static final int num = 10; 
}
其中的 public, static, final 的关键字都可以省略 . 省略后的 num 仍然表示 public 的静态常量 .
提示 :
1. 我们创建接口的时候 , 接口的命名一般以大写字母 I 开头 .
2. 接口的命名一般使用 " 形容词 " 词性的单词 .
3. 阿里编码规范中约定 , 接口中的方法和属性不要加任何修饰符号 , 保持代码的简洁性 .
8、接口当中的普通方法,不能有具体的实现。非要实现,只能通过关键字default来修饰这个方法。
【Java】面向对象编程_第14张图片
9、当一个类实现了一个接口,就必须要重写接口当中的抽象方法。

比较:(继承与接口)
扩展 (extends) vs 实现 (implements)
扩展指的是当前已经有一定的功能了 , 进一步扩充功能
实现指的是当前啥都没有 , 需要从头构造出来

接下来介绍一个常用接口Comparator接口这是一个比较接口,介绍如下:

【Java】面向对象编程_第15张图片

 举个例子:

class Student {
    public int age;
    public String name;
    public double score;

    public Student(int age, String name, double score) {
        this.age = age;
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}
class AgeComparator implements Comparator {

    @Override
    public int compare(Student o1, Student o2) {
        return o1.age-o2.age;
    }
}
class NameComparator implements Comparator{
    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}
class ScoreComparator implements Comparator{
    @Override
    public int compare(Student o1, Student o2) {
        return (int)(o1.age-o2.age);
    }
}

public class Test {
    public static void main(String[] args) {
        Student[] students=new Student[3];
        students[0]=new Student(12,"allay",92.0);
        students[1]=new Student(8,"belly",91.0);
        students[2]=new Student(16,"cidy",96.0);
        AgeComparator ageComparator=new AgeComparator();
        ScoreComparator scoreComparator=new ScoreComparator();
        NameComparator nameComparator=new NameComparator();
        System.out.println(ageComparator.compare(students[0],students[1]));
        System.out.println(scoreComparator.compare(students[0],students[1]));
        System.out.println(nameComparator.compare(students[0],students[1]));
  }
}

【Java】面向对象编程_第16张图片

它可以实现多种数据类型的比较,用起来方便。

小结

以上就是今天的内容了,欢迎大家✌关注、✌点赞、✌留言,有什么问题都可以在评论区留言哦!

【Java】面向对象编程_第17张图片

你可能感兴趣的:(java)