一、概述和变量
面向过程 VS 面向对象:
- 面向过程:以‘函数’为组织单位;是一种执行者思维,适合解决简单的问题。扩展能力差、后期维护难度较大。
- 面向对象:以 类 为组织单位。每种事物都具备自己的 属性 和 行为/功能 。是一种“ 设计者思维 ”,适合解决复杂问题。代码扩展性强、可维护性高。
理解来说,面向过程:如何开车?按步骤一步步实现就行,点火---踩离合---踩油门---行进。
协作。需要从宏观上去考虑。
面向对象离不开面向过程。面向对象的实现其中也包含面向过程的函数。
类(class)和对象 (object):
- 类:具有相同特征的事物的抽象描述,是 抽象的 、概念上的定义。例如:人。
- 对象:实际存在的该类事物的 每个个体 ,是 具体的 ,因而也称为 实例(instance) 。例如:比尔盖茨等。
类的成员:
- 属性:也是成员变量==属性==field(字段、域)。
- 行为:成员方法==函数==method。
面向对象实现具体功能的三个步骤(重要) :
- 创建类,并设计类的内部成员(属性+方法) 。例如 class Phone。
- 创建对象(类的实例化),例如Phone phone=new Phone()。一般是在其它非Phone类中new一个。
- 通过对象,调用其属性和方法。
Java中内存结构的划分
- java中内存结构划分为:虚拟机栈、堆、方法区、程序计数器、本地方法栈 。
- 虚拟机栈:以栈帧为基本单位,有出栈和入栈操作。每个栈帧入栈出栈操作对应一个方法的执行。方法内的局部变量存储在栈帧中。
- 堆空间:new出来的结构(数组、对象)。1、数组:数组的元素在堆中;2、对象的成员变量在堆中
- 方法区:加载的类的模板结构。
类的内存解析:
- 堆:存放new出来的对象,对象中存放的是对象的成员变量(起初是默认值)。没有new,就不会新开辟空间。
- 栈:存放的是对象名,代表的是对象的首地址。调用成员变量时,会给对中的成员变量赋值。
- 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的 代码等数据。
成员变量 VS 局部变量:
- 成员变量:1、类内-方法体外声明的变量。2、随对象的创建,存储在堆空间。3、随着对象的创建而创建,随着对象的消亡而消亡。4、能用权限修饰符修饰。5、有默认值,所以不用必须显式赋值。
- 局部变量:1、方法体内等位置声明的变量。2、存储在栈空间。3、随着方法对应的栈针入栈,局部变量在栈中分配,随着方法对应的栈针出栈,局部变量消亡。4、不能用任何权限修饰符进行修饰。5、在使用局部变量前,必须显式赋值。
任何一个类都可以作为另一个类中属性的类型。作为引用数据类型。
例如,员工的生日属性。首先,需要先创建Mydate类,包含了生日的年、月、日属性。
接着,在另一个类中声明birthday时,可以直接使用Mydate类作为生日的数据类型。
接着,创建对象,调用属性。
二、方法
方法的声明
- public void eat();
- public void sleep(int hour);
- public String interests(String hobby); //调用该方法时要传入形参,并有一个变量接收返回值
- public int age(); //调用该方法时,要有一个变量接收返回值。
在声明方法时,要不要提供返回值类型(要不要有返回值)以及要不要传形参,则具体问题具体分析。
方法注意事项
- 必须先声明后使用,且方法必须定义在类的内部
- 调用一次就执行一次,不调用不执行。
- 方法中可以调用类中的方法或属性,不可以在方法内部定义方法。
return关键字的作用
- 作用 1 :结束一个方法。
- 作用2 :结束一个方法的同时,可以返回数据给方法的调用者。
- return后面不能再声明执行语句。
Q:如果main和方法在同一个类中,在main中不声明对象调用方法的情况下,方法要用static修饰???(后面待解决)
例如,
这样的话,方法会报错。当方法声明时加上static后,可以不用创建对象调用。例如:
方法调用的内存解析
每一个方法对应一个栈针。
- 方法 没有被调用 的时候,都在 方法区 中的字节码文件(.class)中存储。
- 方法 被调用 的时候,需要进入到 栈内存 中运行。方法每调用一次就会在栈中有一个 入栈 动作,即
- 给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值。
- 当方法执行结束后,会释放该内存,称为 出栈 ,如果方法有返回值,就会把结果返回调用处(例如在main方法中调用某个方法,该方法的返回值是返回到main中),如果没有返回值,就直接结束,回到调用处继续执行下一条指令。
- 栈结构:先进后出,后进先出。
三、对象数组
数组的元素可以是基本数据类型,也可以是引用数据类型。当 元素是引用类型中的类 时,我们称为对象数组。
例如,我们要查看20个学生对象的基本信息,其中,每个学生对象都包含多个属性,“学生”就可作为一个类作为引用数据类型。20个学生组成对象数组,其中每个元素即每个学生的数据类型是“学生”。
格式举例:Student[ ] students=new Student[20];
即students[i]=new Student()。
案例一:
定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int)。 创建20个学生对象, * 学号为1到20,年级和成绩都由随机数确定。 * 问题一:打印出3年级(state值为3)的学生信息。 * 问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
public class student_Test {
public static void main(String[] args) {
Student[] students=new Student[20];//创建对象数组。
for(int i=0;i<students.length;i++){
students[i]=new Student();//要为20个学生依次new对象,堆中
students[i].number=i+1;
students[i].state=(int)(Math.random()*6+1);//[1,6]
students[i].score=(int)(Math.random()*101);//[0,100]
}
int state=3;
for(int i=0;i<students.length;i++){
if(state==students[i].state){
System.out.println(students[i].printStudents());
}
}
//局部变量存放在栈中。属性(成员变量)在堆中
for(int i=0;i<students.length-1;i++){
boolean flag=true;//元素已经是排好序的
for(int j=0;j<students.length-1-i;j++){
if(students[j].score>students[j+1].score){
Student temp=students[j];//这里使用students[j].score是错误的,因为我们要把学生的所用属性进行一个交换。
students[j]=students[j+1];
students[j+1]=temp;
flag=false;//设为false,证明元素在该轮交换了一次
}
}
if(flag){ //如果为true,则表明在i轮后,并未再次进行交换,元素已经有序,此时可以跳出循环了
break;
}
}
System.out.println();
for(int i=0;i<students.length;i++){
System.out.println(students[i].printStudents());
}
}
}
案例二:
( 1 )定义矩形类 Rectangle ,包含长、宽属性, area() 返回矩形面积的方法, perimeter() 返回矩形周长的
方法, String getInfo() 返回圆对象的详细信息(如:长、宽、面积、周长等数据)的方法
( 2 )在测试类中创建长度为 3 的 Rectangle[] 数组,用来装 3 个矩形对象,并给 3 个矩形对象的长分别赋值
为 10,20,30 ,宽分别赋值为 5,15,25 ,遍历输出
案例二 内存解析:
area()方法和perimeter()方法调用都将返回值返回到getInfo()中, 然后依次出栈。
四、方法的重载
定义
在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可。【参数列表不同,意味着参数个数或参数类型的不同 ,和形参名、修饰符、返回值类型都没有关系。】
Q:编译器是如何确定调用的是某个具体的方法呢?
答:编译器先通过方法名确定一波重载的方法,再根据形参列表确定具体的某一方法。
注意:下图中,打印出的值为什么中间这个是具体的值,其它是地址值?
因为println是有一系列重载的方法,如下图,其中这个数组arr1和arr2(引用类型了)就对应Object。Object类型默认是打印地址值。
五、可变个数的形参
格式:
- 可变个数形参的方法在调用时,可赋的实参个数可以是0个、1个或多个。
- 可变个数形参的方法与同名的方法之间,彼此构成重载 。
- 可变参数方法的使用与方法参数部分使用数组是一致的,二者不能同时声明,否则报错。
- 方法的参数部分有可变形参,需要放在形参声明的最后.
- 在一个方法的形参中,最多只能声明一个可变个数的形参.
场景举例:
六、方法的值传递机制(重要)
1、(复习)对于方法内声明的局部变量来说:
如果出现赋值操作,
- 如果是基本数据类型的局部变量,则把数据值传递过去。
- 如果是引用数据类型(有数组或对象)的局部变量,则把其地址值传递过去。
2、方法的参数传递机制
- 如果形参是基本数据类型的变量,则将实参保存的数据值传递给形参。
- 如果形参是引用数据类型的变量,则将实参保存的地址值传递给形参。
3、内存解析:
Java面试题:
答案:
//方法一:
public void method(int a,int b){
a=a*10;
b=b*20;
System.out.println("a="+a);
System.out.println("b="+b);
System.exit(0);//正常终止当前运行的Java虚拟机,main函数后面的代码则不会再运行了。
}
//方法二:暂时先不理解了,等后续再看!!
public static void method(int a, int b) {
PrintStream ps = new PrintStream(System.out) {
@Override
public void println(String x) {
if ("a=10".equals(x)) {
x = "a=100";
} else if ("b=10".equals(x)) {
x = "b=200";
}
super.println(x);
}
};
System.setOut(ps);
}
七、方法的递归
举例:
注意:
1. 递归调用会占用大量的系统堆栈,内存耗用多,在递归调用层次多时速度要比循环 慢的
多 ,所以在使用递归时要慎重。
2. 在要求高性能的情况下尽量避免使用递归,递归调用既花时间又 耗内存 。考虑使用循环迭
代。
八、面向对象的特征一:封装性
内聚,指一个模块内各个元素彼此结合的紧密程度;耦合指一个软件结构内不同模块之间互连程度 的度量。内聚意味着重用和独立,耦合意味着多米诺效应牵一发动全身。
1、为什么要封装?
理论上:
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合:仅暴露少量的方法给外部使用,尽量方便外部调用。
通俗来讲,就是把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
2、如何实现数据封装?
- 使用4种权限修饰符:public 、 protected 、 缺省 、 private
其覆盖范围如下:(这里的子类估计就是继承那种的子类)
- 具体修饰结构
- public、缺省
- 成员变量、成员方法、构造器、成员内部类:public、protected、缺省、private
3、封装作用
可以使用4种权限修饰符修饰类及类的内部成员,当这些成员被调用时,体现其可见性的大小。【问什么是封装性呢?】
4、案例
public class AnimalTest {
public static void main(String[] args) {
Animal animal=new Animal();
animal.name="小兔子";
//animal.legs=4;
//animal.legs=-4;//数据非法,此时如果在这里直接赋值是不可以的。但在Animal类中不能直接单纯输入赋值的执行语句。所以要写入set/get方法。
//调用该方法对Animal类中的legs属性赋值,并将Animal类中legs属性私有化(private)
animal.setLegs(4);
animal.getLegs();//获得Animal类中legs的值
}
}
class Animal{
String name;
//因为legs私有化了,所以legs出了Animal类外就不能被调用了。
private int legs;
public void setLegs(int leg){
if(leg>0&&leg%2==0){
legs=leg;
}else
System.out.println("您输入的数据非法");
}
public int getLegs(){
return legs;
}
}
在题目中,我们给Animal的对象的legs属性赋值。在实际的常识中,我们知道legs不能赋值为负数的。但是如果直接调用属性legs,是不能加入判断逻辑的。那怎么办呢? 步骤如下:
- 将legs属性私有化(private),禁止在Animal类的外部直接调用此属性。
- 提供给legs属性赋值的setLegs()方法,在此方法中加入legs赋值的判断逻辑if(legs>=0&& legs % 2 ==0)将此方法暴露出去,使得在Animal类的外部调用此方法,对legs属性赋值。
- 提供给legs属性获取的getLegs()方法,此方法对外暴露。是得在Animal类的外部还可以调用此属性的值。
九、构造器(constructor)
1、作用
- 搭配new关键字,创建类的对象。
- 在创建对象的同时,能够给对象的相关属性赋值。
2、使用说明
- 声明格式:权限修饰符 类名(形参列表){ }
- 创建类以后,在没有显示提供任何构造器的情况下,系统会默认提供一个空参的构造器,且构造器的权限和类的权限相同。如下:
public class Person {
private String name;
private int age;
//默认有的无参构造器(这里显式的展示出来了)
public Person(){ //对于public,如果类名前面没有public,则构造器也不写
}
//方法
public void eat(){
System.out.println("吃饭");
}
}
public class PersonTest {
public static void main(String[] args) {
Person p1=new Person();//其中这个Person()就是一个构造器,对应Person类中显式展现出来的那一句
}
}
- 一旦类中声明了带参的构造器,则系统不再提供默认的那个无参构造器了。
- 一旦类中声明了多个构造器,则彼此之间构成重载。例如:
3、对象数组调用构造函数
具体使用举例:
public class Student {
private String name;
private int age;
private String school;
private String major;
//设置三个有参构造器
public Student(String n, int a){
name=n;
age=a;
}
public Student(String n, int a, String s){
name=n;
age=a;
school=s;
}
public Student(String n, int a, String s, String m){
name=n;
age=a;
school=s;
major=m;
}
public String printStudents(){
return name+"的年龄为"+age+",学校是"+school+",专业是"+major;
}
}
public class StudentTest {
public static void main(String[] args) {
Student[] students=new Student[2];
Scanner scanner=new Scanner(System.in);
String name;
int age;
for(int i=0;i<students.length;i++){
System.out.print("请输入姓名:");
name=scanner.next();
System.out.print("请输入年龄:");
age=scanner.nextInt();
//Student temp=new student(name,age); students[i]=temp; 也正确
students[i]=new Student(name,age);//这里可以根据构造器设置的参数数量进行赋值。
}
for(int i=0;i<students.length;i++){
System.out.println(students[i].printStudents());
}
}
}
4、具体案例练习
案例 账户客户 练习可见Github,目录文件地址:chapter06_oop/src/Exercise06/Test03中. 【点下面那个地址进入github后,需要把’main‘换成’master‘,为啥这末麻烦?我git bash的时候哪句语句需要改一下?有大佬知道吗??呜呜呜~】
Github地址:https://github.com/201917421/JavaSECode.git
Java上传到Github具体步骤参考博主教程: https://mbd.baidu.com/ma/s/cwbNUMeu
http://【如何将项目上传到github(自用)】https://mbd.baidu.com/ma/s/uXgm4JqH
十、JavaBean理解
所谓JavaBean,是指符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
alt+insert可以直接生成get、set方法。
十一、UML类图
小CASE
Idea安装插件可查看字节码文件