java基础知识01

前言:

正所谓万丈高楼平地起,有了扎实的基础才能进阶更深奥的课程,才能让你后面的走得更轻松,学Java亦是如此!所以千万不能忽略基础的重要性,下面一起来温习一下那些容易忽略、容易混淆以及比较重要的Java基础。


欢迎大家关注我的公众号 javawebkf,目前正在慢慢地将文章搬到公众号,以后和公众号文章将同步更新,且上的付费文章在公众号上将免费。


一、概述:

1.JDK:Java Development Kit,java的开发和运行环境。
2.JRE:Java Runtime Environment,java程序的运行环境,java运行的所需的类库+JVM(java虚拟机)。
3.javac命令:当执行javac时,会启动java的编译器程序。对指定扩展名的.java文件进行编译。 生成了jvm可以识别的字节码文件。也就是class文件,也就是java的运行程序。
4. java命令:负责运行的部分,会启动jvm,加载运行时所需的类库,并对class文件进行执行。一个文件要被执行,必须要有一个执行的起始点,这个起始点就是main函数。

二、Java基础:

1.数据类型:
(1). 8种基本类型:byte(1字节)、short(2)、int(4)、long(8)、float(4)、double(8)、char(2)、boolean
(2). 3种引用类型: 数组、类、接口。
级别从低到高为:byte,char,short(这三个平级)-->int-->float-->long-->double
自动类型转换:从低级别到高级别,系统自动转的;
强制类型转换:把一个高级别的数赋给一个别该数的级别低的变量就需要强转。

2、运算符:
(1). & 和 &&区别:第一个叫逻辑与,第二个叫短路与。逻辑与是无论左边结果是什么,右边都参与运算;而短路与,如果在左边为false,那么右边就不参与运算了;显然短路与效率更高。| 和 || 的区别亦是如此。

(2). == 和 equals方法的区别:

  • 当比较的是基本类型:== 比较值是否相等;equals不能比较基本类型。
  • 当比较的是引用类型(除了String):== 和 equals都是比较地址值是否相同,即是否为同一个对象;
  • 当比较的是String时:== 比较的还是地址值,但是由于String类重写了equals方法,所以equals比较的是值是否相等。

(3).++和--:
++和--参与运算时,如果++(--)放在操作数后面,则先运算再自增(减),如果++(--)放在操作数前面,则先自增(减),再参与运算。

3、语句:

(1). switch循环:

String s = "牛逼";
switch (s){
    case "牛逼":
         System.out.println("牛逼");
         break;
    case "厉害":
         System.out.println("厉害");
         break;
    default:
         System.out.println("默认");
         break;
}

这就是switch循环的用法了,switch支持的数据类型有byte、short、int、char、String以及Enum类型。

(2). while循环:

int x = 10;
while( x < 20 ) {
    System.out.print("value of x : " + x );
    x++;
}

(3).do while循环:

int x = 10;
do{
    System.out.print("value of x : " + x );
    x++;
}while( x < 20 );

do while和while的区别:do while至少执行一次。

break: 作用于switch ,和循环语句,用于跳出,或者称为结束。break语句单独存在时,下面不要定义其他语句,因为执行不到,编译会失败。当循环嵌套时,break只跳出当前所在循环。要跳出嵌套中的外部循环,只要给循环起名字即可,这个名字称之为标号。
continue: 只作用于循环结构,继续循环用的。结束本次循环,继续下次循环。该语句单独存在时,下面不可以定义语句,执行不到。

4、数组:用于存储同一类型数据的一个容器。
(1). 数组的初始化

int [] i = {1,2,3} //静态初始化
int [] arr = new int[5]; //动态初始化
arr[0] = 10;

(2).二分查找法(前提:数组中的元素必须有序)
二分查找法基本思想:
定义最大索引max,最小索引min,计算中间索引mid=(max+min)/2,拿要查找的数和中间索引处的数比较,如果中间索引处的数大了,就在左边找,这时最小索引min不变,最大索引max=mid-1,如果中间索引处的数小了,就在右边找,这时最大索引不变,最小索引min=mid+1。
代码实现:

public static int getIndex(int[] arr, int value) {
        int min = 0;
        int max = arr.length - 1;
        int mid = (max + min) / 2;
        while (arr[mid] != value) {
            if (arr[mid] > value) {
                max = mid - 1;
            } else if (arr[mid] < value) {
                min = mid + 1;
            }
            if (min > max) {
                return -1;
            }
            mid = (max + min) / 2;
        }
        return mid;
}

(3).数组排序之冒泡排序:
冒泡排序基本思想:
相邻元素两两比较,大的往后放,这样一轮下来,最大值就出现在了最大索引处,如此这般继续,便可得到排好序的数组。每比完一次,下一次比较就可少比较一个元素,总共需要比较数组长度减一次。
代码实现:

for(int x=0; x arr[y+1]){
             int temp= arr[y];
             arr[y]= arr[y+1];
             arr[y+1]=temp;
        }
    }
}

(4).数组排序之选择排序:
选择排序基本思想:
从0索引处开始,依次和后面的每一个比较,小的往前放,第一次比较完毕,最小值出现在0索引处,如此这般,便可得到排好序的数组。
代码实现:

 for(int x=0; x

(5).数组逆序:

for(int start =0,  end=arry.length-1;  start 

5、Java内存:java分了5片内存。
1:寄存器。2:本地方法区。3:方法区。4:栈。5:堆。
栈:存储的都是局部变量 ( 函数中定义的变量,函数上的参数,语句中的变量 );
堆:用于存储数组和对象,new出来的东西就存在堆内存中。

三、面向对象:

1、面向对象特点:

  • 将复杂的事情简单化。
  • 面向对象将以前的过程中的执行者,变成了指挥者。
  • 面向对象这种思想是符合现在人们思考习惯的一种思想。

2、构造函数:
用于给对象进行初始化的函数,在对象创建时,就被调用,而且初始化动作只执行一次。所有对象创建时,都需要初始化才可以使用。
(1). 特点:

  • 该函数的名称和所在类的名称相同。
  • 不需要定义返回值类型。
  • 该函数没有具体的返回值。
  • 子类构造函数运行时,先运行了父类的构造函数,这是因为子类的所有构造函数中的第一行,其实都有一条隐身的语句super();super(): 表示父类的构造函数,并会调用于参数相对应的父类中的构造函数。

3、面向对象特性:
(1).封装: 封装是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。

  • 好处:将变化隔离;便于使用;提高重用性;安全性。
  • 封装原则:将不需要对外提供的内容都隐藏起来,把属性都隐藏,提供公共方法对其访问。

(2).继承:从已有类得到继承信息创建新类的过程。

  • 好处:提高了代码的复用性;让类与类之间产生了关系,提供了另一个特征多态的前提。
  • 使用时机:当类与类之间存在着所属关系时,才具备了继承的前提。a是b中的一种。a继承b。狼是动物的一种,狼继承动物。子类与父类是所属关系:" is a "。
  • Java支持单继承,多重继承。

(3).多态:允许不同子类型的对象对同一消息做出不同的响应。

  • 前提:必须要有继承或者实现关系。
  • 体现:父类引用或者接口的引用指向了自己的子类对象:Animal a = new Cat();
  • 好处:提高了程序的扩展性。
    关于多态,有个孔子装爹的故事:
class 孔子爹 {
  public int age = 40;
  public void teach() {
    System.out.println("讲解JavaSE");
  }
}
class 孔子 extends 孔子爹 {
    public int age = 20;
    public void teach() {
        System.out.println("讲解论语");
    }
    public void playGame() {
        System.out.println("玩王者荣耀");
    }
}

Java培训特别火,很多人来请孔子爹去讲课。这一天孔子爹被请走了,但是还有人来请,就剩孔子在家,价格还挺高。孔子一想,我是不是可以考虑去呢?然后就穿上爹的衣服,带上爹的眼睛,粘上爹的胡子,就开始装爹,跑到别人家里去讲课:

孔子爹 k爹 = new 孔子(); //向上转型
System.out.println(k爹.age); //40,对外表现的是父类的属性
k爹.teach(); //讲解论语,子类重写了teach方法,所以表现的是子类的,若没重写,则输出“讲解JavaSe”。

讲完了,下班回家了,脱下爹的装备,换上自己的装备,做回了自己:

孔子 k = (孔子) k爹; //向下转型
System.out.println(k.age); //20
k.teach(); //讲解论语
k.playGame(); //英雄联盟

4、抽象类和接口:
(1). 抽象:将一类事物的共同特征或行为抽取出来,而不管是怎么实现的。
(2). 抽象类: 用abstract修饰的类。特点如下:

  • 抽象方法只能定义在抽象类中,抽象类和抽象方法必须由abstract关键字修饰(可以描述类和方法,不可以描述变量)。
  • 抽象方法只定义方法声明,并不定义方法实现,即没有方法体。
  • 抽象类不可以被创建对象(实例化)。
  • 只有通过子类继承抽象类并覆盖了抽象类中的所有抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。

注意如下几点:

  • 抽象类也有构造方法,用于给子类对象进行初始化。
  • 抽象类中可以没有抽象方法,但抽象方法一定是定义在抽象类中。
  • abstract不可以和final , private , static 共存。

(3). 接口:是用关键字interface定义的。
接口中的成员都有固定的修饰符:

  • 成员变量:public static final
  • 成员方法:public abstract

抽象类与接口区别:

  • 抽象类:一般用于描述一个体系单元,将一组共性内容进行抽取。
  • 接口:一般用于定义对象的扩展功能,是在继承之外还需这个对象具备的一些功能。
  • 抽象类只能被继承,而且只能单继承。接口需要被实现,而且可以多实现。接口的出现弥补了Java单继承的缺点。
  • 抽象类中可以定义非抽象方法,子类可以直接继承使用。接口中都是抽象方法,需要子类去实现。
  • 抽象类使用的是 is a 关系;接口使用的 like a 关系。
  • 抽象类的成员修饰符可以自定义;接口中的成员修饰符是固定的。全都是public的。

5、内部类:

  • 定义:

如果A类需要直接访问B类中的成员,而B类又需要建立A类的对象。这时,为了方便设计和访问,直接将A类定义在B类中就可以了。A类就称为内部类。内部类可以直接访问外部类中的成员。而外部类想要访问内部类,必须要建立内部类的对象。

class Outer{
    int num = 4;    
    class  Inner {
        void show(){
            System.out.println("inner show run "+num);// 直接访问外部类成员          
        }
    }
    public void method(){
        Inner in = new Inner();//创建内部类的对象。
        in.show();//调用内部类的方法。 
    }
}
  • 内部类和静态内部类:

看一下静态和非静态内部类创建对象有什么区别。看下面的代码:

public class Outer {
   class Inner{ // 内部类
       void show(){
           System.out.println("内部类");
       }
   }
   static class Inner2{ // 静态内部类
        void show(){
            System.out.println("静态内部类");
        }
    }
   public static void fun1(){
       //new Inner().show(); // 编译报错
       Inner inner = new Outer().new Inner();
       inner.show();
       new Inner2().show();
   }

   public void fun2(){
       new Inner().show();
       new Inner2().show();
   }  
}

这段代码中,有两个内部类,一个内部类Inner,一个静态内部类Inner2。首先看如何创建Inner的对象:在非静态方法fun2中,可以直接new一个Inner的对象,而在静态方法fun1中,不能直接new内部类Inner的对象,编译报错。只能通过Inner inner = new Outer().new Inner()这样的方式来创建对象。这是因为静态只能访问静态,fun1是静态方法,而Inner类不是静态的,所以不能访问。当内部类是静态的时候(Inner2),那就不管要使用它的方法是不是静态,都可以直接new一个Inner2对象。

  • 匿名内部类:实质是一个对象,格式:
new 类名或者接口名(){
     重写方法{};
}

举个栗子:

 public interface inter{
     public void show();
 }
 class outer{
     new inter(){
          public void show(){
                system.out .print ("重写方法")
          }
     }.show();
 }

6、异常:
异常分两种:
(1). 编译时受检异常:只要是Exception及其子类都是编译时被检测的异常。
(2). 其中Exception有一个特殊的子类RuntimeException,以及RuntimeException的子类是运行异常,也就说这个异常是编译时不被检查的异常。
两种异常的区别:

  • 编译被检查的异常在函数内被抛出,函数必须要声明,否编译失败。
  • 声明的原因:是需要调用者对该异常进行处理。
  • 运行时异常如果在函数内被抛出,在函数上不需要声明。
  • 不声明的原因:不需要调用者处理,运行时异常发生,已经无法再让程序继续运行,所以,不让调用处理的,直接让程序停止,由调用者对代码进行修正。
  • 一般写代码时如果有编译受检异常,ide会有警告;而运行时异常只有当程序运行了才能发现,比如空指针异常。

四、常见关键字:

1、this: 代表对象。就是所在函数所属对象的引用。哪个对象调用了this所在的函数,this就代表哪个对象,就是那个对象的引用。
2、Super: 代表是子类所属的父类中的内存空间引用。
3、static: 是一个修饰符,用于修饰成员(成员变量和成员函数)。

  • 想要实现对象中的共性数据的对象共享。可以将这个数据进行静态修饰。
  • 被静态修饰的成员,可以直接被类名所调用。也就是说,静态的成员多了一种调用方式。类名.静态方式。
  • 静态随着类的加载而加载。而且优先于对象存在。
  • 静态方法只能访问静态成员,不可以访问非静态成员。
  • 静态方法中不能使用this,super关键字。因为this代表对象,而静态在时,有可能没有对象,所以this无法使用。
    -静态代码块、构造代码块、构造函数同时存在时的执行顺序:静态代码块 --- > 构造代码块 ---> 构造函数;

4、final:

  • 这个关键字是一个修饰符,可以修饰类,方法,变量。
  • 被final修饰的类是一个最终类,不可以被继承。
  • 被final修饰的方法是一个最终方法,不可以被覆盖。
  • 被final修饰的变量是一个常量,只能赋值一次。

五、多线程:

1、线程与进程:

  • 进程:正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。
  • 线程:其实就是进程中一个程序执行控制单元,一个进程可能包含多个线程。

2、线程的随机性原理:
因为cpu的快速切换造成,哪个线程抢到了cpu的执行权,哪个线程就执行。

3、线程中常用方法:
(1). 线程启动:使用start方法。调用start方法做了两件事:启动了线程;让jvm调用了run方法。
(2). 线程休眠: Thread.sleep(1000); // 休眠1秒钟。
(3). 线程礼让:Thread.yield(); // 使线程看上去更和谐,但不能保证你一次我一次。
(4). 给线程起名字:my1.setName("线程名")。
(5). 设置与获取线程优先级:my1.setPriority()和my1.getPriority();优先级默认是5,最大是10,最小是1。
(6). 守护线程:my1.setDaemon(true);my1设置为守护线程,表示该线程就是守护主线程的,主线程一退出my1也就死了。
(7). 线程中止:my1.interrupt();
(8). 线程加入:my1.join(); my2.start();// 表示my1走完了才轮到my2
(9). 获取当前线程名称:Thread.currentThread().getName();

4、线程状态:

  • 被创建:start()
  • 运行:具备执行资格,同时具备执行权;
  • 冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;
  • 临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;
  • 消亡:stop()

5、实现多线程的方式:
三个窗口出售100张电影票,用两种方式实现:
(1). 继承Thread类 ,由子类复写run方法。步骤:

  • 定义类继承Thread类;
  • 目的是复写run方法,将要让线程运行的代码都存储到run方法中;
  • 通过创建Thread类的子类对象,创建线程对象;
  • 调用线程的start方法,开启线程。
public class sellTicket extends Thread{
    //票要定义成静态成员,才能让三个线程共享。
    private static int tickets=100;
    public void run(){
       while(true){
          if(tickets>0){
              Thread.sleep(100)   //模拟延迟
              System.out.print(Thread.currentThread().getName()+"正在出售第"+tickets-- +"张票");
           }
        }
    }
    public static void main(String[] args){
        sellTicket s1=new sellTicket();
        sellTicket s2=new sellTicket();
        sellTicket s2=new sellTicket();
        s1.setName("窗口1");
        s2.setName("窗口2");
        s3.setName("窗口3");
        s1.start();
        s2.start();
        s3.start();
    }
}

(2).实现Runnable接口。步骤:

  • 定义类实现Runnable接口。
  • 覆盖接口中的run方法(用于封装线程要运行的代码)。
  • 通过Thread类创建线程对象;
  • 将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。
  • 调用Thread对象的start方法,开启线程。
public class sellTicket implements Runnable {
   private int tickets=100;
   public void run(){
       while(true){
           if(tickets>0){  
               Thread.sleep(100);  //模拟延迟
               System.out.print(Thread.currentThread().getName()+"正在出售第"+tickets-- +"张票");
            }
        }
   }
   public static void main(String[] args){
            sellTicket st=new sellTicket();
            Thread t1=new Thread(st,"窗口1");
            Thread t2=new Thread(st,"窗口2");
            Thread t3=new Thread(st,"窗口3");
            t1.start();
            t2.start();
            t3.start();
   }
}

6、同步:
运行上面两段卖票的程序会发现,票会出现负数或者是同一张票卖了好几次的情况,这就是线程安全问题。
(1).如何判断一个程序是否有线程安全问题?

  • 是否是多线程环境
  • 是否有共享数据
  • 是否有多条语句操作共享数据

满足以上3个条件,程序就有线程安全问题。

(2). 同步代码块:解决了线程安全问题,格式:

synchronized(锁对象){ // 锁对象可以是任意对象
   // 多条语句操作共享数据的代码
}

改进上面的卖票案例:

public class sellTicket implements Runnable {
   private int tickets=100;
   Object obj = new Object();  // 锁对象
   public void run(){
      while(true){
         synchronized(obj){
            if(tickets>0){
               Thread.sleep(100);  //模拟延迟
               System.out.print(Thread.currentThread().getName()+"正在出售第"+tickets-- +"张票");
             }
          }
      }
   }
}

(3).同步方法:其实就是将同步关键字定义在方法上,让方法具备了同步性。同步方法的锁对象是this;静态同步方法的锁对象是当前类的class文件。

(4).死锁:两个或两个以上的线程因争夺资源产生相互等待的现象。

(5).线程间的通信问题:


先看下面的例子(省略了student类):


设置学生信息的类:

public class SetStudentInfo implements Runnable {
    private Student s;
    public SetStudentInfo(Student s){
            this.s=s;
    }
    public void run(){
        synchronized(s){
            s.setName("林青霞");
            s.setAge(27);
        }
    }
}

获取学生信息的类:

public class GetStudentInfo implements Runnable {
    private Student s;
    public GetStudentInfo (Student s){
            this.s=s;
    }
    public void run(){
       synchronized(s){
          System.out.println(s.getName()+"---"+s.getAge());
       }
    }
}

开启线程:

public class Test{
   public static void  main(String[] args){
      Student s = new Student ();
      SetStudentInfo setStudentInfo = new SetStudentInfo(s);
      GetStudentInfo getStudentInfo = new  GetStudentInfo(s);
      Thread t1 =new Thread(setStudentInfo );
      Thread t2 =new Thread(getStudentInfo );
      t1.start();
      t2.start();
   }
}

多线程就是多条线程在抢cpu的执行权,上面的程序中,如果是t2先抢到执行权,那就有问题了。因为t2是获取学生信息,我都还没设置学生信息就获取,肯定出错。其实这就是生产消费模式。设置学生信息就是生产者,获取学生信息是消费者。正常的执行是这样的:

  • 生产者:没有产品就生产,有产品就等待,生产完通知消费者来消费。
  • 消费者:有产品就消费,没有产品就等待,消费完通知生产者去生产。

要实现这样的执行过程,Java提供了等待唤醒机制。Object类提供了3个方法:

  • wait():等待
  • notify(): 唤醒
  • notifyAll():唤醒所有

因为这三个方法是通过锁对象调用,而锁对象又可以是任意对象,只有Object才可以表示任意对象,所以这三个方法定义在Object中。


使用等待唤醒机制改进上述案例:


student类中添加一个成员变量作为标记:

boolean flag; // true表示已生产,可以消费,false表示无产品可供消费

设置学生信息:

public class SetStudentInfo implements Runnable {
    private Student s;
    public SetStudentInfo (Student s){
            this.s=s;
    }
    public void run(){
        synchronized(s){
             if(s.flag){ //true表示有
                s.wait();//有就等待
             }
             //没有就生产
             s.setName("林青霞");
             s.setAge(27);
             //修改标记并唤醒另一个线程
             s.flag = true ;  //生产了就修改标记为true
             s.notify(); // 同时唤醒另一线程
        }
    }
}

唤醒以后并不是另一个线程就有了执行权,还是要抢,如果还是当前线程抢到,那么没关系,因为标记已经改成true ,当前线程就会等待。
获取学生信息:

public class GetStudentInfo implements Runnable {
    private Student s;
    public GetStudentInfo (Student s){
            this.s=s;
    }
    public void run(){
       synchronized(s){
          if(!s.flag){ // 是false就等待
               s.wait();
          }
          //被SetStudentInfo唤醒后就走到了这里
          System.out.println(s.getName()+"---"+s.getAge());
          s.flag = false(); //经过消费就没有了,修改为false
          s.notify();// 通知生产者生产
       }
    }
}

上述代码就使用了等待唤醒机制。

总结:

为避免篇幅过长,本文先到此为止。本文总结了一些Java基础知识以及一个重点 —— 多线程。剩下的Java基础知识容我日后再整理。

你可能感兴趣的:(java基础知识01)