课程大纲
- 继承的基本概念
- 继承的限制
- 子类的实例化过程
- 方法的重写
- super 关键字
- 继承的应用示例
- final关键字
- 抽象类
- 接口
- 多态性
- instanceof关键字
- 抽象类应用-模版方法模式
- 接口应用-策略模式
- Object类
- 简单工厂模式
- 静态代理模式
- 适配器模式
- 内部类
- 数据结构之链表
- 基本数据类型 包装类
- 包与访问修饰符
继承的基本概念
继承是从已有的类中创建新类的过程
继承一个父类,只能继承非私有的数据(属性和方法)
- 继承是面向对象三大特征之一
- 被继承的类称为父类(超类),继承父类的类称为子类(派生类)
- 继承是指一个对象直接使用另一个对象的属性和方法。
- 通过继承可以实现代码重用
语法:
[访问权限]class 子类名 extends 父类名{
类体定义;
}
示例:
protected(受保护的访问权限修饰符,用于修饰方法和属性,使用protected修饰的属性和方法可以被子类继承)
继承的限制约定:
- Java只能实现单继承(不能既继承狗,又继承猫)
- 允许多层继承,既一个子类可以有一个父类,这个父类还可以有其他父类。
- 继承只能继承非私有的属性和方法
- 构造方法不能被继承
- 创建子类对象时,父类的构造方法也会被调用,因为子类需要父类的数据,那么就要通过父类的构造方法来初始化数据
- 创建子类对象时会调用父类的默认构造方法,父类没有默认构造方法也要强行凑参数
补充说明:问,Java构造函数能否被继承,为什么?
答:不能,因为子类继承父类的时候,先运行父类的构造函数;具体的说就是运行父类时就会先“调用”父类的构造函数,注意“调用”和继承不是一个含义,实质上是“自动运行”。
继承的含义其实是“扩展”,子类完全没有必要扩展父类的构造函数,因为反正每次调子类的时候都会“自动运行”它父类的构造函数,如果真的需要子类构造函数特殊的形式,子类直接修改或重载自己的构造函数就好了
“调用”一个类有“继承”和“组合(说白了new一个类)”两种方式,当你“调用”一个类的时候就会“自动运行”他的“构造函数”
继承小结
- 继承是发生在多个类之间的
- 继承使用关键字extends
- Java只能单继承,允许多层继承
- 被继承的类叫做父类(超类),继承父类的泪叫做子类(派生类)
- 在父类中的非私有方法和属性可以被子类继承
- protected(受保护的访问权限修饰符),修饰的属性或方法可以被子类继承
- 构造方法不能被继承
- 创建对象会调用构造方法,调用构造方法不一定就是创建对象。例如创建homedog对象,会调用homedog和dog构造方法,但调用dog构造方法并没有创建dog对象
- 实例化子类对象,会先调用父类的父类的构造方法,如果父类中没有默认的构造方法,那么子类必须显示的通过super(…)来调用父类的带参构造方法。super也只能在子类构造方法在的第一句。
举例子:
package test;import java.util.Arrays;
import java.util.Scanner;
public class test{
public static void main(String[] args){
}
}
class Dog{
protected String name;
private String sex;
//Dog 有参构造函数,
//子类调用时发现没有无参构造函数,只能强行调用有参构造函数初始化了
Dog(String name,String sex){
this.name=name;
this.sex=sex;
}
}
class HomeDog extends Dog{
//初始化
public HomeDog(String name) {
super(name, "傻狗");
}
}
class HuskyDog extends Dog{
public HuskyDog(){
//初始化
//即使啥参数不需要,也要强行弄俩参数
super("娘子","啊哈");//只能放在第一句
}
}
继承的好处
- 提高代码的复用性
- 提高代码的维护性
- 让类与类之间产生关系,是多态的前提
继承的缺点
增强了类与类之间的耦合性
开发原则:高内聚,低耦合
3.子类的实例化过程
在子类进行实例化操作的时候,首先会让琦父类进行初始化操作。之后自己再进行实例化操作。
子类实例化过程:
子类实例化会先调用父类的构造方法
如果父类中没有默认的构造方法,在子类构造方法中必须显示的调用父类的构造方法
结论
构造方法只是用于初始化类的字段以及执行一些初始化代码
调用构造方法并不代表会生成对象
4.方法的重写
方法重写(overriding method)
在Java中 ,子类可以继承父类的方法,而不需要编写相同的方法。但有时候子类并不想原封不动地继承父类的方法,而是想做一定的修改,这就需要才有方法的重写,方法重写又称方法覆盖。在子类和父类中,重写方法后,在调用中,以创建方法的对象 类型为准,会调用谁的方法
package test;
import java.util.Arrays;
import java.util.Scanner;
public class test{
public static void main(String[] args){
Dog dog=new Dog();
dog.eat();
HuskyDog husky=new HuskyDog();
husky.eat();
}
}
class Dog{
Dog(){}
public void eat(){
System.out.println("我是Dog,我会吃");
}
}
//重写父类方法,但没有new类对象,因此没被调用
class HomeDog extends Dog{
public void eat(){
System.out.println("我是家狗,我会吃");
}
}
//重写父类的方法;并且哈士奇对象来调用
class HuskyDog extends Dog{
public void eat(){
System.out.println("我是哈士奇,我会吃");
}
}
/输出结果:
我是Dog,我会吃
我是哈士奇,我会吃/
关于方法重写的一些特性:
- 发生在子父类中,方法重写的两个返回值,方法名,参数列表必须完全一致(子类重写父类的方法)
- 子类抛出的异常不能超过父类相应方法抛出的异常(子类异常不能大于父类异常)
- 子类方法的访问级别不能低于父类相应方法的访问级别(子类访问级别不能低于父类访问级别)
- 父类中的方法若使用private,static,final任意修饰符,那么不能被子类重写
为什么要重写方法?或者 方法重写的目的是什么?
若子类从父类中继承过来的方法,不能满足子类特有需求时,子类就需要重写父类中相应的方法,方法的重写也是程序扩展的体现
面试题:overloading 与 overriding的区别?
overloading :方法的重载,发生在同一个类中。方法名相同,参数列表不同,与返回值无关
overriding:方法的重写,发生在子父类中,方法名相同,参数列表相同,返回值相同(三同)
子类的访问修饰符大于或等于父类的修饰符,子类的异常声明必须小于或等于父类的异常声明。如果方法被private static final修饰,那么不能被重写
5.super关键字
super可以完成以下的操作:
-
使用super调用父类中的属性,可以从父类实例处获得信息
class HuskyDog extends Dog{ //super.属性 表示调用父类的属性,如果是继承过来的属性,super可以省略 protected void println (super.name+"我是哈士奇"); }
-
使用super调用父类中的方法,可以委托父类对象完成某件事情
class HuskyDog extends Dog{ public void eat(){ //调用父类的吃饭方法 super.eat(); System.out.printf("我是子类的吃饭"); } }
-
使用super调用父类中的构造方法(super(实参形式)),必须在子类构造方法的第一条语句,调用父类中的构造方法,若不显示的写出来,默认调用父类的无参构造方法,比如:super();
public HuskyDog(){ //初始化 //即使啥参数不需要,也要强行弄俩参数 super("娘子","啊哈");//只能放在第一句 }
this表示当前对象
使用super来调用父类的而属性,方法,和构造方法。
6.继承的应用示例
实现一个化妆品商城中的化妆品管理
- 定义一个化妆品类(Cosmetic);name品牌,type进口或国产 ,price单价
- 定义一个化妆品管理类(CosmeticManager)
(1)实现进货功能
(2)可以输出所有化妆品信息 的功能 - 使用继承实现一个可按单价排序输出所有化妆品的功能
- 使用继承实现一个只输出 进口 化妆品的功能
package test;
import java.util.Arrays;
import java.util.Scanner;
public class test{
public static void main(String[] args){
//可以继承属性和方法,但参数不能继承
CosmeticManager cm=new CosmeticManager ();
SortCosmeticManager scm=new SortCosmeticManager ();
ImportCosmaticManager icm=new ImportCosmaticManager ();
cm.add(new Cosmatic("香奈儿","中国",100));
cm.add(new Cosmatic("大宝","中国",200));
cm.add(new Cosmatic("欧莱雅","外国",180));
cm.add(new Cosmatic("大白兔","外国",60));
scm.add(new Cosmatic("香奈儿","中国",100));
scm.add(new Cosmatic("大宝","中国",200));
scm.add(new Cosmatic("欧莱雅","外国",180));
scm.add(new Cosmatic("大白兔","外国",60));
icm.add(new Cosmatic("香奈儿","中国",100));
icm.add(new Cosmatic("大宝","中国",200));
icm.add(new Cosmatic("欧莱雅","外国",180));
icm.add(new Cosmatic("大白兔","外国",60));
System.out.println("-------原样输出-------");
cm.printInfo();
System.out.println("--------价格升序输出------");
scm.printInfo();
System.out.println("-------只输出外国-------");
icm.printInfo();
}
}
//化妆品管理类
class CosmeticManager{
protected Cosmatic[] cs=new Cosmatic[4];
protected int count;//定义下标
//进货功能
public void add(Cosmatic c){
int size=cs.length;
if(count >=size){
int newlen=size*2;
cs=java.util.Arrays.copyOf(cs, newlen);
}
cs[count]=c;
count++;
}
//输出所有产品方法
public void printInfo(){
for(int i=0;i<count ;i++){
System.out.println(cs[i].getInfo());
}
}
}
//可按单价排序的化妆品管理类
class SortCosmeticManager extends CosmeticManager{
//排序输出所有产品
public void printInfo(){
Cosmatic[] temp=java.util.Arrays.copyOf(cs, count);
Cosmatic c=null;
for (int i=0;i<temp.length-1;i++){
//length-1是为了下标从零开始,不加等于号是因为最后一轮不用比
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(Cosmatic cosmatic:temp){
System.out.println(cosmatic.getInfo());
}
}
}
//
class ImportCosmaticManager extends CosmeticManager {
ImportCosmaticManager(){}
public void printInfo(){
for(int i=0;i<count;i++){
//比较两个字符串的值是否相等,不能使用==,因为等号是比较地址,比价值用equals
//注意!!是"字符串".equals()不能忘记点
//确定的放前面,避免空指针报错
if("外国".equals(cs[i].getType()))
{
System.out.println(cs[i].getInfo());
}
}
}
}
//化妆品类
class Cosmatic {
private String name;//品牌
private String type;//进口或者国产
private int price;//单价
//构造方法
public void Cosmatic(){}
public Cosmatic (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+"的化妆品产自"+type+",价格是"+price+"元人民币";
}
}
7.final关键字
使用final关键字声明一个常量
-
使用final关键字声明一个常量 修饰属性或修饰局部变量(最终变量),也称为常量。
public class test1{ //未来用10的时候,都可以用PERSON_NUM表示 //固定的值没必要申请对象改,所以必须定义成静态static static final int PERSON_NUM=10; public static void main(String[] args){ System.out.println(test1.PERSON_NUM); } }
常量类
//常量必须在构造器或者定义时初始化
public class test1{
public static void main(String[] args){
System.out.println("此为在定义时初始化:"+Constant.PERSON_NUM);
System.out.println("此为在构造器中初始化:"+FinalClass.DAY_NUMBER);
}
}
//常量类(工具类)
class Constant{
//要静态,此为在定义时初始化
public final static int PERSON_NUM=10;//人数
}
class FinalClass{
//不能写静态,因为要在在构造器中初始化
public final int DAY_NUMBER ;
public FinalClass(){
DAY_NUMBER = 22;
}
}
- 使用final关键字声明一个方法 该方法为最终方法,且只能被子类继承,但是不能被子类重写
- 使用final关键字声明一个类 该类就转变为最终类,没有子类的类,final修饰的类无法被继承
- 在方法参数中使用final,在该方法内部不能修改参数的值(再内部类中详解)
8.抽象类
抽象类的基本概念:
-
很多具有相同特征和行为的对象可以抽象为一个类;很多具有相同特征和行为的类可以抽象为一个抽象类。
-
使用abstract关键字声明的类为抽象类。
public class test1{ public static void main(String[] args){ Man m=new Man(); m.eat(); m.move(); } } abstract class Anmal{ public abstract void move();//方法的声明,抽象方法只有声明没有实现 } abstract class Person extends Anmal{ public abstract void eat(); } //继承抽象类的具体类必须实现所有的抽象方法 class Man extends Person{ //Man类要实现两个具体方法 public void eat() { System.out.println("吃"); } public void move() { System.out.println("运动"); } }
抽象类的规则:
- 抽象类可以没有抽象方法(可以有具体方法),有抽象方法的类必须是抽象类
- 非抽象类可以继承抽象类,但必须实现所有抽象方法
- 抽象类可以继承抽象类,可以不实现父类抽象方法
- 抽象类可以有方法实现和属性
- 抽象类不能被实例化
- 抽象类不能声明为final
- 抽象类可以有构造方法
9.接口
接口的定义格式
接口和类是平级的,接口是一种规范,比方说合同。
interface 接口名称
{
全局常量;
抽象方法;
}
接口的概念
-
接口是一组行为的规范··定义,没有实现(jdk1.8默认方法)
//jdk1.8可以定义默认方法,可以被所有实现类继承 public default void print(){ System.out.println("吃"); }
-
使用接口,可以让我们的程序更加有利于变化
-
接口是面向对象编程体系中的思想精髓之一
-
面向对象设计法则:基于接口编程
package test; public class test{ public static void main(String[] args){ Girl mm=new Girl("玛丽"); mm.sleep(); mm.eat(); mm.run(); } } interface IEat{ //接口中只能定义抽象方法,和抽象类不一样,所有有一种简写方法 //public abstract void eat(); void eat();//在接口中定义的方法没有声明修饰符,默认为public abstract //在接口中定义一个常量 //public static final int NUM=10; //同样也有简写 int NUM=10; } 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 +"爱吃"); } @Override public void run() { System.out.println("我爱跑"); } } /*运行结果 我爱睡觉 玛丽爱吃 我爱跑*/
接口的使用规则:
- 定义一个接口,使用interface关键字
- 在一个接口中,只能定义常量,抽象方法,jdk1.8后可以定义默认的实现方法defaul
- 接口可以继承多个接口,extends XXX,XXX
- 一个具体类实现接口使用implements
- 一个类可以实现多个接口
- 抽象类实现接口可以不实现接口的方法
- 在接口定义的方法没有声明 访问修饰符,默认为public
- 接口不能有构造方法
- 接口不能被实例化
面向对象的设计原则
- 对修改关闭,对扩展开放
- 面向接口编程
10.多态性
多态是面向对象三大特征之一
什么是多态性?
对象再运行过程中的多种形态
多态性我们大概可以分为两类:
- 方法的重载和重写
- 对象的多态性
例如:
//用父类的引用指向子类对象(用打得类型去接受校对而类型,向上转型,自动转换)
Chicken home =new HomeChicken();
结论:
在编程时针对抽象类型的编写代码,称为面向抽象编程(或面向接口编程)
父类通常都定义为抽象类,接口
对象的多态性
对象的多态性是从继承关系中的多个类而来,
向上转型:将子类实例转化为父类引用
格式:父类 父类对象=子类实例;一>自动转换
以基本数据类型操作为例:int i=‘a’;
(因为char的容量比int小,所以可以自动完成)
**向下转型:**将父类实例转化为子类实例
格式:子类 子类对象=(子类)父类实例; 强制转换
以基本数据类型操作为例:char c=(char)94;
因为整型是4个字节比char2个字节要大,所以需要强制完成
package test;
public class test {
public static void main(String[] args) {
// 用父类的引用指向子类对象(用大的类型表示小的类型),自动转换(向上转型)
Chicken hc = new HomeChicken("家鸡");
// hc.eat();
eat(hc);
YeChicken yc = new YeChicken("野鸡");
// yc.eat();
eat(yc);
JianJiaoChicken jjj = new JianJiaoChicken("野鸡");
eat(jjj);
}
// 抽象(粒度 ) 面向抽象编程(面向接口编程)
public static void eat(Chicken c) {
System.out.println("鸡吃饭");
c.eat();
//错误示例JianJiaoChicken jjc=Chicken();
JianJiaoChicken jjc=(JianJiaoChicken) c;
jjc.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)等问题,就是完成父类的构造: 它实际上等同于: Super name = name;
super(name);
}
public void eat() {
System.out.println(this.getName() + "我爱吃小米");
}
}
// 野鸡
class YeChicken extends Chicken {
public YeChicken() {
}
public YeChicken(String name) {
// super(name)等问题,就是完成父类的构造: 它实际上等同于: Super name = 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是用于检查对象是否为指定的类型,通常在把父类引用强制转换成子类引用时要使用,以避免发生类型转换异常(ClassCastException)。
格式:
对象 instanceof 类型 --------返回boolean类型值
示例:
if(homeChicken instanceof Chicken){
//....
}
该语句一般用于判断一个对象是否为某个类的实例,是返回ture,否返回false
if(子类对象 instanceof 子类类型){//成立的条件是对现象本身及对象的夫类型,都可以通过检验
子类类型 子类对象=(父类类型)负累对象;
子类对象.(子类方法);
}
父类的设计法则
通过instanceof关键字,我们可以很方便的检查对象的类型,但如果一个父类的子类过多,这样的判断还是显得很繁琐,那么如何去设计一个父类哪?
- 父类通常情况下都设计为抽象类或接口,其中优先考虑接口,如接口不能满足才考虑抽象类。
- 一个具体类尽可能不去继承另一个具体类,这样的好处是无需检查对象是否为父类的对象
12。抽象类的使用一一模版方法模式(煎饼果子)
模版方法模式(Templete Method):定义一个操作中的算法的骨架,而将一些可变部分的实现延迟到子类中。模版方法模式是的子类可以不改变一个算法的结构即可重新定义该算法的某些特定的步骤。
public class test1 {
public static void main(String[] args) {
UserManager um = new UserManager();
um.action("admin", "add");
}
// 在Java中,类中的静态方法不能直接调用动态方法,主程序为public static class开头,所以不能直接调用BaseManager方法
abstract static class BaseManager {
public void action(String name, String menthod) {
if ("admin".equals(name)) {
excute(menthod);
} else {
System.out.println("你没有操作权限,请联系管理员");
}
}
public abstract void excute(String menthod);
}
// 在Java中,类中的静态方法不能直接调用动态方法,主程序为public static class开头,所以不能直接调用UserManager方法
public static class UserManager extends BaseManager {
public void UserManager() {
};
public void excute(String menthod) {
if ("add".equals(menthod)) {
System.out.println("已经完成添加操作");
}
if ("del".equals(menthod)) {
System.out.println("已经完成删除操作");
}
}
}
}
13.接口应用一一策略模式(锦囊妙计)
策略模式(StrategyPattern)定义了一系列的算法,将每一种算法封装起来并可以相互替换使用,策略模式让算法独立于使用他的客户应用而独立变化。
OO设计原则:
-
面向接口编程(面向抽象编程)
-
封装变化
-
多用组合,少用继承
/* 自己写的
1.创建了一个抽象类BaseService,用实体类UserService继承
2.创建了一个接口ISave ,定义方法save()
3.创建俩锦囊类FileSave和NetSave继承接口ISave,并定义save()方法
4.*/
package test;
public class test {public static void main(String[] args) { //小的放大里面 BaseService user=new UserService(); //未来使用时可以替换,如 //user.setiSave(new NetSave()); user.setiSave(new FileSave()); user.add("user"); } } interface ISave { public void save(String data); } //锦囊一 class FileSave implements ISave { public void save(String data) { System.out.println("正在把数据保存到文件当中..." + data); } } //锦囊二 class NetSave 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 ISave getiSave() { return iSave; } public void add(String data){ System.out.println("检查数据合法性..."); iSave.save(data); System.out.println("数据保存完毕..."); } } class UserService extends BaseService{}
14.Object类
object类是类层次结构的根类(祖宗)
每个类都使用Object作为超类。所有的对象(包括数组)都实现这个类的方法
所有类都是object类的子类
public String toString()方法
返回该对象的字符串表示
通常,toString 方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明且移动的信息表达式。建议所有子类都重写此方法。
/*toString */
package test;
public class test {
public static void main(String[] args) {
Student s = new Student(1, "博博",18);
System.out.println(s.toString());
}
}
class Student {
public Student( int sid,String name, int age) {
this.name = name;
this.age = age;
this.sid = sid;
}
public void Student() {
}
private String name;
private int sid;
private int age;
// 重写Object类中的toString方法
public String toString() {
return "sid:" + sid + ",name:" + name + ",age:" + age;
}
}
//运行结果:sid:1,name:博博,age:18
public boolean equals(Object obj)
指示其他某个对象是否与此对象“相等”。equals方法在非空对象引用上实现相等关系:
自反性
对称性
传递性
一致性
/* */
package test;
import java.lang.*;
public class test {
public static void main(String[] args) {
Student s1 = new Student(2, "贝贝", 19);
//System.out.println(s1.toString());
Student s2 = new Student(2, "贝贝", 19);
//boolean b = s.equals(s2);
System.out.println("student equals:"+s1.equals(s2));
String str1 = new String("贝贝");
String str2 = new String("博博");
System.out.println(str1.equals(str2));
}
}
class Student {
public Student(int sid, String name, int age) {
this.name = name;
this.age = age;
this.sid = sid;
}
public void Student() {
}
private String name;
private int sid;
private int age;
// 重写Object类中的toString方法
public String toString() {
return "sid:" + sid + ",name:" + name + ",age:" + age;
}
// 重写equals方法,来实现两个对象的比较,值匹配
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Student) {
Student s = (Student) obj;
if (!this.name.equals(name)) {
return false;
}
if (this.sid != s.sid) {
return false;
}
if (this.age != s.age) {
return false;
}
return true;
}
return false;
}
}
//运行结果
student equals:true
false
protected void finalize()throwable
当垃圾回收器确定不存在对该对象的更多引用时,由对象回收器调用此方法,子类重写finalize方法,以配置系统资源或执行其他清除。
public final Class<?>getClass()
返回此Object的运行时类。获取该类在方法区的类信息
其他方法后续章节中详解
15.简单工厂模式
简单工厂模式是由一个工厂对象界定创建哪一种产品类的实例。简单工厂模式是工厂模式家族中 最简单最实用 的模式。
/*toString */
package test;
public class test {
public static void main(String[] args) {
//使用者与被使用者之间,耦合,产生了依赖,当被使用者改变时,会影响使用者
//使用工厂模式来降低两者的耦合
Product phone=ProductFactory.getProduct("phone");
if(null!=phone){
phone.work();
}
}
}
//工厂类
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;
}
}
}
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):为其他对象提供一种代理以控制之而过对象的访问
代理模式说白了就是“真实对象”的代表,再访问对象时引入一定程度 的间接性,因为这种间接性可以附加多种用途。
package test;
public class test {
public static void main(String[] args) {
Action userAction = new UserAction();
Actionproxy proxy = new Actionproxy(userAction);
proxy.doAction();
}
}
interface Action {
public void doAction();
}
class UserAction implements Action {
// 一些杂乱无章的方法可以单列出来代理
public void doAction() {
System.out.println("用户开始工作...");
}
}
class Actionproxy implements Action {
private Action target;// 被代理的对象
Actionproxy() {
}
Actionproxy(Action target) {
this.target = target;
}
// 执行操作
public void doAction() {
long startTime = System.currentTimeMillis();
target.doAction();// 执行真正的业务
long endTime = System.currentTimeMillis();
System.out.println("共耗时" + (endTime - startTime));
}
}
/*结果:
用户开始工作...
共耗时0
*/
17.适配器模式
适配器模式(Adapter):将一个类的接口转换成客户希望的另一个接口,适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
oo设计原则:
- 面向接口编程(面向抽象编程)
- 封装变化
- 多用组合,少用继承
- 对修改关闭,对扩展开放
package test;
public class test {
public static void main(String[] args) {
PowerA powerA = new PowerAImpl();
work(powerA);
PowerB powerB = new PowerBImpl();
Adapter adapter = new Adapter(powerB);
work(adapter);
}
public static void work(PowerA a) {
System.out.println("正在连接...");
a.insert();
System.out.println("工作结束...");
}
}
interface PowerA {
public void insert();
}
interface PowerB {
public void connet();
}
// 适配器,要适配A才能把接口插到A里面去
class Adapter implements PowerA {
private PowerB powerB;
public Adapter() {
}
public Adapter(PowerB powerB) {
this.powerB = powerB;
}
public void insert() {
powerB.connet();
}
}
class PowerAImpl implements PowerA {
public void insert() {
System.out.println("电源A开始工作");
}
}
class PowerBImpl implements PowerB {
public void connet() {
System.out.println("电源B开始工作");
}
}
/*正在连接...
电源A开始工作
工作结束...
正在连接...
电源D开始工作
工作结束...
*/
适配器的第二种使用方法
interface Animal {
public void sing();
public void jump();
public void rap();
public void basketball();
}
//如果我只想实现rap曾么办?
//适配器,抽象实现
abstract class AnimalFunction{
public void sing(){};
public void jump(){};
public void rap(){};
public void basketball(){};
}
class Man extends AnimalFunction{
public void rap(){
System.out.println("你看这个面,它又长又宽");
}
}
18.内部类
内部类就是在一个类的而内部定义的类
成员内部类格式如下:
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局部变量
静态内部类
在一个类内部定义一个静态内部类:
静态的含义是该内部类可以像其他静态成员一样。没有外部类对象时,也能够访问他。不依赖于对象,静态嵌套类仅能访问外部类的静态成员和方法public class test { public static void main(String[] args) { Outer.Inner n = new Outer.Inner(); } } class Outer { static class Inner { } }
匿名内部类
匿名内部类就是没有名字的内部类。
匿名内部类的三种情况:
- 继承式的匿名内部类
- 接口式的匿名内部类
- 参数式的匿名内部类
在使用匿名内部类时,要记住以下几个原则:
-
不能有构造方法,只能有一个实例。
-
不能定义任何静态变量.静态方法。
-
不能是public,protected,private,static。
-
一定是在new后面的,用其隐含实现一个接口或继承一个类。
-
匿名内部类是局部的。使用局部内部类的所有限制都对其生效。
package test; public class test { public static void main(String[] args) { Outer outer = new Outer(); outer.print1(); outer.print2(); // 参数式匿名内部类 outer.print3(new Eat() { public void eat() { System.out.println("参数式匿名内部类"); } }); } } // ......匿名内部类....... // ......继承式匿名内部类....... abstract class Cat { public abstract void eat(); } class Outer { public void print1() { final Cat cat = new Cat() { public void eat() {// 大括号表示实现 System.out.println("eat:继承式匿名内部类"); } };// 特别注意这个分号 cat.eat(); } // ......接口式匿名内部类....... public void print2() { IEat eat = new IEat() { public void eat() { System.out.println("eat:接口式匿名内部类"); } }; eat.eat(); } // ......参数式匿名内部类....... public void print3(Eat eat) { eat.eat();// eat方法见上面 } } interface IEat { public void eat(); }
问题:局部内部类访问局部变量必须用final修饰,为什么?
当调用这个方法时,局部变量如果没有final修饰,他的生命周期和方法的生命周期是一样的,当方法被调用时会入栈,方法结束后即弹栈,这个局部变量也会消失,那么,如果局部内部类变量还没有马上消失,并企想用这个局部变量,显然已经无法使用了,如果用final修饰,则会在类加载的时候进入常量池。即使方法弹栈。常量池的常量还在,也就可以继续使用了
注意:在jdk 1.8中取消了这个特性,改为编译器默认添加final修饰符了。
内部类的作用
每个内部类都能独立的继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。如果内部类提供的可以继承多个具体或抽象的类的能力,一些设计和编程问题就很难解决,从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效的实现了“多重继承”。
如:
class A extends B{
class C extends D{
}
class E extends F{
}
}
18.递归(略)
19.数据结构之链表
一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,
而是在每一个节点存到是笑一个节点的指针(Pointer)。
链表与数组:线性数据结构
数组适合查找遍历,固定长度
链表适合插入删除,不宜过长,否则会导致遍历性能下降
package test;
public class test {
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("......find.......");
System.out.println(nm.find(6));
System.out.println("......del.......");
nm.del(3);
nm.print();
System.out.println("......update.......");
nm.update(2, 8);
nm.print();
System.out.println("......index.......");
nm.insert(1, 9);
nm.print();
}
}
class NodeManager {
private Node root;// 根节点。root已经是Node类型了
private int currentIndex = 0;// 节点的 序号,每次操作从0开始
// 添加
public void add(int data) {
// 如果 没有根节点,就把node(data)当做根节点,
// 有的话,调用addNode(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 {
// 不删除根节点,调用delNode(data)搜索根节点的下一个
root.delNode(data);
}
}
// 更新
public boolean update(int oldData, int newData) {
if (root == null) {
return false;
}
if (root.getData() == oldData) {
root.setData(newData);// 改的是根节点。直接setDate
return true;
} else {// 否则改的是里面的
return root.updateNode(oldData, newData);
}
}
// 打印所有
public void print() {
if (root == null) {
return;
} else {
System.out.print(root.getData() + "->");
// 为什么结果显示5->4->3->2->1->
// 因为跑到这个的时候发现pritNode()调用发现data.next==null,但已经执行上面的语句了
root.printNode();
System.out.println();
}
}
// 查找
public boolean find(int data) {
if (root == null) {// 连根节点都没有
return false;
} else {
if (root.getData() == data) {
return true;
} else {
return root.findNode(data);
}
}
}
// 插入(向索引之前插入)
// 不知道插入第几个,所以要在前面设置一个变量
public void insert(int index, int data) {
if (index < 1)
return;
currentIndex = 1;// 每次都从1遍历
if (index == currentIndex/* 要插入的是1位置 */) {
Node newNode=new Node (data);
/* 后插法 root.next=newNode;*/
/*前插法*/
newNode.next=root;
/*然后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 void setData(int data) {
this.data = data;
}
public int getData() {
return data;
}
// 添加节点
public void addNode(int data) {
// 因为是root调用了当前方法,所以this是root,第二遍是root.next
if (this.next != null) {
// 如果不是链表尾巴,就遍历到最后一个后添加
this.next.addNode(data);
} else {
// 如果是链表尾巴,就添加
this.next = new Node(data);
}
}
// 删除节点
public void delNode(int data) {
if (this.next != null) {
// 刪除根节点下一个节点,前面已经判断过当前节点,此时应该判断下一节点
if (this.next.data == data) {
this.next = this.next.next;
// 使用下面代码会多删除一位
// this.next.next = this.next.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);
}
} else {
return false;
}
}
// 修改节点
public boolean updateNode(int oldData, int newData) {
if (this.next != null) {// 很长一串
if (this.next.data == oldData) {
this.next.data = newData;
return true;
} else {
return this.next.updateNode(oldData, newData);
}
}
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);
}
}
}
}
20.基本数据类型包装类
在Java中有一个设计的原则“一切皆对象”。Java中的基本数据类型就完全不符合这种设计思想,因为八种基本数据类型并不是引用数据类型,所以Java为了解决这样的问题,jdk1.5之后引入了八种数据类型的包装类
八种数据类型分为两大类型:
- Number:Integer,Short,Long,Double,Float,Byte都是Number的子类,表示是一个数字。
- Object:Character,Blooean都是Object的直接子类。
基本数据类型 | 包装类 |
---|---|
int | Integer |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
byte | Byte |
short | Short |
long | Long |
装箱及拆箱操作
将一个基本数据类型转化为包装类,那么这样的操作称为装箱操作。将一个包装类转化为另一个基本数据类型,这样的操作称为拆箱操作。
方法 | 描述 |
---|---|
byteValue( ) | Byte–>byte |
doubleValue( ) | Double–>double |
floatValue( ) | Float–>float |
intValue( ) | Integer–>int |
longValue( ) | Long–>long |
shortValue() | Short–>short |
package test;
public class test {
public static void main(String[] args) {
// 把基本数据类型转换为包装类,成为自动装箱
Integer i1 = new Integer(10);
// 把包装类转换为基本数据类型。称为自动拆箱
int i2 = i1.intValue();
Integer i3 = 10;// 建议这种方式
// 构造一个字符串,字符串转化为int
Integer i4 = new Integer("123");
System.out.println(i4);// 运行结果:123
//把数值字符串转换成int类型
String num1 = "12";
//建议parseInt()
int i5 = Integer.parseInt(num1);
int i6=Integer.valueOf(num1);
}
}
转型操作
在包装类中,可以将一个字符串变为指定的基本数据类型,一般在输入数据时会使用较多。在Integer类中将String变为int型数据:public static int parseInt(String s)
在Float类中将String变为float型数据:public static float parseFloat(String s)
注意:转型操作时,字符串必须由数字组成
面试题:==比较的是地址,equals比较的才是值
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 = 127;/*java中把一个字节的常量当作享元,so 127是一个字节128不是*/
Integer x6 = 127;
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
享元模式(Flyweight Pattern)它使用共享对象,来尽可能减少内存使用量以及分享资讯给更多的相似对象;它适用于当大量对象只是重复,因而导致无法令人接受的使用大量内存。通常对象中的部分状态可以分享。常见的而做法是把它们放在外部的数据结构,当需要使用时候再将它们传递给享元。
运用共享技术有效的支持大量细粒度的对象
21.包与访问修饰符
包用于对多个Java源文件的管理,就像我们的文件目录一样。
定义一个包:
package com. vince;
该语句只能出现在代码的第一句。
访问修饰符:
访问修饰符 | 同一个类 | 同包 | 不同包子类 | 不同包非子类 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | |
默认 | √ | √ | ||
private | √ |
OO原则总结
- 开闭原则一个软件实体如类,模块和函数都应该对扩展开放,对修改关闭。
- 合成/聚合复用原则新对象的某些功能在已经创建好的对象里已实现,那么尽量用已有对象提供的功能,使之成为新对象的一部分,而不要再重新创建。
- 依赖倒置原则高层模块不应该依赖底层模块,二者都因该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。
- 接口隔离原则客户端不应该依赖他不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
- 迪米特法则一个对象应该对其他对象保持最少的了解
- 里氏替换原则所有引用基类的地方必须能够透明的而是用其子类的对象
- 单一职责原则不要存在多于一个导致类变更的原因,即一个类只负责一项职责