Java核心技术卷I 2.2 ImageViewer 例子编译不能通过

xioacd99
  • 10

刚准备从 C++ 转向 Java,看的是 Java 核心卷 I 的英文版,里面 2.2 有一个 ImageViewer 的例子我按照官网给出的源码编译 javac ImageViewer.java,结果出现如下报错(环境:Java8,Windows10)

image.png

我在网上查了查,好像大家都没有出现过这个问题(或许是我比较背?)

有人知道如何原因或者可以提供一些帮助吗?谢谢:)

如果是 JDK 版本不对的话,有人可以提供 Java8 的实现吗?再次感谢

书上的源码如下

import java.awt.*;
import java.io.*;
import javax.swing.*;

/**
 * A program for viewing images.
 * @version 1.31 2018-04-10
 * @author Cay Horstmann
 */
public class ImageViewer{
   public static void main(String[] args){
      EventQueue.invokeLater(() -> {
         var frame = new ImageViewerFrame();
         frame.setTitle("ImageViewer");
         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         frame.setVisible(true);
      });
   }
}

/**
 * A frame with a label to show an image.
 */
class ImageViewerFrame extends JFrame{
   private static final int DEFAULT_WIDTH = 300;
   private static final int DEFAULT_HEIGHT = 400;

   public ImageViewerFrame(){
      setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

      // use a label to display the images
      var label = new JLabel();
      add(label);

      // set up the file chooser
      var chooser = new JFileChooser();
      chooser.setCurrentDirectory(new File("."));

      // set up the menu bar
      var menuBar = new JMenuBar();
      setJMenuBar(menuBar);

      var menu = new JMenu("File");
      menuBar.add(menu);

      var openItem = new JMenuItem("Open");
      menu.add(openItem);
      openItem.addActionListener(event -> {
         // show file chooser dialog
         int result = chooser.showOpenDialog(null);

         // if file selected, set it as icon of the label
         if (result == JFileChooser.APPROVE_OPTION){
            String name = chooser.getSelectedFile().getPath();
            label.setIcon(new ImageIcon(name));
         }
      });

      var exitItem = new JMenuItem("Exit");
      menu.add(exitItem);
      exitItem.addActionListener(event -> System.exit(0));
   }
}
回复
阅读 360
4 个回答

var(本地变量类型推断)是 JDK 10 的新特性。

低版本的话你改成限定类名不就好了:

JLabel label = new JLabel();

var 是 java10 加入的
所以要么换java11(一步到位17也行,别的都不是LTS),要么把var改成具体类型

这是我当时照着写的,你看看,可以跑起来
image.png

package com.dd.logger;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.*;

/**
 * 日志记录消息显示在日志窗口中
 */
public class LoggingImageViewer {

    public static void main(String[] args) {
        if (System.getProperty("java.util.logging.config.class") == null
                && System.getProperty("java.util.logging.config.file") == null) {
            try {
                Logger.getLogger("com.dd.logger").setLevel(Level.ALL);

                //将日志打印到控制台
                Logger.getLogger("com.dd.logger").setUseParentHandlers(false);
                Handler handler = new ConsoleHandler();
                handler.setLevel(Level.ALL);
                Logger.getLogger("com.dd.logger").addHandler(handler);

                //将日志打印到文件
                final int LOG_ROTATION_COUNT = 10;
                Handler fileHandler = new FileHandler("%h/LoggingImageViewer.log", 0, LOG_ROTATION_COUNT);
                Logger.getLogger("com.dd.logger").addHandler(fileHandler);
            } catch (IOException e) {
                Logger.getLogger("com.dd.logger").log(Level.SEVERE, "Can`t create log file handler", e);
            }
        }

        /**
         * API中:
         * public class java.awt.EventQueue extends java.lang.Object
         * EventQueue 是一个与平台无关的类,它将来自于底层同位体类和受信任的应用程序类的事件列入队列。
         * 它封装了异步事件指派机制,该机制从队列中提取事件,然后通过对此 EventQueue调用dispatchEvent(AWTEvent) 
         * 方法来指派这些事件(事件作为参数被指派)。该机制的特殊行为是与实现有关的。指派实际排入到该队列中的
         * 事件(注意,正在发送到 EventQueue 中的事件可以被合并)的唯一要求是:按顺序指派。
         * 也就是说,不允许同时从该队列中指派多个事件。
         * 指派顺序与它们排队的顺序相同。
         * 也就是说,如果 AWTEvent A 比 AWTEvent B 先排入到 EventQueue 中,那么事件 B 不能在事件 A 之前被指派。
         *
         * 下面是JDK中java.awt.EventQueue.invokeLater的源代码:
         * public static void invokeLater(Runnable runnable) 
         * {
         *     Toolkit.getEventQueue().postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
         * }
         *
         * 使用该方式的原因是:
         * 1)图像文件查看器viewer使用的是Swing组件,而Swing是线程不安全的,是单线程的设计,所以只能从事件派发
         * 线程访问将要在屏幕上绘制的Swing组件,从而保证组件状态的可确定性。
         * 2)使用eventqueue.invokelater()好处是显而易见的,这个方法调用完毕后,它会被销毁,因为匿名内部类是作
         * 为临时变量存在的,给它分配的内存在此时会被释放。这个对于只需要在一个地方使用时可以节省内存,而且这个
         * 类是不可以被其它的方法或类使用的,只能被EventQueue.invokeLater()来使用。但如果你需要一个在很多地方都
         * 能用到的类,而不是只在某一个类里面或者方法里用的话,定义成匿名内部类显然是不可取的。
         * 原文链接:https://blog.csdn.net/m0_37732829/article/details/80515663
         */
        EventQueue.invokeLater(() -> {
            Handler windowHandler = new WindowHandler();
            windowHandler.setLevel(Level.ALL);
            //放入自定义处理器
            Logger.getLogger("com.dd.logger").addHandler(windowHandler);

            //打开文件图片查看器
            JFrame frame = new ImageViewerFrame();
            frame.setTitle("LoggingImageViewer");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

            Logger.getLogger("com.dd.logger").fine("Showing frame");
            frame.setVisible(true);
        });
    }

}

/**
 * 用于展示图片
 */
class ImageViewerFrame extends JFrame {
    private static final int DEFAULT_WIDTH = 300;
    private static final int DEFAULT_HEIGHT = 400;

    private JLabel label;
    private static Logger logger = Logger.getLogger("com.dd.logger");

    public ImageViewerFrame() {
        logger.entering("ImageViewerFrame", "<init>");
        setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

        //设置菜单栏
        JMenuBar menuBar = new JMenuBar();
        setJMenuBar(menuBar);

        JMenu menu = new JMenu("File");
        menuBar.add(menu);

        JMenuItem openItem = new JMenuItem("Open");
        menu.add(openItem);
        openItem.addActionListener(new FileOpenListener());

        JMenuItem exitItem = new JMenuItem("Exit");
        menu.add(exitItem);
        exitItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                logger.fine("Exiting.");
                System.exit(0);
            }
        });

        //使用一个标签展示图片
        label = new JLabel();
        add(label);
        logger.exiting("ImageViewerFrame","<init>");
    }

    private class FileOpenListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            logger.entering("ImageViewerFrame.FileOpenListener", "actionPerformed", e);

            //设置文件选项框
            JFileChooser chooser = new JFileChooser();
            chooser.setCurrentDirectory(new File("."));

            //接受所有以".git,jpg"结尾的文件
            chooser.setFileFilter(new javax.swing.filechooser.FileFilter() {

                @Override
                public boolean accept(File f) {
                    String name = f.getName().toLowerCase();
                    return name.endsWith(".gif") || name.endsWith(".jpg") || f.isDirectory();
                }

                @Override
                public String getDescription() {
                    return "Images";
                }
            });

            //显示文件选择库
            int r = chooser.showOpenDialog(ImageViewerFrame.this);

            //如果图片符合要求,将图标设置到标签上
            if (r == JFileChooser.APPROVE_OPTION) {
                String name = chooser.getSelectedFile().getPath();
                logger.log(Level.FINE, "Reading file {0}", name);
                label.setIcon(new ImageIcon(name));
            }else{
                logger.fine("File open dialog canceld.");
            }
            logger.exiting("ImageViewerFrame.FileOpenListener","actionPerformed");
        }
    }

}

/**
 * 一个将日志信息显示到窗口的处理器
 * <p>
 * SWING包的线程问题:
 * swing包有一个特点,基于线程不安全特性。可以理解为:如果一个函数没有正确return之前,
 * swing的显示是不会响应的,如果执行时间长,甚至会像死机一样没有响应。原因是该函数和主函数的执行处于同一
 * 线程上,该函数的执行占用了CPU的执行时间片。因此,建议所有需要同步显示结果的同行们用如下格式调用(原理
 * 重新开启一个线程)
 */
class WindowHandler extends StreamHandler {
    //swing是单线程的,所以要同步显示日志信息的话,需要使用EventQueue来开启一个新的线程将日志信息输入
    private JFrame frame;

    public WindowHandler() {
        frame = new JFrame();
        //文本框
        final JTextArea output = new JTextArea();
        output.setEditable(false);
        frame.setSize(200, 200);
        //将文本框放到可以滑动的面板上
        frame.add(new JScrollPane(output));
        frame.setFocusableWindowState(false);
        frame.setVisible(true);

        //将日志的输出流指向文本框
        setOutputStream(new OutputStream() {
            @Override
            public void write(int b) throws IOException {
                //不会调用
            }

            public void write(byte[] b, int off, int len) {
                output.append(new String(b, off, len));
            }
        });

    }

    @Override
    public void publish(LogRecord record) {
        if (!frame.isVisible()) return;
        super.publish(record);
        flush();
    }
}
ABUGADAY
  • 4
新手上路,请多包涵

这本书已经出到第11版了(新增加 Java SE 9 10 11)特性,不确定你看的是哪一版,你回到安装JDK 那一小节看看作者建议你安装的是哪一个版本的JDK 再往后写demo
var 局部类型推断 是 10 的特性,你用JDK8 是编译通不过的

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
你知道吗?

宣传栏