小白和老手都应该看看的总结
输入
java.util.Scanner 是 Java5 的新特征,我们可以通过 Scanner 类来获取用户的输入。
下面是创建 Scanner 对象的基本语法:
Scanner s = new Scanner(System.in);
使用方法如下:
//对应类型用对应的方法接收
String stringx=s.next();
String stringy=s.nextLine();
int intx=s.nextInt();
long longx=s.nextLong();
short shortx=s.nextShort();
float floatx=s.nextFloat();
double doublex=s.nextDouble();
BigInteger bigInteger=s.nextBigInteger();
BigDecimal bigDecimal=s.nextBigDecimal();
注意:
1、next和nextLine的区别:next读取到空格停止,nextLine读取到回车停止,读取到空格不会停止。
2、nextInt不要和nextLine混用,如果nextLine在nextInt后面使用,会有吸收掉了本行的换行符而并没有接收到下一行数据的问题
输出
System是java.lang里面的一个类
out是System的静态数据成员,而且这个成员是java.io.PrintStream类的引用
println()和print()就是java.io.PrintStream类里的方法.
被关键字static修饰的成员可以直接通过"类名.成员名"来引用,无需创建类的实例。所以System.out是调用了System类的静态数据成员out。
第一种:
System.out.println();
是最常用的输出语句,它会把括号里的内容转换成字符串输出到控制台,并且结尾换行。
1)输出的是一个基本数据类型,会自动转换成字符串,
2)输出的是一个对象,会自动调用对象的toString()
方法
第二种:
System.out.print();
和第一种一样,只是结尾不换行。
第三种:
System.out.printf();
这个方法延续了C语言的输出方式,通过格式化文本和参数列表输出比如:
八种基本类型
基本数据类型的变量是存储在栈内存中,而引用类型变量存储在栈内存中,保存的是实际对象在堆内存中的地址。
注意:有两个大数类:BigInteger,BigDecimal分别是整数和小数
自动装箱: java自动将原始类型转化为引用类型的过程,编译器调用valueOf方法将原始类型转化为对象类型。
自动拆箱: java自动将引用类型转化为原始类型的过程,编译器调用intValue(),doubleValue()这类方法将对象转换成原始类型值
例子:
Integer a = 3; //自动装箱
int b = a; //自动拆箱
条件分支
1)if语句中必须是一个布尔值,而不能是其他类型,这是java特殊的地方,比如判断x是否为null不能写if(!x)而要写if(x==null)
2)switch 语句中的变量类型可以是: byte、short、int 、char。
从 Java SE 7 开始,switch 支持字符串 String 类型了
Switch语句和if else语句的区别
switch case会生成一个跳转表来指示实际的case分支的地址,而if...else是需要遍历条件分支直到命中条件
1)if-else语句更适合于对区间(范围)的判断,而switch语句更适合于对离散值的判断
2)所有的switch都可以用if-else语句替换(if-else对每个离散值分别做判断即可)
但是不是所有的if-else都可以用switch替换(因为区间里值的个数是无限的)
3)当分支较多时,用switch的效率是很高的。因为switch是确定了选择值之后直接跳转到那个特定的分支
switch...case占用较多的空间,因为它要生成跳表,特别是当case常量分布范围很大但实际有效值又比较少,空间利用率很低。
循环流程
for( 初始化; 终止条件; 更新 ) {
}
---------------------------------------------------------------
for(类型 变量名:数组或集合){
}//注意举例:
for(int i:arr){
i=1;
}//arr数组内的值不会被改变
---------------------------------------------------------------
while(进入循环条件){
}
---------------------------------------------------------------
do{
}while(条件)
---------------------------------------------------------------
数组
静态初始化
正确示例:
int ids[]或int[] ids={ 1,2,3,4,5,6,7,8};
错误示例:
int num2[3] = {1,2,3}; // 编译错误,不能在[ ]中指定数组长度
int[] num3;System.out.println(num3.length); // 编译错误,未初始化不能使用
动态初始化
正确示例:
int series[ ]= new int[4];
二维数组:
int arr[ ] [ ] = { {1,2},{3,4},{5,6}};
long[ ][ ] arr = new long[5][5];
arrays类:
java.util.Arrays类能方便地操作数组,它提供的所有方法都是静态的
常用方法:
–copyOf 实现数组的复制 copyOf(int[] a,int newlength);
–fill 实现数组元素的初始化 fill(int[] a,int val);
–sort 实现数组的排序 sort(int[] a);
–binarySearch 实现排序后的数组元素查找 binarySearch(int[] a,int key);
面向对象的三大核心特性
封装
保证了程序和数据不受外部干扰,不被误用。封装的目的在于保护信息,使用它的主要优点如下。
- 保护信息(阻止外部随意访问内部代码和数据)
- 隐藏细节(一些不需要程序员修改和使用的信息,用户只需要知道怎么用就可以,不需要知道内部如何运行)
- 有利于松耦合,提高系统的独立性。(当一个系统的实现方式发生变化时,只要它的接口不变,就不会影响其他系统的使用)
- 提高软件的复用率,降低成本
继承
继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码,缺点:提高了类之间的耦合性
继承的语法:修饰符 class 子类名 extends 父类名
1)子类拥有父类非private的属性,方法,子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。子类可以用自己的方式实现父类的方法(重写)。
2)Java的继承是一个子类只能继承一个父类,但是可以多重继承:例如A类继承B类,B类继承C类,这是java继承区别于C++继承的一个特性。
多态
•在面向对象理论中,多态性的定义是:同一操作作用于不同的类的对象,将产生不同的执行结果 。
比如动物类可以指向的猫或狗,他们的“叫”方法就是不一样的,“喵喵”和“汪汪”
好处:多态除了代码的复用性外,还可以降低耦合、可替换性、可扩充性
多态的转型分为向上转型和向下转型两种
向上转型:多态本身就是向上转型过的过程
父类类型 变量名=new 子类类型(); 比如:Set<Integer> set=new HashSet<Integer>();
向下转型:一个子类对象可以使用强制类型转换,将父类引用类型转为子类引用各类型
子类类型 变量名=(子类类型) 父类类型的变量;
开发中实现多态?
接口实现、抽象类、继承父类进行方法重写、同一个类中进行方法重载。
关键字
final 关键字:声明类可以把类定义为不能继承的,比如String
super关键字:用于引用父类中的属性和方法,super.属性、super.方法()
this关键字:用于引用本类中的属性和方法,this.属性、this.方法()
权限关键字
抽象类
在继承关系中,有时基类本身生成对象是不合情理的。
例如,动物作为一个基类可以派生出猫、狗等子类,但动物类本身生成对象明显不合常理。
abstract修饰的类称为抽象类。抽象类的特点:
不能实例化对象、类中可以定义抽象方法、抽象类中可以没有抽象方法。
•abstract修饰的方法称为抽象方法,抽象方法只有声明没有实现,即没有方法体。包含抽象方法的类本身必须被声明为抽象的。
abstract class Animal {
private String color ;
public abstract void shout();
}
•派生类继承抽象类必须实现抽象类中所有的抽象方法,否则派生类也必须定义为抽象类。
接口
Java中的接口是一系列方法的声明,可以看做是特殊的抽象类,包含常量和方法的声明,而没有变量和方法的实现。
接口的意义:
•弥补Java中单继承机制的不足。
•接口只有方法的定义没有方法的实现,即都是抽象方法,这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。
接口的定义语法:
interface 接口名称 {
常量
抽象方法
}
类可以通过实现接口的方式来具有接口中定义的功能,基本语法:
–class 类名 implements 接口名 {
–}
–一个类可以同时实现多个接口;
–一个接口可以被多个无关的类实现;
–一个类实现接口必须实现接口中所有的抽象方法,否则必须定义为抽象类。
接口继承:
Java中接口可以继承接口,与类的继承概念一致,
会继承父接口中定义的所有方法和属性。
一个接口可以同时继承多个接口。
接口和抽象类对比
参数 | 抽象类 | 接口 |
默认的方法实现 | 可以有默认的方法实现 | 接口完全是抽象的。不存在方法的实现 |
实现 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 | 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
main方法 | 抽象方法可以有main方法并且我们可以运行它 | 接口没有main方法,因此我们不能运行它。 |
多继承 | 抽象方法可以继承一个类和实现多个接口 | 接口只可以继承一个或多个其它接口 |
速度 | 它比接口速度要快 | 接口稍微慢,因为需要去寻找在类中实现的方法。 |
添加新方法 | 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 | 如果你往接口中添加方法,那么你必须改变实现该接口的类。 |
异常
Java5个异常
ArrayIndexOutOfBoundsExceptions:数组下标越界
NullPointerException:访问null的对象的方法或属性时
ClassCastException:类型转换失败时
ConcurrentModificationException:并发修改异常
ArithmeticException:除零异常
处理异常:
try catch :
try catch:自己处理异常
try {
可能出现异常的代码
} catch(异常类名A e){
如果出现了异常类A类型的异常,那么执行该代码
} ...(catch可以有多个)
finally {
最终肯定必须要执行的代码(例如释放资源的代码)
}
注意:finally不一定执行:前面代码遇到如:System.exit(0); return 0;,就不会执行接下来的语句,包括finally。
抛出:
一个方法不处理它产生的异常,而是沿着调用层次向上传递,由调用它的方法来处理这些异常,叫抛出异常。
throws:使用throws关键字,用来方法可能抛出异常的声明。
例如:public void doA(int a) throws Exception1,Exception3{......}
throw:使用throws关键字,用来抛出异常
语法:throw (异常对象);
如:throw new ArithmeticException();
注意事项:
•避免过大的try块,不要把不会出现异常的代码放到try块里面,尽量保持一个try块对应一个或多个异常。
•细化异常的类型,不要不管什么类型的异常都写成Excetpion。
•不要把自己能处理的异常抛给别人。
自定义异常
•如果JDK提供的异常类型不能满足需求的时候,程序员可以自定义一些异常类来描述自身程序中的异常信息。
•程序员自定义异常必须是Throwable的直接或间接子类。
•在程序中获得异常信息一般会调用异常对象的getMessage,printStackTrace,toString方法,所以自定义异常一般会重写以上三个方法。
public class SpecialException extends Exception {
@Override
public String getMessage() {return "message";}
@Override
public void printStackTrace() {System.out.println(message);}
@Override
public String toString() {return "message";}
}
字符串
String
String是字符串final常量类,值一经赋值,其值不可变(指的是所指向的内存值不可修改,但可以改变指向),而且无法被继承。
初始化
•String name= new String(“小猫”);
和创建对象过程没有区别,创建一个新的String对象,并用name指向它。
•String sex = “女”;
过程:
- 先在常量池中查找“女”,如果没有则创建对象
- 在栈中创建引用sex,
- 将sex指向对象“女”
String s1 = "abc";
//"abc"是一个对象
String s2 = new String("abc");
//这里变成两个对象,在内存中存在两个,包括对象“abc” 和 new 出来的对象
String s3 = "abc"; //这里的‘abc’ַ和s1的‘abc’是同一个对象,二者的内存地址一样。
System.out.println(s1==s2);//false
System.out.println(s1==s3);//true
部分api
字符串连接concat(String str)、“+”运算符
字符串查找indexOf (String str)、lastIndexOf(String str)、charAt(int indexOf)
字符串分割split(String regex)
字符串比较compareTo(String str):
忽略大小写equalslgnoreCase(String str)
变成字符数组toCharArray()
StringBuild和 StringBuffer
因为String的值是不可变的,每次对String的操作都会生成新的String对象,这样效率低下,大量浪费空间。
第一行:我们先栈里起名字叫cat,在堆里开辟空间,存入“小猫”,并用cat指向它
第二行:我们新开辟了一个空间,存入“小猫world”,并用cat指向它。
这个过程显然又费时又费力。
为了应对字符串相关的操作,谷歌引入了两个新的类:StringBuffer类和StringBuild类,它们能够被多次的修改,并且不产生新的未使用对象。
StringBuild和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问),StringBuffer是线程安全的。(StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰)
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。
在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
集合
map/set
每一种set有对应类型的map
HashSet(map)无序,底层哈希表+链表(1.8后,数量大于8后链表改为红黑树优化性能),增删改查O(1)
TreeSet(map)有序(人规定排序的规则),底层是红黑树,但是增删改查O(logN)
LinkerHashSet(map)添加一个双向链表维护了插入的顺序。
一般情况用HashSet(map)因为效率高
list
ArrayList,底层是数组,查询快,增删慢。
线程不安全,速度快
Vector, 底层是数组,查询快,增删慢。
线程安全,速度慢
LinkedList,底层数据结构是链表,查询慢,增删快。
线程不安全,速度快
PriorityQueue优先队列,底层是堆。增删改查O(logN)
API
ArrayList<Integer> list=new ArrayList<Integer>();
增:
add(E e):将指定的元素添加到此列表的尾部
add(int index, E element):将指定的元素插入此列表中的指定位置
删:
remove(int index):移除此列表中指定位置上的元素
改:
set(int index, E element):用element替换index上的数
查:
get(int index):返回下标index上的元素
size():返回此列表中的元素数
//map
Map<Integer, Integer> map=new HashMap<Integer, Integer>();
int size()//K-V关系数量
boolean isEmpty()//是否为空
增:
V put(K key,V value)//放入K-V键值对
void putAll(Map<K,V> m)//放入m包含的所以键值对
删:
V remove(Object key)//删除key对应的键值对
void clear()//删除所有键值对
改:
直接put,会覆盖旧的记录
查:
boolean containsKey(Object key)//是否包含key
boolean containsValue(Object value)//是否包含value
V get(Object key)//得到key对应的value
生成集合:
Set<K> keySet()//返回包含所有key的set
Collection<V> values()//返回包含所有value的Collection
TreeMap特有:
public K firstKey()//返回第一个key(最高)
public K lastKey()//返回最后一个key(最低)
//set,大部分和map类似
增:add
删:remove
查:contains
Iterator
主要功能:用于对容器的遍历
主要方法:
boolean hasNext():判断是否有可以元素继续迭代
Object next():返回迭代的下一个元素
void remove():从迭代器指向的集合中移除迭代器返回的最后一个元素
例子:
Set<String> name = new HashSet<String>();
name.add("LL");
name.add("VV");
name.add("WW");
......
Iterator<String> it = name.iterator();
while(it.hasNext()){
String n = it.next();
...
}
HashMap相关
HashMap
- 哈希冲突:若干Key的哈希值如果落在同一个数组下标上,将组成一条链,对Key的查找需要遍历链上的每个元素执行equals()比较,1.8后优化为红黑树
- 负载极限,“负载极限”是一个0~1的数值,“负载极限”决定了hash表的最大填满程度。当hash表中的负载因子达到指定的“负载极限”时,hash表会自动成倍地增加容量(桶的数量),并将原有的对象重新分配,放入新的桶内,这称为rehashing。默认当HashMap中的键值对达到数组大小的75%时,即会rehashing。
解释0.75:
是时间和空间成本上的一种折中:
- 较高的“负载极限”(也就是数组小)可以降低占用的空间,但会增加查询数据的时间开销
- 较低的“负载极限”(也就是数组大)会提高查询数据的性能,但会增加hash表所占用的内存开销
可以根据实际情况来调整“负载极限”值。
多线程比较
HashMap
线程不安全
HashTable
线程安全,实现的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化
ConcurrentHashMap
线程安全,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。
ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的Hashtable,它们有自己的锁。
只要多个修改操作发生在不同的段上,它们就可以并发进行。
默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶,读操作大部分时候都不需要用到锁。
(JDK1.8已经摒弃了Segment,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本。)
泛型
泛型用一个通用的数据类型T来代替类,在类实例化时指定T的类型,运行时自动编译为本地代码,运行效率和代码质量都有很大提高,并且保证数据类型安全。
泛型的作用就是提高代码的重用性,避免强制类型转换,减少装箱拆箱提高性能,减少错误。