首页编程java编程javacompute什么意思 java注解@immutable什么意思

javacompute什么意思 java注解@immutable什么意思

编程之家2023-10-1398次浏览

大家好,今天来为大家分享javacompute什么意思的一些知识点,和java注解@immutable什么意思的问题解析,大家要是都明白,那么可以忽略,如果不太清楚的话可以看看本篇文章,相信很大概率可以解决您的问题,接下来我们就一起来看看吧!

javacompute什么意思 java注解@immutable什么意思

java返回值

程序一的结构是这样的, class SquareProperties,这个class里面有两个普通method和一个main method.你可以把一个method理解了一个工具包,用来实现一个功能的。double computeArea(double side),这个method的功能就是计算面积。你输入了一个参数side,这个method里面把计算出的面积值返回给你。第二个是求周长的,同理。

你在main method里面调用这几个方法,就能直接实现求周长和面积的功能了。

而程序二,则把这两个方法的内容直接混到main method里面了。

javacompute什么意思 java注解@immutable什么意思

你现在可能觉得程序二这种方法更方便,但如果你以后做一些正规的软件时,如果把各种功能一气都放到main里面,无法重复利用,且非常乱。而把各种功能都分别包到一个个的method里面,需要哪个就用哪个,非常方便。

程序二中的public static。 public是权限,表示所有的程序,无论从哪来的,都能调用这个变量。

一般来说能不public的尽量不要。为了安全和保密起见。

javacompute什么意思 java注解@immutable什么意思

static,静态的。意思就是说,给这个变量单独分配一块内存空间,谁都可以调用。如果这个变量不加static,它就不是一个静态变量。而它在main method的外边,是无法被main里面的东西所调用的。建议学习一下局部变量和全局变量。

这些东西我刚学JAVA的时候也非常纠结,一整个学期都没明白,直到下学期学数据结构的时候才顿悟。如有不懂,可私HI我留QQ之类。

java注解@immutable什么意思

从字面意思来理解就是不会发生变化的类,那么是什么不会发生变化呢,其实就是类的状态,也就是不变类的实例一旦被创建,其状态就不会发生变化,举个例子:如果人是一个class,那么我们中的每一个都是人这个类的具体的instance,如果人这个类只有一个状态就是生身父母,那么它就是一个不变类,因为每一个人在出生的那一刹那,生身父母就已经被设置了值,而且终生都不会发生变化。

不变类有什么好处呢?

1)不变类是线程安全的,由于不变类的状态在创建以后不再发生变化,所以它可以在线程之间共享,而不需要同步。

2)不变类的instance可以被reuse

创建类的实例需要耗费CPU的时间,当这个实例不再被引用时,将会被垃圾回收掉,这时候,又需要耗费CPU的时间。对于不变类而言,一个好处就是可以将常用的实例进行缓存,从而减少了对象的创建。举个例子,对于布尔型,最常用的便是true and false。JDK中的Boolean类就是一个不变类,并且对这两个实例进行了缓冲。

public final class Boolean implements java.io.Serializable{

/**

* The<code>Boolean</code> object corresponding to the primitive

* value<code>true</code>.

*/

public static final Boolean TRUE= new Boolean(true);

/**

* The<code>Boolean</code> object corresponding to the primitive

* value<code>false</code>.

*/

public static final Boolean FALSE= new Boolean(false);

//这个方法不会创建新的对象,而是重用已经创建好的instance

public static Boolean valueOf(boolean b){

return(b? TRUE: FALSE);

}

}

3)不变类的某些方法可以缓存计算的结果

hashCode这个方法来自于Object这个类,这个方法用来返回对象的hashCode,主要用于将对象放置到hashtable中时,来确定这个对象的存储位置。对于一个不变类的实例,它的hashCode也是不变的,所以就可以缓存这个计算的结果,来提高性能,避免不必要的运算,JDK中的String类就是一个例子。

public final class String{

/** Cache the hash code for the string*/

private int hash;// Default to 0

public int hashCode(){

int h= hash;

if(h== 0){

// compute the value

hash= h;// cache the value

}

return h;

}

}

在JDK中, String, the primitive wrapper classes, and BigInteger and BigDecimal都是不变类。

如果一个类是不变类,这个类是不是就不能有改变状态的方法呢?

答案当然是否定的,String是一个不变类,仍然有replace,replaceAll这样的方法,而String仍然是一个不变类,那是因为在这些改变状态的方法中,每次都是新创建一个String对象。

如果大家理解了不变类,那也就不难理解为什么在做String的concatenate时,应当用StringBuffer而不是用+的操作符。

如何正确使用String呢?

1)不要用new去创建String对象。

如果使用new去创建String,那么每次都会创建一个新对象。

public static void main(String[] args){

String A1="A";

String A2="A";// It won't create a new object

checkInstance(A1, A2);// Result: They are same instances

String B1= new String("A");// create a new object

String B2= new String("A");// creat a new object

checkInstance(B1, B2);// Result: They are different instances

}

private static void checkInstance(String a1, String a2){

if(a1== a2){

System.out.println("They are same instances");

} else{

System.out.println("They are different instances");

}

}

2)应当用StringBuffer来做连接操作

因为String是一个不变类,那么在做连接操作时,就会创建临时对象来保存中间的运算结果,而StringBuffer是一个mutable class,这样就不需要创建临时的对象来保存结果,从而提高了性能。

我抄来的自己也学到东西了,以前这个都没接触过的说。

java集合类哪个函数可以

java集合里面的函数

java集合里面的函数_java集合【1】———从集合接口框架说起

百里方欣

原创

关注

0点赞·155人阅读

(一) java集合分类

之前大概分为三种,Set,List,Map三种,JDK5之后,增加Queue.主要由Collection和Map两个接口衍生出来,同时Collection接口继承Iterable接口,所以我们也可以说java里面的集合类主要是由Iterable和Map两个接口以及他们的子接口或者其实现类组成。我们可以认为Collection接口定义了单列集合的规范,每次只能存储一个元素,而Map接口定义了双列集合的规范,每次能存储一对元素。

Iterable接口:主要是实现遍历功能

Collection接口:允许重复

Set接口:无序,元素不可重复,访问元素只能通过元素本身来访问。

List接口:有序且可重复,可以根据元素的索引来访问集合中的元素。

Queue接口:队列集合

Map接口:映射关系,简单理解为键值对,Key不可重复,与Collection接口关系不大,只是个别函数使用到。

整个接口框架关系如下(来自百度百科):

(1) Iterable接口

1.内部定义的方法

java集合最源头的接口,实现这个接口的作用主要是集合对象可以通过迭代器去遍历每一个元素。

源码如下:

//返回一个内部元素为T类型的迭代器(JDK1.5只有这个接口)

Iterator iterator();

//遍历内部元素,action意思为动作,指可以对每个元素进行操作(JDK1.8添加)

default void forEach(Consumer super T> action){}

//创建并返回一个可分割迭代器(JDK1.8添加),分割的迭代器主要是提供可以并行遍历元素的迭代器,可以适应现在cpu多核的能力,加快速度。

default Spliterator spliterator(){

return Spliterators.spliteratorUnknownSize(iterator(), 0);

}

从上面可以看出,foreach迭代以及可分割迭代,都加了default关键字,这个是Java 8新的关键字,以前接口的所有接口,具体子类都必须实现,而对于deafult关键字标识的方法,其子类可以不用实现,这也是接口规范发生变化的一点。

下面我们分别展示三个接口的调用:

1.1 iterator方法

public static void iteratorHasNext(){

List list=new ArrayList();

list.add("Jam");

list.add("Jane");

list.add("Sam");

//返回迭代器

Iterator iterator=list.iterator();

// hashNext可以判断是否还有元素

while(iterator.hasNext()){

//next()作用是返回当前指针指向的元素,之后将指针移向下个元素

System.out.println(iterator.next());

}

}

当然也可以使用for-each loop方式遍历

for(String item: list){

System.out.println(item);

}

但是实际上,这种写法在class文件中也是会转成迭代器形式,这只是一个语法糖。class文件如下:

public class IterableTest{

public IterableTest(){}

public static void main(String[] args){

iteratorHasNext();

}

public static void iteratorHasNext(){

List list= new ArrayList();

list.add("Jam");

list.add("Jane");

list.add("Sam");

Iterator iterator= list.iterator();

Iterator var2= list.iterator();

while(var2.hasNext()){

String item=(String)var2.next();

System.out.println(item);

}

}

}

需要注意的一点是,迭代遍历的时候,如果删除或者添加元素,都会抛出修改异常,这是由于快速失败【fast-fail】机制。

public static void iteratorHasNext(){

List list=new ArrayList();

list.add("Jam");

list.add("Jane");

list.add("Sam");

for(String item: list){

if(item.equals("Jam")){

list.remove(item);

}

System.out.println(item);

}

}

从下面的错误我们可以看出,第一个元素是有被打印出来的,也就是remove操作是成功的,只是遍历到第二个元素的时候,迭代器检查,发现被改变了,所以抛出了异常。

Jam

Exception in thread"main" java.util.ConcurrentModificationException

at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)

at java.util.ArrayList$Itr.next(ArrayList.java:859)

at IterableTest.iteratorHasNext(IterableTest.java:15)

at IterableTest.main(IterableTest.java:7)

1.2 forEach方法

其实就是把对每一个元素的操作当成了一个对象传递进来,对每一个元素进行处理。

default void forEach(Consumer super T> action){

Objects.requireNonNull(action);

for(T t: this){

action.accept(t);

}

}

```java

当然像ArrayList自然也是有自己的实现的,那我们就可以使用这样的写法,简洁优雅。forEach方法在java8中参数是`java.util.function.Consumer`,可以称为**消费行为**或者说**动作**类型。

```java

list.forEach(x-> System.out.print(x));

同时,我们只要实现Consumer接口,就可以自定义动作,如果不自定义,默认迭代顺序是按照元素的顺序。

public class ConsumerTest{

public static void main(String[] args){

List list=new ArrayList();

list.add("Jam");

list.add("Jane");

list.add("Sam");

MyConsumer myConsumer= new MyConsumer();

Iterator it= list.iterator();

list.forEach(myConsumer);

}

static class MyConsumer implements Consumer{

@Override

public void accept(Object t){

System.out.println("自定义打印:"+ t);

}

}

}

输出的结果:

自定义打印:Jam

自定义打印:Jane

自定义打印:Sam

1.3 spliterator方法

这是一个为了并行遍历数据元素而设计的迭代方法,返回的是Spliterator,是专门并行遍历的迭代器。以发挥多核时代的处理器性能,java默认在集合框架中提供了一个默认的Spliterator实现,底层也就是Stream.isParallel()实现的,我们可以看一下源码:

// stream使用的就是spliterator

default Stream stream(){

return StreamSupport.stream(spliterator(), false);

}

default Spliterator spliterator(){

return Spliterators.spliterator(this, 0);

}

public static Stream stream(Spliterator spliterator, boolean parallel){

Objects.requireNonNull(spliterator);

return new ReferencePipeline.Head<>(spliterator,

StreamOpFlag.fromCharacteristics(spliterator),

parallel);

}

使用的方法如下:

public static void spliterator(){

List list= Arrays.asList("1","2","3","4","5","6","7","8","9","10");

//获取可迭代器

Spliterator spliterator= list.spliterator();

//一个一个遍历

System.out.println("tryAdvance:");

spliterator.tryAdvance(item->System.out.print(item+""));

spliterator.tryAdvance(item->System.out.print(item+""));

System.out.println("\n-------------------------------------------");

//依次遍历剩下的

System.out.println("forEachRemaining:");

spliterator.forEachRemaining(item->System.out.print(item+""));

System.out.println("\n------------------------------------------");

// spliterator1:0~10

Spliterator spliterator1= list.spliterator();

// spliterator1:6~10 spliterator2:0~5

Spliterator spliterator2= spliterator1.trySplit();

// spliterator1:8~10 spliterator3:6~7

Spliterator spliterator3= spliterator1.trySplit();

System.out.println("spliterator1:");

spliterator1.forEachRemaining(item->System.out.print(item+""));

System.out.println("\n------------------------------------------");

System.out.println("spliterator2:");

spliterator2.forEachRemaining(item->System.out.print(item+""));

System.out.println("\n------------------------------------------");

System.out.println("spliterator3:");

spliterator3.forEachRemaining(item->System.out.print(item+""));

}

tryAdvance()一个一个元素进行遍历

forEachRemaining()顺序地分块遍历

trySplit()进行分区形成另外的 Spliterator,使用在并行操作中,分出来的是前面一半,就是不断把前面一部分分出来

结果如下:

tryAdvance:

1 2

-------------------------------------------

forEachRemaining:

3 4 5 6 7 8 9 10

------------------------------------------

spliterator1:

8 9 10

------------------------------------------

spliterator2:

1 2 3 4 5

------------------------------------------

spliterator3:

6 7

还有一些其他的用法在这里就不列举了,主要是trySplit()之后,可以用于多线程遍历。理想的时候,可以平均分成两半,有利于并行计算,但是不是一定平分的。

2. Collection接口 extend Iterable

Collection接口可以算是集合类的一个根接口之一,一般不能够直接使用,只是定义了一个规范,定义了添加,删除等管理数据的方法。继承Collection接口的有List,Set,Queue,不过Queue定义了自己的一些接口,相对来说和其他的差异比较大。

2.1内部定义的方法

源码如下:

boolean add(Object o)//添加元素

boolean remove(Object o)//移除元素

boolean addAll(Collection c)//批量添加

boolean removeAll(Collection c)//批量移除

void retainAll(Collection c)//移除在c中不存在的元素

void clear()//清空集合

int size()//集合大小

boolean isEmpty()//是否为空

boolean contains(Object o)//是否包含在集合中

boolean containsAll(Collection c)//是否包含所有的元素

Iterator iterator()//获取迭代器

Object[] toArray()//转成数组

default boolean removeIf(Predicate super E> filter){}//删除集合中复合条件的元素,删除成功返回true

boolean equals(Object o)

int hashCode()

default Spliterator spliterator(){}//获取可分割迭代器

default Stream stream(){}//获取流

default Stream parallelStream(){}//获取并行流

里面获取并行流的方法parallelStream(),其实就是通过默认的ForkJoinPool(主要用来使用分治法(Divide-and-Conquer Algorithm)来解决问题),提高多线程任务的速度。我们可以使用ArrayList来演示一下平行处理能力。例如下面的例子,输出的顺序就不一定是1,2,3...,可能是乱序的,这是因为任务会被分成多个小任务,任务执行是没有特定的顺序的。

List list= Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

list.parallelStream()

.forEach(out::println);

2.2继承Collection的主要接口

graph LR;

Collection-->List-有顺序,可重复

List-有顺序,可重复-->LinkedList-使用链表实现,线程不安全

List-有顺序,可重复-->ArrayList-数组实现,线程不安全

List-有顺序,可重复-->Vector-数组实现,线程安全

Vector-数组实现,线程安全-->Stack-堆栈,先进后出

Collection-->Set-不可重复,内部排序

Set-不可重复,内部排序-->HashSet-hash表存储

HashSet-hash表存储-->LinkHashSet-链表维护插入顺序

Set-不可重复,内部排序-->TreeSet-二叉树实现,排序

Collection-->Queue-队列,先进先出

2.2.1 List extend Collection

继承于Collection接口,有顺序,取出的顺序与存入的顺序一致,有索引,可以根据索引获取数据,允许存储重复的元素,可以放入为null的元素。

最常见的三个实现类就是ArrayList,Vector,LinkedList,ArrayList和Vector都是内部封装了对数组的操作,唯一不同的是,Vector是线程安全的,而ArrayList不是,理论上ArrayList操作的效率会比Vector好一些。

里面是接口定义的方法:

int size();//获取大小

boolean isEmpty();//判断是否为空

boolean contains(Object o);//是否包含某个元素

Iterator iterator();//获取迭代器

Object[] toArray();//转化成为数组(对象)

T[] toArray(T[] a);//转化为数组(特定位某个类)

boolean add(E e);//添加

boolean remove(Object o);//移除元素

boolean containsAll(Collection> c);//是否包含所有的元素

boolean addAll(Collection extends E> c);//批量添加

boolean addAll(int index, Collection extends E> c);//批量添加,指定开始的索引

boolean removeAll(Collection> c);//批量移除

boolean retainAll(Collection> c);//将c中不包含的元素移除

default void replaceAll(UnaryOperator operator){}//替换

default void sort(Comparator super E> c){}//排序

void clear();//清除所有的元素

boolean equals(Object o);//是否相等

int hashCode();//计算获取hash值

E get(int index);//通过索引获取元素

E set(int index, E element);//修改元素

void add(int index, E element);//在指定位置插入元素

E remove(int index);//根据索引移除某个元素

int indexOf(Object o);//根据对象获取索引

int lastIndexOf(Object o);//获取对象元素的最后一个元素

ListIterator listIterator();//获取List迭代器

ListIterator listIterator(int index);//根据索引获取当前的位置的迭代器

List subList(int fromIndex, int toIndex);//截取某一段数据

default Spliterator spliterator(){}//获取可切分迭代器

上面的方法都比较简单,值得一提的是里面出现了ListIterator,这是一个功能更加强大的迭代器,继承于Iterator,只能用于List类型的访问,拓展功能例如:通过调用listIterator()方法获得一个指向List开头的ListIterator,也可以调用listIterator(n)获取一个指定索引为n的元素的ListIterator,这是一个可以双向移动的迭代器。

操作数组索引的时候需要注意,由于List的实现类底层很多都是数组,所以索引越界会报错IndexOutOfBoundsException。

说起List的实现子类:

ArrayList:底层存储结构是数组结构,增加删除比较慢,查找比较快,是最常用的List集合。线程不安全。

LinkedList:底层是链表结构,增加删除比较快,但是查找比较慢。线程不安全。

Vector:和ArrayList差不多,但是是线程安全的,即同步。

2.2.2 Set extend Collection

Set接口,不允许放入重复的元素,也就是如果相同,则只存储其中一个。

下面是源码方法:

int size();//获取大小

boolean isEmpty();//是否为空

boolean contains(Object o);//是否包含某个元素

Iterator iterator();//获取迭代器

Object[] toArray();//转化成为数组

T[] toArray(T[] a);//转化为特定类的数组

boolean add(E e);//添加元素

boolean remove(Object o);//移除元素

boolean containsAll(Collection> c);//是否包含所有的元素

boolean addAll(Collection extends E> c);//批量添加

boolean retainAll(Collection> c);//移除所有不存在于c集合中的元素

boolean removeAll(Collection> c);//移除所有在c集合中存在的元素

void clear();//清空集合

boolean equals(Object o);//是否相等

int hashCode();//计算hashcode

default Spliterator spliterator(){}//获取可分割迭代器

主要的子类:

HashSet

允许空值

通过HashCode方法计算获取hash值,确定存储位置,无序。

LinkedHashSet

HashSet的子类

有顺序

TreeSet

如果无参数构建Set,则需要实现Comparable方法。

亦可以创建时传入比较方法,用于排序。

2.2.3 Queue extend Collection

队列接口,在Collection接口的接触上添加了增删改查接口定义,一般默认是先进先出,即FIFO,除了优先队列和栈,优先队列是自己定义了排序的优先顺序,队列中不允许放入null元素。

下面是源码:

boolean add(E e);//插入一个元素到队列,失败时返回IllegalStateException(如果队列容量不够)

boolean offer(E e);//插入一个元素到队列,失败时返回false

E remove();//移除队列头的元素并移除

E poll();//返回并移除队列的头部元素,队列为空时返回null

E element();//返回队列头元素

E peek();//返回队列头部的元素,队列为空时返回null

主要的子接口以及实现类有:

Deque(接口):Queue的子接口,双向队列,可以从两边存取

ArrayDeque:Deque的实现类,底层用数组实现,数据存贮在数组中

AbstractQueue:Queue的子接口,仅实现了add、remove和element三个方法

PriorityQueue:按照默认或者自己定义的顺序来排序元素,底层使用堆(完全二叉树)实现,使用动态数组实现,

BlockingQueue:在java.util.concurrent包中,阻塞队列,满足当前无法处理的操作。

(2) Map接口

定义双列集合的规范Map,每次存储一对元素,即key和value。

key的类型可以和value的类型相同,也可以不同,任意的引用类型都可以。

key是不允许重复的,但是value是可以重复的,所谓重复是指计算的hash值系统。

下面的源码的方法:

V put(K key, V value);//添加元素

V remove(Object key);//删除元素

void putAll(Map extends K,? extends V> m);//批量添加

void clear()//移除所有元素

V get(Object key);//通过key查询元素

int size();//查询集合大小

boolean isEmpty();//集合是否为空

boolean containsKey(Object key);//是否包含某个key

boolean containsValue(Object value);//是否包含某个value

Set keySet();//获取所有key的set集合

Collection values();//获取所有的value的set集合

Set> entrySet();//返回键值对的set,每一个键值对是一个entry对象

boolean equals(Object o);//用于比较的函数

int hashCode();//计算hashcode

default V getOrDefault(Object key, V defaultValue)//获取key对应的Value,没有则返回默认值()

default void forEach(BiConsumer super K,? super V> action){}//遍历

default void replaceAll(BiFunction super K,? super V,? extends V> function){}//批量替换

//缺少这个key的时候才会添加进去

//返回值是是key对应的value值,如果不存在,则返回的是刚刚放进去的value

default V putIfAbsent(K key, V value){}

default boolean remove(Object key, Object value){}//移除元素

default boolean replace(K key, V oldValue, V newValue){}//替换

default V replace(K key, V value){}//替换

//和putIfAbsent有点像,只不过传进去的mappingFunction是映射函数,也就是如果不存在key对应的value,将会执行函数,函数返回值会被当成value添加进去,同时返回新的value值

default V computeIfAbsent(K key,Function super K,? extends V> mappingFunction){}

//和computeIfAbsent方法相反,只有key存在的时候,才会执行函数,并且返回

default V computeIfPresent(K key,BiFunction super K,? super V,? extends V> remappingFunction){}

//不管如何都会执行映射函数,返回value

default V compute(K key,BiFunction super K,? super V,? extends V> remappingFunction){}

default V merge(K key, V value,BiFunction super V,? super V,? extends V> remappingFunction){}

值得注意的是,Map里面定义了一个Entry类,其实就是定义了一个存储数据的类型,一个entry就是一个.

Map的常用的实现子类:

HashMap:由数组和链表组成,线程不安全,无序。

LinkedHashMap:如果我们需要是有序的,那么就需要它,时间和空间效率没有HashMap那么高,底层是维护一条双向链表,保证了插入的顺序。

ConcurrentHashMap:线程安全,1.7JDK使用锁分离,每一段Segment都有自己的独立锁,相对来说效率也比较高。JDK1.8抛弃了Segment,使用Node数组+链表和红黑树实现,在线程安全控制上使用Synchronize和CAS,可以认为是优化的线程安全的HashMap。

HashTable:对比与HashMap主要是使用关键字synchronize,加上同步锁,线程安全。

(二)总结

这些集合原始接口到底是什么?为什么需要?

我想,这些接口其实都是一种规则/规范的定义,如果不这么做也可以,所有的子类自己实现,但是从迭代以及维护的角度来说,这就是一种抽象或者分类,比如定义了Iterator接口,某一些类就可以去继承或者实现,那就得遵守这个规范/契约。可以有所拓展,每个子类的拓展不一样,所以每个类就各有所长,但是都有一个中心,就是原始的集合接口。比如实现Map接口的所有类的中心思想都不变,只是各有所长,各分千秋,形成了大千集合世界。

【作者简介】:

秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。个人写作方向:Java源码解析,JDBC,Mybatis,Spring,redis,分布式,剑指Offer,LeetCode等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都完全正确,但是我保证所写的均经过实践或者查找资料。遗漏或者错误之处,还望指正。

平日时间宝贵,只能使用晚上以及周末时间学习写作,关注我,我们一起成长吧~

JAVA中,返回值是什么意思

返回值:

在定义java方法时,必需要定义一个返回值类型或者使用void占位符占位,然后在方法体末端return一个返回值,需要与定义时候的返回值类型一致,然后在调用此方法时,就会把返回值传输到调用处。(也就是说,这个方法要做什么事,做完通知我们一声,这个通知也就是return)

例如:

方法:

public static String add(){//String返回值类型的方法

String num=“张亮”;//定义一个String局部变量

return num;//返回局部变量

}

测试:

public static void main(String[] args){

add();//调用add方法

}

OK,关于javacompute什么意思和java注解@immutable什么意思的内容到此结束了,希望对大家有所帮助。

javaee入门有什么好书 自学Java,有哪些书籍推荐北京医保个人账户(北京医保个人账户密码初始)