面向对象(中)
一、包
1.包的本质
包实际上就是创建不同的文件夹来保存类文件。主要是为了相同名字的类的使用,引出包。
2.包的命名
①命名规则
只能包含数字,字母,下划线,小圆点,但不能以数字开头,不能用关键字或者保留字。
②命名规范
一般是字母+小圆点。
一般模板是 com.公司名(个人名).项目.业务模块 。
3.常用的包
java.lang.* :lang包是基本包,默认导入,不需要再引入。
java.util.* :util包系统提供的工具包,工具类。
java.net.* :网络包,网络编程。
java.awt.* :Java界面开发。
4.包的导入
1.基本语法:import 包;
因为我们引入一个包主要是使用该包下的某个内容,所以最好只导入那个内容。
以Scanner为例
我们最好只导入Scanner。如下:
import java.util.Scanner; // 表示只会引入java.util.Scanner类
而不是全部导入,如下:
import java.util.*; // 表示导入java.util下的所有内容
5.包的细节
1.package的作用是声明当前类所在的包,需要写在class文件的最上方,一个类最多只有一行package
2.import指令,位置在package的下面,定义在类的前面,可以多句,没有顺序要求。
二、访问修饰符
1.基本介绍
Java提供四种访问控制修饰符,用于控制方法和属性的访问权限(范围)。
①公开级别:用public修饰,对外公开。
②受保护级别:用protected修饰,对子类的同一个包的类公开。
③默认级别:没有修饰符号,向同一个包的类公开。
④私有级别:用private修饰,只有类本身可以访问,不对外公开。
2.访问修饰符对应表格
访问级别 | 访问修饰符 | 同类 | 同包 | 子类 | 不同包 |
---|---|---|---|---|---|
公开 | public | √ | √ | √ | √ |
受保护 | protected | √ | √ | √ | × |
默认 | 没有修饰符 | √ | √ | × | × |
私有 | private | √ | × | × | × |
3.修饰符细节
1.修饰符可以用来修饰类中属性,方法,以及类。
2.只有默认和public可以修饰类,并且遵循上面表格的规则。
3.子类访问继承的时候再说
4.方法和属性规则是一样的。
举例同包下有A与B两个类
A类,代码如下:
package com.hh6879.modifier;
public class A {
// 四个属性分别使用不同的修饰符修饰
public int num1 = 1;
protected int num2 = 2;
int num3 = 3;
private int num4 = 4;
public void f1() {
// 本类方法可以访问 四个属性
System.out.println("num1 = " + num1 + " num2 = " + num2 + " num3 = " + num3 + " num4 = " + num4);
}
}
B类,代码如下:
package com.hh6879.modifier;
public class B {
public void f2(){
A a = new A(); // 本包下可以访问public
// 在同一个包下, 可以访问public, protected, 没有修饰符(默认), 不能访问private
System.out.println("num1 = " + a.num1 + " num2 = " + a.num2 + " num3 = " + a.num3);
// 访问不了num4, a.num4会报错
}
}
三、封装(重要)
①面向对象编程有三大特征:封装,继承,和多态。
1.基本介绍
封装(encapsulation)就是把抽象出来的数据[属性]和对数据的操作(方法)封装在一起。数据被保护在内部,只能通过程序的其他部分提供授权的操作(方法),才能对数据进行操作。
2.好处
①隐藏实现细节。
②可以对数据进行验证,保证安全合理。
3.封装的步骤
①将属性进行私有化
②通过一个公共的set方法,用于对属性进行判断并赋值。语法如下:
public void setXxx(数据类型 参数名称) {
//加入数据验证业务逻辑
属性 = 参数名;
}
③通过一个公共的get方法,用来返回属性值。语法如下
public xx getXxx() {
//权限判断
return xx;
}
④举一个例子,人(Person)类有三个属性,String类型 名字(name),int类型 年龄(age),double类型 薪水(salary)。且年龄在1-120岁之间,否则赋值18。名字长度在2-6之间,否则赋值无名。代码如下:
class Person {
private String name;
private int age;
private double salary;
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; // 默认值18岁
}
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = 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("名字长度不对, 默认名字无名");
this.name = "无名";
}
}
public void info() {
System.out.println("name = " + name + " age = " + age + "salary = " + salary);
}
}
4.封装与构造方法
为了防止构造方法创建时跳过验证,可以在构造方法里调用方法。代码如下:
public Person(String name, int age, double salary) {
setName(name);
setAge(age);
setSalary(salary);
}
四、继承(重要)
1.基本介绍
继承可以解决代码复用,让编程更加靠近人类的思维。当多个类存在相同的变量(属性)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所在的子类不需要重新定义这些属性和方法,只需要通过extends继承来声明父类继承即可。
2.示意图
3.基本语法
class 子类 extends 父类 {
}
Tips:①子类自动拥有父类定义的属性和方法(前提是要权限允许)。
②父类也叫超类,基类。
③子类也叫派生类。
4.继承的好处
1.提升代码复用性。
2.代码的可扩展性和维护性提高。
5.继承的细节
1.子类继承父类的全部属性和方法,但权限不允许的不能在子类访问,要通过公共方法访问。
2.子类必须要调用父类的构造方法 ,完成父类的初始化。代码如下:
Base父类:
public class Base {
public int num1 = 1;
protected int num2 = 2;
int num3 = 3;
private int num4 = 4;
public Base(){
System.out.println("Base()构造方法被调用");
}
}
Sub子类:
public class Sub extends Base {
public Sub(){
System.out.println("Sub()构造方法被调用"); // super()被隐藏
}
}
调用:
public class ExtendDetail {
public static void main(String[] args) {
Sub sub = new Sub();
}
}
输出结果:
//Base()构造方法被调用
//sSub()构造方法被调用
3.当创建子类对象时,不管使用那个子类的构造方法,默认情况下,总会调用父类的无参构造方法,如果父类没有提供无参构造方法,则必须在子类的构造方法中使用super去指定使用父类的哪个构造方法完成父类的初始化。代码如下:
Base类;
public class Base {
public int num1 = 1;
protected int num2 = 2;
int num3 = 3;
private int num4 = 4;
public Base(){
System.out.println("Base()构造方法被调用");
}
public Base(int num){
System.out.println("Base(int num)构造方法被调用");
}
}
Sub类:
public class Sub extends Base {
public Sub(){
System.out.println("Sub()构造方法被调用"); // super()被隐藏
}
public Sub(int num){
System.out.println("Sub(int num))构造方法被调用"); // super()被隐藏
}
}
调用:
public class ExtendDetail {
public static void main(String[] args) {
Sub sub = new Sub(1);
}
}
运行结果:
//Base()构造方法被调用
//Sub(int num))构造方法被调用
4.如果希望指定去调用父类的某个构造方法,则是显式的调用一下。super(参数列表)。代码不列出完了。只写出子类的构造方法。如下:
public Sub(){
super(1)
System.out.println("Sub()构造方法被调用"); // super()被隐藏
}
5.如果使用super,super需要放在第一行。super只能放在构造器中。
6.super和this都只能放在构造方法的第一行,因此这两个方法不能同时存在在一个构造器。
7.java所有类都是Object类的子类
8.父类构造方法的调用不限于直接父类,将一直往上追溯至Object类(定级父类)。
9.子类最多只能继承一个父类(指直接继承),即Java中是单继承机制。
10.不能滥用继承机制,子类和父类之间必须满足is-a的关系。比如学生是一个人,猫是一个动物。
五、super关键字
1.基本介绍
super代表父类的引用,用于访问父类的属性,方法,构造方法。
2.基本语法
1.super.属性名
访问父类的属性,但不能访问父类的没有权限的属性。
2.super.方法名(参数列表)
访问父类的方法,但不能访问父类的没有权限的方法。
1.super(参数列表)
super(参数列表);只能放在构造方法的第一句,调用父类构造方法。
3.细节
1.调用父类的构造方法的好处(分工明确,父类属性由父类初始化,子类的属性由子类初始化)。
2.当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super,this,直接访问是一样的效果.
3.super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类;如果多个父类中都有同名的成员,使用super访问遵循就近原则。当然也需要遵循访问权限的相关规则。
4.this与super的区别
区别点 | this | super |
---|---|---|
访问属性 | 访问本类的属性,如果本类没有此属性,则从父类中继续查找 | 访问父类的属性 |
调用方法 | 访问本类的方法,如果本类没有此方法,则从父类中继续查找 | 访问父类的方法 |
调用构造方法 | 调用本类的构造方法,必须放在构造方法第一行 | 调用父类的构造方法,必须放在构造方法第一行 |
特殊 | 表示当前对象 | 表示子类访问父类的对象 |
六、方法重写(override)
1.基本介绍
方法重写就是子类有一个方法和父类某个方法的名称,返回类型,参数一样。那么我们就说子类的这个方法重写了父类的那个方法。(有些地方重写也叫覆盖)。
举一个例子,代码如下:
package com.hh6879.override_;
public class Override01 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.speak();
}
}
class Animal {
public void speak() {
System.out.println("动物叫");
}
}
class Dog extends Animal {
// Dog是Animal的子类
// 因为Dog的speak方法和Animal的speak方法的定义形式(名称, 返回类型, 参数)一样
// 这时候我们就说Dog的speak方法重写了Animal的speak方法
public void speak() {
System.out.println("小狗汪汪叫");
}
}
输出结果:
//小狗汪汪叫
2.细节
1.子类方法的方法名和参数,要和父类方法的方法名和参数完全一致。
2.子类的方法返回类型和父类返回类型一样,或者父类返回类型的子类。比如父类返回类型是Object,子类返回类型是String。代码如下:
父类方法:
public Object f1(){
return null;
}
子类方法:
public String f1(){
return null;
}
3.子类方法不能缩小父类方法的返回权限。
七、多态(重要)
1.基本介绍
方法或对象具有多种形态,是面向对象的第三大特征,多态建立在封装和继承的基础之上的。
2.方法的多态
重载和重写体现方法的多态。
3.对象的多态
1.一个对象的编译类型和运行类型可以不一致。
2.编译类型在定义对象时 就确定了,不能改变。
3.运行类型是可以改变的。
4.编译类型就看定义时 =号左边,运行类型就看定义时 =号右边。代码如下,类的定义就不放出了,Animal是父类,Dog,Cat是子类。
public class PolyObject {
public static void main(String[] args) {
Animal animal = new Dog();
// 编译类型是Animal, 运行对象是Dog。
animal.speak(); // 找的是Dog的speak()方法
animal = new Cat();
// 运行类型变成了Cat, 编译类型依然是Animal
animal.speak(); // 找的是Cat的speak()方法
}
}
运行结果:
//Dog 狗在叫...
//Cat 猫在叫...
4.多态向上转型
1.多态的前提是两者存在继承关系
2.多态的向上转型
1)本质就是父类的引用指向子类的对象
2)语法:父类类型 引用名 = new 子类类型();
3)特点:①编译类型看左边,运行类型类型看右边。
②可以调用父类的所有成员(需要遵守访问权限规则)
③不能调用子类的特有成员。因为在编译阶段能调用那些成员是由编译类型决定的。
④最终的运行效果看运行类型的具体实现,即调用成员时,按照运行类型开始查找,规则和上面说的继承规则一样。
代码例子:同上。
5.多态向下转型
1.语法:子类类型 引用名 = (子类类型) 父类引用
特点: 1)只能强转父类的引用,不能强制父类的对象。
2)要求父类的引用必须指向当前目标类型对象。
3)可以调用子类类型中所有成员。
6.属性重写问题
属性没有重写之说,属性的值看编译类型。演示代码如下:
public class PolyDetail03 {
public static void main(String[] args) {
Person person = new Student();
System.out.println(person.age); // 直接看编译类型Person的age
// 输出结果为: 25
}
}
class Person {
int age = 25;
}
class Student extends Person {
int age =18;
}
7.动态绑定机制
1.当调用方法对象方法时,该方法会和该对象的内存地址(运行类型)绑定。
2.当调用属性时,没有动态绑定机制,哪里声明,哪里使用。即当前类返回当前类的属性。
3。举一个例子,对比说明,代码如下:
第一个:
public class DynamicBinding {
public static void main(String[] args) {
A a = new B();
System.out.println(a.sum()); // 输出结果为: 4
System.out.println(a.sum1()); // 输出结果为: 3
}
}
class A { // 父类
public int num = 1;
public int sum() {
return getNum() + 1;
}
public int sum1() {
return num + 1;
}
public int getNum() {
return num;
}
}
class B extends A { // 子类
public int num = 2;
public int sum() {
return getNum() + 2;
}
public int sum1() {
return num + 1;
}
public int getNum() {
return num;
}
}
删掉子类的sum和sum1方法后运行结果如下:
public class DynamicBinding {
public static void main(String[] args) {
A a = new B();
System.out.println(a.sum()); // 输出结果为: 3
System.out.println(a.sum1()); // 输出结果为: 2
}
}
class A { // 父类
public int num = 1;
public int sum() {
return getNum() + 1;
}
public int sum1() {
return num + 1;
}
public int getNum() {
return num;
}
}
class B extends A { // 子类
public int num = 2;
public int getNum() {
return num;
}
}
8.补充:
1)多态数组
数组定义类型为父类,里面保存的实际元素类型可以为子类类型。
2)多态参数
发法定义的形参列表类型为父类,实参允许为子类类型。