Java学习笔记系列

Java基础知识扫盲(二)——接口、lambda 表达式、内部类

2018-07-24  本文已影响0人  SonyaBaby

接口

接口不是类,是对类的一组需求描述,类要遵从接口描述的统一格式进行定义。

接口中为什么新增default 方法

解决默认方法冲突

  1. 超类优先。如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。
  2. 接口冲突。 如果一个接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型 (不论是否是默认参数)相同的方法, 必须覆盖这个方法来解决冲突
class Student implements Person, Named{
    ...
}

Person和Named都提供了一个getName方法,Java编译器会报告一个错误,让程序员解决这个二义性。

当然,如果两个接口都没有为共享方法提供默认实现, 那么就与 Java SE 8之前的情况一样,这里不存在冲突。 实现类可以有两个选择: 实现这个方法, 或者干脆不实现。如果是后一种情况, 这个类本身就是抽象的。

  1. 一个类扩展了一个超类,同时实现了一个接口,并从超类和接口继承了相同的方法。
    只会考虑超类方法, 接口的所有默认方法都会被忽略。

tips:千万不要让一个默认方法重新定义 Object 类中的某个方法。

例如, 不能为 toString或 equals 定义默认方法, 尽管对于 List 之类的接口这可能很有吸引力, 由于“ 类优先”规则, 这样的方法绝对无法超越 Object.toString 或 Objects.equals

回调
可以指出某个特定事件发生时应该采取的动作。例如定时任务:

public class CallBackMeth{
  
  public static void main(String[] args) {
    ActionListener listener = new TimePrinter();
    Timer t = new Timer(1000, listener);
    t.start();

    JOptionPane.showMessageDialog(null,"Quit?");
    System.exit(0);
  }
}

class TimePrinter implements ActionListener
{
  public void actionPerformed(ActionEvent event)
  {
    System.out.println("At the tone, the time is " + new Date());
    Toolkit.getDefaultToolkit().beep(); // 响铃
  }
}

Comparator 比较器
自定义字符串长度比较器:

public class StringLengthComparator {
  public static void main(String[] args) {
    Comparator<String> comparator = new LengthComparator();
    System.out.println(comparator.compare("hello", "world"));
    System.out.println("hello".compareTo("world"));
    
    String[] names = { "songyanyan", "songjunfeng", "syy", "jeff" };
    Arrays.sort(names, comparator);
    System.out.println(names);
  }
  
}

class LengthComparator implements Comparator<String> {
  @Override
  public int compare(String o1, String o2) {
    return o1.length() - o2.length();
  }
}

Cloneable 克隆实现
// clone方法是Object的一个protected方法,不可以直接调用

 * @see java.lang.Cloneable
 */
protected native Object clone() throws CloneNotSupportedException;
对象拷贝和克隆.png

默认的克隆操作是“浅克隆”。如果原对象和浅克隆对象共享的子对象是不可变的,这种共享即是安全的。例如String,或者在对象的生命周期中没有更改器方法会改变它,也没有方法会生成它的引用。但是Date是可变的,例如下面的浅拷贝示例:

浅拷贝.png

而通常子对象都是可变的,必须重新定义clone方法来建立一个深拷贝,同时克隆出所有子对象。

@Override
public Employee clone() throws CloneNotSupportedException {
  // call Object.clone()
  Employee cloned = (Employee)  super.clone();
  // clone mutable fields 克隆可变域
  cloned.hireDay = (Date) hireDay.clone();

  return cloned;
}

所有数组类型都有一个 public 的 clone 方法, 而不是 protected: 可以用这个方法建立一个新数组, 包含原数组所有元素的副本,只适用于(一维)数组。
需要明确的是Java中不存在多维数组的说法,二维数组即为数组的数组

int[][] arrayTestCloned2 = new int[arrayTest.length][];
for (int i = 0; i< arrayTest.length; i++){
  arrayTestCloned2[i] = Arrays.copyOf(arrayTest[1],arrayTest[1].length);
}

lambda表达式

// 比较器
Comparator<String> comparator = (first, second) -> {
  return first.length() - second.length();
};

// 事件监听
ActionListener listener = e -> System.out.println("the time is " + new Date());
Timer timer = new Timer(1000, e -> System.out.println());

函数式接口
对于只有一个抽象方法的接口, 需要这种接口的对象时, 就可以提供一个 lambda 表达式。这种接口称为函数式接口 (functional interface )

@FunctionalInterface
public interface Predicate<T> {

/**
 * Evaluates this predicate on the given argument.
 *
 * @param t the input argument
 * @return {@code true} if the input argument matches the predicate,
 * otherwise {@code false}
 */
boolean test(T t);
...
}

方法引用

Timer timer1 = new Timer(1000, System.out::println);

List<String> list = new ArrayList();
list.sort(String::compareToIgnoreCase);

构造器引用

List<Integer> nums = Arrays.asList(1, 5, 4);
Stream<Apple> appleStream = nums.stream().map(Apple::new);
Apple[] apples = appleStream.toArray(Apple[]::new);

编译器会选择有一个 Integer 参数的构造器, 因为它从上下文推导出这是在对一个整型参数调用构造器。
toArray方法调用这个构造器来得到一个正确类型的数组。然后填充这个数组并返回。

变量作用域

public static void repeatMessage(String text, int delay) {
  ActionListener listener = event ->{
    System.out.println(text);
    Toolkit.getDefaultToolkit().beep();
  };
  new Timer(delay, listener).start();
 }

text是repeatMessage的参数而不是lambda表达式的。lambda表达式有3个部分:

这个text就是lambda表达式自由变量。表示lambda表达式的数据结构一定有存储自由变量的值。

在 lambda 表达式中, 只能引用值不会改变的变量。如果在 lambda 表达式中改变变量, 并发执行多个动作时就会不安全。变量在外部改变,也是不合法的.

public static void numDown(int num, int delay) {
  for(int i=0;i<= num;i++){
    ActionListener listener = event ->{
      System.out.println(num--); //Error: 不能引用可变的变量值num
      System.out.println(i); //Error: 不可以调用可变变量i
    };
    new Timer(delay, listener).start();
  }
}
IDE相关提示.png

即lambda表达式中扑火的变量必须/实际是最终变量(这个变量初始化之后就不会再为它赋新值)

不能声明一个局部变量同名的参数/局部变量。

String first = "Hello";
Comparator<String> stringComparator = (first, second) -> first.length() - second.length(); //Error:在域中已经声明过变量first

在lambda表达式中使用this。会调用Application相应方法。lambda表达式的作用域嵌套在init方法中。lambda表达式中this的含义并没有变化。

处理lambdab表达式

重点:延迟执行。

public static void repeat(int n, IntConsumer  consumer) {
  for (int i = 0; i < n; i++)
    consumer.accept(i);
}

调用:

repeat(10, i -> System.out.println(i));

最好使用这些特殊化规范来减少自动装箱。

基本类型的函数式接口.png 常用的函数式接口.png

大多数标准函数式接口都提供了非抽象方法来生成或合并函数。

如果设计你自己的接口,其中只有一个抽象方法,可以用 @FunctionalInterface 注解来标记这个接口。

Comparator

内部类

特殊语法规则

public class LocalInnerClassTest{
   public static void main(String[] args)
   {
      TalkingClock clock = new TalkingClock();
       clock.start(1000, true);

      // keep program running until user selects "Ok"
      JOptionPane.showMessageDialog(null, "Quit program?");
      System.exit(0);
   }
}

class TalkingClock
{
   /**
    * Starts the clock.
    * @param interval the interval between messages (in milliseconds)
    * @param beep true if the clock should beep
    */
   public void start(int interval, boolean beep){
      class TimePrinter implements ActionListener
      {
         public void actionPerformed(ActionEvent event)
         {
            System.out.println("At the tone, the time is " + new Date());
            if (beep) Toolkit.getDefaultToolkit().beep();
         }
      }
      ActionListener listener = new TimePrinter();
      Timer t = new Timer(interval, listener);
      t.start();
   }
}
public class TalkingClock$TimePrinter
{
  public TalkingClock$TimePrinter(TalkingClock);
  public void actionPerformed(java.awt.event.ActionEvent);
  final TalkingClock this$0;
}

编译器使内部类为了引用外围类, 生成了一个附加的实例域 this$0

class TalkingClock{
  private int interval;
  private boolean beep;
  public TalkingClock(int, boolean);
  static boolean access$0(TalkingClock);
  public void start();
}

TalkingClock(外围类)被编译器添加了一个静态方法access$0,它将(返回作为参数)传递给它的对象域beep(外围类的私有域)

if (beep)
if (TalkingClock.access$0(outer))

存在一定的安全问题。如果内部类访问了私有数据域, 就有可能通过附加在外围类所在包中的其他类访问它们。

局部类可以访问局部变量(必须是事实上的final类型)
TimePrinter 类在 beep 域释放之前将 beep 域用start 方法的局部变量进行备份

查看TalkingClock$TimePrinter类

class TalkingClock$1TimePrinter{
  TalkingClock$1TimePrinter(TalkingClock, boolean);
  public void actionPerformed(java.awt.event.ActionEvent);
  final boolean val$beep;
  final TalkingClock this$0;
}

局部类的方法只可以引用定义为 final 的局部变量,所以在编译之后局部变量在局部类中的拷贝为final类型。

匿名内部类

class TalkingClock{
   /**
    * Starts the clock.
    * @param interval the interval between messages (in milliseconds)
    * @param beep true if the clock should beep
    */
   public void start(int interval, boolean beep)
   {
     ActionListener listener = new ActionListener()
     { //匿名类
        public void actionPerformed(ActionEvent event){
           System.out.println("At the tone, the time is " + new Date());
           if (beep) Toolkit.getDefaultToolkit().beep();
        }
     };
      Timer t = new Timer(interval, listener);
      t.start();
   }
}
Person queen = new Person("Mary");
// a Person object
Person count = new Person("Dracula") { . . . }
// an object of an inner class extending Person
// 如果构造参数的闭小括号后面跟一个开大括号, 正在定义的就是匿名内部类

构造一个数组列表:

new ArrayList<String>() 
{// 匿名子类
  {// 调用外层类ArrayList的方法add 构造对象块
    add("Harry");
    add("Tony");
  }
};

调用 getClass 时调用的是 this.getClass(),而
静态方法没有 this,获取该静态方法的类:

new Object(){}.getCIass().getEnclosingClass() // gets class of static method

new Object(){}会建立 Object 的一个匿名子类的一个匿名对象,getEnclosingClass则得到其外围类, 也就是包含这个静态方法的类

静态内部类

public class StaticInnerClassTest{
   public static void main(String[] args){
      double[] d = new double[20];
      for (int i = 0; i < d.length; i++)
         d[i] = 100 * Math.random();
      ArrayAlg.Pair p = ArrayAlg.minmax(d);
      System.out.println("min = " + p.getFirst());
      System.out.println("max = " + p.getSecond());
   }
}

class ArrayAlg{
   public static class Pair{
      private double first;
      private double second;

      public Pair(double f, double s){
         first = f;
         second = s;
      }

      public double getFirst(){
         return first;
      }

      public double getSecond(){
         return second;
      }
   }

   /**
    * Computes both the minimum and the maximum of an array
    * @param values an array of floating-point numbers
    * @return a pair whose first element is the minimum and whose second element
    * is the maximum
    */
   public static Pair minmax(double[] values){
      double min = Double.POSITIVE_INFINITY;
      double max = Double.NEGATIVE_INFINITY;
      for (double v : values){
         if (min > v) min = v;
         if (max < v) max = v;
      }
      return new Pair(min, max);
   }
}

代理(proxy)

利用代理可以在运行时创建一个实现了一组给定接口的新类。这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用。

代理类可以在运行时创建全新的类。这样的代理类能够实现指定的接口。具备的方法:

不能在运行时定义这些方法的新代码。要提供一个调用处理器(invocationhandler)。调用处理器是实现了 InvocationHandler 接口的类对象

InvocationHandler handler = new InvocationHandler() {
  @Override 
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    ...
    return ...;
  }
}

创建代理对象
需要使用Proxy.newProxyInstance方法,三个参数:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)

如何定义一个处理器? 能够用结果代理对象做些什么?

使用代理和调用处理器跟踪方法调用。

public class ProxyTest {
  public static void main(String[] args) {
    Object[] objects = new Object[500];
    Class[] interfaces = new Class[] { Comparable.class, Iterable.class };

    for (int i = 0; i < objects.length; i++) {
      Integer value = i + 1;
      ClassTraceHandler handler = new ClassTraceHandler(value);
      objects[i] = Proxy.newProxyInstance(null, interfaces, handler);
    }

    Integer key = new Random().nextInt(objects.length) + 1;
    int result = Arrays.binarySearch(objects, key);
    if (result >= 0)
      System.out.println(objects[result]);

  }
}

class ClassTraceHandler implements InvocationHandler {
  private Object target;

  public ClassTraceHandler(Object t) {
    target = t;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.print(target);
    System.out.print("." + method.getName() + "(");
    if (args != null) {
      for (int i = 0; i < args.length; i++) {
        System.out.print(args[i]);
        if (i < args.length - 1)
          System.out.print(", ");
      }
    }
    System.out.println(")");

    return method.invoke(target, args);
  }
}

结果:

250.compareTo(161)
125.compareTo(161)
187.compareTo(161)
156.compareTo(161)
171.compareTo(161)
163.compareTo(161)
159.compareTo(161)
161.compareTo(161)
161.toString()
161

随机生成一个值,二分找到相应数值。
二分会调用compareTo方法,为代理的Comparable接口中的方法。
System.out.println,会调用对象的toString方法输出到控制台。

上一篇下一篇

猜你喜欢

热点阅读