5.面向对象下

以下是《疯狂Java讲义》中的一些知识,如有错误,烦请指正。


Java8增强的包装类

自动装箱就是把一个基本类型的变量直接赋给对应的包装类变量,自动拆箱则与之相反。
把字符串类型转换成基本类型:

  • 除了Character之外的所有包装类都提供了一个parseXxx(String s)静态方法。
  • 利用包装类提供的Xxx(String s)构造器

String提供了多个重载的valueOf()方法,用于将基本类型转换成字符串。或者将基本类型与""进行连接运算,String intstr = 5+"";

public class Primitive2String
{
    public static void main(String[] args)
    {
        String intStr = "123";
        // 把一个特定字符串转换成int变量
        int it1 = Integer.parseInt(intStr);
        int it2 = new Integer(intStr);
        System.out.println(it2);
        String floatStr = "4.56";
        // 把一个特定字符串转换成float变量
        float ft1 = Float.parseFloat(floatStr);
        float ft2 = new Float(floatStr);
        System.out.println(ft2);
        // 把一个float变量转换成String变量
        String ftStr = String.valueOf(2.345f);
        System.out.println(ftStr);
        // 把一个double变量转换成String变量
        String dbStr = String.valueOf(3.344);
        System.out.println(dbStr);
        // 把一个boolean变量转换成String变量
        String boolStr = String.valueOf(true);
        System.out.println(boolStr.toUpperCase());
    }
}

注意:将-128-127之间的同一个整数自动装箱成Integer实例时,永远都是引用cache数组的同一个元素,所以他们全部相等;不在这个范围的整数自动装箱城Integer,只有两个包装类引用指向同一个对象时才相等。

Java 7为所有包装类增加一个新方法:compare(x,y)。该方法用于比较两个包装类实例,当x>y,返回大于0的数;当x==y,返回0;否则返回小于0的数。还有其他的一些方法不做介绍了。

处理对象

打印对象和toString方法:toString方法是系统将会输出该对象的“自我描述”信息,用以告诉外界对象具有的状态信息。
Object 类提供的toString方法总是返回该对象实现类的类名+@+hashCode值。这个并不能真正实现自我描述的功能,因此用户必须在自定义类中重写Object类的toString方法。通常可返回类名[filed1=值1,field2=值2,...]

==和equals比较运算符
==要求两个引用变量指向同一个对象才会返回true,不可用于比较类型上没有父子关系的两个对象;对于基本类型变量,只要变量值相等返回true。equals方法则允许用户提供自定义的相等规则。Object类提供的equals方法判断两个对象相等的标准与==完全相同。因此开发者通常需要重写equals方法。

字符串直接量与字符串对象
Java程序直接使用"hello"的字符串直接量时,JVM将会使用常量池管理这些字符串;当使用new String("hello"),JVM会先使用常量池管理"hello"的字符串直接量,在调用String类的构造器创建新的String对象,新创建得到String对象被保存在堆内存中。

public class StringCompareTest
{
    public static void main(String[] args)
    {
        // s1直接引用常量池中的"疯狂Java"
        String s1 = "疯狂Java";
        String s2 = "疯狂";
        String s3 = "Java";
        // s4后面的字符串值可以在编译时就确定下来
        // s4直接引用常量池中的"疯狂Java"
        String s4 = "疯狂" + "Java";
        // s5后面的字符串值可以在编译时就确定下来
        // s5直接引用常量池中的"疯狂Java"
        String s5 = "疯" + "狂" + "Java";
        // s6后面的字符串值不能在编译时就确定下来,
        // 不能引用常量池中的字符串
        String s6 = s2 + s3;
        // 使用new调用构造器将会创建一个新的String对象,
        // s7引用堆内存中新创建的String对象
        String s7 = new String("疯狂Java");
        System.out.println(s1 == s4); // 输出true
        System.out.println(s1 == s5); // 输出true
        System.out.println(s1 == s6); // 输出false
        System.out.println(s1 == s7); // 输出false
    }
}

equals方法重写

class Person
{
    private String name;
    private String idStr;
    public Person(){}
    public Person(String name , String idStr)
    {
        this.name = name;
        this.idStr = idStr;
    }
    // 此处省略name和idStr的setter和getter方法。
    // name的setter和getter方法
    public void setName(String name)
    {
        this.name = name;
    }
    public String getName()
    {
        return this.name;
    }

    // idStr的setter和getter方法
    public void setIdStr(String idStr)
    {
        this.idStr = idStr;
    }
    public String getIdStr()
    {
        return this.idStr;
    }
    // 重写equals()方法,提供自定义的相等标准
    public boolean equals(Object obj)
    {
        // 如果两个对象为同一个对象
        if (this == obj)
            return true;
        // 只有当obj是Person对象
        if (obj != null && obj.getClass() == Person.class)
        {
            Person personObj = (Person)obj;
            // 并且当前对象的idStr与obj对象的idStr相等才可判断两个对象相等
            if (this.getIdStr().equals(personObj.getIdStr()))
            {
                return true;
            }
        }
        return false;
    }
}
public class OverrideEqualsRight
{
    public static void main(String[] args)
    {
        Person p1 = new Person("孙悟空" , "12343433433");
        Person p2 = new Person("孙行者" , "12343433433");
        Person p3 = new Person("孙悟饭" , "99933433");
        // p1和p2的idStr相等,所以输出true
        System.out.println("p1和p2是否相等?"
            + p1.equals(p2));
        // p2和p3的idStr不相等,所以输出false
        System.out.println("p2和p3是否相等?"
            + p2.equals(p3));
    }
}

注意instanceof当前面对象是后面类的实例或者子类的实例时都将返回true,在重写equals方法里不适用。

类成员

即使通过null对象来访问类成员,程序也不会引发NullPointerException。
静态初始化块也是类成员的一种。

单例类(Singleton)
如果一个类始终只能创建一个对象,称为单例类。
条件:

  1. 我们把该类的构造器使用Private修饰,从而把该 类的所有构造器隐藏起来。
  2. 则需要提供一个public方法作为该类的访问点,用于创建该类的对象,且必须使用static修饰
  3. 该类还必须缓存已经创建的对象,必须用static修饰
class Singleton
{
    // 使用一个类变量来缓存曾经创建的实例
    private static Singleton instance;
    // 将构造器使用private修饰,隐藏该构造器
    private Singleton(){}
    // 提供一个静态方法,用于返回Singleton实例
    // 该方法可以加入自定义的控制,保证只产生一个Singleton对象
    public static Singleton getInstance()
    {
        // 如果instance为null,表明还不曾创建Singleton对象
        // 如果instance不为null,则表明已经创建了Singleton对象,
        // 将不会重新创建新的实例
        if (instance == null)
        {
            // 创建一个Singleton对象,并将其缓存起来
            instance = new Singleton();
        }
        return instance;
    }
}
public class SingletonTest
{
    public static void main(String[] args)
    {
        // 创建Singleton对象不能通过构造器,
        // 只能通过getInstance方法来得到实例
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2); // 将输出true
    }
}

final修饰符

用于表示修饰的类、方法、变量不可变。final修饰的成员变量必须由程序员显式地指定初始值。系统不会对final成员进行隐式初始化。
类变量:必须在静态初始化块或声明该变量时指定初始值
实例变量:必须在非静态初始化块或声明该变量时或构造器中指定初始值。
使用final修饰局部变量时既可以在定义时指定默认值,也可以不指定默认值。

final修饰基本类型和引用变量的区别
当使用final修饰基本数据类型变时,不能对其重新赋值,不能被改变。但对引用类型的变量而言,它仅仅保存的是一个引用,final只能保证他的地址不变,但不能保证对象,所以引用类型完全可以改变他的对象。

class Person
{
    private int age;
    public Person(){}
    // 有参数的构造器
    public Person(int age)
    {
        this.age = age;
    }
    // 省略age的setter和getter方法
    // age的setter和getter方法
    public void setAge(int age)
    {
        this.age = age;
    }
    public int getAge()
    {
        return this.age;
    }
}
public class FinalReferenceTest
{
    public static void main(String[] args)
    {
        // final修饰数组变量,iArr是一个引用变量
        final int[] iArr = {5, 6, 12, 9};
        System.out.println(Arrays.toString(iArr));
        // 对数组元素进行排序,合法
        Arrays.sort(iArr);
        System.out.println(Arrays.toString(iArr));
        // 对数组元素赋值,合法
        iArr[2] = -8;
        System.out.println(Arrays.toString(iArr));
        // 下面语句对iArr重新赋值,非法
        // iArr = null;
        // final修饰Person变量,p是一个引用变量
        final Person p = new Person(45);
        // 改变Person对象的age实例变量,合法
        p.setAge(23);
        System.out.println(p.getAge());
        // 下面语句对p重新赋值,非法
        // p = null;
    }
}

可执行“宏替换”的final变量
对一个final变量来说,不管它是类变量、实例变量,还是局部变量,只要该变量满足3个条件,这个final变量就不再是一个变量,而是相当于一个直接量。

  • 使用final修饰符修饰;
  • 在定义该final变量时指定了初始值;
  • 该初始值可以在编译时就被确定下来。
public class StringJoinTest
{
    public static void main(String[] args)
    {
        String s1 = "疯狂Java";
        // s2变量引用的字符串可以编译时就确定出来,
        // 因此s2直接引用常量池中已有的"疯狂Java"字符串
        String s2 = "疯狂" + "Java";
        System.out.println(s1 == s2);
        // 定义2个字符串直接量
        String str1 = "疯狂";     //①
        String str2 = "Java";     //②
        // 将str1和str2进行连接运算
        String s3 = str1 + str2;
        System.out.println(s1 == s3);//false
    }
}

str1和str2只是普通变量,编译器不会执行宏替换;只要定义时添加final修饰,结果为true。注意对于final实例变量,只有定义该变量时指定初始值才会有宏变量的效果。

final方法
final 修饰的方法不可以被重写。
final 修饰的方法仅仅是不能重写,但它完全可以被重载。

public class PrivateFinalMethodTest
{
    private final void test(){}
}
class Sub extends PrivateFinalMethodTest
{
    // 下面方法定义将不会出现问题
    public void test(){}
}

final类
final 修饰的类不可以被继承

抽象类

抽象方法和类都必须使用abstract来修饰,含有抽象方法的类一定为抽象类,抽象类里也可以没有抽象方法。
抽象类不能被实例化,可以通过其子类给他赋值;普通类里有的抽象里也有。
定义抽象方法只需在普通方法上增加abstract修饰符,并把普通方法的方法体(也就是方法后花括号括起来的部分)全部去掉,并在方法后增加分号即可。
注意:static和abstract不能同时修饰某个方法,即没有所谓的类抽象方法,即使有调用一个没有方法体的方法也会引起错误。

抽象类的作用
抽象类代表了一种未完成的类设计,它体现的是一种模板。

接口

接口定义的是多个类共同的行为规范,这些行为是与外部交流的通道,这就意味着接口里通常是定义一组公用的方法。
接口里不能包含普通方法,所有方法都是抽象方法,Java8允许定义默认方法。

接口定义

[修饰符] interface 接口名 extends 父接口1,父接口2 
{
    零个到多个常量定义...
    零个到多个抽象方法定义...
    零个到多个内部类、接口、枚举定义...
    零个到多个默认方法或类方法定义...
}

修饰符可以是public或者省略。
常量都是:public static final修饰
实例方法都是:public abstract 修饰
类方法用public static修饰
默认方法用public default修饰
内部的类:public static
接口里面没有构造器和初始化块。
类方法可以使用接口直接调用,默认方法要通过使用接口的实例来调用。

接口继承
接口的继承和类继承不一样,接口完全支持多继承,子接口扩展某个父接口将会获得父接口的所有抽像方法,类变量。

使用接口
一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽象方法);
否则,该类将保留从父接口那里继承到的抽象方法,该类也必须定义成抽象类

接口和抽象类的相似性

  • 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
  • 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。

接口与抽象类的区别

  • 接口里只能包含抽象方法,不同包含已经提供实现的方法;抽象类则完全可以包含普通方法。
  • 接口里不能定义静态方法;抽象类里可以定义静态方法。
  • 接口里只能定义静态常量属性,不能定义普通属性;抽象类里则既可以定义普通属性,也可以定义静态常量属性。
  • 接口不包含构造器;抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而让其子类调用这些构造器来完成属于抽象类的初始化操作。
  • 接口里不能包含初始化块,但抽象类则完全可以包含初始化块。
  • 一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java单继承的不足。

内部类

我们把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类,有的也叫嵌套类,包含内部类的类也被称为外部类有的也叫宿主类。内部类提供了更好的封装,内部类成员可以直接访问外部类的私有数据,因为内部类被当成其他外部类成员。匿名内部类适合用于创建那些仅需要一次使用的类。
区别:

  • 可以多使用修饰符private、protected、static
  • 非静态内部类不能拥有静态成员

非静态内部类
非静态内部类中可以访问外部类的private成员,是因为在非静态内部类中保存了外部类对象的引用。

public class DiscernVariable
{
    private String prop = "外部类的实例变量";
    private class InClass
    {
        private String prop = "内部类的实例变量";
        public void info()
        {
            String prop = "局部变量";
            // 通过 外部类类名.this.varName 访问外部类实例变量
            System.out.println("外部类的实例变量值:"
                + DiscernVariable.this.prop);
            // 通过 this.varName 访问内部类实例的变量
            System.out.println("内部类的实例变量值:" + this.prop);
            // 直接访问局部变量
            System.out.println("局部变量的值:" + prop);
        }
    }
    public void test()
    {
        InClass in = new InClass();
        in.info();
    }
    public static void main(String[] args)
    {
        new DiscernVariable().test();
    }
}

非静态内部类的成员只在内部类范围是可知的,并不能直接被外部类调用,如果外部类需要访问,则必须显示创建非静态内部类对象来调用其实例成员。非静态内部类不可以定义静态成员。

静态内部类
如果用static修饰一个内部类,称为静态内部类。
静态内部类可以包含静态成员,也可以包含非静态成员。所以静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。
静态内部类的对象寄存在外部类里,非静态内部类的对象寄存在外部类实例里
外部类依然不能直接访问静态内部类的成员,但可以使用静态内部类的类名访问静态内部类的类成员或者静态内部类对象作为调用者访问静态内部类的实例成员。

使用内部类

  1. 在外部类内部使用内部类-不要在外部类的静态成员中使用非静态内部类,因为静态成员不能访问非静态成员。
  2. 在外部类以外使用非静态内部类。
  • private 修饰的内部类只能在外部类内部使用。
  • 在外部类以外的地方使用内部类,内部类完整的类名应该OuterClass.InnerClass.
  • 在外部类以外的地方使用非静态内部类创建对象的语法如下:OuterInstance.new InnerConstructor(),创建非静态内部类实例以来于外部类实例。
class Out
{
    // 定义一个内部类,不使用访问控制符,
    // 即只有同一个包中其他类可访问该内部类
    class In
    {
        public In(String msg)
        {
            System.out.println(msg);
        }
    }
}
public class CreateInnerInstance
{
    public static void main(String[] args)
    {
        Out.In in = new Out().new In("测试信息");
        /*
        上面代码可改为如下三行代码:
        使用OutterClass.InnerClass的形式定义内部类变量
        Out.In in;
        创建外部类实例,非静态内部类实例将寄存在该实例中
        Out out = new Out();
        通过外部类实例和new来调用内部类构造器创建非静态内部类实例
        in = out.new In("测试信息");
        */
    }
}

创建静态内部类的子类时,必须存在一个外部类对象,然后才能调用非静态内部类的构造器。

public class SubClass extends Out.In
{
    //显示定义SubClass的构造器
    public SubClass(Out out)
    {
        //通过传入的Out对象显式调用In的构造器
        out.super("hello");
    }
}

注意:非静态内部类的子类不一定是内部类,可以是外部类。但其实例必须保留一个引用,指向父类所在外部类的对象。

  1. 在外部类以外使用静态内部类
    在外部类以外的地方使用静态内部类创建对象的语法如下:new OuterClass.InnerConstructer();
    使用静态内部类相对容易,只要把外部类当成静态内部类的包空间即可。所以,一般优先考虑静态内部类。

注意:子类的内部类不可能重写父类的内部类,因为即使内部类类名相同,外部类空间不同,就不可能完全同名。

局部内部类
如果把一个内部类放在方法里定义,这就是局部内部类,仅仅在这个方法里有效。局部内部类不能在外部类以外的地方使用,那么局部内部类也不能使用访部控制符和static修饰。
局部内部类使用很有限。

匿名内部类
匿名内部类适合创建那种只需要一次使用的类,定义匿名内部类的语法格式如下:

new 父类构造器(实例列表) |实现接口)
{
      //匿名内部类的 类体部分
}

必须继承一个父类或者实现一个接口,但最多继承一个父类或者实现一个接口。
两个限制:匿名内部类不能是抽象类;匿名内部类不能定义构造器。

interface Product
{
    public double getPrice();
    public String getName();
}
public class AnonymousTest
{
    public void test(Product p)
    {
        System.out.println("购买了一个" + p.getName()
            + ",花掉了" + p.getPrice());
    }
    public static void main(String[] args)
    {
        AnonymousTest ta = new AnonymousTest();
        // 调用test()方法时,需要传入一个Product参数,
        // 此处传入其匿名实现类的实例
        ta.test(new Product()
        {
            public double getPrice()
            {
                return 567.8;
            }
            public String getName()
            {
                return "AGP显卡";
            }
        });
    }
}

如果局部变量被匿名内部类访问,该变量相当于自动使用final修饰。也就是effective final:对于内部类访问的局部变量可以用final修饰,也可以不用,但是一次赋值后不能再次赋值。

interface A
{
    void test();
}
public class ATest
{
    public static void main(String[] args)
    {
        int age = 8;     // ①
        // 下面代码将会导致编译错误
        // 由于age局部变量被匿名内部类访问了,因此age相当于被final修饰了
        age = 2;
        A a = new A()
        {
            public void test()
            {
                // 在Java 8以前下面语句将提示错误:age必须使用final修饰
                // 从Java 8开始,匿名内部类、局部内部类允许访问非final的局部变量
                System.out.println(age);
            }
        };
        a.test();
    }
}

Lambda表达式

                                                                                  Lambda表达式主要作用就是代替匿名内部类的繁琐语法。                                    它由三部分组成:
  • 形参列表。形参列表允许省略形参类型。如果形参列表中只有一个参数,甚至连形参列表的圆括号也可以省略。
  • 箭头(->)。
  • 代码块。如果代码块只有包含一条语句,Lambda表达式允许省略代码块的花括号,如果省略了代码块的花括号,这条语句不要用花括号表示语句结束。Lambda代码块只有一条return语句,甚至可以省略return关键字。Lambda表达式需要返回值,而它的代码块中仅有一条省略了return的语句,Lambda表达式会自动返回这条语句的值
interface Eatable
{
    void taste();
}
interface Flyable
{
    void fly(String weather);
}
interface Addable
{
    int add(int a , int b);
}
public class LambdaQs
{
    // 调用该方法需要Eatable对象
    public void eat(Eatable e)
    {
        System.out.println(e);
        e.taste();
    }
    // 调用该方法需要Flyable对象
    public void drive(Flyable f)
    {
        System.out.println("我正在驾驶:" + f);
        f.fly("【碧空如洗的晴日】");
    }
    // 调用该方法需要Addable对象
    public void test(Addable add)
    {
        System.out.println("5与3的和为:" + add.add(5, 3));
    }
    public static void main(String[] args)
    {
        LambdaQs lq = new LambdaQs();
        // Lambda表达式的代码块只有一条语句,可以省略花括号。
        lq.eat(()-> System.out.println("苹果的味道不错!"));
        // Lambda表达式的形参列表只有一个形参,省略圆括号
        lq.drive(weather ->
        {
            System.out.println("今天天气是:" + weather);
            System.out.println("直升机飞行平稳");
        });
        // Lambda表达式的代码块只有一条语句,省略花括号
        // 代码块中只有一条语句,即使该表达式需要返回值,也可以省略return关键字。
        lq.test((a , b)->a + b);
    }
}

Lambda表达式与函数式接口
如果采用匿名内部类语法来创建函数式接口的实例,只要实现一个抽象方法即可,在这种情况下即可采用Lambda表达式来创建对象,该表达式创建出来的对象的目标类型就是这个函数式接口。

Lambda表达式有如下两个限制:

  • Lambda表达式的目标类型必须是明确的函数式接口。
  • Lambda表达式只能为函数式接口创建对象。Lambda表达式只能实现一个方法,因此它只能为只有一个抽象方法的接口(函数式接口)创建对象。

为了保证Lambda表达式的目标类型是一个明确的函数式接口,可以有如下三种常见方式:

  • 将Lambda表达式赋值给函数式接口类型的变量。
  • 将Lambda表达式作为函数式接口类型的参数传给某个方法。
  • 使用函数式接口对Lambda表达式进行强制类型转换。

方法引用与构造器引用

如果Lambda表达式的代码块只有一条代码,可以省略表达式中代码块的花括号,还可以在代码块中使用方法引用和构造器引用。

种类 示例 说明 Lambda表达式
引用类方法 类名::类方法 函数式接口中被实现方法的全部参数传给该类方法作为参数。 (a,b,...) -> 类名.类方法(a,b, ...)
引用特定对象的实例方法 特定对象::实例方法 函数式接口中被实现方法的全部参数传给该实例方法作为参数。 (a,b, ...) -> 特定对象.实例方法(a,b, ...)
引用某类对象的实例方法 类名::实例方法 函数式接口中被实现方法的第一个参数作为调用者,后面的参数全部传给该方法作为参数。 (a,b, ...) ->a.实例方法(b, ...)
引用构造器 类名::new 函数式接口中被实现方法的全部参数传给该构造器作为参数 (a,b, ...) ->new 类的构造器(a,b, ...)
import javax.swing.*;
@FunctionalInterface
interface Converter{
    Integer convert(String from);
}
@FunctionalInterface
interface MyTest
{
    String test(String a , int b , int c);
}
@FunctionalInterface
interface YourTest
{
    JFrame win(String title);
}
public class MethodRefer
{
    public static void main(String[] args)
    {
        // 下面代码使用Lambda表达式创建Converter对象
//      Converter converter1 = from -> Integer.valueOf(from);
//      // 方法引用代替Lambda表达式:引用类方法。
//      // 函数式接口中被实现方法的全部参数传给该类方法作为参数。
//      Converter converter1 = Integer::valueOf;
//      Integer val = converter1.convert("99");
//      System.out.println(val); // 输出整数99



        // 下面代码使用Lambda表达式创建Converter对象
//      Converter converter2 = from -> "fkit.org".indexOf(from);
//      // 方法引用代替Lambda表达式:引用特定对象的实例方法。
//      // 函数式接口中被实现方法的全部参数传给该方法作为参数。
//      Converter converter2 = "fkit.org"::indexOf;
//      Integer value = converter2.convert("it");
//      System.out.println(value); // 输出2



        // 下面代码使用Lambda表达式创建MyTest对象
//      MyTest mt = (a , b , c) -> a.substring(b , c);
        // 方法引用代替Lambda表达式:引用某类对象的实例方法。
        // 函数式接口中被实现方法的第一个参数作为调用者,
        // 后面的参数全部传给该方法作为参数。
//      MyTest mt = String::substring;
//      String str = mt.test("Java I Love you" , 2 , 9);
//      System.out.println(str); // 输出:va I Lo



        // 下面代码使用Lambda表达式创建YourTest对象
//      YourTest yt = (String a) -> new JFrame(a);
        // 构造器引用代替Lambda表达式。
        // 函数式接口中被实现方法的全部参数传给该构造器作为参数。
        YourTest yt = JFrame::new;
        JFrame jf = yt.win("我的窗口");
        System.out.println(jf);
    }
}

Lambda表达式与匿名内部类
相同点:

  • Lambda表达式与匿名内部类一样,都可以直接访问“effectively final”的局部变量,以及外部类的成员变量(包括实例变量和类变量)。
  • Lambda表达式创建的对象与匿名内部类生成的对象一样,都可以直接调用从接口继承得到的默认方法。

区别:

  • 匿名内部类可以为任意接口创建实例——不管接口包含多少个抽象方法,只要匿名内部类实现所有的抽象方法即可。但Lambda表达式只能为函数式接口创建实例。
  • 匿名内部类可以为抽象类、甚至普通类创建实例,但Lambda表达式只能为函数式接口创建实例。
  • 匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认方法;但Lambda表达式的代码块不允许调用接口中定义的默认方法。

使用Lambda表达式调用Arrays的类方法
Arrays类的有些方法需要Comparator、XxxOperator、XxxFuncton等接口实例,这些接口都是函数式接口,因此可以使用Lambda表达式调用Arrays方法。

import java.util.Arrays;
public class LambdaArrays
{
    public static void main(String[] args)
    {
        String[] arr1 = new String[]{"java" , "fkava" , "fkit", "ios" , "android"};
        Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length());
        System.out.println(Arrays.toString(arr1));
        int[] arr2 = new int[]{3, -4 , 25, 16, 30, 18};
        // left代表数组中前一个所索引处的元素,计算第一个元素时,left为1
        // right代表数组中当前索引处的元素
        Arrays.parallelPrefix(arr2, (left, right)-> left * right);
        System.out.println(Arrays.toString(arr2));
        long[] arr3 = new long[5];
        // operand代表正在计算的元素索引
        Arrays.parallelSetAll(arr3 , operand -> operand * 5);
        System.out.println(Arrays.toString(arr3));
    }
}

枚举类

实例有限而且固定的类被称为枚举类
枚举类是一种特殊的类,它一样可以有自己的方法和属性,可以实现一个或者多个接口,也可以定义自己的构造器。一个Java源文件中最多只能定义一个public访问权限的枚举类,且该Java源文件也必须和该枚举类的类名相同。

与普通类的区别

  • 枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承了java.lang.Enum类,而不是继承Object类。其中java.lang.Enum类实现了java.lang.Serializable和java.lang. Comparable两个接口。
  • 枚举类的构造器只能使用private访问控制符,如果省略了其构造器的访问控制符,则默认使用private修饰;如果强制指定访问控制符,则只能指定private修饰符。
  • 枚举类的所有实例必须在枚举类中显式列出,否则这个枚举类将永远都不能产生实例。列出这些实例时系统会自动添加public static final修饰,无需程序员显式添加。
  • 所有枚举类都提供了一个values方法,该方法可以很方便地遍历所有的枚举值。
public enum SeasonEnum
{
    // 在第一行列出4个枚举实例
    SPRING,SUMMER,FALL,WINTER;
}
public class EnumTest
{
    public void judge(SeasonEnum s)
    {
        // switch语句里的表达式可以是枚举值
        switch (s)
        {
            case SPRING:
                System.out.println("春暖花开,正好踏青");
                break;
            case SUMMER:
                System.out.println("夏日炎炎,适合游泳");
                break;
            case FALL:
                System.out.println("秋高气爽,进补及时");
                break;
            case WINTER:
                System.out.println("冬日雪飘,围炉赏雪");
                break;
        }
    }
    public static void main(String[] args)
    {
        // 枚举类默认有一个values方法,返回该枚举类的所有实例
        for (SeasonEnum s : SeasonEnum.values())
        {
            System.out.println(s);
        }
        // 使用枚举实例时,可通过EnumClass.variable形式来访问
        new EnumTest().judge(SeasonEnum.SPRING);
    }
}

枚举类的属性、方法和构造器
枚举类也是一种类,只是它是一种比较特殊的类,因此它一样可以使用属性和方法。
枚举类通常应该设计成不可变类,也就说它的属性值不应该允许改变,这样会更安全,而且代码更加简洁。为此,我们应该将枚举类的属性都使用private final修饰。
一旦为枚举类显式定义了带参数的构造器,则列出枚举值时也必须对应地传入参数。

public enum Gender
{
    // 此处的枚举值必须调用对应构造器来创建
    MALE("男"),FEMALE("女");
    private final String name;
    // 枚举类的构造器只能使用private修饰
    private Gender(String name)
    {
        this.name = name;
    }
    public String getName()
    {
        return this.name;
    }
}

实现接口的枚举类
枚举类也可以实现一个或多个接口。与普通类实现一个或多个接口完全一样,枚举类实现一个或多个接口时,也需要实现该接口所包含的方法。
如果需要每个枚举值在调用同一个方法时呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法,每个枚举值提供不同的实现方式,从而让不同枚举值调用同一个方法时具有不同的行为方式。

public enum Gender implements GenderDesc{
    //调用构造器创建枚举值
    MALE("男")
    //下面是类体
    {
        public void info(){
            System.out.println("枚举值代表男性");
        }
    },  
    FEMALE("女"){
        public void info(){
            System.out.println("枚举值代表男性");
        }
    };
    
    private final String name;
    private Gender(String name)
    {
        this.name = name;
    }
    public String getName()
    {
        return this.name;
    }
    
}

创建MALE、FEMALE枚举值时不是直接创建Gender实例,而是创建Gender匿名子类的实例,。注意并不是所有的枚举类都是用了final修饰,非抽象的枚举类才默认使用final修饰。

包含抽象方法的枚举类
可以在枚举类里定义一个抽象方法,然后把这个抽象方法交给各枚举值去实现即可。
枚举类里定义抽象方法时无需显式使用abstract关键字将枚举类定义成抽象类,但因为枚举类需要显式创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。

public enum Operation
{
    PLUS
    {
        public double eval(double x , double y)
        {
            return x + y;
        }
    },
    MINUS
    {
        public double eval(double x , double y)
        {
            return x - y;
        }
    },
    TIMES
    {
        public double eval(double x , double y)
        {
            return x * y;
        }
    },
    DIVIDE
    {
        public double eval(double x , double y)
        {
            return x / y;
        }
    };
    // 为枚举类定义一个抽象方法
    // 这个抽象方法由不同的枚举值提供不同的实现
    public abstract double eval(double x, double y);
    public static void main(String[] args)
    {
        System.out.println(Operation.PLUS.eval(3, 4));
        System.out.println(Operation.MINUS.eval(5, 4));
        System.out.println(Operation.TIMES.eval(5, 4));
        System.out.println(Operation.DIVIDE.eval(5, 4));
    }
}

你可能感兴趣的:(5.面向对象下)