• 1. Java程序设计系列讲座-2 面向对象程序设计黄绍辉 厦门大学计算机科学系 E-mail: hsh@xmu.edu.cn
  • 2. 面向对象程序设计(OOP)基本思想对象的基本概念 对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位。一个对象由一组属性和对这组属性进行操作的一组服务组成。 对象是问题域或实现域中某些事物的一个抽象,它反映该事物在系统中需要保存的信息和发挥的作用;它是一组属性和有权对这些属性进行操作的一组服务的封装体。 客观世界是由对象和 对象之间的联系组成。
  • 3. 类的基本概念类是具有相同属性和服务的一组对象的集合。分类的原则是抽象。 在面向对象的编程语言中,类是一个独立的程序单位,它应该有一个类名并包括属性说明和服务说明两个主要部分。 例如图上的三种 车,可以做成三 个类。具体到某 一辆车子,就是 一个具体对象。
  • 4. 面向对象的基本特征封装性 把对象的属性和服务结合成一个独立的相同单位,并尽可能隐蔽对象的内部细节。 继承性 从一个已有的类来构造新类,除了保留原有的类的特性,还增加了一些新特性。已有的类称为父类superclass,新类称为子类subclass。 多态性 在父类中定义的属性或服务被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为。
  • 5. 面向对象程序设计的框架用对象的思想来组织程序模块。每一个程序至少有一个对象(class); 对象与对象之间一般是并列关系,也可以有包含关系; 对象可以拥有两种东西:属性和服务。属性是一堆的变量,服务是一堆的函数。它们都是对象的成员,所以又叫成员变量、成员函数; 面向对象的程序设计分两步:一是先独立创建各种对象,好好设计每个对象的属性和服务;二是在main函数中使用各个对象,让对象按照设计的流程发挥各自的作用。
  • 6. 先来学习怎么使用类数组专题
  • 7. 数组Java的数组是用纯正OOP的方法设计的,所有专门有一个类,叫Array,用来实现数组的功能。 尽管我们刚刚知道,类和对象还是不同的,但是今后我们不太区分这两个词汇;如果要细究,可以自己结合上下文理解。 下面我们来学习如果使用人家做好的类,等有感觉了再来做自己的类。
  • 8. 数组入门-11 声明一个数组 数据类型[] 数组名; or 数据类型 数组名[]; 例如:double[] myList; int a[]; 2 创建一个数组 数组名 = new 数据类型[数组长度]; 如:myList = new double[10];
  • 9. 数组入门-2合并前两步,声明并创建一个数组 数据类型[] 数组名 = new 数据类型[数组长度]; or 数据类型 数组名[] = new 数据类型[数组长度]; 例如:double[] myList = new double[10]; int a[] = new int[10]; 友情提醒:要使用一个对象,都需要先声明,再创建,然后才能使用,两步缺一不可;当然这两步可以合并,例如上面的例子。 声明的作用仅仅是弄出一个空对象(null)。
  • 10. 数组入门-3数组在创建(new)之后,Java才会为这个数组自动分配空间。(注意声明数组的时候并没有分配数组元素的空间,因为那时候不知道数组多大,所以只是一个空数组,无法容纳任何元素)
  • 11. 数组入门-4任何一个数组,都可以通过属性length得到数组的长度。如: myList.length 数组创建的时候会自动赋零值,对于数值型的,就是0或者0.0或者'\0',对于布尔型的,就是false 任何一个数组,其元素的最小下标是0,最大下标是length-1 数组下标越界会引发异常
  • 12. 数组入门-5数组元素的引用使用[ ],以下两个方法等价,都可以打印出数组myList的每一个元素值: for (int i = 0; i < myList.length; i++) { System.out.print(" "+myList[i]); } 或者(JDK1.5及以上版本才支持的) for (int val : myList) { System.out.print(" "+val); }
  • 13. 数组入门-6数组在创建的时候可以顺便初始化,如: double[] myList = {1.9, 2.9, 3.4, 3.5}; 它相当于: double[] myList = new double[4]; myList[0] = 1.9; myList[1] = 2.9; myList[2] = 3.4; myList[3] = 3.5;
  • 14. 数组例程-1/3 用100以内随机数初始化数组 for (int i = 0; i < myList.length; i++) { myList[i] = Math.random() * 100; }
  • 15. 数组例程-2/3数组求和 double total = 0; for (int i = 0; i < myList.length; i++) { total += myList[i]; }
  • 16. 数组例程-3/3求数组中的最大值 double max = myList[0]; for (int i = 1; i < myList.length; i++) { if (myList[i] > max) max = myList[i]; }
  • 17. 复制数组-1假设list1和list2都是数组,执行语句:list2 = list1 上述语句并不能将list1全部复制给list2,而是仅仅让list2变成list1的引用。换言之,此时list2和list1表示同一个数组,原先list2数组的元素就全被抛弃在内存的某一处,找不回来了!
  • 18. 复制数组-2为了复制一个数组到另一个数组,可以自己写一个循环,一个一个复制。例如,有两个数组: int[ ] sourceArray = {2, 3, 1, 5, 10}; int[ ] targetArray = new int[sourceArray.length]; 一个一个搬的例子 for (int i = 0; i < sourceArray.length; i++) { targetArray[i] = sourceArray[i]; }
  • 19. 总结一下假设Student是一个类,要使用这个类,必须这样: Student a, b; //先声明 a = new Student(); b = new Student(); //再创建 //下面可以随意折腾a和b了,它们是两个对象 对象之间不能通过=完成复制,例如在上面代码的基础上,使用a=b,不是把b的内容给a,而是让a和b共享b的对象;原先a的内容就被a抛弃了。
  • 20. 数组作为函数参数-1对象(包括数组)作为参数,Java采用的是引用传递方式,基本数据类型(int,char等)作为参数,采用值传递方式。 简单一点说,引用传递方式,实际参数和形式参数,操作的其实是同一个对象;值传递方式,实际参数和形式参数,仅仅是值相等而已。 Java中,所有被用作参数的对象,都采用引用方式传递以提高效率(类似于C中的地址传递,因为Java的对象实在太大只了,所有成员都复制一遍不划算,复制对象起始地址就好)
  • 21. 数组作为函数参数-2public class Test { public static void main(String[] args) { int x = 1; // x represents an int value int[] y = new int[10]; // y represents an array of int values m(x, y); // Invoke m with arguments x and y System.out.println("x is " + x); // x值不变,还是1 System.out.println("y[0] is " + y[0]); // y[0]值已改变,本来是0的 } public static void m(int number, int[] numbers) { number = 1001; // Assign a new value to number numbers[0] = 5555; // Assign a new value to numbers[0] } }
  • 22. 数组作为函数参数-3 1 public class TestPassArray { 2 /** Main method */ 3 public static void main(String[] args) { 4 int[] a = {1, 2}; 5 6 // Swap elements using the swap method 7 System.out.println("Before invoking swap"); 8 System.out.println("array is {" + a[0] + ", " + a[1] + "}"); 9 swap(a[0], a[1]); //值传递,返回后不改变原值 10 System.out.println("After invoking swap"); 11 System.out.println("array is {" + a[0] + ", " + a[1] + "}"); 12 13 // Swap elements using the swapFirstTwoInArray method 14 System.out.println("Before invoking swapFirstTwoInArray"); 15 System.out.println("array is {" + a[0] + ", " + a[1] + "}"); 16 swapFirstTwoInArray(a); //引用传递,返回后原值可能改变 17 System.out.println("After invoking swapFirstTwoInArray"); 18 System.out.println("array is {" + a[0] + ", " + a[1] + "}"); 19 }
  • 23. 数组作为函数参数-420 //值传递,返回后不改变原值 21 /** Swap two variables */ 22 public static void swap(int n1, int n2) { 23 int temp = n1; 24 n1 = n2; 25 n2 = temp; 26 } 27 //引用传递,返回后原值可能改变 28 /** Swap the first two elements in the array */ 29 public static void swapFirstTwoInArray(int[] array) { 30 int temp = array[0]; 31 array[0] = array[1]; 32 array[1] = temp; 33 } 34 }
  • 24. 再来一个例子class ClassA { int a = 1; } public class Main { static void f1(int a) { a++; } static void f2(ClassA c) { c.a++; } public static void main(String[] args) { ClassA c = new ClassA(); System.out.println(“a=”+c.a); //输出a=1 f1(c.a); //基本数据类型做参数,c.a不变 System.out.println("a="+c.a); //输出a=1 f2(c); //引用数据类型做参数,c改变 System.out.println("a="+c.a); //输出a=2 } }
  • 25. 数组作为函数返回值函数的返回值是通过return带回来的,每次return可以带回一个基本类型的值(char、int等),也可以带回一个对象(例如数组)
  • 26. 一个例子:计算字符出现的次数解题思路: 随机生成100个小写字母,放在数组chars中;显然,这个数组的大小应该是100; 利用counts数组存储字符出现的次数,下标0,1,2,...分别存储'a','b','c',...的次数;显然,这个数组的大小应该是26。 生成随机小写字母可以借助于Math.random()
  • 27. 关键代码-1/520 /** Create an array of characters */ 21 public static char[] createArray() { 22 // Declare an array of characters and create it 23 char[] chars = new char[100]; 24 25 // Create lowercase letters randomly and assign 26 // them to the array 27 for (int i = 0; i < chars.length; i++) 28 chars[i] = RandomCharacter.getRandomLowerCaseLetter(); 29 30 // Return the array 31 return chars; 32 }
  • 28. 关键代码-2/534 /** Display the array of characters */ 35 public static void displayArray(char[] chars) { 36 37 for (int i = 0; i < chars.length; i++) { 38 if ((i + 1) % 20 == 0) 39 System.out.println(chars[i] + " "); 40 else 41 System.out.print(chars[i] + " "); 42 } 43 }
  • 29. 关键代码-3/545 /** Count the occurrences of each letter */ 46 public static int[] countLetters(char[] chars) { 47 // Declare and create an array of 26 int 48 int[] counts = new int[26]; 49 50 // For each lowercase letter in the array, count it 51 for (int i = 0; i < chars.length; i++) 52 counts[chars[i] - 'a']++; 53 54 return counts; 55 }
  • 30. 关键代码-4/557 /** Display counts */ 58 public static void displayCounts(int[] counts) { 59 for (int i = 0; i < counts.length; i++) { 60 if ((i + 1) % 10 == 0) 61 System.out.println(counts[i] + " " + (char)(i + 'a')); 62 else 63 System.out.print(counts[i] + " " + (char)(i + 'a') + " "); 64 } 65 }
  • 31. 关键代码-5/5 3 public static void main(String args[]) { 4 // Declare and create an array 5 char[] chars = createArray(); 6 7 // Display the array 8 System.out.println("The lowercase letters are:"); 9 displayArray(chars); 10 11 // Count the occurrences of each letter 12 int[] counts = countLetters(chars); 13 14 // Display counts 15 System.out.println(); 16 System.out.println("The occurrences of each letter are:"); 17 displayCounts(counts); 18 }
  • 32. 多维数组-1二维数组的定义 数据类型[][] 数组名; or 数据类型 数组名[][]; 例如: int[][] matrix; 或 int matrix[][]; 注意,这里仅仅说明matrix是一个二维数组,但是这个数组目前是null,所以还不能存储元素。
  • 33. 多维数组-2二维数组有行和列的概念,如:
  • 34. 多维数组-3二维数组可以在声明的时候赋初值,也可以在new之后再一个一个慢慢赋值。 其实二维数组行和行之间是相互独立的,每一行都相当于一个一维数组。
  • 35. 由于二维数组的每一行是一个一维数组,所以这些一维数组的大小不必相等,如: 此时: triangleArray.length = 5, //因为数组有5行 triangleArray[0].length = 5, triangleArray[1].length = 4, triangleArray[2].length = 3, triangleArray[3].length = 2, triangleArray[4].length = 1.多维数组-4
  • 36. 二维数组例程-1随机数填充 for (int row = 0; row < matrix.length; row++) { for (int column = 0; column < matrix[row].length; column++) { matrix[row][column] = (int)(Math.random() * 100); } } 这里matrix是二维数组。过一遍二维数组必须用两层的循环,一般用i循环行,j循环列;如果英文好一点,可以用row循环行,column或者col循环列。
  • 37. 二维数组例程-2打印数组元素 for (int row = 0; row < matrix.length; row++) { for (int column = 0; column < matrix[row].length; column++) { System.out.print(matrix[row][column] + " "); } System.out.println(); }
  • 38. 二维数组例程-3数组求和 int total = 0; for (int row = 0; row < matrix.length; row++) { for (int column = 0; column < matrix[row].length; column++) { total += matrix[row][column]; } }
  • 39. 一个例子:试卷评分假设让计算机改卷,8张试卷和标准答案如下:
  • 40. 关键代码学生的试卷存储在answers二维数组中,答案在keys一维数组中,则评分的代码是: 18 // Grade all answers 19 for (int i = 0; i < answers.length ; i++) { 20 // Grade one student 21 int correctCount = 0; 22 for (int j = 0; j < answers[i].length ; j++) { 23 if (answers[i][j] == keys[j]) 24 correctCount++; 25 } 26 27 System.out.println("Student " + i + "'s correct count is " + 28 correctCount); 29 }
  • 41. 再来学习怎么使用类字符串专题
  • 42. 字符串类-1字符串是Java另一个常用类。 创建一个字符串(三种方法均可) String message = new String("Welcome to Java"); String message = "Welcome to Java"; char[] charArray = {'G', 'o', 'o', 'd'}; String message = new String(charArray);
  • 43. 字符串类-2字符串的内容是常量,一旦创建就无法修改。 String s = "Java"; s = “HTML”; //友情提醒:这句是对的。但是这不是串复制,只是一个s抛弃了“Java”字符串的故事。 以上第2行代码仅仅修改了s的指向,但是没有改动到"Java"这个字符串本身,如图:
  • 44. 字符串类-3正宗的字符串内容判等函数,是equals,不是==(这个符号相当于比较对象地址是否相等) String s1 = new String("Welcome to Java"); String s2 = "Welcome to Java"; System.out.println("s1 == s2 is " + (s1 == s2)); System.out.println("s1.equals(s2) is " + (s1.equals(s2))); 字符串内容比较函数,用compareTo s1.compareTo(s2),其返回值相当于s1-s2,相等为0
  • 45. 字符串类-4字符串长度函数length() String message = new String("Welcome to Java"); int len = message.length(); 注意:数组也有长度的概念,但是数组的长度被设计为数组的属性而不是函数。 int[] arr = new int[5]; int len = arr.length; 取字符串的单个字符charAt() message.charAt(index) 下标范围是0..s.length()–1
  • 46. 字符串类-5字符串拼接concat()或者+ String s3 = s1.concat(s2); 等价于:String s3 = s1 + s2; 截取子串substring String message = "Welcome to Java".substring(0, 11) + "HTML"; //相当于从0开始,到11为止的子串(不包括11) 如果没有提供endIndex,相当于截取到串尾
  • 47. 字符串类-6字符串转换 "Welcome". toLowerCase() //返回welcome "Welcome". toUpperCase() //返回WELCOME " Welcome". trim() //返回Welcome "Welcome". replace('e', 'A') //返回WAlcomA "Welcome". replaceFirst("e", "A") //返回WAlcome "Welcome". replaceAll("e", "A") //返回WAlcomA
  • 48. 字符串类-7查找指定的字符或者子串 indexOf(char ch) //查找指定字符,返回下标 lastIndexOf(char ch) //同上,但是逆向查找 indexOf(int ch, int fromIndex) 从下标fromIndex开始查找指定字符 lastIndexOf(int ch, int endIndex) 同上,但是从下标endIndex开始逆向查找 以上函数都有字符串版本,把参数换成String就可以了。如果查找失败,返回值都是-1。因为这是个无效的下标。
  • 49. 字符串类-8"Welcome to Java".indexOf('W') returns 0. "Welcome to Java".indexOf('o') returns 4. "Welcome to Java".indexOf('o', 5) returns 9. "Welcome to Java".indexOf("come") returns 3. "Welcome to Java".indexOf("Java", 5) returns 11. "Welcome to Java".indexOf("java", 5) returns -1. "Welcome to Java".lastIndexOf('W') returns 0. "Welcome to Java".lastIndexOf('o') returns 9. "Welcome to Java".lastIndexOf('o', 5) returns 4. "Welcome to Java".lastIndexOf("come") returns 3. "Welcome to Java".lastIndexOf("Java", 5) returns -1. "Welcome to Java".lastIndexOf("java", 5) returns -1.
  • 50. 字符串类-9字符串分拆到字符数组toCharArray char[] chars = "Java".toCharArray(); 用字符串的子串去填充数组getChars char[] dst = {'J', 'A', 'V', 'A', '1', '3', '0', '1'}; "CS3720".getChars(2, 6, dst, 4); 头两个参数取子串,后面是填充目标数组和起始位置 字符数组拼成字符串String或valueOf String str = new String(new char[]{'J', 'a', 'v', 'a'}); String str = String.valueOf(new char[]{'J', 'a', 'v', 'a'});
  • 51. 字符串类-10基本类型(char, double, long, int, float)转换成字符串valueOf double d = 5.44; String s = String.valueOf(d); //s="5.44" 其实也可以这样 String s=""+d; //s="5.44" 字符串转成数值型(不是String的函数) Double.parseDouble(str) //"5.44"=>5.44 Integer.parseInt(str) //"123"=>123
  • 52. 例题:检查一个字符串是不是回文关键代码: public static boolean isPalindrome(String s) { int low = 0; int high = s.length() - 1; while (low < high) { if (s.charAt(low) != s.charAt(high)) return false; low++; high--; } return true; }
  • 53. 跑步进入OOP
  • 54. Part I 封装性
  • 55. 从简单对象入手对象不是很深奥的东西,一个对象在Java里面就是一个class而已。例如随便写一个对象: class Point { int x,y; } 上面这个Point对象用来表示平面上的一个点,它和C语言的结构体非常类似;当然,用法显然是不一样了。
  • 56. 对象的用法要使用一个已有的对象不是太难,但是你要做足以下的两件事情: 使用对象的名字声明一个变量 创建对象的一个实例 用上面的Point做例子,就是这样: 使用对象的名字声明一个变量 Point p; 创建对象的一个实例 p = new Point(); 接下来这个p就拥有了Point的全部资源,例如它有两个成员变量,叫x,y,而且还是整型的。
  • 57. 对象的用法对象的声明和创建可以合成一步: Point p = new Point(); 当然也可以隔了很多步: Point p; /* 中间有很多很多很多行,但是和p无关 */ p = new Point(); 只有一个准则:对象在创建之前是不能使用的,所以new很重要。
  • 58. 对象与实例前面说到,对象必须创建才能使用,那么创建是不是只有一种方法呢? 默认的显然是只有一种,所以你只能这样创建p:Point p = new Point(); 这里引入几个术语: Point 是类名,也是对象名,或者干脆叫对象 p 是对象的实例,也就是Point的实例 你可以理解对象是一个图章,它每盖章一次生成一个p,p就是一个实例。
  • 59. 我们来用一下自己的对象class Point { int x,y; } class Test { public static void main(String args[]) { Point a = new Point(); a.x = 10; a.y = 10; Point b = new Point(); b.x = 100; b.y = 100; System.out.print("a("+a.x+", "+a.y+"), b("+b.x+","+b.y+")"); } }
  • 60. 什么是构造函数可不可以换一种方式创建Point?当然可以,不过你要自己加构造函数,例如: class Point { int x,y; Point(int xx, int yy) { x=xx;y=yy;} } 现在你可以这样创建一个p了: Point p = new Point(1,2); 不过你现在反而不能这样创建一个p了: Point p = new Point(); why?
  • 61. 关于构造函数 I构造函数和类的名字完全一样,并且没有返回值,它的作用是提供类的创建方式,所以,根据类必须先创建再使用的原则,构造函数是每一个类一定要具备的东西。 或许你注意到第一个版本的Point没有构造函数,是的,不过Java是如此聪明的一个语言,所以它会帮你补充一个不带参数,没有函数体的构造函数,例如Point类,它偷偷加的代码是:class Point { int x,y; Point() { } //这一行尽管你没看见,但是它确实存在 }
  • 62. 关于构造函数 IIJava自动提供构造函数仅仅当你自己没有提供构造函数的时候才这么干,如果你提供了哪怕只有一个的构造函数,它决不自作多情帮你加一个。所以第二个版本的Point最好改成下面: class Point { int x,y; Point(){ } //由于Java罢工,这个函数只好自己加 Point(int xx, int yy) { x=xx;y=yy;} }
  • 63. 关于成员变量-对内篇每一个类都可以有自己的成员变量,例如Point类的x和y;对于Point类的内部而言,x,y相当于全局变量;如果脱开Point类,那么就要看x,y是怎么声明的了。 class Point { Point(){ x=10; y=10; } Point(int xx, int yy) { x=xx;y=yy;} int x,y; } x,y的作用范围 从这里开始x,y的作用范围 到这里结束
  • 64. 关于成员变量-对外篇成员变量有四种修饰,它们决定了外部访问成员变量的权限: private,default,protected、public 对于private,实际上是不允许外部访问的,所以如果你的Point这样定义: class Point { private int x,y; } 然后有 Point p = new Point(); 那么这两个语句是错的:p.x=10; p.y=10;
  • 65. 关于成员变量-对外篇如果这个时候你想从外面修改x,y的值怎么办?答案是:没法改!!!除非Point类另外提供函数给你用,例如: class Point { private int x,y; public int getX() { return x;} //这就是传说中的getter public void setX(int xx) { x=xx;} //传说中的setter } 上帝保佑,现在你终于可以从外面改x的值了: Point p = new Point(); p.setX(100); System.out.print(p.getX());
  • 66. 关于成员函数 I成员函数的访问权限和成员变量完全一样,所以这里不重复了。 成员函数的调用,也要通过对象的实例才能够调用(当然静态函数除外,后面再讲),不会忘记什么叫对象的实例了吧?Oh, my God! 还是加一个函数试试,例如我们给Point加一个成员函数: public double test(double s) { return x*y*s; }
  • 67. 关于成员函数 II 现在我们来调这个函数(这里是在外部调,如果在类的内部调,那就直接调用就好了) Point p = new Point(3,4); System.out.print(p.test(5.0)); //通过p调用test函数 还是不太放心,演示一下对象内部怎么调test: class Point { private int x,y; public double f() { return test(5.0);} public double test(double s) { return x*y*s; } }
  • 68. 关于成员函数 III 总结一下,一个类的成员函数: 对于类的内部而言,是直接抓过来随便爱怎么调用都行(多大公无私啊),不需要借助任何东西; 对于类的外部而言,如果是private的成员函数,那显然是不能用的;如果遇到public的,那运气还不错,可以通过创建一个类的实例(也就是new一下),来间接调用这个成员函数。 成员变量的结论与此类似。 顺便说一下,在一个类的外部,每次new一个实例的时候,相当于调用一次构造函数;至于调用的是哪一个构造函数,那要看你给的参数了。
  • 69. 关于静态方法和静态成员 I如果一个class有一个static的成员,那使用的时候要小心一点。给一个例子: class A { public int x; public static int y; public static void f() { System.out.print(x+y); } } 还记得怎么调用f,怎么使用x,y么?很简单: A a = new A(); //看到这句不要晕掉,好多a... a.x = 10; a.y = 10; a.f(); //调用函数f,不要参数
  • 70. 关于静态方法和静态成员 II下面来一段复杂的测试代码,测试A这个类: class Test { public static void main(String args[]) { A a = new A(); a.x = 10; a.y = 10; A b = new A(); b.x = 100; b.y = 100; System.out.print("a.x="+a.x+"a.y="+a.y+"b.x="+b.x+"b.y="+b.y); } } 运行上面程序,你会发现一个奇怪的现象: a.x不等于b.x,可是a.y居然等于b.y 前一半比较好理解,因为a和b本来就是不同的实例,虽然它们是一个模子造出来的;可是,那y怎么回事?
  • 71. 关于静态方法和静态成员 III其实,静态成员是如此特殊的一个东西,因为它是所有实例共享的!!!也就是无论你new多少个实例出来,静态成员自始至终就是一个而已。而且,就算是你没有new过实例,它就已经存在了,神奇吧? 所以,Java鼓励你这样直接使用静态成员: A.y = 100; A.f(); //注意这里没有实例,A亲自上阵了 所以,静态成员变量在java里一般被用作共享的常数,例如Math.PI,所以你可以常常看到final和static一起混的情形;静态成员函数一般被用作全局函数,供任意类调用,例如Math.sin()
  • 72. 再次啰唆一下类成员的访问控制类成员总共有四种访问模式:private,protected,public以及default,它们的访问控制如下: 确定访问控制权限的原则 基于安全性的考虑,在能满足设计要求的前提下,访问权限越严格越好
  • 73. 一个例子class Root { private int a; protected float b; public double c; char d; void fun() { a++; b++; c++; d++; //类的内部,成员变量相当于全局 //变量,在任何函数中均可直接用 } } 假设有一个类的实例 Root r = new Root(); 那么 r.a++就是错误的,因为这相当于在类的外部使用变量; 但是 r.c++是正确的,因为c是public,支持外部访问。
  • 74. 关于封装的进一步探讨 Java的封装属性有四个(按开放程度排序): private,default,protected以及public。 一般来说,封装越严格越好,因此尽量不要将成员变量设置为public。 当把成员变量设置为private时,为了能够提供外部类对这些成员变量的存取,一般要设置一堆的public的get方法和set方法。例如对hour成员: public int getHour() { return hour; } public void setHour(int h) { hour = h; }
  • 75. 方法重载方法重载是指 多个方法享有 相同的名字, 但是这些方法 的参数必须不 同,或者是参 数的个数不同,或者是参数类型不同。 返回类型不能 用来区分重载 的方法。
  • 76. 再次啰唆一下构造方法◇ 构造方法是一个特殊的方法。Java 中的每个类都有构造方法,用来初始化该类的一个对象。 ◇ 构造方法具有和类名相同的名称,而且不返回任何数据类型。 ◇ 重载经常用于构造方法。 ◇ 构造方法只能由new运算符调用。
  • 77. 关于构造方法以及new 要生成一个对象的实例,必须使用new运算符。每次new的时候,对象的构造方法会被自动调用。 构造的方法可能有很多个,new的时候调用哪一个取决于new的时候带的参数。 例如: MyTime t1=new MyTime(); //调用无参数的构造方法 MyTime t2=new MyTime(hour, second, minute); //调用使用三个参数的构造方法,注意三个参数的类型 //要和构造方法声明的参数类型保持一致
  • 78. 一个练习写一个类Person表示一个人,用name和age表示其姓名和年龄,并提供以下三个构造函数: Person(); //默认名字为 noname,年龄为0 Person(String name); //年龄默认为0 Person(String name, int age); 在main函数中使用以上三种构造方式创建3个Person对象,然后输出。
  • 79. (本页无文本内容)
  • 80. Part II 继承性
  • 81. 类的继承和实现一个类可以被继承,继承有什么好处呢? 当一个类被继承的时候,它所有说明为protected和public的成员(变量和函数)都可以传递到它的下一代,这就是好处。 例如图形界面的程序,一般都需要继承JFrame,因为JFrame这个类帮你做了很多事情:绘制一个界面,做一个标题栏,可以缩放……要是你自己从头开始做一个类似功能的类,那难度可就太可怕了……
  • 82. 给个例子看看继承的好处import javax.swing.*; class MyGUI extends JFrame { //注意这个MyGUI类,除了继承,啥都没干,可是人家就是有界面 } public class Test { public static void main(String args[]) { MyGUI frame = new MyGUI(); frame.setTitle("早上好!"); frame.setSize(500, 400); frame.setVisible(true); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } }
  • 83. 继承性 创建子类格式: class SubClass extends SuperClass {…} 这里SubClass就可以继承所有SuperClass的public和protected的成员,当然,如果SubClass有同名的成员,那么SuperClass的将被屏蔽。例如: class A { public int a; protected int b; } class B extends A { int a; //此a将屏蔽父类的a void f() { b++; } //这里的b实际上是从A继承来的 }
  • 84. 又一个继承的例子class A{ protected int x; public void f() { x--; } } class B extends A{ B() { x = 100; //这里直接使用的x,其实来自父类A f(); //f函数其实也是来自A,但现在可以直接使用 } }
  • 85. super && thisJava中通过super来实现对父类成员的访问,super用来引用当前对象的父类。super 的使用有三种情况: 1)访问父类被隐藏的成员变量,如:     super.variable; 2)调用父类中被重写的方法,如:     super.Method([paramlist]); 3)调用父类的构造函数,如:     super([paramlist]); this用于调用同一个类的成员变量或者成员方法,用法和super类似,其格式为: this.variable; this.Method([paramlist]);
  • 86. 再来一个继承的例子class A{ protected int x; public void f() { x--; } } class B extends A{ int x; void f() { this.x++; //这里用的是自己的x,也可以直接写x++ super.x = 100; //这里用的是父类继承来的x super.f(); //f函数其实也是来自父类 } }
  • 87. 总结一下继承性public:包内及包外的任何类均可以继承或访问; protected:包内的任何类,及包外的那些继承了此类的子类才能访问; default:包内的任何类都可以访问它,而包外的任何类都不能访问它(包括包外继承了此类的子类) ; private:不能被继承,只能在类内部使用。
  • 88. Part III 多态性
  • 89. 多态性静态多态性 体现在方法的重载,参见方法重载 在编译阶段,具体调用哪个被重载的方法,编译器会根据参数的不同来静态确定调用相应的方法。 动态多态性 由于子类继承了父类所有的属性(私有的除外),所以子类对象可以作为父类对象使用。程序中凡是使用父类对象的地方,都可以用子类对象来代替。一个对象可以通过引用子类的实例来调用子类的方法。 重写方法的调用原则(匹配原则):对子类的一个实例,如果子类重写了父类的方法,则运行时系统调用子类的方法;否则调用父类的方法。
  • 90. Object类-1在Java中,当你随手写一个类的时候,如果没有extends一个超类,Java都会默认这个类extends Object(如图)。换言之,所有Java里面的类都不是孤立的,它们其实有一个共同的祖宗,那就是Object。 Object有三个很常见的成员函数,这些函数一般会在子类中被覆盖: equals,hashCode,toString
  • 91. Object类-2equals函数 此函数默认是比较两个变量是否引用到同一个实例(相当于==,也就是比较指向的内存地址是否相等),如果要实现别的判等规则(例如内容是否相等),子类就需要覆盖这个函数。 hashCode函数 此函数用来指定一个整数,作为一个类的哈希码,以便能够在内存中对这个类进行快速定位。由于涉及到数据结构的知识,这里不做展开。
  • 92. Object类-3toString函数 这个函数用来返回一个对该类进行描述的字符串,由于这个函数会被System.out.print和System.out.println自动调用,所以经常被子类覆盖。建议每个类都应该实现这个函数。 例如假设Loan这个类实现了toString函数,那么可以直接使用print将Loan的实例输出: Loan loan = new Loan(); System.out.print(loan);
  • 93. 多态性的例子 public class Test { public static void main(String[] args) { m(new GraduateStudent()); m(new Student()); m(new Person()); m(new Object()); } public static void m(Object x) { System.out.println(x.toString()); } } class GraduateStudent extends Student { } class Student extends Person { public String toString() { return "Student"; } } class Person extends Object { public String toString() { return "Person"; } }这个程序的输出是: Student Student Person java.lang.Object@61de33
  • 94. 关于动态绑定的解释假设obj是一个类Obj的实例,当程序运行过程中出现这个语句时:obj.f() Java总是优先调用类Obj自身的成员函数f(); 如果Obj没有这个f(),那么就看看Obj的上一级超类有没有,有则调用之; 如果Obj的上一级超类也没有f(),那就继续往上找; 有没有可能上溯到老祖宗Object了还没有这个f()?绝对不会,因为那样就是这句代码写错了,会编译出错的,不能运行的……
  • 95. 类型转换和instanceof-1上一个例子有这样的代码: m(new Student()); 这个相当于: Object o = new Student(); //隐式类型转换 m(o); 这个例子告诉我们:如果需要,子类可以自动被转成超类。但是反之是不行的,例如: Student b = o; //错误 Student b = (Student)o; //显式类型转换 这个例子告诉我们:无论如何,超类不能被自动转成子类,除非强行转换。
  • 96. 类型转换和instanceof-2下面语句都是合法的: Object o = new GraduateStudent(); Object o = new Student(); Object o = new Person(); 很明显的是,虽然o可以指向Student,Person和GraduateStudent的任何一种,但是在程序运行的时候,如何得知o具体指向什么对象呢? instanceof是一个运算符,用来确定一个引用的实际指向的类型。下面这个表达式取值为boolean o instanceof Student 如果上式为true,则o必定指向一个Student对象,于是可以把o强行转成Student,从而调用Student的函数
  • 97. THE END