java入门

有些话、适合烂在心里 提交于 2019-12-01 02:50:21

Java 基础

IDEA 快捷键

快捷键 功能
Alt+Enter 导入包,自动修正代码
Ctrl+Y 删除光标所在行
Ctrl+D 复制光标所在行的内容,插入光标位置下面
Ctrl+Alt+L 格式化代码
Ctrl+/ 单行注释
Ctrl+Shift+/ 选中代码注释,多行注释,再按取消注释
Alt+Ins 自动生成代码,toString,get,set等方法(牢记)
Alt+Shift+上下箭头 移动当前代码行
Ctrl+Alt+v 根据右边值生成左边变量(牢记)

数据类型

数据类型分类

Java的数据类型分为两大类:

  • 基本数据类型:包括 整数浮点数字符布尔
  • 引用数据类型:包括 数组接口

基本数据类型

四类八种基本数据类型:

数据类型 关键字 内存占用 取值范围
字节型 byte 1个字节 -128~127
短整型 short 2个字节 -32768~32767
整型 int(默认) 4个字节 -231次方~2的31次方-1
长整型 long 8个字节 -2的63次方~2的63次方-1
单精度浮点数 float 4个字节 1.4013E-45~3.4028E+38
双精度浮点数 double(默认) 8个字节 4.9E-324~1.7977E+308
字符型 char 2个字节 0-65535
布尔类型 boolean 1个字节 true,false

Java中的默认类型:整数类型是 int 、浮点类型是 double。 long类型:建议数据后加L表示。 float类型:建议数据后加F表示。

public class Variable {     public static void main(String[] args){         //定义字节型变量         byte b = 100;         System.out.println(b);         //定义短整型变量         short s = 1000;         System.out.println(s);         //定义整型变量         int i = 123456;         System.out.println(i);         //定义长整型变量         long l = 12345678900L;         System.out.println(l);         //定义单精度浮点型变量         float f = 5.5F;         System.out.println(f);         //定义双精度浮点型变量         double d = 8.5;         System.out.println(d);         //定义布尔型变量         boolean bool = false;         System.out.println(bool);         //定义字符型变量         char c = 'A';         System.out.println(c);     } }

类型转换

不同范围的类型进行操作时, 会自动进行类型的提升

public static void main(String[] args) {     int i = 1;     double d = 2.5;     //int类型和double类型运算,结果是double类型     //int类型会提升为double类型     double e = d+i;     System.out.println(e); }

范围小的类型向范围大的类型提升, byteshortchar 运算时直接提升为 intbyte、short、char‐‐>int‐‐>long‐‐>float‐‐>double

循环

for, while, do while, 增强for

选择

if, switch case,
switch case不推荐, 如果忘写break, 后果很严重, 而且case后面的类型有限,(python只有if, 可以用dict)

一些常用类

Scanner类

Scanner 类来获取输入信息

    public static void main(String[] args) {         Scanner scanner = new Scanner(System.in);         String s = scanner.nextLine();         System.out.println("s = " + s);     }

Random类

Random生成随机数
public int nextInt(int n): 返回一个伪随机数, 范围在0(包括)和指定值n(不包括)之间的int值。

    public static void main(String[] args) {         Random random = new Random();         int i = random.nextInt();            int i1 = random.nextInt(5);          System.out.println("i = " + i);         System.out.println("i1 = " + i1);     }

ArrayList类

数组集合类, 连续的, 区别于LinkedList

String类

字符串类, 类String中包括用于检查各个字符串的方法,比如用于比较字符串,搜索字符串,提取子字符串以及创建具有翻译为大写或小写的所有字符的字符串的副本。

Arrays类

java.util.Arrays此类包含用来操作数组的各种方法,比如排序搜索等。其所有方法均为静态方法,调用起来非常简单。

Math类

java.lang.Math类包含用于执行基本数学运算的方法,如初等指数对数平方根三角函数。类似这样的工具类,其所有方法均为静态方法,并且不会创建对象,调用起来非常简单。

Object类

java.lang.Object类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。在对象实例化的时候,最终找的父类就是Object

如果一个类没有特别指定父类, 那么默认则继承自Object类。例如:

public class MyClass /*extends Object*/ {     // ... }

toString方法

返回该对象的字符串表示, 默认是对象的类型+@+内存地址值, python中的__str__

public class Person {       private String name;     private int age;      @Override     public String toString() {         return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';     }      // 省略构造器与Getter Setter }

equals方法

==比较的是2个对象的地址,而equals比较的是2个对象的内容
指示其他某个对象是否与此对象“相等”, 默认比较内存地址, python中的==

import java.util.Objects;  public class Person {        private String name;     private int age;          @Override     public boolean equals(Object o) {         // 如果对象地址一样,则认为相同         if (this == o)             return true;         // 如果参数为空,或者类型信息不一样,则认为不同         if (o == null || getClass() != o.getClass())             return false;         // 转换为当前类型         Person person = (Person) o;         // 要求基本类型相等,并且将引用类型交给java.util.Objects类的equals静态方法取用结果         return age == person.age && Objects.equals(name, person.name);     } }

Objects类

在刚才重写equals代码中,使用到了java.util.Objects类,那么这个类是什么呢?

在JDK7添加了一个Objects工具类,它提供了一些方法来操作对象,它由一些静态的实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的),用于计算对象的hashcode、返回对象的字符串表示形式、比较两个对象。

在比较两个对象的时候,Objectequals方法容易抛出空指针异常,而Objects类中的equals方法就优化了这个问题。方法如下:

  • public static boolean equals(Object a, Object b): 判断两个对象是否相等。

源码:

public static boolean equals(Object a, Object b) {       return (a == b) || (a != null && a.equals(b));   }

Date类

java.util.Date类 表示特定的瞬间,精确到毫秒。

import java.util.Date;  public class DateDemo {     public static void main(String[] args) {         System.out.println(new Date());         System.out.println(new Date(0L));         System.out.println(new Date().getTime());     } }   Sun Sep 29 11:31:36 CST 2019 Thu Jan 01 08:00:00 CST 1970 1569727896597

DateFormat类

java.text.DateFormat 是日期/时间格式化子类的抽象类,我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象String对象之间进行来回转换。

  • 格式化:按照指定的格式,从Date对象转换为String对象
  • 解析:按照指定的格式,从String对象转换为Date对象

构造方法

由于DateFormat为抽象类,不能直接使用,所以需要常用的子类java.text.SimpleDateFormat。这个类需要一个模式(格式)来指定格式化或解析的标准。构造方法为:

  • public SimpleDateFormat(String pattern):用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat

参数pattern是一个字符串,代表日期时间的自定义格式。
标识字母(区分大小写) | 含义
------------|---
y | 年
M | 月
d | 日
H | 时
m | 分
s | 秒

常用方法

  • public String format(Date date):将Date对象格式化为字符串。
  • public Date parse(String source):将字符串解析为Date对象。
import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date;  public class DateDemo {     public static void main(String[] args) throws ParseException {         System.out.println(new Date());         System.out.println(new Date(0L));         System.out.println(new Date().getTime());          DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");         System.out.println(format.format(new Date()));         System.out.println(format.parse("2019-09-29 11:46:13"));     } }  Sun Sep 29 11:47:14 CST 2019 Thu Jan 01 08:00:00 CST 1970 1569728834328 2019-09-29 11:47:14 Sun Sep 29 11:46:13 CST 2019

System类

java.lang.System 类中提供了大量的静态方法,可以获取与系统相关的信息或系统级操作,在System类的API文档中,常用的方法有

  • public static long currentTimeMillis():返回以毫秒为单位的当前时间。
  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):将数组中指定的数据拷贝到另一个数组中。

currentTimeMillis方法

currentTimeMillis方法就是 获取当前系统时间与1970年01月01日00:00点之间的毫秒差值

System.out.println(System.currentTimeMillis());

arraycopy方法

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):将数组中指定的数据拷贝到另一个数组中。

数组的拷贝动作是系统级的,性能很高。System.arraycopy方法具有5个参数,含义分别为:

参数序号 参数名称 参数类型 参数含义
1 src Object 源数组
2 srcPos int 源数组索引起始位置
3 dest Object 目标数组
4 destPos int 目标数组索引起始位置
5 length int 复制元素个数
import java.util.Arrays;  public class Demo11SystemArrayCopy {     public static void main(String[] args) {         int[] src = new int[]{1,2,3,4,5};         int[] dest = new int[]{6,7,8,9,10};         System.arraycopy( src, 0, dest, 0, 3);         /*代码运行后:两个数组中的元素发生了变化          src数组元素[1,2,3,4,5]          dest数组元素[1,2,3,9,10]         */     } }

StringBuilder类

由于String类的对象内容不可改变,所以每当进行字符串拼接时,总是会在内存中创建一个新的对象, 当进行多次拼接时, 会造成时间和空间上的浪费

在API中对String类有这样的描述:字符串是常量,它们的值在创建后不能被更改

StringBuilder被称为可变字符序列,它是一个类似于String的字符串缓冲区,是一个容器, 可以装很多字符串, 并且能够对其中的字符串进行各种操作。默认开辟16字符空间,超过自动扩充

构造方法

常用的两个

  • public StringBuilder():构造一个空的StringBuilder容器。
  • public StringBuilder(String str):构造一个StringBuilder容器,并将字符串添加进去。
    public StringBuilder() {         super(16);     }          ...      public StringBuilder(String str) {         super(str.length() + 16);         append(str);     }

常用方法

public StringBuilder append(...):添加任意类型数据的字符串形式,并返回当前对象自身。
public String toString():将当前StringBuilder对象转换为String对象。

包装类

Java提供了两个类型系统,基本类型引用类型,使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类,如下:

基本类型 对应的包装类(位于java.lang包中)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

面向对象

访问修饰符(私有, 保护, 公开, 默认)

public protected default(空的) private
同一类中
同一包中(子类与无关类)
不同包的子类
不同包中的无关类

public具有最大权限。private则是最小权限。

编写代码时,如果没有特殊的考虑,建议这样使用权限:

  • 成员变量使用 private ,隐藏细节。
  • 构造方法使用 public ,方便创建对象。
  • 成员方法使用 public ,方便调用方法。

不加权限修饰符,其访问能力与default修饰符相同

static关键字

类变量

static修饰成员变量时,该变量称为类变量。该类的每个对象都共享同一个类变量的值。任何对象都可以更改该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作。

类变量:使用 static关键字修饰的成员变量。 static 数据类型 变量名;

静态方法

static修饰成员方法时,该方法称为类方法 。静态方法在声明中有static,建议使用类名来调用,而不需要创建类的对象。调用方式非常简单。

类方法:使用 static关键字修饰的成员方法,习惯称为静态方法。

    修饰符 static 返回值类型 方法名 (参数列表){     // 执行语句      }

静态方法调用的注意事项:
静态方法可以直接访问类变量和静态方法。
静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问类变量或静态方法。
静态方法中,不能使用this关键字。
静态方法只能访问静态成员。

调用格式

static修饰的成员可以并且建议通过类名直接访问。虽然也可以通过对象名访问静态成员,原因即多个对象均属于一个类,共享使用同一个静态成员,但是不建议,会出现警告信息。

// 访问类变量 类名.类变量名; // 调用静态方法 类名.静态方法名(参数);

静态原理图解

static 修饰的内容:

  • 是随着类的加载而加载的,且只加载一次。
  • 存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。
  • 它优先于对象存在,所以,可以被所有对象共享。

静态代码块

静态代码块:定义在成员位置,使用static修饰的代码块{ }。

  • 位置:类中方法外。
  • 执行:随着类的加载而执行且执行一次,优先于main方法和构造方法的执行。
   public class ClassName{        static {        // 执行语句        }    }

作用:给类变量进行初始化赋值。用法演示,代码如下:

public class Game {     public static int number;     public static ArrayList<String> list;          static {         // 给类变量赋值         number = 2;         list = new ArrayList<String>();             // 添加元素到集合中             list.add("张三");             list.add("李四");     } }

小贴士:
static 关键字,可以修饰变量、方法和代码块。在使用的过程中,其主要目的还是想在不创建对象的情况
下,去调用方法。

super和this

父类空间优先于子类对象产生

在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构造方法调用时,一定先调用父类的构造方法。

super和this的含义

  • super :代表父类的存储空间标识(可以理解为父亲的引用)。
  • this :代表当前对象的引用(谁调用就代表谁)。

子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。 super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

继承

封装, 继承, 多态是面向对象的三大特征, Java仅支持单继承, 但可实现多接口

使用继承后, 子类中如果想使用父类中的同名变量时, 需要使用super关键字; 子类使用自身的变量则不需要super关键字

class Fu{     int age=35;     String name="fu";  }  class Zi extends Fu{     int age=10;     String name = "zi";      void show(){         System.out.println("Zi.age = " + age);         System.out.println("Zi.name = " + name);                  System.out.println("Fu.age = " + super.age);         System.out.println("Fu.name = " + super.name);     } }      public class Extend {      public static void main(String[] args) {         Zi zi = new Zi();         zi.show();     } }   Zi.age = 10 Zi.name = zi Fu.age = 35 Fu.name = fu

仅可访问非私有父类成员变量, 若想访问父类的私有成员变量, 则需要父类提供getset方法(封装原则)

子类继承父类后, 若要重写父类的方法, 则需要保证权限大于等于父类权限 返回值类型、函数名和参数列表都要一模一样。

多态

同一行为, 具有多个不同表现形式.

  1. 继承或者实现【二选一】
  2. 方法的重写【意义体现:不重写,无意义】
  3. 父类引用指向子类对象【格式体现】
父类类型 变量名 = new 子类对象; 变量名.方法名();  // 父类类型:指子类对象继承的父类类型,或者实现的父接口类型。 Fu f = new Zi(); f.method();

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写后方法。

定义父类:

public abstract class Animal {       public abstract void eat();   }  

定义子类:

class Cat extends Animal {       public void eat() {           System.out.println("吃鱼");       }   }     class Dog extends Animal {       public void eat() {           System.out.println("吃骨头");       }   }

定义测试类:

public class Test {     public static void main(String[] args) {         // 多态形式,创建对象         Animal a1 = new Cat();           // 调用的是 Cat 的 eat         a1.eat();                     // 多态形式,创建对象         Animal a2 = new Dog();          // 调用的是 Dog 的 eat         a2.eat();                    }   }

多态的好处

实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。

定义父类:

public abstract class Animal {       public abstract void eat();   }

定义子类:

class Cat extends Animal {       public void eat() {           System.out.println("吃鱼");       }   }     class Dog extends Animal {       public void eat() {           System.out.println("吃骨头");       }   }

定义测试类:

public class Test {     public static void main(String[] args) {         // 多态形式,创建对象         Cat c = new Cat();           Dog d = new Dog();            // 调用showCatEat          showCatEat(c);         // 调用showDogEat          showDogEat(d);            /*         以上两个方法, 均可以被showAnimalEat(Animal a)方法所替代         而执行效果一致         */         showAnimalEat(c);         showAnimalEat(d);      }       public static void showCatEat (Cat c){         c.eat();      }       public static void showDogEat (Dog d){         d.eat();     }       public static void showAnimalEat (Animal a){         a.eat();     } }

引用类型转换

多态的转型分为向上转型与向下转型两种:

向上转型

向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。

当父类引用指向一个子类对象时,便是向上转型。

父类类型  变量名 = new 子类类型(); 如:Animal a = new Cat();

向下转型

向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。

一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。

子类类型 变量名 = (子类类型) 父类变量名; 如:Cat c =(Cat) a;  

为什么要转型

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。

定义类:

abstract class Animal {       abstract void eat();   }     class Cat extends Animal {       public void eat() {           System.out.println("吃鱼");       }       public void catchMouse() {           System.out.println("抓老鼠");       }   }     class Dog extends Animal {       public void eat() {           System.out.println("吃骨头");       }       public void watchHouse() {           System.out.println("看家");       }   }

定义测试类:

public class Test {     public static void main(String[] args) {         // 向上转型           Animal a = new Cat();           a.eat();                // 调用的是 Cat 的 eat           // 向下转型           Cat c = (Cat)a;                c.catchMouse();         // 调用的是 Cat 的 catchMouse     }   }

转型的异常

public class Test {     public static void main(String[] args) {         // 向上转型           Animal a = new Cat();           a.eat();               // 调用的是 Cat 的 eat           // 向下转型           Dog d = (Dog)a;                d.watchHouse();        // 调用的是 Dog 的 watchHouse 【运行报错】     }   }

这段代码可以通过编译,但是运行时,却报出了 ClassCastException ,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。

为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验

变量名 instanceof 数据类型  如果变量属于该数据类型,返回true。 如果变量不属于该数据类型,返回false。
public class Test {     public static void main(String[] args) {         // 向上转型           Animal a = new Cat();           a.eat();               // 调用的是 Cat 的 eat           // 向下转型           if (a instanceof Cat){             Cat c = (Cat)a;                    c.catchMouse();        // 调用的是 Cat 的 catchMouse         } else if (a instanceof Dog){             Dog d = (Dog)a;                    d.watchHouse();       // 调用的是 Dog 的 watchHouse         }     }   }

接口

接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法(JDK7及以前),默认方法和静态方法(JDK 8),私有方法(JDK 9)。

接口的定义,它与定义类方式相似,但是使用interface关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。

引用数据类型:数组接口

接口的使用,它不能创建对象,但是可以被实现(implements类似于被继承)。一个实现接口的类(可以看做是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。

格式

public interface 接口名称 {     // 抽象方法     // 默认方法     // 静态方法     // 私有方法 }

抽象方法:使用 abstract关键字修饰,可以省略,没有方法体。该方法供子类实现使用。

public interface InterFaceName {     public abstract void method(); }

默认方法:使用 default 修饰,不可省略,供子类调用或者子类重写。 静态方法:使用 static 修饰,供接口直接调用。

public interface InterFaceName {     public default void method() {         // 执行语句     }     public static void method2() {         // 执行语句         } }

私有方法:使用 private 修饰,供接口中的默认方法或者静态方法调用。

public interface InterFaceName {     private void method() {         // 执行语句     } }

基本实现

非抽象子类实现接口:

  1. 必须重写接口中所有抽象方法.
  2. 继承了接口的默认方法, 即可以直接调用, 也可以重写.

实现格式:

class 类名 implements 接口名 {     // 重写接口中抽象方法【必须】     // 重写接口中默认方法【可选】 } 

抽象方法的使用: 必须全部实现
默认方法的使用: 可以继承,可以重写,二选一,但是只能通过实现类的对象来调用。
静态方法的使用: 静态与.class文件相关,只能使用接口名调用,不可以通过实现类的类名或者实现类的对象调用
私有方法的使用 : 私有方法:只有默认方法可以调用。私有静态方法:默认方法和静态方法可以调用。

静态方法的使用

public interface LiveAble {     public static void run(){         System.out.println("跑起来~~~");     } }  public class Animal implements LiveAble {     // 无法重写静态方法 }  public class InterfaceDemo {     public static void main(String[] args) {         // Animal.run(); // 【错误】无法继承方法,也无法调用         LiveAble.run(); //      } }  跑起来~~~

接口的多实现

一个类可以实现多个接口

class 类名 [extends 父类名] implements 接口名1,接口名2,接口名3... {     // 重写接口中抽象方法【必须】     // 重写接口中默认方法【不重名时可选】 } 

抽象方法

接口中,有多个抽象方法时,实现类必须重写所有抽象方法如果抽象方法有重名的,只需要重写一次。

interface A {     public abstract void showA();     public abstract void show(); }   interface B {     public abstract void showB();     public abstract void show(); }
public class C implements A,B{     @Override     public void showA() {         System.out.println("showA");     }       @Override     public void showB() {         System.out.println("showB");     }       @Override     public void show() {         System.out.println("show");     } }

默认方法

接口中,有多个默认方法时,实现类都可继承使用。如果默认方法有重名的,必须重写一次。

interface A {     public default void methodA(){}     public default void method(){} }   interface B {     public default void methodB(){}     public default void method(){} }
public class C implements A,B{     @Override     public void method() {         System.out.println("method");     } }

静态方法

接口中,存在同名的静态方法并不会冲突,原因是只能通过各自接口名访问静态方法。

优先级的问题

当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的默认方法重名,子类就近选择执行父类的成员方法。

定义接口:

interface A {     public default void methodA(){         System.out.println("AAAAAAAAAAAA");     } }

定义父类:

class D {     public void methodA(){         System.out.println("DDDDDDDDDDDD");     } }

定义子类:

class C extends D implements A {     // 未重写methodA方法 }

定义测试类:

public class Test {     public static void main(String[] args) {         C c = new C();         c.methodA();      } }   DDDDDDDDDDDD

接口的多继承

一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承使用 extends 关键字,子接口继承父接口的方法。如果父接口中的默认方法有重名的,那么子接口需要重写一次。

定义父接口:

interface A {     public default void method(){         System.out.println("AAAAAAAAAAAAAAAAAAA");     } }   interface B {     public default void method(){         System.out.println("BBBBBBBBBBBBBBBBBBB");     } }

定义子接口:

interface D extends A,B{     @Override     public default void method() {         System.out.println("DDDDDDDDDDDDDD");     } }

子接口重写默认方法时,default关键字可以保留。 子类重写默认方法时,default关键字不可以保留。

小结

  • 接口中,无法定义成员变量,但是可以定义常量,其值不可以改变,默认使用public static final修饰。
  • 接口中,没有构造方法,不能创建对象。
  • 接口中,没有静态代码块

抽象类

父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类。

修饰符 abstract 返回值类型 方法名 (参数列表);  public abstract void run();

如果一个类中含有抽象方法, 那么该类必须是抽象类

public abstract class Animal {     public abstract void run(); }

使用

继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。

注意事项

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

    理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

  2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

    理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

  3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

    理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

  4. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。

    理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

final

用于修饰不可改变内容。
final: 不可改变。可以用于修饰类、方法和变量。

  • :被修饰的类,不能被继承。
  • 方法:被修饰的方法,不能被重写。
  • 变量:被修饰的变量,不能被重新赋值。

修饰类

final class 类名 {    } 

public final class Stringpublic final class Mathpublic final class Scanner等,都是被final修饰的,目的就是供我们使用,而不让我们随意改变其内容。

修饰方法

修饰符 final 返回值类型 方法名(参数列表){     //方法体 }

重写被 final 修饰的方法,编译时就会报错。

修饰变量

局部变量 - 基本类型

基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改。

public class FinalDemo1 {     public static void main(String[] args) {         // 声明变量,使用final修饰         final int a;         // 第一次赋值          a = 10;         // 第二次赋值         a = 20; // 报错,不可重新赋值             // 声明变量,直接赋值,使用final修饰         final int b = 10;         // 第二次赋值         b = 20; // 报错,不可重新赋值     } }

局部变量 - 引用类型

引用类型的局部变量,被final修饰后,只能指向一个对象,地址不能再更改。但是不影响对象内部的成员变量值的修改

public class FinalDemo2 {     public static void main(String[] args) {         // 创建 User 对象         final   User u = new User();         // 创建 另一个 User对象         u = new User(); // 报错,指向了新的对象,地址值改变。           // 调用setName方法         u.setName("张三"); // 可以修改     } }

成员变量

成员变量涉及到初始化的问题,初始化方式有两种,只能二选一:

  • 显示初始化
public class User {     final String USERNAME = "张三";     private int age; }
  • 构造方法初始化
public class User {     final String USERNAME ;     private int age;     public User(String username, int age) {         this.USERNAME = username;         this.age = age;     } }

被final修饰的常量名称,一般都有书写规范,所有字母都大写。

内部类

class 外部类 {     class 内部类{       } }

访问特点

  • 内部类可以直接访问外部类的成员,包括私有成员。
  • 外部类要访问内部类的成员,必须要建立内部类的对象。
外部类名.内部类名 对象名 = new 外部类型().new 内部类型();

定义类:

public class Person {     private  boolean live = true;     class Heart {         public void jump() {             // 直接访问外部类成员             if (live) {                 System.out.println("心脏在跳动");             } else {                 System.out.println("心脏不跳了");             }         }     }       public boolean isLive() {         return live;     }       public void setLive(boolean live) {         this.live = live;     }   }

定义测试类:

public class InnerDemo {     public static void main(String[] args) {         // 创建外部类对象          Person p  = new Person();         // 创建内部类对象         Person.Heart heart = p.new Heart();           // 调用内部类方法         heart.jump();         // 调用外部类方法         p.setLive(false);         // 调用内部类方法         heart.jump();     } }   心脏在跳动 心脏不跳了

内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号 。
比如,Person$Heart.class

匿名内部类

匿名内部类 :是内部类的简化写法。它的本质是一个带具体实现的 父类或者父接口的 匿名的 子类对象

匿名内部类必须继承一个父类或者实现一个父接口

new 父类名或者接口名(){     // 方法重写     @Override      public void method() {         // 执行语句     } };

定义接口:

public abstract class FlyAble{     public abstract void fly(); }

创建匿名内部类,并调用:

public class InnerDemo {     public static void main(String[] args) {         /*         1.等号右边:是匿名内部类,定义并创建该接口的子类对象         2.等号左边:是多态赋值,接口类型引用指向子类对象         */         FlyAble  f = new FlyAble(){             public void fly() {                 System.out.println("我飞了~~~");             }         };           //调用 fly方法,执行重写后的方法         f.fly();     } }

通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递

public class InnerDemo2 {     public static void main(String[] args) {         /*         1.等号右边:定义并创建该接口的子类对象         2.等号左边:是多态,接口类型引用指向子类对象        */         FlyAble  f = new FlyAble(){             public void fly() {                 System.out.println("我飞了~~~");             }         };         // 将f传递给showFly方法中         showFly(f);     }     public static void showFly(FlyAble f) {         f.fly();     } }

以上两步,也可以简化为一步

public class InnerDemo3 {     public static void main(String[] args) {                   /*         创建匿名内部类,直接传递给showFly(FlyAble f)         */         showFly( new FlyAble(){             public void fly() {                 System.out.println("我飞了~~~");             }         });     }       public static void showFly(FlyAble f) {         f.fly();     } }

引用类型用法

基本类型可以作为成员变量、作为方法的参数、作为方法的返回值,那么当然引用类型也是可以的。

class作为成员变量

interface作为成员变量

interface作为方法参数和返回值类型

接口作为参数时,传递它的子类对象。
接口作为返回值类型时,返回它的子类对象。

集合

集合是java中提供的一种容器,可以用来存储多个数据. 集合的长度是可变的, 可以存储不同的数据类型

单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是java.util.Listjava.util.Set。其中,List的特点是元素有序元素可重复Set的特点是元素无序,而且不可重复List接口的主要实现类有java.util.ArrayListjava.util.LinkedListSet接口的主要实现类有java.util.HashSetjava.util.TreeSet

Collection 常用功能

Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(ListSet)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:

  • public boolean add(E e):把给定的对象添加到当前集合中 。
  • public void clear(): 清空集合中所有的元素。
  • public boolean remove(E e): 把给定的对象在当前集合中删除。
  • public boolean contains(E e): 判断当前集合中是否包含给定的对象。
  • public boolean isEmpty(): 判断当前集合是否为空。
  • public int size(): 返回集合中元素的个数。
  • public Object[] toArray(): 把集合中的元素,存储到数组中。

Iterator迭代器

JDK提供了一个接口java.util.IteratorIterator接口也是Java集合中的一员,但它与CollectionMap接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历Collection中的元素,因此Iterator对象也被称为迭代器

想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作.

  • public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的。
  • 迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。

Iterator接口的常用方法:

  • public E next():返回迭代的下一个元素。
  • public boolean hasNext():如果仍有元素可以迭代,则返回true
import java.util.ArrayList; import java.util.Collection; import java.util.Iterator;  public class IteratorDemo {     public static void main(String[] args) {         // 使用多态方式 创建对象         Collection<String> coll = new ArrayList<String>();          // 添加元素到集合         coll.add("123");         coll.add("456");         coll.add("789");         //遍历         //使用迭代器 遍历   每个集合对象都有自己的迭代器         Iterator<String> it = coll.iterator();         //  泛型指的是 迭代出 元素的数据类型         while(it.hasNext()){ //判断是否有迭代元素             String s = it.next();//获取迭代出的元素             System.out.println(s);         }     } }  123 456 789

在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生java.util.NoSuchElementException没有集合元素的错误。

java/util/ArrayList.java

/**      * An optimized version of AbstractList.Itr      */     private class Itr implements Iterator<E> {         int cursor;       // index of next element to return         int lastRet = -1; // index of last element returned; -1 if no such         int expectedModCount = modCount;          Itr() {}          public boolean hasNext() {             return cursor != size;         }          @SuppressWarnings("unchecked")         public E next() {             checkForComodification();             int i = cursor;             if (i >= size)                 throw new NoSuchElementException();             Object[] elementData = ArrayList.this.elementData;             if (i >= elementData.length)                 throw new ConcurrentModificationException();             cursor = i + 1;             return (E) elementData[lastRet = i];         }          public void remove() {             if (lastRet < 0)                 throw new IllegalStateException();             checkForComodification();              try {                 ArrayList.this.remove(lastRet);                 cursor = lastRet;                 lastRet = -1;                 expectedModCount = modCount;             } catch (IndexOutOfBoundsException ex) {                 throw new ConcurrentModificationException();             }         }          @Override         @SuppressWarnings("unchecked")         public void forEachRemaining(Consumer<? super E> consumer) {             Objects.requireNonNull(consumer);             final int size = ArrayList.this.size;             int i = cursor;             if (i >= size) {                 return;             }             final Object[] elementData = ArrayList.this.elementData;             if (i >= elementData.length) {                 throw new ConcurrentModificationException();             }             while (i != size && modCount == expectedModCount) {                 consumer.accept((E) elementData[i++]);             }             // update once at end of iteration to reduce heap write traffic             cursor = i;             lastRet = i - 1;             checkForComodification();         }          final void checkForComodification() {             if (modCount != expectedModCount)                 throw new ConcurrentModificationException();         }     }

增强for

增强for是一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。

for(元素的数据类型  变量 : Collection集合or数组){      //写操作代码 }

泛型

泛型通配符

当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。

public interface Collection<E> extends Iterable<E>{          ...      boolean addAll(Collection<? extends E> c);     boolean removeAll(Collection<?> c);     boolean containsAll(Collection<?> c);     ...  }

通配符高级使用----受限泛型

设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限下限

  • 泛型的上限
    格式: 类型名称 <? extends 类 > 对象名称
    意义: 只能接收该类型及其子类

  • 泛型的下限
    格式: 类型名称 <? super 类 > 对象名称
    意义: 只能接收该类型及其父类型

比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类

    public static void main(String[] args) {         Collection<Integer> list1 = new ArrayList<Integer>();         Collection<String> list2 = new ArrayList<String>();         Collection<Number> list3 = new ArrayList<Number>();         Collection<Object> list4 = new ArrayList<Object>();                  getElement(list1);         getElement(list2);//报错         getElement(list3);         getElement(list4);//报错                getElement2(list1);//报错         getElement2(list2);//报错         getElement2(list3);         getElement2(list4);            }     // 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类     public static void getElement1(Collection<? extends Number> coll){}     // 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类     public static void getElement2(Collection<? super Number> coll){}

List集合

List接口中常用方法

List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法:

  • public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
  • public E get(int index) :返回集合中指定位置的元素。
  • public E remove(int index) : 移除列表中指定位置的元素, 返回的是被移除的元素。
  • public E set(int index, E element) :用指定元素替换集合中指定位置的元素,返回值的更新前的元素。

ArrayList

java.util.ArrayList集合数据存储的结构是数组结构。元素增删慢查找快

LinkedList

java.util.LinkedList 集合数据存储的结构是链表结构, 是一个双向链表。方便元素添加删除的集合。

对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。

  • public void addFirst(E e) : 将指定元素插入此列表的开头。
  • public void addLast(E e) : 将指定元素添加到此列表的结尾。
  • public E getFirst() : 返回此列表的第一个元素。
  • public E getLast() : 返回此列表的最后一个元素。
  • public E removeFirst() : 移除并返回此列表的第一个元素。
  • public E removeLast() : 移除并返回此列表的最后一个元素。
  • public E pop() : 从此列表所表示的堆栈处弹出一个元素。
  • public void push(E e) : 将元素推入此列表所表示的堆栈。
  • public boolean isEmpty() :如果列表不包含元素,则返回true。

Set接口

java.util.Set 接口和 java.util.List 接口一样,同样继承自 Collection 接口,它与 Collection 接口中的方法基本一致,并没有对 Collection 接口进行功能上的扩充,只是比 Collection 接口更加严格了。与 List 接口不同的是, Set 接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。

HashSet

java.util.HashSetSet 接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。 java.util.HashSet 底层的实现其实是一个java.util.HashMap 支持

HashSet 是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取查找性能。保证元素唯一性的方式依赖于: hashCodeequals 方法。

HashSet存储自定义类型元素

HashSet中存放自定义类型元素时,需要重写对象中的hashCodeequals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一

IDEA自动生成的方法

import java.util.Objects;  public class Student {     private String name;     private int age;      public Student() {     }      public Student(String name, int age) {         this.name = name;         this.age = age;     }      public String getName() {         return name;     }      public void setName(String name) {         this.name = name;     }      public int getAge() {         return age;     }      public void setAge(int age) {         this.age = age;     }      @Override     public boolean equals(Object o) {         if (this == o) return true;         if (o == null || getClass() != o.getClass()) return false;         Student student = (Student) o;         return age == student.age &&                 name.equals(student.name);     }      @Override     public int hashCode() {         return Objects.hash(name, age);     }      @Override     public String toString() {         return "Student{" +                 "name='" + name + '\'' +                 ", age=" + age +                 '}';     } }                                       
import java.util.HashSet;  public class HashSetDemo {     public static void main(String[] args) {         HashSet<Student> hashSet = new HashSet<>();          hashSet.add(new Student("张三", 20));         hashSet.add(new Student("李四", 18));         hashSet.add(new Student("王五", 22));         hashSet.add(new Student("赵六", 25));          for (Student s : hashSet) {             System.out.println(s);         }     } }  Student{name='张三', age=20} Student{name='王五', age=22} Student{name='李四', age=18} Student{name='赵六', age=25}

LinkedHashSet

java.util.LinkedHashSet, 它是链表和哈希表组合的一个有序数据存储结构。

import java.util.HashSet; import java.util.LinkedHashSet;  public class LinkedHashSetDemo {     public static void main(String[] args) {         LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();         HashSet<String> hashSet = new HashSet<>();          linkedHashSet.add("张三");         linkedHashSet.add("李四");         linkedHashSet.add("王五");         linkedHashSet.add("赵六");          hashSet.add("张三");         hashSet.add("李四");         hashSet.add("王五");         hashSet.add("赵六");          System.out.println("hashSet = " + hashSet.toString());         System.out.println("linkedHashSet = " + linkedHashSet.toString());     } }  hashSet = [李四, 张三, 王五, 赵六] linkedHashSet = [张三, 李四, 王五, 赵六]

Collections

java.utils.Collections 是集合工具类,用来对集合进行操作。

  • public static <T> boolean addAll(Collection<T> c, T... elements) :往集合中添加一些元素。
  • public static void shuffle(List<?> list) :打乱集合顺序。
  • public static <T> void sort(List<T> list) :将集合中元素按照默认规则排序。
  • public static <T> void sort(List<T> list,Comparator<? super T> ) :将集合中元素按照自定义的规则排序。
import java.util.ArrayList; import java.util.Collections;  public class CollectionsDemo {     public static void main(String[] args) {         ArrayList<String> arrayList = new ArrayList<>();          // arrayList.add("123");         // arrayList.add("789");         // arrayList.add("654");         // arrayList.add("369");          Collections.addAll(arrayList, "123", "789", "654", "369");          System.out.println("arrayList: " + arrayList.toString());          Collections.sort(arrayList);          System.out.println("arrayList: " + arrayList.toString());     } }

Comparator比较器

public static <T> void sort(List<T> list) : 将集合中元素按照默认规则排序。

public int compare(String o1, String o2) : 比较其两个参数的顺序。

两个对象比较的结果有三种:大于,等于,小于。
如果要按照升序排序, 则o1 小于o2,返回(负数),相等返回0,01大于02返回(正数) 如果要按照降序排序 则o1 小于o2,返回(正数),相等返回0,01大于02返回(负数)

Comparable和Comparator两个接口的区别

  • Comparable强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。需重写compareTo方法
  • Comparator强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sortArrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。需重写compare方法

Comparable

import java.util.Objects;  public class Student implements Comparable<Student>{     private String name;     private int age;      Student(String name, int age) {         this.name = name;         this.age = age;     }      @Override     public boolean equals(Object o) {         if (this == o) return true;         if (o == null || getClass() != o.getClass()) return false;         Student student = (Student) o;         return age == student.age &&                 name.equals(student.name);     }      @Override     public int hashCode() {         return Objects.hash(name, age);     }      @Override     public String toString() {         return "Student{" +                 "name='" + name + '\'' +                 ", age=" + age +                 '}';     }      @Override     public int compareTo(Student o) {         int res = name.compareTo(o.name);         if (0==res) return age - o.age;         else return res;     } }
import java.util.ArrayList; import java.util.Collections;  public class ComparableDemo {     public static void main(String[] args) {         ArrayList<Student> arrayList = new ArrayList<Student>();          arrayList.add(new Student("123", 22));         arrayList.add(new Student("567", 17));         arrayList.add(new Student("567", 15));         arrayList.add(new Student("346", 20));          System.out.println(arrayList.toString());          Collections.sort(arrayList);          System.out.println(arrayList.toString());     } }  [Student{name='123', age=22}, Student{name='567', age=17}, Student{name='567', age=15}, Student{name='346', age=20}] [Student{name='123', age=22}, Student{name='346', age=20}, Student{name='567', age=15}, Student{name='567', age=17}]

Comparator

import java.util.Objects;  public class Student /*implements Comparable<Student>Student*/{     private String name;     private int age;      public String getName() {         return name;     }      public void setName(String name) {         this.name = name;     }      public int getAge() {         return age;     }      public void setAge(int age) {         this.age = age;     }      Student(String name, int age) {         this.name = name;         this.age = age;     }      @Override     public boolean equals(Object o) {         if (this == o) return true;         if (o == null || getClass() != o.getClass()) return false;         Student student = (Student) o;         return age == student.age &&                 name.equals(student.name);     }      @Override     public int hashCode() {         return Objects.hash(name, age);     }      @Override     public String toString() {         return "Student{" +                 "name='" + name + '\'' +                 ", age=" + age +                 '}';     }  //    @Override //    public int compareTo(Student o) { //        int res = name.compareTo(o.name); //        if (0==res) return age - o.age; //        else return res; //    } }
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator;  public class ComparatorDemo {     public static void main(String[] args) {          ArrayList<Student> arrayList = new ArrayList<Student>();          arrayList.add(new Student("123", 22));         arrayList.add(new Student("567", 17));         arrayList.add(new Student("567", 15));         arrayList.add(new Student("346", 20));          System.out.println(arrayList.toString());          Collections.sort(arrayList, new Comparator<Student>() {             @Override             public int compare(Student o1, Student o2) {                 int res = o1.getAge() - o2.getAge();                 if (res == 0)return o1.getName().compareTo(o2.getName());                 else return res;             }         });          System.out.println(arrayList.toString());     } }  [Student{name='123', age=22}, Student{name='567', age=17}, Student{name='567', age=15}, Student{name='346', age=20}] [Student{name='567', age=15}, Student{name='567', age=17}, Student{name='346', age=20}, Student{name='123', age=22}]

map

  • HashMap:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
  • LinkedHashMapHashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证键的唯一不重复,需要重写键的hashCode()方法、equals()方法。

Map接口中的集合都有两个泛型变量,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量的数据类型可以相同,也可以不同。

import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map;  public class MapDemo {      private void addElement(Map<String, String> map){         map.put("张三", "aaa");         map.put("李四", "bbb");         map.put("王五", "ccc");         map.put("赵六", "ddd");     }      private void showHashMap(){         HashMap<String, String> stringHashMap = new HashMap<>();         addElement(stringHashMap);          System.out.println(stringHashMap.toString());     }      private void showLinkedHashMap(){         LinkedHashMap<String, String> stringLinkedHashMap = new LinkedHashMap<>();         addElement(stringLinkedHashMap);          System.out.println(stringLinkedHashMap.toString());     }       public static void main(String[] args) {         new MapDemo().showHashMap();         new MapDemo().showLinkedHashMap();     } }  {李四=bbb, 张三=aaa, 王五=ccc, 赵六=ddd} {张三=aaa, 李四=bbb, 王五=ccc, 赵六=ddd}

常用方法

  • public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。
  • public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
  • public V get(Object key): 根据指定的键, 在Map集合中获取对应的值, 不存在则返回null
  • public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。
  • public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。

使用put方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中;若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值。

Entry键值对对象

Map中的一个键值对对象就是一个Entry

  • public K getKey() :获取Entry对象中的键。
  • public V getValue() :获取Entry对象中的值。
//        Set<Map.Entry<String, String>> entries = stringHashMap.entrySet(); // //        for (Map.Entry<String, String> entry : entries){ //            System.out.print(entry.getKey()); //            System.out.println(entry.getValue()); //        }          for (Map.Entry<String, String> entry : stringHashMap.entrySet()) {             System.out.println("entry = " + entry);         }

Map集合不能直接使用迭代器或者foreach进行遍历。但是转成Set之后就可以使用了。

HashMap存储自定义类型键值

  • 给HashMap中存放自定义对象时,如果自定义对象作为key存在,这时要保证对象唯一,必须复写对象的hashCodeequals方法
  • 如果要保证map中存放的key和取出的顺序一致,可以使用 java.util.LinkedHashMap 集合来存放

异常

程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。

异常机制其实是帮助我们找到程序中的问题,异常的根类是 java.lang.Throwable ,其下有两个子类:
java.lang.Errorjava.lang.Exception, 平常所说的异常指java.lang.Exception

Throwable体系:

  • Error: 严重错误Error,无法通过处理的错误,只能事先避免,好比绝症。
  • Exception: 表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。好比感冒、阑尾炎。

Throwable中的常用方法:

  • public void printStackTrace() :打印异常的详细信息。包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace���
  • public String getMessage() :获取发生异常的原因。提示给用户的时候,就提示错误原因。
  • public String toString() :获取异常的类型和异常描述信息(不用)。

异常的处理

throw

throw new NullPointerException("要访问的arr数组不存在");   throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");

Objects非空判断

java/util/Objects.java:202      public static <T> T requireNonNull(T obj) {         if (obj == null)             throw new NullPointerException();         return obj;     }

声明异常throws

修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{   }
public class ThrowsDemo {     public static void main(String[] args) throws FileNotFoundException {         read("a.txt");     }       // 如果定义功能时有问题发生需要报告给调用者。可以通过在方法上使用throws关键字进行声明     public static void read(String path) throws FileNotFoundException {         if (!path.equals("a.txt")) {//如果不是 a.txt这个文件              // 我假设  如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常  throw             throw new FileNotFoundException("文件不存在");         }     } }

捕获异常try…catch

try{      编写可能会出现异常的代码 }catch(异常类型  e){      处理异常的代码      //记录日志/打印异常信息/继续抛出异常 }
public class TryCatchDemo {     public static void main(String[] args) {         try {// 当产生异常时,必须有处理方式。要么捕获,要么声明。             read("b.txt");         } catch (FileNotFoundException e) {// 括号中需要定义什么呢?             //try中抛出的是什么异常,在括号中就定义什么异常类型             System.out.println(e);         }         System.out.println("over");     }     /*      *      * 我们 当前的这个方法中 有异常  有编译期异常      */     public static void read(String path) throws FileNotFoundException {         if (!path.equals("a.txt")) {//如果不是 a.txt这个文件              // 我假设  如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常  throw             throw new FileNotFoundException("文件不存在");         }     } }

finally代码块

finally:有一些特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些语句执行不到。而finally就是解决这个问题的,在finally代码块中存放的代码都是一定会被执行的。

try...catch....finally:自身需要处理异常,最终还得关闭资源。
public class TryCatch{     public static void main(String[] args) {         try {             read("a.txt");         } catch (FileNotFoundException e) {             //抓取到的是编译期异常  抛出去的是运行期              throw new RuntimeException(e);         } finally {             System.out.println("不管程序怎样,这里都将会被执行。");         }         System.out.println("over");     }     /*      *      * 我们 当前的这个方法中 有异常  有编译期异常      */     public static void read(String path) throws FileNotFoundException {         if (!path.equals("a.txt")) {//如果不是 a.txt这个文件              // 我假设  如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常  throw             throw new FileNotFoundException("文件不存在");         }     } }

当只有在try或者catch中调用退出JVM的相关方法,此时finally才不会执行,否则finally永远会执行。

自定义异常

1. 自定义一个编译期异常: 自定义类 并继承于 java.lang.Exception 。 2. 自定义一个运行时期的异常类:自定义类 并继承于 java.lang.RuntimeException 。

线程和进程

  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

一个程序运行后至少有一个进程,一个进程中可以包含多个线程

线程调度:

  • 分时调度
    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
  • 抢占式调度
    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

并发与并行

  • 并发:指两个或多个事件在同一个时间段内发生。
  • 并行:指两个或多个事件在同一时刻发生(同时发生)。

在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。

而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核CPU,便是多核处理器,核 越多,并行处理的程序越多,能大大的提高电脑运行的效率。

注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

多线程

Java中通过继承Thread类来创建并启动多线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程

多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。
通过调用start()方法来通知JVM来开辟新的线程空间, 并在新的线程空间里面执行对象的run方法
当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。

Thread类

构造方法

  • public Thread() :分配一个新的线程对象。
  • public Thread(String name) :分配一个指定名字的新的线程对象。
  • public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

常用方法

  • public String getName() :获取当前线程名称。
  • public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
  • public void run() :此线程要执行的任务在此处定义代码。
  • public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
  • public static Thread currentThread() :返回对当前正在执行的线程对象的引用。

创建线程的方式总共有两种,一种是继承Thread类方式,一种是实现Runnable接口方式

Runnable接口

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Threadtarget来创建Thread对象,该Thread对象才是真正的线程对象。
  3. 调用线程对象的start()方法来启动线程。
public class MyRunnable implements Runnable{     @Override     public void run() {         for (int i = 0; i < 20; i++) {             System.out.println(Thread.currentThread().getName()+" "+i);         }     } }  public class Demo {     public static void main(String[] args) {         //创建自定义类对象  线程任务对象         MyRunnable mr = new MyRunnable();         //创建线程对象         Thread t = new Thread(mr, "小强");         t.start();         for (int i = 0; i < 20; i++) {             System.out.println("旺财 " + i);         }     } }

Runnable对象仅仅作为Thread对象的targetRunnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其targetrun()方法。

Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

实现Runnable接口比继承Thread类所具有的优势:

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程。

匿名内部类方式实现线程的创建

public class NoNameInnerClassThread {     public static void main(String[] args) {         //      new Runnable(){ //          public void run(){ //              for (int i = 0; i < 20; i++) { //                  System.out.println("张宇:"+i); //              } //          }   //      }; //‐‐‐这个整体  相当于new MyRunnable()         Runnable r = new Runnable(){             public void run(){                 for (int i = 0; i < 20; i++) {                     System.out.println("张宇:"+i);                 }             }           };          new Thread(r).start();           for (int i = 0; i < 20; i++) {             System.out.println("费玉清:"+i);         }     } }

线程安全

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
多线程如果不加约束, 很容易出问题的.

线程安全问题都是由全局变量静态变量引起的。若每个线程中对全局变量、静态变量只有操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

线程同步

要解决多线程并发访问一个资源的安全性问题,Java中提供了同步机制(synchronized)来解决。

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。

  1. 同步代码块。
  2. 同步方法。
  3. 锁机制。

同步代码块

同步代码块synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式:

synchronized(同步锁){      需要同步操作的代码 }

同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

  1. 锁对象 可以是任意类型。
  2. 多个线程对象 要使用同一把锁。

在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着
(BLOCKED)。

public class Ticket implements Runnable{     private int ticket = 100;          Object lock = new Object();     /*      * 执行卖票操作      */     @Override     public void run() {         //每个窗口卖票的操作          //窗口 永远开启          while(true){             synchronized (lock) {                 if(ticket>0){//有票 可以卖                     //出票操作                     //使用sleep模拟一下出票时间                      try {                         Thread.sleep(50);                     } catch (InterruptedException e) {                         // TODO Auto‐generated catch block                         e.printStackTrace();                     }                     //获取当前线程对象的名字                      String name = Thread.currentThread().getName();                     System.out.println(name+"正在卖:"+ticket‐‐);                 }             }         }     } }

同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

public synchronized void method(){     可能会产生线程安全问题的代码 }

同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

public class Ticket implements Runnable{     private int ticket = 100;     /*      * 执行卖票操作      */     @Override     public void run() {         //每个窗口卖票的操作          //窗口 永远开启          while(true){             sellTicket();         }     }          /*      * 锁对象 是 谁调用这个方法 就是谁       *   隐含 锁对象 就是  this      *          */     public synchronized void sellTicket(){         if(ticket>0){//有票 可以卖                //出票操作             //使用sleep模拟一下出票时间              try {                 Thread.sleep(100);             } catch (InterruptedException e) {                 // TODO Auto‐generated catch block                 e.printStackTrace();             }             //获取当前线程对象的名字              String name = Thread.currentThread().getName();             System.out.println(name+"正在卖:"+ticket‐‐);         }     } }

锁机制(Lock锁)

java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

Lock锁也称同步锁,加锁与释放锁方法化了,如下:

public void lock(): 加同步锁。
public void unlock(): 释放同步锁。

public class Ticket implements Runnable{     private int ticket = 100;          Lock lock = new ReentrantLock();     /*      * 执行卖票操作      */     @Override     public void run() {         //每个窗口卖票的操作          //窗口 永远开启          while(true){             lock.lock();             if(ticket>0){//有票 可以卖                 //出票操作                  //使用sleep模拟一下出票时间                  try {                     Thread.sleep(50);                 } catch (InterruptedException e) {                     // TODO Auto‐generated catch block                     e.printStackTrace();                 }                 //获取当前线程对象的名字                  String name = Thread.currentThread().getName();                 System.out.println(name+"正在卖:"+ticket‐‐);             }             lock.unlock();         }     } }

线程状态

API中 java.lang.Thread.State 这个枚举中给出了六种线程状态

线程状态 导致状态发生条件
NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
Blocked(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed Waiting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止) 因为run方法正常退出而死亡, 或者因为没有捕获的异常终止了run方法而死亡。

Timed Waiting(计时等待)

Timed Waiting在API中的描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。

当调用了sleep方法之后,当前执行的线程就进入到“休眠状态”,其实就是所谓的Timed Waiting(计时等待)

实现一个计数器,计数到100,在每个数字之间暂停1秒,每隔10个数字输出一个字符串

 public class MyThread extends Thread {     public void run() {         for (int i = 0; i < 100; i++) {             if ((i) % 10 == 0) {                 System.out.println("‐‐‐‐‐‐‐" + i);             }             System.out.print(i);             try {                 Thread.sleep(1000);                 System.out.print("    线程睡眠1秒!\n");             } catch (InterruptedException e) {                 e.printStackTrace();             }         }     }      public static void main(String[] args) {         new MyThread().start();     } }
  1. 进入TIMED_WAITING状态的一种常见情形是调用的 sleep 方法,单独的线程也可以调用,不一定非要有协作关系
  2. 为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程中会睡眠
  3. sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态

    sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始立刻执行。

BLOCKED(锁阻塞)

Blocked状态在API中的介绍为:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。

线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。

这是由Runnable状态进入Blocked状态。除此Waiting以及Time Waiting状态也会在某种情况下进入阻塞状态

多进程

网络编程

jdk8

jdbc

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!