Java 函数、类、枚举、接口、泛型、lambda表达式


 

方法 | 函数

递归函数

在方法体内调用方法本身,方法要有出口。

 

构造方法

如果没有显式提供构造方法,会自动提供一个无参的构造方法作为默认的构造方法;如果显式提供了构造方法,则不再自动提供默认的构造方法。
 

如果子类显式提供了构造方法

  • 如果显式调用了父类的构造方法:则调用语句必须是函数体中的第一个语句
  • 如果没有显式调用父类的构造方法:会自动在函数体的首行调用父类的无参构造方法,如果父类没有提供无参的构造方法,则编译报错
     

不能直接使用 父类名() 的方式调用父类的构造方法,要通过 super() 来调用,super() 只能在构造方法中使用。

可以在实例方法中通过super对象可以访问父类的成员,super对象属于实例、不是类的公共属性,不能在静态方法中使用super对象。

 

函数重载

函数重载、重写的区别

  • 重载 overload:发生在同一个类中,方法名相同,但参数类型或个数不同,与返回值类型、访问修饰符无关。
  • 重写 override:发生在子类中,修改、覆盖父类方法的实现,只能重写被继承的方法(访问权限不为private),方法名、形参表都必须相同,返回值类型要属于(小于等于)父类方法的返回值类型,抛出的异常要属于(小于等于)父类方法抛出异常,方法访问权限要大于等于父类

构造方法不能被重写,但可以被重载。

 

参数个数可变

放在形参表末尾,调用时该位置可传入0个或多个指定类型的参数,实质是以数组方式传入,在函数体中以数组方式进行操作。

public void test(String str, String... args) {
	//jvm会预先创建一个数组来接收个数可变的参数,即使传了0个参数,args也不为null
	//可根据数组的元素个数来判断是否传入了参数
    if(args.length!=0){
        for (String arg : args) {
        	//...
        }
    }
}
  • 传参方式:java只有一种传参方式——传值,把实参的值拷贝一份传给形参。拷贝使用的是浅拷贝,基本数据类型、String拷贝的是其中的数值常量、字符串常量,修改形参不会影响到实参;引用类型拷贝的是对象引用,形参、实参指向同一个对象,修改形参就是修改实参。

 

Nullable、Nonnull 提示

可以在形参前面、方法上面标注

  • @Nullable:告诉编译器可以为null
  • @Nonnull:告诉编译器不能为null,标注在方法上面时,表示方法返回值不会为null;标注再形参前面时,表示该参数不能传null。

这2个注解推荐使用 javax、spring 提供的,javax需要引入 jsr305,这个依赖 guava 之类常见的框架中已经包含了。有的公司引入的是 jetbrains.annotations,使用 @Nullable、@NotNull。

<dependency>
    <groupId>com.google.code.findbugs</groupId>
    <artifactId>jsr305</artifactId>
</dependency>

<dependency>
    <groupId>org.jetbrains</groupId>
    <artifactId>annotations</artifactId>
    <version>16.0.1</version>
</dependency>

 

面向对象的3大特性

继承

构造方法

  • 父类的构造方法不管被哪种访问权限修饰,都不会被子类继承;
  • 子类构造方法必须在方法体的首行调用父类的构造方法,可以 super(xxx) 显式调用父类的指定构造方法,也可以缺省,缺省时编译器会自动在首行加上 super() 调用父类无参的构造方法。
     

重写父类的方法

  • 方法名相同、形参表相同
  • 如果父类方法是类方法(static)| 实例方法,子类方法也要是static | 实例方法,与父类保持一致
  • 子类方法的返回值类型要 <= 父类方法的返回值类型
  • 子类方法抛出的异常类型要 <= 父类方法抛出的异常类型
  • 子类方法的访问权限要 >= 父类方法的访问权限(如果父类方法使用private修饰,则子类不会继承该方法,也就不能重写)
     

在子类中访问父类的成员

super(xxx)  //调用父类的构造函数
super.xxx   //访问父类的成员变量(要有权限才可以)
super.xxx(xxx)  //调用父类的成员方法

 
创建对象时,会先初始化这个类所有的父类。

 

多态

引用型变量有2个类型:编译时类型、运行时类型。

编译时类型是该变量声明的类型,运行时类型是该变量实际的类型。

多态:编译时类型相同,但运行时类型可以不一致,可以是不同的子类。
 

public void f(User user) {

}

编译时类型是User,假设User有2个子类Student、Teacher,运行时可以传入Student、Teacher类的对象,类型不一致,即多态。
 

判断某个引用型变量是否是某个类 | 接口的实例

User user = new User();
if(user instanceof User){  //返回boolean值

}

变量 instanceof 类 | 接口名,变量要声明为该类|接口类型,否则通不过编译。

 

内部类

内部类:类中嵌套其它类的定义

匿名内部类:没有类名的内部类,只能使用一次,可以用来实现接口、抽象类,会在该处创建匿名内部类的一个实例,可以用new来写实现。

静态内部类:使用static修饰的内部类,可以通过 外部类.内部类 的方式进行访问。

 

抽象类
  • 抽象类本身使用abstract修饰,不能被实例化,是用来被继承、重写的。
  • 抽象方法使用abstract修饰,没有方法体。
  • 抽象类中可以不含抽象方法,但如果类中有抽象方法,该类必须声明为抽象类。

 

枚举

枚举常用于表示同一种类的常量集合,比如订单状态、用户角色等。
 

以前的方式

public class Role {
    public static final Integer COMMON = 1;
    public static final Integer VIP = 2;
    public static final Integer SVIP = 3;
	//...
}

 

枚举

  • 提供 全参构造器、getter方法,全参构造器最好定义为 priavte 对外隐藏;
  • 枚举实例要放在成员变量之前,不然会报错。
@AllArgsConstructor //手写改为 private 更好
@Getter
public enum RoleEnum {

    //实质是调用构造函数创建当前类的实例,作为类的静态成员
    COMMON(1),
    VIP(2),
    SVIP(3),
    ;

    private Integer code;
    
}

 
可以不要成员变量

public enum RoleEnum {
    COMMON, VIP, SVIP;
}

 

枚举与switch

/**
 * 根据code获取对应的枚举实例
 *
 * @param code
 * @return
 */
public static RoleEnum getEnumByCode(Integer code) {
    if (Objects.isNull(code)) {
        return null;
    }
    for (RoleEnum roleEnum : RoleEnum.values()) {
        if (roleEnum.getCode().equals(code)) {
            return roleEnum;
        }
    }
    return null;
}

不建议返回null,可能导致NPE,推荐设置一个 OTHER 之类的枚举实例,没有匹配的枚举实例时返回该实例,避免后续的判空、NPE。
 

switch (RoleEnum.getEnumByCode(roleCode)) {
    case COMMON:
        //...
        break;
    case VIP:
        //...
        break;
    case SVIP:
        //...
        break;
    default:
}

switch() 的参数不能为null,否则会报NPE

 

说明

  • 枚举会自动继承抽象类Enum,java中的类只支持单继承,所以枚举不能再继承其它类。
  • 编译时会把枚举替换成一个对应的类,且使用final修饰,不能被继承。枚举实例会以类的实例的形式做为类的静态成员,使用static final修饰。
  • 枚举实例之间的判断,用 ==、equals() 均可
RoleEnum roleEnum1 = RoleEnum.COMMON;
RoleEnum roleEnum2 = RoleEnum.COMMON;

System.out.println(RoleEnum.COMMON == roleEnum1);
System.out.println(RoleEnum.COMMON.equals(roleEnum1));

System.out.println(roleEnum1 == roleEnum2);
System.out.println(roleEnum1.equals(roleEnum2));

均为 true

 

接口

  • 类是对实例的抽象,抽象类、接口是对类的抽象。
  • 接口定义程序设计规范,本身并不提供任何实现,体现了规范(设计)、实现分离的设计方式。
public interface Xxx {
    // 成员变量默认使用public static final修饰,即常量,在声明时就必须指定初始值
    String XXX = "";

    // 方法默认使用public abstract修饰
    String xxx();


    // 可以定义默认方法,但必须要有方法体
    default void test2() {

    }

    //接口中可以定义静态方法方法,但必须要有方法体,默认使用(也只能使用)public修饰
    static void test3() {

    }

    // 内部也可以定义类、接口、接口,均默认使用(也只能使用)public static修饰

    // 不能有构造函数、初始化块、私有方法

}

 

// 类只支持单继承,接口支持多继承
classA extendsB {

}

interface 接口A extends 接口B, 接口C {

}


// 一个类可以实现多个接口
classA implements 接口A, 接口B {

}

// 类在继承的同时可以实现接口
classA extendsB implements 接口A, 接口B {
    
}

 

接口、抽象类的区别

  • 接口中只能定义常量、方法,不能表达对象状态的变化(常量不可变);抽象类中可以定义对象的成员字段,可以表达对象状态的变化。
  • 接口中成员的访问权限默认为且只能为 public,方法可以abstract、default、static之一修饰,默认使用 abstract 修饰,抽象类则无此限制。
  • 类可以同时实现多个接口,但不能同时继承多个类。

一般把子类共有的成员变量、实现相同的公共方法抽象出来放在抽象类中,实现不同的公共方法也可以作为抽象方法放在抽象类中;子类中公共的常量,以及实现不同的公共方法,可以放在接口中。

 

泛型

泛型可以限制对象|元素类型,在编译时对数据类型进行校验。

//在普通的成员变量中使用泛型
public class Test<T> {

    private T t;

    public Test(T t) {
        this.t = t;
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
    
}
//静态成员字段不能使用泛型,但静态方法的返回值类型、参数类型可以使用泛型
class Test {

	//需要在返回值类型前用< >进行声明
    public static <T> T test(T t) {
        return t;
    }
    
}
//类名后面标注的泛型可以用extend指定上边界,但不能用super指定下边界
public class Test<T extends Number> {

}


//可以使用多个泛型
public class Test<T extends Number, E> {

}


public class Test<T> {

    //使用泛型声明变量、返回值类型时,可以用? extend指定上边界,必须要是Number的子类
    public Test<? extends Number> handler1(Test<? extends Number> test) {
        return test;
    }

    //可以用? super声明下边界,必须要是Integer的父类
    public void handler2(Test<? super Integer> test) {

    }

	//上边界、下边界都包含边界本身
	
}

 

注意点

  • 泛型不能使用int、float等基本类型
  • 编译时会擦除泛型,生成的字节码中没有泛型。
  • 对于同一个类,不管使用哪种泛型,都是同一个类,getClass()获取到的都是同一个Class对象,实例都属于同一个类。
  • 静态成员变量不能使用泛型,是因为类加载时会初始化静态成员,如果使用泛型则不能确定数据类型,无法赋初值。

 

lambda表达式

函数式接口:接口中的抽象方法有且只有1个。可以有其它成员,但抽象方法必须要有且只能有1个。

lambda表达式是函数式接口的匿名内部类实现的语法糖,可以代替函数式接口的匿名内部类,只能代替函数式接口的匿名内部类,不能代替其它的匿名内部类。

@FunctionalInterface   //声明为函数式接口
interface FI {
    void out(String str1,String str2);
}


class T{
    
    // lambda表达式
    private FI fi = (str,str2) -> {  //参数表直接写参数名,不写类型
        System.out.println(str + " " + str2);
    };
    
}

lambda表达式用于实现函数式接口、并在该处创建一个实例,和匿名内部类相似,但匿名内部类可以实现任何抽象类、接口,lambda表达式只能实现函数式接口。
 

lambda表达式语法

(形参表) -> {  //形参表不写参数类型,直接写参数名
  //......
}

lambda表达式其实就是函数式接口中抽象方法的实现。

只有一个形参时,可以缺省();函数体只有一条语句,且该语句是return语句时,可以缺省{ }、关键字return。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值