第6章 - 面向对象(下)

面向对象(下)

1. 包装类

Java 为 8 种基本数据类型分别定义了相应的引用类型,使之可以当成 Object 类型变量使用,并称之为基本数据类型的包装类。

基本数据类型和包装类的对应关系:

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
charCharacter
floatFloat
doubleDouble
booleanBoolean
  • 自动装箱:可以把一个基本类型变量直接赋值给对应的包装类变量;
  • 自动拆箱:允许直接把包装类对象直接赋给一个对应的基本类型变量。
// 自动装箱
Integer inObj = 5;
// 自动拆箱
int it = inObj;

包装类可以实现基本类型变量和字符串之间的转换:

  • parseXxx(String s); (除 Character 之外所有包装类都提供了该方法)
  • valueOf(String s);
// 将字符串转换为基本类型
String intStr = "123";
int int1 = Integer.parseInt(intStr);
int int2 = Integer.valueOf(intStr);String doubleStr = "4.56";
double double1 = Double.parseDouble(doubleStr);
double double2 = Double.valueOf(doubleStr);String boolStr1 = "true";
String boolStr2 = "true1";
String boolStr3 = "false";
Boolean boolean1 = Boolean.parseBoolean(boolStr1);
Boolean boolean2 = Boolean.parseBoolean(boolStr2);
Boolean boolean3 = Boolean.parseBoolean(boolStr3);
System.out.println(boolean1);  // true
System.out.println(boolean2);  // false
System.out.println(boolean3);  // false// 将基本类型转换为字符串
String str1 = String.valueOf(6.78f);
String str2 = String.valueOf('\u9999');
System.out.println(str1);  // 6.78
System.out.println(str2);  // 香// 更加简单的写法
String str3 = 5 + "";
System.out.println(str3 == "5");  // true

比较值大小:

Integer in1 = 8;
Integer in2 = 8;
Integer ina = 128;
Integer inb = 128;
Integer inc = 500;
System.out.println(in1 == in2);  // true,Integer缓存了-128~127范围内的整数,可以直接判断
System.out.println(ina == inb);  // false
System.out.println(Integer.compare(ina, inb));  // 0
System.out.println(Integer.compare(ina, inc));  // -1
System.out.println(Integer.compare(inc, inb));  // 1// 比较布尔值
System.out.println(Boolean.compare(true, false));  // 1
System.out.println(Boolean.compare(true, true));  // 0
System.out.println(Boolean.compare(false, true));  // -1

2. 处理对象

2.1 toString() 方法

toString() 方法是 Object 类里的一个实例方法,所有的 Java 类都是 Object 类的子类,因此所有的 Java 对象都具有 toString() 方法。

toString() 方法是一个“自我描述”方法,该方法通常用于实现这样一个功能:当程序员直接打印该对象时,系统将会输出该对象的“自我描述”信息,用以告诉外界该对象具有的状态信息。

class Person {private String name;public Person(String name) {this.name = name;}
}public class PrintObject {public static void main(String[] args) {Person p = new Person("孙悟空");System.out.println(p);  // Person@368239c8System.out.println(p.toString());  // Person@368239c8}
}

重写 toString() 方法:

class Apple {private String color;private double weight;public Apple(String color, double weight) {this.color = color;this.weight = weight;}// 重写 toString() 方法public String toString() {return "Apple[color=" + color + ", weight=" + weight + "]";}
}public class PrintObject {public static void main(String[] args) {Apple a = new Apple("红色", 5.68);System.out.println(a);  // Apple[color=红色, weight=5.68]}
}

2.2 == 和 equals 方法

  • ==:用于判断两个基本类型变量是否相等;两个字符串直接量是否相同;两个引用类型变量是否指向同一个对象;
  • equals:判断两个引用变量是否相等。
String str1 = "学习Java";
String str2 = "学习";
String str3 = "Java";
String str4 = "学习" + "Java";
String str5 = "学" + "习" + "Java";
String str6 = str2 + str3;
String str7 = new String("学习Java");
String str8 = new String("学习Java");System.out.println(str1 == str4);  // true
System.out.println(str1 == str5);  // true
System.out.println(str1 == str6);  // false
System.out.println(str1 == str7);  // false
System.out.println(str7 == str8);  // false
System.out.println(str1.equals(str6));  // true
System.out.println(str1.equals(str7));  // true
System.out.println(str7.equals(str8));  // true

3. 类成员

static 关键字修饰的成员就是类成员。类成员属于整个类,不属于单个实例。

单例类

大部分时候都把类的构造器定义成 public 访问权限,允许任何类自由创建该类的对象。但有时候,不需要创建这么多对象。比如:系统可能只有一个窗口管理器、一个数据库引擎访问点。如果一个类始终只能创建一个实例,则这个类被称为单例类。

class Singleton {// 使用一个类变量来缓存曾经创建的实例private static Singleton instance;// 隐藏构造器private Singleton() {}// 提供一个静态方法,保证只产生一个 Singleton 对象public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}public class SingletonTest {public static void main(String[] args) {Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s1 == s2);  // true}
}

4. final 修饰符

final 关键字可用于修饰类、变量和方法,用于表示它修饰的类、方法和变量不可改变。

final 修饰变量时,表示该变量一旦获得了初始值就不可被改变。final 即可以修饰成员变量,也可以修饰局部变量、形参。

4.1 final 成员变量

final 修饰的成员变量必须由程序员显式地指定初始值

public class FinalVariableTest {final int a = 6;final String str;final int c;final static double d;// 初始化块,可对没有指定默认值的实例变量指定初始值{str = "Hello";}// 静态初始化块,可对没有指定默认值的类变量指定初始值static {d = 5.6;}// 构造器public FinalVariableTest() {c = 5;}public void changeFinal() {// 不能对 final 字段变量进行赋值}public static void main(String[] args) {var ft = new FinalVariableTest();System.out.println(ft.a);  // 6System.out.println(ft.c);  // 5System.out.println(ft.d);  // 5.6}
}

4.2 final 局部变量

系统不会对局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用 final 修饰局部变量时,既可以在定义时指定默认值,也可以不指定默认值。

public void test(final int a) {// a = 5;  // 不能对 final 修饰的形参赋值
}public static void main(String[] args) {final String str = "Hello";// str = "Java";  // str 已指定默认值,无法再赋值final int n;n = 6;// n = 7;  // final 修饰的变量,只能赋值一次
}

4.3 final 修饰基本类型变量和引用类型变量的区别

final 修饰的引用类型变量不能被重新赋值,但可以改变引用类型变量所引用对象的内容。

final int[] arr = {5, 2, 1, 4, 3};Arrays.sort(arr);
System.out.println(Arrays.toString(arr));  // [1, 2, 3, 4, 5]arr[2] = 6;
System.out.println(Arrays.toString(arr));  // [1, 2, 6, 4, 5]// arr = null;  // 不能对 final 修饰的变量重新赋值

4.4 可执行“宏替换”的 final 变量

对一个 final 变量来说,不管它是类变量、实例变量,还是局部变量,只要该变量满足三个条件,这个 final 变量就不再是一个变量,而是相当于一个直接量。

  • 使用 final 修饰符修饰
  • 在定义该 final 变量时指定了初始值
  • 该初始值可以在编译时就被确定下来
public static void main(String[] args) {final int a = 5;System.out.println(a);
}

对于这个程序来说,变量 a 其实根本不存在,当程序执行 System.out.println(a); 代码时,实际转换为执行 System.out.println(5);

final 修饰符的一个重要用途就是定义“宏变量”。当定义 final 变量时就为该变量指定了初始值,而且该初始值可以在编译时就确定下来,那么这个 final 变量本质上就是一个“宏变量”,编译器会把程序中所有用到该变量的地方直接替换成该变量的值。

如果被赋值的表达式只是基本的算术表达式或字符串连接运行,没有访问普通变量,调用方法,Java 编译器同样会将这种 final 变量当成“宏变量”处理。

4.5 final 方法

final 修饰的方法不可被重写,如果出于某些原因,不希望重写父类的某个方法,则可以使用 final 修饰该方法。

public class TestClass {public final void test() {}// 可以重载 final 方法public final void test(String msg) {}
}class Sub extends TestClass {// public void test() {}  // 不能重写 final 方法
}

4.6 final 类

final 修饰的类不可以有子类。例如:java.lang.Math 类就是一个 final 类。

public final class FinalClass {}

4.7 不可变类

不可变类的意思是创建该类的实例后,该实例的实例变量是不可改变的。Java 提供的 8 个包装类和 java.lang.String 类都是不可变类,当创建它们的实例后,其实例的实例变量不可改变。

class Name {private String firstName;private String lastName;public Name(String firstName, String lastName) {this.firstName = firstName;this.lastName = lastName;}public void setFirstName(String firstName) {this.firstName = firstName;}public String getFirstName() {return firstName;}public void setLastName(String lastName) {this.lastName = lastName;}public String getLastName() {return lastName;}
}public class Person {private final Name name;public Person(Name name) {// 设置 name 实例变量为临时创建的 Name 对象this.name = new Name(name.getFirstName(), name.getLastName());}public Name getName() {// 返回一个匿名对象return new Name(name.getFirstName(), name.getLastName());}public static void main(String[] args) {Name n = new Name("悟空", "孙");PersonTest p = new Person(n);System.out.println(p.getName().getFirstName());  // 悟空n.setFirstName("八戒");System.out.println(p.getName().getFirstName());  // 悟空System.out.println(n.getFirstName());  // 八戒PersonTest p2 = new PersonTest(n);System.out.println(p2.getName().getFirstName());  // 八戒}
}

上面程序可以看出,虽然改变对象 n 的成员变量值,但是保存在对象 p 里的成员变量 name 值并未改变。因此,如果引用类型的成员变量的类是可变的,就必须采取必要措施来保护该成员变量所引用的对象不会被修改,这样才能创建真正的不可变类。

5. 抽象类

有些时候,某个父类只知道其子类应该包含怎样的方法,但无法准确地知道这些子类如何实现这些方法。这时就可以使用抽象方法,抽象方法只有方法签名,没有方法实现的方法。

抽象方法和抽象类必须使用 abstract 修饰符来定义,有抽象方法的类只能被定义成抽象类,抽象类里可以没有抽象方法。

  • 抽象类和抽象方法必须使用 abstract 修饰符来修饰,抽象方法不能有方法体;
  • 抽象类不能被实例化,无法使用 new 关键字来调用抽象类的构造器创建抽象类的实例;
  • 抽象类可以包含成员变量、方法、构造器、初始化块、内部类 5 种成分;
  • 含有抽象方法的类,只能被定义成抽象类。
// 抽象类 - 形状
public abstract class Shape {private String color;// 定义一个计算周长的抽象方法public abstract double calPerimeter();// 定义一个返回形状的抽象方法public abstract String getType();// 构造器public Shape() {}public Shape(String color) {this.color = color;}public void setColor(String color) {this.color = color;}public String getColor() {return this.color;}
}// 类 - 三角形
public class Triangle extends Shape {// 定义三角形的三边private double a;private double b;private double c;public Triangle(String color, double a, double b, double c) {super(color);this.setSides(a, b, c);}public void setSides(double a, double b, double c) {if (a >= b + c || b >= a + c || c >= a + b) {System.out.println("三角形两边之和必须大于第三边");return;}this.a = a;this.b = b;this.c = c;}// 重写Shape类的的计算周长的抽象方法public double calPerimeter() {return a + b + c;}// 重写Shape类的的返回形状的抽象方法public String getType() {return getColor() + "三角形";}
}// 类 - 圆
public class Circle extends Shape {private double radius;public Circle(String color, double radius) {super(color);this.radius = radius;}public void setRadius(double radius) {this.radius = radius;}// 重写Shape类的的计算周长的抽象方法public double calPerimeter() {return 2 * Math.PI * radius;}// 重写Shape类的的返回形状的抽象方法public String getType() {return getColor() + "圆形";}public static void main(String[] args) {Shape s1 = new Triangle("黑色", 3, 4, 5);Shape s2 = new Circle("黄色", 3);System.out.println(s1.getType());  // 黑色三角形System.out.println(s1.calPerimeter());  // 12.0System.out.println(s2.getType());  // 黄色圆形System.out.println(s2.calPerimeter());  // 18.84955592153876}
}

6. 接口

6.1 接口的概念和定义

接口是从多个相似类中抽象出来的规范,接口不提供任何实现。接口体现的是规范和实现分离的设计哲学。

[修饰符] interface 接口名 {常量定义抽象方法定义内部类、接口、枚举定义私有方法、默认方法、类方法定义
}
  • 修饰符可以是 public 或者省略,如果省略了 public 修饰符,则默认采用包权限访问控制符;
  • 接口名应与类名采用相同的命名规范;
  • 一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类;
  • 接口中定义成员变量时,不管是否使用修饰符,成员变量总是总是使用 public static final 修饰符修饰;
  • 系统将自动为接口内普通方法增加 public abstract 修饰符,且不能有方法体。
public interface Output {// 接口里定义的成员变量只能是常量,系统会默认加上 public static final 修饰符int MAX_CACHE_LINE = 50;// 普通方法void out();void getData(String msg);// 默认方法default void print(String... msgs) {for (String msg : msgs) {System.out.println(msg);}}default void test() {System.out.println("默认的 test() 方法");}// 类方法static void staticTest() {System.out.println("类方法");}// 私有方法private void foo() {System.out.println("私有方法");}// 私有类方法private static void bar() {System.out.println("私有静态方法");}
}

6.2 接口的继承

[修饰符] interface 接口名 extends 父接口1, 父接口2... {...
}
interface InterfaceA {int PROP_A = 5;void testA();
}interface InterfaceB {int PROP_B = 6;void testB();
}interface InterfaceC extends InterfaceA, InterfaceB {int PROP_C = 7;void testC();
}public class InterfaceTest {public static void main(String[] args) {System.out.println(InterfaceC.PROP_A);  // 5System.out.println(InterfaceC.PROP_B);  // 6System.out.println(InterfaceC.PROP_C);  // 7 }
}

6.3 使用接口

[修饰符] class 类名 implements 接口1, 接口2... {...
}
// 定义一个 Product 接口
interface Product {int getProduceTime();
}// 让 Printer 类实现 Output 和 Product 接口
public class Printer implements Output, Product {private String[] printData = new String[MAX_CACHE_LINE];private int dataNum = 0;  // 记录当前打印数public void out() {// 只要还有作业,就继续打印while (dataNum > 0) {System.out.println("打印机打印:" + printData[0]);// 把作业队列整体前移一位,并将剩下的作业数减 1System.arraycopy(printData, 1, printData, 0, --dataNum);}}public void getData(String msg) {if (dataNum >= MAX_CACHE_LINE) {System.out.println("输出队列已满,添加失败");} else {// 把打印数据添加到队列里,已保存数据的数理加 1printData[dataNum++] = msg;}}public int getProduceTime () {return 45;}public static void main(String[] args) {Output o = new Printer();o.getData("道可道,非常道;,名可名,非常名。");o.getData("无,名天地之始;有,名万物之母。");o.out();// --> 打印机打印:道可道,非常道;,名可名,非常名。// --> 打印机打印:无,名天地之始;有,名万物之母。o.getData("故常无,欲以观其妙;常有,欲以观其徼。");o.getData("此两者同出而异名,同谓之玄,玄之又玄,众妙之门。");o.out(); // --> 打印机打印:故常无,欲以观其妙;常有,欲以观其徼。// --> 打印机打印:此两者同出而异名,同谓之玄,玄之又玄,众妙之门。o.print("孙悟空", "猪八戒", "白骨精");// --> 孙悟空// --> 猪八戒// --> 白骨精Product p = new Printer();System.out.println(p.getProduceTime());  // 45}}

6.4 接口和抽象类

接口和抽象类都剧透如下特征:

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

从某种程度上来看,接口类似于整个系统的“总纲”,它制定了系统各模块应该遵循的标准,因此一个系统中的接口不应该经常改变。一旦接口被改变,对整个系统甚至其他系统的影响将是辐射式的,导致系统中大部分类都需要改写。

抽象类不一样,抽象类作为系统中多个子类的共同父类,它所体现的是一种模板式设计。抽象类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能,但这个产品依然不能当成最终产品,必须有更进一步的完善,这种完善可能有几种不同方式。

6.5 面向接口编程

接口体现的是一种规范和实现分离的设计哲学,充分利用接口可以极好地降低程序各模块之间的耦合,从而提高系统的可扩展性和可维护性。

1. 简单工厂模式
// 定义 Computer 类
public class Computer {private Output out;public Computer (Output out) {this.out = out;}public void keyIn (String msg) {out.getData(msg);}public void print() {out.out();}
}public class OutputFactory {// Output 工厂,负责生成 Output 对象public Output getOutput() {return new Printer();}public static void main(String[] args) {OutputFactory of = new OutputFactory();Computer c = new Computer(of.getOutput());c.keyIn("天下皆知美之为美,斯恶已;皆知善之为善,斯不善已。");c.keyIn("故:有无相生,难易相成,长短相较,高下相倾,音声相合,前后相随。");c.print();// --> 打印机打印:天下皆知美之为美,斯恶已;皆知善之为善,斯不善已。// --> 打印机打印:故:有无相生,难易相成,长短相较,高下相倾,音声相合,前后相随。}
}

如果我们有了更好的 BetterPrinter 需要替换旧的 Printer:

// 定义一个新的更好的 BetterPrinter 类
public class BetterPrinter implements Output {private String[] printData = new String[MAX_CACHE_LINE * 2];private int dataNum = 0;public void out() {while (dataNum > 0) {System.out.println("高速打印机正在打印:" + printData[0]);System.arraycopy(printData, 1, printData, 0, --dataNum);}}public void getData(String msg) {if (dataNum >= MAX_CACHE_LINE * 2) {System.out.println("输出队列已满,添加失败");} else {printData[dataNum++] = msg;}}
}

只需更改一步就可以了:

public class OutputFactory {// Output 工厂,负责生成 Output 对象public Output getOutput() {// return new Printer();  // 只要改变这里就可以了return new BetterPrinter();}public static void main(String[] args) {OutputFactory of = new OutputFactory();Computer c = new Computer(of.getOutput());c.keyIn("天下皆知美之为美,斯恶已;皆知善之为善,斯不善已。");c.keyIn("故:有无相生,难易相成,长短相较,高下相倾,音声相合,前后相随。");c.print();// --> 高速打印机正在打印:天下皆知美之为美,斯恶已;皆知善之为善,斯不善已。// --> 高速打印机正在打印:故:有无相生,难易相成,长短相较,高下相倾,音声相合,前后相随。}
}
2. 命令模式

先使用一个 Command 接口来定义一个方法,用这个方法来封装“处理行为”:

public interface Command {void process(int element);
}

分别定义不同的方法:

// 直接打印元素
public class PrintCommand implements Command {public void process(int element) {System.out.println("迭代输出目标数组的元素:" + element);}
}// 打印元素的平方
public class SquareCommand implements Command {public void process(int element) {System.out.println("数组元素的平方是:" + (element * element));}
}

命令模式的使用:

public class ProcessArray {// 只有当调用此方法时,才真正传入一个 Command 对象,才确定对数组的处理行为public void process(int[] target, Command cmd) {for (int t : target) {cmd.process(t);}}
}public class CommandTest {public static void main(String[] args) {ProcessArray pa = new ProcessArray();int[] target = {3, -4, 6, 4};pa.process(target, new PrintCommand());      System.out.println("=============");     pa.process(target, new SquareCommand());}
}

结果为:

迭代输出目标数组的元素:3
迭代输出目标数组的元素:-4
迭代输出目标数组的元素:6
迭代输出目标数组的元素:4
=============
数组元素的平方是:9
数组元素的平方是:16
数组元素的平方是:36
数组元素的平方是:16

7. 内部类

把一个类放在另一个类的内部定义,这个类被称为内部类,包含内部类的类也被称为外部类。

  • 内部类比外部类多使用三个修饰符:private、protected、static;
  • 非静态内部类不能拥有静态成员。

7.1 非静态内部类

public class Outer {private int prop = 9;private String outerStr = "外部 private 变量";class Inner {private int prop = 8;public void info() {System.out.println(prop);  // 8System.out.println(this.prop);  // 8System.out.println(Outer.this.prop);  // 9System.out.println(outerStr);  // 外部 private 变量}}static class StaticInner {private static int prop = 6;public void info() {System.out.println("static 内部类");}}public void test() {// 内部调用内部类System.out.println(new Inner().prop);  // 8System.out.println(StaticInner.prop);  // 6}public static void main(String[] args) {new Outer().test();// 在外部使用内部类Outer.Inner in1 = new Outer().new Inner();in1.info();// new Inner();  // 不能访问非静态内部类new StaticInner().info();  // static 内部类System.out.println(StaticInner.prop);  // 6// 在外部类以外使用静态内部类Outer.StaticInner in = new Outer.StaticInner();in.info();  // static 内部类}
}

非静态内部类里不能有静态方法、静态成员变量、静态初始化块。

7.2 静态内部类

静态内部类是外部类的一个静态成员,因此外部类的所有方法、所有初始化块中可以使用静态内部类来定义变量、创建对象等。

7.3 使用内部类

  • 在外部类内部使用内部类:Inner in = new Inner();
  • 在外部类以外使用非静态内部类:Outer.Inner in = new Outer().new Inner();
  • 在外部类以外使用静态内部类:Outer.Inner in = new Outer.Inner();

7.4 局部内部类

如果把一个类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法里有效。由于局部内部类不能再外部类的方法以外的地方使用,因此局部内部类也不能使用访问控制符和 static 修饰符修饰。

7.5 匿名内部类

匿名内部类适合创建那些只需一次使用的类。创建匿名内部类时会创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用。

  • 匿名内部类不能是抽象类
  • 匿名内部类不能定义构造器
interface Product1 {double getPrice();String getName();
}public class AnonymousTest {public void test(Product1 p) {System.out.println("购买了一个" + p.getName() + "花掉了" + p.getPrice());}public static void main(String[] args) {// 匿名内部类访问的局部变量,必须使用 final 修饰。// Java 8 以后的版本可以省略 final 不写,但必须按照有 final 修饰的方式来用 —— 即一次赋值后,不能再重新赋值。String name = "老王";AnonymousTest ta = new AnonymousTest();// 使用匿名内部类实例ta.test(new Product1() {{System.out.println("土豪" + name);}public double getPrice() {return 12999.0;}public String getName() {return "华硕 RTX-2080Ti ";}});}
}
// 输出结果:
// --> 土豪老王
// --> 购买了一个华硕 RTX-2080Ti 花掉了12999.0

8. Lambda 表达式

Lambda 表示式是 Java 8 的重要更新。Lambda 表示式支持将代码块作为方法参数,允许使用更简洁的代码来创建只有一个抽象方法的接口(这种接口被称为函数式接口)的实例。

// 方式一:
(形参列表) -> {// 代码块;
}// 只有一个形参,简写方式:
形参 -> {// 代码块;
}// 没有形参,简写方式:
() -> {// 代码块;
}// 只有一条语句,简写方式:
(a, b) -> a = b
class ProcessArray {public void process(int[] target, Command cmd) {for (int t : target) {cmd.process(t);}}
}public class LambdaTest {public static void main(String[] args) {ProcessArray pa = new ProcessArray();int[] array = {3, -4, 6, 4};// 使用匿名内部类pa.process(array, new Command() {@Overridepublic void process(int e) {System.out.println("数组元素 " + e + " 的平方是:" + e * e);}});// 使用 Lambda 表达式,更加简洁明了System.out.println("====================");pa.process(array, (int e) -> {System.out.println("数组元素 " + e + " 的平方是:" + e * e);});}
}

Lambda 表达式的两个限制:

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

Lambda 表达式对方法和构造器的引用:

import java.util.Arrays;@FunctionalInterface
interface Command {void process(int element);
}@FunctionalInterface
interface Converter {Integer convert(String from);
}@FunctionalInterface
interface MyTest {String test(String str, int a, int b);
}@FunctionalInterface
interface MyTest2 {PersonTest setName(String name);
}class PersonTest {private String name;public PersonTest(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}public class LambdaTest {public static void main(String[] args) {// 1.引用类方法,函数式接口中被实现方法的全部参数传递给该类方法作为参数Converter c1 = Integer::valueOf;System.out.println(c1.convert("129"));  // 129// 2.引用特定对象的实例方法,函数式接口中被实现方法的全部参数传递给该方法作为参数Converter c2 = "君不见黄河之水天上来"::indexOf;System.out.println(c2.convert("黄"));  // 3// 3.引用某类对象的实例方法,第一个参数作为调用者,后面的参数全部传递给该方法作为参数MyTest mt = String::substring;System.out.println(mt.test("君不见黄河之水天上来", 3, 7));  // 黄河之水// 4.引用构造器,函数式接口中被实现方法的全部参数传递给该构造器作为参数MyTest2 mt2 = PersonTest::new;mt2.setName("老王");// 使用 Lambda 表达式调用 Arrays 的类方法String[] arr1 = {"埃斯库罗斯", "白居易", "仓央嘉措", "李白"};Arrays.parallelSort(arr1, (a, b) -> a.length() - b.length());System.out.println(Arrays.toString(arr1));  // [李白, 白居易, 仓央嘉措, 埃斯库罗斯]int[] arr2 = {3, -4, 25, 16, 30, 18};Arrays.parallelPrefix(arr2, (l, r) -> l + r);System.out.println(Arrays.toString(arr2));  // [3, -1, 24, 40, 70, 88]int[] arr3 = new int[5];Arrays.parallelSetAll(arr3, index -> index * 5);System.out.println(Arrays.toString(arr3));  // [0, 5, 10, 15, 20]}
}

9. 枚举类

在某些情况下,一个类的对象是有限且固定的,比如季节类,它只有4个对象。这种实例有限且固定的类,在 Java 里被称为枚举类。

public enum Season {SPRING, SUMMER, AUTUMN, WINTER;
}

枚举类与普通类的区别:

  • 枚举类可以实现一个或多个接口,使用 enum 定义的枚举类默认继承了 java.lang.Enum 类,而不是默认继承 Object 类,因此枚举类不能显式继承其他父类;
  • 使用 enum 定义、非抽象的枚举类默认会使用 final 修饰;
  • 枚举类的构造器只能使用 private 访问控制符,如果省略了构造器的访问控制符,则默认使用 private 修饰;如果强制指定访问控制符,则只能指定 private 修饰符。由于枚举类的所有构造器都是 private 的,而子类构造器总要调用父类构造器一次,因此枚举类不能派生子类;
  • 枚举类的所有实例必须在枚举类的第一行显式列出,否则这个枚举类永远都不能产生实例。列出这些实例时,系统会自动添加 public static final 修饰,无须程序员显式添加。
public class EnumTest {private void judge(Season s) {switch (s) {case SPRING:System.out.println("春天到了,播种的季节");break;case SUMMER:System.out.println("夏天到了,游泳的季节");break;case AUTUMN:System.out.println("秋天到了,收获的季节");break;case WINTER:System.out.println("冬天到了,下雪的季节");break;default:System.out.println("你已跳出四季轮回,祝你渡劫成功!");}}public static void main(String[] args) {// 使用枚举类默认的 values() 方法,返回该枚举类的所有实例for (Season s : Season.values()) {System.out.println(s);}// 使用枚举类实例new EnumTest().judge(Season.AUTUMN);  // 秋天到了,收获的季节}
}

10. 修饰符的适用范围

外部类/接口成员属性方法构造器初始化块成员内部类局部成员
public
protected
包访问控制符
private
abstract
final
static
strictfp
synchronized
native
transient
volatile
default
  • strictfp 关键字含义是 FP-strict,也就是精确浮点的意思。使用了此修饰符的类、接口、方法,可以在进行浮点运算时更加精确。
  • native 关键字修饰的方法类似于一个抽象方法,通常采用 C 语言来实现。如果某个方法需要利用平台相关特性,或者访问系统硬件等,则可以使用 native 修饰该方法,再把该方法交给 C 去实现。一旦 Java 程序中包含了 native 方法,这个程序将失去跨平台的功能。
  • 4 个访问控制符是互斥的,最多只能出现其中之一。
  • abstract 和 final 永远不能同时使用。
  • abstract 和 static 不能同时修饰方法,可以同时修饰内部类。
  • abstract 和 private 不能同时修饰方法,可以同时修饰内部类。
  • private 和 final 同时修饰方法虽然语法是合法的,但没有太大意义(private 修饰的方法不可能被子类重写,因此使用 final 修饰没有意义)

11. 练习

  • 通过抽象类定义车类的模板,然后通过抽象的车类来派生拖拉机、卡车、小轿车。
  • 定义一个接口,并使用匿名内部类方式创建接口的实例。
  • 定义一个函数式接口,并使用 Lambda 表达式创建函数式接口的实例。
  • 定义一个类,该类用于封装一桌梭哈游戏,这个类应该包含桌上剩下的牌的信息,并包含 5 个玩家的状态信息:他们各自的位置、游戏状态(正在游戏或已放弃)、手上已有的牌等信息。如果有可能,这个类还应该实现发牌方法,这个方法需要控制从谁开始发牌,不要发牌给放弃的人,并修改桌上剩下的牌。
  • 将学到的知识和遇到的问题,整理成笔记,记录下来


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部