Java基础——面向对象(高级)
1.多线程的创建
java.lang.thread
创建Thread类的匿名子类的方式:
new Thread(){public void run(){}}.start();
方式一:继承于Thread类
1.创建一个继承于Thread类的子类
2.重写Thread类的run() --> 将此线程执行的操作声明在run()中
3.创建Thread类的子类的对象
4.通过对象调用start():启动线程,调用run()
方式二:实现Runnable接口
1.创建一个实现Runnable接口的类
2.实现类去实现Runnable类中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()
比较两种创建方式:
优先选择方式二,没有类的单继承局限性,更适合处理多个线程有共享数据的情况
相同点两种方式都需要重写run()
jdk5.0
新增方式一:实现Callable接口
1.创建一个实现Callable的实现类
2.实现call方法,将此线程需要执行的操作声明在call()中
3.创建Callable接口实现类的对象
4.将此Callable接口实现的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
6.获取Callable中call方法的返回值
Callable与Runnable比较:
call()有返回值,可以抛出异常,被外面的操作捕获,获取异常的信息,Callable支持泛型
新增方式二:使用线程池
1.提供指定线程数量的线程池
2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
3.关闭线程池
好处:
提高响应速度,降低资源消耗,便于线程管理
2.Thred类常用方法
void start():启动线程,并执行run()
run():线程被调度时执行的操作
String getName():返回线程的名称
void setName(String Name):设置线程的名称
static Thread currentThread():返回当前线程
yield():释放当前cpu的执行权
join():在线程a中调用线程b的join(),此时线程a进入阻塞状态,知道线程b执行完才结束阻塞状态
sleep(long millitime):让当前线程“睡眠”指定的毫秒,在指定毫秒期间,保持阻塞状态
stop():结束当前线程
isAlive():判断当前线程是否存活
3.线程的优先级
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
getPriority():获取优先级
setPriority(int p):设置优先级
4.线程的生命周期
新建、就绪、运行、阻塞、死亡
5.线程安全
方式一:同步代码块
synchronized(同步监视器){//需要被同步的代码}
同步监视器:任何一个类的对象都能充当锁,多个线程必须要共用同一把锁
需要被同步的代码:操作共享数据的代码
方式二:同步方法
如果操作共享数据的代码完整声明在一个方法中
同步监视器:一个对象则this,多个对象则方法加static
饿汉式
方式三:Lock锁
实例化reentrantLock()
try里调用锁定方法lock()
finally里调用解锁方法unlock()
6.死锁问题
不同线程分别占同对方需要的同步资源不放弃
7.线程通信
同步代码块或同步方法中:(3个方法的调用者是同步监视器)
wait()进入阻塞(释放同步监视器)
notify()唤醒被wait的一个线程
notifyAll()唤醒被wait的所有线程
sleep()和wait()的异同:
相同点:一旦执行,进入阻塞
不同点:
位置不同,Thread类中声明sleep(),Object类中声明wait()
调用的要求不同,sleep()任何情况,wait()需在同步代码块或同步方法中
sleep不释放同步监视器(yield、suspend)
8.String
1.声明为final,不可被继承
2.实现了Serializable接口:表示字符串是支持序列化的
实现了Comparable接口:表示String能比较大小
3.内部定义了final char[] value用于存储字符串数据
4.代表不可变的字符序列,对字符串重新赋值或进行连接操作或调用replace()修改指定字符串时,需要重写制定内存区域赋值,不是原有地址
5.String a = “abc” 通过字面量的方式给一个字符串赋值,此时的字符串声明在字符串常量池中,不会存储相同内容的字符串;String a = new String(“abc”) 通过new+构造器的方式定义对象在堆中,地址不同。
6.String s = "a"+"abc"常量和常量的拼接结果在常量池;String s = s1+"abc"只要其中有一个变量,结果就在堆中;String s = s1.intern()如果拼接结果调用intern()方法,返回值在常量池中。
7.string传值进方法,外面定义的string值不变
9.String常用方法
int length():获取字符串的长度
char charAt(int index):获取指定索引处的字符
boolean isEmpty():判断指定字符串是否为空
String toLowerCase():把字符串转换为小写字符串
String toUpperCase():把字符串转换为大写字符串
String trim():去除字符串两端空格
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):比较字符串的内容是否相同,忽略大小写
String concat(String str):在原有的字符串的基础上加上指定字符串,等价于 “+”
String compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex截取的子串
String substring(int beginIndex, int endIndex):返回一个新的字符串,它是此字符串的从beginIndex截取的,到endIndex截止的子串(左闭右开)
boolean endsWith(String str):判断字符串对象是否以指定的字符结尾
boolean startsWith(String prefix):判断字符串对象是否以指定的字符开头(区分大小写)
boolean startsWith(String prefix,int toffset):判断字符串对象是否以指定的字符开头,参数toffset为指定从哪个下标开始
boolean contains(CharSequence s):查看字符串中是都含有指定字符序列
int indexOf(String str):获取str在符串对象中第一次出现的索引
int indexOf(String str, int fromIndex):获取str在符串对象中第一次出现的索引,从fromIndex开始
int lastIndexOf(String str):返回指定字符出现的最后一次的下标
int lastIndexOf(String str, int fromIndex):返回指定字符出现的最后一次的下标,从fromIndex开始
String replace(char oldChar, char newChar):将指定字符替换成另一个指定的字符(或string)
String replace(charSquence target, charSquence replacement):使指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串
String replaceAll(String regex,String replasement):使用给定的replacement替换字符串所有匹配给定的正则表达式的子字符串
String replaceFirst(String regex,String replacement):使用给定的replacement替换字符串所有匹配给定的正则表达式的第一个子字符串
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式
String[] split(String regex):根据给定的正则表达式的匹配拆分此字符串
String[] split(String regex, int limit):根据给定的正则表达式的匹配拆分此字符串,最多不超过limit个
10.String类型转换
String和基本数据类型、包装类转换:
把字符串转换为基本数据类型、包装类:调用包装类的静态方法parseXxx(str)
把基本数据类型、包装类转换为字符串:调用String重载的valueOf(xxx)
String和char[]转换:
把字符串转换为字符数组:char[] toCharArray()
把字符数组转换为字符串:调用String构造器,new String(char arr)
String和byte[]转换:
把字符串转换为字节数组:调用String的getBytes()
把字节数组转换为字符串:调用String构造器,new String(byte[] arr)
11.StringBuffer和StringBuilder
StringBuffer:可变的字符序列,线程安全,效率低,底层用char[]存储
StringBuilder:可变的字符序列,线程不安全,效率高,底层用char[]存储
新建构造器,底层创建一个长度为16的字符数组
扩容问题:如果添加数据底层数组盛不下了,默认扩容为原来容量的2倍+2
指导意义:避免扩容,开发中使用StringBuffer/Builder(int capacity)
效率:StringBuilder>StringBuffer>String
12.StringBuffer和StringBuilder常用方法
StringBuffer append(xxx):用于字符串拼接
StringBuffer delete(int start, int end):删除指定位置的内容
StringBuffer replace(int start, int end, String str):把start,end位置替换为str
StringBuffer insert(int offset, xxx):在指定位置插入xxx
StringBuffer reverse():用于字符序列逆转
public int indexOf(String str):返回str首次出现的位置
public String subString(int start, int end):返回左闭右开区间的子字符串
public int length():长度
public char charAt(int n):查找指定位置字符
public void setCharAt(int n, char ch):改指定位置字符
总结:增、删、改、查、插、长度、遍历
13.日期时间API
jdk1.8之前:
System类中的currentTimeMillis()
java.util.Date类
--java.sql.Date类:对应着数据库中的日期类型的变量
构造器:
1.new Date():创建一个对应当前时间的Date对象
2.new Date(155030620140L):创建指定毫秒数的Date对象
3.new java.sql.Date(35235325345L):创建java.sql.Date类对象
4.将java.util.Date转换为java.sql.Date对象
情况一:
Date date = new java.sql.Date(234151615161L)
java.sql.Date date1 = (java.sql.Date) date
情况二:
Date date = new Date()
java.sql.Date data1 = new java.sql.Date(data.getTime())
方法:
1.toString():显示当前年、月、日、时、分、秒
2.getTime():获取当前Date对象对应的毫秒数。(时间戳:返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差)
java.text.SimpleDateFormat类:对日期Date的格式化和解析(日期--字符串)
1.格式化:sdf.fomat(date),返回string
2.解析:sdf.parse(string),返回data
java.util.Calendar
实例化:静态方法Calendar.getInstance()
常用方法:
get()、set()、add()
Calendar类--Data类:getTime()、setTime()
由于可变性(日期时间应该不可变)、偏移性(Date类年份从1900开始,月份从0开始)、格式化(格式化只对Date有用,Calendar不行)、都不是线程安全、不能处理闰秒,引出新的api
jdk1.8:
java.time
LocalDate、LocalTime、LocalDateTime
now():获取当前时间
of():设置时间
getXxx():获取相关属性
withXxx():设置相关属性(不可变性)
Instant
now():获取本初子午线时间
atOffset(ZoneOffset.ofHours(8)):添加时间偏移量
toEpochMilli:获取对应毫秒数(1970.1.1)
java.time.format.DateTimeFormatter格式化或解析日期、时间
方式一:预定义的标准格式ISO_LOCAL_DARE_TIME
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DARE_TIME
格式化->string:formatter.fomat(localdatetime)
解析->LocalDateTime:formatter.parse(str)
方式二:本地化相关的格式ofLocalizeDateTime()
DateTimeFormatter.ofLocalizeDateTime(FormatStyle.SHORT)
方式三:自定义的格式ofPattern("yyyy-MM-dd hh:mm:ss")
DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")
其他api:Zoneld、ZoneldDateTime、Clock、TemporalAdjuster、TemporalAdjusters
14.比较器
比较对象大小:Compable或Comparator
Compable接口:
1.String、包装类实现了Compable接口,重写了compareto()规则。
2.重写compareto(obj)规则:
如果当前对象this大于形参对象obj,返回正整数
3.自然排序,规则写在compareto(obj)里
Comparator接口:
定制排序
对比:Compable接口方式一旦一定,在任何位置都可以比较,Comparator接口属于临时性比较
15.其他类
1.Syetem
2.Math
3.BigImteger和BigDecimal
16.枚举类
定义一组常量
jdk5.0之前:自定义
jdk5.0之后:关键字enum,多个对象之间,逗号隔开
values():返回枚举类型的对象数组
valueOf(String str):可以把一个字符串转为对应的枚举类对象
toString():返回当前枚举类对象常量的名称
实现接口:
情况一:实现接口,在enum类中实现接口
情况二:让枚举类的对象分别实现
17.注解Annotation
示例:1.文档注解;2.编译时格式检查:@Override重写,@Deprecated过时,@SuppressWarnings抑制编译器警告;3.跟踪代码依赖性
自定义注解:参照SuppressWarnings
元注解(对现有注解进行解释说明的注解):
1.Retention:指定所修饰的Annotation的生命周期,SOURCE\CLASS\RUNTIME,声明为RUNTIME才能通过反射获取
2.Target:用于指定被修饰的Annotation能用于修饰哪些程序元素
3.Documented:表示所修饰的注解在被javadoc解析时,保留下来
4.Inherited:被它修饰的注解将有继承性
JDK8新特性:可重复注解、类型注解
18.集合
Collection:单列数据,存储对象(包装类)
List有序可重复
ArrayList、LinkList、Vector
Set无序不可重复
HashSet、LinkedHashSet、TreeSet
Map:双列数据,映射“key-value”
HashMap、LinkHashMap、TreeMap、Hashtable、Properties
19.Collection
add(Object e):将元素e添加到集合中
size():获取添加的元素的个数
addAll(Collection coll):将coll集合中的元素添加到当前的集合中
clear():清空集合元素
isEmpty():判断当前集合是否为空
contains(Object obj):判断当前集合中是否包含obj,重写equals()
containsAll(Collection coll):判断当前集合中是否包含coll
remove(Object obj):移除当前集合中obj
removeAll(Collection coll):移除当前集合中coll
retainAll(Collection coll):获取当前集合和coll的交集,并返回给当前集合
equals(Collection coll):要想返回true,需要当前集合和形参集合的元素都相同
hashCode():返回当前对象的哈希值
toArray():集合->数组
asList():数组->集合
Iterator():返回Iterator接口的实例,用于遍历集合元素
20.迭代器
Iterator:提供一种方法访问容器(数组、集合)中的各个元素。
Iterator iterator = new coll.iterator()
iterator.hasNext():是否还有元素
iterator.next():下一个元素(指针下移)
iterator.remove():移除元素
21.foreach(JDK5.0)
for(Object obj: coll)
集合元素类型 局部变量:集合对象
22.List
替代数组,长度动态
ArrayList:作为List接口的主要实现类;线程不安全,效率高;底层使用Object[] elementData存储
LinkedList:对于频繁的插入、删除操作,效率比ArrayList高;底层使用双向链表存储
Vector:作为List接口的古老实现类;线程安全,效率低;底层使用Object[] elementData存储
JDK1.7
new ArrayList():单例的饿汉式。底层创建数组,长度为10,默认扩容1.5倍,同时将原有数组中的数据复制到新的数组中,建议开发中使用带参的构造器new ArrayList(int capacity)
JDK1.8
new ArrayList():单例的懒汉式,延迟数组创建,节省内存。底层Object[] elementData初始化为{},并没有创建长度为0的数组,第一次调用add(),底层才创建长度为10的数组
new LinkedList():内部声明了Node类型的fitst和last属性,默认值为null
add(123):将123封装到Node中,创建了Node对象
new Vector():底层创建数组,长度为10,默认扩容2倍.
常用接口:
void add(int index, Object ele):将元素e添加到index位置
bolean addAll(int index, Collection coll):将coll集合中的元素添加到index位置
Object get(int index):获取指定位置元素
int indexOf(Object obj):返回obk在集合中首次出现的位置
int lastIndexOf(Object obj):返回obk在集合中末次出现的位置
Object remove(int index):移除指定位置元素,并返回此元素
Object remove(Object obj):移除对应元素,并返回此元素
Object set(int index, Object ele):设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex):范围从from到to的子集合
遍历:迭代器、foreach、普通for
要求:重写equals()方法(remove时调用)
23.Set
HashSet:作为Set接口的主要实现类;线程不安全,可以存储null值
LinkedHashSet:HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历(双向链表记录地址,对于频繁的遍历操作,效率高于HashSet)
TreeSet:可以按照添加对象的指定属性,进行排序
Set接口中没有额外定义新的方法,跟ccollection一样
HashSet():
无序性:存储无序,不代表随机,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值添加,遍历输出顺序不随机,保持不变。
不可重复性:保证添加的元素按照equals()判断时,不能返回true。
添加元素的过程:向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置,判断数组在此位置上是否已经有元素:
如果此位置上没有其他元素,则添加成功
如果有其他元素(链表形式),则比较hash值
如果hash值不相同,则添加成功
如果hash值相同,调用a的equals方法
返回true,添加失败
返回false,添加成功
要求:向Set中添加的数据,其所在的类一定要重写hashCode()和euqals()方法,重写的方法尽可能保持一致性(直接生成),相等的对象必须具有相等的散列码,对象中用作equals()方法比较的Field,都应该用来计算hashCode。
TreeSet():
添加的数据要求是相同类的对象
两种排序方式:自然排序(实现Comparable接口),定制排序(Comparator)
自然排序中,比较两个对象是否相同的标准为:compareTo()返回0
定制排序中,比较两个对象是否相同的标准为:compare()返回0
24.Map
|----Map:双列数据,存储key-value对的数据 ---类似于高中的函数:y = f(x)
|----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
|----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap。
|----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序,底层使用红黑树
|----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
|----Properties:常用来处理配置文件。key和value都是String类型
HashMap的底层:数组+链表 (jdk7及之前)
数组+链表+红黑树 (jdk 8)
面试题:
- HashMap的底层实现原理?
- HashMap 和 Hashtable的异同?
- CurrentHashMap 与 Hashtable的异同?(暂时不讲)
Map中的key:无序的、不可重复的,使用Set存储所有的key ---> key所在的类要重写equals()和hashCode() (以HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所有的value --->value所在的类要重写equals()
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所有的entry
HashMap():
jdk7:
HashMap map = new HashMap():
在实例化以后,底层创建了长度是16的一维数组Entry[] table。
map.put(key1,value1):
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
如果equals()返回false:此时key1-value1添加成功。----情况3
如果equals()返回true:使用value1替换value2。
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
jdk8:
- new HashMap():底层没有创建一个长度为16的数组
- jdk 8底层的数组是:Node[],而非Entry[]
- 首次调用put()方法时,底层创建长度为16的数组
- jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量 * 填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
TreeMap():
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
因为要按照key进行排序:自然排序 、定制排序
Properties:常用来处理配置文件。key和value都是String类型
25.Collections:操作Collection、Map的工具类
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
copy函数:
List dest = Arrays.asList(new Object[list.size()]);
Collections.copy(dest,list);.
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
//返回的list1即为线程安全的List
List list1 = Collections.synchronizedList(list);
26.泛型
jdk 5.0新增的特性
ArrayList<Integer> list = new ArrayList<>();
总结:
① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
② 在实例化集合类时,可以指明具体的泛型类型
③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
比如:add(E e) --->实例化以后:add(Integer e)
④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。
自定义泛型结构:泛型类、泛型接口;泛型方法
1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:< E1,E2,E3>
2. 泛型类的构造器如下 public GenericClass(){} 。而下面是错误的public GenericClass<E>(){} 3. 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
4. 泛型不同的引用不能相互赋值。尽管在编译时 ArrayList 和 ArrayList 是两种类型,但是,在运行时只有一个 ArrayList 被加载到 JVM 中。
5. 泛型如果不指定,将被擦除,泛型对应的类型均按照 Object 处理,但不等价于Object。经验: 泛型要使用一路都用。要不用,一路都不要用。
6. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象 。
7. jdk1.7,泛型的简化 操作 ArrayList<Fruit> flist = new ArrayList<>();
8. 泛型的指定中不能使用基本数据类型,可以使用包装类替换。
9. 在类接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
10. 异常类不能是泛型的
11. 不能使用 new E[] 。但是可以 E[] elements = (E[])new Object[capacity];
参考:ArrayList 源码中声明: Object[] elementData 而非泛型参数类型数组。
12. 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
子类不保留父类的泛型:按需实现
没有类型 擦除
具体类型
子类保留父类的泛型:泛型子类
全部保留
部分保留
结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自 己的泛型
泛型方法的格式:
[访问权限] <泛型> 返回类型 方法名 ([泛型标识 参数名称]) 抛出的异常
<泛型>表示声明,定义了一个 T 类型形参,这个 T 类型形参就可以在该方法内当成普通类型使用。与接口、类声明中定义的类型参数不同的是,方法声明中定义的形参只能在该方法内使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。
泛型在继承方面的体现:
虽然类A是类B的父类,但是G<A>和G<B>二者不具备子父类关系,二者是并列关系。
补充:类A是类B的父类,A<G> 是 B<G> 的父类
通配符:?
类A是类B的父类,G<A>和G<B>是没有关系的,二者共同的父类是:G<?>
分析:
因为?是两者的共同父类,所以可以将list1和2传递给list
添加(写入):对于List<?>就不能向其内部添加数据。除了null
读取:允许读取数据,读取的数据类型为Object。
有限制条件的通配符的使用:
? extends A:(<=)
G<? extends A> 可以作为G<A>和G<B>的父类,其中B是A的子类
? super A:(>=)
G<? super A> 可以作为G<A>和G<B>的父类,其中B是A的父类
27ile
File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
File类声明在java.io包下
File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点"
创建File类的实例:
File(String filePath)
File(String parentPath,String childPath)
File(File parentFile,String childPath)
相对路径:相较于某个路径下,指明的路径。
绝对路径:包含盘符在内的文件或文件目录的路径
路径分隔符:
windows:\\
unix:/
*/
public String getAbsolutePath():获取绝对路径
public String getPath() :获取路径
public String getName() :获取名称
public String getParent():获取上层文件目录路径。若无,返回null
public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified() :获取最后一次的修改时间,毫秒值
如下的两个方法适用于文件目录:
public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
public boolean renameTo(File dest):把文件重命名为指定的文件路径
比如:file1.renameTo(file2)为例:
要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。
public boolean isDirectory():判断是否是文件目录
public boolean isFile() :判断是否是文件
public boolean exists() :判断是否存在
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏
创建硬盘中对应的文件或文件目录:
public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs() :创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建
删除磁盘中的文件或文件目录:
public boolean delete():删除文件或者文件夹
删除注意事项:Java中的删除不走回收站。
要想删除成功,io4文件目录下不能有子目录或文件
28.流
1.操作数据单位:字节流、字符流
2.数据的流向:输入流、输出流
3.流的角色:节点流、处理流
抽象基类 | 节点流(或文件流) | 缓冲流(处理流的一种) |
---|---|---|
InputStream | FileInputStream(read(byte[] buffer)) | BufferedInputStream (read(byte[] buffer)) |
OutputStream | FileOutputStream(write(byte[] buffer,0,len) | BufferedOutputStream (write(byte[] buffer,0,len) / flush() |
Reader | FileReader (read(char[] cbuf)) | BufferedReader (read(char[] cbuf) / readLine()) |
Writer | FileWriter (write(char[] cbuf,0,len) | BufferedWriter (write(char[] cbuf,0,len) / flush() |
节点流:
IO流基本操作:
1.File类的实例化
2.FileReader流的实例化
3.读入的操作
4.资源的关闭
输入:
1. read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
2. 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
3. 读入的文件一定要存在,否则就会报FileNotFoundException。
学会对read()操作升级:使用read的重载方法
read():返回读入的一个字符。如果达到文件末尾,返回-1
不能使用字符流来处理图片等字节数据
读取文件的推荐方式:
while((len = fr.read(cbuf)) != -1){
String str = new String(cbuf, 0 ,len);
System.out.println(str);
}
输出:
1. 输出操作,对应的File可以不存在的。并不会报异常
2. File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
File对应的硬盘中的文件如果存在:
如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):对原有文件的覆盖
如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容
FileInputStream和FileOutputStream的使用:
对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字节流处理
使用字节流FileInputStream处理文本文件,可能出现乱码。
处理流之一:
缓冲流:
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
作用:提供流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区
处理流,就是“套接”在已有的流的基础上
关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略
步骤:
1.造文件
2.造流
2.1 造节点流
2.2 造缓冲流
3.复制的细节:读取、写入
4.资源关闭
处理流之二:
转换流:
转换流:属于字符流
InputStreamReader:将一个字节的输入流转换为字符的输入流
OutputStreamWriter:将一个字符的输出流转换为字节的输出流
作用:提供字节流与字符流之间的转换
解码:字节、字节数组 --->字符数组、字符串
编码:字符数组、字符串 ---> 字节、字节数组
字符集:
ASCII:美国标准信息交换码。用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表。用一个字节的8位表示。
GB2312:中国的中文编码表。最多两个字节编码所有字符
GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
其他处理流:
标准的输入、输出流:System.in/out,字节流,需转换流
打印流:PrintStream
数据流:DataInputStream、DataOutputStream
对象流:
1.ObjectInputStream 和 ObjectOutputStream
2.作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
3.要想一个java对象是可序列化的,需要满足相应的要求。见Person.java
4.序列化机制:
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
自定义对象需要满足如下的要求,方可序列化
1.需要实现接口:Serializable
2.当前类提供一个全局常量:serialVersionUID
3.除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性
也必须是可序列化的。(默认情况下,基本数据类型可序列化)
补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
随机存取文件流:
RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口
RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
可以通过相关的操作,实现RandomAccessFile“插入”数据的效果
29.Path
jdk 7.0 时,引入了 Path、Paths、Files三个类。
此三个类声明在:java.nio.file包下。
Path可以看做是java.io.File类的升级版本。也可以表示文件或文件目录,与平台无关
如何实例化Path:使用Paths.
static Path get(String first, String … more) : 用于将多个字符串串连成路径
static Path get(URI uri): 返回指定uri对应的Path路径
String toString() : 返回调用 Path 对象的字符串表示形式
boolean startsWith(String path) : 判断是否以 path 路径开始
boolean endsWith(String path) : 判断是否以 path 路径结束
boolean isAbsolute() : 判断是否是绝对路径
Path getParent():返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
Path getRoot() :返回调用 Path 对象的根路径
Path getFileName() : 返回与调用 Path 对象关联的文件名
int getNameCount(): 返回Path 根目录后面元素的数量
Path getName(int idx): 返回指定索引位置 idx 的路径名称
Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
Path resolve(Path p) :合并两个路径,返回合并后的路径对应的Path对象
File toFile(): 将Path转化为File类的对象
Files:
操作文件或目录的工具类
Path copy(Path src, Path dest, CopyOption … how): 文件的复制
Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错
void deleteIfExists(Path path): Path对应的文件/目录如果存在,执行删除.如果不存在,正常执行结束
Path move(Path src, Path dest, CopyOption…how): 将 src 移动到 dest 位置
long size(Path path): 返回 path 指定文件的大小
boolean exists(Path path, LinkOption … opts): 判断文件是否存在
boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件
boolean isHidden(Path path) : 判断是否是隐藏文件(要求此path对应的物理上的文件需要存在。才可判断是否隐藏。否则,抛异常)
boolean isReadable(Path path) : 判断文件是否可读
boolean isWritable(Path path) : 判断文件是否可写
boolean notExists(Path path, LinkOption … opts): 判断文件是否不存在
InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
OutputStream newOutputStream(Path path, OpenOption…how): 获取 OutputStream 对象
SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式
DirectoryStream<Path> newDirectoryStream(Path path) : 打开 path 指定的目录
30.网络编程
问题:
1.如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
2.找到主机后如何可靠高效地进行数据传输
要素:
1.对应问题一:IP和端口号(哪台主机和哪个应用程序)
2.对应问题二:提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)
要素一:IP和端口号
1.IP:唯一的标识 Internet 上的计算机(通信实体)
2.在Java中使用InetAddress类代表IP
3.IP分类:IPv4 和 IPv6 ; 万维网 和 局域网
4.域名: www.baidu.com www.mi.com www.sina.com www.jd.com www.vip.com
5.本地回路地址:127.0.0.1 对应着:localhost
6.如何实例化InetAddress:两个方法:getByName(String host) 、 getLocalHost()
两个常用方法:getHostName() / getHostAddress()
7.端口号:正在计算机上运行的进程。
要求:不同的进程有不同的端口号
范围:被规定为一个 16 位的整数 0~65535。
8.端口号与IP地址的组合得出一个网络套接字:Socket
客户端步骤分为:
创建Socket对象,指明服务器端的ip和端口号(关键字: Socket、InetAddress、getByName)
获取一个输出流,用于输出数据(关键字: OutputStream、 socket.getOutputStream)
写出数据的操作(关键字: getBytes)
关闭资源(后用先关,先关输出流,再关socket)
服务端步骤分为:
创建服务器端的ServerSocket,指明自己的端口号(关键字: ServerSocket)
调用accept()表示接收来自于客户端的socket(关键字: accept)
获取输入流(关键字: getInputStream)
读取输入流中的数据(关键字: ByteArrayOutputStream)
关闭资源
关闭数据的输出再接收
socket.shutdownOutput();
UDP:DatagramSocket、DatagramPacket
URL网络编程:
1.URL:统一资源定位符,对应着互联网的某一资源地址
2.格式:
http://localhost:8080/examples/beauty.jpg?username=Tom
协议 主机名 端口号 资源地址 参数列表
url.getProtocol( ): 获取该URL的协议名
url.getHost( ): 获取该URL的主机名
url.getPort( ): 获取该URL的端口号
url.getPath( ): 获取该URL的文件路径
url.getFile( ): 获取该URL的文件名
url.getQuery( ): 获取该URL的查询名
31.反射
在Person类外部,不可以通过Person类的对象调用其内部私有结构。可以通过反射,创建Person类的对象,调用对象指定的属性、方法,甚至可以通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性。
疑问1:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用那个?
建议:直接new的方式。
什么时候会使用:反射的方式。 反射的特征:动态性
疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?
不矛盾。
java.lang.Class类:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
换句话说,Class的实例就对应着一个运行时类。
加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式 来获取此运行时类。
获取Class的实例的方式
(前三种方式需要掌握)
方式一:调用运行时类的属性:.class
Class clazz1 = Person.class;
System.out.println(clazz1);
方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("com.atguigu.java.Person");
System.out.println(clazz3);
方式四:使用类的加载器:ClassLoader (了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
System.out.println(clazz4);
类的加载器:
对于自定义类,使用系统类加载器进行加载
调用系统类加载器的getParent():获取扩展类加载器
调用扩展类加载器的getParent():无法获取引导类加载器
引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
类加载器作用是用来把类(class)装载进内存的。JVM规范定义了如下类型的类的加载器。
Properties:用来读取配置文件
方式一在当前module下,方式二在当前module的src下
使用getResourceAsStream读取文件
将要读去的数据写入一个.properties文件中,创建方式New --> Resource Bundle
此时的文件默认在当前的module下。
读取配置文件的方式一:
FileInputStream fis = new FileInputStream("src\\jdbc1.properties");
pros.load(fis);
读取配置文件的方式二:使用ClassLoader
配置文件默认识别为:当前module的src下
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);
newInstance():
调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
要想此方法正常的创建运行时类的对象,要求:
1.运行时类必须提供空参的构造器
2.空参的构造器的访问权限得够。通常,设置为public。
在javabean中要求提供一个public的空参构造器。原因:
1.便于通过反射,创建运行时类的对象
2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
获取运行时类的属性结构:
getFields():获取当前运行时类及其父类中声明为public访问权限的属性
getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
获取运行时类的权限修饰符 数据类型 变量名
getModifiers():权限修饰符
getType():数据类型
getName():变量名
获取运行时类的方法结构:
getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
获取注解、权限修饰符、返回值类型、方法名、形参列表、抛出的异常
getAnnotations(): 获取方法声明的注解
Modifier.toString(m.getModifiers()): 权限修饰符
获取的权限修饰符比较特殊, getModifiers()返回的是int类型(如1, 2, 3, 4),这些数字分别代表public, protected等
使用Modifier.toString将它转化为字符串形式的权限修饰符 getReturnType().getName():返回值类型
32.代理
静态代理:
代理类和被代理类在编译期间,就确定下来
动态代理:
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
问题二:当通过代理类的对象调用方法是,如何动态的去调用被代理类中的同名方法
Proxy.newProxyInstance()
33.Lambda表达式
(o1,o2)->Integer.compare(o1,o2)
格式:
->:lambda操作符或箭头操作符
->左边:lambda形参列表(其实就是接口中的抽象方法的形参列表)
->右边:lambda体(其实就是重写的抽象方法的方法体)
使用:
->左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只有一个参数,其一对()也可以省略
->右边:lambda体应该使用一对{}包裹;如果lambda体只有一条执行语句(可能是return语句),可以省略这一对{}和return关键字
本质:
接口的实例
34.函数式接口
4个核心函数式接口:
消费型接口Comsumer<T> void accept(T t)
供给型接口Supplier<T> void get()
函数型接口Function<T, R> R apply(T t)
断定型接口Predicate<T> boolean test(T t)
35.方法引用与构造器引用
36.Strean API
37.Optional类的引用