匿名内部类(对象)
我们已经使用过匿名对象了,匿名对象的含义是这个对象是没有名字,没有引用指向它
那么如果一个类没有名字,那么这个类就是匿名类(Anonymous Class)
显然一个正常的类是不可能没有名字的,我们今天就来介绍匿名内部类
案例
回顾一下,以往当需要一个子类对象的时候,我们需要分两步走
- 定义子类继承(实现)父类(接口)
- 创建子类对象
- 那么有没有一步直达的方式呢?
- 我们可以通过局部内部类得到一个实现子类
- 如果把局部内部类更进一步,变成匿名(局部)内部类呢?
匿名内部类的创建
-
首先明确两点使用匿名内部类的前提
- 匿名内部类是特殊的局部内部类,所以匿名内部类必须定义在局部位置
- 匿名内部类是局部内部类的更进一步
- 匿名内部类的成员特征、访问特征和局部内部类没有区别
- 匿名内部类访问方法的局部变量时,该变量仍然是常量
- 存在一个已定义类或者接口,这里的类可以是具体类也可以是抽象类
- 匿名内部类是特殊的局部内部类,所以匿名内部类必须定义在局部位置
-
语法:
-
new 类名或者接口名(){ //重写方法 };
-
注意右大括号的结尾分号,不可省略
-
匿名内部类的本质是继承了一个(抽象)类或者实现了接口的子类对象
匿名内部类对象的使用
- 直接使用对象去调方法即可
- 也可以用父类接收,但是父类接收无法调用子类独有方法
- 当我们只使用一次某类或者某接口的,子类对象,此时用匿名内部类对象,会稍微方便一点
- 但是,如果要多次访问,匿名内部类对象中的成员,就比较麻烦了
- 因为每一次访问都得创建一次匿名内部类对象
- 通常,在只使用一次类、接口的子类对象的情况下,比较适合使用匿名内部类对象来完成
- 本质:是一个继承了类或者实现了接口的子类匿名对象
小试牛刀
按照要求,补齐代码
要求在控制台输出”HelloWorld”
public class Test{
public static void main(String[] args) {
Outer.method().show();
}
}
interface Inter {
void show();
}
class Outer {
public static Inter method(){
return new Inter{
@Override
public void show() {
System.out.println("Hello world!");
}
}
}
}
开发中的匿名内部类
- 作为方法的实际参数
- 学习多态的时候我们曾经讲过,可以将父类作为形式参数,而将子类作为实际参数在方法调用的时候传入
- 匿名内部类的匿名对象就是一个子类匿名对象,所以,可以使用匿名内部类改进以前的做法
- 在方法体中,作为方法的返回值
- 方法体中,return 返回值只会执行一次,如果方法返回一个接口类型的对象,我们也可以使用匿名内部类
课上代码
/**
常规做法:定义一个新的类实现接口,然后创建类对象,传参
稍高端做法:定义局部内部类,然后定义局部内部类传参
高端做法:匿名内部类
*/
public class Demo {
public static void main(String[] args) {
method(new IAImpl());
//稍微高端一些的方法,用局部内部类
class LocalIAImpl implements IA{
@Override
public void test() {
System.out.println("我是稍微高端的写法!");
}
}
method(new LocalIAImpl());
//高端的写法: 匿名内部类来完成功能
method(new IA (){
@Override
public void test() {
System.out.println("高端的写法");
}
});
//直接创建匿名内部类对象去使用
//使用方式一: 直接使用匿名的匿名内部类对象
//优点是,不需要引用接收,那么它使用起来更简洁,而且由于它就是子类对象,所以它可以访问子类独有的成员
//缺点是,没有引用指向,只能用一次
(new IA(){
@Override
public void test() {
System.out.println("hello Interface");
}
}).test();
int a = 10;
new AbstractPerson(){
int a = 10;
@Override
public void test() {
System.out.println("Hello Abstract");
}
//定义匿名子类独有的成员
public void testSon(){
System.out.println("Hello 儿子");
}
}.testSon();
//使用方式二: 用引用接收该子类对象,只能用父类引用接收
//优点是,接收后可以多次使用该对象
//缺点是,无法访问子类独有成员,甚至不能强转,所以是真正的无法访问,除了这个大缺点外,它还比较麻烦
NormalClazz nc = new NormalClazz(){
@Override
public void test() {
System.out.println("你好,师范");
}
public void testSon(){
System.out.println("独有方法");
}
};
//((NormalClazz) nc).testSon();
nc.test();
}
public static void method(IA a) {
a.test();
}
}
interface IA {
void test();
}
//常规的做法
class IAImpl implements IA {
@Override
public void test() {
System.out.println("常规做法");
}
}
abstract class AbstractPerson{
public abstract void test();
}
class NormalClazz{
public void test(){
System.out.println("Normal");
}
}
匿名内部类的使用优缺点
- 当我们只使用一次某类或者某接口的子类对象时,使用匿名内部类,会方便一点,简洁一点
- 如果需要多次访问子类对象的成员,必须要接收这个匿名内部类对象,否则会更麻烦
- 需要用匿名内部类对象的父类接收
- 无法访问匿名内部类中的独有方法
- 如果访问匿名子类中的独有方法,必须用匿名对象去访问,就无法用引用去接收了
lambda表达式
仅做参考与了解:lambda表达式
Lambda 表达式是 JDK8 的一个新特性,可以取代接口的匿名内部类,写出更优雅的 Java 代码
匿名内部类实际上是局部内部类的更进一步,简化了局部内部类
那么lambda就是匿名内部类更进一步,语法上更简洁了,代码更优雅了,是高端的玩法
是人上人的玩法
lambda表达式基本使用:
- 若想使用lambda简化接口的匿名内部类,需要该接口是一个功能接口
- 有且仅有一个抽象方法的接口称之为功能接口(FunctionInterface)
- 功能接口有一个专门的注解标记它,写在接口声明的上面,就叫**@FunctionInterface**
- 这个注解可以用来验证该接口是不是功能接口
- 有且仅有一个抽象方法的接口称之为功能接口(FunctionInterface)
1.功能接口当中只能有一个方法吗?
不是,因为java8之后接口中有默认方法和静态方法,它们不需要实现
2.功能接口当中只能有一个抽象方法吗?
不是,因为有些抽象方法不需要子类实现(挖个坑)
如果抽象方法是Object类当中存在的方法,那么就无需子类实现,因为Object类已经有实现了
*
- 下面给出六个接口,都是常见的功能接口形式,我们的练习就基于这六个接口
//无返回值无参数的功能接口
@FunctionalInterface
interface INoReturnNoParam {
void test();
}
//无返回值有一个参数的功能接口
@FunctionalInterface
interface INoReturnOneParam {
void test(int a);
}
//无返回值两个参数的功能接口
@FunctionalInterface
interface INoReturnTwoParam {
void test(int a, int b);
}
//有返回值无参数的功能接口
@FunctionalInterface
interface IHasReturnNoParam {
int test();
}
//有返回值一个参数的功能接口
@FunctionalInterface
interface IHasReturnOneParam {
int method(int a);
}
//有返回值两个参数的功能接口
@FunctionalInterface
interface IHasReturnTwoParam {
int test(int a, int b);
}
-
接口准备完毕后,就可以开始使用lambda表达式了
-
lambda基础语法
(接口中那个抽象方法的形参列表) -> { //这里面重写这个抽象方法,也就是方法体 }
-
基础语法解释
- () 小括号中要写接口中抽象方法的形参,没有参数就不写
- -> 是lambda运算符,读作“goes to”
- {}表示重写的方法的方法体 (形式参数) ->{方法体}
- {}只有一对,只能重写一个方法;()参数列表也只有一个,所以要求功能接口必须只有一个抽象方法
- 注意:整个lambda表示式表示功能接口的一个实现类对象,和匿名内部类的本质都是创建一个对象
-
重要:lambda表达式的类型推断
- 按照以上语法,直接写完代码,肯定是要报错的
- 因为编译器无法仅仅通过这个基础语法,就判断出这个lambda表达式究竟创建的是哪个接口实现类对象
- 由于Java是强类型语言,所以必须在编译时期就确定该对象的数据类型
- 编译器去确定lambda表达式(对象)的数据类型的过程称之为lambda表达式的类型推断
- 实际上编译器是通过,lambda表达式所处的上下文代码去判断其类型的
主要有下面四种常见的方式:
-
1,直接用父类引用去接收这个对象 2,直接告诉编译器lambda表达式的数据类型,但是不接收它,语法上和强制类型转换很像 在lambda表达式之前用 (接口的名字)lambda表达式 3,如果方法的形参是一个接口,那么编译器就已经知道了这里要传什么类型对象 所以可以直接把lambda表达式写在实参的位置,借助方法完成类型推断 4,如果方法的返回值类型是一个接口,那么编译器就已经知道了这里要返回什么类型对象 所以可以直接把lambda表达式写在返回值的位置,借助方法完成类型推断
public class Demo {
public static void main(String[] args) {
//创建ITest的实现类对象
ITest it1 = () -> {
System.out.println("hello world!");
};
it1.test();
IA ia = () -> {
System.out.println("hello IA!");
};
ia.test();
((ITest) () -> {
System.out.println("hello world!");
}).test();
method(() -> {
System.out.println("helloKitty!");
});
method().test();
}
public static void method(ITest i){
i.test();
}
public static IA method(){
return () -> {
System.out.println("hello 张三!");
};
}
}
@FunctionalInterface
interface ITest {
void test();
/*
default void testDefault(){}
static void testStatic(){}*/
}
@FunctionalInterface
interface IA {
void test();
}
lambda表达式继续简写
lambda表达式的目标接口,有且仅有一个抽象方法,这是lambda表达式继续简化的前提
- ()里的参数列表肯定是固定的,于是可以省略形参中的数据类型,仅写形参名
- 在上面的基础上,如果抽象方法的形参只有一个,小括号()也可以省略
- 如果{}中的方法体仅有一句,大括号可以省略
- 在上面的基础上,如果方法有返回值,且返回值return语句仅有一条,那么连return都可以省略
lambda表达式去引用一个已经实现的方法
有时候功能接口中的方法已经有实现了,如果不想自己再去重写这个方法
可以利用 lambda表达式的接口快速指向一个已经被实现的方法
-
语法
-
(接口中那个抽象方法的形参列表) -> 已实现的某个方法
-
-
更进一步简写
-
方法归属者::方法名
-
静态方法的归属者为类名,普通方法归属者为对象名
-
lambda表达式的作用域问题
lambda表达式对象没有自己单独的作用域,和方法共享作用域
lambda表达式的优缺点
- 优点:
- 极大得简化了代码,使代码变得更加优雅
- 函数式编程的代表,可能是未来高端的编程趋势
- Stream API
- 配合集合类去用,非常优雅和简洁,并且高效,十分常用
- 缺点:
- 过于简单的lambda表达式,显然可读性很低
lambda表达式简写
public class Demo {
public static void main(String[] args) {
//int aaa = 10;
INoReturnTwoParam ip1 = (int a, int b) -> {
//int aaa = 100;
System.out.println("a + b = " + (a + b));
};
ip1.test(10, 20);
//简化一下(形参)
INoReturnTwoParam ip2 = (a, b) -> {
System.out.println("a - b = " + (a - b));
};
ip2.test(10, 20);
INoReturnOneParam ip3 = a -> {
System.out.println("a的是值是: " + a);
};
ip3.test(10);
//简化掉大括号,lambda表达式就有高端的感觉了
((INoReturnNoParam) () -> System.out.println("PHP是世界上最好的语言!")).test();
IHasReturnOneParam ip4 = a -> {
return ++a;
};
System.out.println(ip4.test(10));
//简化一下上面的大括号
System.out.println(((IHasReturnOneParam) a -> ++a).test(999));
IHasReturnTwoParam ip5 = (a, b) -> method(a, b);
System.out.println(ip5.test(2, 2000));
IHasReturnTwoParam ip6 = Demo::method;
System.out.println(ip6.test(3, 300));
System.out.println(((IHasReturnTwoParam) (Demo::method)).test(4, 400));
INoReturnOneParam ip7 = new Demo()::method;
ip7.test(666);
//System.out.println();
//INoReturnNoParam ip8 = System.out::println;
//ip8.test();
//System.out.println(10);
INoReturnOneParam ip8 = System.out::println;
ip8.test(777777);
//需求: 希望有一个功能接口,然后完成所有对象数组的遍历
Student[] studs = new Student[3];
studs[0] = new Student(10, "马明");
studs[1] = new Student(16, "刘亦菲");
studs[2] = new Student(18, "杨超越");
ITraverseObjectArr i = Demo::traverseObjectArr;
i.traverse(studs);
}
//自定义对象数组遍历方法
public static void traverseObjectArr(Object[] arr){
for (Object o : arr) {
System.out.println(o);
}
}
//定义一个方法作为IHasReturnTwoParam功能接口的抽象方法的实现
//该实现方法要求和抽象方法形参一致,返回值类型一致
//这种方式在开发者很常见,可以写出更简洁的lambda表达式调用
public static int method(int var1, int var2) {
//非常多行代码
return var1 * var2;
}
public void method(int var1) {
//假设有很多代码
System.out.println("你好 " + var1);
}
}
//定义遍历对象数组的功能接口
interface ITraverseObjectArr{
void traverse(Object[] o);
}
class Student {
int age;
String name;
public Student() {
}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
}
//无返回值无参数的功能接口
@FunctionalInterface
interface INoReturnNoParam {
void test();
}
//无返回值有一个参数的功能接口
@FunctionalInterface
interface INoReturnOneParam {
void test(int a);
}
//无返回值两个参数的功能接口
@FunctionalInterface
interface INoReturnTwoParam {
void test(int a, int b);
}
//有返回值无参数的功能接口
@FunctionalInterface
interface IHasReturnNoParam {
int test();
}
//有返回值一个参数的功能接口
@FunctionalInterface
interface IHasReturnOneParam {
int test(int a);
}
//有返回值两个参数的功能接口
@FunctionalInterface
interface IHasReturnTwoParam {
int test(int a, int b);
}
Object引入
API概念的引入
- API,全称Application Programming Interface,也就是应用程序编程接口
- 此接口并非真正的接口,在Java当中指的是一些预先定义好的类和方法
- API的作用
- 开发者可以在不关注具体实现细节的前提下,使用这些已经预先定义好的类和方法实现自己的需求
Object类概述
- Object类是所有类继承层次的祖先类。
- 所有类(包括数组)都直接或者间接的继承自该类,都实现了该类的方法
- 自定义类时,我们并不需要特别的标注extends Object
- 这是一个隐式的继承
(所以不确定一个参数的类型是,会用到Object)
- 这是一个隐式的继承
为什么所有类都有一个默认无参?
- 当一个类没有定义构造方法的时候,就会自动添加默认构造方法
- 一旦有默认构造方法,在创建子类对象的时候,就会执行子类对象的隐式初始化
- 隐式初始化,默认调用父类的无参构造
- 所以最终,一定能保证,调用到Object类的无参构造方法,先初始化Object这个父类
Object的成员方法:
- public final Class getClass()
- public String toString()
- public boolean equals(Object obj)
- public int hashCode()
- protected void finalize()
- 不重要,Java9开始用@Deprecated标记该方法
- protected Object clone()
getClass()
public native final Class<?> getClass()
getClass()概述
-
语法
对象名.getClass();
-
其作用是返回调用此方法的Object的运行时类(的Class对象)
-
native代表本地方法,被其修饰的方法表示那些被C/C++实现的一些方法,本地方法不是Java代码实现的,Java程序员无需关心本地方法的实现,本地方法是调用别的实现,无需方法体
- <?>表示泛型,后面会学
什么是Class对象?:一个类的全部信息构成Class对象
- 需要注意的是Class首字母大写,这是一个类的对象,和String一样
- JVM每加载一个类,都会在内存中创建唯一一个和该类对应的Class对象
- 这个Class对象包含了这个类的全部信息
- 帮助程序员在运行时期,了解该对象的属性和行为
- Class对象处于堆上
- 在运行时期程序员可以通过这个类,获取该类型的所有信息
- 由于Class对象和类是一一对应的,所有Class对象很多时候被称为类对象
- Class对象和方法区中加载的字节码文件,都是在触发类加载的时候生成的
- 类加载只会触发一次,Class对象也只独一份
- 类的对象和类对象的区别
- 一个类的Class对象叫做类对象,也称之为运行时类对象,整个程序运行期间独一份
- 类的对象是类的一个实例,程序运行期间可以创建多个
- Class类对象是反射的基础
- 反射实质就是Class类的API使用
Class类的常用API
- getName()
- 获取类的全限定类名
- getSimpleName()
- 获取类名
Class aClazz = a.getClass(); Class bClazz = b.getClass(); System.out.println(aClazz); System.out.println(bClazz); System.out.println(aClazz.getName()); System.out.println(bClazz.getName()); System.out.println(aClazz.getSimpleName()); System.out.println(bClazz.getSimpleName());
toString()
public String toString()
toString()概述
-
语法
对象名.toString()
总结来说
- toString()方法就是简洁明了的一句话告诉我们这个对象长啥样
- 建议在子类中都重写这个方法
- 对象的成员变量就是描述对象的外貌特征的
- 理想情况下,我们希望这个方法能够输出对象中所有成员变量的取值
toString的使用
回想一下,我们之前直接打印数组名或者对象名,得到的是什么?
- 当我们直接打印数组名或者对象名时
- 其实都默认隐含了调用toString()方法,输出toString()方法返回的字符串
- 这个时候我们并没有重写toString()方法,打印的是全限定类名加地址值,实际上是我们打印对象名得到的结果
Object类当中的toString()方法
-
Object类的toString()方法返回一个字符串
-
该字符串由类的全限定类名、at标记符“@”和此对象十六进制地址值组成
-
getClass().getName() + '@' + Integer.toHexString(hashCode())
-
hashcode方法获取一个十进制类型的地址值
Object类的toString()方法往往不能满足我们的需求
我们需要自己重写toString()方法,这也是官方给我们的建议
-
常见的toString()方法的格式
-
对象所属类型{成员变量1 = '值1' ,成员变量2 = '值2'....}
-
-
一个类重写toString()后再打印对象名,就会自动调用该方法
注意事项
-
直接打印数组名或者对象名,默认调用toString()方法,然后打印该方法返回的字符串
-
用一个字符串和一个对象直接拼接,默认拼接该类的toString()方法字符串
-
debug时下一步会打印toString()返回的字符串
- 不要在toString()方法里对对象进行操作,避免造成奇怪的bug,debug模式会出现问题
-
如果类中有别的引用类型,可以在返回语句中调用该引用类型的toString()方法(这里看代码,比如Cat中又有Dog类成员变量,如果Dog已经重写了toString(),直接调用dog.toString())
打印对象名会自动调用toString方法,也可以显式调用,显式调用好不好?
不好,调用的对象如果本身为null,容易引发空指针异常
public class Demo {
public static void main(String[] args) {
Student s = new Student(18, 88, new Teacher(28));
System.out.println(s);
Student s1 = new Student(18, 77, null);
System.out.println(s);
}
}
class Student {
int age;
double score;
Teacher t;
//String name;
public Student() {
}
public Student(int age, double score) {
this.age = age;
this.score = score;
}
public Student(int age, double score, Teacher t) {
this.age = age;
this.score = score;
this.t = t;
}
/* @Override
public String toString() {
//希望重写的toString()用来打印属性值
return "年龄是:" + age + " 分数是:" + score;
}*/
//toString方法可以直接使用idea快捷键来自动生成
@Override
public String toString() {
age = 999;
System.out.println("hello world!");
return "Student{" +
"age=" + age +
", score=" + score +
", " + t + // 默认输出对象名就调用了toString
'}';
}
}
class Teacher{
int age;
public Teacher(int age) {
this.age = age;
}
public Teacher() {
}
@Override
public String toString() {
return "Teacher{" +
"age=" + age +
'}';
}
}
equals()
public boolean equals(Object obj)
equals() 概述
-
语法
对象名.equals(其他对象名)
-
指示其他某个对象是否与此对象“相等”
-
对象相等的含义
- 我们理想状态下的对象相等: 类型相等,成员变量相等
- Object类中的相等:只有两个对象的内存地址相等,才叫相等,Object类中的equals()方法等价于”==“
- Object类中的equal()方法不能满足我们需求,需要自己手动重写该方法
设计equals() 方法的原则(常规协定)
-
自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true
-
排他性:当比对的不是同种类型的对象或者是一个null时,默认返回false
-
对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true
如果x=y,那么也y=x
-
传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true
- 那么x.equals(z) 应返回 true
-
一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false
判断是否是同种类型对象的两种方式:
当传入一个o作为被比较的对象时
- if ( ! (o instanceof 当前类) )
- 当使用instaceof关键字时,o可以传入后面类型的对象,也可以是子类对象
- if (o == null || this.getClass() != o.getClass())
- 当使用getClass()方法的运行时类型判断时,必须要求传入一致类型的对象
equals方法使用注意事项:
- 对于任何非空引用值 x,x.equals(null) 都应返回 false
- 不要使用一个null常量去调用方法,会引发程序错误,报空指针异常
- 在方法中,我们只能对方法的参数进行校验,没办法校验调用者
- 应该在外部写代码,防止使用一个null去调用方法
- 如果类中有引用类型的成员变量,继续调用该引用类型的equal()方法判断
public class Demo {
public static void main(String[] args) {
Cat c1 = new Cat(1, 10000, new Person(18));
Cat c2 = new Cat(1, 10000, new Person(18));
System.out.println(c1 == c2); //false
System.out.println(c1.equals(c2));
}
}
class Cat {
int age;
double price;
Person p;
public Cat(int age, double price, Person p) {
this.age = age;
this.price = price;
this.p = p;
}
public Cat() {
}
public Cat(int age, double price) {
this.age = age;
this.price = price;
}
//用idea的快捷键自动生成equals方法
@Override
public boolean equals(Object o) {
if (this == o) return true; //自反性, 先比较两个对象的地址,如果相等,就说明是同一个
if (o == null || getClass() != o.getClass()) return false; //排他性
// 这里也可以使用instanceof
// if (o == null || o instanceof Cat) return false
Cat cat = (Cat) o;
if (age != cat.age) return false;
if (Double.compare(cat.price, price) != 0) return false;
return p.equals(cat.p); // 调用引用类型成员变量的
}
}
class Person{
int money;
public Person() {
}
public Person(int money) {
this.money = money;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return money == person.money;
}
}
补充知识点:
- 浮点类型比较大小,不建议直接使用“=”号,而是用Double类和Float类中的compare() 方法
- 调用方式:Double.compare(double d1,double d2) Float.compare(float f1,float f2)
- 判断方式
- 如果这两个数字数学意义上相等,则返回 0
- 如果前者在数字上小于后者,则返回小于 0 的值
- 如果前者在数字上大学后者,则返回大于 0 的值
public class Demo2 {
public static void main(String[] args) {
double a1 = 1;
double a2 = 0.9;
System.out.println(a1 - a2);
//创建BigDecimal对象
BigDecimal b1 = new BigDecimal("1");
BigDecimal b2 = new BigDecimal("0.9");
//System.out.println(b1 - b2);
//调用减法的方法: subtract
BigDecimal result = b1.subtract(b2);
System.out.println(result);
}
}
- 财务金额上使用的确保精度的数字类型
- 如果使用double或者float作为金额的数据类型,会出现一些不可预知的精度问题
- 推荐使用BigDecimal这个类
作业题
练习一
定义一个计算(compute)接口,接口中有加减乘除四个抽象方法。
然后使用匿名内部类去实现加减乘除并测试
public class Demo01 {
public static void main(String[] args) {
calculate(new Compute() {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int minus(int a, int b) {
return a - b;
}
@Override
public int multiply(int a, int b) {
return a * b;
}
@Override
public int divide(int a, int b) {
return a / b;
}
}, 100, 10);
}
public static void calculate(Compute c, int a, int b){
System.out.println("两数相加:" + c.add(a, b));
System.out.println("两数相减:" + c.minus(a, b));
System.out.println("两数相乘:" + c.multiply(a, b));
System.out.println("两数相除:" + c.divide(a, b));
}
}
interface Compute{
int add(int a, int b);
int minus(int a, int b);
int multiply(int a, int b);
int divide(int a, int b);
}
练习2:lambda表达式的练习
lambda表达式的书写,除了注意格式外,最重要的是关注类型推断
- 提供以下6个功能接口,请用lambda表达式分别创建对象,调用test()方法
- 自由发挥lambda表达式的书写
package homework;
/**
* > lambda表达式的书写,除了注意格式外,最重要的是关注类型推断
*
* - 提供以下6个功能接口,请用lambda表达式分别创建对象,调用test()方法
* - 自由发挥lambda表达式的书写
*/
public class Demo02 {
public static void main(String[] args) {
INoReturnNoParam ip1 = ()->{
System.out.println("这对应着一个无参数、无返回值的功能接口!");
};
ip1.test();
// 这种方式传参是在后面,不实在lambda表达式的括号里,括号里是形参的定义
((INoReturnOneParam)(int a)->{
System.out.println("这对应着有一个参数、无返回值的功能接口!,传过来的参数是" + a );
}).test(1011011);
INoReturnTwoParam ip3 = (a, b) ->{
System.out.println("这对应着有两个参数,无返回值的功能接口!a的值是" + a +",b的值是" + b );
};
ip3.test(998, 996);
IHasReturnNoParam ip4 = () -> {return 999;};
System.out.println("这对应着没有参数,有返回值的功能接口!返回值为" + ip4.test() );
System.out.println("这对应着一个参数,有返回值的功能接口!a的值为:" + ((IHasReturnOneParam) a -> a).method(1000));
IHasReturnTwoParam ip6 = (a, b) -> multiply(a,b);
System.out.println("这对应着两个参数,有返回值的功能接口!返回值为"+ip6.test(50,80));
}
public static int multiply (int a, int b){
return a * b;
}
}
练习3:toString()和equals()方法的练习
手动重写Object类的方法
equals()方法尝试两种方式重写,instanceof和getClass,并比较它们的区别
package homework;
/**
* 定义一个类Cat:
* 成员变量:int age,String name,Dog d
* 其中的类Dog:
* 成员变量:age
* 手写Cat类的toString()和equals()方法
*/
public class Demo03 {
public static void main(String[] args) {
Cat cat = new Cat("YouKnowWho", 2000, new Dog(18));
System.out.println(cat.toString());
Cat cat1 = new Cat("YouKnowWho", 2000, new Dog(18));
System.out.println(cat.equals(cat1));
}
}
class Cat{
String name;
int age;
Dog d;
public Cat(String name, int age, Dog d) {
this.name = name;
this.age = age;
this.d = d;
}
public Cat() {
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
", d=" + d +
'}';
}
@Override
public boolean equals(Object o){
// 使用最高父类做形参,保证所有的类都能传过来
if(this == o) return true; // 如果地址都是自己,那肯定equal
if(o == null || getClass() != o.getClass()){
return false;
}
Cat c = (Cat) o;
if(!this.name.equals(c.name)) return false;
if(this.age != c.age) return false;
return this.d.equals(c.d);
}
}
class Dog{
int age;
public Dog(int age) {
this.age = age;
}
public Dog(){}
@Override
public String toString() {
return "Dog{" +
"age=" + age +
'}';
}
@Override
public boolean equals(Object obj){
if(this == obj) return true;
if(obj == null || getClass() != obj.getClass()) return false;
Dog d = (Dog) obj;
if(this.age != d.age) return false;
return true;
}
}
public boolean equals(Object o){
if(this == o) return true; // 如果地址都是自己,那肯定equal
if(o == null || !(o instanceof Cat)){
return false;
}
Cat c = (Cat) o;
if(!this.name.equals(c.name)) return false;
if(this.age != c.age) return false;
return this.d.equals(c.d);
}
@Override
public boolean equals(Object obj){
if(this == obj) return true;
if(obj == null || !(obj instanceof Dog)) return false;
Dog d = (Dog) obj;
if(this.age != d.age) return false;
return true;
}
```
#### 练习4:getClass()方法练习
> 定义两个类,然后分别创建对象,调用getClass方法
>
> 调用Class类的API方法getName()和getSimpleName()
>
> 最后用“==”号比较它们是否相等,为什么相等和不等
>
> 理解运行时类对象、类加载、类的对象的区别
一个类的Class对象叫做类对象,在运行期间只存在一份,而类的对象指的是类的具体的一个实例,可以创建很多份
```java
package homework;
public class Demo04 {
public static void main(String[] args) {
Aaa a = new Aaa();
Bbb b = new Bbb();
Class aClazz = a.getClass();
Class bClazz = b.getClass();
System.out.println(aClazz);
System.out.println(bClazz);
System.out.println(aClazz.getName());
System.out.println(bClazz.getName());
System.out.println(aClazz.getSimpleName());
System.out.println(bClazz.getSimpleName());
// 通过比较可以发现不相等,getSimpleName()得到的是类名,而getName()得到的是全限定类名,包含着类所在的包名
System.out.println(aClazz.getSimpleName() == bClazz.getSimpleName());
}
}
class Aaa{
}
class Bbb{
}