[关闭]
@liayun 2016-12-07T17:32:34.000000Z 字数 9927 阅读 1174

初识面向对象

java基础


面向对象概念

理解面向对象

面向对象的特点

面向对象开发,设计,特征

类与对象的关系

类就是对现实生活中事物的描述;对象就是这类事物实实在在存在的个体。
比如:现实生活中的对象:张三,李四。若想要描述:即提取对象中共性内容,也即对具体对象的抽象。描述时,这些对象的共性有:姓名,年龄,性别,学习java的功能。映射到java中,描述就是class定义的类,具体对象就是对应java在堆内存中用new建立的实体
类与对象的关系如图所示:
类与对象的关系图
可以这样理解类与对象的关系:

例,需求1:描述汽车(颜色、轮胎数)。
(其实定义类)描述事物,其实就是在描述事物的属性和行为

  1. class Car {
  2. // 描述颜色
  3. String color = "红色";
  4. // 描述轮胎数
  5. int num = 4;
  6. // 运行行为
  7. void run() {
  8. System.out.println(color+"..."+num);
  9. }
  10. }

描述完汽车,接下来就是生产汽车,在java中通过new操作符来完成,其实就是在堆内存产生一个实体。即:

  1. Car c = new Car(); // c就是一个类类型变量。记住:类类型变量指向对象。

需求2:将已有车的颜色改成蓝色,就需要指挥该对象做事情,在java中指挥方式是:对象.对象成员

  1. c.color = "blue";

对象内存结构图
试分析如下代码:

  1. Car c = new Car();
  2. c.color = "blue";
  3. c.run();
  4. Car c1 = new Car();
  5. c1.run();

在内存中的表示:
对象内存结构图
而对于代码:

  1. Car c = new Car();
  2. c.num = 5;
  3. Car c1 = c;
  4. c1.color = "green";
  5. c.run();

在内存中表示为:
对象内存结构图
可以得出成员变量和局部变量的区别

匿名对象
匿名对象是对象的简化形式。
匿名对象使用方式:

  1. 当对对象的方法只调用一次时,可以用匿名对象来完成,这样写比较简化。如果对一个对象进行多个成员调用,必须给这个对象起个名字
  2. 可以将匿名对象作为实际参数进行传递

例,

  1. new Car().num = 5;
  2. new Car().color = "blue";
  3. new Car().run();

在内存中的表现形式为:
对象内存结构图
需求3:汽车修配厂对汽车进行改装,将来的车都改成黑色,3个轮胎。

  1. public static void show(Car c) {
  2. c.num = 3;
  3. c.color = "black";
  4. c.run();
  5. }

将匿名对象作为实际参数进行传递,如下:

  1. public static void main(String[] args) {
  2. show(new Car());
  3. }

此时,在内存中的表现形式为:
对象内存结构图
而代码

  1. public static void main(String[] args) {
  2. Car q = new Car();
  3. show(q);
  4. }

在内存中的表示如图:
对象内存结构图
若要让堆内存中的对象成为垃圾,可让q = null

封装

封装:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
好处:

  1. 将变化隔离
  2. 便于使用
  3. 提高重用性
  4. 提高安全性

封装原则:

  1. 将不需要对外提供的内容都隐藏起来
  2. 把属性都隐藏,提供公共方法对其访问

private(私有)关键字

具体到实例,即是:将age私有化以后,类以外即使建立对象也不能直接访问了,但是人应该有年龄,就需要在Person类中提供对应的访问age的方式。之所以对外提供访问方式,就因为可以在访问方式中加入逻辑判断等语句,对访问的数据进行操作,提高代码的健壮性。代码如下:

  1. class Person {
  2. private int age;
  3. public void setAge(int a) {
  4. if(a > 0 && a < 130) {
  5. age = a;
  6. speak();
  7. }
  8. else
  9. System.out.println("非法年龄");
  10. }
  11. public int getAge() {
  12. return age;
  13. }
  14. void speak() {
  15. System.out.println("age="+age);
  16. }
  17. }
  18. class PersonDemo {
  19. public static void main(String[] args) {
  20. Person p = new Person();
  21. // p.age = -20;
  22. p.setAge(-40);
  23. // p.speak();
  24. }
  25. }

注意:私有仅仅是封装的一种表现形式。

构造函数

对象一建立就会调用与之对应的构造函数。
特点:

  1. 函数名和类名相同
  2. 没有返回值类型
  3. 没有具体的返回值

作用:可用于给对象进行初始化。
小细节:

构造函数和一般函数的区别:构造函数和一般函数在写法上有不同,在运行上也有不同。构造函数是在对象一建立就运行,给对象初始化。而一般函数是对象调用才执行,是给对象添加对象具备的功能。一个对象建立,构造函数只运行一次,而一般函数可以被该对象调用多次。
什么时候定义构造函数呢?
答:当分析事物时,该事物存在具备一些特性或者行为,那么将这些内容定义在构造函数中。

构造代码块

格式:

  1. {
  2. 构造代码块中的执行语句
  3. }

构造代码块定义的是不同对象共性的初始化内容。
作用:给对象进行初始化,对象一建立就运行,而且优先于构造函数执行
和构造函数的区别:构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化。
例,

  1. class Person {
  2. private String name;
  3. private int age;
  4. // 构造代码块
  5. {
  6. cry();
  7. }
  8. Person() {
  9. System.out.println("A:name="+name+",age="+age);
  10. }
  11. Person(String n) {
  12. name = n;
  13. System.out.println("B:name="+name+",age="+age);
  14. }
  15. Person(String n, int a) {
  16. name = n;
  17. age = a;
  18. System.out.println("C:name="+name+",age="+age);
  19. }
  20. public void cry() {
  21. System.out.println("cry.......");
  22. }
  23. }
  24. class PersonDemo {
  25. public static void main(String[] args) {
  26. Person p1 = new Person();
  27. Person p2 = new Person("lisi");
  28. }
  29. }

this关键字

this:看上去,是用于区分局部变量和成员变量同名情况。那么this到底代表的是什么呢?
this:代表本类的对象,到底代表哪一个呢?this代表它所在函数所属对象的引用,简单说:哪个对象在调用this所在的函数,this就代表哪个对象。
this的应用:当定义类中功能时,该函数内部要用到调用该函数的对象时,这时用this来表示这个对象。但凡本类功能内部使用到了本类对象都用this表示。
例,

  1. class Person {
  2. private String name;
  3. private int age;
  4. Person(int age) {
  5. this.age = age;
  6. }
  7. Person(String name) {
  8. this.name = name;
  9. }
  10. Person(String name, int age) {
  11. this.name = name;
  12. this.age = age;
  13. }
  14. public void speak() {
  15. System.out.println("name="+this.name+"...age="+this.age);
  16. this.show();
  17. }
  18. public void show() {
  19. System.out.println(this.name);
  20. }
  21. /*
  22. 需求:给人用于比较年龄是否相同的功能,也即是否是同龄人。
  23. */
  24. public boolean compare(Person per) {
  25. return this.age == per.age;
  26. }
  27. }
  28. class PersonDemo3 {
  29. public static void main(String[] args) {
  30. Person p = new Person(20);
  31. Person p1 = new Person(25);
  32. boolean b = p1.compare(p2);
  33. System.out.println(b);
  34. }
  35. }

还有,this语句:用于构造函数之间互相调用,而且this语句只能定义在构造函数的第一行,因为初始化要先执行。

static(静态)关键字

static用法:是一个修饰符,用于修饰成员(成员变量、成员函数)。
当成员被静态修饰后,就多了一个调用方式,除了可以被对象调用外,还可以直接被类名调用:类名.静态成员
被修饰后的成员具备以下特点:

  1. 随着类的加载而加载,类的消失而消失,说明它的生命周期最长。
  2. 优先于对象存在,明确一点:静态是先存在的,对象是后存在的。
  3. 被所有对象所共享。
  4. 可以直接被类名所调用。

成员变量,也叫实例变量;静态的成员变量,也叫类变量。那么实例变量和类变量的区别:

  1. 存放位置
    • 类变量随着类的加载而存在于方法区中。
    • 实例变量随着对象的建立而存在于堆内存中。
  2. 生命周期
    • 类变量生命周期最长,随着类的消失而消失。
    • 实例变量生命周期随着对象的消失而消失。

静态使用注意事项:

  1. 静态方法只能访问静态成员,非静态方法既可以访问静态也可以访问非静态。
  2. 静态方法中不可以定义thissuper关键字。因为静态优先于对象存在,所以静态方法中不可以出现this
  3. 主函数是静态的。

静态有利有弊:
利处:对对象的共享数据进行单独空间的存储,节省空间,没有必要每一个对象中都存储一份;可以直接类名调用。
弊端:生命周期过长,访问出现局限性(静态虽好,只能访问静态)。

静态代码块

格式:

  1. static {
  2. 静态代码块中的执行语句
  3. }

特点:随着类的加载而执行,而且只执行一次,并优先于主函数。用于给类进行初始化。
其应用场景为:类不需要创建对象,但是需要初始化,这时将部分代码存储到静态代码块中。
例,

  1. class StaticCode {
  2. static {
  3. System.out.println("a");
  4. }
  5. }
  6. class StaticCodeDemo {
  7. static {
  8. System.out.println("b");
  9. }
  10. public static void main(String[] args) {
  11. new StaticCode(); // 将StaticCode.class这个类加载进内存
  12. new StaticCode(); // 注意StaticCode.class已经加载进内存,不会再次加载
  13. System.out.println("over");
  14. }
  15. static {
  16. System.out.println("c");
  17. }
  18. }

输出结果:

b
c
a
over

注意:StaticCode s = null;,StaticCode类并没有被加载进内存。只有用到了类中的内容,才涉及类的加载问题,光建立引用,是不会加载的。
面试一般这样考:

  1. class StaticCode {
  2. int num = 9;
  3. StaticCode() {
  4. System.out.println("b");
  5. }
  6. static {
  7. System.out.println("a");
  8. }
  9. {
  10. System.out.println("c"+this.num);
  11. }
  12. StaticCode(int x) {
  13. System.out.println("d");
  14. }
  15. public static void show() {
  16. System.out.println("show run");
  17. }
  18. }
  19. class StaticCodeDemo {
  20. public static void main(String[] args) {
  21. new StaticCode(4);
  22. }
  23. }

问运行结果?
答:输出为:

a
c9
d

主函数

主函数:是一个特殊的函数,作为程序的入口,可以被JVM调用。
主函数的定义:

主函数是固定格式的:JVM设别。JVM在调用主函数时,传入的是new String[0];。我们可以通过以下代码测试:

  1. class MainDemo {
  2. public static void main(String[] args) {
  3. System.out.println(args); // [Ljava.lang.String;@139a55
  4. System.out.println(args.length); // 0
  5. }
  6. }

须知主函数也是一个函数,即支持函数重载,所以以下代码也是可行的:

  1. class MainDemo {
  2. // 函数的重载
  3. public static void main(int x) {
  4. }
  5. public static void main(String[] args, int x) {
  6. }
  7. public static void main(String[] args) {
  8. }
  9. }

我们也可以在运行的时候指定输入一些字符串,打印在控制台上。

  1. class MainDemo {
  2. public static void main(String[] args) {
  3. for (int x = 0; x < args.length; x++) {
  4. System.out.println(args[x]);
  5. }
  6. }
  7. }

运行时指令:java MainDemo li yun ling
输出:

li
yun
ling

当然了,如果不爽也可以这样做:

  1. class MainDemo {
  2. public static void main(String[] args) {
  3. String[] arr = {"haha", "hehe", "heihei", "xixi", "haihai"};
  4. MainTest.main(arr);
  5. }
  6. }
  7. class MainTest {
  8. public static void main(String[] args) {
  9. for (int x = 0; x < args.length; x++) {
  10. System.out.println(args[x]);
  11. }
  12. }
  13. }

练习:通过以下程序代码,试说明Person p = new Person("张三", 23);该句话都做了什么事情?

  1. class Person {
  2. private String name = "haha";
  3. private int age;
  4. private static String country = "CN";
  5. Person(String name, int age) {
  6. this.name = name;
  7. this.age = age;
  8. }
  9. {
  10. System.out.println(name+".."+age);
  11. }
  12. public void setName(String name) {
  13. this.name = name;
  14. }
  15. public void speak() {
  16. System.out.println(this.name+"..."+this.age);
  17. }
  18. public static void showCountry() {
  19. System.out.println("country="+country);
  20. method();
  21. }
  22. public static void method() {
  23. System.out.println("method run");
  24. }
  25. }
  26. class PersonDemo {
  27. public static void main(String[] args) {
  28. Person p = new Person("张三", 23);
  29. p.setName("lisi");
  30. }
  31. }

解:
Person p = new Person("张三", 23);该句话都做了什么事情呢?

  1. 因为new用到了Person.class,所以会找到Person.class文件并加载到内存。
  2. 执行该类中的static代码块,如果有的话,给Person.class类进行初始化。
  3. 在堆内存中开辟空间,分配内存地址。
  4. 在堆内存中建立对象的特有属性,并进行默认初始化。
  5. 对属性进行显示初始化。
  6. 对对象进行构造代码块初始化。
  7. 对对象进行对应的构造函数初始化。
  8. 将内存地址赋给栈内存中的p变量。

什么时候使用静态?
要从两方面下手:因为静态修饰的内容有成员变量和函数。
什么时候定义静态变量(类变量)呢?
当对象中出现共享数据时,该数据被静态所修饰,对象中的特有数据要定义成非静态,存在堆内存中。
什么时候定义静态函数呢?
当功能内部没有访问到非静态数据(对象的特有数据),那么该功能就定义成静态的

静态的应用

每一个应用程序中都有共性的功能,可以将这些功能进行抽取,独立封装,以便复用。
我们试图建立一个可以对数组进行操作的工具类,该类中提供了获取最值、排序等功能。
分析:虽然可以通过建立ArrayTool的对象使用这些工具方法,对数组进行操作,发现了问题:

  1. 对象是用于封装数据的,可是ArrayTool对象并未封装特有数据
  2. 操作数组的每一个方法都没有用到ArrayTool对象中的特有数据

这时就考虑,让程序更严谨,是不需要对象的。可以将ArrayTool中的方法都定义成static的。直接通过类名调用即可。
将方法都静态后,可以方便于使用,但是该类还是可以被其他程序建立对象的。为了更为严谨,强制让该类不能建立对象。可以通过将构造函数私有化完成。
并且,为了能生成API文档,我们加入java文档注释。代码为:

  1. /**
  2. 这是一个可以对数组进行操作的工具类,该类中提供了获取最值、排序等功能。
  3. @author 李阿昀
  4. @version V1.1
  5. */
  6. public class ArrayTool {
  7. // ArrayTool() {}
  8. /**
  9. 空参数构造函数
  10. */
  11. private ArrayTool() {
  12. }
  13. /**
  14. 获取一个整型数组中的最大值
  15. @param arr 接受一个int类型的数组
  16. @return 会返回一个该数组中的最大值
  17. */
  18. public static int getMax(int[] arr) {
  19. int max = 0;
  20. for (int x = 0; x < arr.length; x++) {
  21. if(arr[x] > arr[max])
  22. max = x;
  23. }
  24. return arr[max];
  25. }
  26. /**
  27. 获取一个整型数组中的最小值
  28. @param arr 接受一个int类型的数组
  29. @return 会返回一个该数组中的最小值
  30. */
  31. public static int getMin(int[] arr) {
  32. int min = 0;
  33. for (int x = 0; x < arr.length; x++) {
  34. if(arr[x] < arr[min])
  35. min = x;
  36. }
  37. return arr[min];
  38. }
  39. /**
  40. 给int数组进行选择排序
  41. @param arr 接受一个int类型的数组
  42. */
  43. public static void selectSort(int[] arr) {
  44. for (int x = 0; x < arr.length - 1; x++) {
  45. for (int y = x + 1; y < arr.length; y++) {
  46. if(arr[x] > arr[y]) {
  47. swap(arr, x, y);
  48. }
  49. }
  50. }
  51. }
  52. /**
  53. 给int数组进行冒泡排序
  54. @param arr 接受一个int类型的数组
  55. */
  56. public static void bubbleSort(int[] arr) {
  57. for (int x = 0; x < arr.length - 1; x++) {
  58. for (int y = 0; y < arr.length - x - 1; y++) {
  59. if(arr[y] > arr[y+1]) {
  60. swap(arr, y, y+1);
  61. }
  62. }
  63. }
  64. }
  65. /**
  66. 给数组中的元素进行位置的置换
  67. @param arr 接受一个int类型的数组
  68. @param a 要置换的位置
  69. @param b 要置换的位置
  70. */
  71. private static void swap(int[] arr, int a, int b) {
  72. int temp = arr[a];
  73. arr[a] = arr[b];
  74. arr[b] = temp;
  75. }
  76. /**
  77. 用于打印数组中的元素。打印形式是:[element1, element2, ...]
  78. @param arr 接受一个int类型的数组
  79. */
  80. public static void printArray(int[] arr) {
  81. System.out.print("[");
  82. for (int x = 0; x < arr.length; x++) {
  83. if(x != arr.length - 1)
  84. System.out.print(arr[x]+", ");
  85. else
  86. System.out.println(arr[x]+"]");
  87. }
  88. }
  89. }

接下来,将ArrayTool.class文件发送给其他人,其他人只要将该文件设置到classpath路径下,就可以使用该工具类。但是,很遗憾,该类中定义了多少个方法,对方不清楚,因为该类并没有使用说明书。开始制作程序的说明书,java的说明书通过文档注释来完成。
javac -d myhelp -author -version ArrayTool.java
myhelp:为文档生成的目录,默认为当前目录,也可以指定为其他目录
-author:文档作者的名字
-version:文档版本

单例设计模式

设计模式:解决某一类问题最行之有效的方法。JAVA中有23种设计模式。
单例设计模式:解决一个类在内存中只存在一个对象的问题。
想要保证对象唯一:

  1. 为了避免其他程序过多建立该类对象,先禁止其他程序建立该类对象。
  2. 还为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象。
  3. 为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式。

这3步怎么用代码体现?

  1. 将构造函数私有化
  2. 在类中创建一个本类对象
  3. 提供一个方法可以获取到该对象

对于事物该怎么描述,还怎么描述。当需要将该事物的对象保证在内存中唯一时,就将以上的3步加上即可。
例,

  1. class Single {
  2. private int num;
  3. public void setNum(int num) {
  4. this.num = num;
  5. }
  6. public int getNum() {
  7. return num;
  8. }
  9. private Single() {}
  10. private static Single s = new Single();
  11. public static Single getInstance() {
  12. return s;
  13. }
  14. }

那么,以下代码

  1. Single s1 = Single.getInstance();
  2. Single s2 = Single.getInstance();

在内存中的结构如图所示:
单例模式
以上代码是先初始化对象。称为:饿汉式。Single类一进内存,就已经创建好了对象。
开发原则:定义单例,建议使用饿汉式。
懒汉式(面试常考)
对象是方法被调用时,才初始化,也叫做对象的延时加载。称为:懒汉式。
Single类进内存,对象还没存在,只有调用getInstance()方法时,才建立对象,如:

  1. class Single {
  2. private static Single s = null;
  3. private Single() {}
  4. //synchronized相当于上了一个锁,但程序效率降低了
  5. public static Single getInstance() {
  6. if(s == null) // 双重判断,可解决这个问题(涉及多线程)
  7. synchronized(Single.class) {
  8. if(s == null)
  9. s = new Single();
  10. }
  11. return s;
  12. }
  13. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注