Java学习 day12_lambda&Object

匿名内部类(对象)

我们已经使用过匿名对象了,匿名对象的含义是这个对象是没有名字,没有引用指向它

那么如果一个类没有名字,那么这个类就是匿名类(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**
      • 这个注解可以用来验证该接口是不是功能接口

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()方法尝试两种方式重写,instanceofgetClass,并比较它们的区别

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

#### 练习4getClass()方法练习

> 定义两个类,然后分别创建对象,调用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{
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值