目录
一、包
1. 基本概念
- 包的作用:
- 包的本质分析:
- 包的命名规则:
2. 基本用法
- 常用的包:
- java.lang.* :lang 包是基本包,默认引入,不需要再引入;
- java.util.* :util 包,系统提供的工具包, 工具类,使用 Scanner ;
- java.net.* :网络包,网络开发 ;
- java.awt.* :是做 java 的界面开发,GUI;
- 如何引入包:
3. 注意事项和使用细节
- 注意事项:
- 总结:
- 引入包的概念是为了区别同名的类;
- 一个类只能存在于一个包内;
- 一个包内的所有类,类名都是唯一的,不能重复;
- 不同的包内的类,可以取相同的类名;
二、访问修饰符
1. 基本概念
- java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
- 公开级别:用 public 修饰,对外公开;
- 受保护级别:用 protected 修饰,对子类和同一个包中的类公开;
- 默认级别:没有修饰符号,向同一个包的类公开;
- 私有级别:用 private 修饰,只有类本身可以访问,不对外公开;
- 访问范围:
- 注意:这里的子类指的是不同包下面的子类;和父类在同一个包下的子类,是属于同包关系的;
- 对于访问修饰符的理解,可以类比一个人,他的个人信息,举例如下:
- 有一部分是只有他自己知道的,这部分就是 private;他自身就相当于 同类/本类;
- 有一部分是只有他和他的兄弟姐妹知道的,这部分就是 默认,兄弟姐妹就相当于 同一个包的的其他类;有个例外,就是同一个包下的子类,也相当于他的兄弟姐妹!
- 有一部分是他和他的兄弟姐妹还有他的孩子知道的,这部分就是 protected,他的孩子就相当于 子类;
- 剩下的一部分是所有人都知道的,这部分就是 public,所有人就相当于 不同包下面的全部类。
2. 注意事项和使用细节
- 注意事项:
- 注意,protected 和 private 是不能修饰一个类的。
- 修饰父类的两种修饰符的意义:
- 第一种,父类的访问修饰符是 public,此时该父类可以被所有类继承;
- 第二种,父类的访问修饰符是 默认,此时该父类只能被和他同一个包下面的所有类继承。
三、封装
1. 基本概念
- 封装(encapsulation):就是把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(方法),才能对封装起来的数据进行操作。
- 封装的好处:
2. 基本使用
- 封装的实现步骤:
- 案例入门:
设置程序事实现下面的要求:
- 代码实现:
public class Encapsulation01 {
public static void main(String[] args) {
Person person = new Person();
//person.age = 30; 会报错,不能直接修改Person类private 属性
person.setName("韩顺平");
person.setAge(30);// 只能通过Person类提供的方法来修改其中的private 属性
person.setSalary(30000);
System.out.println(person.info());
System.out.println(person.getSalary());
}
}
/*
不能随便查看人的年龄,工资等隐私,并对设置的年龄进行合理的验证。年龄合理就设置,否则给默认。
年龄, 必须在 1-120, 年龄, 工资不能直接查看 , name的长度在 2-6字符 之间
*/
class Person {
public String name; //名字公开
private int age; //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), 给默认年龄18 ");
this.age = 18;//给一个默认年龄
}
}
public double getSalary() {
//可以这里增加对当前对象的权限判断
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
//写一个方法,返回属性信息
public String info() {
return "信息为 name=" + name + " age=" + age + " 薪水=" + salary;
}
}
- 思考:如果在Person 类中提供了构造器,那么在其他类中就可以直接给Person类中的private 变量赋值了,而且可以跳过Person类中的权限控制,这样封装不就没有用了吗?
- 回答:确实可以直接使用Person类提供的构造器直接给private 变量赋值,但是我们可以在Person类的构造器中调用set 方法,便可以解决权限控制的问题了。
- 代码如下:
public class Encapsulation01 {
public static void main(String[] args) {
//如果我们自己使用构造器指定属性
Person smith = new Person("smith", 80, 50000);
System.out.println("====smith的信息======");
System.out.println(smith.info());
}
}
class Person {
public String name; //名字公开
private int age; //age 私有化
private double salary;
//构造器 alt+insert
public Person() {
}
//有三个属性的构造器
public Person(String name, int age, double salary) {
// this.name = name;
// this.age = age;
// this.salary = salary;
// 我们可以将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), 给默认年龄18 ");
this.age = 18;//给一个默认年龄
}
}
public double getSalary() {
//可以这里增加对当前对象的权限判断
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
//写一个方法,返回属性信息
public String info() {
return "信息为 name=" + name + " age=" + age + " 薪水=" + salary;
}
}
四、继承
1. 基本概念
- 继承可以解决代码复用,让我们的编程更加靠近人类思维。
- 当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,父类的所有子类不需要再重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。
- 继承的示意图如下:
- 继承的好处:
- 代码的复用性提高了 ;
- 代码的扩展性和维护性提高了;
2.基本用法
- 基本语法:
3. 继承的细节(重要!)
- 细节如下:
- 子类继承了父类所有的属性和方法,父类中的非私有属性和方法可以在子类直接访问, 但是父类中的私有属性和方法不能在子类直接访问,要通过父类提供的公共方法去访问 ;
- 子类调用构造器初始化时,必须调用父类的构造器, 完成父类的初始化 ;
- 当新创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类的无参构造器被覆盖了,则必须在子类的构造器中用 super 关键字 去指定使用父类另外的构造器完成对父类的初始化工作,否则,编译不会通过;
- 当新创建子类对象时,如果想要指定调用父类的某个构造器,则需要在子类的构造器中显式的调用 : super(参数列表) ;
- super 关键字在子类构造器中使用时,必须放在其构造器第一行,并且super 只能在构造器中使用 ;
- 由于super() 和 this() 都只能放在构造器的第一行,因此这两个方法不能同时存在于一个构造器中 ;
- java中的所有类都是 Object 类的子类, Object 是所有类的基类;
- 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类) ;
- 子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制; 思考:如何让 A 类继承 B 类和 C 类? 【答案:A 继承 B, B 继承 C】
- 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系;
- 代码举例:
public class ExtendsDetail {
public static void main(String[] args) {
System.out.println("====对象====");
Sub sub3 = new Sub("king", 10); //创建了子类对象 sub2
//sub.sayOk();
}
}
//子类
public class Sub extends Base {
/*
1. 调用父类的无参构造器super(), 或者什么都不写,默认就是调用super();
super();// 父类的无参构造器
2. 调用父类的 Base(String name) 构造器;
super("hsp");
3. 调用父类的 Base(String name, int age) 构造器;
super("king", 20);
*/
//细节: super在使用时,必须放在构造器第一行;
//细节: super() 和 this() 都只能放在构造器第一行,因此这两个方法不能同时存在于一个构造器;
public Sub() {// 子类的无参构造器
this(String name, int age);// 指定调用了自己的有参构造器,其实父类的构造器就不会调用了;
System.out.println("子类Sub()构造器被调用....");
}
public Sub(String name) {
// 什么都不写,隐式地存在 super();
System.out.println("子类Sub(String name)构造器被调用....");
}
public Sub(String name, int age) {//子类的有参构造器
//super(); // 默认调用父类的无参构造器;
super("smith", 10);// 指定调用父类的 Base(String name) 构造器;
System.out.println("子类Sub(String name, int age)构造器被调用....");
}
// 子类的普通方法
public void sayOk() {
//细节:父类的非私有的属性和方法可以在子类直接访问
//但是父类的私有属性和方法不能在子类直接访问
System.out.println(n1 + " " + n2 + " " + n3);
test100();
test200();
test300();
//test400(); 报错
//要通过父类提供公共的方法去访问;
System.out.println("n4=" + getN4());// 可以访问私有属性n4了
callTest400();// 可以访问父类的私有方法 test400()
}
}
//父类
public class Base {
//4个属性
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
// 3个不同的构造器:
public Base() { //无参构造器
System.out.println("父类Base()构造器被调用....");
}
public Base(String name, int age) { //有参构造器
//默认super()
System.out.println("父类Base(String name, int age)构造器被调用....");
}
public Base(String name) { //有参构造器
System.out.println("父类Base(String name)构造器被调用....");
}
//父类提供一个public的方法,返回了n4;
public int getN4() {
return n4;
}
// 父类的call方法,调用了私有的test400()方法;
public void callTest400() {
test400();
}
// 4种不同权限的普通方法:
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");
}
}
4. 继承的本质分析
- 案例分析:
- 代码如下:
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son();
System.out.println(son.name);//返回就是子类的name= 大头儿子
// System.out.println(son.age);//会报错,访问受限
System.out.println(son.getAge());//使用父类的公共get方法,返回的就是父类的age= 39
System.out.println(son.hobby);//返回的就是爷类的hobby= 旅游
}
}
//爷类
class GrandPa {
String name = "大头爷爷";
String hobby = "旅游";
}
//父类
class Father extends GrandPa {
String name = "大头爸爸";
private int age = 39;
public int getAge() {
return age;
}
}
//子类
class Son extends Father {
String name = "大头儿子";
}
- 内存示意图如下:
- 内存布局解释:
- Son son = new Son(),首先在方法区中加载了Son 类和Son类继承的所有父类的信息,先查找Son 类的所有父类信息,从最顶层的父类开始加载,也就是Object -> GrandPa -> Father -> Son。
- 加载完类的信息后,其次在堆内存中开辟一个内存空间(地址),空间里面又划分了几个独立的类空间块(空间块个数由加载类的个数决定)。
- 接着给每个类空间块分配(初始化)属性,第一个是爷类空间块,分配爷类的属性;第二个是父类空间块,分配父类的属性;最后是子类空间块,分配子类的属性;由于各个类空间块是相互独立的,所以属性名相同也不会相互影响。
- 在分配完毕对象内存空间后,最后将内存地址返回给main 栈中的对象名 son,此时son 便指向了该对象内存,可以引用里面的信息。
- 假设现在要用 son 输出某个属性,注意:要按照查找关系来返回信息,如下:
- 首先看子类是否有该属性;
- 如果子类有这个属性,并且可以访问,则返回信息;
- 如果子类没有这个属性,接着看直接的父类有没有这个属性:
(1)如果父类有该属性,并且可以访问,就返回信息;
(2)如果父类有该属性,但是不可以访问(访问修饰符限制),则直接返回访问受限的错误信息(报错),不会跳过父类接着访问上级的父类属性。- 如果直接的父类没有这个属性,才可以按照 3 的规则,继续找上级父类,直到 Object类 结束,或者找不到这个属性返回错误信息;
五、super 关键字
1. 基本概念
- super 关键字代表父类的引用,用于访问父类的属性、方法、构造器;
- 基本语法:
- super的注意事项:
- super 虽然代表了父类的引用,但并不完全等同于父类,由于父类中私有变量/方法受到 private 的 限制,所以除了父类自身的方法外,其他所有类都不能访问。因此,super 关键字并不完全等同于父类自身;
- 而this 关键字代表了对象对自身的引用,类自身中的方法访问类中的私有变量/方法时不会受到 private 的 限制,因此this 关键字可以完全等同于对象自己。
- super 的访问不限于直接的父类,如果爷爷类和子类中有同名的成员,也可以使用 super 去访问爷爷类的成员;如果多个父类(上级类)中都有同名的成员,使用 super 访问时遵循就近原则。先是父类,再到爷类,再到祖宗类…
- super的使用细节:
- 注意:
- 在子类的方法中访问父类的属性/方法时,若是使用 this.属性/方法,则会从子类的属性/方法开始查找,子类没有该属性/方法,再查找父类、爷类…
- 在子类的方法中访问父类的属性/方法时,若是使用 super.属性/方法,则会直接从父类的属性/方法开始查找,父类没有,再向爷类…查找;
2. super 和 this 的比较
- 如下图所示:
六、方法重写
1. 基本概念
- 方法重写(覆盖):子类中有一个方法,和父类的某个方法的名称、返回类型、形参列表一样,就说子类的这个方法重写了父类的方法。
- 为什么要引入方法重写:
- 当一个子类继承一个父类时,它同时继承了父类的属性和方法。子类可以直接使用父类的属性和方法,如果父类的方法不能满足子类的需求,则可以在子类中对父类的方法进行重写(或覆盖)。
- 在方法重写时,如果子类需要引用父类中原有的方法,可以使用 super 关键字。当子类重写父类方法后,在子类对象使用该同名方法时,会执行子类中重写的方法。
2. 注意事项和使用细节
如下图所示:
- 对于第3点的解释:子类重写方法的修饰符 >= 父类方法的修饰符。
3. 方法重载和方法重写的比较
如下图所示:
总结
- 本文是小白博主在学习B站韩顺平老师的Java网课时整理的学习笔记,在这里感谢韩顺平老师的网课,如有有兴趣的小伙伴也可以去看看。