JavaNotes
2021-05-17 23:00:37 2 举报
AI智能生成
大三小白的Java笔记嗷~
作者其他创作
大纲/内容
JavaNotes
基础知识
数据类型
基本数据类型
整型
byte
1 byte = 8 bits
byte为基本数据单位
-128(-2^7)~127(2^8-1)
short
2个字节存储
-32768(-2^15)~32767(2^15-1)
int
4个字节存储
-2147483648 (-2^31)~247483647(2^31-1)
long
8个字节存储
-9223372036854775808 (-2^63)~9223372036854775807(2^63-1)
字符型
char
英文1个字节,中文2个字节
浮点型
float
1.4E-45~3.4028235E38
double
4.9E-324~1.7976931348623157E308
布尔型
boolean
true
false
注意,true和false不能用1和0来表示
引用数据类型
Java类库中的类以及自定义的一些类,如String、Math
变量
类变量
独立于方法之外的变量,用 static 修饰。
实例变量
独立于方法之外的变量,不用static修饰
局部变量
类方法中的变量
例子代码
数组
泛型数组列表
声明数组列表
var staff = new ArrayList<E>();
ArrayList<E> staff = new ArrayList<>();
add方法可以将元素添加到数组列表中
staff.add(new E(\"...\
size方法返回数组列表中包含的实际元素个数
staff.size()
等价于数组a的a.length
trimToSize方法
这个方法将存储块的大小调整为保存当前元素数量所需要的存储空间
垃圾回收器将回收多余的存储空间
a = b
在Java中这条赋值语句的操作结果是让a和b引用同一个数组列表
访问数组列表
访问或改变数组列表的元素:get和set方法
使用toArray方法将数组元素拷贝到一个数组中
使用remove方法删除指定索引位置的元素
运算符
算术运算符
+
-
*
/
%
取余运算符
++
自增运算符
i++
先用后加
++i
先加后用
--
自减运算符
原理与自增运算符相同
赋值运算符
=
+=
sum += 1 和 sum = sum + 1是相同的
-=
同上
*=
/=
(%)=
<<=
左移位赋值运算符
c <<= 2等价于 c = c << 2
>>=
右移位运算符
c >>= 2 等价于 c = c >> 2
&=
按位与赋值运算符
^=
按位异或赋值操作符
|=
按位或赋值运算符
逻辑运算符
||
逻辑或运算符
&&
逻辑与运算符
!
逻辑非运算符
用来反转操作数的逻辑状态
关系运算符
==
!=
>
<
>=
<=
位运算符
|
如果相应位都是0,则结果为0,否则为1
&
如果相对应位都是1,则结果为1,否则为0
^
如果相对应位都是0,则结果为0,否则为1
~
按位取反运算符翻转操作数的每一位,即0变成1,1变成0。
<<
按位左移运算符。左操作数按位左移右操作数指定的位数。
>>
按位右移运算符。左操作数按位右移右操作数指定的位数。
>>>
按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充
条件运算符
?:
判断语句?x : y
true: 采用x赋值
false:采用y赋值
instanceof
用来在运行时指出对象是否是特定类的一个实例。
用法:result = object instanceof class
循环/判断/选择
if/else、if/else if/.../(else)
while、do/while
for/for each
switch/case/default/break
面向对象编程
封装
继承
用关键字extends来表示继承
子类与父类是“is-a”的关系
Java中,所有的继承都是公共继承,而没有C++中的私有继承和保护继承
超类(superclass)和子类(subclass)是Java程序员最常用的两个术语
子类比超类拥有的功能更多
在设计类的时候,应该将最一般的方法放在超类中,而将更特殊的方法放在子类中
覆盖方法
@Override
super关键字
调用超类的方法
调用超类构造器
动态绑定
在运行时能自动地选择适当的方法
重要特性:无须对现有的代码进行修改就可以对程序进行扩展
在继承层次中,从某个特定的类到其祖先的路径被称为该类的继承链
Java不支持多重继承,但提供了类似多重继承的功能——接口
is-a规则的另一种表述是替换规则
在Java中,子类引用的数组可以转换成超类引用的数组,而不需要使用强制类型转换
再覆盖一个方法的时候,子类方法不能低于超类方法的可见性
final类和方法
阻止继承
强制类型转换的唯一原因
要在暂时忽视对象的实际类型之后使用对象的全部功能
在将超类强制转换为子类之前,应该使用instance of进行检查
当强制类型转换失败时,Java不会生成null对象,而是抛出一个异常
静态绑定
如果是private方法,static方法,final方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法
抽象
abstract类
abstract方法
多态
一个对象变量可以指示多种实际类型的现象
在Java中,对象变量是多态的
Overload/Override
接口
包
借助包可以方便地组织直接的代码并将自己的代码与别人提供的代码库分开管理
使用包的主要原因是确保类名的唯一性
要用一个因特网域名以逆序的形式作为包名,然后对于不同的工程使用不同的子包
从编译器的角度来看,嵌套的包之间没有任何关系
类的导入
使用完全限定名
使用import语句
只能使用星号(*)导入一个包,而不能使用import java.*或import java.*.*导入以java为前缀的所有包
静态导入
有一种import语句允许导入静态方法和静态字段,而不只是类
无名包
如果没有在源文件中放置package语句,这个源文件中的类就属于无名包
类之间的关系
依赖(uses-a)
聚合(has-a)
继承(is-a)
对象包装器与自动装箱
包装器
所有的基本类型都有一个与之对应的类,这些类称为包装器
Integer
Long
Float
Double
Short
Byte
Charater
Boolean
Number
包装器类是final的,因此不能派生它们的子类
自动装箱
调用list.add(3)将自动地变换成list.add(Integer.valueOf(3));的这种变换称为自动装箱
装箱和拆箱时编译器要做的工作,而不是虚拟机
同一性
==运算符可以应用于包装器对象,不过检测的是对象是否有相同的内存位置
在比较两个包装器对象时调用equals方法
intValue()
toString()
parseInt()
valueOf()
关键字
访问修饰符
public
对外部完全可见
protected
对本包和所有子类可见
(default)
对本包可见
private
仅对本类可见
基本数据类型关键字
循环与判断相关的关键字
void
空类型修饰符
final
被其指定的类或变量或方法均无法被继承(或修改)
abstract
this
指示隐式参数
super
super.父类变量、super.父类方法
var(not keyword)
可以用此关键字来声明变量,然后可以通过从变量的初始值推断出它的类型,不可以用于数值类型,如int,long,double等
extends
子类继承父类的关键字
implements
类实现接口的关键字
import
导包关键字
package
new
new一个对象
return
static
静态字段修饰符(其实用类字段比较贴切)
interface
enum
continue
break
const
可用final取代,目前未被使用
goto
没有用到
throw
throws
assert
断言
常用的类
java.lang
String
compareTo()
equals()
equalsIgnoreCase()
length()
toCharArray()
toUpperCase()/toLowerCase()
indexOf()
substring()
join()
repeat()
trim()
一定不要使用“==”运算符检测两个字符串是否相等!
Math
pow()
PI、E
log()
sin()...
System
System.out.println()
System.out.print()
System.out.printf()
java.util
Scanner
next()
nextLine()
nextInt()
nextDouble()
Arrays
sort()
foreach
copyOf()
length
binarySearch()
fill()
ArrayList
Date
java.time
LocalDate
of()
getYear()/getMonthValue()/getDayOfMonth()
plusDays()
minusDays()
枚举类
enum关键字放在类名前面,表示这是一个枚举类
所有的枚举类都是Enum类的子类
最有用的一个继承方法是toString方法
返回枚举常量名
toString的逆方法是静态方法valueOf
ordinal方法返回enum声明中枚举常量的位置,位置从0开始计数
方法
静态方法
不在对象上执行的方法
可以提供类名来调用这个方法
可以使用对象调用静态方法
两种情况可以使用静态方法
方法不需要访问对象状态,因为它需要的所有参数都通过显式参数提供
方法只需要访问类的静态字段
工厂方法
静态方法的另一种常见用途
main方法
静态的main方法将执行并构造程序所需要的对象
方法的名字和参数列表称为方法的签名
参数数量可变的方法
printf方法
省略号... 是Java代码的一部分,它表明这个方法可以接任意数量的对象(fmt参数之外)。
对于printf的实现者来说,Object..参数类型与Object[]完全一样
甚至可以将main方法声明以下形式:
public static void main(String... args)
文档注释
javadoc
javadoc实用工具从以下几项中抽取信息
模块
公共类与接口
公共类和受保护的字段
公共的和受保护的构造器及方法
几种注释
类注释
必须放在import之后,类定义之前
方法注释
@param
@renturn
@throws
字段注释
只需要对公共字段(通常指静态常量)建立文档
通用注释
@author
@version
@see
@link
包注释
需要在每一个包目录中添加一个单独的文件
提供一个名为package-info.java的Java文件
提供一个名为package.html的HTML文件
类中
方法参数
按值传递
两种类型:基本数据类型、对象引用
对象构造
重载
如果多个方法有相同的名字、不同的参数,便出现了重载
重载解析:编译器必须挑选出具体调用哪个方法。它用各个方法首部中的参数类型与特定方法调用中所使用的值类型进行匹配,,来选出正确的方法。如果编译器找不到匹配的参数,就会产生编译时错误,因为根本不存在匹配,或者没有一个比其他更好的
默认字段初始化
如果在构造器中没有显式地为字段设置初值,那么就会被自动赋为默认值:数值为0、布尔型为false、对象引用为null。
显式字段初始化
初始化块
对象初始化块
静态初始化块
构造器
如果写一个类时没有编写构造器,就会为你提供一个无参数构造器。
这个构造器将所有的实例字段设置为默认值。
如果类中提供了至少一个构造器,但是没有提供无参数的构造器,那么构造对象时如果部提供参数就是hi不合法的。
仅当类没有任何其他构造器的时候,你才会得到一个默认的无参数构造器
对象析构
Java会完成自动的垃圾回收,不需要人工回收内存,所以Java不支持析构器。
类路径
类路径必须与包名匹配
在一个JAR文件中,可以包含多个压缩形式的类文件和子目录。
为了使类能够被多个程序共享,需要做到下面几点
把类文件放一个目录中
将JAR文件放在一个目录中
设置类路径(class path)
设置类路径
最好使用-classpath或-cp选项指定类路径
java -classpath c:\\classdir;.;c:\\archive.jar MyProg
类设计技巧
一定要保证数据私有(private)
一定要对数据进行初始化
不要在类中使用过多的基本类型
不是所有的字段都需要单独的字段访问器(getter)和字段更改器(setter)
分解有过多职责的类
类名和方法名要能够体现它们的职责
优先使用不可变的类
内部类
为什么需要使用内部类
内部类可以对同一个包中的其他类隐藏
内部类方法可以访问定义这个类的作用域中的数据,包括原本私有的数据
内部类原先对于简洁地使用回调非常重要,不过如今lambda表达式在这方面可以做得更好
使用内部类访问对象状态
一个内部类可以访问自身的数据字段,也可以访问创建它的外围类对象的数据字段
内部类的对象总有一个隐式引用,指向创建它的外部类对象
只有内部类可以是私有的,而常规类可以有包可见性或公共可见性
内部类的特殊语法规则
可以采用以下语法更加明确地编写内部类对象的构造器
outerObject.new InnerClass(construction parameters)
在外围类的作用域之外,可以这样引用内部类
OuterClass.InnerClass
内部类中声明的所有静态字段都必须是final,并初始化为一个编译时常量
内部类不能有static方法
也可以允许有静态方法,但只能访问外围类的静态字段和方法
内部类是否必要、有用和安全
内部类是一个编译器现象,与虚拟机无关。编译器将会把内部类转换为常规的类文件,用$分隔外部类名与内部类名
javap -private innerClass.outerClass\\$innerClass
查看内部类的实际工作
内部类可以访问外围类的私有数据
局部内部类
声明局部内部类不能有访问说明符
局部类的作用域被限定在声明这个局部类的块中
由外部方法访问变量
匿名内部类
new SuperType(construction parameters) { inner class methods and data }
SuperType可以是接口也可以是类
静态内部类
使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类有外围类对象的一个引用
可以将内部类声明为static 这样就不会生成上述引用
只要内部类不需要访问外围类对象,就应该使用静态内部类
与常规内部类不同,静态内部类可以有静态字段和方法
在接口中声明的内部类自动是static和public
Object
只有基本类型不是对象
equals方法
自反性
对称性
传递性
一致性
hashcode方法
散列码
toString方法
最好自定义一个toString方法
接口不是类,而是对希望符合这个接口的类的一组需求
接口中的所有方法都自动是public方法
在接口中声明方法时,不必提供关键字public
Java8之前,接口中绝对不会实现方法
Java8之后,可以在接口中提供简单方法
这些方法不能引用实例字段
接口没有实例
为了让类实现一个接口,通常需要完成下面两个步骤
将类声明为实现给定的接口
对接口中的所有方法提供定义
要将类声明为实现某个接口,需要使用关键字implements
接口的属性
接口不是类,不能使用new运算符实例化一个接口
尽管不能构造接口的对象,却能声明接口的变量
接口变量必须引用实现了这个接口的类对象
可以使用instanceof检查一个对象是否实现了某个特定的接口
与建立类的继承层次一样,也可以扩展接口
虽然在接口中不能包含实例字段,但时可以包含常量
与接口中的方法都自动被设置为public一样,接口中的字段总是public static final
有些接口只定义产量,而没有定义方法
这样使用接口更像是退化,所以我们建议最好不要这样使用
尽管每个类只能有一个超类,但却能实现多个接口
可以用逗号将想要实现的各个接口分隔开
接口与抽象类
使用抽象类表示通用属性存在一个严重的问题
每个类只能扩展一个类
但是,每个类可以实现多个接口
Java设计者选择了不支持多重继承,其主要原因是多重继承会让语言变得非常复杂或者效率会降低
接口中的方法
静态和私有方法
在Java8中,允许在接口中增加静态方法
只是这有违于将接口作为抽象规范的初衷
通常的做法都是将静态方法放在伴随类中
在Java9中,接口中的方法可以是private
private方法可以是静态方法或实例方法
由于私有方法只能在接口中使用,所以它们的用法很有限,只能作为接口中其他方法的辅助方法
默认方法
必须用default修饰符标记这样一个方法
一个重要用法是“接口演化”
解决默认方法冲突
超类优先
接口冲突
覆盖
“类优先”规则可以确保与Java7的兼容性
千万不要让一个默认方法重新定义Object类中的某个方法
接口与回调
Comparator接口
对象克隆
Cloneable接口
这个接口指示一个类提供了一个安全的clone方法
clone方法
clone方法是Object的一个protected方法
异常
处理错误
异常分类
Throwable
Error
Exception
IOException
RuntimeException
错误的强制类型转换
数组访问越界
访问null指针
非派生于RuntimeException的异常
试图超越文件末尾继续读取数据
试图打开一个不存在的文件
试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在
非检查型异常
派生于Error类或RuntimeException类的所有异常
检查型异常
声明所有检查型异常
会抛出异常的情况
调用了一个抛出检查型异常的方法
检测到一个错误,并且利用throw语句抛出一个检查型异常
程序出现错误
Java虚拟机或运行时库出现内部错误
如果出现前两种情况,则必须告诉调用这个方法的程序员有可能抛出异常
一个方法必须声明所有可能抛出的检查型异常,而非检查型异常要么在你的控制之外(Error),要么是由从一开始就应该避免的情况所导致的(RuntimeException).
如果子类中覆盖了超类的一个方法,子类方法中声明的检查型异常不能比超类方法中声明的异常更通用
如果超类方法没有抛出任何检查型异常,子类也不能抛出任何检查型异常
如何抛出异常
找到一个合适的异常类
创建这个类的一个对象
将对象抛出
一旦方法抛出了异常,这个方法就不会返回到调用者
创建异常类
定义一个派生于Exception的类,或者派生于Exception的某个子类,如IOException
习惯做法
自定义的这个类应该包含两个构造器,一个是默认的构造器,另一个是包含详细描述信息的构造器
捕获异常
try/catch语句
try/catch/catch...
捕获多个异常
finally子句
不管是否有异常被捕获,finally子句中的代码都会执行
执行finally子句的情况
代码没有抛出异常
代码抛出一个异常,并在一个catch子句中捕获
try语句可以只有finally子句,而没有catch子句
不要把控制流的语句(return,throw,break,continue)放在finally子句中
try-with-Resources语句
堆栈轨迹元素
StackFrame
StackTraceElement
使用异常的技巧
异常处理不能代替简单的测试
不要过分地细化异常
充分利用异常层次结构
不要压制异常
在检测错误时,“苛刻”要比放任更好
不要羞于传递异常
早抛出,晚捕获
断言的概念
断言机制允许在测试期间向代码中插入一些检查,而在生产代码中自动删除这些检查
Java语言引入了关键字assert,有两种形式
assert condition
assert condition : expression
\"表达式\"(expression)部分的唯一目的是产生一个消息字符串
启用和禁用断言
在默认情况下,断言是禁用的
启用断言使用:java -enableassertions MyApp 或 java -ea MyApp
不必重新编译程序来启用或禁用断言
启用或禁用断言是类加载器(class loader)的功能
禁用断言时,类加载器会去除断言代码
也可以用选项 -disableassertions或 -da在某个特定类和包中禁用断言
使用断言完成参数检查
在Java语言中,给出了3种处理系统错误的机制
抛出异常
日志
使用断言
何时应该选择断言
断言失败时致命的,不可恢复的错误
断言检查只是在开发和测试阶段打开
不应该使用断言向程序的其他部分通知发生了可恢复性的错误
前置条件
使用断言提供假设文档
断言是一种测试和调试阶段使用的战术性工具
与之不同,日志是一种在程序整个生命周期都可以使用的战略性工具
基本日志
全局日志记录器
Logger.getGlobal().info(\"File\" -> Open menu item selected);
取消所有日志
Logger.getGlobal().setLevel(Level.OFF);
高级日志
调用getLogger方法创建或获取日志记录器
未被任何变量引用的日志记录器可能会被垃圾回收。
7个日志级别
SEVERE
WARNING
INFO
CONFIG
FINE
FINER
FINEST
在默认情况下,实际上只记录前3个级别
还可以使用Level.ALL开启所有级别的日志记录,或者使用Level.OFF关闭所有级别的日志记录
默认的日志处理器会抑制低于INFO级别的消息
修改日志管理器配置
默认情况下,配置文件位于:conf/logging.properties
日志记录器并不将消息发送到控制台,那是处理器的任务
日志管理器在虚拟机启动时初始化,也就是在main方法执行前
本地化
处理器
过滤器
格式化器
日志技巧
lambda表达式
为什么引入lambda表达式
lambda表达式是一个可传递的代码块,可以在以后执行一次或多次
lambda表达式的语法
第一个lambda表达式
lambda表达式就是一个代码块,以及必须传入代码的变量规范
表达式形式
参数,箭头(->)以及一个表达式
即使lambda表达式没有参数,仍然要提供空括号,就像无参数方法一样
如果推导出一个lambda表达式的参数类型,则可以忽略其类型
lambda表达式的返回类型总是会由上下文推导得出
如果一个lambda表达式只在某系分支返回一个值,而另外一些分支不返回值,这是不合法的
函数式接口
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式
这种接口称为函数式接口
最好把lambda表达式当作是一个函数,而不是一个对象,另外要接受lambda表达式可以传递到函数式接口
方法引用
方法引用也不是一个对象
只有当lambda表达式的体只调用一个方法而不做其他操作时,才能把lambda表达式重写为方法引用
可以在方法引用中使用this参数
构造器引用
变量作用域
lambda表达式有三部分
一个代码块
参数
自由变量的值,这是指非参数而且不在代码中定义的变量
关于关于代码块以及自由变量值有一个术语:闭包。Java中,lambda表达式就是闭包
如果在lambda表达式中引用一个变量,而这个变量可能在外部改变,这也是不合法的。
lambda表达式可以捕获外围作用域中的变量的值
在lambda表达式中,引用值不会改变的变量
lambda表达式中捕获的变量必须实际上是事实最终变量
lambda表达式的体与嵌套块有相同的作用域
在一个lambda表达式中使用this关键字时,是指创建这个lambda表达式的方法的this参数
处理lambda表达式
使用lambda表达式的重点是延迟执行
之所以希望以后再执行代码,原因有
在一个单独的线程中运行代码
多次运行代码
在算法的适当位置运行代码
发生某种情况时执行代码
只在必要时才运行代码
反射
反射库提供了一个丰富且精巧的工具集,可以用来编写能够动态操纵Java代码的程序。
能够分析类能力的程序称为反射。
反射机制可以用来
在运行时分析类的能力。
在运行时检查对象,例如,编写一个适用于所有类的toString方法。
实现泛型数组操作代码。
利用Method对象。这个对象很像C++中的函数指针。
Class类
forName(String className)
getConstructor(Class.. parameterTypes)
虚拟机为每个类型管理一个唯一的Class对象
声明异常
异常两种类型
非检查型
检查型
如果一个方法包含一条可能抛出检查型异常的语句,则在方法名上增加一个throws子句
资源
图像和声音文件
包含消息字符串和按钮标签的文本文件
URL getResource(String name)
InputStream getResourceAsStream(String name)
利用反射分析类的能力
检查类的结构
getName()
getType()
getModifiers()
getMethods()
getConstructor()
isPublic()
JAR文件
创建JAR文件
jar cvf jarFileName file1 file2 ...
jar options file1 file2
清单文件
每个JAR文件还包含一个清单文件,用于描述归档文件的特殊特性
MANIFEST.MF
要想编辑清单文件,需要将希望添加到清单文件中的行放到文本文件中
然后运行:jar cfm jarFileName manifestFileName . . .
要想更新一个已有的JAR文件的清单,则需要将增加的部分放置到一个文本文件中
然后执行以下命令:jar ufm MyArchive.jar manifest-additions.mf
可执行JAR文件
java -jar MyProgram.jar
多版本JAR文件
多版本JAR文件的唯一目的是支持你的某个特定版本的程序或库能够在多个不同的JDK版本上运行。
服务加载器和代理
服务加载器
代理
栈、堆、队列
泛型程序设计
为什么要使用泛型程序设计
类型参数的好处
在Java中增加泛型类之前,泛型程序设计是用继承实现的
类型参数表达形式(示例)
var files = new ArrayList<String>();
ArrayList<String> files = new ArrayList();
ArrayList<String> passwords = new ArrayList<>() { public String get(int n) {return super.get(n).replaceAll(\".\
它们会让你的程序更易读,也更安全
谁想成为泛型程序员
大多数Java程序员都会使用类似ArrayList<String>这样的类型,就好像它们是Java内置的类型一样(就像String[] 数组)。
当然,数组列表比数组更好,因为数组列表可以自动扩展
应用程序员很可能不会编写太多的泛型代码
JDK开发人员已经做出了很大的努力,为所有的集合类型提供了类型参数。
定义简单的泛型类
public class Pair<T> { code to do something}
可以用具体的类型替换类型变量来实例化泛型类型
Pair<String>
泛型类相当于普通类的工厂
Java的泛型类类似于C++的模板类。唯一明显的不同是Java没有特殊的template关键字
两种机制有着本质的区别
泛型方法
示例
示例中的方法是在普通类中定义的,而不是在泛型类中。这是一个泛型方法
类型变量放在修饰符(这里的修饰符就是public static)的后面,并在返回类型的前面
泛型方法可以在普通类中定义,也可以在泛型类中定义
调用实例
类型变量的限定
<T extends BoundingType>
<T extends Comparable & Serializable>
泛型代码和虚拟机
类型擦除
虚拟机没有泛型类型对象
所有对象都属于普通类
无论何时定义一个泛型类型,都会自动提供一个相应的原始类型(raw type)
这个类型的名字就是去掉类型参数后的泛型类型名
转换泛型表达式
编写一个泛型方法调用时,如果擦除了返回类型,编译器会插入强制类型转换
转换泛型方法
类型擦除也会出现在泛型方法中
泛型方法:public static <T extends Comparable> T min(T[] a)
类型擦除后
public static Comparable min(Comparable[] a)
桥方法
对于Java泛型的转换,需要记住以下几个事实
虚拟机中没有泛型,只有普通的类和方法
所有的类型参数都会替换为它们的限定类型
会合成桥方法来保持多态
为保持类型安全性,必要时会插入强制类型转换
调用遗留代码
设计Java泛型时,主要目标是允许泛型代码和遗留代码之间能够互操作
@SupressWarnings(\"unchecked\")
这个注解会关闭对方法中所有代码的检查
限制与局限性
不能用基本类型实例化类型参数
其原因在于类型擦除
运行时类型查询只适用于原始类型
虚拟机中的对象总有一个特定的非泛型类型
不能创建参数化类型的数组
Varargs警告
@SafeVarargs
只能声明为static、final或(Java9中)private的构造器和方法
不能实例化类型变量
最好的解决办法是让调用者提供一个构造器表达式
不能构造泛型数组
泛型类的静态上下文中类型变量无效
不能再静态字段或方法中引用类型变量
禁止使用带有类型变量的静态字段和方法
不能抛出或捕获泛型类的实例
实际上,泛型类扩展Throwable甚至都是不合法的
在异常规范中使用类型变量是允许的
可以取消对检查型异常的检查
Java异常处理的一个基本原则是,必须为所有检查型异常提供一个处理器
不过可以利用泛型取消这个机制
通过使用泛型类,擦除和@SupressWarnings注解,我们就能消除Java类型系统的部分基本限制
注意擦除后的冲突
当泛型类型被擦除后,不允许创建引发冲突的条件
补救的办法是重新命名引发冲突的方法
倘若两个接口类型是同一接口的不同参数化,一个类或类型变量就不能同时作为这两个接口类型的子类
泛型类型的继承规则
无论S和T有什么关系,通常,Pair<S>于Pair<T>都没有任何关系
总是可以将参数化类型转换为一个原始类型
转换成原始类型会产生类型错误
通配符类型
通配符概念
在通配符类型中,允许类型参数发生变化
Pair<? extends Employee>
现在已经有办法区分安全的访问器方法和不安全的更改器方法了
通配符的超类型限定
? super Manager
可以为方法提供参数,但不能使用返回值
如果你是一名库程序员,你一定要熟悉通配符
无限定通配符
Pair<?>
Pair<?>与Pair本质的不同在于
可以用任意Object对象调用原始Pair类的setFirst方法
通配符捕获
通配符不是类型变量
因此,不能在编写代码的时候使用“?”作为一种类型
swapHelper方法的参数T捕获通配符
通配符捕获机制是不可避免的
通配符只有在非常限定的情况下才是合法的
编译器必须能够保证单个确定的类型
反射和泛型
泛型Class类
Class类是泛型的
String.class实际上是一个Class<String>类的对象
Class<T>的以下方法就使用了类型参数
T newInstance()
T cast(Object obj)
T[] getEnumConstants()
Class<? super T> getSuperclass()
Constructor<T> getConstructor(Class... parameterTypes)
Constructor<T> getDeclaredConstructor(Class... parameterTypes)
使用Class<T>参数进行类型匹配
虚拟机中的泛型类型信息
可以使用反射API来确定
这个泛型方法有一个名为T的类型参数
这个类型参数有一个子类型限定,其自身又是一个泛型类型
这个限定类型有一个通配符参数
这个通配符参数有一个超类型限定
这个泛型方法有一个泛型数组参数
你可以重新构造实现者声明的泛型类和方法的所有有关内容
你不会知道对于特定的对象或方法调用会如何解析类型参数
类型字面量
CDI和Guice等注入框架就使用类型字面量来控制泛型类型的注入
相关的类
Class<T>
reflect
Method
TypeVariable
WildcardType
ParameterizedType
GenericArrayType
集合
Java集合框架
集合接口与实现分离
队列
可以在队列的尾部添加元素,在队列头部删除元素
并且可以查找队列中的元素
当需要收集对象时,并按照“先进先出”方式检索对象时就应该使用队列
队列通常有两种实现方式
使用循环数组
使用链表
当在程序中使用队列时,一旦构造了集合,就不需要知道究竟使用了哪种实现
因此,只有在构造集合对象时,才会使用具体的类
循环数组要比链表更高效,因此多数人优先选择循环数组
循环数组是一个有界集合,即容量有限
如果程序中要收集的对象数量没有上限,就最好使用链表来实现
Collection接口
集合类的基本接口是Collection接口
boolean add(E element);
用于向集合中添加元素
Iterator<E> iterator();
iterator方法用于返回一个实现了Iterator接口的对象
使用这个迭代器对象一次访问集合中的元素
迭代器
Iterator接口包含4个方法
E next();
通过反复调用next方法,可以逐个访问集合中的元素
boolean hasNext();
如果到达了集合的末端,next方法将抛出一个NoSuchElementException
因此,需要在调用next之前调用hasNext方法
如果迭代器对象还有多个可以访问的元素,就返回true
void remove();
将会删除上次调用next方法时返回的元素
next方法和remove方法调用之间存在依赖性
如果调用remove之前没有调用next,将是不合法的
如果这样做,将会抛出一个IllegalStateException异常
default void forEachRemain(Consumer<? super E> action);
访问元素的顺序取决于集合类型
虽然可以确保在迭代过程中能够遍历到集合中的所有元素,但是无法预知访问个元素的顺序
查找一个元素的唯一方法是调用next,而在执行查找操作的同时,迭代器的位置就会随之向前移动
可以认为Java迭代器位于两个元素之间。
当调用next时,迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用
Collection接口扩展了Iterable接口
因此,对于标准类库中的任何集合都可以使用“for each”循环
泛型实用方法
Collection接口声明的一部分方法
int size();
boolean isEmpty();
boolean constains(Object obj);
boolean containAll(Collection<?> c);
boolean equals(Object other)
boolean addAll(Colleaction<? extends E> from)
boolean remove(Object obj);
boolean removeAll(Collection<?> c)
void clear()
boolean retainAll(Collection<?> c)
Object[] toArray()
<T> T[] toArray(T[] arrayToFill)
集合框架中的接口
集合有两个基本接口
Colleaction
Map
V get(K key)
List是一个有序集合
元素会增加到容器中的特定位置
可以采用两种方式访问元素
使用迭代器访问
必须顺序的访问元素
使用一个整数索引访问
随机访问
可以按任意顺序访问元素
List接口定义了多个用于随机访问的方法
void remove(int index)
E get(int index)
Set接口
等同于Collection接口
不过其方法的行为有更严谨的定义
set的add方法不允许增加重复的元素
要适当地定义集的equals方法
只要两个集包含同样的元素就认为它们是相等的
而不要求这些元素有同样的顺序
hashCode方法定义要保证包含相同元素的两个集会得到相同的散列码
从概念上讲,并不是所有的集合都是集
Java6引入了接口NavigableSet和NavigableMap
其中包含一些用于搜索和遍历有序集和映射的方法
TreeSet和TreeMap类实现了这些接口
具体集合
链表
从数组中间删除一个元素开销很大,其原因是数组中位于被删除元素之后的所有元素都要向数组前端移动
集合框架中的类
Abstract Collection
AbstractList
AbstractSequentialList
LinkedList
AbstractSet
HashSet
LinkedHashSet
EnumSet
TreeSet
AbstractQueue
PriorityQueue
ArrayQueue
AbstractMap
HashMap
LinkedHashMap
TreeMap
EnumMap
WeekHashMap
IdentityHashMap
数组是在连续的存储位置上存放对象引用,而链表则是将每个对象存放在单独的链接中
每个链接还存放着序列中下一个链接的引用
在Java中,所有的链表实际上都是双向链接的
即每个链接还存放着其前驱的引用
从链表中间删除一个元素是一个很轻松的操作,只需要更新所删除元素周围的链接即可
链表与泛型集合之间有一个重要的区别
链表是一个有序集合,每个对象的位置十分重要
LinkedList.add方法将对象添加到链表的尾部
java.util.List<E>
ListIterator<E>
LinkedList<E>
数组列表
List接口用于描述一个有序集合,并且集合中每个元素的位置很重要
有两种访问元素的协议
一种是通过迭代器
一种是通过get和set方法随机地访问每个元素
不适用于链表
对数组很有用
ArrayList封装了一个动态再分配的对象数组
ArrayList方法是不同步的
散列集
链表和数组允许你根据意愿指定元素的次序
如果不在意元素的顺序,有几种能够快速查找元素的数据结构
其缺点是无法控制元素出现的次序
散列表
为每个对象计算一个整数
对实例对象的实例字段得出的一个整数
不同数据的对象将产生不同的散列码
在Java中,散列表用链表数组实现
每个列表被称为桶(bucket)
要想查找表中对象的位置,就要先计算它的散列码,然后与桶的总数取余,所得到的结果就是保存这个元素的桶的索引
有时候会遇到桶已经被填充的情况
这种现象被称为散列冲突
hash collision
在Java8中,桶满时会从链表变为平衡二叉树
如果散列表太满,就需要再散列
rehashed
集是没有重复元素的元素集合
HashSet类
只有不关心集合中元素的顺序时才使用HashSet
树集
一个有序集合
可以以任意顺序将元素插入到集合中
在对集合进行遍历时,值将自动地按照排序后的顺序呈现
树的排列顺序必须是全序
任意两个元素都必须是可比的,并且只有在两个元素相等时结果才为0
TreeSet<E>
SortedSet<E>
NavigableSet<E>
队列与双端队列
队列允许你高效地在尾部添加元素,并在头部删除元素
双端队列允许在头部和尾部都高效地添加或删除元素
不支持在队列中间添加元素
Queue<E>
Deque<E>
ArrayDeque<E>
优先队列
优先队列中的元素可以按照任意的顺序插入,但会按照有序的顺序进行检索
无论何时调用remove方法,总会获得当前优先队列中最小的元素
优先队列并没有对所有元素进行排序
如果迭代处理这些元素,并不需要对它们进行排序
精巧且高效的数据结构
堆(heap)
堆是一个可以自组织的二叉树,其添加和删除操作可以让最小元素移动到根
而不必花费时间对元素进行排序
优先队列既可以保存实现了Coparable接口的类对象,也可以保存构造器中提供的Comparator对象
典型用法是任务调度
每个任务都有一个优先级,任务以随机顺序添加到队列中
每当启动一个新的任务时,都将优先级最高的任务从队列中删除
java.util.PriorityQueue
映射
基本映射操作
映射用来存放键/值对
如果提供了键,就能够查找到值
Java类库为映射提供了两个通用的实现:HashMap和TreeMap
两个类都实现了Map接口
散列映射对键进行散列,树映射根据键的顺序将元素组织为一个搜索树
与集一样,散列稍微快一些,如果不需要按照有序的顺序访问键,最好选择散列映射
如果映射中没有存储与给定键对应的信息,get将返回null
键必须是唯一的。不能对同一个键存放两个值。如果对同一个键调用两次put方法,第二个值就会取代第一个值。
实际上,put将返回与这个键参数关联的上一个值
remove方法从映射中删除给定键对应的元素。
size方法返回映射中的元素数
要迭代处理映射的键和值,最容易的方法是使用forEach方法
更新映射条目
处理映射的一个难点就是更新映射条目
getOrDefault()
putIfAbsent()
映射视图
集合框架不认为映射本身是一个集合
可以得到映射的视图(view)-- 这是实现了Collection接口或某个子接口的对象
三种视图
键集
KeySet不是HashSet或TreeSet
而是实现了Set接口的另外某个类的对象
Set接口扩展了Collection接口
值集合
键/值对集
枚举一个映射的所有键
如今只需要forEach方法
如果在键集视图上调用迭代器的remove方法,实际上会从映射中删除这个键和与它相关的值
不能向键集视图中添加元素
如果添加一个键而没有添加值也是没有意义的
如果试图调用add方法,它会抛出一个UnsupportedOperationException
弱散列映射
WeakHashMap
当对键的唯一引用来自散列表映射条目时,这个数据结构将于垃圾回收器协同工作一起删除键/值对
WeakHashMap使用弱应用保存键
WeakReference对象将包含另一个对象的引用
WeakHashMap将周期性地检查队列,以便找出新添加的弱引用
一个弱引用进入队列意味着这个键不再被他人使用,并且已经回收
WeakHashMap将删除相关联的映射条目
链接散列集与映射
LinkedHashSet和LinkedHashMap类会记住插入元素项的顺序
这样就可以避免散列表中的项看起来顺序是随机的
在表中插入元素项时,就会并入到双向链表中
链接散列映射可以使用访问顺序而不是插入顺序来迭代处理映射条目
每次调用get或put时,受到影响的项将从当前的位置删除,并放在项链表的尾部
访问顺序对于实现缓存的“最近最少使用”原则十分重要
当在表中找不到元素项而且表已经相当满时,可以得到表的一个迭代器,并删除它枚举的前几个元素
这些项是近期最少使用的几个元素
枚举集与映射
EnumSet是一个枚举类型元素集的高效实现
由于枚举类型只有有限个实例,所以EnumSet内部用位序列实现
如果对应的值在集中,则相应的位被置为1
EnumSet类没有公共的构造器
要使用静态工厂方法构造这个集
EnumMap是一个键类型为枚举类型的映射
它可以直接且高效地实现为一个值数组
标识散列映射
在这个类中,键的散列值不是用hashCode函数计算的,而是用System.identityHashCode方法计算的
视图和包装器
可以使用视图(view)获得其他实现了Collection接口或Map接口的对象
keySet方法返回一个实现了Set接口的类对象,由这个类的方法操纵原映射。这种集合称为视图
小集合
Java 9引入了一些静态方法,可以生成给定元素的集或列表,以及给定键/值对的映射
元素、键或值不能为null
of和ofEntries方法可以生成某些类的对象,这些类对于每个元素会有一个实例变量,或者有一个后备数组提供支持
这些集合对象是不可修改的
of方法是Java9新引入的
Java没有Pair类,有些程序员会使用Map.Entry作为对组(pair),但这种做法并不好
子范围
可以为很多集合建立子范围(subrange)视图
可以使用subList方法来获得这个列表子范围的视图
可以删除整个子范围
元素会自动地从staff列表中清除,并且group2为空
这些方法将返回大于等于from且小于to的所有元素构成的子集
SortedSet<E> headSet(E to)
SortedSet<E> tailSet(E from)
Java6引入的NavigableSet接口允许更多控制这些子范围操作
不可修改的视图
可以使用下面八个方法来获得不可修改视图
Colletions.
unmodifiableCollection
unmodifiableList
unmodifiableSet
unmodifiableSortedSet
unmodifiableNavigableSet
unmodifiableMap
unmodifiableSortedMap
unmodifiableNavigableMap
不可修改的视图并不是集合本身不可更改
仍然可以通过集合的原始引用对集合进行修改,并且仍然可以对集合的元素调用更改器方法
由于视图只是包装了接口而不是具体的集合对象,所以只能访问接口中定义的方法
同步视图
如果从多个线程访问集合,就必须确保集合不会被意外地破坏
Collections类的静态synchronizedMap方法可以将任何一个映射转换成有同步访问方法的Map
现在就可以从多线程访问这个map对象了
检查型视图
只有当另一部分代码调用get方法,并且将结果强制转换为String时,才会出现一个类强制转换异常
checkedList()
关于可选操作的说明
通常,视图有一些限制,可能只读,可能无法改变大小,或者可能只支持删除而不支持插入(如映射的键视图)
如果试图执行不恰当的操作,受限制的视图就会抛出一个UnsupportedOperationException
在集合和迭代器接口的API文档中,许多方法描述为“可选操作”
你应该能够找到一种合适的解决方案,而不必依赖“可选”接口操作这种极端做法
算法
为什么使用泛型算法
泛型集合接口有一个很大的优点,即算法只需要实现一次
排序和混排
Collections类中的sort方法可以对实现了List接口的集合进行排序
如果想按照降序对列表进行排序,可以使用静态的便利方法Collections.reverseOrder(). 这个方法将返回一个比较器,比较器则返回b.compareTo(a)
实际上,可以使用一种归并排序对链表高效地进行排序
Java并不是这样做
它只是将所有的元素转入一个数组,对数组进行排序,然后,再将排序后的序列复制回列表
集合类库中使用的排序算法比快速排序要慢一些,快速排序是通用排序算法的传统选择
归并算法有一个主要的优点:归并是稳定的
它不会改变相等元素的顺序
因为集合不需要实现所有的“可选”方法,因此,所有接受集合参数的方法必须描述什么时候可以安全地将集合传递给算法
下面是有关的术语定义
如果列表支持set方法,则是可修改的(modifiable)
如果列表支持add和remove方法,则是可改变大小的(resizable)
Collections类有一个算法shuffle,其功能与排序正好相反,它会随机地混排列表中元素的顺序。
如果提供的列表没有实现RandomAccess接口,shuffle方法会将元素复制到数组中,然后打乱数组中的元素的顺序,最后再将打乱顺序后的元素复制回列表。
二分查找
如果数组是有序的,可检查中间的元素,查看是否大于要查找的元素
如果是,就在数组的前半部分继续查找
否则,在数组的后半部分继续查找
Collections类的binarySearch就实现了这个算法
集合必须是有序的,否则算法会返回错误的答案
如果binarySearch方法返回一个非负的值,这表示匹配对象的索引
如果返回负值,则表示没有匹配的元素
如果为binarySearch算法提供一个链表,它将自动退化为线性查找
这个方法的时间复杂度是O(a(n)log n)
n是列表的长度,a(n)是访问一个元素的平均时间
简单算法
其他算法还包括有:
将一个列表中的元素复制到另一个列表中
copy()
用一个常量值填充容器
逆置一个列表的元素顺序
reverse()
批操作
coll1.removeAll(coll2);
将从coll1中删除coll2中出现的所有元素
通过使用子范围视图,可以把批操作限制在子列表和子集中
集合与数组的转换
尽管你知道集合中包含的是一个特定类型的对象,但不能使用强制类型转换
实际上,必须使用toArray方法的一个变体,提供一个指定类型而且长度为0的数组。
这样一来,返回的数组就会创建为相同的数组类型
如果愿意,可以构造一个大小正确的数组
编写自己的算法
如果编写自己的算法,应该尽可能地使用接口,而不要使用具体的实现
既然将集合接口作为方法参数和返回类型是个很好的想法,为什么Java类库不一致地遵循这个规则呢?
原因很简单:时间问题
遗留的集合
Hashtable类
经典的Hashtable类与HashMap类的作用一样,实际上,接口也基本相同
与Vector类的方法一样,Hashtable方法也是同步的
如果对与遗留代码的兼容性没有任何要求,就应该使用HashMap
如果需要并发访问,则要使用ConcurrentHashMap
枚举
遗留的集合使用Enumeration接口遍历元素序列
Enumeration接口有两个方法,即hasMoreElements和nextElement
这两个方法完全类似于Iterator接口的hasNext方法和next方法
如果发现遗留的类实现了这个接口,可以使用Collections.list将元素收集到一个ArrayList中
有时还会遇到遗留的方法希望得到枚举参数。
静态方法Collections.enumeration将产生一个枚举对象,枚举集合中的元素
可能会在某些遗留代码中发现枚举,因为在Java1.2的集合框架出现之前,这是唯一可以使用的泛型集合机制。
属性映射
属性映射(property map)是一个特殊类型的映射结构。它有下面3个特性
键和值都是字符串
这个映射可以很容易地保存到文件以及从文件加载
有一个二级表存放默认值
实现属性映射的Java平台类名为Properties
属性映射对于指定程序的配置选项很有用
System.getProperties方法会生成Properties对象描述系统信息
因此,可以使用Map接口的get和set方法。
不过,get方法返回类型为Object,而put方法允许插入任意的对象。
所以最好坚持使用处理字符串而不是对象的getProperty和setProperty方法
Properties类有两种提供默认值的机制
只要找到一个字符串的值,可以指定一个默认值,这样当键不存在时就会自动使用这个默认值
如果觉得在每个getProperty调用中指定默认值太过麻烦,可以把所有默认值都放在一个二级属性映射中,并在主属性映射的构造器中提供这个二级映射
属性是没有层次结构的简单表格
通常会用类似window.main.color、window.main.title等引入一个假想的层次结构
不过Properties类没有方法来帮助组织这样一个层次结构
如果要存储复杂的配置信息,就应该改为使用Preferences类
栈
Stack类扩展了Vector类
java.util.Stack<E>
E push(E item)
将item压入栈中,并返回item
E pop()
弹出并返回栈顶的item
如果栈为空,不要调用这个方法
E peek()
返回栈顶元素,不弹出
位集
Java平台的BitSet类用于存储一个位序列
它不是数学上的集,如果称为位向量或位数组可能更为合适
由于位集将位包装在字节里,所以使用位集要比使用Boolean对象的ArrayList高效得多
BitSet类提供了一个便于读取、设置或重置各个位的接口
java.util.BitSet
图形用户界面程序设计
Java用户界面工具包简史
AWT
Abstract Window Toolkit
使用AWT构建GUI应用程序看起来没有原生的Windows或Macintosh应用那么漂亮,也没有提供那些平台用户所期望的功能
IFC
Internet Foundation Class
它将按钮、菜单等用户界面元素绘制在空白窗口上
底层窗口系统所需的唯一功能就是能够显示一个窗口,并在这个窗口中绘制
Swing
是不基于对等元素的GUI工具包的官方名字
Swing不是完全替代AWT,而是构建在AWT架构之上
Swing只是提供了更加强大的用户界面组件
显示窗体
在Java中,顶层窗口称为窗体
AWT库中有一个称为Frame的类,用于描述这个顶层窗口
这个类的Swing版本名为JFrame,它扩展了Frame类
JFrame是极少数几个不绘制在画布上的Swing组件之一
窗口的修饰部件(按钮、标题栏、图标等)由用户的窗口系统绘制,而不是由Swing绘制
绝大多数Swing组件类都以“J”开头,例如“JButton”、“JFrame”等。
将Swing和AWT组件混合在一起使用将会导致视觉和行为的不一致
创建窗体
Swing类位于javax.swing包中
包名javax表示这是一个Java扩展包,而不是核心包
从1.2版本开始,每个Java实现中都包含这个类
在每个Swing程序中,有两个技术问题需要强调
所有的Swing组件必须由事件分派线程配置,这是控制线程,它将鼠标点击和按键等事件传递给用户接口组件
定义用户关闭这个窗体时的响应动作
很多Swing程序并没有在事件分派线程中初始化用户界面
原先完全可以接受在主线程中完成初始化
遗憾的是,随着Swing组件变得越来越复杂,JDK开发人员无法保证这种方式的安全性
为了显示窗体,main方法需要强调用窗体的setVisible方法
退出main方法并没有终止程序,终止的只有主线程
事件分派线程会保持程序处于激活状态,直到关闭窗体或调用System.exit方法终止程序
窗体属性
JFrame类本身只包含若干个改变窗体外观的方法
当然,利用继承的魔力,大多数处理窗体大小和位置的方法都来自JFrame的各个超类
其中最重要的有以下的方法
setLocation方法和setBounds方法用于设置窗体的位置
setIconImage方法用于告诉窗口系统在标题栏、任务切换窗口等位置显示哪个图标
setTitle方法用于改变标题栏的文字
setResizable利用一个boolean值确定是否允许用户改变窗体的大小
一对获取/设置方法被称为属性
属性有一个名和一个类型
将get或set之后的第一个字母改为小写字母就可以得到相应的属性名
java.awt
Component
Window
Frame
Toolkit
java.swing.ImageIcon
在组件中显示信息
简介
可以将消息字符串直接绘制在窗体中,但这并不是一种好的编程习惯
在Java中,窗体实际上设计为组件的容器,如菜单栏和其他用户界面元素
在JFrame中有四层窗格
其中根窗格、层级窗格和玻璃窗格人们并不关心
它们用来组织菜单栏和内容窗格以及实现观感
Swing程序员最关心的是内容窗格
添加到窗体的所有组件都会自动添加到内容窗格中
要在一个组件上绘制,需要定义一个扩展JComponent的类,并覆盖其中的paintComponent方法
paintComponent方法有一个Graphics类型的参数,Graphics类型的参数,Graphics对象保存着用于绘制图像和文本的一组设置
在Java中,所有的绘制都必须通过Graphics对象完成,其中包含了绘制图案、图像和文本的方法
无论何种原因,只要窗口需要重新绘制,事件处理器就会通知组件,从而引发执行所有组件的paintCompent方法
绝对不要自己调用paintComponent方法
只要应用的某个部分需要重新绘制,就会自动调用这个方法,不要人为地干预这个自动的处理过程
如果需要强制重新绘制屏幕,需要调用repaint方法而不是paintComponent方法
repaint方法将引发采用适当配置的Graphics对象调用所有组件的paintComponent方法
paintComponent方法只有一个Graphics类型的参数
Graphics对象的度量单位是像素
坐标(0,0)指示所绘制组件的左上角
Graphics类有很多绘制方法,显示文本是一种特殊的绘制
覆盖getPreferredSize方法,返回一个有首选宽度和高度的Dimension类对象
在窗体中填入一个或多个组件时,如果你只想要使用它们的首选大小,可以调用pack方法而不是setSize方法
处理2D图形
要想使用Java2D库绘制图形,需要获得Graphics2D类的一个对象
这个类是Graphics类的子类
Java2D库采用面向对象的方式组织几何图形
要想绘制一个图形,首先要创建一个实现了Shape接口的类的对象,然后调用Graphics2D类的draw方法
Java2D库针对像素采用的是浮点坐标,而不是整数坐标
内部计算都采用单精度float
有时候程序员处理float并不太方便,这是因为Java程序设计语言在将double值转换成float值时必须进行强制类型转换
2D库的设计者决定为每个图形类提供两个版本
一个是为那些想节省空间的程序员提供的版本,要使用float类型的坐标
另一个是为那些懒惰的程序员提供的版本,会使用double类型的坐标
直接使用Double图形类可以完全避免处理float类型的值
不过如果需要构造上千个图形对象,还是应该考虑使用Float类,这样节省存储空间
Rectangle2D和Ellipse2D类都是由公共超类RectangularShape继承来的
比较常用的方法有getWidth、getHeight、getCenterX、getCenterY等
构造椭圆时,通常知道椭圆的中心,宽和高,而不是外接矩形的四角顶点
setFrameFromCenter方法使用中心点,但还要给出四个顶点中的一个
使用颜色
可以用一种颜色填充一个封闭图形的内部
只需要将调用draw替换为调用fill
要想用多种颜色绘制,就需要选择一个颜色、绘制图形、再选择另外一种颜色、再绘制图形
fill方法会在右侧和下方少绘制一个像素
Color类用于定义颜色
要想设置背景颜色,需要使用Component类中的setBackground方法
使用字体
要想使用某种字体绘制字符,必须首先创建一个Font类的对象
需要指定字体名、字体风格和字体大小
在Font构造器中,提供字体名的位置也可以给出逻辑字体名
可以使用deriveFont方法得到所需大小的字体
接下来,将字符串居中,而不绘制在任意位置
需要知道字符串占据的宽和高的像素数
这两个值取决于下面三个因素
使用的字体
字符串
绘制字体的设备
要想得到表示屏幕设备字体属性的对象,需要调用Graphics2D类中的getFontRenderContext方法
显示图像
可以使用ImageIcon类从文件读取图像
现在变量image包含一个封装了图像数据的对象的引用
相关的方法
java.awt.Graphics
开发工具
集成开发环境(IDE)
Eclipse
IntelliJ IDEA
VS code(实际上是编辑器+扩展)
Notepad++ + jdk
Subline Text3 + jdk
项目管理/构建工具
Maven
Gradle
代码管理工具
SVN
Git
JDK
jdk1.8
jdk11
API Docs
JDK 11 Docs
框架
web层框架
Spring MVC
Spring
MyBatis
JQWEB
服务层框架
EJB
持久层框架
Hibernate
JPA
TopLink
编程规约
关键术语
简单性
Java语法是C++语法的“纯净”版本
面向对象
Java与C++的主要不同点在于多重继承
分布式
Java有一个丰富的例程库,用于处理像HTTP和FTP之类的TCP/IP协议
健壮性
Java与C/C++最大的不同在于Java采用的指针模型可以消除重写内存和损坏数据的可能性
安全性
不可信代码在一个沙箱环境中执行,在这里它不会影响主系统
体系结构中立
Java虚拟机、.class文件
可移植性
不仅程序是可移植的,Java API往往也比原生API质量更高
解释型
Java解释器可以在任何移植了解释器的机器上直接执行Java字节码
高性能
现在的即时编译器已经相当出色,可以与传统编译器相媲美
多线程
多线程可以带来更快的交互响应和实时行为
动态性
从很多方面来看,Java比C/C++相比更加具有动态性
0 条评论
下一页
为你推荐
查看更多
抱歉,暂无相关内容