从java最基础的基本类型到网络,遍历一遍难点,不管从 java编程思想,还是从其他书籍学习,java难点集中在,集合,接口,抽象类,四中类,线程等几个方面是比较难的部分。
java基本类型:
Java整型
int | 4字节 | -2147483648 ~ 2147483647 (正好超过20亿) |
short | 2字节 | -32768 ~ 32767 |
long | 8字节 | -9223372036854775808 ~ 9223372036854774807 |
byte | 1字节 | -128 ~ 127 |
浮点类型
float | 4字节 | 大约±3.40282347E+38F (有效位数为6-7位) |
double | 8字节 | 大约±1.79769313486231570E+308 (有效位数为15位) |
一些需要注意:
浮点数值不适合用于禁止出现舍入误差的金融计算中。例如System.out.println( 2.0 - 1.1);将打印0.899999999999999,而不是0.9。因为浮点数值采用二进制系统表示,而二进制无法精确表示分数1/10,就像十进制无法精确表示1/3一样。如果需要在数值计算中不含有舍入误差,就应该使用BigDecimal类。
char类型
在Java中,char类型用UTF-16编码描述一个代码单元。强烈建议不要在程序中使用char。java的char类为16位。
boolean类型
在C或C++中数值或指针可以代替boolean的值,0相当于flase,非0相当于true,而在Java中则不行,并且在编译时就会报错。
有关类型转换的问题:
short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
short s1 = 1; s1 = s1 + 1; (s1+1运算结果是int型,需要强制转换类型)
short s1 = 1; s1 += 1;(可以正确编译)
Math.round(11.5)等於多少? Math.round(-11.5)等於多少?
Math.round(11.5)==12
Math.round(-11.5)==-11
round方法返回与参数最接近的长整数,参数加1/2后求其floor.
返回最接近参数的 long。通过加上 1/2 将该结果舍入为整数,取结果的基数并将其强制转换为 long 类型。换句话说,结果等于以下表达式的值:
(long)Math.floor(a + 0.5d)
String s= new String( "xyz ");是几个对象?
知道在java中除了8中基本类型外,其他的都是类对象以及其引用。所以 "xyz "在java中它是一个String对象.对于string类对象来说他的对象值是不能修改的,也就是具有不变性。
看:
String s= "Hello";
s= "Java ";
String s1= "Hello";
String s2=new String( "Hello");
s所引用的string对象不是被修改了吗?之前所说的不变性,去那里了啊?
你别着急,让我告诉你说发生了什么事情:
在jvm的工作过程中,会创建一片的内存空间专门存入string对象。我们把这片内存空间叫做string池。
String s= "Hello ";当jvm看到 "Hello ",在string池创建string对象存储它,并将他的引用返回给s。
s= "Java ",当jvm看到 "Java ",在string池创建新的string对象存储它,再把新建的string对象的引用返回给s。而原先的 "Hello "仍然在string池内。没有消失,他是不能被修改的。
所以我们仅仅是改变了s的引用,而没有改变他所引用的对象,因为string对象的值是不能被修改的。
String s1= "Hello ";jvm首先在string池内里面看找不找到字符串 "Hello ",找到,返回他的引用给s1,否则,创建新的string对象,放到string池里。这里由于s= "Hello "了,对象已经被引用,所以依据规则s和s1都是引用同一个对象。所以s==s1将返回true。(==,对于非基本类型,是比较两引用是否引用内存中的同一个对象)
String s2=String( "Hello ");jvm首先在string池内里面看找不找到字符串 "Hello ",找到,不做任何事情,否则,创建新的string对象,放到string池里面。由于遇到了new,还会在内存上(不是string池里面)创建string对象存储 "Hello ",并将内存上的(不是string池内的)string对象返回给s2。所以s==s2将返回false,不是引用同一个对象。
好现在我们看题目:
String s= new String( "xyz ");
首先在string池内找,找到?不创建string对象,否则创建,这样就一个string对象
遇到new运算符号了,在内存上创建string对象,并将其返回给s,又一个对象
所以总共是2个对象
先看下Object对象的几个方法。
protected Object |
clone() 创建并返回此对象的一个副本。 |
boolean |
equals(Object obj) 指示某个其他对象是否与此对象“相等”。 |
protected void |
finalize() 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。 |
Class extends Object> |
getClass() 返回一个对象的运行时类。 |
int |
hashCode() 返回该对象的哈希码值。 |
void |
notify() 唤醒在此对象监视器上等待的单个线程。 |
void |
notifyAll() 唤醒在此对象监视器上等待的所有线程。 |
String |
toString() 返回该对象的字符串表示。 |
void |
wait() 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。 |
void |
wait(long timeout) 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。 |
void |
wait(long timeout, int nanos) 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。 |
在看看集合大家族。
集合框架中的接口
Collection Map
Set List SortedMap
SortedSet
集合框架中的实现类
实现是继承,虚线是实现
Set------------->HashSet List---->ArrayList Map---------->HashMap
| ------------->LinkedHashSet ---->LinkedList |
SortedSet----->TreeSet SortedMap-->TreeMap
Collection:集合层次中的根接口,jdk没有提供这个接口直接的实现类。
提供了add方法,把元素加入到集合当中,但是没有get方法
Set:不能包含重复的元素。SortedSet是一个按照升序排列元素的Set。
List:是一个有序的集合,可以包含重复的元素。提供了按索引访问的方式。
对Collection增加了一个get(int incex)方法,按照索引过去元素
Map:包含了key-value对。Map不能包含重复的key。SortedMap是一个按照升序排列key的map。
Java当中通用的访问集合的方法是Iterator接口
面试难点
List Map Set 区别是什么?
首先这是Collection , Map和的区别问题。List,Set是实现了Collection接口而 Map是与Collection并行的接口。
1、Collection 和 Map 的区别
容器内每个为之所存储的元素个数不同。
Collection类型者,每个位置只有一个元素。
Map类型者,持有 key-value pair,像个小型数据库。
2、各自旗下的子类关系
Collection
--List:将以特定次序存储元素。所以取出来的顺序可能和放入顺序不同。
--ArrayList / LinkedList / Vector
--Set : 不能含有重复的元素
--HashSet / LinkHashSet,TreeSet
Map
--HashMap
--HashTable
--TreeMap
3、其他特征
* List,Set,Map将持有对象一律视为Object型别。
* Collection、List、Set、Map都是接口,不能实例化。
继承自它们的 ArrayList, Vector, HashMap是具象class,这些才可被实例化。
* vector容器确切知道它所持有的对象隶属什么型别。vector不进行边界检查。
集合实现的遍历问题,java,和jstl俩种?
对List的遍历有三种方式
List list = new ArrayList();
list.add(new A());
list.add(new A());
JSTL中遍历List
<%
pageContext.setAttribute("voList", voList);
%>
用Iterator来遍历Set
//实例化HashSet对象
HashSet hs = new HashSet();
hs.add(new String("第一个元素"));
//没有get方法 只能遍历去元素
/*
* 得到Iterator,然后遍历输出
*/
public void show1(HashSet hs){
Iterator i = hs.iterator();
while(i.hasNext()){
String temp = (String)i.next();
System.out.println(temp);
}
}
/*
* 转换成数组,遍历并输出HashSet中的元素
*/
public void show2(HashSet hs){
Object o[] = hs.toArray();
for(int i=0;i
}
}
Set的JSTL遍历与List一样
java中使用HashMap是主要有两种遍历方法,代码如下:
HashMap a = new HashMap();
a.put("name", "abcdef"); // key是name,value是字符串abcdef
a.get("name"); //Object类型key来获取值
第一种:
Map map = new HashMap();
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
}
效率高,以后一定要使用此种方式!
第二种:
Map map = new HashMap();
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
Object key = iter.next();
Object val = map.get(key);
}
效率低,以后尽量少使用!
JSTL遍历MAP元素
对于keySet其实是遍历了2次,一次是转为iterator,一次就从hashmap中取出key所对于的value。而entryset只是遍历了第一次,他把key和value都放到了entry中,所以就快了。
<%
Map map = new HashMap();
map.put("a", "12345");
map.put("b", "abcde");
out.println(map.get("a"));
request.setAttribute("map",map);
%>
ArrayList 和 LinkList 区别
ArrayList底层用数组来完成。而LinkList是双向链表,每个元素里有除了本身之后还有指向前后元素的地址内容。
而在操作上,ArrayList地址链接,循环快。但是如果增加数据或者删除数据就会整个数组要重新copy一份在赋值,所以慢。
LinkList 内存地址散列,遍历慢。加元素,删除元素,之需要前后元素的地址,所以快。
HashMap和HashSet以及HashTable区别
HashSet是Set接口的实现,元素不可重复。对与自己建立的对象而言,情况如下:
HashSet hs=new HashSet(); hs.add("one"); hs.add("two"); hs.add("three"); hs.add("one"); //不会被打印出来 实现了Set接口 不重复 hs.add(new HStudent(1,"zhangsan")); hs.add(new HStudent(2,"lisi")); hs.add(new HStudent(3,"wangwu")); hs.add(new HStudent(1,"zhangsan")); //因为这是新的一个对象所以hashSet用对象的内存地址算出散列码 所以不算重复的元素。 //排序规则是用内存地址所以不是按放的顺序 ,如果要指定的化 HStudent类要重写hashCode方法
以上代码上看到,String'类的eques方法和tostring是已经实现了hashcode的,所以加入HashSet不会重复,对于Student来说默认的equese和tostring是不可以实现对象的一直的,tostring返回的是对象内存地址。
public boolean equals(Object o){ HStudent s=(HStudent)o; return num==s.num && name.equals(s.name); //重写此方法是为了实现不重复的对象 } public String toString(){ return num+":"+name; }
这2个方法的重写可以保证Set中加入的对象已经是重复的。
要实现Set的排序也要重写hashCode方法
public int hashCode(){ return num*name.hashCode(); //使用了String的散列码乘以num数字 }
HashMap
HashMap对key进行散列。
keySet()、values()、entrySet()。
实现了Map接口的hash表,此实现提供了所有可选的map操作,允许空值和空键 null values null key
跟Collection接口无关系 没有add
put(Object key,Object value) get(Object key)
Set keySet()返回map当中键的视图
Collection values()返回值的视图
Set entrySet()返回键值对的视图
返回的每个元素都是Map.Entry类型,静态接口Map.Entry 在Map的成员变量。
其中有 Object getKey() 和 Object getValue()方法
循环代码如下:
HashMap hm=new HashMap(); //键值对的视图 Set entry=hm.entrySet(); Iterator it=entry.iterator(); while(it.hasNext()){ Map.Entry me=(Map.Entry)it.next(); System.out.println(me.getKey()+":"+me.getValue()); }
从这里看出Set不允许重复元素,但值中可以有null 切只有一个
Map的Key是可以为null,也只有一个,值是可以有多个null
HashTable的应用非常广泛,HashMap是新框架中用来代替HashTable的类,Hashtable是Dictionary的子类,HashMap是Map接口的一个实现类;
HashMap HashTable
null可以为key 不可以
线程不同步 线程同步
不能用get判断是否存在某个键 有put 和 get方法
因为null本来是key
应该用containskey()方法来判断
集合难点基本就这些了。
接口和抽象类的概念应该都清楚了,主要是他们的区别以及在什么地方该用哪个是关键!
抽象类 abstract
1: 当一个类只有方法的定义,而没有实现这种情况下我们一般会做成抽象类
public abstract class Ab {
//无方法体--抽象方法
public abstract void print();
}
2: 当一个类有抽象方法,则本类必须声明为抽象类,反之,一个类声明为抽象类,未必有抽象方法.
3: 一个抽象类不能被实例化.
Ab a=new Ab();//错误
4: 抽象类需要子类去实现抽象方法.
5: 抽象类的子类如果不实现父类的抽象方法,则必须生明为抽象类
6:一般在设计的时候采用抽象类,象设计人员只关心宏观问题,而把方法的具体实现交给别人来做。
接口的特性 interface 实现类为implements
1:接口相当于一种特殊的抽象类,不能有具体实现,全部都是方法的定义,使一种完全抽象的类型。
2:接口与接口之间可以多继承。
3:类和接口之间可以多实现,也就是说一个类可以实现多个接口。
4:一个类可以即继承一个类,又可以实现多个接口。
5:抽象类也可以实现多个接口,抽象类可以不实现接口的方法。
6:如果实现了接口A,B两个接口具有同样的方法,则子类只实现一次就可以。
7:接口一般用途:是由设计人员在设计的过程中只关注宏观方法定义,而把具体方法的实现交给具体编码的人员。
8:接口中不能有构造函数,不能有变量,只能由static final(静态常量)。
9:接口可以抽象public abstract interface f{}
接口和抽象类之间的区别
1:接口不能有实现,而抽象类可以有具体的实现
2:接口被实现,而抽象类被继承
3:接口中只允许静态常量,而抽象类什么都允许
4:接口间可以多继承,而抽象类只能单继承
共同点:都不能实例化,都需要把方法交给子类去实现。
抽象类的使用场合
tercher类和student类都有相同实现步骤的方法save,实现的代码完全相同,此时我们可以从中抽取出save方法放到抽象类person的实现方法save中,
tercher,student都继承person类,这样可以减少代码的重用,person还有个hello方法是抽象方法,子类的实现方法不同都有自己的hello方法
接口的使用场合
当teacher,student等子类的所有共同方法实现都不同时候用接口给他们协定一个规则。
接口是一种协定,抽象类则相当于类模板。
使用抽象类,而不要使用接口来分离协定与实现。
如果需要提供多态层次结构的值类型,使用接口。
如果一个类型必须实现多个协定,或者协定适用于多种类型,使用接口。
虽然抽象类和接口都支持将协定与实现分离开来,但接口不能指定以后版本中的新成员,而抽象类可以根据需要添加成员以支持更多功能。
优先考虑定义类,而不是接口。
以上说明都理解了就可以很方面的用接口和抽象类了。
在看看4个类类型
1.内部类
类A中内部类B为静态的时候 它就看作是A类的静态方法
new A.b() 创建内部类的实例
1.静态的内部类 static inner class
public static class N{ public N(M m){ System.out.println("in N()"); System.out.println(m); } }
第一: 跟普通方法评级的;
第二: 只能访问外部类的静态变量和静态方法;
第三: 外部类.内部类 对象名=new 外部类.内部类();
2.成员内部类 member inner classes
class Outer{ private int id; private String name; public Outer(int id,String name){ this.id=id; this.name=name; } //成员内部类 class Inner{ int age; public Inner(int age){ this.age=age; System.out.println("age="+age); System.out.println("outer.id="+Outer.this.id); System.out.println("outer.name="+Outer.this.name); } } }
初始化的方式
Outer outer = new Outer(100,"qgl");
//因为是成员内部类,成员内部类的对象完全依赖外部类的对象,如果外部类的对象没有创建,则无法创建成员内部类的对象
Outer.Inner inner=outer.new Inner(30);
内部类可以调用外部类的成员变量
但是外部类不可以调用内部的.
System.out.println("outer.id="+Outer.this.id);
System.out.println("outer.name="+Outer.this.name);
3.本地内部类 local inner classes
在方法内部的临时类
public static void main(String args[]){ class Local{ public void print(String name){ System.out.println("hello "+name); } } Local local=new Local(); local.print("qgl"); }
只能访问final的本地常量
4.匿名内部类 anonymous inner classes
public class N { public static void main(String args[]) { //没有类名,没有实现或者继承关键字 Anon anon=new Anon(){ //没有名字也就没有构造函数 public void print(String name){ System.out.println("welcom "+name); } };//注意 因为是以行语句 , 行的结束需要分号 anon.print("qgl"); } } interface Anon//接口 { public void print(String name); }
下面看看线程方面的知识:
首先搞明白多线程同步关键词synchronized
public class LockTest extends Thread { private static final Object LOCKER=new Object(); private int id; public LockTest(int id){this.id=id;} /*public void print(){ //一个对象一把锁 线程一把此锁拿走以后,线程二在 //来拿这把锁就拿不到了,阻塞在这里等待线程一释放锁 //synchronized块执行完后就释放锁 synchronized(LOCKER){//拿锁 try{ for(int i=0;i<10;i++){ sleep(1000); System.out.println("in thread "+id); } }catch(Exception e){} //释放 } }*/ public synchronized void print(){//普通方法加锁 锁的是this try{ for(int i=0;i<10;i++){ sleep(1000); System.out.println("in thread "+id); } }catch(Exception e){} } public synchronized static void p(){//静态方法锁的是LockTest.class对象 } public void run(){ print(); } public static void main(String[] args) { LockTest lock1=new LockTest(1); LockTest lock2=new LockTest(2); lock1.start(); lock2.start(); } }
理解的关键就在一个对象一把锁,普通方法锁的是对象,静态方法锁的是类对象。如果多个对象之间同步需要定义一个公共属性,private final object。
以上是线程的状态图
sleep和wait的区别 面试题之一
sleep 即为暂停,恢复之后又主程序继续控制。
wait 为阻塞。必须有notify来通知才唤醒。
sleep是thread的方法。wait和notify,notifyAll 都是object的方法。
sleep不释放锁,wait会释放锁。
sleep必须捕获异常,并在任何地方都可以调用。
而wait和notify,notifyAll只有在同步块里用到。
线程启动的两种方式
public class T1 extends Thread {
public void run(){
System.out.print("run t1");
}
public static void main(String[] args) {
T1 t1=new T1();
t.1start();
}
}
public class T2 implements Runnable {
public void run(){
System.out.print("run T2");
}
public static void main(String[] args) {
T2 t2=new T2();
new Thread(t2).start();
}
}
问题:什么情况下使用Thread 什么情况下使用Runnable
1、通常情况下不需要修改线程类当中的除了run方法之外的其他方法的情况下使用实现Runnable接口
这样有2个好处:
一、如果本来已经继承了一个类而有需要线程化那么就必须实现Runnable接口
二、多个线程访问同一个资源的时候方便
反而继承了Thread的时候每个对象都拥有一个资源无法做到资源的共享
当然这些共能可以通过内部类来实现
下面看一个线程面试题:
要求:设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1.写出程序.
回答: 以下程序使用内部类实现线程,对j增减的时候没有考虑顺序问题.
public class ThreadTest1 { private int j; public static void main(String args[]) { ThreadTest1 tt = new ThreadTest1(); Inc inc = tt.new Inc(); Dec dec = tt.new Dec(); for (int i = 0; i < 2; i++) { Thread t = new Thread(inc); t.start(); t = new Thread(dec); t.start(); } } private synchronized void inc() { j++; System.out.println(Thread.currentThread().getName() + "-inc:" + j); } private synchronized void dec() { j--; System.out.println(Thread.currentThread().getName() + "-dec:" + j); } class Inc implements Runnable { public void run() { for (int i = 0; i < 100; i++) { inc(); } } } class Dec implements Runnable { public void run() { for (int i = 0; i < 100; i++) { dec(); } } } }