Java面对对象编程(超详细)

Java面对对象编程(超详细)1、成员变量和成员方法成员变量(又叫属性,字段)成员方法2、类和对象的内存分配机制Java内存的结构分析栈:一般存放基本数据类型(局部变量)堆:存放对象(Catcat,数组等)

大家好,又见面了,我是你们的朋友全栈君。

1、成员变量和成员方法

成员变量(又叫属性,字段)

成员方法

 

2、类和对象的内存分配机制

Java 内存的结构分析

  1. 栈: 一般存放基本数据类型(局部变量)

  2. 堆: 存放对象(Cat cat , 数组等)

  3. 方法区:常量池(常量,比如字符串), 类加载信息

对象在内存中存在形式:

<span role="heading" aria-level="2">Java面对对象编程(超详细)

分析:age的变量类型为基本数据类型int,值直接存在堆中,而name和color是引用数据类型String,堆中存放其地址,其值存在方法区的常量池,堆中的地址指向方法区的常量池,类加载也在方法区中

习题

class Person {
String name;
int age;
}

public class Test {
public static void main(String[] args) {
Person p1 = new Person();
p1.age = 10;
p1.name = "小明";
Person p2 = p1; //把p1 赋给了 p2 , 让p2指向p1
System.out.println(p2.age); // 10
p2.age = 80;
System.out.println(p1.age); //80
p2 = null;
System.out.println(p1.age); //80
System.out.println(p2.age); //抛出空指针异常 NullPointerException
}
}

 

3、(方法的传参机制)值传递和引用传递

  1. 基本数据类型传递的是值本身,形参的任何改变不影响实参

  2. 引用数据类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参!

 

4、方法的调用机制

image-20211212172427794

 

5、方法的递归调用

image-20211212173633964

class T {
public void test(int n) {
if (n > 2) {
test(n - 1);
}
System.out.println("n=" + n);
}
}

public class Recursion01 {
public static void main(String[] args) {
T t1 = new T();
t1.test(4);//输出 n=2 n=3 n=4
}
}

/*
运行结果:
n=2
n=3
n=4
*/

注意输出的是 n=2 n=3 n=4,而不是 n=4 n=3 n=2

 

递归的重要原则

image-20211212180840357

 

6、方法的重载(overload)

  1. 定义:java 中允许同一个类中,多个同名方法的存在,但要求形参列表不一致!

  2. 使用细节

    image-20211212182243283

    注意返回类型不要求相同 

  3. 习题

    image-20211212182643949

 

7、可变参数

  1. 定义: Java 允许将同一个类中 多个同名同功能参数个数不同的方法,封装成一个方法。

  2. 基本语法

    访问修饰符返回类型方法名(数据类型 … 形参名) { }

  3. 案例

    public class Text01 {
    public static void main(String[] args) {
    System.out.println(sum(1, 5, 100)); //106
    System.out.println(sum(1,19)); //20
    }

    public static int sum(int... nums) {
    int res = 0;
    for(int i = 0; i < nums.length; i++) {
    res += nums[i];
    }
    return res;
    }
    }

 

8、作用域

 

 

9、数据类型的默认赋值

int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000,boolean false,String null

 

10、构造器

  1. 定义:构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。

  2. 基本语法

    [修饰符] 方法名(形参列表){ 方法体; }

  3. 使用细节

    1. 构造器的修饰符可以默认, 也可以是public protected private

    2. 构造器没有返回值

    3. 方法名和类名字必须一样

    4. 参数列表和成员方法一样的规则

    5. 构造器的调用, 由系统完成

    6. 如果没有给一个类定义构造器,系统会生成一个默认的无参构造器,一旦自己定义了构造器,默认的无参构造器就会被覆盖,想使用无参构造器需要显式定义一下

    image-20211213100724452

    image-20211213100742859

  4. 对象创建的流程分析

    image-20211213101256490

 

11、this关键字

  1. 简单说明:哪个对象调用,this就代表哪个对象。(如下图,相当于 this指向调用对象在堆里的地址

image-20211213102843171

  1. 使用细节

    1. this 关键字可以用来访问本类的属性、方法、构造器

    1. this 用于区分当前类的属性和局部变量

    2. 访问成员方法的语法:this.方法名(参数列表);

    1. 用this访问构造器语法:this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一条语句)

    2. this 不能在类定义的外部使用,只能在类定义的方法中使用。

    public class Text {
    public static void main(String[] args) {
    T t = new T();
    }
    }

    class T{
    private String name;
    private int age;

    public T() {
    this("A", 10); //必须放在第一条语句
    System.out.println("无参构造器");
    }

    public T(String name, int age) {
    this.name = name;
    this.age = age;
    System.out.println("T(String name, int age)构造器");
    }
    }

    /*
    运行结果:
    T(String name, int age)构造器
    无参构造器
    */
    public class Text {
    public static void main(String[] args) {
    T t = new T();
    t.f1();
    System.out.println("===========");
    t.f2();
    }
    }

    class T {
    private int age = 10;

    public void f1() {
    int age = 20;
    System.out.println("this.age:" + this.age);
    System.out.println("age:" + age);
    }

    public void f2() {
    System.out.println("this.age:" + this.age);
    System.out.println("age:" + age);
    }
    }

    /*
    运行结果:
    this.age:10
    age:20
    ===========
    this.age:10
    age:10
    */

分析:在f()方法中,无论有没有局部变量age,this.age都是指成员变量age,而f()的age则会根据就近原则,如果f()中有定义局部变量age,则代表局部变量,否则代表成员表量。

 

12、4种访问修饰符的访问范围

  1. 级别

    1. 公开级别:用public 修饰,对外公开

    2. 受保护级别:用protected 修饰,对子类和同一个包中的类公开

    3. 默认级别:没有修饰符号,向同一个包的类公开.

    4. 私有级别:用private 修饰,只有类本身可以访问,不对外公开.

image-20211213111400163

  1. 使用细节

    1. 修饰符可以用来修饰类中的属性,成员方法以及类

    2. 只有默认的和public才能修饰类

 

13、封装

  1. 介绍

    image-20211213112006783

  2. 封装的步骤

    image-20211213112058332

  3. 将构造器和setXxx 结合

    public Person(String name, int age, double salary) {
    setName(name);
    setAge(age);
    setSalary(salary);
    }

 

14、继承

  1. 继承:继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends 来声明继承父类即可。

    image-20211213113656887

  2. 基本语法

    image-20211213113724538

  3. 使用细节

    1. 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问

    2. 子类必须调用父类的构造器, 完成父类的初始化

    3. 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。

    4. 如果希望指定去调用父类的某个构造器,则显式的调用一下: super(参数列表)

    5. super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)

    6. super() 和this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器

    7. java 所有类都是Object 类的子类, Object 是所有类的基类.

    8. 父类构造器的调用不限于直接父类!将一直往上追溯直到Object 类(顶级父类)

    9. 子类最多只能继承一个父类(指直接继承),即java 中是单继承机制 思考:如何让A 类继承B 类和C 类? 【A 继承B, B 继承C】

    10. 不能滥用继承,子类和父类之间必须满足is-a 的逻辑关系

public class Text {
public static void main(String[] args) {
Sub sub1 = new Sub();
System.out.println("========");
Sub sub2 = new Sub(10);
System.out.println("========");
Sub sub3 = new Sub(10, 20);
}
}

class Base {
private int age ;
private int id ;

public Base() {
System.out.println("父类无参构造器被调用");
}

public Base(int age) {
this.age = age;
System.out.println("父类Base(int age)被调用");
}

public Base(int age, int id) {
this.age = age;
this.id = id;
System.out.println("父类Base(int age, int id)被调用");
}
}

class Sub extends Base{
public Sub() {
System.out.println("子类无参构造器被调用");
}

public Sub(int age) {
System.out.println("子类Sub(int age)被调用");
}

public Sub(int age, int id) {
super(age, id);
System.out.println("子类Sub(int age, int id)被调用");
}
}

/*
运行结果:
父类无参构造器被调用
子类无参构造器被调用
========
父类无参构造器被调用
子类Sub(int age)被调用
========
父类Base(int age, int id)被调用
子类Sub(int age, int id)被调用
*/
  1. 继承的本质

    当子类对象创建好后,建立查找关系

    按照查找关系来返回信息:

    1. 首先看子类是否有该属性

    2. 如果子类有这个属性,并且可以访问,则返回信息

    3. 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息..)

    4. 如果父类没有就按照(3)的规则,继续找上级父类,直到Object

image-20211213140738846

public class Text {
public static void main(String[] args) {
Son son = new Son();
System.out.println(son.name); //大头儿子
System.out.println(son.hobby); //旅游
System.out.println(son.age); //报错:age 在 Father 中是 private 访问控制
}
}

class GrandPa {
String name = "大头爷爷";
String hobby = "旅游";
int age = 60;
}

class Father extends GrandPa {
String name = "大头爸爸";
private int age = 39;
}

class Son extends Father {
String name = "大头儿子";
}

 

15、super关键字

  1. 基本介绍:super 代表父类的引用,用于访问父类的属性、方法、构造器

  2. 基本语法

    image-20211213141013663

  3. 使用细节

    image-20211213142129482

    image-20211213142159935

class A {
public void f(){
}
}

class B extends A{
public void f(){
}
public void t(){
/*(1)先找本类,如果有,则调用
(2)如果没有,则找父类(如果有,并可以调用,则调用)
(3)如果父类没有,则继续找父类的父类,整个规则,就是一样的,直到 Object类
提示:如果查找方法的过程中,找到了,但是不能访问, 则报错, cannot access
如果查找方法的过程中,没有找到,则提示方法不存在*/
f();
this.f();
super.f();
// f()和this.f()完全等价,super.f()会直接调用父类的f()
}
}

 

16、super和this比较

image-20211213143347900

 

17、方法重写/覆盖(override)

  1. 基本概念

image-20211213143743875

  1. 使用细节

image-20211213143906926

 

18、重写(override)和重载(overload)比较

image-20211213144123207

 

19、多态

1 多态存在的三个必要条件

  • 继承

  • 重写

  • 父类引用指向子类对象

2 编译类型和运行类型

image-20211213145505509

image-20211213145242011

当使用实例调用方法时,看的是运行类型,即调用运行类型对应的该方法

3 多态的向上转型和向下转型

向上转型

image-20211213145539077

class Animal {
String name = "动物";
int age = 10;
public void sleep(){
System.out.println("睡");
}
public void run(){
System.out.println("跑");
}
public void eat(){
System.out.println("吃");
}
public void show(){
System.out.println("hello,你好");
}
}

class Cat extends Animal {
public void eat(){//方法重写
System.out.println("猫吃鱼");
}
public void catchMouse(){//Cat特有方法
System.out.println("猫抓老鼠");
}
}

public class Text{
public static void main(String[] args) {
Animal animal = new Cat();
animal.catchMouse();//编译报错,catchMouse()是Cat的特有方法,实例animal不能调用
animal.eat();//猫吃鱼..
animal.run();//跑
animal.show();//hello,你好
animal.sleep();//睡
}

代码细节分析:

  1. 实例animal编译类型是Animal,运行类型是Cat

  2. 向上转型调用方法的规则如下:

    • 可以调用父类中的所有成员(需遵守访问权限)

    • 不能调用子类的特有的成员(上面catchMouse()是Cat的特有方法,实例animal不能调用),因为在编译阶段,能调用哪些成员,是由编译类型来决定的

    • 最终运行效果看运行类型的具体实现, 即调用方法时,按照从运行类型开始查找方法,然后调用,子类(运行类型)没有,找父类。

向下转型

image-20211213145724846

//接上个代码
public class Text{
public static void main(String[] args) {
Animal animal = new Cat(); //向上转型
animal.catchMouse();//编译报错,catchMouse()是Cat的特有方法,实例animal不能调用
animal.eat();//猫吃鱼..
animal.run();//跑
animal.show();//hello,你好
animal.sleep();//睡

Cat cat = (Cat) animal; //向下转型
cat.catchMouse();//猫抓老鼠,成功运行
}

属性没有重写,属性的值看编译类型

public class Test {
public static void main(String[] args) {
//属性没有重写之说!属性的值看编译类型
Base base = new Sub();//向上转型
System.out.println(base.count);// 10
Sub sub = new Sub();
System.out.println(sub.count);//? 20
}
}

class Base {
int count = 10;
}
class Sub extends Base {
int count = 20;
}

4 instanceOf 比较操作符,用于判断对象的 运行类型是否为XX 类型或XX 类型的子类型

public class PolyDetail03 {
public static void main(String[] args) {
BB bb = new BB();
System.out.println(bb instanceof BB);// true
System.out.println(bb instanceof AA);// true

AA aa = new BB();
System.out.println(aa instanceof AA);// true
System.out.println(aa instanceof BB);// true

Object obj = new Object();
System.out.println(obj instanceof AA);//false
String str = "hello";
//System.out.println(str instanceof AA); //报错
System.out.println(str instanceof Object);//true
}
}

class AA {}
class BB extends AA {}

5 动态绑定机制

image-20211213155937726

public class DynamicBinding {
public static void main(String[] args) {
A a = new B();//向上转型
System.out.println(a.sum());//40
System.out.println(a.sum1());//30
}
}

class A {
public int i = 10;

public int sum() {
return getI() + 10;
}

public int sum1() {
return i + 10;
}

public int getI() {
return i;
}
}

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;
}
}

将子类的sum()和sum1()删掉

package com.hspedu.poly_.dynamic_;

public class DynamicBinding {
public static void main(String[] args) {
A a = new B();//向上转型
System.out.println(a.sum());//30
System.out.println(a.sum1());//20
}
}

class A {
public int i = 10;

public int sum() {
return getI() + 10;
}

public int sum1() {
return i + 10;
}

public int getI() {
return i;
}
}

class B extends A {
public int i = 20;

public int getI() {
return i;
}
}

代码分析:

  1. 当子类的sum()被删掉后,再调用sum()方法时,会调用父类的sum(),父类的sum()中有getI()方法,而父类和子类都有getI()方法,根据动态绑定机制,方法与运行类型绑定,此时,会调用子类的getI()方法,子类的getI()方法返回i的值就是子类中属性i的值,因为属性在哪声明,在哪调用。

  2. 当子类的sum1()被删掉后,再调用sum1()方法时,会调用父类的sum1(),父类中 return i + 10 中的i就是父类中属性i的值。

 

20、Object类

1 equals 方法

==和equals 的对比

image-20211213213522379

image-20211213213533192

Object中的equals判断的是地址是否相等,String重写了Object中的equals,判断内容是否相等

public class EqualsExercise01 {
public static void main(String[] args) {
Person person1 = new Person("jack", 10, '男');
Person person2 = new Person("jack", 10, '男');
Person person3 = new Person("jack", 20, '男');

System.out.println(person1.equals(person2)); //true
System.out.println(person1.equals(person3)); //false
}
}

class Person { //extends Object
private String name;
private int age;
private char gender;

public boolean equals(Object obj) {
//判断如果比较的两个对象是同一个对象,则直接返回true
if(this == obj) {
return true;
}
//类型判断
if(obj instanceof Person) {//是Person,我们才比较
//进行 向下转型, 因为我需要得到obj的 各个属性
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
}
//如果不是Person ,则直接返回false
return false;
}

public Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
}

2 hashCode方法

  1. 提高具有哈希结构的容器的效率!

  2. 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!

  3. 两个引用,如果指向的是不同对象,则哈希值是不一样的

  4. 哈希值主要根据地址号来的,不能完全将哈希值等价于地址。

3 toString 方法

  1. 基本介绍:默认返回:全类名+@+哈希值的十六进制

  2. 重写toString 方法,打印对象或拼接对象时,都会自动调用该对象的toString 形式.

  3. 当直接输出一个对象时, toString 方法会被默认的调用, 比如System.out.println(monster) ; 就会默认调用monster.toString()

4 finalize 方法

  1. 当对象被回收时,系统自动调用该对象的finalize 方法。子类可以重写该方法,做一些释放资源的操作

  2. 什么时候被回收:当某个对象没有任何引用时,则jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用finalize 方法。

  3. 垃圾回收机制的调用,是由系统来决定(即有自己的GC 算法), 也可以通过System.gc() 主动触发垃圾回收机制,

 

21、类(静态)变量和类(静态)方法

类变量

  1. 定义 image-20211213222729733

  2. 语法 image-20211213222749066

  3. 访问类变量

    image-20211213222906491

  4. 使用细节

    image-20211213222947150image-20211213223523931

静态变量是类加载的时候,就初始化了

类方法

  1. 基本介绍

    image-20211213223633799

  2. 调用

    image-20211213223657059

  3. 类方法经典的使用场景

    image-20211213223744524

  4. 使用细节

    image-20211213223848317image-20211213223854491

 

22、代码块

  1. 基本介绍

    image-20211213234208088

  2. 语法

    image-20211213234451335

  3. 使用

    image-20211213234935347

  4. 使用细节

    image-20211213235518850image-20211213235924488

注意类被加载的3种情况及类的调用顺序

总结: 静态代码块在类加载时执行,普通代码块在实例化对象执行

public class Test {
public static void main(String[] args) {
A a = new A();
}
}

class A {
{ //普通代码块
System.out.println("A 普通代码块01");
}
private int n2 = getN2();//普通属性的初始化

static { //静态代码块
System.out.println("A 静态代码块01");
}

//静态属性的初始化
private static int n1 = getN1();

public static int getN1() {
System.out.println("getN1被调用...");
return 100;
}
public int getN2() { //普通方法/非静态方法
System.out.println("getN2被调用...");
return 200;
}

//无参构造器
public A() {
System.out.println("A() 构造器被调用");
}

}

/*
运行结果:
A 静态代码块01
getN1被调用...
A 普通代码块01
getN2被调用...
A() 构造器被调用
*/

image-20211214102135554

public class Test{
public static void main(String[] args) {
new BBB();
}
}

class AAA { //父类Object
{
System.out.println("AAA的普通代码块");
}
public AAA() {
//(1)super()
//(2)调用本类的普通代码块
System.out.println("AAA() 构造器被调用....");
}
}

class BBB extends AAA {
{
System.out.println("BBB的普通代码块...");
}
public BBB() {
//(1)super()
//(2)调用本类的普通代码块
System.out.println("BBB() 构造器被调用....");
}
}

/*
运行结果
AAA的普通代码块
AAA() 构造器被调用....
BBB的普通代码块...
BBB() 构造器被调用....
*/

image-20211214102817043

当有继承时类的调用顺序

package com.hspedu.codeblock_;

public class CodeBlockDetail04 {
public static void main(String[] args) {
new B02();
}
}

class A02 { //父类
private static int n1 = getVal01();
static {
System.out.println("A02的一个静态代码块..");//(2)
}
{
System.out.println("A02的第一个普通代码块..");//(5)
}
public int n3 = getVal02();//普通属性的初始化
public static int getVal01() {
System.out.println("getVal01");//(1)
return 10;
}

public int getVal02() {
System.out.println("getVal02");//(6)
return 10;
}

public A02() {//构造器
//隐藏
//super()
//普通代码和普通属性的初始化......
System.out.println("A02的构造器");//(7)
}

}

class B02 extends A02 {

private static int n3 = getVal03();

static {
System.out.println("B02的一个静态代码块..");//(4)
}
public int n5 = getVal04();
{
System.out.println("B02的第一个普通代码块..");//(9)
}

public static int getVal03() {
System.out.println("getVal03");//(3)
return 10;
}

public int getVal04() {
System.out.println("getVal04");//(8)
return 10;
}
public B02() {//构造器
//隐藏了
//super()
//普通代码块和普通属性的初始化...
System.out.println("B02的构造器");//(10)
}
}

/*
运行结果:
getVal01
A02的一个静态代码块..
getVal03
B02的一个静态代码块..
A02的第一个普通代码块..
getVal02
A02的构造器
getVal04
B02的第一个普通代码块..
B02的构造器
*/
  1. 练习题

    class Person {
    public static int total;//静态变量
    static {//静态代码块
    total = 100;
    System.out.println("in static block!");
    }
    }

    class CodeBlockExercise01 {
    public static void main(String[] args) {
    System.out.println("total = "+ (Person.total++));
    System.out.println("total = "+ (++Person.total));
    }
    }

    /*
    运行结果:
    in static block!
    total = 100
    total = 102
    */

代码分析:静态代码块只在类加载时执行,类只加载一次,顾“in static block!”只在第一次时打印,输出语句中,Person.total++也是先输出再加一,++Person.total是先加再输出

public class CodeBlockExercise02 {
public static void main(String str[])
{
Test a=new Test();
}
}

class Sample
{
Sample(String s)
{
System.out.println(s);
}
Sample()
{
System.out.println("Sample默认构造函数被调用");
}
}
class Test{
Sample sam1=new Sample("sam1成员初始化");//
static Sample sam=new Sample("静态成员sam初始化 ");//
static{
System.out.println("static块执行");//
if(sam==null)System.out.println("sam is null");
}
Test()//构造器
{
System.out.println("Test默认构造函数被调用");//
}
}

/*
运行结果:
静态成员sam初始化
static块执行
sam1成员初始化
Test默认构造函数被调用
*/

代码分析:Test a=new Test(),调用Test()构造器前,会按顺序执行Test类的静态属性和静态代码块,普通属性和普通代码块,再执行构造器。

 

23、final 关键字

  1. final可以修饰类、属性、方法和局部变量

  2. 使用场景

    image-20211214125059190

  3. 使用细节

    image-20211214140918649image-20211214141108124

注意:final static 搭配使用定义的变量,在被调用时,不会导致类的加载,因为底层编译器做了优化

public class Test {
public static void main(String[] args) {
System.out.println(A.n1);
System.out.println("=======");
System.out.println(B.n1);
}
}

class A {
public static int n1 = 1;

static {
System.out.println("静态代码块被调用");
}
}

class B {
public final static int n1 = 1;

static {
System.out.println("静态代码块被调用");
}
}

/*
运行结果:
静态代码块被调用
1
=======
1
*/

代码分析:使用A中的静态变量n1引起了A类的加载,导致静态代码块执行;而B类中的静态变量n1用了final修饰,使用B.n1不会引起类加载。

 

24、抽象类

  1. 语法

    image-20211214165721356

  2. 使用细节

    image-20211214165854337image-20211214170004868image-20211214170131158

 

25、接口

  1. 简介

    image-20211214171306750

interface A {
default void f1() {
}

static void f2() {
}

void f3();
}

代码分析: jdk8后,接口中的方法可以实现,但必须加上static或default,否则不能有方法体{},不然会报错。

  1. 使用细节

    image-20211214233623344image-20211214235059981

注意:

  1. 接口中的方法是抽象的,是public的

  2. 接口中定义int a实际上是public static final int a

  3. 接口可以继承多个其他接口

public class Test {
public static void main(String[] args) {
System.out.println(A.a); //说明a是static
//A.a = 2; 报错,A.a是final类型,不能修改
}
}

interface A {
int a = 1;
}
interface B {}
interface C extends A,B{} //接口可以继承多个其他接口*
  1. 练习

    image-20211215000936972

 

26、实现接口vs 继承类

image-20211215001105298

class Monkey {
private String name;
public Monkey(String name) {
this.name = name;
}
public void climbing() {
System.out.println(name + " 会爬树...");
}
}

interface Fishable {
void swimming();
}

interface Birdable {
void flying();
}

class LittleMonkey extends Monkey implements Fishable,Birdable {
public LittleMonkey(String name) {
super(name);
}
@Override
public void swimming() {
System.out.println(getName() + " 通过学习,可以像鱼儿一样游泳...");
}
@Override
public void flying() {
System.out.println(getName() + " 通过学习,可以像鸟儿一样飞翔...");
}
}

 

27、内部类

  1. 分类

    定义类在局部位置(方法中/代码块) :(1) 局部内部类(2) 匿名内部类 定义在成员位置(1) 成员内部类(2) 静态内部类

    image-20211215002449303

  2. 简介

    image-20211215001838400

  • 类的五大成员:属性、方法、构造器、代码块、内部类

  • 内部类最大特点:可以直接访问外部类的私有属性

  1. 基本语法

    image-20211215002347400

  2. 4种内部类使用

1 局部内部类的使用

image-20211215002738873

image-20211215101427133

public class LocalInnerClass {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.m1();
System.out.println("outer02的hashcode=" + outer02);
}
}


class Outer02 {//外部类
private int n1 = 100;
public void m1() {
//1.局部内部类是定义在外部类的局部位置,通常在方法
//3.不能添加访问修饰符,但是可以使用final 修饰
//4.作用域 : 仅仅在定义它的方法或代码块中
final class Inner02 {//局部内部类(本质仍然是一个类)
//2.可以直接访问外部类的所有成员,包含私有的
private int n1 = 800;
public void f1() {
//5. 局部内部类可以直接访问外部类的成员,比如下面 外部类n1 和 m2()
/* 7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,
使用 外部类名.this.成员去访问*/
//Outer02.this 本质就是外部类的对象, 即哪个对象调用了m1, Outer02.this就是哪个对象
System.out.println("n1=" + n1 + " 外部类的n1=" + Outer02.this.n1);
System.out.println("Outer02.this hashcode=" + Outer02.this);
}
}
//6. 外部类在方法中,可以创建Inner02对象,然后调用方法即可
Inner02 inner02 = new Inner02();
inner02.f1();
}
}

/*
运行结果:
n1=800 外部类的n1=100
Outer02.this hashcode=com.hspedu.innerclass.Outer02@1b6d3586
outer02的hashcode=com.hspedu.innerclass.Outer02@1b6d3586*/

代码分析:

  1. 局部内部类是定义在外部类的局部位置,通常在方法(或代码块)中

  2. 使用该局部内部类:外部类在方法中,可以创建内部类对象,然后调用方法即可。

  3. 外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,使用 外部类名.this.成员(Outer02.this本质就是外部类的对象)去访问。可以看到Outer02.this和创建的Outer02对象的hashcode值是相等的。


2 匿名内部类的使用

  1. 特点

    image-20211215103255878

public class AnonymousInnerClass {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
}
}

class Outer04 { //外部类
private int n1 = 10;//属性
public void method() {//方法
//基于接口的匿名内部类
//1.需求: 想使用IA接口,并创建对象
//2.传统方式,是写一个类,实现该接口,并创建对象
//3.该类只是使用一次,后面再不使用
//4. 可以使用匿名内部类来简化开发
//5. tiger的编译类型 ? IA
//6. tiger的运行类型 ? 就是匿名内部类 Outer04$1
/*
底层 会分配 类名 Outer04$1
class Outer04$1 implements IA {
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
}
*/
/*7. jdk底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1实例,并且把地址
返回给 tiger*/
IA tiger = new IA() {
@Override
public void cry() {
System.out.println("IA的匿名内部类");
}
};
System.out.println("tiger的运行类型=" + tiger.getClass());
tiger.cry();

//演示基于类的匿名内部类
//分析
//1. father编译类型 Father
//2. father运行类型 Outer04$2
//3. 底层会创建匿名内部类
/*
class Outer04$2 extends Father{
@Override
public void test() {
System.out.println("匿名内部类重写了test方法");
}
}
*/
//4. 同时也直接返回了 匿名内部类 Outer04$2的对象
//5. 注意("jack") 参数列表会传递给 构造器
Father father = new Father("jack"){

@Override
public void test() {
System.out.println("father的匿名内部类");
}
};
System.out.println("father对象的运行类型=" + father.getClass());//Outer04$2
father.test();

//基于抽象类的匿名内部类
Animal animal = new Animal(){
@Override
void eat() {
System.out.println("animal的匿名内部类");
}
};
animal.eat();
}
}

interface IA {//接口
public void cry();
}

class Father {//类
public Father(String name) {//构造器
System.out.println("接收到name=" + name);
}
public void test() {}
}

abstract class Animal { //抽象类
abstract void eat();
}

/*
tiger的运行类型=class com.hspedu.innerclass.Outer04$1
IA的匿名内部类
接收到name=jack
father对象的运行类型=class com.hspedu.innerclass.Outer04$2
father的匿名内部类
animal对象的运行类型=class com.hspedu.innerclass.Outer04$3
animal的匿名内部类*/

代码分析:

  1. 使用场景:有时候我们只需要创建一个对象就不使用一个类了,如果创建该类,就有点浪费,为了简化开发,可以使用匿名内部类

  2. 匿名内部类创建的对象的运行类型的格式为:外部类的名字$数字(如Outer04$1,Outer04$2,Outer04$3)

 

  1. 使用细节

image-20211215112510684image-20211215112517976image-20211215112524385

public class AnonymousInnerClass {
public static void main(String[] args) {

Outer05 outer05 = new Outer05();
outer05.f1();
}
}

class Outer05 {
private int n1 = 99;
public void f1() {
Person p = new Person(){
private int n1 = 88;
@Override
public void hi() {
System.out.println("匿名内部类的第一种调用方式");
}
};
p.hi();

new Person(){
@Override
public void hi() {
System.out.println("匿名内部类的第二种调用方式");
}
}.hi();//动态绑定, 运行类型是 Outer05$2

new Person(){}.hi(); //没有重写,调用Person的hi()
}
}

class Person {
public void hi() {
System.out.println("Person hi()");
}
}

/*
运行结果:
匿名内部类的第一种调用方式
匿名内部类的第二种调用方式
Person hi()
*/

代码分析:

  1. 不能添加访问修饰符,因为它的地位就是一个局部变量, 局部变量不能添加访问修饰符

  2. 两种方法使用,如上面代码

  3. 如果匿名内部类中没有重写Person的hi() 方法,则调用Person的hi() 方法;如果重写了,根据 动态绑定机制

调用重写的(运行类型的)hi() 方法

 

  1. 最佳使用场景

    当做实参直接传递

public class InnerClassExercise01 {
   public static void main(String[] args) {

       //当做实参直接传递,简洁高效
       f1(new IL() {
           @Override
           public void show() {
               System.out.println("这是一副名画~~...");
          }
      });
       //传统方法:需要创建一个类实现接口,再调用
       f1(new Picture());

  }

   //静态方法,形参是接口类型
   public static void f1(IL il) {
       il.show();
  }
}

interface IL {
   void show();
}

//传统方法
class Picture implements IL {

   @Override
   public void show() {
       System.out.println("这是一副名画XX...");
  }
}

3 成员内部类

  1. 使用细节

    image-20211215120222221image-20211215120228983image-20211215120351558

package com.hspedu.innerclass;

public class MemberInnerClass01 {
   public static void main(String[] args) {
       Outer08 outer08 = new Outer08();
       outer08.t1();
       //外部其他类,使用成员内部类的两种方式
       // 第一种方式
       // outer08.new Inner08(); 相当于把 new Inner08()当做是outer08成员
       // 这就是一个语法,不要特别的纠结.
       Outer08.Inner08 inner08 = outer08.new Inner08();
       inner08.say();
       // 第二方式 在外部类中,编写一个方法,可以返回 Inner08对象
       Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
       inner08Instance.say();


  }
}

class Outer08 { //外部类
   private int n1 = 10;
   public String name = "张三";

   private void hi() {
       System.out.println("hi()方法...");
  }

   //1.注意: 成员内部类,是定义在外部内的成员位置上
   //2.可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
   public class Inner08 {//成员内部类
       private double sal = 99.8;
       private int n1 = 66;
       public void say() {
           //可以直接访问外部类的所有成员,包含私有的
           //如果成员内部类的成员和外部类的成员重名,会遵守就近原则.
           //,可以通过 外部类名.this.属性 来访问外部类的成员
           System.out.println("n1 = " + n1 + " name = " + name + " 外部类的n1=" + Outer08.this.n1);
           hi();
      }
  }
   //方法,返回一个Inner08实例
   public Inner08 getInner08Instance(){
       return new Inner08();
  }


   //写方法
   public void t1() {
       //使用成员内部类
       //创建成员内部类的对象,然后使用相关的方法
       Inner08 inner08 = new Inner08();
       inner08.say();
       System.out.println(inner08.sal);
  }
}

/*
运行结果:
n1 = 66 name = 张三 外部类的n1=10
hi()方法...
99.8
n1 = 66 name = 张三 外部类的n1=10
hi()方法...
n1 = 66 name = 张三 外部类的n1=10
hi()方法...
*/

代码分析:

  1. 外部类调用成员内部类,直接创建一个方法,方法中实例化内部类的对象进行调用

  2. 外部其他类,使用成员内部类的两种方式如上面代码所示


4 静态内部类

  1. 使用细节

    image-20211215143806897image-20211215143652953image-20211215143659603image-20211215143706370

public class StaticInnerClass01 {
   public static void main(String[] args) {
       //外部类 使用静态内部类
       Outer10 outer10 = new Outer10();
       outer10.m1();
       System.out.println("=========");
       //外部其他类 使用静态内部类
       //方式1
       //因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
       Outer10.Inner10 inner10 = new Outer10.Inner10();
       inner10.say();
       //方式2
       //编写一个方法,可以返回静态内部类的对象实例.
       Outer10.Inner10 inner10_ = Outer10.getInner10();
       System.out.println("=========");
       inner10_.say();

  }
}

class Outer10 { //外部类
   private int n1 = 10;
   private static String name = "外部类";
   private static void cry() {}
   //Inner10就是静态内部类
   //1. 放在外部类的成员位置
   //2. 使用static 修饰
   //3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
   //4. 可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
   //5. 作用域 :同其他的成员,为整个类体
   static class Inner10 {
       private static String name = "内部类";
       public void say() {
           //如果外部类和静态内部类的成员重名时,静态内部类访问的时,
           //默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.成员)
           System.out.println(name + " 外部类name= " + Outer10.name);
           cry();
      }
  }

   public void m1() { //外部类---访问------>静态内部类 访问方式:创建对象,再访问
       Inner10 inner10 = new Inner10();
       inner10.say();
  }

   public static Inner10 getInner10() {
       return new Inner10();
  }
}

/*
运行结果:
内部类 外部类name= 外部类
=========
内部类 外部类name= 外部类
=========
内部类 外部类name= 外部类
*/

韩顺平_循序渐进学Java零基础

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/155201.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 安装scala和配置环境变量

    安装scala和配置环境变量安装scala和配置环境变量

  • Centos7安装-多节点Torque

    Centos7安装-多节点TorqueCentos7安装-多节点Torque1简介PBS(PortableBatchSystem)最初由NASA的Ames研究中心开发,主要为了提供一个能满足异构计算网络需要的软件包,用于灵活的批处理,特别是满足高性能计算的需要,如集群系统、超级计算机和大规模并行系统。PBS的目前包括openPBS,PBSPro和Torque三个主要分支.其中OpenPBS是最早的PBS系统,目前已经没有

  • 英特尔CPU后缀「建议收藏」

    英特尔CPU后缀「建议收藏」X后缀X代表Extreme,中文意思是至尊级,代表同一时代性能最强的CPU。如Corei7-5960X、Corei7-4960X。X代表在同一代中只有一款CPU黄袍加身,地位至高无上。加上没有竞争对手可以望其项背,从露面都退出市场,期待的弑君者没有出现。SandyBridge时代到现在,竞争的天平一直向Intel倾斜。K后缀=解锁倍频且更高性能:自从SandyBridge时代Inte…

  • flex_java文件上传(一)[通俗易懂]

    flex_java文件上传(一)[通俗易懂]功能如下:能够批量上传勾上的文件,能够批量删除指定的文件java服务器端: 

    2022年10月19日
  • JSONPath表达式[通俗易懂]

    JSONPath表达式[通俗易懂]前言JSONPath是一种简单的方法来提取给定JSON文档的部分内容。JSONPath提供的json解析非常强大,它提供了类似正则表达式的语法,基本上可以满足所有你想要获得的json内容。JSONPath表达式语法1、操作符?:问号,标记表达式的开头。使用的语法[?(表达)]例如:[?(Expression)]@:在符号处表示正在处理的当前节点。语法使用$.books[?@.price>100]注意:使用JSONPath的[]操作符操作一个对象或者数组,索引是从0开始。

  • idea里面怎么查看自己的激活码(注册激活)2022.03.11

    (idea里面怎么查看自己的激活码)JetBrains旗下有多款编译器工具(如:IntelliJ、WebStorm、PyCharm等)在各编程领域几乎都占据了垄断地位。建立在开源IntelliJ平台之上,过去15年以来,JetBrains一直在不断发展和完善这个平台。这个平台可以针对您的开发工作流进行微调并且能够提供…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号