👻👻介是小白飘飘记录韩顺平老师的Java视频中的面向对象的后半部分笔记(286~422),如有不妥请指出🎈🎈
封装
把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(方法),再能对数据进行操作。
封装的三个步骤
● 将属性进行私有化(private)
● 提供一个公共的(public)set方法,用于对属性判断并赋值
● 提供一个公共的(public)get方法,用于获取属性的值
public class objectOriented {
public static void main(String[] args) {
Account p = new Account("sherry", 1, "zbc");
// String name = p.setName("sherry");
// double balance = p.setBalance(1);
// String password = p.setPassword("abc");
System.out.println(p.name + '\t' + p.balance + '\t' + p.password);
}
}
class Account {
String name;
double balance;
String password;
Account(String name, double balance, String password) {
setName(name);
setBalance(balance);
setPassword(password);
}
Account() {
}
public String setName(String name) {
// 长度只能是2,3,4位
if (name.length() > 4 || name.length() < 2) {
this.name = "香飘飘";
return this.name;
} else {
return name;
}
}
public double setBalance(double balance) {
// balance > 20
if (balance < 20) {
this.balance = 20;
return this.balance;
} else {
return balance;
}
}
public String setPassword(String password) {
// 密码长度必须大于6位,否则警告并设为默认值
if (password.length() < 6) {
this.password = "123456";
return this.password;
}
return password;
}
}
继承🔔
继承的引出(代码冗余度高,下面小栗子中的小学生与大学生类几乎是一样的)
public class objectOriented {
public static void main(String[] args) {
pupil p = new pupil("小椰", 22);
p.test();
p.setScore(90);
p.info();
undergraduate wills = new undergraduate("沐沐", 24);
wills.test();
wills.setScore(91);
wills.info();
}
}
/*
模拟小学生考试情况
*/
class pupil {
public String name;
public int age;
private double score;
pupil (String name, int age) {
this.name = name;
this.age = age;
}
public void setScore(double score) {
this.score = score;
}
public void test() {
System.out.println("001号小学生" + name + "正在考数学。。。");
}
public void info() {
System.out.println(name + '\t' + age + '\t' + score);
}
}
class undergraduate {
public String name;
public int age;
private double score;
undergraduate (String name, int age) {
this.name = name;
this.age = age;
}
public void setScore(double score) {
this.score = score;
}
public void test() {
System.out.println("001号大学生" + name + "正在考数学分析。。。");
}
public void info() {
System.out.println(name + '\t' + age + '\t' + score);
}
}
001号小学生小椰正在考数学。。。
小椰 22 90.0
001号大学生沐沐正在考数学分析。。。
沐沐 24 91.0
如何解决上面代码出现的问题:
😃😃继承可以解决代码复用,让我们的编程更靠近人类思维,当多个类存在相同的属性(变量)和方法时
,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends继来声明承父类。
- 基本语法
class 子类 extends 父类 {}
Java 与 C++ 定义继承类的方式十分相似。Java 用关键字extends代替了 C++ 中的
冒号 :
,在 Java 中,所有的继承都是公有继承,而没有 C++ 中的私有继承和保护继承。
下面例子中尽管student类是一个超类,但并不是因为它优于子类或者拥有比子类更多的功能。 实际上恰恰相反,子类比超类拥有的功能更加丰富。
public class objectOriented {
public static void main(String[] args) {
pupil p = new pupil("小耶", 22);
p.test();
p.setScore(99);
p.info();
System.out.println("=============");
undergraduate q = new undergraduate("沐沐", 23);
q.test();
q.setScore(100);
q.info();
}
}
/*
模拟小学生考试情况
*/
//父类
class student {
public String name;
public int age;
private double score;
student (String name, int age) {
this.name = name;
this.age = age;
}
public void setScore(double score) {
this.score = score;
}
public void info() {
System.out.println(name + '\t' + age + '\t' + score);
}
}
//子类:小学生pupil
class pupil extends student{
pupil(String name, int age) {
super(name, age);
}
public void test() {
System.out.println("小学生正在考小学数学。。。");
}
}
//子类:大学生:undergraduate
class undergraduate extends student {
undergraduate(String name, int age) {
super(name, age);
}
public void test() {
System.out.println("大学生正在考数学分析。。。");
}
}
小学生正在考小学数学。。。
小耶 22 99.0
=============
大学生正在考数学分析。。。
沐沐 23 100.0
子类继承父类
1.子类访问父类的私有属性与方法
子类继承了所有属性和方法,非私有的属性和方法可以直接在子类访问,但私有属性和方法不能直接在子类访问,要通过公共方法(接口)去访问。
- 上面代码的student类中的score属性是私有的,我们直接访问是不行的,可以在父类中创建一个公共的方法返回属性score的值。
2.子类必须调用父类的构造器,完成父类的初始化
● 子类无参构造器中隐藏的super()默认调用父类的无参构造器。
● 当创建子类对象时,不管子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类中没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的构造器完成对父类的初始化,否则编译不通过。
public class objectOriented {
public static void main(String[] args) {
pupil p1 = new pupil();
System.out.println("=========");
pupil p2 = new pupil("沐沐", 23);
}
}
/*
模拟小学生考试情况
*/
//父类
class student {
public String name;
public int age;
private double score;
student(String name, int age) {
this.name = name;
this.age = age;
System.out.println("父类的构造器student(String name, int age)被调用。。。");
}
//无参构造器
student() {
System.out.println("父类的无参构造器被调用。。。");
}
}
//子类:小学生pupil
class pupil extends student {
pupil() {
System.out.println("子类pupil无参构造器被调用。。。");
}
pupil(String name, int age) {
super(name, age);
System.out.println("子类pupil构造器pupil(String name, int age)被调用。。。");
}
public void test() {
System.out.println("小学生正在考小学数学。。。");
}
}
父类的无参构造器被调用。。。
子类pupil无参构造器被调用。。。
=========
父类的构造器student(String name, int age)被调用。。。
子类pupil构造器pupil(String name, int age)被调用。。。
把父类的构造器注释掉后,编译会报错
3.super关键字
因为 super 不是一个对象的引用,不能将 super 赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。
- 用途
- 访问父类的属性,但
不能直接访问私有属性
。(super.属性名)- 调用父类的方法。当父类和子类都具有相同的方法名时,可以使用 super 关键字访问父类的方法。但
不能直接访问私有方法
。(super.方法名)- 调用父类的构造器。调用构造器的语句只能作为另一个构造器的第一条语句出现 。构造参数既可以传递给本类( this) 的其他构造器,也可以传递给超类(super ) 的构造器 。(super(***))
- 注意事项与使用细节
- super()必须是子类构造器的首行。
- super与this不能同时出现。(这两都要放首行,在一起会打起来!!!)
- 子类构造器中super(形参1, 形参2, …); 会找父类中对应参数个数的构造器执行以完成初始化。
- super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员,如果多个父类中都有同名的成员,使用super访问遵循就近原则。
- this与super区别
区别 | this | super |
---|---|---|
访问属性 | 访问本类中的属性,如果本类中没有此属性则从父类中继续查找 | 从父类开始查找属性 |
调用方法 | 访问本类中的方法,如果本类中没有此方法则从父类中继续查找 | 从父类开始查找方法 |
调用构造器 | 调用本类构造器,必须放在构造器首行 | 调用父类构造器,必须放在子类构造器的首行 |
特殊 | 表示当前对象 | 子类中访问父类对象 |
this 指的是当前对象的引用,super 是当前对象的父对象的引用。如果构造>方法的第一行代码不是 this() 和super(),则系统会默认添加 super()。
● super 关键字的用法:
○ super.父类属性名:调用父类中的属性
○ super.父类方法名:调用父类中的方法
○ super():调用父类的无参构造方法
○ super(参数):调用父类的有参构造方法
● this 关键字的用法:
○ this.属性名:表示当前对象的属性
○ this.方法名(参数):表示调用当前对象的方法
4.Java中所有类都是Object类的子类,Object类是顶级父类
- Object类存储在java.lang包中,使用的时候无需显示导入,编译时由编译器自动导入。是所有java类(Object类除外)的终极父类,不过接口不继承Object类。
- 可以使用类型为Object的变量指向任意类型的对象。Object类的变量只能用作各种值的通用持有者,要对他们进行任何专门的操作,都需要知道它们的原始类型并进行类型转换。
5.super调用父类成员不限于直接父类,一直追溯到顶级父类Object
● 子类最多只能继承一个父类(直接继承),Java中是单继承机制。让A类继承B类和C类:先让A继承B,再让B继承C。
6.不能滥用继承,子类和父类必须满足 is-a 的逻辑关系
有一个用来判断是否应该设计为继承 关系的简单规则,这就是“
is-a
”规则,它表明子类的每个对象也是超类的对象,比如每个经理都是雇员,反之,并不是每个雇员都是经理。“ is-a”规则的另一种表述法是置换法则。它表明程序中出现超类对象的任何地方都可以用子类对象置换。
7.阻止继承:final关键字
● final 修饰的类不能被继承,但是可以实例化对象。
● final 修饰的方法不能被子类重写,但是可以被继承。
public class objectOriented {
public static void main(String[] args) {
A a = new A();
a.out();
}
}
class B {
//B类不是final类,但含有final方法。该方法虽然不能重写,但可以被继承
public final void out() {
System.out.println("final修饰的out()方法。。。");
}
}
class A extends B {
}
- final 修饰的变量(成员变量或局部变量)即成为常量,只能赋值一次。
- final 修饰的成员变量必须在声明的同时赋值,如果在声明的时候没有赋值,那么只有 一次赋值的机会,而且只能在构造方法中显式赋值,然后才能使用。
- final 修饰的局部变量可以只声明不赋值,然后再进行一次性的赋值:
○ final修饰的普通属性
:
■ 在声明时赋值。
■ 在普通代码块中赋值。
■ 在构造器中赋值。
○ final修饰的静态属性
:
■ 在声明时赋值。
■ 在静态代码块中赋值。
class A {
//定义时直接赋值
public final double MY_NUM = 777;
//在构造器中赋值
public final double MY_NUM2;
public A(double MY_NUM2) {
this.MY_NUM2 = MY_NUM2;
}
//在代码块中赋值
public final double MY_NUM3;
{
this.MY_NUM3 = 666;
}
}
class B {
//定义时直接赋值
public static final double MY_NUM = 777;
//在代码块中赋值
public static final double MY_NUM3;
static {
MY_NUM3 = 666;
}
}
● 如果一个类已经被final修饰,就没有必要再其含有的方法用final修饰。
● final不能修饰构造器。
● final往往与static搭配使用(如不会导致类的加载,仅加载修饰的成员)。
下面两段代码的区别:
public class objectOriented {
public static void main(String[] args) {
System.out.println(B.num);
}
}
class B {
public static int num = 123;
static {
System.out.println("B 的静态代码块被执行");
}
}
B 的静态代码块被执行
123
public class objectOriented {
public static void main(String[] args) {
System.out.println(B.num);
}
}
class B {
public final static int num = 123;
static {
System.out.println("B 的静态代码块被执行");
}
}
123
继承的本质
- 小栗子
public class objectOriented {
public static void main(String[] args) {
Son son = new Son();
//1.先看子类Son是否有该属性
//2.若子类Son中有该属性并且可以访问,直接返回
//3.若子类Son的中没有这个属性,就看其父类中是否有该属性(若有并且可以访问就返回该信息)
//4.父类Father中没有按照3一直追溯到Object,都没有就要报错
System.out.println(son.name);
System.out.println(som.age);
System.out.println(son.label);
}
}
//超类
class GrandFa {
String name = "小耶";
String label = "小财迷";
int age = 19;
}
//父类1
class Father extends GrandFa {
String name = "沐沐";
private int age = 20;
}
//子类2
class Son extends Father{
String name = "狗总";
String hobby = "口嗨";
}
狗总
19
小财迷
(1)在new Son();时先加载的是其父类,最先加载的是Object顶级父类,再加载GrandFa父类,再加载Father父类,再加载Son;
(2)访问属性信息时,就近原则。没有就找父类,一直追溯到Object。
(3)当父类的属性被 private 修饰时变成私有的了,那上面的Father中的属性age,依然在堆中对象里,只是不能访问(只能在本类访问),又回到上面说的,让Father提供一个公有的方法,通过Son继承后再访问。
方法重写/覆盖
子类有一个方法和父类的某个方法的名称、返回类型、参数一样,那么就称子类的方法覆盖了父类的方法。
(1)子类的方法的形参列表
、方法名称
,要和父类方法的参数,方法名称完全一样
。
(2)子类方法的返回类型和父类方法返回类型一样,或者是父类的返回类型的子类。比如父类返回类型是Object,子类返回类型是String
(3)子类不能方法不能缩小父类方法的访问权限,允许相等或扩大。
重载与重写区别
名称 | 发生范围 | 方法名 | 形参列表 | 返回类型 | 修饰符 |
---|---|---|---|---|---|
重载(overload) | 本类 | 必须一样 | 类型、个数或者顺序至少有一个不同 | 无要求 | 无要求 |
重写(override) | 父子类 | 必须一样 | 相同 | 返回类型一样,或者是父类的返回类型的子类 | 子类不能方法不能缩小父类方法的访问权限,允许相等或扩大 |
- 重写(override)小栗子
public class Rose {
public static void main(String[] args){
Student p =new Student("小耶", 20, "1001", 90);
String res = p.say();
System.out.println(res);
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String say() {
return "name=" + name + "\tage=" + age;
}
}
class Student extends Person {
private String id;
private double score;
public Student(String name, int age, String id, double score) {
super(name, age);
this.id = id;
this.score = score;
}
public String say() {
return super.say() + "\tid=" + id + "\tscore=" + score;
}
}
name=小耶 age=20 id=1001 score=90.0
Object类
equals()方法
● ==和equals的区别
(1)==是一个比较运算符。既可以判断基本类型
,又可以判断引用类型
。如果判断基本类型,判断的是值是否相等。如果判断引用类型,判断的是地址相是否等,即判断是不是同一个对象。
(2)equals()方法用于检测一个对象是否等于另外一个对象,只能判断引用类型。在子类中定义eqauls()方法时,首先调用超类的eqauls()方法。如果检测失败,对象就不可能相等。如果超类中的域都相等,就需要比较子类的实例域(域就是java里的字段,也叫属性或成员变量)。
● equals()特性
(1)自反性:对于任何非空引用x,x.equals(x)
应该返回true。
(2)对称性:对于任何引用x和y,当且仅当y.equals(x)
返回true,x.equals(y)也应该返回true。
(3) 传递性:对于任何引用x,y,z,如果x.equals(y)
返回true,y.equals(z)
返回true,x.equals(z)也应该返回true。
(4)一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y)
应该返回相同的结果。
(5)对于任意非空引用x
,x.equals(null)
应该返回false。
//Object中的equals()
public boolean equals(Object obj) {
return (this == obj); //默认比较地址,即是否判断同一对象
}
//String中的equals()把Object中的equals()方法重写了,变成了两个字符串的值是否相等
public boolean equals(Object anObject) {
if (this == anObject) { //如果是同一对象直接返回true
return true;
}
if (anObject instanceof String) { //判断类型
String anotherString = (String)anObject; //向下转型
int n = value.length;
if (n == anotherString.value.length) { //如果长度相同
char v1[] = value; //转成char数组,一个一个比较字符
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true; //如果两个字符串的所有字符都相等,则返回true
}
}
return false; //如果比较的不是字符串,则返回false
}
//Integer中也重写了Object的equals()方法,变成判断两个值是否相等
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person("鬼鬼", 19, '女');
Person p2 = new Person("鬼鬼", 19, '女');
//未重写equals方法返回的是false
System.out.println(p1.equals(p2));
}
}
class Person {
private String name;
private int age;
private char gender;
public Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
//重写Object的equals()
public boolean equals(Object obj) {
if (this == obj) { //先判断是否是同一个对象,是就直接返回true
return true;
}
if (obj instanceof Person) { //类型判断,是Person我们才比较
Person p = (Person) obj; //向下转型
return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
}
return false;
}
}
true
hashcode()方法
● hash
● hashcode
● 小结
(1)提高具有哈希结构的容器的效率。
(2)两个引用,如果指向的是同一个对象,则哈希值肯定是一样的;如果指向的是不同对象,则哈希值是不一样的。
(3)哈希值主要根据物理地址得到,但不能将哈希值当等价于地址。hashcode代表对象的地址说的是对象在hash表中的位置,物理地址说的是对象在内存中的位置。通过对象的物理地址转换成一个整数,然后该整数通过hash函数得到hashcode。
(4)在集合中也会向equals()那样进行重写。
toString()方法
(1)
返回该对象的字符串表示
。
(2)Object中的toString()方法(默认方法)返回:全类名(包名+类名) + @ + 哈希值的十六进制。换句话说,该方法返回一个字符串,他的值等于:getClass().getName() + ‘@’ + Integer.toHexString(hashCode())
(3)子类往往重写toString()方法,用于返回对象的属性信息。
(4)当直接输出一个对象时,toString()方法会被默认调用。
public class Rose {
public static void main(String[] args) {
Monster ms = new Monster("鬼鬼", 19, 20000);
//默认返回:Monster@1b6d3586 hashcode=460141958
//重写后:Monster{name='鬼鬼', age=19, salary=20000.0} hashcode=460141958
System.out.println(ms.toString() + "\thashcode=" + ms.hashCode());
}
}
class Monster {
private String name;
private int age;
private double salary;
public Monster(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
//重写toString()方法,输出对象的属性
// alt + ins -> toString
@Override
public String toString() {
return "Monster{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}
finalize()方法
● 当垃圾回收器确定不存在对该改对象的更多引用时,由对象的垃圾回收器调用此方法。
(1)当对象被回收时,系统自动调用该对象的finalize()方法,子类可以重写该方法,做一些释放资源的操作。
(2)什么时候被回收:当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁对象前,会调用finalize()方法。
(3)垃圾回收机制的调用,是由系统决定的,也可以通过System.gc()主动触发垃圾回收机制。
(4)finalize()与C++中的析构函数不是对应的。C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中的finalize的调用具有不确定性。
(5)一般避免使用它,可能出现的情况是在我们耗尽资源之前,gc却仍未触发。不建议用finalize方法完成“非内存资源”的清理工作。但建议用于本地对象清理。
多态
● 小栗子引出多态
Master类中有一个feed方法,可以完成主人给动物为食的信息。
public class Rose {
public static void main(String[] args){
Master p = new Master("小耶", 20);
Dog dog = new Dog("狗总");
Bone ribs = new Bone("大猪排");
p.feed(dog, ribs);
Cat cat = new Cat("沐沐");
Fish fish = new Fish("冰淇凌鱼");
System.out.println("========");
p.feed(cat, fish);
}
}
// 主人类
class Master {
private String name;
private int age;
public Master(String name, int age) {
this.name = name;
this.age = age;
}
// 主人给小狗喂骨头
public void feed(Dog dog, Bone bone) {
System.out.println("主人" + name + "给" + dog.getName() + "吃" + bone.getFood());
}
// 主人给小猫喂鱼
public void feed(Cat cat, Fish fish) {
System.out.println("主人" + name + "给" + cat.getName() + "吃" + fish.getFood());
}
}
// 食物类
class Food {
private String food;
public Food(String food) {
this.food = food;
}
public String setFood(String food) {
return food;
}
public String getFood() {
return food;
}
}
// 鱼类,食物子类
class Fish extends Food {
public Fish(String food) {
super(food);
}
}
// 骨头类,食物子类
class Bone extends Food {
public Bone(String food) {
super(food);
}
}
// 动物类
class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 猫类
class Dog extends Animal {
public Dog(String name) {
super(name);
}
}
// 狗类
class Cat extends Animal {
public Cat(String name) {
super(name);
}
}
主人小耶给狗总吃大猪排
========
主人小耶给沐沐吃冰淇凌鱼
当有多种食物与动物时,feed方法就会重载多次,这样就显得很low。多态的使用一节中对Master类的feed()方法作出修改。
多态(polymorphic):方法或对象具有多种形态,是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
对象的多态
● 一个对象的编译类型和运行类型可以不一致。
● 编译类型在定义对象时就确定了,不能改变。
● 运行类型可以改变。
●编译类型
看定义时 = 号左边,运行类型
看 = 号右边。
● 以运行类型为主。
public class Rose {
public static void main(String[] args){
// 对象多态的特点
// 父类的一个引用可以指向子类的一个对象,animal编译类型是Animal,运行类型是Dog
Animal animal = new Dog();
// animal的运行类型变成了Cat,编译类型仍然是Animal
animal.cry(); //在执行cry()时,animal的运行类型是Dog,所以cry()就是Dog的cry()
//编译类型还是Animal,运行类型变成Cat
animal = new Cat();
animal.cry();
}
}
class Animal {
public void cry() {
System.out.println("Animal cry() 动物在叫。。。");
}
}
class Cat extends Animal {
@Override
public void cry() {
//super.cry();
System.out.println("Cat cry() 小猫喵喵喵。。。");
}
}
class Dog extends Animal {
@Override
public void cry() {
System.out.println("Dog cry() 小狗汪汪汪。。。");
}
}
Dog cry() 小狗汪汪汪。。。
Cat cry() 小猫喵喵喵。。。
多态的使用
使用多态的前提:两个对象(或类)存在继承关系。
对多态开头提的Master类的feed方法做出如下改进。//使用多态机制统一管理喂食方法 //animal的编译类型是Animal。可以指向(接收)Animal子类的对象 //food的编译类型是Food,可以指向(接收)Food子类的对象 public void feed(Animal animal, Food food) { System.out.println("主人" + name + "给" + animal.getName() + "吃" + food.getFood()); }
多态的向上转型
● 本质:父类的引用指向了子类的对象。
● 语法:父类类型 引用名 = new 子类类型();
● 特点:
○ 编译类型看左边,运行类型看右边。
○ 可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成>员。
○ 最终运行效果看子类的具体体现。
多态的向下转型
● 语法:
子类类型 引用名 = (子类类型) 父类引用;
● 只能强转父类的引用,不能强转父类的对象。
● 要求父类的引用必须指向的是当前目标类型的对象。
● 即:把指向子类对象的父类引用,转成指向子类对象的子类引用。
● 当向下转型后,可以调用子类类型中所有的成员。
public class Rose {
public static void main(String[] args) {
//向上转型
Animal animal = new Cat("小椰", 21, "白");
//不能调用子类的特有成员。
//在编译阶段,由编译器决定能调用哪些成员,animal的编译类型是Animal,在Animal中找不到catchMouse方法
//animal.catchMouse(); //报错了
//在运行阶段,由于animal的运行类型是Cat,按就近原则,先在Cat中找eat()方法
animal.eat();
//向下转型
Cat cat = (Cat)animal;
cat.catchMouse();
//要求父类的引用必须指向的是当前目标类型的对象。
//因为原先的animal指向对象就是Cat:Animal animal = new Cat("小椰", 21, "白");
//animal的引用cat,其类型也是Cat;Cat cat = (Cat)animal;
//换句话说原来就是一只猫,现在还是一只猫,是可以的,但不能指猫为狗。
Dog dog = (Dog)animal; //报错
dog.fly();
}
}
class Animal {
String name;
int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void sleep() {
System.out.println("睡");
}
public void eat() {
System.out.println("吃");
}
}
class Cat extends Animal {
String color;
public Cat(String name, int age, String color) {
super(name, age);
this.color = color;
}
public void eat() {
System.out.println("猫猫在吃");
}
public void catchMouse() {
System.out.println("猫猫抓老鼠");
}
}
猫猫在吃
猫猫抓老鼠
Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to Dog
at Rose.main(Rose.java:19)
属性重写问题
● 属性没有重写之说!
属性的值
看编译类型。方法
看运行类型。
●instanceof
比较操作符,用于判断对象的类型(运行类型)是否为某某类型或某某类型的子类型。
package com.xiaoye;
public class Jasmine {
public static void main(String[] args) {
// num的编译类型是Base,运行类型是Sub
Base num = new Sub();
System.out.println(num.count);
//num1的编译类型,运行类型都是Sub
Sub num1 = new Sub();
System.out.println(num1.count);
Object obj = new Object();
System.out.println(num instanceof Base);
System.out.println(num instanceof Sub);
System.out.println(obj instanceof Base);
}
}
class Base {
int count = 10;
}
class Sub extends Base {
int count = 20;
}
10
20
true
true
false
动态绑定机制
● java的动态绑定机制
(1)当调用对象方法
的时候,该方法会和该对象的内存地址/运行类型绑定。
(2)当调用对象属性
时,没有动态绑定机制,哪里声明,哪里使用。
public class Jasmine {
public static void main(String[] args) {
A a = new B();
System.out.println(a.i); //10
System.out.println(a.getI()); //20
System.out.println(a.sum()); //40
System.out.println(a.sum1()); //30
}
}
public class A {
public int i = 10;
public int getI() {
return i;
}
public int sum() {
return getI() + 10;
}
public int sum1() {
return i + 10;
}
}
public class B extends A{
public int i = 20;
public int sum() {
return i + 20;
}
public int getI() {
return i;
}
public int sum1() {
return i + 10;
}
}
10
20
40
30
删掉子类B中的sum()和getI()方法
public class Jasmine {
public static void main(String[] args) {
A a = new B();
System.out.println(a.i); //10->10
System.out.println(a.getI()); //20->10
System.out.println(a.sum()); //40->20
System.out.println(a.sum1()); //30->30
}
}
public class A {
public int i = 10;
public int getI() {
return i;
}
public int sum() {
return getI() + 10;
}
public int sum1() {
return i + 10;
}
}
public class B extends A{
public int i = 20;
public int sum1() {
return i + 10;
}
}
10
10
20
30
多态应用
多态数组
数组的定义类型为父类类型,里面保存的元素的类型为子类类型。
● 小栗子05——创建1个Person对象,2个Student对象,2个Teacher对象。统一放在数组中,并调用每个对象的say方法,对Student和Teacher增加study和teach功能。
package com.xiaoye;
public class Jasmine {
public static void main(String[] args) {
//创建1个Person对象,2个Student对象,2个Teacher对象
//统一放在数组中,并调用每个对象的say方法
Person[] persons =new Person[5];
persons[0] = new Person("小椰", 20);
persons[1] = new Student("狗总", 21, 90);
persons[2] = new Student("飘飘", 20, 79);
persons[3] = new Teacher("随云", 25, 18000);
persons[4] = new Teacher("沐沐", 22, 20000);
for (int i = 0; i < persons.length; i++) {
System.out.println(persons[i].say()); //会有动态绑定机制
// persons[i].study()报错,因为persons[i]的编译类型是Person,Person中没有子类的特有方法
//判断persons[i]的运行类型是否是Student
if (persons[i] instanceof Student) {
//运行类型是Student的话,就将编译类型强转成Student,向下转型
//或((Student)persons[i]).study("数据结构与算法")
Student student = (Student)persons[i];
System.out.println(student.study("数据结构与算法"));
} else if (persons[i] instanceof Teacher) {
System.out.println(((Teacher)persons[i]).teach("数据库"));
} else if (persons[i] instanceof Person) {
System.out.println("非老师/学生");
} else {
System.out.println("输入类型有误。。。");
}
}
}
}
public 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 String say() {
return "name=" + name + "\tage=" + age;
}
}
public class Student extends Person{
private double score;
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String say() {
return super.say() + "\tscore=" + score;
}
//特有方法
public String study(String book) {
return "学生" + getName() + "正在学" + book;
}
}
public class Teacher extends Person{
private double salary;
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String say() {
return super.say() + "\tsalary=" + salary;
}
//特有方法
public String teach(String book) {
return getName() + "老师" + "正在教" + book;
}
}
name=小椰 age=20
非老师/学生
name=狗总 age=21 score=90.0
学生狗总正在学数据结构与算法
name=飘飘 age=20 score=79.0
学生飘飘正在学数据结构与算法
name=随云 age=25 salary=18000.0
随云老师正在教数据库
name=沐沐 age=22 salary=20000.0
沐沐老师正在教数据库
多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型。
● 小栗子06——定义员工类Employee,包含姓名和月工资, 以及计算年工资的方法getAnnual()。普通员工和经理继承了员工类,经理类多了奖金属性bonus和管理方法manage(),普通员工多了work()方法,经理类要求重写计算年薪的方法。测试类中添加一个方法showEmpAnnal(Employee e),实现获取任何员工对象的年薪,并在main方法中调用该方法.测试类中添加一个方法testwork(),如果是普通员工,则调用work()方法,如果是经理,则调用manage()方法。
package com.piaopiao;
public class Stick {
public static void main(String[] args) {
Staff mm= new Staff("沐沐", 20000);
Manager xy = new Manager("小椰", 25000, 200000);
Stick stick = new Stick();
stick.showEmployeeAnnual(mm);
stick.showEmployeeAnnual(xy);
stick.testWork(mm);
stick.testWork(xy);
}
//获取任何对象的年薪
public void showEmployeeAnnual(Employee e) {
System.out.println(e.getAnnual());
}
//testWork
public void testWork(Employee e) {
if (e instanceof Staff) {
System.out.println(((Staff) e).work());
} else if (e instanceof Manager) {
System.out.println(((Manager) e).manage());
}
}
}
240000.0
500000.0
员工沐沐正在干活。。。
经理小椰正在阿巴阿巴。。
package com.piaopiao;
public class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary; //单位:万
}
//员工名字
public String getName() {
return name;
}
//获取月工资
public double getSalary() {
return salary;
}
//计算年薪
public double getAnnual() {
return salary * 12;
}
}
package com.piaopiao;
public class Staff extends Employee{
public Staff(String name, double salary) {
super(name, salary);
//this.overtime = overtime;
}
//员工的work()方法
public String work() {
return "员工" + getName() + "正在干活。。。";
}
}
package com.piaopiao;
public class Manager extends Employee{
private double bonus; //经理有奖金
public Manager(String name, double salary, double bonus) {
super(name, salary);
this.bonus = bonus;
}
@Override
public double getAnnual() {
return super.getAnnual() + bonus;
}
public String manage() {
return "经理" + getName() + "正在阿巴阿巴。。";
}
}
类变量和类方法
类变量
也叫
静态变量/静态属性
,是独立于方法之外的变量,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,得到的都是相同的值;同样任何一个该类的对象去修改它时,修改的也是同一个变量,用static修饰。
// 1.定义类变量
// 访问修饰符 static 数据类型 变量名
// static 访问修饰符 数据类型 变量名
// 2.访问类变量(静态变量的访问修饰符的访问权限和范围和普通属性一样的)
// 类名.类变量名 或者 对象名.类变量名
public class objectOriented {
public static void main(String[] args) {
// 类变量是随着类的加载而创建,即使没有创建对象实例也可以访问
System.out.println(AA.name); //沐沐
}
}
class AA {
public static String name = "沐沐";
}
● 类变量的使用
(1)当某个类需要所有对象都共享一个变量时,就可以考虑使用类变量(静态/全局变量)。
(2)类变量是在类加载时就初始化了,即使没有创建对象,只要类加载了,就可以使用类变量。
(3)类变量的生命周期是随类加载(类在何时加载)开始,随着类消亡而销毁。
● 类变量与实例变量(普通属性)的区别
(1)类变量是该类的所有对象共享,其中一个对象将它的值改变,其他对象得到是改变后的值。
(2)实例变量是每个对象独享(私有),某一个对象将其值改变,不影响其他对象的调用。
类方法
也叫
静态方法
。将类本身作为对象进行操作的方法。用static修饰类中的方法,类方法属于整个类的,所以类方法的方法体中不能有与类的对象有关的内容。
// 定义类方法
// 访问修饰符 static 数据返回类型 方法名(){}
// static 访问修饰符 数据返回类型 方法名(){}
// 类方法的调用
//类名.类方法名 或 对象名.类方法名
public class objectOriented {
public static void main(String[] args) {
Student.payFee(1000);
Student straw = new Student("Straw");
straw.payFee(2000);
Student.getFee(); //3000.0
}
}
class Student {
private String name;
private static double fee = 0;
public Student(String name) {
this.name = name;
}
public static void payFee(double money) {
Student.fee += money;
}
public static void getFee() {
System.out.println("总费用:" + Student.fee);
}
}
● 类方法的使用
(1)当方法中不涉及到任何和对象相关的成员(如与对象有关的关键字this和super),可以将方法设计成静态方法。
(2)类方法(静态方法)中只能访问静态变量或静态方法。
(3)普通成员方法既可以访问非静态成员,也可以访问静态成员。
(4)静态方法只能被继承,不能被重写。如果子类有和父类相同的静态方法,那么父类的静态方法将会被隐藏,对于子类不可见。也就是说,子类和父类中相同的静态方法是没有关系的方法,他们的行为不具有多态性。 但是父类的静态方法可以通过父类.方法名调用。
main()方法
● 定义:public static void main(String[] args) {}
(1)main()方法由java虚拟机调用。
(2)java虚拟机需要调用类的main()方法,所以main()方法的访问权限必须是public。
(3)java虚拟机在执行main()方法时不需要创建对象,所以main()方法必须是static。
(4)main()方法没有返回值,所以只能是void。
(5) IDEA中动态传入参数:Edit Configurations -> 输入参数Program arguments -> Apply -> OK->运行
(6) 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数。
public class objectOriented {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println(String.format("args[%d]", i) + "=" + args[i]);
}
}
}
args[0]=22
args[1]=3
args[2]=1
args[3]=4
代码块
又称为
初始化块
,属于类中的成员,类似于方法,将逻辑语句封装在大括号{ }中。但是不同于方法,没有方法名,没有返回值,没有参数,只有方法体,而且不用通过对象或类显示调用,而是加载类时或创建对象时隐式调用。
[修饰符] {
代码块
};
● 注意
- 修饰符可写可不写,但是要写只能是static。
- 有static修饰的代码块叫做静态代码块,没有修饰符修饰的叫普通代码块。
- 最后的分号 ; 可写可不写。
- 代码块相当于另外一种形式的构造器,可以做初始化的操作。
- 如果多个构造器中有重复的语句,可以抽取放入代码块中。
代码块使用细节
- static代码块:对类进行初始化,随
类的加载
而执行,且只会执行一次。- 类什么时候被加载:(详细见上节类变量中的链接)
a. 创建对象实例时(new)。
b. 创建子类对象实例,父类对象也会被加载。
c. 使用类的静态成员(静态变量、静态方法)。- 普通代码块在创建对象实例时,会被隐式调用,每创建一次就会被调用一次,如果只使用类的静态成员时,普通代码块并不会被执行。
- 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。
- 构造器的最前面其实隐含了
super()
和调用普通代码块
。
package com.piaopiao;
public class Demo01 {
public static void main(String[] args) {
new B();
//A的构造器被调用。。。
//B的普通代码块
//B的构造器被调用。。。
}
}
class A {
public A() {
System.out.println("A的构造器被调用。。。");
}
}
class B extends A {
public B() {
System.out.println("B的构造器被调用。。。");
}
{
System.out.println("B的普通代码块");
}
}
● 创建一个对象时,一个类中的调用顺序如下(优先级高的序号靠前)
(1)静态代码块和静态属性初始化调用的优先级一样,按顺序调用。
(2)普通代码块的和普通属性初始化调用的优先级一样,也按顺序调用。
(3)调用构造方法。
package com.piaopiao;
public class Demo01 {
public static void main(String[] args) {
A a = new A();
// A的静态属性
// A的静态代码块
// A的普通属性
// A的普通代码块
// A的无参构造器
}
}
class A {
public A() {
System.out.println("A的无参构造器");
}
private static int age = setAge();
private String name = setName();
//普通代码块
{
System.out.println("A的普通代码块");
}
//静态代码块
static {
System.out.println("A的静态代码块");
}
public String setName() {
System.out.println("A的普通属性");
return "飘飘";
}
public static int setAge() {
System.out.println("A的静态属性");
return 100;
}
}
● 多个类中的调用顺序如下
- 父类的静态代码块与静态属性(优先级一样,谁先定义就先调用谁)。
- 子类的静态代码块与静态属性(优先级一样)。
- 父类的普通代码块与普通属性(优先级一样)。
- 父类的构造方法。
- 子类的普通代码块与普通属性(优先级一样)。
- 子类的构造方法。
抽象层
抽象类
抽象类
当父类的某些方法需要声明,但又不确定如何实现,可以将其声明称抽象(abstract)方法,那这个类就是抽象类。
● 抽象类不能实例化对象,其他功能与普通类一样。
● 因为抽象类不能被实例化,所以抽象类必须被继承。
● 抽象类中可以没有抽象方法。但抽象方法所在的类一定是抽象类。
● 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明成抽象类。
● 抽象方法不能被private、final和static修饰,因为这些关键字和重写相违背。
public class objectOriented {
public static void main(String[] args) {
commonEmployee p = new commonEmployee("沐沐","0001",20000);
p.work();
Manager m = new Manager("小椰", "0002",1200,50000);
m.work();
}
}
abstract class Employee {
private String name;
private String id;
private double salary;
public Employee(String name, String id, double salary) {
this.name = name;
this.id = id;
this.salary = salary;
}
//声明抽象方法work()
public abstract void work();
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
}
class Manager extends Employee{
private double bonus;
public Manager(String name, String id, double salary, double bonus) {
super(name, id, salary);
this.bonus = bonus;
}
@Override
public void work() {
System.out.println("经理" + getName() + "正在工作,赚了" + (getSalary() + bonus) + "元");
}
}
class commonEmployee extends Employee {
public commonEmployee(String name, String id, double salary) {
super(name, id, salary);
}
@Override
public void work() {
System.out.println("普通员工" + getName() + "正在工作,赚了" + getSalary() + "元");
}
}
普通员工沐沐正在工作,赚了20000.0元
经理小椰正在工作,赚了51200.0元
接口
接口
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况补全其方法体。
//1.接口
[可见度] interface 接口名称 [extends 其他的接口名] {
// 声明变量
// 抽象方法
}
//2.实现接口
class 类名 implements 接口名[, 其他接口名...] {
//本类属性
//本类方法
//必须实现接口的抽象方法
}
//3.接口的继承
public interface 子类接口名 extends 父类接口名[, 父类接口名1,...]
● 使用细节
- 接口不能实例化,没有构造器。(在匿名内部类中,只需要在new后实现定义的方法,本质上是类的实例化)
- 接口不是被类继承(extends),而是被类实现(
implements
)。- 接口中所有属性肯定是
public static final
的。在jdk7.0之前,所有的方法都是抽象方法(接口中抽象方法可以省略abstract关键字),在jdk8.0后可以有静态方法、默认方法,即接口中可以有方法具体实现(需要static或default关键字修饰)。- 接口类型可以用来声明变量,他们可以成为一个空指针,或者被绑定在一个以此接口实现的对象。
- 接口中属性访问形式:接口名.属性名。
- 接口的修饰符只能是
public
或默认的
。- 接口不能继承其他类,但是可以继承多个别的接口。
接口vs继承
- 当子类继承父类,就自动拥有父类的功能。如果子类需要扩展功能,可以通过实现接口的方式扩展,接口可以看作是对继承的一种补充。
- 接口比继承更灵活,继承是满足
is-a
关系,接口是满足like-a
关系。- 接口在一定程度上实现代码解耦。
package com.Interface;
public class Jasmine {
public static void main(String[] args) {
littleMonkey p = new littleMonkey("沐沐");
p.climbing();
littleMonkey02 pp = new littleMonkey02("小耶");
pp.climbing();
pp.swimming();
}
}
class Monkey {
private String name;
public Monkey(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void climbing() {
System.out.println(getName() + "会上树。");
}
}
class littleMonkey extends Monkey {
public littleMonkey(String name) {
super(name);
}
@Override
public void climbing() {
super.climbing();
}
}
interface Fish {
public void swimming();
}
class littleMonkey02 extends Monkey implements Fish {
public littleMonkey02(String name) {
super(name);
}
@Override
public void swimming() {
System.out.println(getName() + "又学会了游泳。");
}
}
沐沐会上树。
小耶会上树。
小耶又学会了游泳。
接口的多态
● 多态参数
接口引用可以指向实现了接口的类的对象实例
。
● 多态数组
package com.Interface;
public class Jasmine {
public static void main(String[] args) {
//接口的多态体现
Usb[] usb = new Usb[2];
usb[0] = new Phone(); //接口类型变量 可以指向 实现了接口的对象实例
usb[1] = new Camera();
//继承的多态:
//父类引用 指向 继承了父类的子类 的对象实例。
for (Usb value : usb) {
if (value instanceof Phone) {
((Phone) value).call();
}
value.work();
}
}
}
interface Usb{
public void work();
}
class Phone implements Usb {
@Override
public void work() {
System.out.println("手机开始接入。");
}
public void call() {
System.out.println("手机还能打电话。");
}
}
class Camera implements Usb {
@Override
public void work() {
System.out.println("相机开始接入。");
}
}
手机还能打电话。
手机开始接入。
相机开始接入。
● 多态传递
如果接口2继承了接口1,而某个类实现了接口2,那么也相当于该类实现了接口1。
package com.Interface;
public class Jasmine {
public static void main(String[] args) {
Usb01 u = new Phone();
Usb02 u2 = new Phone();
u2.hi();
}
}
interface Usb01 {
public void hi();
}
interface Usb02 extends Usb01 {}
class Phone implements Usb02 {
//因为Usb02继承了Usb01,那么相当于Phone类也实现了Usb01,必须实现Usb01的方法。
@Override
public void hi() {
System.out.println("hi");
}
}
内部类
一个类的内部又完整的嵌套了另一个类的类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。
● 内部类的最大特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。class Outer { //外部类 class Inner { //内部类 } } class Other { }
● 内部类的分类
- 定义在外部类的
局部位置
上
a. 局部内部类(有类名)
b. 匿名内部类(没有类名)- 定义在外部类的
成员位置
上
a. 成员内部类(无static修饰)
b. 静态内部类(static修饰)
局部内部类
指的是定义在外部类的局部位置上,比如方法中,且有类名。
● 使用细节
- 可以直接访问外部类的所有成员。
- 不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量不能使用修饰符,但是可以使用final修饰。
- 作用域:仅仅在定义它的方法或代码块中。
- 外部类在方法中创建局部内部类的对象。
- 外部其他类不能访问局部内部类。
- 当外部类和局部内部类的成员重名时,默认遵循就近原则,如果向访问外部类的成员,可以使用外部类名.this.成员名(当该成员是静态的可以省略.this,普通成员要加上,外部类名.this指的是外部类的对象)。
package com.Interface;
public class Jasmine {
public static void main(String[] args) {
Outer outer = new Outer();
outer.func();
}
}
class Outer {
private int n = 666;
//private static int n = 666;
private void fun() {
System.out.println("外部类的fun()方法");
}
public void func() {
//局部内部类定义在外部类的局部位置,通常在方法中
//2.内部类不能使用修饰符,可以用final修饰
class Inner { //局部内部类(本质还是一个类,五大成员也可以有)
private int n = 777;
//1.可以访问外部类的所有成员
public void f1() {
//6.外部类与内部类成员重名,访问成员遵循就近原则,访问外部
System.out.println("内部类的n=" + n);
System.out.println("外部类的n=" + Outer.this.n);
System.out.println("Outer.this hashcode=" + Outer.this);
//System.out.println("外部类的n=" + Outer.n);
fun();
}
}
//4.外部类在方法中创建局部内部类的对象
Inner inner = new Inner();
inner.f1();
}
}
outer hashcode=com.Interface.Outer@1b6d3586
内部类的n=777
外部类的n=666
Outer.this hashcode=com.Interface.Outer@1b6d3586
外部类的fun()方法
匿名内部类
指的是定义在外部类的局部位置上,比如方法中,但是没有类名(有名字但是不告诉俺们。有,但没完全有)。
● 使用细节
- 匿名内部类既是一个类的定义,也是一个对象。
- 可以直接访问外部类的所有成员。
- 不能添加访问修饰符,因为它的地位就是一个局部变量。
- 作用域:仅仅在定义它的方法或代码块中。
- 外部类在方法中创建局部内部类的对象。
- 外部其他类不能访问局部内部类。
- 当外部类和局部内部类的成员重名时,默认遵循就近原则,如果向访问外部类的成员,可以使用外部类名.this.成员名。
- 匿名内部类当作实参直接传递,简洁高效。
(1)基于接口的匿名内部类
package com.Interface;
public class Jasmine {
public static void main(String[] args) {
Outer outer = new Outer();
outer.f1();
}
}
class Outer {
private int n = 777;
public void f1() {
//基于接口的匿名内部类
//传统写法:写一个类,实现接口方法,创建对象
/*
新写法:接口不能实例化,这里本质还是类的实例化
a的编译类型:接口A类型
a的运行类型:匿名内部类xxxx -> Outer$1(程序运行时临时生成的类,编译时找不到)
底层创建匿名内部类时,就立即创建了Outer$1实例,并把地址返回给a
匿名内部类只能使用一次,但其创建的对象可以想用几次用几次
class Outer$1 implements A {
@Override
public void cry() {
System.out.println("哭唧唧");
}
}
*/
A a = new A() {
@Override
public void cry() {
System.out.println("哭唧唧");
}
};
System.out.println("a的运行类型:" + a.getClass().getName());
a.cry();
}
}
interface A {
public void cry();
}
a的运行类型:com.Interface.Outer$1
哭唧唧
(2)基于类的匿名内部类
package com.Interface;
public class Jasmine {
public static void main(String[] args) {
Outer outer = new Outer();
outer.f1();
}
}
class Outer {
//private int n = 777;
public void f1() {
/*
b的编译类型:类B
b的运行类型:xxxx -> Outer$1
class Outer$1 extends B {
public Outer$1(String name) {
super(name);
}
}
*/
B b = new B("沐沐") {
@Override
public void f2() {
super.f2();
System.out.println(name + ",再见");
}
};
System.out.println("B对象的运行类型:" + b.getClass().getName());
b.f2();
/*
基于抽象类的匿名内部类
*/
new C() {
@Override
void f3() {
System.out.println("贴贴");
}
}.f3();
}
}
class B {
public String name;
public B(String name) {
this.name = name;
}
public void f2() {
System.out.println(name + ",你好");
}
}
B对象的运行类型:com.Interface.Outer$1
沐沐,你好
沐沐,再见
贴贴
(3)匿名内部类当作实参直接传递
package com.Interface;
public class Jasmine {
public static void main(String[] args) {
cellPhone cellPhone = new cellPhone();
cellPhone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("懒猪起床了");
}
});
cellPhone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("小伙伴起床了");
}
});
}
}
interface Bell {
void ring();
}
class cellPhone {
public void alarmClock(Bell bell) {
bell.ring(); //动态绑定
}
}
懒猪起床了
小伙伴上课了
成员内部类
可以访问外部类的私有成员,作为外部类的一个成员存在,与外部类的属性、方法并列。但是没有static修饰。
● 使用细节
- 可以直接访问外部类的所有成员。
- 可以添加任意访问修饰符(public、protected、默认、private)。
- 作用域:和外部类的成员一样。为整个类体。
- 外部类访问内部类要先创建对象;成员内部类可直接访问外部类,如果成员内部类与外部类的属性重名,会遵守就近原则,如果想访问外部类的属性:外部类名.this.属性名。
- 外部类其他类访问成员内部类的两种方式。
package com.Interface;
public class Jasmine {
public static void main(String[] args) {
Outer outer = new Outer();
outer.test();
//外部其他类访问成员内部类的两种方式
//1.用外部类的一个对象创建了一个内部类的实例,相当于把new Inner()当作outer的一个成员
Outer.Inner inner1 = outer.new Inner();
inner1.outPut();
//2.在外部类的一个方法中返回内部类对象实例
Outer.Inner inner2 = outer.getInner();
inner2.outPut();
}
}
class Outer {
private int n = 666;
//private static int n = 666;
private void fun() {
System.out.println("外部类的fun()方法");
}
public class Inner {
private double m = 7.7;
public void outPut() {
System.out.println("成员内部类");
}
}
public void test() {
Inner inner = new Inner();
inner.outPut();
System.out.println(inner.m);
}
//2.在外部类的一个方法中返回内部类对象实例
public Inner getInner() {
return new Inner();
}
}
成员内部类
7.7
成员内部类
成员内部类
静态内部类
定义在外部类的成员位置,并且有static修饰。
● 使用细节
- 可以直接访问外部类的所有静态成员,包括私有的,但是不能直接访问非静态成员。
- 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
- 作用域:和外部类的成员一样。为整个类体。
- 外部类访问静态内部类方式:创建对象再访问;
- 外部其他类访问静态内部类两种方法。
package com.Interface;
public class Jasmine {
public static void main(String[] args) {
Outer outer = new Outer();
outer.test();
//外部其他类访问静态内部类
//1.类Inner是Outer的静态成员,Outer.Inner就代表该静态内部类。
Outer.Inner inner1 = new Outer.Inner();
inner1.outPut();
//2.在外部类中写个方法(静态/非静态都可以)返回静态内部类对象实例
Outer.getInner().outPut();
}
}
class Outer {
private int n1 = 666;
private static int n2 = 888;
private static void fun() {
System.out.println("沐沐");
}
protected static class Inner {
private int m = 777;
public void outPut() {
fun();
System.out.println(n2);
}
}
public void test() {
Inner inner =new Inner();
inner.outPut();
}
public static Inner getInner() {
return new Inner();
}
}
沐沐
888
沐沐
888
沐沐
888
🚀🚀小白飘飘接着冲~~