java普通类、抽象类和接口的再学习

声明:   

感谢能有以下参考学习链接的分享使我个人在初期学习时得以拓展,

正文内容仅为以下链接文章的个人摘录+总结,请大家以原链接经典内容为主加以学习,

如有侵犯敬请告知,谢谢!!!

http://www.cnblogs.com/dolphin0520/p/3811437.html

http://blog.csdn.net/chenssy/article/details/12858267

http://blog.csdn.net/ttgjz/article/details/2960451

http://www.cnblogs.com/dolphin0520/p/3803432.html

http://www.cnblogs.com/dolphin0520/p/3811445.html

普通类:

java类文件以.java为后缀,每个类文件中最多只允许出现一个public类,

当有public类的时候,类文件的名称必须和public类的名称相同,

若不存在public,则类文件的名称可以为任意的名称(当然以数字开头的名称是不允许的)

类内部的成员变量在定义时没有显示的赋值初始化时Java会保证类的每个成员变量都得到恰当的初始化,即默认值

对于  char、short、byte、int、long、float、double等基本数据类型的变量来说会默认初始化为0

boolean变量默认会被初始化为false

对于引用类型的变量,会默认初始化为null

没有显示地定义构造器,则编译器会自动创建一个无参构造器,

但是要记住一点,如果显示地定义了构造器,编译器就不会自动添加那个无参的构造器了。

注意,所有的构造器默认为static的。

当程序执行时,需要生成某个类的对象,Java执行引擎会先检查是否加载了这个类,

如果没有加载,则先执行类的加载再生成对象,如果已经加载,则直接生成对象。

在类的加载过程中,类的static成员变量会被初始化,另外,如果类中有static语句块,则会执行static语句块。

static成员变量和static语句块的执行顺序同代码中的顺序一致。

记住,在Java中,类是按需加载,只有当需要用到这个类的时候,才会加载这个类,并且只会加载一次。


在生成对象的过程中,会先初始化对象的成员变量,然后再执行构造器。

也就是说类中的变量会在任何方法(包括构造器)调用之前得到初始化,即使变量散布于方法定义之间。


当创建一个类时总存在继承,如果没有明确指出要继承的类,就总是隐式地从根类Object进行继承

一个类最多只能显示地继承于一个父类。但是一个类却可以被多个类继承

1.子类继承父类的成员变量

当子类继承了某个类之后,便可以使用父类中的成员变量,但是并不是完全继承父类的所有成员变量。

具体的原则如下:

1)能够继承父类的public和protected成员变量;不能够继承父类的private成员变量;

2)对于父类的包访问权限成员变量,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;

3)对于子类可以继承的父类成员变量,如果在子类中出现了同名称的成员变量,则会发生隐藏现象,

即子类的成员变量会屏蔽掉父类的同名成员变量。如果要在子类中访问父类中同名成员变量,

需要使用super关键字来区分进行引用。

2.子类继承父类的方法

同样地,子类也并不是完全继承父类的所有方法。

1)能够继承父类的public和protected成员方法;不能够继承父类的private成员方法;

2)对于父类的包访问权限成员方法,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;

3)对于子类可以继承的父类成员方法,如果在子类中出现了同名称的成员方法,则称为覆盖

即子类的成员方法会覆盖掉父类的同名成员方法。如果要在子类中访问父类中同名成员方法,

需要使用super关键字来进行引用。

注意:隐藏和覆盖是不同的。隐藏是针对成员变量和静态方法的,而覆盖是针对普通方法的。

3.构造器

子类不能继承父类的构造器,但是如果父类的构造器都是带有参数的,则必须在子类的构造器中显示地通过super关键字调用父类的构造器并配以适当的参数列表。如果父类有无参构造器,则在子类的构造器中用super关键字调用父类构造器不是必须的,如果没有使用super关键字调用,系统也会自动调用父类的无参构造器。





4.super

1)super.成员变量/super.成员方法;

2)super(parameter1,parameter2....)

第一种用法主要用来在子类中调用父类的同名成员变量或者方法;

第二种主要用在子类的构造器中显示地调用父类的构造器,要注意的是,

如果是用在子类构造器中,则必须是子类构造器的第一个语句。


类继承时构造器的调用顺序和初始化顺序

父类的构造器调用以及初始化过程一定在子类的前面

类中的成员变量早于任何方法(包括构造器)初始化





覆盖只针对非静态方法(静态方法不能被继承,所以就不存在覆盖一说了

而隐藏是针对成员变量和静态方法的

只有覆盖方法才会进行动态绑定

只有覆盖方法才会进行动态绑定,而隐藏是不会发生动态绑定的。

在Java中,除了static方法和final方法,其他所有的方法都是动态绑定

内部类:

将一个类定义在另一个类里面或者方法里面就称这个类称为内部类

1成员内部类


类Draw像是类Circle的一个成员,Circle称为外部类。

成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。

即外部类对于成员内部类是全通明的;

不过当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,

即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,

需要以下面的形式进行访问:

1
2
外部类. this .成员变量
外部类. this .成员方法
而外部类访问成员内部类确不是很随意:

在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:


成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象


内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。比如上面的例子,如果成员内部类Inner用private修饰,则只能在外部类的内部访问,如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。我个人是这么理解的,由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。

通过反编译字节码文件看看究竟。事实上,编译器在进行编译的时候,会将成员内部类单独编译成一个字节码文件


编译器会默认为成员内部类添加了一个指向外部类对象的引用

虽然我们在定义的内部类的构造器是无参构造器,编译器还是会默认添加一个参数,该参数的类型为指向外部类对象的一个引用,所以成员内部类中的Outter this&0 指针便指向了外部类对象,因此可以在成员内部类中随意访问外部类的成员。从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,则无法对Outter this&0引用进行初始化赋值,也就无法创建成员内部类的对象了。


2.局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。


3.匿名内部类

匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。下面这段代码是一段Android事件监听代码:


代码中需要给按钮设置监听器对象,使用匿名内部类能够在实现父类或者接口中的方法情况下同时产生一个相应的对象,但是前提是这个父类或者接口必须先存在才能这样使用

匿名内部类也是不能有访问修饰符和static修饰符的

匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。


4.静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。



从前面可以知道,静态内部类是不依赖于外部类的,也就说可以在不创建外部类对象的情况下创建内部类的对象。另外,静态内部类是不持有指向外部类对象的引用的,这个读者可以自己尝试反编译class文件看一下就知道了,是没有Outter this&0引用的。



ps:

为什么局部内部类和匿名内部类只能访问局部final变量?


这段代码会被编译成两个class文件:

Test.class和Test1.class

默认情况下,编译器会为匿名内部类和局部内部类起名为Outterx.class(x为正整数)。

根据上图可知,test方法中的匿名内部类的名字被起为 Test$1。

上段代码中,如果把变量a和b前面的任一个final去掉,这段代码都编译不过。

当test方法执行完毕之后,变量a的生命周期就结束了,而此时Thread对象的生命周期很可能还没有结束,

那么在Thread的run方法中继续访问变量a就变成不可能了,但是又要实现这样的效果,怎么办呢?

Java采用了 复制  的手段来解决这个问题。将这段代码的字节码反编译可以得到下面的内容:

我们看到在run方法中有一条指令:

bipush 10
这条指令表示将操作数10压栈,表示使用的是一个本地局部变量。这个过程是在编译期间由编译器默认进行,如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类(局部内部类)的常量池中添加一个内容相等的字面量或直接将相应的字节码嵌入到执行字节码中。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等,因此和方法中的局部变量完全独立开。

也就说如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。

  从上面可以看出,在run方法中访问的变量a根本就不是test方法中的局部变量a。这样一来就解决了前面所说的 生命周期不一致的问题。但是新的问题又来了,既然在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?

  对,会造成数据不一致性,这样就达不到原本的意图和要求。为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。

为什么在Java中需要内部类?总结一下主要有以下四点:

  1.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,

  2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。

  3.方便编写事件驱动程序

  4.方便编写线程代码

  个人觉得第一点是最重要的原因之一,内部类的存在使得Java的多继承机制变得更加完善。


(1)成员内部类:
对于成员内部类,必须先产生外部类的实例化对象,才能产生内部类的实例化对象
Test test = new Test();    
Test.Bean1 bean1 = test.new Bean1();   
(2)静态内部类:

静态内部类不用产生外部类的实例化对象即可产生内部类的实例化对象

Test.Bean2 b2 =new Test.Bean2(); 

(3)外部类的外部类的成员内部类

Bean bean = new Bean();     
Bean.Bean3 bean3 =  bean.new Bean3();  


抽象方法:

是一种只声明固有算法而不实现的技巧方法,
本质就是为了供多个子类在基础算法的基础上去灵活拓展实现而用的,是代码复用和拓展的需要;

所有的对象都是通过类来描述的,但是并不是所有的类都是来描述对象的。

如果一个类没有足够的信息来描述一个具体的对象,而需要其他具体的类来支撑它,

那么这样的类我们称它为抽象类。


抽象类:

多是对一类事物即对象的类抽象,偏向整体,是多个子类拓展的一个模板

且对实现子类有强制实现之的要求,是满足共有性的一种方式

抽象类体现了数据抽象的思想,是实现多态的一种机制。它定义了一组抽象的方法,至于这组抽象方法的具体表现形式由派生类来实现。同时抽象类提供了继承的概念,它的出发点就是为了继承,否则它没有存在的任何意义。所以说定义的抽象类一定是要用来继承的,同时在一个以抽象类为节点的继承关系等级链中,叶子节点一定是具体的实现类。创建抽象类和抽象方法非常有用,因为他们可以使类的抽象性明确起来,并告诉用户和编译器打算怎样使用他们.抽象类还是有用的重构器,因为它们使我们可以很容易地将公共方法沿着继承层次结构向上移动。
如果子类无法对父类的抽象方法进行详细的描述应该将改方法继续抽象化,提供给子类来做具体的实现
抽象类要全部实现抽象方法,普通类可以不用实现。抽象类所提供给子类的就是一套模板,你继承我就需要按照我的意愿来(必须实现我的抽象方法),这个作用是普通类无法替代的。抽象类往往用来表征我们在对问题领 域进行分析 、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象 比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形 这 样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念 在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题 领域没有对 应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。

接口:

多是对局部属性特征的一种规范,对于子类而言是有需要即实现的状态,有灵活选择插入的感觉,

像某些特有行为多用接口方式;接口的隔离作用:当对象作为接口类型使用的时候,

与接口逻辑无关的对象方法属性是不可见的。


r:

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

对于抽象类而言,它是自下而上来设计的,我们要先知道子类才能抽象出父类,

而接口则不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,至于什么子类、

什么时候怎么实现它一概不知。

比如我们只有一个猫类在这里,如果你这时就抽象成一个动物类,是不是设计有点儿过度?

我们起码要有两个动物类,猫、狗在这里,我们在抽象他们的共同点形成动物抽象类吧!

所以说抽象类往往都是通过重构而来的!

但是接口就不同,比如说飞,我们根本就不知道会有什么东西来实现这个飞接口,

怎么实现也不得而知,我们要做的就是事前定义好飞的行为接口。

所以说抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。

其实抽象类表示的是"is-a"关系,接口表示的是"like-a"关系

 批注:ISP(Interface Segregation Principle):面向对象的一个核心原则。

它表明使用多个专门的接口比使用单一的总接口要好。

一个类对另外一个类的依赖性应当是建立在最小的接口上的。

一个接口代表一个角色,不应当将不同的角色都交给一个接口。

没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。

抽象类和接口是java语言中两种不同的抽象概念,他们的存在对多态提供了非常好的支持,

虽然他们之间存在很大的相似性。但是对于他们的选择往往反应了您对问题域的理解。

只有对问题域的本质有良好的理解,才能做出正确、合理的设计。

如果一个类继承了一个抽象类并实现了一个接口,

可以理解成“这是个什么东西(抽象类)可以干什么事(实现接口方法)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值