4.面向对象
- 属性+方法变成一个类
Java面向对象学习三条主线:
1.Java类及类的成员:属性,方法,构造器;代码块,内部类
2.面向对象的三大特征:封装性,继承性,多态性,(抽象性)
3.其他关键字:this,super,static,final,abstract,interface,package,import
- 面向对象编程思想?
(类,对象; 面向对象的三大特征;…) - 谈谈你对面向对象中类和对象的理解,并指出二者关系?
类: 是对一类事物的描述, 抽象的, 概念上的内容
对象: 实实在在存在的一个个体. new出来的东西,在内存当中存在的, 在内存中真正的创建了一个对象,占据了内存一定空间
对象: 是由类派生(new)出来的
4.1.什么是面向对象
-
面向对象编程(Objected-Oriented-Programming,OOP),Java的核心思想
-
面向对象编程的本质是: 以类的方式组织代码,以对象的形式组织(封装)数据.
-
抽象 把相似的抽取出来
-
三大特性:
- 封装 把一个东西打包封装,留一个口方便把东西拿出
- 继承 子类可以继承父类的所有东西,多个子类继承的东西是一样的
- 多态 同一个事物会有多种形态
-
从认识论角度考虑是先有对象后有类. 对象,是具体的事物. 类,是抽象的, 是对对象的抽象
-
从代码运行角度考虑是先有类后有对象. 类是对象的模板.
-
值传递
// 值传递
public static void main(String[] args){
int i = 1;
System.out.println(i);
Demo04.change(i);
System.out.println(i);
i = Demo04.change1(i);
System.out.println(i);
}
// 返回值为空
public static void change(int a){
a = 10;
}
// 返回值不为空
public static int change1(int a){
a = 10;
return a;
}
- 引用传递
public class Demo05 {
// 一个类可以有多个public class 但是可以有多个class
// 引用传递: 对象,本质还是值传递
public static void main(String[] args) {
// Person是一个引用,指向的是堆里面的对象
Person person = new Person(); // 实例化Person对象
System.out.println(person.name); // null
// Demo05.change(person);
change(person); // 引用传递传递的是对象的地址
System.out.println(person.name);// dijia
// 变量名对应的内存地址不一样
// 值传递,传递后的值被改了不影响原变量
// 因此只是将方法中的变量指向了字符串,并未改变main方法原变量的引用
}
public static void change(Person person) {
// person是一个对象:指向Person这个类
// 或是 Person person = new Person();这是个具体的人,可以改变属性!
person.name = "dijia";
}
}
// 定义一个Person类,有一个属性: name
class Person {
String name;
}
4.2.创建与初始化对象
- 面向对象的两个要素:
- 类:对一类事物的描述,是抽象的,概念上的定义
- 对象:是实际存在的该类事物的每个个体,因而也称为实例(instance)
- 类和对象的创建
面向对象思想的体现一 ,类和对象的创建和执行操作(面向对象思想落地地实现):
1.创建类,设计类的成员
2.创建类的对象
3.通过"对象.属性"或"对象.方法()"调用对象的结构
二,如果创建了一个类的多个对象,每个对象都独立的拥有一套类的属性(无static关键字的)
意味着,如果我们修改一个对象的属性a,不影响另一个属性a的值.(static为可共享属性)
// 测试类
public class PersonTest {
// main方法作为程序入口
public static void main(String[] args) {
// 创建Person类的对象=类的实例化=实例化类
Person p1 = new Person(); // 类是对象的类型,对象是类的实例,Person是引用的变量类型
// 调用对象的(功能和行为)结构: 属性和方法
// 调用属性: "对象.属性;"
p1.name = "tom";
p1.isMale = true;
System.out.println(p1.name);
// 调用方法: "对象.方法();"
p1.eat();
p1.sleep();
p1.talk("Chinese");
System.out.println("================");
Person p2 = new Person();
System.out.println(p2.name); // null
System.out.println(p2.isMale); // false
System.out.println("===================");
//
Person p3 = p1; // p1对象的地址值赋给了p3,导致p1和p3指向了堆空间中的同一个对象实体
System.out.println(p3.name); // tom
p3.age = 10;
System.out.println(p1);
System.out.println(p3);
System.out.println(p1.age); // 10
}
}
// 创建类,设计类的成员:包括成员方法,成员变量
class Person{
// 属性= 成员变量= 域,字段
String name;
int age = 1;
boolean isMale;
// 设计方法:行为
public void eat(){
System.out.println("人可以吃饭");
}
public void sleep(){
System.out.println("人可以睡觉");
}
public void talk(String language){
System.out.println("人可以说话使用:" + language);
}
- 对象的内存解析:
- 栈的特点:先进后出
1.new Person(); 先造了一个对象,new的结构都在堆里,意味着在堆空间中要造一个对象的实体,该对象会有个地址值称作首地址值; 在栈空间中声明了一个变量p1,此时的p1实际是定义在main方法中的,方法中定义的变量都是局部变量,此时,new结构的数据赋值给p1,其实赋过来的是个地址,通过赋值地址值后,栈空间中的p1就指向了堆空间中造的对象实体,类在设计的时候声明过三个属性,这些属性不同于局部变量,属性存在于堆空间中,在造好的对象里面,在赋值过程中会给定默认的初始化值和先前赋好的值
2.通过p1找到new的结构调用name把null改成tom,tom等赋值后的数据实际在方法区中的字符串常量池里
3.重新在堆空间中造一个对象实体, 该实体有一个首地址值,将这个地址值赋值给栈空间中现在加载的变量p2,栈空间中的p2对象就可以指向堆空间的这个对象实体,对象实体也独立生成一份默认初始化值和先有的值
4.又在main方法(栈空间)中声明了一个p3局部变量,p3拿p1赋的值,p1存的是地址值,所以传给p3也是相同地址值,通过p1传过来的地址值,p3也顺着地址指向p1的在堆空间中的结构,所以p3不能叫新创建的一个对象,只能算新声明的一个变量,此时p1和p3指向了堆空间中的同一个对象实体
4.3.属性与局部变量的对比
类中属性的使用
属性(成员变量) vs 局部变量
- 1.相同点:
- 1.1.定义变量的格式: 数据类型 变量名 = 变量值
- 1.2.先声明,后使用
- 1.3.变量都有其对应的作用域
- 2.不同点:
- 2.1.在类中声明的位置不同
属性:直接定义在类的一对{}里
局部变量:声明在方法内,方法形参,代码块内,构造器形参,构造器内部的变量 - 2.2.关于权限修饰符的不同
属性:可以在声明属性时,指明其权限,使用权限修饰符.
常用的权限修饰符: private,public,缺省(相当于默认,default),protected —>类的封装性
目前,声明属性时,使用缺省就行了
局部变量:不可以使用权限修饰符,可理解为其权限被方法的权限代替了 - 2.3.默认初始化值的情况:
属性: 类的属性,根据其类型,都有默认初始化值.
整型(byte,short,int,long): 0
浮点型(float,double): 0.0
字符型(char), 0 (或’\u0000’)
布尔型(boolean): false
引用数据类型:(类(特殊的String),数组,接口): null
局部变量: 没有初始化值,意味着调用局部变量之前,一定要显示赋值
特别地: 形参在调用时,赋值即可. - 2.4.二者在内存中加载的位置:
(非static)属性:加载到堆空间中(static属性都放在方法区中)
局部变量:加载到栈空间中
- 2.1.在类中声明的位置不同
public static void main(String[] args) {
User u1 = new User();
System.out.println(u1.name);
System.out.println(u1.age);
System.out.println(u1.isMale);
u1.talk("中文");
u1.eat();
}
}
class User{
// 属性(成员变量)
String name; // private权限小,出了定义的这个类就不能调了
public int age; // public权限大,在外的就能调
boolean isMale;
public void talk(String language){ // language:形参(局部变量)
System.out.println("我们使用" + language + "交流");
}
public void eat(){
String food = "烙饼"; // (声明)定义在方法内的变量叫局部变量
System.out.println("北方人吃" + food);
}
4.4.空指针异常(NullPointerException)
关于垃圾回收器: GC
在Java语言中,垃圾回收器主要针对的是堆内存.
当一个Java对象没有任何引用指向该对象的时候,
GC会考虑将该垃圾数据释放回收掉.
- 出现空指针异常的前提条件是?
- "空引用"访问实例(对象相关,例如id)相关的数据时,都会出现呢空指针异常.
/*
空指针异常
*/
public class NullPointerTest {
public static void main(String[] args) {
// 创建客户对象
Customer1 c1 = new Customer1();
// 访问这个客户对象
System.out.println(c1.id);
// 重新给id赋值
c1.id = 242;
System.out.println(c1.id);
c1 = null;
// NullPointerException 空指针异常
// 编译器没问题,因为编译器只检查语法,编译器发现c是Customer类型,
// Customer类型中由id属性,所以可以: 调用c.id,语法过了,
// 但是运行的时候需要对象的存在,但是对象丢失,就只能出现异常
System.out.println(c1.id);
}
}
// 客户类
class Customer1{
// 客户id属性
int id; // 成员变量中的实例变量,应该先创建对象,再通过"引用."的方式访问
}
4.5.对象数组
public class StudentTest1 {
public static void main(String[] args) {
// 声明自定义类Student类型的对象数组
Student1[] stus = new Student1[20]; // 类似于String[] arr = new String[];
for (int i = 0;i < stus.length;i++){
// 给数组元素赋值
stus[i] = new Student1();
// 给Student对象的属性赋值
stus[i].number = (i + 1);
// 年级: [1,6]
// 随机数a~b公式: int value = (int)(Math.random() * (b - a + 1) - a)
stus[i].state = (int)(Math.random() * (6 - 1 + 1) + 1);
// 分数: [0~100]
stus[i].score = (int)(Math.random() * (100 - 0 + 1));
}
// 想在main方法里调用其他方法,要在main方法公共类里造一个当前类的对象,
StudentTest1 test = new StudentTest1();
// 遍历学生数组
test.print(stus);
/*for (int i = 0;i < stus.length;i++){
// stus[i].info();
System.out.println(stus[i].info2());
}*/
System.out.println("======================");
// 打印三年级(state值为3)的学生信息
test.searchState(stus,3);
/*for (int i = 0;i < stus.length;i++){
if (stus[i].state == 3){
stus[i].info();
}
}*/
System.out.println("========================");
// 使用冒泡排序按学生成绩排序,并遍历所有学生信息
/* for (int i = 0;i < stus.length - 1;i++){
for (int j = 0;j < stus.length - 1 - i;j++){
if (stus[j].score > stus[j+1].score){
// 这里要交换数组元素的对象!意思是把整个人交换
// 如果只交换成绩,学号和年级都没换,相当于"作弊"
Student1 temp = stus[j];
stus[j] = stus[j+1];
stus[j+1] = temp;
}
}
}*/
/*for (int i = 0;i < stus.length;i++){
System.out.println("学号:" + stus[i].number + "年级:" + stus[i].state + "成绩:"+ stus[i].score);
}*/
}
// 遍历Student1[]类型数组的操作
/**
* @Description 遍历Student1[]类型数组的操作
* @author tiga
* @date time
* @param stus
*/
public void print(Student1[] stus){
// 传入Student1[]类型数组的形参
for (int i = 0;i < stus.length;i++){
stus[i].info();
}
}
// 查找某年级的学生信息
/**
*
* @Description 查找Student类型数组中指定年级的学生信息
* @author tiga
* @date time
* @param stus 要查找的数组
* @param state 要查找的年级
*/
public void searchState(Student1[] stus,int state){
// 传入要找的那个数组和要找的年级为形参
for (int i = 0;i < stus.length;i++){
if (stus[i].state == state){
System.out.println(stus[i].info2());
}
}
}
// 冒泡排序学生成绩,遍历学生信息
public void sort(Student[] stus){
for (int i = 0;i < stus.length - 1;i++){
for (int j = 0;j < stus.length - 1 - i;j++){
if (stus[j].score > stus[j+1].score){
Student temp = stus[j];
stus[j] = stus[j+1];
stus[j+1] = temp;
}
}
}
}
}
class Student1{
int number; // 学号
int state; // 年级
int score; // 成绩
public void info(){
System.out.println("学号:" + number + "年级:" + state + "成绩:" + score);
}
public String info2(){
return "学号:" + number + "年级:" + state + "成绩:" + score;
}
}
- 对象数组的内存解析
引用类型变量只能存储null或对象的地址值(含变量的类型).
4.6.匿名对象
一.理解"万事万物皆对象"
1.在Java语言范畴中,都将功能,结构等封装到类中,通过类的实例化,来调用具体的功能结构
Scanner,String
文件:File
网络资源: URL
2.涉及到Java语言与前端HTML,后端的数据库交互时,前后端的结构在Java层面交互时,都体现为类,对象
二.内存解析的说明:
1.引用类型的变量,只可能存储两类值:null 或 地址值(含变量的类型)
三.匿名对象的使用
1.理解:创建的对象,没有显式的赋给一个变量名.即为匿名对象
2.特征: 匿名对象只能调用一次
3.使用: 可以将匿名对象放入方法里多次调用
- 内存简单分析
在调用方法里new完Phone这个类的对象,在堆里产生的空间也会有一个地址值,调用时new的对象其实是赋值给了定义类里的方法里作为形参,形参又是个局部变量,且该变量会存入栈中,相当于把new Phone这个类出来的对象的地址值赋给了形参,只要是赋给了变量,对象就可以多次使用,从而形参就指向了堆中new的新对象, 就能通过新对象的地址调用其他方法, 这时调用的是同一个匿名对象,这相当于把匿名的对象赋给了有名的对象(形参).
public class InstanceTest {
public static void main(String[] args) {
Phone p = new Phone(); // p就是Phone对象的变量名
// p = null;
System.out.println(p);
p.sentEmail();
p.playGame();
// 匿名对象
// 两者调用的不是同一对象
new Phone().sentEmail();
new Phone().playGame();
new Phone().price = 2000;
new Phone().showPrice(); // 0.0
System.out.println("======================");
// 匿名对象的使用
PhoneMall mall = new PhoneMall();
mall.show(new Phone()); // 理论上是匿名
ShowAddress add = new ShowAddress();
add.printAdd(new PhoneMall());
}
}
class ShowAddress{
public void printAdd(PhoneMall phone){
System.out.println(phone);
}
}
class PhoneMall{
public void show(Phone phone){
// 实际上形参就是匿名函数的名字
phone.sentEmail();
phone.playGame();
}
}
class Phone{
double price; // 价格
public void showPrice(){
System.out.println("手机价格是:" + price);
}
public void sentEmail(){
System.out.println("发送邮件");
}
public void playGame(){
System.out.println("玩游戏");
}
}
- 理解变量的赋值
- 当在方法中定义了基本数据类型的局部变量,其变量存放在栈中,给变量赋值的数字就是变量在栈内存中存放的东西, 变量之间的赋值传递, 也是传递数字.
int m = 10;
int n = m; // n == 10
- 当在方法中定义了引用类型的局部变量,其变量也是存放在栈中,但是变量赋给的值的地址值, 所以当同类型变量之间赋值传递, 是传递地址,而不是数字.
- 值传递机制: 针对基本数据类型
一.方法的形参的传递机制: 值传递
1.形参: 方法定义时,声明的小括号内的参数
2.实参,方法调用时,实际传递给形参的数据,可以是具体数也可以是变量
二.值传递机制: 如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值.
- 内存分析.
1.m和n没有换成的原因: 在栈中,先从main方法进来,main方法中定义的m, n分别赋值10, 20;
2.通过对象v调swap方法,v就不往里面写了,当调用swap方法时候,将上面实参m, n表示数据的值,赋给了下面swap方法的形参m, n(新定义的),这两个形参还要在栈中提供,形参m, n分别是实参赋值过来的,也是10和20,此时的m, n 是swap方法中的;
3.接着v调用swap方法就进入方法执行,一进去又声明新变量temp,temp拿了形参m赋的值,接着形参n的值给了m,接着又把temp给了n,最swap方法实现效果是把swap方法里的m和n交换,如果在方法里面输出,m和n就会交换,但是如果方法内不输出,swap方法执行完以后就销毁了(出栈), 销毁之后接着在main方法下面再输出,输出的m和n的值是main方法赋的值,因为swap方法出栈以后,输出的m和n保存的值是main里面定义的m和n两变量的值, main里面定义的变量在main的整个大括号区间里都是有效的,所以main里面输出的还是main里定义的值,所以两数没换成. 数组元素的值交换也是同理
public static void main(String[] args) {
int m = 10;
int n = 20;
System.out.println(m + "," + n);
ValueTransferTest1 test = new ValueTransferTest1();
test.swap(m,n); // 实参
/*int temp = m;
m = n;
n = temp;*/
System.out.println(m + "," + n); // 10, 20
}
public void swap(int m,int n){
// 新变量实参传入的形参
int temp = m;
m = n;
n = temp;
// System.out.println(m + "," + n);
}
-
值传递机制: 针对引用数据类型
静态方法调用非静态方法要先new一个对象
静态方法调用静态方法直接调用
非静态方法调用非静态方法直接调用 -
内存分析
1.从main方法进来,在栈中new了一个data变量,也在堆空间中new了一个对象,该对象会有个地址值,并把地址值赋值给栈空间的data变量,通过地址值,栈空间中的data变量指向堆空间中的对象实体
2.接着下一步,data(对象)变量调用Data类中定义的变量m, n,一开始m, n的初始值都为0,通过对象调属性改了m, n的值,此时输出就是10, 20.
3.接着新建的对象去调swap方法,顺便把引用类型data传入作为形参,传入的引用类型data是上面的实参,所以形参是引用类型就存的是地址值,形参在栈中加载,相当于把main方法中data的地址值复制了一份,形参有了地址值后,他就指向堆空间中同一对象实体
4.进入swap方法体,通过data(形参)变量调用m的值,赋给一个swap方法内部声明的局部变量temp,temp也加载在栈中,data的n的值赋给data的m,temp的值又赋给了data的n,至此方法执行结束,结束后,swap方法定义的temp和data变量就出栈了,出栈后形参data的指针就没了,但是堆中对象还在,还有实参data的指向,这样判断堆中对象就不是垃圾,不能回收了.
5.回到main方法接着执行输出 data.m和data.n, 这两data变量是实参data, m和n的值就交换了
- 练习: 方法的参数传递
4.7.面向对象的特征一: 封装和隐藏
面向对象的特征一: 封装和隐藏
一.问题的引入:
当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值,这里,赋值操作要瘦到属性的数据类型和存储范围的制约
除此之外,没有其他制约条件,但是在实际问题中,我们往往需要给属性赋值加入额外的限制调价.这个特条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加.(比如: setLegs())
同时,我们需要避免用户再使用"对象.属性"的方式对属性进行赋值.则需要将属性声明为私有的(private)
–>此时,针对于属性就体现了封装性
二.封装性的体现之一(不等同于封装性):
将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
拓展: 封装性的体现: 1.如上 2.不对外暴露的私有的方法 3.单例模式: 把构造器私有化
三.封装性的体现,需要权限修饰符来配合
1.Java规定的4种权限(从小到大排列): private,缺省,protected,public
2.4种权限可以用来修饰类及类的内部结构: 属性,方法,构造器,内部类,(代码块不行)
3.具体的,4中权限都可以用来修饰类的内部结构:属性,方法,构造器,内部类
修饰类的话,只能用: 缺省,public,不能用private
总结封装性: Java提供了4中权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小
体现一: 将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
体现二: 不对外暴露的私有的方法
体现三:单例模式(将构造器私有化,外边不能随便调构造器, 里面自己只造一个对象,大家都只拿一个对象用)
体现四: 如果不希望类在包外被调用,可以将类设置为缺省的.
public class AnimalTest {
public static void main(String[] args) {
Animal a = new Animal();
a.name = "大黄";
// a.age = 1; // 将属性私有化后不能直接调用,要通过public方法调用
// a.legs = 4; // 作用域不可见 The field Animal.legs is not visible
a.show(); // 谁调的show方法,方法里显示的属性值就是谁的
a.setLegs(-6);
// a.legs = -4; // The field Animal.legs is not visible