补全Java基础(一)JavaSE基础

1. super和this

    类在继承时,会用到this和super,之前也很少用到,以前看过,今天看了一下,只记得以前看过,理解还是不深。

  • this

    1. 直接引用,相当于指向对象本身

    2. 型参与成员变量名字相同,用this来区分

      class A{
          private int number = 15;
          A(){
              System.out.println("number:"+number);
          }
          public int getNumber(int number){
              this.number = number; //用来区分参数number和A类中的属性number(this.number)
              return this.number;
          }
      }
      复制代码
    3. 引用构造函数

      this(参数):调用本类中另一种构造函数(应在构造函数的第一行)

  • super

    1. 直接引用,指向当前对象的父类,可以用super.xx来引用父类的属性

    2. 子类的变量/方法与父类的变量/方法重名

      在子类种调用时,重名的方法/变量,直接使用是子类的,this.xx或this.xx()属于调用父类的。

    3. 引用构造函数

      super(参数):调用父类中某种构造函数(应在构造函数的第一行)


参考:Java中this和super的用法总结


2. equals()和hashcode()

    这两个方法都是Object中带有的方法,即所有方法都有equals和hashcode方法。

    在没有重写的情况下,equals方法即"==",hashcode方法获取哈希码,即确定对象在散列表中的索引位置。

    如果不重写equals方法,那么比如User这种对象,一般我们equals比较的是它包含的属性是否相同,不重写的话,依照Object的equals方法,两个对象的内存地址不同,所以返回false,这显然不是我们想要要的。所以要重写,在方法中判断属性是否相同。

    如果只重写equals方法,而没有重写hashcode方法,比如User对象放在hashset中,hashset的特性是不允许重复。当我们放入两个数据相同的User时,重写后的equals方法返回true,但是没有重写hashcode方法,依照Object的hashcode方法,两个对象的hash值不同,所以不是同一对象,hashset中都可以存入,但是当我们将User放入hashset中,要求如果是要求数据不同的话,就会无法实现。所以需要重写hashcode方法,使得包含数据相同的两个User对象,返回的hash值相同,而此时,hashset会判断重复,就只能存入一个。

```
    而这其中就涉及到hashset判断重复的依据
    add(object)会先用Object的equals方法和hashcode方法判断set中,
    是否已有重复元素,当然,如果两个元素如果不是指向同一对象,无论如何也不会重复。
    所以,当我们需要剔除数据相同的元素,就需要在此基础上重新给元素对应的类型重写hashcode方法。
```
复制代码

参考:
重写equals方法后重写hashCode方法的必要性
如何重写hashCode算法
HashSet重复元素判断


3. 线程

    对我来说,是急需加强的一个点,因为之前用到的不多,所以也就不求甚解。

    Java多线程的两种实现方式:继承Thread类、实现Runnable接口

  • 继承Thread类

    package cn.wh3t;
    
    public class MultiThread extends Thread{
    	private String name;
    	
    	public MultiThread(String name) {
    		// TODO 自动生成的构造函数存根
    		this.name = name;
    	}
    	
    	@Override
    	public void run() {
    		// TODO 自动生成的方法存根
    		for(int i =0;i<5;i++) {
    			System.out.println("Thread-"+name+"----hello"+i);
    		}
    	}
    	
    	public static void main(String[] args) {
    		MultiThread multiThread1 = new MultiThread("A");
    		MultiThread multiThread2 = new MultiThread("B");
    		multiThread1.start();
    		System.out.println("hello1");
    		System.out.println("hello2");
    		multiThread2.start();
    		System.out.println("hello3");
    		System.out.println("hello4");
    	}
    }
    复制代码
    输出结果://多次运行结果都会不同
    hello1
    hello2
    Thread-A----hello0
    Thread-A----hello1
    Thread-A----hello2
    hello3
    Thread-A----hello3
    hello4
    Thread-A----hello4
    Thread-B----hello0
    Thread-B----hello1
    Thread-B----hello2
    Thread-B----hello3
    Thread-B----hello4
    复制代码

    实际启动的时候并不是调用Thread的run方法,而是strat方法。

    那么run()和start()有什么区别

    在Java中,线程通常都有5种状态:创建、就绪、运行、阻塞、死亡

        1. 创建:生成线程对象时,还未调用start方法

        2. 就绪:调用了该线程对象的start,线程进入就绪状态,但是并未把该线程设成当前线程。在线程运行之后,从等待或者睡眠中回来,也会进入就绪状态

        3. 运行:将该线程设为当前线程,线程进入运行状态,开始运行run()中的代码

        4. 阻塞:线程正在运行时,被暂停,通常是为了某个时间发生或者加载某个资源之后再继续运行。sleep、suspend、wait都会造成阻塞

        5. 死亡:线程的run()结束,或者调用了stop方法,线程就会死亡。对于死亡的线程,再使用start方法也不会使其进入就绪状态

    总之就是说,如果没有start方法,只有run方法。就不是多线程执行了。因为start方法使线程进入就绪状态,并没有立即执行,通过上面代码的运行结果也可以看出,线程1的start要早于hello1、hello2的打印,却比他们输出要晚。如果没有start,只是run(),就会依次执行,回归单线程(当前主线程)执行的状态。


  • 实现Runnable接口

    package cn.wh3t;
    
    public class MultiThreadByRunnable implements Runnable{
    
    	private int count = 20;
    	
    	@Override
    	public void run() {
    		// TODO 自动生成的方法存根
    		for(int i =0;i<40;i++) {
    			if(count>0) {
    				System.out.println(Thread.currentThread().getName()+"  "+count--);
    			}
    		}
    	}
    	
    	public static void main(String[] args) {
    		MultiThreadByRunnable runnable = new MultiThreadByRunnable();
    		Thread thread1 = new Thread(runnable,"A");
    		Thread thread2 = new Thread(runnable,"B");
    		thread1.start();
    		System.out.println("hello1");
    		System.out.println("hello2");
    		thread2.start();
    		System.out.println("hello3");
    		System.out.println("hello4");
    	}
    }
    复制代码
    输出结果://多次运行结果都会不同
    hello1
    A  20
    hello2
    A  19
    A  18
    A  17
    hello3
    hello4
    A  16
    B  15
    B  13
    B  12
    A  14
    B  11
    B  10
    B  9
    B  8
    B  6
    B  5
    B  4
    B  3
    B  2
    B  1
    A  7
    
    复制代码

    两种实现方式不同:相比之下,实现Runnable接口更有优势

      1. 适合多个代码相同的程序去处理同一资源;

      2. 可以避免Java中的单继承,可以扩展实现更多接口

      3. 增加程序的健壮性,代码可以被多个程序共享,代码和数据独立


  • wait()和notify()

    sleep()、suspend()、yield()等都是Thread类的方法,wait()和notify()确实属于Object的方法,即所有类都可以执行这两个方法。因为这两个方法阻塞时要释放占用的锁,而所有对象都有锁。wait()导致线程阻塞,释放该对象上占用的锁。调用notify()则导致因调用该对象的wait()而阻塞的线程中,随机选择一个解除阻塞(但是要等到真正获得锁以后才能执行)。这两个方法必须在synchronized方法或块中执行,因为synchronized方法/块才能占有锁,有锁才能释放。

    关于wait和notify要注意的三个点:

    1. 调用notify方法而接触阻塞的线程是从wait方法阻塞的线程中随机选取的,所以无法预料哪个线程被选取,所以要小心,避免不确定性产生的问题

    2. 除了notify,notifyAll方法也可以唤醒,解除阻塞的是因wait方法而阻塞的所有线程,当然,只有获得锁的那个线程才能进入可执行状态

    3. wait和notify必须成对存在

    package cn.wh3t;

    public class CountThread extends Thread{
    	int total;
    
    	@Override
    	public void run() {
    		// TODO 自动生成的方法存根
    		synchronized (this) {
    			for(int i=0;i<100;i++) {
    				total = total +1 ;
    			}
    			this.notify();//唤醒被阻塞的线程
    			System.out.println("notify"+total);
    		}
    	}
    }
复制代码
    package cn.wh3t;
    
    public class TestWaitAndNotify {
    	public static void main(String[] args) {
    		CountThread thread = new CountThread();
    		thread.start();
    		
    		synchronized(thread) {
    			System.out.println("等到线程结束"+thread.total);
    			try {
    				thread.wait();
    				System.out.println("wait"+thread.total);
    			}catch(Exception e) {
    				e.printStackTrace();
    			}
    			System.out.println("计算的结果是:"+thread.total);
    		}
    	}
    }   
复制代码
(主线程等待技术线程结束后才打印输出)
等到线程结束0
notify100
wait100
计算的结果是:100
复制代码

参考:
java 线程详解
Thread的run()与start()的区别
java thread中的wait()和notify()


转载于:https://juejin.im/post/5d07372f6fb9a07ee30e1948

猜你喜欢

转载自blog.csdn.net/weixin_34351321/article/details/93177480