六、面向对象 进阶
封装、继承、多态-----------java三大特性
- 类、接口间关系
- 类 与 类 之间是:单根继承
- 类 与 接口 之间是:多实现
- 接口 与 接口 之间是:多继承
1 - 封装
核心:只对需要的类可见,用户无需知道对象内部方法的实现细节,但可以根据对象提供的外部接口(对象名和参数)访问该对象
同时也是为了减少代码冗余
特性:
安全特性;
便于使用特性;
提供重复性的特性;
好处:
- 隐藏信息和实现细节
通过控制访问权限可以将不希望客户端程序员看见的信息隐藏起来(如客户的银行密码需要保密,便需要只对客户开放权限)
- 实现了专业的分工
将能实现某一特定功能的代码封装成一个单独的类,在程序员需要使用的时候可以随时调用,从而实现了专业的分工
- 提高对象数据的安全性
- 如果不使用封装,很容易赋值错误,并且任何人都可以更改,造成信息的不安全。
- 封装以后,设置类的属性为private(关键字),不能使用对象名.属性名的方式直接访问对象的属性,提高了其安全性。
- 封装时,类的属性为private,类的方法为public;
1 - 1 成员的访问权限
private | default | protected | public | |
---|---|---|---|---|
同一个类 | 可用 | 可用 | 可用 | 可用 |
同一个包中的类 | 可用 | 可用 | 可用 | |
子类 | 可用 | 可用 | ||
其他包中的类 | 可用 |
2 - 继承(extends)
将多个类具有共同特征和行为封装成一个类,实现一次定义,减少代码冗余,实现了代码的复用性
一个拥有自己独特特征的类(子类 扩展类) 继承(extends) 拥有共同特征的类(父类 基类)
子类继承父类,拥有父类的属性,因而子类的对象可以给共同属性赋值
java中一个类只能继承一个类(单根继承)
继承是一个很普遍的现象:
如果一个类明确指明了继承(extends)自某一个类,则这个类就是他的父类
如果一个类并未通过extends指明继承自某一个类,则这个类继承自Object类(顶级父类—最大的类)
public class Animal { // 父类
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Dog extends Animal{ // 子类
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public String toString() {
return "Dog{" +
"type='" + type + '\'' +
"} " + super.toString();
}
}
public class Cat extends Animal{
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Cat{" +
"address='" + address + '\'' +
"} " + super.toString();
}
}
public class Main { // 测试类
public static void main(String[] args) {
Cat cat = new Cat();
// 共同特征(通过继承获取)
cat.setName("hello KiTi");
cat.setAge(3);
// 独有特征
cat.setAddress("北京");
System.out.println(cat);
Dog dog = new Dog();
// 共同特征(通过继承获取)
dog.setName("旺财");
dog.setAge(4);
// 独有特征
dog.setType("拉布拉多");
System.out.println(dog);
}
}
3 - 多态
同一个行为具有多个不同的表现形式。
封装和继承是多态的表现形式
多态实现的三个充要条件:
- 继承
- 重写父类方法
- 父类引用指向子类对象
-
此处
Fruit apple = new Apple();
父类的引用指向子类的对象,这便是多态的一种体现(向上转型),因为Apple继承与Fruit并重写了eatFruit(),所以能够表现多种状态的形式。 -
向上转型(小范围 ------> 大范围)
Fruit apple = new Apple();
-
动态绑定(自动实现的)
向上转型后,用父类的变量接收子类的对象(
Fruit apple = new Apple();
),运行时会找父类类型变量(apple
)中存放数据的实际类型(Apple()
),并执行这个实际类(Apple()
)中的对应方法(eatFruit()
),而不是父类(Fruit
)中的方法(eatFruit()
),用static修饰可以解决此问题
/**
* 水果
*/
public class Fruit {
String fruit; //
public String getFruit() {
return fruit;
}
public void setFruit(String fruit) {
this.fruit = fruit;
}
public void eatFruit(){
System.out.println("吃水果!");
}
@Override
public String toString() {
return "Fruit{" +
"fruit='" + fruit + '\'' +
'}';
}
}
/**
* 苹果
*/
class Apple extends Fruit{
private String color; // 颜色
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Apple{" +
"color='" + color + '\'' +
"} " + super.toString();
}
@Override
public void eatFruit(){
color = "红色";
super.fruit = "苹果";
System.out.println("吃" + color + fruit);
}
}
/**
* 橙子
*/
class Orange extends Fruit{
private String exterior; // 外观
public String getExterior() {
return exterior;
}
public void setExterior(String exterior) {
this.exterior = exterior;
}
@Override
public void eatFruit() {
exterior = "圆的";
super.fruit = "橘子";
System.out.println("吃" + exterior + fruit);
}
@Override
public String toString() {
return "Orange{" +
"exterior='" + exterior + '\'' +
"} " + super.toString();
}
}
3 - 1 向上转型的应用场景
向上转型会隐藏子类扩展出来的功能
public class Test {
// 将参数列表改为各种水果的父类,只需要定义一次,就可以重复利用,而不需要利用方法重写来实现同一个动作
public static void DoThing(Fruit fruit){
System.out.println("---------------------------------------");
fruit.eatFruit();
System.out.println("---------------------------------------");
}
//--------------------方法重写,会导致代码冗余--------------------
/*public static void DoThing(Orange orange){
System.out.println("---------------------------------------");
fruit.eatFruit();
System.out.println("---------------------------------------");
}
public static void DoThing(Apple apple){
System.out.println("---------------------------------------");
fruit.eatFruit();
System.out.println("---------------------------------------");
}*/
//--------------------------------------------------------------
}
public static void main(String[] args) {
DoThing(new Apple());
DoThing(new Orange());
}
}
3 - 2 向下转型
大范围 -----> 小范围 (强制类型转换):把父类说成是子类
向下转型要求:父类必须拥有子类的属性,不能拥有就会出现类型转换异常
Apple apple = (Apple) new Fruit();
-
向下转型的应用场景
明确知道了传入的数据是什么类型,才能进行向下转型
如果要进行向下转型,必须先向上转型,在向下转型,不然会出错
public class Test { public static void main(String[] args) { // Apple apple = (Apple) new Fruit(); // 单纯的进行强制类型转换会出现异常 Fruit fruit = DoThing(1); if (fruit instanceof Orange){ Orange orange = (Orange) fruit; System.out.println(orange); }else{ Apple apple = (Apple) fruit; System.out.println(apple); } } public static Fruit DoThing(int number){ if (number == 0){ Orange orange = new Orange(); orange.setFruit("橘子"); orange.setExterior("圆的"); return orange; }else{ Apple apple = new Apple(); apple.setFruit("苹果"); apple.setColor("红色"); return apple; } } }
4 - 组合
组合就是将对象引用置于新类中。
组合也是一种提高代码复用性的方式。
如果你不想让类有更多的扩展功能,你需要记住一句话多用组合,少用继承
-
组合和继承是有区别的
特征 组合 继承 关系 组合是一种 has - a
的关系,可以理解为有一个继承是一中 is - a
的关系,可以理解为是一个耦合性 组合是一种松耦合的关系 继承双方紧耦合 是否具有多态 组合不具备多态和向上转型 继承是多态的基础,可以向上转型 时期 组合是运行期绑定 继承是编译期绑定 -
Fruit类引用了Apple类、Orange类,从而调用他们各自的属性和方法
public class Test{
public static void main(String[] args) {
Fruit fruit = new Fruit();
fruit.setFruit("苹果");
Apple apple = new Apple();
apple.setColor("红的");
fruit.setApple(apple);
Orange orange = new Orange();
orange.setExterior("圆的");
fruit.setOrange(orange);
System.out.println(fruit);
}
}
/**
* 水果
*/
public class Fruit {
private String fruit;
private Apple apple;
private Orange orange;
public Apple getApple() {
return apple;
}
public void setApple(Apple apple) {
this.apple = apple;
}
public Orange getOrange() {
return orange;
}
public void setOrange(Orange orange) {
this.orange = orange;
}
public String getFruit() {
return fruit;
}
public void setFruit(String fruit) {
this.fruit = fruit;
}
public void eatFruit(){
System.out.println("吃水果!");
}
@Override
public String toString() {
return "Fruit{" +
"fruit='" + fruit + '\'' +
", apple=" + apple +
", orange=" + orange +
'}';
}
}
/**
* 苹果
*/
class Apple{
private String color; // 颜色
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Apple{" +
"color='" + color + '\'' +
'}';
}
}
/**
* 橙子
*/
class Orange{
private String exterior; // 外观
public String getExterior() {
return exterior;
}
public void setExterior(String exterior) {
this.exterior = exterior;
}
@Override
public String toString() {
return "Orange{" +
"exterior='" + exterior + '\'' +
'}';
}
}
5 - 接口和抽象类
5 - 1 接口(interface)
因为接口不能被实例化,故接口中不能有任何构造方法
如果一个事务只有共同行为(功能)而没有共同特征(属性),则用接口定义
接口注重功能,所以接口中只有方法,且默认为抽象方法(即默认是用
public abstract
修饰的)接口可以多实现(implements)
// 定义接口
public interface Shape {
void area(); // 计算面积
void perimeter(); // 计算周长
}
// 实现接口
public class Circle implements Shape {
@Override
public void area() {
System.out.println("计算Circle的面积");
}
@Override
public void perimeter() {
System.out.println("计算Circle的周长");
}
}
5 - 1 - 1 接口实现解耦合
耦合:个体相互之间有依赖关系
好处:
- 便于修改
- 业务并行
- 分工协作
- 通用性强(低耦合)
- 案例:
public class Test {
public static void main(String[] args) {
Computer computer = new Computer();
computer.setMainBoard("I7 的主板"); // 同一块主板可以使用不同的cpu
computer.setCpu(new I3());
computer.getCpu().jiSuan();//调用i3主板的计算功能
computer.setCpu(new I5());
computer.getCpu().jiSuan();//i5主板的计算功能
computer.setCpu(new I7());
computer.getCpu().jiSuan();//i7主板的计算功能
}
}
public class Computer {
private String mainBoard; //主板
private Cpu cpu; // 相当于电脑预留了一个cpu接口,可以安装任意版本
public String getMainBoard() {
return mainBoard;
}
public void setMainBoard(String mainBoard) {
this.mainBoard = mainBoard;
}
public Cpu getCpu() {
return cpu;
}
public void setCpu(Cpu cpu) {
this.cpu = cpu;
}
@Override
public String toString() {
return "Computer{" +
"mainBoard='" + mainBoard + '\'' +
", cpu=" + cpu +
'}';
}
}
public interface Cpu { void jiSuan(); // cpu的计算功能}class I3 implements Cpu{ @Override public void jiSuan() { System.out.println("I3 Cpu 的计算功能!"); }}class I5 implements Cpu{ @Override public void jiSuan() { System.out.println("I5 Cpu 的计算功能!"); }}class I7 implements Cpu{ @Override public void jiSuan() { System.out.println("I7 Cpu 的计算功能!"); }}
5 - 2 抽象类(abstract)
抽象类约束没有接口严格,抽象类中可以定义 构造方法、抽象方法、普通属性、普通方法、静态属性和静态方法
抽象方法一定要存在于抽象类中,但抽象类中不一定有抽象方法,也可以有具体的方法
抽象类不能创建对象(
Fruit fruit = new Fruit()
会报错)
public abstract class Fruit{ // 父类,此时父类也是抽象类
private String fruit;
public String getFruit();
public void setFruit(String fruit) {
this.fruit = fruit;
}
public abstract void eatFruit(); // 抽象方法
}
class Apple extends Fruit{ // 子类
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public void eatFruit() { // 必须要添加父类中的行为,否则会报错
System.out.println("吃"+color+"苹果!");
}
}
6 - static
在方法区中会独立分配一块空间用来存放static修饰的数据,static修饰的东西都会存入其中,并且其中的数据是通用的
java中的关键字-----静态的
可以修饰属性和方法
static用在没有创建对象时调用方法/属性
-
静态成员变量
可以修饰全局变量,不能修饰局部变量
class test{ public static String name = "码农"; // 可以修饰全局变量(类变量) public void Do{ static String name2 = "码羊"; // 报错,不能修饰局部变量 } }
-
静态方法
可以使用类名.方法名调用
static
不能修饰 get方法、set方法、构造方法、抽象方法、类同一个类中调用类方法,可以省略类名
在静态方法中 ** 不能访问 非静态方法和非静态成员变量**
public class Circle implements Shape { private static Double PI = 3.14; // 静态成员变量 private int r; // 非静态成员变量 // 静态方法 public static void area2(){ // 2*PI*r; // 报错,因为此为静态方法,而r为非静态成员变量 } @Override public void area() { System.out.println(PI*r*r); } @Override public void perimeter() { System.out.println("计算Circle的周长"); } }
-
静态代码快
用于类的初始化操作,进而提示程序性能
静态代码块随着类的加载而执行,因此,很多时候会将 只需要 执行一次的初始化操作放在static代码块中进行
public class StaicBlock { static{ System.out.println("I'm A static code block"); } }
7 - final
最后的,最终的
可以修饰类、属性(全局变量和局部变量)、方法
-
final修饰类时
一旦修饰,表示此类不能被继承
成员变量可以根据需要设定为final
注意:final中的所有成员方法会隐式的指定为final方法
-
final修饰方法时
修饰方法时,表示此方法不能被子类重写
只有在明确不希望此方法被子类重写时才会将其设定为final
-
final修饰变量时
final没有优先分配的功能,所以必须对其进行赋值(可以使用构造代码块对其进行赋值,因为构造代码块会在构造方法前执行,且调用几次构造方法,便加载几次构造代码块)
-
修饰基本数据类型
表示数据类型的值 不能 被改变
public class Circle implements Shape { private static Double PI = 3.14; private static final Double PII = 3.14; public static void main(String[] args) { PI = 4.5; // 可以正常修改 // PII = 3.5; // 报错 } }
-
修饰引用数据类型时
表示对其初始化后便 不能 在指向另一个对象
public class Test { public static void main(String[] args) { final Fruit fruit = new Fruit(); fruit.setFruit("苹果"); fruit = null; // 报错:Cannot assign a value to final variable 'fruit' } } class Fruit{ private String fruit; public String getFruit() { return fruit; } public void setFruit(String fruit) { this.fruit = fruit; } }
-