面向对象编程

面向对象编程

一、类和对象

(1)什么是对象?

万物皆对象,客观存在的事物都是对象。

例如:一部手机,一条狗,一只猫,一张火车票,一个订单等等。

(2)什么是类?

类是对具有相同特征和行为的事物的抽象。

例如:手机、狗、猫、火车票、订单、帅哥、美女等。

类其实就是类型。类的存在是为了弥补数据类型不足的问题。

(3)什么是对象的属性?

**属性是对象具有的各种特征。**对象可以有很多个属性,每个属性都有特定的值。

例如:一部手机有品牌、价格、内存、颜色等属性,不同的手机属性值不同,下面是一部小米手机的属性。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZSswtIjp-1615184582733)(11_面向对象编程/对象的属性.png)]

(4)什么是对象的行为?

行为就是对象能做什么事情,即对象能执行的操作。

例如:手机可以打电话,发短信等。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k1Pynf72-1615184582751)(11_面向对象编程/对象的行为.png)]

(5)类和对象的关系

类是对象的抽象,用于描述对象,是对象的数据类型。

对象是类的具体体现,是类的实例。

例如: 手机是个类,用于描述什么是手机。手机应该具有品牌、价格、颜色等属性,具有打电话、发短信等功能。小米手机是个对象,华为手机是个对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ERFe8iN9-1615184582756)(11_面向对象编程/类和对象的关系.png)]

再例如:人是一个类,张三是一个具体的人,是对象。电脑是个类,我的电脑是个对象。

下面哪些是类,哪些是对象?

汽车、宝马、手机、iPhone X、我的iPhone X、狗、藏獒、小红家的藏獒。

注意:类可以细分,细分后的类也是类。细分的类可以认为是子类。

类是Java程序的基本组成单位,任何代码都要依托于类存在。想要做好Java开发必须要掌握类的定义以及对象的使用。

二、类的定义

类是对对象的抽象,是对对象的描述。它来定义对象都有哪些属性,有哪些行为。

因为对象包含属性和行为,由于类是对对象的描述,所以类也包含属性和行为。

对象的属性,在定义类的时候称为实例变量或成员变量或属性。

对象的行为,在定义类的时候称为实例方法或成员方法。

类本质上就是一种自定义的数据类型,定义出来类型以后,可以用这个类型去创建变量(创建的变量称为对象)。

(1)类的定义格式

使用class关键字来定义类。

public class 类名{
    //实例变量(或成员变量)
    数据类型 变量1;
    数据类型 变量2;
    ....
    //实例方法(或成员方法)
    方法1;//此方法要去掉static关键字
    方法2;//此方法要去掉static关键字
    ...
}

注意事项:

  1. 类名使用大驼峰法命名,即每个单词首字母大写。
  2. 实例方法不能用static修饰。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gsEV0ETn-1615184582761)(11_面向对象编程/类的定义格式.png)]

(2)类的定义示例

  1. 需求:定义上述的手机类。手机包含品牌、价格,能打电话、发短信。

  2. 代码:

    public class Phone {
    	//实例变量(成员变量)
    	String brand;	//品牌
    	int price;		//价格
    	
    	//实例方法(成员方法)
    	public void call() {
    		System.out.println("打电话");
    	}
    	
    	public void sendMessage() {
    		System.out.println("发短信");
    	}
    }
    

一个文件中能定义多个类。----只要用class去定义就可以。

但只能有一个public修饰的类。被public修饰的类必须和文件名相同。

之所以只能由一个public修饰的类,是因为一个文件只有一个文件名。

大多数情况下一个文件中只定义一个类。

属性的默认值与数组元素的默认值相同:引用类型的默认值是null,基本数据类型默认值是: 整数是0,小数是0.0,布尔值是false,字符值是0。

三、对象的使用

对象的使用指的是:使用对象的属性 和 使用对象的方法。

要想使用对象,必须先创建对象。

(1)对象的创建

对象创建的格式:

类名 对象名 = new 类名();

对象创建的示例:

Phone phone = new Phone();

如果在一个类中要使用别的类,分2种情况。

  1. 别的类和当前类在同一个包中。不需要导包。
  2. 别的类和当前类在不同的包中。需要导包。快捷键 Ctrl+Shift+O

(2)对象的使用

1. 使用对象的属性

使用对象的属性包括:给属性赋值、使用属性的值。

给对象的属性赋值格式:

对象名.属性名 =;

示例:

phone.brand = "iPhone";
phone.price = 6888;

使用对象的属性值格式:

数据类型 变量名 = 对象名.属性名;

示例:

String yourPhoneBrand = phone.brand;
int yourPhonePrice = phone.price;
2. 使用对象的方法

使用对象的方法指的就是调用对象的方法。

格式:

对象名.方法名();//如果方法有参数,在小括号内写上实参。如果方法有返回值,可以用变量接收返回值。

示例:

phone.call();//调用打电话方法
phone.sendMessage();//调用发短信方法

(3)测试类和对象

  1. 新建一个类(ClassTest)用于测试Phone对象的使用,在main方法中创建1部手机,测试其属性和方法。

  2. 代码:

    public class ClassTest {
    
    	public static void main(String[] args) {
    		//创建phone对象
    		Phone phone = new Phone();
    		//给brand、price赋值
    		phone.brand = "iPhone";
    		phone.price = 6888;
    		//打印手机的brand和price
    		System.out.println(phone.brand);
    		System.out.println(phone.price);
    		//打电话
    		phone.call();
    		//发信息
    		phone.sendMessage();
    	}
    
    }
    

四、对象在内存中的表示

(1)单个对象在内存中的表示

	public static void main(String[] args) {
		//创建phone对象
		Phone phone = new Phone();
		System.out.println(phone);
		//打印手机的brand和price
		System.out.println(phone.brand);
		System.out.println(phone.price);
		//给brand、price赋值
		phone.brand = "iPhone";
		phone.price = 6888;
		//打印手机的brand和price
		System.out.println(phone.brand);
		System.out.println(phone.price);
		//打电话
		phone.call();
		//发信息
		phone.sendMessage();
	}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-liqYsg9B-1615184582766)(11_面向对象编程/单个对象内存图1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zJa6eiFX-1615184582767)(11_面向对象编程/单个对象内存图2.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QcLtbtxm-1615184582780)(11_面向对象编程/单个对象内存图3.png)]

(2)多个对象在内存中的表示

	public static void main(String[] args) {
		// 创建phone对象
		Phone phone = new Phone();
		System.out.println(phone);
		// 打印手机的brand和price
		System.out.println(phone.brand);
		System.out.println(phone.price);
		// 给brand、price赋值
		phone.brand = "iPhone";
		phone.price = 6888;
		// 打印手机的brand和price
		System.out.println(phone.brand);
		System.out.println(phone.price);
		// 打电话
		phone.call();
		// 发信息
		phone.sendMessage();
		System.out.println("--------------");
		// 创建phone对象
		Phone phone2 = new Phone();
		System.out.println(phone2);
		// 打印手机的brand和price
		System.out.println(phone2.brand);
		System.out.println(phone2.price);
		// 给brand、price赋值
		phone2.brand = "华为";
		phone2.price = 2999;
		// 打印手机的brand和price
		System.out.println(phone2.brand);
		System.out.println(phone2.price);
		// 打电话
		phone2.call();
		// 发信息
		phone2.sendMessage();
	}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CbD6jkDV-1615184582788)(11_面向对象编程/单个对象内存图1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SkDlvKfY-1615184582790)(11_面向对象编程/单个对象内存图2.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FNREEASx-1615184582792)(11_面向对象编程/单个对象内存图3.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XxctG46C-1615184582793)(11_面向对象编程/多个对象内存图1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QZT5wJsh-1615184582796)(11_面向对象编程/多个对象内存图2.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dm2dwQ2W-1615184582801)(11_面向对象编程/多个对象内存图3.png)]

(3)多个对象指向相同堆区

	public static void main(String[] args) {
		// 创建phone对象
		Phone phone = new Phone();
		System.out.println(phone);
		// 打印手机的brand和price
		System.out.println(phone.brand);
		System.out.println(phone.price);
		// 给brand、price赋值
		phone.brand = "iPhone";
		phone.price = 6888;
		// 打印手机的brand和price
		System.out.println(phone.brand);
		System.out.println(phone.price);
		// 打电话
		phone.call();
		// 发信息
		phone.sendMessage();
		System.out.println("--------------");
		Phone phone2 = phone;
		System.out.println(phone2);
		// 打印手机的brand和price
		System.out.println(phone2.brand);
		System.out.println(phone2.price);
		// 给brand、price赋值
		phone2.brand = "华为";
		phone2.price = 2999;
		// 打印手机的brand和price
		System.out.println(phone2.brand);
		System.out.println(phone2.price);
		// 打电话
		phone2.call();
		// 发信息
		phone2.sendMessage();
	}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qEgkMRQX-1615184582804)(11_面向对象编程/单个对象内存图1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F5JwT5Pu-1615184582805)(11_面向对象编程/单个对象内存图2.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GUy12N48-1615184582807)(11_面向对象编程/单个对象内存图3.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s0DgkGVe-1615184582809)(11_面向对象编程/指向相同对象1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j7v0Wz7K-1615184582810)(11_面向对象编程/指向相同对象2.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1dO2mW7e-1615184582812)(11_面向对象编程/多个对象内存图3.png)]

new出来的内存区域才是真正的对象。

phone和phone2只是保存了对象的地址,相当于phone和phone2代表了对象。我们习惯把这种保存了对象地址的变量叫做对象。

如果多个变量指向同一个对象,那么任何一个变量都可以修改对象的内容。

五、类和对象练习

(1)学生类

  1. 需求:创建一个代表学生的类,并创建学生对象测试效果。属性只写出姓名、年龄即可,方法只写出学习、做作业即可。

  2. 代码:

    学生类代码:

    public class Student {
    	//属性
    	String name;	//姓名
    	int age;		//年龄
    	//方法
    	public void study() {
    		System.out.println(name + "正字努力学习Java面向对象");
    	}
    	
    	public void doHomework() {
    		System.out.println("十万代码成就十万年薪。" + name + "正在努力做练习,离10万年薪越来越近了");
    	}
    }
    

    测试代码:

    	public static void main(String[] args) {
    		Student stu = new Student();
    		stu.name = "马化腾";
    		stu.age = 22;
    		stu.study();
    		stu.doHomework();
    		
    		Student stu2 = new Student();
    		stu2.name = "李彦宏";
    		stu2.age = 19;
    		stu2.study();
    		stu2.doHomework();
    	}
    

实例变量(成员变量)和局部变量:

  1. 实例变量:定义在类中方法外的变量。

  2. 局部变量:在方法中定义的变量(包括方法的形参)。

    区别实例变量(成员变量)局部变量
    类中定义的位置不同类中方法外方法内(含形参)
    内存中位置不同堆内存栈内存
    生命周期不同随着对象的存在而存在,对着对象的消失而消失随着方法的调用而存在,随着方法的调用完毕而消失
    初始值不同有默认的初始值没有默认的初始值,必须先定义,定义时要给初始值。
    作用域1.类内;2.对象使用时,随着对象使用。只能在方法内使用,出了方法将不能使用。

(2)圆

  1. 设计一个代表圆的类。要求能计算圆的周长和面积。

  2. 代码:

    圆类代码:

    public class Circle {
    	//属性
    	double r;	//半径
    	
    	//方法
    	//计算周长
    	public double primeter() {
    		return 2 * 3.1415926 * r;
    	}
    	
    	//计算面积
    	public double area() {
    		return 3.1415926 * r * r;
    	}
    	
    }
    

    测试代码:

    	public static void main(String[] args) {
    		Circle c1 = new Circle();
    		c1.r = 10;
    		System.out.println(c1.primeter());
    		System.out.println(c1.area());
    		
    		Circle c2 = new Circle();
    		c2.r = 4;
    		System.out.println(c2.primeter());
    		System.out.println(c2.area());
    	}
    

(3)教师数组

  1. 定义一个教师类,教师包含工号(num)和工资(salary)两个属性。定义一个数组,保存10名教师的信息。教师的编号从100开始,教师的工资是[8k, 15k]之间的一个数。打印出薪资是12k的教师的信息;对教师按薪资从小到大进行排序。

  2. 代码:

    教师类代码:

    public class Teacher {
    	int num;	//工号
    	int salary;	//工资
    	
    	public void showInfo() {
    		System.out.println("工号:" + num + ",工资:" + salary);
    	}
    }
    

    测试代码:

    	public static void main(String[] args) {
    		// 创建一个数组,保存10个教师
    		Teacher[] teachers = new Teacher[10];
    		// 创建Random对象,用于随机工资。
    		Random random = new Random();
    		// 通过循环创建10个教师,并放入数组中。
    		for (int i = 0; i < teachers.length; i++) {
    			Teacher t = new Teacher();
    			t.num = i + 100;
    			t.salary = (random.nextInt(15 - 8 + 1) + 8) * 1000;
    			teachers[i] = t;
    		}
    
    		// 遍历数组
    		for (int i = 0; i < teachers.length; i++) {
    			teachers[i].showInfo();
    		}
    		System.out.println("-----------");
    		// 查找工资为12k的教师
    		for (int i = 0; i < teachers.length; i++) {
    			if (teachers[i].salary == 12000) {
    				teachers[i].showInfo();
    			}
    		}
    		System.out.println("-----------");
    		// 按薪资从小到大对教师进行排序。冒泡法
    		for (int i = 0; i < teachers.length - 1; i++) {
    			for (int j = 0; j < teachers.length - 1 - i; j++) {
    				if (teachers[j].salary > teachers[j + 1].salary) {
    					Teacher temp = teachers[j];
    					teachers[j] = teachers[j + 1];
    					teachers[j + 1] = temp;
    				}
    			}
    		}
    
    		// 遍历数组
    		for (int i = 0; i < teachers.length; i++) {
    			teachers[i].showInfo();
    		}
    	}
    
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-84JWDA6P-1615184582814)(11_面向对象编程/教师数组.png)]

六、面向对象编程、面向过程编程

面向对象编程(Object Oriented Programing)是相对于面向过程编程(Procedure Oriented Programing)而言的。它们是2种不同的编程思想。

(1)面向过程编程(POP)

面向过程编程是一种以功能为中心来进行思考和组织的编程方法,它关注的是功能如何实现,就是分析出解决问题所需要的的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个调用就可以了。----关注的是功能的实现

(2)面向对象编程(OOP)

面向对象编程是一种以事物(对象)为中心的编程思想,它关注的是完成某件事情需要哪些对象参与,这些对象应该具有什么样的属性和方法。通过对象的协作完成某件事情。----关注的是参与者(类)怎么设计

(3)面向对象编程和面向过程编程的区别

需求:把大象关进冰箱里
面向过程编程
  1. 编写函数实现打开冰箱门的功能

    编写函数实现把大象装进去的功能

    编写函数实现把冰箱门关闭的功能

  2. 在main函数中依次调用函数:打开冰箱门、把大象装进去、把冰箱门关上。

面向对象编程
  1. 分析把大象关进冰箱需要什么对象参与:大象、冰箱、人。

  2. 设计各个类

    public class 冰箱{
        public void 开门(){
            ...
        }
        
        public void 关门(){
            ...
        }
    }
    
    public class 大象{
        public void 进入(冰箱 icebox){
            ...
        }
    }
    
    public class{
        public void 打开(冰箱 icebox){
            icebox.开门();
        }
        
        public void 抬起(大象 elephant){
            ...
            elephant.进入(icebox);
            ...
        }
        
        public void 关闭(冰箱 icebox){
            icebox.关门();
        }
    }
    
  3. 在main方法中:

    创建人(张三),创建冰箱(海尔冰箱),创建大象(艾米丽)

    张三.打开(海尔冰箱);

    张三.抬起(艾米丽);

    张三.关闭(海尔冰箱);

无论面向过程,还是面向对象都能实现把大象装进冰箱这么一件事。从上述的过程中,我们不难发现,面向过程编程的核心是完成某件事;而面向对象的核心是设计参与者,最后才去完成具体的事。

项目种类越大,面向对象越有优势,而面向过程这越难以实现。例如:拍一个几分钟的小品,可以使用面向过程的思想,从头到尾设计每个细节。但拍一部电影,或者拍摄一部电视剧就得用面向对象的思想了,对每个参与者进行分工,各自干好自己的事情,镜头也未必从前往后拍摄,今天下雨可以先拍下雨的戏份,今天到场的演员多,可以拍摄演员多的戏份,最后进行剪辑拼接制作成电影和电视剧。

七、封装

面向对象语言有三大特性:封装、继承和多态

在讲解**封装(encapsulation)**的概念之前,我们先学习一下private关键字。

(1)private

先看下面的代码:

有一个Student类

public class Student {
	//实例变量
	String name;	//姓名
	int age;		//年龄
	
	//实例方法
	public void study() {
		System.out.println(name + "正在努力学习Java面向对象的知识。");
	}
	
	public void doHomework() {
		System.out.println(name + "在努力的写作业。十万行代码成就你十万年薪");
	}
	
	public void showInfo() {
		System.out.println("姓名:" + name + ",年龄:" + age);
	}
}

Student类创建了2个对象。

	public static void main(String[] args) {
		
		Student stu1 = new Student();
		stu1.name = "马化腾";
		stu1.age = 22;
		stu1.showInfo();
		
		Student stu2 = new Student();
		stu2.name = "李彦宏";
		stu2.age = -20;
		stu2.showInfo();
	}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XfFC7aCx-1615184582816)(11_面向对象编程/输出结果.png)]

上述的输出结果是没有错的,程序也正常执行的。但是人的年龄是-20是有点违背逻辑的。

之所以出现这种问题,是因为外界可以直接操作属性。想要避免这种问题,最好的办法是不让外界直接操作属性,把属性隐藏起来,给外界提供访问属性的方法。

1. 通过private把属性隐藏起来

private单词的含义是私有的。private可以修饰类中的属性和方法,修饰属性的时候,表示属性私有,外界无法直接访问(所谓的直接访问是通过对象名.属性名访问),但是在本类中可以访问。

public class Student {
	//实例变量
	private String name;	//姓名
	private int age;		//年龄
	
	//实例方法
	public void study() {
		System.out.println(name + "正在努力学习Java面向对象的知识。");
	}
	
	public void doHomework() {
		System.out.println(name + "在努力的写作业。十万行代码成就你十万年薪");
	}
	
	public void showInfo() {
		System.out.println("姓名:" + name + ",年龄:" + age);
	}
}

上述代码实现了属性的私有,一旦是有,外界将不能使用对象名.属性名去访问对象的属性。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LINNQysO-1615184582818)(11_面向对象编程/不能访问私有属性.png)]

2. 通过setter、getter方法访问属性

setter方法:为属性(实例变量)赋值的方法。

getter方法:获取属性(实例变量)值的方法。

由于getter、setter方法是我们特意为外界定义的方法,方便外界能访问属性,因此要有public修饰。

	public String getName() {
		return name;
	}

	public void setName(String n) {
		name = n;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int a) {
		age = a;
	}

setter方法既然是为属性赋值,就必须要有参数,而且参数的类型和属性的类型相同。setter方法的方法名是set+首字母大写的属性名。例如:属性名是name,setter方法的方法名是setName,属性名是age,setter方法的方法名是setAge。

getter方法既然是要获取属性的值,就必须有返回值,而且返回值的类型和属性的类型相同。getter方法的方法名是get+首字母大写的属性名。例如:属性名是name,getter方法的方法名是getName,属性名是age,getter方法的方法名是getAge。

提供了方法以后,外界就可以通过方法去访问属性。

	public static void main(String[] args) {
		
		Student stu1 = new Student();
		stu1.setName("马化腾");
		stu1.setAge(22);
		stu1.showInfo();
		
		Student stu2 = new Student();
		stu2.setName("李彦宏");
		stu2.setAge(-20);
		stu2.showInfo();
	}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6J61eZuA-1615184582819)(C:\Users\lx\Desktop\java\输出结果.png)]

输出结果还是-20,并没有解决任何问题呀!稍作改进,改进代码如下:

	public void setAge(int a) {
		if(a < 0) {
			System.out.println("您输入的年龄有误。");
		}else {
			age = a;
		}
	}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KArzP095-1615184582821)(11_面向对象编程/输出结果2.png)]

3. setter/getter和直接访问的异同

相同点:都是在访问属性,包括赋值和取值。

不同点:1.直接访问属性优势是简单快捷,一步到位;劣势是有可能会出现数据错误。2.通过setter/getter访问属性优势是数据没有直接赋值给属性,在赋值之前可以做一些操作和处理;劣势是代码量略大。

在开发中属性一般情况下都定义private,对外提供pulic的getter和setter方法。

(2)this

讲解this之前,先看一下刚才写的setter、getter方法

	public String getName() {
		return name;
	}

	public void setName(String n) {
		name = n;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int a) {
		if(a < 0) {
			System.out.println("您输入的年龄有误。");
		}else {
			age = a;
		}
	}

上面方法中的参数并没有做到见名知意。如果做到见名知意,应改为:

	public String getName() {
		return name;
	}

	public void setName(String name) {
		name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		if(age < 0) {
			System.out.println("您输入的年龄有误。");
		}else {
			age = age;
		}
	}

改成这样以后,确实见名知意了,但是程序的结果却不对了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tefwva7I-1615184582822)(11_面向对象编程/输出结果3.png)]

这是因为:在一个类中,如果局部变量和实例变量名称相同,在方法中使用变量的时候,使用的是局部变量,而不是实例变量。想要在方法中使用实例变量的话,需要使用this.实例变量名

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		if(age < 0) {
			System.out.println("您输入的年龄有误。");
		}else {
			this.age = age;
		}
	}

this的英文含义是这个,在代码中this是一个特殊的对象,它始终指的是调用方法的对象,即谁调用方法,this就是谁。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o2P2BnYk-1615184582824)(11_面向对象编程/this是谁.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tZMZzvLr-1615184582825)(11_面向对象编程/this是谁2.png)]

可以通过打印内存地址的方式,来检验this究竟是谁。

this关键字

  1. this修饰的变量用于指代实例变量。方法的形参如果与实例变量同名,不带this的变量是形参,而不是实例变量。方法的形参如果和实例变量不同名,不需要用this修饰。
  2. this代表的就是调用方法的那个对象。

(3)构造方法

构造方法是一种特殊的方法,它是创建对象时调用的方法,用于在创建对象的时候对属性进行初始化。

Student stu1 = new Student();//Student()就是一个构造方法。
1. 构造方法的特点
  1. 构造方法只能用在对象创建的时候。
  2. 构造方法的作用是对属性进行初始化。
  3. 构造方法的方法名必须和类名相同。
  4. 构造方法没有返回值,连void都不能写。
  5. 如果自己没有提供构造方法,系统会默认生成一个无参数的构造方法。
  6. 如果自己提供了任何一个构造方法,系统将不再生成无参数的构造方法。
  7. 构造方法可以重载。
2. 构造方法的书写格式
public 类名(参数列表){
    
}
3. 构造方法示例
	public Student() {
		
	}

	public Student(String name) {
		this.name = name;
	}
	
	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

上面的三个方法都是构造方法。

4. 构造方法的使用
public static void main(String[] args) {
	
	Student stu1 = new Student("马化腾",22);
	stu1.showInfo();
	
	Student stu2 = new Student("李彦宏");
	stu2.setAge(20);
	stu2.showInfo();
	
	Student stu3 = new Student();
	stu3.setName("马云");
	stu3.setAge(25);
	stu3.showInfo();
}

在开发过程中,我们通过会至少给2个构造方法。其中一个是无参构造方法,另外一个是全参的构造方法。除此以外根据需求添加别的构造方法。

(4)标准的Java类

1. 标准Java类的定义原则
  1. 属性用private修饰
  2. 提供属性对应的setter、getter方法
  3. 提供1个或多个构造方法
  4. 提供正常的功能性方法
2. 标准Java类示例
public class Circle {
	//属性
	private double r;	//半径
	
	public double getR() {
		return r;
	}

	public void setR(double r) {
		this.r = r;
	}

	public Circle() {
		
	}
	
	public Circle(double r) {
		this.r = r;
	}

	//实例方法
	public double perimeter() {
		return 2 * Math.PI * r;
	}
	
	public double area() {
		return Math.PI * r * r;
	}
}

使用圆的时候:

	public static void main(String[] args) {
		// 使用无参构造创建对象
		Circle c1 = new Circle();
		c1.setR(10);
		c1.perimeter();
		c1.area();
		
		// 使用有参构造创建对象
		Circle c2 = new Circle(4);
		c2.perimeter();
		c2.area();
	}

(5)类的练习(Fraction类)

  1. 需求:定义一个分数(Fraction)类,包含分子(numerator)和分母(denominator)2个属性。实现分数的加、减、乘、除、约分以及打印分数功能。

  2. 代码:

    public class Fraction {
    	private int numerator; // 分子
    private int denominator; // 分母
    
    	public int getNumerator() {
    		return numerator;
    	}
    
    	public void setNumerator(int numerator) {
    		this.numerator = numerator;
    	}
    
    	public int getDenominator() {
    		return denominator;
    	}
    
    	public void setDenominator(int denominator) {
    		this.denominator = denominator;
    	}
    
    	// 无参构造
    	public Fraction() {
    
    	}
    
    	// 全参构造
    	public Fraction(int numerator, int denominator) {
    		this.numerator = numerator;
    		this.denominator = denominator;
    	}
    
    	// 打印分数
    	public void showInfo() {
    		// 是否是负数
    		boolean isNegative = numerator * denominator < 0 ? true : false;
    
    		// 定义一个表示分数的字符串。
    		String fractionString = "";
    		if (isNegative == true) {// 如果是负数,加上负号
    			fractionString += "-";
    		}
    		fractionString += Math.abs(numerator);// 拼上分子的绝对值
    		fractionString += "/";// 拼上 /
    		fractionString += Math.abs(denominator);// 拼上分母的绝对值
    		System.out.println(fractionString);
    	}
    
    	// 约分
    	public void reduce() {
    		// 拿到分子分母的绝对值
    		int num1 = Math.abs(numerator);
    		int num2 = Math.abs(denominator);
    
    		// 定义一个变量接收最大公约数
    		int gcd = greatestCommonDivisor(num1, num2);
    
    		// 更新分子和分母
    		numerator /= gcd;
    		denominator /= gcd;
    	}
    
    	// 最大公约数 ----不想对外公开的方法,用private修饰。最大公约数只是约分的一个中间过程,不应该对外公开
    	private int greatestCommonDivisor(int x, int y) {
    		// 辗转相除法找最大公约数。
    		while (x % y != 0) {
    			int temp = x % y;
    			x = y;
    			y = temp;
    		}
    		return y;
    	}
    
    	// 求和
    	public Fraction add(Fraction fraction) {
    		// 求和后的分子
    		int num1 = this.numerator * fraction.denominator + this.denominator * fraction.numerator;
    		// 求和后的分母
    		int num2 = this.denominator * fraction.denominator;
    		Fraction result = new Fraction(num1, num2);
    		result.reduce();
    		return result;
    	}
    
    	// 求差
    	public Fraction minus(Fraction fraction) {
    		// 求差后的分子
    		int num1 = this.numerator * fraction.denominator - this.denominator * fraction.numerator;
    		// 求差后的分母
    		int num2 = this.denominator * fraction.denominator;
    		Fraction result = new Fraction(num1, num2);
    		result.reduce();
    		return result;
    	}
    
    	// 求积
    	public Fraction multiply(Fraction fraction) {
    		// 求积后的分子
    		int num1 = this.numerator * fraction.numerator;
    		// 求积后的分母
    		int num2 = this.denominator * fraction.denominator;
    		Fraction result = new Fraction(num1, num2);
    		result.reduce();
    		return result;
    	}
    
    	// 求商
    	public Fraction divide(Fraction fraction) {
    		// 求商后的分子
    		int num1 = this.numerator * fraction.denominator;
    		// 求商后的分母
    		int num2 = this.denominator * fraction.numerator;
    		Fraction result = new Fraction(num1, num2);
    		result.reduce();
    		return result;
    	}
    }
    

    private关键字,不但可以修饰属性,还可以修饰方法。被private的修饰的方法,不能被外界访问,但是在本类中是可以访问的。

    一般完整的功能用public修饰,不完整的功能(只是某功能的一个步骤)通常用private修饰。这样可以避免外界误用了不完整的功能,导致逻辑错误。

    测试代码:

    	public static void main(String[] args) {
    		// 定义一个分数 3/5
    		Fraction f1 = new Fraction(3, 5);
    		f1.showInfo();
    		// 定义一个分数-2/4
    		Fraction f2 = new Fraction(2, -4);
    		f2.reduce();// 约分
    		f2.showInfo();
    
    		// 计算 3/5 + (-2/4)的和
    		Fraction sum = f1.add(f2);
    		sum.showInfo();
    
    		// 计算 3/5 - (-2/4)的差
    		Fraction minus = f1.minus(f2);
    		minus.showInfo();
    
    		// 计算 (3/5) * (-2/4)的乘积
    		Fraction multiply = f1.multiply(f2);
    		multiply.showInfo();
    
    		// 计算 (3/5) / (-2/4)的商
    		Fraction divide = f1.divide(f2);
    		divide.showInfo();
    	}
    

(6)封装

封装:隐藏对象的内部细节,对外提供接口(访问方式)。

**封装的原则:**将类的某些信息隐藏在类内部,不允许外界直接访问,而是给外界提供接口(方法)。外界通过接口访问内部的数据以及类的功能。

  1. getter、setter封装了实例变量。

  2. 方法封装了功能的实现细节。

  3. 类封装了属性和方法。

封装的好处:

  1. 通过方法来控制实例变量的操作,提高了代码的安全性。

  2. 把代码用方法进行封装,提高了代码的复用性。

八、继承

**继承(inherit)**也是面向对象三大特性之一。

生活中的继承:某某继承了父亲百亿资产;某某继承了家族产业等等。

简单来看,继承就是获得了原本不属于自己的东西。在Java中亦是如此。

(1)教师、辅导员、学生

某公司正在开发一款高校教学、教务管理系统。其中涉及到教师(Teacher)、辅导员(Assistant)、学生(Student)的信息管理。抛开教师、辅导员、学生的增删改查,如何用Java类表示教师、辅导员、学生?

通过分析:我们需要3个类Teacher、Assistant、Student

Teacher类有姓名、性别、年龄、职称,能授课、能管理学生。

Assistant类有姓名、性别、年龄,能管理学生。

Student类有姓名、性别、年龄,能学习。

Teacher类:

public class Teacher {
	//属性
	private String name;	//姓名
	private String sex;		//性别
	private int age;		//年龄
	private String title;	//职称
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	
	//构造方法
	public Teacher() {

	}
	
	public Teacher(String name, String sex, int age, String title) {
		this.name = name;
		this.sex = sex;
		this.age = age;
		this.title = title;
	}
	
	//方法
	public void teach() {
		System.out.println(name + "老师正在认真的讲课");
	}
	
	public void manageStudents() {
		System.out.println("管理学生");
	}
    
    public void showInfo() {
		System.out.println(name + "," + sex + "," + age + "," + title);
	}
}

Assistant类:

public class Assistant {
	//属性
	private String name;	//姓名
	private String sex;		//性别
	private int age;		//年龄
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	//构造方法
	public Assistant() {
		
	}
	
	public Assistant(String name, String sex, int age) {
		this.name = name;
		this.sex = sex;
		this.age = age;
	}
	
	//方法
	public void manageStudents() {
		System.out.println("管理学生");
	}
    
    public void showInfo() {
		System.out.println(name + "," + sex + "," + age);
	}
}

Student类:

public class Student {
	//属性
	private String name;	//姓名
	private String sex;		//性别
	private int age;		//年龄
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	//构造方法
	public Student() {
		
	}
	
	public Student(String name, String sex, int age) {
		this.name = name;
		this.sex = sex;
		this.age = age;
	}
	
	//方法
	public void study() {
		System.out.println("Good good study, day day up");
	}
    
    public void showInfo() {
		System.out.println(name + "," + sex + "," + age);
	}
}

测试代码:

	public static void main(String[] args) {
		Teacher t = new Teacher("张三", "男", 32, "教授");
		t.showInfo();
		t.teach();
		t.manageStudents();
		
		Assistant a = new Assistant("李思思", "女", 27);
		a.showInfo();
		a.manageStudents();
		
		Student stu1 = new Student("张磊", "男", 21);
		stu1.showInfo();
		stu1.study();
		
		Student stu2 = new Student("李静", "女", 20);
		stu2.showInfo();
		stu2.study();
	}

通过观察,你会发现这3个类有部分特征和行为是相同的。都有姓名、性别、年龄属性,都有对应的setter、getter方法。如果要完整的表示这3个类,会更多的属性,更多的setter、getter方法。

如果一个项目比较大,有几百个类,要写多少代码?能不能优化?

(2)教师、辅导员、学生使用继承

Java提供了继承(inherit)功能。继承可以解决我们上述的问题。

父类:在继承关系里,被继承的类叫做父类,也叫基类或超类。

子类:在继承关系里,继承别的类的类叫子类,也叫派生类。

父类和子类是相对而言的。

1. 继承的语法
public class 类名 extends 父类名{
    //属性
    //方法
}

我们可以将上述案例中相同的特征和行为进行抽取,放到一个单独的类中,把这个类作为父类,再让现有的类继承于这个单独的类,这样就可以继承父类中的特征和行为。

公共父类Person

public class Person {
	//属性
	private String name;	//姓名
	private String sex;		//性别
	private int age;		//年龄
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
}

Teacher类

public class Teacher extends Person{
	//属性
	private String title;	//职称
	
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	
	//构造方法
	public Teacher() {

	}
	
	public Teacher(String name, String sex, int age, String title) {
		this.setName(name);
		this.setSex(sex);
		this.setAge(age);
		this.title = title;
	}
	
	//方法
	public void teach() {
		System.out.println(this.getName() + "老师正在认真的讲课");
	}
	
	public void manageStudents() {
		System.out.println("管理学生");
	}
    
    public void showInfo() {
		System.out.println(this.getName() + "," + this.getSex() + "," + this.getAge() + "," + title);
	}
}

Assistant类:

public class Assistant extends Person{
	//属性
	
	//构造方法
	public Assistant() {
		
	}
	
	public Assistant(String name, String sex, int age) {
		this.setName(name);
		this.setSex(sex);
		this.setAge(age);
	}
	
	//方法
	public void manageStudents() {
		System.out.println("管理学生");
	}
    
    public void showInfo() {
		System.out.println(this.getName() + "," + this.getSex() + "," + this.getAge());
	}
}

Student类:

public class Student extends Person{
	//属性
	
	//构造方法
	public Student() {
		
	}
	
	public Student(String name, String sex, int age) {
		this.setName(name);
		this.setSex(sex);
		this.setAge(age);
	}
	
	//方法
	public void study() {
		System.out.println("Good good study, day day up");
	}
    
    public void showInfo() {
		System.out.println(this.getName() + "," + this.getSex() + "," + this.getAge());
	}
}

测试代码:

	public static void main(String[] args) {
		Teacher t = new Teacher("张三", "男", 32, "教授");
		t.showInfo();
		t.teach();
		t.manageStudents();
		
		Assistant a = new Assistant("李思思", "女", 27);
		a.showInfo();
		a.manageStudents();
		
		Student stu1 = new Student("张磊", "男", 21);
		stu1.showInfo();
		stu1.study();
		
		Student stu2 = new Student("李静", "女", 20);
		stu2.showInfo();
		stu2.study();
	}

通过继承,代码明显少了很多,而且能实现继承前全部的功能。但美中不足的地方是,构造方法赋值比较麻烦,能不能优化的?

2. 父类、子类构造方法

子类可以从父类中继承属性和方法,无法继承构造方法,但可以调用父类中的构造方法为我们继承过来的属性赋值

Person类中添加构造方法:

public class Person {
	//属性
	private String name;	//姓名
	private String sex;		//性别
	private int age;		//年龄
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	//父类构造方法
	public Person() {
		
	}
	public Person(String name, String sex, int age) {
		this.name = name;
		this.sex = sex;
		this.age = age;
	}
}

Teacher类修改构造方法:

public class Teacher extends Person{
	//属性
	private String title;	//职称
	
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	
	//构造方法
	public Teacher() {

	}
	
	public Teacher(String name, String sex, int age, String title) {
		super(name, sex, age);
		this.title = title;
	}
	
	//方法
	public void teach() {
		System.out.println(this.getName() + "老师正在认真的讲课");
	}
	
	public void manageStudents() {
		System.out.println("管理学生");
	}
	
	public void showInfo() {
		System.out.println(this.getName() + "," + this.getSex() + "," + this.getAge() + "," + title);
	}
}

Assistant类修改构造方法:

public class Assistant extends Person{
	//属性
	
	//构造方法
	public Assistant() {
		
	}
	
	public Assistant(String name, String sex, int age) {
		super(name, sex, age);
	}
	
	//方法
	public void manageStudents() {
		System.out.println("管理学生");
	}
	
    public void showInfo() {
		System.out.println(this.getName() + "," + this.getSex() + "," + this.getAge());
	}
}

Student类修改构造方法:

public class Student extends Person{
	//属性
	
	//构造方法
	public Student() {
		
	}
	
	public Student(String name, String sex, int age) {
		super(name, sex, age);
	}
	
	//方法
	public void study() {
		System.out.println("Good good study, day day up");
	}
	
    public void showInfo() {
		System.out.println(this.getName() + "," + this.getSex() + "," + this.getAge());
	}
}

修改之后,代码仍然能正确的运行。

3. super关键字

super是一个特殊的关键字,这个关键字用于访问父类中的方法和属性(super相当于父类类名)。

注:private修饰的属性和方法无法直接访问(super.属性名或者super.方法名())。因为private修饰的属性和方法只能在本类中访问。

虽然不能访问,但肯定是继承了。

通过下面的案例学习一下super关键字的用法。

定义2个类,Son类和Father类,Son继承于Father。

Father类代码:

public class Father {
	//属性
	int a;
	private int b;
	
	//getter、setter
	public int getB() {
		return b;
	}
	public void setB(int b) {
		this.b = b;
	}
	//构造方法
	public Father() {
		
	}
	public Father(int a, int b) {
		this.a = a;
		this.b = b;
	}
	
	//打印对象信息
	public void showInfo() {
		System.out.println("a = " + a + ",b = " + b);
	}
	
	//私有方法--私有方法只能在类内部供类中别的方法调用,例如在publicMethod方法中访问
	private void privateMethod() {
		System.out.println("这是私有方法");
	}
	
	//公开方法,在这个方法中调用私有方法。
	public void publicMethod() {
		privateMethod();//等价于this.privateMethod();
	}
}

Son类代码:

public class Son extends Father {
	//属性
    private int a;
	private int c;
	
	//setter、getter
    public int getA() {
		return a;
	}

	public void setA(int a) {
		this.a = a;
	}
	public int getC() {
		return c;
	}

	public void setC(int c) {
		this.c = c;
	}

	//构造方法
	public Son() {
		super();
	}

	public Son(int a, int b) {
		super(a, b);
	}

	public Son(int a, int b, int c) {
		super(a, b);
		this.c = c;
	}
	
	//打印自身信息
	public void showInfo() {
		System.out.println("a = " + a + ",b = " + this.getB() + ",c = " + c);
	}
	
	//测试super调用父类方法
	public void testSuperMethod() {
		super.showInfo();
	}
	
	//测试super调用父类属性
	public void testSuperProperty() {
		System.out.println(a);
		System.out.println(this.a);
		System.out.println(super.a);
		//私有属性不能直接访问,因为private修饰的属性,只能在本类中直接访问。我们可以通过方法间接访问。
		//System.out.println(super.b);//报错
		System.out.println(super.getB());
	}
	
	//测试调用父类中的 私有方法
	public void testSuperPrivateMthod() {
		//私有方法不能直接访问,因为private修饰的方法,只能在本类中直接方法。我们可以通过方法间接访问。
		//super.privateMethod();//报错
		super.publicMethod();
	}
}
	public static void main(String[] args) {
		Son son = new Son(10,20,30);
		son.showInfo();//如果子类和父类有同名方法,调用的是自身的方法,不是父类继承过来的方法。
		son.testSuperMethod();//测试super调用父类方法
		son.testSuperPrivateMthod();//测试super调用父类私有方法
		son.testSuperProperty();//测试super调用父类属性
		son.publicMethod();//调用继承过来的方法
		son.setB(100);//调用继承过来的setter方法
		son.showInfo();
	}
  1. 通过super访问父类构造方法。语法:super(参数列表); 主要作用:给继承过来的属性赋初始值。通过super调用父类构造方法时,代码必须写在第一行。
  2. 通过super访问父类中的其他方法(非私有)。语法:super.方法名(参数列表);
  3. 通过super访问父类中的属性(非私有)。语法:super.属性名;
4. this关键字

前面的课程,我们讲了this关键是一个特殊的对象,代指当前对象,即谁调用方法,this就是谁。this可以用于区分实例变量和局部变量。

this除了用于区分实例变量和局部变量之外,还有以下2个功能:

  1. this调用本类的其他构造方法。语法:this(参数列表); 通过this调用本类构造方法时,代码必须写在第一行。
  2. this调用本类中其他的方法。(this可省略)
5. this和super的区别
比较事项thissuper
访问实例变量this.实例变量
访问本类中的实例变量
super.实例变量
访问父类中的实例变量(非私有)
访问实例方法this.实例方法(…)
访问本类中的实例方法
super.实例方法(…)
访问父类中的实例方法(非私有)
访问构造方法this(…)
调用本类中的构造方法,写在构造方法的第一行
super(…)
调用父类中的构造方法,写在构造方法的第一行
是否是对象是对象,代表当前对象不是对象,只是关键字

继承中构造方法的访问特点:如果子类构造方法中没有明确写出调用哪个父类构造方法,会默认调用父类的无参构造方法,即super(); -----即使你没有写super(),系统也会默认在第一行代码中调用super()。如果父类不提供无参构造方法,提供有参数的构造方法,子类会报错。

继承中实例方法的访问特点:1.首先在子类中查找要调用的方法,如果有直接调用;2.如果没有,在父类中查找要调用的方法,如果有就执行;3.如果没有,继续向上找父类,如果更上一级父类中有要调用的方法,就执行,如果没有继续向上找,直到根类Object,如果Object中也没有,就报错。

在继承中,子类对象的堆区内存里,既会有子类自身实例变量,也会有父类继承来的实例变量。如果子类和父类有同名的变量,堆内存会有2个变量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rbCicdq4-1615184582827)(11_面向对象编程/子类的堆区内存表示.png)]

6. 方法重写

方法重写:在继承关系里,如果子类中的某个方法和父类中的方法相同(方法名和参数也要相同),称为子类重写了父类的方法。子类重写父类方法往往是因为父类的方法满足不了子类的需求,所以子类才需要自己实现这个方法。

方法重写、方法重载的区别:

方法重载:在同一个类中,如果多个方法具有相同的方法名,但是参数个数或者参数类型不同(或都不同),这称为方法的重载。

方法重写:在继承关系里,父类的实现满足不了子类的需求,子类可以重新实现父类中定义的方法,这是方法重写。

如果子类重写了父类的方法,我们习惯上在重写的方法上面添加@Override注解。—注解是我们后面要学习的内容。@Override注解的作用是检测方法是否和父类中的方法相同。

(3)继承

继承是Java提供一种语法,这种语法允许子类得到父类的属性和方法。

继承能在保证程序功能不变的情况下,大大简化代码量,因此继承能帮我们更好的去设计类。

在Java中使用extends关键字实现继承。被继承的类叫做父类,或超类,或基类。

1. Java中继承的特点
  1. 一个类只能有一个父类。----不允许多继承
  2. 子类会继承父类全部的属性和方法(私有的也能继承,只是不能直接访问),无法继承构造方法。
  3. 如果要访问父类中的属性、方法、构造方法使用super关键字。
  4. 子类可以重写父类的方法。调用时默认调用子类的方法。
  5. 可以多层继承。A extends B,B extends C,这样A将拥有B和C中的内容
  6. 所有类的根类是Object。如果一个类没有继承任何类,默认继承Object类。
  7. 通常在子类的构造方法中调用父类的构造方法。

根类:没有父类的类。已经是最顶层的类了。

2. 继承的好处和弊端

继承的好处:

  1. 提高了代码的复用性(多个类相同的内容可以放到同一个类中)。
  2. 提高了代码的维护性(如果方法的代码需要修改,修改一处即可)。

继承弊端:

  1. 继承让类与类之间产生了关联,类的耦合性增强了,当父类发生变化时,子类也不得不跟着变化,削弱了子类的独立性。

什么时候使用继承?

  1. 继承体现的关系是: is a (是一个)
  2. 如果两个类A和B,如果他们在逻辑上满足A是B的一种,或者B是A的一种,就说明他们是继承关系,这个时候可以使用继承来体现,否则就是滥用继承。
  3. 例如:苹果和水果,苹果属于水果,可以让苹果类继承于水果类。再例如:苹果手机和手机,苹果手机是手机的一种,可以让苹果手机类继承于手机类。再例如:猫和狗,不能让猫继承于狗,也不能让狗继承于猫,而是定义一个动物类,他们都继承于动物。

(4)继承示例

  1. 需求:通过继承设计猫(Cat)和狗(Dog)类,并编写代码测试。猫有姓名和年龄属性,有抓老鼠的方法(catchMouse)。狗有姓名和年龄,有看门的方法(lookDoor)。

  2. 代码:

    父类Animal:

    public class Animal {
    	//实例变量
    	private String name;	//姓名
    	private int age;		//年龄
    	
    	//setter、getter
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public int getAge() {
    		return age;
    	}
    	public void setAge(int age) {
    		this.age = age;
    	}
    	//构造方法
    	public Animal() {
    		super();
    	}
    	public Animal(String name, int age) {
    		super();
    		this.name = name;
    		this.age = age;
    	}
    }
    

    Cat类:

    public class Cat extends Animal {
    
    	public Cat() {
    		super();
    	}
    
    	public Cat(String name, int age) {
    		super(name, age);
    	}
    	
    	public void catchMouse() {
    		System.out.println("抓老鼠");
    	}
    }
    

    Dog类:

    public class Dog extends Animal {
    
    	public Dog() {
    		super();
    	}
    
    	public Dog(String name, int age) {
    		super(name, age);
    	}
    	
    	public void lookDoor() {
    		System.out.println("看门");
    	}
    }
    

    测试代码:

    	public static void main(String[] args) {
    		Cat c1 = new Cat("花花", 2);
    		c1.catchMouse();
    		System.out.println(c1.getName() + "," + c1.getAge());
    		
    		Dog d1 = new Dog("旺财", 5);
    		d1.lookDoor();
    		System.out.println(d1.getName() + "," + d1.getAge());
    	}
    
  3. 需求:通过继承设计枪(Gun)和匕首(Dagger)类。枪和匕首都有名称、攻击力、价格。枪可以发射子弹,能更换弹夹;匕首可以攻击。

  4. 代码:

    公共父类Weapon:

    public class Weapon {
    	private String name;	//武器的名字
    	private int ap;			//攻击力
    	private int price;		//价钱
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public int getAp() {
    		return ap;
    	}
    	public void setAp(int ap) {
    		this.ap = ap;
    	}
    	public int getPrice() {
    		return price;
    	}
    	public void setPrice(int price) {
    		this.price = price;
    	}
    	public Weapon() {
    		super();
    	}
    	public Weapon(String name, int ap, int price) {
    		super();
    		this.name = name;
    		this.ap = ap;
    		this.price = price;
    	}
    	public void showInfo() {
    		System.out.println("武器名称:" + name + ",攻击力:" + ap + ",价格:" + price);
    	}
    }
    

    Gun类:

    public class Gun extends Weapon {
    
    	public Gun() {
    		super();
    	}
    
    	public Gun(String name, int ap, int price) {
    		super(name, ap, price);
    	}
    	
    	//发射子弹
    	public void fire() {
    		System.out.println("biubiu~biu");
    	}
    	
    	//更换弹夹
    	public void reload() {
    		System.out.println("更换弹夹");
    	}
    }
    

    Dagger类:

    public class Dagger extends Weapon {
    
    	public Dagger() {
    		super();
    	}
    
    	public Dagger(String name, int ap, int price) {
    		super(name, ap, price);
    	}
    	
    	//攻击
    	public void attack() {
    		System.out.println("砍敌人,刺敌人");
    	}
    	
    }
    

    测试代码:

    	public static void main(String[] args) {
    		
    		Gun gun = new Gun("AWM", 1000, 5000);
    		gun.showInfo();//调用继承的方法
    		gun.fire();
    		gun.reload();
    		
    		Dagger dagger = new Dagger("猛虎刀", 500, 2000);
    		dagger.showInfo();//调用继承的方法
    		dagger.attack();
    	}
    

九、多态

**多态(Polymorphism)**是面向对象三大特征的最后一个特征。

(1)什么是多态

多态:指的是对象的多态性,同一对象在不同时刻表现出来的不同形态。

例如:猫

我们可以说猫是猫猫 cat = new 猫();

也可以说猫是动物动物 animal = new 猫();

这里猫在不同的时刻表现出来了不同的形态,就是多态。

(2)程序中的多态

多态的前提:

  1. 有继承或者实现关系
  2. 有方法重写
  3. 父类引用指向子类对象(或子类的对象赋值给父类的引用)

实现关系:后面会学习接口(interface),类和接口就是实现关系,类实现接口。

实现关系也可以看成是继承关系

(3)多态示例

父类Animal:

public class Animal {
	public void eat() {
		System.out.println("吃东西");
	}
}

子类Cat:

public class Cat extends Animal{
	
	@Override
	public void eat() {//子类重写父类方法
		System.out.println("猫吃鱼");
	}
    
    public void catchMouse() {
		System.out.println("猫抓老鼠");
	}
}

测试类ClassTest:

public class ClassTest {
	public static void main(String[] args) {
		//正常使用:
		Animal animal = new Animal();
		animal.eat();
		
		Cat cat1 = new Cat();
		cat1.eat();
        cat1.catchMouse();
		System.out.println("----------")
		//多态:
		Animal animal2 = new Cat();//父类引用指向子类对象(或子类的对象赋值给父类的引用)
		animal2.eat();//执行Animal类的eat方法,还是执行Cat类的eat方法
        animal2.catchMouse();//此处会报错
	}
}

上面的Animal animal2 = new Cat();就是多态的体现,猫可以被当做动物来看待。

animal2.eat();这就是多态的使用。虽然猫可以当做动物来看待,但真正执行方法的时候,还是执行子类的方法。

animal2.catchMouse();会报错,当把猫当动物看的时候,编译器会查看动物类是否有catchMouse方法,发现没有,就会报错。

(4) 多态中成员访问的特点

父类Animal:

public class Animal {
	int a = 20;
	
	public void eat() {
		System.out.println("吃东西");
	}
}

子类Cat:

public class Cat extends Animal{
	int a = 100;
	int b = 200;
	@Override
	public void eat() {//重写父类的方法
		System.out.println("猫吃鱼");
	}
	
	public void catchMouse() {//子类独有的方法
		System.out.println("猫抓老鼠");
	}
}

测试类ClassTest:

public class ClassTest {
	public static void main(String[] args) {
		//正常使用:
		Animal animal = new Animal();
		animal.eat();
		
		Cat cat1 = new Cat();
		System.out.println(cat1.a);
		System.out.println(cat1.b);
		cat1.eat();
		cat1.catchMouse();
		System.out.println("--------------");
		//多态:
		Animal animal2 = new Cat();//父类引用指向子类对象(或子类的对象赋值给父类的引用)
		System.out.println(animal2.a);
//		System.out.println(animal2.b);//会报错
		animal2.eat();//执行Cat类的eat
//		animal2.catchMouse();//此处会报错。
	}
}

回顾:Java代码运行的流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YY3WPrkA-1615184582829)(11_面向对象编程/Java代码执行流程.png)]

编译期:Java源代码(.java)编译成字节码文件(.class)的时期。

运行期:运行字节码的时期。

成员编译期运行期
实例变量看等号左边(即父类)看等号左边(即父类)
实例方法看等号左边(即父类)看等号右边(即子类)

为什么实例变量和实例方法的访问不一样呢?

因为实例方法有重写,实例变量没有。

(5)多态的应用-参数多态

  1. 需求:定义一个Person类,完成饲养猫,饲养狗的功能。设计猫和狗类的时候,使用继承完成。

  2. 代码:

    父类Animal:

    public class Animal {
    	
    	public void eat() {
    		System.out.println("动物进食");
    	}
    }
    

    猫类Cat:

    public class Cat extends Animal{
    	@Override
    	public void eat() {
    		System.out.println("猫吃鱼");
    	}
    	
    	//抓老鼠
    	public void catchMouse() {
    		System.out.println("猫抓老鼠");
    	}
    }
    

    狗类Dog:

    public class Dog extends Animal {
    	@Override
    	public void eat() {
    		System.out.println("狗吃骨头");
    	}
    	
    	
    	//看门
    	public void lookDoor() {
    		System.out.println("狗看门");
    	}
    }
    

    人类Person:

    public class Person {
    	//养猫
    	public void raiseCat(Cat cat) {
    		cat.eat();
    	}
    	
    	//养狗
    	public void raiseDog(Dog dog) {
    		dog.eat();
    	}
    	
    	//养任何动物------方法的参数多态
    	public void raiseAnimal(Animal animal) {
    		animal.eat();
    	}
    }
    

    测试类ClassTest:

    public class ClassTest {
    
    	public static void main(String[] args) {
    		//创建猫和狗
    		Cat c = new Cat();
    		Dog d = new Dog();
    		
    		//创建人
    		Person p = new Person();
    		
    		//饲养猫,饲养狗----不用多态
    		p.raiseCat(c);
    		p.raiseDog(d);
    		System.out.println("---------");
    
    		//饲养猫,饲养狗----使用多态
    		p.raiseAnimal(c);//猫可以看做动物。raiseAnimal要求的参数是Animal(即父类型),我们把子类型赋值给了父类型,这就是多态。
    		p.raiseAnimal(d);//狗可以看做动物。raiseAnimal要求的参数是Animal(即父类型),我们把子类型赋值给了父类型,这就是多态。
    	}
    }
    

(6) 多态的应用-数组元素多态

  1. 需求:创建一个数组,存放猫和狗。注意数组只能存放相同数据类型的数据。

  2. 代码:

    猫类、狗类、动物类使用之前定义好的Cat、Dog以及Animal。

    测试代码:

    public class ClassTest {
    
    	public static void main(String[] args) {
    		//创建猫和狗
    		Cat c1 = new Cat();
    		Cat c2 = new Cat();
    		Cat c3 = new Cat();
    		Dog d1 = new Dog();
    		Dog d2 = new Dog();
    		Dog d3 = new Dog();
    		
    		Cat[] arr = new Cat[6];
    		arr[0] = c1;
    		arr[1] = c2;
    		arr[2] = c3;
    //		arr[3] = d1;//报错 Cat数组只能存放Cat对象,不能存放Dog对象
    //		arr[4] = d2;//报错 Cat数组只能存放Cat对象,不能存放Dog对象
    //		arr[5] = d3;//报错 Cat数组只能存放Cat对象,不能存放Dog对象
    		
    		Dog[] arr2 = new Dog[6];
    //		arr2[0] = c1;//报错 Dog数组只能存放Dog对象,不能存放Cat对象
    //		arr2[1] = c2;//报错 Dog数组只能存放Dog对象,不能存放Cat对象
    //		arr2[2] = c3;//报错 Dog数组只能存放Dog对象,不能存放Cat对象
    		arr2[3] = d1;
    		arr2[4] = d2;
    		arr2[5] = d3;
    		
    		//创建Animal数组存放猫和狗。---多态
    		Animal[] animals = new Animal[6];
    		animals[0] = c1;//把Cat看成Animal存入数组
    		animals[1] = c2;//把Cat看成Animal存入数组
    		animals[2] = c3;//把Cat看成Animal存入数组
    		animals[3] = d1;//把Dog看成Animal存入数组
    		animals[4] = d2;//把Dog看成Animal存入数组
    		animals[5] = d3;//把Dog看成Animal存入数组
    		for(int i = 0; i < animals.length; i++) {
    			Animal a = animals[i];
    			a.eat();//多态的对象在运行期,执行子类的方法。
    		}
    	}
    }
    

(7)多态的应用-数组元素或方法返回值多态

  1. 需求:用Animal数组存放Cat和Dog。遍历数组,如果元素是猫,执行抓老鼠的方法,如果是狗执行看门方法。

  2. 代码:

    猫类、狗类、动物类使用之前定义好的Cat、Dog以及Animal。

    测试代码ClassTest:

    public class ClassTest {
    
    	public static void main(String[] args) {
    		//创建猫和狗
    		Cat c1 = new Cat();
    		Cat c2 = new Cat();
    		Cat c3 = new Cat();
    		Dog d1 = new Dog();
    		Dog d2 = new Dog();
    		Dog d3 = new Dog();
    		
    		//创建Animal数组存放猫和狗。---多态
    		Animal[] animals = new Animal[6];
    		animals[0] = c1;//把Cat看成Animal存入数组
    		animals[1] = c2;//把Cat看成Animal存入数组
    		animals[2] = c3;//把Cat看成Animal存入数组
    		animals[3] = d1;//把Dog看成Animal存入数组
    		animals[4] = d2;//把Dog看成Animal存入数组
    		animals[5] = d3;//把Dog看成Animal存入数组
    		for(int i = 0; i < animals.length; i++) {
    			Animal a = animals[i];
    			if(a instanceof Cat) {
    				Cat c = (Cat)a;//强制类型转换
    				c.catchMouse();
    			}else if(a instanceof Dog) {
    				Dog d = (Dog)a;
    				d.lookDoor();
    			}
    		}
    	}
    }
    

    返回值多态:

    动物管理员AnimalManager:

    public class AnimalManager {
    	public Animal getAnimalByIndex(int index) {
    		Cat c1 = new Cat();
    		Cat c2 = new Cat();
    		Cat c3 = new Cat();
    		Dog d1 = new Dog();
    		Dog d2 = new Dog();
    		Dog d3 = new Dog();
    		Animal[] animals = new Animal[6];
    		animals[0] = c1;
    		animals[1] = c2;
    		animals[2] = c3;
    		animals[3] = d1;
    		animals[4] = d2;
    		animals[5] = d3;
    		
    		return animals[index];
    	}
    }
    

    测试代码:

    public class ClassTest {
    
    	public static void main(String[] args) {
    		
    		AnimalManager am = new AnimalManager();
    		Cat a = (Cat)am.getAnimalByIndex(0);
    		a.eat();
    		a.catchMouse();
    		Dog b = (Dog)am.getAnimalByIndex(3);
    		b.eat();
    		b.lookDoor;
        }
    }
    

    intanceof关键字的作用是:判断对象是不是某个类的实例。

    例如:a instanceof Cat是判断对象a是否是Cat类的实例,如果是就返回true,否则就是false

    如果a是Cat子类的实例,Cat继承于Animal,那么a instanceof Animal也是true。

    向上转型:子类类型转换为父类类型。即把子类对象赋值给父类引用。系统自动完成。

    向下转型:父类类型转换为子类类型。需要强制类型转换。

方法的返回值类型也可以使用多态(即父类型),实际返回的是子类对象。

例如:在Java桌面编程里,可以把控件添加到页面上,也可以从页面上获取组件。添加组件和获取组件用的就是多态。添加组件的方法可以把父类组件作为方法的参数,调用的时候,添加子类(按钮、输入框、复选框等),获取组件的方法返回值可以设置为组件类型,方法返回的是实际的组件(按钮,输入框,复选框等),通过强制类型转换,转成自己需要的类型。

(8)多态的好处和弊端

好处:提高了程序的扩展性,容易设计出通用的代码。

弊端:屏蔽了子类独有的功能。(不过,可以强转类型)

十、其他相关内容

(1)包(package)

1. 什么是包?

包本质上就是文件夹,作用是对类进行分类管理。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fR2DT9bn-1615184582831)(11_面向对象编程/包的定义格式.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pvhglZdC-1615184582832)(11_面向对象编程/类的存放位置.png)]

开发中,经常会出现同名的类,如果没有包对他们进行区分,他们是没有办法共存的。有了包,我们可以把同名的类放在不同的包里,这样就不会出现冲突。

2. 包的定义

包的定义格式:

package 包名;  //多级包用.分隔

示例:

package com.lanou.oop;

由于包是用于分类管理类的,因此类要定义在包中。即在类定义代码的上面,定义所在的包。

例如:

package com.lanou.weapon;//此处定义了Weapon位于哪个包中

public class Weapon {
	//属性
	private String name;	//名称
	private int ap;			//攻击力
	private int price;		//价钱
	//getter和setter
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAp() {
		return ap;
	}
	public void setAp(int ap) {
		this.ap = ap;
	}
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}
	//构造方法
	public Weapon() {
		super();
	}
	public Weapon(String name, int ap, int price) {
		super();
		this.name = name;
		this.ap = ap;
		this.price = price;
	}
	
	public void showInfo() {
		System.out.println("名称:" + name + ",攻击力:" + ap + ",价格:" + price);
	}
}
3. 导包

先看一个案例,通过案例了解什么是导包。

  1. 需求:Student类所属的包是com.lanou.bean,ClassTest类所属的包是com.lanou.main,即Student类和ClassTest类不在同一个包中,在ClassTest的main方法中创建Student对象,并测试Student对象的功能。

  2. 代码:

    Student类:

    package com.lanou.bean;
    
    public class Student {
    	public void study() {
    		System.out.println("Good good study, day day up!");
    	}
    }
    
    

    ClassTest类:

    package com.lanou.main;
    
    public class ClassTest {
    	public static void main(String[] args) {
    		Student stu = new Student();//此处代码会报错!原因是不认识Student类
    		stu.study();
    	}
    }
    

    修改后的ClassTest类:

    package com.lanou.main;
    
    public class ClassTest {
    	public static void main(String[] args) {
    		com.lanou.bean.Student stu = new com.lanou.bean.Student();//给出类的完整路径后,错误就消失了,而且程序可以正常运行。
    		stu.study();
    	}
    }
    

    简化方式,提前引入Student类。

    package com.lanou.main;
    
    import com.lanou.bean.Student;//引入Student类,这就是导包。或者import com.lanou.bean.*;
    
    public class ClassTest {
    	public static void main(String[] args) {
    		Student stu = new Student();
    		stu.study();
    	}
    }
    

导包的语法格式:

import 包名.类名;
或者
import 包名.*;

不推荐import 包名.*;

因为他是把包内所有的类都导入进来了。

导包的快捷键:Ctrl + Shift + O

这个快捷键能帮你管理包,添加缺少的包,删除多导入的包。

java.lang包下的类不用导包。

同一个包内的类不用导包

示例:

import com.lanou.bean.Student;

(2)访问修饰符

Java中提供了4种权限访问修饰符(从小到大):private、缺省、protected、public

4种修饰符可以修饰以及类的成员(属性、方法、构造方法)。

  1. 如果修饰类:只能使用public和缺省。

  2. 如果修饰类的成员:private、缺省、protected、public都可以。

1. 访问修饰符修饰类
public class A {
	
}

class B{
	
}

public修饰的类可以在任何地方使用(所属的包,以及其他包)。

缺省权限的类只能在本包中使用。

2. 访问修饰符修饰类的成员
  1. 在类内部,4种访问权限修饰的成员都可以访问。(指的是可以通过对象.属性名或者对象.方法名()这种形式访问属性和方法

    package com.lanou.p1;
    
    public class A {
    	private int a;
    	int b;
    	protected int c;
    	public int d;
    	
    	private void privateMethod() {
    		
    	}
    	
    	void method() {
    		
    	}
    	
    	protected void protectdMethod() {
    		
    	}
    	
    	public void publicMethod() {
    		
    	}
    	
    	private A() {
    		
    	}
    	
    	A(int x){
    		
    	}
    	
    	protected A(int x, int y) {
    		
    	}
    	
    	public A(int x, double y) {
    		
    	}
    	
    	
    	public void test() {
    		this.a = 100;
    		this.b = 200;
    		this.c = 300;
    		this.d = 400;
    		
    		A a1 = new A();
    		A a2 = new A(100);
    		A a3 = new A(100, 200);
    		A a4 = new A(100, 3.5);
    		
    		this.privateMethod();
    		this.method();
    		this.protectdMethod();
    		this.publicMethod();
    	}
    	
    }
    
  2. 在同包的其他类中,缺省、protected、public修饰的成员可以访问。(指的是可以通过对象.属性名或者对象.方法名()这种形式访问属性和方法

    public class TestA {
    
    	public static void main(String[] args) {
    		//A a1 = new A();//private修饰的构造方法在同包其他类中不能用!
    		A a2 = new A(100);
    		A a3 = new A(100, 200);
    		A a4 = new A(100, 3.5);
    		
    		//a4.a = 100;//private修饰的属性在同包的其他类中不能使用。
    		a4.b = 200;
    		a4.c = 300;
    		a4.d = 400;
    		
    		//a4.privateMethod();//private修饰的方法在同包其他类中不能使用
    		a4.method();
    		a4.protectdMethod();
    		a4.publicMethod();
    	}
    }
    
    package com.lanou.p1;
    
    public class SubA extends A {
    	public SubA() {
    		super(100);
    	}
    
    	public void test() {
    		//this.a = 100;//在同包子类中,不能访问private修饰的属性
    		this.b = 200;
    		this.c = 300;
    		this.d = 400;
    		
    		//A a1 = new A();//在同包子类中,不能访问private修饰的构造方法
    		A a2 = new A(100);
    		A a3 = new A(100, 200);
    		A a4 = new A(100, 3.5);
    		
    		//this.privateMethod();//在同包子类中,不能访问private修饰的方法
    		this.method();
    		this.protectdMethod();
    		this.publicMethod();
    	}
    }
    
  3. 不同包的子类中,protected、public修饰的成员可以访问。(指的是可以通过对象.属性名或者对象.方法名()这种形式访问属性和方法

    package com.lanou.p2;
    
    import com.lanou.p1.A;
    
    public class SubA extends A {
    
    	public SubA() {
    		super(100, 200);
    	}
    
    	public void test() {
    		//this.a = 100;
    		//this.b = 200;
    		this.c = 300;
    		this.d = 400;
    		
    		//A a1 = new A();//在不同包的子类中,private修饰的构造方法不能使用
    		//A a2 = new A(100);//在不同包的子类中,缺省的构造方法不能使用
    		A a3 = new A(100, 200);
    		A a4 = new A(100, 3.5);
    		
    		//this.privateMethod();//在不同包的子类中,private修饰的方法不能使用;
    		//this.method();//在不同包的子类中,缺省的方法不能使用;
    		this.protectdMethod();
    		this.publicMethod();
    //		a4.privateMethod();
    //		a4.method();
    		//a4.protectedMethod();
    		a4.publicMethod();
    	}
    
    }
    
  4. 不同包的无关类,public修饰的成员可以访问。(指的是可以通过对象.属性名或者对象.方法名()这种形式访问属性和方法

    package com.lanou.p2;
    
    import com.lanou.p1.A;
    
    public class ClassTest {
    	public static void main(String[] args) {
    		// A a1 = new A();//private修饰的构造方法在不同包其他类中不能用!
    		//A a2 = new A(100);//缺省的构造方法在不同包其他类中不能用!
    		//A a3 = new A(100, 200);
    		A a4 = new A(100, 3.5);
    
    		// a4.a = 100;//private修饰的属性在不同包的其他类中不能使用。
    		//a4.b = 200;//缺省的属性在不同包的其他类中不能使用。
    		//a4.c = 300;//protected修饰的属性在不同包的其他类中不能使用
    		a4.d = 400;
    
    		//a4.privateMethod();//private修饰的方法在不同包其他类中不能使用
    		//a4.method();//缺省的方法在不同包其他类中不能使用
    		//a4.protectdMethod();//protected修饰的方法在不同包其他类中不能使用
    		a4.publicMethod();
    	}
    }
    

访问修饰符修饰类的成员总结:

修饰符类内部同包类不同包子类不同包其他类
private
缺省
protected
public

在开发中,只会用到public和private,想私有就用private修饰,不想私有就用public。另外2种几乎不用。

一般类都用public修饰。属性用private修饰,getter、setter用public修饰,构造方法用public修饰,方法一般是public,个别不想暴露的方法用private。

(3)final关键字

final单词的含义是最终的,在Java中final可以修饰类,方法,变量(实例变量,局部变量)。

1. final修饰类

final修饰类:说明类是最终的类,即类不能有子类。换句话说就是类不能被继承。

package com.lanou.testfinal;

public final class A {

}
package com.lanou.testfinal;

public class B extends A{//会报错
	
}
2. final修饰方法

final修饰方法:说明方法是最终的方法,即方法不能被子类重写。

package com.lanou.testfinal;

public class AA {
	public final void test() {
		System.out.println("这是final修饰的方法,不允许被重写");
	}
}

package com.lanou.testfinal;

public class BB extends AA {
	public void test() {//此处会报错!
		System.out.println("这是子类实现");
	}
}
3. final修饰变量(实例变量、局部变量)

final修饰变量:说明变量是最终的,即变量的值不能发生变化。(如果变量没赋值,有一次赋值机会)。

package com.lanou.testfinal;

public class AAA {
	private final int A = 100;
	
	public void method() {
		A = 200;//此处会报错!
	}
}
package com.lanou.testfinal;

public class TestFinal {
	public static void main(String[] args) {
		final int A;
		A = 10;
		A = 20;//此处会报错
	}
}
package com.lanou.testfinal;

public class TestFinal {
	public static void main(String[] args) {
		final int A = 10;
		A = 20;//此处会报错
	}
}	

因为final修饰的变量,值不能发生变化,所以我们通常把final修饰的变量称为常量,变量名一般用纯大写命名。

(4)static关键字

static单词的含义是静态的。在Java中static可以修饰属性、方法、代码块、内部类。—代码块、内部类以及static修饰他们,后面讲解。

1. static修饰属性

如果属性用static修饰,那么这个属性不再是实例变量,而是类变量。

实例变量,即实例的变量,有多少个实例就有多少个变量

类变量,即类的变量,这个类所有的实例共用这个变量。

package com.lanou.teststatic;

public class Person {
	private String name;	//姓名
	private int age;		//年龄
	public static String nationality; //国籍
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public Person() {
		super();
	}
	
	public void showInfo() {
		System.out.println(name + "," + age + "," + nationality);
	}
}
package com.lanou.teststatic;

public class PersonTest {

	public static void main(String[] args) {
		Person p1 = new Person("张三", 21);
		p1.nationality = "中国";//p1设置国籍以后,p2也会是这个国籍
		p1.showInfo();

		Person p2 = new Person("李四", 20);
		p2.showInfo();
		p2.nationality = "美国";//p2改变国籍,p1也会改变国籍
		p2.showInfo();
		p1.showInfo();
	}
}

被static修饰的属性,既可用对象访问,也可以用类访问。类属性应该使用类去访问,不要使用对象去访问(尽管可以)。

package com.lanou.teststatic;

public class PersonTest {

	public static void main(String[] args) {
		Person p1 = new Person("张三", 21);
		p1.nationality = "中国";//p1设置国籍以后,p2也会是这个国籍
		p1.showInfo();

		Person p2 = new Person("李四", 20);
		p2.showInfo();
		p2.nationality = "美国";//p2改变国籍,p1也会改变国籍
		p2.showInfo();
		p1.showInfo();
		
		Person.nationality = "朝鲜";
		p2.showInfo();
		p1.showInfo();
	}
}
2. static修饰方法

如果方法用static修饰,那么这个方法不再是实例方法,而是类方法。

实例方法:实例的方法,即对象的方法。由对象来调用方法。

类方法:类的方法,这个类所有的对象共用这个方法。

package com.lanou.teststatic;

public class Person {
	private String name;	//姓名
	private int age;		//年龄
	public static String nationality; //国籍
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public Person() {
		super();
	}
	
	public void showInfo() {
		System.out.println(name + "," + age + "," + nationality);
	}
	
	//静态方法(类方法)
	public static void staticMethod() {
		System.out.println("这是静态方法。");
	}
}
package com.lanou.teststatic;

public class PersonTest {

	public static void main(String[] args) {
		Person p1 = new Person("张三", 21);
		p1.staticMethod();//p1可以调用静态方法
		Person p2 = new Person("李四", 20);
		p2.staticMethod();//p2也可以调用静态方法
	}
}

被static修饰的方法,既可以被对象访问,也可以被类访问**。类方法应该使用类去访问,不要使用对象去访问(尽管可以)。**

package com.lanou.teststatic;

public class PersonTest {

	public static void main(String[] args) {
		Person p1 = new Person("张三", 21);
		p1.staticMethod();//p1可以调用静态方法
		Person p2 = new Person("李四", 20);
		p2.staticMethod();//p2也可以调用静态方法
		
		Person.staticMethod();
	}
}

3. 静态方法中不能访问实例变量
package com.lanou.teststatic;

public class Person {
	private String name;	//姓名
	private int age;		//年龄
	public static String nationality; //国籍
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public Person() {
		super();
	}
	
	public void showInfo() {
		System.out.println(name + "," + age + "," + nationality);
	}
	
	//静态方法(类方法)
	public static void staticMethod() {
		System.out.println("这是静态方法。");
		System.out.println(name);//此处会报错!
        name = "王五";//此处会报错!
        showInfo();//此处会报错!
	}
}

静态方法中,访问实例变量会报错。之所以报错是因为,静态方法是类方法,不是实例方法,因此访问实例变量的时候,不知道是访问的是哪个对象的实例变量。

静态方法中,访问实例方法会报错。之所以报错是因为,静态方法中访问实例方法,不知道访问的是哪个对象的实例方法。

静态方法中,不能使用this。this是一个代表当前方法调用者的对象。因为类通常是用类名调用的,不是具体的对象,所以不能用this。

4. static总结
  1. static修饰的属性和方法被所有对象共享,即可以被对象访问。
  2. static修饰的属性和方法可以被类访问。推荐使用类去访问静态属性和静态方法。
  3. 静态方法中只能访问静态属性和静态方法。

(5)代码块、静态代码块

用{}括起来的代码叫做代码块

在Java中一共3种代码块:

  1. 局部代码块
  2. 初始化代码块
  3. 静态代码块。
1. 局部代码块

定义在方法体内的代码块叫局部代码块。

public class BlockTest {

	public static void main(String[] args) {
		int a = 10;
		{
			int b = 20;
			System.out.println(a);
            System.out.println(b);
		}
		//System.out.println(b);//此处会报错。
	}

}

上面的代码,在main方法中定义了一个局部代码块。代码块外定义的变量可以在代码块中使用,但代码块内定义的变量不能在代码块外使用,即代码块内的变量作用域仅限于代码块内。

**局部代码块平时很少使用。**switch…case中具体的case可以使用,可以避免多个case定义相同的变量出现重名的问题。

2. 初始化代码块

初始化代码块是定义在类中的代码块。它和属性、方法、构造方法属于是一个层级的东西。由于通常用它来做初始化,所以叫做初始化代码块。

public class Person {
	private String name;
	private int age;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public Person() {
		super();
		System.out.println("无参构造方法");
	}
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
		System.out.println("全参构造方法");
	}
	
	{
		name = "zhangsan";
		age = 20;
		System.out.println("这是一个初始化块!我 比 构造方法执行 先执行");
	}
	
	public void showInfo() {
		System.out.println("姓名: " + name + ",年龄:" + age);
	}
}

测试代码:

public class BlockTest {

	public static void main(String[] args) {
		Person p = new Person();
		p.showInfo();
		
		Person p2 = new Person("lisi", 22);
		p2.showInfo();
	}

}

初始化代码块作用

  1. 可以为属性赋值----等价于构造方法。
  2. 可以做公共初始化。----如果多个构造方法,他们有共同的代码,共同的代码可以提到初始化代码块中。

初始化代码块的特点

  1. 初始化代码块定义格式和方法类似,只不过只有{},没有参数,没有返回值。
  2. 初始化代码块不能主动调用,创建对象的时候自动执行,先于构造方法执行。
  3. 一个类中可以有多个初始化代码块,写在上面的初始化代码块先执行。尽管可以定义多个,一般最多定义一个。
3. 静态代码块

用static修饰的代码块称为静态代码块。

静态代码块是类的代码块,随着类的加载而调用,因为类只会加载一次,所以静态代码块也只执行一次。

public class StaticBlock {
	public static int a = 100;
	private int b;
	
	public int getB() {
		return b;
	}
	public void setB(int b) {
		this.b = b;
	}
	
	//静态代码块
	static {
		System.out.println("静态代码块");
	}
	
	//初始化代码块
	{
		System.out.println("初始化代码块");
	}

	//构造方法
	public StaticBlock(int b) {
		super();
		this.b = b;
		System.out.println("有参构造方法");
	}
	public StaticBlock() {
		super();
		System.out.println("无参构造方法");
	}
}

测试代码:

public class BlockTest {

	public static void main(String[] args) {
		int num = StaticBlock.a;
		System.out.println(num);
		
		StaticBlock sb1 = new StaticBlock();
		StaticBlock sb2 = new StaticBlock(20);
	}
}

静态代码块的特点:

  1. 静态代码块的书写格式和初始化代码块的格式类似,在前面加一个static关键字即可。
  2. 静态代码块不能主动调用,类加载的时候自动调用。因为类只加载一次,所以只调用一次。
  3. 静态代码块可以有多个,写在上面的先执行。一般最多只写一个。
  4. 静态代码块中不可以使用实例变量,不可以调用实例方法。
  5. 静态代码块比初始化代码块先执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hWisAu7l-1615184582835)(11_面向对象编程/静态代码块用于数据库连接池.png)]

4. 继承关系里,代码块执行顺序

父类Father:

public class Father {
	static {
		System.out.println("父类--静态代码块");
	}
	
	{
		System.out.println("父类----初始化代码块");
	}

	public Father() {
		super();
		System.out.println("父类------构造方法");
	}
}

子类Son:

public class Son extends Father{
	static {
		System.out.println("zi类--静态代码块");
	}
	
	{
		System.out.println("zi类----初始化代码块");
	}

	public Son() {
		super();
		System.out.println("zi类------构造方法");
	}
}

测试类:

public class BlockTest {

	public static void main(String[] args) {
		Son s = new Son();
		System.out.println("==========");
		Son s2 = new Son();
	}
}

在继承关系里,创建子类对象时,会先加载类,再创建对象。

加载类时,先加载父类,再加载子类。— 类只有首次用的时候才加载,加载之后就一直在内存中。

创建对象时,先执行父类的初始化代码块和构造方法,再执行子类的初始化代码块和构造方法。

(6)abstract关键字

abstract单词的含义是抽象。它可以用来修饰方法,也可以用来修饰类。被abstract修饰的类称为抽象类,被abstract修饰的方法称作抽象方法。

1. 抽象类

抽象类最大的特点就是不能实例化对象,即不能创建对象

public abstract class Animal {
	
}

测试代码:

public class ClassTest {

	public static void main(String[] args) {
		Animal a = new Animal();//会报错
	}

}
2. 抽象类有什么用?

有些时候,父类只是为了被子类继承,在用的时候,我们希望别人用子类,而不是用父类,这个时候,我们就可以把父类定义成抽象类。

例如:Animal只是表示动物,即使创建了对象也没有什么意义,我们更希望创建的是猫、狗等更具体的对象。为了防止别人误用,防止创建Aninal对象,我们可以通过abstract把Animal定义为抽象类,这样别人就无法创建Animal对象了。

3. 抽象类中可以定义属性、方法吗?

能!一个普通类能包含的内容,抽象类都能包含。

抽象类和普通类的唯一区别就是不能创建对象。—我不能创建对象,但是我东西可以被子类继承呀!

public abstract class Animal {
	//属性
	private String name;
	private int age;
    //常量
	public static final int TEST = 100;
	//静态属性
	public static int test;
	//setter、getter
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	//构造方法
	public Animal() {
		super();
	}
	public Animal(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	//静态代码块
	static {
		System.out.println("静态代码块");
	}
	
	//初始化代码块
	{
		System.out.println("初始化代码块");
	}
	
	//实例方法
	public void eat() {
		System.out.println("吃东西");
	}
	
	//静态方法
	public static void staticMethod() {
		System.out.println("静态方法");
	}
}
4. 抽象方法

抽象方法:被abstract修饰的方法。

抽象方法必须定义在抽象类中!

抽象方法不能有方法实现!

public abstract class Animal {
	public abstract void eat();
}
5. 抽象方法有什么用?

**抽象方法是专门为多态设计的!**是专门设计用来被子类重写的!

Animal做为动物类,它比较抽象,即使实现了eat方法,也会被子类重写,不如不实现。只定义出来有这个一个方法,但不实现,由具体的子类来实现。

如果一个类继承于抽象类,那么必须实现抽象类中所有的抽象方法!

或者自己也定义成抽象类,由自己的子类来实现抽象方法!

Cat类实现Animal类的抽象方法:

public class Cat extends Animal {

	@Override
	public void eat() {
		System.out.println("猫吃鱼");
	}

}

Cat类不实现Animal类的抽象方法:

public abstract class Cat extends Animal {

}

子类BosiCat实现抽象方法:

public class BosiCat extends Cat {

	@Override
	public void eat() {
		System.out.println("波斯猫吃鱼");
	}

}
6. 抽象类不能创建对象怎么办?

可以借助多态,用子类创建对象,抽象类的引用指向子类创建的对象。

public class ClassTest {

	public static void main(String[] args) {
		Animal a = new BosiCat();
		a.eat();
	}

}
7. 饲养宠物
  1. 需求:定义一个Person类,完成饲养猫,饲养狗的功能。设计猫和狗类的时候,使用抽象和继承完成。

  2. 代码:

    父类Animal:

    public abstract class Animal {
    	private String name;
    	private int age;
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public int getAge() {
    		return age;
    	}
    	public void setAge(int age) {
    		this.age = age;
    	}
    	public Animal() {
    		super();
    	}
    	public Animal(String name, int age) {
    		super();
    		this.name = name;
    		this.age = age;
    	}
    	
    	//声明抽象方法  eat
    	public abstract void eat();
    	
    	public void showInfo() {
    		System.out.println(name + "," + age + "岁");
    	}
    }
    

    Cat类:

    public class Cat extends Animal {
    
    	@Override
    	public void eat() {
    		System.out.println("猫吃鱼");
    	}
    
    	public Cat() {
    		super();
    	}
    
    	public Cat(String name, int age) {
    		super(name, age);
    	}
    
    }
    

    Dog类:

    public class Dog extends Animal {
    
    	@Override
    	public void eat() {
    		System.out.println("狗吃骨头");
    	}
    
    	public Dog() {
    		super();
    	}
    
    	public Dog(String name, int age) {
    		super(name, age);
    	}
    
    }
    

    Person类:

    public class Person {
    	public void raiseAnimal(Animal animal) {
    		animal.eat();
    	}
    }
    

    测试代码:

    public class ClassTest {
    
    	public static void main(String[] args) {
    		Animal a1 = new Cat("花花", 2);
    		Animal a2 = new Dog("旺财", 5);
    		a1.eat();
    		a2.eat();
    		System.out.println("----------");
    		Person p = new Person();
    		p.raiseAnimal(a1);
    		p.raiseAnimal(a2);
    	}
    }
    

总的来说:抽象类属于类的设计层面上的问题。它是为了设计类而存在,不是为了创建对象而存在。它最佳的使用场景就是多态。

抽象类声明子类必须要实现的方法(即自身的抽象方法),子类根据自己特征实现父类中声明的抽象方法。在创建和使用对象的时候,借助多态,用父类引用指向子类对象。这样可以充分发挥多态的优势。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值