首页 > 代码库 > Java知识汇集(2)

Java知识汇集(2)

1、多线程

线程与进程的区别

多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响. ?线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。

多线程编程的目的,就是"最大限度地利用CPU资源",当某一线程的处理不需要占用CPU而只和I/O等资源打交道时,让需要占用CPU资源的其它线程有机会获得CPU资源。从根本上说,这就是多线程编程的最终目的。

了解一下java在多线程中的基础知识

1.Java中如果我们自己没有产生线程,那么系统就会给我们产生一个线程(主线程,main方法就在主线程上运行),我们的程序都是由线程来执行的。

2. 进程:执行中的程序(程序是静态的概念,进程是动态的概念)。

3. 线程的实现有两种方式,第一种方式是继承Thread类,然后重写run方法;第二种是实现Runnable接口,然后实现其run方法。

4. 将我们希望线程执行的代码放到run方法中,然后通过start方法来启动线程,start方法首先为线程的执行准备好系统资源,然后再去调用run方法。当某个类继承了Thread类之后,该类就叫做一个线程类。

5. 一个进程至少要包含一个线程。

6. 对于单核CPU来说,某一时刻只能有一个线程在执行(微观串行),从宏观角度来看,多个线程在同时执行(宏观并行)。

7. 对于双核或双核以上的CPU来说,可以真正做到微观并行。

Thread源码研究:

1) Thread类也实现了Runnable接口,因此实现了Runnable接口中的run方法;

2) 当生成一个线程对象时,如果没有为其设定名字,那么线程对象的名字将使用如下形式:Thread-number,该number将是自动增加的,并被所有的Thread对象所共享(因为它是static的成员变量)。

3) 当使用第一种方式来生成线程对象时,我们需要重写run方法,因为Thread类的run方法此时什么事情也不做。

4)当使用第二种方式生成线程对象时,我们需要实现Runnable接口的run方法,然后使用new Thread(new MyThread())(假如MyThread已经实现了Runnable接口)来生成线程对象,这时的线程对象的run方法或调就会MyThread类的run方法,这样我们自己编写的run方法就执行了。

说明:

Public void run(){

If(target!=null){

Target.run();

}}

当使用继承Thread生成线程对象时,target为空,什么也不执行,当使用第二种方式生成时,执行target.run(),target为runnable的实例对象,即为执行重写后的方法

总结:两种生成线程对象的区别:

1.两种方法均需执行线程的start方法为线程分配必须的系统资源、调度线程运行并执行线程的run方法。

2.在具体应用中,采用哪种方法来构造线程体要视情况而定。通常,当一个线程已继承了另一个类时,就应该用第二种方法来构造,即实现Runnable接口。

此外,多线程还有一部分并发和同步的内容,这个就不讲了。

推荐资料:http://www.cnblogs.com/dennisit/p/3690378.html

http://blog.csdn.net/csh624366188/article/details/7318245

2、泛型

泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。通俗的说,就是泛泛的指定对象所操作的类型,而不像常规方式一样使用某种固定的类型去指定。泛型的本质就是将所操作的数据类型参数化,也就是说,该数据类型被指定为一个参数。这种参数类型可以使用在类、接口以及方法定义中。

泛型类中的类型参数几乎可以用于任何可以使用类名的地方。例如,下面是 java.util.Map 接口的定义的摘录:

public interface Map<K, V> {

public void put(K key, V value);

public V get(K key);

}

在使用泛型时,请注意其使用规则和限制,如下:

1、泛型的参数类型只能是引用类型,而不能是简单类型。比如,<int>是不可使用的。

2、可以声明多个泛型参数类型,比如<T, P,Q…>,同时还可以嵌套泛型,例如:<List<String>>

3、泛型的参数类型可以使用extends语句,例如<T extends collection>。

使用extends语句将限制泛型参数的适用范围。这里<T extends collection> ,则表示该泛型参数的使用范围是所有实现了collection接口的calss。如果传入一个<String>则程序编译出错。

4、泛型的参数类型可以使用super语句,例如< T super List >。

super语句的作用与extends一样,都是限制泛型参数的适用范围。区别在于,super是限制泛型参数只能是指定该class的上层父类。这里表示该泛型参数只能是List和List的上层父类。

5、泛型还可以使用通配符,例如<? extends ArrayList>

使用通配符的目的是为了解决泛型参数被限制死了不能动态根据实例来确定的缺点。

举个例子:public class SampleClass < T extends S> {…}

假如A,B,C,…Z这26个class都实现了S接口。我们使用时需要使用到这26个class类型的泛型参数。那实例化的时候怎么办呢?依次写下

SampleClass<A> a = new SampleClass();

SampleClass<B> a = new SampleClass();

SampleClass<Z> a = new SampleClass();

这显然很冗余,还不如使用Object而不使用泛型,呵呵,是吧?

别着急,咱们使用通配符,就OK了。

SampleClass<? Extends S> sc = new SampleClass();

只需要声明一个sc变量,很方便把!

 

到目前为止,Java 类库中泛型支持存在最多的地方就是集合框架。所有的标准集合接口都是泛型化的 —— Collection<V>、List<V>、Set<V> 和 Map<K,V>。类似地,集合接口的实现都是用相同类型参数泛型化的,所以 HashMap<K,V> 实现 Map<K,V> 等。

集合类也使用泛型的许多“技巧”和方言,比如上限通配符和下限通配符。例如,在接口 Collection<V> 中,addAll 方法是像下面这样定义的:

interface Collection<V> {

boolean addAll(Collection<? extends V>);

}

该定义组合了通配符类型参数和有限制类型参数,允许您将 Collection<Integer> 的内容添加到 Collection<Number>。

类的泛型

A B 处分别应该填什么?

 

① interface Hungry<E>{

          void munch(E x);

    }

② abstract class Animal{}

 

③ abstract class Plant{}

 

④ class Grass extends Plant{}

 

⑤ interface Carnivore<E extends __A__> extends Hungry<E>{}

 

⑥ interface Herbivore<E extends __B__> extends Hungry<E>{}

 

⑦ class Sheep extends Animal implements Herbivore<Grass>{

           public void munch(Grass x){}

     }

 

⑧ class Wolf extends Animal implements Carnivore<Sheep>{

          public void munch(Sheep x){}

    }

 

分析:这是一道典型的泛型例子,咋一看挺复杂,只要理清了思路,其实也很简单。先看⑦行,可知⑥行中的E = Grass;再看④行,可知B = Plant。同样由⑧行可知⑤行中的E = Sheep,再由⑦行可得出 A = Animal。

推荐资料:http://www.cnblogs.com/panjun-Donet/archive/2008/09/27/1300609.html

http://www.cnblogs.com/Fskjb/archive/2009/08/23/1552506.html

3、反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

Java反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。

具体看例子:

import java.lang.reflect.Constructor;

 

class Person{

     

    public Person() {

         

    }

    public Person(String name){

        this.name=name;

    }

    public Person(int age){

        this.age=age;

    }

    public Person(String name, int age) {

        this.age=age;

        this.name=name;

    }

    public String getName() {

        return name;

    }

    public int getAge() {

        return age;

    }

    @Override

    public String toString(){

        return "["+this.name+"  "+this.age+"]";

    }

    private String name;

    private int age;

}

 

class hello{

    public static void main(String[] args) {

        Class<?> demo=null;

        try{

            demo=Class.forName("Reflect.Person");

        }catch (Exception e) {

            e.printStackTrace();

        }

        Person per1=null;

        Person per2=null;

        Person per3=null;

        Person per4=null;

        //取得全部的构造函数

        Constructor<?> cons[]=demo.getConstructors();

        try{

            per1=(Person)cons[0].newInstance();

            per2=(Person)cons[1].newInstance("abc");

            per3=(Person)cons[2].newInstance(10);

            per4=(Person)cons[3].newInstance("abc",20);

        }catch(Exception e){

            e.printStackTrace();

        }

        System.out.println(per1);

        System.out.println(per2);

        System.out.println(per3);

        System.out.println(per4);

    }

}

推荐资料:http://blog.csdn.net/csh624366188/article/details/7309435

http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html

4、Socket

网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket。Socket通常用来实现客户方和服务方的连接。Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。

但是,Socket所支持的协议种类也不光TCP/IP一种,因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。

Server端Listen(监听)某个端口是否有连接请求,Client端向Server 端发出Connect(连接)请求,Server端向Client端发回Accept(接受)消息。一个连接就建立起来了。Server端和Client 端都可以通过Send,Write等方法与对方通信。

对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤:

(1) 创建Socket;

(2) 打开连接到Socket的输入/出流;

(3) 按照一定的协议对Socket进行读/写操作;

(4) 关闭Socket.(在实际应用中,并未使用到显示的close,虽然很多文章都推荐如此,不过在我的程序中,可能因为程序本身比较简单,要求不高,所以并未造成什么影响。

java在包java.net中提供了两个类Socket和ServerSocket,分别用来表示双向连接的客户端和服务端。这是两个封装得非常好的类,使用很方便。

客户端发送消息到服务端:

Socket clientToServer = new Socket(“192.168.1.1”, 0000);

//建立连接到socket的PrintWtiter

//字符数据与字节间的转换桥梁,可以连接String和Socket两端

PrintWriter writer = new PrintWriter(clientToServer.getOutputStream());

//写入数据

Writer.println(“***”);

客户端从服务端读取消息:

Socket serverToClient = new Socket(“192.168.1.1”, 0000);

//建立连接到socket上低层输入串流的InputStreamReader

InputStreamReader stream = new InputStreamReader(serverToClient.getInputStream());

//建立BufferReader来读取

BufferReader reader = new BufferReader(stream);

String message = reader.readLine();

 

推荐资料:Head First Java P471-485

5、垃圾回收

Java 语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管 理。由于有个垃圾回收机制,Java中的对象不再有"作用域"的概念,只有对象的引用才有"作用域"。垃圾回收可以有效的防止内存泄露,有效的使用可以使 用的内存。垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收,程序员不能 实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。

判断对象是否该被回收算法

1.引用计数算法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,当引用失效时,计数器值就减1;任何时刻计数器值都为0时对象就表示它不可能被使用了。这个算法实现简单,但很难解决对象之间循环引用的问题,因此Java并没有用这种算法!这是很多人都误解了的地方。

2.根搜索算法

通过一系列名为“GC ROOT”的对象作为起始点,从这些结点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC ROOT没有任何引用链相连时,则证明这个对象是不可用的。如果对象在进行根搜索后发现没有与GC ROOT相连接的引用链,则会被第一次第标记,并看此对象是否需要执行finalize()方法(忘记finalize()这个方法吧,它可以被try-finally或其他方式代替的),当第二次被标记时,对象就会被回收。

三种垃圾回收器 

1.串行收集器

使用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。但是,也无法使用多处理器的优势,所以此收集器适合单处理器机器。当然,此收集器也可以用在小数据量(100M左右)情况下的多处理器机器上。可以使用-XX:+UseSerialGC打开。

2.并行收集器

对年轻代进行并行垃圾回收,因此可以减少垃圾回收时间。一般在多线程多处理器机器上使用。使用-XX:+UseParallelGC.打开。

3.并发收集器

可以保证大部分工作都并发进行(应用不停止),垃圾回收只暂停很少的时间,此收集器适合对响应时间要求比较高的中、大规模应用。使用-XX:+UseConcMarkSweepGC打开。

推荐资料:http://blog.csdn.net/csh624366188/article/details/8042649

http://www.cnblogs.com/xwdreamer/archive/2012/05/06/2485473.html

http://www.cnblogs.com/gw811/archive/2012/10/19/2730258.html

6、内存管理

推荐资料:http://www.cnblogs.com/gw811/archive/2012/10/18/2730117.html

http://hllvm.group.iteye.com/group/wiki/3053-JVM

7、Java8之Lambda表达式

Java 8的另一大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。当开发者在编写Lambda表达式时,也会随之被编译成一个函数式接口。

首先看看在老版本的Java中是如何排列字符串的:

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {

    @Override

    public int compare(String a, String b) {

        return b.compareTo(a);

    }

});

只需要给静态方法 Collections.sort 传入一个List对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。

在Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式:

Collections.sort(names, (String a, String b) -> {

    return b.compareTo(a);

});

看到了吧,代码变得更段且更具有可读性,但是实际上还可以写得更短:

Collections.sort(names, (String a, String b) -> b.compareTo(a));

 

对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字,但是你还可以写得更短点:

Collections.sort(names, (a, b) -> b.compareTo(a));

 

Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。

推荐资料:http://www.cnblogs.com/wxfvm/p/3676730.html

8、Java8之函数式接口

Java 8 引入的一个核心概念是函数式接口。如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。比如,java.lang.Runnable就是一个函数式接口,因为它只顶一个一个抽象方法:

public abstract void run();

留意到“abstract”修饰词在这里是隐含的,因为这个方法缺少方法体。为了表示一个函数式接口,并非想这段代码一样一定需要“abstract”关键字。

默认方法不是abstract的,所以一个函数式接口里可以定义任意多的默认方法,这取决于你。

同时,引入了一个新的Annotation:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。

推荐资料:http://www.oschina.net/translate/everything-about-java-8

9、Java8之Stream

java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。

首先看看Stream是怎么用,首先创建实例代码的用到的数据List:

List<String> stringCollection = new ArrayList<>();

stringCollection.add("ddd2");

stringCollection.add("aaa2");

stringCollection.add("bbb1");

stringCollection.add("aaa1");

stringCollection.add("bbb3");

stringCollection.add("ccc");

stringCollection.add("bbb2");

stringCollection.add("ddd1");

过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。

stringCollection

    .stream()

    .filter((s) -> s.startsWith("a"))

    .forEach(System.out::println);

// "aaa2", "aaa1"

排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。

stringCollection

    .stream()

    .sorted()

    .filter((s) -> s.startsWith("a"))

    .forEach(System.out::println);

// "aaa1", "aaa2"

 

需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的

中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。

stringCollection

    .stream()

    .map(String::toUpperCase)

    .sorted((a, b) -> b.compareTo(a))

    .forEach(System.out::println);

// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"

Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。

boolean anyStartsWithA =

    stringCollection

        .stream()

        .anyMatch((s) -> s.startsWith("a"));

 

System.out.println(anyStartsWithA);      // true

 

boolean allStartsWithA =

    stringCollection

        .stream()

        .allMatch((s) -> s.startsWith("a"));

 

System.out.println(allStartsWithA);      // false

 

boolean noneStartsWithZ =

    stringCollection

        .stream()

        .noneMatch((s) -> s.startsWith("z"));

 

System.out.println(noneStartsWithZ);      // true

计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。

long startsWithB =

    stringCollection

        .stream()

        .filter((s) -> s.startsWith("b"))

        .count();

 

System.out.println(startsWithB);    // 3

这是一个最终操作,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规越后的结果是通过Optional接口表示的:

Optional<String> reduced =

    stringCollection

        .stream()

        .sorted()

        .reduce((s1, s2) -> s1 + "#" + s2);

 

reduced.ifPresent(System.out::println);

// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

 

推荐资料:http://datalab.intyt.com/archives/370