Stéphane's Java course : Lecture 5

class OuterClass{

    . . .

   class NestedClass{

       . . .

   }

}

class OuterClass{

    . . .

    public void doSomething(){

        class LocalClass{

            . . .

      }

    }

}

Lambda表达式可以有一个嵌套类和方法内部类

这样做是为了让代码更清晰,这样对象就会是"技术对象",你不会想在其他地方提到这个东西,也不会用它。

匿名类 : 

class NamedClass implements Interface{

     . . . 

}

Interface anObject = new NamedClass(. . .) ;

有时候我们并不是真正需要一个对象,而只是需要其中一个方法。

Interface anObject = new Interface() { . . . }

Java可以动态地创建一个接口,并在传递参数到参数列表的时候实例化一个对象。

继承可以有同样的效果 :

class NamedClass extends ParentClass{

    . . . 

NamedClass anObject = new NamedClass( . . . ) ;

ParentClass anObject = new ParentClass(){//attribute and method definition};

很多例子都体现在图形接口(graphical interface)

Button btn = new Button() ;

btn.setText("Say Hi") ; 

btn.setOnAction(new

                       EventHandler<ActionEvent>(){

                    @Override

                    public void handle(ActionEvent e){

                          System.out.println("Hi!") ;

                    }

}) ;


如果你的接口只需要一个函数,Lambda函数就可以派上用场。

Lambda函数表达式来自Lambda微积分:

                                                     λx.(4x^3 + 2x + 1)

括号前面的标记说明了x是变量,括号内的内容就是函数表达式,通常标记为M。带入值E的写法:

                                              ( ( λx.M ) E ) -> ( M[ x:=E ] )


Lambda表达式只是一个符号,就像0一样,它也可以像0一样掀起一个领域的全新远景的打开。它使程序更容易阅读——这意味着更少的BUG。

Lambda表达式只在函数接口(Functional interface)中管用,函数接口是指只有一个抽象方法的接口 :

                                                @FunctionalInterface

写法 : 

                                               ( 参数列表 ) -> { 方法体 }

参数列表中的参数类型可以选填。因为只有一个方法,方法的名字都可以省掉

前面的例子可以改为:

btn.setOnAction( (e)->{

                      System.out.println("Hi!") ;

                    }) ;


Lambda函数还可以用在容器中的查找,下面的例子会介绍:

class Film {

    private String title ;

    private String countries ;

    private int       year ;

    private float    billionRMB ;

    //构造器

    public Film(String title, String countries, int year, float billionRMB) { . . . }

    //get方法

    //toString()重写

    . . . . . .

}

假设一个Film类存储了一些票房信息。

从文件中提取数据到list :

       ArrayList<Film> films = new ArrayList<Film>() ;

现在的问题是如何检索这个容器。我们可以检索很多标准:电影名字,发行时间,国家等。

第一步:添加一些方法到Film类 :

public boolean selectByTitle(String str){ return this.title.contains(str) ; }

public boolean selectByCountry(String cntry){ return this.countries.contains(cntry) ; }

. . . . . .(体力劳动,继续添加,限制条件又很多,必须一条一条添加

第二步 :使用匿名类

interface SelectFilm { boolean test(Film film) ; }

static void showFilms(SelectFilm tester){

    for(Film f : films)

        if(tester.test(f)) System.out.println(f);

}

showFilms(new SelectFilm() { public boolean test(Film f){

                                                     return f.getYear() == 2014 ;

                                              }

                                        } ) ;

第三步:转为Lambda表达式

showFilms( (f) -> { return f.getYear() == 2014 } )

实际上,因为返回类型是直接计算的,可以更加简化:

showFilms( (f) -> f.getYear() == 2014 ) ;

第四步:多样的内部函数接口

由于需要测试的东西实在太多了,就需要一个可以判断多点东西的方法。

import java.util.function.Predicate ;

        Predicate<Film> pred

下面是一个使用Predicate接口的例子:

static boid filter(Predicate<Film> pred) {

    Film f ;

    ListIerator<Film> iter = films.listIterator() ;

    while (iter.hasNext()){

        f = iter.next() ;

        if(pred.test(f)) System.out.println(f) ;

    }

}

filter( (film) -> film.getYear() == 2014 ) ;

这里有几个可以用的函数式接口:

Supplier/Consumer与多线程有关,以后的课会讲。

Predicate<T>                   T -> boolean

Supplier<R>               void -> R

Consumer<T>                 T -> void

Function<T,R>                 T -> R

UnaryOperator<T>          T->T



Streams(流)

流和文件(InputStream, OutputStream)没有半毛钱关系,它是关于链式过程的编程方法

当你对String应用一个返回String的方法的时候,你可以继续应用新方法 :

String str = "now let's have some fun" ;

str. toUpperCase(). replace('S','D'). substring(15,19). replace('M','N')

有一种函数式编程(functional programming)的方法可以表示这一种过程

因为使用的函数都是返回String,我们可以将它们串起来。

同样的,容器类也可以应用这种方法。甚至SQL也要用这种思想。

流操作需要一个返回流的方法,且不总结流过程。

中间操作(Intermediate operations)

filter 

distinct

sorted

map

终结操作(Termial operations)

foreach

toArray

reduce

count

min, max

对于上面的例子,我们可以把一个容器转换为流:

ArrayList<Film> films = . . .

films.stream()

filims.stream().filter( (film)->film.getYear() == 2014 )

这种操作会对容器内的每一个元素进行。我们也可以详细地打印出这个过程:

films.stream().filter( (film)->film.getYear() == 2014 ).forEach(System.out : : println ) ;

forEach()是一个终结操作,println()方法应用在forEach()上。特别的符号" : : "指出这个方法应用在每个元素上。

如果Film类有comparable<T>接口,可以对其排序:

films.

stream().filter( (film)->film.getYear() == 2014 ).sorted().forEach(System.out : : println)


平行流(Parallel streams) : .parallelStream()

就像百川汇入大海,流也可以分成多个平行的分支,再达到相同的目标。我们会在以后讨论平行化(parallelism), 数据科学和大数据全是这种东西!



用户图形界面(Graphical User Interfaces, GUI)

你在实验室写的程序比你每天用的丑,丑上天际:在consoles里执行,从键盘读取,输出纯粹的文本 . . .八十年代,美丽的图形界面要花很多代码,其中的代码逻辑还和你平时写的不一样。

因为有很多图形包(Tons of graphcial packages),你不需要什么东西都自己写,你只需要躺着疯狂import。有一些低级的图形包可以生成线或曲线,一些高级的图形包可以生成按钮。

历史上java有几个图形包:1995年的AWT,1996年的Java Foundation Classes。在Java编程中,可以同时用几个包来建立GUI。Swing是依赖于AWT的,当写Swing的时候,就必须import AWT包,其他图像包也一样(javax.imageio.*)。

2008年,迎合安卓系统的发展,java有了JavaFX包。它也允许在程序外自定义修改界面,这个修改的文件叫作层叠样式表(Cascading Style Sheet, CSS),这是一种从Web编程借来的方法。

现在还有很多程序是用Swing编写的,同时学习JavaFX和Swing是个不错的选择。尽管它们的一些名词变了,但核心思想还是相近的。

JavaFX应用通常会根据一个固定结构设计:模型/界面/控制(Model/View/Controller,MVC),模型是为了管理数据,界面是为了用户友好,控制是为了操作逻辑更清晰。


不管用哪种语言,图形界面编程和算法编程有很大的区别,这种编程方法叫作事件驱动编程(Event-driven programming)

一个图形应用是一个大循环。在这个循环中,除了等待,你的程序什么都不需要做,等待什么?等待用户坐在电脑面前操作(除了挠头)。你的程序休眠并等待用户操作,当有用户来操作的时候,换句话来说,就是用户触发事件的时候,程序开始工作。

事件可以是任何事情,敲键盘,点鼠标,移动鼠标,点击屏幕,在网络摄像头前面跳起来...所有的这些都可以转换为电脑可以接收的电子信号。

你的图形应用在Windows管理器下运行也能够对事件发生反应。举个例子,鼠标可能会移动到程序的外面,但Windows管理器会获取鼠标在任何位置的坐标,这也是一个事件。这个事件会让不同的窗口的状态发生变化:鼠标指向一个新窗口,系统可以让这个窗口从休眠变成活跃,而刚才活跃的窗口变成休眠状态,利用这个状态可以调节窗口显示顺序。所有的这些都是Windows管理器做的,你不需要写这种代码。

你需要记住的是,图形界面是在Windows管理器的环境下的。


可视元素叫作窗口小部件(Window gadget, widget),如单选框,文本框,按钮,标签这些...还有一些不可视的部件,叫作容器(container)。容器的目的是创建一个有序布局(layout),这个布局可以让很多窗口小部件有序且有联系地展示在屏幕上。

如果你有一个尺寸不可变的窗口,事情会简单很多。当然,很多人会想到用变化比例来控制布局,但这件事情没那么简单。有一些容器的尺寸是固定的。

容器有一些可以解决这个问题的方法。两个盒子,横向盒(Horizontal boxes),和纵向盒(Vertical boxes)。

另一种就是网格(grids),网格允许按照行列关系坐标系来放置小部件,而不用距离坐标系。

关于容器最关键的就是它们是可嵌套的。你可以放一个纵向盒到横向盒里面,或放置一个网格,反之亦然。

例如,你可以放置两个纵向盒到一个横向盒里面。当窗口被调整尺寸的时候,全部局会按照盒的位置来调整。




做出反应(Callback) : 与事件有关的函数

最后一个很重要的概念就是处理器(handlers),这是一个与事件有关的函数。例如,单击一个按钮,会开始检索数据库。这个与按钮和数据库有关的行为就需要你来动手写。

预先定义事件(Predefined events):

destory window

button press/release

key press/release

focus in/out

move in/out

预先定义事件有很多(上面是一些例子),你只需要处理对你来说重要的事情就可以了,例如,窗口被关闭了,要弹出是否保存文本的提示框。



一个样例:移动鼠标到按钮时,按钮会移动到鼠标外的位置,使得鼠标无法点击按钮

一个JavaFX应用源自JavaFX的Application类,它是自动继承标准的抽象类和方法的。

创建一个Application类的示例:


使用init()函数:

init()

这个方法默认什么都不做,你可以重写这个方法,使它可以连接数据库,网络或者读取文件。

有一个必须重写的方法就是start()方法,这个方法把窗口小部件添加到窗口上,定义它们的外观和行为。

start( javafx.stage.Stage )

等待程序关闭的函数:

Platform.exit()

你必须且只需要写一个事件处理器。javaFx会运行程序知道它退出(可能与"退出"按钮有关,也看是检测到窗口被退出)。

使用stop()函数:

可以用stop()函数撤销你在init()函数做的操作:关闭数据库或网络。与init()一样,重写这个方法是可选的。



示例代码:

import java.util.Random

import javafx.application.Application

import javafx.event.ActionEvent;

import javafx.event.EventHandler

import javafx.scene.Group

import javafx.scene.Scene;

import javafx.scene.layout.VBox;

import javafx.scene.control.Button;

import javafx.scene.control.Label;

import javafx.scene.image.Image;

import javafx.scene.image.ImageView

import javafx.scene.input.MouseEvent;

import javafx.stage.Stage

import javafx.stage.Screen

import javafx.geometry.Rectangle2D;

import javafx.geometry.Insets;

import javafx.geometry.Pos;

public class Soothsayerfx extends Application {

    private Rectangle2D screenBounds

                                 = Screen.getPrimary().getVisualBounds() ;

    private Random  rand_generator ;

    private double    x ;

    private double    y ;

    public static void main(String[] args){

         launch(args) ;  //launch(input)函数架设了与start()函数的桥梁,提供了一个窗口                                        //(stafe)

    }

    public void start (Stage stage){

        this.rand_generator = new Random() ;

        stage.setTitle("Meaning of Life") ;

        stage.setResizable(false) ;

        Group root = new Group() ;                               //创建一个名为root的Group

        Scene scene = new Scene(root) ;                       //创建一个Scene对象,传入root

        Scene.getStylesheets().add("soothsayer.css") ;  //添加风格修改文本

    }

}

如果没有CSS文件,console就会发出警告。

示例:

.root {

         -fx-font-size : 28 pt ;

}

可以使用java反射机制找到文件的位置 :

private String diretory = Soothsayerfx class.getProtectionDomain()

                                                                      .getCodeSource()

                                                                      .getLocation()

                                                                      .toString() ;

scene.getStylesheets().add(directory + "soothsayer.css" ) ;


接着上面的示例代码 : 

        Group root = new Group() ;                               //创建一个名为root的Group

        Scene scene = new Scene(root) ;                       //创建一个Scene对象,传入root

        Scene.getStylesheets().add("soothsayer.css") ;  //添加风格修改文本

        VBox vBox = new VBox() ;                                  //创建一个纵向盒

        vBox.setPadding(new Insets(10)) ;                      //

        vBox.setAlignment(Pos.CENTER) ;

        root.getChildren().add(vBox) ;

        Label label = 

              new Label("Click the button to have the meaning of life revealed") ;

        vBox.getChildren().add(label) ;

        Image myPicture = new Image("psychic.png") ;

        ImageView img = new ImageView() ;

        img.setImage(myPicture) ;

        vBox.getChildren.add(img) ;

        Button button = new Button("Click to Learn") ;

        vBox.getChildren().add(button) ;

        button.addEventHandler(MouseEvent.MOUSE_ENTERED, 

                  new EventHandler<MouseEvent>(){

                          @Override

                          public void handle(MouseEvent e){

                                   moveWindow(stage) ;

                          }

                    }) ;

        stage.setScene(scene) ;

        stage.show() ;

        stage.setX( (screenBounds.getWidth() - stage.getWidth() )/ 2  ) ;

        stage.setY( (screenBounds.getHeight() - stage.getHeight() )/ 2  ) ;

        this.x = stage.getX() ;

        this.y = stage.getY() ;

    }

    private void moveWindow(Stage stage){

        double height = screenBounds.getHeight() ;

        double width = screenBounds.getWidth() ;

        double x_move = width / 10 + rand_generator.nextDouble() * width / 2 ;

        double y_move = height / 10 +rand_generator.nextDouble() * height / 2 ;

        this.x = (double)((long)(this.x + x_move) % (long)(width - stage.getWidth())) ;

        this.y = (double)((long)(this.x + x_move) % (long)(height - stage.getHeight())) ;

        stage.setX(this.x) ; 

        stage.setY(this.y) ;

    }

}

整个代码没有明确的测试和循环,只有事件。

你可以自定义自己的窗口小部件。可以使用工具创建XML(FXML)文件去描述窗口小部件和容器。JavaFx会装载这个部件,部件的static方法会创建这个对象。

有很多个布局生成器,有一个比较特殊的是Scene Builder

有了Scene Builder,你可以把窗口小部件来拖去,放在自己想要的位置。设计完毕后保存为.fxml文件。

<?xml version="1.0" encoding="UTF-8"?> 

<?import javafx.geometry.Insets?>

<?import javafx.scene.control.Button?>

<?import javafx.scene.control.Label?> 

<?import javafx.scene.image.Image?>

<?import javafx.scene.image.ImageView?>

<?import javafx.scene.layout.VBox?>

<?import javafx.scene.text.Font?> 

<vBox alignment="CENTER" maxHeight="-Infinity" maxWidth="Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" spacing="8.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http:// javafx.com/fxml/1" fx:controller="Soothsayerfxml"> 


你必须创建一个FXML加载器对象,这个对象的构造函数会抛出异常。因为代码被分成不同的模块,所以行为必须是public的,一些属性必须是static的。

生成代码 -> Soothsayerfxml控制器



事件和监听器(Events and Change Listeners)

事件和监听器的关系就像exceptions和catch,以这种模式来定义它们。

有很多诸如此类的代码 :

.setOnSomeAction () 

KeyPressed, KeyReleased, KeyTyped, MouseClicked, MouseExited


.setOnAction() 方法是给Buttons, Radio Buttons, Check Boxes的

setOnAction()里面要有一个处理器接口,这就可以使用Lambda表达式了 :

Button btn = new Button() ;

btn.setText("Say Hi") ;

btn.setOnAction( (e) -> {    System.out.println("Hi!") ;   } ) ;

另一种在示例代码中的方法就是 :

button.addEventHandle(MouseEvent.MOUSE_ENTERED, (e)->{moveWindow(stage);});








猜你喜欢

转载自blog.csdn.net/weixin_40804987/article/details/80033217
今日推荐