五、面向对象(下)
01.继承
- 基本概念:从已有的类创建新类的过程;
1.1 一个对象直接使用另一个对象的属性和方法(非私有);
1.2 通过继承实现代码重用; - 语法:[访问权限] class 子类名 extends 父类名{类定义};
- 限制:
3.1 只能实现单继承,也就是一个类只能有一个父类;
3.2 允许多层继承,即一个子类可以有一个父类,一个父类还可以有其他父类;
3.3 只能继承非私有属性和方法;
3.4 构造方法不能被继承; - 小结:
02.方法的重写
- 子类继承父类的方法,不需要重新编写相同的方法;但有时子类并不想原封不动地继承弗雷德方法,而是想做一定的修改,这就需要方法的重写(方法的覆盖);
- 特点:
2.1 发生在子父类中,两个方法返回值、方法名、参数列表必须完全一致;
2.2 子类抛出的异常不能超过父类相应方法抛出的异常(子类异常不能大于父类异常);
2.3 子类访问级别不能低于父类的访问级别;
2.4 父类中的方法若使用private、static、final任意修饰符修饰,那么不能被子类重写; - super关键字:
3.1 调用父类中的属性,从父类实例处获得信息;
3.2 调用父类中的方法,委托父类对象帮助完成某件事;
3.3 调用父类中的构造方法,必须在子类构造方法的第一条语句; - 应用实例:
//化妆品类
public class Cosmetic {
private String name;
private String type;
private int price;
public Cosmetic(){}
public Cosmetic(String name, String type, int price) {
super();
this.name = name;
this.type = type;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getInfo() {
return "name=" + name + ",type=" + type + ",price=" + price;
}
}
//化妆品管理类
import java.util.Arrays;
public class CosmeticManager {
Cosmetic[] cs = new Cosmetic[4];
protected int count=0;
//动态数组
public void add(Cosmetic c){
int size = cs.length;
if(count>=size){
int newLength = size*2;
cs=Arrays.copyOf(cs, newLength);
}
cs[count]=c;
count++;
}
//打印所有
public void printInfo(){
for(int i=0;i<count;i++){
System.out.println(cs[i].getInfo());
}
}
public int length(){
return count;
}
}
//排序输出化妆品管理
import java.util.Arrays;
public class CosmeticSort extends CosmeticManager {
@Override
public void printInfo() {
Cosmetic[] temp = Arrays.copyOf(cs, count);
//冒泡排序
Cosmetic c = null;
for(int i=0;i<temp.length-1;i++){
for(int j=0;j<temp.length-1-i;j++){
if(temp[j].getPrice()>temp[j+1].getPrice()){
c=temp[j];
temp[j]=temp[j+1];
temp[j+1]=c;
}
}
}
//遍历输出
for(Cosmetic cc:temp){
System.out.println(cc.getInfo());
}
}
}
//只输出进口
public class CosmeticImport extends CosmeticManager {
@Override
public void printInfo() {
for(int i=0;i<count;i++){
if("进口".equals(cs[i].getType())){
System.out.println(cs[i].getInfo());
}
}
}
}
//化妆品测试
public class CosmeticTest {
public static void main(String[] args) {
CosmeticManager cm = new CosmeticManager();
cm.add(new Cosmetic("香奈儿","进口",1000));
cm.add(new Cosmetic("圣罗兰","进口",800));
cm.add(new Cosmetic("大宝","国产",20));
cm.add(new Cosmetic("万紫千红","国产",15));
System.out.println("-----无序输出-----");
cm.printInfo();
CosmeticSort cs = new CosmeticSort();
cs.add(new Cosmetic("香奈儿","进口",1000));
cs.add(new Cosmetic("圣罗兰","进口",800));
cs.add(new Cosmetic("大宝","国产",20));
cs.add(new Cosmetic("万紫千红","国产",15));
System.out.println("-----有序输出-----");
cs.printInfo();
CosmeticImport ci = new CosmeticImport();
ci.add(new Cosmetic("香奈儿","进口",1000));
ci.add(new Cosmetic("圣罗兰","进口",800));
ci.add(new Cosmetic("大宝","国产",20));
ci.add(new Cosmetic("万紫千红","国产",15));
System.out.println("-----进口输出-----");
ci.printInfo();
//多态
CosmeticManager cm1 = new CosmeticSort();
CosmeticManager cm2 = new CosmeticImport();
cm1.add(new Cosmetic("香奈儿","进口",1000));
cm1.add(new Cosmetic("圣罗兰","进口",800));
cm1.add(new Cosmetic("大宝","国产",20));
cm1.add(new Cosmetic("万紫千红","国产",15));
cm2.add(new Cosmetic("香奈儿","进口",1000));
cm2.add(new Cosmetic("圣罗兰","进口",800));
cm2.add(new Cosmetic("大宝","国产",20));
cm2.add(new Cosmetic("万紫千红","国产",15));
System.out.println("-----有序输出-----");
cm1.printInfo();
System.out.println("-----进口输出-----");
cm2.printInfo();
}
}
- final关键字完成以下操作:
5.1 修饰属性或者局部变量,也称为常量,必须在定义时或在默认构造器中初始化;
5.2 声明一个方法(最终方法),只能被子类继承,不能被重写;
5.3 声明一个类(最终类),没有子类,即不能被继承;
5.4 在方法参数中使用final,在该方法内部不能修改参数的值。
03.抽象类
- 具有相同特征和行为的对象可以抽象为一个类,具有相同特征和行为的类可以抽象为一个抽象类;
- 使用abstract关键字声明;
- 抽象类规则:
3.1 可以没有抽象方法,有抽象方法的类必须是抽象类;
3.2 非抽象类继承抽象类必须实现所有的抽象方法;
3.3 可以继承抽象类,可以不实现父类的抽象方法;
3.4 可以有方法实现和属性;
3.5 不能被实例化;
3.6 不能声明为final;
3.7 可以有构造方法; - 接口:
4.1 概念:一组行为的规范、定义,没有实现;
4.1 定义格式:interface 接口名称{全局常量;抽象方法};
4.2 具体类实现接口使用implements关键字,必须实现所有的抽象方法;
4.3 可以继承多个接口,实现多个接口;
4.4 抽象类实现接口可以不实现接口的方法;
4.5 不能有构造方法;
4.6 接口中定义的方法没有声明访问修饰符,默认public;
4.7 不能被实例化。
04.多态性
- 概念:对象在运行过程中的多种形态;
- 分为两类:方法的重载与重写;对象的多态性;
- 用父类的引用指向子类的对象(大类型接受小类型,向上转型、自动转换),大的类型转换为晓得类型,需要强制转换;
- instanceof关键字:把父类的实例转换为子类引用时,避免类型转换异常,需要在转换之前做判断。
05.抽象类应用—模板方法模式
模板方法模式:定义一个操作中的算法的骨架,而将一些可变部分的实现延迟到子类中;使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定的步骤。
//权限查询模板
abstract class BaseManager {
public void action(String name, String method){
if("admin".equals(name)){
excute(method);
}else{
System.out.println("没有操作权限,请联系管理员");
}
}
public abstract void excute(String method);
}
public class UserManager extends BaseManager {
//用户执行操作前需要先判断是否有管理员权限,不光用户管理需要,
//其他管理模块也需要,通过定义一个权限查询模板,就不需要每个
//管理模块都添加权限查询代码。
@Override
public void excute(String method) {
if("add".equals(method)){
System.out.println("执行添加操作");
}else if("del".equals(method)){
System.out.println("执行删除操作");
}
}
}
//测试
public class ManagerTest {
public static void main(String[] args) {
UserManager um = new UserManager();
um.action("admin","add");
um.action("test","add");
um.action("admin","del");
}
}
06.接口应用—策略模式
定义一系列的算法,将每种算法封装起来并可以相互替换使用,让算法独立于使用它的客户应用而独立变化。
//把可变的行为抽象出来,定义为一个接口
//(这里的例子是要实现数据的不同的保存方式)
interface Save {
public void save(String data);
}
//通过实现接口定义这些可变的行为,
//以后要想改变行为,直接添加一个实现类就可以了
//分别定义文件保存和网络保存的实现类
public class FileSave implements Save {
@Override
public void save(String data) {
System.out.println("把数据保存到文件中"+data);
}
}
public class NetSave implements Save {
@Override
public void save(String data) {
System.out.println("把数据保存到网络上"+data);
}
}
//使用上面的模板模式,不管是用户还是其他人员使用,
//都要先检查数据的合法性,检查完才能执行相应的保存操作
public abstract class BaseService {
private Save save;
public void setSave(Save save) {
this.save = save;
}
public void add(String data){
System.out.println("检查数据合法性");
save.save(data);
System.out.println("保存完毕");
}
}
//用户保存数据操作
public class UserService extends BaseService {}
//测试
public class SaveTest {
public static void main(String[] args) {
BaseService user = new UserService();
user.setSave(new FileSave());//在这里就可以随意切换保存数据方式
user.add("user");
}
}
07.Object类
- 每个类都使用Object作为超类,所有类(包括数组)都实现这个类的方法,所有类都是Object类的子类;
- toString()方法:建议所有子类都重写此方法;
- equals():比较两个对象是否相等,重写需满足以下关系:
3.1 自反性:对于任何非空引用值x,x.equals(x)都应返回true;
3.2 对称性:对于任何非空引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)才应返回true;
3.3 传递性:对于任何非空引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)应返回true;
3.4 一致性:对于任何非空引用值x和y,多次调用x.equals(y)始终返回true或false,前提是对象上equals比较中所用的信息没有被修改;
3.5 对于任何非空引用值x,x.equals(null)都应返回false。
08.简单工厂模式
- 简单工厂模式是由一个工厂对象决定创建出哪一种产品的类的实例,是工厂模式家族中最简单实用的模式;
- 使用者和被使用者之间耦合,产生依赖,当被使用者改变时会影响使用者。
//定义产品接口(共有的操作)
interface Product {
public void work();
}
//不同产品各自实现(实现不同的操作方式)
public class Phone implements Product {
@Override
public void work() {
System.out.println("手机开始工作...");
}
}
public class Computer implements Product {
@Override
public void work() {
System.out.println("电脑开始工作...");
}
}
//创建工厂,在工厂内区分不同的产品(创建返回需要的对象)
public class ProductFactory {
public static Product getProduct(String name){
if("phone".equals(name)){
return new Phone();
}else if("computer".equals(name)){
return new Computer();
}else{
return null;
}
}
}
//测试
public class ProductTest {
public static void main(String[] args) {
Product phone = ProductFactory.getProduct("phone");
if(null!=phone){
phone.work();
}
}
}
09.静态代理模式
代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问,在访问对象时引入一定程度的间接性。
//需要代理的对象接口
public interface Action {
public void doAction();
}
//需要给对象添加的动作
public class ActionUser implements Action {
@Override
public void doAction() {
System.out.println("用户开始工作...");
}
}
//产生代理对象的实现类
public class ActionProxy implements Action {
private Action target;
public ActionProxy(Action target) {
this.target = target;
}
@Override
public void doAction() {
long startTime = System.currentTimeMillis();
target.doAction();
long endTime = System.currentTimeMillis();
System.out.println("共耗时:"+(endTime-startTime));
}
}
//测试
public class ActionTest {
public static void main(String[] args) {
Action userAction = new ActionUser();//需要代理的对象
ActionProxy proxy = new ActionProxy(userAction);//产生代理
proxy.doAction();
}
}
10.适配器模式
将一个类的接口转换成客户希望的另外一个接口,使原来由于接口不兼容而不能一起工作的那些类可以一起工作。
//两个不兼容的接口
public interface PowerA {
public void insert();
}
public interface PowerB {
public void connect();
}
//两个不兼容接口的实现类
public class PowerAImpl implements PowerA {
@Override
public void insert() {
System.out.println("A开始工作");
}
}
public class PowerBImpl implements PowerB {
@Override
public void connect() {
System.out.println("B开始工作");
}
}
//定义适配器,使他们兼容
public class PowerAdapter implements PowerA {
private PowerB powerB;
public PowerAdapter(PowerB powerB) {
this.powerB = powerB;
}
@Override
public void insert() {
powerB.connect();
}
}
//测试
public class PowerTest {
public static void main(String[] args) {
PowerA powerA = new PowerAImpl();
work(powerA);
PowerB powerB = new PowerBImpl();
PowerAdapter adapter = new PowerAdapter(powerB);
work(adapter);
}
public static void work(PowerA a){
a.insert();
}
}
11.内部类
- 在一个类的内部定义的类;
1.1 成员内部类:直接在类中定义的类;
1.2 方法内部类:在一个类的方法内定义一个类,只能在定义改内部类的方法内实例化,方法内部类对象不能使用该内部类所在方法的非final局部变量;
1.3 静态内部类:在一个类内部定义一个静态修饰的内部类,仅能访问外部类的静态成员和方法;
1.4 匿名内部类:继承式、接口式、参数式匿名内部类; - 格式:class Outer{ class Inner{} };
- 编译会产生两个文件:Outer.class,Outer$Inner.class;
- 使用匿名内部类原则:
4.1 不能有构造方法,只能有一个实例;
4.2 不能定义任何静态成员、静态方法;
4.3 不能是public、protected、private、static(都没有类怎么加这些修饰符);
4.4 一定在new后面,用其隐含实现一个接口或继承实现一个类;
4.5 匿名内部类为局部的,所以局部内部类的所有限制都对其生效。
class Outer{
//成员内部类
private String name;
public void innerPrint(){
Inner inner = new Inner();
inner.print();
}
class Inner{
public void print(){
System.out.println("成员内部类");
}
}
//方法内部类
public void show(){
final int x=10;//只能是final,因为内部类对象的生命周期可能比方法长,当对象还没消失时,局部变量一消失,内部类对象就无法使用这个局部变量;jdk1.8可以不显示的写出final;
class Inner{
public void print(){
System.out.println("方法内部类");
}
}
Inner inner = new Inner();
inner.print();
}
//静态内部类(不会产生内存泄漏)
static class Inner{
public void print(){
System.out.println("静态内部类");
}
}
//继承式匿名内部类
public void print(){
Outer outer = new Outer(){
public void print(){
System.out.println("继承式匿名内部类");
}
};
outer.print();
}
//接口式匿名类
public void print(){
Outer outer = new Outer(){
public void print(){
System.out.println("接口式匿名内部类");
}
};
outer.print();
}
//参数式匿名内部类
public void print(Outer outer){
outer.print();
}
}
abstract class Outer{
public abstract void print();
}
interface Outer{
public void print();
}
public class Test{
public static void main(String[] args) {
Outer outer = new Outer();
//创建成员内部类实例,需要依赖外部类的对象
//不建议这样来实例化内部类对象
//Outer.Inner inner = outer.new Inner();
//inner.print();
//一般在外部类实例化内部类的对象,并提供公共访问方法
outer.innerPrint();//推荐使用
outer.show();
Outer.Inner inner = new Outer.Inner();
inner.print();
outer.print();
outer.print();
outer.print(new Outer(){
public void print(){
System.out.println("参数式匿名内部类");
}
});
}
}
12.数据结构之链表
- 在链表数据结构中,使用递归算法;
- 递归算法:直接或者间接地调用自身算法的过程;
2.1 递归必须要有出口;
2.2 递归内存消耗大,容易发生内存溢出; - 链表:一种常见的数据结构,是一种线性表,但并不会按线性的顺序存储顺序,而是在每一个节点里存到下一个节点的指针。
public class Test {
public static void main(String[] args) {
System.out.println(factorial1(10));
System.out.println(factorial2(10));
}
//循环
public static int factorial1(int i){
int result=i;
while(i>1){
result*=(i-1);
i--;
}
return result;
}
//递归
public static int factorial2(int i){
if(i==1) return 1;
else return i*factorial2(i-1);
}
}
//实现链表增删改查
public class NodeManager {
private Node root;
private int currentIndex;
// 添加节点
public void add(int data) {
if(root==null){
root=new Node(data);
}else{
root.addNode(data);
}
}
// 删除节点
public void del(int data) {
if(root==null) return;
if(root.getData()==data){
root=root.next;
}else{
root.delNode(data);
}
}
// 打印所有节点
public void print() {
if(root!=null){
System.out.print(root.getData()+"->");
root.printNode();
System.out.println();
}
}
// 查找节点
public boolean find(int data) {
if(root==null) return false;
if(root.getData()==data) return true;
else{
return root.findNode(data);
}
}
// 更新节点
public boolean update(int oldData, int data) {
if(root==null) return false;
if(root.getData()==oldData){
root.setData(data);
return true;
}else{
return root.updateNode(oldData, data);
}
}
// 插入节点
public void insert(int index, int data) {
if(index<0) return;
currentIndex=0;
if(index==currentIndex){
Node newNode = new Node(data);
newNode.next=root;
root=newNode;
}else{
root.insertNode(index, data);
}
}
private class Node {
private int data;
private Node next;
public Node(int data) {
this.data = data;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
// 添加节点
public void addNode(int data) {
if(this.next==null){
this.next=new Node(data);
}else{
this.next.addNode(data);
}
}
// 删除节点
public void delNode(int data) {
if(this.next!=null){
if(this.next.getData()==data){
this.next=this.next.next;
}else{
this.next.delNode(data);
}
}
}
// 打印所有节点
public void printNode() {
if(this.next!=null){
System.out.print(this.next.data+"->");
this.next.printNode();
}
}
// 查找节点
public boolean findNode(int data) {
if(this.next!=null){
if(this.next.data==data){
return true;
}else{
return this.next.findNode(data);
}
}
return false;
}
// 更新节点
public boolean updateNode(int oldData, int data) {
if(this.next!=null){
if(this.next.data==oldData){
this.next.data=data;
return true;
}else{
return this.next.updateNode(oldData, data);
}
}
return false;
}
// 插入节点
public void insertNode(int index, int data) {
currentIndex++;
if(index==currentIndex){
Node newNode= new Node(data);
newNode.next=this.next;
this.next=newNode;
}else{
this.next.insertNode(index, data);
}
}
}
}
//测试
public class NodeManagerTest {
public static void main(String[] args) {
NodeManager nm = new NodeManager();
System.out.println("----add----");
nm.add(5);
nm.add(4);
nm.add(3);
nm.add(2);
nm.add(1);
nm.print();
System.out.println("----del----");
nm.del(4);
nm.print();
System.out.println("----find----");
System.out.println(nm.find(1));
System.out.println("----update----");
nm.update(3, 10);
nm.print();
System.out.println("----insert----");
nm.insert(3, 6);
nm.print();
}
}
13.基本数据类型包装类
- java有一个设计原则“一切皆对象”,基本数据类型就完全不符合这种设计思想,因为八种基本数据类型并不是引用数据类型,为解决这样的问题引入包装类;
- 基本数据类型:byte short int long float double boolean char;
- 对应的类:Byte Short Integer Long Float Double Boolean Character;
- 自动装箱:把基本数据类型转换为包装类(Integer i = new Integer(10)),自动拆箱:把包装类转换为基本数据类型(i.intValue());
- 数值字符串转换成int类型:Integer.parseInt()/Integer.valueOf()(自动拆箱,效率降低);
- 享元模式:使用共享对象,尽可能减少内存使用量以及分享资讯给尽可能多的相似对象,适合用于当大量对象只是重复因而导致无法令人接受的使用大量内存,常见做法是把它们放在外部数据结构、使用时将它们传递给享元(Integer只能缓存1字节以内的数);
- 包与访问修饰符:
7.1 包用于对多个java源文件的管理,就像文件目录一样;
7.2 访问修饰符:
- 面向对象原则总结:
8.1 开闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭;
8.2 合成/聚合复用原则:新对象的某些功能在已创建好的对象里已实现,那么尽量用已有对象提供的功能,使之成为新对象的一部分,而不要再重新创建;
8.3 依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象;
8.4 接口隔离原则:客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上;
8.5 迪米特法则:一个对象应该其他对象保持最少的了解;
8.6 里式替换原则:所有引用基类的地方必须能透明地使用其子类的对象;
8.7 单一职责原则:不要存在多于一个导致类变更的原因,即一个类只负责一项职责。