关键字
this
创建对象时,会得到对象在堆内的地址,this储存的就是对象在堆内地址
this可以调用的结构:成员变量、方法、构造器
this的理解:当前对象(在方法中调用时) 或 当前正在创建的对象(在构造器中调用时)
java
public class main {
public static void main(String[] args) {
Person p1 = new Person();
System.out.println(p1);
}
}
class Person {
// 属性
String name;
int age;
// 构造器
public Person() {
System.out.println("Person(...)");
System.out.println(this);
this.eat();
}
public Person(int age) {
System.out.println("Person(...)");
}
// 方法
public void eat() {
System.out.println("人吃饭");
}
}
this调用属性与方法
【针对方法内的使用情况:】(非static修饰的方法)
一般情况下,我们通过对象a调用方法,可以在方法内调用当前对象a的属性或其他方法。此时,我们可以在属性和其他方法前使用"this.",表示当前属性或方法所属的对象a。但是,一般情况下,我们都选择省略此"this."结构。
特殊情况:如果方法的形参与对象的属性同名了,我们必须使用"this."进行区分。使用this.修饰的变量即为属性(或成员变量),没有使用this.修饰的变量,即为局部变量。
【针对于构造器内的使用情况:】
一般情况下,我们通过构造函数创建对象时,可以在构造函数内调用当前正在创建的对象的属性或方法。此时,我们可以在属性和方法前使用"this.",表示当前属性或方法所属的对象。但是,一般情况下,我们都选择省略此"this."结构。
特殊情况:如果构造器的形参与正在创建的对象具有同名了,我们必须使用"this."进行区分。使用this.修饰的变量即为属性(或成员变量),没有使用this.修饰的变量,即为局部变量。
this调用构造器
-
格式: "this(形参列表)"
-
我们可以在类的构造器中,调用当前类中指定的其它构造器
-
要求: "this(形参列表)"必须声明在当前构造器的首行
-
结论: "this(形参列表)"在构造器中最多声明一个
-
如果一个类中声明了n个构造器,则最多有n-1个构造器可以声明有"this(形参列表)"的结构
java
public class main {
public static void main(String[] args) {
//只创建了User类的一个对象
User u1 = new User("Tom", 12);
}
}
class User {
String name;
int age;
public User() {
//模拟对象创建时需要50行代码
}
public User(String name) {
this();
this.name = name;
}
public User(String name, int age) {
this(name);
this.age = age;
}
}

super
super可以调用的结构是父类的属性、方法、构造器
super调用属性、方法
子类继承父类以后,我们就可以在子类的方法或构造器中,调用父类中声明的属性或方法。(满足封装性的前提下)需要使用"super."的结构,表示调用父类的属性或方法。
一般情况下,我们可以考虑省略"super."的结构。但是,如果出现子类重写了父类的方法或子类中出现了同名的属性时,则必须使用"super."的声明,显式的调用父类被重写的方法或父类中声明的同名的属性。
java
public class main {
public static void main(String[] args) {
Student s1 = new Student();
s1.eat();
s1.sleep();
s1.show2();
}
}
class Person {
// 属性
String name;
int usage;
int id = 1001;//身份证号码
// 方法
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
public void doSport() {
System.out.println("人运动");
}
}
class Student extends Person {
// 属性
String school;
int id = 1002;//学号
// 方法
public void study() {
System.out.println("学生学习");
}
public void eat() {
System.out.println("学生多吃有营养的食物");
}
public void sleep() {
System.out.println("学生保证每天不低于10个小时的睡眠");
}
//测试super调用方法、属性
public void show() {
eat(); // 省略了this
this.eat();
super.eat();
}
public void show1() {
doSport();
this.doSport();
super.doSport();
}
public void show2() {
System.out.println(id);//就近原则 1002
System.out.println(this.id);//1002
System.out.println(super.id);//1001
}
}
super调用构造器
① 子类继承父类时,不会继承父类的构造函数。只能通过"super(形参列表)"的方式调用父类指定的构造函数。
② 规定:"super(形参列表)",必须声明在构造函数的首行。
③ 我们前面讲过,在构造函数的首行可以使用"this(形参列表)",调用本类中重载的构造函数,
结合②,结论:在构造函数的首行,"this(形参列表)"和"super(形参列表)"只能二选一。
④ 如果在子类构造函数的首行既没有显示调用"this(形参列表)",也没有显式调用"super(形参列表)",则子类构造函数默认调用"super()",即调用父类中空参数的构造函数。
⑤ 由③和④得到结论:子类的任何一个构造函数,要么会调用本类中重载的构造函数,要么会调用父类的构造函数。只能是这两种情况之一。
⑥ 由⑤得到:一个类中声明有n个构造函数,最多有n-1个构造函数中使用了"this(形参列表)",
则剩下的那个一定使用"super(形参列表)"。
注:我们在通过子类的构造器创建对象时,一定在调用子类构造器的过程中,直接或间接的调用到父类的构造器。也正因为调用过父类的构造器,我们才会将父类中声明的属性或方法加载到内存中,供子类对象使用。
java
public class main {
public static void main(String[] args) {
// Student s1 = new Student();
Student s2 = new Student("11",11);
}
}
class Person {
// 属性
String name;
int usage;
int id;
public Person() {
System.out.println("Person constructor");
}
public Person(String name, int usage) {
this.name = name;
this.usage = usage;
}
public Person(String name, int usage, int id) {
this.name = name;
this.usage = usage;
this.id = id;
}
// 方法
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
public void doSport() {
System.out.println("人运动");
}
}
class Student extends Person {
// 属性
String school;
public Student() {
super();
// this("bb",11);//二选其一
System.out.println("Student constructor");
}
public Student(String name, int usage) {
// super(name, usage);//默认调用super()空参
System.out.println("Student Parm constructor");
}
// 方法
public void study() {
System.out.println("学生学习");
}
public void eat() {
System.out.println("学生多吃有营养的食物");
}
public void sleep() {
System.out.println("学生保证每天不低于10个小时的睡眠");
}
}
案例
注:1、每new的对象都是独立的,都具有自己的空间,控制自己的变量不影响其他空间
2、当子类没有重写继承的方法时,调用方法要到父类去查找,而查找时访问任何变量都是借助父类的方法,是父类中的数据(Father 类里写的 return info;,无论在哪执行,都永远指向 Father 自己定义的 info)
java
public class main {
public static void main(String[] args) {
Father f = new Father();
Son s = new Son();
System.out.println(f.getInfo()); // atguigu
//在方法内部访问成员变量时,访问的是"当前这个类(即定义该方法的类)"所看到的变量--就近原则
System.out.println(s.getInfo());// atguigu
s.test();// atguigu atguigu
System.out.println("---");
s.setInfo("大硅谷");
//对象的独立性
System.out.println(f.getInfo());// atguigu
System.out.println(s.getInfo());// 大硅谷
s.test();// 大硅谷 大硅谷
}
}
class Father {
private String info = "atguigu";
public void setInfo(String info) {
this.info = info;
}
public String getInfo() {
return info;
}
}
class Son extends Father {
private String info = "尚硅谷";
public void test() {
System.out.println(this.getInfo());
System.out.println(super.getInfo());
}
// public String getInfo() {
// return info;
// }
}
当我们创建子类对象后,子类对象就获取了其父类中声明的所有属性和方法,
在权限允许的情况下,可以直接调用。
当我们通过子类的构造器创建对象时,子类的构造器一定会直接或间接的调用到其父类的构造器,
而其父类的构造器同样会直接或间接的调用到其父类的父类的构造器,......,
直到调用了Object类中的构造器为止。
正因为我们调用过子类所有的父类的构造器,所以我们就会
将父类中声明的属性、方法加载到内存中,供子类的对象使用。
面向对象特性
之二:继承性
理解
从上而下:定义了一个类A,在定义另一个类B时,发现类B的功能与类A相似,考虑类B继承于类A

至下而上:定义了类B、C、D等,发现B、C、D有类似的属性和方法,则可以考虑将相同的属性和方法进行抽取,封装到类A中,让类B、C、D继承于类A,同时,B、C、D中的相似的功能就可以删除了。

继承的出现减少了代码冗余,提高了代码的复用性。
继承的出现,更有利于功能的扩展。(通用功能到特殊事件)
继承的出现让类与类之间产生了is-a的关系,为多态的使用提供了前提。(具体is-a抽象)
格式
类A:父类、superClass、超类、基类
类B:子类、subClass、派生类
[修饰符] class 类A {
//属性与方法
...
}
[修饰符] class 类B extends 类A {
//父类的属性与方法
//自己的属性与方法
...
}
java
public class main {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "Tony";
p1.eat();
Student s1 = new Student();
s1.name = "Tom";
s1.eat();
}
}
class Person {
// 属性
String name;
int age;
// 方法
public void eat() {
System.out.println("人吃饭");
}
public void sleep() {
System.out.println("人睡觉");
}
}
class Student extends Person {
// 属性
// String name;
// int age;
String school;
// 方法
// public void eat() {
// System.out.println("人吃饭");
// }
//
// public void sleep() {
// System.out.println("人睡觉");
// }
public void study() {
System.out.println("学生学习");
}
}
注意:
java
子类就获取到了父类中声明的所有的属性和方法。
由于封装性的影响,可能子类不能直接调用父类中声明的属性或方法。(继承不影响封装)
子类在继承父类以后,还可以扩展自己特有的功能(体现:增加特有的属性、方法)
(extends: 延展、扩展、延伸,子类不是父类的子集,而是对父类的扩展)
不要为了继承而继承。在继承之前,判断一下是否有is a的关系。
Java支持单继承(一父多子)与多层继承(直接父类、间接父类),不支持多继承
继承拓展:方法重写
子类在继承父类以后,就获取了父类中声明的所有的方法。但是,父类中的方法可能不太适用于子类,换句话说,子类需要对父类中继承过来的方法进行覆盖、覆写的操作。(这一操作称为方法的"重写")
java
权限修饰符 返回值类型 方法名(形参列表) [throws 异常类型] {
//方法体
}
规范:
① 父类被重写的方法与子类重写的方法的名字和形参列表必须相同。
② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
> 子类不能重写父类中声明为private权限修饰符的方法。
③ 关于返回值类型:
> 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型必须是void
> 父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须与被重写的方法的返回值类型相同。
> 父类被重写的方法的返回值类型是引用数据类型(比如类),则子类重写的方法的返回值类型可以与被重写的方法的返回值类型相同 或 是被重写的方法的返回值类型的子类
④ (超纲)子类重写的方法抛出的异常类型可以与父类被重写的方法抛出的异常类型相同,或是父类被重写的方法抛出的异常类型的子类。
java
public class main {
public static void main(String[] args) {
CheckAccount acct = new CheckAccount();
acct.withdraw(100); // 执行的是子类重写父类的方法
}
}
class Account {//账户
double balance;//余额
//取钱
public void withdraw(double amt) {
//判断balance余额是否够amt取钱的额度
}
}
class CheckAccount extends Account {//信用卡
double protectedBy;//透支额度
public void withdraw(double amt) {
//判断balance余额是否够amt取钱的额度
if (amt <= balance) {
balance = balance - amt;
} else if (amt <= balance + protectedBy) {
//如果不够,还可以考虑从protectedBy额度里取
balance = 0;
protectedBy -= amt;
}
}
}
重载:"两同一不同"(同一类、相同方法名,不同参数列表)
重写:继承以后,子类覆盖父类中同名同参数的方法
之三:多态性
理解
理解为一个事物的多种形态。
子类对象的多态性:父类的引用指向子类的对象、或子类的对象赋给父类的引用(抽象指向具体)
java
多态性的应用:虚拟方法调用
在多态的场景下,调用方法时,
编译时,认为方法是左边声明的父类的类型的方法(即被重写的方法)
执行时,实际执行的是子类重写父类的方法。
简称为:编译看左边、运行看右边
格式
java
public class main {
public static void main(String[] args) {
// 多态性之前的场景:
Person p1 = new Person();
Man m1 = new Man();
// 多态性:子类对象的多态性
Person p2 = new Man();
/*
多态性的应用:虚拟方法调用
在多态的场景下,调用方法时,
编译时,认为方法是左边声明的父类的类型的方法(即被重写的方法)
执行时,实际执行的是子类重写父类的方法。
简称为:编译看左边、运行看右边
*/
p2.eat();
p2.walk();
}
}
class Person {
String name;
int age;
public void eat() {
System.out.println("人吃饭");
}
public void walk() {
System.out.println("人走路");
}
}
class Man extends Person {
boolean isSmoking;
public void eat() {
System.out.println("男人多吃肉,长肌肉");
}
public void walk() {
System.out.println("男人笔挺的走路");
}
public void earnMoney() {
System.out.println("男人挣钱养家");
}
}
class Woman extends Person {
boolean isBeauty;
public void eat() {
System.out.println("女人应该少吃,减肥");
}
public void walk() {
System.out.println("女人婀娜的走路");
}
public void goShopping() {
System.out.println("女人喜欢逛街...");
}
}
注:1、多态性的使用前提:① 要有类的继承关系 ② 要有方法的重写
2、适用于方法(编译看左边、运行看右边),不适用于属性(看它的声明是什么,即编译)
3、没有多态会导致多次重载,冗余(优势)
4、在多态的场景下,我们创建了子类的对象,也加载了子类特有的属性和方法。但是由于声明为父类的引用,导致我们没有办法直接调用子类特有的属性和方法。(弊端)
使用父类做方法的形参,是多态使用最多的场合。即使增加了新的子类,方法也无需改变,提高了扩展性,符合开闭原则。
开闭原则OCP:对扩展开放,对修改关闭
- 通俗解释:软件系统中的各种组件,如模块(Modules)、类(Classes)以及功能(Functions)等,应该在不修改现有代码的基础上,引入新功能
java
public void adopt(Animal animal){
//动态绑定
animal.eat();
animal.jump();
}
public void adopt(Dog dog){
dog.eat();
dog.jump();
}
public void adopt(Cat cat){
cat.eat();
cat.jump();
}
// ...
多态转型
java
public static void main(String[] args) {
Person p1 = new Man();
// 不能直接调用子类特有的结构
// p1.earnMoney();
// System.out.println(p1.isSmoking);
// 向下转型:使用强转符
Man m1 = (Man)p1;
m1.earnMoney();
System.out.println(m1.isSmoking);
System.out.println(p1 == m1);//true, p1 和 m1 指向堆空间中的同一个对象。
/**
* 向下转型可能会出现:类型转换异常(ClassCastException)
*/
Person p2 = new Woman();
// Man m2 = (Man)p2;
// m2.earnMoney();
/**
* 1. 建议在向下转型之前,使用instanceof进行判断,避免出现类型转换异常
* 2. 格式:a instanceof A;判断对象a是否是类A的实例。
* 3. 如果a instanceof A 返回true,则:
* a instanceof superA 返回也是true。其中,A 是superA的子类。
*/
if(p2 instanceof Man){
Man m2 = (Man)p2;
m2.earnMoney();
}
if(p2 instanceof Woman){
System.out.println("Woman");
}
if(p2 instanceof Person){
System.out.println("Person");
}
if(p2 instanceof Object){
System.out.println("Object");
}
}
向上转型:多态
向下转型:强转
-
建议在向下转型之前,使用instanceof进行判断,避免出现类型转换异常
-
格式:a instanceof A;判断对象a是否是类A的实例。
-
如果a instanceof A 返回true,则:
a instanceof superA 返回也是true。其中,A 是superA的子类。
object类
Java中声明的类,如果没有显式的声明其父类时,则默认继承于java.lang.Object
类 java.lang.Object 是类层次结构的根类,即所有其它类的父类。每个类都使用 Object 作为超类。
Object类中声明的结构(属性、方法等)就具有通用性。
Object类中没有声明属性
Object类提供了一个空参的构造器(与类没有空参构造器时系统提供的空参构造器无关)
重点关注:Object类中声明的方法
重点方法: equals() \ toString()
equals() :任何引用数据类型均可使用
-
自定义的类在没有重写 `Object` 中 `equals()` 方法的情况下,调用的就是 `Object` 类中声明的 `equals()`。比较两个对象的引用地址是否相同。(或比较两个对象是否指向了堆空间中的同一个对象实体)
-
对于像 `String、File、Date` 和包装类等,它们都重写了 `Object` 类中的 `equals()` 方法,用于比较两个对象的实体内容是否相等。
java
import java.io.File;
import java.util.Objects;
public class main {
public static void main(String[] args) {
User u1 = new User("Tom", 12);
User u2 = new User("Tom", 12);
System.out.println(u1.equals(u2));//false --> true
System.out.println(u1 == u2);//false
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2);//false
System.out.println(str1.equals(str2));//true
File file1 = new File("d:\\abc.txt");
File file2 = new File("d:\\abc.txt");
System.out.println(file1.equals(file2));//true
}
}
class User {
String name;
int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
// @Override
// public boolean equals(Object obj) {
// if (this == obj) {
// return true;
// }
// if (obj instanceof User) {
// User user = (User) obj;
// //方式1:
// // if(this.age == user.age && this.name.equals(user.name)){
// // return true;
// // }else{
// // return false;
// // }
// // 方式2 (更简洁):
// return this.age == user.age && this.name.equals(user.name);
// }
// return false;
// }
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age && Objects.equals(name, user.name);
}
}
==与equals()
java
==:运算符
①使用范围:基本数据类型、引用数据类型
② 基本数据类型:判断数据值是否相等
③引用数据类型变量,比较两个引用变量的地址值是否相等。(或比较两个引用是否指向同一个对象实体)
equals():方法
> 使用范围:只能使用在引用数据类型上。
> 具体使用:对于类来说,重写equals()和不重写equals()的区别。
toString()
-
自定义的类,在没有重写 `Object` 类的 `toString()` 的情况下,默认返回的是当前对象的地址值。
-
像 `String、File、Date` 或包装类等 `Object` 的子类,它们都重写了 `Object` 类的 `toString()`,在调用 `toString()` 时,返回当前对象的实体内容。
java
import java.io.File;
import java.util.Date;
public class main {
public static void main(String[] args) {
User u1 = new User("Tom", 12);
System.out.println(u1.toString());// User@b4c966a --> User{name='Tom', age=12}
System.out.println(u1);// User@b4c966a --> User{name='Tom', age=12}
String s1 = new String("hello");
System.out.println(s1.toString());
File file = new File("d:\\abc.txt");
System.out.println(file);
Date date = new Date();
System.out.println(date);
}
}
class User {
String name;
int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
了解方法: clone() \ finalize()
clone():克隆生成一个内容、结构完全相同的对象
java
public class main {
public static void main(String[] args) {
Animal a1 = new Animal("花花");
try {
Animal a2 = (Animal) a1.clone();
a2.setName("毛毛");
System.out.println("原始对象:" + a1);
System.out.println("a1[name = " + a1.getName() + "]");
System.out.println("clone之后的对象:" + a2);
System.out.println("a2[name = " + a2.getName() + "]");
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
class Animal implements Cloneable {
private String name;
public Animal() {
super();
}
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
finalize():当GC要回收此对象时调用(已被抛弃,无法使用)
注:finalize() 可能导致内部出现循环引用,导致此对象不能被回收。(多个对象回收前调用对方)
java
public class main {
public static void main(String[] args) {
Person p = new Person("Peter", 12);
System.out.println(p);
p = null; // 此时对象实体就是垃圾对象,等待被回收。但时间不确定。
System.gc(); // 强制性释放空间
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = 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;
}
//此方法调用的时机:当GC要回收此对象时,调用如下的方法:
//子类重写此方法,可在释放对象前进行某些操作
//finalize() 可能导致内部出现循环引用,导致此对象不能被回收。
// @Override
// protected void finalize() throws Throwable {
// System.out.println("对象被释放--->" + this);
// }
}