p122 JDK8和JDK9接口的新特性
jdk8:接口中可以定义有方法体的方法(有逻辑的默认方法或者静态方法)
详解:
jdk8版本开始允许在接口中定义带有逻辑的方法,如果是动态方法就需要用default关键字修饰,这些方法就是默认方法。
默认方法的定义格式:
default 返回值类型 方法名(参数列表){}
举例:
default void show(){}
创建之后这些方法就类似于被继承的具体方法,可以被实现类拿去调用。
作用:解决接口升级添加新方法之后旧的没有重写新方法的类无法使用的问题(如果没有新加的这个特性,那么所有旧的实现这个接口的类都要重写新加的方法)
注意事项:
默认方法的权限固定是public;
实现类也可以对接口中的默认方法进行重写;
实现类中如果要调用接口中的默认方法需要用接口名.super.方法名的形式来调用,直接使用super.方法名会使用父类的方法而不是接口中的方法;
如果实现类实现了多个接口、并且多个接口中有多个相同方法声明的默认方法,就需要强制子类重写这个默认方法;
jdk8版本中同样允许定义带有逻辑的静态方法。
理解:接口既然已经允许方法带有方法体(默认方法),干脆也开放静态方法(static),可以直接用接口名调用;
注意事项:
接口中定义的静态方法的权限固定是public;
接口中定义的静态方法不允许用接口类对象名.方法名的形式调用,只允许用接口名.方法名的形式调用。
jdk9:接口中可以定义私有方法
详解:
接口中可以编写私有(private)方法,可以提供给本接口内部编写的方法使用,把多个方法中出现的重复的代码段集合成接口内部可用的私有方法可以节省接口内部编写方法的代码篇幅,减少冗余。
p123 代码块
什么是代码块?
被一个大括号{}括起来的代码被称为代码块
代码块分为:
局部代码块 构造代码块 静态代码块 同步代码块
其中同步代码块在学到多线程之后再去细讲。
1.局部代码块
位置:方法中的一对大括号
作用:限定变量的生命周期,可以提早释放内存(脱离代码块范围即释放)
2.构造代码块
位置:类中 方法外的一堆大括号
特点:在创建对象、执行构造方法的时候就会执行,并且优先于构造方法执行
原理:在编译阶段,系统会把构造代码块的内容分散到每一个构造方法中的最开始的时间点。
作用:可以将多个构造方法中重复的代码抽取到构造代码块中,提高代码的复用性
3.静态代码块
位置:类中 方法外的一对大括号,但是需要加上static关键字
特点:随着类的加载而执行,只要这个类被加载就会执行且只执行一次(因为一次运行一个类只会加载一次)
作用:对数据进行初始化(可以用来初始化一些创建之后赋值很麻烦的数据,比如对象类的数据)
p124 内部类
内部类是什么?
内部类就是定义在类里面的类。
内部类创建对象的格式:
外部类名.内部类名 对象名 = new 外部类对象().new 内部类对象();
举例:
Outer.Inner in = new Outer().new Inner();
在它所在的外部类中创建内部类的对象可以省略外部类名,按照普通创建对象的格式。
成员访问细节
在内外没有声明相同的成员的情况下:
1.内部类中访问外部成员:直接访问,包括私有成员
2.外部类中访问内部成员:需要创建内部类的对象进行访问
在内外有声明相同的成员的情况下:
1.内部类中访问外部成员:外部类名.this.成员名
2.外部类中访问内部成员:需要创建内部类的对象进行访问
内部类、外部类出现相同声明的成员的举例:
成员变量:
public static void main(String[] args) { InnerTest1.Inner inner = new InnerTest1().new Inner(); inner.test(); System.out.println(a); System.out.println(inner.a); } public class Inner { static int a = 20; public void test() { int a = 30; System.out.println(a); System.out.println(this.a); System.out.println(InnerTest1.this.a); } } }
该案例中不仅有外部类定义的a、内部类定义的a、还有位于方法里的临时变量a,值分别为10、20、30。
打印结果为30 20 10 10 20,由此结果可见:
内部类调用临时变量直接用变量名,调用内部类的成员变量使用this.变量名的形式,调用外部类的成员变量使用 外部类.this.变量名的形式。
外部类调用内部类的成员变量需要创建内部类的对象再调用,调用外部类直接用变量名。
成员方法:
public class InnerTest1 { public static void print() { System.out.println("outer"); } public static void main(String[] args) { InnerTest1.Inner inner = new InnerTest1().new Inner(); inner.test(); print(); inner.print(); } public class Inner { public static void print() { System.out.println("inner"); } public void test() { print(); InnerTest1.this.print(); } } }
该案例中不仅有外部类定义的print方法,还有内部类定义的print方法。调用内部类的方法时打印inner,调用外部类方法时打印outer。
打印结果为inner outer outer inner,由该结果可得:
内部类调用内部类成员方法直接用方法名调用,调用外部类成员方法需要用外部类.this.方法名 的形式来调用。
外部类调用内部类成员方法需要创建内部类对象调用,调用外部类成员方法直接用方法名调用。
为什么要学习内部类?
案例举例:
需求:写一个JavaBean描述汽车,这个汽车有品牌、车龄、颜色、发动机品牌、发动机使用年限等属性。
编码过程即创建一个类包含进这五条属性再封装成JavaBean,但是在此基础上,发动机品牌、发动机使用年限这两条属性可以单独提出来加进一个发动机的类来代表发动机的属性,同时发动机类是车类的内部类也代表发动机是车的一部分,代码整体的封装性更好。
实际编码生活中,由于内外部类成员调用太过于复杂,所以几乎不会实际用到。
在本p中讲解的这种编写位置和成员变量、成员方法在一个位置的内部类叫做成员内部类,内部类分为四种,分别是成员内部类、静态内部类、局部内部类、匿名内部类,其中只有最后一种使用的最多,前三种基本只要了解程度就够。
p125 静态内部类/局部内部类
局部内部类
静态内部类就是有static修饰的成员内部类。
注:只有内部类可以被static修饰,普通的类不行。
创建对象的格式和编写内部类的格式都和成员内部类类似,只有编写时候需要加上一个static关键字。
相对于成员内部类的区别:
外部类调用静态内部类的成员可以直接用静态内部类名.成员名的格式调用。(联想静态的性质)
注意事项(关于静态而不是关于内部类):静态只能访问静态
局部内部类
位于方法、代码块、构造器等执行体中的内部类称为局部内部类。
是个特别鸡肋的语法,知道有这么个东西就行。
p126 匿名内部类
概述:匿名内部类是一个特殊的局部内部类(定义在方法内部)
前提:需要存在一个接口或类
格式:
new 类名/接口(){}
结论:匿名内部类可以让代码变的更加简介,可以在定义类的时候对其进行实例化
常用适用场合:调用一个方法,方法的参数是一个类或者接口,并且内部的抽象方法数量不是很多,为了避免在外部新建一个类用来实现或者继承该类或者接口 造成的代码冗余,直接在参数部分使用匿名内部类来创建对象实现这个接口/继承这个抽象类,并且重写内部的抽象方法。
p127 Lambda表达式
Lambda表达式是JDK8开始后的一种新语法形式,可以简化匿名内部类的代码写法。
Lambda表达式的简化格式:
() -> {}
表达式的具体格式:
(匿名内部类被重写方法的形参列表) -> {
被重写的方法的方法体代码
}
示例:
public class LambdaDemo1 { public static void main(String[] args) { useInterA(new InterA() { @Override public void show(int a, double b, String c) { System.out.println("匿名类"); } }); useInterA((a, b, c) -> { System.out.println("Lambda"); }); } public static void useInterA(InterA a) { a.show(0, 0, null); } } interface InterA { void show(int a, double b, String c); }
接口InterA 内部有一个方法show,传入三个不同类型的参数;
主函数所在的类包含一个useInterA(InterA a)方法,参数为接口InterA的接口类对象,作用为调用这个对象内部的show方法;
主函数运行这个useInterA方法两次,第一次使用匿名类的形式,第二次使用Lambda表达式;
经过对比可见,Lambda表达式实际上就是把匿名类中重写了的方法传入的参数加进箭头左边的小括号、把重写了的方法的方法体加进箭头右边的大括号;
注意事项:
Lambda表达式只允许操作函数式编程接口(即该抽象类或者接口有且仅有一个抽象方法)。
p128 Lambda表达式的省略写法规则
Lambda表达式的省略写法:
参数类型可以省略不写(上例中小括号里只标注了变量名a,b,c且没有标注变量类型);
如果只有一个参数,参数类型和小括号本身都可以省略(只保留一个变量名);
如果Lambda表达式的方法体只有一行代码,可以省略大括号和分号不写,并且在此基础上,如果这行代码是return语句,必须省略return不写;
结合这三条省略规则,例如:
(int a)->{return a;}
由规则一可以省略为
(a)->{return a;}
由规则二:
a -> {return a;}
由规则三:
a -> return a;
并且由于这行语句是return语句必须省略掉return:
最终省略结果:a -> a
个人理解:小括号里给进元素,箭头代表把这些元素传给大括号里的方法体;如果这个方法体只有一句就可以省略掉代表方法体的大括号(和以前循环结构用到的如果循环体只有一句也可以省略大括号是一样的);如果只有一句并且是return就可以省略大括号并且必须删掉return,省略之后的整体可以理解为 箭头左侧的数据,经过运算,得出了箭头右侧的数据并且返回。
(一节课看到最后才发现实际上idea可以直接把符合要求的匿名内部类转化成lambda表达式的形式 不过还是自己记一下的好)
p129 Lambda表达式和匿名内部类的区别 构建窗体
Lambda表达式和匿名内部类的区别:
使用限制不同:
匿名内部类可以操作类和接口
Lambda表达式只能操作函数式接口(只有一个抽象方法的接口)
实现原理不同:
匿名内部类编译之后产生一个单独的.class字节码文件(因为是个完整的类,虽然只存在在这个方法里但是至少是个类)
Lambda表达式不会产生单独的.class字节码文件
窗体对象JFrame
构建一个窗体最基本的代码(基本上每个窗口都得用 重要):
JFrame frame = new JFrame();//创建窗体对象
JFrame有很多构造方法,比较常用的一个是在括号里传入一个字符串作为窗口的标题,可以省略后面的一个调用方法设置窗口标题的步骤
frame.setSize(800,600);//设置窗体的大小
传入数字的单位是像素,不设置的话默认窗体大小就是0*0
frame.setLayout(null)//取消窗体的默认布局
不取消默认布局的话没法自定义窗口各个组件的位置
frame.setVisible(true);//设置使得窗体可见
其他常用方法:
jFrame.setDefaultCloseOperation(3);//设置使得程序随着窗口关闭而关闭
从api文档搜索可得,这个方法用来设置用户在此窗口上发起“close”也就是关闭窗口时候默认执行的操作,方法接收一个int类型参数用来作为选择操作的信号值。信号值为3即代表“窗口关闭则程序关闭”。
api文档里并没有说明各个操作对应的信号值,这个信号值是通过源代码配合api文本进行分析所得出来的结论。实际工作中也需要建立起这种将api文本和源代码配合起来进行理解的思考方式。
jFrame.setTitle()//设置窗口的标题
传入一个字符串作为窗口的标题(实际前面提到过在创建对象的时候就可以设置窗口标题了)
jFrame.setBounds(x,y,width,height)//设置窗口打开后的初始出现位置和大小
x和y代表出现时候窗口左上角的坐标,width代表宽度,height代表高度
重点注意:
setVisible(true)一定要放在所有设置窗口相关信息的代码的最后面,可以避免一些日后在窗口内加入了图片、文字、按钮等元素之后可能出现的一些问题
p130 窗体里添加按钮
如何创建窗体里的组件?
java中的组件的实质也是一个个封装好的对象。
本节主要介绍窗体中的按钮组件(JButton对象)并且介绍几个组件对象通用的方法。
按钮对象的构造方法:
JButton() 创建一个没有设置文本或者图标的按钮
JButton(String text) 创建一个带有text文本的按钮
如何给窗体中添加一个已有的组件对象?
窗体对象.getContentPane().add(已有的组件对象);
这一行代码实质上是获取窗体中的面板和向面板中添加组件对象的链式编程形式简化后的结果,分开写的话是这种形式:
JFrame frame = new JFrame();//创建窗体 Container contentPane = frame.getContentPane();//获取窗体里的面板 contentPane.add(组件对象名);//向面板中添加创建好的组件对象
在练习中可以发现建造出来的按钮无论设置的大小是多少,甚至是设置按钮的大小(大小为0*0),最终它都布满了整个窗口,这是因为窗体给面板都有设置一个默认的布局方式。使用窗体对象.setLayout(null)方法可以取消窗体的默认布局。
重要:取消了窗体的默认布局的话就需要手动指定组件的摆放位置和大小。
组件对象的一些常用方法:
组件对象.setBounds(int x,int y,int width,int height) 设置按钮的摆放位置和大小,四个参数分别代表按钮左上角位置的横坐标、纵坐标、左右宽度、上下高度。
本小节总结:
向窗体中加入组件的初步设置:
-
窗体对象.setLayout(null); 取消默认布局并且自定义每个组件的位置和大小
-
创建组件对象
-
组件对象.setBounds(x,y,width,height); 设置对象摆放位置和大小
-
窗体对象.getContentPane().add(组件对象); 向窗体中加入组件对象
p131 JLabel组件
JLabel组件的作用:
用于展示图片和文本
如何使用它展示文本和图片:
使用JLabel的构造方法:
JLabel(String text) 使用指定的文本创建JLabel对象
JLabel(Icon image) 使用指定的图片创建JLabel对象
第一种在这里不做多解释,主要解释第二种
这里的icon实际上是一个接口,也就是说第二种构造方法需要传入的参数是一个实现类对象。
icon接口有一个实现类ImageIcon,这里要用到的构造方法为:
ImageIcon icon = new ImageIcon(String filename);
这里的filename是一个字符串,内容含义是文件所在的路径和文件名,并且这一部分中每一层中间必须相隔两个反斜线而不是一个反斜线(反斜线就是从左上到右下的斜线,这里必须用两个是因为在字符串中,单独一个反斜线加文本会被理解为标识符,无论这个标识符是否存在或者是否报错,然而在使用字符串纪录文件路径的使用情况下,无论是否出现标识符报错,对我们来说都是不利的),例如我在联系中调用了D盘下Desktop文件夹下的1.png图片,就要将括号里的内容修改为:
"D:\\Desktop\\1.png"
随后再将这个实现类对象传递给JLabel的构造方法。
在熟悉这一过程之后还可以使用链式编程的思想将这两部替换为:
JLabel image_label = new JLabel(new ImageIcon("D:\Desktop\1.png"));
这一部分也可以再和 向面板中加入组件 的代码进行链式编程,这里不做赘述了(弄太多看着就麻烦了,本末倒置)
几个注意事项:
1.窗口中多个组件出现位置重叠的情况时,会以”先来后到“的形式被覆盖(后面代码的组件会被前面代码的组件覆盖)
可以理解为组件是从窗口的下面逐渐填上而不是从上面压到下面
2.如果给JLabel设置的大小小于图片的分辨率,会导致图片显示不完整;如果给JLabel设置的大小大于图片的分辨率,会导致图片位置出现偏差,所以正常情况都会使得容纳图片的JLabel组件的大小与图片的分辨率契合。(至少现在还没遇到要有不同的时候)
p132 事件监听
什么是事件?
事件是可以被组件识别的操作;当用户对组件有了某种特定的操作之后就会执行对应的代码,例如鼠标点击、键盘按键等。
关于事件的几个名词:
事件源:事件的源头,也就是要对哪一个组件进行操作
事件操作:对组件做出的操作,比如鼠标单击、键盘按下等;
绑定监听:当事件源上发生了事件操作,触发执行某段代码
如何实现绑定监听?
需要用到监听器。
这里暂时只介绍两个监听器,分别是ActionListener(动作监听)和KeyListener(键盘监听)
动作监听可以监听到的操作有鼠标点击和空格按下,一般用于点击的按钮;
键盘监听可以监听到键盘上所有的按键并且区分出具体哪一个按键被按下。
代码演示:
按钮的动作监听:
import javax.swing.*; public class ActionListenerTest1 { public static void main(String[] args) { //创建窗口,设置使得程序随窗口关闭而停止,取消默认布局,设置窗口标题、位置和大小 JFrame jFrame = new JFrame("测试窗口"); jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); jFrame.setLayout(null); jFrame.setBounds(0,0,960,540); //创建一个测试按钮1 JButton button1 = new JButton("按钮1"); button1.setBounds(50,50,200,50); //给这个按钮添加动作监听ActionListener,每点击一次按钮或者按一次空格之后在控制台输出一次 button1.addActionListener(actionEvent -> System.out.println("按了按钮1")); button1.setFocusable(false); jFrame.getContentPane().add(button1);//取消按钮创建后的焦点,后面会提到 //设置使得窗口可见 jFrame.setVisible(true); } }
键盘监听:
键盘上的每个按键都会有一个对应的int类型数值给到KeyEvent类对象,使用这个类的对象的getKeyCode方法可以把这个数值提取出来,配合判断语句来达成在按下特定按键时才做出反应的效果。
以下方的例子举例,其中37、38、39、40分别代表小键盘的左、上、右、下,四个按键。
public class KeyListenerTest1 { public static void main(String[] args) { JFrame jFrame = new JFrame("测试窗口"); jFrame.setBounds(50, 50, 400, 300); jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); jFrame.addKeyListener(new KeyListener() { @Override//键入时触发 public void keyTyped(KeyEvent keyEvent) { } @Override//按下时触发 public void keyPressed(KeyEvent keyEvent) { switch (keyEvent.getKeyCode()) { case 37 -> System.out.println("按下了左"); case 38 -> System.out.println("按了上"); case 39 -> System.out.println("按了右"); case 40 -> System.out.println("按了下"); } } @Override//松开时触发 public void keyReleased(KeyEvent keyEvent) { } }); jFrame.setVisible(true); } }
其中的 keyTyped部分只能监听到键盘中的一部分按键,监听不到例如fn、ctrl、esc、上下左右等按键(说白了就是有点鸡肋,很少用,而且本人测试的时候同样的方法体在按下释放部分可以生效,在键入部分不生效,原因不明)
注意:按钮组件在创建之后会默认吸引程序的焦点,但是实际上不需要,并且如果焦点一直停留在按钮上会使得它的事件监听和其他组件的监听或者窗口的监听产生冲突问题,所以创建按钮组件后一般使用按钮对象的setFocusable()方法传入一个false来取消按钮的焦点。
p133 适配器设计模式
什么是设计模式?
设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。 使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。 设计模式一共有23种。
适配器设计模式
作用:
解决接口与接口的实现类之间的矛盾问题
举例:
有一个接口有多个需要实现类重写的方法,有一个实现类要用到接口中的某一个方法,但是不需要其他方法,如果直接实现这个接口就需要重写里面的所有方法,实际上只有有用的方法会被重写并且写入运算逻辑,其余方法重写也只会有一个没有逻辑的空方法体,还会显得代码整体太过繁琐。
实现步骤:
这种情况下,如果创建一个抽象类实现原本的接口,重写里面的多个方法但是不写入逻辑(也就是用空方法重写方法),再让真正需要使用接口的类继承这个抽象类,那么这个子类就只需要重写它需要写入逻辑的方法,不需要重写它不使用的方法。这个中间层的类设定为抽象类是为了让其他的类无法调用这个中间类的空方法。这个中间类就被称为适配器(Adapter),这个设计方法就叫做适配器设计模式。
这个适配器设计模式在上一p的键盘监听部分也有用到。上一p中使用的KeyListener就拥有一个适配器KeyAdapter,用后者替换前者之后可以省略掉创建监听器部分的 关于键入时触发和松开时触发 的两个方法的重写,以省略不必要的代码部分。
适用场合:
需要一个类实现某个接口,但是这个类并不需要对这个接口中的所有方法进行重写,就可以创建一个适配器(本质是抽象类)实现这个接口,再让本来的实现类继承这个抽象类。
p134 模板设计模式
模板设计模式:
把抽象类整体看做一个模板,模板中不能决定的东西定义成抽象方法,让使用模板的类(继承抽象类的类)去重写抽象方法实现各自的需求
好处:
模板中已经定义了通用的结构,使用者只需要关心自己需要实现的功能即可
举例:
比如写定番作文,需要确定开头和结尾,并且每个人写各自想出来的作文内容,也就是这个题目只决定开头和结尾,无法决定作文的主题内容,这种情况就可以把开头、主体、结尾提出成三个方法,其中开头和结尾是final方法(为了防止被套用模板的类重写而失去作为模板的意义),主体为了让每个套用模板的人各自写出不一样的内容所以是抽象方法。
类比到代码中就是将内容或者逻辑固定并且继承给子类的内容写成final关键字修饰的方法,将需要子类各自重写的方法写成抽象方法,这些工作完成后这个抽象的父类就可以称为模板,使用模板进行编码的这个模式就称为模板设计模式。
本章学习目标:
- 清楚JDK8、JDK9接口的新特性
- 掌握匿名内部类的使用
- 能够使用Lambda表达式改造匿名内部类
- 掌握窗体对象的构建,并能够在窗体中加入按钮和图片能够为组件注册动作事件和键盘事件
- 理解适配器设计模式和模板设计模式的作用