2018/9/27

JavaSE学习笔记-1

目录:

  1. Java的起源
  2. Java语言概述

1、Java的起源  

  现代编程语言的发展,大致可以理解为,机器码语言---汇编语言---C语言---C++语言---Java语言。每一次新编程语言的诞生都会有背后的原因。例如从C语言发展到C++语言,就是因为复杂性(complexity)。由于C语言是面向过程编程,即使采用了结构化编程的方法,当程序大小达到25000-100000行时,就很难从整体上把握其复杂性。为解决这个问题,面向对象编程新方法诞生了。面向对象的编程是通过使用继承性、封装性和多态性来帮助组织复杂程序的编程方法。由此C++语言诞生,最初把这种新语言称为“带类的C”。1983年,改名为C++。C++通过增加面向对象的特性扩充了C。因为C++产生在C的基础之上,因此它包括了C所有的特征、属性和优点。这是C++作为语言成功的一个关键原因。C++的发明不是企图创造一种全新的编程语言,而是对一个已经高度成功的语言的改进。C++在1997年11月被标准化,目前的标准是ANSI/ISO。

  那么既然已经有了C++这种面向对象语言的诞生,还需要Java语言呢?最初的原因是因特网的诞生。由于因特网由不同的、分布式的系统组成,其中包括各种类型的计算机、操作系统和CPU。因此,同一个程序,用户就希望其能够运行在不同类型的平台。而要用C++语言在不同的CPU上编写程序,需要一个完整的以该CPU为目标的C++编译器,而创建一个编译器是一项既费钱又费力的工作,因此需要找到替代的办法。此时Java语言崭露头角,其可移植,跨平台而且独立于平台的特性使得Java能够生成运行于不同环境、不同CPU芯片上的代码。

  Java能够实现其安全、可移植性的关键在于Java编译器的输出并不是可执行的代码,而是字节码(bytecode)。字节码是一套设计用来在Java运行时系统下执行的高度优化的指令集,该Java运行时系统称为Java虚拟机(JavaVirtual Machine,JVM)。在其标准形式下,JVM 就是一个字节码解释器。将一个Java程序翻译成字节码,有助于它更容易地在一个大范围的环境下运行程序。原因非常直接:只要在各种平台上都实现Java虚拟机就可以了。在一个给定的系统中,只要系统运行包存在,任何Java程序就可以在该系统上运行。记住:尽管不同平台的Java虚拟机的细节有所不同,但它们都解释同样的Java字节码。如果一个Java程序被编译为本机代码,那么对于连接到Internet上的每一种CPU类型,都要有该程序的对应版本。这当然不是一个可行的解决方案。因此,对字节码进行解释是编写真正可移植性程序的最容易的方法。

  对Java程序进行解释也有助于它的安全性。因为每个Java程序的运行都在Java虚拟机的控制之下,Java虚拟机可以包含这个程序并且能阻止它在系统之外产生副作用。尽管被解释的程序的运行速度通常确实会比同一个程序被编译为可执行代码的运行速度慢一些。但是对Java来说,这两者之间的差别不太大。使用字节码能够使Java运行时系统的程序执行速度比你想象的快得多。尽管Java被设计为解释执行的程序,但是在技术上Java并不妨碍动态将字节码编译为本机代码。SUN公司在Java 2发行版中提供了一个字节码编译器——JIT(Just In Time,即时)。JIT是Java虚拟机的一部分,它根据需要、一部分一部分地将字节码实时编译为可执行代码。它不能将整个Java程序一次性全部编译为可执行的代码,因为Java要执行各种检查,而这些检查只有在运行时才执行。记住这一点是很重要的,因为JIT只编译它运行时需要的代码。尽管如此,这种即时编译执行的方法仍然使性能得到较大提高。即使对字节码进行动态编译后,Java程序的可移植性和安全性仍能得到保证,因为运行时系统(该系统执行编译)仍然能够控制Java程序的运行环境。不管Java程序被按照传统方式解释为字节码,还是被动态编译为可执行代码,其功能是相同的。

2、Java语言概述

  首先,Java作为一门面向对象的编程语言,具有面向对象的三个基本原则,即封装、继承、多态。  

  1.封装                                                                                                  封装(Encapsulation)是将代码及其处理的数据绑定在一起的一种编程机制,该机制保证了程序和数据都不受外部干扰且不被误用。理解封装性的一个方法就是把它想成一个黑匣子,它可以阻止在外部定义的代码随意访问内部代码和数据。封装代码的好处是每个人都知道怎么访问它,但却不必考虑它的内部实现细节,也不必害怕使用不当会带来负面影响。

     java语言封装的基本单位是类,类内部包括类成员与类方法。类中的成员与方法均可被声明为私有private或者公有public。私有方法和数据仅能被一个类的成员代码所访问,其他任何不是类的成员的代码都不能访问私有的方法或变量。既然类的私有成员仅能被程序中的其他部分通过该类的公共方法访问,那么你就能保证不希望发生的事情就一定不会发生。

  2.继承

      继承是一个对象获得另一个对象属性的过程。继承性与封装性相互作用。如果一个给定的类封装了一些属性,那么它的任何子类将具有同样的属性,而且还添加了子类自己特有的属性。这是面向对象的程序在复杂性上呈线性而非几何性增长的一个关键概念。新的子类继承它的所有祖先的所有属性。它不与系统中其余的多数代码产生无法预料的相互作用。

  2.多态

      多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。假设类B、C、D均是类A的子类,有代码如下:

    A a1 = new B();

    A a2 = new C();

    A a3 = new D();

      如上,实例化了三个B、C、D、对象,其引用变量并且向上转型为父类的引用对象(向上转型的缺点是,父类类型的引用可以调用父类中定义的所有属性和方法,对于只存在与子类中的方法和属性它就望尘莫及了)。对于多态,指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。对于面向对象而言,多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。

        实现多态的三个必要条件:继承、重写、向上转型。

      继承:在多态中必须存在有继承关系的子类和父类。

      重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。

      向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

  只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。对于Java而言,它多态的实现机制遵循一个原则:当父类对象引用变量引用(指向)子类对象时,是由被引用对象的类型决定了调用谁的成员方法,而不由引用对象的类型决定,但是这个被调用的方法必须是在父类中定义过的,也就是说被子类重写的方法。

      实现形式:继承与接口。

        继承实现:基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。对于引用子类的父类类型,在处理该引用时,它适用于继承该父类的所有子类,子类对象的不同,对方法的实现也就不同,执行相同动作产生的行为也就不同。

        接口实现:继承是通过重写父类的同一方法的几个不同子类来体现的,那么就可通过实现接口并覆盖接口中同一方法的几不同的类体现的。在接口的多态中,指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性。

public class A {
public String show(D obj) {
return ("A and D");
} public String show(A obj) {
return ("A and A");
} } public class B extends A{
public String show(B obj){
return ("B and B");
} public String show(A obj){
return ("B and A");
}
} public class C extends B{ } public class D extends B{ } public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D(); System.out.println("1--" + a1.show(b));//实际是this.show((super)O) 结果"A and A"
System.out.println("2--" + a1.show(c));//实际是this.show((super)O) ,C->B->A,结果"A and A" 
System.out.println("3--" + a1.show(d));//实际是this.show(O),结果"A and D"
System.out.println("4--" + a2.show(b));//实际是this.show((super)O),同时由于满足多态的三个条件,执行子类B的show(A obj)方法。
System.out.println("5--" + a2.show(c));//实际是this.show((super)O),同时由于满足多态的三个条件,执行子类B的show(A obj)方法。
System.out.println("6--" + a2.show(d));//实际是this.show(O),结果"A and D"
System.out.println("7--" + b.show(b));//实际是this.show(o),结果"B and B"
System.out.println("8--" + b.show(c));//实际是this.show((super)O),执行类B的show(B obj)方法
System.out.println("9--" + b.show(d));//实际是super.show(O),执行类A的show(D obj)方法
}
}

      注:继承链中对象方法调用的优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O),this代表该引用变量对应类的类型

      1---//调用的是类A的show(A obj)方法,因为b对象是A的子类B的实例,满足向上转型(b是一种A),不能向下转型为D

      2---//调用的是类A的show(A obj)方法,原因同上,不过是两层继承

      3---//调用类A的show(D obj)方法

      4---//a2是指向B类型的一个引用变量,调用show方法时,从被引用的对象方法中执行相应方法,条件是这个方法在父类中必须定义过,也就是被子类重写的方法。在B类中有两个方法,show(B obj)方法由于参数列表与父类A的show方法不一样,故不算是重写。show(A obj)方法满足重写条件,故执行的是show(A obj)方法。

      5---//a2是A类型的引用变量,所以this就代表了A,a2.show(c),它在A类中找发现没有找到,于是到A的超类中找(super),由于A没有超类(Object除外),所以跳到第三级,也就是this.show((super)O),C的超类有B、A,所以(super)O为B、A,this同样是A,这里在A中找到了show(A obj),同时由于a2是B类的一个引用且B类重写了show(A obj),因此最终会调用子类B类的show(A obj)方法,结果也就是B and A。

      6---//a2.show(d),a2是A类型的引用变量,所以this就代表了A,在A中找到了show(D obj)方法,因此直接执行该方法。

      7---//b.show(b),首先this代表B,在B中找到了show(B b),因此直接执行该方法。

      8---//b.show(c),首先this代表B,在B中未找到show(C c),因此在super.show(O)中找,也就是A类中有没有show(C c),当然是没有。接下来在this.show((super)O),找到了show(B b),因此该方法得到执行。

      9---//b.show(d),首先this代表B,在B中未找到show(D d),因此在super.show(O)中找,也就是A类中有没有show(D d),当然是有了,直接执行该方法。

      综上所述,当父类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类重写的方法,但是它仍然要根据继承链中方法调用的优先级来确认方法,该优先级为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

TIPS:重载与重写

      重载是指在同一个类中,可以多个参数不同、方法体不同的方法共同用一个方法名,调用此方法时根据参数列表来选择具体调用的方法。重载与返回值无关,因为只需要根据方法名与参数列表就知道要调用那一个函数了。具体的应用是类的构造函数,多个不同的带参构造函数可以初始化对象中不同属性的值。

public class A{

    private int i;
public A(){ } public A(int i){
this.i = i;
}
   public static void main(String[] args){
   A a1 = new A();
A a2 = new A(10);
   }
}

      重写在上文中多态已经谈过,是子类对于父类中方法的一种改写以适应子类自己使用,也叫子类的方法覆盖父类的方法。重写要满足三个条件(三同一小一大):

      1、要求返回值、方法名和参数都相同。

          2、子类抛出的异常不能超过父类相应方法抛出的异常的范围。(子类异常不能超出父类异常)

          3、子类方法的的访问级别不能低于父类相应方法的访问级别(子类访问级别不能低于父类访问级别)

05-27 19:18