黑马程序员——Java面向对象(三)

------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

一  抽象类 abstract

1.  抽象类理解

我们都知道在面向对象的领域一切都是对象,同时所有的对象都是通过类来描述的,但是并不是所有的类都是来描述对象的。如果一个类没有足够的信息来描述一个具体的对象,而需要其他具体的类来支撑它,

我们只抽取功能定义,而不抽取功能主体。那么这样的类我们称它为抽象类。

比如猫是动物的一种,如果我问你动物是什么样子的,那就不好定义了,它没有一个具体动物的概念,所以他就是一个抽象类。需要一个具体的动物,如猫,你就可以详细的描述猫是什么样子的。

2.抽象类

     在面向对象领域由于抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能实例化的。

     同时,抽象类体现了数据抽象的思想,是实现多态的一种机制。它定义了一组抽象的方法,至于这组抽象方法的具体表现形式由派生类来实现。同时抽象类提供了继承的概念,它的出发点就是为了继承,否则它没有存在的任何意义。所以说定义的抽象类一定是用来继承的。

抽象类的特点:

a:抽象方法一定在抽象类中;抽象类中不一定都是抽象方法;

b:抽象方法和抽象类都必须被abstract关键字修饰;

c:抽象类不能使用new创建对象,必须由子类复写所有的抽象方法后,d:建立对象调用。如果该类只覆盖了部门的抽象方法,那么该类也是抽象类。

d: abstract 不能与private、static、final或native并列修饰同一个方法。abstract不能与final并列修饰同一个类。

e: 如果子类也是抽象类,那么子类中的抽象方法不能与父类的抽象方法同名。

注意:抽象类中可以没有抽象方法,只是不让该类进行建立对象。

示例:

abstract class Animal
{
	public abstract void call();
} 
class Cat extends Animal
{
	//实现抽象类中的方法。
	public void call(){
		System.out.println("猫叫:喵...喵...");
	}
}
class Dog extends Animal
{
	//实现抽象类中的方法。
	public void call(){
		System.out.println("狗叫:汪...汪...");
	}
}
public class Test1
{
	public static void main(String[] args){
		Animal a=new Cat();
		Animal a1=new Dog();
		a.call();
		a1.call();
}
}

二  接口 Interface

1.接口理解

接口是一个特殊的抽象类,当抽象类中的方法都是抽象类时,那么该类可以通过接口的形式来表示。接口是抽象类的延伸,java了保证数据安全是不能多重继承的,也就是说继承只能存在一个父类,但是接口不同,一个类可以同时实现多个接口,不管这些接口之间有没有关系,所以接口弥补了抽象类不能多重继承的缺陷,但是推荐继承和接口共同使用,因为这样既可以保证数据安全性又可以实现多重继承。

2.接口

接口中常见定义:常量和抽象方法。

定义注意事项:

a:接口中的成员都有固定的修饰符,常量 public static final  方法:publicabstract

b:接口中的成员都是public的。

c:接口是不可以创建对象的,因为有抽象方法,需要被子类实现,子类对接口中的抽象方法都实现后,子类才可以实例化,不然子类就是抽象类。

d: Interface的方所有法访问权限自动被声明为public。

e: 接口中不存在实现的方法。

f:实现接口的非抽象类必须要实现该接口的所有方法。抽象类可以不用实现。

g: 在实现多接口的时候一定要避免方法名的重复。

接口的特点:

接口对外暴露的原则;

接口是程序的功能扩展;

接口使得俩个事物之间的耦合性降低;

接口可以被类多实现。也是对多继承不支持的另一种实现形式。

类与类之间继承关系,类与接口之间是实现关系且可以多实现,接口与接口之间是继承关系,并且可以多继承。

3  抽象类与接口的区别

(1)语法层次

抽象类方式中,抽象类可以拥有任意范围的成员数据,同时也可以拥有自己的非抽象方法,但是接口方式中,它仅能够有静态、不能修改的成员数据(但是我们一般是不会在接口中使用成员数据),同时它所有的方法都必须是抽象的。在某种程度上来说,接口是抽象类的特殊化。

(2)设计层次

1、 抽象层次不同。抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。

 2、 跨域不同。抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类。我们知道抽象类是从子类中发现公共部分,然后泛化成抽象类,子类继承该父类即可,但是接口不同。实现它的子类可以不存在任何关系,共同之处。抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is-a" 关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的,仅仅是实现了接口定义的契约而已。

 3、 设计层次不同。对于抽象类而言,它是自下而上来设计的,我们要先知道子类才能抽象出父类,而接口则不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,至于什么子类、什么时候怎么实现它一概不知。抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。

示例:

  我们有一个Door的抽象概念,它具备两个行为open()和close(),此时我们可以定义通过抽象类和接口来定义这个抽象概念:

抽象类:

abstract class Door
{
	abstract void open();
	abstract void close();
}

接口:

interface Door
{
	void open();
	void close();
}

现在如果我们需要门具有报警的功能,那么该如何实现呢?

解决方法一:给Door增加一个报警方法:clarm();

抽象类:

abstract class Door
{
	abstract void open();
	abstract void close();
	abstract void clarm();
}

接口:

interface Door
{
	void open();
	void close();
	void clarm();
}

    这种方法违反了面向对象设计中的一个核心原则 ISP (Interface Segregation Principle)—见批注,在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变而改变,反之依然。

ISP(Interface Segregation Principle):

面向对象的一个核心原则。它表明使用多个专门的接口比使用单一的总接口要好。
一个类对另外一个类的依赖性应当是建立在最小的接口上的。
一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。


解决方法二:

 既然open()、close()和alarm()属于两个不同的概念,那么我们依据ISP原则将它们分开定义在两个代表两个不同概念的抽象类里面,定义的方式有三种:

       1、两个都使用抽象类来定义。

      2、两个都使用接口来定义。

      3、一个使用抽象类定义,一个是用接口定义。

 由于java不支持多继承所以第一种是不可行的。后面两种都是可行的,但是选择何种就反映了你对问题域本质的理解。

 

如果选择第二种都是接口来定义,那么就反映了两个问题:

1、我们可能没有理解清楚问题域,AlarmDoor在概念本质上到底是门还报警器。

2、如果我们对问题域的理解没有问题,比如我们在分析时确定了AlarmDoor在本质上概念是一致的,那么我们在设计时就没有正确的反映出我们的设计意图。因为你使用了两个接口来进行定义,他们概念的定义并不能够反映上述含义。


 第三种,如果我们对问题域的理解是这样的:AlarmDoor本质上Door,但同时它也拥有报警的行为功能,这个时候我们使用第三种方案恰好可以阐述我们的设计意图。AlarmDoor本质是们,所以对于这个概念我们使用抽象类来定义,同时AlarmDoor具备报警功能,说明它能够完成报警概念中定义的行为功能,所以alarm可以使用接口来进行定义。

abstract class Door
{
	abstract void open();
	abstract void close();
}
interface Alarm
{
	void alarm();
}

class AlarmDoor extends Door implements Door
{
	void opne(){   }
	void close(){  }
	void alarm(){  }
}

    这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实抽象类表示的是"is-a"关系,接口表示的是"like-a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。

      抽象方法是必须实现的方法。就象动物都要呼吸。但是鱼用鳃呼吸,猪用肺呼吸。动物类要有呼吸方法。怎么呼吸就是子类的事了。现在有很多讨论和建议提倡用interface代替abstract类,两者从理论上可以做一般性的混用,但是在实际应用中,他们还是有一定区别的。抽象类一般作为公共的父类为子类的扩展提供基础,这里的扩展包括了属性上和行为上的。而接口一般来说不考虑属性,只考虑方法,使得子类可以自由的填补或者扩展接口所定义的方法,就像JAVA王子所说的事件中的适配器就是一个很好的应用。用一个简单的例子,比如说一个教师,我们把它作为一个抽象类,有自己的属性,比如说年龄,教育程度,教师编号等等,而教师也是分很多种类的,我们就可以继承教师类而扩展特有的种类属性,而普遍属性已经直接继承了下来。 
而接口呢~还是拿教师做例子,教师的行为很多,除了和普通人相同的以外,还有职业相关的行为,比如改考卷,讲课等等,我们把这些行为定义成无body的方法,作为一个集合,它是一个interface。而教师张三李四的各自行为特点又有不同,那么他们就可以扩展自己的行为body。从这点意义上来说,interface偏重于行为。 
总之,在许多情况下,接口确实可以代替抽象类,如果你不需要刻意表达属性上的继承 的话。

简单的来说抽象类和接口的区别。

A: 抽象类在java语言中所表示的是一种继承关系,一个子类只能存在一个父类,但是可以存在多个接口。

B:在抽象类中可以拥有自己的成员变量和非抽象类方法,但是接口中只能存在静态的不可变的成员数据(不过一般都不在接口中定义成员数据),而且它的所有方法都是抽象的。

C:抽象类和接口所反映的设计理念是不同的,抽象类所代表的是“is-a”的关系,而接口所代表的是“like-a”的关系。

三 内部类

1.理解内部类

当描述事物时,事物的内部还有事物,该事物可以用内部类来描述。就是将一个类定义到另一个类的里面,里面的类就称为内部类(内置类、嵌套类)。

为什么要使用内部类?

在《Think in java》中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

内部类最大的好处就在于每个类都能够独立的继承类或实现某个接口,所以外部类不影响其内部类,它能够非常好的解决多重继承的问题,如果没有内部类提供的可以继承多个具体的或抽象的类,一些设计与编程就很难解决,它使得多重继承的解决办法更完整,接口解决了部门,内部类有效的实现多重继承。

使用内部类有以下特性:

1)内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。

(2)在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。

(3)创建内部类对象的时刻并不依赖于外围类对象的创建。

(4)内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。

(5)内部类提供了更好的封装,除了该外围类,其他类都不能访问。

  在Java中内部类主要分为成员内部类、局部内部类、匿名内部类、静态内部类

2 成员内部类

作为外部类的一个成员存在,与外部类的属性和方法并列。


<pre name="code" class="java">//局部内部类
abstract class  Inner
{
	public abstract void innerMethod();
}
class Outer
{
	private int x=11;
	public static void outerMethod(){
		System.out.println("外部静态类方法");
	}
	class Inner
	{
		private int x;
		public void innerMethod(){
		//访问外部类属性;
		Outer.this.x=10;
		//访问外部类静态方法;和访问外部类属性
		System.out.println(x+"内部普通类方法"+Outer.this.x);
		Outer.outerMethod();
		}
	}
}
public class Test1
{
	public static void main(String[] args){
		//访问内部类普通方法
		Outer.Inner in=new Outer().new Inner();
		in.innerMethod();
	}
}


 

3 局部内部类

在方法中定义的类称为局部内部类,与局部变量类似,局部内部类不能有访问修饰符,因为他不是外部类的一部门,但是它可以访问当前代码块内的常量和此外围类的所有成员。

注意:

为什么局部内部类只能访问外部类方法的final局部变量?

(1) 从程序设计语言的理论上:局部内部类,由于本身就是在方法内部,因而访问方法中的局部变量是天经地义的.是很自然的(2) 为什么JAVA中要加上一条限制:只能访问final型的局部变量?因为JAVA语言的编译程序的设计者当然全实现:局部内部类能访问方法中的所有的局部变量(因为:从理论上这是很自然的要求),但是:编译技术是无法实现的或代价极高,局部变量的生命周期与局部内部类的对象的生命周期的不一致性!        比如:设方法f被调用,从而在它的调用栈中生成了变量i,此时产生了一个局部内部类对象inner_object,它访问了该局部变量i .当方法f()运行结束后,局部变量i就已死亡了,不存在了.但:局部内部类对象inner_object还可能   一直存在(只能没有人再引用该对象时,它才会死亡),它不会随着方法f()运行结束死亡.这时:出现了一个"荒唐"结果:局部内部类对象inner_object要访问一个已不存在的局部变量i!       所以当变量是final时,通过将final局部变量"复制"一份,复制品直接作为局部内部中的数据成员.这样:当局部内部类访问局部变量时,其实真正访问的是这个局部变量的"复制品"(即:这个复制品就代表了那个局部变量).因此:当运行栈中的真正的局部变量死亡时,局部内部类对象仍可以访问局部变量(其实访问的是"复制品"),给人的感觉:好像是局部变量的"生命期"延长了.        当变量是final时,若是引用类型,由于其引用值不变(即:永远指向同一个对象),因而:其复制品与原始的引用变量一样,永远指向同一个对象(由于是final,从而保证:只能指向这个对象,再不能指向其它对象),达到:局部内部类中访问的复制品与方法代码中访问的原始对象,永远都是同一个即:语义效果是一样的.否则:当方法中改原始变量,而局部内部类中改复制品时,就无法保证:复制品与原始变量保持一致了(因此:它们原本就应该是同一个变量.)

4 匿名内部类

匿名内部类就是内部类的简写格式。简单说匿名内部类就是没有名字的内部类。

匿名内部类格式:new 父类或者接口(){定义子类的内容}

什么时候用到内部类呢?

A:只用到一个类的引用。

B:类在定义后马上用到。

C:类非常小。

匿名内部类的利与弊:

好处:简化书写

弊端:

1、不能直接调用自己的特有方法、

2、不能做强转动作。

3、如果继承的父类或接口中有很多方法时,使用匿名内部类阅读性会非常差,且调用会很麻烦。所以匿名内部类中定义的方法有一般不超过3个。

注意:当所在的方法的形参需要被内部类里面使用时,该形参必须为final

这时因为拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变

5 静态内部类

如果你不需要内部类对象与外围类对象之间有联系,那么你可以在成员内部类的前面加static变成静态内部类。

public class Outer {
     int x=1;
     static int a =6;
	 //静态内部类一种写法
//   static class Inner{
//        void show(){
//            System.err.println(a);
//        }
//   }
//   静态内部类第二种写法
     static Object function(){
           return new Object(){
               void show(){
                   System. err.println(a );
              }
          };
     }
//   void method(){
//        final int y=5;
//        class Inner extends AbsDemo{
//           int a=5;
//            void show(){
//                 System.err.println(a);
//            }

//        }
//        new AbsDemo(){
//            void show(){
//                 System.err.println(y);
//            }
//        }.show();
//        
}
public class InnerClassDemo {
     public static void main(String[] args) {
           // TODO Auto-generated method stub
          Outer. function();
           //Outer.function()表示function类是静态的。
           //function().method()表示function()返回的是一个对象 调用method()方法。    
     }
}


------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值