Java(八)--面向对象(一)

介绍

面向对象和面向过程

  •     都是编程思想
  •     面向过程强调的是 “怎么做”,面向过程的程序,是以函数为基本单位。        
  •     面向对象强调的是“谁来做”,面向对象是关注的是对象的个体,以类为基本单位;重点在于类的设计,某个行为,数据是在哪个类中描述更合适。

面向对象的好处

  •     解决中大型的问题,使用面向对象 代码简化
  •     以人类的思维方式来思考问题,比较好理解

面向对象的思考步骤

  1.     先分析里面涉及到几个类,分别是什么
  2.     抽取每个类中的属性和方法,再加以定义
  3.     面向过程正常调用和执行

类和对象

(1)介绍

对现实世界中,具有共同特性的某一类事物的抽象的描述,在Java中使用一个类来描述一类事物;
类是描述了一组有相同特性(属性)和相同行为(方法)的一组对象的集合。

  •     对象执行的操作称为类的方法
  •     对象或实体所拥有的特征在类中表示时称为类的属性

类是构造面向对象程序的基本单位

  •     是抽取了同类对象的共同属性和方法所形成的对象或实体的“模板”
  •     是 Java 中的一种重要的引用数据类型
  •     是组成 Java 程序的基本要素

类是对象的模板

(2)创建

//声明一个名称为 Person 的类。

public class Person {
    // 类的主体

    //类的属性
    private String name;    // 姓名
    private int age;    // 年龄
    
    //类的无参构造器
    public Person(){
    }

    //类的方法
    public void tell() {   
        // 定义说话的方法
        System.out.println(name+"今年"+age+"岁!");
    }
}

说明:

类的访问控制符只能是空或者 public;每个 Java 程序的主类都必须是 public 类。

(3)类的使用

//创建一个子对象的调用过程

public void T1(){
public static void main(String[] args) {

		System.out.println("77777777777777");

		new Son();//输出结果顺序:3 6 7 2  1  5  4

		System.out.println("=====================================");

		new Son();//输出结果顺序:2  1  5  4

		System.out.println("=====================================");

		new Father();//输出结果顺序:2 1
	}
}

//定义一个子类
public class Son extends Father {

	public Son() {
		System.out.println("4444444444");
	}

	{
		System.out.println("555555555555");
	}

	static {
		System.out.println("66666666666");
	}
}

//定义一个父类
class Father {
	{
		System.out.println("22222222222");
	}
	public Father() {
		System.out.println("11111111111");
	}
	static {
		System.out.println("3333333333");
	}
}

对象

(1)介绍

对象是类的一个具体的个体,是类的一个实例(instance);

对象是现实世界中实体的描述,对象要创建才存在,有了对象才能对对象进行操作;

创建对象的网络表达:

  •     创建一个对象
  •     实例化一个对象
  •     把类实例化
  •     ......

(2)对象的创建方式

//方式1:
Cat cat = new Cat();


/*方式2:
语法格式;
java.lang.Class Class类对象名称 = java.lang.Class.forName(要实例化的类全称);
类名 对象名 = (类名)Class类对象名称.newInstance();
*/

/**
调用 java.lang.Class 类中的 forName() 方法时,需要将要实例化的类的全称(比如 com.mxl.package.Student)作为参数传递过去,然后再调用 java.lang.Class 类对象的 newInstance() 方法创建对象。
**/

    Class c1 = Class.forName("package com.lhyedu.java.bean.Student");
    Student student01 = (Student)c1.newInstance();
    System.out.println(student01);


//方式2:
//获取构造方法并创建对象
Constructor c2 = c1.getDeclaredConstructor(String.class,String.class,int.class);

//根据构造方法创建对象并设置属性
Student student02 = (Student)c2.newInstance("张三","男",20);

    System.out.println(student01);

//方式3:
/*
使用该方法创建对象时,要实例化的类必须继承 java.lang.Cloneable 接口;

不会调用类的构造方法,它会创建一个复制的对象,这个对象和原来的对象具有不同的内存地址,但它们的属性值相同;

 调用对象的 clone() 方法创建对象的语法格式如下:

类名对象名 = (类名)已创建好的类对象名.clone();
*/

public class Student implements Cloneable {   
    // 实现 Cloneable 接口
}

 // 调用对象的 clone() 方法创建对象
        Student student3 = (Student)student2.clone();
        System.out.println(student3);

(3)对象创建的流程

  1. 加载person类的信息(person.class),(属性和方法信息, 只会加载一次)
  2. 在堆中分配空间(地址);
  3. 进行对象初始化;【3.1 默认初始化 age =0 name=null; 3.2 显式初始化 age = 90, name = null;3.3 构造器初始化 age = 20,name = 张三;】
  4. 将对象在堆中的地址返回给P;P是对象名,可理解为对象的引用
  5. 进行指定初始化, 比如 p.name =”jack”; p.age = 22;
//定义一个person类
public class Person {
	int age = 90;
	String name;

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

//创建对象
Person person = new Person(20, "张三");
person.age = 22;
person.name = "jack";

说明:

每个对象都是相互独立的,在内存中占有独立的内存地址,并且每个对象都具有自己的生命周期;

当一个对象的生命周期结束时,对象就变成了垃圾,由 Java 虚拟机自带的垃圾回收机制处理。

(4)对象的内存结构图

Person p1 = new Person();

p1.age = 10 ;
p1.name = "小明";

Person p2 = p1;



class Person {
//四个属性
int age;
String name;
}

栈:

  • 指虚拟机栈,用于存储局部变量等。
  • 局部变量表存放编译期可知长度的各种基本数据类型( boolean、 byte、char 、 short 、 int 、 float 、 long 、double) 、 对象引用( reference类型,它不等同于对象本身, 是对象在堆内存的首地址) 。
  • 方法执行完, 自动释放。

堆:

  • 内存区域的唯一目的就是存放对象实例, 几乎所有的对象实例都在这里分配内存。
  •  这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。

方法区(Method Area)

  •  用于存储已被虚拟机加载的类信息、 常量、 静态变量、 即时编译器编译后的代码等数据。

比较

区别

  •     类是抽象的
  •     对象是具体的,实际存在的,也称为 实体或实例

关系

  •     类是由对象总结或抽取出来的一个概念。 是对象的所属类型
  •     对象是通过类创建出来的具体的实体
  •     类是对象的设计模板,对象是类的实例

类的内部成员

属性

说明:

  • 访问修饰符: 控制属性的访问范围;
  • 局部变量和变量可以重名,访问时候遵循就近原则;
class Person {

//四个属性
int age;
String name;
double sal;
boolean isPass;

}

方法

一个完整的方法通常包括方法名称、方法主体、方法参数和方法返回值类型;
方法:代表一个功能的定义,具有可重用性,就可以把这样的一段代码,抽象成一个方法;

语法格式

public class Test {
    [public|private|protected] [static] <void|return_type> <method_name>([paramList]) {
        // 方法主体        
    }
}
/*
说明
	形参列表[paramList]
		表示成员方法输入 cal(int n) , getSum(int num1, int num2)
		可以有0个或多个参数,中间用逗号隔开

	返回数据类型<void|return_type>
		表示成员方法输出, void 表示没有返回值

	方法主体
		表示为了实现某一功能代码块

	return语句
		不是必须

	方法体
		完成功能的具体语句
		包括 输入、输出、变量、运算、分支、循环、方法调用

	访问修饰符
		public、private、protected:表示成员方法的访问权限
*/

说明:

方法只有被调用才会执行;

要不要传实参,看被调用的方法是否声明了形参;

  •     实参和形参的类型要一致或兼容、个数、顺序必须一致
  •     形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元
  •     实参可以是常量、变量、表达式、方法等,在赋值给形参时需要获得确定值
  •     方法调用中发生的数据传送是单向的,由实参传给形参

 一个方法最多有一个返回值;

  •     返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
  •     无返回值的方法的调用,只能是单独一个语句;方法体中可以没有 return 语句,或者 只写 return ;
  •     方法有返回数据类型,则方法体中最后的执行语句必须为return值,且返回值类型和return的值类型一致或兼容

方法不能嵌套定义方法

  •     方法中只能调用方法或属性, 不可以在方法内部定义方法

方法被static修饰时,称为类方法或静态方法

  • 当我们希望不创建实例,也可以调用某个方法(即当做工具来使用)时,把方法做成静态方法非常合适;
  • 静态方法就hi只访问静态属性/变量

方法的调用机制

说明       

  • 对无参成员方法来说,是没有实际参数列表的(即没有 paramList),但方法名后的括号不能省略
  •     对带参数的成员方法来说,实参的个数、顺序以及它们的数据类型必须与形式参数的个数、顺序以及它们的数据类型保持一致,各个实参间用逗号分隔。
  •     实参名与形参名可以相同,也可以不同
  •     实参也可以是表达式,此时一定要注意使表达式的数据类型与形参的数据类型相同,或者使表达式的类型按 Java 类型转换规则达到形参指明的数据类型。
  •     实参变量对形参变量的数据传递是“值传递”,即只能由实参传递给形参,而不能由形参传递给实参。
  •    程序中执行到调用成员方法时,Java 把实参值复制到一个临时的存储区(栈)中,形参的任何修改都在栈中进行,当退出该成员方法时,Java 自动清除栈中的内容

方法的递归调用        

递归就是方法自己调用自己,每次调用时传入不同的变量;

应用实例

  • 打印问题
  • 阶乘问题
  • 求出斐波那契数1,1,3,5,8,13...,给出一个整数n,求出它的值是什么
  • 猴子吃桃子问题,猴子第一天吃其中一半,并再多吃一个!以后每天都吃其中一半,并再多吃一个。当到第10天时,想再吃时(还没吃),发现只有1个,问总共有几个?
  • 迷宫问题  
  • 汉诺塔问题    
  • 八皇后问题
  • 比如快排、归并排序、二分查找、分治算法等。
//打印问题
public class Test {
    public static void main(String[] args) {
        T t1 = new T();
        t1.test(4);//输出什么? n=2 n=3 n=4

        int res = t1.factorial(5);

        System.out.println("5的阶乘 res =" + res); //120
    }
}

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


//factorial 阶乘
    public  int factorial(int n) {
        if (n == 1) {
            return 1;
        } else {
            return factorial(n - 1) * n;
        }
    }
}


/*
猴子吃桃子问题:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!
以后每天猴子都吃其中的一半,然后再多吃一个。当到第 10 天时,
想再吃时(即还没吃),发现只有 1 个桃子了。问题:最初共多少个桃子?
*/

/*
思路分析 逆推
1. day = 10 时 有 1 个桃子
2. day = 9 时 有 (day10 + 1) * 2 = 4
3. day = 8 时 有 (day9 + 1) * 2 = 10
4. 规律就是 前一天的桃子 = (后一天的桃子 + 1) *2
5. 递归
*/

public int peach(int day) {

if(day == 10) {//第 10 天,只有 1 个桃
    return 1;
} else if ( day >= 1 && day <=9 ) {
    return (peach(day + 1) + 1) * 2;//规则
} else {
    System.out.println("day 在 1-10");
    return -1;
   }
}

//迷宫问题
public class T2 {
@Test
public void t2() {
/*思路
1. 先创建迷宫,用二维数组表示 int[][] map = new int[8][7];
2. 先规定 map 数组的元素值: 0 表示可以走 1 表示障碍物
*/
int[][] map = new int[8][7];
// 3. 将最上面的一行和最下面的一行,全部设置为 1,作为边框
for (int i = 1; i < map[0].length; i++) {
	map[0][i] = 1;
	map[7][i] = 1;
}
// 4.将最右面的一列和最左面的一列,全部设置为 1
for (int i = 0; i < map.length - 1; i++) {
	map[i][0] = 1;
	map[i][6] = 1;
}
// 输出当前的地图
System.out.println("=====当前地图情况======");
for (int i = 0; i < map.length; i++) {
	for (int j = 0; j < map[i].length; j++) {
		System.out.print(map[i][j] + " ");// 输出一行
	}
	System.out.println();
}

// 使用 findWay 给老鼠找路
T t1 = new T();
// 下右上左的方向
t1.findWay(map, 1, 1);

System.out.println("\n====找路的情况如下=====");
for (int i = 0; i < map.length; i++) {
	for (int j = 0; j < map[i].length; j++) {
		System.out.print(map[i][j] + " ");// 输出一行
	}
	System.out.println();
}
}

class T {
/*
解读:
1. findWay 方法就是专门来找出迷宫的路径
2. 如果找到,就返回 true ,否则返回 false
3. map 就是二维数组,即表示迷宫
4. i,j 就是老鼠的位置,初始化的位置为(1,1)
5. 因为我们是递归的找路,所以我先规定 map 数组的各个值的含义: 0 表示可以走 1 表示障碍物 2 表示可以走 3 表示走过,但是走不通是死路
6. 当 map[6][5] =2表示终点,则找到出口,就可以结束,否则就继续找.
7. 先确定老鼠找路策略 下->右->上->左
 */
public boolean findWay(int[][] map, int i, int j) {
	if (map[6][5] == 2) {
		return true;
	} else {
		if (map[i][j] == 0) {// 当前这个位置 0,说明表示可以走
			// 我们假定可以走通
			map[i][j] = 2;
			// 使用找路策略,来确定该位置是否真的可以走通
			// 下->右->上->左
			if (findWay(map, i + 1, j)) {// 先走下
				return true;
			} else if (findWay(map, i, j + 1)) {// 右
				return true;
			} else if (findWay(map, i - 1, j)) {// 上
				return true;
			} else if (findWay(map, i, j - 1)) {// 左
				return true;
			} else {
				map[i][j] = 3;
				return false;
			}
		} else { // map[i][j] = 1 , 2, 3
			return false;
		}
	}
}
}
}
//汉诺塔
/*
汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着 64 片圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
*/



//八皇后
/*
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于 1848 年提出:在 8× 8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
*/



说明:

  • 执行方法时,就创建一个新的受保护的独立空间(栈空间)
  • 方法的局部变量是独立的,不会受到影响
  • 若方法中使用的是引用类型变量,就会共享该引用类型的数据
  • 递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError
  • 当一个方法执行完毕,或遇到return,就会返回
  • 遵守谁调用,就将结果返回给谁,同时当方法执行完毕或返回时,该方法就执行完毕

方法的参数传递

1)基本类型

  •     实参赋给形参的是实参真实存储的数据值,传参相当于拷贝了一个备份到形参;
  •     赋值的是变量所保存的数据值;
  •     形参的改变不影响实参;
public class MethodParameter01 { 

	//编写一个main方法
	public static void main(String[] args) {

		int a = 10;
		int b = 20;
		//创建AA对象 名字 obj
		AA obj = new AA();
		obj.swap(a, b); //调用swap
		System.out.println("main方法 a=" + a + " b=" + b);//a=10 b=20
	}
}

class AA {
	public void swap(int a,int b){

		System.out.println("\na和b交换前的值\na=" + a + "\tb=" + b);//a=10 b=20

		//完成了 a 和 b的交换
		int tmp = a;
		a = b;
		b = tmp;
		System.out.println("\na和b交换后的值\na=" + a + "\tb=" + b);//a=20 b=10
	}
}

2)引用类型

  •     值代表的是地址号,传参相当于传递了一个地址号(引用)到形参。
  •     实参赋给形参的是实参存储数据的地址值
  •     赋值的是变量所保存的数据的地址值
  •     形参的改变影响实参 
注意
p=null 和 p = new Person(); 

方法的形式

说明:

class TestReview{
    //getVolume方法
	public static int getVolume(int area, int height){
		return area * height;
	}

    //getArea方法
	public static double getArea(double lenght, double width){
		return lenght * width;
	}

	public static void main(String[] args){
		int a = 2;//长
		int b = 3;//宽
		int h = 4;//高

//把方法的返回值赋值给一个变量,变量area的类型要与方法getArea()的返回值类型 一致(或兼容)
		double area = getArea(a,b);

//getArea(a,b)的返回值类型是double,而getVolume()的第一个形参是int
		//int v = getVolume(getArea(a,b) ,h);//错误 
									
//把方法的返回值 作为表达式的一部分
		double v = getArea(a,b) * h; 

	}
}

可变参数

说明:

  • 一个方法只能有一个可变参数,可变参数的数据类型 可以 是任意类型
  • 在声明它的方法中,可变参数当做数组使用,形参名就是数组名
  • 可变参数可以和普通类型的参数一起放在形参列表,但可变参数必须在方法的形参列表的最后一个

public class VarParameter01 { 

	//编写一个main方法
	public static void main(String[] args) {

		HspMethod m = new HspMethod();
		System.out.println(m.sum(1, 5, 100)); //106
		System.out.println(m.sum(1,19)); //20
	}
}

class HspMethod {
	//可以计算 2个数的和,3个数的和 , 4. 5, 。。

	//1. int... 表示接受的是可变参数,类型是int ,即可以接收多个int(0-多) 
	//2. 使用可变参数时,可以当做数组来使用 即 nums 可以当做数组
	//3. 遍历 nums 求和即可

	public int sum(int... nums) {
		//System.out.println("接收的参数个数=" + nums.length);
		int res = 0;
		for(int i = 0; i < nums.length; i++) {
			res += nums[i];
		}
		return res;
	}
}
//找多个整数中的最大值,至少一个

int getMax(int a , int... b){
	int max= a;
	for(int  num : b){
		if(max<num){
          max=num;	
		}
	}

	return max;
}

方法的重载

特点:

  •     同一个类中,方法名相同,参数列表不同(参数个数或参数类型或参数顺序)不同,返回类型无要求

作用:

  •     允许功能相似的方法重名,使方法定义和调用都简化了

调用:

  •     正常方法调用即可,传进对应的参数,编译器自动选择 你指定类型参数的方法执行

public class OverLoad01 { 

	//编写一个main方法
	public static void main(String[] args) {
	
		MyCalculator mc = new MyCalculator();
		System.out.println(mc.calculate(1, 2));
		System.out.println(mc.calculate(1.1, 2));
		System.out.println(mc.calculate(1, 2.1));
	}
}

class MyCalculator  {

	//下面的四个 calculate方法构成了重载
	//两个整数的和
	public int calculate(int n1, int n2)  {
		System.out.println("calculate(int n1, int n2) 被调用");
		return n1 + n2;
	}

	//一个整数,一个double的和
	public double calculate(int n1, double n2) {
		return n1 + n2;
	}
	//一个double ,一个Int和 
	public double calculate(double n1, int n2) {
		System.out.println("calculate(double n1, int n2) 被调用..");
		return n1 + n2;
	}
	//三个int的和
	public int calculate(int n1, int n2,int n3) {
		return n1 + n2 + n2;
	}

}

方法覆盖/重写

特点

  •     子列的方法,与父类的某个方法的名称、返回类型、参数一样,即子类的这个方法重写了父类的方法

细节

  •     子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类
  •     子类方法不能缩小父类方法的访问权限

与重载的区别

构造器

说明:

  • 如果为构造方法定义了返回值类型或使用 void 声明构造方法没有返回值,编译时不会出错,但 Java 会把这个所谓的构造方法当成普通方法来处理;
  • 实际上,类的构造方法是有返回值的; 当使用 new 关键字来调用构造方法时,构造方法返回该类的实例,可以把这个类的实例当成构造器的返回值,因此构造器的返回值类型总是当前类,无须定义返回值类型
  • 但必须注意不要在构造方法里使用 return 来返回当前类的对象,因为构造方法的返回值是隐式的;
  • 使用构造器为属性赋值:Person p1 = new Person("smith", 80);
  • 构造器是完成对象的初始化,并不是创建对象;

代码块


 内部类

  • 内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系(多见于底层源码)
  • 一个类的内部又完整的嵌套了另一个类结构,被嵌套的类称为内部类
  • 在一个类还有一个完整的结构(有属性,方法等),需要使用内部类,而且这个内部类只为外部类服务
  • 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号
  • 内部类不能用普通的方式访问,内部类拥有外部类的所有元素的访问权限;当内部类声明成静态的,仍然是只能访问外部类的静态成员变量;

成员内部类

//成员内部类

//1、定义
public class InnerClass01 {//外部其他类
    public static void main(String[] args) {
    }
}
class Outer {//外部类
    private int n1 = 100;//属性

    public Outer(int n1) {//构造器
        this.n1 = n1;
    }

    public void m1() {//方法
        System.out.println("m1()");
    }

    {//代码块
        System.out.println("代码块...");
    }

    class inner {//成员内部类,在Outer类的内部
    }

}



//访问
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();
		System.out.println("=========================");
		// 第二方式 在外部类中,编写一个方法,可以返回 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()方法...
 */

静态内部类

//静态内部类

//访问
public class StaticInnerClass01 {
	public static void main(String[] args) {
		Outer10 outer10 = new Outer10();
		outer10.m1();//李四 外部类name= 张三

		// 外部其他类 使用静态内部类
		// 方式1
		// 因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
		Outer10.Inner10 inner10 = new Outer10.Inner10();
		inner10.say();//李四 外部类name= 张三

		// 方式2
		// 编写一个方法,可以返回静态内部类的对象实例.
		Outer10.Inner10 inner101 = outer10.getInner10();
		System.out.println("============");
		inner101.say();//李四 外部类name= 张三

		Outer10.Inner10 inner10_ = Outer10.getInner10_();
		System.out.println("************");
		inner10_.say();//李四 外部类name= 张三
	}
}


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

	private static void cry() {
	}

	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 Inner10 getInner10() {
		return new Inner10();
	}

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

局部内部类

//局部内部类

//2、访问
public class LocalInterClass {
    public static void main(String[] args) {
        Outer02 outer02 = new Outer02();
        outer02.m1();
        System.out.println("outer02 的 hashcode=" + outer02);
    }
}


class Outer02 {//外部类
    private int n1 = 100;

    private void m2() {//私有方法
        System.out.println("Outer02 m2()");
    }

    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);
                m2();
            }
        }
        //6. 外部类在方法中,可以创建 Inner02 对象,然后调用方法即可
        Inner02 inner02 = new Inner02();
        inner02.f1();
    }
}


/*
结果
n1=800 外部类的 n1=100
Outer02.this hashcode=com.hspedu.Outer02@14ae5a5
Outer02 m2()
outer02 的 hashcode=com.hspedu.Outer02@14ae5a5
 */
public class TestLocal {
//在外部类的外面,可以得到内部类的对象
	public static void main(String[] args) {
		MyInter m = new Out().test1();
		Object obj = new Out().test2();
		MyInter  obj02 =(MyInter) new Out().test2();
	}

}

interface MyInter {
	void test01();
}

class Out {//外部类
	// 使用父接口类型返回
	public MyInter test1() {
		class Inner implements MyInter {//局部内部类

			@Override
			public void test01() {
				System.out.println("Inner01 = " + Inner.class);
			}
		}
		return new Inner();
	}

	// 使用父类类型返回
	public Object test2() {
		class Inner implements MyInter {//局部内部类

			@Override
			public void test01() {
				System.out.println("Inner02 = " + Inner.class);
			}
		}
		return new Inner();
	}
}

匿名内部类

//匿名内部类

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接口,并创建对象,且子类Tiger/Dog 类只是使用一次,后面再不使用
		// 2. 可以使用匿名内部类来简化开发
		// 3. tiger的编译类型 ? IA
		// 4. tiger的运行类型 ? 就是匿名内部类  Outer04$1
        /*
            我们看底层 会分配 类名 Outer04$1
            class Outer04$1 implements IA {
                @Override
                public void cry() {
                    System.out.println("老虎叫唤...");
                }
            }
         */
		// 5. jdk底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1实例,并且把地址返回给 tiger
		IA tiger = new IA() {
			@Override
			public void cry_IA() {
				System.out.println("老虎叫唤...");
			}
		};
		System.out.println("tiger的运行类型=" + tiger.getClass());
		tiger.cry_IA();  // 老虎叫唤...Outer04$1
		tiger.cry_IA();  // 老虎叫唤...Outer04$1
		tiger.cry_IA();  // 老虎叫唤...Outer04$1

// 演示基于类的匿名内部类
		// 分析
		// 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_Father() {
				System.out.println("匿名内部类重写了test方法");
			}
		};

		System.out.println("father对象的运行类型=" + father.getClass());// Outer04$2
		father.test_Father();



// 基于抽象类的匿名内部类
		Animal animal = new Animal() {
			@Override
			void eat_Animal() {
				System.out.println("小狗吃骨头...");
			}
		};
		animal.eat_Animal();// Outer04$3
	}
}


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

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

	public void test_Father() {// 方法
	}
}

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

//匿名内部类的访问

package com.hspedu;
//匿名内部类的访问

public class AnonymousInnerClassDetail {
	public static void main(String[] args) {
		Outer05 outer05 = new Outer05();
		outer05.f1();
		// 外部其他类---不能访问----->匿名内部类
		System.out.println("main outer05 hashcode=" + outer05);
	}
}

class Outer05 {
	private int n1 = 99;

	public void f1() {
// 创建一个基于类的匿名内部类
		Person p = new Person() {
			private int n1 = 88;

			@Override
			public void hi() {
				// 可以直接访问外部类的所有成员,包含私有的
				// 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,
				// 默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.this.成员)去访问
				System.out.println("匿名内部类重写了 hi 方法 n1=" + n1 + " 外部内的 n1=" + Outer05.this.n1);
				// Outer05.this 就是调用 f1 的 对象
				System.out.println("Outer05.this hashcode=" + Outer05.this);
			}
		};
		p.hi();// 动态绑定, 运行类型是 Outer05$1
		// 也可以直接调用, 匿名内部类本身也是返回对象
		// class 匿名内部类 extends Person {}
		//调用匿名内部类的ok(str)方法
		new Person() {
			@Override
			public void hi() {
				System.out.println("匿名内部类重写了 hi 方法,哈哈...");
			}

			@Override
			public void ok(String str) {
				super.ok(str);
			}
		}.ok("jack");

//调用匿名内部类的hi()方法
		new Person() {
			@Override
			public void hi() {
				System.out.println("匿名内部类重写了 hi 方法,呵呵呵...");
			}

			@Override
			public void ok(String str) {
				super.ok(str);
			}
		}.hi();
	}
}

class Person {// 类
	//3个继承者
	public void hi() {//3个重写
		System.out.println("Person hi()");
	}

	public void ok(String str) {//2个重写
		System.out.println("Person ok() " + str);
	}
}
// 抽象类/接口...

/*
输出结果:
匿名内部类重写了 hi 方法 n1=88 外部内的 n1=99
Outer05.this hashcode=com.hspedu.Outer05@14ae5a5
Person ok() jack
匿名内部类重写了 hi 方法,呵呵呵...
main outer05 hashcode=com.hspedu.Outer05@14ae5a5
*/
//匿名内部类的使用01

/**
 * 匿名内部类做实参直接传递;
 */
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();//2个实现
}

// 类->实现 IL => 编程领域 (硬编码)
class Picture implements IL {
	@Override
	public void show() {
		System.out.println("这是一副名画 XX...");
	}
}

/*
结果输出:
这是一副名画~~...
这是一副名画 XX...
 */


/**
 * 匿名内部类的使用02
 * 1.有一个铃声接口 Bell,里面有个 ring 方法。
 * 2.有一个手机类 Cellphone,具有闹钟功能 alarmClock,参数是 Bell 类型
 * 3.测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
 * 4.再传入另一个匿名内部类(对象),打印:该上学了
 */
public class AnonymousInnerTest02 {
	public static void main(String[] args) {

		Cellphone cellphone = new Cellphone();
		// 1. 传递的是实现了 Bell 接口的匿名内部类 AnonymousInnerTest02$1
		// 2. 重写了 ring
		cellphone.alarmClock(new Bell() {
			@Override
			public void ring() {
				System.out.println("懒猪起床了");
			}
		});
		cellphone.alarmClock(new Bell() {
			@Override
			public void ring() {
				System.out.println("该上学了");
			}
		});
	}
}

class Cellphone {//类
	public void alarmClock(Bell bell) {///形参是 Bell 接口类型
		System.out.println(bell.getClass());
		bell.ring();// 动态绑定
	}
}

interface Bell {//接口
	void ring();//2个实现
}

  • 14
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hahaha2221

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值