文章目录
1.继承的基本概念
继承是三大面向对象特征之一,继承无处不在,继承不仅是代码重用,精髓之处在于用抽象思维编写代码,以达到程序更强的可扩展性.
继承是从已有的类创建新类的过程.
继承是面向对象三大特征之一.
被继承的类称为父类(超类),继承父亲的类称为子类(派生类)
继承是指一个对象直接使用另一对象的属性和方法
通过继承可以实现代码重用
protected(受保护的访问权限修饰符,用于修饰属性和方法,使用protected修饰的属性和方法可以被子类继承)
2.继承的限制
继承的限制约定:
java只能实现单继承,也就是一个类只能有一个父类
允许多层继承,即:一个子类可以有一个父类,一个父类还可以有其他的父类
继承只能继承非私有的属性和方法
构造方法不能被继承
创建子类对象时,父类的构造方法也会被调用,为什么?
因为子类要使用到父类的数据,那么就要通过父类的构造方法来初始化数据
如果创建子类对象时使用默认的构造方法,那么父类的默认构造方法也会被调用
//如果创建子类对象时使用默认的构造方法,那么父类的默认构造方法也会被调用
如果创建子类对象时会调用父类的默认构造方法
继承小结
继承是发生在多个类之间
继承使用关键字extends
JAVA只能单继承,允许多层继承
被继承的类叫父类(超类),继承父类的类叫子类(派生类)
在父类中的非私有属性和方法可以被子类继承
protected(受保护的访问权限修饰符),修饰的属性或方法可以被子类继承
构造方法不能被继承
创建对象会调用构造方法,调用构造方法不一定就是创建该类对象
实例化子类对象,会先调用父类的构造方法,如果父类中没有默认的构造方法,那么子类必须显示的通过super(…)来调用父类的带参构造方法,super也只能在子类构造方法中第一句
继承的好处
提高代码的复用性
提高代码的维护性
让类与类之间产生关系,是多态的前提
继承的缺点
增强了类与类之间的耦合性
开发原则,高内聚,低耦合
/**
2022-2-20
继承:继承是从已有的类创建新类的过程
继承一个父类,只能继承非私有的数据(属性和方法)
protected访问权限修饰符,在继承关系中使用,在父类中使用protected修饰的属性或方法可以被子类继承
*/
public class Test34{
public static void main(String[] args){
HomeDog hd=new HomeDog();
hd.print();
System.out.println("---------------------------------");
HomeDog homedog=new HomeDog("旺财");
homedog.print();
homedog.eat();//提高代码的重用性
System.out.println("---------------------------------");
HuskDog huskdog=new HuskDog("哈士奇","男");
huskdog.show();
}
}
class Dog{
protected String name;
protected String sex;
public Dog(){
System.out.println("我是Dog的默认构造方法!");
}
public Dog(String name,String sex){
this.name=name;
this.sex=sex;
System.out.println("我是Dog的有参构造方法!");
}
//提高代码的维护性
public void eat(){
System.out.println("吃饭!");
}
}
class HomeDog extends Dog{
public HomeDog(){
super();
System.out.println("我是HomeDog的默认构造方法!");
}
public HomeDog(String name){
this.name=name;
System.out.println("HomeDog带参数的构造方法被执行!");
}
public void print(){
System.out.println("我是一只家狗!");
}
}
class HuskDog extends Dog{
public HuskDog(){
super();
System.out.println("我是HuskDog的默认构造方法!");
}
public HuskDog(String name,String sex){
super(name,sex);//只能在第一句
System.out.println("我是HuskDog的有参构造方法!");
}
public void show(){
System.out.println("我是一只哈士奇,我能拆家!");
}
}
3.子类的实例化过程
在子类进行实例化操作的时候,首先会先让其父类进行实例化操作,之后子类再自己进行实例化操作.
子类的实例化过程:
子类实例化时会先调用父类的构造方法
如果父类中没有默认的构造方法,在子类的构造方法中必须显示的调用有父类的构造方法
结论:
构造方法只是用于初始化类中的字段以及执行一些初始化代码
调用构造方法并不代表会生成对象
4.方法的重写
面试题重写与重载的区别,切记方法的重写是发生在子父类继承关系中,不同于方法重载发生在同一个类中.
方法重写(overriding method)
在JAVA中,子类可继承父类中的方法,而不需要重新编写相同的方法.但有时子类并不想原封不同地继承父类的方法,而是想做一定的修改,这就需要采用方法的重写.方法重写又称为方法覆盖.
在子类和父类中,重写方法后,在调用时,以创建的对象类型为准,会调用谁的方法.
关于方法重写的一些特性:
发生在子父类中,方法重写的两个方法返回值,方法名,参数列表必须完全一致(子类重写父类的方法)
子类抛出的异常不能超过父类相应方法抛出的异常(子类异常不能大于父类异常)
子类方法的访问级别不能低于父类相应方法的访问级别(子类访问级别不能低于父类访问级别)
父类中的方法若使用private,static,final任意修饰符修饰,那么,不能被子类重写.
为什么要重写方法?或者方法重写的目的是什么?
若子类从父类中继承过来的方法,不能满足子类特有的需求时,子类就需要父类中相应的方法,方法的重写也是程序扩展的体现.
面试题:overloading与overriding的区别?
overloading:方法的重载,发生在同一个类中,方法名相同,参数列表不同,返回值无关.
overriding:方法的重写,发生在子父类中,方法名相同,参数列表相同,返回值相同,子类的访问修饰符要大于或等于父类的访问修饰符,子类的异常声明必须小于或等于父类的异常声明,如果方法被private,static,final修饰,则不能被重写
5.super关键字
super可以完成以下的操作:
使用super调用父类中的属性,可以从父类实例处获得信息.
使用super调用父类中的方法,可以委托父类对象帮助完成某件事情.
使用super调用父类中的构造方法(super(实参)形式),必须在子类构造方法额第一条语句,调用父类中相应的构造方法,若不显示的写出来,默认调用父类的无参构造方法,比如:super();
/**
2022-2-20
继承:继承是从已有的类创建新类的过程
继承一个父类,只能继承非私有的数据(属性和方法)
protected访问权限修饰符,在继承关系中使用,在父类中使用protected修饰的属性或方法可以被子类继承
*/
public class Test34{
public static void main(String[] args){
HomeDog hd=new HomeDog();
hd.print();
System.out.println("---------------------------------");
HomeDog homedog=new HomeDog("旺财");
homedog.print();
homedog.eat();//提高代码的重用性
System.out.println("---------------------------------");
HuskDog huskdog=new HuskDog("哈士奇","男");
huskdog.show();
huskdog.eat();
}
}
class Dog{
protected String name;
protected String sex;
public Dog(){
System.out.println("我是Dog的默认构造方法!");
}
public Dog(String name,String sex){
this.name=name;
this.sex=sex;
System.out.println("我是Dog的有参构造方法!");
}
//提高代码的维护性
public void eat(){
System.out.println("吃饭!");
}
}
class HomeDog extends Dog{
public HomeDog(){
super();
System.out.println("我是HomeDog的默认构造方法!");
}
public HomeDog(String name){
this.name=name;
System.out.println("HomeDog带参数的构造方法被执行!");
}
public void print(){
System.out.println("我是一只家狗!");
}
public void eat(){
//重写父类的方法
System.out.println("我是家狗,我喜欢吃骨头!");
}
}
class HuskDog extends Dog{
public HuskDog(){
super();
System.out.println("我是HuskDog的默认构造方法!");
}
public HuskDog(String name,String sex){
super(name,sex);//只能在第一句
System.out.println("我是"+super.name+"的有参构造方法!");
}
public void show(){
System.out.println("我是一只"+super.name+",我能拆家!");
}
public void eat(){
super.eat();
System.out.println("我是哈士奇,我喜欢吃牛肉!");
}
}
6.继承的应用示例
题目:
实现一个化妆品商城中化妆品管理
1.定义一个化妆品类(Cosmetic)name,type,price
2.定义一个化妆品管理类(CosmeticManger)
(1)实现进货功能
(2)可以输出所有化妆品信息功能
3.使用继承实现一个可按单价排序输出所有化妆品的功能
4.使用继承实现一个只输出进口化妆品的功能
/**
2022-2-20
实现一个化妆品商城中化妆品管理
1.定义一个化妆品类(Cosmetic)name,type,price
2.定义一个化妆品管理类(CosmeticManger)
(1)实现进货功能
(2)可以输出所有化妆品信息功能
3.使用继承实现一个可按单价排序输出所有化妆品的功能
4.使用继承实现一个只输出进口化妆品的功能
*/
import java.util.Arrays;
import java.util.Scanner;
public class Test35{
public static void main(String[] args){
Scanner input=new Scanner(System.in);
System.out.println("请输入初始化妆品库值(整数):");
int num=input.nextInt();
System.out.println("------------------------------");
CosmeticManger cm=new CosmeticManger(num);
cm.add(new Cosmetic("力士","国产",30));
cm.add(new Cosmetic("香奈儿","进口",1000));
cm.add(new Cosmetic("可奈","进口",800));
cm.add(new Cosmetic("大宝","国产",20));
cm.allPrintInfo();
System.out.println("------------------------------");
SortCosmeticManger cm1=new SortCosmeticManger(num);
cm1.add(new Cosmetic("力士","国产",30));
cm1.add(new Cosmetic("香奈儿","进口",1000));
cm1.add(new Cosmetic("可奈","进口",800));
cm1.add(new Cosmetic("大宝","国产",20));
cm1.allPrintInfo();
System.out.println("------------------------------");
ImportCosmeticManger cm2=new ImportCosmeticManger(num);
cm2.add(new Cosmetic("力士","国产",30));
cm2.add(new Cosmetic("香奈儿","进口",1000));
cm2.add(new Cosmetic("可奈","进口",800));
cm2.add(new Cosmetic("大宝","国产",20));
cm2.allPrintInfo();
}
}
//使用继承实现一个只输出进口化妆品的功能
class ImportCosmeticManger extends CosmeticManger{
public ImportCosmeticManger(){}
public ImportCosmeticManger(int num){
super(num);
}
public void allPrintInfo(){
//比较两字符串的值是否相等,不能使用==,使用equals()
for(int i=0;i<count;i++){
if("进口".equals(cs[i].getType())){
System.out.println(cs[i].getInfo());
}
}
}
}
//单价排序输出所有化妆品的功能
class SortCosmeticManger extends CosmeticManger{
public SortCosmeticManger(int num){
super(num);
}
//单价排序输出所有化妆品的功能
public void allPrintInfo(){
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-i-1;j++){
if(temp[j].getPrice()>temp[j+1].getPrice()){
c=temp[j];
temp[j]=temp[j+1];
temp[j+1]=c;
}
}
}
for(Cosmetic cs: temp){
System.out.println(cs.getInfo());
}
}
}
//化妆品管理类
class CosmeticManger{
protected Cosmetic[] cs;
protected int count=0;
public CosmeticManger(){}
public CosmeticManger(int num){
cs=new Cosmetic[num];
}
//进货功能
public void add(Cosmetic c){
int size=cs.length;
if(count>=size){
int newLen=size*2;
cs=Arrays.copyOf(cs,newLen);
}
cs[count]=c;
count++;
}
//输出所有产品
public void allPrintInfo(){
for(int i=0;i<count;i++){
System.out.println(cs[i].getInfo());
}
}
}
//化妆品类
class Cosmetic{
private String name;//品牌
private String type;//进口或国产
private int price;//价格
public Cosmetic(){}
public Cosmetic(String name,String type,int price){
this.name=name;
this.type=type;
this.price=price;
}
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
public void setType(String type){
this.type=type;
}
public String getType(){
return type;
}
public void setPrice(int price){
this.price=price;
}
public int getPrice(){
return price;
}
public String getInfo(){
return "name="+name+"\ttype="+type+"\tprice="+price;
}
}
7.final关键字
final关键字表示最终的,声明的属性值不能该,声明的方法不能被子类重写,声明的类无法被继承(扩展)
使用final关键字完成以下操作:
使用final关键字声明一个常量:修饰属性或者修饰局部变量(最终变量),也称为常量.
使用final关键字声明一个方法:该方法为最终方法,且只能被子类继承,但是不能被子类重写
使用final关键字声明一个类:该类就转变为最终类,没有子类的类,final修饰的类无法被继承.
在方法参数中使用final,在该方法内部不能被修改参数的值(在内部类中详解)
final应用:
定义一个常量:
public static final int NUM=10;
定义一个final方法(不常用)
定义一个final类:通常在常量类中使用
/**
2022-2-20
final关键字
1.使用final声明一个属性,就是常量,常量的命名规则建议使用全大写
2.常量必须在定义时或在构造器中初始化
3.使用final声明的方法,不能被子类重写
4.
*/
public class Test36{
//private static final int PERSON_NUM=10;
public static void main(String[] args){
System.out.println(Constant.PERSON_NUM);
}
}
//常量类(工具类的一种):在实际项目开发中,常量类通常用于定义项目中一些公共的,不变的,数据
class Constant{
public static final int PERSON_NUM=10;//人数
}
class FinalClass{
public final int DAY_NUMBER;//工作天数
public FinalClass(){
DAY_NUMBER=22;
}
}
8.抽象类
抽象类可以理解为类的抽象层次,思考,很多对象就可以抽象出一个类,很多类就可以抽象出抽象类.
抽象的基本概念
很多具有相同特征和行为的对象可以抽象为一个类;很多具有相同特征和行为的类可以抽象为一个抽象类.
使用abstract关键字声明的类为抽象类.
抽象类的规则
抽象类可以没有抽象方法,有抽象方法的类必须是抽象类
非抽象类继承抽象类必须实现所有抽象方法
抽象类可以继承抽象类,可以不实现父类抽象方法.
抽象类可以有方法实现和属性
抽象类不能被实例化
抽象类不能声明为final
抽象类可以有构造方法
/**
2022-2-21
抽象类
*/
public class Test37{
public static void main(String[] args){
Man man=new Man();
man.eat();
man.move();
Women women=new Women();
women.eat();
women.move();
}
}
//定义一个抽象类
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("我喜欢逛街!");
}
}
9.接口
接口是所有行为的抽象,面向接口编程,目的是让程序易于维护,扩展性强,接口只定义标准,而非具体实现.
接口的概念
接口是一组行为的规范,定义,没有实现(JDK1.8默认方法)
使用接口,可以让我们的程序更加利于变化
接口是面向对象编程体系中的思想精髓之一
面向对象设计法则:基于接口编程
接口的定义格式:
interface 接口名称{
全局变量;
抽象方法;
}
接口的使用规则
定义一个接口,使用interface关键字
在一个接口中,只能定义常量,抽象方法,JDK1.8后可以定义默认的实现方法
接口可以继承多个接口,extends xxx,xxx
一个具体类实现接口使用implements关键字
一个类可以实现多个接口
抽象类实现接口可以不实现接口的方法
在接口定义的方法没有声明 访问修饰符,默认为public
接口不能有构造方法
面向对象设计原则:
对修改关闭,对扩展开发
面向接口编程
/**
2022-2-21
接口
*/
public class Test38{
public static void main(String[] args){
Girl girl=new Girl("天神");
girl.sleep();
girl.eat();
girl.run();
girl.print();
}
}
//接口
interface IEat{
//public abstract void eat();
void eat();//接口中定义的方法没有声明修饰符,默认为public abstract
//public static final int NUM=10;
//在接口中定义一个常量
int NUM=10;//常量
//在JDK1.8后新特性,可以被所有实现类继承
public default void print(){
System.out.println("eat!");
}
}
interface IRun{
void run();
}
//接口之间可以多继承(注意:类是只能单继承)
interface ISleep extends IEat,IRun{
void sleep();
}
//具体类实现接口必须实现接口的所有方法
class Girl implements ISleep,IEat{
private String name;
public Girl(){}
public Girl(String name){
this.name=name;
}
public void sleep(){
System.out.println("我爱睡觉!");
}
public void eat(){
System.out.println("我是"+name+",我爱吃猪肉!");
}
public void run(){
System.out.println("吃完就跑!");
}
}
10.多态性
多态是面向对象三大特征之一,是抽象编程思维的精髓所在,用抽象的思维写代码,在运行过程中再传入具体的对象,同面向接口编程如出一辙.
多态是面向对象三大特性之一
什么是多态性?
对象在运行过程中的多种形态.
多态性我们大概可以分为两类
(1)方法的重载与重写
(2)对象的多态性
结论:在编程时针对抽象类型的编写代码,称为面向抽象编程(或面向接口编程)
父类通常都定义为抽象类,接口
对象的多态性
对象多态性是从继承关系中的多个类而来.
向上转型:将子类实例转换为父类引用
格式:父类 父类对象=子类实例;(->自动转换)
以基本数据类型操作为例:int l=‘a’;
(因为char的容量比int小,所以可以自动完成)
向下转型:将父类实例转为子类实例
格式:子类 子类对象=(子类)父类实例;实例转换
以基本数据类型操作为例:char c=(char)97;
因为整型是4个字节比char 2个字节要大,所以需要强制完成.
多态性小结
方法的重载与重写就是方法的多态性实例
多个子类就是父类中的多种形态
父类引用可以指向子类对象,自动转换
子类对象指向父类引用需要强制转换(注意:类型不会保异常)
在实际开发中尽量使用父类引用(更利于扩展)
/**
2022-2-21
多态性
*/
public class Test39{
public static void main(String[] args){
System.out.println("----------------------------------");
HomeChicken hc=new HomeChicken("小红");
hc.eat();
YeChicken yc=new YeChicken("小野鸡");
yc.eat();
//用父类的应用指向子类对象呢(用大的类型来表示小的类型),自动转换(向上转型)
System.out.println("----------------------------------");
Chicken hc1=new HomeChicken("小红1");
hc1.eat();
Chicken yc1=new YeChicken("小野鸡1");
yc1.eat();
System.out.println("----------------------------------");
hc1=yc1;
hc1.eat();
System.out.println("----------------------------------");
//eat(hc);//正确
System.out.println("----------------------------------");
Chicken jianjiaochicken=new JianJiaoChicken("尖叫鸡!");
eat(jianjiaochicken);
}
//抽象(粒度) 面向对象编程(面向接口编程)
public static void eat(Chicken c){
System.out.println("小鸡们吃饭!");
c.eat();
//强制转换
JianJiaoChicken jianjiaochicken=(JianJiaoChicken)c;//大的转小的
jianjiaochicken.song();
//c.song();//错误
}
}
//鸡
abstract class Chicken{
private String name;
public Chicken(){}
public Chicken(String name){
this.name=name;
}
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
public abstract void eat();
}
//家鸡
class HomeChicken extends Chicken{
public HomeChicken(){}
public HomeChicken(String name){
super(name);
}
public void eat(){
System.out.println(this.getName()+",我爱吃大米");
}
}
//野鸡
class YeChicken extends Chicken{
public YeChicken(){}
public YeChicken(String name){
super(name);
}
public void eat(){
System.out.println(this.getName()+",我爱吃虫子");
}
}
//尖叫鸡
class JianJiaoChicken extends Chicken{
public JianJiaoChicken(){}
public JianJiaoChicken(String name){
super(name);
}
public void eat(){
System.out.println("我是"+this.getName()+",我不吃东西!");
}
public void song(){
System.out.println("咯咯咯咯,我是尖叫鸡!");
}
}
11.instanceof关键字
注意:instanceof关键字是验明对象正身的好利器,简单使用靠谱
instanceof是用于检查对象是否为指定的类型,通常在把父亲引用强制转换为子类引用时要使用,以避免发生类型转换异常(classCastException)
语法格式如下:
对象 instanceof 类型 – 返回boolean类型值
该语句一般用于判断一个对象是否为某个类的实例,是返回true,否返回false.
父类的设计法则
通过instanceof关键字,我们可以很方便的检查对象的类型,但如果一个子类的子类过多,这样的判断还是显得繁琐,那么如何去设计一个父类呢?
父类通常情况下都设计为抽象类或接口,其中优先考虑接口,如接口不能满足才考虑抽象类.
一个具体的类尽可能不去继承另一个具体类,这样的好处是无需检查对象是否为父类的对象.
/**
2022-2-21
多态性
*/
public class Test39{
public static void main(String[] args){
System.out.println("----------------------------------");
HomeChicken hc=new HomeChicken("小红");
hc.eat();
YeChicken yc=new YeChicken("小野鸡");
yc.eat();
//用父类的应用指向子类对象呢(用大的类型来表示小的类型),自动转换(向上转型)
System.out.println("----------------------------------");
Chicken hc1=new HomeChicken("小红1");
hc1.eat();
Chicken yc1=new YeChicken("小野鸡1");
yc1.eat();
System.out.println("----------------------------------");
hc1=yc1;
hc1.eat();
System.out.println("----------------------------------");
//eat(hc);//正确
System.out.println("----------------------------------");
Chicken jianjiaochicken=new JianJiaoChicken("尖叫鸡!");
eat(jianjiaochicken);
System.out.println("----------------------------------");
eat(hc);
eat(yc);
}
//抽象(粒度) 面向对象编程(面向接口编程)
public static void eat(Chicken c){
System.out.println("小鸡们吃饭!");
c.eat();
//当我们需要把父类的实例强制转换为子类引用时,为了
//避免类型转换异常java.lang.ClassCastException
//那我们需要在转换之前做类型判断
if(c instanceof JianJiaoChicken){
//成立的条件是,对象本身及对象的父类型,都可以通过检查
//强制转换
JianJiaoChicken jianjiaochicken=(JianJiaoChicken)c;//大的转小的
jianjiaochicken.song();
}
//c.song();//错误
}
}
//鸡
abstract class Chicken{
private String name;
public Chicken(){}
public Chicken(String name){
this.name=name;
}
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
public abstract void eat();
}
//家鸡
class HomeChicken extends Chicken{
public HomeChicken(){}
public HomeChicken(String name){
super(name);
}
public void eat(){
System.out.println(this.getName()+",我爱吃大米");
}
}
//野鸡
class YeChicken extends Chicken{
public YeChicken(){}
public YeChicken(String name){
super(name);
}
public void eat(){
System.out.println(this.getName()+",我爱吃虫子");
}
}
//尖叫鸡
class JianJiaoChicken extends Chicken{
public JianJiaoChicken(){}
public JianJiaoChicken(String name){
super(name);
}
public void eat(){
System.out.println("我是"+this.getName()+",我不吃东西!");
}
public void song(){
System.out.println("咯咯咯咯,我是尖叫鸡!");
}
}
12.抽象类应用-模板方法模式
注意:模板方法模式的精髓之处在于定义一个算法框架,也就是把共有抽象出来,这对未来底层框架设计非常有用
模板方法模式:定义一个操作中的算法的骨架,而将一些可变部分的实现延迟到子类中.
模板方法模式使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定的步骤.
/**
2022-2-21
模板方法模式
*/
public class Test40{
public static void main(String[] args){
UserManger um=new UserManger();
um.action("admin","add");
}
}
abstract class BaseManger{
public void action(String name,String method){
if("admin".equals(name)){
execute(method);
}else{
System.out.println("你没有操作权限!");
}
}
public abstract void execute(String method);
}
class UserManger extends BaseManger{
//用户是否登录的验证
//验证成功后才可以执行以下操作
public void execute(String method){
if("add".equals(method)){
System.out.println("执行了添加操作");
}
else if("del".equals(method)){
System.out.println("执行了删除了操作!");
}
}
}
class ClassManger{
}
13.接口应用-策略模式
策略模式是非常常用的,一个接口有多个实现,不同的实现独立封装,可以按运行时需求相互转换,可维护性就强了,新增接口实现也不会影响其它实现类.
策略模式,定义了一系列的算法,将每一种算法封装起来并可以相互替换使用,策略模式让算法独立于使用它的客户应用而独立变化.
OO设计原则
面向接口编程(面向抽象编程)
封装变化
多用组合,少用继承
/**
2022-2-22
*/
public class Test41{
public static void main(String[] args){
BaseService user=new UserService();
user.setISave(new FileISave());
user.add("小红");
user.setISave(new NetISave());
user.add("小明");
}
}
//把可变行为抽象出来,定义一系列的算法
interface ISave{
public void save(String data);
}
class FileISave implements ISave{
public void save(String data){
System.out.println("把数据保存到文件中..."+data);
}
}
class NetISave implements ISave{
public void save(String data){
System.out.println("把数据保存到网络上..."+data);
}
}
abstract class BaseService{
private ISave iSave;
public void setISave(ISave iSave){
this.iSave=iSave;
}
public void add(String data){
System.out.println("检查数据合法性!");
iSave.save(data);
System.out.println("数据已经保存完成!");
}
}
class UserService extends BaseService{
}
14.Object类
所有类的祖宗Object,称为根类,所以所有类天生就有toString(),hashCode(),equals()等这些Object中继承下来的方法.
Object类是类层次结构的根类
每个类都使用Object作为超类,所有对象(包括数组)都实现这个类的方法.
所有类都是Object的子类.
/**
2022-2-22
*/
public class Test42{
public static void main(String[] args){
Student stu=new Student("小红",12,23);
System.out.println(stu.toString());
String str1=new String("小红");
String str2=new String("小红");
System.out.println(str1.equals(str2));
//重写equals方法后
Student s1=new Student("小明",1,1);
Student s2=new Student("小明",1,1);
System.out.println(s1.equals(s2));
}
}
class Student{
private String name;
private int sid;
private int age;
public Student(){}
public Student(String name,int sid,int age){
this.name=name;
this.sid=sid;
this.age=age;
}
//重写Object类中toString()方法
public String toString(){
return "name="+this.name+"\tsid="+this.sid+"\t age="+this.age;
}
//重写Object类中equals()方法
public boolean equals(Object obj){
if(this==obj){
return true;
}
if(obj instanceof Student){
Student s=(Student)obj;
if(!this.name.equals(s.name)){
return false;
}
if(this.sid!=s.sid){
return false;
}
if(this.age!=s.age){
return false;
}
return true;
}
return false;
}
}
15.简单工厂模式
工厂模式的应用很广泛,精髓之处在于通过工厂类来获取对象,而不是直接创造对象,这样的好处在于不依赖要创建的具体对象类型,以达到解耦的目的.
简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例,简单工厂模式是工厂模式家族中最简单实用的模式.
/**
2022-2-22
简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例,
简单工厂模式是工厂模式家族中最简单实用的模式.
*/
public class Test43{
public static void main(String[] args){
//使用者和被使用者两者之间,耦合,产生依赖,当被使用者改变时,会影响使用者
//使用工厂模式来降低两者之间的依赖
Product phone=new Phone();
phone.work();
phone=ProductFactory.getProduct("phone");
phone.work();
Product computer=ProductFactory.getProduct("computer");
computer.work();
}
}
//简单工厂模式
//工厂类
class ProductFactory{
public static Product getProduct(String method){
if("phone".equals(method)){
return new Phone();
}else if("computer".equals(method)){
return new Computer();
}else{
return null;
}
}
}
interface Product{
public void work();
}
class Phone implements Product{
public void work(){
System.out.println("手机类,手机开始进行工作...");
}
}
class Computer implements Product{
public void work(){
System.out.println("电脑类,电脑开始进行工作...");
}
}
16.静态代理模式
注意:代理模式是通过代理对象来达到控制目标对象的目的
代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问.
代理模式说白了就是"真实对象"的代表,在访问对象时引入一定程度的间接性,因为这种间接性可以附加多种用途.
/**
2022-2-21
*/
public class Test44{
public static void main(String[] args){
Action user=new UserAction();
Action target=new ActionProxy(user);
target.doAction();
}
}
class ActionProxy implements Action{
private Action target;//被代理的对象
public ActionProxy(){}
public ActionProxy(Action target){
this.target=target;
}
public void doAction(){
long startTime=System.currentTimeMillis();
target.doAction();//执行的操作
long endTime=System.currentTimeMillis();
System.out.println("共耗时是"+(endTime-startTime));
}
}
interface Action{
public void doAction();
}
class UserAction implements Action{
public void doAction(){
for(int i=0;i<100;i++){
System.out.println("第"+(i+1)+"次执行用户执行操作!");
}
}
}
17.适配器模式
Spring中MethodInterceptor适配器用来解决Aop的方法拦截器必须是实现了MethodInterceptor的问题.
适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口.适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作.
OO(面向对象)设计原则
面向接口编程(面向抽象编程)
封装变化
多用组合,少用继承
对修改关闭,对扩展开放
/**
2022-2-22
*/
public class Test45{
public static void main(String[] args){
PowerA a=new PowerAIplm();
work(a);
//用适配器将powerb进行work方法输出
PowerB b=new PowerBIplm();
Adapter adapter=new Adapter(b);
work(adapter);
Dog dog=new Dog();
dog.eat();
Cat cat=new Cat();
cat.eat();
}
public static void work(PowerA a){
System.out.println("正在连接...");
a.insert();
System.out.println("正在结束...");
}
}
interface Aniaml{
public void eat();
public void run();
public void sound();
public void heard();
}
//适配器抽象类
abstract class AnimalFunction{
public void eat(){}
public void run(){}
public void sound(){}
public void heard(){}
}
class Dog implements Aniaml{
public void eat(){
System.out.println("我是一只小狗,喜欢吃骨头!");
}
public void run(){}
public void sound(){}
public void heard(){}
}
/*
如果只实现接口中多种方法中的一种,
用抽象类比较合适
不用每个方法都重写一遍
*/
class Cat extends AnimalFunction{
public void eat(){
System.out.println("我是一只小猫,喜欢吃小鱼!");
}
}
//将B适配A的适配器
class Adapter implements PowerA{
private PowerB b;
public Adapter(){}
public Adapter(PowerB b){
this.b=b;
}
public void insert(){
b.connect();
}
}
interface PowerB{
public void connect();
}
class PowerBIplm implements PowerB{
public void connect(){
System.out.println("电机B开始工作...");
}
}
interface PowerA{
public void insert();
}
class PowerAIplm implements PowerA{
public void insert(){
System.out.println("电机A开始工作...");
}
}
18.内部类
注意:内部类的概念在一定程度上打破了类的定义,但在一些较复杂的类设计中,内部类解决了Java不能多继承的问题.
内部类就是在一个类的内部定义的类.
成员内部类
格式如下:
class Outer{
class Inner{}
}
编译上述代码会产生两个文件:
Outer.classs和OuterSInner.class.
方法内部类
内部类可以作为一个类的成员外,还可以把类放在方法内定义.
注意:
方法内部类只能定义在该内部类的方法内实例化,不可以在此方法外对齐实例化.
方法内部类对象不能使用该内部类所在方法的非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).
内部类的作用
每个内部类都能独立地继承自一个(接口)实现),所以无论外部类是否是否已经继承了某个(接口的)实现,对于内部类都没有影响.如果没有内部类提供的可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决.从这个角度看,内部类使得多重继承的解决方案变得完整,接口解决了部分问题,而内部类有效的实现了"多重继承".
我们在项目开发中如何选择?
成员:成员内部类/静态内部类
局部:方法内部类/匿名内部类
依赖外部类对象的,成员内部类,方法内部类,匿名内部类
静态内部类不依赖外部类的对象.所以,我们在项目中优先考虑选择静态内部类.(不会产生内存泄露)
/**
2022-2-23
内部类
1.成员内部类,直接在类中定义的类
2.方法内部类,在一个类中方法内定义一个类
3.静态内部类
*/
public class Test46{
public static void main(String[] args){
Outer out=new Outer();
//在外部创建成员内部类的实例,因为成员内部类需要依赖外部类的对象
/* Outer.Inner inner=out.new Inner();
inner.print(); */
out.innerPrint();
out.doSomething();
//静态内部类使用
Outer.Inner2 inner2=new Outer.Inner2();
inner2.print();
out.print1();
out.print2();
//参数式匿名内部类的使用
out.print3(new Dog(){
public void eat(){
System.out.println("Dog接口的参数式匿名内部类!");
}
});
}
}
class Outer{
private String name;
//建议在外部类中定义一个方法,对外提供访问内部类的接口
public void innerPrint(){
Inner inner=new Inner();
inner.print();
}
//成员内部类
class Inner{
public void print(){
System.out.println("成员内部类Inner");
}
}
//方法内部类
//方法内局部变量或方法的参数,实际上必须是常量,也就是final修饰声明的
public void doSomething(){
final int x=10;
class Inner1{
public void print(){
System.out.println("方法内部类Inner1!"+x);
}
}
Inner1 inner1=new Inner1();
inner1.print();
}
//不能在外提供方法内部类中方法的接口
//静态内部类
//不能访问静态内部类之外的非静态变量
static class Inner2{
public void print(){
System.out.println("静态内部类!");
}
}
//匿名内部类
//继承式
public void print1(){
Cat cat=new Cat(){
public void eat(){
System.out.println("抽象Cat类的继承式匿名内部类!");
}
};
cat.eat();
}
//接口式
public void print2(){
Dog dog=new Dog(){
public void eat(){
System.out.println("Dog接口的接口式匿名内部类!");
}
};
dog.eat();
}
//参数式
public void print3(Dog dog){
dog.eat();
}
}
interface Dog{
public void eat();
}
abstract class Cat{
public abstract void eat();
}
/* class A extends B{
class D extends C{
}
} */
19.数据结构之链表
递归算法
注意:递归算法规则要掌握,层级太深容易内存溢出,递归是把双刃剑,慎用
在链表数据结构中,我们需要使用到递归算法
递归算法是一种直接或者间接地调用自身算法的过程,在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往是算法描述间接而且容易理解.
递归必须要有出口
递归内存消耗大,容易发生内存溢出
层次调用越多,越危险
/**
2022-2-24
递归算法
*/
public class Test47{
public static void main(String[] args){
int result=factorials1(10);
System.out.println("循环方式计算10的阶乘:"+result);
result=factorials2(10);
System.out.println("\n递归算法计算10的阶乘:"+result);
result=factorials2(100);
System.out.println("\n递归算法发生内存溢出(超出int范围)时:"+result);
}
//递归算法
public static int factorials2(int num){
if(num==1)return 1;
return num*factorials2(num-1);
}
//循环方式计算阶乘
public static int factorials1(int num){
int result=num;
int i=num-1;
do{
result=result*i;
i--;
}while(i>1);
return result;
}
}
链表
链表(Linked list)一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个结点例存到是下一个节点的指针(Pointer).
/**
2022-2-25
数据结构之链表
*/
public class Test48{
public static void main(String[] args){
//测试
//添加
NodeManager node=new NodeManager(1);
node.add(2);
node.add(3);
node.add(4);
node.add(5);
node.add(6);
//打印
node.print();
//删除
node.del(6);
node.print();
//修改结点
node.updata(5,10);
node.print();
//查找结点是否存在
boolean flag=node.find(4);
if(flag==true){
System.out.println("链表中存在查找结点");
}else{
System.out.println("链表中不存在查找结点");
}
//插入结点
node.insert(0,13);
node.insert(4,11);
node.print();
node.insert(7,12);
}
}
class NodeManager{
private Node root;
public NodeManager(){}
public NodeManager(int data){
this.root=new Node(data);
}
//添加
public void add(int data){
if(root==null){
root=new Node(data);
}else{
root.addNode(data);
}
}
//删除结点
public void del(int data){
if(root==null){
System.out.println("空链表,删除失败!");
}else{
if(root.getData()==data){
root=root.next;
}else{
root.delNode(data);
}
}
}
//修改结点
public void updata(int oldData,int newData){
if(root==null){
System.out.println("空链表,修改结点失败!");
}else{
if(root.getData()==oldData){
root.setDate(newData);
System.out.println("修改结点成功!");
}else{
root.updataNode(oldData,newData);
}
}
}
//查找结点是否存在
public boolean find(int data){
if(root==null){
System.out.println("空链表,结点不存在!");
return false;
}else{
if(root.getData()==data){
return true;
}else{
return root.findNode(data);
}
}
}
//插入结点
public void insert(int index,int data){
if(index==0){
Node node=new Node();
node.next=root.next;
root.next=node;
node.data=root.data;
root.data=data;
System.out.println("插入根结点成功!");
}else{
root.insertNode(index,data);
}
}
//打印所有结点
public void print(){
if(root!=null){
System.out.print(root.getData());
root.printAllNode();
System.out.println();
}
}
private class Node{
private int data;
private Node next;//把当前类型当做属性
public Node(){}
public Node(int data){
this.data=data;
}
public void setDate(int data){
this.data=data;
}
public int getData(){
return 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){
System.out.println("链表不存在删除值,删除失败!");
}else{
if(this.next.data==data){
this.next=this.next.next;
System.out.println("链表存在删除值,删除成功!");
}else{
this.next.delNode(data);
}
}
}
//修改结点
public void updataNode(int oldData,int newData){
if(this.next==null){
System.out.println("链表内不存在旧数据,修改结点失败!");
}else{
if(this.next.data==oldData){
this.next.data=newData;
System.out.println("修改结点成功!");
}else{
this.next.updataNode(oldData,newData);
}
}
}
//查找结点是否存在
public boolean findNode(int data){
if(this.next==null){
return false;
}else{
if(this.next.data==data){
return true;
}else{
return this.next.findNode(data);
}
}
}
//插入结点
public void insertNode(int index,int data){
if(this.next==null){
System.out.println("超出范围,无法进行插入结点!");
}else{
if(index==1){
Node node=new Node(data);
node.next=this.next;
this.next=node;
System.out.println("插入结点成功!");
}else{
this.next.insertNode(index-1,data);
}
}
}
//打印所有结点
public void printAllNode(){
if(this.next!=null){
System.out.print("->"+this.next.data);
this.next.printAllNode();
}
}
}
}
掌握数组与链表的优缺点,才知道使用时如何选择,基于数组实现的动态数组有ArrayList等,基于链表实现的有LinkedList等.
数组适合查找,遍历,固定长度
链表适合插入,删除,不宜过长,否则会导致遍历性能下降
20.基本数据类型包装类
注意:在JAVA中有一个设计的原则"一切皆对象",java中的基本数据类型就完全不符合这种设计思想,因为八种基本数据类型并不是引用数据类型,所以Java中为了解决这样的问题,JDK1.5以后引入了八种基本数据类型的包装类.
八种包装类分为两大类型:
Number:Integer,Short,Long,Double,Float,Byte都是Number的子类表示一个数字.
Object:Character,Boolean都是Object的直接子类.
装箱及拆箱操作
将一个基本数据类型转换为包装类,那么这样的操作称为装箱操作.将一个包装类转换为基本数据类型,这样的操作称为拆箱操作.
方法:
byteValue():将Byte包装类拆箱为byte
doubleValue():将Double包装类拆箱为double
floatValue():将Float包装类拆箱为float
intValue():将Integer包装类拆箱为int
longValue():将Long包装类拆箱为long
shortValue():将Short包装类拆箱为short
转型操作
在包装类中,可以将一个字符串变为指定的基本数据类型,一般在输入数据时会使用较多.
注意:转型操作时,字符串必须由数字组成,否则会出现错误.
享元模式
它使用共享对象,用来尽可能减少内存使用量以及分享资讯尽可能多的相似对象;它适合用于当大量对象只是重复因而导致无法令人接受的使用大量内存.通常对象中的部分状态是可以分享.
常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元.
运用共享技术有效的支持大量细粒度的对象.
/**
2022-2-25
基本数据类型包装类
*/
public class Test50{
public static void main(String[] args){
//装箱:把基本数据类型转换为包装类,称为自动装箱
Integer i1=new Integer(10);
//把包装类转换为基本数据类型,称为自动拆箱
int i2=i1.intValue();
Integer i3=10;//简写,建议方式
Integer i4=new Integer("123");
//把数值字符串转换为int类型
String num1="12";
int i5=Integer.parseInt(num1);
System.out.println(i5);
Integer x1=new Integer(10);
Integer x2=new Integer(10);
System.out.println(x1==x2);//false
System.out.println(x1.equals(x2));//true
Integer x3=new Integer(128);
Integer x4=new Integer(128);
System.out.println(x3==x4);//false
System.out.println(x3.equals(x4));//true
Integer x5=10;
Integer x6=10;
System.out.println(x5==x6);//true
System.out.println(x5.equals(x6));//true
Integer x7=128;//超过一个字节,不适用享元模式
Integer x8=128;
System.out.println(x7==x8);//false
System.out.println(x7.equals(x8));//true
}
}
21.包与访问修饰符
包用于对多个java源文件的管理,就像我们的文件目录一样.
定义一个包:package com.vince;
该语句只能出现在代码中的第一句.
访问修饰符
public(同一个类,同包,不同包子类,不同包非子类)
protected(同一个类,同包,不同包子类)
默认(同一个类,同包)
private(同一个类)
OO(面向对象)原则总结
(1)开闭原则
一个软件实体,如类,模块和函数应该对扩展开放,对修改关闭.
(2)合成/聚合复用原则
新对象的某些功能在已创建好的对象里已实现,那么尽量用已有对象提供的功能,使之成为新对象的一部分,而不要再重新创建.
(3)依赖倒置原则
高层模块不应该依赖底层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象.
(4)接口隔离原则
客户端不应该依赖它不需要的接口;一个类另一个类的依赖应该建立在最小的接口上.
(5)迪米特法则
一个对象应该对其他对象保持最少的了解
(6)里氏替换原则
所有引用基类的地方必须能透明地使用其子类的对象.
(7)单一职责原则
不要存在多于一个导致类变更的原因,即一个类只负责一项职责.