Java学习第九天~第十天——类与对象(面向对象初级):
24.10.31学到(218/910)。
24.11.01学到(262/910)。
我们先来看一段代码:
public class Object01{
public static void main(String[] args){
// 单独用变量解决问题: => 不利于数据的管理,因为我们把猫的信息拆解了。
String cat1Name = "小白";
int cat1age = 3;
String cat1Color = "白色";
String cat2Name = "小花";
int cat2age = 100;
String cat2Color = "花色";
// 数组 => (1)数据类型体现不出来;(2)只能通过下标获取信息,造成变量名和内容不对应;
// (3)不能体现猫的行为
String[] cat1 = {"小白","3","白色"};
String[] cat2 = {"小花","100","花色"};
}
}
现有的技术存在很多的缺陷:
- 不利于数据的管理;
- 效率低。
所以才需要类与对象(OOP)。
类与对象概述:
一个程序可以看作是一个世界,其内部含有很多事物(对象——对象有属性和行为)。
- 类——把一类事物(如猫,狗)的特征(属性)和行为提取出来;是我们自定义的数据类型。
- 对象——就是一个实例(若类是猫,实例就是一个实际存在的猫,如上文的小白)。
再解释的清楚一点就是,人类就是一个类,而你就是一个对象。
从类到对象有几个说法:
- 创建一个对象;
- 实例化一个对象;
- 把类实例化。
public class Object01{
public static void main(String[] args){
// 单独用变量解决问题: => 不利于数据的管理,因为我们把猫的信息拆解了。
String cat1Name = "小白";
int cat1age = 3;
String cat1Color = "白色";
String cat2Name = "小花";
int cat2age = 100;
String cat2Color = "花色";
// 数组 => (1)数据类型体现不出来;(2)只能通过下标获取信息,造成变量名和内容不对应;
// (3)不能体现猫的行为
String[] cat_1 = {"小白","3","白色"};
String[] cat_2 = {"小花","100","花色"};
// 使用oop面向对象的方法解决:
// 实例化一只猫(创建一个猫的对象):
// 1、new Cat()表示创建一只猫;
// 2、把创建的猫(对象)赋值给cat1;
// 3、cat1就是一只猫啦。
Cat cat1 = new Cat();
cat1.name = "小白";
cat1.age = 3;
cat1.color = "白色";
// 创建第二只猫:
Cat cat2 = new Cat();
cat2.name = "小花";
cat2.age = 100;
cat2.color = "花色";
// 怎么访问对象的属性呢?
System.out.println("第一只猫的信息:" + cat1.name + cat1.age + cat1.color);
}
}
// 使用面向对象的方式来解决问题:
//
// 定义一个猫类-》自定义的数据类型:
//
class Cat{
// 属性:
// 名字、年龄、花色
String name;
int age;
String color;
// 行为:
}
类和对象的区别:
- 类是抽象的,概念的,代表一类事物;
- 对象是具体的,实际的,代表一个具体事物;
- 类是对象的模板,对象是类的一个个体,对应一个实例。
属性/成员变量:
从概念和叫法上:成员变量 = 属性 = field(字段)
属性是类的一个组成部分,一般是基本数据类型,也可以是引用类型(数组,字符串)
注意事项和细节:
- 属性的定义语法和变量一样。
访问修饰符 数据类型 属性名;
- 属性的定义类型可以为任意类型,包含基本数据类型和引用类型;
- 属性如果不赋值,有默认值,规则和数组一致。
访问修饰符的基本介绍(再中级OOP中详细讲解):控制属性的访问范围
- public;
- protected;
- private;
- 默认。
创建对象与访问属性:
如何创建对象:
- 先声明再创建:
- Cat cat;
- cat = new Cat();
- cat只是对象名,new Cat()才是真正的对象(数据空间)
- 直接创建:
- Cat cat = new Cat();
如何访问属性:
对象名.属性名;
对象分配机制:
Person p1 = new Person();
Person p2 = p1; // 将p2指向p1,指向的对象是一样的。
这是引用传递,不是值传递。(除了基本数据类型,都是引用传递)。
Java内存的结构分析:
- 栈:一般存放基本数据类型;(局部变量)
- 堆:存放对象;(Cat cat,数组等)
- 方法区:常量池(常量,字符串),类加载信息。
Java创建对象的流程简单分析:
- 先加载类信息(属性和方法信息,同一类只会加载一次);
- 在堆中分配空间,进行默认初始化,把地址赋值给对象名;
- 进行指定的初始化
成员方法:
在正常的情况下,需要定义成员方法(方法)。为什么呢?我们上面定义了一个Cat类,猫除了有那些属性外,还能跑,叫,跳等,这用属性没有办法很好地表示,所以我们需要成员方法。
import java.util.Scanner;
public class Method01{
public static void main(String[] args){
// 方法使用:
// 1.若不被调用,是没有输出的;
// 2.先创建一个对象,然后调用方法
Person p1 = new Person();
Scanner myScanner = new Scanner(System.in);
System.out.print("n=");
int n = myScanner.nextInt();
p1.name = "江弦";
p1.age = 21;
p1.speak();
p1.cal01();
p1.cal02(n);
System.out.println(p1.getSum(1,1));
}
}
class Person{
String name;
int age;
// method
// 1.public 表示方法是公开的;
// 2.void 表示方法没有返回值。
// 3.spark() 是方法名,()为形参列表。
// 4.{} 方法体,可以写我们要执行的代码。
public void speak(){
System.out.println("我是一个好人。");
}
public void cal01(){
int res = 0;
for(int i = 1; i <= 1000; i++){
res += i;
}
System.out.println("1+...+1000=" + res);
}
// (int n)表示形参列表,表示当前有一个int类型的形参n,可以接受用户输入。
public void cal02(int n){
int res = 0;
for(int i = 1; i <= n; i++){
res += i;
}
System.out.println("1+...+" + n + "=" + res);
}
// 1.int表示方法执行后,返回一个int类型;
// 2.getSum为方法名;
// 3.(int num1, int num2)为形参列表,有两个int类型的形参用于接受用户输入。
public int getSum(int num1, int num2){
int res = num1 + num2;
return res;
}
}
方法调用:
- 当程序执行到方法时,就会开辟一个独立的空间(栈空间);
- 当方法执行完毕,或者执行到 return 语句时,就会返回;
- 返回到调用方法的地方。
- 返回后,继续执行方法后面的代码。
- 当main方法(栈)执行完毕,整个程序退出。
成员方法的意义:
便于我们减少重复代码的编写。我们可以把需要反复调用的代码,写成一个成员方法,当我们需要再次使用的时候就调用该方法即可。
好处:
- 提高代码的复用性;
- 可以将实现的细节封装起来,然后供其它用户来调用即可。
成员方法的定义:
语法:
访问修饰符 返回数据类型 方法名(参数列表){
语句;
return 返回值;
}
- 参数列表:表示成员方法的输入;
- 数据类型:表示成员方法输出类型,void表示没有返回值;
- 方法主体:表示为了实现某一功能代码块;
- return 语句不是必须的。
成员方法的细节:
- 一个方法最多有一个返回值(这和C++很像,和Python不像),若有多个值要返回,将其放入数组中返回;
- 返回类型可以为任意的类型,包含基本类型或引用类型;
- 若方法要求有返回数据类型,那么方法体的最后执行语句必须时return 值语句,而且要求返回值类型必须和return的值类型一致或兼容;
- 若为void类型,那么方法体可以没有return语句,或者只写return后面不跟值
- 遵循驼峰命名法(第一个单词不大写,后面的单词首字母大写)。
- 不写访问修饰符就是默认访问。
形参列表的细节:
- 一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开;
- 参数类型时可以任意的,包含基本数据类型和引用类型;
- 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数;
- 方法定义时的参数称为形参;方法调用时的参数称为实参;实参和形参的类型要一致或兼容、个数、顺序必须一致。
方法体细节:
里面写完成功能的具体的语句,可以为输入、输出、变量、运算、分支、循环、方法调用,但是里面不能够再定义方法。
方法调用细节说明:
- 同一类中的方法调用:直接调用即可——不用加对象名;
- 跨类中的方法A类调用B类方法:需要通过对象名调用
- 特别说明:跨类的调用和方法的访问修饰符相关。(暂时只提一嘴)。
练习:
import java.util.Scanner;
public class MethodExercise{
public static void main(String[] args){
System.out.println("请输入一个整数:");
Scanner myScanner = new Scanner(System.in);
AA a = new AA();
int n = myScanner.nextInt();
boolean res = isOdd(n);
if(res){
System.out.println(n + "是奇数。");
}else{
System.out.println(n + "是偶数。");
}
a.print(10,10,'*');
}
// 需要注意,由于java中,非静态方法不能直接被静态方法调用,所以需要再AA前用static修饰
public static boolean isOdd(int n){
boolean res = false;
if(n % 2 != 0){res = true;}
return res;
}
}
class AA{
public void print(int row, int col, char c){
for(int i = 0; i < row; i++){
for(int j = 0; j < col; j++){
System.out.print(c);
}
System.out.println();
}
}
}
类的完整定义(暂时):
class 类名{
属性;
构造方法;
成员方法;
}
传参机制:
基本数据类型的传参机制:
基本数据类型,传递的是值(值传递/值拷贝),形参的任何改变不影响实参。
public class MethodParameter{
public static void main(String[] args){
AA a = new AA();
int num1 = 10;
int num2 = 20;
a.swap(num1, num2);
System.out.println("num1=" + num1 + " num2=" + num2);
}
}
class AA{
public void swap(int a, int b){
// 仅仅操作的是swap中的a和b,对main中的num1和num2处于两个独立的空间中
// 且是值传递,实际上互不影响。
int tmp = a;
a = b;
b = tmp;
System.out.println("a=" + a + " b=" + b);
}
}
引用数据类型的传参机制:
引用类型传递的是地址(传递的是值,但是值是地址),可以通过形参影响实参。
public class MethodParameter02{
public static void main(String[] args){
B b = new B();
int[] arr = {1,2,3,4,5};
b.change(arr);
for(int i = 0; i < arr.length; i++){
System.out.print(arr[i] + "\t");
}
}
}
class B{
// B类中编写一个方法test100;
// 可以接收一个数组,在方法中修改该数组,看看原来的数组是否变化
// 引用类型传递的是地址!所以新空间里的arrb也指向arr指向的空间,所以会同步修改。
public void change(int[] arrb){
arrb[0] = 100000;
}
}
public class CopyPerson{
public static void main(String[] args){
Person p1 = new Person();
p1.name = "江弦";
p1.age = 21;
Person p2 = p1.copy();
p1.age = 1000;
System.out.println("p1.age:" + p1.age);
System.out.println("p2.age:" + p2.age);
}
}
class Person{
String name;
int age;
public Person copy(){
Person newp = new Person();
newp.name = name;
newp.age = age;
return newp;
}
}
方法递归调用:
基本介绍:
递归就是自己调用自己,每次调用时传入不同的变量,递归有助于编程者解决复杂问题(每次缩减问题的规模),同时可以让代码变得简洁。
public class Recursion01{
public static void main(String[] args){
T t = new T();
int n = 10;
t.test(n);
}
}
class T{
public void test(int n){
if(n>2){
test(n-1);
}
System.out.println("n=" + n);
}
}
重要规则:
- 执行一个方法时,就创建一个新的受保护的独立空间(栈空间);
- 方法的局部变量是独立的,不会相互影响;
- 若方法中使用的时引用类型变量,就会共享该引用类型的数据;
- 递归必须向退出递归的条件逼近,否则就是无限递归(问题规模要缩小);
- 当一个方法执行完毕,或者遇到return,就会返回,遵循谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也将执行完毕
练习:
public class RecursionExercise01{
public static void main(String[] args){
T t = new T();
System.out.println("第六个数的fibonacci数为:" + t.fibonacci(6));
System.out.println("第一天有多少个桃子:" + t.peach(1));
}
}
class T{
public int fibonacci(int n){
if(n == 1 || n == 2){
return 1;
}else{
return fibonacci(n-1) + fibonacci(n-2);
}
}
// 猴子吃桃:
public int peach(int day){
if(day == 10){
return 1;
}else if(day >= 1 && day <= 9){
return (peach(day+1)+1) * 2;
}else{
System.out.println("不在范围内。");
return -1;
}
}
}
走迷宫:
对findWay的一些解释:
- 只要终点位置不为2就继续递归(若起点位置为3会退出函数);
- 每次将当前位置假设为通路(2),向四个方向探测,只要有一个方向的值为0,就视为假设正确,否则将当前位置设置为3,返回到上一个位置(回溯)。
- 不同的寻路策略,对路径是有影响的。
public class MiGong{
public static void main(String[] args){
// 思路:
// 1、先创建迷宫,用二维数组表示 int[][] map = new int[8][7];
// 2、规定 map 数组的元素值:0表示可走,1表示障碍物。
int[][] map = new int[8][7];
// 3、将最上面的一行和最下面的一行全部设置为1;
for(int i = 0; i < 7 ; i++){
map[0][i] = 1;
map[7][i] = 1;
}
// 4、将最左边一行和最右边一行全部设置为1;
for(int i = 0; i < 8; i++){
map[i][0] = 1;
map[i][6] = 1;
}
// 5、单独设置障碍物:
map[3][1] = 1;
map[3][2] = 1;
map[2][2] = 1;
// 输出当前的地图:
for(int i = 0; i < 8;i++){
for(int j = 0; j < 7; j++){
System.out.print(map[i][j] + " ");
}
System.out.println();
}
// 使用findWay找路;
T t1 = new T();
t1.findWay(map, 1, 1);
System.out.println("找到的路径如下:");
for(int i = 0; i < 8;i++){
for(int j = 0; j < 7; j++){
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
}
class T{
// 使用递归回溯的思想来解决:
//
// 1、 findWay方法就是用来专门找出路径的;
// 2、 若找到,返回true,否则为false;
// 3、 map为二维数组,即表示迷宫;
// 4、 i,j就是老鼠的位置,初始化的位置为(1,1);
// 5、 因为我们是递归找路,所以需要预先规定各个值的意义:
// 0 表示可以走; 1 表示障碍物; 2 表示可以走; 3 表示走过,但是走不通。
// 6、什么时候可以退出呢?终点位置为2,说明走通了,即map[6][5]为2,就可以结束了。否则就继续找。
// 7、找路的策略对路径是会有影响的:
// 我们使用先找下,再走右面,右面走不通走上面,上面走不通找左面;
// 下-》右-》上-》左
public boolean findWay(int[][] map, int i, int j){
if(map[6][5] == 2){
return true;
}else{
if(map[i][j] == 0){// 当前位置为0说明可以走
// 假定可以走通:
map[i][j] = 2;
// 使用找路策略,来确定该位置是否可以走通;
if(findWay(map, i+1, j)){
return true;
}else if(findWay(map, i, j+1)){
return true;
}else if(findWay(map, i-1, j)){
return true;
}else if(findWay(map, i, j-1)){
return true;
}else{
map[i][j] = 3;
return false;
}
}else{// 不等于0,只可能为1,2,3,说明已经测试过了
return false;
}
}
}
}
扩展:如何找到最短路径?
- 穷举法;
- 使用图(在数据结构和算法中讲解。)
汉诺塔:
核心思想就是简化。
public class HanoiTower{
public static void main(String[] args){
T t = new T();
t.move(5, 'A', 'B', 'C');
}
}
class T{
// a,b,c分别表示A柱,B柱,C柱
public void move(int num, char a, char b, char c){
if(num == 1){
System.out.println(a + "->" + c);
}else{
// 将上面的盘看作一个整体,挪到中间盘,c柱现在为过度。
move(num - 1, a, c, b);
// 把下面的盘挪动到c
System.out.println(a + "->" + c);
// 再把b柱上的所有盘移动到c盘,借助a:
move(num-1, b, a, c);
}
}
}
方法重载(OverLoad):
基本介绍:
java中允许一个类中,多个同名方法的存在,但要求i 形参列表不一致。
好处:
- 减轻了起名的麻烦;
- 减轻了记名的麻烦。
注意事项和使用细节:
- 方法名:必须相同;
- 形参列表:必须不同(形参类型或个数或顺序,至少有一样不同,参数名无要求);
- 返回类型:无要求(若方法名相同,形参列表完全相同,仅有返回类型不同,这不是重载,是错误的)。
可变参数:
基本概念:
java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。
基本语法:
访问修饰符 返回类型 方法名(数据类型... 形参名){
}
注意事项和使用细节:
- 可变参数的实参可以为 0 个或任意多个;
- 可变参数的实参为数组;
- 可变参数的本质是数组;
- 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数再最后;
- 一个形参列表中只能出现一个可变参数。
作用域:
再面向对象中,变量作用域是非常重要的知识点。
- 再java编程中,主要的变量就是属性(成员变量)和局部变量;
- 局部变量:一般指的是成员方法中定义的变量,也可以是代码块中定义的变量;
- 作用域的分类:
- ==全局变量:==就是属性,作用域是整个类体;
- ==局部变量:==就是除了属性之外的其它变量,作用域为所在的代码块。
- ==全局变量(属性)==可以不用赋值,直接使用,因为其有默认值(同数组);局部变量必须赋值后,才能使用,因为没有默认值。
注意事项和使用细节:
- 属性和局部变量可以重名,访问遵循就近原则;
- 在同一个作用域(代码块)中,不允许两个局部变量重名;
- 属性的生命周期长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量的生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的销毁而销毁(即一次方法调用)。
- 作用域范围不同:
- 全局变量/属性——只能被本类使用,或其它类使用(通过对象调用,即引用传递);
- 局部变量——只能在本类中对应的方法中使用。
- 修饰符不同:
- 全局变量/属性——可以加修饰符;
- 局部变量——不能加修饰符。
构造器方法/构造器:
便于我们在创建完对象后,就指定对象的年龄和姓名。类似于C++中的构造函数。
基本语法:
[修饰符] 方法名(形参列表){
方法体;
}
- 构造器的修饰符可以默认,也可以是public、protected、private;
- 构造器没有返回值;
- 方法名 必须和 类名一致;
- 参数列表 和 成员方法类似;
- 构造器的调用由系统完成。
- 在创建对象时,系统会自动的调用该类的构造器完成对象的初始化。
使用细节和注意事项:
- 一个类可以定义多个不同的构造器,即构造器重载;
- 构造器名和类名要相同;
- 构造器没有返回值;
- 构造器是完成对象的初始化,而不是创建对象;
- 在创建对象时,系统自动的调用该类的构造方法;
- 若程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(默认构造器);
- 一旦定义了自己的构造器,默认的构造器就被覆盖了,不能再使用默认的无参构造器了。
对象创建的流程分析:
- 加载类信息,只会加载一次;
- 在堆中分配空间;
- 完成对象初始化
- 默认初始化,即在类中的属性定义完便有默认值;
- 显示初始化,即在类中对属性赋值;
- 构造器的初始化
- 把对象在堆中的地址,返回给对象名(在栈中)。
this关键字:
使用细节和注意事项:
- this关键字可以用来访问本类的属性、方法、构造器;
- this用于区分当前类的属性和局部变量。
this.age = age;
this后面的是属性,没有this的age是局部变量; - 访问成员方法的语法:
this.方法名(参数列表);
- 访问构造器语法:
this(参数列表);
需要注意的是,只能在构造器中使用,即只能在构造器中访问另外一个构造器,必须放在第一条语句; - this不能再类定义的外部定义。
Homework:
题一:
public class Homework01{
public static void main(String[] args){
T t = new T();
double[] arr = {1,2,3,4,5};
double max = t.max(arr);
System.out.println("max:" + max);
}
}
class T{
public double max(double[] arr){
double res = 0.0;
for(int i = 0; i < arr.length; i++){
if(res <= arr[i]){
res = arr[i];
}
}
return res;
}
}
题二:
public class Homework02{
public static void main(String[] args){
A02 a = new A02();
String s1 = "Hello";
String[] ss = {"Hello","world"};
System.out.println(a.find(s1, ss));
}
}
class A02{
public int find(String s1, String[] ss){
for(int i = 0; i < ss.length; i++){
if(s1.equals(ss[i])){
return i;
}
}
return -1;
}
}
题三:
public class Homework03{
public static void main(String[] args){
Book b = new Book();
int price1 = 190, price2 = 110;
System.out.println(b.updatePrice(price1));
System.out.println(b.updatePrice(price2));
}
}
class Book{
public int updatePrice(int price){
if(price > 150){
price = 150;
}else if(price > 100){
price = 100;
}
return price;
}
}
题四:
public class Homework04{
public static void main(String[] args){
int arr[] = {1,2,3,4,5};
A03 a = new A03();
int[] newarr = a.copyArr(arr);
for(int i = 0; i < newarr.length; i++){
System.out.print(newarr[i] + "\t");
}
}
}
class A03{
public int[] copyArr(int[] arr){
int[] newarr = new int[arr.length];
for(int i = 0; i < arr.length; i++){
newarr[i] = arr[i];
}
return newarr;
}
}
题五:
public class Homework05{
public static void main(String[] args){
Circle c = new Circle();
c.r = 3.0;
c.showPerimeter();
c.showArea();
}
}
class Circle{
double r;
double pi = 3.14;
public void showPerimeter(){
System.out.println("周长为:" + 2*pi*r);
}
public void showArea(){
System.out.println("面积为:" + pi*r*r);
}
}
题六:
public class Homework06{
public static void main(String[] args){
Cale c1 = new Cale();
Cale c2 = new Cale();
c1.op1 = 10.0;
c1.op2 = 2.0;
System.out.println("add:" + c1.add());
System.out.println("sub:" + c1.sub());
System.out.println("mul:" + c1.mul());
System.out.println("div:" + c1.div());
c2.op1 = 9;
c2.op2 = 0;
System.out.println("add:" + c2.add());
System.out.println("sub:" + c2.sub());
System.out.println("mul:" + c2.mul());
System.out.println("div:" + c2.div());
}
}
class Cale{
double op1;
double op2;
public double add(){
return op1 + op2;
}
public double sub(){
return op1 - op2;
}
public double mul(){
return op1 * op2;
}
public double div(){
if(op2 == 0){
System.out.println("非法参数!");
return -1;
}
return op1 / op2;
}
}
题七:
public class Homework07{
public static void main(String[] args){
Dog d = new Dog("小白","white",5);
d.show();
}
}
class Dog{
String name;
String color;
int age;
public Dog(String name, String color, int age){
this.name = name;
this.color = color;
this.age = age;
}
public void show(){
System.out.println("name:" + name + " color:" + color + " age:" + age);
}
}
题十四:
import java.util.Random;
import java.util.Scanner;
public class Homework14{
public static void main(String[] args){
Tom t = new Tom();
int num = t.fingerGuessingGame();
System.out.println(num);
}
}
class Tom{
public int fingerGuessingGame(){
Scanner myScanner = new Scanner(System.in);
Random rand = new Random();
int count = 0;
while(true){
System.out.println("开始猜拳游戏,0代表石头,1代表剪刀,2代表布:");
int player = myScanner.nextInt();
int n = rand.nextInt(3);
switch(player){
case 0:
if(n == 0) {
System.out.println("平局。");
}else if(n == 1) {
System.out.println("你获胜了!");
count++;
}else if(n == 2){
System.out.println("你输了。");
}
break;
case 1:
if(n == 1) {
System.out.println("平局。");
}else if(n == 2) {
System.out.println("你获胜了!");
count++;
}else if(n == 0){
System.out.println("你输了。");
}
break;
case 2:
if(n == 2) {
System.out.println("平局。");
}else if(n == 0) {
System.out.println("你获胜了!");
count++;
}else if(n == 1){
System.out.println("你输了。");
}
break;
default:
System.out.println("你的输入非法!");
}
System.out.println("是否要继续?");
char c = myScanner.next().charAt(0);
if(c == 'n'){
break;
}
}
return count;
}
}