Java基础入门(2)

前言

第一篇教程里,我们已经学习了java的基本程序结构和基础语法,下面我们就来学习java面向对象(OOP)的语法。当然最重要的理解面对对象的编思思想,以便读者将来学习其他面对对象的语言时能触类旁通。

-------------------------------------------更新于 2020年4月14日 关于面向对象和面向过程的个人理解------------------------------------

面向对象的具体概念及和面向过程的区别网上有很多,在这里笔者说下个人面向过程和面向对象的理解(不一定准确):

面向过程:是站在计算机的角度去抽象问题和解决问题。以实现一个简单的贪吃蛇游戏为例,利用面向过程的思想实现可能会是这样:先编写绘制地图的函数,然后编写绘制贪吃蛇的函数,再编写上下左右移动的函数等等等,写完一系列的函数后,再依次调用。这就是面向过程的思想。

面向对象:是站在现实世界的角度去抽象和解决问题。还是以贪吃蛇游戏为例,利用面向对象的过程实现可能会这样:先编写一个地图类,地图类包含地图的生成方法;再编写一个贪吃蛇的类,包含了贪吃蛇的生成,上下左右移动等方法。写完所有的类之后,再根据类生成相应的对象,然后根据逻辑执行对象所携带的方法。

简单来说,面向过程的思想,数据和行为是相互独立的两个部分。在面向对象的思想里,数据和操作这些数据的行为看成是一个整体,并放到一个对象里。

-----------------------------------------------------------------------------------------------------------------------------------------

另外笔者觉得理解一个由几个深奥名词拼接起来的概念对于初学者来说可能会难以理解,甚至可能起到适得其反的效果。所以笔者建议:在学习java的过程中,自己感受和对比下面向对象和面向过程编程的不同,然后试着自己去总结,最后再比对网上的总结查漏补缺

 

类(class)和对象(object)

类和对象的概念非常重要,是学习Java语言的基础,读者一定要好好掌握。

类这个字我们生活中也经常用到,比如说:小明,小李和小张是一类人。这个类字就说明小明,小李和小张拥有某种相同的特征,比如都是学生。

在java语言里,用一句话概括:"类"(class)是构造对象的模板和蓝图。举个例子,我们可以把"类"想象“切割动物小饼干的切割机”,将"对象"想象成“动物小饼干”。我们把“用切割机制造出小饼干的过程”也就是“由"类"构造对象的过程”称为"创建类的实例"。 或者用上面小明的例子,小明、小李、小张都是一个个具体的对象(或者说人),“学生”就是一个类,是一个泛指。

学过C语言的同学应该都学过结构体,其实C语言里的结构体就相当于没有定义方法(Java里的方法实际上就是C语言里的函数)的Java类,换句话说,Java里的类是C语言结构体的拓展,结构体里不能定义函数,但是在Java里,类里还能定义方法。而对象就是类的实例化,实例化是Java里一个术语,读者可以理解成由设计图纸造出汽车的过程,设计图纸就相当于类,只是一个模板;而汽车就是具体的对象。

 

类和对象教程:类和对象

补充:关于构造方法的一些理解

总结来说,构造方法是类和对象之间的纽带,类通过构造方法初始化对象。当使用new初始化一个新的对象的时候,其实就是在调用类的构造方法。由于Java支持方法的重载,所以一个类可以有一个或多个构造方法。

Java基础入门(2)

构造方法是一种特殊的方法,但没有返回值(不能用void修饰该方法),它与类名必须保持相同,如Date类的构造方法名为Date。如果在类中没有显式的构造方法(就是不写构造方法),类在编译的过程中,编译器会自动在类中添加默认的一个无参的构造方法,用于对象的初始化。你也可以在类中添加一个或多个构造方法,用于不同情况下对象的初始化。但多个构造方法的签名不能一致(参数的类型和个数不能完全一样),此时,编译器将不会自动添加构造方法。

 
  1. public class Pet {

  2. String name;

  3. String color;

  4. int age;

  5.  
  6. // 四个不同的构造方法,这里用到了方法重载,后面会学到

  7. public Pet(String aName, String aColor, int aAge) {

  8. name = aName;

  9. color = aColor;

  10. age = aAge;

  11. }

  12.  
  13. public Pet(String aName, int aAge) {

  14. name = aName;

  15. color = "white";

  16. age = aAge;

  17. }

  18.  
  19. public Pet(String aName, String aColor) {

  20. name = aName;

  21. color = aColor;

  22. age = 2;

  23. }

  24.  
  25. public Pet() {

  26. name = "Amy";

  27. color = "white";

  28. age = 1;

  29. }

  30. //重写toString方法以便格式化输出

  31. public String toString() {

  32. return "name=" + name + " color=" + color + " age=" + age;

  33. }

  34.  
  35. public static void main(String[] args) {

  36. // 使用不同的构造方法初始化对象,程序会根据参数的不同选择正确的构造方法

  37. Pet pet1 = new Pet("Jack", "black", 2);

  38. Pet pet2 = new Pet("Dam", "purple");

  39. Pet pet3 = new Pet("John", 3);

  40. Pet pet4 = new Pet();

  41. // 打印查看結果,注意:如果沒有重写toString方法,直接打印对象不能打印出对象的状态。

  42. //打印一个对象的时候,会自动调用该对象的toString()方法

  43. System.out.println(pet1);//等价于System.out.println(pet1.toStrng());

  44. System.out.println(pet2);

  45. System.out.println(pet3);

  46. System.out.println(pet4);

  47.  
  48. }

  49.  
  50. }

运行结果如下:

 
  1. name=Jack color=black age=2

  2. name=Dam color=purple age=2

  3. name=John color=white age=3

  4. name=Amy color=white age=1

修饰符

在Java里,我们在很多地方都会用到修饰符,比如public就是修饰符。修饰符主要是用来控制类、方法、变量的访问权限的。

教程:java 修饰符

 

怎样识别类

在写面向过程的程序时,如C语言,会从顶部的main函数开始写。但在面向对象的程序设计里,是没有“顶部”这个说法的,那么从哪里开始写一个java程序?答案是:从设计类开始。那么怎么识别类就很重要了。

比较简单好用的的方法是:名词作为类,动词作为类的方法

先来思考一个问题,假如要写一个订单处理系统,运用面向对象的方法要怎么写?

我们先来找找订单处理系统中可能涉及的名词,看哪些可以作为类:

商品

订单

用户

送货地址

.....

这些名词很可以设计成单独的一个类,如Item类(商品类),Order类(订单类)、User类(用户类)等。

然后我们再来找找看可能涉及的动词:

添加商品到订单中,发送订单或取消订单。

上面提到的动词,如“添加”、“取消”、“发送”等,在设计过程中,应该要明确这些动作是由什么对象完成的。

例如:“添加商品到订单”,那么在Order类(订单类)中,应该有一个add方法,方法参数为Item对象,从而达到添加商品到订单的目的。

 
  1. public class Order{

  2. ...

  3. public void add(Item item){ //方法参数为Item对象

  4. ...

  5. // 该方法实现将商品添加到订单里

  6. }

  7. ...

  8. }

  9.  

 

类似的,我们根据动词可以往相应的类里添加相应的方法了。当然,这种所谓的“找名词找动词”只是一种经验,在创建类的过程中,哪些名词和动词重要主要还是靠个人的开发经验。

 

另外,从这个例子里,我们也可以感受到面向对象设计和面向过程设计的差异,读者可以慢慢比对两者的不同点。

包以及类的导入

教程:包以及import关键字

如果没有在源文件的开头没有package语句(即没有注明该源文件所属的包),那么这个源文件中的类就被放置在一个默认包(default package)中,默认包是一个没有名字的包。

简单介绍在设计类的几点小技巧:

1.一定要保证数据私有

即,在我们设计类的时候,声明的变量要用private修饰,保证数据的私有性。这样可以保证类的封装性不被破坏。

2.一定要对数据初始化

Java不对局部变量进行初始化,但会对对象的实例域进行初始化,最好不要依赖于系统的默认值,而是应该显式的初始化所有的数据,具体的初始化方式可以使提供默认值,也可以是在所有构造器中设置默认值。

3.不要在类中使用过多的基本类型

就是说,用其他的类代替多个相关的的基本类型的使用,这样使得类更加容易理解和修改。

例如,有一个person类有以下表示地址的实例域

 
  1. private String city;

  2. private String street;

  3. private String state;

  4. private int zip;

比较好的处理方式是,新建一个包含以上实例域的Address的类,那么person类实例域在声明的时候只要一行

 
  1. public class person{

  2. Address address;

  3. ...

  4. }

就可以达到同样的效果,且当需要修改地址的显示方法的时候,只需要修改Address类就可以了,而无需修改person类

在这篇教程里,还涉及到父类和子类的概念,那么我们现在就来讲java的三大特性。

java 三大特性

封装

上面的java修饰符,我们学到了private修饰符,java三大特性中的封装,和这个private修饰符息息相关。

先来说一下封装的思想。我们生活很多地方都用到了这个思想。我举一个可能不太恰当的例子,比如一台电视机,包含了很多物理元件,这些物理元件都被电视机的外壳“封装”起来,只给用户提供了电源键、菜单键、音量键等按钮。这样做有几个好处,一个是安全性,内部元件不被破坏,第二个是简单方便,用户不用了解电视机复杂的构造,凭借几个按钮就可以控制电视机。这就是封装的思想,把类里的定义的变量“隐藏”起来,不能直接访问和修改这些变量,只能通过特地的方法才能访问修改变量,保证了数据的安全性。

封装教程: 封装

以后,在我们设计类的过程中,要尽量降低变量的访问权限,尽量不要将变量设置为public,这样会破坏类的封装性,且如果这个变量的值域被破坏(或者不正常),破坏的原因可以出现在任何地方,因为public让变量随意访问。

继承

接着说继承的思想。很多时候,很多东西都是用一些共同点的。比如现在需要写几个汽车的类,一个是宝马,一个是迈巴赫,一个是劳斯莱斯。然后写好后发现,几个类中很多属性都是重复的,毕竟车的总体结构都是差不多的。所以我们花费了很多时间,写了很多差不多一样的代码,我们的效率就很低下了,而且代码重复性高。为了提高效率和代码复用率,我们可以把这些相同的属性放到一个公共的类里,当成父类,具体的汽车类(比如迈巴赫类)继承这些相同的属性,再根据自身特点加入其他属性。这就是继承的思想,继承的特性可以使得代码变得简洁,并减少重复的代码。

另外,值得注意的是,父类中的私有属性和私有方法(即用private修饰的属性和方法)不能被子类继承,非私有的属性和方法子类都可以继承。再举个可能不那么恰当的例子:你爸爸藏了私房钱,这些钱你不知道,也没办法得到。(哈哈)

继承教程继承

这篇教程很详细的讲述了继承的概念和使用方法。那我们如何判断两个类是否应该设计为继承关系呢?有一个简单的规则:“is-a”规则.它表明子类的每个对象也是父类的对象。如student is a person。每个学生都是人,所以person应该设计为student的父类。

值得注意的是,子类可以继承父类的全部方法(包括构造方法)和非私有数据域(即父类中用private修饰的变量子类不会继承,但是子类可以通过继承父类的特定方法来访问父类的私有变量),子类可以重写父类的方法(方法重写),也可以添加新的方法。

另外,如果父类中如果定义了构造方法,子类也必须定义一个构造方法,且必须在构造方法的第一行用super调用父类的构造函数。

 
  1. public class Person{ //父类

  2. private String name;

  3. private int age;

  4. public Person(String name,int age){

  5. this.name =name;

  6. this.age = age;

  7. }

  8.  
  9. public String getName() {

  10. return name;

  11. }

  12.  
  13. public void setName(String name) {

  14. this.name = name;

  15. }

  16.  
  17. public int getAge() {

  18. return age;

  19. }

  20.  
  21. public void setAge(int age) {

  22. this.age = age;

  23. }

  24. }

 
  1. public class Teacher extends Person {

  2. public Teacher(String name,int age){ //子类构造方法

  3. super(name,age);//用super调用父类Person的构造函数,这里相当于Person(name,age);

  4. }

  5.  
  6. public static void main(String[] args) {

  7. Teacher teacher = new Teacher("hello",15);

  8. System.out.println(teacher.getName()+teacher.getAge());

  9. }

  10. }

这里,笔者补充关于方法重写的概念。

方法重写:

子类在继承父类的过程中,会继承父类的方法,但是父类的方法不一定适合子类,这时候我们就要重写一个专属于子类的方法,来覆盖父类的方法,这个过程就叫方法的重写。(注意和之前讲的方法的重载区分)。

关于方法重写的特性和注意点百度百科总结得很好,引用如下:

Java基础入门(2)

还有一点需要注意的,在子类中可以增加域,增加方法,重写父类的方法,但是,绝对不能删除继承自父类的任何域和方法

多态

我们可以用一种简单的思路来理解多态:

上面,我们讲了一种“is-a”规则。“Student is a Person”。

那么,我们可以将一个子类Student的对象的引用赋给父类类型的变量,即 Person p = new Student();  (这就是多态),这是很容易理解的:如果是学生,那么肯定是人。所以Person类变量引用student对象是完全没有问题的。但反过来就不行了,因为不是每一个人都是学生。所以子类变量不能引用父类对象(即使通过强制类型转换可以转换通过,但是使用过程中还是可能会出错,如调用子类自定义的的方法时)。

将子类对象的引用赋给父类变量:   要理解这句话,就要要区别变量引用。变量是声明就可以了,比如 Person person,这样person就是一个变量,但是没有引用一个具体的对象(引用为空)。 Teacher teacher = new Teacher("hello",15); 这条语句中,定义了一个teacher变量,让teacher变量引用了一个具体的实例化对象。而多态就是 person =teacher ; 这里就把teacher引用的具体对象赋给person。把前面继承的实例代码简单改改,如下:

 
  1. public class Teacher extends Person {

  2. public Teacher(String name,int age){

  3. super(name,age);

  4. }

  5. public static void main(String[] args) {

  6. Person person = new Teacher("hello",15);

  7. System.out.println(person.getName()+person.getAge());

  8. // 等同于System.out.println(teacher.getName()+teacher.getAge());

  9. }

  10. }

所以多态有什么用呢?笔者个人理解是:便于程序的扩展。举个例子,如果现在又需要扩展一个Doctor类来继承Person类,新建一个Doctor类之后,在main方法里,只需要把Docotr类的实例引用赋给person就可以了,如Person person = new Doctor("hello",15);其他地方不需要更改。

 

多态教程:多态