zoukankan      html  css  js  c++  java
  • java核心技术(第十版卷一)笔记(纯干货!)

    这是我读过的第三本关于java基础的书.第一本<<java从入门到精通>>这本书让我灵识初开.第二本<<java敏捷开发>>这本书则是有一次被一位师傅批评基础太差时读的.今日我向一位前辈请教应该怎样再进步一点.前辈推荐我读<<Java核心技术>>并告诉我,

    先读了这两本查漏补缺,然后在给我推荐进阶的书.工具书这种东西,每一本都不可能相同,比如我们的数学书,同样是小学的,两个地区的书可能就不同.

    以下为这本java核心技术中所积累的知识,仅为记录笔记

    --------------------------------------------------------------------------------------------------------

    自动类型转换
    两个数值.如果有double,则转换成double.否则有float,转换成float.否则有long,转换long.
    否则都转换成int

    类之间的关系
    依赖(uses-a):A类依赖B类.A类需要调用B类
    聚合(has-a):A类包含B类.
    继承(is-a):A类继承自B类.A类包含B类的属性行为,以及有自己的属性和行为

    对于方法参数
    其实是有一个隐藏的参数,就是this.表示当前的调用对象,而静态方法中,
    则不存在this.因为静态方法不依赖实例对象

    final修饰类的原则
    如果一个类的内部,没有提供可修改类本身状态的方法.
    则可将该类设为final(被final修饰的类不可以被继承,
    如果你继承了一个final类,
    则代表你可以自定义方法修改final类的属性,
    其破坏了封装性.这个坑在某个面试中,被坑了....

       如果一个class被声明为final,那么其所有的方法也默认被设置为final,而字段则不在final中.被final修饰的类主要目的还是阻止其他类通过成为其子类的方式而修改本身类的行为.



    java的参数统一是按照值传递的.基本数据类型传递的值的副本.引用数据类型传递的引用的副本,
    所以方法内部对参数进行的修改,不会影响实参
    .
    public class Person {
        public int age = 1;
    }
    public class Test {
        public static void main(String[] args) {
            int y = 1;
            change(1);
            System.out.println(y);//1
            Person p = new Person();
            change(p);
            System.out.println(p.age);//2
            change2(p);
            System.out.println(p.age);//2
        }
    
        public static void change(int x) {
            x = 2;
        }
    
        public static void change(Person p) {
            p.age = 2;
        }
    
        public static void change2(Person p) {
            p = new Person();
            p.age = 3;
        }
    }

    以上例子可以看出当我们传递一个基本数据类型参数,方法中的改变不会更改原有的值.这个对于上边的法则是能对于的.但是我们传递一个引用类型得到实参,则有了令人产生歧义的结果.实际上,这个结果是很迷惑人的

    (起码在一段长的时间,我也没有真正搞懂上边规则的真正意思.)我们先来看change2()这个方法,这个方法内部,对传入的实参重新new了一个对象,也就是说,当前方法中的p已经被重新实例化了.所以,当你把新的实例化的对象

    中的属性域修改了值,那么根据以上的法则,当然不会影响main()方法的实参.再来看,change1()的内部构造,他直接将实参对象的一个属性域的值修改了,结果竟然映射到了,main()方法中.这岂不是有悖于以上法则?其实不然,

    我们可以追溯到基本数据类型,当一个int i = 1;被创建则 1  已经被分配了内存空间.我们我们将 i = 2 那么  i 只是换了一个应用值2,实际的1还是存在的(这里我们暂且不考虑垃圾回收)话题回来,我们在change()中修改了实参的

    一个属性,那么这个实参的实际存储地址变了吗?没有的.而change2()则是直接新建了一个对象,新开辟了一片内存,你在新的地址里更改自己的属性,当然也不会影响实参的属性.(ps关于这类的问题,我曾经看过很多形参

    ,实参,传值,传址之类的博客,其实看得越多,越迷糊.如果你看到以上我的描述,觉得没有茅塞顿开那么就请忘掉他.避免对你造成更大的困扰)

    关于构造函数

    在一个类中,如果有成员 int i; 而改成员没有值.则会在new出该对象实例时,赋值为0;这个动作原来是在构造函数执行的.数值类型被默认为0,Boolean被默认为false,引用类型被默认为null.

    equals特性

    1 自反性: 对于任何非空引用x,x.equals(x);应当返回true.

    2 对称性: 对于任何引用x和y,当且仅当y.equals(x);返回true,x.equals(y);也应当返回true.

    3 传递性: 对于任何引用x,y,z如果x.equals(y)返回true; y.equals(z)返回true;那么x.equals(z);应当返回true

    4 一致性: 如果x和y的引用对象没有发生变化,返回调用x.equals(y);应当返回同样的结果.

    5 对于任意非空引用 x,x.equals(null);应当返回false.

    关于包装类型Integer

    对于包装类型我们大家都熟悉.对于以下操作我们则是使用了包装类型的特性:自动装箱.自动拆箱

    public static void main(String[] args) {
            Integer i = 100;
            i = i + 1;
            System.out.println(i);
        }

    这里有个对于自动装箱有个值得思考的问题,如果两个相同的数值用==判断,如果数值相同,则返回true.那么如果将这两个数值装箱为Integer还能用==判断吗?

        public static void main(String[] args) {
            Integer i = 1000;
            Integer x = 1000;
            System.out.println(i == x);//false
    
            Integer z = 100;
            Integer y = 100;
            System.out.println(z == y);//true
        }

    i 和 x 的==判断是我们意料之中的.两个对象 == 如果地址不同肯定是false嘛!那么 z 和 y呢?

    书中的解释

    自动装箱规范要求 booleanbytechar 127, 介于 -128 ~ 127 之间的 shortint 被包装到固定的对象中。

    但是需要注意的如果我们显式的创建两个Integer可不会有这个特性.

      public static void main(String[] args) {
            Integer i = 127;
            Integer x = 127;
            System.out.println(i == x);//true
    
            Integer z = new Integer(127);
            Integer y = new Integer(127);
            System.out.println(z == y);//false
        }

    另外这里还有一点.之前我们说,当向一个方法传递引用类型的实参时,我们是有可能修改这个实参的属性的.那么我们来看这个

     public static void main(String[] args) {
            Integer i = new Integer(1);
            change(i);
            System.out.println(i);//1
        }
    
        public static void change(Integer i) {
           i = i.valueOf(2);
           i = 3;
        }

    白日做梦~~~~怎么可能会让你修改一个基本类型的值呢.~~~~~

    关于接口的默认方法

    在JDK8中接口中可以提供默认的实现方法.如果,一个类的父类和接口有同样的一个方法.子类会选择使用谁的呢?

    public class P1 {
        public void add(){
            System.out.println("1");
        }
    }
    public interface P2 {
        default public void add(){
            System.out.println("2");
        }
    }
    public interface P3 {
        default public void add(){
            System.out.println("3");
        }
    }
    public class Person extends P1 implements P2,P3{
        public static void main(String[] args) {
            Person p = new Person();
            p.add();//1
        }
    }

    结果是继承优先.

    那如果两个接口有同样的默认方法呢?

    结果是报错.

    public class Person  implements P2{
        public static void main(String[] args) {
          P2.get();
        }
    }

    接口的静态方法

     此外jdk8还运行接口定义静态方法

    public interface P2 {
        public static void get(){
            System.out.println("get");
        }
    }
    public class Person  implements P2{
        public static void main(String[] args) {
          P2.get();
        }
    }

    我们可以直接使用接口 . 的方式使用静态方法.

    但是一下调用是不可以的.

     可以看出当一个父接口有静态方法时,子实现是没有继承父类的静态方法的.这一点和继承是不一样的.

    我们来看下继承中的静态方法.

    public class P1 {
        public static void add(){
            System.out.println("1");
        }
    }
    public class Person  extends P1{
        public static void main(String[] args) {
            Person.add();
        }
    }

    这两个的区别要搞清楚.


    Comparable 和  Comparator

    在我们使用一些工具类的sort排序方法时,比如Arrays或者Collections时他们总是需要我们将需要排序的类实现 Comparable 接口中的.compareTo().原因就是,假设要对一个集合中的User进行排序,那么您总要告诉工具类您要的排序

    规则是啥对吧.那么再来说说既然有了Comparable 为啥还要有Comparator这个接口.在面向对象的设计理念中,一个类,他最好只有属于自己特点功能的方法显然,排序这个方法,只能算是工具方法.所以在一个类的设计初期,非常可能的

    并没有实现Comparable 中的compareTo()方法.而在后期的开发业务功能时,我们发现,我们需要对User类进行排序呀.这时为了不破坏原有的代码,我们则可以另外创建一个类,并让这个类实现Comparator接口中的compare()方法.并在

    使用工具类时,将Comparator的实现类传递给工具类.这样的设计理念主要还是为了保证一个类的完整性,已经单一功能性.

    关于clone

    在Object中有一个方法clone()此方法是一个受保护的方法.clone().关于这个受保护的clone()方法在子类中不能直接使用的问题.PS必须实现Cloneable标记方法,而且您还要覆盖Objece类的clone()才能使用.至于为啥

    这个clone明明是受保护的类子类不能直接用,百度了好久,也没找到答案,本着不钻牛角尖的精神.咱们只来探究下clone()一起的浅拷贝深拷贝问题.

    public class Test1 implements Cloneable{
        public int age = 1;
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    public class Run {
        public static void main(String[] args)throws Exception{
            Test1 test1 = new Test1();
            Test1 test11 = (Test1) test1.clone();
            test11.age = 2;
            System.out.println(test1.age);//1
            System.out.println(test11.age);//2
        }
    }

    结果是被拷贝的对象被改变了,不会影响原对象.这算是深拷贝.那咱们再来试试原对象中有引用类型,会怎样.

    public class Test1 implements Cloneable{
        public int age = 1;
        public String name = "张三";
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    public class Run {
        public static void main(String[] args)throws Exception{
            Test1 test1 = new Test1();
            Test1 test11 = (Test1) test1.clone();
            test11.age = 2;
            test11.name="李四";
            System.out.println(test1.age);//1
            System.out.println(test11.age);//2
            System.out.println(test1.name);//张三
            System.out.println(test11.name);//李四
        }
    }

    引用类型也会深拷贝.那咱们在来换个自定义的引用类型.

    public class Person {
        public int height;
    
        public Person(int height) {
            this.height = height;
        }
    }
    public class Test1 implements Cloneable{
        public int age = 1;
        public String name = "张三";
        public Person person = new Person(1);
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    public class Run {
        public static void main(String[] args)throws Exception{
            Test1 test1 = new Test1();
            Test1 test11 = (Test1) test1.clone();
            test11.age = 2;
            test11.name="李四";
            test11.person.height = 3;
            System.out.println(test1.age);//1
            System.out.println(test11.age);//2
            System.out.println(test1.name);//张三
            System.out.println(test11.name);//李四
            System.out.println(test1.person.height);//3
            System.out.println(test11.person.height);//3
        }
    }

    结果,这次就变成浅拷贝了.这是几个意思呢?为啥String 和 Person都是引用类型.String就是深拷贝,Person就是浅拷贝嘞.还是看看黄金屋中怎么说的.

     所以Object的clone()方法就是浅拷贝.只不过对于 int 和 String  以及一些不可变的类型是安全的.所以给了我们是深拷贝的假象.所以要想实现深拷贝,我们还要覆盖Object的clone()方法才行.

    public class Test1 implements Cloneable {
        public int age = 1;
        public String name = "张三";
        public Person person = new Person(1);
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            Test1 test1 = new Test1();
            test1.age = age;
            test1.name = name;
            Person person = new Person(this.person.height);
            test1.person = person;
            return test1;
        }
    }
    public class Run {
        public static void main(String[] args)throws Exception{
            Test1 test1 = new Test1();
            Test1 test11 = (Test1) test1.clone();
            test11.age = 2;
            test11.name="李四";
            test11.person.height = 3;
            System.out.println(test1.age);//1
            System.out.println(test11.age);//2
            System.out.println(test1.name);//张三
            System.out.println(test11.name);//李四
            System.out.println(test1.person.height);//1
            System.out.println(test11.person.height);//3
        }
    }

    如此一来,则成功实现了深拷贝.PS实现深拷贝的方法有很多.例如更常见的利用序列化进行深拷贝.有兴趣的可以自行了解下.

    关于lambda(只记得这一些十分皮毛的东西,假如要应用到真正的开发,确实不是短时间内,我可以做到的.)

    先来看一个例子,说明下lambda的基本语法

    public class Person {
        private int age;
    
        public Person(int age) {
            this.age = age;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    public class Test {
        public static void main(String[] args) {
            Person[] p = new Person[3];
            p[0] = new Person(3);
            p[1] = new Person(2);
            p[2] = new Person(1);
    
            Arrays.sort(p, (Person x, Person y) ->{return x.getAge() - y.getAge();});
    
            for (Person person : p) {
                System.out.println(person.getAge());
            }
        }
    }
    以上是使用Arrays中的sort()对Person对象数组进行排序,显而易见之前sort()中的第二个参数是一个比较器.此时我们没有new一个Comparator,实现compare()而是采用了lambda表达式.看看这个怪异的写法.两个参数,一个 -> 然后是一个表达式.
    嗯这么拆开看已经很明白啦.使用lambda表达式假装创建了一个Comparator接口的匿名内部类,我们只需要关注compare方法的实现细节即可.

    还可以这样写.
    public class Test {
        public static void main(String[] args) {
            Person[] p = new Person[3];
            p[0] = new Person(3);
            p[1] = new Person(2);
            p[2] = new Person(1);
    
            Arrays.sort(p, ( x,  y) ->x.getAge() - y.getAge());
    
            for (Person person : p) {
                System.out.println(person.getAge());
            }
        }
    }

    因为已经确定了数组中的类型为Person,所以省略.compare方法中只有一个行代码.不带大括号,也不使用return关键字.

    关于lambda暂且放下.等待抽一个时间系统学习

  • 相关阅读:
    我的浏览器收藏夹分类
    我的浏览器收藏夹分类
    Java实现 LeetCode 318 最大单词长度乘积
    Java实现 LeetCode 318 最大单词长度乘积
    Java实现 LeetCode 318 最大单词长度乘积
    Java实现 LeetCode 316 去除重复字母
    Java实现 LeetCode 316 去除重复字母
    Java实现 LeetCode 316 去除重复字母
    Java实现 LeetCode 315 计算右侧小于当前元素的个数
    Java实现 LeetCode 315 计算右侧小于当前元素的个数
  • 原文地址:https://www.cnblogs.com/zumengjie/p/11563390.html
Copyright © 2011-2022 走看看