二、面向对象

131 阅读4分钟

java基础学习的文章目录

一、对象与数组的内存分析

类的定义、对象的创建

public class Dog {
	public int age;
	public double weight;
	
	public void run() {
		System.out.println(age + "_" + weight + "_run");
	}
	
	public void eat(String food) {
		System.out.println(age + "_" + weight + "_eat_" + food);
	}
	
}

对象的创建

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Dog dog = new Dog();
		dog.weight = 5.6;
		dog.age = 20;
		dog.run();
		dog.eat("apple");
	}

对象的内存

java中素有对象都是new出来的,所有对象的内存都在堆空间,所有保存对象的变量都是引用类型

java运行时有个垃圾回收器(garbage collector),会自动回收不再使用的内存

当一个对象没有任何引用指向时,会被GC回收掉

对象数组的内存

数组内存储的是对象的地址

方法存储在哪里?

在方法区

java程序的内存划分

java 虚拟机在执行java程序时,会将内存划分为若干个不同的数据区域,主要有

  • PC寄存器(Program Counter Register)存储java虚拟器正在执行的字节码指令地址
  • java虚拟机栈(java virtual machine stack) 存储栈帧
  • 堆(heap) 存储GC所管理的各种对象
  • 方法区(method area) 存储一个类的结构信息,比如字段、构造方法、普通方法的字节码
  • 本地方法栈(Native method stack) 用来支持native方法的调用(比如c语言编写的方法)

二、构造方法

  • 构造方法,也叫构造器,能够方便的创建一个对象
    • 方法名必须和类名一样
    • 没有返回类型
    • 可以重载
public class Dog {
	public int age;
	public double weight;
	
	public Dog() {
		
	}
	
	public Dog(int age) {
		this.age = age;
	}
	
	public Dog(int age , double weight) {
		this.age = age;
		this.weight = weight;
	}
	
	
	public void run() {
		System.out.println(age + "_" + weight + "_run");
	}
	
	public void eat(String food) {
		System.out.println(age + "_" + weight + "_eat_" + food);
	}
	
}

使用构造函数

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Dog dog = new Dog();
		dog = new Dog(18);
		dog = new Dog(30, 55);
		
		dog.run();
		dog.eat("apple");
	}

this

  • this 是一个指向当前对象的引用,常见用途是
    • 访问当前类中定义的成员变量
    • 调用当前类中定义的方法(包括构造函数)
  • this的本质是一个隐藏的,位置最靠前的方法参数
  • 只能在构造方法中使用this调用其他构造函数
  • 如果在构造方法中调用了其他构造方法
    • 构造方法调用语句必须是构造方法中的第一条语句
dog.run();  //等价于 run(dog)

this 调用构造函数的示例:

	public Dog(int age) {
		this(age, 0);
	}
	
	public Dog(int age , double weight) {
		this.age = age;
		this.weight = weight;
	}

默认构造方法

  • 如果一个类没有自定义构造方法,编译器会自动为他提供无参的默认构造方法
  • 一旦自定义了构造方法,默认构造方法就不再存在

三、包(package)

  • java中包就是其他语言中的命名空间,包的本质是文件夹,常见作用是
    • 将不同的类进行组织管理,访问控制
    • 解决命名冲突

命名建议 * 为了保证报名的唯一性,一般包名都是以公司的域名的倒写开头,如 com.baidu.* * 全小写(以避免与某些类名或者接口名冲突)

类的第一句代码必须使用package 生命属于哪个包

  • 比如package com.mj.model

如何使用一个类

要想正常使用一个类,必须得知道这个类的具体位置(在那个包), 有3中常见方式来使用一个类

  • 使用类的全名 com.mj.model.Dog dog = new com.mj.model.Dog();
  • 使用import 导入指定的类名 import com.mj.model.Dog
  • 使用import导入整个包的所有类 import com.mj.model.*

导入细节

为了方便,java编译器会为每个源文件自动导入两个包

  • import java.lang.*; 包含了java开发中最常用的一些类型
  • import 源文件所在的包.*;

import aa.bb.*;

  • 仅仅是import了直接存放在aa.bb包中的类型
  • 并不包含import aa.bb.xx.*;

四、继承 Inheritance

Persion的定义

package com.lijian.lession4;

public class Persion {
	public int age;
	
	public void run() {
		System.out.println(age + "_run");
	}
}

Student的定义

package com.lijian.lession4;

public class Student extends Persion{
	public int no;
	
	public void study() {
		System.out.println(age + "_" + no + "_study");
	}
}

测试代码

package com.lijian.lession4;

public class Main {
	public static void main(String[] args) {
		Persion persion = new Persion();
		
		persion.age = 15;
		persion.run();
		
		Student student = new Student();
		student.age = 20;
		student.no = 2014;
		student.run();
		student.study();
	}

}

Object

任何类最终都继承自java.lang.Object, 一般称它为基类

同名的成员变量

子类可以定义根父类同名的成员变量(但不推荐这么做)

public class Persion {
	public int age = 1;

}
public class Student extends Persion{
	public int age = 2;
	
	public void show() {
		System.out.println(age); //2
		
		System.out.println(this.age); // 输出2
		
		System.out.println(super.age); // 输出1
	}
}
public class Main {
	public static void main(String[] args) {

		Student student = new Student();
		student.show();
	}
}

就近原则

五、重写(override)

重写: 子类的方法签名与父类的方法签名一样。

子类的返回值类型 <= 父类的返回值类型

super

super的常见用途是:

  • 访问父类中定义的成员变量
  • 调用父类中定义的方法(包括构造函数)
public class Persion {
	public int age = 1;
	
	public Persion(int age) {
		this.age = age;
	}

}


public class Student extends Persion{
	public Student(int age) {
		super(age);
	}
}


public class Main {
	public static void main(String[] args) {
		Student student = new Student(10);
		System.out.println(student.age); //10
		
	}
}

六、构造函数的细节

  • 子类的构造函数必须先调用父类的构造方法,再执行后面的代码
  • 如果子类的构造方法中没有显示的调用父类的构造方法
    • 编译器会自动调用父类无参的构造方法
    • 此时如果父类没有无参的构造方法,编译器会报错

七、注解Annotation

3种常见的注解

  • @Override:告诉编译器 这是一个重写方法
  • @SuppressWarnnings("警告类型"):让编译器不生产警告信息
    • @SuppressWarning({"rawtypes", "unused"})
    • @SuppressWarning("unused") *@Deprecated: 表示这个内容已经过期,不推荐使用

八、访问控制

java 中有4个访问权限,从高到底如下所示:

  • public:在任何地方都是可见的
  • protected:仅在自己的包中、自己的子类中可见
  • 无修饰符(package-private):仅在自己的包中可见
  • private:仅在自己的类中可见

使用注意

  • 上述4个访问权限都可以修饰类的成员,比如成员变量、方法、嵌套(Nested class)等
  • 只有public、无修饰符可以修饰顶级类(Top level class)
  • 上述4个访问权限不可以修饰局部类、局部变量
  • 一个java源文件中可以定义多个顶级类,public顶级类的名字必须和文件名一样
修饰符classPackagesubclassworld
publicokokokok
protectedokokokno
无修饰符okoknono
privateoknonono

九、封装

成员变量private化,提供public的getter、setter

toString 方法:打印一个对象时,会自动调用toString方法。

十、static

static 常用来修饰的成员:成员变量、方法、嵌套类

变量:

  • 被static修饰:类变量、静态变量、静态字段
    • 在程序运行过程中只占用一份固定的内存(存储在方法区)
    • 可以通过实例、类访问
  • 没有被static修饰:实例变量
    • 每个实例内部都有一份内存
    • 只能通过实例访问,不能通过类访问
  • 不推荐使用实例访问类变量、类方法
  • 在同一个类中
    • 不能有同名的示例变量和类变量,不能有相同签名的示例方法和类方法【为什么:因为实例变量能访问静态变量,如果两个都有,那么到时访问的是哪个,就有歧义了】

方法:

  • 被static修饰:类方法、静态方法
    • 可以通过实例、类访问
    • 内部不能使用this
    • 可以直接访问类变量、类方法
    • 不可以直接访问实例变量、实例方法
  • 没有被static修饰: 实例方法
    • 只能通过实例访问、不能通过类访问
    • 内部可以使用this
    • 可以直接访问实例变量、实例方法
    • 可以直接访问类变量、类方法【为什么:因为实例方法中有this, 又应为通过实例能访问类变量,所以在实例方法中可以直接访问类变量、方法】

十一、静态导入

使用了静态导入后,就可以省略类名来访问静态成员(成员变量、方法、嵌套类)

  • 静态导入:可以消除一些重复的类名,提高代码的可读性
  • 过度使用静态导入:会导致读者分不清静态成员在哪个类中定义
  • 建议:谨慎使用静态导入
package com.lijian.lession5;

public class Persion {
	public static int age  = 37;
	public static void show() {
		System.out.println("age is "+ age);
	}
	
	public static class Foot {
		public void run() {
			System.out.println("run");
		}
	}
}
// 静态导入,*表示导入Persion中的所有静态成员,建议只导入使用的成员
import static com.lijian.lession5.Persion.*;

public class Main {
	public static void main(String[] args) {
		System.out.println(age); // 37
		
		show(); //  age is 37
		
		Foot foot = new Foot();
		foot.run(); //run
	}
}

十二、初始化

成员变量的初始化

编译器会自动为未初始化的成员变量设置初始值

如何手动给实例变量提供初始值

  • 在声明中
  • 在构造方法中
  • 在初始化块中

编译器会讲初始化块复制到每个构造方法的头部(每创建一个实例对象,就会执行一次初始化块)

如何手动给类变量提供初始值

  • 在声明中
  • 在静态初始化块中

当一个类被初始化的时候执行静态初始化块 当一个类第一次被主动使用时,JVM对类进行初始化

public class Persion {
	static {
		System.out.println("static block 1");
	}
	
	static {
		System.out.println("static block 2");
	}
	
	{
		System.out.println("block");
	}
	
	public Persion() {
		System.out.println("Persion construct");
	}
	
	public Persion(int age) {
		System.out.println("Persion(int) construct");
	}
}


class Main {
	public static void main(String[] args) {
		new Persion();
		System.out.println("---------");
		new Persion(37);
	}
}

输出如下

static block 1
static block 2
block
Persion construct
---------
block
Persion(int) construct
  • 可以有多个(静态)初始化模块,按照在源码中出现的顺序被执行

14、单例模式

如果一个类设计成单例模式,那么在程序运行过程中,这个类只能创建一个实例

public class Rocket {
	// 静态的类成员
	private static Rocket instance = new Rocket();
	// 构造函数私有化
	private Rocket() { }
	// 静态的方法获取实例
	public static Rocket getInstance() {
		return instance;
	}
}

15、final常量

  • 被 final 修饰的类:不能被子类化,不能被继承
  • 被 final 修饰的方法:不能被重写
  • 被 final 修饰的变量:只能进行1次赋值

常量(Constant)

  • 常量的写法
  • 如果将基本类型或字符串定义为常量,并且在编译时就能确定值
    • 编译器会使用常量值替代各处的常量名(类似于 C 语言的宏替换)
    • 称为编译时常量( compile-time constant)

16、内部类

嵌套类: 定义在另一个类中的类

public class OuterClass {
	//静态嵌套类
	static class StaticNestClass {
		
	}
	// 非静态嵌套类--内部类
	class InnerClass {
		
	}
}

在嵌套类外层的类,称为:外部类(Outer Class)

最外层的外部类,称为:顶级类(Top-level Class)

  • 内部类:没有被 static 修饰的嵌套类,非静态嵌套类
  • 跟实例变量、实例方法一样,内部类与外部类的实例相关联
    • 必须先创建外部类实例,然后再用外部类实例创建内部类实例
    • 内部类不能定义除编译时常量以外的任何 static 成员
  • 内部类可以直接访问外部类中的所有成员(即使被声明为 private)
  • 外部类可以直接访问内部类实例的成员变量、方法(即使被声明为 private)

内部类举例

public class Persion {
	private int age;
	public class Hand {
		private int weight;
	}
}

public class Main {
	Persion p1 = new Persion();
	Hand h1 = p1.new Hand();
	
	Persion p2 = new Persion();
	Hand h2 = p2.new Hand();
}

使用示例

package com.lijian.lession5;

public class Company {
	private String name;
	
	public Company(String name) {
		this.name = name;
	}
	
	public void fire(Employee e) {
		System.out.println(name + " fire " + e.no);
	}
	
	public class Employee {
		private int no;
		public Employee(int no) {
			this.no = no;
		}
		
		public void show() {
			System.out.println(name + ":" + no);
		}
	}
}
package com.lijian.lession5;

public class Main {
	public static void main(String[] args) {
		Company c = new Company("Google");
		Company.Employee e = c.new Employee(1);
		
		e.show();
		c.fire(e);
	}
}

输出

Google:1
Google fire 1

内部类使用细节

package com.lijian.lession5;

public class OuterClass {
	private int x = 1;
	
	public class InnerClass {
		private int x = 2;
		
		public void show() {
			System.out.println(x);
			
			System.out.println(this.x);
			
			System.out.println(OuterClass.this.x);
		}
	}
	
	public static void main(String[] args) {
		new OuterClass().new InnerClass().show();
	}
}

输出

2
2
1

17、静态嵌套类

  • 静态嵌套类:被 static 修饰的嵌套类
  • 静态嵌套类在行为上就是一个顶级类,只是定义的代码写在了另一个类中
  • 对比一般的顶级类,静态嵌套类多了一些特殊权限
    • 可以直接访问外部类中的成员(即使被声明为 private)

什么情况使用嵌套类

  • 如果类 A 只用在类 C 内部,可以考虑将类 A 嵌套到类 C 中

    • 封装性更好
    • 程序包更加简化,看着类会减少
    • 增强可读性、维护性,一眼看到是两个类相关。
  • 如果类 A 需要经常访问类 C的非公共成员,可以考虑将类 A嵌套到类 C 中

    • 另外也可以根据需要将类 A 隐藏起来,不对外暴露
  • 如果需要经常访问非公共的实例成员,设计成内部类(非静态嵌套类),否则设计成静态嵌套类

    • 如果必须先有 C 实例,才能创建 A 实例,那么可以将 A 设计为 C 的内部类

18、局部类

  • 局部类:定义在代码块中的类(可以定义在方法中、 for 循环中、 if 语句中等)
  • 局部类不能定义除编译时常量以外的任何 static 成员
  • 局部类只能访问 final 或者 有效 final 的局部变量
    • 从 Java 8 开始,如果局部变量没有被第二次赋值,就认定为是有效 final
  • 局部类可以直接访问外部类中的所有成员(即使被声明为 private)
    • 局部类只有定义在实例相关的代码块中,才能直接访问外部类中的实例成员(实例变量、实例方法)
public class Persion {
	public void test() {
		int a = 10; // 有效final
		
		class Test {
			int age = 1;
			void test() {
				// 这里类似是值捕获,如果是引用捕获(如多线程的情况),可能a的内存已经释放,访问就不安全
				System.out.println(a);
			}
		}
		
		Test t = new Test();
		t.test();
	}
}
package com.lijian.lession5;

public class Persion {
	private int age;
	public void test() {
		class Dog {
			void speak() {
				System.out.println(age);
			}
		}
		
		//在实例方法中,有实例对象,所有可以直接访问实例变量
		Dog dog = new Dog();
		dog.speak();
	}
}

局部类举例

public class TestLocalClass {
	private int a = 1;
	private static int b = 2;
	private static void test1() {};
	private void test2() {};
	public void test3() {
		int c = 2;
		class LocalClass {
			static final int d = 4;
			void test4() {
				//a 是外部类的实例变量, 应为test3调用的时候,有this指针,可以访问
				// b 是静态的,作用于内可见,所以可以访问
				// c 没有被进行二次赋值,编译器认为是常量,可以访问。
				System.out.println(a + b + c + d);
				test1();
				test2();
			}
		}
		new LocalClass().test4();
	}
}

19、抽象类

抽象方法

  • 抽象方法:被 abstract 修饰的实例方法
    • 只有方法声明,没有方法实现(参数列表后面没有大括号,而是分号)
    • 不能是 private 权限(因为定义抽象方法的目的让子类去实现)
    • 只能定义在抽象类、接口中

抽象类

  • 抽象类:被 abstract 修饰的类
    • 可以定义抽象方法
    • 不能实例化,但可以自定义构造方法
    • 子类必须实现抽象父类中的所有抽象方法(除非子类也是一个抽象类)
    • 可以像非抽象类一样定义成员变量、常量、嵌套类型、初始化块、非抽象方法等
      • 也就说,抽象类也可以完全不定义抽象方法
  • 常见使用场景
    • 抽取子类的公共实现到抽象父类中,要求子类必须要单独实现的定义成抽象方法

抽象类的目的就是用来继承的。

抽象类在非抽象类的基础上增加了定义抽象方法的功能,同事也减少了一个功能:实例化。

抽象类的功能其实普通的类也能实现,为什么要用抽象类呢?

  • 不是实例化
  • 强制子类实现
package com.lijian.lession5;

public abstract class Shape {
	protected double area;
	protected double girth;
	public double getArea() {
		return area;
	}
	
	public double getGirth() {
		return girth;
	}
	
	public void show() {
		calculate();
		System.out.println(area + "_" + girth);
	}
	protected abstract void calculate() ;
}

子类:

package com.lijian.lession5;

public class Rectangle extends Shape{
	private double width;
	private double height;
	public Rectangle(double width, double height) {
		super();
		this.width = width;
		this.height = height;
	}
	
	@Override
	protected void calculate() {
		area = width * height;
		
		girth = (width + height)*2;
		
	}
	
	public static void main(String[] args) {
		Rectangle rectangle = new Rectangle(10, 20);
		rectangle.show();
	}
}

子类2:

package com.lijian.lession5;

public class Circle extends Shape{

	private double radius;
	
	public Circle(double radius) {
		this.radius = radius;
	}
	
	@Override
	protected void calculate() {
		double half = Math.PI *radius;
		area = half * radius;
		girth = half*2;
		
	}
	public static void main(String[] args) {
		Circle circle = new Circle(10);
		circle.show();

	}
}

20、接口基本使用

  • 看到“接口”二字首先想到的什么?
    • 网线接口? USB 接口?
  • 接口的英文单词是 Interface, 这个单词是否很熟悉?
  • API(Application Programming Interface)
    • 应用编程接口,提供给开发者调用的一组功能(无须提供源码)
  • Java 中的接口
    • 一系列方法声明的集合
    • 用来定义规范、标准

接口可以定义的内容

  • 可以定义:抽象方法、常量、嵌套类型,从 Java 8 开始可以定义:默认方法、静态方法(类方法)
    • 上述可以定义的内容都是隐式 public 的,因此可以省略 public 关键字
    • 从 Java 9 开始可以定义 private 方法
  • 常量可以省略 static、 final
  • 抽象方法可以省略 abstract
  • 不能自定义构造方法、不能定义(静态)初始化块、不能实例化

21、接口细节

  • 接口名称可以在任何使用类型的地方使用
  • 一个类可以通过 implements 关键字实现一个或多个接口
    • 实现接口的类必须实现接口中定义的所有抽象方法,除非它是个抽象类
    • 如果一个类实现的多个接口中有相同的抽象方法,只需要实现此方法一次
    • extends 和 implements 可以一起使用, implements 必须写在 extends 的后面
    • 当父类、接口中的方法签名一样时,那么返回值类型也必须一样
  • 一个接口可以通过 extends 关键字继承一个或者多个接口
    • 当多个父接口中的方法签名一样时,那么返回值类型也必须一样

接口升级问题

  • 如果接口需要升级,比如增加新的抽象方法
    • 会导致大幅的代码改动,以前实现接口的类都得改动
  • 若想在不改动以前实现类的前提下进行接口升级,从 Java 8 开始,有 2 种方案
    • 默认方法(Default Method)
    • 静态方法(Static Method)

22、对比抽象类

  • 抽象类和接口的用途还是有点类似,该如何选择?
  • 何时选择抽象类?
    • 在紧密相关的类之间共享代码
    • 需要除 public 之外的访问权限
    • 需要定义实例变量、非 final 的静态变量
  • 何时选择接口?
    • 不相关的类实现相同的方法
    • 只是定义行为,不关心具体是谁实现了行为
    • 想实现类型的多重继承

23、默认方法

  • 用 default 修饰默认方法
  • 默认方法只能是实例方法
package com.lijian.lession6;

public interface Eatable {
	default void eat(String name) {
		System.out.println("Eatable - eat" + name);
	}
}

Cat类

package com.lijian.lession6;

public class Cat implements Eatable {
	@Override
	public void eat(String name) {
		Eatable.super.eat(name);
		System.out.println("Cat eat - " + name);
	}
	
	public static void main(String[] args) {
		Cat cat = new Cat();
		//Eatable - eatbone
		//Cat eat - bone
		cat.eat("bone");
		
	}
}

Dog 类

package com.lijian.lession6;

public class Dog implements Eatable {
	public static void main(String[] args) {
		Dog dog = new Dog();
		dog.eat("bone"); //Eatable - eatbone
	}
}

默认方法的使用

  • 当一个类实现的接口中有默认方法时,这个类可以
    • 啥也不干,沿用接口的默认实现
    • 重新定义默认方法,覆盖默认方法的实现
    • 重新声明默认方法,将默认方法声明为抽象方法(此类必须是抽象类)
  • 当一个接口继承的父接口中有默认方法时,这个接口可以
    • 啥也不干,沿用接口的默认实现
    • 重新定义默认方法,覆盖默认方法的实现
    • 重新声明默认方法,将默认方法声明为抽象方法

默认方法实现细节

  • 如果父类定义的非抽象方法与接口的默认方法相同时,最终将调用父类的方法
  • 如果父类定义的抽象方法与接口的默认方法相同时,要求子类实现此抽象方法
    • 可以通过 super 关键字调用接口的默认方法
  • 如果(父)接口定义的默认方法与其他(父)接口定义的方法相同时,要求子类型实现此默认方法

24、静态方法

接口中定义的静态方法只能通过接口名调用,不能被继承

public interface Eatable {
	static void eat(String name) {
		System.out.println("eatable - eat -" + name);
	}
}
public class Main {
	public static void main(String[] args) {
		Eatable.eat("bone");
	}
}

25、多态 Polymorphism

  • 什么是多态?
    • 具有多种形态
    • 同一操作作用于不同的对象,产生不同的执行结果
  • 多态的体现
    • 父类(接口)类型指向子类对象
    • 调用子类重写的方法
  • JVM 会根据引用变量指向的具体对象来调用对应的方法
    • 这个行为叫做:虚方法调用(virtual method invocation)
    • 类似于 C++ 中的虚函数调用
package com.lijian.lession6;

public class Animal {
	public static void run() {
		System.out.println("Animal - run");
	}
}
package com.lijian.lession6;

public class Dog extends Animal{
	public static void run() {
		System.out.println("Dog - run");
	}
	
	public static void main(String[] args) {
		Dog.run(); //Dog - run
		Animal.run(); //Animal - run
		
		Dog dog1 = new Dog();
		dog1.run(); //Dog - run
		
		
		Animal dog2 = new Dog();
		dog2.run(); //Animal - run
		//这说明了静态方法没有多态的特性
	}
}

成员宾利的访问细节

成员变量访问也没有多态的特性。

package com.lijian.lession6;

public class Persion {
	public int age = 1;
	public int getPAge() {
		return age;
	}
}
package com.lijian.lession6;

public class Student extends Persion{

	public int age = 2;
	public int getSAget() {
		return age;
	}
	public static void main(String[] args) {
		Student  student1 = new Student();
		System.out.println(student1.age); //2
		System.out.println(student1.getPAge()); // 1
		System.out.println(student1.getSAget()); //2
		
		Persion student2 = new Student();
		System.out.println(student2.age); //1
		System.out.println(student2.getPAge()); // 1 
	}
}

instanceof

  • 可以通过 instanceof 判断某个类型是否属于某种类型

26、使用接口的好处

27、匿名类的基本使用 Anonymous Class

  • 当接口、抽象类的实现类,在整个项目中只用过一次,可以考虑使用匿名类
package com.lijian.lession6;

public interface Eatable {
	String name();
	int energy();
}
package com.lijian.lession6;

public class Persion {
	public void eat(Eatable e) {
		System.out.println("eat- " + e.name() + "-" + e.energy());
	}
	
	public static void main(String[] args) {
		Persion persion = new Persion();
		
		//输出: eat- apple-100
		persion.eat(new Eatable() {
			
			@Override
			public String name() {
				return "apple";
			}
			
			@Override
			public int energy() {
				return 100;
			}
		});
		
		
		Eatable beefEatable = new Eatable() {
			
			@Override
			public String name() {
				return "Beef";
			}
			
			@Override
			public int energy() {
				return 500;
			}
		};
		//输出: eat- Beef-500
		persion.eat(beefEatable);
	}
}

匿名类注意事项

  • 匿名类不能定义除编译时常量以外的任何 static 成员
  • 匿名类只能访问 final 或者 有效 final 的局部变量
  • 匿名类可以直接访问外部类中的所有成员(即使被声明为 private)
    • 匿名类只有在实例相关的代码块中使用,才能直接访问外部类中的实例成员(实例变量、实例方法)
  • 匿名类不能自定义构造方法,但可以有初始化块
  • 匿名类的常见用途
    • 代码传递
    • 过滤器
    • 回调

28、匿名类的用途

实现一个统计代码执行功能的工具类。 接口定义如下:

public interface Block {
	public void execute();
}

工具类定义如下:

public class Times {
	public static void test(Block block) {
		long begin = System.currentTimeMillis();
		block.execute();
		long end = System.currentTimeMillis();
		double duration = (end - begin)/ 1000.0;
		System.out.println("耗时:" + duration + "s");
	}
}

调用类:

public class Main {
	public static void main(String[] args) {
		Times.test(new Block() {
			
			@Override
			public void execute() {
				int age = 100;
				for(int i = 0; i < age; i++) {
					System.out.println(i);  //耗时:0.002s
				}
			}
		});
	}
}

29、匿名类_排序

30、lambda基本使用

  • Lambda 表达式是 Java 8 开始才有的语法,发音:美 [ˈlæmdə]
  • 函数式接口(Functional Interface):只包含 1 个抽象方法的接口
    • 可以在接口上面加上 @FunctionalInterface 注解,表示它是一个函数式接口
  • 当匿名类实现的是函数式接口时,可以使用 Lambda 表达式进行简化
public class Main {
	@FunctionalInterface
	public interface Caculator {
		int caculate(int v1, int v2);
	}
	
	static void execute(int v1, int v2, Caculator c) {
		System.out.println(c.caculate(v1, v2));
	}
	public static void main(String[] args) {
		execute(10, 20, (int v1, int v2) -> {
			return v1 + v2;
		}); //30
		
		execute(11, 22, (v1, v2) -> v1 + v2); //33
	}
}

31、lambda使用注意

 Lambda 只能访问 final 或者 有效 final 的局部变量  Lambda 没有引入新的作用域

32、方法引用

如果lambda中内容仅仅是某个方法,可以使用方法引用(method reference)来简化

种类用法
引用静态方法ClassName::staticMethodName
引用特定对象的实例方法ObjectName::instanceMethodName
引用特定类型的任意对象的实例方法ClassName::methodName
引用构造方法ClassName::new
引用当前类中定义的实例方法this::instanceMethodName
引用父类中定义的实例方法supper::instanceMethodName