文章目录
- 继承
- 继承的基本概念
- 继承的限制
- 继承小结
- 子类的实例化过程
- 方法的重写
- super关键字
- 继承的应用示例
- final关键字
- 抽象类
- 接口
- 多态性
- instanceof关键字
- 抽象类和接口的应用
- 抽象类应用—模板方法模式
- 接口应用—策略模式
- Object类
- 模式
- 简单工厂模式
- 静态代理模式
- 适配器模式
- 内部类
- 数据结构之链表
- 基本数据类型包装类
- 包与访问修饰符
- OO原则总结
继承
继承的基本概念
继承是从已有的类创建新类的过程
- 继承是面向对象三大特征之一
- 被继承的类称为父类(超类),继承父类的类称为子类(派生类)
- 继承是指一个对象直接使用另一对象的属性和方法。
- 通过继承可以实现代码重用
语法:
[访问权限] class 子类名 extends 父类名{类体定义;
}
示例:
public class Dog{private String name;private String sex;public void eat(){System.out.println(“吃饭”);}
}
public class HomeDog extends Dog{//类的定义
}
public class HuskyDog extends Dog{//类的定义
}
protected(受保护的访问权限修饰符,用于修饰属性和方法,使用protected修饰的属性和方法可以被子类继承)
继承的限制
继承的限制约定:
- Java只能实现单继承,也就是一个类只能有一个父类
- 允许多层继承,即:一个子类可以有一个父类,一个父类还可以有其他的父类。
- 继承只能继承非私有的属性和方法。
- 构造方法不能被继承
继承小结
- 继承是发生在多个类之间
- 继承使用关键字extends
- JAVA只能单继承,允许多层继承
- 被继承的类叫父类(超类),继承父类的类叫子类(派生类)
- 在父类中的非私有属性和方法可以被子类继承
- protected(受保护的访问权限修饰符),修饰的属性或方法可以被子类继承
- 构造方法不能被继承
- 创建对象会调用构造方法,调用构造方法不一定就是创建对象
- 实例化子类对象,会先调用父类的构造方法,如果父类中没有默认的构造方法,那么子类必须显示的通过super(…)来调用父类的带参构造方法,super也只能在子类构造方法中的第一句
继承的好处:
- 提高代码的复用性
- 提高代码的维护性
- 让类与类之间产生关系,是多态的前提
继承的缺点:
- 增强了类与类之间的耦合性
- 开发原则:高内聚,低耦合
子类的实例化过程
在子类进行实例化操作的时候,首先会先让其父类进行初始化操作。之后子类再自己进行实例化操作。
子类的实例化过程:
- 子类实例化时会先调用父类的构造方法
- 如果父类中没有默认的构造方法,在子类的构造方法中必须显示的调用父类的构造方法
结论:
- 构造方法只是用于初始化类中的字段以及执行一些初始化代码
- 调用构造方法并不代表会生成对象
方法的重写
方法重写(overriding method)
- 在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想做一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。
- 在子类和父类中,重写方法后,在调用时,以创建的对象类型为准,会调用谁的方法。
关于方法重写的一些特性:
- 发生在子父类中,方法重写的两个方法返回值、方法名、参数列表必须完全一致(子类重写父类的方法)
- 子类抛出的异常不能超过父类相应方法抛出的异常(子类异常不能大于父类异常)
- 子类方法的访问级别不能低于父类相应方法的访问级别(子类访问级别不能低于父类访问级别)
- 父类中的方法若使用private、static、final任意修饰符修饰,那么,不能被子类重写。
为什么要重写方法?或者方法重写的目的是什么?
- 若子类从父类中继承过来的方法,不能满足子类特有的需求时,子类就需要重写父类中相应的方法,方法的重写也是程序扩展的体现。
面试题 :overloading与overriding的区别?
super关键字
super可以完成以下的操作:
- 使用super调用父类中的属性,可以从父类实例处获得信息。
- 使用super调用父类中的方法,可以委托父类对象帮助完成某件事情。
- 使用super调用父类中的构造方法(super(实参)形式),必须在子类构造方法的第一条语句,调用父类中相应的构造方法,若不显示的写出来,默认调用父类的无参构造方法,比如:super();
this表示当前对象
使用super来调用父类的属性,方法,和构造方法
继承的应用示例
实现一个化妆品商城中的化妆品管理
- 定义一个化妆品类(Cosmetic)name,type,price
- 定义一个化妆品管理类(CosmeticManager)
- 实现进货功能
- 可以输出所有化妆品信息功能
- 使用继承实现一个可按单价排序输出所有化妆品的功能
- 使用继承实现一个只输出进口化妆品的功能
final关键字
使用final关键字完成以下的操作:
- 使用final关键字声明一个常量:修饰属性或者修饰局部变量(最终变量),也称为常量。
- 使用final关键字声明一个方法:该方法为最终方法,且只能被子类继承,但是不能被子类重写。
- 使用final关键字声明一个类:该类就转变为最终类,没有子类的类,fianl修饰的类无法被继承。
- 在方法参数中使用final,在该方法内部不能修改参数的值(在内部类中详解)
final应用:
- 定义一个常量:
public static final int NUM = 10;
- 定义一个final方法(不常用)
- 定义一个final类:通常在常量类中使用
//常量类:在该类中只有常量,通常是应用程序中公共的常量或标记
public final class Constant{public static final String SERVER_ROOT_URL = "http://www.baidu.com";public static final String CACHE_PATH = "data_cache";//....
}
抽象类
抽象类的基本概念
- 很多具有相同特征和行为的对象可以抽象为一个类;很多具有相同特征和行为的类可以抽象为 一个抽象类。
- 使用abstract关键字声明的类为抽象类。
定义一个抽象类
abstract class Animal{public abstract void move();
}
abstract class Person extends Animal{private String name;//... public abstract void eat();//抽象方法
}
//具体类
class Man extends Person{public void eat(){System.out.println("我是男人,我爱吃肉");}public void move(){System.out.println("我爱跑步");}
}
class Women extends Person{public void eat(){System.out.println("我是女人,我爱吃香蕉");}public void move(){System.out.println("我喜欢逛街");}
}
抽象类的规则:
- 抽象类可以没有抽象方法,有抽象方法的类必须是抽象类
- 非抽象类继承抽象类必须实现所有抽象方法
- 抽象类可以继承抽象类,可以不实现父类抽象方法。
- 抽象类可以有方法实现和属性
- 抽象类不能被实例化
- 抽象类不能声明为final
- 抽象类可以有构造方法
接口
接口的概念
- 接口是一组行为的规范、定义,没有实现(JDK1.8默认方法)
- 使用接口,可以让我们的程序更加利于变化
- 接口是面向对象编程体系中的思想精髓之一
- 面向对象设计法则:基于接口编程
接口的定义格式:
interface 接口名称{全局常量 ;抽象方法 ;
}
示例:
interface IEat{//public abstract void eat();void eat();//public static final int NUM = 10;int NUM = 10; }interface ISleep extends IEat{void sleep();
}//实现接口的类
class Girl implements IEat,ISleep{private String name;public Girl(String name){this.name = name;}public void eat(){System.out.println("我是"+name+"的女票,我爱吃香蕉");}public void sleep(){System.out.println("我爱睡觉");}
}
接口的使用规则:
- 定义一个接口,使用interface关键字
- 在一个接口中,只能定义常量、抽象方法,JDK1.8后可以定义默认的实现方法
- 接口可以继承多个接口:extends xxx,xxx
- 一个具体类实现接口使用implements关键字
- 一个类可以实现多个接口
- 抽象类实现接口可以不实现接口的方法
- 在接口中定义的方法没有声明 访问修饰符,默认为public
- 接口不能有构造方法
- 接口不能被实例化
面向对象设计原则:
- 对修改关闭,对扩展开放
- 面向接口编程
多态性
多态是面向对象三大特性之一
什么是多态性?
- 对象在运行过程中的多种形态。
- 多态性我们大概可以分为两类:
- 方法的重载与重写
- 对象的多态性
//用父类的引用指向子类对象(用大的类型去接受小的类型,向上转型、自动转换)
Chicken home = new HomeChicken();
结论:
- 在编程时针对抽象类型的编写代码,称为面向抽象编程(或面向接口编程)
- 父类通常都定义为抽象类、接口
对象的多态性:
- 对象多态性是从继承关系中的多个类而来,
向上转型: 将子类实例转为父类引用
格式:父类 父类对象 = 子类实例 ;-> 自动转换
以基本数据类型操作为例:int i = ‘a’ ;
(因为char的容量比int小,所以可以自动完成)
向下转型: 将父类实例转为子类实例
格式:子类 子类对象 = (子类)父类实例 ;强制转换
以基本数据类型操作为例:char c = (char)97;
因为整型是4个字节比char 2个字节要大,所以需要强制完成
多态性小结:
- 方法的重载与重写就是方法的多态性表现
- 多个子类就是父类中的多种形态
- 父类引用可以指向子类对象,自动转换
- 子类对象指向父类引用需要强制转换(注意:类型不对会报异常)
- 在实际开发中尽量使用父类引用(更利于扩展)
instanceof关键字
instanceof 是用于检查对象是否为指定的类型,通常在把父类引用强制转换为子类引用时要使用,以避免发生类型转换异常(ClassCastException)。
- 语法格式如下:对象 instanceof 类 型 --返回boolean类型值
- 示例:
if(homeChicken instanceof Chicken){//...
}
该语句一般用于判断一个对象是否为某个类的实例,是返回true,否返回false
父类的设计法则
通过instanceof关键字,我们可以很方便 的检查对象的类型,但如果一个父类的子类过多,这样的判断还是显得很繁琐,那么如何去设计一个父类呢?
- 父类通常情况下都设计为抽象类或接口,其中优先考虑接口,如接口不能满足才考虑抽象类。
- 一个具体的类尽可能不去继承另一个具体类,这样的好处是无需检查对象是否为父类的对象。
抽象类和接口的应用
抽象类应用—模板方法模式
模板方法模式(Templete Method):定义一个操作中的算法的骨架,而将一些可变部分的实现延迟到子类中。模版方法模式使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定的步骤。
接口应用—策略模式
策略模式(Strategy Pattern),定义了一系列的算法,将每一种算法封装起来并可以相互替换使用,策略模式让算法独立于使用它的客户应用而独立变化。
OO设计原则:
- 面向接口编程(面向抽象编程)
- 封装变化
- 多用组合,少用继承
Object类
Object类 是类层次结构的根类
- 每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法
- 所有类都是Object类的子类。
public String toString()方法
- 返回该对象的字符串表示。
- toString 方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明且易于读懂的信息表达式。建议所有子类都重写此方法。
public boolean equals(Object obj)
- 指示其他某个对象是否与此对象“相等”。 equals 方法在非空对象引用上实现相等关系:自反性、对称性、传递性、一致性
protected void finalize()throws Throwable
- 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
- 子类重写 finalize 方法,以配置系统资源或执行其他清除。
public final Class<?> getClass()
- 返回此 Object 的运行时类。
其它方法在后续章节中详解。
模式
简单工厂模式
简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式。
静态代理模式
代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问。
代理模式说白了就是“真实对象”的代表,在访问对象时引入一定程度的间接性,因为这种间接性可以附加多种用途。
适配器模式
适配器模式( Adapter ):将一个类的接口转换成客户希望的另外一个接口。适配器模式使原本由于接口不兼容而不能一起工作的那些类可以一起工作。
OO设计原则:
- 面向接口编程(面向抽象编程)
- 封装变化
- 多用组合,少用继承
- 对修改关闭,对扩展开放
内部类
内部类就是在一个类的内部定义的类。
成员内部类格式如下:
class Outer {class Inner{}
}
编译上述代码会产生两个文件:Outer.class和Outer$Inner.class。
在外部创建内部类对象
- 内部类除了可以在外部类中产生实例化对象,也可以在外部类的外部来实例化。
- 那么,根据内部类生成的*.class文件:Outer I n n e r . c l a s s “ Inner.class “ Inner.class“” 符号在程序运行时将替换成“.”
- 所以内部类的访问:通过“外部类.内部类”的形式表示。
Outer out = new Outer() ;// 产生外部类实例
Outer.Inner in = null; // 声明内部类对象
in = out.new Inner() ; // 实例化内部类对象
方法内部类
内部类可以作为一个类的成员外,还可以把类放在方法内定义。
- 方法内部类只能在定义该内部类的方法内实例化,不可以在此方法外对其实例化。
- 方法内部类对象不能使用该内部类所在方法的非final局部变量。 格式如下:
class Outer {public void doSomething(){class Inner{public void seeOuter(){}}}
}
静态内部类
在一个类内部定义一个静态内部类:静态的含义是该内部类可以像其他静态成员一样,没有外部类对象时,也能够访问它。静态嵌套类仅能访问外部类的静态成员和方法。
class Outer{static class Inner{}
}
class Test {public static void main(String[] args){Outer.Inner n = new Outer.Inner();}
}
匿名内部类
匿名内部类就是没有名字的内部类。
匿名内部类的三种情况:
- 继承式的匿名内部类
- 接口式的匿名内部类
- 参数式的匿名内部类
在使用匿名内部类时,要记住以下几个原则:
- 不能有构造方法,只能有一个实例。
- 不能定义任何静态成员、静态方法。
- 不能是public,protected,private,static。
- 一定是在new的后面,用其隐含实现一个接口或继承一个类。
- 匿名内部类为局部的,所以局部内部类的所有限制都对其生效。
问题:局部内部类访问局部变量必须用final修饰,为什么?
- 当调用这个方法时,局部变量如果没有用final修饰,他的生命周期和方法的生命周期是一样的,当方法被调用时会入栈,方法结束后即弹栈,这个局部变量也会消失,那么如果局部内部类对象还没有马上消失想用这个局部变量,显然已无法使用了,如果用final修饰会在类加载的时候进入常量池,即使方法弹栈,常量池的常量还在,也就可以继续使用了。
- 注意:在jdk1.8中取消了在局部内部类中使用的变量必须显示的使用final修饰,编译器默认会为这个变量加上final
内部类的作用
- 每个内部类都能独立地继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
- 如果没有内部类提供的可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。
成员
成员内部类
静态内部类
局部
方法内部类
匿名内部类
依赖外部类对象的:成员内部类,方法内部类,匿名内部类
静态内部类不依赖外部类的对象。所以,我们在项目中优先考虑选择静态内部类(不会产生内存泄露)
我们项目开发中如何选择?
数据结构之链表
链表(Linked list)一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到是下一个节点的指针(Pointer)
在链表数据结构中,我们需要使用到递归算法。
递归算法是一种直接或者间接地调用自身算法的过程。在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于理解
基本数据类型包装类
在Java中有一个设计的原则“一切皆对象”,Java中的基本数据类型就完全不符合这种设计思想,因为八种基本数据类型并不是引用数据类型,所以Java中为了解决这样的问题,JDK1.5以后引入了八种基本数据类型的包装类。
八种包装类分为两大类型:
- Number:Integer、Short、Long、Double、Float、Byte都是Number的子 类表示是一个数字。
- Object:Character、Boolean都是Object的直接子类
装箱及拆箱操作
将一个基本数据类型转换为包装类,那么这样的操作称为装箱操作。将一个包装类转换为一个基本数据类型,这样的操作称为拆箱操作。
转型操作
- 在包装类中,可以将一个字符串变为指定的基本数据类型,一般在输入数据时会使用较多。
- 在Integer类中将String变为int型数据:public static int parseInt(String s)
- 在Float类中将String变为float型数据:public static float parseFloat(String s)
注意:转型操作时,字符串必须由数字组成,否则会出现错误
享元模式(Flyweight Pattern) 它使用共享对象,用来尽可能减少内存使用量以及分享资讯给尽可能多的相似对象;它适合用于当大量对象只是重复因而导致无法令人接受的使用大量内存。通常对象中的部分状态是可以分享。常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元。
运用共享技术有效的支持大量细粒度的对象。
包与访问修饰符
包用于对多个java源文件的管理,就像我们的文件目录一样。
定义一个包:
package com.vince;
该语句只能出现在代码中的第一句。
访问修饰符:
OO原则总结
- 开闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
- 合成/聚合复用原则:新对象的某些功能在已创建好的对象里已实现,那么尽量用已有对象提供的功能,使之成为新对象的一部分,而不要再重新创建。
- 依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
- 接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
- 迪米特法则:一个对象应该对其他对象保持最少的了解
- 里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象
- 单一职责原则:不要存在多于一个导致类变更的原因,即一个类只负责一项职责。