一、基础数据结构
四类八种
整形:byte short int long
浮点型:float double
布尔值:boolean
字符型:char
1、取值范围:
整形:byte -2^7~2^7-1 10000000(-128)
浮点型:符号位+阶码(指数为)+尾数
char一个字符连个字节(unicode)
2、字符发展
ASCII(七位)->ISO8859-1(单字节)->unicode(两个字节)
->utf-8(可变长的字符集,英文一个字节,中文三个字节)
GBK GB2312中国的国标字符集
3、变量
变量的类型 变量的名字 = 具体的值;
定义和赋值两部分
他在内存里开辟了一片空间,可以存数据
他可以反复的到处使用
变量其实就是一个引用,他指向堆内存的一块区域,我们想操作一个对象只需要使用它的引用即可
4、标识符命名规则
可以用下划线_和$但是不建议使用
可以用数字和字母,但是数字不能开头
不能使用关键字
所有的名字必须驼峰式命名、变量、方法首字母小写,类名首字母。静态常量全部大写用下划线分割。
尽量少用拼音,如果要用就整体都是拼音,不能拼音加因为
二、运算符
1、逻辑运算符
与 (&): 全部为真才是真,有一个是假就是假 串联电路 1 & 1 = 1;0 & 0 = 0;1 & 0 = 0
或 (|):全部为假才是假,有一个为真就是真 并联电路 1 | 1 = 1; 1 | 0 = 1;0 | 0 =0
非(!): 非真即是假 非假就是真
异或(^): 相同为假,不同为真 1 ^ 1 = 0; 0 ^ 0 =0; 1 ^ 0 =1
双与(&&):短路运算符,前边为假直接返回假,后边的就不进行计算了
双或(||):短路运算符,前边为真直接返回真,后边的就不进行j计算了
2、算术运算符
加 减 乘 除 取余
3、赋值运算符
=
++
--
+=
-=
4、位移运算符
>>有符号右移
<<有符号左移
>>>无符号右移
5、三目运算符
条件?结果一:结果二
三、流程控制语句
if语句
if(i==0){
}else{
}
if(i==0){
}
if(i!=2){
}
switch语句
//i可以是byte short int String enum的数据
switch(i){
case1:
System.out.println();
break;
case2:
System.out.println();
break;
default:
System.out.println();
break;
}
while语句
//如果不是死循环,一定要想办法退出
booleanflag=true;
while(flag){
if(条件)
flag=false
}
do{
if(条件)
flag=false
}while(flag);
for语句
for(inti=0;i<10;i++){
}
四、数组
1、定义
类型 名字[] = new 类型[长度]
类型 名字[] = {1,2,3}
类型 名字[]; 名字 = new 类型[]{1,2,3,4};
2、特性
一旦创建就不能改变,类型不能改,长度不能改
length代表他的长度
下表从零开始,最后一个是length-1
名字[下标] = 0;
如果使用时超出下标,ArrayIndexOutOfBoundsException
3、几个例子
遍历、取最大最小值、求和、交换
inttemp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
五、方法
权限修饰符(static静态)(
1、权限修饰符
修饰词本类同一个包的类继承类其他类
private√×××
无(默认)√√××
protected√√√×
public√√√√
2、返回值类型
3、可变参数
只能出现一次
必须放在最后
调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中一数组的形式访问可变参数。
publicvoidf1(int...nums)
4、方法的重载
方法名一样
参数列表不一样
每一个方法都有个签名 就是用名字和参数签名的
六、面向对象
1、封装
需要什么对象,就创建一个什么类,万物皆对象
publicclassUser{
// 私有的属性
//构造器
//实例方法
//getter and setter
//toString(如果需要)
}
可能还用工具类和常量类,工具类里一般大多都是静态方法,常量类里大多是静态常量
publicstaticfinalStringORDER_STATUS="已发货"
1.1属性
属性全部私有,使用getter取值,使用setter赋值,能做安全控制
1.2方法
1.3构造器
new 一个对象会主动调用他的构造器
构造器可以有多个,可以有不同参数的构造器
构造器如果不写,或默认送你一个无参构造,一旦你写了有参构造方法,无参构造器需要手写
2、继承
extends关键字,子类拥有父类(超类)一切(除了被private修饰的)
构造子类时,一定会先构造一个父类
父类的静态字段——>父类静态代码块——>子类静态字段——>子类静态代码块——>父类成员变量(非静态字段)——>父类非静态代码块——>父类构造器——>子类成员变量——>子类非静态代码块——>子类构造器
子类可以重写父类的方法,重写的方法需要加一个注解@override
Object是所有类的顶级父类
3、多态
有继承
有重写
有父类引用指向子类对象
List
list.add(1);
4、抽象类和接口
抽象类:拥有抽象方法的类,必须使用abstract修饰
继承抽象类必须实现它的抽象方法
接口:全部都是抽象方法(没有方法体的方法)
实现接口必须实现所有的抽象方法
5、Object的几个方法
equals
hashcode(hash算法)
重写equals必须重写hashcode,主要是因为有hashmap,hashset这类的集合
toString()
6、this和super
this指向本实例
super指向父类的实例
this和super可以当构造器使用,如果想使用super(),必须放在第一行
7、类的加载顺序
父类的静态属性
父类的静态代码块
子类的静态属性
子类的静态代码块
父类的非静态属性
父类的非静态代码块
父类的构造器
子类的非静态属性
子类的非静态代码快
子类构造器
七、集合
集合是存东西的,能存很多同一种类型(包含他的子类)的对象
1、Collection
2、list(列表)
需要定义泛型
ArrayLIst和LInkedLIst
区别:
ArrayList基于动态数组实现的非线程安全的集合;LinkedList基于链表实现的非线程安全的集合。
对于随机index访问的get和set方法,一般ArrayLIst的速度要由于LinkedLIst。因为ArrayList直接通过数组下标直接找到元素;LinkedList要移动指针遍历每个元素知道找到为止。
新增和删除元素,一般LinkedList的速度要优于ArrayList。因为ArrayList在新增和删除元素时,可能扩容和复制数组;LinkedList实例化对象需要时间外,只需要修改指针即可。
LinkedList集合不支持高效的随机访问(RandomAccess)。
ArrayList的空间浪费主要体现在在list列表的结尾要预留一定的容量空间,而LinkedList的空间话费则体现在它的每一个元素都需要消耗相当的空间。
3、set
set和list的区别
list:有序(插入的顺序),可重复
set:无序,不可重复
hashset:怎么判断一个对象是不是重复的:equals和hashcode这两个方法,如果两个对象不是用一个地址但是他们 equal,并且hashcode一样就说明一样
4、map(映射)
hashmap最重要的集合,如果有兴趣可以看看hashmap的源码解读,特别复杂
5、集合的遍历方式
list的遍历方式:普通for循环,增强for循环,迭代器(iterator)
set的遍历方式:不能用普通for循环,增强for循环,迭代器(iterator)
hashmap的遍历方式:entrySet来遍历,还能用迭代器
ArrayList
List
list=new ArrayList (); list.add("Hello");
list.add("World");
list.add("HAHAHAHA");
//第一种遍历方法使用 For-Each 遍历 List
for (String str : list) { //也可以改写 for(int i=0;i
System.out.println(str);
}
//第二种遍历,把链表变为数组相关的内容进行遍历
String[] strArray=new String[list.size()];
list.toArray(strArray);
for(int i=0;i
{
System.out.println(strArray[i]);
}
//第三种遍历 使用迭代器进行相关遍历
Iterator
ite=list.iterator(); while(ite.hasNext())//判断下一个元素之后有值
{
System.out.println(ite.next());
}
Map
Map
map = new HashMap (); map.put("1", "value1");
map.put("2", "value2");
map.put("3", "value3");
//第一种:普遍使用,二次取值
System.out.println("通过Map.keySet遍历key和value:");
for (String key : map.keySet()) {
System.out.println("key= "+ key + " and value= " + map.get(key));
}
//第二种
System.out.println("通过Map.entrySet使用iterator遍历key和value:");
Iterator
> it = map.entrySet().iterator(); while (it.hasNext()) {
Map.Entry
entry = it.next(); System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
//第三种:推荐,尤其是容量大时
System.out.println("通过Map.entrySet遍历key和value");
for (Map.Entry
entry : map.entrySet()) { System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
//第四种
System.out.println("通过Map.values()遍历所有的value,但不能遍历key");
for (String v : map.values()) {
System.out.println("value= " + v);
}
}
八、泛型
1、什么是泛型?
Java泛型设计原则:只要在编译时期没有出现警告,那么运行时期就不会出现ClassCastException异常.
泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型
参数化类型:
把类型当作是参数一样传递
<数据类型> 只能是引用类型
2、泛型基础
2.1泛型类
泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来....这样的话,用户明确了什么类型,该类就代表着什么类型...用户在使用的时候就不用担心强转的问题,运行时转换异常的问题了。
在类上定义的泛型,在类的方法中也可以使用!
/*
1:把泛型定义在类上
2:类型变量定义在类上,方法中也可以使用
*/
public class ObjectTool
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
测试代码:
用户想要使用哪种类型,就在创建的时候指定类型。使用的时候,该类就会自动转换成用户想要使用的类型了。
public static void main(String[] args) {
//创建对象并指定元素类型
ObjectTool
tool.setObj(new String("钟福成"));
String s = tool.getObj();
System.out.println(s);
//创建对象并指定元素类型
ObjectTool
/**
* 如果我在这个对象里传入的是String类型的,它在编译时期就通过不了了.
*/
objectTool.setObj(10);
int i = objectTool.getObj();
System.out.println(i);
}
2.2泛型方法
前面已经介绍了泛型类了,在类上定义的泛型,在方法中也可以使用.....
现在呢,我们可能就仅仅在某一个方法上需要使用泛型....外界仅仅是关心该方法,不关心类其他的属性...这样的话,我们在整个类上定义泛型,未免就有些大题小作了。
定义泛型方法....泛型是先定义后使用的
//定义泛型方法..
public
System.out.println(t);
}
测试代码:
用户传递进来的是什么类型,返回值就是什么类型了
public static void main(String[] args) {
//创建对象
ObjectTool tool = new ObjectTool();
//调用方法,传入的参数是什么类型,返回值就是什么类型
tool.show("hello");
tool.show(12);
tool.show(12.5);
}
2.3泛型类派生出的子类
前面我们已经定义了泛型类,泛型类是拥有泛型这个特性的类,它本质上还是一个Java类,那么它就可以被继承
那它是怎么被继承的呢??这里分两种情况
子类明确泛型类的类型参数变量
子类不明确泛型类的类型参数变量
2.3.1子类明确泛型类的类型参数变量
泛型接口
/*
把泛型定义在接口上
*/
public interface Inter
public abstract void show(T t);
}
实现泛型接口的类.....
/**
* 子类明确泛型类的类型参数变量:
*/
public class InterImpl implements Inter
@Override
public void show(String s) {
System.out.println(s);
}
}
2.3.2子类不明确泛型类的类型参数变量
当子类不明确泛型类的类型参数变量时,外界使用子类的时候,也需要传递类型参数变量进来,在实现类上需要定义出类型参数变量
/**
* 子类不明确泛型类的类型参数变量:
* 实现类也要定义出
*
*/
public class InterImpl
@Override
public void show(T t) {
System.out.println(t);
}
}
测试代码:
public static void main(String[] args) {
//测试第一种情况
//Inter
//i.show("hello");
//第二种情况测试
Inter
ii.show("100");
}
值得注意的是:
实现类的要是重写父类的方法,返回值的类型是要和父类一样的!
类上声明的泛形只对非静态成员有效
3、类型通配符
为什么需要类型通配符????我们来看一个需求.......
现在有个需求:方法接收一个集合参数,遍历集合并把集合元素打印出来,怎么办?
按照我们没有学习泛型之前,我们可能会这样做:
public void test(List list){
for(int i=0;i System.out.println(list.get(i)); } } 上面的代码是正确的,只不过在编译的时候会出现警告,说没有确定集合元素的类型....这样是不优雅的... 那我们学习了泛型了,现在要怎么做呢??有的人可能会这样做: public void test(List for(int i=0;i System.out.println(list.get(i)); } } 这样做语法是没毛病的,但是这里十分值得注意的是:该test()方法只能遍历装载着Object的集合!!! 强调:泛型中的 那现在咋办???我们是不清楚List集合装载的元素是什么类型的,List这样是行不通的........于是Java泛型提供了类型通配符 ? 所以代码应该改成这样: public void test(List> list){ for(int i=0;i System.out.println(list.get(i)); } } ?号通配符表示可以匹配任意类型,任意的Java类都可以匹配..... 现在非常值得注意的是,当我们使用?号通配符的时候:就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。 记住,只能调用与对象无关的方法,不能调用对象与类型有关的方法。因为直到外界使用才知道具体的类型是什么。也就是说,在上面的List集合,我是不能使用add()方法的。因为add()方法是把对象丢进集合中,而现在我是不知道对象的类型是什么。 3.1设定通配符上限 首先,我们来看一下设定通配符上限用在哪里.... 现在,我想接收一个List集合,它只能操作数字类型的元素【Float、Integer、Double、Byte等数字类型都行】,怎么做??? 我们学习了通配符,但是如果直接使用通配符的话,该集合就不是只能操作数字了。因此我们需要用到设定通配符上限 List extends Number> 上面的代码表示的是:List集合装载的元素只能是Number的子类或自身 public static void main(String[] args) { //List集合装载的是Integer,可以调用该方法 List test(integer); //List集合装载的是String,在编译时期就报错了 List test(strings); } public static void test(List extends Number> list) { } 3.2设定通配符下限 既然上面我们已经说了如何设定通配符的上限,那么设定通配符的下限也不是陌生的事了。直接来看语法吧 //传递进来的只能是Type或Type的父类 super Type> 设定通配符的下限这并不少见,在TreeSet集合中就有....我们来看一下 public TreeSet(Comparator super E> comparator) { this(new TreeMap<>(comparator)); } 那它有什么用呢??我们来想一下,当我们想要创建一个TreeSet 那么这个Comparator的选择就有很多了,它可以是Comparator 这样做,就非常灵活了。也就是说,只要它能够比较字符串大小,就行了 经评论去补充:在泛型的上限和下限中有一个原则:PECS(Producer Extends Consumer Super) 书上是这样写的: 带有子类限定的可以从泛型读取【也就是--->(? extend T)】-------->Producer Extends带有超类限定的可以从泛型写入【也就是--->(? super T)】-------->Consumer Super也有相关博文写得很好: http://blog.51cto.com/flyingc...https://blog.csdn.net/xx32666... 4、通配符和泛型方法 大多时候,我们都可以使用泛型方法来代替通配符的..... //使用通配符 public static void test(List> list) { } //使用泛型方法 public } 上面这两个方法都是可以的.....那么现在问题来了,我们使用通配符还是使用泛型方法呢?? 原则: 如果参数之间的类型有依赖关系,或者返回值是与参数之间有依赖关系的。那么就使用泛型方法 如果没有依赖关系的,就使用通配符,通配符会灵活一些. 5、泛型擦除 泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。 6、兼容性 JDK5提出了泛型这个概念,但是JDK5以前是没有泛型的。也就是泛型是需要兼容JDK5以下的集合的。 当把带有泛型特性的集合赋值给老版本的集合时候,会把泛型给擦除了。 值得注意的是:它保留的就类型参数的上限。 List //类型被擦除了,保留的是类型的上限,String的上限就是Object List list1 = list; 如果我把没有类型参数的集合赋值给带有类型参数的集合赋值,这又会怎么样?? List list = new ArrayList(); List 它也不会报错,仅仅是提示“未经检查的转换” 1、进程和线程 进程:是正在运行的程序 是系统进行资源分配和调用的独立单位 每一个进程都有它自己的内存空间和系统资源 线程:是进程中的单个顺序控制流,是一条执行路径 单线程:一个进程如果只有一条执行路径,则称为单线程程序 多线程:一个进程如果有多条执行路径,则称为多线程程序 2、实现多线程的方式: 一、:继承Thread类 1、方法介绍 方法名说明 void run()在线程开启后,此方法将被调用执行 void start()使此线程开始执行,Java虚拟机会调用run方法() 2、实现步骤 定义一个类MyThread继承Thread类 在MyThread类中重写run()方法 创建MyThread类的对象 启动线程 public class MyThread extends Thread { @Override public void run() { for(int i=0; i<100; i++) { System.out.println(i); } } } public class MyThreadDemo { public static void main(String[] args) { MyThread my1 = new MyThread(); MyThread my2 = new MyThread(); // my1.run(); // my2.run(); //void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法 my1.start(); my2.start(); } } 注意: 为什么要重写run()方法? 因为run()是用来封装被线程执行的代码 run()方法和start()方法的区别? run():封装线程执行的代码,直接调用,相当于普通方法的调用 start():启动线程;然后由JVM调用此线程的run()方法 3、设置和获取线程名称 方法名说明 void setName(String name)将此线程的名称更改为等于参数name String getName()返回此线程的名称 Thread currentThread()返回对当前正在执行的线程对象的引用 4、线程优先级 线程调度 两种调度方式 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些 Java使用的是抢占式调度模型 随机性 假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的 优先级相关方法 方法名说明 final int getPriority()返回此线程的优先级 final void setPriority(int newPriority)更改此线程的优先级 线程默认优先级是5;线程优先级的范围是:1-10 5、线程控制 相关方法 方法名说明 static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的毫秒数 void join()等待这个线程死亡 void setDaemon(boolean on)将此线程标记为守护线程。当其他线程结束,守护线程也同时结束 6、线程的生命周期 线程一共有五种状态,线程在各种状态之间转换。 二、实现多线程方式二:实现Runnable接口 1、Thread构造方法 方法名说明 Thread(Runnable target)分配一个新的Thread对象 Thread(Runnable target, String name)分配一个新的Thread对象 2、实现步骤 定义一个类MyRunnable实现Runnable接口 在MyRunnable类中重写run()方法 创建MyRunnable类的对象 创建Thread类的对象,把MyRunnable对象作为构造方法的参数 启动线程 3、相比继承Thread类,实现Runnable接口的好处 避免了Java单继承的局限性 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想 4、代码实现 public class SellTicket implements Runnable { private int tickets = 100; //在SellTicket类中重写run()方法实现卖票,代码步骤如下 @Override public void run() { while (true) { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票"); tickets--; } } } } public class SellTicketDemo { public static void main(String[] args) { //创建SellTicket类的对象 SellTicket st = new SellTicket(); //创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称 Thread t1 = new Thread(st,"窗口1"); Thread t2 = new Thread(st,"窗口2"); Thread t3 = new Thread(st,"窗口3"); //启动线程 t1.start(); t2.start(); t3.start(); } } 3、线程同步 1、同步代码块格式: synchronized(任意对象) { 多条语句操作共享数据的代码 } synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁 2、同步的好处和弊端 好处:解决了多线程的数据安全问题 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率 3、同步方法 1、同步方法的格式 同步方法:就是把synchronized关键字加到方法上 修饰符 synchronized 返回值类型 方法名(方法参数) { 方法体; } 同步方法的锁对象是什么呢? this 2、静态同步方法 同步静态方法:就是把synchronized关键字加到静态方法上 修饰符 static synchronized 返回值类型 方法名(方法参数) { 方法体; } 同步静态方法的锁对象是什么呢? 类名.class 4、线程安全的类 StringBuffer 线程安全,可变的字符序列 从版本JDK 5开始,被StringBuilder 替代。 通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步 Vector 从Java 2平台v1.2开始,该类改进了List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Vector被同步。 如果不需要线程安全的实现,建议使用ArrayList代替Vector Hashtable 该类实现了一个哈希表,它将键映射到值。 任何非null对象都可以用作键或者值 从Java 2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Hashtable被同步。 如果不需要线程安全的实现,建议使用HashMap代替Hashtable 5、Lock锁 Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化 1、ReentrantLock构造方法 方法名说明 ReentrantLock()创建一个ReentrantLock的实例 2、加锁解锁方法 方法名说明 void lock()获得锁 void unlock()释放锁 3、代码演示 public class SellTicket implements Runnable { private int tickets = 100; private Lock lock = new ReentrantLock(); @Override public void run() { while (true) { try { lock.lock(); if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票"); tickets--; } } finally { lock.unlock(); } } } } public class SellTicketDemo { public static void main(String[] args) { SellTicket st = new SellTicket(); Thread t1 = new Thread(st, "窗口1"); Thread t2 = new Thread(st, "窗口2"); Thread t3 = new Thread(st, "窗口3"); t1.start(); t2.start(); t3.start(); } } 1、String String是引用数据类型,它是final的,一旦创建就不能改变。 String,StringBuffer,StringBuilder的区别面试常问。 String是不可变的,线程安全的 StringBuffer是可变字符串,线程安全 StringBulider是可变字符串,线程不安全 当有大量字符串拼接的代码时,没有线程安全的要就就使用StringBuilder(一般情况),有要求就用StringBuffer 2、工具类 Data Math Collections Arrays Canlader 1、定义枚举类型 定义枚举类型需要使用enum关键字,例如: 枚举可以用equals来比较,也可以用==来比较 public enum Direction { FRONT, BEHIND, LEFT, RIGHT; } Direction d = Direction.FRONT; 2、枚举与switch 枚举类型可以在switch中使用 Direction d = Direction.FRONT; switch(d) { case FRONT: System.out.println("前面");break; case BEHIND:System.out.println("后面");break; case LEFT: System.out.println("左面");break; case RIGHT: System.out.println("右面");break; default:System.out.println("错误的方向"); } Direction d1 = d; System.out.println(d1); 3、枚举的方法 int compareTo(E e):比较两个枚举常量谁大谁小,其实比较的就是枚举常量在枚举类中声明的顺序,例如FRONT的下标为0,BEHIND下标为1,那么FRONT小于BEHIND; boolean equals(Object o):比较两个枚举常量是否相等; int hashCode():返回枚举常量的hashCode; String name():返回枚举常量的名字; int ordinal():返回枚举常量在枚举类中声明的序号,第一个枚举常量序号为0; String toString():把枚举常量转换成字符串; static T valueOf(Class enumType, String name):把字符串转换成枚举常量。 枚举类的构造器枚举类也可以有构造器,构造器默认都是private修饰,而且只能是private。因为枚举类的实例不能让外界来创建! enum Direction { FRONT, BEHIND, LEFT, RIGHT;//[在枚举常量后面必须添加分号,因为在枚举常量后面还有其他成员时,分号是必须的。枚举常量必须在枚举类中所有成员的上方声明。] Direction()//[枚举类的构造器不可以添加访问修饰符,枚举类的构造器默认是private的。但你自己不能添加private来修饰构造器。] { System.out.println("hello"); } } 其实创建枚举项就等同于调用本类的无参构造器,所以FRONT、BEHIND、LEFT、RIGHT四个枚举项等同于调用了四次无参构造器,所以你会看到四个hello输出。 5、枚举类可以有成员 其实枚举类和正常的类一样,可以有实例变量,实例方法,静态方法等等,只不过它的实例个数是有限的,不能再创建实例而已。 enum Direction { FRONT("front"), BEHIND("behind"), LEFT("left"), RIGHT("right"); private String name; Direction(String name) { this.name = name; } public String getName() { return name; } } Direction d = Direction.FRONT; System.out.println(d.getName()); 因为Direction类只有唯一的构造器,并且是有参的构造器,所以在创建枚举项时,必须为构造器赋值:FRONT(“front”),其中”front”就是传递给构造器的参数。你不要鄙视这种语法,你应该做的是接受这种语法! Direction类中还有一个实例域:String name,我们在构造器中为其赋值,而且本类还提供了getName()这个实例方法,它会返回name的值。 6、枚举类中还可以有抽象方法 还可以在枚举类中给出抽象方法,然后在创建每个枚举项时使用“特殊”的语法来重复抽象方法。所谓“特殊”语法就是匿名内部类!也就是说每个枚举项都是一个匿名类的子类对象! 通常fun()方法应该定义为抽象的方法,因为每个枚举常量都会去重写它。 你无法把Direction声明为抽象类,但需要声明fun()方法为抽象方法。 enum Direction { FRONT() { public void fun() { System.out.println("FROND:重写了fun()方法"); } }, BEHIND() { public void fun() { System.out.println("BEHIND:重写了fun()方法"); } }, LEFT() { public void fun() { System.out.println("LEFT:重写了fun()方法"); } }, RIGHT() { public void fun() { System.out.println("RIGHT:重写了fun()方法"); } }; public abstract void fun()[只需要把fun()方法修改为抽象方法,但不可以把Direction类声明为抽象类。]; } 7、每个枚举类都有两个特殊方法 每个枚举类都有两个不用声明就可以调用的static方法,而且这两个方法不是父类中的方法。这又是枚举类特殊的地方,下面是Direction类的特殊方法。 static Direction[] values():返回本类所有枚举常量; static Direction valueOf(String name):通过枚举常量的名字返回Direction常量,注意,这个方法与Enum类中的valueOf()方法的参数个数不同。 关键字 input 输入 output 输出 stream 流 writer 字符输入流 reader 字符输入流 File文件 只要会了字节流就都会了 字节流能处理一切 按输入输出分 按字节和字符分 节点流和处理流 1、checked 检查性异常 在编译的时候使用try,catch来预先捕获异常来提供解决方案 FileNotFoundException IoException InterruptedException 这种异常继承自Exception,必须捕获,并处理 2、运行时异常 去坐飞机,没带护照,这是你自己的原因,可以通过检查一下解决 ArrayIndexOutOfBoundsException ClassCastException 数学类异常 这种异常继承RuntimeException,不需要捕获,需要通过检查来预防。 3、错误 Error stackOutOfMemoryError 比如递归出不去 4、自定义异常 很多时候我们需要自己定义一些异常来帮助我们来处理一些业务。 1、排序 选择排序 冒泡排序 插入排序 希尔排序 快速排序 归并排序 堆排序 大根堆 小根堆 完全二叉树 同排序 基数排序 计数排序 2、查找 二分查找 3、链表 数组和链表的区别: 数组按下标查找块,插入效率慢 链表查找慢,插入效率快 4、队列和栈 5、hash算法,可以将任何的文件转化成一个定长的字符串 hash碰撞:当两个文件生成的字符串一样了,就叫hash碰撞 5、递归 方法自己调用自己 一定要有出口,没有出口就会栈内存溢出; 案例:斐波那契数列 6、超级集合 1、概述 一、网络编程三要素 1、IP地址 要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识 2、端口 网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识 3、协议 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的**规则**被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。**常见的协议有UDP协议和TCP协议** 二、IP地址 IP地址:是网络中设备的唯一标识 IP地址分为两大类 IPv4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。例如一个采用二进制形式的IP地址是“11000000 10101000 00000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号“.”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多 IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题 DOS常用命令: ipconfig:查看本机IP地址 ping IP地址:检查网络是否连通 特殊IP地址: 127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用 ### 三、InetAddress InetAddress:此类表示Internet协议(IP)地址 相关方法 方法名说明 static InetAddress getByName(String host)确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址 String getHostName()获取此IP地址的主机名 String getHostAddress()返回文本显示中的IP地址字符串 代码演示 public class InetAddressDemo { public static void main(String[] args) throws UnknownHostException { //InetAddress address = InetAddress.getByName("itheima"); InetAddress address = InetAddress.getByName("192.168.1.66"); //public String getHostName():获取此IP地址的主机名 String name = address.getHostName(); //public String getHostAddress():返回文本显示中的IP地址字符串 String ip = address.getHostAddress(); System.out.println("主机名:" + name); System.out.println("IP地址:" + ip); } } ### 四、端口和协议 端口 设备上应用程序的唯一标识 端口号 用两个字节表示的整数,它的取值范围是0~65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败 协议 计算机网络中,连接和通信的规则被称为网络通信协议 UDP协议 用户数据报协议(User Datagram Protocol) UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。 由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议 TCP协议 传输控制协议 (Transmission Control Protocol) TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手” 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠 第一次握手,客户端向服务器端发出连接请求,等待服务器确认 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求 第三次握手,客户端再次向服务器端发送确认信息,确认连接 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等 2、UDP 一、Java中的UDP通信 UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念 Java提供了DatagramSocket类作为基于UDP协议的Socket 二、发送数据 构造方法 方法名说明 DatagramSocket()创建数据报套接字并将其绑定到本机地址上的任何可用端口 DatagramPacket(byte[] buf,int len,InetAddress add,int port)创建数据包,发送长度为len的数据包到指定主机的指定端口 相关方法 方法名说明 void send(DatagramPacket p)发送数据报包 void close()关闭数据报套接字 void receive(DatagramPacket p)从此套接字接受数据报包 发送数据的步骤 创建发送端的Socket对象(DatagramSocket) 创建数据,并把数据打包 调用DatagramSocket对象的方法发送数据 关闭发送端 public class SendDemo { public static void main(String[] args) throws IOException { //创建发送端的Socket对象(DatagramSocket) // DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口 DatagramSocket ds = new DatagramSocket(); //创建数据,并把数据打包 //DatagramPacket(byte[] buf, int length, InetAddress address, int port) //构造一个数据包,发送长度为 length的数据包到指定主机上的指定端口号。 byte[] bys = "hello,udp,我来了".getBytes(); DatagramPacket dp = new DatagramPacket(bys,bys.length,InetAddress.getByName("192.168.1.66"),10086); //调用DatagramSocket对象的方法发送数据 //void send(DatagramPacket p) 从此套接字发送数据报包 ds.send(dp); //关闭发送端 //void close() 关闭此数据报套接字 ds.close(); } } 三、接受数据 接收数据的步骤 创建接收端的Socket对象(DatagramSocket) 创建一个数据包,用于接收数据 调用DatagramSocket对象的方法接收数据 解析数据包,并把数据在控制台显示 关闭接收端 构造方法 方法名说明 DatagramPacket(byte[] buf, int len)创建一个DatagramPacket用于接收长度为len的数据包 相关方法 方法名说明 byte[] getData()返回数据缓冲区 int getLength()返回要发送的数据的长度或接收的数据的长度 示例代码 public class ReceiveDemo { public static void main(String[] args) throws IOException { //创建接收端的Socket对象(DatagramSocket) DatagramSocket ds = new DatagramSocket(12345); while (true) { //创建一个数据包,用于接收数据 byte[] bys = new byte[1024]; DatagramPacket dp = new DatagramPacket(bys, bys.length); //调用DatagramSocket对象的方法接收数据 ds.receive(dp); //解析数据包,并把数据在控制台显示 System.out.println("数据是:" + new String(dp.getData(), 0, dp.getLength())); } } } 3、TCP 一、Java中的TCP通信 Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。 Java为客户端提供了Socket类,为服务器端提供了ServerSocket类 二、发送数据 构造方法 方法名说明 Socket(InetAddress address,int port)创建流套接字并将其连接到指定IP指定端口号 Socket(String host, int port)创建流套接字并将其连接到指定主机上的指定端口号 相关方法 方法名说明 InputStream getInputStream()返回此套接字的输入流 OutputStream getOutputStream()返回此套接字的输出流 示例代码 public class ClientDemo { public static void main(String[] args) throws IOException { //创建客户端的Socket对象(Socket) //Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号 Socket s = new Socket("192.168.1.66",10000); //获取输出流,写数据 //OutputStream getOutputStream() 返回此套接字的输出流 OutputStream os = s.getOutputStream(); os.write("hello,tcp,我来了".getBytes()); //释放资源 s.close(); } } 三、接收数据 构造方法 方法名说明 ServletSocket(int port)创建绑定到指定端口的服务器套接字 相关方法 方法名说明 Socket accept()监听要连接到此的套接字并接受它 示例代码 public class ServerDemo { public static void main(String[] args) throws IOException { //创建服务器端的Socket对象(ServerSocket) //ServerSocket(int port) 创建绑定到指定端口的服务器套接字 ServerSocket ss = new ServerSocket(10000); //Socket accept() 侦听要连接到此套接字并接受它 Socket s = ss.accept(); //获取输入流,读数据,并把数据显示在控制台 InputStream is = s.getInputStream(); byte[] bys = new byte[1024]; int len = is.read(bys); String data = new String(bys,0,len); System.out.println("数据是:" + data); //释放资源 s.close(); ss.close(); } } 1、Lambda表达式 1.1、Lambda表达式的使用前提 使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法 必须有上下文环境,才能推导出Lambda对应的接口 根据局部变量的赋值得知Lambda对应的接口 Runnable r = () -> System.out.println("Lambda表达式"); 根据调用方法的参数得知Lambda对应的接口 new Thread(() -> System.out.println("Lambda表达式")).start(); 1.2、Lambda表达式的标准格式 格式: (形式参数) -> {代码块} 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可 ->:由英文中画线和大于符号组成,固定写法。代表指向动作 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容 组成Lambda表达式的三要素: 形式参数,箭头,代码块 代码演示: //方式一的线程类 public class MyRunnable implements Runnable { @Override public void run() { System.out.println("多线程程序启动了"); } } public class LambdaDemo { public static void main(String[] args) { //方式一 // MyRunnable my = new MyRunnable(); // Thread t = new Thread(my); // t.start(); //方式二 // new Thread(new Runnable() { // @Override // public void run() { // System.out.println("多线程程序启动了"); // } // }).start(); //方式三 new Thread( () -> { System.out.println("多线程程序启动了"); } ).start(); } } 1.3Lambda表达式和匿名内部类的区别 所需类型不同 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类 Lambda表达式:只能是接口 使用限制不同 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式 实现原理不同 匿名内部类:编译之后,产生一个单独的.class字节码文件 Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成 2、接口 2.1接口中默认方法 格式 public default 返回值类型 方法名(参数列表) { } 范例 public default void show3() { } 注意事项 默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉default关键字 public可以省略,default不能省略 2.2接口中静态方法 格式 public static 返回值类型 方法名(参数列表) { } 范例 public static void show() { } 注意事项 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用 public可以省略,static不能省略 2.3接口中私有方法 私有方法产生原因 Java 9中新增了带方法体的私有方法,这其实在Java 8中就埋下了伏笔:Java 8允许在接口中定义带方法体的默认方法和静态方法。这样可能就会引发一个问题:当两个默认方法或者静态方法中包含一段相同的代码实现时,程序必然考虑将这段实现代码抽取成一个共性方法,而这个共性方法是不需要让别人使用的,因此用私有给隐藏起来,这就是Java 9增加私有方法的必然性 定义格式 格式1 private 返回值类型 方法名(参数列表) { } 范例1 private void show() { } 格式2 private static 返回值类型 方法名(参数列表) { } 范例2 private static void method() { } 注意事项 默认方法可以调用私有的静态方法和非静态方法 静态方法只能调用私有的静态方法 3、方法引用九、多线程
十、常用类
十一、枚举
十二、流
十三、异常
十四、算法
十五、网络编程
十六、JDK新特性