Java基础小知识

 类型转换、ASCII码、除法取余、三元表达式  

        long x = 100;//int->long自动类型转换(隐式) 1.特点:代码不需要进行特殊处理,自动完成。2.规则:数据范围从小到大。
        double y = 2.5F;//2.5 float->double自动类型转换(隐式)

        int x1 = (int)100L;//long->int强制类型转换(显式)  范围大到小  需要强转,不能自动转
        int y1 = (int)3.99;//double->int  抹去小数位变为3
        int z1 = (int)6000000000L;//long->int丢失精度  1705032704

        int charToInt = 'A'+1;//char->int
        int e = (byte)65+(byte)66;//byte->int byte取值-128~127
        int e1 = (byte)65+(short)66;//short取值范围-215~214;

        //ASCII码 '0'为48  'A'为65  'a'为97
        int charToInt2 = 'A'+0;//65 char底层保存的数字

        //除法与取余
        int r1 = 10/3;//3
        int r2 = 10%3;//1

        //三元表达式
        int a2 = 3>5? (int) 2.5:1;//冒号两侧都需符号精度要求

switch

    public static void main(String[] args) {
        int num  = 2;
        //匹配哪一个case就从哪一个位置向下执行,直到遇到了break或者整体结束为止。
        switch (num){
            case 1:
                System.out.println("你好");
            case 2:
                System.out.println("我好");
                //break;
            case 3:
                System.out.println("大家好");
                break;
            case 4:
                System.out.println("hello");
                break;
            default:
                System.out.println("hello~hello~hello~");
                break;
        }
    }

do-while()、while-do()、for循环

    public static void main(String[] args) {
        /*
        * do-while()和while-do()初始化语句参数出了循环后还可使用,但for循环初始化语句参数只能for循环内容使用
        * 若条件判断从来没满足过,do-while()也会执行1次,while-do()和for()不会执行
        * */
        System.out.println("=====================================");
        int i = 1;
        do {
            System.out.println("hello" + i);//循环体
            i++;//迭代语句,步进语句
        } while (i <= 10);//条件判断
        System.out.println("======================================");
        int x = 1;
        while (x<=10){
            System.out.println("xHello" + x);//循环体
            x++;//迭代语句,步进语句
        }
        System.out.println("======================================");
        for (int j = 1; j <=10 ; j++) {//迭代语句和变量定义在for循环参数里,只能当前for循环使用该参数
            System.out.println("jHello" + j);//循环体
        }
        /*
         * for循环的break和continue
         * */
        System.out.println("==========break=====================");
        for (int j = 1; j <=10 ; j++) {
            if (j==5){
                break;//如果j为5,跳出整个for循环,后续迭代都不要了
            }
            System.out.println("jHello" + j);
        }
        System.out.println("===========continue===================");
        for (int j = 1; j <=10 ; j++) {
            if (j==5){
                continue;//如果j为5,当次迭代的此处后续代码不执行了,马上开始下一个迭代继续执行for循环体
            }
            System.out.println("jHello" + j);/*jHello1 jHello2 jHello3 jHello4 jHello6 jHello7 jHello8 jHello9 jHello10*/
        }
    }

方法和return

/*
 * 方法: 1、方法定义于类中,方法中不可定义方法。2、类中方法无先后顺序。3、方法定义后需要(单独调用、打印调用、赋值调用)才能执行。
 * return:1、return的数据和方法返回值类型一致。2、void类型的方法可以不写return也可写为return;。
 * 方法中可多个return,要保证最终只有一个被执行到
 * */
public class JavaTest {
    public static int hello1() {
        return 10;
    }
    public static void hello2() {
        System.out.println("hello2");
    }
    public static void hello3() {
        int i = 2;
        if (i == 2) {
            System.out.println("hello2");
            return;
        } else {
            System.out.println("hello222");
        }
    }
}

方法重载

/*
 * 方法重载:方法名相同,参数类型或参数个数不同。对返回值类型没有要求。
 * 方法重载参数不一样。可以用来适配多种用户输入的数值类型提供的方法。 println()方法其实就是一个重载
 * */
public class JavaTest {
    public static void main(String[] args) {
        System.out.println(sum(1,2));
        System.out.println(sum(1,2,3));
        System.out.println(sum("1",2));
        System.out.println(sum(1,"2"));
    }
    public static int sum(int a,int b){
        return a+b;
    }
    public static int sum(int a,int b,int c){
        return a+b+c;
    }
    public static int sum(String a,int b){
        return Integer.valueOf(a)+b;
    }
    public static String sum(int a,String b){
        return String.valueOf(a+Integer.valueOf(b));
        //注:如果单返回值不同,但参数列表相同,也不是重载
    }
}

 访问权限修饰符

public(所有位置的类都可访问)、 protected(同包其他类、其他包该类的子类可访问)、

缺省不写(同包其他类可访问)、private(只能本类内部访问)

数组

/*
 * 数组:存放多个同一类型数据值的引用类型且运行期间长度不可变的容器
 * 动态初始化默认值:整数默认0、浮点型默认0.0、字符类型默认'\u0000'、布尔类型默认false、引用类型默认null;
 * 静态初始化也有默认值过程,只不过马上替换成大括号中的内容了。
 * 异常有下标0~arraA.length-1的数组索引越界异常ArrayIndexOutOfBoundsException,
 * 没有new数组直接如int [] a = null;a[0]的空指针异常NuLLPointerException。
 * 数组作为方法的参数或返回值其实传递进去的是地址值。
 * */
public class JavaTest {
    public static void main(String[] args) {
        //静态初始化
        int [] arrayA = new int[]{1,2,3};
        int [] arrayB = {1,2,3};
        System.out.println(arrayA);//地址值
        System.out.println(Arrays.toString(arrayB));//[1, 2, 3]
        System.out.println(arrayA[0]);
        int num2 = arrayB[2];System.out.println(num2);
        arrayB[0]=3;
        System.out.println(Arrays.toString(arrayB));//[3, 2, 3]
        //动态数组
        System.out.println("=====================");
        int [] arrC = new int[3];
        arrC[0] = 3;
        System.out.println(arrC[0]);
        System.out.println(arrC[1]);
        //遍历数组arrayA.fori
        for (int i = 0; i < arrayA.length; i++) {
            System.out.println(arrayA[i]);
        }
        //反转遍历数组arrayA.forr
        for (int i = arrayA.length - 1; i >= 0; i--) {
            System.out.println(arrayA[i]);
        }
    }
}

对象数组

com.kdy.domain包
public class Person {
    public String name;
    public Person(String name) {
        this.name = name;
    }
    public Person() {
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}
com.kdy.test包
public class JavaTest {
    public static void main(String[] args) {
        Person[] array = new Person[3];
        Person zhangsan = new Person("zhangsan");
        Person wangwu = new Person("wangwu");
        Person sunwukong = new Person("sunwukong");
        array[0]=zhangsan;
        array[1]=wangwu;
        array[2]=sunwukong;
        System.out.println(Arrays.toString(array));
    }
}

 ArrayList数组

/*
 * ArrayList长度可变的集合,<E>泛型为引用类型
 * list.add();添加;list.get(0);索引拿数组元素值;list.remove(0);索引删除;list.size();集合长度;
 * list.fori遍历集合
 * */
public class JavaTest {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("王五");
        list.add("王五1");
        list.add("王五2");
        System.out.println(list);//[王五]
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

Arrays类和Collections类

常用Arrays.toString(数组) 和 Arrays.sort(数组);   Arrays.asList("a","b","c")长度不可变的List

Collections.addAll(所有集合通用)——即集合名.allAll(集合名);

对于list集合有效:Collections.shuffle(poker);  Collections.sort(集合)排序     

com.kdy.domain
public class Person implements Comparable<Person> {
    private int id;
    private String name;
    private int age;
    public Person() {
    }
    public Person(int id, String name,int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    @Override
    public int compareTo(Person o) {
        //return 0;//认为元素都是相同的
        //return this.id - o.id;//按id升序
        return this.age - o.age;//按年龄升序,年龄相同,list集合按存入先后顺序再排
        //return o.age - this.age;//按年龄降序
    }
}
public class Student {
    private int id;
    private String name;
    private int age;
    public Student() {
    }
    public Student(int id, String name, int age) {
        this.id = id;
        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 String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
com.kdy.test 
public class JavaTest {
    public static void main(String[] args) throws ParseException {
     /*
     * 以ArrayList为例,学习Collections类
     * */
        ArrayList<String> ArrayList = new ArrayList<>();
        HashSet<String> hashSet = new HashSet<>();
        LinkedList<String> linkedList = new LinkedList<>();
        LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
        Collections.addAll(ArrayList,"a","b","c","d","e","f","g");
        Collections.addAll(hashSet,"a","b","c","d","e","f","g");
        Collections.addAll(linkedList,"a","b","c","d","e","f","g");
        Collections.addAll(linkedHashSet,"a","b","c","d","e","f","g");
        System.out.println(ArrayList);//[a, b, c, d, e, f, g]
        System.out.println(hashSet);//[a, b, c, d, e, f, g]
        System.out.println(linkedList);//[a, b, c, d, e, f, g]
        System.out.println(linkedHashSet);//[a, b, c, d, e, f, g]
        Collections.shuffle(ArrayList);
        Collections.shuffle(linkedList);
        //Collections.shuffle(hashSet);//set集合无索引,无法打乱
        //Collections.shuffle(linkedHashSet);//set集合无索引,无法打乱
        System.out.println("shuffle后==========================");
        System.out.println(ArrayList);//[b, e, d, a, f, c, g]...
        System.out.println(linkedList);//[a, f, b, g, c, e, d]...
        Collections.sort(ArrayList);
        Collections.sort(linkedList);
        //Collections.sort(hashSet);//set集合无索引,不可根据大小排序
        //Collections.sort(linkedHashSet);//set集合无索引,不可根据大小排序
        System.out.println("sort后===================================");
        System.out.println(ArrayList);//[a, b, c, d, e, f, g]
        System.out.println(linkedList);//[a, b, c, d, e, f, g]
        /*想要使用Collections.sort()方法必须集合元素的类实现comparable<>接口,并重写接口中的方法*/
        java.util.ArrayList<Person> personArrayList = new ArrayList<>();
        personArrayList.add(new Person(3,"张678w41",23));
        personArrayList.add(new Person(2,"张375342",20));
        personArrayList.add(new Person(3,"张34576433",26));
        personArrayList.add(new Person(1,"张324552",23));
        personArrayList.add(new Person(5,"张34567435",25));
        Collections.sort(personArrayList);
        System.out.println(personArrayList);
        /*想要使用Collections.sort()方法也可也可以Collections.list()比较时使用匿名对象comparator,里面重写排序方法*/
        java.util.ArrayList<Student> studentList = new ArrayList<>();
        studentList.add(new Student(3,"张678w41",23));
        studentList.add(new Student(2,"张375342",20));
        studentList.add(new Student(3,"张34576433",26));
        studentList.add(new Student(1,"张324552",23));
        studentList.add(new Student(5,"张34567435",25));
        Collections.sort(studentList, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                int result =  o1.getAge()-o2.getAge();
                if (result==0){
                    result = o1.getName().charAt(0) - o2.getName().charAt(0);
                }
                return result;
            }
        });
        System.out.println(studentList);
    }
}

 Collection集合、List集合、Set集合

顶层:Collcetion接口(定义所有单列集合共享方法,没有带索引的方法)

List接口继承了Collection接口:有序【存取元素顺序相同】、允许元素重复、有索引

Set接口继承了Collection接口:不允许元素重复、无索引

Vector集合实现了List接口:先作了解。

ArrayList集合实现了List接口:底层数组可扩容,有下标,随机访问效率高,尾部增删正常,但越往头部增删越慢(因为得移动批量元素到上一个或下一个索引),扩容时新建复制也耗时,需连续内存空间,开销小一些(只需新建或扩容时数组空间用不了可能)。

LinkedList实现类List接口:底层无头节点的双链表,增删快,随机访问越往后越慢(链表得一个个的履),不需要连续内存空间,开销大一些(节点需保存信息还要保存前后节点的地址值)

TreeSet:继承了AbstractSet(实现了Set接口),先作了解。

HashSet:实现了Set接口,无序((对于存放的顺序而言,读取的顺序是无序的),底层哈希表查询非常快,jdk1.8前采用数组加链表,jdk1.8后的哈希表又采用了数组加链表或数组加红黑树(链表长度超过8自动转为红黑树)的形式提高了查询速度—非常快。

LinkedHashSet:实现了Set接口,且继承了HashSet,有序(对于存放的顺序而言,读取的顺序是无序的))。

Collection方法 、Iterator、foreach、list、set

com.kdy.test 
public class JavaTest {
    public static void main(String[] args) throws ParseException {
        /*
         * 以Collection接口的子接口List接口的实现类ArrayList为例看Collection接口抽象方法
         * add、clear、remove、isEmpty、size、toArray
         * */
        ArrayList<String> list = new ArrayList<>();
        list.add("abc");//添加add()
        list.add("def");
        list.clear();//清空clear()
        list.add("abc");
        list.add("def");
        boolean flag = list.remove("def");//移除元素remove()
        System.out.println(list.isEmpty());//判断集合是否为空isEmpty()
        int size = list.size();//集合元素个数size()
        Object[] objects = list.toArray();//集合转数组toArray()
        /*
         * List接口特有方法如下set和get
         * */
        list.set(0,"替换的元素");//用元素替换某个下标的元素
        list.get(0);//通过索引获取元素
        /*
         * Iterator遍历集合的方式
         * Collection接口返回Iterator对象的方法iterator()
         * */
        Iterator<String> iterator = list.iterator();//将集合元素放入迭代器中
        while (iterator.hasNext()){//判断指针后一个有无元素
            String str = iterator.next();//后移指针,取出元素。
            System.out.println(str);
        }
        /*
         * 增强for
         * */
        for (String str :list) {
            System.out.println(str);
        }
        /*
         * LinkedList为例看Collection接口抽象方法
         * */
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.add("abc");//添加add()
        linkedList.add("def");
        linkedList.clear();//清空clear()
        linkedList.add("abc");
        linkedList.add("def");
        boolean flag2 = linkedList.remove("def");//移除元素remove()
        System.out.println(linkedList.isEmpty());//判断集合是否为空isEmpty()
        int size2 = linkedList.size();//集合元素个数size()
        Object[] objects2 = linkedList.toArray();//集合转数组toArray()
        /*
         * LinkedList特有的方法
         * */
        linkedList.addFirst("helloFirst");//插入到第头个
        linkedList.addLast("lastHello");//插入到最后一个
        System.out.println(linkedList);//[helloFirst, abc, lastHello]
        linkedList.push("hello");// 推入栈顶 头节点
        System.out.println(linkedList);//[hello, helloFirst, abc, lastHello]
        String pop = linkedList.pop();//从栈顶弹出,弹出头节点移除并返回
        System.out.println(pop);//hello
        System.out.println(linkedList);//[helloFirst, abc, lastHello]
        String removeFirst = linkedList.removeFirst();//返回值为被移除的元素
        linkedList.removeLast();//返回值为被移除的元素
        /*
        * hashCode
        * 普通对象继承object重写了hashCode方法  460141958
        * String类重写了Object的hashCode方法,不过"通话"和"重地"的hash值重复
        * */
        Person person = new Person();
        System.out.println(person.hashCode());// 460141958
        System.out.println("通话".hashCode());//1179395
        System.out.println("重地".hashCode());//1179395
        System.out.println(System.identityHashCode("通话"));//1163157884
        System.out.println(System.identityHashCode("重地"));//1956725890
        System.out.println("通话".hashCode()=="重地".hashCode());//true :哈希冲突了
        System.out.println("通话".equals("重地"));//false:使用的String的重写的equals方法:详见String类的equals方法
        System.out.println("通话"=="重地");//false:地址值不同,字符串常量池中还是不是一个地址
        /*
        * hashSet集合
        * hashSet采用了数组加链表或数组加红黑树的方式,它的算法是先判断当前元素hash值是否集合中存在,不存在就直接放数组,存在就再比较equals,相等就不替换,不相等就该数组该同等hash值的索引作为头节点,接入该节点作为链表中下一个节点
        *
        * */
        HashSet<String> hashSet = new HashSet<>();
        String s1 = new String("abc");
        String s2 = new String("abc");
        System.out.println(s1.equals(s2));//true
        hashSet.add(s1);
        hashSet.add(s2);
        hashSet.add("abc");
        //s1和s2和"abc":地址值不同、HashCode相同、toString如果是String不重写toString的话打印的内容也相同,重写的equals也相等
        hashSet.add("通话");
        hashSet.add("重地");
        System.out.println(hashSet);//[通话, 重地, abc]
        //创建hashSet存储Person
        HashSet<Person> set = new HashSet<>();
        set.add(new Person(1,"张三",20));
        set.add(new Person(2,"张三",20));
        set.add(new Person(1,"张三",20));
        //System.out.println(set);//如果Person不重写equals方法 [Person{id=2, name='张三', age=20}, Person{id=1, name='张三', age=20}, Person{id=1, name='张三', age=20}]
        System.out.println(set);// 如果Person重写了equals方法和hashCode方法  [Person{id=2, name='张三', age=20}, Person{id=1, name='张三', age=20}]
        /*集合中的泛型
         * 使用泛型可将运行期间的异常提升到编译期
         * 避免了类型转换时可能出现的异常,存放的是什么类型,取出时就是什么类型
         * */
        ArrayList<Object> objectList = new ArrayList<>();//不使用泛型的话,集合默认object类型,当我们向下转型时,编译不会报错,但运行时可能会报错(如该集合中存储各种类型的数据,到时候用的时候可能向下转型异常)
        objectList.add("abcd");
        objectList.add(true);
        objectList.add(666);
        objectList.add(2.56f);
        objectList.add(666L);
        Object o = objectList.get(0);
        //float o1 = (float)objectList.get(1);//可能忘记之前存放集合中是什么类型的数据了,转型写错会报错
    }
}}
com.kdy.domain 
public class Person {
    private int id;
    private String name;
    private int age;
    public Person() {
    }
    public Person(int id, String name,int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return id == person.id &&
                age == person.age &&
                Objects.equals(name, person.name);
    }
    @Override
    public int hashCode() {
        return Objects.hash(id, name, age);
    }
}

 集合的斗地主案例

public class JavaTest {
    public static void main(String[] args) throws ParseException {
        ArrayList<String> poker = new ArrayList<>();
        String [] colors = {"♠","♥","♣","♦"};
        String [] numbers = {"2","A","K","Q","J","10","9","8","7","6","5","4","3"};
        poker.add("大王");
        poker.add("小王");
        for (String num:numbers) {
            for (String col:colors) {
                poker.add(col+num);
            }
        }
        System.out.println(poker);
        Collections.shuffle(poker);
        ArrayList<String> player01 = new ArrayList<>();
        ArrayList<String> player02 = new ArrayList<>();
        ArrayList<String> player03 = new ArrayList<>();
        ArrayList<String> dipai = new ArrayList<>();
        for (int i = 0; i < poker.size(); i++) {
            String p = poker.get(i);
            if (i>=51){
                dipai.add(p);
            }else if(i%3==0){
                player01.add(p);
            }else if(i%3==1){
                player02.add(p);
            }else if(i%3==2){
                player03.add(p);
            }
        }
        System.out.println(player01);
        System.out.println(player02);
        System.out.println(player03);
        System.out.println(dipai);
    }
}

Map集合

map接口:双列集合,一个元素包含key和value(一一对应),key和value类型可不同,key不可重复(重复会覆盖value),value可重复。

HashMap:实现了map接口,底层哈希表,查询特别快,无序(存入元素的顺序和取出元素的顺序可不一致)。但HashMap会根据key的大小自动从小到大排序。

LinkedHashMap:继承了HashMap也实现了Map接口,底层哈希表加链表(加链表保证存入的元素顺序和取出元素顺序一致),有序。

HashTable:实现了map接口,子类Properties是唯一和IO流相结合的键值对集合。

com.kdy.domain
public class Person {
    private int id;
    private String name;
    private int age;
    public Person(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    public Person() {
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return id == person.id &&
                age == person.age &&
                Objects.equals(name, person.name);
    }
    @Override
    public int hashCode() {
        return Objects.hash(id, name, age);
    }
    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
com.kdy.test
public class JavaTest {
    public static void main(String[] args) throws ParseException {
        /*
        * Map接口的方法,以HashMap为例
        *map.put()、map.remove()、map.get()、map.containsKey()、map.clear();
        * */
        HashMap<String, String> hashMap = new HashMap<>();
        LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
        hashMap.put("key1","value1");
        linkedHashMap.put("key1","value1");
        hashMap.clear();//清空集合
        linkedHashMap.clear();
        System.out.println(hashMap);
        System.out.println(linkedHashMap);
        hashMap.put("key1","value1");//添加元素
        hashMap.put("key2","value2");
        hashMap.put("key3","value3");
        hashMap.put("key4","value4");
        hashMap.put("key5","value5");
        linkedHashMap.put("key1","value1");
        linkedHashMap.put("key2","value2");
        linkedHashMap.put("key3","value3");
        linkedHashMap.put("key4","value4");
        linkedHashMap.put("key5","value5");
        hashMap.remove("key1");//根据key删除元素
        linkedHashMap.remove("key1");
        System.out.println(hashMap);
        System.out.println(linkedHashMap);
        System.out.println(hashMap.get("key2"));//根据key拿value
        System.out.println(linkedHashMap.get("key2"));
        System.out.println(hashMap.containsKey("key2"));//查该该map是否包含该key
        System.out.println(linkedHashMap.containsKey("key2"));
        System.out.println(hashMap.containsValue("value2"));//查该该map是否包含该value,下一步可迭代遍历找到对应的key
        System.out.println(linkedHashMap.containsValue("value2"));
        /*使用keySet加迭代器的方式遍历map集合*/
        Set<String> hashMapKeySet = hashMap.keySet();//hashMap无序
        Iterator<String> hashMapKeySetIterator = hashMapKeySet.iterator();
        while (hashMapKeySetIterator.hasNext()){
            String key = hashMapKeySetIterator.next();
            String value = hashMap.get(key);
            System.out.println(key+"="+value);
        }
        Set<String> linkedHashMapKeySet = linkedHashMap.keySet();//linkedHashMap有序
        Iterator<String> linkedHashMapKeySetIterator = linkedHashMapKeySet.iterator();
        while (linkedHashMapKeySetIterator.hasNext()){
            String key = linkedHashMapKeySetIterator.next();
            String value = linkedHashMap.get(key);
            System.out.println(key+"="+value);
        }
        /*使用keySet加增强for的方式遍历map集合*/
        Set<String> hashMapKeySet2 = hashMap.keySet();//hashMap无序
        for (String key:hashMapKeySet2 ) {
            String value = hashMap.get(key);
            System.out.println(key+"="+value);
        }
        Set<String> linkedHashMapKeySet2 = linkedHashMap.keySet();//linkedHashMap有序
        for (String key:linkedHashMapKeySet2 ) {
            String value = linkedHashMap.get(key);
            System.out.println(key+"="+value);
        }
        /*使用entrySet加迭代器遍历map*/
        Set<Map.Entry<String, String>> hashMapEntrySet = hashMap.entrySet();
        Iterator<Map.Entry<String, String>> hashMapEntrySetIterator = hashMapEntrySet.iterator();
        while (hashMapEntrySetIterator.hasNext()){
            Map.Entry<String, String> entry = hashMapEntrySetIterator.next();
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key+":"+value);
        }
        Set<Map.Entry<String, String>> linkedHashMapEntrySet = linkedHashMap.entrySet();
        Iterator<Map.Entry<String, String>> linkedHashMapEntrySetIterator = linkedHashMapEntrySet.iterator();
        while (linkedHashMapEntrySetIterator.hasNext()){
            Map.Entry<String, String> entry = linkedHashMapEntrySetIterator.next();
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key+":"+value);
        }
        /*使用entrySet加增强for遍历map*/
        Set<Map.Entry<String, String>> hashMapEntrySet2 = hashMap.entrySet();
        for (Map.Entry<String ,String> entry:hashMapEntrySet2) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key+":"+value);
        }
        Set<Map.Entry<String, String>> linkedHashMapEntrySet2 = linkedHashMap.entrySet();
        for (Map.Entry<String ,String> entry:linkedHashMapEntrySet2) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key+":"+value);
        }
        /*
         * 自定义的类的对象作为key,存储hashmap中,需要在类中重写hashcode与equals方法来保证map规则key不重复
         */
        HashMap<Person, String> personPositionHashMap = new HashMap<>();//无序
        personPositionHashMap.put(new Person(1,"zhangsan",20),"英国");
        personPositionHashMap.put(new Person(2,"zhangsan",20),"埃及");
        personPositionHashMap.put(new Person(3,"zhangsan",20),"阿联酋");
        personPositionHashMap.put(new Person(1,"zhangsan",20),"俄罗斯");
        System.out.println(personPositionHashMap);//map集合不遍历,直接打印也可,重写了toString
        LinkedHashMap<Person, String> personPositionLinkedHashMap = new LinkedHashMap<>();//有序
        personPositionLinkedHashMap.put(new Person(1,"zhangsan",20),"英国");
        personPositionLinkedHashMap.put(new Person(2,"zhangsan",20),"埃及");
        personPositionLinkedHashMap.put(new Person(3,"zhangsan",20),"阿联酋");
        personPositionLinkedHashMap.put(new Person(1,"zhangsan",20),"俄罗斯");
        System.out.println(personPositionLinkedHashMap);//map集合不遍历,直接打印也可,重写了toString
    }
}

 Map计算一个字符串字符每次出现的次数

public class JavaTest {
    public static void main(String[] args) throws ParseException {
        Scanner scanner = new Scanner(System.in);
        String str = scanner.next();
        HashMap<Character, Integer> map = new HashMap<>();
        for (char c: str.toCharArray()) {
            if (map.containsKey(c)){
                Integer value = map.get(c);
                value++;
                map.put(c,value);
            }else{
                map.put(c,1);
            }
        }
        System.out.println(map);
    }
}

map集合斗地主有序版 

public class JavaTest {
    public static void main(String[] args) throws ParseException {
        HashMap<Integer, String> poker = new HashMap<>();
        ArrayList<Integer> pokerIndex = new ArrayList<>();
        ArrayList<String> colors = new ArrayList<>();
        Collections.addAll(colors,"♠", "♥", "♣", "♦");
        ArrayList<String> numbers = new ArrayList<>();
        Collections.addAll(numbers,"2", "A", "K", "Q","J","10","9","8","7","6","5","4","3");
        int index = 0 ;
        poker.put(index,"大王");
        pokerIndex.add(index);
        index++;
        poker.put(index,"小王");
        pokerIndex.add(index);
        index++;
        for (String number:numbers) {//一定是numbers在外
            for (String color: colors) {
                poker.put(index,color+number);
                pokerIndex.add(index);
                index++;
            }
        }//这样pokerIndex和poker的key可一一对应,且随着index和pokerIndex的增加,poker中value是逐渐逻辑上纸牌大小越来越小的
        Collections.shuffle(pokerIndex);
        ArrayList<Integer> dipai = new ArrayList<>();
        ArrayList<Integer> play01 = new ArrayList<>();
        ArrayList<Integer> play02 = new ArrayList<>();
        ArrayList<Integer> play03 = new ArrayList<>();
        for (int i = 0; i < pokerIndex.size(); i++) {//pokerIndex中元素顺序被打乱了,i是下标0~size
            Integer in = pokerIndex.get(i);
            if (in>51){
                dipai.add(in);
            }else if(i%3==0){
                play01.add(in);
            }else if (i%3==1){
                play02.add(in);
            }else if (i%3==2){
                play03.add(in);
            }//由于pokerIndex和poker中的index一一对应,所以打乱pokerIndex后分发给每个玩家乱序的pokerIndex就可从poker中对应乱序的牌值。
        }
        Collections.sort(play01);
        Collections.sort(play02);
        Collections.sort(play03);
        Collections.sort(dipai);
        JavaTest.lookPoker("play01",poker,play01);
        JavaTest.lookPoker("play02",poker,play02);
        JavaTest.lookPoker("play03",poker,play03);
        JavaTest.lookPoker("dipai",poker,dipai);
    }
    public static void lookPoker(String name,HashMap<Integer,String> poker,ArrayList<Integer> list){
        System.out.print(name+"的牌为:");
        for (Integer key: list) {
            String value = poker.get(key);
            System.out.print(value);
        }
        System.out.println();
    }
}

Stream流

用于数组或集合的流 。关注的是做什么,而不是怎么做。非常优雅。

整体来看,流式思想类似于工厂车间的“生产流水线"。

管道流。终结方法后不可再用。

filter 、map 、skip都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法count执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。

获取流

获取流可以使用集合.stream或stream.Of(数组)

根据Collection获取流

java.util.collection 接口中加入了default方法 stream用来获取流,所以其所有实现类均可获取流。

public class JavaTest {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Stream<String> stream1 = list.stream();
        HashSet<String> set = new HashSet<>();
        Stream<String> stream2 = set.stream();
        HashMap<String, String> map = new HashMap<>();
        Stream<String> stream3 = map.keySet().stream();
        Collection<String> values = map.values();
        Stream<String> stream4 = values.stream();
        Set<Map.Entry<String, String>> entries = map.entrySet();
        Stream<Map.Entry<String, String>> stream5 = entries.stream();
    }
}
将数组转为stream流
public class JavaTest {
    public static void main(String[] args) {
        Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5);
        Integer [] arr = {1,2,3,4,5};
        Stream<Integer> stream2 = Stream.of(arr);
        String [] arr2 = {"aaa","bbb","ccc"};
        Stream<String> stream3 = Stream.of(arr2);
    }
}

Stream流常用方法

延迟方法︰返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
终结方法︰返回值类型不再是Stream接口自身类型的方法,因此不再支持类似stringBuilder 那样的链式调用。本小节中,终结方法包括count和forEach方法。

详见  https://www.matools.com/api/java8   中java.util.stream

终结方法forEach

stream流的forEach遍历数据,是一个终结方法,调用结束不能再次调用stream的别的方法

public class JavaTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("张三","李四","王五","赵六","田七");
        stream.forEach((String name)->{
            System.out.println(name);
        });
    }
}
filter

stream的filter方法过滤方法:将一个流转为另一个子集流

public class JavaTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("张三","李四","王五","赵六","田七");
        Stream<String> stream2 = stream.filter((String name) -> {
            return name.startsWith("王");
        });
        stream2.forEach((String name)->{
            System.out.println(name);
        });
    }
}
map

stream流的map映射方法,依赖function接口,是一个转换数据类型的方法

public class JavaTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("1","2","3","4","5");
        Stream<Integer> stream2 = stream.map((String s) -> {
            return Integer.parseInt(s);});
        stream2.forEach( i-> System.out.println(i));
    }
}
终结方法count

stream流的count方法,统计stream流的集合中的数据个数。

public class JavaTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("1","2","3","4","5");
        long count = stream.count();//可统计数组或集合的元素个数
        System.out.println(count);
    }
}
limit

stream流的limit方法:取用前几个数据组成一个stream。

public class JavaTest {
    public static void main(String[] args) {
        String [] arr  ={"喜羊羊","美羊羊","懒羊羊","沸羊羊","红太狼","灰太狼"};
        Stream<String> stream = Stream.of(arr);
        Stream<String> stream2 = stream.limit(3);
        stream2.forEach((String name)->{
            System.out.print(name+" ");//喜羊羊 美羊羊 懒羊羊 
        });
    }
}
skip

stream流的skip方法,跳过前几个,取用后几个数据组成一个stream。

public class JavaTest {
    public static void main(String[] args) {
        String [] arr  ={"喜羊羊","美羊羊","懒羊羊","沸羊羊","红太狼","灰太狼"};
        Stream<String> stream = Stream.of(arr);
        Stream<String> stream2 = stream.skip(3);
        stream2.forEach((String name)->{
            System.out.print(name+" ");//沸羊羊 红太狼 灰太狼 
        });
    }
}
contact

stream流的contact方法,组合两个stream流集合为一个stream。

public class JavaTest {
    public static void main(String[] args) {
        String [] arr  ={"喜羊羊","美羊羊","懒羊羊","沸羊羊","红太狼","灰太狼"};
        String [] arr2  ={"张三","李四","王五","赵六","田七"};
        Stream<String> stream1 = Stream.of(arr);
        Stream<String> stream2 = Stream.of(arr2);
        Stream<String> stream3 = Stream.concat(stream1, stream2);
        stream3.forEach((String name)->{
            System.out.print(name+" ");//喜羊羊 美羊羊 懒羊羊 沸羊羊 红太狼 灰太狼 张三 李四 王五 赵六 田七 
        });
    }
}
使用Stream简化传统集合处理的案例
传统集合处理
public class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}
public class JavaTest {
    public static void main(String[] args) {
        ArrayList<String> one = new ArrayList<>();//队伍一
        one.add("喜羊羊");
        one.add("美羊羊");
        one.add("懒羊羊");
        one.add("沸羊羊");
        one.add("红太狼");
        one.add("灰太狼");
        one.add("张三");
        one.add("李四");
        one.add("王五");
        one.add("赵六");
        one.add("田七");
        ArrayList<String> one1 = new ArrayList<>();
        for (String name:one){
            if (name.length()==3){//名称长度为3
                one1.add(name);
            }
        }
        ArrayList<String> one2 = new ArrayList<>();
        for (int i = 0; i < 3; i++) {//只放前3人
            one2.add(one1.get(i));
        }
        ArrayList<String> two = new ArrayList<>();//队伍二
        two.add("马小跳");
        two.add("光头强");
        two.add("孙悟空");
        two.add("佩奇");
        two.add("乔治");
        two.add("马大哈");
        two.add("马文强");
        two.add("马保国");
        ArrayList<String> two1 = new ArrayList<>();
        for (String name: two){
            if (name.startsWith("马")){//名称以马开头
                two1.add(name);
            }
        }
        ArrayList<String> two2 = new ArrayList<>();
        for (int i = 2; i <two1.size() ; i++) {//不要前两个
            two2.add(two1.get(i));
        }
        ArrayList<String> all = new ArrayList<>();
        all.addAll(one2);
        all.addAll(two2);
        ArrayList<Person> list = new ArrayList<>();
        for (String name:all){
            list.add(new Person(name));
        }
        System.out.println(list);//[Person{name='喜羊羊'}, Person{name='美羊羊'}, Person{name='懒羊羊'}, Person{name='马文强'}, Person{name='马保国'}]
    }
}
使用stream流改进
public class JavaTest {
    public static void main(String[] args) {
        ArrayList<String> one = new ArrayList<>();
        one.add("喜羊羊");
        one.add("美羊羊");
        one.add("懒羊羊");
        one.add("沸羊羊");
        one.add("红太狼");
        one.add("灰太狼");
        one.add("张三");
        one.add("李四");
        one.add("王五");
        one.add("赵六");
        one.add("田七");
        Stream<String> oneStream = one.stream().filter( name -> name.length() == 3).limit(3);
        ArrayList<String> two = new ArrayList<>();//队伍二
        two.add("马小跳");
        two.add("光头强");
        two.add("孙悟空");
        two.add("佩奇");
        two.add("乔治");
        two.add("马大哈");
        two.add("马文强");
        two.add("马保国");
        Stream<String> twoStream = two.stream().filter((String name) -> { return name.startsWith("张");}).skip(2);
        Stream.concat(oneStream,twoStream).map((String name)->{return new Person(name);}).forEach(p-> System.out.println(p));
    }
}

泛型 

com.kdy.domian
/*
* 带泛型的类
* */
public class GenericClass<E> {
    private E name;
    public E getName() {
        return name;
    }
    public void setName(E name) {
        this.name = name;
    }
    /*
    * 带泛型成员方法
    * */
    public <M> void methods1(M m){//方法中要加上<泛型>,入参中才可写泛型类型,传递什么类型的参数就是什么泛型的方法
        System.out.println("成员方法带入参泛型,入参为:"+m);
    }
    /*
     * 带泛型静态方法
     * */
    public static  <S> void methods2(S s){//方法中要加上<泛型>,入参中才可写泛型类型,传递什么类型的参数就是什么泛型的方法
        System.out.println("静态方法带入参泛型,入参为:"+s);
    }
}
public interface GenericInterface<I> {
    public abstract void method(I i);
}
/*
* 实现带泛型的接口 实现类确定接口泛型类型了
* */
public class GenericInterfaceImpl  implements GenericInterface<String>{
    @Override
    public void method(String s) {
        System.out.println(s);
    }
}
/*
 * 实现带泛型的接口 实现类泛型跟着接口走
 * */
public class GenericInterfaceImpl2<I> implements GenericInterface<I> {
    @Override
    public void method(I i) {
        System.out.println(i);
    }
}
com.kdy.test
public class JavaTest {
    public static void main(String[] args) throws ParseException {
        /*测试带泛型的类*/
        GenericClass gc1 = new GenericClass();
        gc1.setName("String类型的数据");
        Object name = gc1.getName();//不写泛型默认object类型
        GenericClass<String> gc2 = new GenericClass<>();//使用泛型
        //gc2.setName(1);//设置int的1就会编译报错了
        gc2.setName("abcdefg");
        System.out.println(gc2.getName());
        /*测试带泛型的方法*/
        gc1.methods1("123");
        gc1.methods1(new GenericClass<>());
        gc2.methods1(666);
        GenericClass.methods2(new GenericClass<>());
        GenericClass.methods2(666);
        GenericClass.methods2("abcdefg");
        /*测试带泛型的接口和其实现类中确定其泛型的实现类*/
        GenericInterfaceImpl gi1 = new GenericInterfaceImpl();
        gi1.method("hello666");
        /*测试带泛型的接口和其实现类中实现类泛型跟着接口走的实现类*/
        GenericInterfaceImpl2<Integer> gi2 = new GenericInterfaceImpl2<>();
        GenericInterfaceImpl2<String> gi22 = new GenericInterfaceImpl2<>();
        gi2.method(666);
        gi22.method("String类型字符串");

    }
    /*测试泛型通配符?只能作为方法的参数,不能作为创建对象使用*/
    public static void printArray(ArrayList<?> list){
        //ArrayList<?> list03 = new ArrayList<?>();//不能作为创建的对象使用泛型通配符
        Iterator<?> iterator = list.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
    /*泛型通配符泛型上限,必须是Number的类型或其子类*/
    public static void getElement1(Collection<? extends Number> coll){
        System.out.println("hello666");
    }
    /*泛型通配符泛型下限,必须是Number的类型或其父类*/
    public static void getElement2(ArrayList<? super Number> coll){
        System.out.println("hello666");
    }

可变参数

当你参数的数据类型确定,参数个数不确定,可使用可变参数。一个方法的参数表只能有一个可变参数,放最后。

public class JavaTest {
    public static void main(String[] args) throws ParseException {
        /*
        定义计算(0-n)整数和的方法
        已知:计算整数的和,数据类型已经确定int
        但是参数的个数不确定,不知道要计算几个整数的和,就可以使用可变参数add();就会创建一个长度为的数组,new int[o]
        */
        System.out.println(JavaTest.add(1, 2, 35));
        System.out.println(JavaTest.add(50, 100,780,900,450,60,70,90));
    }
    public static int add(int ...arr){
        System.out.println(arr);//底层是一个数组
        System.out.println("数组长度:"+arr.length);
        int sum = 0;
        for (int a :arr) {
            sum+=a;
        }
        return sum;
    }
    public static void method(String a,Double b,float f,boolean d,int ...arr){
    }
    //可变参数特殊(终极)写法
    public static void method2(Object ...obj){
    }
}

 基本数据类型的包装类

byte(-128~127)->Byte short(-215~214)->Short int->Integer long->Long float->FLoat

double->Double char->Character boolean->Boolean

取值范围详见一下连接mysql博客中的数据类型部分:mysql的基础使用_阳光明媚UPUP的博客-CSDN博客

Integer a = new Integer(1)//int转Integer;a.intValue()//Integer转int;

jdk1.5后支持自动拆装箱。

Java内存划分

1、栈Stack:方法局部变量(入参或方法体中的参数),方法运行在栈中。超出作用域即消失

2、堆Heap:new出来的东西放堆中,均有16进制地址值可被引用。堆中数据默认值整数默认0、浮点型默认0.0、字符类型默认'\u0000'、布尔类型默认false、引用类型默认null;

3、方法区Method Area:存储.class相关信息含方法的信息。

4、本地方法栈Native Method Stack:与操作系统有关。

5、寄存器pc Register:与CPU相关。

局部变量与成员变量

成员变量在类中,整个类中均可用,存放堆中,有默认值,生命周期随对象创建和回收;

局部变量在方法中只能本方法可用,存放栈中,无默认值,生命周期随方法进栈出栈。

this关键字

访问本类(对象)成员内容

public class Person {
    String name ;
    public void sayHello(String name){
        System.out.println(name+"你好,我是"+this.name);
        System.out.println(this);
    }
    public static void main(String[] args) {
        Person person = new Person();
        person.name = "张三";
        person.sayHello("王五");
    }
}

Java的API文档

Java 8 中文版 - 在线API手册 - 码工具

打开在线文档后,ctrl+f查找包,再找到某个类如Date类,看类的解释和说明,学习构造和使用成语方法。

Scanner类

/*
 *Scanner类
 * */
public class JavaTest {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int intNum = sc.nextInt();//int数字类型
        System.out.println(intNum);
        String strNum = sc.next();//String类型
        System.out.println(strNum);
    }
}

Random类

/*
 * Random类
 * */
public class JavaTest {
    public static void main(String[] args) {
        Random r = new Random();
        int intNum = r.nextInt();
        System.out.println(intNum);//1299152027  -1834692549  -1102748919  1094924722 ...
        int intNum2 = r.nextInt(10);//范围0-9
        System.out.println(intNum2);
        int intNum3 = r.nextInt(100)+1;//范围1-10
        System.out.println(intNum3);
    }
}

Math类

 Math.PI圆周率、Math.abs(doubleNum)绝对值、Math.ceil(doubleNum)向上取整、

Math.floor(doubleNum)向下取整、Math.round(doubleNum)四舍五入

String字符串类

/*
 * String字符串:内容永不可变,效果相当于char[],底层原理为byte[]
 * 字符串常量池:直接写上的双引号字符串,就在字符串常量池中。jdk1.8的字符串的常量池在堆中
 * ==和equals
 * 字符串常用方法:str.length()字符串长度、str.concat(str1)拼接字符串、str.charAt(3)获取索引字符、str.indexOf("el")查找第一次出现位置(没有返回-1)
 * str.subString(3)从下标3截取到尾、str.subString(0,3)从下标0(包含)截取到下标3(不包含)
 * str.replace("l","A")替换字符串全部替换
 * str.toCharArray()转为字符数组
 * str.split(",")分割字符串为字符串数组,英文.要转义str.split("\\.")
 * */
public class JavaTest {
    public static void main(String[] args) {
        String str = "hello";//直接创建 在字符串常量池中
        String str2 = "hello";
        String str5 = "HELlo";
        String str6 = "a,b,c,d,e";
        String str7 = "a.b.c.d.e";
        String helloStr = new String();//new一个空字符串
        char [] charArr = {'h','e','l','l','o'};
        String str3 = new String(charArr);//根据char数组创建
        byte [] byteArr = {97,65,48};
        String str4 = new String(byteArr);//根据byte数组  aA0
        System.out.println(str==str2);//true 字符串常量池中的相等
        System.out.println(str==str3);//false  new出来放到堆中的地址值肯定和string常量池中的元素不相等
        System.out.println("============================");
        System.out.println(str.equals(str2));//true 字符串常量池中的相等
        System.out.println(str2.equals(str3));//true  String重写了equals()和hashCode()
        System.out.println(str.equalsIgnoreCase(str5));//true  String重写了equals()和hashCode()
        System.out.println("============================");
        System.out.println(str.length());
        System.out.println(str.concat("666"));
        System.out.println(str.charAt(3));
        System.out.println(str.indexOf("el"));
        System.out.println("============================");
        System.out.println(str.substring(3));
        System.out.println(str.substring(0,3));
        System.out.println("============================");
        System.out.println(str.replace("l","A"));//heAAo
        System.out.println("============================");
        System.out.println(str.toCharArray()[3]);
        System.out.println("============================");
        System.out.println(Arrays.toString(str6.split(",")));
        System.out.println(Arrays.toString(str7.split("\\.")));
        System.out.println("============================");
        String reName = new StringBuffer(name).reverse().toString();//字符串反转
    }
}
统计字符串大小写与数字出现的次数
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String input = scanner.next();
        int countUpper = 0;
        int countLower = 0;
        int countNumber = 0;
        int countOther = 0;
        char[] charArray = input.toCharArray();
        for (int i = 0; i < charArray.length; i++) {
            char ch = charArray[i];
            if ('A'<=ch &&ch <= 'Z'){
                countUpper++;
            }else if ('a'<=ch &&ch <= 'z'){
                countLower++;
            }else if ('0'<=ch &&ch <= '9'){
                countNumber++;
            }else{
                countOther++;
            }
        }
        System.out.println(countUpper);
        System.out.println(countLower);
        System.out.println(countNumber);
        System.out.println(countOther);
    }

 ==和equas、重写equals、hashCode、地址值

==对基本数据类型比较的是值,==对引用数据类型比较引用指针地址值。

equals在比较对象没有重新equals方法时等同于==,比较对象需重写equals和hashCode

.equals()如果比较双方一个常量—变量,推荐把常量字符串写在前面。        

com.kdy.domain
public class Person {
}
/*
 * 该类有属性,且重写了hashCode和equals方法,后期创建的对象只要这两个属性id和name相等,则这些对象的地址值和hash值都是相等的。
 * */
public class Person2 {
    private int id;
    private String name;
    public Person2() {
    }
    public Person2(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;//内存地址值相同返回true
        if (o == null || getClass() != o.getClass()) return false;//入参为空或入参和本类不是同一个类返回false
        Person2 person2 = (Person2) o;//入参和本类是同一个类,先把入参强转为本类
        return id == person2.id &&//当入参的对象的id和本类对象的id(基本类型)相等时 且 参的对象的name(引用类型)和本来的name相等时【要么地址值相等,要么根据String类型的equals判断相等】时 ,返回true
                Objects.equals(name, person2.name);//return (a == b) || (a != null && a.equals(b));
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);//将对象的属性id和name作为参数求hash作为该对象的hash码
    }
}
 com.kdy.test
public class Test {
    public static void main(String[] args) {
        /*
        * Person中未重写equals和hashCode,equals方法比较栈中地址值
        * */
        Person person11 = new Person();
        Person person12 = new Person();
        //toString:打印object父类的toString return getClass().getName() + "@" + Integer.toHexString(hashCode());
        System.out.println(person11);//直接打印的其实就是调用的toString()方法   com.kdy.domain.Person@1b6d3586
        System.out.println(person12);//直接打印的其实就是调用的toString()方法   com.kdy.domain.Person@4554617c
        System.out.println(person11.toString());//com.kdy.domain.Person@1b6d3586
        System.out.println(person12.toString());//com.kdy.domain.Person@4554617c
        System.out.println(person11.toString().equals(person12.toString()));//两个对象的toString不相等  false  比较的是String类型的重写的equals和hashCode
        System.out.println(person11.toString()==person12.toString());//false 比较的是String类型的地址值
        //地址值:打印两个对象的实际地址值 System.identityHashCode(obj)
        System.out.println(System.identityHashCode(person11));//460141958
        System.out.println(System.identityHashCode(person12));//1163157884
        System.out.println(person11==person12);//false :==比较地址值  460141958!=1163157884
        //hash值:打印两个对象的hash值:发现和地址值相等
        System.out.println(person11.hashCode());//460141958
        System.out.println(person12.hashCode());//1163157884
        System.out.println(person11.hashCode()==person12.hashCode());//false
        //因为hash值(等于地址值)不相等导致toString也不相等(未重写):因为object父类的toString return getClass().getName() + "@" + Integer.toHexString(hashCode());
        //未重写equals和hashCode 使用equals比较为false(即使两个对象的各个成员属性都一致)
        System.out.println(person11.equals(person12));//false
        /*
        * Person2中有id和name两个属性,且重写了hashCode和equals方法(地址值和hash值根据id和name确定,
        * id和name一样hash和地址值就都一样),即使id和name都不给值创建空对象,也是equals相等的。
        * */
        Person2 person21 = new Person2();
        Person2 person22 = new Person2();
        //toString:打印object父类的toString return getClass().getName() + "@" + Integer.toHexString(hashCode());
        System.out.println(person21);// 直接打印的其实就是调用的toString()方法   com.kdy.domain.Person2@3c1
        System.out.println(person22);//直接打印的其实就是调用的toString()方法   com.kdy.domain.Person2@3c1
        System.out.println(person21.toString());//com.kdy.domain.Person2@3c1
        System.out.println(person22.toString());//com.kdy.domain.Person2@3c1
        System.out.println(person21.toString().equals(person22.toString()));//两个对象的toString相等  true  比较的是String类型的重写的equals和hashCode
        System.out.println(person21.toString()==person22.toString());//false 比较的是String类型的地址值
        //地址值:打印两个对象的实际地址值 System.identityHashCode(obj)
        System.out.println(System.identityHashCode(person21));//1956725890
        System.out.println(System.identityHashCode(person22));//356573597
        System.out.println(person21==person22);//false :==比较地址值  956725890!=356573597
        //hash值:打印两个对象的hash值
        System.out.println(person21.hashCode());//961
        System.out.println(person22.hashCode());//961
        System.out.println(person21.hashCode()==person22.hashCode());//true
        //因为hash值相等导致toString也相等(未重写):因为object父类的toString return getClass().getName() + "@" + Integer.toHexString(hashCode());
        //equals方法:两个对象的类重写了equals方法,比较的是要么地址值相等返回true,要么两个对象的重写equals中的成员属性都相等就返回true
        System.out.println(person21.equals(person22));//true
    }
}
String类重写的equals和hashCode
public class Test {
    public static void main(String[] args) {
        /*
         * hashCode
         * 普通对象继承object重写了hashCode方法  460141958
         * String类重写了Object的hashCode方法,不过"通话"和"重地"的hash值重复
         * */
        Person person = new Person();
        System.out.println(person.hashCode());// 460141958
        System.out.println("通话".hashCode());//1179395
        System.out.println("重地".hashCode());//1179395
        /*
        * String的重写的hashCode:
        public int hashCode() {
            int h = hash;  //hash是int类型成员变量默认是0
            if (h == 0 && value.length > 0) {//为0,且当前String长度大于零
                char val[] = value;//val[]接收当前String字符串数组
    
                for (int i = 0; i < value.length; i++) {//进行迭代算出最终的h
                    h = 31 * h + val[i];
                }
                hash = h;//返回h
            }
            return h;
        }
        * */
        System.out.println(System.identityHashCode("通话"));//1163157884
        System.out.println(System.identityHashCode("重地"));//1956725890
        System.out.println("通话".hashCode()=="重地".hashCode());//true :哈希冲突了
        System.out.println("通话".equals("重地"));//false:使用的String的重写的equals方法
        /*
         * String的重写的equals:
        public boolean equals(Object anObject) {
            if (this == anObject) {  //地址值相同表示同一对象
                return true;
            }
            if (anObject instanceof String) {  //如果是String类型多态的再向下转型转成String类型,如果不是String类型地址值又不同直接返回false
                String anotherString = (String)anObject;
                int n = value.length;   //当前String对象的长度
                if (n == anotherString.value.length) {  //如果入参String对象的长度和当前对象长度不等,返回false,相等继续往下判断
                    char v1[] = value;
                    char v2[] = anotherString.value;
                    int i = 0;                 //然后就开始当前String和入参String都转成字符数组,一个个的匹对,匹对完也完全一致就返回true,有差错就返回false
                    while (n-- != 0) {
                        if (v1[i] != v2[i])
                            return false;
                        i++;
                    }
                    return true;
                }
            }
            return false;
        }
         * */
        System.out.println("通话"=="重地");//false:地址值不同,虽然他俩独特哈希冲突,但字符串常量池中还是不是一个地址
    }
}
String类重写的hashCode

 Static关键字

属于类的,所有对象共用一份

静态成员变量举例: 
com.kdy.domain下的
public class Student {
    private int id;
    private String name;
    private Integer age;
    public static String room;
    private static int idCounter = 0;//使用static实现id自增
    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
        this.id = ++idCounter;//使用static实现id自增
    }
    public Student() {
        this.id = ++idCounter;//使用static实现id自增
    }
    public int getId() {
        return id;
    }
}
com.kdy.test下的
public class JavaTest {
    public static void main(String[] args) {
        Student zhangsan = new Student("zhangsan", 20);
        zhangsan.room = "701";
        Student wangwu = new Student("wangwu", 20);
        System.out.println(wangwu.room);
        System.out.println(zhangsan.getId());
        System.out.println(wangwu.getId());
    }
}
静态方法举例
com.kdy.domain包
public class Student {
    private static int num;
    private int age;
    public void method(){
        System.out.println("成员方法");
    }
    public static void methodStatic(){
        methodStatic2();
        num=2;
        //method();//报错
        //age=2;//age报错
        System.out.println("静态方法");
    }
    public static void methodStatic2(){
    }
}
com.kdy.test包
public class JavaTest {
    public static void main(String[] args) {
        Student student = new Student();
        student.method();
        Student.methodStatic();
        Student.num=2;//静态方法中只能直接访问静态方法或变量,不能直接访问其他成员变量或成员方法
        methodOwnStatic();
        new JavaTest().methodOwn();
    }
    private static void methodOwnStatic(){
        System.out.println("自己的静态方法");
    }
    private void methodOwn(){
        System.out.println("自己的成员方法");
    }
}

静态方法中不能直接访问非静态的内容(成员方法成员变量),只能直接访问静态方法或变量

静态方法中不能使用this或super等表示对象的关键字。

存放位置:静态变量和类.class一起存放在方法区中。

静态代码块

第一次用到本类时,执行唯一的一次。

典型用途:一次性的为静态成员变量赋值。

package com.kdy.domain;
public class Person {
    static {
        System.out.println("本类的静态代码块执行...");
    }

    public Person() {
        System.out.println("无参构造执行...");
    }
}
package com.kdy.test;
public class JavaTest {
    public static void main(String[] args) {
        Person person = new Person();
        Person person2 = new Person();
    }
}
结果:
本类的静态代码块执行...
无参构造执行...
无参构造执行...

继承

父类(基类)、子类(派生类)

继承解决的是共性抽取问题。子类既可有父类内容,也可有自己专属内容。

继承是多态的前提。

一个子类只能继承一个父类,但可多级继承A->B->C。一个父类可被多个子类继承。

com.kdy.domain中Fu
public class Fu {
    public int num = 10;
    public int fuNum = 10;
    public void sameMethod(){
        System.out.println("父类重名方法");
    }
    public void fueMethod(){
        System.out.println("父类自己的方法");
    }
    public Fu() {
        System.out.println("父类构造执行");
    }
    public Fu(int num, int fuNum) {
        System.out.println("父类有参构造");
        this.num = num;
        this.fuNum = fuNum;
    }
    public Fu(int num) {
        System.out.println("父类有参构造");
        this.num = num;
    }
    public static void staticMethod(){
        System.out.println("父类静态方法");
    }
    protected Fu overrideMethod(){//子类可重写父类的成员方法
        return new Fu();
    }
}
com.kdy.domain中Zi
public class Zi extends Fu{
    public int num= 20;
    public int age= 20;
    public void sameMethod(){
        System.out.println("子类重名方法");
    }
    public void ziMethod(){
        int num = 30;
        System.out.println(num);
        System.out.println(this.num);
        this.sameMethod();//this关键字:访问本类成员变量、成员方法中访问另一个成员方法、构造方法中访问另一个构造方法
        System.out.println(super.num);
        super.fueMethod();//super关键字:调用父类成员变量、成员方法、构造方法
    }
    public Zi() {
        //super();//不写也是子类构造中先加载父类构造
        super(65);//如果子类构造中要写super,只能写一个。
        System.out.println("子类构造执行");
    }
    public Zi(int num) {
        this(32,23);
        this.num = num;
    }
    public Zi(int num, int age) {
        this.num = num;
        this.age = age;
    }
    /*
    * 重写父类方法:方法名和参数列表要完全一样。
    * 要求:
    * 子类重写的方法权限要大于父类被重写的方法权限,
    * 子类重写的方法的返回值类型要小于父类被重写方法的返回值类型。
    * 特点:
    * 调用时,创建子类对象,优先使用子类中的重写方法。
    * 设计原则: 对于已经投入使用的类,尽量不要进行修改。推荐定义一个新的类,来重复利用其中共性内容,并且添加改动新内容。
    * */
    @Override//注解可写可不写,用来检测的
    public Zi overrideMethod(){
        return new Zi();
    }
}
com.kdy.test中JavaTest
/*
 * 继承。
 * */
public class JavaTest {
    public static void main(String[] args) {
        /*
         * new子类时在堆中创建该子类对象的子类空间和父类空间,
         * 先构造父类对象(父类的属性和行为)放入父类空间,
         * 再构造子类对象(子类的属性和行为)放入子类空间
         * */
        Zi zi = new Zi();
        Fu zi2 = new Zi();//这里new对象是为了举例说明子类属性和父类属性重名的情况,看等号左面的类型决定
        System.out.println("=================================");
        /*
         * 若子类属性名和父类属性名重名,根据变量zi左侧类型决定用子类还是父类的属性
         * */
        System.out.println(zi.num);//输出子中的num为20
        System.out.println(zi2.num);//输出父中的num为10
        System.out.println(((Fu) new Zi()).num);//输出父中的num为10
        System.out.println(zi.fuNum);//输出父中的fuNum为10
        System.out.println(zi2.fuNum);//输出父中的fuNum为10
        /*
         * 若子类方法和父类方法重名,new的是什么对象,调用的就是谁的方法,只不过若子类没有再找父类方法
         * */
        zi.fueMethod();//父对象的方法
        zi2.fueMethod();//父对象的方法
        zi.sameMethod();//子对象的同名方法
        zi2.sameMethod();//子对象的同名方法
        new Fu().sameMethod();//父对象的同名方法
        System.out.println("=================================");
        zi.ziMethod();//子对象自己的方法,测试子类内容成员变量num和this.num和super.num的指定。
        Fu.staticMethod();//父类直接调用静态方法
        Zi.staticMethod();//子类调用父类静态方法
        System.out.println("=================================");
        /*
         * 测试重写方法
         * */
        System.out.println(zi.overrideMethod() instanceof Zi ? "zi":"fu");
//        System.out.println(new Fu().overrideMethod() instanceof Zi ? "zi":"fu");//父类的
//        System.out.println(zi2.overrideMethod() instanceof Zi ? "zi":"fu");//父类的
    }
}

使用继承写一个简单的发红包程序

com.kdy.domain
public class User {
    private String name;//姓名
    private int money;//余额

    public User(String name, int money) {
        this.name = name;
        this.money = money;
    }
    public User() {
    }
    
    public void show(){
        System.out.println(this.getMoney());
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getMoney() {
        return money;
    }
    public void setMoney(int money) {
        this.money = money;
    }
}
public class Manager extends User {
    public Manager() {
    }

    public Manager(String name, int money) {
        super(name, money);//有参构造,将manager的名称和余额存放在堆中该子对象的父对象父类空间中
    }

    public ArrayList<Integer> send(int totalMoney, int count) {
        ArrayList<Integer> redList = new ArrayList<>();
        int leftMoney = super.getMoney();//当前余额
        if (totalMoney > leftMoney) {
            System.out.println("余额不足");
            return redList;//空集合
        }
        super.setMoney(leftMoney - totalMoney);//扣钱
        int avg = totalMoney / count;
        int mod = totalMoney % count;//零头
        for (int i = 0; i < count-1; i++) {
            redList.add(avg);
        }
        int last = avg+mod;
        redList.add(last);//除不开的零头放最后一个红包中
        return redList;
    }
}
public class Member extends User {
    public Member() {
    }
    public Member(String name, int money) {
        super(name, money);//有参构造,将member的名称和余额存放在堆中该子对象的父对象父类空间中
    }
    public void receive(ArrayList<Integer> list){
        int index = new Random().nextInt(list.size());//随机从当前集合获取索引
        Integer del = list.remove(index);
        int money = super.getMoney();//当前member余额
        super.setMoney(money+del);//入账
    }
}
 com.kdy.test
/*
 * 继承:发红包案例
 * */
public class JavaTest {
    public static void main(String[] args) {
        Manager manager = new Manager("群主Name666", 100);
        Member one = new Member("成员A", 20);
        Member two = new Member("成员B", 10);
        Member three = new Member("成员C", 30);
        manager.show();
        one.show();
        two.show();
        three.show();
        System.out.println("=======================");
        ArrayList<Integer> redList = manager.send(20, 3);//扣钱、存入redList
        one.receive(redList);//收钱入账
        two.receive(redList);
        three.receive(redList);
        manager.show();
        one.show();
        two.show();
        three.show();
    }
}

 抽象类

抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。
抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可。

抽象类中不一定有抽象方法,可有成员方法、静态方法、构造方法(初始化父类成员使用)...

抽象方法的使用:不能new抽象类,必须有个子类继承这个抽象父类并重写所有抽象方法。

com.kdy.domain 
/*
* 抽象类就是普通类加上了个抽象方法,且不能直接new,子类可实现。
* */
public abstract class Animal {
    private String name;//成员变量
    public static int NUM =  3;//成员变量
    public void normalMethod(){
        System.out.println("成员方法...");//成员方法
    }
    public static void staticMethod(){
        System.out.println("静态方法...");//静态方法
    }
    public Animal() {
        System.out.println("构造方法");//构造方法
    }
    public abstract void eat();//抽象方法
}
public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}
com.kdy.test 
/*
 * 接口
 * */
public class JavaTest {
    public static void main(String[] args) {
        //Animal animal = new Animal();//抽象类不可直接new
        Cat cat = new Cat();
        cat.eat();//子类重写的方法
        cat.normalMethod();//调用父类成员方法
        Cat.staticMethod();//子类调用父类静态方法
    }
}

接口

接口是多个类的公共规范,是引用数据类型,编译后仍为.class文件。

接口:jdk7(常量、抽象方法)、jdk8(常量、抽象方法、默认方法、静态方法)、jdk9(常量、抽象方法、默认方法、静态方法,私有方法)。

接口常量:public static final修饰,可以不写即默认。一旦赋值,不可修改。

接口抽象方法:public abstract修饰,可不写即默认。

接口默认方法:public  default修饰,可省略public,且有方法体。

接口静态方法:public static,可省略public,且有方法体。

接口不能直接使用,必须由实现类来实现该接口并重写所有的抽象方法。如果实现类没有重写所实现接口的所有抽象方法,那么该实现类就是抽象类才可。

一个类只能继承一个父类,但可实现多接口。一个接口可继承多个接口。

1、实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。

2、实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类。
3、如果实现类锁实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写,且实现类也要带着default关键字。

4、一个类如果直接父类中的方法,和接口中默认方法产生了冲突,优先用父类当中的方法。

com.kdy.test
public interface MyInterface {
    int CONSTANT_NUM1 = 666;//接口常量,一旦赋值,不可修改
    public static final int CONSTANT_NUM2 = 666;

    void method1();
    public abstract void method2();

    default void defaultMethod(){
        System.out.println("默认方法");
    }
    public static void staticMethod(){
        System.out.println("静态方法");
    }
}
public class MyInterfaceImpl implements MyInterface{
    @Override
    public void method1() {
        System.out.println("实现接口重写方法1");
    }

    @Override
    public void method2() {
        System.out.println("实现接口重写方法2");
    }

    /*接口中默认方法也可被实现类重写*/
/*    @Override
    public void defaultMethod() {
        System.out.println("实现类中重写接口的默认方法");
    }*/
}
/*
 * 接口
 * */
public class JavaTest {
    public static void main(String[] args) {
        MyInterfaceImpl myInterfaceImpl = new MyInterfaceImpl();
        myInterfaceImpl.method1();//调用方法:new的哪个对象就调用谁的
        myInterfaceImpl.method2();
        /*调用方法:new的哪个对象就调用谁的。没有再向上找。(实现类中没有就向上找接口中的默认方法)。类似继承中的子类对象方法调用。*/
        myInterfaceImpl.defaultMethod();
        MyInterface.staticMethod();//接口静态方法
        System.out.println(MyInterface.CONSTANT_NUM1);//接口常量
    }
}

多态

父类引用指向子类对象:

格式:父类名 对象名 = new 子类名称();  或   接口名 对象名 = new 实现类名称();

若子类属性名和父类属性名重名,根据变量zi左侧类型决定用子类还是父类的属性。

若子类方法和父类方法重名,new的是什么对象,调用的就是谁的方法,只不过若子类没有再找父类方法。

多态写法等于向上转型(子转父)是安全的,向下转型(父转子)必须是父本来是该子类对象还原

com.kdy.domain
public class Animal {
}
public class Cat extends Animal {
    public void catMethod(){
        System.out.println("cat~");
    }
}
public class Dog extends Animal {
    public void dogMethod(){
        System.out.println("dogMethod~");
    }
}
com.kdy.test
public class JavaTest {
    public static void main(String[] args) {
        Animal animal = new Cat();
        if(animal instanceof Cat){
            Cat cat = (Cat)animal;
            cat.catMethod();
        }else if (animal instanceof Dog){
            Dog dog = (Dog)animal;
            dog.dogMethod();
        }
    }
}

接口加多态实现USB接口案例

定义USB接口,具备最基本的开启功能和关闭功能。鼠标和键盘要想能在电脑上使用,那么鼠标和键盘也必须遵守USB规范,实现USB接口,否则鼠标和键盘的生产出来也无法使用。

com.kdy.domain
public interface USB {
    public abstract void open();//打开设备
    public abstract void close();//关闭设备
}
public class Mouse implements USB {
    @Override
    public void open() {
        System.out.println("打开鼠标");
    }
    @Override
    public void close() {
        System.out.println("关闭鼠标");
    }
    public void click(){
        System.out.println("鼠标点击...");
    }
}
public class Keyboard implements USB{
    @Override
    public void open() {
        System.out.println("打开键盘");
    }

    @Override
    public void close() {
        System.out.println("关闭键盘");
    }
    public void type(){
        System.out.println("键盘按下...");
    }
}
public class Computer {
    public void powerOn(){
        System.out.println("电脑开机");
    }
    public void powerOff(){
        System.out.println("电脑关机");
    }
    //使用USB设备,接口作为参数,可传入相应实现类——多态写法
    public void userDevice(USB usb){
        usb.open();//打开设备
        if (usb instanceof Mouse){
            Mouse mouse = (Mouse)usb;
            mouse.click();
        }else if(usb instanceof Keyboard){
            Keyboard keyboard = (Keyboard)usb;
            keyboard.type();
        }
        usb.close();//关闭设备
    }
}
com.kdy.test 
public class JavaTest {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.powerOn();
        Mouse usbMouse = new Mouse();//这样写传入computer.userDevice(usbMouse)会自动转型
        computer.userDevice(usbMouse);
        USB usbKeyboard = new Keyboard();//这样手动转型多态写法后传入computer.userDevice(usbKeyboard)也可
        computer.userDevice(usbKeyboard);
        computer.powerOff();
    }
}

final关键字

修饰类,和其他类的区别是该类不可被继承,方法也就不可被重写。

修饰方法,该方法不可被覆盖重写,所以final和abstract不可共用。

修饰成员变量:

修饰的其实是栈中的值:对于基本数据类型,一次赋值终不可变;对于引用数据类型,栈中地址值不可改变但是其引用类型的普通成员仍可以通过set进行换数值。

修饰的成员变量必须定义时赋值或通过类构造方法赋值(二者选其一),且所有构造都要赋值,且必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值。

对final修饰成员变量举例:
com.kdy.domain
public class Phone {
    private String color;
    public Phone(String color) {
        this.color = color;
    }
    public Phone() {
    }
    public void setColor(String color) {
        this.color = color;
    }
    public String getColor() {
        return color;
    }
}
public class Person {
    public final static String staticNum = "hello666"; //常量
    public static String staticNum2 = "hello666"; //静态变量

    private final String name/*="zhangsan"*/;//成员变量:需定义时赋值或构造方法赋值,且重载的构造都要赋值
    private final Phone phone = new Phone("green");

    public Person() {
        this.name="zhangsan";
    }
    public Person(String name) {
        this.name=name;
    }
    public String getName() {//final成员变量只有get方法,无set方法
        return name;
    }
    public Phone getPhone() {//final成员变量只有get方法,无set方法
        return phone;
    }
}
com.kdy.test
public class JavaTest {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person.getName());
        System.out.println(person.getPhone().getColor());
        person.getPhone().setColor("yellow");
        System.out.println(person.getPhone().getColor());
        System.out.println(Person.staticNum);
        Person.staticNum2 = "hello777";
        System.out.println(Person.staticNum2);
    }
}

内部类——类中类

成员内部类、局部内部类(含匿名内部类)

演示如下:
com.kdy.domain
/*
* 外部类:public/缺省 class
* 成员内部类:public/protected/缺省/private class
* 局部内部类:缺省 class
* */
public class OutClass {
   public String sameNum = "outer member variable";//重名的成员变量
   private String outName;
   public String getOutName() {
      return outName;
   }
   public void setOutName(String outName) {
      this.outName = outName;
   }

   public class InnerClass{//外部类中的成员内部类
      public String sameNum = "outer member variable";//重名的成员变量
      private String innerName;
       public void innerMethod(){
          System.out.println("内部类成员方法");
          System.out.println("内部类成员方法直接调用外部类成员变量:"+outName);
       }
       /*如果内部类的成员变量和外部类的成员变量重名了*/
      public void innerMethod2(){
         String sameNum="local variable";//重名的成员变量
         System.out.println(sameNum);//方法局部变量
         System.out.println(this.sameNum);//内部类成员变量,这里的this就近原则指代该类对象
         System.out.println(OutClass.this.sameNum);//外部类的成员变量:外部类名.this.成员变量
      }
   }

   public void outMethod(){
      System.out.println("外部类成员方法");
      System.out.println("外部类成员方法调用内部类成员变量需要创建内部类对象:"+new InnerClass().innerName);
   }
   public void interInnerMethodByOutMethod(){//测试类中使用成员内部类的方式一:通过外部类的某个方法调用内部类方法
      new InnerClass().innerMethod();
   }

   public void outMethod2(){//外部类成员方法中的局部内部类
      int a  = 3;
      final int b = 5;
      int c  = 3;
      a=3;//二次变值了,局部内部类不能访问了
      class PartInner{//局部内部类:  只有当前所属的方法才能使用它,出了这个方法外面就不能用了。
         int num = 10 ;
         public void partInnerMethod(){
            System.out.println(num);
            /*局部内部类访问外部类的成员方法的局部变量,该局部变量必须时【有效的final,即不能二次变值】类型
            * 因为:方法变量在栈中,new的内容在堆中,方法出栈后变量消失,但堆中的内容还不会立马消失。
            * */
            //System.out.println(a);
            System.out.println(c);
            System.out.println(b);
         }
      }
      new PartInner().partInnerMethod();//调用
   }
   /*
   * 匿名内部类:可用于类或方法里
   * 如果接口的实现类或父类的子类只需使用唯一的一次,那么这种情况下可以不重新建个文件定义了,改用匿名内部类的形式
   * 接口名 对象名 = new 接口名(){//覆盖抽象方法}
   * */
   MyInterface obj = new MyInterface(){
      @Override
      public void method() {
         System.out.println("匿名内部类~");
      }
   };
   public void outMethod3() {//外部类成员方法中的匿名内部类
      obj.method();
      MyInterface obj = new MyInterface(){
         @Override
         public void method() {
            System.out.println("匿名内部类~");
         }
      };
      obj.method();
      new MyInterface(){//匿名内部类省略对象名就变成了匿名对象,只能在方法中这样写。
         @Override
         public void method() {
            System.out.println("匿名对象~");
         }
      }.method();
   }
}
public interface MyInterface {
    void method();
}
com.kdy.test
public class JavaTest {
    public static void main(String[] args) {
        OutClass outClass = new OutClass();
        outClass.interInnerMethodByOutMethod();//测试类中使用成员内部类的方式一:通过外部类的某个方法调用内部类方法
        System.out.println("==========================");
        //测试类中使用成员内部类的方式二:使用公式
        OutClass.InnerClass innerClass = new OutClass().new InnerClass();
        innerClass.innerMethod();
    }
}

 匿名对象(不是匿名内部类)

com.kdy.domian包下:
public class Person {
    public String name ;
    public void showName(){
        System.out.println(this.name);
    }
    public void hello(){
        System.out.println("hello~");
    }
}
com.kdy.test包下:
/*
 *匿名对象:确定要用到某个对象且仅使用一次
 * */
public class JavaTest {
    public static void main(String[] args) {
        Person person = new Person();
        person.name="zhangsan";
        person.showName();
        new Person().hello();
    }
}

类或接口作其他类成员变量

com.kdy.domain
public class Person {
    private String name;
    private int age;
    private Phone phone;
    private Skill skill;
    public Person() {
    }
    public Person(String name, int age, Phone phone) {
        this.name = name;
        this.age = age;
        this.phone = phone;
    }
    public Person(String name, int age, Phone phone,Skill skill) {
        this.name = name;
        this.age = age;
        this.phone = phone;
        this.skill = skill;
    }
    public void show(){//表演才艺
        skill.show();
    }
}
public class Phone {
    private String color;
    public Phone() {
    }
    public Phone(String color) {
        this.color = color;
    }
}
public interface Skill {
    void show();//表演才艺的抽象方法
}
com.kdy.test 
public class JavaTest {
    public static void main(String[] args) {
        Person person = new Person("zhangsan",20,new Phone("yellow"));
        Person person2 = new Person("zhangsan", 20, new Phone("yellow"), new Skill() {
            @Override
            public void show() {
                System.out.println("给大家唱个歌吧");
            }
        });
        person2.show();
    }
}

接口作为方法的参数或返回值

public class JavaTest {
    public static List<String> addNames(List<String> list){
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        list.add("eee");
        return list;
    }
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list = JavaTest.addNames(list);
        System.out.println(list);

    }
}

toString

public class JavaTest {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person);//直接打印对象名其实就是打印的对象的toString方法
        System.out.println(person.toString());//因为Person没有重写toString方法,调用的是其最终父类Object的toString方法return getClass().getName() + "@" + Integer.toHexString(hashCode())
        /*看一个类是否重写了toString,直接打印这个对象即可,没重写的话打印的是地址值*/
    }
}

时间日期类

public class JavaTest {
    public static void main(String[] args) throws ParseException {
        Date date = new Date();//打印系统当前时间 Fri Jul 21 19:07:58 CST 2023
        System.out.println(date);
        System.out.println(date.getTime());//getTime()打印的是系统当前时间距离元年的毫秒数  1689937742453
        Date date1 = new Date(0L);//有参的date对象,将参数转化成距离元年的时间日期  Thu Jan 01 08:00:00 CST 1970
        System.out.println(date1);

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat sdf2= new SimpleDateFormat("yyyy-MM-dd HH时mm分ss秒");
        String dateStr = sdf.format(date);
        String dateStr2 = sdf2.format(date);//String转date
        System.out.println(dateStr);//2023-07-21
        System.out.println(dateStr2);//2023-07-21 19时11分20秒
        Date date2 = sdf2.parse(dateStr2);//Date转String
        System.out.println(date2);//Fri Jul 21 19:12:16 CST 2023

        Calendar calendar = Calendar.getInstance();
        System.out.println(calendar.get(Calendar.YEAR)+"--"+calendar.get(Calendar.MONTH));//月份返回西方月份0-11,而不是东方月份1-12.
        calendar.set(Calendar.YEAR,9999);
        calendar.set(Calendar.MONTH,9);
        Date date3 = calendar.getTime();//Calendar转Date
        System.out.println(date3);
        calendar.setTime(date);//Date转Calendar
        System.out.println(calendar.getTime());
        Instant instant = date.toInstant();//Date转Instant
        System.out.println(instant);//2023-07-21T12:05:32.956Z
        Date date4 = Date.from(instant);//Instant转Date
        System.out.println(date4);//Fri Jul 21 20:06:43 CST 2023

        long currentTimeMillis = System.currentTimeMillis();
        System.out.println(currentTimeMillis);//1689941385742
        Date date5 = new Date(currentTimeMillis);
        System.out.println(date5);//Fri Jul 21 20:09:15 CST 2023
    }
}

System.arrayCopy()、Arrays.copyOf

public class JavaTest {
    public static void main(String[] args) throws ParseException {
        int [] a = {1,2,3,4,5,6,7};
        int [] b = {11,12,13,14,15,16,17};
        int [] c = {11,12,13,14,15,16,17};
        //将a数组复制从下标1开始持续3特元素到b数组的下标1的位置
        System.arraycopy(a,1,b,1,3);
        System.out.println(Arrays.toString(b));//[11, 2, 3, 4, 15, 16, 17]
        int[] d = Arrays.copyOf(a, 3);//复制a数组的前3个元素到新数组
        int[] e = Arrays.copyOfRange(a, 3,5);//复制a数组的前下标3到5的元素到新数组,包含from的下标,不包含to的下标
        System.out.println(Arrays.toString(d));//[1, 2, 3]
        System.out.println(Arrays.toString(e));//[4, 5]
    }
}

StringBuilder

长度可变的字符串,在内存中是一个数组,占用内存小,操作效率高:比如进行简单的字符串拼接时就比string内存空间少,操作效率高。

String创建后变成final修饰的常量不可改,进行字符串拼接时占空间效率也低:如String  s = "a"+"b"+"c";就会先a+b成ab,后ab+c成c。

StringBuilder底层不被final修饰的长度可变的数组,占空间少、效率高、可扩容。

public class JavaTest {
    public static void main(String[] args) throws ParseException {
        StringBuilder bu1 = new StringBuilder();
        StringBuilder bu2 = new StringBuilder("abc");
        System.out.println(bu1);//""
        System.out.println(bu2);//"abc"
        bu1.append("abc").append(1).append(true).append(8.8).append("中");
        System.out.println("abc".toUpperCase().toLowerCase().toUpperCase().toLowerCase());//链式编程:返回值是一个对象可继续调用其方法
        System.out.println(bu1);//abc1true8.8中
        String str = bu1.toString();//StringBuilder转String
        System.out.println(str);//abc1true8.8中
    }
}

String与基本数据类型的转换

public class JavaTest {
    public static void main(String[] args) throws ParseException {
        byte byte1 = 50;
        short short1 = 200;
        int int1 = 500;
        long long1 = 200092233720368547L;//long类型必须加L,不然默认是int类型只不过向上转型成long不报错,但当超出int长度且小于long长度会报错,加上L指定定位long类型
        float float1 = 500.005f;//float类型必须加上f结尾,如5就会默认为int,5.5就会默认为double,向下转型成float会报错加上f指定定位float类型
        double double1 = 777.99965;
        char char1 = 97;//或写成char char1 = 'a';
        byte[] bytes1 = {97,65,48};
        System.out.println(new String(bytes1));//aA0  byte数组转String   对char数组也适用
        System.out.println(new String(bytes1,0,3));//aA0    new String(byte数组 , 起始索引 , 截取几个);   对char数组也适用
        boolean boolean1 = false;
        System.out.println(byte1+"");//50
        System.out.println(short1+"");//200
        System.out.println(int1+"");//500
        System.out.println(long1+"");//200092233720368547
        System.out.println(float1+"");//500.005
        System.out.println(double1+"");//777.99965
        System.out.println(char1+"");//a
        System.out.println(boolean1+"");//false
        System.out.println("===================================");//封装类toString方法:基本数据类型转String
        //打印结果同上
        System.out.println(Byte.toString(byte1));
        System.out.println(Short.toString(short1));
        System.out.println(Integer.toString(int1));
        System.out.println(Long.toString(long1));
        System.out.println(Float.toString(float1));
        System.out.println(Double.toString(double1));
        System.out.println(Character.toString(char1));
        System.out.println(Boolean.toString(boolean1));
        System.out.println("===================================");//封装类的parseXxx方法:String转基本数据类型
        System.out.println(Arrays.toString("好".getBytes()));//[-27, -91, -67]utf-8中一个中文汉字翻译为3个字节   对char数组也适用getChars
        System.out.println(Byte.parseByte(Byte.toString(byte1))+12);//62
        System.out.println(Short.parseShort(Short.toString(short1))+12);//212
        System.out.println(Integer.parseInt(Integer.toString(int1))+12);//512
        System.out.println(Long.parseLong(Long.toString(long1))+12);//200092233720368559
        System.out.println(Float.parseFloat(Float.toString(float1))+12.5f);//512.505
        System.out.println(Double.parseDouble(Double.toString(double1))+12.56);//790.5596499999999
        System.out.println((Character.toString(char1)).charAt(0)+2);//99
        System.out.println(!Boolean.parseBoolean(Boolean.toString(boolean1)));//true
    }
}

Debug

打断点,debug

F8:逐行执行

F7:进入方法           shift+F8:跳出方法

F9:跳到下一断点        

ctrl+F2:退出debug结束运行           点击Console切换到控制台

异常

程序的非正常导致JVM非正常停止。

Java中异常是一个类,产生异常就是创建异常对象并抛出,JVM处理异常方式为中断处理。

Throwable:异常根类

Error:继承Throwable

Exception:继承Throwable,包含编译期异常(写代码时的报错)和运行期异常

RuntimeException继承了Exception,java程序运行过程中的问题

com.kdy.test
public class JavaTest {
    public static void main(String[] args) throws ParseException {
        m1();//使用try-catch处理完异常的直接调用,catch后的代码可正常执行
        //m2();//使用throw如果不进行try-catch的话只能一直向上抛异常,抛给main方法,交给JVM进行处理,即使main方法没有抛出的异常,运行期间报错也会交给JVM中断处理
        //getElement(null, 3);//运行期异常,可用这里不用处理,交给JVM打印中断处理
        //readFile("c:\\b.txt");//调用的该方法抛出了译期异常,需要main方法继续抛出给JVM或try-catch
        try {
            readFile("d://d.txt");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            StringWriter sw = new StringWriter();
            e.printStackTrace(new PrintWriter(sw, true));//拿到异常信息到StringWriter
            String str = sw.toString();
            System.out.println(str);
        }
        m3();//多个异常一次捕获,多次处理
        m4();//多个异常一次捕获,一次处理
    }
    
    /*
     * 使用try-catch捕获处理掉异常
     * */
    public static void m1() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            Date date = sdf.parse("1999-0909");//可能出现异常的代码
        } catch (ParseException e) {
            e.printStackTrace();//异常处理的逻辑
        }
        System.out.println("后续代码1");
        int[] a = {1, 2, 3};
        try {
            int a3 = a[3];
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();//异常处理的逻辑
        }
        System.out.println("后续代码2");
    }
    /*
     * 使用throws向上抛,交由方法的调用者处理,如这里的main方法,如果main当方法中没有进一步try-catch,就会交由JVM进行打印中断处理
     * */
    public static void m2() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date date = sdf.parse("1999-0909");
        System.out.println("后续代码");
    }
    /*
     * throw来抛出一个异常
     * throw new Exception("产生异常的原因")
     * 必须写在方法内部,throw的对象必须时Exception或其子类
     * throw抛出异常后,我们必须进行处理:若是RunTimeException获取子对象,我们可用交由JVM处理进行中断打印异常信息
     * 若是编译器异常,我们需要通过try-catch或throws继续向上抛
     * */
    //如下面这个NullPointerException是运行期异常,可用不用处理:
    public static int getElement(int[] arr, int index) throws ParseException {
        if (arr == null) {
            throw new NullPointerException("传递的数组为null");
        } else {
            return arr[index];
        }
    }
    //再如FileNotFoundException是编译器异常,必须处理才能通过编译
    public static void readFile(String fileName) throws FileNotFoundException {
        if (!"c:\\a.txt".equals(fileName)) {
            throw new FileNotFoundException("传递的文件路径不是c:\\a.txt");
        }
    }

    /*
     *多个异常一次捕获,多次处理
     * 注意:当存在异常子父类关系时,父类异常写在子类异常下面(原因:抛出的异常对象,会从上到下依次赋值给catch中定义的异常变量)
     * */
    public static void m3() {
        try {
            int[] arr = {1, 2, 3};
            System.out.println(arr[3]);//数组下标越界ArrayIndexOutOfBoundsException
            List<Integer> list = Arrays.asList(1, 2, 3);
            System.out.println(list.get(3));//数组下标越界ArrayIndexOutOfBoundsException
            ArrayList<String> arrayList = new ArrayList<>();
            arrayList.add("1");
            arrayList.add("2");
            arrayList.add("3");
            System.out.println(arrayList.get(3));//索引越界异常IndexOutOfBoundsException
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println(e);
            e.printStackTrace();
        } catch (IndexOutOfBoundsException e) {
            System.out.println(e);
            e.printStackTrace();
        }
    }
    /*
     *多个异常一次捕获,一次处理
     * //final中不管抛没抛异常,都会执行到,避免在这里写return。
     * */
    public static void m4() {
        try {
            int[] arr = {1, 2, 3};
            System.out.println(arr[3]);//数组下标越界ArrayIndexOutOfBoundsException
            List<Integer> list = Arrays.asList(1, 2, 3);
            System.out.println(list.get(3));//数组下标越界ArrayIndexOutOfBoundsException
            ArrayList<String> arrayList = new ArrayList<>();
            arrayList.add("1");
            arrayList.add("2");
            arrayList.add("3");
            System.out.println(arrayList.get(3));//索引越界异常IndexOutOfBoundsException
        } catch (Exception e) {
            System.out.println(e);
            e.printStackTrace();
        }finally {
            //final中不管抛没抛异常,都会执行到,避免在这里写return。
        }
    }

    /*
    *继承时的异常
    *父类中定义的方法且定义throw异常,其子类在继承父类重写父类方法时,要抛出父类相同异常或子类异常或不抛出异常
    * */
}
com.kdy.domain
/*
 * 如果父类中定义的方法且定义throw异常,其子类在继承父类重写父类方法时,要抛出父类相同异常或子类异常或不抛出异常
 * 父类成员方法没有throw异常,子类也不能抛出异常,只能try-catch
 * 原因:多态写法时,向下转型可顺利进行
 * */
public class Fu {
    public void show1() throws NullPointerException, ClassCastException {}
    public void show2() throws IndexOutOfBoundsException {}
    public void show3() throws IndexOutOfBoundsException {}
    public void show4() {}
}
class Zi extends Fu {
    //子类重写父类方法时,抛出和父类相同的异常
    @Override
    public void show1() throws NullPointerException, ClassCastException {}
    //子类重写父类方法时,抛出和父类异常的子类
    @Override
    public void show2() throws ArrayIndexOutOfBoundsException {}
    //子类重写父类方法时,不抛出异常
    @Override
    public void show3() {}
    //父类成员方法没有throw异常,子类也不能抛出异常,只能try-catch
    @Override
    public void show4() {
        try {
            throw new Exception("编译期异常");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
com.kdy.exception
/*
* 自定义异常类:必须继承Exception或RunTimeException,且必须重写有参无参构造,命名一般以Exception结尾
* 继承Exception编译期异常必须处理,继承RunTimeException运行期异常,可不用处理交由JVM
* */
public class RegisterException extends Exception{
    public RegisterException() {
        //super();//super()可省略不写,也是先调父类构造
    }
    public RegisterException(String message) {//所有异常类一般都有这个有参构造,调用父类异常信息构造,交由父类处理这个异常信息
        super(message);
    }
}

class Test{
    static String [] usernames = {"张三","李四","王五"};
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入要注册的用户名:");
        String username = sc.next();
        checkUsername(username);
    }
    public static void checkUsername(String username){
        for (String name : usernames ){
            if (name.equals(username)){
//                throw new RegisterException("该用户名已被注册");//通过方法throws出
                try {
                    throw new RegisterException("该用户名已被注册");//或通过try-catch自己处理
                } catch (RegisterException e) {
                    e.printStackTrace();
                    return;//自己处理后,要实现业务效果需要这里直接结束方法。
                }
            }
        }
        System.out.println("注册成功");
    }
}

多线程

并发与并行

并发︰指两个或多个事件在同一个时间段内发生,快速交替进行。
并行∶指两个或多个事件在同一时刻发生(同时发生)。

线程与进程

进程︰是指一个内存中运行的应用程序(如运行QQ.exe时qq进程就进入到内存了,在任务管理器中可看到,结束进程就把该程序占用的内存强制清空了),每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程∶线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。线程是操作系统计算的最小单位
简而言之︰一个程序运行后至少有一个进程,一个进程中可以包含多个线程

cpu核数和线程数、多线程

cpu可指挥电脑中软硬件干活,4核8线程的cpu能同时进行8个线程任务。最小的执行单元线程任务。单核单线程的cpu如果用户点击电脑很多程序一起运行cpu就会在这些进程的非常多的线程任务之间以1/n毫秒的速度高速切换。4核8线程就会提高8倍的效率。

举例如果用户使用360管家,这就是开启一个进程,进入到内存中,同时执行病毒查杀、清理垃圾和电脑加速的话,就是同时执行了三个线程,单核单线程的cpu会高速切换处理,4核8线程的cpu效率更高。

多线程的好处:效率高,各线程之间互不影响,可让cpu的使用率更高。

线程调度

分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU 的时间。

抢占式调度:优先让优先级高的线程使用CPU,如果线释的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

可在任务管理器-详细信息中设置现成的优先级。

main线程

JVM执行main方法时的主线程。

单线程

单线程异常JVM中断处理后,异常代码后面代码无法继续执行,

创建多线程——继承Thread类
/*
* 实现多线程方式一:继承Thread类并重写run方法
* 使用该子类对象的start方法开启线程
* 多个线程有自己独立占空间,互不影响
* 所有的线程对象都必须是Thread类或其子类的实例
* */
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread线程getName:"+getName());//当前线程名   MyThread线程getName:Thread-0  。setName后:MyThread线程getName:线程666
        System.out.println("MyThread线程:"+Thread.currentThread().getName());//获取当前线程名  MyThread线程:线程666
        for (int i = 0; i < 20; i++) {
            System.out.println("run:"+i);
        }
    }
    public MyThread() {//默认super();
    }
    public MyThread(String name) {
        super(name);
    }
}
class Test{
    public static void main(String[] args) {
        System.out.println("main线程:"+Thread.currentThread());//获取当前线程名    main线程:Thread[main,5,main]
        MyThread myThreadWithName = new MyThread("线程777");//需要在MyThread中重写有参构造调用父类有参构造
        System.out.println(myThreadWithName.getName());//线程777
        MyThread myThread = new MyThread();
        System.out.println("myThread线程getName:"+myThread.getName());//当前线程名   myThread线程getName:Thread-0
        myThread.setName("线程666");
        System.out.println(myThread.getName());//线程666
        myThread.start();//开启的Thread子类重写run方法的线程
        for (int i = 0; i < 20; i++) {//main线程
            System.out.println("main:"+i);
        }
        /*结果:两个线程抢夺cpu执行时间,结果就看到是交替进行
        * main:0 main:1 main:2 main:3main:4main:5main:6main:7main:8main:9run:0run:1run:2run:3run:4run:5run:6run:7run:8run:9run:10run:11run:12run:13run:14run:15run:16run:17run:18run:19main:10main:11main:12main:13main:14main:15main:16main:17main:18main:19
        * */
        /*线程休眠*/
        try {
            Thread.sleep(5000);//线程休眠5000毫秒——5秒种
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main线程休眠后的代码");
    }
}
创建多线程——实现Runnable接口
/*
* 实现多线程方式二:实现Runnable接口,并实现里面的run方法
* 通过创建该Runnable接口实现类对象作为创建Thread对象的参数,再调用Thread对象的start即可
* Runnable接口创建线程的好处,可以继续继承其他的父类
* */
public class RunnableImpl  implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
class Test6{
    public static void main(String[] args) {
        RunnableImpl runnable = new RunnableImpl();
        Thread thread = new Thread(runnable);
        thread.start();
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }

        /*
        * 匿名内部类创建线程 new 父类
        * */
        new Thread(){   //省略了对象名,其实也可称为匿名对象
            @Override
            public void run(){
                System.out.println("匿名内部类线程new父类");
            }
        }.start();
        /*
         * 匿名内部类创建线程 new 接口
         * */
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类线程new接口");
            }
        };
        new Thread(r).start();
    }
}

线程安全问题

多个线程访问共享数据会出现线程安全问题。

public class TicketThreadRunnableImpl implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票...");
                ticket--;
            }
        }
    }
}
/*
* 多个线程t1、t2、t3访问共享资源r中的成员属性时,就会发生线程安全问题
* */
class Test666{
    public static void main(String[] args) {
        TicketThreadRunnableImpl r = new TicketThreadRunnableImpl();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.start();
        t2.start();
        t3.start();
        //出现了重复卖票,无顺序(当切换到另一个线程开始买卖票,卖的第一张都比上一个被切换的线程的最后一张序号大,)
    }
}
解决线程安全问题

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

/*
* 使用同步代码块
* 1.通过代码块中的锁对象,可以使用任意的对象。2.但是必须保证多个线程使用的锁对象是同一个。3.锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行
* 同步代码块中的内容将被锁住,拿到锁的线程若还没执行完同步代码块中的内容被其他线程抢占了cpu,其他线程没有锁也不能执行只能最终cu有把执行权给了有锁对象的线程去执行,等待其执行完同步代码块中的内容后释放锁对象,其他线程接着抢锁对象。
* */
public class TicketThreadRunnableImpl implements Runnable {
    Object obj = new Object();//创建锁对象
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            synchronized (obj) {//使用同步代码块
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票...");
                    ticket--;
                }
            }
        }
    }
}
/*
* 同步代码块解决线程安全问题
* */
class Test666{
    public static void main(String[] args) {
        TicketThreadRunnableImpl r = new TicketThreadRunnableImpl();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.start();
        t2.start();
        t3.start();
    }
}
/*
 * 使用同步方法
 * 1.把访问了共享数据的代码抽取出来,放到一个方法中。2.在方法上添加synchronized修饰符。
 * 同步方法的锁对象是这个线程的实现类,也就是this.
 * */
public class TicketThreadRunnableImpl implements Runnable {
    private static int ticketStatic = 100;
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            payTicket();
        }
    }
    public synchronized void payTicket() {//同步方法的锁对象是这个线程的实现类,也就是this.
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票...");
            ticket--;
        }
    }
    public synchronized static void payTicket2() {//静态同步方法的锁对象是本类的class文件对象.
        if (ticketStatic > 0) {
            System.out.println(Thread.currentThread().getName() + "正在卖第" + ticketStatic + "张票...");
            ticketStatic--;
        }
    }
}
/*
 * 同步方法解决线程安全问题
 * */
class Test666 {
    public static void main(String[] args) {
        TicketThreadRunnableImpl r = new TicketThreadRunnableImpl();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.start();
        t2.start();
        t3.start();
    }
}
/*
 * 使用Lock锁
 * 1.在成员位置创建一个ReentrantLock对象。2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁。3.在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
 * */
public class TicketThreadRunnableImpl implements Runnable {
    private int ticket = 100;
    Lock l = new ReentrantLock();//在成员位置创建一个ReentrantLock对象
    @Override
    public void run() {
        while (true) {
            l.lock();//在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
            if (ticket > 0) {
                try {
                    Thread.sleep(10);//提高安全问题出现的概率,让程序睡眠
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票...");
                ticket--;
            }
            l.unlock();//在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
        }
    }
}
/*
 * Lock锁解决线程安全问题
 * */
class Test666 {
    public static void main(String[] args) {
        TicketThreadRunnableImpl r = new TicketThreadRunnableImpl();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.start();
        t2.start();
        t3.start();
    }
}

线程间通信

多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。

比如∶线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。

为什么要处理线程间通信:

多个线程并发执行时,在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。

如何保证线程间通信有效利用资源:

多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变星的使用或操作。就是多个线程在操作同一份数据时,避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即――等待唤醒机制。wait/notify就是线程间的一种协作机制。

wait/notify:

就是在一个线程进行了规定操作后,就进入等待状态( wait()),等待其他线程执行完他们的指定代码过后再将其唤醒( notify() );在有多个线程进行等待时,如果需要,可以使用notifyAll()来唤醒所有的等待线程。

注意:线程被唤醒后,还需要持有锁才能执行。且wait()与notify()必须是一个锁对象才可。

 注:

阻塞状态:具有cpu的执行资格,等待cpu空闲时执行

休眠状态:放弃cpu的执行资格,cpu空闲,也不执行

挂起和阻塞不同:

阻塞仍在内存,挂起换出到磁盘中了。
阻塞一般等待资源(IO、CPU、信号量)时发生,挂起一般是由于用户或系统的需要。
阻塞在资源得到时(如获取了锁)后会就绪,挂起一般在时机合适时主动激活。

线程通信案例--包子铺
public class JavaTest {
    public static void main(String[] args) throws ParseException {
        Object obj = new Object();
        new Thread(){
            @Override
            public void run(){//顾客线程
                while (true){
                    synchronized (obj){
                        System.out.println("顾客线程告知老板线程需要的包子和数量");
                        try{
                            obj.wait();//等待老板线程做包子,中断等待老板线程做好包子后唤醒继续后续代码
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                        System.out.println("顾客线程等到老板线程做好包子的通知唤醒,开始吃包子");
                    }
/*                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }*/
                }
            }
        }.start();
        new Thread(){
            @Override
            public void run(){//老板线程
                while (true){
                    try {
                        Thread.sleep(3000);//花3秒中做包子
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj){
                        System.out.println("老板线程做好包子了,唤醒顾客线程,通知顾客开始吃了");
                        obj.notify();//唤醒obj锁对象中被锁住的线程,即上面的顾客线程之前调用了obj.wait()现在唤醒的是它
                    }
                }
            }
        }.start();
        /*
        * 这个基础案例,是先顾客线程wait(只能notify唤醒,cpu即使空闲也执行不到),wait时释放锁,老板线程拿到锁和cpu执行权后
        * 老板线程先sleep(只能等到计时结束,即使cpu空闲也不执行),sleep3秒后,再notify唤醒顾客线程,顾客线程被唤醒后需等待老板线程的synchronized执行完释放锁后,
        * 顾客线程执行后面打印语句,顾客线程synchronized方法执行完后,释放锁资源。这时顾客线程和老板线程随机抢占锁资源和cpu执行权,
        * 但老板线程抢到cpu后先sleep3秒再抢锁,就都让给了顾客线程锁和cpu执行权。
        * */
    }
}

wait会释放锁,synchronized结束后也会释放锁,sleep不会释放锁仅仅休眠一些时间,notify唤醒其他线程也需要拿到锁后才能执行。

包子铺案例升级版
com.kdy.domain
//仅作为锁对象和提供当前包子的信息和状态的工具对象使用,并不是实际包子个数
public class BaoZi {
    String pi;
    String xian;
    boolean flag = false;//包子状态
}
public class BaoZiPu  extends Thread{
    private BaoZi bz;
    public BaoZiPu(BaoZi bz) {
        this.bz = bz;
    }
    @Override
    public void run(){
        int count = 0;
        while (true){
            synchronized (bz){//同步代码块
                if (bz.flag==true){//如果 有包子,包子铺线程就使用包子对象作为锁进行wait
                    try {
                        bz.wait();//释放锁,等待其他线程拿到锁后执行
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }//吃货线程把bz的状态改为false,且把本线程唤醒了,从wait的地方也就是这里继续往下执行
                if (count%2==0){//赋值一些信息
                    bz.pi="薄皮";
                    bz.xian="三鲜馅";
                }else{
                    bz.pi="冰皮";
                    bz.xian="猪肉大葱馅";
                }
                count++;
                System.out.println("包子铺正在生成:"+bz.pi+bz.xian+"的包子");
                try {
                    Thread.sleep(3000);//本线程生成包子时先sleep3秒钟,这时吃货线程还是wait没有被唤醒就不执行,本线程计时结束本线程方可继续执行
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                bz.flag=true;//包子铺生成好后,修改bz信息状态为true,且唤醒正在等待的吃货线程
                bz.notify();//唤醒bz对象作为锁的此时正在wait的线程,目前是吃货线程刚刚bz.wait了.等待该synchronized结束释放锁后,被唤醒的线程抢到锁后就可以执行了。
                System.out.println("包子铺生产好了:"+bz.pi+bz.xian+"的包子,吃货可以吃了");
            }
        }
    }
}
public class ChiHuo extends Thread {
    private BaoZi bz;
    public ChiHuo(BaoZi bz) {
        this.bz = bz;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (bz) {//同步代码块
                if (bz.flag == false) {
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {//如果包子铺判断的包子状态为true,包子铺wait了,本吃货线程就可拿到锁后执行到该else了
                    System.out.println("吃货正在吃:" + bz.pi + bz.xian + "的包子");
                    bz.flag = false;
                    bz.notify();//吃完了,修改bz状态为false,唤醒包子铺线程,本synchronized执行完就会释放锁给被唤醒的包子铺线程执行了
                    System.out.println("吃货已吃完" + bz.pi + bz.xian + "的包子,包子铺开始生成包子");
                    System.out.println("----------------------------");
                }
            }
        }
    }

}
com.kdy.test
public class JavaTest {
    public static void main(String[] args) throws ParseException {
        BaoZi bz = new BaoZi();
        new BaoZiPu(bz).start();
        new ChiHuo(bz).start();
    }
}

线程池

线程池其实就是底层为集合的容器。线程池中可用有多个线程,以便任务队列中有多个任务时,可从这线程池获得线程对象,执行任务。

任务队列中任务太多了需要等待,任务队列中执行完任务的线程会回归到线程池,任务队列中其他等待的任务再从线程池中获取线程对象执行任务。

合理利用线程池能够带来三个好处:

1.降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

⒉.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3.提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

com.kdy.domain
public class RunnableImpl  implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
com.kdy.test
public class JavaTest {
    public static void main(String[] args) throws ParseException {
        ExecutorService es = Executors.newFixedThreadPool(3);//创建执行线程数位3的线程池
        //线程池中初始默认的线程名命名为:pool-1-thread-1    pool-1-thread-2    pool-1-thread-3
        es.submit(new RunnableImpl());//创建一个新的线程执行
        //线程池会一直开启,使用完了的线程会归还给线程池,线程池可继续使用
        es.submit(new RunnableImpl());//创建一个新的线程执行
        es.submit(new RunnableImpl());//创建一个新的线程执行
        es.submit(new RunnableImpl());//创建一个新的线程执行
        es.submit(new RunnableImpl());//创建一个新的线程执行
        es.submit(new RunnableImpl());//创建一个新的线程执行
        //销毁线程池(不建议使用)
        es.shutdown();
    }
}

lambda表达式(λ)

面向对象的思想:
做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情。

函数式编程思想:
只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程。

使用前提:

使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。

方法的参数或局部变量类型必须为Lambda对应的接口类型。

com.kdy.domain
public class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public int getAge() {
        return age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
com.kdy.test
/*
* lambda表达式   参数()  箭头->  一段代码{}  :
* 参数为抽象方法的参数列表,有就写,逗号分开,没有就空着。
* 箭头:将参数传给方法体
* {}重写的方法体。
* */
public class JavaTest {
    public static void main(String[] args) throws ParseException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"线程创建了");
            }
        }).start();
        //使用lambda简化1
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"线程创建了");
        }).start();
        //使用lambda简化2  无参  无返  方法体中只有一句   还可更简
        new Thread(()-> System.out.println(Thread.currentThread().getName()+"线程创建了") ).start();
        /*
         * Arrays.sort或Collections.sort如果是对引用类型排序,方法一是引用类型实现Comparable接口,方法二是sort方法中传入Comparator匿名内部类
         * */
        Person [] arr  = {
                new Person("aaa",21),
                new Person("bbb",18),
                new Person("ccc",19)
        };
/*        Arrays.sort(arr, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });*/
        //lambda简化
        Arrays.sort(arr, (Person o1, Person o2)-> {
                return o1.getAge()-o2.getAge();
        });
        //进一步简化
        Arrays.sort(arr, (Person o1, Person o2)->  o1.getAge()-o2.getAge());//方法体中只有一行代码,省略大括号和return关键字
        System.out.println(Arrays.asList(arr));
    }
}
自定义符合lambda规范的接口
public interface Cook {
    String makeFood(String food);//接口唯一抽象方法且带参数
}
class Test678{
    public static void main(String[] args) {
        String str = invokeCook("apple", new Cook() { //按照invokeCook参数列表传参
            @Override
            public String makeFood(String food) {
                return "吃" + food;
            }
        });
        System.out.println(str);
        //使用lambda表达式简化
        String str2 = invokeCook("banana", (String food)-> { return "吃"+food; });//lambda简化
        String str3 = invokeCook("banana", (String food)->"吃"+food);//lambda进一步简化,方法体中只有一行代码,省略大括号和return关键字
    }
    public static String invokeCook(String food,Cook cook){//该方法的第二个参数为接口类型,可提供一个实现类作为参数,直接new接口的匿名内部类的形式
        return cook.makeFood(food);//该方法的第一个参数又作为参数传递给了第二个参数的成员方法作为 入参,并返回该成员方法的返回值。
    }
}

 注:Idea中匿名内部类转lambda表达式可点击匿名内部类的类名,然后alt+enter替换成lambda即可。

函数式编程

有且只有一个abstract抽象方法的接口(可以含有其他非抽象的成员方法)

@FunctionInterface注解:加在接口上,可以检测接口是否是一个函数式接口。

log优化案例
public class JavaTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        String msg1="hello";
        String msg2 = "world";
        String msg3  ="Java";
        showLog(2,msg1+msg2+msg3);//性能浪费,因为先传递的level为2,不是1,所以不会打印。但是msg1+msg2+msg3还是会计算的。
        //可通过函数式接口编程来解决上问题:
        showLog2(1,()->{return msg1+msg2+msg3;});
    }
    public static void showLog(int level,String msg){
        if (level==1){
            System.out.println(msg);
        }
    }
    public static void showLog2(int level, MessageBuilder mb){
        if (level==1){
            System.out.println(mb.builderMessage());
        }
    }
}
@FunctionalInterface
public interface MessageBuilder {
    public abstract String builderMessage();
}
函数式接口作为方法参数
public class JavaTest {
    public static void startThread(Runnable run){
        new Thread(run).start();
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"-->线程启动了");
            }
        });
        startThread(()->System.out.println(Thread.currentThread().getName()+"-->线程启动了"));
    }
}
函数式接口作为方法返回值
public class JavaTest {
    public static Comparator<String> getComparator(){
        return new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.length()-o2.length();
            }
        };
    }
    public static Comparator<String> getComparator2(){
        //return (String o1, String o2) -> { return  o1.length()-o2.length();};
        return (o1, o2) -> o1.length()-o2.length();
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        String [] arr = {"aa","bbbbb","ccc"};
        System.out.println(Arrays.toString(arr));
        Arrays.sort(args,getComparator());
        System.out.println(Arrays.toString(arr));
    }
}

常用函数式接口

JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在java.util.function包中被提供。

supplier接口

生产型接口supplier,T get()

public class JavaTest {
    public static String getString(Supplier<String> sup){//泛型中转递什么类型,get时就拿到什么类型
        return sup.get();
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        String s = getString(()->{
            return "字符串";
        });
        System.out.println(s);//字符串
    }
}
//求数组中最大值
public class JavaTest {
    public static Integer getMax(Supplier<Integer> sup) {//泛型中转递什么类型,get时就拿到什么类型
        return sup.get();
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        int[] arr = {100, 0, 12, 234, 3456, 657, 56732};
        int maxValue = getMax(() -> {
            int max = arr[0];
            for (int i : arr) {
                if (i > max) {
                    max = i;
                }
            }
            return max;
        });
        System.out.println(maxValue);
    }
}
Consumer接口

消费型接口void accpt(T t)

public class JavaTest {
    public static void method(String name, Consumer<String> con) {
        con.accept(name);//消费
    }
    public static void main(String[] args) {
        method("张三", name -> {
            String reName = new StringBuffer(name).reverse().toString();
            System.out.println(reName);
        });
    }
}

andThen()多次处理消费

public class JavaTest {
    public static void method(String name, Consumer<String> con1, Consumer<String> con2) {
        /*con1.accept(name);//消费
        con2.accept(name);//消费*/
        //改写成
        con1.andThen(con2).accept(name);
    }
    public static void main(String[] args) {
        method("hello", name -> {System.out.println(name.toLowerCase());},
                name->{System.out.println(name.toUpperCase());});
    }
}
Predicate接口

test(T t):用于场景判断

public class JavaTest {
    public static boolean checkString(String s, Predicate<String> pre) {
        return pre.test(s);
    }
    public static void main(String[] args) {
        String s = "abcdef";
        boolean b = checkString(s, (String str) -> {
            return str.length() > 5;
        });
        System.out.println(b);//true
    }
}

默认方法and():相当于&&,与运算:有错则错

public class JavaTest {
    public static boolean checkString(String s, Predicate<String> pre1,Predicate<String> pre2) {
        //return pre1.test(s)&&pre2.test(s);
        //'与'也可写为下面格式
        pre1.and(pre2).test(s);
    }
    public static void main(String[] args) {
        String s = "abcdef";
        boolean b = checkString(s, (String str) -> {
            return str.length() > 5;
        },(String str)->{return str.contains("a");});
        System.out.println(b);//true
    }
}

默认方法or():相当于||,或运算:有真则真

public class JavaTest {
    public static boolean checkString(String s, Predicate<String> pre1,Predicate<String> pre2) {
        //return pre1.test(s)||pre2.test(s);
        //'或'也可写为下面格式:
        return pre1.or(pre2).test(s);
    }
    public static void main(String[] args) {
        String s = "abcdef";
        boolean b = checkString(s, (String str) -> {
            return str.length() > 5;
        },(String str)->{return str.contains("a");});
        System.out.println(b);//true
    }
}

默认方法negate():相当于!,非运算:非真则假,非假则真

public class JavaTest {
    public static boolean checkString(String s, Predicate<String> pre) {
        //return !pre.test(s);
        //‘非’也可写成下面格式:
        return pre.negate().test(s);
    }
    public static void main(String[] args) {
        String s = "abcdef";
        boolean b = checkString(s, (String str) -> {
            return str.length() > 5;
        });
        System.out.println(b);//false
    }
}
Predicate集合信息筛选案例
public class JavaTest {
    public static ArrayList<String> filter(String [] arr, Predicate<String> pre1,Predicate<String> pre2) {
        ArrayList<String> list = new ArrayList<>();
        for (String s:arr){
            boolean b = pre1.and(pre2).test(s);
            if (b){
                list.add(s);
            }
        }
        return list;
    }
    public static void main(String[] args) {
        String [] arr = {"海绵宝宝,男","红太狼,女","喜羊羊,男","美羊羊,女"};
        ArrayList<String> list = filter(arr, (String s) -> {
            return s.split(",")[1].equals("男");
        }, (String s) -> {
            return s.split(",")[0].length() == 4;
        });
        //条件同时满足4个字符和男
        System.out.println(list);//[海绵宝宝,男]
    }
}
Function接口

根据一个数据类型得到另一个数据类型;apply(T t)

public class JavaTest {
    public static void change(String s, Function<String,Integer> fun) {
        Integer in = fun.apply(s);
        System.out.println(in);
    }
    public static void main(String[] args) {
        String s = "1234";
        change(s,(String str)->{
            return Integer.parseInt(str);
        });
    }
}

默认方法andThen()

public class JavaTest {
    public static void change(String s, Function<String,Integer> fun1,Function<Integer,String> fun2) {
        String ss = fun1.andThen(fun2).apply(s);
        System.out.println(ss);
    }
    public static void main(String[] args) {
        String s = "123";
        change(s,(String str)->{
            return Integer.parseInt(str)+10;
        },(Integer i)->{return i+"";});
    }
}
function接口实现自定义函数模型拼接案例
public class JavaTest {
    public static int change(String s, Function<String,String> fun1,Function<String,Integer> fun2,Function<Integer,Integer> fun3) {
        return fun1.andThen(fun2).andThen(fun3).apply(s);
    }
    public static void main(String[] args) {
        String str = "孙悟空,20";
        int change = change(str, (String s) -> {
            return s.split(",")[1];
        }, (String s) -> {
            return Integer.parseInt(s);
        }, (Integer i) -> {
            return i + 130;
        });
        System.out.println(change);
    }
}

方法引用

 方法引用改写lambda表达式:

Lambda中传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常

@FunctionalInterface
public interface Printable {
    void print(String s);
}
public class JavaTest {
    public static void printString(Printable p){
        p.print("hello world");
    }
    public static void main(String[] args) {
        printString((s)-> System.out.println(s));
        //上面的的lambda可写为下面方法引用
        printString(System.out::println);//注:Lambda中传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常
        /*
        * 第一种语义是指∶拿到参数之后经Lambda之手,继而传递给 System.out.println方法去处理。
        * 第二种等效写法的语义是指∶直接让5ystem.out 中的println方法来取代Lambda。
        * 两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案,更加简洁。
        * */
    }
}
通过对象名引用成员方法
public class MethodRefObject {
    public void printUpperCase(String str){
        System.out.println(str.toUpperCase());
    }
}
@FunctionalInterface
public interface Printable {
    void print(String s);
}
public class JavaTest {
    public static void printString(Printable p){
        p.print("hello");
    }
    public static void main(String[] args) {
        printString((s -> {
            MethodRefObject obj = new MethodRefObject();
            obj.printUpperCase(s);//对s调用obj的成员方法转为大写。
            //上面lambda中调用成员方法的步骤也可写为下面这种方法引用,只要lambda方法参数为obj对象的printUpperCase的方法参数即可。
            printString(obj::printUpperCase);
        }));
    }
}
通过类名引用静态方法
@FunctionalInterface
public interface Calcable {
    int calsAbs(int number);
}
public class JavaTest {
    public static int method(int num, Calcable c){
        return c.calsAbs(num);
    }
    public static void main(String[] args) {
        int number = method(-10,(n ->{
            return Math.abs(n);
        } ));
        //上面的lambda也可写为下面的这种方式
        int number2 = method(-10, Math::abs);//只要保证lambda的入参可用放入Math类的静态方法abs中的入参中即可。
        System.out.println(number2);
        System.out.println(number);
    }
}
通过父类super引用成员方法
@FunctionalInterface
public interface GreetAble {
    void greet();
}
public class HumanFu {
    void sayHello(){
        System.out.println("Hello 我是HumanFu!");
    }
}
public class ManZi extends HumanFu{
    @Override
    void sayHello() {
        System.out.println("hello ,我是ManZi");
    }
    public void method(GreetAble g){
        g.greet();
    }
    public void show(){
        method(()->{
            /*HumanFu h = new HumanFu();
            h.sayHello();*/
            //调用父类的sayHello,可用new父类成员调用其成员方法,还可用super关键字表示该类(对象)的父类(对象)直接调用super.sayHello()
            super.sayHello();
        });
        //再进一步,上面的该lambda表达式使用方法引用即可
        method(super::sayHello);//由于lambda入参为空,且符合super类的sayHello方法的入参。
    }
    public static void main(String[] args) {
        new ManZi().show();
    }
}
通过this关键字引用本类的成员方法
@FunctionalInterface
public interface Richable {
    void buy();
}
public class Husband {
    public void buyFruit(){
        System.out.println("买pain apple");
    }
    public void hungry(Richable r){
        r.buy();
    }
    public void soHappy(){
        hungry(()->{this.buyFruit();});
        //上面也开改写为下面的这种方法引用的形式
        hungry(this::buyFruit);//只要hungry入参函数接口的入参和this(代指本对象)的成员方法bugFruit入参一致即可。
    }
    public static void main(String[] args) {
        new Husband().soHappy();
    }
}
构造器引用
@FunctionalInterface
public interface PersonBuilder {
    Person builderPerson(String name);
}
public class Person {
    private String name;
    public Person() {
    }
    public Person(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
public class JavaTest {
    public static void printName(String name, PersonBuilder pb){
        Person person = pb.builderPerson(name);
        System.out.println(person.getName());
    }
    public static void main(String[] args) {
        printName("喜羊羊",(String name)->{return new Person(name);});
        //上面的lambda表达式也可改写为
        printName("喜羊羊",Person::new);//Person需要有个以lambda入参为入参的构造器。
    }
}
数组构造器的引用
@FunctionalInterface
public interface ArrayBuilder {
    int [] builderArray(int length);
}
public class JavaTest {
    public static int[] createArray(int length, ArrayBuilder ab){
        return ab.builderArray(length);
    }
    public static void main(String[] args) {
        int [] arr1 = createArray(10,(length -> {
            return new int[length];
        }));
        //上面改写使用方法引用创建数组
        int[] arr2 = createArray(10, int[]::new);
        System.out.println(arr1);
        System.out.println(arr2);
    }
}

File类

文件和目录路径名的抽象表示形式。
java把电脑中的文件和文件夹(目录)封装为了一个File类,我们可以使用FiLe类对文件和文件夹进行操作我们可以使用File类的方法。

我们可以:创建一个文件/文件夹、删除文件/文件夹、获取文件/文件夹、判断文件/文件夹是否存在、对文件夹进行遍历、获取文件的大小。
File类是一个与系统无关的类,任何的操作系统都可以使用这个类中的方法。

file:文件、directory:文件夹/目录、path:路径

路径分隔符:File.pathSeparator:windows是;分号 。linux是:冒号。

文件名称分隔符:File.separator:windows是\反斜杠 。linux是/正斜杠。

绝对路径:盘符开头。

相对路径:相对于当前目录:若当前目录C:\\User\\a,则绝对路径的C:\\User\\a\\a.txt可简化为a.txt。

File构造方法和常用方法
com.kdy.test 
/*
 *File的构造方法、获取绝对路径或构造方法路径、获取文件名或目录名、获取文件大小、判断文件是否存在、判断是否为目录或文件夹、
 * 创建文件、创建单级或多级目录、删除文件或文件夹、遍历list和listFiles、递归遍历、递归遍历加文件搜索、listFiles(new FileFilter(){})
 * */
public class JavaTest {
    public static void main(String[] args) throws ParseException, IOException {
        //一个参的构造
        File file = new File("C:\\ProgramData\\kingsoft\\office6\\wpscloudsvr.exe");
        System.out.println(file);
        //两个参数的构造父路径拼接子路径,拼接后的字符串要符合规范,别少了\\
        File file2 = new File("C:\\ProgramData\\kingsoft\\" + "office6\\wpscloudsvr.exe");
        File file22 = new File("C:\\ProgramData\\kingsoft" + "\\office6\\wpscloudsvr.exe");
        System.out.println(file2);
        System.out.println(file22);
        //两个参数的构造:父文件拼接子路径
        File file3 = new File("C:\\ProgramData\\kingsoft");
        File file33 = new File(file3 + "\\office6\\wpscloudsvr.exe");//第一个File也会转成之前写的字符串路径进行拼接,拼接后的字符串要符合规范,别少了\\
        System.out.println(file33 + "   :  file33");
        /*
         * 获取file对象的绝对路径
         * */
        System.out.println(file.getAbsolutePath());//C:\ProgramData\kingsoft\office6\wpscloudsvr.exe
        /*获取构造方法中传递的路径*/
        System.out.println(file.getPath());//C:\ProgramData\kingsoft\office6\wpscloudsvr.exe
        System.out.println(file22.getPath());//C:\ProgramData\kingsoft\office6\wpscloudsvr.exe
        System.out.println(file33.getPath());//C:\ProgramData\kingsoft\office6\wpscloudsvr.exe

        //Idea里的相对路径当然是相对idea的本项目的上级目录,也就是创建的相对路径的文件会与本项目位置平级

        /*获取file的文件名或目录名,即构造方法中传递的路径结尾部分的文件或目录*/
        System.out.println("==========");
        System.out.println(file.getName());//wpscloudsvr.exe
        System.out.println(file22.getName());//wpscloudsvr.exe
        System.out.println(file33.getName());//wpscloudsvr.exe
        File file11 = new File("C:\\ProgramData\\kingsoft\\office6");
        System.out.println(file11.getName());//office6
        /*获取文件大小  ,file只能为文件的时候会有大小,file为目录时大小为0,file路径不存在时大小也是0 */
        System.out.println(file.length());//1660160
        System.out.println(file11.length());//0
        /*file判断表示的文件或目录是否存在*/
        System.out.println(file.exists());//true
        System.out.println(file11.exists());//true
        File file111 = new File("C:\\ProgramData\\xxxxx\\xxxxx");
        System.out.println(file111.exists());//false
        /*
         * 判断file是否为目录
         * 判断file是否为文件
         * */
        System.out.println("+++++++++");
        if (file.exists()) {
            System.out.println(file.isDirectory());//false
            System.out.println(file.isFile());//true
        }
        if (file11.exists()) {
            System.out.println(file11.isDirectory());//true
            System.out.println(file11.isFile());//false
        }
        /*
         * file创建文件,注意是创建文件不是文件夹,注意:若绝对路径需要除了文件不存在上级目录都必须真实存在。
         * 若该文件已经存在了,createNewFile返回false
         * */
        System.out.println("===========+++++++++++=============");
        File fileCreate = new File("d:\\img\\c.txt");//不存在的路径但img存在,只是该文件c.txt不存在
        File fileCreate2 = new File("a666.txt");//不存在的路径,想创建一个相对路径的
        boolean b = fileCreate.createNewFile();
        boolean b2 = fileCreate2.createNewFile();
        System.out.println(b);
        System.out.println(b2);
        /*
         * file创建单级目录。注意:若绝对路径需要除了本目录不存在上级目录都必须真实存在
         * 若该目录已经存在了,mkdir返回false
         * */
        File fileCreateDirSingle = new File("d:\\img\\single");//不存在的路径但img存在,只是该目录single不存在
        boolean mkdir = fileCreateDirSingle.mkdir();
        System.out.println(mkdir);
        /*
         * file创建多级目录。绝对路径除了本目录外父目录也可以不存在的,会创建多级目录的
         * 若该目录已经存在了,mkdirs返回false
         * */
        File fileCreateDirMany = new File("d:\\img3\\hello666\\many");//除了本目录外父目录也可以不存在的,会创建多级目录的
        boolean mkdirs = fileCreateDirMany.mkdirs();
        System.out.println(mkdirs);
        /*file删除文件或文件夹,注意不走回收站,删除需谨慎*/
        File fileCreateDirManyToDelete = new File("d:\\img3\\hello666\\many");
        boolean delete = fileCreateDirManyToDelete.delete();//这里只是删除了many文件夹,hello66和上级文件夹都还在
        System.out.println(delete);
        File fileCreateToDelete = new File("d:\\img\\c.txt");
        boolean delete1 = fileCreateToDelete.delete();//这里只是删除了c.txt,父级目录都还在
        System.out.println(delete1);
        /*
         * File文件遍历 list
         * */
        System.out.println("++++++++++++++++");
        File file6 = new File("d:\\img");
        String[] list = file6.list();//需要是一个目录(无论目录有没有子内容均可),不能是文件,文件的话list为null,后面空指针。
        for (String name : list) {
            System.out.println(name);
        }
        /*
         * File文件遍历 listFiles
         * */
        File[] listFiles = file6.listFiles();
        for (File f : listFiles) {
            System.out.println(f);//遍历该路径下的子文件或子文件夹,没有继续自动遍历子子..文件夹或文件,需使用递归遍历
        }
        System.out.println("++++++++++===========+++++++++");
        /*递归遍历file目录的子文件夹或子目录活子子文件夹或目录*/
        getAllFile(file6);
        System.out.println("++++++++++++++++");
        /*文件搜索:递归遍历file目录的子文件夹或子目录活子子文件夹或目录*/
        getAllFile2(file6);
        System.out.println("++++++++++++++++=====+++++++++++++++++");
        /*文件搜索:使用listFiles的带参数的重载方法传入一个实现FileFilter的类对象*/
        getAllFile3(file6);
    }
    /*递归遍历file目录的子文件夹或子目录活子子文件夹或目录*/
    public static void getAllFile(File dir) {
        File[] files = dir.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                getAllFile(file);
            } else {
                System.out.println(file);
            }
        }
    }
    /*文件搜索:递归遍历file目录的子文件夹或子目录活子子文件夹或目录*/
    public static void getAllFile2(File dir) {
        File[] files = dir.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                getAllFile(file);
            } else {
                //String name = file.getName();//a.txt之类
                //String path = file.getPath();//d:\\img\\a.txt之类,构造方法当时传递的参数
                String s = file.toString();//d:\\img\\a.txt之类,构造方法当时传递的参数
                if (s.endsWith(".png")) {//如果文件以.png结尾就输出
                    System.out.println(file);
                }
            }
        }
    }
    /*文件搜索:使用listFiles的带参数的重载方法传入一个实现FileFilter的类对象*/
    public static void getAllFile3(File dir) {
        /*
        * 1.listFiles方法会对构造方法中传递的目录进行遍历,获取目录中的每一个文件/文件夹-->封装为File对象
        * 2.listFiles方法会调用参数传递的过滤器(FileFilterImpl)中的方法accept
        * 3.listFiles方法会把遍历得到的每一个File对象,传递给FileFilterImpl的accept方法的入参pathname
        * */
        File[] files = dir.listFiles(new FileFilterImpl());
        for (File file : files) {
            if (file.isDirectory()) {
                getAllFile(file);
            } else {
                System.out.println(file);
            }
        }
    }
    /*文件搜索:使用listFiles的带参数的重载方法传入FileFilter的匿名内部类*/
    public static void getAllFile4(File dir) {
        File[] files = dir.listFiles(new FileFilter(){
            @Override
            public boolean accept(File pathname) {
                return pathname.isDirectory() || pathname.getName().toLowerCase().endsWith(".png");
            }
        });
        File[] files2 = dir.listFiles(pathname ->  pathname.isDirectory() || pathname.getName().toLowerCase().endsWith(".png"));//lambda简化上面书写
        for (File file : files) {
            if (file.isDirectory()) {
                getAllFile(file);
            } else {
                System.out.println(file);
            }
        }
    }
}
com.kdy.domain 
/*
* 创建过滤器FileFilter的实现类,重写过滤方法accept,定义过滤规则
* */
public class FileFilterImpl  implements FileFilter {
    /*
    * accept方法返回值是一个布尔值:true:就会把传递过去的File对象保存到File数组中。false:就不会把传递过去的File对象保存到File数组中
    * */
    @Override
    public boolean accept(File pathname) {
        //return true;//方法体中只有这一句是全部放行
        if (pathname.isDirectory()){
            return true;//如果是文件夹无条件放行。
        }
        return pathname.getName().toLowerCase().endsWith(".png");//如果endsWith为true就放行,false就拦截。
    }
}

递归

某个方法的方法体调用自己这个方法。或A方法调用B方法,B方法调用C方法,C方法再调用A方法这种间接递归

注意事项:
递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。Exception in thread "main" java.lang.StackOverflowError。

在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。

构造方法禁止递归。

/*
 * 使用递归计算1-n的和,使用递归计算阶乘。
 * */
public class JavaTest {
    public static void main(String[] args){
        int res = sum(5);//对于自己类内部返回空的成员方法,静态方法可直接调。对于有返回值的自己类的方法只能是静态的才能被静态方法调用
        System.out.println(res);
        int jc = jc(3);
        System.out.println(jc);
    }
    private static int sum(int i ){
       if (i==1){
           return 1;
       }else{
           return i + sum(i-1);
       }
    }
    public static int jc(int n){
        if (n==1){
            return 1;
        }else{
            return n * jc(n-1);
        }
    }
}

IO

流:输入流(到内存)、输出流(内存输出到其他设备)。或字节流和字符流。

字节流

一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。

FileOutputStream 

字节输出流:

OutputStream:抽象超类,共性方法有:close关闭该流释放资源。flush刷新流写出。write(byte[])写出。write(byte[] b,int off,int len)写出指定位置的数据。write(int b)写出。

FileOutputStream:继承了OutputStream。

java程序-->JVM(java虚拟机)-->OS(操作系统)-->OS调用写数据的方法-->把数据写入到文

public class JavaTest {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("d:\\img\\a.txt");//构造方法1
        File file = new File("d:\\img\\b.txt");
        FileOutputStream fos2 = new FileOutputStream(file);//构造方法2]
        fos.write(97);
        fos.write(65);
        fos.write(48);
        //运行该Java程序后,会把之前的a.txt文件覆盖掉,会将aA0三个char字符写入到a.txt
        fos.close();
        //批量写入byte数组
        byte [] bytes = {65,66,67,68,69};//如果写的第一个字节是正数(0-127),那么显示的时候会查询ASCII表
        byte [] bytes2 = {-65,66,67,-68,69};//如果写的第一个字节是负数,那第一个字节会和第二个字节,两个字节组成一个中文显示,查询系统黑认码表(GBK)
        fos2.write(bytes);//ABCDE
        fos2.write(bytes2);//緽C糆
        fos2.write(bytes,2,2);//CD,从2索引开始写两个。
        fos2.close();
        byte[] bytes6 = "好".getBytes();
        System.out.println(Arrays.toString(bytes6));//[-27, -91, -67]   utf-8中一个中文汉字翻译为3个字节
        FileOutputStream fos3 = new FileOutputStream("d:\\img\\a.txt",true);//构造方法3,不覆盖源文件,进行续写追加
        fos3.write("续写的内容".getBytes());//aA0续写的内容
        fos3.close();
    }
}
FileInputStream 

字节输入流:

InputStream:抽象超类。共性方法:read()从输入流中读下一个字节。read(byte [] b)从输入流中读取一定数量的字节并将其存放缓冲数组b中。close关闭释放。

FileInputStream:继承了InputStream

public class JavaTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("d:\\img\\a.txt");//构造方法1
        File file11 = new File("d:\\img\\b.txt");
        FileInputStream fis2 = new FileInputStream(file11);//构造方法1
        int len = fis.read();
        System.out.println(len);//97
        len = fis.read();//读下一个字节
        System.out.println(len);//65
        len = fis.read();
        System.out.println(len);//48
        len = fis.read();
        System.out.println(len);//231
        fis.close();
        System.out.println("+++++++++==============+++++++++++");
        int len2 = 0;
        while ((len2=fis2.read())!=-1){//fis.read()读当前,指针后移一位,如果当前没有就返回-1;
            System.out.print(len2+" ");//65 66 67 68 69 191 66 67 188 69 67 68
            //System.out.print((char)len2+" ");//A B C D E ¿ B C ¼ E C D
        }
        fis2.close();
        System.out.println();
        System.out.println("+++++++++==============+++++++++++");
        FileInputStream fis3 = new FileInputStream("d:\\img\\a.txt");
        byte [] bytes666 = new byte[8];
        int len666 = fis3.read(bytes666);//read(byte [] b)从输入流中读取一定数量的字节并将其存放缓冲数组b中
        System.out.println(len666);//8,说明当前读取了3个
        System.out.println(new String(bytes666));//aA0续�   utf-8中一个中文汉字翻译为3个字节
        len666 = fis3.read(bytes666);
        System.out.println(len666);//8
        System.out.println(new String(bytes666));//�的内�
        len666 = fis3.read(bytes666);
        System.out.println(len666);//2
        System.out.println(new String(bytes666));//����内�
        len666 = fis3.read(bytes666);
        System.out.println(len666);//-1
        System.out.println(new String(bytes666));//����内�
        System.out.println("+++++++++++++++++++++++++++++++++");
        FileInputStream fis1 = new FileInputStream("d:\\img\\a.txt");
        byte[] bytes1 = new byte[1024];//1024的整数倍
        int len1 = 0;
        while ((len1=fis1.read(bytes1))!=-1){
            System.out.println(new String(bytes1));//aA0续写的内容【后面有无数个空格】(虽然这里println换行,但发现一行就打印出了这行数据)
        }
        FileInputStream fis11 = new FileInputStream("d:\\img\\a.txt");
        while ((len1=fis11.read(bytes1))!=-1){
            System.out.println(new String(bytes1,0,len1));//aA0续写的内容  后面无空格  这个方法可以更优化读取多个字节,不多使用内存空
            //new String(byte数组 , 起始索引 , 截取几个);
        }
    }
}
图片或文件复制案例

一切文件皆是字节。

public class JavaTest {
    public static void main(String[] args) throws IOException {
        //copy1();
        copy2();
    }
    public static void copy1() throws IOException {
        FileInputStream fis = new FileInputStream("D:\\img\\rotPhoto\\1.jpg");
        FileOutputStream fos = new FileOutputStream("D:\\img\\新建图片.jpg");
        int len = 0;
        while ((len=fis.read())!=-1){
            fos.write(len);
        }
        fis.close();
        fos.close();
    }
    public static void copy2() throws IOException {
        FileInputStream fis = new FileInputStream("D:\\img\\rotPhoto\\1.jpg");
        FileOutputStream fos = new FileOutputStream("D:\\img\\新建图片6.jpg");
        byte [] bytes =new byte[1024];//数组缓冲
        int len = 0;
        while ((len=fis.read(bytes))!=-1){//数组缓冲
            fos.write(bytes,0,len);//数组缓冲
        }
        fis.close();
        fos.close();
    }
}

字符流

专门处理文本文件。 

FileReader 

字符输入流:

抽象类Reader:共性方法有close()关闭流释放资源。read()读取一个字符。read(char[] cbuf)从输入流读取一些字符并存入字符数组cbuf中。
FileReader:继承了InputStreamReader 继承了Reader

public class JavaTest {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("D:\\img\\a.txt");//构造方法1
        File file = new File("D:\\img\\a.txt");
        FileReader fr2 = new FileReader(file);//构造方法2
        int len = 0;
        /*while ((len = fr.read())!=-1){
            System.out.print(len+" ");//97 65 48 32493 20889 30340 20869 23481
        }*/
        while ((len = fr.read()) != -1) {
            System.out.print((char) len);//aA0续写的内容  没有字符乱码
        }
        fr.close();
        System.out.println();
        System.out.println("============");
        /*使用字符数组优化字符符取*/
        char [] cs = new char[1024];//1024整数倍
        int len2 = 0;
        while ((len2=fr2.read(cs))!=-1){
            System.out.println(new String(cs,0,len2));
        }
        fr2.close();
    }
}
FileWriter 

字符输出流:
抽象类Writer。共性方法:write(int c)写入单个字符。write(cahr [] cbuf , int off ,int len)写入字符数组的某一部分,off起使,len个数。write(String str)写入字符串。write(String str,int off,int len)写入字符串的某一部分。flush()刷新该流的缓冲。close关闭该流。

FileWriter:继承OutputStreamWriter继承Writer

public class JavaTest {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("D:\\img\\a.txt");//构造方法1
        File file = new File("D:\\img\\a.txt");
        FileWriter fw2 = new FileWriter(file);//构造方法2
        fw.write(65);//会将之前的文件内容全部覆盖掉
        //fw.close();//相比字节输出流,字符输出流必须close才会写入。   如果不close也不flush,之前的文件数据会被覆盖,且这里的数据也不会写进去
        fw.flush();//如果不close,必须flash才可刷入到文件中  使用flush相比close的好处是flush不会把流关闭,后续还可继续使用该流。
        /*字符输出流写出数组*/
        char[] cs = {'a', 'b', 'c', 'd', 'e'};
        fw.write(cs);
        fw.write(cs, 1, 3);
        fw.write("你好");
        fw.write("你好666hello", 2, 3);
        fw.close();
        /*开启续写追加功能*/
        FileWriter fw3 = new FileWriter(file, true);//构造方法3,不会覆盖原文件,可以追加。
        for (int i = 0; i < 10; i++) {
            fw3.write("\r\n" + "hello666" + i);//\r回车,\n换行
        }
        fw3.close();
    }
}
不使用throws而使用try-catch
public class JavaTest {
    public static void main(String[] args) throws IOException {
        m1();
        m2();
    }
    /*使用try-catch-finally处理*/
    public static void m1() {
        FileWriter fw5 = null;
        try {
            fw5 = new FileWriter("D:\\img\\b.txt");
            for (int i = 0; i < 10; i++) {
                fw5.write("\r\n" + "hello666" + i);//\r回车,\n换行
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fw5!=null) {//非空判断,防止空指针
                try {
                    fw5.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    /*使用try(创建流对象的代码)-catch,会自动释放流。省略finally和close*/
    public static void m2() {
        try(
        FileInputStream fis = new FileInputStream("D:\\img\\rotPhoto\\1.jpg");
        FileOutputStream fos = new FileOutputStream("D:\\img\\新建图片6.jpg");){
            byte [] bytes =new byte[1024];//数组缓冲
            int len = 0;
            while ((len=fis.read(bytes))!=-1){//数组缓冲
                fos.write(bytes,0,len);//数组缓冲
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

Propertie

继承HashTable,HashTable实现Map接口,Properties是唯一和IO流相结合的键值对集合。

Properties的持久化集合的方法store()可把properties集合的临时数据持久化到集合中去。

Properties的反持久化方法load()方法可将硬盘中数据读取到Properties集合中。

public class JavaTest {
    public static void main(String[] args) throws IOException {
        Properties prop = new Properties();
        prop.setProperty("aaa","500");
        prop.setProperty("bbb","300");
        prop.setProperty("ccc","600");
        Set<String> set = prop.stringPropertyNames();
        for (String key:set) {
            String value = prop.getProperty(key);
            System.out.println(key+"="+value);
        }
        /*prop.store持久化集合到文件*/
        FileWriter fw = new FileWriter("D:\\img\\d.txt",true);//加个append为true的参数,不覆盖。
        //fw的路径文件不存在会自动创建文件,但不会自动创建目录,所以最后一个文件可用是不存在的但是前面的目录路径要是真实的。
        prop.store(fw,"save data...");//参数,第一个参数可为OutputStream(不可写中文)或Writer(可写中文)。第二个参数为comments注释,解释保存的文件是做什么的,该参数不能使用中文会乱码,一般使用空字符串""
        fw.close();
/*
* 结果:
ABCDE緽C糆CD#save data...//文件原来的旧数据。
#Tue Jul 25 17:06:50 CST 2023
bbb=300
aaa=500
ccc=600
* */
        System.out.println("======================");
        /*prop.load反持久化文件到properties集合*/
        Properties properties = new Properties();
        properties.load(new FileReader("D:\\img\\d.txt"));//参数InputStream不能读取中文字符的键值对。Reader可用读取中文字符的键值对。
        Set<String> set666 = properties.stringPropertyNames();
        for (String key:set666) {
            String value = properties.getProperty(key);
            System.out.println(key+"="+value);
        }
/*
 *结果:
bbb=300
aaa=500
ccc=600
* */
    }
}

缓冲流

提高读写效率:

缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

字节缓冲流
BufferedOutputStream 

BufferedOutputStream字节缓冲输出流:

继承了FilterOutputStream又继承了OutputStream。有来自OutputStream抽象父类的共性方法:close关闭该流释放资源。flush刷新流写出。write(byte[])写出。write(byte[] b,int off,int len)写出指定位置的数据。write(int b)写出。

public class JavaTest {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("D:\\img\\a.txt",true);//加上append为true就不会将原文件中的内容覆盖掉
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        bos.write("写入数据到缓冲区中".getBytes());
        bos.flush();
        bos.close();//有close可省略flush
    }
}
BufferedInputStream 

BufferedInputStream字节缓冲输入流:

继承了FilterInputStream又继承了InputStream.有来自InputStream抽象父类的共性方法:read()从输入流中读下一个字节。read(byte [] b)从输入流中读取一定数量的字节并将其存放缓冲数组b中。close关闭释放。

public class JavaTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("D:\\img\\a.txt");
        BufferedInputStream bis = new BufferedInputStream(fis);
        int len = 0 ;
        while ((len=bis.read())!=-1){
            System.out.print(len);//229134153229133165230149176230141174229136176231188147229134178229140186228184173229134153229133165230149176230141174229136176231188147229134178229140186228184173
        }
        bis.close();
        System.out.println();
        System.out.println("+==========+");
        /*字节缓冲输入流加上byte数组再次缓冲*/
        FileInputStream fis2 = new FileInputStream("D:\\img\\a.txt");
        BufferedInputStream bis2 = new BufferedInputStream(fis2);
        byte [] bytes = new byte[1024];
        int len2 = 0 ;
        while ((len2=bis2.read(bytes))!=-1){
            System.out.print(new String(bytes,0,len2));//写入数据到缓冲区中写入数据到缓冲区中
        }
        bis2.close();
    }
}
字节缓冲流文件复制
/*
 * 有个参考如下:
 * 文件大小780831字节
 * 普通字节输入输出流单个字节读取存入为6043毫秒。普通字节输入输出流加数组缓冲多个字节并写入为10毫秒。
 * 使用缓冲输入缓冲输出流普通读写为32毫秒。使用缓冲输入缓冲输出流加数组的读写为5毫秒。
 * */
public class JavaTest {
    public static void main(String[] args) throws IOException {
        //m1();
        m2();
    }
    public static void m1() throws IOException {
        long l1 = System.currentTimeMillis();
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\img\\rotPhoto\\1.jpg"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\img\\hello.jpg"));
        int len = 0;
        while ((len = bis.read()) != -1) {
            bos.write(len);
        }
        bos.close();
        bis.close();
        long l2 = System.currentTimeMillis();
        System.out.println("耗时" + (l2 - l1) + "毫秒");
    }
    public static void m2() throws IOException {
        long l1 = System.currentTimeMillis();
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\img\\rotPhoto\\1.jpg"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\img\\hello.jpg"));
        byte [] bytes = new byte[1024];
        int len = 0;
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes,0,len);
        }
        bos.close();
        bis.close();
        long l2 = System.currentTimeMillis();
        System.out.println("耗时" + (l2 - l1) + "毫秒");
    }
}
字符缓冲流
BufferedWriter 

BufferedWriter字符缓冲输出流:

继承了Writer。有来自Writer抽象父类的共性方法:write(int c)写入单个字符。write(cahr [] cbuf , int off ,int len)写入字符数组的某一部分,off起使,len个数。write(String str)写入字符串。write(String str,int off,int len)写入字符串的某一部分。flush()刷新该流的缓冲。close关闭该流。

还有好用的方法入bw.newLine()

public class JavaTest {
    public static void main(String[] args) throws IOException {
       BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\img\\a.txt",true));//fw使用append为true。不覆盖原来文件内容。
        for (int i = 0; i < 10; i++) {
            bw.write("使用字符缓冲输出流写出中文字符");
            //bw.write("\r\n");
            bw.newLine();
        }
        bw.flush();
        bw.close();//有close可不写flush。
    }
}
BufferedReader 

BufferedReader字符缓冲输入流:

继承了Reader。有来自抽象父类Reader的共性方法:close()关闭流释放资源。read()读取一个字符。read(char[] cbuf)从输入流读取一些字符并存入字符数组cbuf中。

还有好用的方法readLine。

public class JavaTest {
    public static void main(String[] args) throws IOException {
        //m1();
        m2();
    }
    public static void m1() throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("D:\\img\\a.txt"));
        String line = br.readLine();
        System.out.println(line);
        String line2 = br.readLine();
        System.out.println(line2);
        String line3 = br.readLine();
        System.out.println(line3);
        br.close();
    }
    public static void m2() throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("D:\\img\\a.txt"));
        String line;
        while ((line=br.readLine())!=null){
            System.out.println(line);
        }
        br.close();
    }
}
使用字符缓冲流文本排序案例
/*
a.txt文件内容如下:
5.第五段。
7.第五段。
9.第五段。
1.第五段。
6.第五段。
3.第五段。
4.第五段。
8.第五段。
10.第五段。
12.第五段。
11.第五段。
*/
public class JavaTest {
    public static void main(String[] args) throws IOException {
        m2();
    }
    public static void m1() throws IOException {
        HashMap<String, String> map = new HashMap<>();
        BufferedReader br = new BufferedReader(new FileReader("D:\\img\\a.txt"));
        BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\img\\out.txt"));
        String line;
        while ((line =br.readLine())!= null){
            String[] arr = line.split("\\.");
            map.put(arr[0], arr[1]);
            //虽然hashMap底层hash表的原因,会根据key进行自动排序。但我们自然段有11和12会跑到第一自然段附近。所以下面还需要对map排序。
        }
        for (String key:map.keySet()){//由于使用map的话还是自动排序,但是有11和12自然段会跑到第1自然段附近。
            String value = map.get(key);
            line=key+"."+value;
            bw.write(line);
            bw.newLine();
        }
/*
排序结果不对:由于使用map的话还是自动排序,但是有11和12自然段会跑到第1自然段附近。
11.第11段。
1.第1段。
12.第12段。
2.第2段。
3.第3段。
4.第4段。
5.第5段。
6.第6段。
7.第7段。
8.第8段。
9.第9段。
10.第10段。
* */
        br.close();
        bw.close();
    }
    public static void m2() throws IOException {
        HashMap<String, String> map = new HashMap<>();
        BufferedReader br = new BufferedReader(new FileReader("D:\\img\\a.txt"));
        BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\img\\out.txt"));
        String line;
        while ((line =br.readLine())!= null){
            String[] arr = line.split("\\.");
            map.put(arr[0], arr[1]);
            //虽然hashMap底层hash表的原因,会根据key进行自动排序。但我们自然段有11和12会跑到第一自然段附近。所以下面还需要对map排序。
        }
        List arrList = new ArrayList(map.entrySet());
        Collections.sort(arrList, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Map.Entry obj1 =(Map.Entry) o1;
                Map.Entry obj2 =(Map.Entry) o2;
                return Integer.parseInt(obj1.getKey().toString())-Integer.parseInt(obj2.getKey().toString());
            }
        });
        System.out.println(arrList);
/*        for (String key:map.keySet()){//由于使用map的话还是自动排序,但是有11和12自然段会跑到第1自然段附近。
            String value = map.get(key);
            line=key+"."+value;
            bw.write(line);
            bw.newLine();
        }*/
        //我们使用下面的这种方式遍历再输入输出
        for (Object obj: arrList){
            Map.Entry entry =(Map.Entry) obj;
            bw.write(entry.getKey()+"."+entry.getValue());
            bw.newLine();
        }
/*
* 排序结果:
1.第1段。
2.第2段。
3.第3段。
4.第4段。
5.第5段。
6.第6段。
7.第7段。
8.第8段。
9.第9段。
10.第10段。
11.第11段。
12.第12段。
* */
        br.close();
        bw.close();
    }
}

转换流

字符编码和字符集

字符编码Character Encoding:

字符以某种规则以二进制形式存入计算机称为编码。计算机二进制以某种规则解析显示出来称为解码。编解码规则不同就会乱码。

字符集Charset:

当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。

ASCII编码->ASCII字符集、GBK编码->GBK字符集、UTF8|UTF16|UTF32编码->Unicode字符集。windows默认使用GBK编码格式,但Idea采用UTF-8编码,文件中中文在idea中打印就会乱码。

OutputStreamWrite():字符输出转换流

继承Writer。

有来自Writer抽象父类的共性方法:write(int c)写入单个字符。write(cahr [] cbuf , int off ,int len)写入字符数组的某一部分,off起使,len个数。write(String str)写入字符串。write(String str,int off,int len)写入字符串的某一部分。flush()刷新该流的缓冲。close关闭该流。

 InputStreamRead():字符输入转换流

继承了Reader。有来自抽象父类Reader的共性方法:close()关闭流释放资源。read()读取一个字符。read(char[] cbuf)从输入流读取一些字符并存入字符数组cbuf中。

public class JavaTest {
    public static void main(String[] args) throws IOException {
        //m1();
        //m2();
        m3();
    }
    public static void m1() throws IOException {//传统FileReader读取windows文本文件到Idea的Java内存中可能会出现乱码问题
        FileReader fr = new FileReader("D:\\img\\GBK.txt");//GBK.txt保存时内容为:GBK文件内容。编码格式为ANSI即GBK。所以现在打印会出现乱码
        int len = 0;
        while ((len= fr.read())!=-1){//一次读取一个字符
            System.out.print((char)len);//GBK�ļ����ݡ�
        }
        fr.close();
    }
    public static void m2() throws IOException {
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\img\\GBK.txt",true),"utf-8");//构造方法1,使用指定的字符编码创建osw流
        OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("D:\\img\\GBK.txt",true));//构造方法2,使用默认的字符编码创建osw流
        osw.write("你好");//写入utf-8格式的内容
        osw.flush();//GBK.txt在windows的记事本打开内容为GBK文件内容。浣犲ソ
        osw.close();
    }
    public static void m3() throws IOException {
        InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\img\\GBK.txt"), "utf-8");//构造方法1,使用指定的字符编码创建isr流
        InputStreamReader isr2 = new InputStreamReader(new FileInputStream("D:\\img\\GBK.txt"));//构造方法2,使用默认的字符编码创建isr流
        int len=0;
        while ((len=isr.read())!=-1){//一次读取一个字符
            System.out.print((char)len);//GBK�ļ����ݡ�你好   因为前面是GBK编码,而后面你好两个字是utf-8编码的。
        }
        isr.close();
    }
}
转换文件编码案例
public class JavaTest {
    public static void main(String[] args) throws IOException {
        //GBK.txt为GBK编码的。内容为hello你好,我是喜羊羊。
        InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\img\\GBK.txt"), "GBK");
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\img\\GbkToUtf-8.txt"),"UTF-8");//会自动创建该文件
        int len = 0;
        while ((len=isr.read())!=-1){
            osw.write(len);
        }
        osw.close();
        isr.close();
    }
}

序列化

Java提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象。

ObjectOutputStream:

ObjectOutputStream对象的序列化流:        

继承OutputStream。把对象以流的方式写入到文件中保存。

要想序列化一个对象,必须对象的类要实现Serializable标记接口:

类通过实现.java.io. Serializable接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。

com.kdy.domain
public class Person implements Serializable {
    private String name;
    private int age;
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Person() {
    }
}
com.kdy.test 
public class JavaTest {
    public static void main(String[] args) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\\img\\person.txt"));
        oos.writeObject(new Person("张三hello",20));
        oos.close();
    }
}
 ObjectInputStream:

ObjectInputStream:对象的反序列化流:

继承InputStream。把文件中保存的对象,以流的方式读取出来使用。

反序列化前提:

类必须实现serializable,必须存在类对应的class文件。

还是接着使用上面序列化时的Person类:
public class JavaTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:\\img\\person.txt"));
        Object o = ois.readObject();
        ois.close();
        System.out.println(o);//Person{name='张三hello', age=20}
        Person person = (Person) o;
        System.out.println(person.toString());//Person{name='张三hello', age=20}
    }
}
transient瞬态关键字

transient瞬态关键字:标记不需要序列化的成员属性。该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的。

注意:被static修饰的成员变量也不能被序列化的,序列化的都是对象。

注意:若对象序列化后,这个对象类的.class文件改变了(即动了该类的Java文件),那么会反序列化失败。

原因:该类的序列版本号与从流中读取的类描述符的版本号不匹配;该类包含未知数据类型;该类没有可访问的无参数构造方法。        Serializable接口给需要序列化的类,提供了一个序列版本号。[serialVersionUID] 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。

解决方法:我们可以在类的成员变量位置加一个final一个serialVersionUID。

但切记系列化后的文件不可修改,即使改过去再改回来也会反序列化失败。

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private int money;
//... ... 全参、无参、toString
}

序列化集合案例

 还是用上面的Person类
public class JavaTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("zhangsan",18));
        list.add(new Person("王五",20));
        list.add(new Person("孙悟空",22));
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\\img\\list.txt"));
        oos.writeObject(list);
        /*反序列化*/
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:\\img\\list.txt"));
        Object o = ois.readObject();
        ArrayList<Person> list2 = (ArrayList<Person>)o;
        System.out.println(list2);
        ois.close();
        oos.close();
    }
}

打印流

打印流:PrintStream:可以将System.out.println()打印写入到文件中 

平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。

public class JavaTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        PrintStream ps = new PrintStream("d://img//print.txt");
        ps.write(97);//a
        ps.println(97);//97  原样输出
        ps.println(8.8);//8.8)
        ps.println('a');//a
        ps.println("Hello World");//Hello World
        ps.println(true);//true
        ps.close();
    }
}
改变System.out.println()的打印流向
public class JavaTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        PrintStream ps = new PrintStream("d://img//print.txt");
        System.out.println("控制台输出");
        System.setOut(ps);
        System.out.println("打印流目的地里面输出");
        ps.close();
    }
}

网络编程入门

网络编程,就是在一定的协议下,实现两台计算机的通信的程序。

网络通信协议:

TCP/UDP:立用层、传输层、网络层和链路层:

链路层︰链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。
网络层∶网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
运输层︰主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。

应用层︰主要负责应用程序的协议,例如HTTP协议、FTP协议等。

UDP协议:

UDP∶用户数据报协议(User Datagram Protocol)。UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。

TCP︰传输控制协议(Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手"。
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
第三次握手,客户端再次向服务器端发送确认信息,确认连接。

完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。

网络编程三要素:协议、IP地址、常用命令

IP:cmd中使用ipconfig,选取对应的网络下的IPV4即可。

常用命令:检查网络是否连通cmd中使用ping IP地址

com.kdy.test
public class TCPClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8888);//客户端Socket,绑定服务器的IP和端口,(当前TCPServer.java中服务器为本机,指定的端口8888)
        OutputStream os = socket.getOutputStream();
        os.write("服务器你好,我是客户端,第一次请求连接".getBytes());//其实这里也会把最后的-1结束符给写出到服务器端
        InputStream is = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);
        System.out.println(new String(bytes,0,len));
        socket.close();
    }
}
public class TCPServer {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(8888);//创建服务器对象,确定本系统要指定的端口为8888
        Socket socket = server.accept();
        InputStream is = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);
        System.out.println(new String(bytes,0,len));
        OutputStream os = socket.getOutputStream();
        os.write("我是服务器,已收到客户段的第一次请求".getBytes());
        socket.close();
        server.close();
    }
}

先运行服务器的main,再运行客户端的main。

TPC文件上传案例

客户端读取本地文件,输入到服务器端,服务器读取上传的文件,写出到本地文件中。

public class TCPClient {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("d:\\img\\a.txt");
        Socket socket = new Socket("127.0.0.1", 8888);//客户端Socket,绑定服务器的IP和端口,(当前TCPServer.java中服务器为本机,指定的端口8888)
        OutputStream os = socket.getOutputStream();
        int len = 0;
        byte[] bytes = new byte[1024];
        while ((len=fis.read(bytes))!=-1){
            os.write(bytes,0,len);//写出给服务器端
        }
        //由于上面fis读取时不会读取到-1结束符,所以os也不会写出给服务器最终的-1结束符,所以服务器端while ((len=is.read(bytes))!=-1){}会因为读取不到-1结束符会一直阻塞下去。
        socket.shutdownOutput();//通过给服务器写一个结束标记。禁用此套接字输出流,对于改套接字来说之前写的数据会正常发送并后跟TCP正常的连接终止序列。
        InputStream is = socket.getInputStream();//从服务器端读取到客户端内存
        while ((len = is.read(bytes))!=-1){
            System.out.println(new String(bytes,0,len));
        }
    }
}
public class TCPServer {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(8888);//创建服务器对象,确定本系统要指定的端口为8888
        Socket socket = server.accept();
        InputStream is = socket.getInputStream();
        File file = new File("d:\\img\\upload");//服务器端的文件保存的目录
        if (!file.exists()){
            file.mkdirs();
        }
        String fileName = "kdy"+System.currentTimeMillis()+new Random().nextInt(999999)+".txt";//自定义文件命名规范,防止同名文件被覆盖:域名+毫秒值+随机数
        FileOutputStream fos = new FileOutputStream(file + "\\"+fileName);
        byte[] bytes = new byte[1024];
        int len = 0;
        while ((len=is.read(bytes))!=-1){
            fos.write(bytes,0,len);//fos写出到文件
        }
        OutputStream os = socket.getOutputStream();
        os.write("上传成功".getBytes());//写出到客户端
        fos.close();
        socket.close();
        server.close();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值