常用的DOS命令
cd
dir:查看目录
盘符
ipconfig
del 文件
ping
java语言的特性
- 简单性
- 面向对象
- 健壮性
- 多线程
- 可移植性
java程序设计的核心
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉
- 低耦合:仅暴露少量的方法供外界使用
JDK、JRE、JVM三者之间的关系
- JDK:开发工具包
- JRE:(运行环境)运行时类库
- JVM:java虚拟机
范围从小到大:
JVM -> JRE -> JDK
java编译和运行
# 假设需要编译的文件为 Hello.java
javac Hello.java
# 编译成功后会生成Hello.class文件
# 运行
java Hello
注释
// 单行注释
/*
多行注释
*/
/**
* 文档注释
*/
标识符与关键字
标识符的命名规则
标识符只能由数字、字母(可以是中文,但不推荐)、$、下划线(_)组成,首字母不能是数字。
可以标识
- 类名
- 方法名
- 变量名
- 接口名
- ……
标识符的命名规范
见名知意
包名:首字母小写 + 驼峰命名
变量名:首字母小写 + 驼峰命名
方法名:首字母小写 + 驼峰命名
常量名:全大写,并且单词之间用"_"衔接
类名:首字母大写 + 驼峰命名
接口名:首字母大写 + 驼峰命名
变量
分类
- 基本类型
- 整数型(默认值:0)
- byte
- short
- int
- long(一般数值后面加 L 区分)
- 浮点型(默认值:0.0)
- float(一般数值后面加 F 区分)
- double
- char
- boolean(默认值:false)
- false
- true
- 整数型(默认值:0)
- 引用类型
- 类
- 数组
- 接口
作用域
- 类变量(加了static关键字)
- 实例变量
- 局部变量
常量
// 初始化后,不允许修改值。
final 数据类型 常量名 = 值;
运算符
-
算术运算符:+、-、*、/、%、++、–
-
赋值运算符:=
-
关系运算符:>、<、>=、<=、==、!=、instanceof
-
逻辑运算符:&&、||、!
-
位运算符 教程:&、|、^、~、>>、<<、>>>
-
条件运算符:? :
-
扩展赋值运算符:+=、-=、*=、/=
包机制(package)
优点
- 解决类名重名的问题
- 为了方便程序的管理。不同功能的类分别存放在不同的包下
用法
package 包名1[.包名2.包名3....];
// import 必须放在 package下面
import package1[.package2.package3...].(classname|*);
命名规范
- 一般采用公司域名倒序的方式。
- 公司域名倒序 + 项目名 + 模块名 + 功能名
注意
- package语句只允许出现在java源代码的第一行。
import
使用场景
A类中使用B类时
- A类和B类在同一个包下,不需要使用import
- A类和B类不在同一个包下,需要使用import
java.lang.*;这个包下的类不需要使用import导入
用法
import语句只能出现在package语句之下,class声明语句之上。
import语句还可以使用*号的方式。
java Doc
参数信息
- @author 作者名
- @version 版本号
- @since 指明需要最早使用的jdk版本
- @param 参数名
- @return 返回值情况
- @throws 异常抛出情况
# 文件为Doc.java
javadoc [-encoding UTF-8] [-charset UTF-8] Doc.java
进制
- 二进制 数值以"0b"开头
- 十进制
- 八进制 数值以"0"开头
- 十六进制 数值以"0x"开头
转义字符
\n、\t、
类型转换
规则
基本类型中,除了boolean无法和其他类型转换外,其余七种基本类型都可以相互转换。
byte、short、char混合运算,先各自转换成int再运算。
多种数据混合运算,各自先转换成容量大的一种再运算。
容量从小到大排序
byte < short(char) < int < long < float < double
分类
自动类型转换
强制类型转换
弊端
- 可能出现精度损失
控制语句
if控制
switch控制
循环
for循环
while循环
do…while循环
增强for循环
面向对象
- OOA:面向对象分析
- OOD:面向对象设计
- OOP:面向对象编程
特征
- 封装
- 继承
- 多态
类和对象
类:现实世界中的某些事物具有相同的特征提取出来的概念,是抽象的结果,类就是一个“模板“
对象:实际存在的个体。
封装
属性私有化,提供get方法、set方法
继承 Extends
多态
存在的三个必要条件
- 存在继承关系
- 子类重写父类的方法
- 父类引用指向子类对象
static
所有static修饰的都是类相关的,类级别的。
所有static修饰的,都是采用“类名.”方式访问的。不需要对象的参与即可访问,不会发生空指针异常的情况。
静态变量存储在方法区
应用场景
- 当所属的内容在所有的对象都一样时,可以认为是类的内容,可以设为static。
final
final可以修饰变量、方法、类等。
final表示最终的,不可变。
static final联合修饰的变量成为“常量”,常量名建议全部大写,单词之间用_衔接。
注意
- final修饰的类无法被其它类继承
- final修饰的方法无法被重写
- final修饰的变量只能赋值一次
实例变量如果没有手动赋值的话,系统会默认赋值;但final修饰的实例变量,必须初始化,否则编译报错。
案例
package com.bz02.finalDemo;
public class Test01 {
public static void main(String[] args) {
}
}
class A {
int a;
// 没有初始化
// final int b;
// 实例变量是在构造方法执行中初始化(new的时候),所以final修饰的实例变量在构造方法内手动赋值也行
final int c;
public A() {
this.c = 10;
}
}
抽象类
抽象类:将类与类之间的共同特征(类的基础上)进一步抽象提取形成了抽象类,因为类本身是不存在的,所以抽象类无法创建对象。
抽象类无法实例化,无法创建对象(无法实例化)。但是抽象类有构造方法,供子类使用。
非抽象类继承抽象类必须将抽象方法实现。
final和abstract不能联合使用,这两个修饰符是对立的。
类到对象是实例化,对象到类是抽象。
语法
[修饰符列表] abstract class 类名 {
类体
}
接口
集合
集合在java中本质上就是一个容器,是一个对象,也有内存地址。
集合中任何时候存储的都是“引用”。不能存储基本数据类型,也不能直接存储对象,都是存储对象的内存地址。
java中不同的集合对应不同的数据结构。
所有的集合类和集合接口都在java.util包下。
分类
- 单个方式存储元素,这类的超级父接口:java.util.Collection;
- 以键值对方式存储元素,这类的超级父接口:java.util.Map;
集合框架图
备注:
- 实线空心箭头:继承关系(泛化关系)(is a)
- 虚线空心箭头:实现关系
- 实线实心箭头:关联关系(has a)
数据结构
Collection接口
Collection接口的常用方法
方法名 | 描述 |
---|---|
boolean add(E e) | 添加元素 |
int size() | 获取元素个数,不是获取集合容量 |
void clear() | 清空元素 |
boolean contains(Object o) | 底层调用equals()进行判断,判断当前集合是否包含某个元素;如果包含返回true,否则返回false |
boolean remove(Object o) | 删除元素中某个元素 |
boolean isEmpty() | 判断集合中元素的个数是否为0;如果个数为0返回true,否则返回false |
Object[] toArray() | 调用这个方法可以把集合转换成数组 |
Iterator(迭代器)
注意
Iterator只有在Collection及子类中才能使用,Map集合不能使用。(具体查看集合框架图)
当集合的结构发生改变,迭代器必须重新获取,如果还是用老的迭代器,会出现异常:java.util.ConcurrentModificationException。
方法
方法名 | 描述 |
---|---|
boolean hasNext() | 如果仍有元素可以迭代,则返回true |
E next() | 返回迭代的下一个元素 |
void remove() |
案例
package com.bz.collectionDemo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorTest01 {
public static void main(String[] args) {
/*
Collection是接口,没法new(创建)
Collection c = new ArrayList(); 多态的体现(1.继承关系 2.重写方法 3.父级引用指向子级对象)
*/
// 创建Collection集合
Collection c = new ArrayList();
// 向集合添加数据
c.add("第一条数据");
c.add(1);
c.add(new Object());
/*
方法一:不采用迭代器来实现
// 集合转换为数组,才能进行循环
Object[] obj = c.toArray();
for(int i=0; i<obj.length; i++) {
System.out.println(obj[i]);
}
*/
/*
使用Iterator迭代器实现
*/
// 获取迭代器
Iterator it = c.iterator();
// hasNext():是否还有下一条数据
while(it.hasNext()) {
Object obj = it.next(); // 控制迭代器指针下移,并读取下移后的记录
System.out.println(obj);
}
}
}
List接口
特点
有序、有下标(下标从0开始)、数据可重复
List接口特有的方法
方法名 | 备注 |
---|---|
void add(int index, E element) | 在列表的指定位置插入指定元素(可选操作)。 |
E get(int index) | 返回列表中指定位置的元素。 |
int indexOf(Object o) | 返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。 |
int lastIndexOf(Object o) | 返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1。 |
E remove(int index) | 移除列表中指定位置的元素(可选操作)。 |
E set(int index, E element) | 用指定元素替换列表中指定位置的元素(可选操作)。 |
ArrayList集合(对象)
ArrayList集合底层为Object类型的数组,非线程安全。
ArrayList集合默认初始化容量为10(底层先创建了一个长度为0的数组,在添加第一个元素时,初始化容量为10)。
扩容到原容量的1.5倍。
可以使用java.util.Collections(工具类)把ArrayList集合非线程安全转换成线程安全。
案例
package com.bz.collectionDemo;
import java.util.ArrayList;
import java.util.List;
public class ArrayListTest01 {
public static void main(String[] args) {
// list1默认容量为10(底层先创建了一个长度为0的数组,在添加第一个元素时,初始化容量为10)
// list1长度为0
List list1 = new ArrayList();
System.out.println(list1.size()); // 结果:0
// list2容量为20
// list2长度为0
List list2 = new ArrayList(20);
System.out.println(list2.size()); // 结果:0
/*
注意:
size()获取的是集合中元素的个数,并非获取集合的容量
*/
}
}
package com.bz.collectionDemo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* ArrayList集合变成线程安全的
*/
public class ArrayListTest02 {
public static void main(String[] args) {
List list = new ArrayList(); // 非线程安全的
// 变成线程安全
Collections.synchronizedList(list);
list.add("abc");
list.add("efg");
list.add("xyz");
for(int i=0; i<list.size(); i++) {
System.out.println(list.get(i));
}
}
}
优点
- 【检索功能发挥到极致】检索效率高(每个元素占用的空间大小相同,内存地址连续,知道首元素的内存地址,然后知道下标,就可以通过数学表达式计算出内存地址,所以效率高)
缺点
- 随机增删元素效率比较低(向数组末尾追加元素效率高)
- 数组无法存储大数据量(很难找到一块很大的连续的内存空间)
LinkedList集合(对象)
LinkedList集合底层采用了双向链表数据结构
LinkedList集合不会默认初始化容量。
元素在空间存储,内存地址不连续。
优点
- 【把随机增删功能发挥到极致】随机增删元素效率较高(因为内存地址不是连续的,所以增删时不涉及到大量元素位移)
缺点
- 查询效率较低,每次查找某个元素时都需要从头节点开始往下遍历。
单向链表
基本单元是节点Node
节点Node有两个属性
- 数据
- 下个节点Node的内存地址
双向链表
基本单元也是节点Node
节点Node有三个属性
- 上个节点Node的内存地址
- 数据
- 下个节点Node的内存地址
链表优点
- 随机增删元素效率较高(因为内存地址不是连续的,所以增删时不涉及到大量元素位移)
链表缺点
- 查询效率较低,每次查找某个元素时都需要从头节点开始往下遍历。
Vector集合(对象)
Vector集合底层采用了数组这种数据结构
Vector集合中所有的方法都是线程同步的,线程安全,但效率较低,使用较少。
初始容量为10,扩容之后是原容量2倍。
ArrayList集合和Vector集合的区别
相同点
ArrayList集合和Vector集合的底层都是采用数组这种数据结构。
不同点
ArrayList集合是非线程安全,效率较高,可以使用java.util.Collections(工具类)把ArrayList集合非线程安全转换成线程安全。
Vector集合是线程安全,效率较低,现在有其它的方案代替,所以使用较少。
Set接口
特点
无序、无下标、不可重复
HashSet集合(对象)
HashSet集合底层采用了哈希表数据结构。
HashSet集合在new时,实际上底层是new HashMap集合。言外之意:向HashSet存储数据时,实际上是存储到HashMap中。
HashSet集合元素也需要重写hashCode()和equals();【原因在HashMap出查看】
package com.bz.collectionDemo;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* HashSet用法:无序、不可重复
*/
public class HashSetTest01 {
public static void main(String[] args) {
// 创建集合
Set<Integer> set = new HashSet<>();
// 往集合添加元素
set.add(1);
set.add(100);
set.add(40);
set.add(20);
set.add(40);
// 迭代器
Iterator<Integer> it = set.iterator();
while(it.hasNext()) {
Integer num = it.next();
System.out.print(num + "\t");
}
// 结果:1 100 20 40
}
}
SortedSet接口
因为继承的是Set接口,所以其特点也是无序、无下标、不可重复。但放在SortedSet集合中的元素(非自定义类型)可以自动排序,我们称为可排序集合。放到该集合中的数据会自动按照大小顺序排序。
TreeSet集合(对象)
TreeSet集合底层采用了二叉树数据结构。
TreeSet集合在new时,实际上底层是new TreeMap集合。言外之意:向TreeSet存储数据时,实际上是存储到TreeMap中。
package com.bz.collectionDemo;
import java.util.Set;
import java.util.TreeSet;
/**
* TreeSet:无序、不可重复,但会从小到大自动排序
*/
public class TreeSetTest01 {
public static void main(String[] args) {
Set<Integer> set = new TreeSet<>();
set.add(5);
set.add(10);
set.add(30);
set.add(15);
set.add(4);
set.add(15);
set.add(24);
for(Integer num : set) {
System.out.print(num + "\t");
}
// 结果:4 5 10 15 24 30
}
}
Map接口
- Map集合和Collection集合没有关系
- Map集合以Key/value这种键值对的方式存储元素
- key和value都是引用类型,存储java对象的内存地址
- key起到主导地位,value是key的一个附属品
- 所有Map集合的key特点:无序、不可重复
- Map集合的key和Set集合存储元素的特点相同
Map接口的常用方法
方法名 | 说明 |
---|---|
V put(K key, V value) | 将指定的值与此映射中的指定键关联(可选操作)。 |
V get(Object key) | 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。 |
void clear() | 从此映射中移除所有映射关系(可选操作)。 |
boolean containsKey(Object key) | 如果此映射包含指定键的映射关系,则返回 true 。 |
boolean containsValue(Object value) | 如果此映射将一个或多个键映射到指定值,则返回 true 。 |
boolean isEmpty() | 如果此映射未包含键-值映射关系,则返回 true。 判断集合中元素个数是否为0,为0返回true |
Set keySet() | 返回此映射中包含的键的 Set 视图。 获取Map集合中所有的key |
V remove(Object key) | 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。 |
int size() | 返回此映射中的键-值映射关系数。 |
Collection<V> values() | 返回此映射中包含的值的 Collection 视图。 |
Set<Map.Entry<K,V>> entrySet() | 返回此映射中包含的映射关系的 Set 视图。 |
HashMap集合(类)
底层是哈希表(散列表)数据结构,非线程安全。
HashMap集合的默认初始化容量为16,默认加载因子为0.75(即当集合中的元素个数达到集合的总容量的75%时,数组开始扩容)
HashMap集合允许key、value允许为null
哈希表:
-
数组和单向链表的结合体
-
查询、随机增删方面效率高
put(k, v)和get(k)方法通过查看底层逻辑发现:k调用了hashCode()和equals()方法。所以这两个方法需要重写*(原因:官方提供的是比较内存地址,而我们需要的是比较内容)*。
注意:
同一个单向链表上所有节点的哈希值是相同,因为他们的数组下标是一样的;单同个链表上k和k的equals都是不同的。
HashMap集合初始化容量必须还是2的倍数,这个是官方推荐的,因为这样达到散列均匀,为了提高HashMap集合的存放效率,这个是所必须的。
重点:
放在HashMap集合key部分的元素,以及放在HashSet集合的元素,需要同时重写hashCode方法 和 equals方法。
HashMap集合的key,null值只能有一个
Hashtable集合(类)
底层是哈希表数据结构,线程安全。效率低,现在很少用,因为有其它方案替代。
Hashtable集合的key、value都不允许为null。
Hashtable集合的默认初始化容量为11,默认加载因子为0.75f
Hashtable集合的扩容: 原容量 * 2 + 1
Properties(类)【708集】
Properties是一个Map集合,继承Hashtable
Properties被称为“属性类”
Properties集合是线程安全的,存储元素的时候也是key/value形式,并且key和value只支持String类型
常用方法
方法名 | 备注 |
---|---|
Object setProperty(String key, String value) | 底层调用Hashtable的put方法 |
String setProperty(String key) | 用指定的键在此属性列表中搜索属性 |
SortedMap接口
SortedMap集合的特点是无序不可重复。但放在SortedMap集合中元素的key部分可以自动排序,我们称为可排序集合。放到该集合中数据的key部分会自动按照大小顺序排序。
TreeMap集合
底层采用了二叉树数据结构。
所有实现类的总结
- ArrayList:底层是数组,非线程安全,效率较高
- LinkedList:底层是双向链表
- Vector:底层是数组,线程安全,效率较低,使用较少
- HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分。
- TreeSet:底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分。
- HashMap:底层是哈希表,非线程安全,效率较高
- HashTable:底层是哈希表,线程安全,效率较低,使用较少
- Properties:线程安全,key和value只能存储字符串(String),又称“属性类”
- TreeMap:底层二叉表,TreeMap集合的key可以自动按照大小顺序排序
List集合存储元素的特点
- 有序可重复
Set(Map)集合存储元素的特点
- 无序不可重复
SortedSet(SortedMap)集合存储元素的特点
- 无序不可重复,但是SortedSet(SortedMap)集合中的元素是可排序的(可以按照大小顺序自动排序)。
泛型
泛型这种语法机制,只在编译阶段起作用,只是给编译器参考的(运行阶段泛型没用!)
好处
- 集合中存储的元素类型统一
- 从集合中取出的类型是泛型指定的类型,不需要大量的”向下转型“
缺点
- 导致集合中存储的元素类型缺乏多样性
案例1
测试类:
package com.bz.collectionDemo.fanxingdemo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 泛型的使用
* jdk5.0之后推出泛型机制
* 泛型这种语法机制,只在编译阶段起作用,只是给编译器参考的(运行阶段泛型没用!)
*/
public class Test01 {
public static void main(String[] args) {
/*
没有使用泛型时的做法,存在哪些缺陷
// 创建一个集合
List list = new ArrayList();
// 创建一只猫
Cat cat = new Cat();
// 创建一只鸟
Bird bird = new Bird();
// 把猫添加到集合中
list.add(cat);
// 把鸟添加到集合中
list.add(bird);
// 创建迭代器
Iterator iterator = list.iterator();
// 循环
while(iterator.hasNext()) {
Object obj = iterator.next();
if(obj instanceof Animal) {
Animal animal = (Animal) obj;
animal.Move();
}
}
*/
// jdk5新增了泛型机制
// 使用泛型List<Animal>之后,表示List集合中只允许存储Animal类型的数据
// 用泛型来指定集合中存储的数据类型
// 用了泛型后,集合中元素的数据类型更统一了。
List<Animal> list = new ArrayList<Animal>();
/*
指定list集合只能存储Animal数据,那么存储String类型的数据,就会编译报错:
Required type: Animal
Provided: String
list.add("a");
*/
Cat cat = new Cat();
Bird bird = new Bird();
list.add(cat);
list.add(bird);
// 获取迭代器
// 这个表示迭代器迭代的是Animal类型
Iterator<Animal> iterator = list.iterator();
while(iterator.hasNext()) {
// 使用泛型后,每一次迭代返回的数据都是Animal类型
Animal a = iterator.next();
// 这里不需要强制类型转换,直接调用
a.move();
// 如果调用子类特有的方法还是需要”向下转型“
if(a instanceof Cat) {
Cat cat2 = (Cat)a;
cat2.catchMouse();
}
if (a instanceof Bird) {
Bird bird2 = (Bird)a;
bird2.fly();
}
}
}
}
Animal类:
package com.bz.collectionDemo.fanxingdemo;
/**
* 动物类
*/
public class Animal {
// 父类自带方法
public void move() {
System.out.println("动物在移动");
}
}
Cat类:
package com.bz.collectionDemo.fanxingdemo;
/**
* 猫类
*/
public class Cat extends Animal {
// 特有方法
public void catchMouse() {
System.out.println("猫抓老鼠!");
}
}
Bird类:
package com.bz.collectionDemo.fanxingdemo;
/**
* 鸟类
*/
public class Bird extends Animal {
// 特有方法
public void fly() {
System.out.println("鸟在飞!!!");
}
}
案例2
其他类用案例1的代码,测试类:
package com.bz.collectionDemo.fanxingdemo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* jdk8之后引入了:自动类型推断机制(又称:钻石表达式)
*/
public class Test02 {
public static void main(String[] args) {
// ArrayList<这里的类型会自动推断>()
// 自动类型推断
List<Animal> list = new ArrayList<>();
// 创建对象
Cat c = new Cat();
Bird b = new Bird();
// 往集合添加元素
list.add(c);
list.add(b);
// 创建迭代器
Iterator<Animal> it = list.iterator();
if(it.hasNext()) {
Animal a2 = it.next();
a2.move();
}
}
}
自定义泛型案例
package com.bz.collectionDemo.fanxingdemo;
/**
* 自定义泛型
* @param 泛型名称 <标识符可以随便写>,一般用E(Element)或T(Type)
*/
public class DiyFanXiangTest01<标识符可以随便写> {
public void doSome(标识符可以随便写 o) {
System.out.println(o);
}
public static void main(String[] args) {
// 创建对象指定了泛型是:String类型
DiyFanXiangTest01<String> diyfx = new DiyFanXiangTest01<>();
// doSome(String)实参类型只能为String类型,不是String类型会报错
diyfx.doSome("泛型");
DiyFanXiangTest01<Integer> diyfx2 = new DiyFanXiangTest01<Integer>();
diyfx2.doSome(1);
}
}
增强for循环(foreach)
package com.bz.arrayDemo;
/**
* jdk5.0之后推出:增强for循环(又称foreach)
*
*/
public class ForEachTest01 {
public static void main(String[] args) {
// 创建数组
int[] arr = {2, 3, 5, 1, 10, 7, 2};
/*
普通循环
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
*/
/*
增强for循环语法
for(数据类型 变量名 : 数组或集合) {
……
}
缺点:
没有下标
在需要使用下标的循环中,不建议使用增强for循环
*/
for(int num : arr) {
System.out.print(num + "\t");
}
}
}
package com.bz.arrayDemo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 集合使用增强for循环(foreach)
*/
public class ForEachTest02 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("abc");
list.add("efg");
list.add("xyz");
// 使用迭代器方式
System.out.println("************* 使用迭代器方式 **************");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String str = it.next();
System.out.print(str + "\t");
}
System.out.println();
System.out.println();
System.out.println("************* 使用foreach方式 **************");
// 循环
for(String str : list) {
System.out.print(str + "\t");
}
}
}
注意点
- 存放在一个集合(Collection)中的类型,一定要重写contains()、remove()等,具体看源码。
package com.bz.collectionDemo;
import java.util.ArrayList;
import java.util.Collection;
/**
* 测试contains方法、remove方法的使用及注意点
*/
public class ContainsTest01 {
public static void main(String[] args) {
Collection c = new ArrayList();
User u1 = new User("张三");
c.add(u1);
String str1 = new String("abc");
c.add(str1);
User u2 = new User("张三");
String str2 = new String("abc");
System.out.println(c.contains(u2)); // User类没重写equals方法前结果:false 重写equals方法后结果为:true
System.out.println(c.contains(str2)); // 结果:true 因为contains底层调用的是equals方法,但官方有对String类的equals方法进行过重写,改为内容比较
// User类没重写equals方法前结果:删除不掉u2对象
// User类没重写equals方法后结果:可以删除u2对象
c.remove(u2);
System.out.println(c.size());
}
}
class User {
String name;
// 无参构造器
public User() {
}
// 有参构造器
public User(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if(obj == null || !(obj instanceof User)) {
return false;
}
if(this == obj) {
return true;
}
User u = (User)obj;
return this.name.equals(u.name);
}
}
IO
-
定义
I:input
O: output
-
作用
通过IO可以完成文件的读和写。
-
IO流的分类
- 输入流
- 输出流
- 字节流
- 字符流
-
按照流的方向进行分类
以内存作为参照物,往内存中去,叫做输入Input(读Read);从内存中出来,就做输出Output(写Write)
-
按照读取数据方式不同进行分类
- 字节流:有的流是按照字节的方式读取数据,一次读取1个字节(byte),等同于一次读取8个二进制位,这种流是万能的,什么类型的文件都可以读取,包括文本文件、图片、声音文件、视频文件……
- 字符流:有的流是按照字符的方式读取数据的,一次读取一个字符,这种流为了方便读取普通文本文件而存在的,这种流不能读取:图片、声音、视频等文件。只能读取纯文本文件,连word文件都无法读取。
-
java中所有流都在:java.io.*下
-
java IO流的四大家族:
- java.io.InputStream:字节输入流
- java.io.OutputStream:字节输出流
- java.io.Reader:字符输入流
- java.io.Writer:字符输出流
注意:
- 在java中只要类名以"Stream"结尾的都是字节流,以"Reader/Writer"结尾的都是字符流。
- 四大家族的首领都是抽象类(abstract class)
-
所有的流都实现了
- 所有的流都实现了:java.io.Closeable接口,都是可关闭的,都有close()方法。流毕竟是一个管道,这个是内存和硬盘之间的通道,用完后一定要关闭,不然会占用很多资源。
-
所有的输出流都实现了
- 都实现了java.io.Flushable接口,都是可刷新的,都有flush()方法。养成一个好习惯,输出流在最终输出之后,一定要记得flush()刷新一下,这个刷新是将通道/管道当中剩余未输出的数据强行输出(清空管道)。刷新的作用就是清空管道。
- 注意:如果没有flush()可能会导致丢失数据。
-
java.io包下需要掌握的流有16个:
- 文件专属
- java.io.FileInputStream
- java.io.FileOutputStream
- java.io.FileReader
- java.io.FileWriter
- 转换流(将字节流转换为字符流)
- java.io.InputStreamReader
- java.io.OutputStreamWriter
- 缓冲流专属
- java.io.BufferedReader
- java.io.BufferedWriter
- java.io.BufferedInputStream
- java.io.BufferedOutputStream
- 数据流专属
- java.io.DataInputStream
- java.io.DataOutputStream
- 标准输出流
- java.io.PrintWriter
- java.io.PrintStream
- 对象专属流
- java.io.ObjectInputStream
- java.io.ObjectOutputStream
- 文件专属
-
FileInputStream和FileOutputStream案例
FileInputStream案例1
package com.bz.javaio.demo01; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; /** * java.io.FileInputStream * 1.文件字节输入流,万能的,任何类型 */ public class FileInputStreamTest01 { public static void main(String[] args) { FileInputStream fis = null; try { // IDEA中工程Project的根就是IDEA的默认当前路径 fis = new FileInputStream("src/com/bz/javaio/demo01/test"); // while(true) { // int data = fis.read(); // if(data == -1) { // break; // } // System.out.println(data); // } int data = 0; while ((data = fis.read()) != -1) { System.out.println(data); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if(null != fis) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
FileInputStream案例2
package com.bz.javaio.demo01; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; /** * FileInputStreamTest01基础上优化 */ public class FileInputStreamTest02 { public static void main(String[] args) { FileInputStream fis = null; byte[] b = new byte[4]; try { fis = new FileInputStream("src/com/bz/javaio/demo01/test"); // FileInputStream常用方法 System.out.println("总共有:" + fis.available() + "个字节!\n"); System.out.println("跳过一个字符\n"); fis.skip(1); // Add exception to method signature:方法抛出异常 int readCount = 0; while((readCount = fis.read(b)) != -1) { String str = new String(b, 0, readCount); /* 因为中文会被拆分,结果: a中 b哈 哈� ��� �哈 */ System.out.println(str); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if(fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
FileOutputStream案例1
package com.bz.javaio.demo01; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /** * FileOutputStream练习1 * 负责写:从内存到硬盘 */ public class FileOutputStreamTest01 { public static void main(String[] args) { FileOutputStream fos = null; try { // outputTest不存在时,会自动新建 fos = new FileOutputStream("src/com/bz/javaio/demo01/test"); // 开始写 byte[] b = {97, 'b', 102, 'd'}; fos.write(b); // 写完之后,最后一定要刷新 fos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if(fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
FileOutputStream案例2
package com.bz.javaio.demo01; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /** * FileOutputStream内容追加测试 */ public class FileOutputStreamTest02 { public static void main(String[] args) { FileOutputStream fos = null; // outputTest不存在时,会自动新建, true:在文件末尾追加内容 try { fos = new FileOutputStream("src/com/bz/javaio/demo01/test", true); byte[] byteData = {97, 98, 99, 100, 102}; // 写入内容 fos.write(byteData); String str = "哈!哈哈哈哈~~~"; byte[] bytes = str.getBytes(); fos.write(bytes); // 写完内容后记得刷新 fos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if(fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
FileInputStream、FileOutputStream结合使用
package com.bz.javaio.demo01; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /** * 使用FileInputStream + FileOutputStream 完成文件的拷贝 * 拷贝的过程应该时一边读一边写 * 使用以上的字节流拷贝文件的时候,文件类型随意,万能的。什么样的文件都可以拷贝。 */ public class CopyTest01 { public static void main(String[] args) { FileInputStream fis = null; FileOutputStream fos = null; try { // 创建一个输入流对象 fis = new FileInputStream("src\\com\\bz\\javaio\\demo01\\fileInputTest.jpg"); // 创建一个输出流对象 fos = new FileOutputStream("src\\com\\bz\\javaio\\demo01\\jpgOutputTest.jpg"); byte[] bytes = new byte[1024 * 1024]; // 1MB = 1024 * 1024 (1次最多拷贝1MB) int readCount = 0; while((readCount = fis.read(bytes)) != -1) { fos.write(bytes, 0, readCount); } // 写入完成记得刷新一下 fos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 分开try,不要一起try // 原因:一起try时,其中一个出异常,会影响后面的代码(流的关闭)执行。 if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } if(fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
FileReader和FileWriter案例
FileReader案例
package com.bz.javaio.demo02; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; /** * FileRead使用: * 文件字符输入流,只能读取普通文本 * 读取文本内容时,比较方便,快捷。 */ public class FileReaderTest01 { public static void main(String[] args) { FileReader fileReader = null; try { fileReader = new FileReader("src/com/bz/javaio/demo01/test"); char[] chars = new char[4]; int readCount = 0; while((readCount = fileReader.read(chars)) != -1) { System.out.println(new String(chars, 0, readCount)); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fileReader != null) { try { fileReader.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
FileWriter案例
package com.bz.javaio.demo02; import java.io.FileWriter; import java.io.IOException; /** * FileWrite使用 */ public class FileWriterTest01 { public static void main(String[] args) { FileWriter fw = null; try { // 每次都覆盖 // fw = new FileWriter("src/com/bz/javaio/demo01/test"); // 在末尾追加 fw = new FileWriter("src/com/bz/javaio/demo01/test", true); char[] chars = {'j', 'a', 'v', 'a', '修', '炼'}; fw.write(chars); // 写入后需要刷新 fw.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if (fw != null) { try { fw.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
FileReader、FileWriter结合使用
package com.bz.javaio.demo02; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; /** * FileReader、FileWriter结合使用 * 只能拷贝普通文本文件 */ public class CopyTest01 { public static void main(String[] args) { // 创建一个字符流读取对象 FileReader fr = null; // 创建一个字符流写入对象 FileWriter fw = null; try { fr = new FileReader("src/com/bz/javaio/demo01/inputTest"); fw = new FileWriter("src/com/bz/javaio/demo01/outputTest"); char[] chars = new char[4]; int readCount = 0; while((readCount = fr.read(chars)) != -1) { // 写入 fw.write(chars, 0, readCount); } // 写入需要刷新 fw.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fw != null) { try { fw.close(); } catch (IOException e) { e.printStackTrace(); } } if (fr != null) { try { fr.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
File
File类和流的四大家族没有关系,所以File类不能完成文件的读和写操作。
File对象代表什么?
文件和目录路径名的抽象表示形式。
File对象有可能对应的是目录,也有可能是文件。
常用方法
方法 | 描述 |
---|---|
boolean createNewFile() | 创建文件 |
boolean mkdir() | 创建单层目录 |
boolean mkdirs() | 创建多层目录 |
String getParent() | 获取父路径 |
File getParentFile() | 获取父路径 |
String getAbsolutePath() | 获取绝对路径 |
String getName() | 获取文件名 |
boolean isFile() | 判断是否是一个文件,true表示是一个文件 |
boolean isDirectory() | 判断是否是一个目录,true表示是一个目录 |
long lastModified() | 获取文件最后修改时间 |
long length() | 获取文件的大小 |
File[] listFiles() | 目录下所有的子文件/子目录列表 |
序列化与反序列化
序列化:Serialize,java对象存储到文件中,将java对象的状态保存下来的过程。
反序列化:DeSerialize,将硬盘上的数据重新恢复到内存当中,恢复成java对象。
ObjectOutputStream:序列化
ObjectInputStream:反序列化
参与序列化和反序列化的对象,必须实现Serializable接口,否则会报错:java.io.NotSerializableException(即:对象不支持序列化)。
注意:
通过源代码发现,Serializable接口只是一个标志接口(接口当中什么代码也没有)
Serializable接口具体起到什么作用?
起到标识的作用,标志的作用。java虚拟机看到这个类实现了这个接口,可能对这个类进行特殊待遇。
Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成一个序列化版本号。
序列化版本号的作用:
java.io.InvalidClassException: com.bz.javaio.demo06.User;
local class incompatible:
stream classdesc serialVersionUID = 7212538078076707134, (新版本的序列化版本号)
local class serialVersionUID = -8490852119502027634 (旧版本的序列化版本号)
java语言中是采用什么机制来区分类的?
- 首先通过类名进行比对,如果类名不一样,肯定不是同一个类。
- 如果类名一样,靠序列化版本号进行区分。
最终结论
凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。
这样,以后这个类即使代码修改了,但是版本号不改,java虚拟机会认为是同一个类。
案例:所有属性参与序列
Student.java文件
package com.bz.javaio.demo06;
import java.io.Serializable;
public class Student implements Serializable {
// 建议把序列化版本号手动写出来,不建议自动生成
private static final long serialVersionUID = 1L; // java虚拟机识别一个类的顺序:先通过类名;如果类名相同,再根据序列版本号
private int no;
private String name;
public Student() {
}
public Student(int no, String name) {
this.no = no;
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
ObjectOutputStreamTest02.java文件
package com.bz.javaio.demo06;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 序列化
*/
public class ObjectOutputStreamTest02 {
public static void main(String[] args) {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("ObjectOutputStreamTest02"));
List<Student> list = new ArrayList<>();
Student st1 = new Student(1, "zhangsan");
Student st2 = new Student(2, "lisi");
Student st3 = new Student(3, "wangwu");
list.add(st1);
list.add(st2);
list.add(st3);
oos.writeObject(list);
// 写入完成需要刷新
oos.flush();
// 运行报错:java.io.NotSerializableException: com.bz.javaio.demo06.User
// Student类无法被序列化
// 解决方案:Student类需要继承Serializable接口
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != oos) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
ObjectInputStreamTest02.java 文件
package com.bz.javaio.demo06;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.List;
/**
* 集合
* 反序列化
*/
public class ObjectInputStreamTest02 {
public static void main(String[] args) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("ObjectOutputStreamTest02"));
List<Student> list = (List) ois.readObject();
for(Student st : list) {
System.out.println(st);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (null != ois) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
案例:某个属性不参与序列化
如果对象中某个属性不参与序列化,添加transient关键字,如下:
Student.java文件
package com.bz.javaio.demo06;
import java.io.Serializable;
public class Student implements Serializable {
// 建议把序列化版本号手动写出来,不建议自动生成
private static final long serialVersionUID = 2L; // java虚拟机识别一个类的顺序:先通过类名;如果类名相同,再根据序列版本号
private int no;
// transient关键字表示游离的,不参与序列化
private transient String name; // name不参与序列化操作
public Student() {
}
public Student(int no, String name) {
this.no = no;
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
ObjectOutputStreamTest02.java文件
package com.bz.javaio.demo06;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 序列化
*/
public class ObjectOutputStreamTest02 {
public static void main(String[] args) {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("ObjectOutputStreamTest02"));
List<Student> list = new ArrayList<>();
Student st1 = new Student(1, "zhangsan");
Student st2 = new Student(2, "lisi");
Student st3 = new Student(3, "wangwu");
list.add(st1);
list.add(st2);
list.add(st3);
oos.writeObject(list);
// 写入完成需要刷新
oos.flush();
// 运行报错:java.io.NotSerializableException: com.bz.javaio.demo06.User
// Student类无法被序列化
// 解决方案:Student类需要继承Serializable接口
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != oos) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
ObjectInputStreamTest02.java 文件
package com.bz.javaio.demo06;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.List;
/**
* 集合
* 反序列化
*/
public class ObjectInputStreamTest02 {
public static void main(String[] args) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("ObjectOutputStreamTest02"));
List<Student> list = (List) ois.readObject();
for(Student st : list) {
System.out.println(st);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (null != ois) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
IO + Properties 联合使用
配置文件
非常好的一个设计理念:
- 以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取,
-
将来只需要修改这个文件的内容,java代码不需要改动,不需要重新
-
编译,服务器也不需要重启。就可以拿到动态的信息。
-
类似于以上机制的这种文件被称为:配置文件。
属性配置文件内容格式
key1=value
key2=value
java规范中有要求:属性配置文件建议以.properties结尾,但这不是必须的。
案例
userinfo.properties 文件
# 用户名
username=bz
# 密码
password=123
IoPropertiesTest01 文件
package com.bz.javaio.demo07;
import javax.annotation.processing.Filer;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
/**
* IO + Properties 联合使用
* 非常好的一个设计理念:
* 以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取,
* 将来只需要修改这个文件的内容,java代码不需要改动,不需要重新
* 编译,服务器也不需要重启。就可以拿到动态的信息。
*
* 类似于以上机制的这种文件被称为:配置文件。
* 并且当配置文件中的内容格式是:
* key1=value
* key2=value
* 的时候,我们把这种配置文件叫做属性配置文件。
*
* java规范中有要求:属性配置文件建议以.properties结尾,但这不是必须的。
*/
public class IoPropertiesTest01 {
public static void main(String[] args) {
/**
* Properties是一个Map集合,key和value都是String类型
* 想将userinfo文件中的数据加载到Properties对象中
*/
FileReader fr = null;
try {
fr = new FileReader("src\\com\\bz\\javaio\\userinfo.properties");
Properties pro = new Properties();
pro.load(fr);
String userName = pro.getProperty("username");
System.out.println(userName);
String password = pro.getProperty("password");
System.out.println(password);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != fr) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
多线程
什么是进程?
进程是一个应用程序(1个进程是一个软件)
什么是线程?
线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程。
优点
提交效率
实现方式
-
编写一个类,直接继承java.lang.Thread,重写run方法
package com.bz.threaddemo; /** * 实现线程的第一种方式: * 编写一个类,继承java.lang.Thread,重写run方法 * * 怎么创建线程对象? * 通过new对象 * * 怎么启动线程? * 调用线程对象的start()方法 * * 注意: * 亘古不变的的道理: * 方法体中的代码永远都是自上而下的顺序依次逐行执行的。 * * 以下程序的输出结果有这样的特点: * 有先有后 * 有多有少 */ public class ThreadTest01 { public static void main(String[] args) { // 这里是main方法,这里的代码属于主线程,在主栈中运行 // 新建一个分支线程对象 AThread aThread = new AThread(); /* start()方法的作用: 启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。 这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了, 线程就启动成功啦。 启动成功的线程会自动调用run()方法,并且run方法在分支栈的底部(压栈) run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。 */ // 启动线程 // aThread.run(); // 不会启动线程,不会分配新的分支栈 aThread.start(); // 这里的代码还是运行在主线程中 for (int i = 0; i < 1000; i++) { System.out.println("主线程---> " + i); } } } class AThread extends Thread { @Override public void run() { // 编写程序,这段程序运行在分支线程中(分支栈) for (int i = 0; i <1000; i++) { System.out.println("分支线程---> " + i); } } }
-
编写一个类,实现java.lang.Runnable接口,实现run方法
package com.bz.threaddemo; /** * 实现线程的第二种方式: * 编写一个类,实现java.lang.Runnable接口,实现run方法。 */ public class ThreadTest02 { public static void main(String[] args) { // 创建一个可运行的对象 BThread bt = new BThread(); // 将可运行的对象封装成一个线程对象 Thread thread = new Thread(bt); // 启动线程 thread.start(); for (int i = 0; i < 1000; i++) { System.out.println("主线程----> " + i); } } } class BThread implements Runnable { @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("分支线程----> " + i); } } }
注意:
第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其它的类,更灵活。
生命周期
- 新建状态
- 就绪状态
- 运行状态
- 阻塞状态
- 死亡状态
守护线程
注意
- 进程之间的内存独立不共享。
- 在java语言中,线程之间堆内存和方法区内存共享。但是栈内存独立(一个线程一个栈)。栈与栈之间互不干扰。
- 使用多线程之后,main方法结束,有可能程序还没结束;main方法结束只代表主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。
线程安全
-
什么时候数据在多线程并发的环境下会存在安全问题?
- 多线程并发
- 有共享数据
- 共享数据有修改的行为
-
如何解决线程安全问题?
线程排队执行(不能并发),这种机制被称为“线程同步机制”。
java中的三大变量
- 实例变量:在堆中
- 静态变量:在方法区
- 局部变量:在栈中
局部变量和常量 永远都不会存在线程安全问题,因为局部变量在栈中,永远都不会共享。(一个线程一个栈);
实例变量在堆中,堆只有1个;静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
用法
-
同步代码块(灵活)
synchronized (线程共享的对象) {
同步代码块;
}
-
在实例方法上使用synchronized,表示共享对象一定是this,并且同步代码块是整个方法体
-
在静态方法上使用synchronized,表示找类锁。
类锁永远只有1把,就算创建了100个对象,类锁都只有1把。
如何处理线程安全问题?
- 尽量使用局部变量代替“实例变量”和“静态变量”
- 如果必须是实例变量,那么可以考虑创建多个对象,这里实例变量的内存就不共享了。(1个线程对应1个对象,100个线程对应100个对象)
- 如果不能使用局部变量,对象也不能创建多个,这种情况就只能使用synchronized。线程同步机制。
反射机制
作用
- 通过java语言中的反射机制可以操作字节码文件;通过反射机制可以操作代码片段(class文件)
- 可以使程序变得更灵活
缺点
- 打破封装
相关类在哪个包下?
java.lang.reflect.*;
相关的重要的类有哪些?
- java.lang.Class:代表整个字节码,代表一个类型,代表整个类
- java.lang.reflect.Method:代表字节码中的方法字节码,代表类中的方法
- java.lang.reflect.Constructor:代表字节码中的构造方法字节码,代表类中的构造方法
- java.lang.reflect.Field:代表字节码中的属性字节码,代表类中的成员变量(静态变量 + 实例变量)
java.lang.Class
如何获取实例
-
Class c = Class.forName(“完整的类名”);
说明:
- 静态方法
- 方法的参数是一个字符串
- 字符串需要的是一个完整类名
- 完整类名必须带有包名(类似:java.lang.String)
-
Class c = 对象.getClass();
-
java语言中任何一种类型(包括基本数据类型)都有.class属性。Class c = 任何类型.class;
注解(Annotation)
注解,或者叫做注释类型。
注解是一种引用数据类型。编译之后也是生成xxx.class文件。
定义语法
[修饰符列表] @interface 注解类型名 {
}
使用语法
@注解类型名
应用场景
- 注解可以出现在类上、属性上、方法上、变量上等……
- 注解还可以出现在注解类型上
注解当中的属性支持哪些类型
byte, short, int, long, float, double, boolean, char, String, Class, 枚举类型
以及以上每一种的数组形式
jdk内置的注解
@Deprecated(表示已过时)
@Override(表示重写方法)
源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
Override注解只能注解方法,是给编译器参考的,和运行阶段没有关系。
凡是java中的方法带有Override注解的,编译器都会进行编译检查,如果这个方法不是重写父类的方法,编译器报错。
元注解
用来标注“注解类型”的注解,称为元注解。类似上面的Override注解源码,Target注解就是元注解。
常见的元注解
-
Target
用来标注“被标注的注解”可以出现在哪些位置上
- @Target(ElementType.METHOD):表示”被标注的注解“只能出现在方法上
- @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
- 表示”被标注的注解“可以出现在构造方法上、字段上、局部变量上、方法上、包上、参数上、类上
-
Retention
用来标注“被标注的注解”最终保存在哪里
@Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中。
@Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中。
@Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取。