【JAVA 核心编程】面向对象中级:封装与访问控制
一、包
1)包的本质
实际上就是创建不同的文件夹来保存类文件
2)包的命名
只能包含数字,字母,下划线,小圆点,但不能用数字开头,不能是关键字或保留字
一般是小写字母+小圆点
3)常用的包
java.lang.*//lang包是基本包,默认引用,不需要再引入java.util.*//util包,系统提供的工具包,工具类,使用Scannerjava.net.*//网络包,网络开发java.awt.*//是做java界面的界面开发,GUI
4)引用的包
语法:import 包
import java.util.Arrays;
public class Main{public static void main(String[] args){int[] arr = {-1,20,2,19,5};Arrays.sort(arr);for(int i = 0;i < arr.length;i++)System.out.print(arr[i] + "\t");}
}
5)注意事项和使用细节
- package 的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package
- import指令 位置放在package的下面,在类定义前面,可以有多句且没有顺序要求
二、访问修饰符
四种访问修饰符
1.公开级别:用public 修饰,对外公开
2.受保护级别:用protected修饰,对子类和一个包中的类公开
3.默认级别:没有修饰符号,向同一个包的类公开
4.私有级别:用private 修饰,只有类本身可以访问,不对外公开
三、封装
1)封装介绍
封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作【方法】封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作[方法],才能对数据进行操作。
2)封装好处
- 隐藏实现细节:方法(连接数据库)<–调用(传入参数.)
- 可以对数据进行验证,保证安全合理
Person {name, age}
Person p = new Person();
p.name = "jack";
p.age = 1200;
3)封装的实现步骤
- 将属性进行私有化private 【不能直接修改属性】
- 提供一个公共的(public)set方法,用于对属性判断并赋值
public void setXxx(类型 参数名){//Xxx 表示某个属性//加入数据验证的业务逻辑属性 = 参数名;
}
- 提供一个公共的(public)get方法,用于获取属性的值
public 数据类型 getXxx(){ //杈限判断,Xxx 某个属性return xx;
}
4)get与set
1.灵活性
比如我们有一个Person类,我们给它设置一个属性name,但是我们希望在取名字的时候,不是只显示名字,而是把名字按我们的要求输出,比如”我的名字叫XX”
我们只要定义一个person对象,并给他setName(xx),再getName的时候,就会显示我的名字叫XX。在实际业务中还可以有更灵活的运用,这里就不举例了。
2.安全性
只读,还是只写,还是可读写,这里的读写就好比get/set。
对于类来说,如果直接用public定义某个属性,那么这个属性是可读可写的,如果你希望一个类的某个属性是只能读取,不能写入的时候,上面用public定义某个属性就不能满足了,我们可以使用private定义属性,再定义一个get方法,但是不定义set方法来实现。反之,只定义set,不定义get则表示该属性是只写不能读的。
public class Main {public static void main(String[] args) {Person person = new Person();person.setName("jack2222222");person.setAge(22);person.setSalary(30000);System.out.println(person.infp());}
}//函数封装
class Person{public String name;;private int age;;private double salary;public String getName() {return name;}public void setName(String name) {if(name.length() >=2 && name.length() <= 6){this.name = name;}else{System.out.println("名字长度不对,需要(2-6)个字符,默认无名人");this.name = "无名人";}}public int getAge() {return age;}public void setAge(int age) {if(age>=1&&age<=120){this.age = age;}else{System.out.println("年龄需要在1-120");this.age = 18;//设置默认年龄}}public double getSalary() {return salary;}public void setSalary(double salary) {this.salary = salary;}public String infp(){return name + age + salary;}
}
5)封装与构造器
public class Main {public static void main(String[] args) {Person person = new Person();person.setName("jack2222222");person.setAge(22);person.setSalary(30000);System.out.println(person.infp());Person smithwww = new Person("smithwww", 28, 2000);System.out.println(smithwww.infp());}
}//函数封装
class Person{public String name;;private int age;;private double salary;//添加构造器public Person(){}****************************************************public Person(String name, int age,double salary ) {//这样并不能起到防护机制/*this.salary = salary;this.age = age;this.name = name;*///所以将set方法写在构造器中。仍可以验证数据,起到防护机制setName(name);setAge(age);setSalary(salary);}****************************************************public String getName() {return name;}public void setName(String name) {if(name.length() >=2 && name.length() <= 6){this.name = name;}else{System.out.println("名字长度不对,需要(2-6)个字符,默认无名人");this.name = "无名人";}}public int getAge() {return age;}public void setAge(int age) {if(age>=1&&age<=120){this.age = age;}else{System.out.println("年龄需要在1-120");this.age = 18;//设置默认年龄}}public double getSalary() {return salary;}public void setSalary(double salary) {this.salary = salary;}public String infp(){return name + age + salary;}
}
6)实例
public class Main {public static void main(String[] args) {Account account = new Account();account.setName("jack");account.setBalance(500);account.setPwd("243567");account.Showfo();}
}
//函数封装
class Account{private String name;private double balance;private String pwd;public String getName() {return name;}public void setName(String name) {if(name.length() >= 2 && name.length() <= 4){this.name = name;}else{System.out.println("名字格式不对,默认姓名为HELL");this.name = "HELL";}}public String getPwd() {return pwd;}public void setPwd(String pwd) {if(pwd.length() == 6){this.pwd = pwd;}else{System.out.println("密码格式不对,默认密码123456");this.pwd = "123456";}}public double getBalance() {return balance;}public void setBalance(double balance) {if(balance > 0){this.balance = balance;}else{System.out.println("余额不足");this.balance = 0;}}public void Showfo() {System.out.println("姓名:" + name + " 余额:" + balance + " 密码:" + pwd);}
}
四、继承
提出代码复用的问题:
我们编写了两个类,一个是Pupil 类(小学生),一个是Graduate(大学毕业生)问题:
两个类的属性和方法有很多是相同的,怎么办?
=> 继承(代码复用性~)
1)继承基本介绍和示意图
继承可以解决代码复用,让我们的编程更加靠近人类思维,
当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,
所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可。画出继承的示意图
继承的基本语法
class 子类 extends 父类{
}
1)子类就会自动拥有父类定义的属性和方法.
2)父类又叫 超类,基类。
3)子类又叫派生类。
2)简单示例
主函数
package D412;public class testExtend {public static void main(String[] args) {Pupil pupil = new Pupil();pupil.name = "银角大王";pupil.age = 18;pupil.testing();pupil.setScore(50);pupil.showInfo();Graduate graduate = new Graduate();graduate.name = "金角大王";graduate.age = 20;graduate.testing();graduate.setScore(80);graduate.showInfo();}
}
父类 student 类
package D412;public class Student {//共有属性public String name;public int age;private double score;//共有方法public void setScore(double score){this.score = score;}public void showInfo(){System.out.println("Name: " + name + " Age: " + age + " Score: " + score);}
}
子类
pupil类
package D412;//让 Pupil 继承 Student 的类
public class Pupil extends Student {public void testing(){System.out.println("小学生 " + name + "正在考小学数学..");}
}
package D412;public class Graduate extends Student{public void testing() {System.out.println("大学生 " + name + " 正在考大学数学..");}
}
3)继承的细节
1.子类继承了所有的属性和方法,非私有的属性和方法可以直接访问,但是私有属性不能在子类直接访问,要通过公共的方法去访问。
package D417;public class Extends {public static void main(String[] args) {Sub sub = new Sub();sub.sayOK();}
}
父类
package D417;public class Base {public int n1 = 100;protected int n2 = 200;int n3 = 300;private int n4 = 400;public Base() {System.out.println("base()...");}//父类提供一个public的方法,返回了一个n4public int getN1() {return n4;}public void test100() {System.out.println("test100");}protected void test200() {System.out.println("test200");}void test300() {System.out.println("test300");}private void test400() {System.out.println("test400");}public void calltest400(){test400();}
}
子类
package D417;public class Sub extends Base {public Sub() {System.out.println("sub(....");}public void sayOK(){//我们发现父类的非private属性和方法都可以访问System.out.println(n1+" "+n2+" "+n3+" ");test100();test200();test300();//test400();错误//通过父类提供公共的方法去访问System.out.println("n4 = "+ getN1());calltest400();}
}
2.子类必须调用父类的构造器,完成父类的初始化。
3.当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。
4.如果希望指定去调用父类的某个构造器,则在子类显式的调用一下:super(参数列表)
5.super在使用时,需要放在构造器第一行
6.super()和this()都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
7.java所有类都是Object类的子类,Object是所有类的基类
8.父类构造器的调用不限于直接父类!将一直往上追溯到Object类(顶级父类)
9.子类最多只能继承一个父类(直接继承),java中是单继承类
10.不能滥用继承,子类和父类之间必须满足is-a的逻辑关系
五、super用法
-
调用父类的构造器的好处 (分工明确,父类属性由父类初始化,子类的属性由子类初始化)
-
当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名使用super、this、直接访问是一样的效果![举例]
-
super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,使用super访问遵循就近原则。A->B->C,当然也需要遵守访问权限的相关规则
super代表父类的引用,用于访问父类的属性,方法,构造器
1.访问父类的属性,但不能访问父类的private属性
super.属性名;
2.访问父类的方法,不能访问父类的private方法
super.方法名(参数列表);
3.访问父类的构造器
super(参数列表);
只能放在构造器的第一句,只能出现一句!
//希望调用父类-A的cal方法//这时,因为子类B没有cal方法,因此我可以使用下面三种方式
//找cal方法时,顺序是:
//(1)先找本类,如果有,则调用
//(2)如果没有,则找父类(如果有,并可以调用,则调用)
//(3)如果父类没有,则继续找父类的父类,整个规则,就是一样的,直到 0bject类
//提示:如果查找方法的过程中,找到了,但是不能访问,则报错,cannot access11如果查找方法的过程中,没有找到,则提示方法不存在
cal();
this.cal(); //等价 cal
super.cal();// 找cal方法的顺序是直接査找父类,其他的规则一样,直接跳过子类
六、多态
1.方法或对象具有多种形态,多态是建立在封装和继承基础之上的。
2.对象的多态
(1)一个对象的编译类型与运行类型可以不一样
(2)编译类型在定义对象时,就确定了,不能改变
(3)运行类型时可以变化的
(4)编译类型看定义时 = 号的左边,运行类型看 = 号右边
Animal animal = new Dog();【animal 编译类型是Animal,运行类型Dog)
animal = new Cat();【animal 的运行类型变成了 Cat, 编译类型仍然是 Animal)
1)向上转型
多态的前提是:
两个对象(类)存在继承关系多态的向上转型
-
本质:父类的引用指向了子类的对象
-
语法:父类类型引用名 = new 子类类型();
-
特点:编译类型看左边,运行类型看右边。可以调用父类中的所有成员(需遵守访问权限);
不能调用子类中特有成员;最终运行效果看子类的具体实现!
Animal animal = new Cat();
2)向下转型
-
语法:子类类型 引用名 = (子类类型) 父类引用;
-
只能强转父类的引用,不能强转父类的对象
-
要求父类的引用必须指向的是当前目标类型的对象
-
可以调用子类类型中所有的成员
Cat car = (Cat) animal;
#属性没有重写之说!属性的值看编译类型
3)instanceof 比较操作符
用于判断对象的运行类型是否为XX类型或XX类型的子类型
4)java动态访问机制
1.当调用对象方法的时候,该方法和该对象的内存地址/运行类型绑定。
2.当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。
5)多态数组
1.概述:多态数组,需要满足数组类型定义为父类类型,而里面保存的实际元素类型为子类类型(可同时存放多种子类)。实际达到的效果就是父类引用指向子类对象。2.使用:多态数组在使用时往往与instanceof关键字配合使用。即在遍历数组时,通过增加 if条件语句进行判断,使得不同类型的子类对象元素可以分别使用它们各自的特有方法。
七、Object类
1)equals
既可以判断基本类型,又可以判断引用类型
如果判断基本类型,判断的是值是否相等。示例:int i=10; double d=10.0;
如果判断引用类型,判断的是地址是否相等,即判定是不是同一个对象【案例说明】
IDEA查看jdk源码
File -> Project Structure ->SDKs ->Sourcepath 然后点击右侧绿色的加号
是Object类,只能判断引用类型
//默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等如何重写equals方法
应用实例: 判断两个Person对象的内容是否相等,如果两个Person对象的各个属性值都一样;
则返回true,反之false。
public class Equals01 {public static void main(String[] args) {A a = new A();A b = a;A c = b;System.out.println(a == c);//trueSystem.out.println(b == c);//trueB bObj = a;System.out.println(bObj == c);//trueint num1 = 10;double num2 = 10.0;System.out.println(num1 == num2);//基本数据类型,判断值是否相等//equals 方法,源码怎么查看. //把光标放在 equals 方法,直接输入 ctrl+b/*//带大家看看 Jdk 的源码 String 类的 equals 方法//把 Object 的 equals 方法重写了,变成了比较两个字符串值是否相同public boolean equals(Object anObject) {if (this == anObject) {//如果是同一个对象return true;//返回 true}if (anObject instanceof String) {//判断类型String anotherString = (String)anObject;//向下转型int n = value.length;if (n == anotherString.value.length) {//如果长度相同char v1[] = value;char v2[] = anotherString.value;int i = 0;while (n-- != 0) {//然后一个一个的比较字符if (v1[i] != v2[i])return false;i++;}return true;//如果两个字符串的所有字符都相等,则返回 true}}return false;//如果比较的不是字符串,则直接返回 false}*/"hello".equals("abc");//看看 Object 类的 equals/*//即 Object 的 equals 方法默认就是比较对象地址是否相同//也就是判断两个对象是不是同一个对象. public boolean equals(Object obj) {return (this == obj);}*//*//从源码可以看到 Integer 也重写了 Object 的 equals 方法, //变成了判断两个值是否相同public boolean equals(Object obj) {if (obj instanceof Integer) {return value == ((Integer)obj).intValue();}return false;}*/Integer integer1 = new Integer(1000);Integer integer2 = new Integer(1000);System.out.println(integer1 ==integer2);//falseSystem.out.println(integer1.equals(integer2));//trueString str1 = new String("hspedu");String str2 = new String("hspedu");System.out.println(str1 ==str2);//falseSystem.out.println(str1.equals(str2));//true
}
}class B {
}class A extends B {
}
equals重写
Person person1 = new Person("jack",10,'男');
Person person2 = new Person("jack",10,'男');//判断两个person属性是否相等//重写0bject 的 equals方法
public boolean equals(object obj) {//判断如果比较的两个对象是同一个对象,则直接返回trueif (this == obj) {return true;}//类型判断if (obj instanceof Person) {//是Person,我们才比较//进行 向下转型,因为我需要得到obj的 符个属性Person p = (Person) obj;return this,name.equals(p.name) && this.age == p.age && this.gender == p.gender;}return false;
}
2)HashCode
1.提高具有哈希结构的容器效率
2.两个引用,如果指向的是同一个对象,则哈希值肯定一样
3.两个引用,如果指向的是不同对象,则哈希值是不一样的
4.哈希值主要根据地址号来,不能完全将哈希值等价于地址
3)toString
基本介绍默认返回:全类名+@+哈希值的十六进制,【查看Object 的 toString方法】
(1)子类往往重写toString方法,用于返回对象的属性信息
public string tostring(){return getclass().getName()+"@"+ Integer.toHexString(hashCode());
}
(2)重写toString方法,打印对象或拼接对象时,都会自动调用该对象的toString
(3)当直接输出一个对象时,toString 方法会被默认的调用, 比如 System.out.println(monster); 就会默认调用
monster.toString()
//重写后,一般把对象的属性输出
//快捷键 alt+insert ->toString
class Person{String name;int age;char gender;@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +", gender=" + gender +'}';}
}
4)finalize
finalize方法
//Finalize .java
1.当对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法做一些释放资源的操作【演示】
2.什么时候被回收:当某个对象没有任何引用时,则ivm就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用finalize方法。
3.垃圾回收机制的调用,是由系统来决定,也可以通过System.gc()主动触发垃圾回收机制,测试:Car [name]
举例
import java.util.*;class RunoobTest extends GregorianCalendar {public static void main(String[] args) {try {// 创建 RunoobTest 对象RunoobTest cal = new RunoobTest();// 输出当前时间System.out.println("" + cal.getTime());// finalize calSystem.out.println("Finalizing...");cal.finalize();System.out.println("Finalized.");} catch (Throwable ex) {ex.printStackTrace();}}
}
八、断点调试
1)介绍
(1)重要提示: 在断点调试 过程中,是运行状态,是以对象的 运行类型来执行的。
A extends B : B b= new A(); b.xx();
(2)断点调试是指在程序的某一行设置一个断点,调试时,程序运行到这-一行就会停住,然后你可以一步一步往下调试,调试过程中可以看各个变量当前的值,出错的话,调试到出错的代码行即显示错误,停下。进行分析从而找到这个Bug
(3)断点调试是程序员必须掌握的技能。
(4)断点调试也能帮助我们查看java底层源代码的执行过程,提高程序员的Java水平。
2)快捷键
F7: | (跳入)跳入方法内 |
---|---|
F8: | (跳过)逐行执行代码 |
F9: | (resume,执行到下一个断点) |
shift+F8: | 跳出方法 |