Android 与 Java
一,资源
基础教学:lecture, video, lecturer: Matt Stoker
Java教学:http://www.runoob.com/java/java-intro.html【菜鸟教程,非常适合复习】
Ref: JAVA 学到什么水平就可以转战 Android 了
Ref:设计模式菜鸟教程
Ref:Java教程
Ref: Java code 实例
二,Android开发初探
第一课:
观后感,应该总结一套控件代码:控件代码收集。
看上去不错de样子。后记,貌似没有用了,有了Material Design。
Ref: chenyangcun/MaterialDesignExample
Ref: 加载图片助手:https://www.fresco-cn.org/
第二课:
现代Android开发:一些建议。
关于Material Design,控件章节做介绍。
相关视频讲解:谷歌Material Design设计语言详解
三,课程体系
本次学习基于廖雪峰 的 Java课程.
以及 Spring系类:https://spring.io/projects, such as Spring Boot
具体详见:[Spring] What is Spring Framework
/* continue */
Java初步复习
这里先复习下Java语言基础知识,耗时五小时。
参考:Java 教程
Java分为三个体系
- JavaSE (J2SE) (Java2 Platform Standard Edition,java平台标准版)
- JavaEE (J2EE) (Java 2 Platform,Enterprise Edition,java平台企业版)
- JavaME (J2ME) (Java 2 Platform Micro Edition,java平台微型版)。
基本语法
- 主方法入口:所有的Java 程序由public static void main(String []args)方法开始执行。
- # javac Hello.java --> Hello.class.
- # java Hello.class执行.
Java标识符
- 所有的标识符都应该以字母(A-Z或者a-z),美元符($)、或者下划线(_)开始
- 首字符之后可以是字母(A-Z或者a-z),美元符($)、下划线(_)或数字的任何字符组合
Java修饰符
像其他语言一样,Java可以使用修饰符来修饰类中方法和属性。主要有两类修饰符:
- 访问控制修饰符 : default, public , protected, private
- 非访问控制修饰符 : final, abstract, strictfp (用于限制浮点计算并在执行浮点变量操作时确保每个平台上的结果相同)
Java变量
Java中主要有如下几种类型的变量
- 局部变量
- 类变量(静态变量)
- 成员变量(非静态变量)
Java数组
Java枚举
Java关键字
下面列出了Java保留字。这些保留字不能用于常量、变量、和任何标识符的名称。
类别 | 关键字 | 说明 |
---|---|---|
访问控制 | private | 私有的 |
protected | 受保护的 | |
public | 公共的 | |
类、方法和变量修饰符 | abstract | 声明抽象 |
class | 类 | |
extends | 扩允,继承 | |
final | 最终值,不可改变的 | |
implements | 实现(接口) | |
interface | 接口 | |
native | 本地,原生方法(非Java实现) | |
new | 新,创建 | |
static | 静态 | |
strictfp | 严格,精准 | |
synchronized | 线程,同步 | |
transient | 短暂 | |
volatile | 易失 | |
程序控制语句 | break | 跳出循环 |
case | 定义一个值以供switch选择 | |
continue | 继续 | |
default | 默认 | |
do | 运行 | |
else | 否则 | |
for | 循环 | |
if | 如果 | |
instanceof | 实例 | |
return | 返回 | |
switch | 根据值选择执行 | |
while | 循环 | |
错误处理 | assert | 断言表达式是否为真 |
catch | 捕捉异常 | |
finally | 有没有异常都执行 | |
throw | 抛出一个异常对象 | |
throws | 声明一个异常可能被抛出 | |
try | 捕获异常 | |
包相关 | import | 引入 |
package | 包 | |
基本类型 | boolean | 布尔型 |
byte | 字节型 | |
char | 字符型 | |
double | 双精度浮点 | |
float | 单精度浮点 | |
int | 整型 | |
long | 长整型 | |
short | 短整型 | |
null | 空 | |
变量引用 | super | 父类,超类 |
this | 本类 | |
void | 无返回值 | |
保留关键字 | goto | 是关键字,但不能使用 |
const | 是关键字,但不能使用 |
Java注释
Java 空行
继承
被继承的类称为超类(super class),派生类称为子类(subclass)。
接口
/* implement */
Java进阶复习
大数据实战总结
Consumer.java
[1]
Kafka的各个版本:https://archive.apache.org/dist/kafka/
import kafka.utils.ShutdownableThread;
public class Consumer extends ShutdownableThread {
...
}
ShutdownableThread是一个可以循环执行某个方法(doWork方法)的线程,也提供了shutdown方法同步等待该线程真正运行结束,代码比较简单。利用了CountDownLatch来阻塞调用shutdown的线程,待doWork真正执行结束时,再唤醒其他阻塞的线程。
@Override
public void doWork() { consumer.subscribe(Collections.singletonList(this.topic));
ConsumerRecords<Integer, String> records = consumer.poll(Duration.ofSeconds(1));
debug_index++;
System.out.println("\n[" + debug_index + "] 消费到消息数:" + records.count());
if (records.count() > 0) {
for (ConsumerRecord<Integer, String> record : records) {
LOG.warn("[" + debug_index + "]Received message: (" + record.key() + ", " + record.value() + ") at offset " + record.offset());
String value = record.value();
/* 将消息进行下一步处理 */
}
}
System.out.println("[" + debug_index + "] nothing.");
}
[2]
如何导入Kafka包?
Ref: Kafka Using Java. Part 1.
Ref: Kafka Using Java. Part 2.
Ref: Configuring IntelliJ IDEA for Kafka
import kafka.utils.ShutdownableThread;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
原文链接:https://blog.csdn.net/hwssg/article/details/48623745
首先创建一个java project,网上很多说需创建maven工程,本人经过测验,发现maven工程和普通的java project均可调用。
需要注意,工程所采用的jar包,可以在相应版本的kafka安装文件夹的lib目录下引用,不同版本的jar包可能不通用,
如果出现java.lang.NoClassDefFoundError: scala/reflect/ClassManifest的报错,可能是由于jar包不匹配引起的。
通过Maven,需要在依赖中设置.
[3]
Java的模板类.
# 原型
public class KafkaConsumer<K, V> implements Consumer<K, V>
# 使用案例
consumer = new KafkaConsumer<>(props);
语法特性 (详细)
1. 基本数据类型
- 内置数据类型
byte,节省空间。
System.out.println("基本类型:byte 二进制位数:" + Byte.SIZE);
System.out.println("包装类:java. .Byte");
System.out.println("最小值:Byte.MIN_VALUE=" + Byte.MIN_VALUE);
System.out.println("最大值:Byte.MAX_VALUE=" + Byte.MAX_VALUE);
- 引用类型
对象、数组都是引用数据类型。
所有引用类型的默认值都是null。
- Java 常量
final double PI = 3.1415927;
- 自动类型转换
低 ------------------------------------> 高 byte,short,char—> int —> long—> float —> double
- 强制类型转换
- 隐含强制类型转换
Ref: 例子不错的链接。
2. 变量类型
3. 修饰符
- 访问修饰符
我们可以通过以下表来说明访问权限:
- 非访问修饰符
static变量
static方法
final变量,宏
final方法,不能被子类修改
final类,不能被继承其任何特性
abstract方法
abstract类,包含若干个抽象方法则必为抽象类,或抽象类可以不包含抽象方法
synchronized 修饰符
// Synchronized 锁定的是对象而非函数或代码。
public synchronized void showDetails(){
.......
}
transient 修饰符
对应的变量加上transient关键字,那么,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
参考:https://www.cnblogs.com/widow/p/3952827.html
volatile 修饰符
Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。
这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。
使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。
由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。
4. 运算符
instanceof 运算符
String name = "James";
boolean result = name instanceof String; // 由于 name 是 String 类型,所以返回真
5. Java 循环结构 - for, while 及 do...while
Java 增强 for 循环,为方便数组访问。
public class Test {
public static void main(String args[]){
int [] numbers = {10, 20, 30, 40, 50};
for(int x : numbers ){
System.out.print( x );
System.out.print(",");
}
}
}
6. 分支结构 - if...else/switch
# 浮点数
Math.abs(x-0.1) < 0.00001 # 是否指向同一对象
s1 == s2 # 判断内容是否相等,要小心s1=null,会报错
s1 != null && s1.equals("hello")
"hello".equals(s1)
7. 数字(Number & Math 类)
- Number类
所有的 包装类(Integer、Long、Byte、Double、Float、Short)都是抽象类 Number 的子类。
当 x 被赋为整型值时,
(1) 由于x是一个对象,所以编译器要对x进行装箱。
(2) 然后,为了使x能进行加运算,所以要对x进行拆箱。
简单一点说,
(1) "装箱" 就是自动将基本数据类型转换为包装器类型;
(2) "拆箱" 就是自动将包装器类型转换为基本数据类型。
Integer x = 5;
x = x + 10;
- Math 类
该类提供了独立的 "变量" 和 "方法"
public class Test {
public static void main (String []args)
{
System.out.println("90 度的正弦值:" + Math.sin(Math.PI/2));
System.out.println("0度的余弦值:" + Math.cos(0));
System.out.println("60度的正切值:" + Math.tan(Math.PI/3));
System.out.println("1的反正切值: " + Math.atan(1));
System.out.println("π/2的角度值:" + Math.toDegrees(Math.PI/2));
System.out.println(Math.PI);
}
}
8. 字符串
- Char类
Character ch = new Character('a');
提供了一些好用的方法:
1 |
isLetter() 是否是一个字母 |
2 |
isDigit() 是否是一个数字字符 |
3 |
isWhitespace() 是否是一个空格 |
4 |
isUpperCase() 是否是大写字母 |
5 |
isLowerCase() 是否是小写字母 |
6 |
toUpperCase() 指定字母的大写形式 |
7 |
toLowerCase() 指定字母的小写形式 |
8 |
toString() 返回字符的字符串形式,字符串的长度仅为1 |
Java String 类
1 char charAt(int index)
返回指定索引处的 char 值。
2 int compareTo(Object o)
把这个字符串和另一个对象比较。
3 int compareTo(String anotherString)
按字典顺序比较两个字符串。
4 int compareToIgnoreCase(String str)
按字典顺序比较两个字符串,不考虑大小写。
5 String concat(String str)
将指定字符串连接到此字符串的结尾。
6 boolean contentEquals(StringBuffer sb)
当且仅当字符串与指定的StringButter有相同顺序的字符时候返回真。
7 static String copyValueOf(char[] data)
返回指定数组中表示该字符序列的 String。
8 static String copyValueOf(char[] data, int offset, int count)
返回指定数组中表示该字符序列的 String。
9 boolean endsWith(String suffix)
测试此字符串是否以指定的后缀结束。
10 boolean equals(Object anObject)
将此字符串与指定的对象比较。
11 boolean equalsIgnoreCase(String anotherString)
将此 String 与另一个 String 比较,不考虑大小写。
12 byte[] getBytes()
使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
13 byte[] getBytes(String charsetName)
使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
14 void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
将字符从此字符串复制到目标字符数组。
15 int hashCode()
返回此字符串的哈希码。
16 int indexOf(int ch)
返回指定字符在此字符串中第一次出现处的索引。
17 int indexOf(int ch, int fromIndex)
返回在此字符串中第一次出现指定字符处的索引,从指定的索引开始搜索。
18 int indexOf(String str)
返回指定子字符串在此字符串中第一次出现处的索引。
19 int indexOf(String str, int fromIndex)
返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。
20 String intern()
返回字符串对象的规范化表示形式。
21 int lastIndexOf(int ch)
返回指定字符在此字符串中最后一次出现处的索引。
22 int lastIndexOf(int ch, int fromIndex)
返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索。
23 int lastIndexOf(String str)
返回指定子字符串在此字符串中最右边出现处的索引。
24 int lastIndexOf(String str, int fromIndex)
返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。
25 int length()
返回此字符串的长度。
26 boolean matches(String regex)
告知此字符串是否匹配给定的正则表达式。
27 boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len)
测试两个字符串区域是否相等。
28 boolean regionMatches(int toffset, String other, int ooffset, int len)
测试两个字符串区域是否相等。
29 String replace(char oldChar, char newChar)
返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
30 String replaceAll(String regex, String replacement
使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
31 String replaceFirst(String regex, String replacement)
使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
32 String[] split(String regex)
根据给定正则表达式的匹配拆分此字符串。
33 String[] split(String regex, int limit)
根据匹配给定的正则表达式来拆分此字符串。
34 boolean startsWith(String prefix)
测试此字符串是否以指定的前缀开始。
35 boolean startsWith(String prefix, int toffset)
测试此字符串从指定索引开始的子字符串是否以指定前缀开始。
36 CharSequence subSequence(int beginIndex, int endIndex)
返回一个新的字符序列,它是此序列的一个子序列。
37 String substring(int beginIndex)
返回一个新的字符串,它是此字符串的一个子字符串。
38 String substring(int beginIndex, int endIndex)
返回一个新字符串,它是此字符串的一个子字符串。
39 char[] toCharArray()
将此字符串转换为一个新的字符数组。
40 String toLowerCase()
使用默认语言环境的规则将此 String 中的所有字符都转换为小写。
41 String toLowerCase(Locale locale)
使用给定 Locale 的规则将此 String 中的所有字符都转换为小写。
42 String toString()
返回此对象本身(它已经是一个字符串!)。
43 String toUpperCase()
使用默认语言环境的规则将此 String 中的所有字符都转换为大写。
44 String toUpperCase(Locale locale)
使用给定 Locale 的规则将此 String 中的所有字符都转换为大写。
45 String trim()
返回字符串的副本,忽略前导空白和尾部空白。
46 static String valueOf(primitive data type x)
返回给定data type类型x参数的字符串表示形式。
Java StringBuffer 和 StringBuilder 类
当对字符串进行修改:
- StringBuilder:更快! # 可以高效拼接字符串,预分配缓冲区;支持链式操作.
- StringBuffer:线程安全!
自己特有的方案,其他与String相似。
序号 | 方法描述 |
---|---|
1 | public StringBuffer append(String s) 将指定的字符串追加到此字符序列。 |
2 | public StringBuffer reverse() 将此字符序列用其反转形式取代。 |
3 | public delete(int start, int end) 移除此序列的子字符串中的字符。 |
4 | public insert(int offset, int i) 将 int 参数的字符串表示形式插入此序列中。 |
5 |
replace(int start, int end, String str) 使用给定 String 中的字符替换此序列的子字符串中的字符。 |
9. 数组
参考阅读,值传递:为什么 Java 只有值传递,但 C# 既有值传递,又有引用传递,这种语言设计有哪些好处?
- 初始化
二维数组的每一个子列不需要长度相同.
// 初始化方法
dataType[] arrayRefVar = new dataType[arraySize];
dataType[] arrayRefVar = {value0, value1, ..., valuek}; String str[][] = new String[3][4];
- 遍历
for (int n: ns) {
...
}
或者,ArrayToString() 快速打印数组的所有内容.
对于二维数组,需要 Arrays.deepToString(),否则打印的是子列的地址.
- Arrays 类
java.util.Arrays 类是 JDK 提供的一个工具类,用来处理数组的各种方法,而且每个方法基本上都是静态方法,能直接通过类名Arrays调用。
序号 | 方法和说明 |
---|---|
1 |
public static int binarySearch(Object[] a, Object key) 用二分查找算法在给定数组中搜索给定值的对象(Byte,Int,double等)。数组在调用前必须排序好的。如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。 |
2 |
public static boolean equals(long[] a, long[] a2) 换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。 |
3 |
public static void fill(int[] a, int val) 将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。 |
4 |
public static void sort(Object[] a) 对指定对象数组根据其元素的自然顺序进行升序排列。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。 |
More: JDK1.8源码(四)——java.util.Arrays 类
import java.util.Arrays;
public class main
{
public static void main(String[] args)
{
int []arr=new int[10];
Arrays.fill(arr, 5);
System.out.println(Arrays.toString(arr)); int [] nums = {6,1,5,9,3,5,7,2,3,9};
Arrays.sort(nums);
System.out.println(Arrays.toString(nums)); # 排序后,修改了数组本身 int [] nums1 = {6,1,5,9,3,5,7,2,3,9};
int [] nums2 = {6,2,5,9,3,5,7,2,3,9};
int [] nums3 = {6,1,5,9,3,5,7,2,3,9};
System.out.println(Arrays.equals(nums1, nums2));
System.out.println(Arrays.equals(nums1, nums3)); int xb=Arrays.binarySearch(nums, 5);
System.out.println(xb);
}
}
10. Java 日期时间
import java.util.Date; public class DateDemo {
public static void main(String args[]) {
// 初始化 Date 对象
Date date = new Date(); // 使用 toString() 函数显示日期时间
System.out.println(date.toString());
}
}
11. Java 正则表达式
参见:[PyProj] Think in Python : Regex
java.util.regex 包主要包括以下三个类:
-
Pattern 类:
pattern 对象是一个正则表达式的编译表示。Pattern 类没有公共构造方法。要创建一个 Pattern 对象,你必须首先调用其公共静态编译方法,它返回一个 Pattern 对象。该方法接受一个正则表达式作为它的第一个参数。
-
Matcher 类:
Matcher 对象是对输入字符串进行解释和匹配操作的引擎。与Pattern 类一样,Matcher 也没有公共构造方法。你需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象。
-
PatternSyntaxException:
PatternSyntaxException 是一个非强制异常类,它表示一个正则表达式模式中的语法错误。
-
Pattern 类:
import java.util.regex.*; class RegexExample1{
public static void main(String args[]){ // 参数
String content = "I am noob " + "from runoob.com.";
String pattern = ".*runoob.*"; boolean isMatch = Pattern.matches(pattern, content); // <----
System.out.println("字符串中是否包含了 'runoob' 子字符串? " + isMatch);
}
}
More详见原文。
12. Java 方法/函数
- 可变参数
public class VarargsDemo {
public static void main(String args[]) {
// 调用可变参数的方法
printMax(34, 3, 3, 2, 56.5);
printMax(new double[]{1, 2, 3});
} public static void printMax( double... numbers) { //numbers已经变成了一个数组
if (numbers.length == 0) {
System.out.println("No argument passed");
return;
} double result = numbers[0]; for (int i = 1; i < numbers.length; i++){
if (numbers[i] > result) {
result = numbers[i];
}
}
System.out.println("The max value is " + result);
}
}
- finalize() 方法
public class FinalizationDemo {
public static void main(String[] args) {
Cake c1 = new Cake(1);
Cake c2 = new Cake(2);
Cake c3 = new Cake(3); c2 = c3 = null;
System.gc(); //调用Java垃圾收集器,当垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法。
}
}
class Cake extends Object { //finalize()是在java.lang.Object里定义的
private int id;
public Cake(int id) {
this.id = id;
System.out.println("Cake Object " + id + "is created");
} protected void finalize() throws java.lang.Throwable {
super.finalize();
System.out.println("Cake Object " + id + "is disposed");
}
}
Java 允许定义这样的方法,它在对象被垃圾收集器析构(回收)之前调用,这个方法叫做 finalize( ),它用来清除回收对象。
注意:但有一种JNI(Java Native Interface)调用non-Java程序(C或C++),finalize()的工作就是回收这部分的内存。【在自动回收内存垃圾之外的使用情况】
13. Java 流(Stream)、文件(File)和IO
控制台的流
(1) 控制台"输入"
import java.util.Scanner; public class Hello { public static void main(String[] args) { Scanner scanner = new Scanner(System.in);
String name = scanner.nextline(); # 读字符串
int age = scanner.nextInt(); # 读整数
} }
格式化输出,类似c++.
(2) next 方法
import java.util.Scanner; public class ScannerDemo {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
// 从键盘接收数据 // next方式接收字符串
System.out.println("next方式接收:");
// 判断是否还有输入
if (scan.hasNext()) {
String str1 = scan.next();
System.out.println("输入的数据为:" + str1);
}
scan.close();
}
}
(3) next() 与 nextLine() 区别
next():
- 一定要读取到有效字符后才可以结束输入。
- 对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
- 只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
- next() 不能得到带有空格的字符串。
nextLine():
- 以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
- 可以获得空白。
读写文件
(1) 通过 FileInputStream "获得"文件流.
# 直接获得
InputStream input = new FileInputStream("C:/java/hello");
# 间接获得:File --> InputStream
File f = new File("C:/java/hello");
InputStream input = new FileInputStream(f);
序号 | 方法及描述 |
---|---|
1 |
public void close() throws IOException{} 关闭此文件输入流并释放与此流有关的所有系统资源。抛出IOException异常。 |
2 |
protected void finalize() throws IOException {} 这个方法清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出IOException异常。 |
3 |
public int read(int r) throws IOException{} 这个方法从 InputStream 对象读取指定字节的数据。返回为整数值。返回下一字节数据,如果已经到结尾则返回-1。 |
4 |
public int read(byte[] r) throws IOException{} 这个方法从输入流读取r.length长度的字节。返回读取的字节数。如果是文件结尾则返回-1。 |
5 |
public int available() throws IOException{} 返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取的字节数。返回一个整数值。 |
其他的输入流:
(2) 通过 FileOutputStream "输出"文件流.
OutputStream output = new FileOutputStream("C:/java/hello") File f = new File("C:/java/hello");
OutputStream output = new FileOutputStream(f);
序号 | 方法及描述 |
---|---|
1 |
public void close() throws IOException{} 关闭此文件输入流并释放与此流有关的所有系统资源。抛出IOException异常。 |
2 |
protected void finalize() throws IOException {} 这个方法清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出IOException异常。 |
3 |
public void write(int w) throws IOException{} 这个方法把指定的字节写到输出流中。 |
4 |
public void write(byte[] w) 把指定数组中w.length长度的字节写到OutputStream中。 |
其他的输出流
以上是字节流; --> 例子,解决乱码问题
下面是字符流;--> 关于文件和I/O的类
(3) 直接使用 File 操控文件.
import java.io.*;
public class FileRead{
public static void main(String args[])throws IOException{
File file = new File("Hello1.txt");
// 创建文件
file.createNewFile();
// creates a FileWriter Object
FileWriter writer = new FileWriter(file);
// 向文件写入内容
writer.write("This\n is\n an\n example\n");
writer.flush();
writer.close();
// 创建 FileReader 对象
FileReader fr = new FileReader(file);
char [] a = new char[50];
fr.read(a); // 读取数组中的内容
for(char c : a)
System.out.print(c); // 一个一个打印字符
fr.close();
}
}
Java 异常处理
一,Exception 是什么
Ref: 链接这里。[菜鸟教程,exception]
程序运行时的常见问题:
- 用户输入了非法数据。
- 要打开的文件不存在。
- 网络通信时连接中断,或者JVM内存溢出。
总结可归纳为三类:
-
检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
- IOException, NumberFormatException
-
运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。# 不是必须要求捕获.
- NullPointerException, IllegalArgumentException
-
错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
- OutOfMemoryError, NoClassDefFoundError, *Error
-
检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
二,异常类型
- 异常类
在 java.lang 包的非检查性异常类
异常 | 描述 |
---|---|
ArithmeticException | 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。 |
ArrayIndexOutOfBoundsException | 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 |
ArrayStoreException | 试图将错误类型的对象存储到一个对象数组时抛出的异常。 |
ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 |
IllegalMonitorStateException | 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。 |
IllegalStateException | 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 |
IllegalThreadStateException | 线程没有处于请求操作所要求的适当状态时抛出的异常。 |
IndexOutOfBoundsException | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 |
NegativeArraySizeException | 如果应用程序试图创建大小为负的数组,则抛出该异常。 |
NullPointerException | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常 |
NumberFormatException | 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
SecurityException | 由安全管理器抛出的异常,指示存在安全侵犯。 |
StringIndexOutOfBoundsException | 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。 |
UnsupportedOperationException | 当不支持请求的操作时,抛出该异常。 |
在 java.lang 包中的检查性异常类
异常 | 描述 |
---|---|
ClassNotFoundException | 应用程序试图加载类时,找不到相应的类,抛出该异常。 |
CloneNotSupportedException | 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。 |
IllegalAccessException | 拒绝访问一个类的时候,抛出该异常。 |
InstantiationException | 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。 |
InterruptedException | 一个线程被另一个线程中断,抛出该异常。 |
NoSuchFieldException | 请求的变量不存在 |
NoSuchMethodException | 请求的方法不存在 |
异常方法
下面的列表是 Throwable 类的主要方法:
序号 | 方法及说明 |
---|---|
1 |
public String getMessage() 返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。 |
2 |
public Throwable getCause() 返回一个Throwable 对象代表异常原因。 |
3 |
public String toString() 使用getMessage()的结果返回类的串级名字。 |
4 |
public void printStackTrace() 打印toString()结果和栈层次到System.err,即错误输出流。 |
5 |
public StackTraceElement [] getStackTrace() 返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。 |
6 |
public Throwable fillInStackTrace() 用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。 |
三,申明 Exception
- try...catch 捕获异常
try {
file = new FileInputStream(fileName);
x = (byte) file.read();
} catch (IOException i){
i.printStackTrace();
return -;
} catch (FileNotFoundException f){
f.printStackTrace();
return -;
}
throws/throw 关键字
(1) 为何用
说到处理异常,我们当然会想到 try catch finally
在java中我们会对异常的处理有更高的认识 我们会学习 throw throws等更好的处理异常。
手动抛出异常,但是有时候有些错误在jvm看来不是错误,比如说
int age = 0;
age = -100;
System.out.println(age);
所以,我们需要自己手动引发异常,这就是throw的作用。
int age = 0;
age = -100;
if(age < 0)
{
Exception e = new Exception(); // ----> 创建异常对象
throw e; // ----> 抛出异常
}
System.out.println(age);
(2) 怎么用
异常被抛到这个方法的调用者那里,这个有点像下属处理不了的问题就交到上司手里一样,称为回避异常。
但是这使得调用这个方法就有了危险,因为谁也不知道这个方法什么时候会丢一个什么样的异常给调用者。
所以,在定义方法时,就需要在方法头部分使用throws来声明这个方法可能回避的异常。
/**
* fun()可能会抛出两种异常
*/
void fun()throws IOException,SQLException
{
...
} try {
fun();
/**
* 调用fun()的时候,就知道该如何做准备来处理异常了
*/
} catch (IOException e){
...
} catch (SQLException e){
...
} finally {
...
}
(3) suppressed exception
在这里,RuntimeException不再抛出,因为finally中抛出的exception将其覆盖,也就是"被屏蔽"掉了.
public static void main(String[] args) {
try {
process1("");
} catch (Exception e) {
System.out.println("catched");
throw new RuntimeException(e);
} finally {
System.out.println("finally");
throw new NullPointerException();
}
} static void process1(String s) {
throw new IllegalArgumentException(e);
}
四,自定义异常
- 定义
在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点。
- 所有异常都必须是 Throwable 的子类。
- 如果希望写一个检查性异常类,则需要继承 Exception 类。
- 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。
# 其他exception 都继承这个自定义的 BaseException
public class BaseException extends RuntimeException {
public BaseException() {
super();
} public BaseException(String message, Throwable cause) {
super(message, cause);
} public BaseException(String message) {
super(message);
} public BaseException(Throwable cause) {
super(cause);
}
} public class UserNotFoundException extends BaseException { } public class LoginFailedException extends BaseException { }
- 演示
简单实例,帮助理解:
(a). 自定义异常类
当出现异常(取出钱多于余额)时,所缺乏的钱.
// 文件名InsufficientFundsException.java
import java.io.*; //自定义异常类,继承Exception类
public class InsufficientFundsException extends Exception
{
//此处的amount用来储存当出现异常(取出钱多于余额时)所缺乏的钱
private double amount;
public InsufficientFundsException(double amount)
{
this.amount = amount;
}
public double getAmount()
{
return amount;
}
}
(b). 工具类
在类的其中一个函数中,应用到了该 Exception.
// 文件名称 CheckingAccount.java
import java.io.*; //此类模拟银行账户
public class CheckingAccount
{
//balance为余额,number为卡号
private double balance;
private int number;
public CheckingAccount(int number)
{
this.number = number;
}
//方法:存钱
public void deposit(double amount)
{
balance += amount;
}
//方法:取钱
public void withdraw(double amount) throws InsufficientFundsException
{
if(amount <= balance)
{
balance -= amount;
}
else
{
double needs = amount - balance;
throw new InsufficientFundsException(needs); // --> 02.抛出了自定义异常
}
}
//方法:返回余额
public double getBalance()
{
return balance;
}
//方法:返回卡号
public int getNumber()
{
return number;
}
}
(c). 使用main函数。
处理异常.
//文件名称 BankDemo.java
public class BankDemo
{
public static void main(String [] args)
{
CheckingAccount c = new CheckingAccount(101);
System.out.println("Depositing $500...");
c.deposit(500.00);
try {
System.out.println("\nWithdrawing $100...");
c.withdraw(100.00);
System.out.println("\nWithdrawing $600...");
c.withdraw(600.00); // --> 01.透支,需要抛出异常
/**
* 上述函数可能会抛出异常,那么下面就做好处理的准备。
* catch是了解withdraw会抛出怎样异常的!
*/
}catch(InsufficientFundsException e) { // --> 03. 这里捕获自定义异常
System.out.println("Sorry, but you are short $" + e.getAmount());
e.printStackTrace();
}
}
}
调试
一,断言
二,Logging
三,Commons Logging
四,单元测试
/* implement */
Java 面向对象
三大特征
1. Java 对象和类
一个典型的类.
// package语句
// import语句
// 类定义 public class Puppy{ int puppyAge;
static int cls_var = 10 public Puppy(String name){
System.out.println("小狗的名字是 : " + name );
} public void setAge( int age ){
puppyAge = age;
} public int getAge( ){
System.out.println("小狗的年龄为 : " + puppyAge );
return puppyAge;
} public static void main(String []args){ Puppy myPuppy = new Puppy( "tommy" );
myPuppy.setAge( 2 );
myPuppy.getAge( );
System.out.println("变量值 : " + myPuppy.puppyAge );
}
}
源文件声明规则
- 一个源文件中只能有一个public类
- 一个源文件可以有多个非public类
- import语句和package语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。
- ref: Java安装相关路径查找
面向对象的三个基本特征是:封装、继承、多态.
2. Java 封装
- JavaBean
类似于c#的set, get属性。
/* 文件名: EncapTest.java */
public class EncapTest{ private String name;
private String idNum;
private int age; public int getAge(){
return age;
} public String getName(){
return name;
} public String getIdNum(){
return idNum;
} public void setAge( int newAge){
age = newAge;
} public void setName(String newName){
name = newName;
} public void setIdNum( String newId){
idNum = newId;
}
} /* F文件名 : RunEncap.java */
public class RunEncap{
public static void main(String args[]){
EncapTest encap = new EncapTest();
encap.setName("James");
encap.setAge(20);
encap.setIdNum("12343ms"); System.out.print("Name : " + encap.getName() + Age : " + encap.getAge());
}
}
面向对象的三个基本特征是:封装、继承、多态.
3. Java 继承
- 单继承,多继承
所有的类都是继承于 java.lang.Object。
"单继承":
class 子类 extends 父类 {
...
}
通过接口"多继承":
public interface A {
public void eat();
public void sleep();
} public interface B {
public void show();
} public class C implements A,B {
...
}
- super 与 this 关键字
class Animal {
void eat() {
System.out.println("animal : eat");
}
} class Dog extends Animal {
void eat() {
System.out.println("dog : eat");
}
void eatTest() {
this.eat(); // this 调用自己的方法
super.eat(); // super 调用父类方法
}
} public class Test {
public static void main(String[] args) {
Animal a = new Animal();
a.eat();
Dog d = new Dog();
d.eatTest();
}
}
- final关键字
定义为不能继承的,即最终类;
Or 用于修饰方法,该方法不能被子类重写。
final class 类名 {//类体}
- 构造方法
构造方法顺序:
class SuperClass {
private int n;
SuperClass(){
System.out.println("SuperClass()");
}
SuperClass(int n) {
System.out.println("SuperClass(int n)");
this.n = n;
}
}
class SubClass extends SuperClass{
private int n;
SubClass(){
super(300); // 给自己的父类传参
System.out.println("SubClass");
}
public SubClass(int n){
System.out.println("SubClass(int n):"+n);
this.n = n;
}
}
public class TestSuperSub{
public static void main (String args[]){
SubClass sc = new SubClass();
SubClass sc2 = new SubClass(200);
}
}
父类的构造方法如果是有参数的,不是默认的,那么子类的构造函数在通过super调用父类构造函数时,要考虑进去这些.
至少,此时子类通过默认的super是不行了,必须手动调用.
- 向上转型 upcasting
用父类指向子类对象:Person ps = new Student();
- 向下转型 downcasting
(1) 注意一
Student n = null; System.out.println(n instanceof Student); //false
(2) 注意二
Person p = new Person("Xiao Ming");
Student s = new Student("Xiao Hong"); Person ps = s;
// 明眼人明白,这里的ps的实际类型,其实原本还是Student. Student s2 = (Student) ps;
s2.run();
判断原本是不是某个类型就比较重要,可以使用:instanceof(...)
if (p instanceof Student) {
Student s2 = (Student) p;
s2.run();
}
面向对象的三个基本特征是:封装、继承、多态.
"多态" 的实现方式:
方式一:重写;
方式二:抽象类和抽象方法;
方式三:接口.
4. Java 重写(Override) 与 重载(Overload)
- Override 【父类与子类的多态性表现】
public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象 a.move(); // 执行 Animal 类的方法
b.move(); // 执行 Dog 类的方法
b.bark();
}
}
- Overload 【一个类的多态性表现】
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
5. Java 抽象类
From:Java中抽象类和接口的用法和区别
(一)语法层次
在某种程度上来说,接口是抽象类的特殊化。
(二)设计层次
1、 抽象层次不同
[抽象类]:
对类抽象;对整个类整体进行抽象,包括属性、行为。
[接口 ]:
对行为的抽象;对类局部(行为)进行抽象。
2、 跨域不同
[抽象类]:
所跨域的是具有相似特点的类,
[接口 ]:
可以跨域不同的类。
3、 设计层次不同
[抽象类]:
它是自下而上来设计的,我们要先知道子类才能抽象出父类。
比如我们只有一个猫类在这里,如果你抽象成一个动物类,是不是设计有点儿过度?我们起码要有两个动物类,猫、狗在这里,我们在抽象他们的共同点形成动物抽象类吧!所以说抽象类往往都是通过重构而来的!
[接口 ]:
不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,至于什么子类、什么时候怎么实现它一概不知。
但是接口就不同,比如说飞,我们根本就不知道会有什么东西来实现这个飞接口,怎么实现也不得而知,我们要做的就是事前定义好飞的行为接口。所以说抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。
6. Java 接口
- 接口的继承
一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。
下面的Sports接口被Hockey和Football接口继承:
// 文件名: Sports.java
public interface Sports
{
public void setHomeTeam(String name);
public void setVisitingTeam(String name);
}
//----------------------------------------------------------------------------------------------
// 文件名: Football.java
public interface Football extends Sports
{
public void homeTeamScored(int points);
public void visitingTeamScored(int points);
public void endOfQuarter(int quarter);
} // 文件名: Hockey.java
public interface Hockey extends Sports // Hockey接口自己声明了四个方法,从Sports接口继承了两个方法
{
public void homeGoalScored();
public void visitingGoalScored();
public void endOfPeriod(int period);
public void overtimePeriod(int ot);
}
- 标记接口
Ref: 第37条:用标记接口定义类型
Ref: 为什么这些java接口没有抽象方法?浅谈Java标记接口
在jdk的源码中,存在这样的一些接口,他们不包含任何的(抽象)方法,但是却广泛的存在。
这种接口我们称之为Mark Interface,也就是标记接口。
这些接口呢,我们不用来实现任何的方法,他们的作用就是当某个类实现这个接口的时候,我们就认为这个类拥有了这个接口标记的某种功能了。
好处一:这样针对于不同的List采取不同的遍历形式,可以让遍历的速度更快。
例如:RandomAccess
import java.util.List;
import java.util.RandomAccess; public class SourceLearning
{
public void iteratorElements(List<String> list) // 这里的list可能只是override
{
if (list instanceof RandomAccess)
{
for (int i = 0, size = list.size(); i < size; i++)
{
String element = list.get(i);
}
}
else
{
for (String element : list)
{
// ... ...
}
}
}
}
判断是否实现RandomAccess接口,就可以决定采取哪种遍历的方式。体现了:该接口只是个"标记"作用.
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//...
}
// 而LinkedList就没有实现该标记接口
public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
//...
}
好处二:优雅地表示对象是否具备这个能力
例如:Cloneable
标记该对象的是否拥有克隆的能力。
public interface Cloneable {
... ...
}
例如:java.io.Serializable
这个接口是用来标记类是否支持序列化的。所谓的序列化就是将对象的各种信息转化成可以存储或者传输的一种形式.
/**
* Write the specified object to the ObjectOutputStream. The class of the
* object, the signature of the class, and the values of the non-transient
* and non-static fields of the class and all of its supertypes are
* written. Default serialization for a class can be overridden using the
* writeObject and the readObject methods. Objects referenced by this
* object are written transitively so that a complete equivalent graph of
* objects can be reconstructed by an ObjectInputStream.
*
* <p>Exceptions are thrown for problems with the OutputStream and for
* classes that should not be serialized. All exceptions are fatal to the
* OutputStream, which is left in an indeterminate state, and it is up to
* the caller to ignore or recover the stream state.
*
* @throws InvalidClassException Something is wrong with a class used by
* serialization.
* @throws NotSerializableException Some object to be serialized does not
* implement the java.io.Serializable interface.
* @throws IOException Any exception thrown by the underlying
* OutputStream.
*/
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
作用域
1. 何为包
在一个.java文件中可以一个public类和多个非public类,如果要将这些类组织在一个包当中,则在.java文件中除注释以外的第一行使用关键字package即可实现。
定义一个包,覆盖了 {
public类
多个非public类
}
(1) 为了尽量使包名保持唯一性,包名通常采用小写、按倒写互联网址的形式进行定义。如:com.hank.www表示包文件放置的文件路径为com/hank/www。
(2) 在进行命名包时,应该避免使用与系统发生冲突的名字,如java.lang、java.swing 等。
(3) 位于同一个包的类,可以访问"包作用域"的字段和方法:不用public, protected, private修饰的字段和方法就是"包作用域"。
2. 创建包
[Create file1: define interface]
创建了一个叫做animals的包:
/* 文件名: Animal.java */
package animals;
interface Animal {public void eat();
public void travel();
}
[Create file2: implement interface]
MammalInt.java 文件代码:在同一个包中加入该接口的实现。
package animals;
/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{ public void eat(){
System.out.println("Mammal eats");
} public void travel(){
System.out.println("Mammal travels");
} public int noOfLegs(){
return 0;
} public static void main(String args[]){
MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}
放在文件夹animals里,然后命令运行:
$ java animals/MammalInt
Mammal eats
Mammal travel
3. 导入包
- import 关键字
Boss.java 文件代码:
package payroll; public class Boss
{
public void payEmployee(Employee e) //payroll 包已经包含了 Employee 类,不用实用功能全名:payroll.Employee
{
e.mailCheck();
}
}
- 包的使用和设置
通常,一个公司使用它互联网域名的颠倒形式来作为它的包名。
例如:互联网域名是 runoob.com,所有的包名都以 com.runoob 开头。包名中的每一个部分对应一个子目录。
(1). 该目录下常见文件Runoob.java
....\com\runoob\test\Runoob.java
// 文件名: Runoob.java
package com.runoob.test;
public class Runoob { }
class Google { }
(2). 编译
$javac -d . Runoob.java
得到.class文件
.\com\runoob\test\Runoob.class
.\com\runoob\test\Google.class
放置其它位置,但包名要保持一致!
<path-one>\sources\com\runoob\test\Runoob.java
<path-two>\classes\com\runoob\test\Google.class
|-- class path ---|--- package ---|
这样,你可以将你的类目录分享给其他的编程人员,而不用透露自己的源码。
(3). 设置 CLASSPATH 系统变量
一个 class path 可能会包含好几个路径,多路径应该用分隔符分开。
默认情况下,编译器和 JVM 查找当前目录。
JAR 文件按包含 Java 平台相关的类,所以他们的目录默认放在了 class path 中。
用下面的命令显示当前的CLASSPATH变量: Windows 平台(DOS 命令行下):C:\> set CLASSPATH
UNIX 平台(Bourne shell 下):# echo $CLASSPATH
删除当前CLASSPATH变量内容:
Windows 平台(DOS 命令行下):C:\> set CLASSPATH=
UNIX 平台(Bourne shell 下):# unset CLASSPATH; export CLASSPATH
设置CLASSPATH变量: Windows 平台(DOS 命令行下): C:\> set CLASSPATH=C:\users\jack\java\classes
UNIX 平台(Bourne shell 下):# CLASSPATH=/home/jack/java/classes; export CLASSPATH
Java 高级教程
Java 数据结构
一,Java 集合框架
Java 集合框架提供了一套性能优良,使用方便的接口和类,java集合框架位于java.util包中, 所以当使用集合框架的时候需要进行导包。
Ref:Java中的集合框架
更详细的图,参考:java集合框架综述
以下俩结构,只是作为补充.
Enumeration
import java.util.Vector;
import java.util.Enumeration; public class EnumerationTester { public static void main(String args[]) {
Enumeration<String> days;
Vector<String> dayNames = new Vector<String>();
dayNames.add("Sunday");
dayNames.add("Monday");
dayNames.add("Tuesday");
dayNames.add("Wednesday");
dayNames.add("Thursday");
dayNames.add("Friday");
dayNames.add("Saturday");
days = dayNames.elements();
while (days.hasMoreElements()){
System.out.println(days.nextElement());
}
}
}
- BitSet
理解:bits2.and(bits1); bits2与bits1做运算,bits2发生变化。
import java.util.BitSet; public class BitSetDemo { public static void main(String args[]) {
BitSet bits1 = new BitSet(16);
BitSet bits2 = new BitSet(16); // set some bits
for(int i=0; i<16; i++) {
if((i%2) == 0) bits1.set(i);
if((i%5) != 0) bits2.set(i);
}
System.out.println("Initial pattern in bits1: ");
System.out.println(bits1);
System.out.println("\nInitial pattern in bits2: ");
System.out.println(bits2); // AND bits
bits2.and(bits1);
System.out.println("\nbits2 AND bits1: ");
System.out.println(bits2); // OR bits
bits2.or(bits1);
System.out.println("\nbits2 OR bits1: ");
System.out.println(bits2); // XOR bits
bits2.xor(bits1);
System.out.println("\nbits2 XOR bits1: ");
System.out.println(bits2);
}
}
二, 链表结构
List<E> --> ArrayList, LinkedList
- Vector(旧接口)
能根据需要动态的变化,和ArrayList和相似,区别是什么?
Ref: Arraylist与Vector的区别
- 白色的部分是需要去了解的,
- 黄色部分是我们要去重点了解的,不但要知道怎么去用,至少还需要读一次源码。
- 绿色部分内容已经很少用了,但在面试题中有可能会问到
- Vector vs ArrayList
标准答案:
1、Vector是线程安全的,ArrayList不是线程安全的。
2、ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。
所以:
无一例外,只要是关键性的操作,方法前面都加了synchronized关键字,来保证线程的安全性。
- 单线程的环境中,Vector效率要差很多。
- 多线程环境不允许用ArrayList,需要做处理。
- ArrayList vs LinkedList
- ArrayList可以直接通过数组下标得到元素;
- LinkedList则需要根据所给的下标从头部或尾部开始往下标的位置依次获得下一个元素或上一个元素。
- "遍历" 链表
# 1
get(int index);
# 2
List<String> list = ...
for (Iterator<String> it = list.iterator(); it.hasNext(); )
{
String s = it.next();
} # 3. 除了数组外,实现了Iterable接口的都可以使用foreach
for (String s: list) {
...
}
- List和Array转换
List --> Array
List<Integer> list = new ArrayList<>(); list.add(1);
list.add(2);
list.add(3); Integer[] array = list.toArray(new Integer[3]);
Array --> List
Integer[] array = {1,2,3};
List<Integer> arrayList = new ArrayList<>(Arrays.asList(array));
- contains支持"自定义类"
实现 equals方法,此处略.
三,Map结构
- HashMap
(1) Dictionary类已经过时了。在实际开发中,你可以实现Map接口来获取键/值的存储功能。
import java.util.*; public class CollectionsDemo { public static void main(String[] args) {
Map m1 = new HashMap();
m1.put("Zara", "8");
m1.put("Mahnaz", "31");
m1.put("Ayan", "12");
m1.put("Daisy", "14");
System.out.println();
System.out.println(" Map Elements");
System.out.print("\t" + m1);
}
}
遍历key and value.
Map<String, Integer> map = ...
for ( Map.Entry<String, Integer> entry : map.entrySet() ) { String key = entry.getKey();
Integer value = entry.getValue();
}
(2) Java Hashtable/Hashmap 类
hashmap |
线程不安全 |
允许有null的键和值 |
效率高一点 |
方法不是Synchronize的要提供外同步 |
有containsvalue和containsKey方法 |
HashMap 是Java1.2 引进的Map interface 的一个实现 |
HashMap是Hashtable的轻量级实现 |
hashtable |
线程安全 |
不允许有null的键和值 |
效率稍低 |
方法是是Synchronize的 |
有contains方法方法 |
Hashtable 继承于Dictionary 类 |
Hashtable 比HashMap 要旧 |
- SortedMap
自定义排序算法:
Map<String, Integer> map = new Tree<>(new Comparator<String>() { public int compare(String o1, Stringo2) {
return -o1.compareTo(o2);
}
});
四,Properties结构
Ref: Java中Properties类的操作
从hashtable派生,但不建议使用这些方法,还是使用自己本身的.
在Java中,其配置文件常为.properties文件,格式为文本文件,文件的内容的格式是“键=值”的格式,文本注释信息可以用"#"来注释。
它提供了几个主要的方法:
1. getProperty ( String key),用指定的键在此属性列表中搜索属性。也就是通过参数 key ,得到 key 所对应的 value。
2. load ( InputStream inStream),从输入流中读取属性列表(键和元素对)。通过对指定的文件(比如说上面的 test.properties 文件)进行装载来获取该文件中的所有键 - 值对。以供 getProperty ( String key) 来搜索。
3. setProperty ( String key, String value) ,调用 Hashtable 的方法 put 。他通过调用基类的put方法来设置 键 - 值对。
4. store ( OutputStream out, String comments),以适合使用 load 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。与 load 方法相反,该方法将键 - 值对写入到指定的文件中去。
5. clear (),清除所有装载的 键 - 值对。该方法在基类中提供。
总结:
五,Set结构
六,Queue结构
Queue
/* implement */
PriorityQueue
/* implement */
Deque
/* implement */
七,Stack结构
import java.util.*; public class StackDemo { static void showpush(Stack<Integer> st, int a) {
st.push(new Integer(a));
System.out.println("push(" + a + ")");
System.out.println("stack: " + st);
} static void showpop(Stack<Integer> st) {
System.out.print("pop -> ");
Integer a = (Integer) st.pop();
System.out.println(a);
System.out.println("stack: " + st);
} public static void main(String args[]) {
Stack<Integer> st = new Stack<Integer>();
System.out.println("stack: " + st); showpush(st, 42); // push
showpush(st, 66);
showpush(st, 99);
showpop(st); // pop
showpop(st);
showpop(st); try {
showpop(st);
} catch (EmptyStackException e) {
System.out.println("empty stack");
}
}
}
八,Iterator
九,Collections工具类
这里其实是一个总结.
boolean addAll(Collection<? super T> c, T... elements) List<T> emptyList()
Map<K,V> emptyMap()
Set<T> emptySet() Set<T> singleTon(T o)
List<T> singletonList(T o)
Map<K, V> singletonMap(K key, V value) void sort(List<T> list)
void sort(List<T> list, Comparator<? super T> c) void shuffle(List<?> list) List<T> unmodifiableList(List<? extends T> list)
Set<T> unmodifiableSet(Set<? extends T> set)
Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m) List<T> synchronizedList(List<T> list)
Set<T> synchronizedSet(Set<T> s)
Map<T> synchronizedMap(Map<K,V> m)
JVM的反射
一、JVM的反射
- 是什么
JVM内部创建的Class类型。
class Class { }
通过Class实例获得class信息的方法称为“反射”。
JVM总是动态加载class, 可以在运行期根据条件控制加载class。
- 怎么用
简单的使用案例
# sol 1, 直接获得
Class cls = String.class; # sol 2, 间接获得
String s = "Hello";
Class cls = s.getClass();
# sol 3
Class cls = Class.forName("java.lang.String")
提取某个class的元类型Class,通过反射查看类的信息。
二、访问字段
通过更为原始本质的方式操控 field字段。
Integer n = new Integer(123);
Class cls = n.getClass();
Field[] fs = cls.getFields(); for (Field f : fs) {
f.getName(); // field name
f.getType(); // field type
f.getModifiers(); // modifiers
} Field f = cls.getDeclaredField("value");
f.set(n, 456); // 相当于 n.value = 456
三、获取方法
类似访问字段,这里只是使用了invoke()。
Integer n = new Integer(123);
Class cls = n.getClass();
Method m = cls.getMethod("compareTo", Integer.class);
int r = (Integer) m.invoke(n, 456);
四、调用构造方法
五、获取继承关系
六、关于注解
Java 泛型
一,理解泛型
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,定义一个模板。
假定我们有这样一个需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?
答案:可以使用 Java 泛型。
- 泛型方法
(a) 定义了一个方法 printArray(),能处理不同的类型。
public class GenericMethodTest
{
// 泛型方法 printArray
public static <E> void printArray( E[] inputArray )
{
// 输出数组元素
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
} public static void main( String args[] )
{
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { 'H', 'E', 'L', 'L', 'O' }; System.out.println( "整型数组元素为:" );
printArray( intArray ); // 传递一个整型数组 System.out.println( "\n双精度型数组元素为:" );
printArray( doubleArray ); // 传递一个双精度型数组 System.out.println( "\n字符型数组元素为:" );
printArray( charArray ); // 传递一个字符型数组
}
}
(b) 参数中两个是泛型.
public class GenericMethod {
public <K,V> void f(K k,V v) {
System.out.println(k.getClass().getSimpleName());
System.out.println(v.getClass().getSimpleName());
} public static void main(String[] args) {
GenericMethod gm = new GenericMethod();
gm.f(new Integer(0), new String("generic"));
}
}
代码输出:
Integer
String
(c) 可以对模板对应的类型做一点限制,例如:必须得是可比较的对象。
对模板类型做了限制:T extends Comparable<T>
public class MaximumTest
{
// 比较三个值并返回最大值
public static <> T maximum(T x, T y, T z)
{
T max = x;
if ( y.compareTo( max ) > 0 ){
max = y;
}
if ( z.compareTo( max ) > 0 ){
max = z;
}
return max;
}
public static void main( String args[] )
{
System.out.printf( "%d, %d 和 %d 中最大的数为 %d\n\n", 3, 4, 5, maximum( 3, 4, 5 ) );
System.out.printf( "%.1f, %.1f 和 %.1f 中最大的数为 %.1f\n\n", 6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) );
System.out.printf( "%s, %s 和 %s 中最大的数为 %s\n", "pear", "apple", "orange", maximum( "pear", "apple", "orange" ) );
}
}
- 泛型类
public class Box<T> { private T t; public void add(T t) {
this.t = t;
} public T get() {
return t;
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>(); integerBox.add(new Integer(10));
stringBox.add(new String("菜鸟教程")); System.out.printf("整型值为 :%d\n\n", integerBox.get());
System.out.printf("字符串为 :%s\n", stringBox.get());
}
}
二,类型擦除
- "伪"泛型
Java 的泛型使用了类型擦除机制,这个引来了很大的争议,以至于 Java 的泛型功能受到限制,只能说是"伪泛型"。
什么叫类型擦除呢?简单的说就是,类型参数只存在于编译期,在运行时,Java 的虚拟机 ( JVM ) 并不知道泛型的存在。
例如:
public class ErasedTypeEquivalence {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
}
}
两个不同的 ArrayList
:ArrayList<Integer>
和 ArrayList<String>
。
但是通过比较它们的 Class
对象,上面的代码输出是 true
。
这说明在 JVM 看来它们是同一个类。而在 C++、C# 这些支持真泛型的语言中,它们就是不同的类。
简单的理解:相当于所有出现 T
的地方都用 Object
替换.
擦除带来的问题
既然是模板,不知道了具体的类型,如何确定该模板持有的方法呢?
class HasF {
public void f() { // 这里虽然定义了f()这个函数,但调用处搞不清楚.
System.out.println("HasF.f()");
}
} //--------------------------------------------------------------
public class Manipulator<T> { // <---- 这里需要之后改进 -_-b
private T obj; public Manipulator(T obj) {
this.obj = obj;
} public void manipulate() {
obj.f(); //无法编译 找不到符号 f(),编译器不清楚有没有,没有根据.
} public static void main(String[] args) {
HasF hasF = new HasF();
Manipulator<HasF> manipulator = new Manipulator<>(hasF);
manipulator.manipulate(); }
}
解决方案:继承一下HasF,父类确保有这个方法.
class Manipulator2<T extends HasF> {
private T obj;
public Manipulator2(T x) { obj = x; }
public void manipulate() { obj.f(); }
}
- 类型擦除的补偿
[较难理解]
因为是"伪泛型",任何在运行期需要知道确切类型的代码都无法工作。
一是由于类型擦除;
二是由于编译器不知道 T
是否有默认的构造器。
public class Erased<T> {
private final int SIZE = 100;
public static void f(Object arg) {
if(arg instanceof T) {} // Error
T var = new T(); // Error
T[] array = new T[SIZE]; // Error
T[] array = (T)new Object[SIZE]; // Unchecked warning
}
}
(1) 一种解决的办法是传递一个工厂对象并且通过它创建新的实例。
interface FactoryI<T> {
T create();
}
class Foo2<T> {
private T x;
public <F extends FactoryI<T>> Foo2(F factory) {
x = factory.create(); // 通过factory实现了new的功能
}
// ...
} //-----------------------------------------------------------
class IntegerFactory implements FactoryI<Integer> {
public Integer create() {
return new Integer(0);
}
}
class Widget {
public static class Factory implements FactoryI<Widget> {
public Widget create() {
return new Widget();
}
}
} //-----------------------------------------------------------
public class FactoryConstraint {
public static void main(String[] args) {
new Foo2<Integer>(new IntegerFactory());
new Foo2<Widget>(new Widget.Factory());
}
}
(2) 另一种解决的方法是利用模板设计模式:
abstract class GenericWithCreate<T> {
final T element;
GenericWithCreate() { element = create(); }
abstract T create();
}
class X {}
class Creator extends GenericWithCreate<X> {
X create() { return new X(); }
void f() {
System.out.println(element.getClass().getSimpleName());
}
}
public class CreatorGeneric {
public static void main(String[] args) {
Creator c = new Creator();
c.f();
}
}
- 一些补充
public class IntPair extends Pair<Integer> { } IntPair ip = new IntPair(1, 2); Class<IntPair> clazz = IntPair.class;
Type t = clazz.getGenericSuperclass();
// Type的实际类型就是如下所示的 ParameterizedType
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) t;
Type[] types = pt.getActualTypeArguments();
Type firstType = types[0];
Class<?> typeClass = (Class<?>) firstType; System.out.println(typeClass); // Integer
}
尽量理解,最后之总结如下:
二,泛型数组
较难,具体参见:Java 泛型总结(二):泛型与数组
三,通配符的使用
- 不支持协变
(a) 数组是"有协变".
继承关系:Fruit --> Apple --> Jonathan
创建了一个 Apple
数组并把它赋给 Fruit
数组的引用。这是有意义的,Apple
是 Fruit
的子类,一个 Apple
对象也是一种 Fruit
对象,所以一个 Apple
数组也是一种 Fruit
的数组。这称作数组的协变.
However,Java 把数组设计为协变的,对此是有争议的,有人认为这是一种缺陷。
尽管 Apple[]
可以 “向上转型” 为 Fruit[]
,但数组元素的实际类型还是 Apple
,我们只能向数组中放入 Apple
或者 Apple
的子类。在上面的代码中,向数组中放入了 Fruit
对象和 Orange
对象。对于编译器来说,这是可以通过编译的,但是在运行时期,JVM 能够知道数组的实际类型是 Apple[]
,所以当其它对象加入数组的时候就会抛出异常。
虽然是用fruit表示,但赋值时只能使用Apple,Jonathan.
Fruit[] fruit = new Apple[10];
fruit[0] = new Apple(); // OK
fruit[1] = new Jonathan(); // OK
(b) 但泛型没有
当涉及到泛型时, 尽管 Apple
是 Fruit
的子类型,但是 ArrayList<Apple>
不是 ArrayList<Fruit>
的子类型,泛型不支持协变。
// Compile Error: incompatible types:
ArrayList<Fruit> flist = new ArrayList<Apple>(); // 引来问题 --->
如果我们确实需要建立这种 “向上转型” 的关系怎么办呢?这就需要通配符来发挥作用了。
- 上边界限定通配符
(1) "问号" 确定上边界
利用 <? extends Fruit>
形式的通配符,可以实现泛型的向上转型.
List<? extends Fruit> flist = new ArrayList<Apple>(); // 解决问题 <---
现在,我们并不关心这个实际的类型到底是什么,反正是 Fruit
的子类型,Fruit
是它的上边界。
(2) 失去赋值的能力
在上面的代码中,向 flist
中添加任何对象,无论是 Apple
还是 Orange
甚至是 Fruit
对象,编译器都不允许,唯一可以添加的是 null
。
所以,如果做了泛型的向上转型,那么我们也就失去了向这个 List 添加任何对象的能力,即使是 Object
也不行。
public class Holder<T> {
private T value;
public Holder() {}
public Holder(T val) { value = val; }
public void set(T val) { value = val; }
public T get() { return value; }
public boolean equals(Object obj) {
return value.equals(obj);
}
public static void main(String[] args) {
Holder<Apple> Apple = new Holder<Apple>(new Apple());
Apple d = Apple.get();
Apple.set(d);
// Holder<Fruit> Fruit = Apple; // Cannot upcast
Holder<? extends Fruit> fruit = Apple; // OK
Fruit p = fruit.get();
d = (Apple)fruit.get(); // Returns ‘Object’
try {
Orange c = (Orange)fruit.get(); // No warning
} catch(Exception e) {
System.out.println(e);
}// fruit
的类型是Holder<? extends Fruit>
,所以set()
方法不会接受任何对象的添加。
// fruit.set(new Apple()); // Cannot call set()
// fruit.set(new Fruit()); // Cannot call set()
System.out.println(fruit.equals(d)); // OK
}
} /* Output: (Sample)
java.lang.ClassCastException: Apple cannot be cast to Orange
- 下边界限定通配符
(1) 目的:使用这种形式的通配符,我们就可以 "传递对象"了。
参数 apples
的类型是 List<? super Apple>
,它表示某种类型的 List,这个类型是 Apple
的基类型。
也就是说,我们不知道实际类型是什么,但是这个类型肯定是 Apple
的父类型。
public class SuperTypeWildcards {
static void writeTo(List<? super Apple> apples) {
apples.add(new Apple());
apples.add(new Jonathan());
// apples.add(new Fruit()); // Error,Fruit是Apple的父类!
}
}
(2) 一个综合性的例子:
如何向泛型类型中 “写入” ( 传递对象给方法参数) 以及如何从泛型类型中 “读取” ( 从方法中返回对象 )。
public class Collections {
public static <T> void copy(List<? super T> dest, List<? extends T> src)
{
for (int i=0; i<src.size(); i++) {
dest.set(i, src.get(i));
}
}
}
- 无边界通配符
List<?>
,也就是没有任何限定。不做任何限制,跟不用类型参数的 List
有什么区别呢?
List<?> list
表示 list
是持有某种特定类型的 List,但是不知道具体是哪种类型。那么我们可以向其中添加对象吗?当然不可以!
因为并不知道实际是哪种类型,所以不能添加任何类型,这是不安全的。
单独的 List list
,也就是没有传入泛型参数,表示这个 list 持有的元素的类型是 Object
,因此可以添加任何类型的对象,只不过编译器会有警告信息。
【如此说来,貌似没什么大用】
- 总结
通配符的使用可以对泛型参数做出某些限制,使代码更安全,对于上边界和下边界限定的通配符总结如下:
- 使用
List<? extends C> list
这种形式,表示 list 可以引用一个ArrayList
( 或者其它 List 的 子类 ) 的对象,这个对象包含的元素类型是C
的子类型 ( 包含C
本身)的一种。 - 使用
List<? super C> list
这种形式,表示 list 可以引用一个ArrayList
( 或者其它 List 的 子类 ) 的对象,这个对象包含的元素就类型是C
的超类型 ( 包含C
本身 ) 的一种。
- 使用
大多数情况下泛型的使用比较简单,但是如果自己编写支持泛型的代码需要对泛型有深入的了解。这几篇文章介绍了泛型的基本用法、类型擦除、泛型数组以及通配符的使用,涵盖了最常用的要点,泛型的总结就写到这里。
Java 序列化
- 优缺点比较
优点 | 缺点 | |
Serializable | 使用方便,可序列化所有类 | 速度慢,占空间 |
Protostuff | 速度快,基于protobuf | 需静态编译 |
一个类的对象要想序列化成功,必须满足两个条件:
- 该类必须实现 java.io.Serializable 对象。
- 该类的"所有属性"必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。
- 序列化:SerializeDemo.java
ObjectOutputStream 类用来序列化一个对象,如下:实例化了一个 Employee 对象,并将该对象序列化到一个文件中。
该程序执行后,就创建了一个名为 employee.ser 文件。
import java.io.*; public class SerializeDemo
{
public static void main(String [] args)
{
Employee e = new Employee();
e.name = "Reyan Ali";
e.address = "Phokka Kuan, Ambehta Peer";
e.SSN = 11122333;
e.number = 101;
try {
File OutputStream fileOut = new FileOutputStream("/tmp/employee.ser"); # 序列化文件约定这么起名字
Object OutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(e);
out.close();
fileOut.close();
System.out.printf("Serialized data is saved in /tmp/employee.ser");
} catch(IOException i) {
i.printStackTrace();
}
}
}
- 反序列化:DeserializeDemo.java
/tmp/employee.ser 存储了 Employee 对象。
import java.io.*; public class DeserializeDemo
{
public static void main(String [] args)
{
Employee e = null;
try {
File InputStream fileIn = new FileInputStream("/tmp/employee.ser");
Object InputStream in = new ObjectInputStream(fileIn);
e = (Employee) in.readObject();
in.close();
fileIn.close();
} catch(IOException i) {
i.printStackTrace();
return;
} catch (ClassNotFoundException c) {
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
System.out.println("Deserialized Employee...");
System.out.println("Name: " + e.name);
System.out.println("Address: " + e.address);
System.out.println("SSN: " + e.SSN);
System.out.println("Number: " + e.number);
}
}
Java 网络编程
客户端:通过 socket 连接到服务器并发送一个请求,然后等待一个响应。
// 文件名 GreetingClient.java import java.net.*;
import java.io.*; public class GreetingClient
{
public static void main(String [] args)
{
String serverName = args[0];
int port = Integer.parseInt(args[1]);
try
{
System.out.println("连接到主机:" + serverName + " ,端口号:" + port);
Socket client = new Socket(serverName, port); // Create socket
System.out.println("远程主机地址:" + client.getRemoteSocketAddress());
OutputStream outToServer = client.getOutputStream(); // Create stream channel at local
DataOutputStream out = new DataOutputStream(outToServer); // Create data channel at local out.writeUTF("Hello from " + client.getLocalSocketAddress()); // <---- 发数据
InputStream inFromServer = client.getInputStream(); // <---- Create stream channel at local 等回应
DataInputStream in = new DataInputStream(inFromServer);
System.out.println("服务器响应: " + in.readUTF());
client.close();
}catch(IOException e)
{
e.printStackTrace();
}
}
}
服务端:使用 Socket 来监听一个指定的端口。
// 文件名 GreetingServer.java import java.net.*;
import java.io.*; public class GreetingServer extends Thread
{
private ServerSocket serverSocket; public GreetingServer(int port) throws IOException
{
serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(10000);
} public void run()
{
while(true)
{
try
{
System.out.println("等待远程连接,端口号为:" + serverSocket.getLocalPort() + "...");
Socket server = serverSocket.accept();
System.out.println("远程主机地址:" + server.getRemoteSocketAddress());
DataInputStream in = new DataInputStream(server.getInputStream()); // ----> 先stream 后data
System.out.println(in.readUTF());
DataOutputStream out = new DataOutputStream(server.getOutputStream()); // ----> 准备应答,先stream 后data
out.writeUTF("谢谢连接我:" + server.getLocalSocketAddress() + "\nGoodbye!");
server.close();
}catch(SocketTimeoutException s)
{
System.out.println("Socket timed out!");
break;
}catch(IOException e)
{
e.printStackTrace();
break;
}
}
}
public static void main(String [] args)
{
int port = Integer.parseInt(args[0]);
try
{
Thread t = new GreetingServer(port); // Create thread to listen
t.run();
}catch(IOException e)
{
e.printStackTrace();
}
}
}
Java 发送邮件
略,详见链接。
Java 多线程编程
Java 提供了三种创建线程的方法:
- 通过实现 Runnable 接口;
- 通过继承 Thread 类本身;
- 通过 Callable 和 Future 创建线程。
一,"实现" Runnable接口
class RunnableDemo implements Runnable {
private Thread t;
private String threadName; RunnableDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
} public void run() { // 运行状态
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// 让线程睡眠一会
Thread.sleep(50);
}
}catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
} public void start() { // 就绪状态
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
} public class TestThread { public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo( "Thread-1");
R1.start(); // ----> 进入就绪状态 RunnableDemo R2 = new RunnableDemo( "Thread-2");
R2.start();
}
}
二,继承Thread类
与上述在写法上没什么大区别。
Thread本来就是实现了Runnable,包含Runnable的功能是很正常的啊!!至于两者的真正区别最主要的就是一个是继承,一个是实现;
还有一些面向对象的思想,
- Runnable就相当于一个作业,
- Thread才是真正的处理线程,
我们需要的只是定义这个作业,然后将作业交给线程去处理,这样就达到了松耦合,也符合面向对象里面组合的使用,
另外也节省了函数开销,继承Thread的同时,不仅拥有了作业的方法run(),还继承了其他所有的方法。
综合来看,用Runnable比Thread好的多。
class ThreadDemo extends Thread {
private Thread t;
private String threadName; ThreadDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
} public void run() { // ----> 重写该类的run()方法,线程的执行体
... ...
} public void start() {
... ...
} public class TestThread { public static void main(String args[]) {
ThreadDemo T1 = new ThreadDemo( "Thread-1");
T1.start(); ThreadDemo T2 = new ThreadDemo( "Thread-2");
T2.start();
}
}
三,Callable 和 Future
Java5策略 - 裸线程的进步callable
Callable和Future,一个产生结果,一个拿到结果。
Callable接口类似于Runnable,
- 但是Runnable不会返回结果,并且无法抛出返回结果的异常,
- 而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值。
Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大,可以有返回值!
Java5提供了Future接口来代表Callable接口里call()方法的返回值。
FutureTask实现了两个接口,Runnable和Future,所以
- 它既可以作为Runnable被线程执行
- 又可以作为Future得到Callable的返回值
public class CallableAndFuture {
public static void main(String[] args) {
Callable<Integer> callable = new Callable<Integer>() {
public Integer call() throws Exception { // ----> call作为线程执行体,且可以有返回值
return new Random().nextInt(100);
}
}; /*
* 1. callable是个类的实例,里面定义了线程执行体函数call()。
* 2. future这个类来代表callable所代表线程的返回值。
*/
FutureTask<Integer> future = new FutureTask<Integer>(callable); // ----> Future接口代表 Callable接口里call()方法的返回值
/* 3. 准备就绪,线程start */
new Thread(future).start();
try {
Thread.sleep(5000);// 可能做一些事情
System.out.println(future.get()); // <---- 得到返回值
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
Java5之后的策略 - Executor框架
简化并发编程,Executor使我们无需显式地去管理线程的生命周期,是JDK 5之后启动任务的首选方式。
Ref:Java并发的四种风味:Thread、Executor、ForkJoin和Actor
Executor接口的定义非常简单:
public interface Executor {
void execute(Runnable command);
}
* Executor:一个接口,其定义了一个接收Runnable对象的方法executor,其方法签名为executor(Runnable command),
* ExecutorService:是一个比Executor使用更广泛的子类接口,其提供了生命周期管理的方法,以及可跟踪一个或多个异步任务执行状况返回Future的方法
* AbstractExecutorService:ExecutorService执行方法的默认实现
* ScheduledExecutorService:一个可定时调度任务的接口
* ScheduledThreadPoolExecutor:ScheduledExecutorService的实现,一个可定时调度任务的线程池
* ThreadPoolExecutor:线程池,可以通过调用Executors以下静态工厂方法来创建线程池并返回一个ExecutorService对象:
线程池例子:任务执行完成后并返回执行结果
public class CallableAndFuture {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor(); // ExecutorService继承自Executor,管理Thread对象
Future<Integer> future = threadPool.submit(new Callable<Integer>() { // 线程池的线程提供了获取返回值future的服务。
public Integer call() throws Exception { // 线程的执行体
return new Random().nextInt(100);
}
});
try {
Thread.sleep(5000);// 可能做一些事情
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
例1:使用newScheduledThreadPool来模拟心跳机制
public class HeartBeat {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(5); // <---- 有五个线程的线程池
Runnable task = new Runnable() {
public void run() {
System.out.println("HeartBeat.........................");
}
};
executor.scheduleAtFixedRate(task,5,3, TimeUnit.SECONDS); //5秒后第一次执行,之后每隔3秒执行一次
}
}
例2:线程可以重用,节省资源
public class ThreadPoolTest {
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool = Executors.newCachedThreadPool();//线程池里面的线程数会动态变化,并可在线程线被移除前重用 for (int i = 1; i <= 3; i ++) {
final int task = i;//TimeUnit.SECONDS.sleep(1); // 第六行!
threadPool.execute(new Runnable() { //接受一个Runnable实例
public void run() {
System.out.println("线程名字: " + Thread.currentThread().getName() + " 任务名为: "+task);
}
});
}
}
}
输出结果:
// 输出:(为每个任务新建一条线程,共创建了3条线程)
线程名字: pool-1-thread-1 任务名为: 1
线程名字: pool-1-thread-2 任务名为: 2
线程名字: pool-1-thread-3 任务名为: 3 // 去掉第6行的注释其输出如下:(始终重复利用一条线程,因为newCachedThreadPool能重用可用线程)
线程名字: pool-1-thread-1 任务名为: 1
线程名字: pool-1-thread-1 任务名为: 2
线程名字: pool-1-thread-1 任务名为: 3
例3:启动10条线程,谁先执行完成就返回谁
CompletionService:将执行完成的任务放到阻塞队列中,通过take或poll方法来获得执行结果。
public class CompletionServiceTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(10); //创建含10条线程的线程池
CompletionService completionService = new ExecutorCompletionService(executor);
for (int i =1; i <=10; i ++) {
final int result = i;
completionService.submit(new Callable() {
public Object call() throws Exception {
Thread.sleep(new Random().nextInt(5000)); //让当前线程随机休眠一段时间
return result;
}
});
}
System.out.println(completionService.take().get()); //获取执行结果
}
}
Java Applet 基础
略,已被js取代。
Java 文档注释
javadoc 工具将你 Java 程序的源代码作为输入,输出一些包含你程序注释的HTML文件。
详见链接。
Java 实例
功能代码demo参考,详见链接。
Java 8 新特性
编程风格的不同:
import java.util.Collections; // 使用 java 7 排序
private void sortUsingJava7(List<String> names){
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
}
// 上面的compare名字其实不重要
// 也就侧面说明了匿名函数的必要性
// 使用 java 8 排序
private void sortUsingJava8(List<String> names){
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
}
其他详见:http://www.runoob.com/java/java8-new-features.html
Java MySQL 连接
使用 JDBC 连接 MySQL 数据库。
Java 连接 MySQL 需要驱动包,最新版下载地址为:http://dev.mysql.com/downloads/connector/j/,解压后得到jar库文件,然后在对应的项目中导入该库文件。
package com.runoob.test;
import java.sql.*; public class MySQLDemo { // JDBC 驱动名及数据库 URL
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/RUNOOB"; // 数据库的用户名与密码,需要根据自己的设置
static final String USER = "root";
static final String PASS = "123456";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try{
// 1. 注册 JDBC 驱动
Class.forName("com.mysql.jdbc.Driver"); // 2. 打开链接
System.out.println("连接数据库...");
conn = DriverManager.getConnection(DB_URL,USER,PASS); // 3. 执行查询
System.out.println(" 实例化Statement对象...");
stmt = conn.createStatement();
String sql;
sql = "SELECT id, name, url FROM websites";
ResultSet rs = stmt.executeQuery(sql); // 展开结果集数据库
while(rs.next()){
// 通过字段检索
int id = rs.getInt("id");
String name = rs.getString("name");
String url = rs.getString("url"); // 输出数据
System.out.print("ID: " + id);
System.out.print(", 站点名称: " + name);
System.out.print(", 站点 URL: " + url);
System.out.print("\n");
}
// 完成后关闭
rs.close();
stmt.close();
conn.close();
}catch(SQLException se){
// 处理 JDBC 错误
se.printStackTrace();
}catch(Exception e){
// 处理 Class.forName 错误
e.printStackTrace();
}finally{
// 关闭资源
try{
if(stmt!=null)
stmt.close();
}catch(SQLException se2){
}// 什么都不做
try{
if(conn!=null) conn.close();
}catch(SQLException se){
se.printStackTrace();
}
}
System.out.println("Goodbye!");
}
}