面向对象

面向对象编程

包(package) 是组织类的一种方式.
使用包的主要目的是保证类的唯一性.

导入包中的类
可以使用 import java.util.导入各种需要的类
静态导入
使用 import static 可以导入包中的静态的方法和字段
将类放到包中
基本规则
在文件的最上方加上一个 package 语句指定该代码在哪个包中.
包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 ).
包名要和代码路径相匹配. 例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存
储代码.
如果一个类没有 package 语句, 则该类被放到一个默认包中.
包的访问权限控制
如果某个成员不包含 public 和 private 关键字, 此时这个成员可以在包内部的其他类使用, 但是不能在包外部的类使
用.

继承

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

// 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 + "正在飞 ︿( ̄︶ ̄)︿");
	}
}

我们在代码中发现了很多的冗余的代码时,就可以通过继承来删除带冗余的代码,其余的代码保留事物所独有的特性

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

语法规则

class 子类 extends 父类 {
}

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

protected 关键字
如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了我们 “封装” 的初衷.
两全其美的办法就是 protected 关键字

对于类的调用者来说, protected 修饰的字段和方法是不能访问的
对于类的 子类 和 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的

Java 中对于字段和方法共有四种访问权限
private: 类内部能访问, 类外部不能访问
默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
public : 类内部和类的调用者都能访问

final 关键字
final 关键字也能修饰类, 此时表示被修饰的类就不能被继承
final 关键字的功能是 限制 类被继承
“限制” 这件事情意味着 “不灵活”. 在编程中, 灵活往往不见得是一件好事. 灵活可能意味着更容易出错.用 final 修饰的类被继承的时候, 就会编译报错, 此时就可以提示我们这样的继承是有悖这个类设计的初衷的
String 字符串类, 就是用 final 修饰的, 不能被继承
super关键字
在子类的构造方法内部,调用父类的构造方法,super();显示调用父类的构造方法。所以,构造方法不是被继承的,而是在子类显示被调用的。
子类在构造方法的时候,先帮助父类构造方法,有参数的要写参数
super不能在静态方法中使用它。
在这里插入图片描述

多态

向上转型

Bird bird = new Bird("圆圆");
Animal bird2 = bird;
// 或者写成下面的方式
Animal bird2 = new Bird("圆圆");

此时 bird2 是一个父类 (Animal) 的引用, 指向一个子类 (Bird) 的实例. 这种写法称为 向上转型
bird2只能访问自己的,不能访问子类特有的属性或方法
为啥叫 “向上转型”?
在面向对象程序设计中, 针对一些复杂的场景(很多类, 很复杂的继承关系), 会画一种 UML 图的方式来表
示类之间的关系. 此时父类通常画在子类的上方. 所以我们就称为 “向上转型” , 表示往父类的方向转

向上转型本质:父类的引用,引用子类的对 象。
向上转型发生的时机:
直接赋值
方法传参
方法返回

向下转型

向上转型是子类对象转成父类对象, 向下转型就是父类对象转成子类对象. 相比于向上转型来说, 向下转型没那么常见,
但是也有一定的用途

为了让向下转型更安全, 我们可以先判定一下看看 animal 本质上是不是一个 Bird 实例, 再来转换

Animal animal = new Cat("小猫");
if (animal instanceof Bird) {
	Bird bird = (Bird)animal;
	bird.fly();
}

instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全了

动态绑定
父类引用 引用子类对象
通过父类引用 调用父类和子类的同名覆盖方法 此时就会发生运行时绑定
在运行时,父类和子类有相同的构造方法,可能会发生重写
在 Java 中, 调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引
用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为 动态绑定

重载
1.方法名相同
2.参数列表不同
3.返回值不做要求

重写
1.方法名相同
2.参数列表相同
3.返回值相同

关于重写的注意事项

  1. 重写和重载完全不一样. 不要混淆(思考一下, 重载的规则是啥?)
  2. 普通方法可以重写, static 修饰的静态方法不能重写
  3. 重写中子类的方法的访问权限不能低于父类的方法访问权限.
  4. 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外).
    在这里插入图片描述
    理解多态

有了面的向上转型, 动态绑定, 方法重写之后, 我们就可以使用 多态(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 对应的实例相关), 这种行为就称为多态.
多态顾名思义, 就是 “一个引用, 能表现出多种不同形态”

使用多态的好处

  1. 类调用者对类的使用成本进一步降低.
    封装是让类的调用者不需要知道类的实现细节.
    多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.
    因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低

  2. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
    例如我们现在需要打印的不是一个形状了, 而是多个形状. 如果不基于多态, 实现代码如下:

public static void drawShapes() {
	Rect rect = new Rect();
	Cycle cycle = new Cycle();
	Flower flower = new Flower();
	String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
	for (String shape : shapes) {
		if (shape.equals("cycle")) {
			cycle.draw();
		} else if (shape.equals("rect")) {
			rect.draw();
		} else if (shape.equals("flower")) {
			flower.draw();
		}
	}
}

如果使用使用多态, 则不必写这么多的 if - else 分支语句, 代码更简单

public static void drawShapes() {
	// 我们创建了一个 Shape 对象的数组.
	Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
	new Rect(), new Flower()};
	for (Shape shape : shapes) {
		shape.draw();
	}
}

什么叫 “圈复杂度” ?
圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如果有很
多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”. 如果一
个方法的圈复杂度太高, 就需要考虑重构.

  1. 可扩展能力更强.
    如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低
class Triangle extends Shape {
	@Override
	public void draw() {
		System.out.println("△");
	}
}

对于类的调用者来说(drawShapes方法), 只要创建一个新类的实例就可以了, 改动成本很低.
而对于不用多态的情况, 就要把 drawShapes 中的 if - else 进行一定的修改, 改动成本更高

抽象类

语法规则
在刚才的打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由
Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract
method), 包含抽象方法的类我们称为 抽象类(abstract class)

abstract class Shape {
	abstract public void draw();
}

在 draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体
代码).
对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类

注意事项

  1. 抽象类不能直接实例化.
Shape shape = new Shape();
// 编译出错
Error:(30, 23) java: Shape是抽象的; 无法实例化
  1. 抽象方法不能是 private 的
abstract class Shape {
abstract private void draw();
}
// 编译出错
Error:(4, 27) java: 非法的修饰符组合: abstractprivate
  1. 抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写,
    也可以被子类直接调用
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

4.当一个普通类继承了一个抽象类,那么当前这个普通类一定要重写抽象类当中的抽象方法。否则会报错

abstract class Shape {
	abstract public void draw();
}
class Rect extends Shape {
}
//代码报错

5.当普通类继承了抽象类,且不想实现抽象类当中的抽象方法时,那么这个普通类可以被修改为抽象类,此时就不需要进行实现了。

abstract class Shape {
	abstract public void draw();
}
abstract class Rect extends Shape {
}

抽象类的作用
抽象类存在的最大意义就是为了被继承.
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,
使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.
很多语法存在的意义都是为了 “预防出错”, 例如我们曾经用过的 final 也是类似. 创建的变量用户不去修改, 不就相当于常量嘛? 但是加上 final 能够在不小心误修改的时候, 让编译器及时提醒我们.
充分利用编译器的校验, 在实际开发中是非常有意义的.

接口

接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量

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();
	}
}

使用 interface 定义一个接口
接口中的方法一定是抽象方法, 因此可以省略 abstract
接口中的方法一定是 public, 因此可以省略 public
Cycle 使用 implements 继承接口. 此时表达的含义不再是 “扩展”, 而是 “实现”
在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例.
接口不能单独被实例化

接口中只能包含抽象方法. 对于字段来说, 接口中只能包含静态常量(final static).

interface IShape {
	void draw();
	public static final int num = 10;
}

其中的 public, static, final 的关键字都可以省略. 省略后的 num 仍然表示 public 的静态常量

实现多个接口
有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.
然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果

interface D extends A,B,C
//可以实现,此时extends为扩展

Clonable 接口和深拷贝
Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”. 但是要想合法调用 clone 方法, 必须要先
实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常

class Animal implements Cloneable {
	private String name;
	@Override
	public Animal clone() {
		Animal o = null;
		try {
			o = (Animal)super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return o;
	}
}
public class Test {
	public static void main(String[] args) {
	Animal animal = new Animal();
	Animal animal2 = animal.clone();
		System.out.println(animal == animal2);
	}
}
// 输出结果
// false

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值