字符串
- == 判断引用类型的地址而不是值
- 当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量进行检查。
如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回。否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用。
String s1 = new String("abc");String s2 = "abc";System.out.println(s1 == s2); //falseSystem.out.println(s1.equals(s2)); //trueString s1 = "abc";String s2 = "abc";System.out.println(s1 == s2); //trueSystem.out.println(s1.equals(s2)); //trueString s1 = "a" + "b" + "c";String s2 = "abc";System.out.println(s1 == s2); //trueSystem.out.println(s1.equals(s2)); //trueString s1 = "ab";String s2 = "abc";String s3 = s1 + "c";System.out.println(s3 == s2); //falseSystem.out.println(s3.equals(s2)); //true
第三段s1和s2都是以字面量的形式创建,s1首先创建,s2创建的时候会在常量池中找是否相同内容的字符串对象,找到了s1,把s1的引用返回给s2。所以s1和s2的地址是相同的。
凡是String s=“a”+“b”;或者String s=“ab”;产生一个字符串常量在栈中。
第四段【s1+“c”】是调用stringBuffer操作并创建一个String对象也就是说+操作符使用StringBuffer的append方式实现的最后返回一个新创建的String对象,而不是string常量。String s3=s1+“c”;保存在堆中。
凡是字符串变量与字符串变量或者字符串常量之间使用+都会产生一个String对象到堆中。
String类的对象内容不可改变,所以每当进行字符串拼接时,总是会在内存中创建一个新的对象,所以经常改变内容的字符串最好不要用String,因为每次生成对象都会对系统性能产生影响。
StringBuilder又称为可变字符序列,是JDK5.0中新增加的一个类,它是一个类似于String的字符串缓冲区,通过某些方法调用可以改变该序列的长度和内容。即它是一个容器,容器中可以装很多字符串,并且能够对其中的字符串进行各种操作。它的内部拥有一个数组用来存放字符串内容,进行字符串拼接时,直接在数组中加入新内容,StringBuilder会自动维护数组的扩容。
日期
public static void main(String[] args) throws ParseException {//获取当前的日期,并把这个日期转换为指定格式的字符串,如2088-08-08 08:08:08。Date d1 = new Date();SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String formatDate1 = sdf1.format(d1);System.out.println(formatDate1);//使用SimpleDateFormat类,把2018-03-04转换为2018年03月04日。SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd");Date d2 = sdf2.parse("2018-03-04");SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy年MM月dd日");String formatDate2 = sdf3.format(d2);System.out.println(formatDate2);//用程序判断2018年2月14日是星期几Calendar c = Calendar.getInstance();c.set(2018,2,14);int week = c.get(Calendar.DAY_OF_WEEK);System.out.println(week);System.out.println(c.get(Calendar.YEAR)+"年"+c.get(Calendar.MONTH)+"月"+c.get(Calendar.DATE)+"日是星期"+(week-1));}
集合
集合框架:
- 集合包括单列集合Collection接口,以及双列集合Map。Collection接口,它有两个子接口List和Set。
- List的特点是有序,可重复。List接口的实现类有ArrayList、LinkedList、Vetor。ArrayList的底层是数组,查询快,增删慢。LinkedList的底层是链表,查询慢,增删快。
- Set的特点是无序,不可重复。Set接口的实现类的HashSet(LinkedHashSet)、TreeSet。HashSet的底层是数组+链表+红黑树(超过阈值8时)。TreeSet的底层是二叉树。
- Map是双列集合接口,可以同时存放键值。键唯一,值可以不唯一,键值对唯一。她的实现类包括HashMap和LinkedHashMap
list转为数组,可以调用toArray方法
public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<>();list.add(1);list.add(2);list.add(3);Object[] o = list.toArray();System.out.println(Arrays.toString(o));}
迭代器的实现原理
Iterator it = list.iterator();
首先调用iterator方法获得一个迭代器对象。然后用hasNext方法判断是否还有下一个元素,用Next方法获取下一个元素。
迭代器在遍历集合的时候,内部采用指针来跟踪集合中的元素。在调用Next之前,指针指在第一个元素之前,不指向任何元素。每调用一个Next,指针后移一位,并将当前指向元素返回。直到遇到hasNext返回false结果时停止移动指针。
//求返回s在al里面第一次出现的索引,如果s没出现过返回-1。
public static int listTest(ArrayList<Integer> al, Integer s){Iterator<Integer> it = al.iterator();int index = 0;while(it.hasNext()){Integer num = it.next();if(num.equals(s)){return index;}index++;}return -1;}
Comparable和Comparator两个接口的区别
Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
Comparator强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。
异常
异常的继承体系
- 异常的继承体系:java.lang.Throwable --> java.lang.Error & java.lang.Exception Exception又分为编译时期异常:checked异常,与运行时期异常:runtime异常。
- Error:表示不可修复的恶性的错误,只能通过修改代码规避错误的产生,通常是系统级别的,所以很严重。
- Exception:表示可修复的良性(相对于错误)的异常,异常产生后程序员可以并且应该通过代码的方式纠正,使程序继续运行,是必须要处理的。
- 运行时期异常:runtime异常。在运行时期,检查异常。在编译时期,运行异常编译器不会检测(不报错)。
throw与throws的区别
- throw写在方法体内,throw一个异常对象。执行到throw处即停止。
- throws写在方法声明处,用来指定可能抛出的异常。多个异常可以使用逗号隔开。当在主函数中调用该方法时,如果发生异常,就会将异常对象抛给方法调用处。
异常处理方式
-
try-catch-finally:自己定义异常的处理逻辑,处理之后的程序可以继续运行。try是可能发生异常的部分,catch写明异常的处理逻辑,finally代码块是无论是否发生异常,都必须执行的代码,用于释放资源。
-
throws:在方法声明处写上异常类,抛出给调用者进行处理,最后由JVM进行中断处理。
常见异常和出现原因
- 空指针异常:当应用试图在要求使用对象的地方使用了null时,抛出该异常。
- 数组越界异常:当对数组的索引值为负数或大于等于数组大小时抛出此异常。
- 算数运算异常:除零操作
- 数字格式异常:当试图将一个String转换为指定的数字类型,而该字符串并不满足数字类型要求的格式时,抛出该异常。
并发和并行的区别
并行:指两个或多个事件在同一时刻发生(同时发生)。
并发:指两个或多个事件在同一个时间段内发生。(不同时)
进程和线程的区别
进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。
线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
一个程序运行后至少有一个进程,一个进程中可以包含多个线程,但一个进程中至少包含一个线程。比如使用迅雷软件下载网络文件时,同时下载多个文件,就使用到了多线程下载。
自定义异常
public class NoScoreExcpetion extends Exception {public NoScoreExcpetion() {}public NoScoreExcpetion(String message) {super(message);}
}
public class Student {private String name;private int id;private int score;
......
//其他方法省略
......public void setScore(int score) throws NoScoreExcpetion {if(score <= 0){throw new NoScoreExcpetion("成绩必须大0");}this.score = score;}
}
public class Test {public static void main(String[] args) throws NoScoreExcpetion {Student s1 = new Student();s1.setName("张三");s1.setId(1);s1.setScore(-100);System.out.println(s1);}
}
多线程
创建多线程对象,开启多线程。
在子线程中输出1-100之间的偶数,主线程输出1-100之间的奇数。
public class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (i % 2 == 0) {System.out.println(i);}}}
}
public class Test{public static void main(String[] args) {/*创建多线程对象,开启多线程。在子线程中输出1-100之间的偶数,主线程输出1-100之间的奇数。*/MyThread t = new MyThread();t.start();for (int i = 0; i < 100; i++) {if(i%2 != 0){System.out.println(i);}}}
}
start()方法与run()方法的区别
start开启线程,让jvm调用run()方法在开启的线程中执行。
run不开启线程,只是对象调用方法
创建线程的两种方式
继承Thread:定义线程子类,继承Tread,重写run方法。创建线程子类对象,调用start方法开启线程。
public class MyThread extends Thread {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
}
public class Test{public static void main(String[] args) {MyThread t = new MyThread();t.start();System.out.println(Thread.currentThread().getName());}
}
实现Runnable:实现Runnable类,并重写run方法。创建Runnable实现类对象,传入Thread对象的含参构造方法中,通过Thread对象调用start方法开启线程。
public class Test{public static void main(String[] args) {Runnable r = new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}};Thread t = new Thread(r);t.start();System.out.println(Thread.currentThread().getName());}
}
实现Runnable接口的优势
- 接口可以多实现,而类只能单继承
- 适合多个相同的程序代码的线程去共享同一个资源。
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数据独立。
- 线程池只能放入实现Runnable或callable类线程,不能直接放入继承Thread的类。
线程状态
- new:新建状态
- blocked:阻塞状态。当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
- time-waiting:休眠状态。一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
- waiting:无限等待 。同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
- runnable:运行状态
- terminate:终止状态
线程池:是一个容纳多个线程的容器,里面的线程可以反复拿出来使用,无需反复创建线程对象,而过多消耗系统资源。
线程池的优点:
- 降低资源消耗:减少创建和销毁线程的次数,可以反复利用线程。
- 提高线程的可管理性:可以根据系统的承受能力,调整线程池中工作线程数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
- 提高响应速度:不需要创建线程就可以执行任务。
重写和重载
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重载是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
下面的代码编译会报错,因为没有重写run方法,run里有参数,不是重写!
public class Test07 implements Runnable {public static void main(String[] args) {Thread t = new Thread(new Test07());t.start();}public void run(int num) {for (int i = 0; i < num; i++) {System.out.println(i);}}
}
Lambda
省略规则
- ()内的参数类型可以省略
- ()内如果只有一个参数,()可省略
- {}内如果只有一个语句可以省略{}、return和分号
格式
- ():参数列表
- ->:指向动作
- {}:方法体括号
使用前提
- 必须有接口,且抽象方法唯一。
- 必须可推断,方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
Runnable a = ()->{for(int i = start; i <= end; i++){System.out.println(i);}};
无参无返回值Lambda练习
给定一个导演 Director接口,内含唯一的抽象方法makeMovie,且无参数、无返回值,使用lambda表达式在Test中完成调用。
public interface Director {public abstract void makeMovie();
}
public class Test {public static void main(String[] args) {Director d = ()-> System.out.println("我在拍电影");invoke(d);}public static void invoke(Director d){d.makeMovie();}
}
有参有返回值Lambda练习
给定一个计算器 Calculator 接口,内含抽象方法 calc (减法),其功能是可以将两个数字进行相减,并返回差值。使用Lambda表达式在Test中完成调用。
public interface Calculator {public abstract int calc(int num1, int num2);
}
public class Test {public static void main(String[] args) {Calculator c = (num1, num2)-> num1-num2;int result = invoke(c, 5, 1);System.out.println(result);}public static int invoke(Calculator c, int num1, int num2){return num1-num2;}
}