java面向对象(OOP)
面向过程&面向对象
- 面向过程
- 第一步做什么, 第二步做什么
- 微观操作
- 面向对象
- 分类的思维
- 适合处理复杂的, 多人协作的问题
对于描述复杂的事物, 为了从宏观上把握, 从整体上合理分析, 需要面向对象分析整个系统
但是, 具体到微观操作, 仍然需要面向过程
面向对象
-
Object-Oriented Programming, OOP
-
本质: 以类的方式组织代码, 以对象的方式封装数据
-
特性
-
封装
-
继承
-
多态
-
封装
- 高内聚: 隐藏对象的属性和实现细节,仅对外公开接口
- 属性私有, 通过get和set来获取
- 在get和set方法内部, 可以进行一些合法性检查(比如, 年龄不能是999岁), 规避掉直接修改的风险
继承
修饰符
-
extends
- 在Java中,没有明确写
extends
的类,编译器会自动加上extends Object
- Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有
Object
特殊,它没有父类
- 在Java中,没有明确写
-
protected
- 子类无法访问父类的
private
字段或者private
方法 -
protected
关键字可以把字段和方法的访问权限控制在继承树内部,一个protected
字段和方法可以被其子类,以及子类的子类所访问 - 四个优先级: public, protected, default, private
- 子类无法访问父类的
-
final
- 一般情况下,只要某个class没有
final
修饰符,那么任何类都可以从该class继承。
- 一般情况下,只要某个class没有
构造方法
-
传入的参数, 直接初始化
public Person(String name, int age) { this.name = name; this.age = age; }
-
super
关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName
。class Person { //属性 private String name; private int age; //初始化 public Person(String name, int age) { super();//调用父类构造方法 this.name = name; this.age = age; } //方法 public String getName() { return this.name; } public int getAge() { return this.age; } }
-
构造顺序
public class Person{ public Person{ System.out.println("父类构造"); } } public class Student extends Person{ public Student{ System.out.println("子类构造"); } } public class Test{ public static void main(String[] args){ Student student = new Student(); } } /*输出 父类构造 子类构造 */
父类先构造完毕, 然后构造子类
向上转型与向下
-
向上转型
-
父类的引用指向子类
引用类型是把数据和功能组织在一起的结构,也称对象定义,它描述了自己的对象应有的属性和方法。
萌新的简单理解: 栈里面存的是Person类型的引用, 但其指向的堆里面存的是Student类型
Person p = new Student();
-
方法被重写*(或者说方法被覆盖)*
class A { public void print() { System.out.println("A:print"); } } class B extends A { public void print() { System.out.println("B:print"); } } public class Hello { public static void main(String args[]) { A a = new B(); a.print(); } //B:print
向上转型丢失子类的方法, 但是子类override父类方法之后, 子类方法是有效的
本来子类的方法应该丢失, 但由于父类也有这个方法, 所以这个子类的方法覆盖了父类的方法
父类有的, 就会被覆盖.
父类没有的, 就会丢失.
静态方法不会被重写, 因此不会被覆盖.
class A { public static void print() { System.out.println("A"); } } class B extends A { public static void print() { System.out.println("B"); } } public class Test { public static void main(String args[]) { A a = new B(); a.print(); } //A
方法可重写,属性不可重写。父类的方法被子类覆盖,父类的属性不被子类覆盖。
public class Son extends Father { public String name = "子类属性"; public void show() { System.out.println("子类方法"); } public static void main(String[] args) { Father son = new Son(); son.show(); System.out.println(son.name); } } class Father { public String name = "父类属性"; public void show() { System.out.println("父类方法"); } } /*输出: 子类方法 父类属性 */
-
作用
减少重复代码,传入参数
public class Test { public static void main(String[] args) { A(new Daughter());//可视为Father daughter = new Daughter(); A(new Son());//可视为Father son = new Son(); } public static void A(Father children){ children.eat(); } }
-
-
向下转型
-
父类强制转换为子类, 从而调用子类的独有的方法(在工程中很少用)
-
instanceof: 判断某对象是否是某类的实例
A a = new B(); //向上转型 (B类是A的子类) a instanceof A; //返回true. a instanceof B; //返回true a instanceof C; //返回false
-
向上转型传入参数, instanceof分辨是哪个子类
class A { public void print() { System.out.println("A:print"); } } class B extends A { public void print() { System.out.println("B:print"); } public void funcB(){ System.out.println("funcB"); } } class C extends A { public void print() { System.out.println("C:print"); } public void funcC(){ System.out.println("funcC"); } } public class Test{ public static void func(A a) { a.print(); if(a instanceof B) { B b = (B)a; //向下转型,通过父类实例化子类 b.funcB(); //调用B类独有的方法 } else if(a instanceof C) { C c = (C)a; //向下转型,通过父类实例化子类 c.funcC(); //调用C类独有的方法 } } public static void main(String args[]) { func(new A()); func(new B()); func(new C()); } }
-
-
重写override和重载overload
重写是子类父类才有的: 子类重写父类的方法(针对于方法, 属性不能被重写. 因此父类的属性不会被覆盖)
-
区别: 参数列表必须相同, 不然变成重载了
-
修饰符: 范围可能扩大, 但不能缩小 public > protected > default > private
-
抛出的异常: 范围可以缩小, 但不能变大 ClassNotFoundException --> Exception(大)
-
**重写override是覆盖的意思, 即覆盖之前的方法. 之前的方法不能使用了, 被覆盖掉了. **
而重载overload是方法的复用. 重载的方法, 和之前的方法都能使用.
-
多态
-
多态基于override
-
允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。
-
功能拓展基于重写override
不知道实际传入的参数类型是Person, 还是Person的某个子类
public void runTwice(Person p) { p.run(); }
-
父类利用多态派生子类, 覆写相应的方法
多态使用例子, 详见下文抽象类.
其他
-
static
-
静态的属性和方法, 可以通过类调用.
类加载过程中, 将静态变量, 静态方法, 常量存入方法区
-
匿名代码块和静态代码块
public class Person{ { System.out.println("匿名代码块"); } static{ System.out.println("静态代码块"); } public Person(){ System.out.println("构造方法"); } public static void main(String[] args){ Person person1 = new Person(); System.out.println("============="); Person person2 = new Person(); } } /*输出 静态代码块 匿名代码块 构造方法 ============= 匿名代码块 构造方法 */
静态代码块只执行一次.
匿名代码块每次执行: 用于赋初始值
-
-
import
-
为了让同名的方法, 变量可以区分, 把它放入不同的package中
使用这些方法, 变量的时候, 需要输入全名: 包名.方法名
但是, import导入包里面的类, 可以不用输入包名, 直接输入对应的方法名即可. 这样做, 不用再写很长的名称, 很方便.
-
两种导入方式
导入子包内某个类: import java.util.ArrayList;
导入子包内所有类: import java.util.*;(按需导入声明)
一般大型项目不用按需导入
因为: 编译慢, 命名冲突, 可读性差, 无名包问题
-
import static 静态导入
import导入类和接口, 但是对于类里面的变量和方法只能静态导入. (因为其必须挂靠于类或接口)
//直接导入具体的静态变量、常量、方法方法,注意导入方法直接写方法名不需要括号。 import static java.lang.Math.random;//方法 import static java.lang.Math.PI;//属性 import static java.lang.Math.*;//按需导入
-
抽象类与接口
抽象类
首先我们需要了解什么是抽象.
抽象, abstract. 英文有简化的意思. 即抽象, 就是把很多信息压缩简化.
例如, 一个学生类, 有如下属性: 学号, 姓名, 性别, 出生日期.
但是, 除了这些, 必定会有高矮胖瘦美丑等外貌的属性, 父母健在或双亡等家庭的属性.
因为我们不需要这些信息, 所以我们没有写入学生类.
我们把很多信息去掉, 简化, abstract的过程, 就是抽象.
什么是抽象类?
抽象类即类的抽象.
比如狗有很多种, 比如牧羊犬, 柴犬, 拉布拉多, 博美, 等.
牧羊犬等这些狗的细分种类, 都会抽象为"狗"类. 这些类都会继承自"狗"类. 即他们的固有的共性就是"狗"类.
这个"狗"类, 即为抽象类.
-
特点
-
abstract修饰
-
含有抽象方法, 但也可以含有普通的成员方法, 成员变量.
abstract void fun();
抽象方法: 只有声明, 没有具体的实现
抽象方法必须为public或protected. 缺省时, 默认为public
-
抽象类不能被实例化。
-
抽象类里面的抽象方法必须全部被子类实现,如果子类不能全部实现,那么子类必须也是抽象类。
-
-
使用
[public] abstract class ClassName { abstract void fun(); }
-
抽象类主要用于继承
被继承之后, 子类会重写(override)抽象类中的抽象方法, 从而可根据子类的不同需求而进行实现.
比如:
抽象类
飞行物类
有抽象方法abstract void fly();
.飞机类
和鸟类
可以继承飞行物类
.而
飞机类
和鸟类
有不同的飞行方式, 即可重写fly(), 按照各自的飞行方式分别进行实现.
-
接口
什么是接口?
抽象类由于继承而存在, 把所有东西传承给子类.
而接口为了附加而存在, 为了把某东西附加给某个类.
一个是全盘接受, 一个是附加.
例如门都会开和关. 这是门本身固有的属性. (抽象类)
但是有些门可以报警, 有些门可以密码解锁, (接口)
我们可以给某些门加上这些接口.
但不是所有的门都一定要有报警系统, 密码解锁. 只是一种附加行为.
接口是规范,是一个大家都遵守的规则。
如同法律一样,所有人都遵守,就能在一起富强*文明和谐。
-
特点
-
interface修饰
-
接口中可以含有 变量和方法。(但一般不在接口中定义变量)
-
接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误)
而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误)
-
接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。
接口里面的方法也必须全部被子类实现,如果子类不能实现那么子类必须是抽象类。
-
一个类可以实现多个接口
-
-
使用
[public] interface InterfaceName { }
-
实现一个接口需要用到关键字
implements
,它表示:“我这个类遵从了接口的协议,如果你想使用我,看接口就行了,具体实现不用关心。”class ClassName implements Interface1,Interface2,[....]{ }
-
和抽象类的向上转型一样, 实现接口的类也可以向上转型为接口.
interface A { void func(); } class B implements A { @Override public void func() { System.out.println("func"); } } public class Test { public static void func(A a) { a.func(); } public static void main(String[] args) { B b = new B(); func(b); } }
-
接口的适配器模式
-
适配器模式——针对调用者的需求对原有的接口进行转接。
如果类直接实现该接口的话,就需要对两个方法进行实现。
如果我们只需要对其中一个方法进行实现的话,就可以使用一个抽象类作为中间件,即适配器(AdapterCoach)。
用这个抽象类实现接口,并对抽象类中的方法置空(方法体只有一对花括号,因为该方法为非抽象方法)。
这时候,新类就可以绕过接口,继承抽象类,我们就可以只对需要的方法进行覆盖,而不是接口中的所有方法。
-
-
区别
-
禁止多继承
继承多个父类时, 这些父类有同名的不同方法, 会造成二义性. (还有其他原因, 此处简单理解即可)
java禁止多继承, 但引入了interface(接口). 而接口可以实现多个,弥补了不能多继承的缺陷
-
固有与附加 (先天与后天)
抽象类是子类的整体抽象, 一种模板式设计. 是先天的, 本身固有的属性.
接口是延伸的附加行为, 是后天的, 附加的属性.
接口用途广泛. 如报警装置, 可以使用在门, 室内, 手机, 车…
-
修改
抽象类可以直接修改该类, 不需要改动子类.
接口更新的话, 需要对每个实现该接口的类进行改动.
-
设计
接口的设计目的,是对类的行为进行约束(更准确的说是一种“有”约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。对“接口为何是约束”的理解,我觉得配合泛型食用效果更佳。
而抽象类的设计目的,是代码复用。当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。在这个抽象类中实现了B,避免让所有的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。正是因为A-B在这里没有实现,所以抽象类不允许实例化出来(否则当调用到A-B时,无法执行)。
-
语法层面
-
抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
-
抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
-
接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
-
一个类只能继承一个抽象类,而一个类却可以实现多个接口。
-
参考资料: