网站宽度960,wordpress百度采集采集器,广东做陶瓷的网站,网站建设开发免费咨询视频#xff1a;《零基础学习Android开发》第五课 类与面向对象编程1-1类的定义、成员变量、构造方法、成员方法一、从数据与逻辑相互关系审视代码通过前面的课程#xff0c;我们不断接触Java语言的知识#xff0c;不断增加自己的语言表达能力。到现在为止#xff0c;我已经…视频《零基础学习Android开发》第五课 类与面向对象编程1-1类的定义、成员变量、构造方法、成员方法一、从数据与逻辑相互关系审视代码 通过前面的课程我们不断接触Java语言的知识不断增加自己的语言表达能力。到现在为止我已经可以高兴地向大家宣布你的语言能力其实已经可以让完成一个完整的程序了可是有的同学会问那不对啊我现在还知道怎么操作图形界面不知道文件怎么读写不知道数据库如何访问不知道网络如何连接……这个不用急因为这些并不属于语言的范围而是关于如何使用平台资源的事。并且这些使用资源的知识你们只是不知道该如何找找到了学起来其实是非常简单的。再说这些内容我们后面的课也会讲的。但是有一个问题。那就是我们现在所有的代码都是写在一个文件里的也就是在MainActivity.java里。如果要做的事多了复杂了肯定要把代码分到不同文件里这样才使代码文件有一个比较好的组织结构今后维护也方便些。那该依据什么逻辑原则来划分文件呢 我们重新看一下现在已经写了的代码就会发现代码就是由数据结构与算法组成的。一部分代码就是讲这是什么数这些数是怎么组织的。另一部分代码就是讲数该如何算。再看一下就会发现有些数据结构与一些逻辑代码之间的联系很紧密而与另一些逻辑代码的关系则不大。比如说 int[] pokers new int[52]; // 一副牌组成的数组 for(int i 0; i 52; i){ pokers[i] i 1; // 初始化牌数组序号从0~51点数从1到52 } pokers shuffle(pokers); // 调用洗牌方法 可以看到pokers这个数组与初始化数组、洗牌算法的关系比较紧密在后面的代码中只在发牌的时候才引用了一次。而表示每个玩家手里的牌的数组则是与叫牌、拿牌、求和、亮牌等逻辑代码相关度较高而且因为有两个玩家所以类似的逻辑代码有两处。这样就给了我们一个印象那就是在代码中一些数据和一些逻辑的关系是紧密的是应该在一起的。我们通过前面的学习知道了数据与数据是可以组合在一起成为数据结构的比如数组。那有没有可能把数据与处理它的逻辑代码组合在一起也做成一种结构呢有的。这就是“类”。在介绍类的基本概念之前我们可以试着想想按刚才所说的数据和逻辑的组合要构造类的话应该包括哪些内容。首先说第一组。这可以概括成一副扑克牌它的内容包括一个代表牌的长度为52的整型数组(数据)将从1~52的数字依次放入数组的初始化代码(逻辑)将数组中的值打乱的洗牌代码(逻辑)后面的发牌其实就是从这个数组中按序取出一个元素来也是对这个牌的数组数据的处理代码因此应该包括发牌的代码(逻辑) 根据以上分析我先把扑克牌的“类”写出来。在IDE左侧的项目工具窗口中右击“appjavacom.example.helloworld”在弹出菜单中选择“NewJava Class”在对话框的Name一栏中填入类名Pokers这样就新建了一个Pokers.java文件在该类文件中敲如下代码package com.example.helloworld;public class Pokers { private int[] pokers; // 成员变量一副牌的数组 private int index; // 成员变量表示当前发牌的序号 // 构造方法 public Pokers(){ index 0; this.pokers new int[52]; // 创建成员变量数组 for(int i 0; i 52; i){ pokers[i] i 1; // 初始化牌数组序号从0~51点数从1到52 } pokers shuffle(pokers); // 洗牌 } // 成员方法。洗牌 private int[] shuffle(int[] nums) { java.util.Random rnd new java.util.Random(); for (int i nums.length - 1; i 0; i--) { int j rnd.nextInt(i 1); int temp nums[i]; nums[i] nums[j]; nums[j] temp; } return nums; } // 成员方法。发一张牌即给调用者返回一张牌的数值 public int getNextPoker(){ int poker pokers[index]; index; // 发完一张牌后序号加1 return poker; }} 同学们可以看到在这个类Pokers里有数据pokers和index还有逻辑代码即对这些数据进行处理的方法Pokers、shuffle和getNextPoker分别完成初始化、洗牌与发牌的功能。这就是我们构建的第一个类。二、类1. 类的基本组成 从上面的Pokers类可以看到组成一个类的基本部分是4个分别是类声明、成员变量、构造方法与成员方法。 在Pokers类的声明public class Pokers中public是访问修改符。类的访问修饰符共有4种我们先不讲有何区别我们都写为public就行。class是关键词表明这是一个类。Pokers是类名类名的第一个字母一般大写如果是由多个单词组合成的则每个单词第一个字母都大写。 pokers和index都是成员变量英文是field(也有的翻译成“域”或“字段”)成员变量在整个类的范围内都能进行访问因此它是不同于方法内部声明的变量(那个叫部局部变量)的也可以说成员变量的作用域是整个类。在它们之前的private是访问修饰符这表明它是私有的意思是只能在本类的内部才能访问而不能被类之外的调用者访问。如果将修饰符改为public就可以让类外的调用者访问但是最好不要使用这种方式。因为这种方式使得类内部的数据可以被随意改变这很容易失控。类里的数据应该通过类的成员方法来访问这样取值的时候可以根据需要对数值经过某种转换再给出比如我们的程序里数组里存的是牌的序号可以将其转为实际点数再给出。改变数值的时候也可以加入某些限制免得超出范围。比如我们的数组中存入的值就应该在1~52之间。 与类名相同的方法是构造方法英文是constructor在类的变量被创建(或者叫“初始化”。大家记得吧类是引用类型因此创建类的对象要用new)时首先被调用。构造方法没有返回值参数列表可以有也可以没有。构造方法可以是多个。如果没有给出构造方法编译时会自动生成一个不带参数列表的默认构造方法。构造方法访问修饰符有public、protected、private3种可以从中任选一种我们现在暂时只用public也就是公共的可以让外部调用的。private的成员变量没有默认值必须被初始化在构造方法里对成员变量进行初始化是比较合适的当然也可以进行其它操作。 方法在上一课已经讲了在类的内部就叫成员方法。这里主要讲一下它的访问修饰符这里在shuffle前面是private意思是私有的只能本类中被调用而getNextPoker之前是public意思是公共的可以在其它类中被调用。 以上类的4个组成部分除了类声明是必须的其它都是可选的。可以只要成员变量比如我们声明一个学生类public class Student{ public int index;// 学号 public String name;// 姓名} 这样就是一个纯数据结构其中的成员变量必须是public的不然外部访问不到就没有意义。同学们可以看到这种数据结构就可以把不同类型的数据组织在一起。上一课中方法输出时也如果把sum与text两个变量组织成一个类就可以输出两种数据。 但是这种在成员变量之前加public的用法没有把类的优势发挥出来。如果只是想用类来组合一个数据结构最好也是将成员变量设为private而用成员方法来对成员变量进行存取。上面的Student可以改为如下代码public class Student{ private int index; // 学号私有的成员变量外部不可访问 private String name; // 姓名私有的成员变量外部不可访问 public Student(){ // 私有成员变量没有默认值必须初始化 this.index 0; this.name ; } public int getIndex() { // 获得学号公共的成员方法外部可访问 return this.index; } public void setIndex(int index) { // 设置学号公共的成员方法外部可访问 this.index index; } public String getName() { // 获得姓名公共的成员方法外部可访问 return this.name; } public void setName(String name) { // 设置姓名公共的成员方法外部可访问 this.name name; }} 在存与取的方法里都可以加上一些对数据进行限制或转换的代码这样可以保证数据的安全也可以增强数据的外部可读性。 这里可以看到访问成员变量时用的是“this.”的方式this表明的是本类的对象“.”是成员访问操作符。通过在index与name前面加上“this.”就表明是成员变量就可以与参数列表里的参数变量进行区别。我给大家举这个例子呢目的是让大家不要对类的概念产生陌生感你完全可以把类就看成一个数据结构但是这个数据结构里不仅放了数据还放了处理这些数据的方法。这样程序文件就有了很好的组织性。 也可以不要成员变量只要成员方法。比如public class ToolsClass{ public void doSth(){ } public int getNum(){ return 0; }} 内部只有方法的类一般作为工具类因为其成员方法的代码与成员变量无关可以在方法前上static静态的这就成为静态方法。上面的类可以改为public class ToolsClass{ public static void doSth(){ } public static int getNum(){ return 0; }} 这样的好处是调用时不需要先用new来创建类的对象而是可以直接通过类名来调用如ToolsClass.doSth()、ToolsClass.getNum()就可以。static不仅可以加到成员方法前面也可以加到成员变量前至于静态与非静态成员的区别我在后面再讲。2. 创建类的对象并访问其成员 因为类是引用类型因此创建类的变量(也称为对象Object、实例Instance)是用new创建出对象后就可以通过该对象加“.”来访问其公共成员方法或变量了。比如上面声明的Student类 Student s new Student(); s.setIndex(1); s.setName(Tom); int index s.getIndex(); String name s.getName();三、用类来重新组织代码 我们重新看一遍已经建立的Pokers类它是将对一副扑克牌相关的数据与逻辑都移入该类之中(术语叫“封装”英文为Encapsulation)。现在我们把与它相关的代码变换为使用Pokers的对象来进行。原来的代码为 int[] pokers new int[52]; // 一副牌组成的数组 for(int i 0; i 52; i){ pokers[i] i 1; // 初始化牌数组序号从0~51点数从1到52 } pokers shuffle(pokers); // 调用洗牌方法 调整后的代码为Pokers pokers new Pokers(); 因为我们把顺序点数放牌数组及洗牌的动作全放在构造函数里了因此当Pokers类的对象pokers被创建时我们就有了一副已经洗好的牌了。在调用者的角度屏蔽了很多细节同时如果发现与扑克牌相关的数据与逻辑有问题的话也能很快定位到Pokers类中不用去其它地方找Bug。这就是封装的好处。后面发牌的动作也用pokers.getNextPoker()一条语句就可以了。调用者不用考虑具体的实现细节只要知道要调用类对象中的哪个方法能得到自己想要的东西就行。按照这个思路程序的主要逻辑就可以变得非常的简洁。主要逻辑不用操心细节就需要知道把工作“分类”不同的工作分给不同的类再找这些类的对象要东西就行。是不是有点领导的感觉了事来了这个事是业务部的那个事是财务部那个事是人事部的通通分下去再找业务部安排的小李、财务部的小刘和人事的小张跟他们要东西这事就办妥了。 我们接着往下看代码。我们看一下原来的代码 int[] pokersA new int[4]; // 玩家手里的牌 int[] pokersB {7, 8, 0, 0}; // 电脑手里的牌 for(int i 0; i 4; i){ pokersA[i] pokers.getNextPoker();// 改为通过Pokers类对象获得下一张牌 // 获得当前玩家手中牌的总点数 String[] texts {}; int sum getSum(pokersA, texts); // 在玩家手中牌点数大于电脑时停止叫牌 if(sum 15){ break; } } // 显示玩家手里的牌及总数 String[] texts {你手中的牌分别是} ; int sumA getSum(pokersA, texts); texts[0] 总数是 sumA 。对家手里的牌是; // 显示电脑手里的牌及总数 int sumB getSum(pokersB, texts); 这里是否还有数据与逻辑相关似很强呢有的数组pokersA与发牌、结算的逻辑相关性强数组pokersB没有发牌逻辑是因为我们做了简化其实是应该有的它也有结算逻辑。pokersA与pokersB代表的是两个玩家手里的牌我们是否需要建立两个类呢答案是不需要因为虽然是两个玩家但是每个玩家数据的结构以及操作数据的逻辑是完全相同的唯一不同的是数据的值。而数据的值是在类的对象并创建之后再通过各种方法去设置的因此值的差异只是对象之间的差异而不是类的差异。就像int类型的两个变量它们的值不同但它们都是int型的。如果它们的值相同那也是同类型的不同变量。相同的同一类的不同对象它们的成员变量的值不同但都是同一类的。就算他们的成员变量值相同也是同一类的不同变量。这也好比人就是一个类人与人之间的很多数据的值是不一样的高矮胖瘦、年龄姓名、生活经历但是基本功能是相同的能跑能跳能吃能睡。只是因为其数据值的不同造成其功能运行时的差异。在这个游戏里两个玩家的功能(即行为逻辑)是相同的出现差异的原因是其成员变量的值不同造成的。因此它们是同类的不同对象。我们归纳一下玩家类的包括哪些内容一个代表要到的牌的数组(数据)接收牌的功能(逻辑)汇总手中牌点数的功能(逻辑)给出结算信息的功能(逻辑) 根据以上信息我们可以写出下面的“玩家类”public class Player { private int[] pokers; private int index; public Player(){ this.pokers new int[12]; // 最极端情况下拿到4个A4个24个3因此长度设为12即可 this.index 0; } // 要一张牌 public boolean wantPoker(int num){ if (this.index 12) { // 限制性代码避免数组序号出错 return false; } this.pokers[index] num; index; // 序号加1 return true; } // 获得当前的点数之和 public int getSum(){ int sum 0; for (int i 0; i this.pokers.length; i){ if (pokers[i] 0){ break; } sum (pokers[i] - 1) % 13 1; // 获得真正点数 } return sum; } // 获得牌局结算信息 public String getStatString(){ String txt ; for (int i 0; i this.pokers.length; i){ if (pokers[i] 0){ break; } switch ((pokers[i] - 1) / 13){ // 根据真正的花色的值进行选择 case 0:{ txt 黑桃; break; } case 1:{ txt 红桃; break; } case 2:{ txt 梅花; break; } case 3:{ txt 方片; break; } } txt ((pokers[i] - 1) % 13 1) ; // 获得真正点数的字符串 } return txt; }} 上面这个Player类就实现了我们对它的要求。但是看这段代码我们可以发现一个问题原来我们说所有与扑克牌实现的细节是被封装在Pokers类里面的但是在Player类的getSum与getStatString两个方法里却要知道关于牌的很多细节要知道真正的点数值与花色值如何求出还要知道如何将花色值翻译成字符表达这是与我们上面表达的原则是相违背的。因此这两个方法里的很多实现的细节应该被放到Pokers类里去。我们对Pokers类进行扩充加几个方法// 获得真正点数public static int getCount(int num){ return (num - 1) % 13 1;}//从牌的序号中得出花色值public static int getColor(int num){ return (num - 1) / 13; }// 从牌的序号中得出花色值的文字表达public static String getColorString(int num){ String txt ; switch (getColor(num)){ case 0:{ txt 黑桃; break; } case 1:{ txt 红桃; break; } case 2:{ txt 梅花; break; } case 3:{ txt 方片; break; } } return txt;} 这样关于扑克牌的实现细节就真正地全部封装在Pokers类里了。因为这3个方法并不是对Pokers的成员变量进行操作也就不需要成为对象方法(对象方法需要通过实例化的类的对象才能调用)我们在前面加上static修饰符使其成为静态方法这样调用时直接使用类名就可以调用了。再重构Player类为如下代码public class Player { private int[] pokers; private int index; public Player(){ this.pokers new int[12]; // 最极端情况下拿到4个A4个24个3因此长度设为12即可 this.index 0; } // 要一张牌 public boolean wantPoker(int num){ if (this.index 12) { // 限制性代码避免数组序号出错 return false; } this.pokers[index] num; index; // 序号加1 return true; } // 获得当前的点数之和 public int getSum(){ int sum 0; for (int i 0; i this.pokers.length; i){ if (pokers[i] 0){ // 为0时表示当前已经没牌了 break; } sum Pokers.getCount(pokers[i]); // 获得真正点数 } return sum; } // 获得牌局结算信息 public String getStatString(){ String txt ; for (int i 0; i this.pokers.length; i){ if (pokers[i] 0){ break; } txt Pokers.getColorString(pokers[i]) Pokers.getCount(pokers[i]) ; } return txt; }} 现在我们已经建立了管牌的Pokers类和玩牌的Player类那么一个牌局就可组起来了。让我们开干把主程序的调用代码改了 Pokers pokers new Pokers(); pokers.getNextPoker(); Player p1 new Player(); Player p2 new Player(); for(int i 0; i 4; i){ p1.wantPoker(pokers.getNextPoker()); p2.wantPoker(pokers.getNextPoker()); } String text player1 p1.getStatString() 总数 p1.getSum() player2 p2.getStatString() 总数 p2.getSum(); TextView txtResult (TextView)findViewById(R.id.txtResult); txtResult.setText(text); 运行以后效果如下 同学们可以看到变为使用类以后主程序的逻辑变得清晰简单了(因为我们还没有做是否要牌的决策方法就用每个玩家发4张牌来代替)每个具体的工作都有专门的类来管理与执行主逻辑只需要指挥(调用)对应的对象就能把整个任务完成好。这看起来是不是就很像我们真实社会运行的状态了呢我们可以看到我们的代码发生重大的变化这是因为我们对代码的组织方式发生了变化而这个新的方式就是“面向对象编程”。