java面向对象(OOP)
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类型的; 
- 
接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法; 
- 
一个类只能继承一个抽象类,而一个类却可以实现多个接口。 
 
- 
参考资料:
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号