二. Java语言基础
7. 方法
-
某个功能代码只需写一遍,使用时“调用”/invoke这个功能,提高代码复用性
-
方法:Method,在C语言中叫做函数”Function“
方法定义在类体中,一个类中可以定义多个方法,方法的位置可以随意
方法体中不能再定义方法
方法体必须由大括号括起来,方法体中的代码遵循自上而下的顺序依次执行
public class Method01{
//类体
public static void main(String[] args){
Method01.sumInt(10,20);
Method01.sumInt(666,888);
Method01.sumInt(111,222);
}
public static void sumInt(int a,int b){ //定义一个sumInt方法
int c = a + b; //该方法完成计算两个int类型数据的和
System.out.println(a + " + " + b + " = " + c);
}
}
-
方法的语法结构:
-
关于修饰符列表:
可选项,不是必须的
目前统一写成:public static
修饰符列表中有“static”关键字时,调用方法为:类名.方法名(实际参数列表); -
返回值:
方法结束后返回执行结果,执行结果可能是一个具体存在的数据,这个数据就是返回值
方法结束后也可以不返回任何数据,但返回值类型必须是void
返回值语句:“return 值;”,且要求返回值数据类型必须和方法的返回值类型一致,否则报错
在同一个作用域中,return语句后不能编写代码,编译器会报错:无法访问的语句 -
返回值类型:
返回值的数据类型
可以是java语言中的任意一种类型,包括基本数据类型和所有的引用数据类型
可以是byte、short、int、long、float、double、boolean、char、String、void…
返回值类型若不是void,表示该方法结束后必须返回具体数值,若没有返回数据,编译器报错
返回值类型是void时,不能使用“return 值;”语句,但可以使用“return;”
只要带有return关键字的语句执行,return语句所在方法结束(不是JVM结束) -
方法名:
合法标识符即可,最好是动词,首字母小写后面单词首字母大写 -
形式参数列表(形参):
形参是局部变量:int a; double b; float c; …
形参的个数可以是0 ~ n个,多个形参之间用逗号“,”隔开
实际参数(实参):调用方法时给方法传递的真实数据
实参列表和形参列表必须满足:数量相同、类型对应相同
-
[修饰符列表] 返回值类型 方法名(形式参数列表){
方法体
}
//方法定义
public static int sum(long 变量名,int 变量名){} //形参列表:(long 变量名,int 变量名)
//方法调用
类名.sum("abc","def"); //编译报错,实参类型与形参不一致
类名.sum(10L,20L); //编译通过,(10L,20L)是实参列表
类名.sum(); //编译报错:参数数量不同
类名.sum(10,20); //编译通过,存在自动类型转换:int-->long
类名.sum(3.0,20); //编译报错,类型不同
类名.sum((long)3.0,20); //编译通过,强制类型转换
public class Method01{ //定义一个公开的类,类名必须与文件名相同
public static void main(String[] args){ //main方法是JVM负责调用的程序入口
//(String[] args):形式参数列表,其中String[]是一种引用数据类型,args是一个局部变量的变量名
//主方法只有args这个局部变量的变量名是随意的,其他不能改动
Method01.sum(10,20); //在main方法中调用Method01的sum方法,传递两个实参(10,20)
//在这里main方法暂停,进入sum方法执行,sum方法结束后再继续执行
int i = 111;
int j = 222;
Method01.sum(i,j); //一个方法可以被重复调用,(i,j)是实参列表
}
//修饰符列表:public static 返回值类型:void 方法名:sum 形式参数列表:(int a,int b)
public static void sum(int a, int b){ //自定义方法,不是程序入口
System.out.println(a + " + " + b + " = " + (a + b)); //方法体
}
}
- 方法中也可以调用方法
public class Method01{
public static void sum(int i, int j){ //3
System.out.print(i + j);
Method01.hello(); //4,调用hello方法
}
public static void hello(){ //5
System.out.println("hello");
}
public static void main(String[] args){ //程序执行顺序:1
Method01.sum(10,20); //2,调用sum方法
} //程序最终输出30 hello
}
- “类名.”可以省略的情况:
public class Method01{
public static void A(){
System.out.println("A");
}
public static void main(String[] args){
A(); //编译通过,同一个类中调用方法可以省略“类名.”
B(); //编译报错,省略“类名.”默认从当前类中找该方法
Method02.B(); //编译通过,不能省略
}
}
class Method02{ //建议在一个java源文件中指定义一个class,这里是为了举例
public static void B(){
System.out.println("B");
}
}
- 返回值类型不是void时:
public static void main(String[] args){
divide(10,3); //调用方法但没有接收返回值
int c = divide(10,3); //将返回值赋给c
}
public static int divide(int a, int b){
return a / b;
}
public static int m(){ //以下程序编译报错,编译器认为无法保证return语句一定执行
int a = 10;
if(a > 3){
return 1;
}
}
public static int m(){ //以下程序编译通过
int a = 10;
if(a > 3){
return 1; //或者使用代码:return a > 3 ? 1 : 0;
}else{
return 0; //可以保证有返回值
}
}
public static int m(){
int a = 10;
if(a > 3){
return 1; //若条件为true,return 1,方法执行结束
}
return 0; //若条件为false,return 0,方法执行结束
}
public static int m(){
int a = 10;
if(a > 3){
return 1;
System.out.println("hello"); //编译报错:无法访问的语句
}
System.out.println("hello"); //编译通过
return 0;
System.out.println("hello"); //编译报错
}
- 在返回值类型为void的方法中使用“return;”语句可以终止方法
public static void m(){ //return也可以结束main方法
for(int i =0 ;i < 10 ;i ++){
if(i == 5){
return; //终止m()方法
break; //终止for循环
}
}System.out.println(i); //两者都输出0 1 2 3 4
}System.out.println("hello"); //return不执行此代码;break执行此代码
-
方法执行时在JVM中的内存分配与变化:
- 方法只定义不调用,就不会执行,并且在JVM中也不会给该方法分配“运行所属”的内存空间
- JVM在内存划分上主要分为三块:方法区内存、堆内存、栈内存
- 关于栈(stack)数据结构:
压栈/入栈/push 弹栈/出栈/pop
栈针指向栈顶元素,栈顶元素处于活跃状态,其他元素静止
栈数据结构存储数据特点:先进后出,后进先出 - 方法代码片段属于.class字节码文件的一部分,字节码文件在类加载时将其放在方法区中
三块主要的内存空间中方法区最先有数据,存放代码片段
方法区中的代码片段只有一份,但可以被重复调用
每一次调用该方法时,在栈内存中分配空间 - 方法在调用的瞬间,会给该方法分配内存空间,发生压栈动作
方法执行结束此内存空间自动释放,发生弹栈动作 - 局部变量在方法体中声明,局部变量在运行阶段内存在栈中分配
-
在EditPlus中红色字体表示JavaSE类库中自带的类,如String.class、System.class
-
方法重载(overload):
功能相似时尽可能让方法名相同;功能不相似时尽可能让方法名不同
不需要记忆更多的代码名、代码美观,靠参数的数据类型区分不同方法
(JavaScript不支持方法重载)
public static void main(String[] args){ //不使用方法重载
System.out.println(sumInt(1,2));
System.out.println(sumDouble(1.0,2.0));
System.out.println(sumLong(1L,2L));
}
public static int sumInt(int a,int b){ //sumInt、sumDouble、sumLong功能相似
return a + b;
}
public static double sumDouble(double a,double b){
return a + b;
}
public static long sumLong(long a,long b){
return a + b;
}
public static void main(String[] args){ //使用方法重载
System.out.println(sum(1,2)); //参数类型不同,对应调用的方法不同
System.out.println(sum(1.0,2.0));
System.out.println(sum(1L,2L));
}
public static int sum(int a,int b){
return a + b;
}
public static double sum(double a,double b){
return a + b;
}
public static long sum(long a,long b){
return a + b;
}
- 方法重载满足条件:同一个类中、方法名相同、参数列表不同
public class Overload01{
public static void main(String[] args){
m1();
m1(10);
m2(1,2.0);
m2(1.0,2);
m3(1);
m3(1.0) //以上均调用成功
}
public static void m1(){}
public static void m1(int a){} //形参数量不同,构成重载
public static void m2(int a,double b){}
public static void m2(double a,int b){} //形参顺序不同,构成重载
public static void m3(int x){}
public static void m3(double x){} //形参类型不同,构成重载
public static void m4(int a,int b){}
public static void m4(int b,int a){} //方法重复,编译报错
}
- 方法重载只和方法名、参数列表有关,和返回值类型、修饰符列表无关
public static void x(){}
public static int x(){ //编译报错,方法重载和返回值类型无关
return 1;
}
void y(){}
public static void y(){} //编译报错,方法重载和修饰符列表无关
调用其他.class文件的方法
public class 工具{
public static void 打印(boolean a){ //定义打印方法,使用方法重载
System.out.println(a);
}
public static void 打印(int a){
System.out.println(a);
}
public static void 打印(long a){
System.out.println(a);
}
public static void 打印(float a){
System.out.println(a);
}
public static void 打印(double a){
System.out.println(a);
}
public static void 打印(char a){
System.out.println(a);
}
public static void 打印(String a){
System.out.println(a);
}
}
public class 调用{
public static void main(String[] args){
工具.打印(1); //调用工具.class的打印方法,输出1
工具.打印(10L);
工具.打印('a');
工具.打印("abc");
工具.打印(1.0F);
工具.打印(2.0);
工具.打印(true);
}
}
public static void main(String[] args){ //以下程序错误:java.lang.StackOverflowError
doSome();
}
public static void doSome(){ //代码片段只有一份,但可以被重复调用
doSome(); //每一次调用都会在栈内存中分配空间(只有进栈没有出栈)
}
- 用递归调用实现计算1 ~ n的和
public class haha{ //没有使用递归
public static void main(String[] args){
java.util.Scanner s = new java.util.Scanner(System.in);
System.out.print("请输入要求整数");
int n = s.nextInt();
int result = sum(n);
System.out.println("和为" + result);
}
public static int sum(int n){
int num = 0;
for (int i = 1; i <= n ; i ++ ){
num = num + i;
}return num;
}
}
public static int sum(int n){ //sum方法使用递归
if (n == 1){
return 1; //最后n == 1时把1赋给sum(2-1),重复计算n + sum(n-1)
}
return (n + sum(n-1)); //重复调用sum方法
}
三. 面向对象
-
面向过程:主要关注具体过程、因果关系(C语言:纯面向过程 C++:半面向对象)
优点:对于业务逻辑较简单的程序,可以达到快速开发,前期投入成本低
缺点:很难实现复杂的业务逻辑,软件元素之间的“耦合度”非常高,其中一个环节出问题会导致整个系统受影响,软件扩展能力差。此外,由于没有独立体的概念,无法达到组件复用
-
面向对象:主要关注对象(独立体)能完成哪些功能(Java:纯面向对象)
优点:耦合度低,扩展力强、更容易实现现实世界中更复杂的业务逻辑、组件复用性强
缺点:前期投入成本高,需要进行独立体的抽取、大量的系统分析与设计
-
面向对象的三大特征:封装、继承、多态
-
面向对象(OO)生命周期:
面向对象的分析:OOA 面向对象的设计:OOD 面向对象的编程:OOP
-
类和对象的概念:
类:在现实世界中不存在的抽象概念,代表了一类事物,是对象的共同特征
属性:描述对象的状态信息
方法:描述对象的动作信息
对象:在现实世界中实际存在的个体,又被称为实例/instance
-
java中所有的class都是引用数据类型
-
引用数据类型的默认值为null
public class Student{ //Student类代表所有的学生对象,Student是一个引用数据类型
int no; //对象的状态信息:属性
String name; //属性通常用变量的方式来定义
boolean sex; //定义在类体中、方法体外的变量为成员变量
... //成员变量未赋值时系统会赋默认值
}
-
对象又被称为实例,实例变量又称为对象变量(对象级别的变量)
-
不创建对象,成员变量的内存空间是不存在的,只有创建对象后,其内存空间才会创建。通过一个类可以实例化n个对象
-
实例化对象语法:new 类名();,new是java语言的一个运算符
new创建对象时在JVM堆内存中开辟新的内存空间
public class OOTest01{ //接上面代码
public static void main(String[] args){
Student s = new Student(); //Student是一个引用数据类型,s是一个变量名
} //new Student()是一个学生对象
} //s是一个局部变量(在栈内存中存储),称为引用
-
对象:new运算符在堆内存中开辟的内存空间称为对象
-
引用:引用是一个变量(包括局部变量和成员变量),此变量中保存了某个java对象的内存地址
-
Java中没有指针,程序员不能直接操作堆内存,程序员只能通过“引用”去访问堆内存中对象内部的实例变量
-
访问实例变量语法格式:
读取数据:引用.变量名
修改数据:引用.变量名 = 值;
System.out.println(s.no); //输出0
System.out.println(s.name); //输出null
System.out.println(s.sex); //输出false
System.out.println(Student.no); //编译报错,实例变量不能直接用“类名”访问
//实例变量是对象级别的变量,必须通过对象才能访问实例变量
s.no = 10; //修改对象数据
s.name = "jack";
s.sex = true;
-
局部变量在栈内存中存储;成员变量在堆内存的java对象内部存储
有n份对象就有n份实例变量
public class Address{ //地址类
String city;
String street;
String zipcode;
}
public class User{ //用户类
//属性:以下都是成员变量之实例变量
int no; //int是一种基本数据类型,no是一个实例变量
String name; //String是一种引用数据类型,name是一个实例变量,name是一个引用
Address addr; //Address是一种引用数据类型,addr是一个实例变量
}
public class OOTest{ //编译此文件时,自动编译Address.java和User.java文件
public static void main(String[] args){
//创建User对象,u是局部变量,是一个引用,u保存地址指向堆内存的User对象
User u = new User();
System.out.println(u.no); //0
System.out.println(u.name); //null
System.out.println(u.addr); //null,引用数据类型默认值为null
//修改User对象内部实例变量的值
u.no = 110;
u.name = "jack" //"jack"属于String对象
u.addr = new Address(); //这里必须new,否则报错
System.out.println(u.name + "城市" + u.addr.city); //输出jack城市null
System.out.println(u.name + "街道" + u.addr.street); //jack街道null
System.out.println(u.name + "邮编" + u.addr.zipcode); //jack邮编null
u.addr.city = "北京"; //赋值
u.addr.street = "朝阳";
u.addr.zipcode = "056003"
System.out.println(u.name + "城市" + u.addr.city); //输出jack城市北京
System.out.println(u.name + "街道" + u.addr.street); //jack街道朝阳
System.out.println(u.name + "邮编" + u.addr.zipcode); //jack邮编056003
}
}
public class OOTest01{
public static void main(String[] args){
Address a = new Address();
u.addr = a;
System.out.println(u.addr.city); //null
a.city = "天津";
System.out.println(u.addr.city); //天津
}
}
Husband与Wife的例子
Public class Husband{ //Husband.java
String name;
Wife w;
}
public class Wife{ //Wife.java
String name;
Husband h;
}
public class OOTest{
public static void main(String[] args){
Husband a = new Husband();
Wife b = new Wife();
a.name = "A";
b.name = "B";
a.w = b;
b.h = a;
System.out.println("A的Wife是" + a.w.name); //A的Wife是B
System.out.println("B的Husband是" + b.h.name); //B的Husband是A
//也可以写成:
System.out.println("A的Wife是" + a.w.h.w.name); //无限套娃
}
}
-
关于JVM内存管理
- 堆内存和方法区内存各有一个,一个线程一个栈内存
- 方法的代码片段以及整个类的代码片段都被存储到方法区内存在类加载时载入
- 成员变量(方法体外声明)包括实例变量(修饰符里没有static)与静态变量(有static)
- 静态变量存储在方法区内存中
- 三块内存区域中变化最频繁的是栈内存,最先有数据的是方法区内存,垃圾回收机制主要针对堆内存
- 当没有引用指向堆内存中的对象时,该对象成为垃圾,会被垃圾回收器回收
public class Customer{
int no;
}
public class OOTest{
public static void main(String[] args){
Customer a = new Customer();
System.out.println(a.no); //输出0
a = null; //编译通过,符合语法,意为空引用
//运行报错:java.lang.NullPointerException
System.out.println(a.no); //空引用访问“实例”相关的数据出现空指针异常
} //“实例”相关的数据:数据访问时必须有对象的参与
}
-
题目:定义一个计算机类(电脑),属性有品牌、型号、颜色
定义一个学生类,属性:学号、姓名、学生有一台电脑
表示以上类,分别创建对象(数量不限),让其中一个学生使用其中一台电脑
class Computer{
String brand;
String style;
String color;
}
class Student{
String no;
String name;
Computer c;
}
public class OOTest{
public static void main(String[] args){
Student a = new Student();
Student b = new Student();
a.no = "001";
a.name = "蛤";
Computer x = new Computer();
Computer y = new Computer();
x.brand = "Lenovo";
x.style = "ThinkPad";
x.color = "black";
a.c = x;
}
}