目录
心情日记
今天的知识点总的来说就两个,上午一个,下午一个:泛型和枚举,听起来就很开心,其实学起来内容也不算太难,这周的重点是多线程,这只是前菜。多线程在下一期博客!!!
知识点
正式进入Java高级部分
在这之前都是JavaSE的基础部分,在此之后会进入JavaSE的高级部分
面试八股文
JavaSEG高级部分
1.泛型
2.枚举
3.多线程(重点)(找工作必备知识,非常难,工作之后前三年可能没资格接触的,但必须会)
4.集合(数据结构,树,二叉树,红黑树,B+树,B-树)
5.IO流
6.反射和注解
7.网络通信(Socket):写tomcat会用到本次课在超级数组和拆机链表的基础上学习(超级数组后面有)
泛型
之前写的超级数组中要么只能能存数据,要么啥都能存,有点鸡肋
利用泛型解决此问题
SuperArray superArray = new SuperArray(5);
superArray.add("你好");
superArray.add(1);
superArray.add(1.5);
}
什么是泛型?
泛型广泛的、普通的类型。
泛型能够帮助我们把【类型明确】的工作推迟到创建对象或者调方法的时候。意思就是:我定义类的时候,不需要考虑这个数组到底要存什么类型。
创建这个超级数组对象的时候把里面要存的数据的类型确定下来。
泛型的修饰
1、泛型类
2、泛型方法
3、泛型接口泛型类把泛型定义在类上,用户在使用类的时候才能把类型给确定。
具体的使用方法使用<>加上一个未知数。通常用T K V E等大写字母表示。
实际上用啥都行,只要是个单词就可以。
public static void main(String[] args) {
// 如果一个泛型类,在创建对象的时候没有指定泛型类型
// 默认还是Object
// 在使用这个类的时候,去确定泛型类型
// 现在这个超级数组就只能存String类型
// 不规范
SuperArray<String> superArray = new SuperArray();
superArray.add("a");
// superArray.add(1);
// 这个超级数组superArray1就只能存Employee类型
// 在JDK7以后,可以省略等号右边的泛型的声明,<>必须得写,规范
// 类型推断
SuperArray<Employee> superArray1 = new SuperArray<>();
superArray1.add(new Employee());
superArray1.add(new Employee());
superArray1.add(new Employee());
superArray1.add(new Employee());
// 完整写法,JDK7以前
SuperArray<Employee> superArray2 = new SuperArray<Employee>();
}
泛型方法
我们只关心某个方法,可以不定义泛型类
只定义泛型方法泛型方法不一定非要在泛型类中
泛型类也不一定要有泛型方法再定义泛型方法时,要首先定义泛型类型
位置在定义在方法的中间,泛型的使用处之前泛型方法最好都有参数,否则返回值不好写,但也可以没有返回值(void)
只有泛型参数,没有定义泛型类型的方法不是泛型方法
public <T> T show(T t){
//拿着T做很多事,再把T返回回去
//或者调用另一个方法
return t;
}
public static void main(String[] args) {
new Ch03().show("哈哈");
}
继承关系
泛型在继承时:
1.父类是一个泛型类,子类是不是泛型类都可以
2.泛型的声明只能在当前声明的类名后或方法中间(不能再继承的父类后)
3.在子类继承父类时,泛型都显示出来时,父类的泛型随子类的泛型确实而确定(例一)
4.在子类继承父类时,没有写成任何泛型,当前子类就不是泛型,默认为Object
class Father<T>{
T t;
}
//例一:
//在确定子类泛型的某一时刻,父类的泛型和子类相同
class Son<T> extends Father<T>{
}
class Son2 extends Father {
}
public class Ch04 {
public static void main(String[] args) {
Son<Employee> son = new Son<>();
son.t = new Employee();
Son2 son2 = new Son2();
son2.t.notify();
}
}
接口的泛型
如果在一个泛型类中,尽量不要在使用泛型方法,泛型方法多出现在非泛型类中
静态泛型方法
静态方法如果是泛型方法,泛型的声明必须写出来,因为静态结构是属于类的,不属于对象
interface Inter<T> {
T show(T t);
static <T> T info(T t){
return t;
}
}
class Demo01<T> implements Inter<T> {
// @Override
// public Object show(Object o) {
// return null;
// }
@Override
public T show(T t) {
return t;
}
}
public class Ch05 {
public static void main(String[] args) {
Inter.info(1);
}
}
?通配符
可以接受任何类型<?>
<? extands Father>:表示类型只能为Father类或其子类
<? super Son>:表示类型只能为Son类或其父类如果使用Object类型,别写泛型,因为泛型是为了约束数据类型的
JDK1.5之后泛型。JDK5
class Animal{
}
class Dog extends Animal {
}
class Teddy extends Dog {
public static void show(SuperArray<? super Teddy> superArray) {
}
}
public class Ch06 {
public static void main(String[] args) {
SuperArray<Animal> superArray = new SuperArray<>();
// superArray.add(new Animal());
// superArray.add(new Dog());
// superArray.add(new Teddy());
Teddy.show(superArray);
}
}
类型参数化:类型擦除
为了一些兼容性,会使用到原始的类型(没有泛型)是可以的。
泛型刚刚出现的时候,还存在大量的不使用泛型的代码
为了保证项目的兼容性,将参数化类型得到实例传递给设计用于原始类型的方法必须是合法的为了保持兼容性,在Java的泛型中,其实有一种伪泛型,因为Java在编译期间,所有的泛型都会没擦掉
Java的泛型语法式在编译期这个维度上实现的
正常来说在生成的字节码文件中,不包含泛型的类型信息
在JVM中看到的只是SuperArray,由泛型附加的类型信息对JVM是看不到的。可以理解为,泛型的本质就是让程序员在编写代码时遵守的一个规则。
比如SuperArray:在确定了泛型之后,这个超级数组中就统一只放同一类型的数据。
如果放入其他类型,编译不通过。经验:开发中,能够在业务上解决的问题,尽量不要在技术上解决。
1.泛型不能是基本数据类型(原则上来说,泛型可以是数组类型,语法的角度来说,不可以,<>中放的是类名,数组是在编译后菜会生成一个类)
2.方法重载: a.同一个类型 b.方法名相同 c.参数不同
原理:类型擦除
3.多态上
可以在参数中传递接口类型,利用多态来实例化。
public static void show(SuperArray<Employee> superArray){
}
public static void main(String[] args) {
SuperArray<Object> superArray = new SuperArray<>();
superArray.add(10);
}
泛型的应用场景
1.父类(接口):起到的是一个规范的作用,对里面的数据类型没有明确要求
2.容器类:存多个数据的(超级数组、链表,队列、栈)当类型无法确定时,使用泛型
在开发中,我们更多的是使用到一些泛型类或泛型接口
练习:
我们之前的超级数组、链表,
它们两个有一个共同的泛型接口,这四个结构都实现这个接口,重写里面的方法
(设计的就是一个工具集)接口类:
接口中存储的一定是大家都能用的。
(removeFirst 、removeLast)
以后我们面向接口编程,接口里的方法不能出现实现类中方法体为空的情况。
@param <T> */
public interface DataContainer<T> {
void add(T t);
void update(Integer index,T t);
T remove(Integer index);
T get(Integer index); }
数组类
现在,这个超级数组的类就是一个泛型类
当我们在一个类后面加上了泛型<T>,这个T就是一个数据类型。
既然T就是一个数据类型,那我们就可以拿过来用。
代码:
public class SuperArray<T> implements DataContainer<T> {
// 声明了一个T类型的变量t
private T t;
// 维护一个数组,要考虑的是怎么存
private Object [] array;
// 超级数组的长度
private int size;
// 数组当前的容量
private int capacity;
public SuperArray(){
// array = new Integer[10];
this(10);
// capacity = 10;
}
public SuperArray(int capacity){
array = new Object[capacity];
this.capacity = capacity;
}
// 添加数据,默认添加,在数组的尾部添加
public void add(T data) {
// 添加时要确保容量足够,如果不够,就需要扩容
ensureCapacity(size + 1);
// 真正的添加数据
array[size++] = data;
}
@Override
public void update(Integer index, T t) {
set(index,t);
}
@Override
public T get(Integer index) {
// 判断一下index和合法性
if(rangeCheck(index)){
return (T) array[index];
}
return null;
}
// 添加数据,传入两个参数
// 在指定位置添加
public void add(int index,T data){
if(rangeCheck(index)){
ensureCapacity(size + 1);
System.arraycopy(array,index,array,index + 1,size - index);
// 真正的添加数据
array[index] = data;
size++;
}
}
// 删除最后一个数据
public T remove(){
if(size > 0){
return (T) array[--size];
}
return null;
}
@Override
// 删除指定下标位置的元素
public T remove(Integer index){
if(rangeCheck(index)){
T res = (T) array[index];
System.arraycopy(array,index + 1,array,index,(--size - index));
return res;
}
return null;
}
// 修改
private boolean set(int index,T data) {
if(rangeCheck(index)){
array[index] = data;
return true;
}
return false;
}
// 获取超级数组的长度
public int size(){
return size;
}
private boolean rangeCheck(int index) {
// index >= 0
// index <= size - 1
return (index >=0 && index <= size - 1);
}
// 这个方法只在当前类使用,所以声明成private
private void ensureCapacity(int needCapacity) {
// System.out.println(needCapacity + "-----" + capacity);
if(needCapacity > capacity){
// 1.5倍
capacity = capacity + (capacity >> 1);
// 创建一个新的扩容好的数组
Object [] newArray = new Object[capacity];
// 把原数组的数据拷贝过来
/*
src:原数组
srcPos:拷贝原始数组的起始位置
dest:目标数组
destPos:目标数组的起始位置
length:拷贝数据的长度
*/
System.arraycopy(array,0,newArray,0,array.length);
array = newArray;
}
}
public static void main(String[] args) {
SuperArray<String> superArray = new SuperArray<>();
superArray.add("a");
superArray.add("d");
superArray.add("c");
superArray.add("b");
for (int i = 0; i < superArray.size(); i++) {
System.out.println(superArray.get(i));
}
}
}
链表类
链表同理
public class SuperLinked<T> {
private class Node {
private Integer data;
private Node next;
public Integer getData() {
return data;
}
public void setData(Integer data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
public Node() {
}
public Node(Integer data, Node next) {
this.data = data;
this.next = next;
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
", next=" + next +
'}';
}
}
// 链表的长度
private int size;
// 链表的第一个结点
private Node first;
// 链表的最后一个结点
private Node last;
// 无参构造器
public SuperLinked() {
}
// 把数组添加到链表的尾部
public boolean add(Integer data){
// 把传入的数据构建成一个结点
Node node = new SuperLinked().new Node(data,null);
// 如果现在链表是空的,那我就是第一个结点
if(first == null) {
first = node;
}else {
// 如果链表不是空,那我就是最后一个结点
// 我应该是在原来的last结点后面
// 我是原来last结点的下一个结点
last.setNext(node);
}
last = node;
size++;
return true;
}
// 在指定位置添加元素
public boolean add(int index,Integer data) {
Node node = getNode(index);
Node newNode = new SuperLinked().new Node(data,null);
if(node != null){
// Node next = node.getNext();
// newNode.setNext(next);
newNode.setNext(node.getNext());
node.setNext(newNode);
} else {
// 如果要插入的位置是null,只有一种情况,就是整个链表都是空
first = newNode;
last = newNode;
}
size++;
return true;
}
// 默认删除头部的数据
public boolean removeFirst() {
if(size < 0){
return false;
}
if(first != null){
first = first.getNext();
size--;
}
return true;
}
// 删除尾部的数据
public boolean removeLast(){
if(size <= 0){
return false;
}
if(size == 1){
first = null;
last = null;
size--;
return true;
}
if(last != null){
last = getNode(size - 2);
last.setNext(null);
size --;
}
return true;
}
public boolean remove(int index) {
if(size < 0){
return false;
}
if(size == 1){
first = null;
last = null;
size--;
return true;
}else {
Node node = getNode(index - 1);
node.setNext(node.getNext().getNext());
}
size--;
return true;
}
// 修改指定下标位置的元素
public boolean set(int index,Integer data){
Node node = getNode(index);
node.setData(data);
return true;
}
// 根据下标获取指定的数据
public Integer get(int index) {
return getNode(index).getData();
}
// 获取链表的长度
public int size() {
return size;
}
// 根据下标获取指定的结点
private Node getNode(int index){
if(index < 0){
index = 0;
}
if(index >= size - 1){
index = size - 1;
}
// 找到第index个
Node cursor = first;
for (int i = 0; i < index; i++) {
cursor = cursor.getNext();
}
return cursor;
}
public static void main(String[] args) {
SuperLinked superLinked = new SuperLinked();
superLinked.add(1);
superLinked.add(2);
superLinked.add(3);
superLinked.add(4);
superLinked.removeFirst();
superLinked.add(0,100);
for (int i = 0; i < superLinked.size(); i++) {
System.out.println(superLinked.get(i));
}
}
}
枚举类型
应用场景:
在某些情况下,一个类的对象是有限个时(不能随便创建对象)
例如:春夏秋冬、24节气、、星期...规定这个类的对象的个数
public static void main(String[] args) {
Ch01 ch01 = new Ch01();
Calendar instance = Calendar.getInstance();
instance.get(Calendar.YEAR);// int类型
}
枚举在switch中使用
JDK1.5之后
枚举类的命名规则:所有的枚举类要以Enum结尾。
大更新
JDK1.5更新了枚举类枚举类中:
把需要用到的对象声明出来
写对应的构造器
可以有set,get方法
public enum SeasonEnum {
SPRING(1,"春天"),
SUMMER(2,"夏天"),
AUTUMN(3,"秋天"),
WINNER(4,"冬天");
// public static final Season SPRING = new Season(1,"春天");
private Integer value;
private String name;
SeasonEnum(Integer value, String name) {
this.value = value;
this.name = name;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) {
SeasonEnum season = SeasonEnum.SPRING;
switch (season) {
case SPRING:
case SUMMER:
case AUTUMN:
case WINNER:
}
}
枚举的静态导入
*号代表导入枚举类的所有对象
import static com.jsoft.afternoon.SeasonEnum.*;
public static void main(String[] args) {
System.out.println(SPRING.getName());
}
单例模式:使用枚举类实现单例模式。
高效Java
单元素的枚举类型已经成为实现单例模式的最佳方案。
class Singleton {
// 私有化构造器
private Singleton() {}
// 提供公有的获取实例的静态方法
public static Singleton getInstance(){
return SingletonHolder.INSTANT.instant;
}
// 声明一个枚举类(内部类)
private enum SingletonHolder{
INSTANT;
private final Singleton instant;
SingletonHolder() {
instant = new Singleton();
}
}
}
public class Ch05 {
public static void main(String[] args) {
System.out.println(Singleton.getInstance() == Singleton.getInstance());
}
}
枚举的优势
1.int类型不具备安全性。假如某个程序员在定义int时少写了个final,
会存在被他人修改的风险。枚举类,它天然就是一个常量类
2.使用int类型,语义不够明确。如果说在控制台打印输入1。例:定义一个新的季节类
public enum SeasonEnum1 {
SPRING(1,"春天1");
private Integer value;
private String name;
SeasonEnum1(Integer value, String name) {
this.value = value;
this.name = name;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Ch06 {
public static void main(String[] args) {
SeasonEnum spring = SeasonEnum.SPRING;
String name = "春天";
SeasonEnum1 spring1 = SeasonEnum1.SPRING;
if(spring1.equals(spring)) {
System.out.println(123);
}
// 枚举里面都是常量,静态
// 推荐枚举的比较使用 ==
Integer seasonStr = 1;
if(seasonStr == SeasonConstant.SPRING) {
System.out.println(1);
}
}
}