本着重新学习(看到什么复习什么)的原则,这一篇讲的是JAVA的特性。看了诸位大神的解释后详细的查了一些东西,记录下来,也感谢各位在网络上的分享!!!
在正式开始JAVA的特性之前,再来回顾一下OOP,上篇学习了AOP面向切面编程,OOP则是面向对象编程。面向对象实际上就是把所有事物都看做是对象的这种思想。面向对象比之面向过程更容易维护,并且代码可复用也更容易拓展,但是性能会比面向过程低。面向对象嘛,就先来看看什么是对象。
1.什么是对象?
在现实生活中的所有客观存在的实体都可以称之为对象。常说的一句话就是“万物皆对象”。
2.什么是类?
类是一个抽象的概念,只是为了描述对象信息,并且确定对象将会拥有的属性(特性)和方法(行为)。也就是一个模子,在类中定义的是各种属性和方法,而这些属性和方法是类所描述的这一类事物的共性。通过这些属性和方法可以确定一个对象。
所以类和对象的关系就是一个抽象出来的概念和一个实体的区别,类是对象的模板,对象则是类的实例。通过类可以创建实例,而每一个实例就是一个对象。而在了解了类和对象后就是JAVA的三大特性,封装,继承,多态。(也有人说有抽象,一起复习下。。。)
3.什么是封装?
这个问题可以用餐厅点餐的场景,每个菜品都有自己的制作方法,这些信息不能暴露给外人看,那么我们可以通过什么去了解该菜品的制作手法呢?我们可以通过餐厅给定的规矩,即给钱买菜品的方式去了解。封装也是这样,它不允许外部程序直接访问某些过程方法或者信息,我们只能通过该类提供的方法来实现对隐藏信息的操作和访问。封装的核心思想也正是将属性和方法结合为一个整体,并尽可能隐藏内部实现细节,只需要关注类本身是如何使用的,而不必关注类内的具体实现。
在封装的实现过程中,由于需要隐藏属性,那么就要设置属性的修饰符为private,其次给定访问方法,也就是创建对应属性的getter/setter方法,用于对属性进行取值和赋值,此外还可以在getter/setter方法中对属性值是否合法进行判定,也就是说取值或者赋值也有可以拥有自己的逻辑处理,从而完成封装,即类中的私有成员变量和方法只能在该类内部进行调用,而不能通过对象进行调用。
我们有时候也将不同类中的相同方法或常用方法抽离出来使用的方式称为使用了封装思想。
4.什么是内部类?
内部类就是定义在某一个类中的类(Inner Class)。内部类的主要作用就在于可以提供更好的封装效果,并且内部类实际上是可以直接访问外部类的所有属性和方法的,也包括被private修饰符修饰的属性和方法,但是外部类不能直接访问内部类的属性和方法。当然,内部类的实现功能是可以被外部类实现的,只不过有时候会选择内部类进行使用(如线程,驱动等)。另外,迫于JAVA的单继承机制,可以使用内部类,内部类可以单独继承一个接口的实现而不用在外部类的位置考虑多继承的问题(外部类不需要继承该接口或实现,对内部类的继承都没有影响)。内部类的使用使得JAVA可以从一定程度上实现多继承。
实际上内部类是一个编译时的概念,在编译后实际上会生成两个独立的class文件。可以看到编译后内部类也拥有了自己的class文件,被命名为“外部类$内部类”的方式。
内部类可以分为四种,成员内部类,静态内部类,方法内部类和匿名内部类。
(1).成员内部类:成员内部类定义在外部类的内部,可以使用任何访问控制符。成员内部类类内方法可以直接访问外部类数据(无需考虑访问控制符)。成员内部类必须用外部类对象来创建(内部类 内部类对象名 = 外部类对象.new 内部类();)。在成员内部类内无法声明静态成员变量(static:会提示你使用static final或者去掉static或者将该类本身定义为static,但是若将内部类本身定义为静态,也再也无法创建内部类类型的对象),除static final外均不可使用。若成员内部类与外部类有相同成员变量或方法,成员内部类默认访问自己的成员变量或方法,可以用外部类.this.类成员变量或方法(TryInnerClass.this.outString)的方式访问外部类的同名成员变量或方法。
(2).静态内部类:静态内部类同样定义在外部类内部,声明时需要使用static修饰符,静态内部类不依赖外部类便能通过new创建对象,同时,静态内部类也不能访问外部类的成员变量或方法了,除非该成员变量或方法是静态的。
(3).方法内部类:方法内部类定义在外部类的方法中,方法内部类只在该方法内部可见。若方法内部类需要访问外部类的成员变量或方法,则必须要求该成员变量或者方法必须拥有final修饰符(这里要注意对于成员变量来说JDK1.8版本以上不用显式修饰,但实际上也是有final修饰符的,只不过隐藏了)。
(4).匿名内部类:可以被视为没有类名的方法内部类。匿名内部类必须继承或实现一个接口,匿名类不能有显式的extends或implements关键字。也不能声明静态成员变量。
package com.day_6.excercise_1;
import java.util.Collection;
import java.util.Iterator;
public class TryInnerClass {
// 成员内部类
private String outString = "外部成员变量";
public class Inner {
private String intString = "内部类成员变量";
public void innerShow() {
System.out.println("内部类调用外部类成员变量:"+TryInnerClass.this.outString);
outShow();
}
}
public void outShow() {
// 编译错误
// System.out.println("内部类成员变量:"+intString);
System.out.println("内部类成员变量:"+new Inner().intString);
}
// 静态内部类
private static String outStaticString = "外部静态成员变量";
public static class StaticInner {
private static String intSaticString = "静态内部类成员变量";
public void innerStaticShow() {
// 编译错误
// System.out.println("静态内部类调用外部类静态成员变量:"+ outString);
System.out.println("静态内部类调用外部类静态成员变量:"+ outStaticString);
// 编译错误
// outShow();
outStaticShow();
}
}
public static void outStaticShow() {
System.out.println("静态内部类成员变量:"+ new StaticInner().intSaticString);
}
// 方法内部类
private String outmethodString = "外部成员变量";
public void tryoutMethodShow() {
class MethodInner {
public String methodString = "方法内部类成员变量";
public void innerMethodShow() {
System.out.println("方法内部类调用外部成员变量:"+outmethodString);
// 可用,打开输出太乱了
// new TryInnerClass().outMethodShow();
}
}
// 供外部类调用方法内部类的内部方法
System.out.println("外部类调用方法内部类的内部方法");
new MethodInner().innerMethodShow();
}
public static void main(String[] args) {
// 成员内部类
TryInnerClass tryInnerClass = new TryInnerClass();
Inner inner = tryInnerClass.new Inner();
inner.innerShow();
tryInnerClass.outShow();
// 静态内部类
TryInnerClass tryInnerStaticClass = new TryInnerClass();
// 编译错误
// StaticInner staticInner = tryInnerClass.new StaticInner();
StaticInner staticInner = new StaticInner();
// 编译错误
// tryInnerStaticClass.innerStaticShow();
staticInner.innerStaticShow();
// 方法内部类
TryInnerClass tryInnerMethodClass = new TryInnerClass();
tryInnerMethodClass.tryoutMethodShow();
// 匿名内部类
Collection<String> noNameCollection = new Collection<String>() {
@Override
public <T> T[] toArray(T[] a) {
return null;
}
......
};
}
}
package com.day_6.excercise_1;
import java.io.PrintStream;
public class TryInnerClass$Inner
{
public TryInnerClass$Inner(TryInnerClass paramTryInnerClass) {}
private String intString = "内部类成员变量";
public void innerShow() { System.out.println("内部类调用外部类成员变量:" + TryInnerClass.access$0(this.this$0));
this.this$0.outShow();
}
}
然后我们来看一下内部类的继承,一会再说继承特性。通过上面的代码可见内部类的编译结果跟正常类的编译结果相同,那么就代表着内部类同样可以被继承。但是在内部类被继承时有些不一样,成员内部类在被继承时需要加上外部类的类名,其次需要在成员内部类的子类的构造方法第一句显式的添加(外部类对象引用.super(参数);)。这里会对这个super()的用法有些疑问,有的解释是这样的:super本身是当前对象的直接父类的无参构造函数,当是某类的内部类时,实际上执行的就是Outer.inner.super(),因为确实我需要在该成员内部类的子类的构造函数中传入一个外部类的对象。我们都知道内部类有一个指向外部类的引用,当需要继承一个内部类的时候,就得取得那个指向外部类的引用,也就是这句话。不过我还是在等待更好的解释,还有一部分说是固定写法,希望能告知的能评论下,万分感谢!(mark一下。。。)
package com.day_6.excercise_1;
public class TryInnerExtends extends TryInnerClass.Inner {
public TryInnerExtends(TryInnerClass innerClass) {
innerClass.super();
}
public static void main(String[] args){
TryInnerClass otherClass = new TryInnerClass();
TryInnerExtends thirdExtends = new TryInnerExtends(otherClass);
thirdExtends.innerShow();
}
}
//public class TryInnerExtends extends TryInnerClass.StaticInner {
//
// public TryInnerExtends() {
// super();
// }
// public static void main(String[] args){
// TryInnerClass otherClass = new TryInnerClass();
// TryInnerExtends thirdExtends = new TryInnerExtends();
// thirdExtends.innerStaticShow();;
// }
//}
上下两个类都可以实现继承,但是编译后的代码不同,这里贴出的代码是对应的。
package com.day_6.excercise_1;
public class TryInnerExtends extends TryInnerClass.Inner
{
public TryInnerExtends(final TryInnerClass innerClass) {
innerClass.getClass();
innerClass.super();
}
public static void main(final String[] args) {
final TryInnerClass otherClass = new TryInnerClass();
final TryInnerExtends thirdExtends = new TryInnerExtends(otherClass);
thirdExtends.innerShow();
}
}
package com.day_6.excercise_1;
public class TryInnerExtends extends TryInnerClass.StaticInner
{
public static void main(final String[] args) {
final TryInnerClass otherClass = new TryInnerClass();
final TryInnerExtends thirdExtends = new TryInnerExtends();
thirdExtends.innerStaticShow();
}
}
另外还要注意的是当一个类继承了一个含有内部类的外部类并覆写了内部类时,两个内部类各自在自己的明明空间中,所以是完全独立的两个不同的实体。
5.什么是继承?
继承的定义很好理解,父类可以派生出子类,子类可继承父类中的属性和方法,并可以拥有自己独有的属性和方法。也可以反过来说,从很多不同的类中抽取出相同的属性或方法,并将这些属性或方法进行封装,成为父类或者基类。这样原有的哪些类只需要继承这个父类,就可以拥有原来重复定义的属性或方法了。在JAVA中,是单继承的,也就是说一个类只能有一个直接父类。但是继承的关系是传递的,这很好理解,类A继承类B,类B继承类C,那么类A中就得有类B和类C中的属性和方法。当然,继承来的属性或方法是可以被使用但是不一定可见的。继承有很多需要注意的:
(1).JAVA是单继承,不允许多继承(但可以使用其他方式实现多继承的思想,如逐层继承)。即使没有声明父类,也会有隐含父类Object存在。(写到这里我想到了一个问题,类A的父类是类Object,类B的父类是类A,那类B是不是同时继承了两个类?实际上不是这样的,如果类B没有继承的父类,那么Object类就是它的隐含父类,但是如果它有继承的父类,那么若只有类Object,类A,类B的话,类Object应该是类B的父类的父类,也就是说最大的基类一定是Object类,其他的类会在一直保持着单继承的前提下,通过逐层继承隐式的成为Object类的派生类)
(2).若子类有与父类同名的成员变量或方法,子类可以通过使用super关键字调用父类的成员变量或方法,这里说的不是super()而是super单独使用。但是所有私有属性或构造方法或方法不可以被直接调用,但是可以通过如getter/setter方法进行调用。
(3).如果子类构造方法中没有显式的调用父类的构造方法,则会在子类构造方法中隐式的调用父类的无参构造方法。如果显式的调用构造,则必须在子类的构造方法第一行使用super()。如果子类构造方法中既没有显式调用父类的构造方法,而父类又没有无参构造方法(当没有构造方法被定义时,系统会自动帮助添加无参构造方法,这一点不变。这里指的是该类存在有参构造方法,则系统不会帮助添加无参构造方法),则编译报错。
(4).子类可以对继承到的父类方法进行重写,并且在调用方法时会优先调用子类方法(毕竟重写,要求返回类型,方法名称,参数类型及个数均与继承父类的方法相同)。
(5).继承的初始化顺序是先初始化父类再初始化子类。另外成员变量的初始化时先执行的,如果构造方法中也有对该成员变量的初始化,会因为后执行构造方法的初始化的原因而改掉原来的成员变量的预设值。
(6).在使用继承时常常会用到final关键字,用final关键字修饰的类,方法,成员变量会不允许被继承或重写。如final修饰类,则该类不允许被继承(与abstract不能同时使用,抽象类的作用就是被继承类实现,与final作用正相反);final修饰方法,则该方法不允许被重写(但是可以被重载);final修饰成员变量,则不能在其它位置修改值,包括构造方法中也包括在其它函数中(若不在外部对成员进行初始化也不在构造方法中进行初始化,则程序报错);final修饰变量,则该变量的值只能被赋一次值,即为常量。
(7).在实例化子类对象前会先调用父类构造方法,再调用子类构造方法。
(8).可以通过向上转型或向下转型完成父子类的类型转换。向上转型是将父类的对象引用指向new操作符创建的子类对象。虽然该对象时通过子类对象向上转型获得的,但是他的类型是父类类型,所以还是不能使用子类对象的方法。向下转型可以通过强制转型的方式完成,但是不一定成功。若是直接进行向下转型,则会因为在子类中可能定义了父类所没有的特有方法而无法转换成功;若是先向上转型而后向下转型,则会因为原本父类对象的引用从指向子类对象中的父类拥有的方法等重新指向整个的子类对象,所以可以成功。可以使用instanceof进行类型的判定,安全的完成类型转换。
package com.day_6.excercise_1;
public class Animal {
public Integer age = 2;
public Animal() {
System.out.println("执行父类构造方法");
age = 10;
}
public void eat() {
System.out.println("Animal eat");
}
public void drink() {
System.out.println("Animal drink");
}
}
package com.day_6.excercise_1;
public class Cat extends Animal {
public Cat() {
System.out.println("执行子类构造方法");
}
public void eat() {
System.out.println("Cat eat");
}
public void play() {
System.out.println("Cat play");
}
}
package com.day_6.excercise_1;
public class TryExtends {
public static void main(String[] args) {
Animal animalUp = new Cat();
// 向上转型成功
animalUp.drink();
// 向上转型成功,但是因为子类对象重写了父类对象的方法,即父类引用指向子类对象,所以出现的是Cat eat
animalUp.eat();
// 向上转型成功,不能使用子类特有的方法
// animalUp.play();
// 先向上转型而后向下转型成功
((Cat) animalUp).play();
// 向下转型失败
// Animal animalDown = new Animal();
// Cat cat = (Cat) animalDown;
// cat.eat();
}
}
6.什么是多态?
多态指的是父类方法或属性可以被子类重写,这样使得一个方法或属性在父类及其不同的子类中可以拥有不同的含义。也就会导致同一个动作(方法)在不同对象使用时,会有不同的执行过程或执行结果。多态的使用,同样是降低了代码的耦合度,并且提高了代码的可拓展性。多态可以从具体实现的角度分为编译时多态(方法重载)和运行时多态(动态绑定)。编译时多态体现在可以通过参数类型,个数,次序来执行方法,也就是重载。运行时多态体现在会在执行一个方法时,顺延着继承链去寻找匹配的方法执行,基于实际的对象类型进行方法使用,而不是基于声明的对象引用类型进行方法使用,也就是动态绑定(出现就对比一下,再说一遍静态绑定,静态绑定在编译时就已经完成了绑定,虽然可以重写,但是在调用时仍会使用原本的对象的静态方法)。在上述的类中加入了一个静态die方法,并且被子类Cat类重写,而后由于向上转型,本来应该输出的是“Cat die”的子类重写的方法,但是由于静态绑定已经完成,所以只能输出“Animal die”的父类方法。当然多态也是有前提条件的,首先必须有继承关系,其次必须涉及子类到父类的类型转换(还可以加上子类必须重写了父类的方法)。
// Animal类
public static void die() {
System.out.println("Animal die");
}
// Cat类
public static void die() {
System.out.println("Cat die");
}
// TryExtends类main函数
public static void main(String[] args) {
Animal animalUp = new Cat();
// 向上转型成功
...
// 静态绑定
animalUp.die();
...
}
7.什么是抽象类?
抽象类是仅需要包含方法定义而不需要具体实现的类。也就是说我们可能会在规约子类必须实现某些方法时使用,或者根据众多含有相同特征的类抽象出一个抽象类,从而以这个抽象类为模板进行其他子类的设计,实际上就是约束子类的方法实现必须拥有哪些,但是又不去具体约束该方法的实现内容。而抽象方法正是只有声明而不需要实现的方法。抽象类和抽象方法的关系是:包含抽象方法的类是抽象类,而抽象类里不一定只包含抽象方法,可以包含普通方法,甚至可以没有抽象方法。另外抽象类自己不能被实例化,需要创建一个指向该抽象类的子类的对象的引用来实例化。
package com.day_6.excercise_1;
public abstract class AbstractClass {
public void getNormalFunc() {
System.out.println("Normal Function");
}
public abstract void getAbstractFunc();
}
package com.day_6.excercise_1;
public class TryAbstract extends AbstractClass {
@Override
public void getAbstractFunc() {
System.out.println("Abstract Function");
}
public static void main(String[] args) {
TryAbstract tryAbstract = new TryAbstract();
tryAbstract.getAbstractFunc();
AbstractClass abstractClass = new TryAbstract();
abstractClass.getNormalFunc();
}
}
下面探讨几个问题:
abstract和static:
这里要说明的是static修饰的是静态方法(或其他),是编译期间就已经完成的,但是abstract修饰的是要在确定对象引用的情况下,即通过类继承等方式动态重写方法,所以两者冲突。
abstract和final:
上面讲过的,final修饰的类或方法等是不可再次进行更改的,而abstract修饰的类内方法在等待被重写,所以两者冲突。
abstract和private:
private修饰的方法是类内可用的,继承时不可访问私有变量,而abstract修饰的目的就是要子类去继承该抽象类并重写对应的抽象类内未被实现的方法,所以两者冲突。
抽象类和接口:
抽象类中也可以用普通方法,但是接口中的所有方法都是必须是抽象的(接口中的方法的默认修饰符是public abstract,成员变量的默认修饰符是public static final)。所以相比较而言,抽象类可以有构造方法(虽然抽象类不能直接创建实例对象,但是实例化子类的时候就要先初始化父类,所以不管父类是不是抽象类都会调用其构造方法进行初始化),普通成员变量,普通方法,静态方法,但是接口相对的,不能拥有构造方法,只能是常量,只能是抽象方法,不能包含静态方法。并且抽象类还可以工通过子类间接实例化,但是接口不能实例化。所以两者在构造上有相似(如都存在abstract关键字在类(接口)定义时,都可以包含抽象方法等)但是更多的是不同。
可否存在 | 构造方法 | 普通成员变量 | 普通方法 | 静态方法 |
抽象类 | √ | √ | √ | √ |
接口 | × | × | × | × |
所以接口的限制使其更多的用于在规范,接口并不关心类的内部实现,而只是规定了实现接口的类必须提供的某些方法,而抽象类更多使用于在代码的抽取复用和个性化上。
继承和实现:
之前在看到集合类的时候有一个存疑,我忘记了接口可以被继承,今天来说一下这个问题。在接口中同样定义着众多需要传递的方法,并且这些方法都是抽象方法,那么如果子类是非抽象类,则必须实现接口中的所有方法,但若子类同样是抽象类或接口,则因为也可以含有抽象方法而不必须实现该接口中的所有方法。另外类是单继承,但是接口是允许多继承的,并且可以多实现的,这也是JAVA的多继承的一种实现方式。
所以JAVA的三大特性在定义上最核心的就是希望实现代码的复用的同时降低代码间的耦合度,简化外部调用的同时还便于调用者进行拓展。
这一篇完成了之前的一个存疑点,之后的文章中会一一把之前的问题抽离出来进行解决。还是一样,感谢各位在网络上的分享,才会让我的复习的思路更加清晰,也能得到更好的答案去解决我的疑虑而后用自己理解的方式记录下来,万分感谢。