JAVA面向对象三大特征—灵魂

6. 面向对象的三大特征

6.1 封装

快捷键:Alt + Insert —>: Generate Getters and Setter

6.1.1 封装的意义:

⼀个类中的某⼀些属性,不希望直接暴露给外界,让外界直接操作。因为如果让外界直接操作的话,对 这个属性进⾏的值的设置,可能不是我们想要的(可能不符合逻辑)。此时就需要将这个属性封装起

来,不让外界直接访问。

是一个较抽象的概念,可以将一个代码段、一个功能包装起来,方便维护和使用。

可以分为 广义的封装 和 狭义的封装

广义的封装:将一个功能封装成一个方法、将一个大的功能封装成一个模块、将某些具有固定格式的数据、 封装成一个类的对象。。。

​ 将某些具有固定格式的数据,封装成一个类的对象。

狭义的封装:将一个类的某些属性私有化,同时提供对应的 set 和 get 方法

​ 应用情形:某些属性若被外界访问,可能被赋予符合语法但不正确的值,此情况下我们需要将 该属性私有化 private 。

从JavaBean规范出发,所有属性都需要私有化。

实例:

public class Person {
  String name;
  private int age; // 1、将属性私有化起来,不让外界直接访问

  // 2、给要访问的属性,设置对应的 setter/getter ⽅法
  public void setAge(int age) {
  	this.age = age;
  }

  public int getAge() {
  	return this.age;
  } 
}
6.1.2 属性封装的过程
  1. 为了不让外界直接访问某些属性,⽤关键字private修饰这些属性。

  2. 给外界提供相应的⽅法,⽤来操作这个被私有化的属性。

  3. 疑问:为什么私有化起来的属性,不希望外界访问,还需要再提供setter和getter方法

    答:可以通过指定的⽅式访问属性,在这些⽅法中,可以添加⼀些数据处理操作

public class Person{
    private String name;
    private int age;
    private char gender;
    public void setAge(int age){
        if(age>=0&&age<=30){
        	this.age=age;
        }
    }
    public int getAge(){
        return this.age;
    }
}
6.1.3 【拓展】流式编程

在对对象进⾏操作的时候,每⼀步得到的结果还是对象本身,继续使⽤⼀个⽅法的返回值再去做其他操

作。这样的编程,叫做流式编程。

Dog dog = new Dog();
dog.setName("xiaobai").setAge(1).setGender('男').setColor("black");
public class Dog {
     private String name;
     private int age;
     private char gender;
     private String color;

     public Dog setName(String name) {
     	this.name = name;
     	return this;
     }

     public Dog setAge(int age) {
     	this.age = age;
     	return this;
     }
     public Dog setGender(char gender) {
     	this.gender = gender;
     	return this;
     }
     public Dog setColor(String color) {
     	this.color = color;
     	return this;
     } 
}

6.2 单例设计模式

设计模式:由前人总结的用来解决特定问题的一种解题思路。

6.2.1 简介:

在项目的任意模块、任意位置,某个类的对象只能是唯一的一个。

若每次获取类的对象需是同一对象,此时,对象的获取不能使用new的方式,因为new代表开辟一个新的空间。两次new出来的空间一定是不同的。
6.2.2 步骤

1.把类的构造方法私有化,杜绝从类外通过new方式来实例对象的可能性。

2.给类添加返回当前类的对象的静态方法。

3.单例分为 饿汉式单例懒汉式单例

  1. 声明一个私有的(private)、静态的(static)、当前类(类名)的对象
  2. 1饿汉式:直接在声明的同时对这个静态属性进行实例化。
public class Boss {
     // 1、将单例类的构造⽅法私有化,杜绝从外界通过new的⽅式实例化对象的可能性。
     private Boss() {
     	System.out.println("⼀个Boss对象出现了");
     }

     // 2、设计⼀个私有的、静态的、当前类的对象
     private static Boss instance;
     static {
     	// 对instance静态对象进⾏实例化
     	instance = new Boss();
     }
    // 3、需要提供⼀个public权限的静态⽅法,可以获取⼀个当前类的对象。
     public static Boss getCurrentBoss() {
     	return instance;
     } 
}

​ 2.2懒汉式:当第一次调用get方法时才实例化。

public class Chairman {
     // 1、将单例类的构造⽅法私有化,杜绝从外界通过new的⽅式实例化对象的可能性。
     private Chairman() {
     	System.out.println("⼀个Chairman对象被实例化了");
     }

     // 2、声明⼀个私有的、静态的、当前类的对象,不实例化
     private static Chairman instance;

     // 3、需要提供⼀个public权限的静态⽅法,可以获取⼀个当前类的对象,在这个⽅法中判断是否
    需要实例化单例类对象,如果需要,就去实例化。
     public static Chairman getChairman() {
         // 使⽤到instance对象的时候,判断是不是null
         if (instance == null) {
         	// 实例化
         	instance = new Chairman();
         }
         return instance;
     } 
}
6.2.3 懒汉式单例和饿汉式单例的对比

基本都是⼀样的,都可以获取到⼀个类的唯⼀的对象。

1、在没有使⽤获取当前类对象之前,懒汉式单例⽐饿汉式单例在内存上有较少的资源占⽤。

2、懒汉式单例在多线程的环境下有问题。

6.3 继承 extends

6.3.1 继承的定义

若有多个相关联的类,拥有相同属性和方法,那么可以把相同部分提取出来单独做一个类。这个公共类称为 父类(也称基类、超类 Super Class),原类的剩余部分分别生成 子类(也称派生类),父类与对应子类之间的关系称为 继承。

6.3.2 继承的语法
class 子类类名 extends 父类类名{
    //剩余成员
}
6.3.3 继承的特点
  1. 包括Java在内,很多语言是单继承的,即一个类只能有一个父类,同时自己可以派生多个子类,虽然抛弃多继承,但会用间接方法实现多继承。
  2. 子类自动继承父类中可视的成员,父类或兄弟类不拥有该子类的特有成员。

注意事项:

⼀定要保证,这多个类之间是有逻辑关联的。不能随便找两个类出来,为了继承⽽继承。

6.3.4访问权限修饰符

访问权限:指的是一个类、属性、方法 可以被访问的范围。

类前面没有public修饰符的话,类名和文件名可以不相同,但是这个类只能被同一个package下的类进行使用,public可以被任何package下的类使用
如果类名和文件名不一致,该类文件不能被设置为public权限

在开发应用中,编写的类内属性多数设定为private权限。

访问权限 修饰符 可以修饰 访问级别
公共权限 public 类、属性、方法 在当前项目的任意位置均可访问
保护权限 protected 属性、方法 在当前包中和跨包的子类中均可访问
包权限 (default)- 类、属性、方法 在当前包中可以访问
私有权限 private 属性、方法 在当前类中可以访问(不可被继承)

权限大小:public>prtected>包权限>private

**在实例化父类部分的时候,会自动调用父类中的无参构造方法,因此如果父类没有非私有权限的无参构造,会导致子类无法正常通过无参构造实例化。**解决方法:

  1. 给父类添加一个可继承的无参构造。
  2. 在子类的构造方法中使用super()来调用父类中可见的有参构造方法。例:
public class Dog extends Animals{
	public Dog(){
		super("xiaoming",2);
	}
}
6.3.5 继承中的构造方法(重点)

一个对象实例化时,在堆上开辟空间会先实例化父类部分。优先给从父类继承的属性分配空间。

关键字 super:代表对父类对象的引用,类似于this关键字

但一般应用于以下两种情况:

  1. 在构造方法中,使用super()调用父类中的构造方法。
  2. 在非静态方法、构造方法中,使用super调用父类中的方法,例:super.function();

注:构造方法不能被继承。

6.3.6 方法的重写(重点) Override

快捷键:Alt + Enter

在子类中对 从父类中或接口继承的方法进行重新实现或功能拓展,重写又称复写。

将父类的实现方式抛弃,改成自己的实现方式。

注意:方法名和参数一定要与父类保持一致,否则不报错很难识别问题。

例:

public class Animal {
    public void bark(){
        System.out.pribtln("Animal bak");
    }
}
public class Dog extends Animal {
	@Override
    public void bark(){
        System.out.pribtln("Dog bak");
    }
}

@Override

是系统内置的一个注解,仅用于方法前。

作用:检测下面的一个方法是不是一个重写的方法。

注意:这个注解仅起到验证作用,并不意味着没有添加注解的就不是重写的方法,但出于规范需要添加

重写对方法的返回值、访问权限的要求

**访问权限:**⼦类重写⽅法的访问权限必须⼤于等于⽗类⽅法的访问权限。

**返回值类型:**⼦类重写⽅法的返回值类型必须和⽗类⽅法的返回值类型⼀致,或者是其⼦类型。

*** 异常抛出类型:**⼦类重写⽅法的异常抛出类型必须⼩于等于⽗类⽅法的异常抛出类型

⾯试题: 简述 Override 和 Overload 的区别

Override: 是重写,是⼦类对⽗类的⽅法进⾏重新实现。

Overload: 是重载,是对同⼀个类中的同名、不同参数⽅法的描述。

6.3.7 Object类

Object是Java的根类,在Java中,所有的类都直接(没有继承类的类的父类)或间接继承自Object类。

Object中的几个方法:

  • getClass( 对象.class 也可以实现功能)

    • 获取用来描述当前类的class对象(获取一个类型)。
  • equals(常用于比较字符串和类)

    • 由于==比较对于对象来说只能比较地址,因此在一些比较时不符合逻辑需求,此时需重写equals来实现需求(默认比较地址)。

    • 注意事项:

      虽然可以在equals方法中进行自定义比较,但指定比较规则时仍需遵循一定的规范。
      1.若参数obj是null,则立即返回false。
      2.若this==obj,则一定返回true。
      3.若两个对象的类型不同,则一定返回false(this.getClass()!=obj.getClass();)。
      4.若a.equals(b)为true,则b.equals(a)也一定为true(满足互换性)。
      5.若a.equals(b)为true,b.equals(c)也是true,则c.equals(a)一定是true(满足传递性)。
      
    • 补充:

      由于参数obj是一个Object类型的参数,因此和this进行比较的时候需进行强制类型转换。

例:与Person类的对象进行比较:

@Override
Person other =(Person)obj;
public boolean equals(Object obj){
    if(obj==null){return false;}
    if(this==obj){return true;}
    if(this.getClass==obj.getClass){return false;}
    return this.name==other.name&&this.age==other.age&&this.gender==other.gender;
}
  • hashCode
    • 获取一个对象在一个散列中的索引(哈希表结构,需使用HashSet、HashMap等)。
  • toString
    • 可以通过重写这个方法,实现自定义的字符串表示形式,sout输出包含重写toString方法的类时自动调用
6.3.8 final关键字
可以修饰 含义
变量 表示值不可以发生改变,是一个常量
表示最终类,该类无法被继承
方法 表示最终方法,该方法无法被重写

6.4 多态

6.4.1 对象的转型

和数据类型转型相似,父类的引用可以指向子类的对象,接口的引用可以指向实现类的对象

例:

public class Program{
    public static void main(String[] args){
        Animal animal = new Dog();
    }
}
class Animal{}
class Dog extends Animal{}
class Hashiqi extends Dog{}
6.4.2 转型的分类
  1. 向上转型

    1. 由子类类型转型为父类类型。
    2. 向上转型一定会成功,是一个隐式转换。
    3. 向上转型后的对象将只能访问目标类型公有的成员(实现方法是子类的方法)。
  2. 向下转型

    1. 由父类类型转型为子类类型。

    2. 向下转型可能失败,需要显式转换。

      向下转型失败:如果需要转型的对象不是目标类型,则会转型失败,出现类型转换异常:ClassCastException。

    3. 判断方法:

      1. 用getClass方法与目标类型作比较。
      2. 用关键字 Instanceof。
    4. 向下转型的对象将可以访问子类中特有的成员。

6.4.3 Instanceof 关键字

向下转型存在失败的可能性,如果转型失败,则会出现 ClassCastException 异常。因此,在向下转型

之前,最好进⾏因此类型判断,判断这个对象是不是要转型的类型。

语法:i

对象 instanceof 类

判断对象是否是指定的类型,返回Boolean值。

例:

// 1、实例化⼀个Dog对象,并且向上转型
Animal animal = new Dog();
// 2、判断类型,判断animal指向的对象是不是⼀个Cat类型
if (animal instanceof Cat) {
 	System.out.println("animal 的确是⼀个Cat对象,可以进⾏向下转型");
}
else {
 	System.out.println("animal不是⼀个 Cat对象,不能进⾏向下转型");
}
6.4.4 多态的结果

使⽤向上转型后的对象,进⾏成员访问,访问⽗类中的⽅法,最终的实现,是子类中的重写实现

因为归根到底,向上转型后的引⽤,指向的其实还是⼀个⼦类的对象。

使⽤不同的类向上转型后的对象,调⽤⽗类中同⼀个⽅法,最终的实现不⼀样。

6.5 抽象类 abstract

6.5.1 抽象类和抽象方法

1.抽象方法:

  1. 抽象方法只有方法声明,没有方法实现,其表现为 没有{},其实现方法均必须由非抽象子类分别重写实现。
  2. 抽象方法只能被定义在抽象类中。

2.抽象类:使用关键字 abstract 修饰的类。

  1. 抽象类除了可以包含所有类的成员以外,还可以包含抽象方法,其中所有的成员都是为子类设计使用的。
  2. 抽象类中不一定只有抽象方法。
  3. 抽象类不能实例化对象
6.5.2 抽象类的使用场景
抽象类的⾮抽象⼦类中,必须重写实现抽象类中定义的所有的抽象⽅法。

可以通过抽象类和抽象方法实现某些简单规则的制定

抽象类中定义的若干方法均可以视为规则。

例:

//制定规则
public abstract class TrafficTools{
    abstract void transport();
}
//非抽象子类实现
public class Bike extends TrafficTools{
    @Override
    void transport(){
        System.out.println("蹬自行车");
    }
}

6.6 接口 Interface

抽象类的规则创建,是通过继承实现的,而在Java中,继承是单继承,因此若仅通过继承指定规则,可能对一个类原有的继承结构造成影响,且以此方式的类最多只能遵循一种规则。为此引入接口。

接口也是一种自定义的引用数据类型,与类相似,可以使用接口进行规则的指定,对所有的实现类进行统一约束,使其具有相同的对外方法和功能。

接口相关的操作尽量不要考虑实现方法,而考虑调用方式,以此减少冗余并、减少出错概率、提高灵活度。

6.6.1 接口的定义

接口中成员的定义:

  1. 可以写方法:无需添加修饰符,默认用public abstract 修饰的
  2. 可以写属性:默认用 public static final 修饰的,声明同时必须赋初始值。
interface USB {
     // 接⼝定义的属性,默认的修饰符是 public static final 。
     // 接⼝中的属性,都是静态常量 。
     int a = 10; 
    // 接⼝中的⽅法,默认的修饰符是 public abstract 。
 	void charge();
}
// 在使⽤接⼝中的属性的时候,需要⽤接⼝类型来访问
System.out.println(USB.a);
6.6.2 接口的实现

接口不是类,不能实例化对象,接口存在的意义是要给某个类实现的。

类在实现接口的时候需使用关键字 implement

语法:

class Mouse implements USB {
     @Override
     public void charge() {
     System.out.println("通过USB接⼝给⿏标充电");
     }
     @Override
     public void transportData() {
     System.out.println("通过USB接⼝,与⿏标进⾏数据的传递");
     } 
}

特点:

  1. 接口的实现对于继承没有影响:一个类可以在继承父类的同时实现多个接口,且先写继承再写接口。例:

    修饰符 class 类名 extends 父类名 implement 接口名1,接口名2,接口名3,。。。{
        //类体
    }
    

    相关名词:若一个类实现了一个接口,称这个类为这个接口的 实现类

  2. 实现类可以“继承”接口中的成员。

注意事项:

  1. 若接口的方法在父类中已被实现,那么子类自动继承,可以不用重写实现。
  2. 若一个类需实现接口的方法中有重复方法,只需且仅能实现一次。

⼀个类,可以实现多个接⼝。如果⼀个类实现了多个接⼝,则每⼀个接⼝中的抽象⽅法,都必须要实

现。

如果⼀个类实现的多个接⼝中,有相同的⽅法,实现类中只需要实现⼀次即可。

class Work {
     void work8Hours();
     int getSalary();
}
class HouseWork {
    void cook();
    void clean();
    int getSalary();
}
class Person implements Work, HouseWork {
    @Override
    public void cook() {
    }
    @Override
    public void clean() {
    }
    @Override
    public void work8Hours() {
    }
    @Override
    public int getSalary() {
        return 0;
    } 
}

Java8的新特性

  1. static

    在Java中可以用static修饰一个接口中的方法,接口中静态的方法需要添加实现。

    接口中的静态方法只能用接口自己来调用。

  2. default

    default方法也需要添加实现。接口中的default方法需用接口的对象来调用。

    其实default就是给接口方法添加一个默认的实现方式,实现类可以重写也可以不重写

6.6.3 接口的继承

接口之间存在多继承;子接口自动继承父接口的所有方法。

例:

public interface NurseSercive{
    void wash();
    void cook();
    void clean();
}
public interface Cook{
    void cook();
}
public interface TakeCareSercive{
    void massage();
}

public interface GirlFriend extends Cook,NurseService,TakeCareService{
}

public class Person implements GirlFriend{
    @Override
    void wash();
    @Override
    void cook();
    @Override
    void clean();
    @Override
    void massage();
}
6.6.4 接口的多态

对象转型

  1. 实现类转为接口类型,是一个向上转型。

    例:

    GirlFriend friend = new Person();
    
  2. 接口类型转为实现类,是一个向下转型。

多态

向上转型的对象调用接口中的方法,最终实现的是实现类中的实现方式。

例:

public interface CanBark{
    void bark();
    String toString();
}
public interface CanWalk{
    void walk();
    String toString();
}
public class Dog extends Animal implements CanBark,CanWalk{
    @Override
    public void walk(){
        System.out.println("四条腿走路");
    }
}

public class Program{
    public static void main(String[] args){
        //1.实例化一个Dog对象
        Dog xiaobai = new Dog();
        //2.将Dog对象向上转型为接口类型
        CanWalk animal = xiaobai;
        //3.转型后的对象调用接口方法,最终的实现是实现类中的实现方式
        //注意:此时转型后的对象没有CanBark接口的一切方法。
        animal.walk();	//结果:四条腿走路
    }
}
6.6.5 接口的案例

例:设计电脑的USB接口,使得任何想连接电脑的设备遵循接口规则后可以连接电脑,电脑输出该设备的信息。

public class Computer{
    //电脑的两个usb接口
    private USB usb1;
    private USB usb2;
    //将一个USB设备连接到usb窗口
    public void setUsb1(USB usb1){
        this.usb1 = usb1;
        System.out.println(this.usb1.getInfo());
    }
    public void setUsb2(USB usb2){
        this.usb2 = usb2;
        System.out.println(this.usb2.getInfo());
    }
}

public interface USB{
    //获取一个设备的描述信息
    String getInfo();
}

//输入设备,是键盘和鼠标的父类
public abstract class InputDevice{
    private String name;
    private int price;
    public void setname(String name){
        this.name = name;
    }
    public String getname(String name){
        return name;
    }
    public void setprice(int price){
        this.price = price;
    }
    public int getprice(int price){
        return price;
    }
    public InputDevice(String name,int price){
        super();
        this.name = name;
        this.price = price;
    }
}

public class Keyboard extends InputDevice implements USB{
    public Keyboard(String name,int price){
        super(name,price);
    }
    @Override
    public String getInfo(){
        return "我是一个键盘,我是名字是"+this.getName()+",我的价格是"+this.getPrice();
    }
}
public class Mouse extends InputDevice implements USB{
    public Mouse(String name,int price){
        super(name,price);
    }
    @Override
    public String getInfo(){
        return "我是一个鼠标,我是名字是"+this.getName()+",我的价格是"+this.getPrice();
    }
}

public class Program{
    public static void main(String[] args){
        //实例化键盘和鼠标
        Keyboard keyboard = new Keyboard("罗技",10);
        Mouse mouse = new Mouse("牧马人",11);
        Computer computer = new Computer();
        //让键盘连接到电脑的usb1接口
        computer.setUsb1(keyboard);
        //让鼠标连接到电脑的usb2接口
        computer.setUsb2(mouse);
    }
}

注意事项:Java中只有接口可以实现回调功能

6.6.6 函数式接口

若一个接口中,有且仅有一个方法是实现类必须要实现的,那么称这样的接口为 函数式接口。

例:

public interface Test{
    default void show(){}			//可实现可不实现
    default void show(int a){}		//可实现可不实现
    static void a(){}				//不需实现
    void display();
    String toString();				//父类Object已实现
    //注意:boolean equals(Test object)与Boolean equals(Object object)是重载关系,仍需实现。
    
}

@FunctionInterface 是一个用来修饰接口的注解,可以验证一个接口是不是一个函数式接口。

6.7 内部类

6.7.1 成员内部类
定义在⼀个类的内部,与这个类的成员(属性、⽅法)平级,并且没有⽤static修饰的类。
1、访问权限可以是任意的权限,类似于⼀个类中的成员。
2、实例化的过程,需要先实例化外部类对象,再使⽤外部类对象进⾏内部类的实例化
3、内部类编译后,也会⽣成.class字节码⽂件。格式:外部类$内部类.class

实例化 例:

class OutterClass{
    public String name;
    protected class InnerClass{
        public String name;
    }
}
public class Program{
    public static void main(String[] args){
        //实例化一个外部类对象
        OutterClass outter = new OutterClass();
        //实例化一个内部类对象
        OutterClass.InnerClass inner = outter.new InnerClass();
    }
}
6.7.2 静态内部类
定义在⼀个类的内部,与这个类的成员(属性、⽅法)平级,并且使⽤static修饰的类。
1、访问权限可以是任意的权限,类似于⼀个类中的成员。
2、实例化的过程中,直接使⽤new实例化⼀个外部类.内部类对象即可。
3、内部类编译后,也会⽣成.class字节码⽂件。格式:外部类$内部类.class

实例化 例:

class OutterClass{
    public String name;
    protected static class InnerClass{
        public String name;
    }
}
public class Program{
    public static void main(String[] args){
        //实例化一个外部类对象
        OutterClass outter = new OutterClass();
        //实例化一个内部类对象
        OutterClass.InnerClass inner = new OutterClass.InnerClass();
    }
}
6.7.3 局部内部类

仅当前类可使用。

定义在某⼀个代码段中的中。
1、没有访问权限修饰符。
2、在当前⽅法中,直接实例化即可
3、内部类编译后,也会⽣成.class字节码⽂件。格式:外部类$序号内部类.class

实例化 例:

class OutterClass{
    public String name;
}
public class Program{
    public static void main(String[] args){
    }
    static void show(){
        class InnerClass{
        }
    }
}
6.7.4 匿名内部类(重点)

没有名字的内部类。

实际应用中一般是配合抽象类或者接口一起使用的,表示这个类的子类。

package TestProgram1;

public class Program{
    public static void main(String[] args){
    	//此时animal(){}就是一个匿名内部类,可视为是animal的子类并向上转型为Animal类型
        Animal animal = new Animal() {	
            @Override
            public void bark(){
                System.out.println("bark");
            }
        };
        animal.bark();		//bark
      
        //此时CanWalk(){}就是一个匿名内部类,可视为CanWalk接口的实现类并向上转型为接口类型
        CanWalk walk = new CanWalk() {	//
        	@Override
        	public void walk() {
        		System.out.println("walk");
        	}
        };
        walk.walk();		//walk
        
    }
}
class Animal{
    public void bark(){
        System.out.println("animal bark");
    }
}

interface CanWalk{
	void walk();
}
注:在匿名内部类中,⼀般情况下不去添加新的成员(属性、⽅法),因为即便进⾏了添加,得到的对象也是向
上转型后的对象,不能访问⼦类中的成员。在匿名内部类中,⼀般是⽤来做⽅法的重写实现的。
匿名内部类也会⽣成.class字节码⽂件,命名格式: 外部类$序号.class

6.8 lambda表达式

6.8.1 lambda表达式的定义

lambda表达式是Java8的新特性之一,本质上来讲是一个匿名函数,主要关注:方法的参数列表 方法体

实际应用中一般是配合抽象类或者接口(常见)一起使用的,lambda表达式仅适合写一些简单的操作依次简化代码并提高灵活度。

可以使用lambda表达式对一个接口实现非常简洁的实现。

要求:lanbda表达式要求接口是一个函数式接口,即有且仅有一个必须要实现的方法

可以使用@FunctionalInterface注解来验证是否是函数式接口。

例:

package TestProgram3;

public class Program {
	public static void main(String[] args) {
		//由于接口无法作为参数
		//解决方案一:创造实现类并向上传参给calculate
//		Calculate calculate = new Add();
//		test(10,calculate);
		//解决方案二:创造匿名内部类并向上传参给calculate
//		Calculate calculate = new Calculate() {
//			
//			@Override
//			public int calculate(int a) {
//				return a+a;
//			}
//		};
//		test(10,calculate);
		//解决方案三:参数直接设置为Add对象
//		test(10,new Add());
		//解决方案四:
//		test(10,n->n+n);
		//解决方案五:lambda表达式
		Calculate calculate = n->n+n;
		test(10,calculate);
		
	}
	static int test(int n,Calculate c) {
		return c.calculate(n);
	}
}

class Add implements Calculate{

	@Override
	public int calculate(int a) {
		return a+a;
	}
	
}
interface Calculate{
	int calculate(int a);
}
6.8.2 lambda表达式的基础语法

关键lambda运算符号: -> 读作:”goes to“

一个完整的表达式由 参数列表 lambda表达式 方法体组成。

其中lambda运算符作用是分隔参数列表方法体

使用lambda表达式来实现接口中的抽象方法,因此在使用接口调用接口中的方法的时候,实际执行的是lambda表达式中的实现方式。

例:

//接口设计
package TestProgram4;
//无参无返接口
public interface NoneReturnNoneParameter {
	void test();
}
//单参无返接口
public interface NoneReturnSingleParameter {
	void test(int a);
}
//多参无返接口
public interface NoneRetrunMultipleParameter {
	void test(int a,int b);
}
//无参单返接口
public interface SingleReturnNoneParameter {
	int test();
}
//单参单返接口
public interface SingleReturnSingleParameter {
	int test(int a);
}
//多参单返接口
public interface SingleReturnMultipleParameter {
	int test(int a ,int b);
}

//接口实现
package clambda;
import TestProgram4.*;
public class Program1 {
	public static void main(String[] args) {
		//无参无返的接口实现
		NoneReturnNoneParameter lambda1 = ()->{
			System.out.println("lambda1");
		} ;
		lambda1.test();
		//单参无返的接口实现
		NoneReturnSingleParameter lambda2 = (int a)->{
			System.out.println("lambda2: a="+ a);
		};
		lambda2.test(10);
		//多参无返的接口实现
		NoneRetrunMultipleParameter lambda3 = (int a,int b)->{
			System.out.println("lambda3: a="+a+",b="+b);
		};
		lambda3.test(10, 20);
		//无参单返的接口实现
		SingleReturnNoneParameter lambda4 = ()->{
					return 10;
		} ;
		int ret1 = lambda4.test();
		System.out.println("lambda4:"+ret1);
		//单参单返的接口实现
		SingleReturnSingleParameter lambda5 = (int a)->{
			return a*2;
		};
		int ret2 = lambda5.test(10);
		System.out.println("lambda5:"+ret2);
		//多参单返的接口实现
		SingleReturnMultipleParameter lambda6 = (int a,int b)->{
			return a+b;
		};
		int ret3 = lambda6.test(10,20);
		System.out.println("lambda6:"+ret3);
	}
}

6.8.3 lambda表达式的语法精简
  1. 方法体部分的精简:

    1. 若一个lambda表达式的方法体中只有一句代码,则大括号可以省略。

      注:若单个语句为带返回值语句,则省略大括号的同时必须也省略 return 。

  2. 参数部分的精简:

    1. 可以省略掉参数的类型,因为参数的类型在接口的方法声明中已经明确了。

      注:若有多个参数,要么都省略要么都不省略。

    2. 若参数列表中只有一个参数,则小括号可以省略。

package clambda;
import TestProgram4.*;
public class Program2 {
	public static void main(String[] args) {
		//1.精简方法体
		NoneReturnNoneParameter lambda1 = ()->System.out.println("lambda1");
		SingleReturnMultipleParameter lambda2 = (a,b)->a+b;
		lambda1.test();
		System.out.println("lambda2:"+lambda2.test(10, 20));
		//2.省略参数的类型
		NoneRetrunMultipleParameter lambda3 = (a,b)->System.out.println("lambda3");
		lambda3.test(10, 20);
		//3.精简小括号
		NoneReturnSingleParameter lambda4 = a->System.out.println("lambda4");
		lambda4.test(10);
	}
}

6.8.4 函数引用(重点)

配合流式编程实现。

本质是用一个已经存在的方法来实现接口中的抽象方法,因此引用方法的参数和返回值必须与接口中规定的一致。

若一个lambda表达式的方法体中,只是调用其他的方法,此情况下方法的参数、方法体、lambda运算符都可以省略,直接用该调用方法即可。

语法:

接口名 对应接口的实现类对象名 = 引用的方法调用方::引用的方法

注意事项:

  1. ⼀定要注意引⽤的⽅法,参数和返回值类型,必须和接口中定义的⼀致。

  2. ⽅法的引⽤,可以引⽤任何的已经存在的⽅法。在引⽤的时候只需要注意⽅法是静态的还是⾮静态的即可。
    静态方法,需要使用类引用,非静态方法需要使用对象引用。

    例:

    package clambda;
    import TestProgram4.*;
    public class Program3 {
    	public static void main(String[] args) {
            //调用静态方法
    		NoneReturnNoneParameter lambda1 = Program3::nine;
    		lambda1.test();
    		NoneReturnSingleParameter lambda2 = Program3::nine;
    		lambda2.test(1);
            //调用非静态方法
    		SingleReturnMultipleParameter lambda3 = new Program3()::add;
    		System.out.println(lambda3.test(10, 20));
    		NoneReturnSingleParameter lambda4 = System.out::println;
    		lambda4.test(10);
    	}
    		private static void nine() {
    			for (int line = 1;line<=9;line++) {
    				for (int colum = 1;colum<=line;colum++){
    					System.out.print(colum+"X"+line+"="+colum*line+"    ");
    				}
    				System.out.println();
    			}
    		}
    		private static void nine(int a) {
    			System.out.print("hello world");
    		}
    		private int add(int a,int b) {
    			return a+b;
    		}
    }
    
  3. Setter、Getter方法的引用: 使用 类::get、使用 类::get,不管方法有无参数,接口引用的时候必须传入对象。

  4. 构造方法的引用:若一个接口中的方法是返回一个类的对象,此时可以直接引用类中的构造方法。

    语法

    接口名 对应接口的实现类对象名 = 类名::new
    

    注意:调用哪个构造方法根据接口引用的函数的参数判断。

    例:

    package clambda;
    public class Program4 {
    	public static void main(String[] args) {
    		//构造方法的引用
    		GetPerson lambda = Person::new;
    		Person xiaoming = lambda.get("xiaoming",12);
    		System.out.println(xiaoming);
    	}
    }
    class Person{
    	private String name;
    	private int age;
    	public Person() {
    		System.out.println("Person()");
    	}
    	public Person(String name,int age) {
    		this.name = name;
    		this.age = age;
    	}
    	@Override
    	public String toString() {
    		return "name = "+name+",age = "+age;
    	}
    }
    interface GetPerson{
    	Person get(String name,int age);
    }
    

发布了17 篇原创文章 · 获赞 0 · 访问量 56

猜你喜欢

转载自blog.csdn.net/weixin_43205419/article/details/105730882