Java
系统整理 Java 程序结构、输入输出、数组、流程控制、对象与基础语言特性。
一、基础部分
程序设计结构
- main方法需要为public static void
- 文件名需要和公共类的名字相同,且使用.java作为扩展名
注释
- 可以通过/** **/来生成注释文档
swith语句
- 可以将switch语句作为右值
- 使用分号而不是break
- case使用->
public class Main {
public static void main(String[] args) {
int a = 0;
int b = switch (a) {
case 1 -> 10;
case 2 -> 20;
default -> 30;
}
}
}
输入和输出
- 输出 java使用System.out的方法print系列进行输出:
public class Main {
public static void main(String[] args) {
System.out.print("Hello World"); //print打印一个对象
System.out.println("Hello World"); //println打印一个对象并换行
}
}
- 输入 要想使用输入,必须包含包util
import java.util.*;
使用输入前需要创建一个Scanner实例,将该实例与输入流绑定,再使用该输入流进行读取操作:
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
int a = cin.next();
String b = cin.nextLine();
}
}
以下是输入Scanner实例的方法:
String cin.next() //读取下一个单词的输入(空格为止
String cin.nextLine() //读取下一行的输入(换行符为止
int cin.nextInt() //读取下一个int
double cin.nextDouble() //读取下一个double
boolean cin.hasNext() //检测输入中是否还有单词
boolean cin.hasNextInt()
boolean cin.hasNextDouble()
数组
Java中的数组和C中的指针数组类似。
int[] array = new int[10]; //初始化一个长度为10的数组,10可以替换为变量
int[] array = { 1, 2, 3, 4 }; //以列表初始化数组
new int[] { 1, 2, 3, 4 }; //声明一个无名数组
array = new int[] { 1, 2, 3, 4 }; //使用无名数组赋值
- 数组拷贝 若使用等号拷贝数组,原数组和拷贝数组将指向同一块内存空间,意味着对其中一个的修改也会影响另一个:
int[] a = { 1, 2, 3 };
int[] b = a;
b[0] = 2; //a[0]也被修改了
可以使用Arrays.copyOf()来拷贝到新的数组中:
int[] a = { 1, 2, 3, 4 };
int[] b = Arrays.copyOf(a, 4); //第一个参数为要拷贝的数组,第二个是拷贝长度。
//可以通过这种方法增加数组长度
b = Arrays.copyOf(b, 2 * b.lenth);
- 数组排序 可以使用Arrays.sort()对数组排序:
int[] a = { 2, 34, 12, 3, 1 };
Arrays.sort(a);
var字段
相当于auto,若可以从右边的表达式中推出变量类型,则可以使用var自动推断变量类型。多用于临时变量。
null字段
可以将对象声明为null值,但如果对其使用方法,将会导致NullPointerException异常:
Player a = null;
String str = a.getName(); //抛出异常
二、面向对象部分
构造器
构造器是构造函数的另一种叫法。 构造器无法使用C++风格的列表初始化。 可以在类中加入代码块,在构造对象时这些代码块会调用:
public class Player {
public int ID;
{
this.ID = Math.random();
}
public Player(int ID)
{
this.ID = ID;
}
}
Java中没有析构器,它能够自动完成垃圾回收。
方法
Java中的实现不能放置于类外。放置于类内并不意味着声明为内联。若遇到同名问题,Java会发生错误,而不是同名遮掩机制。
- main方法 每一个类都可以有一个main方法,可以在其中包含示例代码,单独运行该类文件时就可以运行该代码。
继承
Java中没有private和protected继承,仅有public继承,其语法如下:
public class A extends B {
}
- 在子类中使用超类方法 在子类中调用超类方法需要用到关键字super:
public class A extends B {
private String name;
public A(String str1, String str2)
{
super(str2); //super调用基类构造器
this.name = str1;
}
public String get()
{
String baseStr = super.get(); //super调用基类方法
return name + baseStr;
}
}
- 多态 Java中默认方法为virtual的,若不需要为虚,则使用关键字final。
- instanceof运算符 用于检查对象是否为某类的实例。
Object超类
在java中,所有类型都继承自Object超类,这是一个功能很强大的类,所有子类都可以使用其方法(如equals。
- equals方法 equals方法有两个版本,一是成员函数版本,二是静态方法版本。
public class Main {
public static void main {
Player pl = new Player("KringKoter");
Player p2 = new Player("Afternoon Tea");
System.out.print(p1.equals(p2)); //成员函数版本
System.out.print(Objects.equals(p1, p2)); //静态方法版本(注意是Objects
}
}
- toString方法 此方法将类转换为字符串。当遇到对象+字符串时,将自动调用该对象的toString方法。 每个类都需要覆写一次toString方法,如果不进行覆写, 调用时将生成类名+散列码的字符串。
- hashcode方法 为对象生成一个散列码。
- getClass方法 返回一个类对象,保存了有关对象的信息。
ArrayList
类似于vector容器。ArrayList可以使用菱形语法:
public class Main {
public static void main(String[] args) [
ArrayList<int> array1 = new ArrayList<>(); //菱形语法
var array2 = new ArrayList<int>(); //使用var自动类型推断
ArrayList<int> array3 = new ArrayList<>(100); //指定初始长度
]
}
包
- 导入包 使用import来导入包和包中的所有类,其中,.*只能导入一个包,不能同时导入例如java为前缀的所有包:
import java.lang.System; //导入System包
import java.util.*; //导入util包
import java.*.*; //不能这样写
在两个包中有同名成员时,可以显示指出使用哪一个包的成员:
import java.util.*;
import java.sql.*; //两个包中都有Date类
import java.util.Date; //显式指出使用util包的Date类
如果两个包的同名成员都需要使用,则在使用中加上相应的前缀:
var startTime = new java.util.Date();
var today = new java.sql.Date();
- 在包中添加类 若需要在包中添加类,需要将包名放在类的开头。包名使用一个因特网域名的倒序(如Player.com变为com.Player)。如果未在包中放置package语句,则包为无名包。 以下是Player.java的包声明语法,其文件放置于com\Player\corejava中:
package com.Player.corejava
对象包装
有的时候需要将int这样的基本类型变为对象类型,所有基本类型都有一个与之对应的类。包装器类不可变,不能修改其中的数据,且不能派生其子类。诸如ArrayList的容器类无法使用基本类型,此时可以使用包装器类将基本类型包装:
ArrayList<int> array = new ArrayList<>(); //非法
ArrayList<Integer> array = new ArrayList<>(); //合法
可以直接传递基本类型给array,此时基本类型将自动包装为对象类型;反之,对象类型可以直接赋值给基本类型,称之自动拆箱。
array.add(1); //自动装箱
int i = array.get(2); //自动拆箱
由于java中==运算符比较的是两者是否指向同一片内存空间,对于两个相同包装器对象的比较很有可能失败。此时需要使用equals方法:
Integer int1 = 1000;
Integer int2 = 1000;
if (int1 == int2) return 1; //不能这样比较
if (int1.equals(int2)) return 2; //应该这样比较
可变参数个数
使用…语法可以提供多个且数量不定的参数:
public class Main {
public static int func(int... value) {
int result = 0;
for (var i : value)
result += i;
return result;
}
}
抽象类和抽象方法
使用关键字abstract。包含一个或多个抽象方法的类需要声明为抽象。
public abstract class Super {
public abstract void get();
}
Lambda表达式
基本语法:
(String fisrt, String second) -> first.length() - second.length()
(参数列表) -> 表达式体
如果没有大括号,无需指定返回类型。Java会自动推断返回类型。如果使用了大括号,则需要手动返回值,且不能使用单一情况的表达式:
(String first, String second) -> {
if (first.length() - first.length() > 0)
return 1;
else
return 2;
}
三、接口
声明接口
接口是对希望符合接口的类的一组需求,如果需要使用某方法,意味着类必须实现某接口。如果要使用Arrays类中的sort函数,则需要实现Comparable接口。
public interface Compareable<T> {
int compareTo(T t);
}
接口里的方法不提供实现,且自动声明为public。要声明一个类实现某接口,需要将类声明为实现给定的接口,且对接口中的方法提供定义。要声明一个类实现某接口,需要使用关键字implement:
public class Player implements Complarable<Player> {
private int level;
public int compareTo(Player player) {
return Intager.compare(this.level, player.level);
}
}
接口的属性
接口不是类,不能使用new来声明一个接口,但是可以将一个接口变量与实现了这个接口的一个具体对象绑定。 接口也有继承体系,可以使用extends关键字拓展接口,使其从泛用性强拓展到专用性强:
public interface A {
void func1();
}
public interface B extends A {
void func2();
}
public interface C extends B {
int level;
void func3();
}
接口中不能包含实例字段,但可以包含常量。在接口内的实例字段自动声明为public static final。 可以将接口方法显式标记为public,将实例字段标记为public static final,但java语言规范建议不要使用冗余的关键字。
多个接口
Java没有MI,但是可以实现多个接口。在接口中使用逗号隔开:
public class Player implements A, B {
...
}
默认方法
可以在接口中使用default来提供默认方法,这样实现接口的类可以不提供其实现:
public interface A<T> {
default int compare(T t) {
return 0;
}
}
默认方法冲突
如果同时实现了两个接口,且这两个接口内有签名一致的默认方法,需要手动指定使用哪一个方法:
interface Compare1<T> {
default int compare(T t) {
return 0;
}
}
interface Compare2<T> {
default int compare(T t) {
return 1;
}
}
public class Player implements Compare2<Player>, Compare1<Player> {
public int compare(Player player) {
return Compare1.super.compare(player); //手动指定使用Compare1的compare方法
}
}
四、泛型和集合
声明泛型类
Java中无需使用template来指定泛型:
public class name<T> {
private T element;
public name() { element = null; }
public name(T element) {
this.element = element;
}
public T getElement() {
return this.element;
}
public void setElement(T elemnet) {
this.element = element;
}
}
声明泛型方法
泛型方法可以在普通类中声明,也可以在泛型类中声明。
public class name {
public <T> T function() {
return T;
}
}
集合和Collection接口
Java中的集合类似于STL,包括了ArrayList,SortedMap,set等容器。这些容器都实现自Collection接口(SortedMap实现自Map接口)。要想声明自己的数集,并使用相应的算法,需要实现Collection接口:
public class MySet<E> implements Collection<E> {
public int size() {
reutrn size;
}
public boolean isEmpty {
return size == 0;
}
...
}
如果不是确实需要实现自己的集合,Java库中的现有集合就能满足需求。Collection接口中的具体方法见《Java核心技术·卷一》383页。
迭代器
Java中的迭代器与C++中的不同,它并不是类似于数组索引的存在,而是相间于两个元素之间。
public class Main {
public static void main(String[] args) {
ArrayList<Integer> array = new ArrayList<>();
for (int i = 0; i < 10; i++) { //向集合中添加元素
array.add(i);
}
Interator<Integer> iter = array.interator(); //获得array的迭代器,该迭代器从首前开始。
for (iter.hasNext()) //hasNext函数检查是否有下一个元素
{
int a = iter.next(); //将迭代器移向下一个元素之后,并返回刚才的元素。
System.out.println(a);
}
itera.remove() //删除上次调用next的元素。
}
}
有关迭代器的方法见《Java核心技术·卷一》394页。
散列集(HashSet)
有一种众所周知的数据结构,可以用来快速查找对象,被称作散列表。散列表为每个对象计算一个散列码。要想查找每个元素在表中的位置,要先计算其散列码,再通过运算找到桶的编号。如果桶中已经有了元素,这种现象被称作散列冲突(Hash Collision),这时需要将新对象与原对象进行比较,查看这个数据是否存在。 Java库提供了一个散列集,可以用add方法插入元素,使用contains方法查看元素是否存在。当访问元素时,会通过一种看起来随机的方式,只有不关心集合中元素顺序时才使用HashSet。 这个实例程序读取一行字符,并打印其前20个不重复的单词。
public class Main {
public static void main(String[] args) {
Set<String> stringSet = new HashSet<>();
var cin = new Scanner(System.in);
while (cin.hasNext()) {
String temp = cin.nextLine();
stringSet.add(temp);
}
var iter = stringSet.iterator();
while (iter.hasNext()) {
String temp = iter.next();
System.out.println(temp);
}
}
}
有序树集(TreeSet)
相较于HashSet,基于红黑树排序的树集在add数据时自动排序。添加数据时树集较慢,但查找较快。要使用树集,必须能够比较元素。这些元素必须实现了Comparable接口。 下面的实例程序使用实现了Comparable接口的测试类Item,比较其中的number实例字段。
public class Main {
public static void main(String[] args) {
Set<Item> itemSet = new TreeSet<>();
for (int i = 0; i < 20; i++) {
Random random = new Random();
itemSet.add(new Item(random.nextInt(101)));
}
Iterator<Item> iter = itemSet.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}
}
}
public class Item implements Comparable<Item>{
private int number;
public Item(int number) {
this.number = number;
}
public int compareTo(Item item) {
return number - item.number;
}
@Override
public String toString() {
return "Item{" +
"number=" + number +
'}';
}
}
映射(Map)
映射分为散列映射和树映射,区别在于散列为无序而树为有序,它们都实现了Map接口。使用put函数向映射中添加元素,使用get来获取元素。在使用get时,必须提供key,因此必须记住key。 如果没有找到元素,则返回null,但null并不能包含有效信息,可以使用getOrDefault来使用自定义的默认值:
Map<String, String> map = new HashMap<>();
int a = map.getOrDefault("wobuzhidao", 100); //a将为默认值100
可以使用forEach方法,该方法接受一个lambda表达式,映射中的元素会依次调用此表达式。
public class Main {
public static void main(String[] args) {
Map<Integer, String> stringMap = new HashMap<>();
stringMap.put(20040528, "JSKringKoter");
stringMap.put(31061231, "FlyingDream");
stringMap.put(31060825, "Ou'yang Xingyu");
stringMap.put(20040930, "Afternoontea");
stringMap.put(20040528, "JSKring");
stringMap.remove(2004);
stringMap.forEach((k, v) ->
System.out.println("key=" + k + " value=" + v));
}
}
有关映射的方法在《Java核心技术·卷一》408页。
更新映射条目问题
如果需要更换一个条目,但不知道是否存在,需要进行检查:
Map<String, Integer> counts = new HashMap<>();
counts.put("word", counts.get("word") + 1);
这些代码在word出现后将其引用计数+1,但如果word第一次出现,则会抛出NullPointerException异常。补救方法是,使用getOrDefault函数:
counts.put("word", counts.getOrDefault("word", 0) + 1);
链接散列集与映射
LinkedHashSet和LinkedHashMap会记住元素插入的顺序,使其看起来以一种有序的方法排序。
排序与散排
Collections类实现了一组方法,可以将给定的集合排列或散排,前提是其实现了Comparable接口。
- 排序:Collections.sort()
- 散排:Collections.shuffle()
public class Main {
public static void main(String[] args) {
ArrayList<Integer> integerArrayList = new ArrayList<>();
for (int i = 0; i < 30; i++) {
integerArrayList.add(i);
}
Collections.shuffle(integerArrayList);
System.out.println(integerArrayList);
Collections.sort(integerArrayList);
System.out.println(integerArrayList);
}
}
List接口也有一个sort函数,但其接受一个比较器对象。Comparator.reverseOrder函数返回一个将元素倒序排序的比较器。下面的代码将integerArrayLIst的元素按逆序排序。
integerArrayList.sort(Comparator.reverseOrder());
交集
使用retainAll函数可以快速生产交集,这个函数接受一个Collections,保留两者都有的元素,删除没有的元素。
ArrayList<Integer> oldList = new ArrayList<>();
ArrayList<Integer> newList = new ArrayList<>();
oldList.retainAll(newList);
五、图形用户界面程序设计
创建窗体
Swing类位于javax中,其被认为是一个拓展包。在每个Swing程序中,Swing组件必须由事件分派线程配置,它将鼠标点击和按键等事件传递给用户界面组件,下面的代码用来在事件分派线程中执行语句:
EventQueue.invokeLater(() ->
{
statements
});
想要显示一个界面,需要先声明一个继承了JFrame的类。这个类包含了窗体的基本信息,如长度和高度。在类的构造器中,可以通过函数来设置构造时的基本属性:
class SimpleFrame extends JFrame {
static final int DEFAULT_WIDTH = 300;
static final int DEFAULT_HIGHT = 400;
public SimpleFrame() {
setSize(DEFAULT_WIDTH, DEFAULT_HIGHT);
}
}
然后在主函数中创建这个类的对象,窗体对象创建后可以对其进行配置:
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var frame = new SimpleFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //设置退出窗体时程序即退出
frame.setLocation(100, 100); //设置窗体位置
frame.setResizble(true); //设置是否可以调整大小
frame.setVisible(true); //设置为可见
});
}
- 属性 如下的一组代码称作窗体的一个属性,属性由一个get和一个set组成(对于boolean类型,get为is),将get或set后的单词小写即为其属性:
public String getTitle()
public void setTitle(String title)
以及:
public boolean isResizable();
public void setResizble(boolean resizable)
- 调整合适的窗体大小 要确定合适的窗体大小,需要先知道屏幕的大小。调用Toolkit类的静态方法getDefaultToolkit得到一个Toolkit对象,这个对象中的getScreenSize方法以Dimension对象保存屏幕大小信息。该Dimension对象中的height和width实例字段保存了高度的宽度信息。
Toolkit tool = Toolkit.getDefaultToolkit();
Dimension screen = tool.getScreenSize();
int height = screen.height;
int width = screen.width;
frame.setSize(height / 2, width / 2);
此外,可以提供窗体图标:
Image img = new ImageIcon("icon.gif").getImage();
setIconImage(img);
添加组件
图形界面的组件被放在JComponent的类中,要创建自己的组件时,需要先创建一个继承了JComponent的类,并覆盖其中的paintComponent方法,该方法将一个Graphics对象作为参量:
class SimpleComponent extends JComponent {
public void paintComponent(Graphics g) {
g.drawString("我不知道", 100, 100);
}
}
drawString方法用于打印字符串。
处理图形
我们将使用Java 2D图形库绘制图形,需要获得Graphics2D的一个对象。这个类是Graphics的一个子类,要使用2D,需要进行强制类型转换:
Graphics2D g2 = (Graphics2D) g
可以使用Graphics2D对象的方法来处理图形。