2-Java面向对象-封装
面向对象的三大特征: 继承,封装,多态。
封装:
将类的某些信息隐藏在类内部, 不允许外部程序直接访问;通过该类提供的方法来实现对隐藏信息的操作和访问;
隐藏对象的信息 同时 留出访问的接口
生活中的案例:
ATM机,我们可以通过它存取款、转账、余额查询操作。钞票是ATM内的重要信息,但我们在外部是无法直接看到的,这就是ATM机对于钞票这个重要信息的隐藏。但是ATM拥有操作屏, 插卡口,取钞口等,用户通过简单的操作就可以实现隐藏钞票的操作。
特点:
- 只能通过规定的方法访问数据
- 隐藏类的实例细节,方便修改和实现
封装的代码实现(上)
private 加在属性上,表明这个属性只能在当前类内被访问。
实现步骤: 修改属性的可见性,设为private; 创建getter/setter方法,设为public,用于属性的读写;在getter/setter方法中加入属性控制语句(对属性值的合法性进行判断:性别只能是男和女啊,年龄只能是正值啊)
宠物猫的年龄应该是正数。
- 修改属性的可见性
private String name;
private int age;
private float weight;
private String species;
private表示这个属性只能在Cat类内部进行访问。
可以看到此时这些属性已经无法再类外直接通过对象进行访问了。
访问修饰符: private public
-
还有什么呢?
-
访问权限是怎样的?
-
设置公有的getter/setter方法隐藏; 在get/set方法中添加对属性的限定。
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
可以通过快捷键:
进行快速的生成。
public String getName() {
return "我是一只名叫"+name+"小猫";
}
里面可以加入自己的一些逻辑,包装。下面是封装之后的使用:
Cat oneCat = new Cat();
oneCat.setAge(10);
oneCat.setName("花花");
oneCat.setSpecies("中华田园猫");
oneCat.setWeight(1000);
System.out.println("年龄: "+ oneCat.getAge());
System.out.println("昵称: "+ oneCat.getName());
System.out.println("体重: " + oneCat.getWeight());
System.out.println("品种: " + oneCat.getSpecies());
运行结果:
封装代码实现下
注意只有getXXX方法的属性是只读属性;只有setXXX方法的属性是只写属性。下面我们在set方法中加入一些验证
public void setAge(int age) {
if (age <0){
System.out.println("猫的年龄必须大于0");
}else{
this.age = age;
}
}
// 调用
oneCat.setAge(-1);
可以通过异常处理优化程序,后面会详细介绍。如果在构造函数中不使用get set 而是直接赋值,可以正常运行,但是不推荐。因为写在get/set里的验证逻辑将不会被执行。
public Cat(int age){
this.age = age;
}
Cat oneCat = new Cat(-1);
System.out.println("年龄: "+ oneCat.getAge());
因此推荐在构造方法中也同样使用get/set
编程练习
编写自定义类实现图书信息设置,运行参考效果如下所示:
属性: 书名、作者、出版社、价格
方法: 信息介绍
package cn.mtianyan.book;
public class Book {
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getPulishingHouse() {
return pulishingHouse;
}
public void setPulishingHouse(String pulishingHouse) {
this.pulishingHouse = pulishingHouse;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
if(price < 10){
System.out.println("图书价格最低10元");
this.price =10;
}else{
this.price = price;
}
}
private String title;
private String author;
private String pulishingHouse;
private float price;
public Book(String title,String author,String pulishingHouse,float price){
this.setTitle(title);
this.setAuthor(author);
this.setPulishingHouse(pulishingHouse);
this.setPrice(price);
}
public void info(){
System.out.println("书名: " + this.getTitle());
System.out.println("作者: " + this.getAuthor());
System.out.println("出版社: " + this.getPulishingHouse());
System.out.println("价格: " + this.getPrice());
}
}
package cn.mtianyan.book;
public class BookTest {
public static void main(String[] args) {
Book one = new Book("红楼梦","曹雪芹","人民文学出版社",6);
Book two = new Book("小李飞刀","古龙","中国长安出版社",55.5f);
one.info();
System.out.println("===================");
two.info();
}
}
使用包进行类管理
文件夹进行文件管理,同一个文件中可以存放多个不同的文件,同名的文件只能存放在不同的文件夹中。
在Java中如何进行不同类文件的管理呢:java中我们通过包来管理java文件,解决同名文件冲突。
Java中一个包里不能存在同名类
域名倒序+模块+功能(域名全部小写)
package cn.mtianyan.book;
包的定义必须放在Java源文件中的第一行,定义类时,我们应该遵循单一职责原则,因此在建立包的时候,建议每个包内存储信息功能单一。
如何实现跨包的类调用?有几种调用形式?我如何告诉编译器我要调用的是哪个包里的Cat呢?
import cn.mtianyan.animal.*; // 加载包下的所有类
import cn.mtianyan.animal.cat; // 加载指定包下的指定类
建议采用 import包名.类名
的方式加载,提高效率
可以直接在程序代码中加载cn.mtianyan.animal.cat
cn.mtianyan.animal.Cat cat = new cn.mtianyan.animal.Cat();
加载类的顺序跟import导入语句的位置无关,具体包的指定,优先级大于通配符。
import cn.mtianyan.*
import包名.*
只能访问指定包名下的类,无法访问子包下的类
包的作用:
- 管理Java文件
- 解决同名文件冲突
定义包:
语法: package 包名;例: package cn.mtianyan.animal;
注意: 必须放在Java源文件中的第一行;-个Java源文件中只能有-个package语句;包名全部英文小写;命名方式:域名倒序+模块+功能
导入包语法:
import 包名.类名;
例:导入包中全部类: import cn.mtianyan.*;导入包中指定类: import cn.mtianyan.animal.Cat;
tips(常用系统包):
包名 | 内容 |
---|---|
java.lang | 包含Java语言基础的类,该包系统加载时默认导入 如:System、String、Math |
java.util | 包含Java语言中常用工具 如: Scanner、Random |
java.io | 包含输入、输出相关功能的类 如: File、InputStream |
编程练习
编写自定义类实现用户信息类.
程序参考运行效果图如下:
任务
用户类: 属性(用户名、密码)
用户管理类: 方法(用户信息验证)
package cn.mtianyan.user;
public class User {
private String username;
public User(String username, String password) {
this.setUsername(username);
this.setPassword(password);
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
private String password;
}
package cn.mtianyan.user;
public class UserManage {
public String checkUser(User one,User two){
System.out.println("用户名: "+one.getUsername());
System.out.println("密码: "+one.getPassword());
System.out.println("用户名: "+two.getUsername());
System.out.println("密码: "+two.getPassword());
System.out.println("=================");
if (one.getUsername().equals(two.getUsername())){
return "用户名一致";
}else {
return "用户名不一致";
}
}
}
package cn.mtianyan.user;
public class Test {
public static void main(String[] args) {
User one = new User("Lucy","123456");
User two = new User("Mike","123456");
UserManage userManage = new UserManage();
System.out.println(userManage.checkUser(one,two));
}
}
运行结果:
Static关键字(上)
static表示静态信息,它在Java程序中可以和喞些信息組合使用?用它修饰的信息具有哪些特征呢?
public float price; //cat类中
Cat one = new Cat();
one.price = 2000;
Cat two = new Cat();
two.price = 150;
上述代码的结果显然脑补都可以脑补到.但假如我们为价格添上static修饰
public static int price; //售价
添加了static修饰之后,price变成了斜体。
可以看到在main中取值时,提示我们静态的成员变量,应该以一种静态的访问方式访问。此时的运行结果中,两只猫的价格都变成了150。
Static表示的是一个静态的,修饰成员时叫做一个静态成员或类成员,它是属于这个类所有的。无论这个类实例化出多少的对象,它都会共用同一块内存空间。
后赋值的会将前面的内存中的内容覆盖掉; 类对象共享;类加载时产生,销毁时释放,生命周期长
one.price = 2000;
Cat.price = 3000;
既可以通过对象名,也可以通过类名来访问。
Static关键字中
Static 加在属性前面,被称为静态属性,类属性。同理,Static添加到方法前面就从普通方法,变成了类方法。
public static void eat(){
System.out.println("小猫吃鱼");
}
类方法的调用和我们的静态成员变量的访问时一样的,既可以通过类,也可以通过对象。
oneCat.eat();
Cat.eat();
推荐调用方式:类名.静态成员
除了加在属性,方法,static还可以加在哪些地方呢?
不能加载类名前,类名前仅仅允许添加: public abstract final; 因此static + 类 = 不存在的
方法中我们可以定义局部变量,
但是局部变量的前面也是不允许加static的,局部变量仅仅被允许添加final
普通的成员方法是可以访问当前对象的成员变量和方法的。
public void run(){
eat(); // 可以调用静态方法
this.name = "妞妞";
this.price = 200; // 普通方法也是可以访问静态成员的
}
public static void eat(){
run();
age = 10;
price = 200;
}
可以看到静态方法中是不能访问非静态成员方法和成员属性的,因为它缺少隐含的this参数。
如果非得在静态方法内来访问非静态成员方法呢?
public static void eat(){
Cat cat = new Cat();
cat.run();
cat.age = 10;
price = 200;
}
静态方法中不能直接访问同一个类中的非静态成员,只能直接调用同一个类中的静态成员;非要访问,只能通过对象实例化后,对象.成员方法的方式访问非静态成员。
static关键字(下)
关于代码块的相关知识,java中代码是由大括号括起来的。语句中出现大括号对就叫代码块。
当出现在方法里的时候,叫做普通代码块。普通代码块和一般语句的执行顺序是一样的。
public void run(){
{
System.out.println("我是普通代码块1");
}
System.out.println("小猫在跑");
{
System.out.println("我是普通代码块2");
}
}
普通代码块:顺序执行,先出现, 先执行
当代码块直接在类中定义,与成员方法,属性并列,就被称为是构造代码块。
{
System.out.println("我是构造代码块1");
}// 构造代码块
可以看到,构造代码块比构造函数还先执行。构造代码块:创建对象时调用,优先于构造方法执行。不管构造方法块放在类里面的前后,它都会先于构造函数执行。
多个构造代码块之间有先后顺序,但都先于构造函数执行。
{
System.out.println("我是构造代码块1");
}
// 中间吧啦吧啦一大堆
{
System.out.println("我是构造代码块2");
}
在构造代码块前面加上static修饰,它就会变成静态代码块。
{
System.out.println("我是构造代码块1");
}
// 中间吧啦吧啦一大堆
static {
System.out.println("我是位于后面的静态代码块");
}
猜猜谁先执行呢?当时是先有类再有对象。静态的先执行。
Cat oneCat = new Cat(-1);
Cat twoCat = new Cat(3);
可以看到,当实例化多个对象的时候,静态代码块只会执行一次,而构造代码块是实例化了多少个对象就执行多少次。
静态代码块: 类加载时调用,优先于构造代码块执行。一个类中也可以有多个静态代码块,它们的执行顺序显然是按照先后顺序了,就不举例子了。无论产生多少类实例,静态代码块只执行一次。
{
name = "喵喵";// 非静态
price = 100; // 静态
System.out.println("我是构造代码块1");
}
如图前面所学的一样,构造代码块中可以访问静态 & 非静态成员。静态代码块中只能访问静态成员。
仅希望执行一次的代码可以放在静态代码块中
编程练习
请根据效果图以及任务要求完成代码。
程序参考运行效果图如下:
任务
创建类Code,类中编写构造块、静态代码块以及构造函数; 创建CodeBlock,类中编写的构造块、静态代码块以及构造函数。
在主函数中测试他们的执行的优先顺序
package cn.mtianyan.code;
public class Code {
{
System.out.println("Code的构造块");
}
public Code(){
System.out.println("Code的构造方法");
}
static {
System.out.println("Code的静态代码块");
}
}
package cn.mtianyan.code;
public class CodeBlock {
{
System.out.println("CodeBlock的构造块");
}
public CodeBlock(){
System.out.println("CodeBlock的构造方法");
}
static {
System.out.println("CodeBlock的静态代码块");
}
public static void main(String[] args) {
CodeBlock codeBlock;
System.out.println("CodeBlock的主方法");
System.out.println("产生Code类实例对象");
Code code = new Code();
System.out.println("产生CodeBlock实例对象");
codeBlock = new CodeBlock();
}
}
运行结果:
static关键字(续)
public void run(){
{
System.out.println("我是普通代码块1");
}
System.out.println("小猫在跑");
{
System.out.println("我是普通代码块2");
}
}
run方法里面有两个代码空间,代码块1和代码块2
一个方法中是不能出现同名变量的,比如run中
public void run(){
int temp =10;
int temp =12;
}
public void run(){
// int temp =10;
{
int temp = 11;
System.out.println("我是普通代码块1");
}
System.out.println("小猫在跑");
{
int temp = 12;
System.out.println("我是普通代码块2");
}
}
此时是正常的,两个代码块空间中允许有自己的变量值,不会重名造成冲突。但是不能在方法的全局域也就是注释的位置添加同名变量,会与两个变量都造成重复定义的问题。
temp = 14是从它定义一直作用到结束的。
public void run(){
// int temp =10;
{
int temp = 11;
System.out.println("我是普通代码块1");
}
{
int temp = 12;
System.out.println("我是普通代码块2");
}
int temp = 13;
System.out.println("小猫在跑");
}
编程实践
package cn.mtianyan.ming;
public class Student {
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
Student.age = age;
}
public static int age;
}
package cn.mtianyan.ming;
public class StudentTest {
public static void main(String[] args) {
Student stu = new Student();
stu.setName("小红");
stu.setAge(13);
Student stu1 = new Student();
stu1.setName("小明");
stu1.setAge(18);
System.out.println(stu.getName()+"今年"+stu.getAge()+"岁了!");
System.out.println(stu1.getName()+"今天"+stu.getAge()+"岁了!");
}
}
运行结果:
总结
封装: 通过该类提供的方法来实现对隐藏信息的操作和访问;隐藏对象的信息 & 留出访问的接口
特点: 1. 只能通过规定的方法访问数据 & 2. 隐藏类的实例细节,方便修改和实现
合理的封装符合我们的使用习惯: 我们只需知道如何使用,并不需要深究它的内部实现构造。
java中三个步骤实现封装:
修改属性的可见性(设为private);创建getter/setter方法设为public用于属性的读写;在getter/setter方法中加入属性控制语句对属性值的合法性进行判断。
第一步是实现了内部重要信息的隐藏,二三步是留出了对外访问的接口。
包的作用: 1. 管理Java文件 2. 解决同名文件冲突
定义包: 语法 package 包名;
注意: 1. 必须放在Java源文件中的第一行 2. 一个Java源文件中只能有一个package语句 3. 包名全部英文小写4. 命名方式:域名倒序+模块+功能
跨包访问的语法: import 包名.类名;
例:
// 导入包中全部类
import cn.mtianyan.*;
// 导入包中指定类:
import cn.mtianyan.animal.Cat;
通配符只能导入当前mtianyan包下的类,不能导入它的子包如(animal)的类。
static
- static+属性 2. static+方法 3. static+类 4. static+方法内局部変量 5. static+代码块
静态属性、类属性 & 静态方法、类方法; 不存在静态类,不存在方法中的静态局部变量;
{}来隔离一个代码块出来,存在于方法当中称之为普通代码块;存在于类当中时被称为构造代码块; 当构造代码块前面加上static关键字的时候就变成了静态代码块。
注意问题:
- 静态成员的生命周期: 类加载时产生,销毁时释放,生命周期长
- 静态方法中的成员调用:可以直接访问类内的静态成员,不可以直接访问类内的非静态成员。非得访问,只能在方法内实例化对象再通过对象访问。
- 各种代码块的执行顺序: 静态代码块只执行一次,构造代码块在每次对象构造的时候调用,普通代码块在调用方法时使用。
在下一集中,我们将通过一个综合案例,带领大家进一步学习“封装”在面向对象编程中的应用。