10、Java类与对象
类就是自定义的数据类型,对象就是一个具体的实例
10.1 基本介绍
-
属性/成员变量/实例字段:是类的一个组成部分,一般是基本数据类型,也可以是引用类型(对象;数组)
定义:访问修饰符 属性类型 属性名;若不赋值,则有默认值,规则和数组一致
-
Java内存结构分析:
- 栈:一般存放基本数据类型(局部变量)
- 堆:存放对象,数组等
- 方法区:常量池(常量,比如字符串);类加载信息(属性信息,方法信息)
-
Java创建对象流程(简略):
- 在方法区先加载类信息(属性和方法信息,且只会加载一次)
- 在堆中分配空间,进行默认初始化
- 把地址赋给对象
- 进行指定初始化,覆盖原来的初始化值
- 注意:对象的地址在堆中。对象的引用(对象名)在栈中,指向堆中对应的对象地址
-
Java成员方法的定义:
访问修饰符 返回数据类型 方法名(形参列表…){方法体;return返回值;}
- 访问修饰符:规定访问权限
- 返回数据类型:表示成员方法输出,void没有返回值
- 方法名:表示成员方法的输入
- 方法体:实现该方法的具体实现
- return 不是必须的。如void时可以不用return,或者只写return
- 一个方法最多有一个返回值,返回类型可以是任何类型,包括引用类型(数组,对象)
- 形参列表可以有0个参数,也可以有多个参数;参数类型可以为任何类型,包括引用类型,调用方法时,要对应参数列表传入相同类型或兼容类型的参数
- 方法定义时的参数称为形式参数(形参);方法调用时传入的参数称为实际参数(实参)。实参和形参类型,个数,顺序必须一致。
- 方法里不能嵌套其他方法的定义
- 在两个方法在同一类中,一个方法可以直接调用另一个方法,不用创建对象
- 跨类调用方法,需要创建该类对象,再通过对象名调用该类方法;注意:跨类的方法调用和方法的访问修饰符相关;后续会细说
-
Java方法调用机制:
- 在栈中自动创建一个main栈,当程序执行到方法时,就会开辟一个独立的栈空间
- 当方法执行完毕,或者执行到return语句时,就会返回
- 返回到调用方法的地方
- 返回后,只需执行之后的语句
-
Java成员方法传参机制
- 基本数据类型的传参机制:基本数据类型传递的是值(值拷贝),形参的任何改变不会影响实参
- 引用数据类型的传参机制:引用数据类型传递的是地址(传递的也是值,但值是地址),可以通过形参影响实参
-
练习:编写一个方法copyPerson,可以复制一个Person对象信息,返回复制后的对象;要求两个对象相互独立,只是属性相同
Person person = new Person(); person.age = 10; person.name = "Jack"; Tool T= new Tool(); Person newperson = T.copyPerson(person); System.out.println(newperson.age); System.out.println(newperson.name); System.out.println(person.age); System.out.println(person.name); class Person{ int age; String name; } class Tool{ public Person copyPerson(Person p){ Person newperson =new Person(); newperson.age = p.age; newperson.name = p.name; return newperson; } }
-
递归练习1:给定整数n ,用递归的方式求出斐波那契1,1,2,3,5,8,13…第n个数为多少
public class Test_0009 { public static void main(String[] args) { Fibonacci f = new Fibonacci(); int num = f.fibonacci(8); System.out.println(num); } } class Fibonacci{ public int fibonacci(int n ){ if(n==1||n==2){ return 1; }else{ return fibonacci(n-1)+fibonacci(n-2); } } }
-
递归练习2: 迷宫问题
int[][] map = {{1,1,1,1,1,1,1}, {1,0,0,0,0,0,1}, {1,0,1,1,0,0,1}, {1,1,1,0,0,0,1}, {1,0,0,1,0,0,1}, {1,0,0,0,0,0,1}, {1,0,0,0,0,0,1}, {1,1,1,1,1,1,1}}; Maze m = new Maze(); m.findWay(map,1,1); for(int i = 0;i< map.length;i++){ for(int j = 0;j<map[i].length;j++){ System.out.print(map[i][j]+" "); } System.out.println(); } class Maze{ /** * 1. findWay:表示找出迷宫路径 * 2. 找到返回true * 3. i,j表示当前所在位置,初始化为1,1 * 4。 规定map数组的含义:0表示可以走,1:表示障碍物,2:表示可以走,3:表示可以走,但是走过且走不通 * 5. 当map[6][5]==2时,说明找到出口,打印此时map,2代表走过的路径 */ public Boolean findWay(int[][] map,int m,int n){ if(map[6][5]==2){ return Boolean.TRUE; } else{ if(map[m][n]==0){ map[m][n]=2; if(findWay(map,m+1,n)){ return true; } else if(findWay(map,m,n+1)){ return true; } else if(findWay(map,m-1,n)){ return true; } else if(findWay(map,m,n-1)){ return true; } else{ map[m][n]=3; return false; } }else{ return false; } } } }
-
递归练习3: 汉诺塔
Hanoi hanoi = new Hanoi(); char A = 'A'; char B = 'B'; char C = 'C'; hanoi.move(4,A,B,C); class Hanoi{ //若有多个盘,可以看成两个盘即最下面和上面所有的盘(n-1) public void move(int n,char a,char b,char c){ if(n==1){ System.out.println(a+"->"+c); } else{ //1. 先移动上面所有盘到b塔 move(n-1,a,c,b); //2. 把a塔剩余的一个移动到c塔 move(1,a,b,c); //3. 把b塔所有盘移动到c塔 move(n-1,b,a,c); } } }
-
递归练习4:八皇后
EightQueens eq = new EightQueens(); int num = eq.check(0); System.out.println("num="+num); class EightQueens{ int[] arr = new int[8]; int count = 0; public int check(int row){ if(row ==8){ for(int i =0;i< arr.length;i++){ System.out.print(arr[i]+" "); } System.out.println(); count++; }else{ for(int column = 0;column< arr.length;column++){ arr[row] = column; if(judge(row)){ check(row+1); } } } return count; } public Boolean judge(int row){ for(int i =0;i<row;i++){ if(arr[row]==arr[i] || Math.abs(row-i) == Math.abs(arr[row]-arr[i])){ return false; } } return true; } }
10.2 方法重载(overload)
-
Java中允许同一个类中存在多个同名方法,但要求形参列表(参数类型、个数或顺序至少有一样不同)不一致,只有返回类型不同不能称为方法重载
方法重载好处:减轻取名的麻烦和记名的麻烦
例如:System.out.println()方法中方法名相同但是形参列表不同,该方法可以接收不同的数据类型输出
10.3 可变参数
-
Java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法,可以通过可变参数实现
-
基本语法:访问修饰符 返回类型 方法名(数据类型… 形参名){方法体;}
-
注意事项:可变参数的实参可以为0个或任意个;
可变参数本质是数组;
可变参数和普通类型参数一起放在形参列表中时,必须保证可变参数在最后
一个形参列表中只能出现一个可变参数
VarParameter vp = new VarParameter(); int sum = vp.sum(1,2,3,4);//这里可变参数数量为3个 System.out.println("可变参数和为:"+ sum); class VarParameter{ //计算两个数的和、三个数的和、四个数的和... //可以使用方法重载,但是过于繁琐冗余 //因为方法名相同,功能相同,只是参数个数不同,可以使用可变参数进行封装 //int... 表示方法接收的是可变参数,即可以接收多个int类型的形参 //可以当做数组使用 public int sum(int... n){ int result = 0; for(int i =0 ;i<n.length;i++){ result +=n[i]; } return result; } }
10.4 变量作用域
-
主要变量就是属性(成员变量)和局部变量
-
局部变量一般是指在成员方法中定义的变量和代码块中的变量
-
全局变量:也就是方法的属性,作用域为整个类体
-
局部变量:除了属性之外的其他变量,作用域为定义它的代码块中
-
全局变量可以不赋值直接使用,因为它有默认值;局部变量必须赋值后才能使用
-
属性和局部变量可以重名,使用时遵从就近原则
-
属性的生命周期长,伴随着对象的创建而创建,伴随着对象的销毁而销毁;局部变量生命周期较短,即在一次方法调用过程中创建->销毁
-
作用域范围不同:全局变量(属性):可以被本类使用或其他类(通过对象调用)使用
局部变量:只能在本类中对应的方法中使用
-
修饰符不同:全局变量(属性)可以加修饰符,也可以不加;局部变量不可以加修饰符
10.5 构造方法/构造器(constructor)
-
基本语法:[修饰符] 方法名(形参列表){方法体;}
-
作用:主要完成对象的初始化
-
构造器修饰符可以是默认的,也可以是public、protected、private
-
构造器没有返回值
-
方法名必须和类名一致
-
参数列表和成员方法规则一致
-
构造器调用由系统完成
-
一个类可以定义多个不同的构造器;即构造器重载
-
若程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(默认构造器),使用javap指令反编译查看:javap 类名
-
一旦定义了自己的构造器,默认构造器就被覆盖,就不能再使用默认的无参构造器了,除非显式的定义一下
Constructor constructor1 = new Constructor(16,"Jack"); System.out.println("age:"+constructor1.age+"\tname:"+constructor1.name); Constructor constructor2 = new Constructor(20); System.out.println("age:"+constructor2.age); Constructor constructor3 = new Constructor("Lucy"); System.out.println("name:"+constructor3.name); Constructor constructor4 = new Constructor(); class Constructor{ int age; String name; //构造器没有返回类型,也不能写void //方法名必须和类名一致 public Constructor(int Page,String Pname){ System.out.println("构造器被调用!完成对象属性的初始化~"); age = Page; name = Pname; } public Constructor(int Aage){ age = Aage; } public Constructor(String Aname){ name = Aname; } public Constructor(){ System.out.println("因为系统默认的无参构造器被覆盖,这里显式的创建无参构造器"); } }
-
Java创建对象流程(详细):
- 在方法区先加载类信息(属性和方法信息,且只会加载一次)
- 在堆中分配空间,进行默认初始化
- 把地址赋给对象
- 进行指定初始化,覆盖原来的初始化值;这里先默认初始化(如int类型为0,String类型为null);若有显式初始化,再执行显式初始化(如定义类时直接初始化int为某个具体数值,则将默认初始化改为对应的初始化 );最后再执行构造器的初始化(如定义了构造器,创建对象后先执行前面两次初始化,最后根据个构造器的初始化进行对应改变)
- 把对象在堆中的地址返回给栈中的对象的引用(对象名)
- 注意:对象的地址在堆中。对象的引用(对象名)在栈中,指向堆中对应的对象地址
-
this关键字:
- java虚拟机会给每个对象分配this,代表当前对象。哪个对象调用,this就代表哪个对象
- this可以用来访问本类的属性、方法、构造器
- this用来区分当前类的属性和局部变量
- 访问成员方法的语法:this.方法名(参数列表);
- 访问构造器语法:this(参数列表) 注:只能在构造器中第一个语句调用其他构造器
- this只能在类定义的方法中使用,不能在类定义的外部中使用
ThisKeyWord tkw = new ThisKeyWord(100, "this关键字"); System.out.println("num:"+tkw.num+"\ttype:"+tkw.type+"\t对象地址的哈希码值:"+ tkw.hashCode()); ThisKeyWord tkw1 = new ThisKeyWord(20); System.out.println("num:"+tkw1.num+"\ttype:"+tkw1.type+"\t对象地址的哈希码值:"+ tkw1.hashCode()); tkw1.function2(); tkw.function3(); class ThisKeyWord{ int num; String type; //如果我们的构造器的形参能直接写成属性名,就更加方便明了,但是出现一个问题,就是变量的作用域原则 //下面的构造器形参的num、type是局部变量,而不是属性,导致使用下面构造器时,与本类的属性无关 // public ThisKeyWord(int num,String type){ // num = num; // type = type; // } //用关键词this关键字解决 public ThisKeyWord(int num ,String type){ // this. 就是指向当前对象的属性 this.num = num; this.type = type; System.out.println("this地址的哈希码值:"+this.hashCode()); } public ThisKeyWord(int num){ this(19,"Tom");//这句要放在首句中 this.num = num; } public void function1(){ System.out.println("方法一!"); } public void function2(){ System.out.println("使用this调用同类方法:"); //第一种方式:直接调用 function1(); //第二种方式:用this调用,区别后面继承时候讲解 this.function1(); } public void function3(){ String type = "A"; int num = 10; System.out.println("type="+ type + "\tnum=" + num ); System.out.println("this.type= " + this.type + "\tthis.num="+ this.num); } }
-
练习:创建一个PersonDemo类,含有一个compareTo()方法来比较两个对象属性是否相同,返回true or false;
PersonDemo p1 = new PersonDemo(18, "xiaoming"); PersonDemo p2 = new PersonDemo(20,"xiaohong"); boolean b = p1.compareTo(p2); System.out.println(b); class PersonDemo{ int age; String name; public PersonDemo(){} public PersonDemo(int age,String name){ this.age = age; this.name = name; } public Boolean compareTo(PersonDemo p){ if(this.name.equals(p.name)&&this.age==p.age){ return true; } else return false; } }
10.6 课后练习
-
编写类A01,定义方法max,实现求某个double 数组最大值,并返回
double[] arr = {1.15,3.2,8,6}; A01 a01 = new A01(); Double res = a01.max(arr); if(res!=null){ System.out.println("最大值为:"+res); }else{ System.out.println("输入数组为空!"); } class A01{ public Double max(double[] arr){ if(arr.length>0){ double max = arr[0]; for (int i = 1; i < arr.length ; i++) { if(arr[i]>max){ max = arr[i]; } } return max; } else{ return null; } } }
-
编写类A02,定义方法find ,实现某字符串数组中的元素查找,并返回索引,若找不到,返回-1
String[] srr={"Lucy","Ben","Jack","Tom"}; String tar = "Tom"; A02 a02 = new A02(); Integer res2 = a02.find(tar,srr); if(res2!=null) { System.out.println(res2); }else{ System.out.println("输入数组为空!"); } class A02{ public Integer find(String tar, String[] srr) { if (srr.length > 0) { for (int i = 0; i < srr.length; i++) { if (tar.equals(srr[i])) { return i; } } return -1; } return null; } }
-
编写类Book,定义方法updatePrice,实现更改某本书的价格,如:若价格大于150,则更改为150,若价格大于100,则更改为100,其他不变。
A03 a03 = new A03(99); System.out.println("书原价为:"+a03.price); double res3 = a03.updatePrice(); System.out.println("更改后的价格为:"+res3); class A03{ double price; public A03(double price){ this.price = price; } public double updatePrice(){ if(this.price>150){ this.price = 150; return this.price; }else if(this.price<150&&this.price>100){ this.price = 100; return this.price; } else{ return this.price; } } }
-
编写类A04,定义方法copyArr实现数组的复制功能,输入旧数组,返回新数组,元素和旧数组一样
A04 a04 = new A04(); Double[] a04Arr = {1.2,5.5,7.3,4.2}; Double[] res4 = a04.copyArr(a04Arr); if(res4!=null){ System.out.println("原数组哈希码值:"+ a04Arr.hashCode()); System.out.println("复制后哈希码值:"+ res4.hashCode()+"\n复制后元素为:"); for (int i = 0; i < res4.length; i++) { System.out.print(res4[i] + " "); } }else{ System.out.println("输入数组为空!"); } class A04{ public Double[] copyArr(Double[] arr){ if(arr.length>0){ Double[] copyarr = new Double[arr.length]; for (int i = 0; i < arr.length; i++) { copyarr[i] = arr[i]; } return copyarr; }else{ return null; } } }
-
编写类A05,定义属性半径,分别提供计算圆周长,圆面积的方法
A05 a05 = new A05(1); System.out.println("周长为:"+a05.Perimeter()); System.out.println("面积为:"+a05.Area()); class A05{ double r; public A05(double r){ this.r = r; } public double Perimeter(){ double perimeter= 2*Math.PI*this.r; return perimeter; } public double Area(){ double area = Math.PI*r*r; return area; } }
-
编写类A06,定义两个属性表示操作数,定义四个方法完成加减乘除操作,并创建两个对象,分别测试。
A06 a06 = new A06(1,0); System.out.println("和为:"+ a06.Add()); System.out.println("差为:"+ a06.subtraction()); System.out.println("积为:"+ a06.multiplication()); if(a06.y!=0){ System.out.println("商为:"+ a06.division()); }else{ System.out.println("被除数为0!"); } class A06{ double x; double y; public A06(double x,double y){ this.x = x; this.y = y; } public double Add(){ return this.x + this.y; } public double subtraction(){ return this.x - this.y; } public double multiplication(){ return this.x * this.y; } public Double division(){ if(this.y!=0){ return this.x/this.y; }else{ return null; } } }
-
编写类A07,属性有名字,颜色,年龄;定义方法show()输出其信息。
A07 a07 = new A07("旺财", "黄色", 12); a07.show(); class A07{ String name; String color; int age; public A07(String name,String color,int age){ this.name = name; this.color = color; this.age = age; } public void show(){ System.out.println("名字为:"+ this.name); System.out.println("颜色为:"+ this.color); System.out.println("年龄为:"+ this.age); } }
-
编写类A08,属性有(姓名、年龄、性别、工作、薪水),要求提供三个构造器可以分别初始化(姓名、年龄、性别、工作、薪水)(姓名、年龄、性别、)(姓名),要求充分福永构造器
class A08{ String name; int age; char sex; String job; double sar; public A08(String name){ this.name = name; } public A08(String name,int age,char sex){ this(name); this.age = age; this.sex = sex; } public A08(String name,int age,char sex,String job,double sar){ this(name, age, sex); this.job = job; this.sar = sar; } }
-
定义一个Circle类,包含一个double的radius属性代表半径,findArea()方法返回圆的面积
定义PassObject,在类中定义方法printAreas(Circle c,double times)
在printAreas方法打印1到times之间每个整数的半径值和对应的面积
在main中调用printAreas(),输出
Circle circle = new Circle(); PassObject passObject = new PassObject(); double times = 5.5; passObject.printAreas(circle,times); class Circle{ double radius; public Circle(){} public Circle(double radius){ this.radius = radius; } public double findArea(){ return Math.PI*radius*radius; } public void setRadius(double radius){ this.radius = radius; } } class PassObject{ public void printAreas(Circle c,double times){ System.out.println("Radius\tArea"); for (int i = 1; i <= times; i++) { //c.radius = i; 这里可以直接用c.radius来赋值改变属性,因为radius是public的,可以直接访问,当不是public时,需要用get/set来改变 c.setRadius(i); System.out.println(i+"\t\t"+c.findArea()); } } }
-
设计一种类,可以和电脑猜拳,并显示结果清单。0表示石头;1表示见到;2表示布
FingerGuess fingerGuess = new FingerGuess(); Play play = new Play(); int time = 5; play.play(fingerGuess,5); class FingerGuess{ int finger; int comGuessNum; public void setFinger(int finger){ this.finger = finger; } public int computerNum(){ Random random = new Random(); comGuessNum = random.nextInt(3); return comGuessNum; } } class Play{ public void play(FingerGuess f,int times){ Scanner scanner = new Scanner(System.in); System.out.println("电脑\t 我\t 结果"); for (int i = 1; i <= times; i++) { System.out.println("请输入你要出的0~2:"); int userNum = scanner.nextInt(); if(userNum==0||userNum==1||userNum==2){ f.setFinger(userNum); f.computerNum(); int comGuessNum = f.computerNum(); System.out.println("我的出拳:"+ userNum + "电脑出拳:"+comGuessNum); if(userNum==comGuessNum){ System.out.println("平局"); continue; } else if((userNum==0&&comGuessNum==1)||(userNum==1&&comGuessNum==2)||(userNum==2&&comGuessNum==0)){ System.out.println("用户赢!"); continue; }else{ System.out.println("电脑获胜!"); continue; } }else{ System.out.println("输入有误"); break; } } } }