关于java的一些基础知识要点

JAVA的一些知识要点

关于集合和泛型

集合常用类

  1. Collection 单列集合根接口

    1. List 有序的,可以重复
      1. ArrayList 底层代码使用了Object数组实现,特点在查询速度快,增删慢
      2. LinkedList 底层代码用的是链表数据结构实现,特点在查询速度慢,增删快
      3. Vector 底层跟ArrayList一样用的Object数组实现,但是线程是安全的,操作效率较低
    2. Set 无序的,不可以重复
      1. HashSet 底层代码用的是哈希表实现,特点在存取速度快
      2. TreeSet 底层代码用的是二叉树(红黑树) ,特点在会对集合中的元素自动进行排序
  2. Map

    1. HashMap
    2. TreeMap

集合的遍历方式

  1. List遍历,list.add数据后,for循环遍历调用list.get来获取数据或者用高级for
1
2
3
4
5
6
7
ArrayList<String> arrlist = new ArrayList<String>();
arrlist.add("This is ArrayList 1");
arrlist.add("This is ArrayList 2");
arrlist.add("This is ArrayList 3");
for(int i=0 ;i<arrlist.size();i++){
System.out.println(arraylist.get(i));
}
  1. iterator迭代器遍历,set.add或者list.add数据后,获取迭代器,迭代器始终为true,使用while,hasNext遍历数据,用X.next获取当前数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person{
int id;
String name;
public Persion(int id, String name){
Super();
this.id = id;
this.name = name;
}
@Override
public String toString(){
return "id:" + this.id + "name:" + this.name;
}
}
class xxxx{
public static void main(String[] args){
HashSet<Person> hset = new HashSet<Person>();
hset.add(new Person(1,"ruby"));
hset.add(new Person(2,"node"));
hset.add(new Person(2,"node"));
//hashset,Set集合由于是无序的并且是不可重复的,此处重复了2,"node"后输出依旧会输出,因为此处添加的是一个person对象,而每一个新对象都有不同哈希值,hashset判断重复原理是根据哈希值来判断,如哈希表中该哈希值不存在则存入数据,否则则不允许存入。所以此处应当覆写hashcode方法,返回唯一的id来辨认对象是否一致。并且hashset在确定哈希表中存在相同哈希值的情况是使用equals方法在该位置上的元素再比较一次,所以还需要覆写equals方法,把对象id传入并且对比该对象的id是否一致。如果一致则是有相同元素。不再写入重复元素,否则写入元素
System.out.println(hset);
}
}
1
2
3
4
5
6
7
8
HashSet<String> hset = new HashSet<String>();
hset.add("This is HashSet 1");
hset.add("This is HashSet 2");
hset.add("This is HashSet 3");
Iterator<String> it = hset.iterator();
while(it.hasNext){
System.out.println(it.next());
}
  • foreach循环遍历(高级for),左临时变量获取右集合变量内数据,然后输出临时变量。“底层也是迭代器”,迭代器迭代过程不允许集合对象修改元素的个数,只能用迭代器的方法

    1
    2
    3
    4
    5
    6
    7
    ArrayList<String> arrlist = new ArrayList<String>();
    arrlist.add("This is ArrayList 1");
    arrlist.add("This is ArrayList 2");
    arrlist.add("This is ArrayList 3");
    for(String templist : arrlist){
    System.out.println(templist);
    }

  • map键值遍历,map.put数据后,使用Entry遍历,map对象获取到entrySet,返回Set集合,然后使用foreach循环输出键值对

    1
    2
    3
    4
    5
    6
    7
    8
    HashMap<String,String> map = new HashMap<String,String>();
    map.put("1","Csharp");
    map.put("2","node");
    map.put("3","python");
    Set<Entry<String,String>> entrys = map.entrySet();
    for(Entry<String,String> entry : entrys){
    System.out.println("key:"+ entry.getKey() + " value:" + entry.getValue());
    }

泛型

规范一个对象的类型,把运行时出现的问题提前到编译时(原因是在创建对象或者数组的时候,如果没有指定泛型 ,那么传入多个不同类型的参数时没办法跟返回参数一致。例如一个集合放入字符串String类型,然后获取该集合中的字符串,此时是没问题的,但是如果在该集合中插入添加了Integer类型的情况,那么获取集合中元素或者操作集合元素的时候将会出错,而使用泛型的情况,在编译时就将会提醒用户该集合中插入了不同类型的元素的错误。)

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class xxxxx{
public static void main(String[] args){
ArrayList arrlist = new ArrayList();
arrlist.add("java jdk");
arrlist.add("java jre");
arrlist.add("java jvm");
arrlist.add(19);//此时集合无泛型,可添加Object类,所以String和Int类型都可添加,但是在进行读取胡总操作时将会出错
for(int i = 0 ; i<arrlist.size() ; i++ ){
String str = (String) arrlist.get(i);//此时在集合中的第四个元素,Int类型19在传引用到str时将会报类型转换出错。
System.out.println(str.toUpperCase());//而对集合进行大小写转换操作时,Int类型没有大小写之分,所以也会报错。
}
//正确应当:
ArrayList<String> arrlist2 = new ArrayList<String>();
arrlist.add("java jdk");
arrlist.add("java jre");
arrlist.add("java jvm");
arrlist.add(19);//此时编译器就会报错,添加了非String类型数据
}
}

泛型的好处和注意事项:

  1. 避免无谓的强制转换类型
  2. 泛型没有多态的说法或者概念
  3. 泛型推荐两边都要一致,写单边可以通过是为了兼容老版本系统
1
2
3
4
5
X<S> x = new X<S>(); true
X<S> x = new X<O>(); false
X<O> x = new X<S>(); false
X x = new X<S>(); true
X<S> x = new X(); true
自定义泛型 (自定义泛型的声明可以是合法的名称即可)

自定义泛型就是相当于调用方法传的值 T 是什么类型,而返回就是什么类型。 T占位符 等同于一个变量 相当于声明一个T变量
修饰符 <声明自定义泛型> 返回值类型泛型 函数名(形参)

泛型类的声明

可以在类名上声明出一个自定义泛型,要注意的是:

  1. 在类上自定义泛型的时候,具体的数据类型是在创建对象的时候指定。
  2. 在类上自定义的泛型,如果创建该类的对象没有指定泛型的具体类型,那么默认为泛型类中声明的Object类型。
1
2
3
4
5
6
7
class xxxxx<T>{
Object[] arr = new Object[10];
public xxxxx(){}
public void xxx(T t){}
xxxxx<String> xx = new xxxxx<String>(); //此时xx.xxx(T t)中只能添加String
xxxxx xx = new xxxxx(); //不给泛型类加泛型的时候,xx.xxx(T t)中是Object类型
}
泛型接口的声明

可以在接口上声明出一个自定义泛型,要注意的是:

  1. 在接口上自定义泛型的是hi,具体的数据类型是在实现xxxxx接口的时候指定
  2. 在接口上自定义的泛型,如果实现该接口时没有指定泛型的具体类型,那么默认为Object类型。
  3. 如果希望在使用创建接口实现类对象的时候再指定接口自定义泛型具体类型,则需要在实现接口出声明出然后去创建实现类的时候再指定具体类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface xxxxx<T>{
public void xxx(T t);
public void xxxvvv(T t);
}
class vvvvv implements xxxxx<String>{ //指定为String,实现的方法则为String
@Override
public void xxx(String t);
public void xxxvvv(String t);
}
class vvvvv implements xxxxx{//不去指定泛型,实现的方法则为Object
@Override
public void xxx(Object t);
@Override
public void xxxvvv(Object t);
}
class vvvvv implements xxxxx{//如果想在创建和使用对象的时候指定类型,则需要在实现接口的时候指定类型为<T>,然后再在类方法中创建对象的时候指定具体的类型
@Override
public void xxx(Object t);
@Override
public void xxxvvv(Object t);
}

常用IO操作和Thread操作

IO操作的常用类

  1. 字节流
    1. InputStream //IO操作中字节流的基类(父类),也是个抽象类
      1. FileInputStream //向文件写入字节数据的写入字节流
      2. BufferedInputStream //缓冲写入字节数据的缓冲写入字节流,为写入文件字节提高效率的类
    2. OutputStream
      1. FileOutputStream //向文件输出字节数据的输出字节流
      2. BufferedOutputStream //缓冲输出字节数据的缓冲输出字节流,为输出文件字节提高效率的类
  2. 字符流
    1. Reader //IO操作中字符流的基类(父类),也是个抽象类
      1. FileReader //向文件写入字符数据的写入字符流
      2. BufferedReader //缓冲写入字符数据的缓冲写入字符流,为写入文件字符提高效率的类
    2. Writer
      1. FileWriter //向文件输出字符数据的输出字符流
      2. BufferedWriter //缓冲输出字符数据的缓冲输出字符流,为输出文件字符提高效率的类
  3. 转换流(可以把对应的字节流转换为字符流并且还能指定字符集)
    1. InputStreamReader //输入字节流的转换流
    2. OutputStreamWriter //输出字节流的转换流
转换流的实际用例
  1. 字节输入流转换为字符输入流

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class xxxxx{
    public static void main(String[] args){
    InputStream in = System.in;
    InputStreamReader isr = new InputStreamReader(in); //把控制台输入的字节流转换为字符流
    BufferedReader br = new BufferedReader(isr);
    //此时BufferedReader缓冲字符流需要传入的是一个Reader字符流对象,InputStream无法传入,所以需要进行转换
    String s = br.readLine();
    System.out.println(s);
    }
    }
  2. 字节输出流转换为字符输出流

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class xxxxx{
    public static void main(String[] args){
    Socket socket = new Socket(InetAddress.getLocalHost(),8080);/
    OutputStream out = socket.getOutputStream();///获取到socket的输出流对象
    OutputStreamWriter outsw = new OutputStreamWriter(out);
    //把获取到的输出字节流转换为输出字符流,同时在OutputStreamWriter的构造方法中还有一个形参可以指定字符集,如:new OutputStreamWriter(out,"utf-8")
    outsw.write("Java IOStream");
    }
    public void fileIO(){
    InputStream in = System.in;
    InputStreamReader isr = new InputStreamReader(in);
    BufferedReader br = new BufferedReader(isr);
    String s = br.readLine();
    System.out.println(s);
    FileOutputStream file = new FileOutputStream(
    "C:"+File.separator+"Users"+File.separator+
    "bitam"+File.separator+"Desktop"+File.separator+"x.txt");
    //创建文件和程序的数据连接
    OutputStreamWriter outsw = new OutputStreamWriter(file,"utf-8");
    //创建一个输出字节转换字符流并指定字符集来写入数据
    outsw.write(s);//写入操作
    outsw.close();//关闭IO资源
    }
    }

多线程

多线程的创建方式

  1. extends Thread (继承Thread多线程类)

    1. 创建一个类去继承Thread类
    2. 覆写Thread的run方法,把自定义线程的任务写在run方法中
    3. 创建一个Thread的子类对象(该继承了Thread并覆写了run方法的那个类),并且使用start方法来启动该线程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class xxxxx extends Thread {
    @Override
    public void run(){
    for(int i =0 ; i<100 ; i++){
    System.out.println(Thread.currentThread().getName()+":"+i);//获取当前方法线程名并输出i
    }
    }
    public static void main(String[] args){
    xxxxx x = new xxxxx();
    x.start();
    for(int i =0 ; i<100 ; i++){
    System.out.println(Thread.currentThread().getName()+":"+i);//获取当前方法线程名并输出i
    }
    }
    }
  2. implements Runnable (实现Runnable多线程接口)

    1. 创建一个类实现Runnable接口
    2. 覆写Runnable的run方法,把自定义线程的任务写在run方法中
    3. 创建一个Runnable实现类对象(也就是实现了Runnable接口并覆写了run方法的那个类)
    4. 创建一个Thread对象,然后把Runnable实现类的对象作为参数去传递给Thread对象
    5. 调用Thread对象的strat方法开启线程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class xxxxx implements Runnable{
    @Override
    public void run() {
    for(int i = 0 ; i<100 ; i++){
    System.out.println(Thread.currentThread().getName()+":"+i);
    }
    }
    }
    public class ttttt {
    public static void main(String[] args){
    xxxxx x = new xxxxx();
    Thread t = new Thread(x);
    t.start();
    for(int i = 0 ; i<100 ; i++){
    System.out.println(Thread.currentThread().getName()+":"+i);
    }
    }
    }

线程安全问题的解决方案

线程安全问题出现情况原因:
  1. 必须有存在两个或者以上的线程共享着一个资源的情况才会出现
  2. 操作共享资源的代码必须有两句或者以上
线程安全的解决方式
  1. 同步代码块

    1
    2
    3
    synchronized(锁){
    需要被同步的代码
    }
  2. 同步函数

    1
    2
    3
    修饰符 synchronized 返回值类型 函数名(形参列表...){
    }
注意事项:
  1. 同步代码块的锁可以使任意的对象,同步函数的锁是固定的
  2. 非静态函数的锁对象是this对象,静态函数的锁对象是class对象
  3. 锁对象必须是多线程共享的对象,否则锁不住
  4. 在同步代码块或者是同步函数中调用sleep方法是不会释放锁对象的,如果是调用了wait方法是会释放锁对象的

关于==和equals的区别

从简单来说

==是字面意思,等于。equals则是比较,是否相同。而等于跟相同是两个概念,可以从以下例子来理解:
1
2
3
4
5
6
7
8
9
10
11
12
13
String i = "Java";
String ii = "Java";
String iii = "Java1";
String iiii = new String("Java");
System.out.println(i.equals(ii));//由于String有覆写了equals方法,所以内容相同时,比较返回true
System.out.println(i.equals(iii));//由于内容不相同所以返回false
System.out.println(i.equals(iiii));//由于内容相同,但是String覆写了equals方法,所以内容相同返回true
System.out.println(i==ii);//由于JAVA中对象内容一致的时候,非创建新对象,则堆中内容指向相同的栈引用
System.out.println(i==iiii);//;由于==是比较的地址值,这里new了一个心对象,所以地址值不同,返回false
System.out.println(i.toString()+"|"+i.getBytes()+"|"+i.hashCode());
System.out.println(ii.toString()+"|"+ii.getBytes()+"|"+ii.hashCode());
System.out.println(iii.toString()+"|"+iii.getBytes()+"|"+iii.hashCode());
System.out.println(iiii.toString()+"|"+iiii.getBytes()+"|"+iiii.hashCode());

从Java角度来说

==是判断两个变量或者对象实例是不是指向相同的内存栈(地址值),equals则判断两个变量或者对象实例的hashcode是不是相同(相同引用),使用==的情况会对内存地址值进行比较,equals则对该对象的hashcode进行比较。

1
2
String s = "Java";// hashcode:2301506 内存地址值:[B@5a3e03b3
String srt = new String("Java"); //hashcode:2301506 内存地址值:[B@1d650b0e

实战中

如果只比较两个基本数据类型值,那么只需要使用==,而不能使用equals,因为equals是个函数(方法),而基本类型如int,float,double等不是对象,那么就没有函数,所以是无法equals的。

1
2
3
4
5
6
7
8
9
10
int i = 1;
int y = 1;
System.out.println(i.equals(y));
将会提示错误信息:
Cannot invoke equals(int) on the primitive type int
除非把声明类型换为Integer对象
Integer i = 1;
Integer y = 1;
System.out.println(i.equals(y));
则会通过,并输出true

所以如果比较的是值,建议使用==,而不用equals,具体原因如下。

equals底层源码是:

1
2
3
4
5
6
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}

传入一个对象,如果对比两个实例是否是同一个实例(instanceof),如果是则继续判断该实例对象的hashcode跟该被比较对象的hashcode用==对比,相同则返回true,否则返回false。

而如果传入对象后对比两个实例根本不是同一个实例,那么也就不用在进一步用==对比地址值,直接返回false。

也就是equals其实也是直接调用了==操作,不过是当实例相同时调用它来对比hashcode。

如果想对比一个对象的地址值是否相同,应该按照自己的需求去覆写equals方法。而且如果在不覆写equals方法的情况,使用==就是比较地址值,使用equals就是比较是否同一引用和hashcode是否相同。

Junit 常用注释:

测试体@Test

//测试体不能是static修饰和不能有形参

非静态测试前环境@Before

非静态测试后清理@After

//不带Class在每个测试方法测试的时候会调用一次

静态测试前环境 @BeforeClass

静态测试后清理@AfterClass

//带Class在所有测试方法测试之前和测试之后的时候只调用一次

文章目录
  1. 1. 关于集合和泛型
    1. 1.1. 集合常用类
      1. 1.1.1. 集合的遍历方式
    2. 1.2. 泛型
      1. 1.2.1. 例子
      2. 1.2.2. 泛型的好处和注意事项:
        1. 1.2.2.1. 自定义泛型 (自定义泛型的声明可以是合法的名称即可)
          1. 1.2.2.1.1. 泛型类的声明
          2. 1.2.2.1.2. 泛型接口的声明
  2. 2. 常用IO操作和Thread操作
    1. 2.1. IO操作的常用类
      1. 2.1.0.1. 转换流的实际用例
  3. 2.2. 多线程
    1. 2.2.1. 多线程的创建方式
    2. 2.2.2. 线程安全问题的解决方案
      1. 2.2.2.1. 线程安全问题出现情况原因:
      2. 2.2.2.2. 线程安全的解决方式
      3. 2.2.2.3. 注意事项:
  4. 2.3. 关于==和equals的区别
    1. 2.3.1. 从简单来说
      1. 2.3.1.1. ==是字面意思,等于。equals则是比较,是否相同。而等于跟相同是两个概念,可以从以下例子来理解:
    2. 2.3.2. 从Java角度来说
    3. 2.3.3. 实战中
  5. 2.4. Junit 常用注释:
,