面向对象编程(中级部分)
此为笔者个人笔记,如有错误还请指出
关于IDEA
只要不在DOS下允许,编码都用UTF-8
在IDEA中,当run时,会先编译成一个.class文件,再运行
.class文件在out文件夹中
IDEA快捷键盘
-
删除当前行 ctrl + d
-
复制当前行 ctrl + alt + c
-
补全代码 alt + /
-
ctrl + / 添加注释 去除注释
-
快速导入类 alt + enter
-
ctrl + shift + l 查找
-
ctrl + alt + l 格式对齐 非常舒服
-
查看类的层级关系 ctrl + h
-
ctrl + B可以选择定位到哪个类的方法
-
自动分配变量名用 .var (写完new 方法后.var自动生成new 的对象名)
模板
使用以及设置方法:
public class TestTemplate {
//main模板生成main方法
public static void main(String[] args) {
//sout模板生成System.out.println();
System.out.println("sout yyds");
//fori模板生成for循环
// for (int i = 0; i < ; i++) {
//
// }
}
}

包基本介绍
当类很多时,可以把功能近似的类放在同一包中管理
包的本质是创建不同的文件夹来保存类
可以通过打包在不同包下来区分名称相同的类
通过import …;来引用包
包命名
建议需要上面类就具体导到哪个类就行,不建议用import.util.*;这样访问
package放在类最上面,一个类只能有一个这样的语句
import放在package 语句下面,可以有多个import 语句
访问修饰符
控制访问范围
注意只有默认和public 可以修饰类
封装介绍encapsulation
把抽象出来的数据和其操作封装在一起,数据保护在内部,程序的其他部分只能通过被授权的方法才能对数据进行操作 用户只运用方法就可以,无需在乎其内部实现
好处
实现方法: 提供公共的setter和getter
封装与构造器
为了防止通过构造器来设置与访问封装的数据,可以在构造器中使用setter和getter,而不要直接 this.xxx = xxx;
//我们可以将setter写在构造器中,这样仍然能达到封装的效果
public Person(String name, int age, int salary){
setName(name);
setAge(age);
setSalary(salary);
}
继承extends
基本语法
class 子类 extends 父类{
//...
}
父类又叫做超类,基类,子类又叫做派生类
子类继承父类后要写构造器,如果构造方法与父类一致可以直接super(…参数);
细节
-
子类继承了所有属性和方法,但私有属性和方法不能在子类 直接 访问,可以间接的通过 公共的 方法访问 。注意子类确实继承了 所有的 属性和方法。
-
子类必须调用父类的构造器,完成父类的初始化
-
创建子类时,不管使用的是子类的哪个构造器,默认条件下都会去调用父类的无参构造器,如果父类没有提供无参构造器,必须在子类的构造器中用super()去指定调用哪个父类的构造方法 否则,编译不会通过 这个的原理是编译器在子类的构造器中默认加了一个super()

public Pupil(String name, int age, double score) {
//
setScore(score);
this.name = name;
this.age=age;
System.out.println("调用子类有参构造器");
}

public Pupil(String name, int age, double score) {
//
super(name, age, score);
System.out.println("调用子类有参构造器");
}

-
super在使用时,必须放在第一行
-
super() 和 this() 都只能放在构造器第一个,且两者不能同时出现
-
java的所有类都是Object的子类 ctrl + h可以看到继承关系
-
父类构造器的调用不限于直接父类,将向上一直追溯到Object类(顶级父类)

继承的本质分析
public class Run {
public static void main(String[] args) {
Son son = new Son();
System.out.println(son.name);
System.out.println(son.salary);
System.out.println(son.hobby);
}
}
class Grandpa{
String name="爷爷";
String hobby="玩";
}
class Father extends Grandpa{
String name="爸爸";
double salary=10000.1;
}
class Son extends Father{
String name="儿子";
int age=14;
}
//输出:
儿子
10000.1
玩

规则:1、看子类有没有该属性,有就访问并返回信息 2、没有就看上一层父类,有就访问并返回信息,没有就继续访问上一层父类 3、接着这样操作,直到找到有此属性的父类
Exercise
public class Exercise01 {
public static void main(String[] args) {
B b = new B();
}
}
class A {
A() {
System.out.println("a");
}
A(String name) {
System.out.println("a name");
}
}
class B extends A {
B() {
this("abc");
System.out.println("b");
}
B(String name) {
//默认有super()
System.out.println("b name");
}
}
//运行结果:
a
b name
b
//注意这里输出的 a 是由 B(String name) 调用的,B()使用了this(),因此不能再使用隐藏的super();
//预期结果:
我是A类
hahaha我是B的有参构造
我是c类的有参构造
我是c类的无参构造
Super关键字
作用:1、调用父类构造器;2、当子类中有与父类中的成员重名时,为了访问父类的成员,必须使用super,如果没有重名成员,super和this以及直接访问是一样的效果
好处:1、分工明确,父类属性由父类初始化,子类属性由子类初始化
细节:
- super的访问不限于直接父类,如果爷爷类和本类中都有同名的成员,可以使用super去访问爷爷类成员, 但注意多个基类中都有同名成员时,super访问遵循就近原则。
Override 方法重写(注意不是方法重载)
子类有一个方法,和其某个父类的方法的名称、返回类型、参数 一样,那么我们就说子类的这个方法覆盖了父类的方法
public class Animal {
public void cry(){
System.out.println("动物在叫");
}
}
public class Dog extends Animal {
@Override
public void cry(){
System.out.println("小狗在叫");
}
}
注意
-
子类方法的参数、方法名称要和父类的参数、方法名称 完全一样
-
子类的返回类型要和父类的返回类型 一样 或者 是父类返回类型的子类 例如父类方法返回类型是 Object 子类返回类型是 String
-
子类方法不能缩小父类方法的访问权限 但是可以 扩大
注意方法重写(Override)和方法重写(Overload) 的区分!!!

Exercise
public class first {
public static void main(String[] args) {
Person mxy = new Person("mxy", 19);
Student yll = new Student("yll", 18, "2021212121", 85.01);
mxy.say();
yll.say();
}
}
public class Person {
private String name;
private int age;
public Person(){
setName("无名氏");
setAge(0);
}
public Person(String name, int age){
setName(name);
setAge(age);
}
public void say(){
System.out.println("你好!我叫"+name+",今年"+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 class Student extends Person {
private String id;
private double score;
public Student(){
setId("");
setScore(0);
}
public Student(String name, int age, String id, double score){
super(name, age);
setId(id);
setScore(score);
}
@Override
public void say(){
System.out.println("大家好!我叫"+getName()+",学号是"+id+",今年"
+getAge()+"岁了,成绩是"+score+"分");
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
}
多态
解决的问题:代码的复用性不高,且不利于维护
对象的多态
父类的引用可以指向子类的对象
Animal animal = new Dog();
animal的编译类型是Animal,运行类型是Dog
Animal animal = new Animal();
animal = new Cat();
编译类型是Cat,运行类型是Cat,且运行类型可以变化
编译类型看等号左边,运行类型看等号右边
animal.cry();
具体运行的是哪个类的cry方法看运行类型
因此可以通过使用父类来广泛的接收/指向 子类的对象
注意事项
-
前提:存在继承关系
-
语法: 父类类型的引用 = new 子类对象();
-
特点:编译类型看左边,运行类型看右边
-
可以在访问权限范围内调用父亲的所有成员,但是不能调用子类的特有成员,在编译阶段能调用哪些成员是由编译类型决定的
-
不能调用子类特有成员
-
相当于向上转型(父类引用指向的子类的对象) 最终运行效果看子类的实现
-
调用子类和父类中都有的方法时用子类中的方法,子类中没有的方法用父类的方法
向下转型
上面提到的多态有一个问题:如果我确实需要调用子类特有的属性/方法时该怎么办?这就引出了多态的向下转型
语法: 子类类型 引用名 = (子类类型) 父类引用;
注意
- 只能强转父类引用,不能强转父类对象
- 父类的引用必须指向的是当前目标类型的对象,不能指向其他子类的对象
- 向下转型后可以调用子类类型中所有的成员
- 相当于有两个引用指向子类对象,一个是父类的引用,一个是子类的引用
- 属性没有重写之说,属性的值看的是 编译类型!
- instanceof比较操作符,用于判断对象的类型是否为XX类型或XX类型的子类 在多态中判断的是 运行类型
向下转型时一定是属性看编译类型,方法看运行类型
public class Exercise02 {
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.cnt);
s.say();
Base b = s;
System.out.println(b == s);
System.out.println(b.cnt);
b.say();
}
}
class Base{
int cnt = 10;
public void say(){
System.out.println(this.cnt);
}
}
class Sub extends Base{
int cnt = 20;
public void say(){
System.out.println(this.cnt);
}
}
//向下转型后属性看编译类型,方法看运行类型,输出为
20
20
true
10
20
动态绑定机制
public class DynamicBinding {
public static void main(String[] args) {
A a = new B();
System.out.println(a.getI()); //10
System.out.println(a.sum()); //20
System.out.println(a.sum1()); //30
}
}
class A{
public int i = 20;
public int sum(){
return getI() + 10;
}
public int getI(){
return i;
}
public int sum1(){
return i + 10;
}
}
class B extends A{
public int i = 10;
@Override
public int getI() {
return i;
}
}
分析:
方法有动态绑定机制,所以getI()绑定了运行类型,在a.getI()时调用的时B类中的方法,返回B类中的i;
在a.sum()时,运用了类的继承机制,在类B中找不到sum方法,就去其父类A中找,A中的sum方法调用getI方法,由于方法的动态绑定机制,这里调用的仍是B类中的方法,返回i = 10,运算结果为20
在a.sum1()时,运用了类的继承机制,使用A类中的sum1()方法,由于属性没有动态绑定机制,所以这里使用的是A类中的i值,运算结果为30
多态应用
多态数组 数组类型为父类类型,其中可以存放子类类型的数据
示例:
public class Person {
private String name;
private int age;
public Person(String name, int age){
setName(name);
setAge(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 String say(){
return name+"\t"+age;
}
}
public class Student extends Person {
private double score;
public Student(String name, int age, double score){
super(name, age);
setScore(score);
}
public void Study(){
System.out.println(super.getName()+"正在学习");
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String say() {
return super.say()+"\tscore:"+getScore();
}
}
public class Teacher extends Person {
private double salary;
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
public void teach(){
System.out.println(super.getName()+"老师正在授课");
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String say() {
return super.say()+"\tsalary:"+getSalary();
}
}
public class PolyArr {
public static void main(String[] args) {
//要将1Person 2Student 2Teacher对象放在同一数组
Person[] persons = new Person[5];
persons[0] = new Person("1", 1);
persons[1] = new Student("2",2,100);
persons[2] = new Student("3",3,60);
persons[3] = new Teacher("4",4,10000);
persons[4] = new Teacher("5",5,20000);
for (int i = 0; i < persons.length ; i++) {
System.out.println(persons[i].say());
//假如这里想要调用Student类中的特有方法Study怎么办?
//可以判断运行类型+向下转型
if(persons[i] instanceof Student){
((Student)persons[i]).Study();
}else if(persons[i] instanceof Teacher){
((Teacher)persons[i]).teach();
}
}
}
}
//输出
1 1
2 2 score:100.0
2正在学习
3 3 score:60.0
3正在学习
4 4 salary:10000.0
4老师正在授课
5 5 salary:20000.0
5老师正在授课
多态参数 方法定义的形参类型为父类类型,实参类型允许为子类类型
示例:
public class Employee {
private String name;
private double salary;
public double getAnnual(){
return 12 * getSalary();
}
public Employee(String name, double salary) {
setName(name);
setSalary(salary);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
public class Manager extends Employee {
private double bonus;
public void setBonus(double bonus) {
this.bonus = bonus;
}
public double getBonus() {
return bonus;
}
public Manager(String name, double salary, double bonus) {
super(name, salary);
setBonus(bonus);
}
@Override
public double getAnnual() {
return super.getAnnual() + getBonus();
}
public void manage(){
System.out.println("经理 "+super.getName()+"正在研究管理方法");
}
}
public class OrdEmployee extends Employee {
public OrdEmployee(String name, double salary) {
super(name, salary);
}
@Override
public double getAnnual() {
return super.getAnnual();
}
public void Work(){
System.out.println("员工 "+super.getName()+"正在工作");
}
}
public class PolyParameter {
public static void main(String[] args) {
Employee e1 = new Manager("e1",10000,13.14);
Employee e2 = new OrdEmployee("e2", 7000);
Employee e3 = new Employee("e3", 6000);
showEmpAnnual(e1);
showEmpAnnual(e2);
showEmpAnnual(e3);
testWork(e1);
testWork(e2);
testWork(e3);
}
public static void showEmpAnnual(Employee e){
if(e instanceof OrdEmployee){
System.out.println(((OrdEmployee)e).getAnnual());//动态绑定机制
}else if(e instanceof Manager){
System.out.println(((Manager)e).getAnnual());//动态绑定机制
}else{
System.out.println("类型错误!");
}
}
public static void testWork(Employee e){
if(e instanceof OrdEmployee){
((OrdEmployee)e).Work();//动态绑定机制
}else if(e instanceof Manager){
((Manager)e).manage();//动态绑定机制
}else{
System.out.println("类型错误!");
}
}
}
//输出:
120013.14
84000.0
类型错误!
经理 e1正在研究管理方法
员工 e2正在工作
类型错误!
Object类详解
Object类方法摘要

equals(Object obj)方法
== 与equals的区别?
== 是运算符 既可以判断基本类型,又可以判断引用类型。基本类型判断值是否相等;引用类型判断地址是否相等(是不是同一个对象)。
//注意这段代码输出为true
int a = 65;
float f = 65f;
System.out.println(a == f);
equals 是Object类中的方法,只能判断引用类型
查看jdk源码方法:将光标放在要查看的方法上面,输入ctrl+b即可
//String的equals方法源码:
public boolean equals(Object anObject) {
if (this == anObject) {
return 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;
}
}
return false;
}
//Object类的euqals方法源码:
public boolean equals(Object obj) {
return (this == obj);
}
//每个引用类型都会重写equals方法
重写equals方法,用于自定义对象的比较
public class Person { //extends Object
private String name;
private int age;
private char gender;
//重写Object的equals方法
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;
}
if(obj instanceof Person){
//向下转型
Person p = (Person)obj;
return this.age == p.age && this.name == p.name && this.gender == p.gender;
}else{
return false;
}
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public Person(String name, int age, char gender){
setName(name);
setAge(age);
setGender(gender);
}
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 String say(){
return name+"\t"+age;
}
}
public class EqualsTest {
public static void main(String[] args) {
Person p1 = new Person("jetty", 12,'男');
Person p2 = new Person("jetty", 12,'男');
//如果不重写equals方法,比较会对比地址,从而输出false
System.out.println(p1.equals(p2));
}
}
hashCode方法
返回哈希码值,此方法主要是为了提高哈希表的性能
注意
- 两个引用如果指向同一个对象,两者hashCode相等,指向不同对象一定不等
- 哈希值是根据地址计算得来的,不能简单等价于地址
- 在集合中hashCode如果需要重写的话也要重写
toString 方法
返回对象的字符串表示(包名+类名+hashCode十六进制)
Object的toString()方法源码:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public class EqualsTest {
public static void main(String[] args) {
Person p1 = new Person("jetty", 12,'男');
System.out.println(p1.toString());
}
}
//输出:
com.mat.object_.equals.Person@1b6d3586
//若不toString,直接System.out.println(p1); 会默认调用toString()
用途:(重写)用来打印或拼接对象的属性
直接输出一个对象时,toString方法会被默认的调用
finalize方法 (相当于析构函数)
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法
当对象被回收时,系统会自动调用此方法,子类也可重写此方法,做一些释放资源的操作
当某个对象没有任何引用时,JVM就会认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,销毁对象前,会调用finalize方法
垃圾回收机制的调用是由系统来决定的,也可以通过System.gc()主动出发垃圾回收机制
重写finalize示例:
public class Test {
public static void main(String[] args) {
Cat kitty = new Cat("meow");
kitty = null;
System.gc();
System.out.println("由于没有引用指向此Cat对象," +
"系统调用了finalize方法将它回收了");
}
}
class Cat{
public String name;
public Cat(String name) {
this.name = name;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
//如果不主动调用System.gc();这里会没输出,由于JVM运用了特殊的算法来决定垃圾何时回收,这里后面讲到JVM的时候再讲
System.out.println("调用了重写的finalize方法");
}
}
//输出:
由于没有引用指向此Cat对象,系统调用了finalize方法将它回收了
调用了重写的finalize方法
本文深入探讨面向对象编程的核心概念,包括IDEA使用技巧、封装、继承、多态及其实现方式等内容,帮助读者掌握面向对象编程的关键技能。

被折叠的 条评论
为什么被折叠?



