Java知识总结(一)

JAVA知识总结

本人在学习java基础知识时对一些问题做了总结,当中借鉴的资料都已附上链接,希望能够共同学习,若当中存在错误或者不准确的地方,还希望指正!

面向对象和面向过程的区别

面向过程:

优点:

直观上来说面向对象生成对象实例过程消耗的资源较大。

从根本上来说面向过程语言大多数是直接编译成机械码在计算机中运行,而面向对象语言(Java)是半编译语言,通过编译器将源文件编译成JVM可识别的字节码文件,通过解释器将字节码文件解释成计算机可以识别的机械码文件。

缺点:相比面向对象语言,它不利于维护、复用以及扩展。

面向对象:

优先:面向对象语言能够实现代码易维护、易复用以及易扩展的特点,具有封装、继承、多态等特性,设计出低耦合系统、是系统更加灵活。

缺点:相比于面向过程语言,面向对象语言的性能较低.

  • 补充

[ 图解高内聚与低耦合 - 大道方圆 - 博客园 (cnblogs.com) ]()

高内聚和低耦合 ------->基于模块

模块: 从逻辑上将系统分解为更细微的部分, 分而治之, 复杂问题拆解为若干简单问题, 逐个解决

耦合:主要是模块与模块之间的联系,关系越密切,耦合性越强,模块的独立性越差.

内聚 :是指模块内部的元素,比如:方法,变量,对象等等.

低耦合

如果模块与模块之间的关系型越密切,耦合性越强,改变一个模块中的数据可能会导致其他模块中的数据跟着改变,

模块的独立性越差,不利于维护.因此设计出低耦合的关系模块.

比如:给出两个模块,模块1和模块2, 如果我想要和模块2做数据的交互,低耦合的设计应该是通过模块1数据与模块2进行交互,而不是模块1直接操作模块2上的数据.

image-20210709093116623image-20210816104154566

高内聚

模块内部的元素,相互之间关系越好,内聚就越强,模块的单一性更强.一个模块能够独立完成某个功能.

低内聚的模块代码,不利于维护和重构,不够健壮.

image-20210709093440360

​ 低内聚

image-20210709093552180

​ 高内聚


Java语言特点

  1. 简单易学
  2. 面向对象
  3. 平台无关性
  4. 可靠性
  5. 安全性
  6. 支持多线程
  7. 支持网络编程
  8. 编译与解释并存

面向对象三大特点

封装:将对象的属性私有化,外界不能够直接访问对象的属性,对象提供了方法供外界访问,很安全,易维护.

继承:已经存在的类作为基础类(父类),在此基础上建立的新类,新类又称为子类或派生类. 提高代码的复用率和扩展.

  • 子类拥有父类中所有的属性和方法,但子类只能访问非私有属性和方法.
  • 子类可用拥有自己的属性和方法,对本身进行扩展
  • 子类可以对父类方法进行重写(子类不能覆写父类中的静态方法.在子类中定义与父类完全相同的静态方法,父类的会被隐藏)
  • 补充:子类不可以继承父类的构造方法,子类实例化之前都需要调用父类的构造方法,一般子类构造方法中,首行是super关键字调用父类构造方法,若没有显示调用父类构造方法,子类就会去调用父类的无参构造方法,若父类不存在无参构造方法(父类中定义了有参构造方法,默认的无参构造方法就不存在了,需要自己在父类中定义无参构造),抛出异常。

多态:允许不同子类型对象对同一消息做出不同的响应.父类类型的引用指向子类型对象.

实现多态的形式:继承(多个子类对同一方法(非静态方法)进行重写)和接口(实现接口并覆盖接口中同一方法)

注意:多态是一种运行期行为,不是编译行为,不要将函数重载理解为多态

编译时多态性(前绑定):方法重载(overLoad)

运行时多态性(后绑定):方法重写(override)

特点:

  • 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误.如果有,再去调用子类的同名方法.
  • 静态方法特殊,静态方法只能继承,不能覆盖,如果子类和父类有相同的静态方法,只能起到隐藏父类方法的作用,这时候,谁的引用就调用谁的方法.
public class Father {


    public void say(){
        System.out.println("father");
    }


    public static void action(){
        System.out.println("爸爸打儿子!");
    }
}


public class Son extends Father{

    public void say() {
        System.out.println("son");
    }

   public static void action(){
        System.out.println("打打!");
    }


    public static void main(String[] args) {
        Father f=new Son();
        f.say();
        f.action();

    }
}

输出:son
          
爸爸打儿子!

平台无关性:

java是半编译语言,也叫解释型语言和跨平台语言,这都是java语言的特性.

java的跨平台不是源文件的跨平台,而是字节码文件跨平台.将源文件通过编译器(javac)编译成jvm可以识别的字节码文件,字节码文件与平台无关,因此编译好的字节码文件通过对应平台的jvm解释成平台可以是识别执行的机器码文件.

java和c++区别

  • 都是面向对象语言,都支持封装,继承,多态
  • java不提供指针访问内存,程序内存更加安全
  • java类是单继承,接口可以多继承,c++支持多重继承
  • java有自动内存管理机制,无需手动释放无用内存
  • 在c语言中,字符串和字符数组最后都会有一个额外的字符’\0’表示结束,但java没有这一概念.
在C语言中字符串和字符数组基本上没有区别,都需要结束符;如:char s[4]={'a','b','c','d'};此字符数组的定义编译可以通过,但却没有关闭数组,若其后需要申请内存,那么以后的数据均会放入其中,尽管它的长度不够,但若为 char s[5]={'a','b','c','d'};则系统会自动在字符串的最后存放一个结束符,并关闭数组,说明字符数组是有结束符的;

java中无需结束符的原因

Java里面一切都是对象,是对象的话,字符串肯定就有长度,即然有长度,编译器就可以确定要输出的字符个数,当然也就没有必要去浪费那1字节的空间用以标明字符串的结束了。比如,数组对象里有一个属性length,就是数组的长度,String类里面有方法length()可以确定字符串的长度,因此对于输出函数来说,有直接的大小可以判断字符串的边界,编译器就没必要再去浪费一个空间标识字符串的结束。

类和对象的区别

1,类是一个抽象的概念,它不存在于现实中的时间/空间里,类只是为所有的对象定义了抽象的属性与行为。就好像“Person(人)”这个类,它虽然可以包含很多个体,但它本身不存在于现实世界上。
  2,对象是类的一个具体。它是一个实实在在存在的东西。
  3,类是一个静态的概念,类本身不携带任何数据。当没有为类创建任何对象时,类本身不存在于内存空间中。
  4,对象是一个动态的概念。每一个对象都存在着有别于其它对象的属于自己的独特的属性和行为。对象的属性可以随着它自己的行为而发生改变。


字符串常量和字符型常量的区别

形式上:

字符常量是单引号引起的⼀个字符; 字符串常量是双引号引起的若⼲个字符

含义上:

字符常量相当于⼀个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表⼀个地

址值(该字符串在内存中存放位置)

占内存大小:

字符常量只占 2 个字节; 字符串常量占若⼲个字节 (注意: char Java 中占两

个字节)

image-20210709104049032


重载和重写(覆盖)区别

重载重写
发生范围同一类子类
发生阶段编译时期运行时期
参数列表修改参数(个数,类型,位置)必须相同
返回类型可修改子类方法返回值类型 <= 父类(必须在继承关系的前提下)
异常可修改子类方法声明抛出异常类型 <= 父类异常
访问修饰符可修改子类访问修饰符 >= 父类访问权限

补充:

重写返回值类型:如果方法的返回类型是void和基本数据类型,则返回值重写时不可修改,但是如果返回类型时引用类型,重写时可以返回该引用类型的子类.


String为什么不可变,String,StringBuffer,StringBuilder区别

String类实现用字符数组(char value[])存储字符串,字符数组是被final修饰的,因此无法修改,所以String类不可以改变

(java9之后,String类改用byte数组存储字符串)

StringBuilder 与 StringBuffer 都继承⾃ AbstractStringBuilder 类.

AbstractStringBuilder 中使用字符数组(char value[])保存字符串,数组没有用final关键字修饰,因此StringBuffer和StringBuilder都是可变的.

AbstractStringBuilderStringBuilderStringBuffer
image-20210709121133846image-20210709121205147image-20210709121056095
补充:String、StringBuilder、StringBuffer类都是被final修饰的,都是限制他们所存储的引用地址不可以修改,同时还表示该类无法被继承。而StringBuilder、StringBuffer的字符数组可以被修改。也就是内容可以被修改

线程安全

String类中的对象是不可变的,线程安全

StringBuilder中并没有对方法加同步锁,线程不安全

StringBuffer中对方法或调用方法加同步锁,线程安全

性能

对String类型进行更改时,都会生成一个新的String对象,将指针指向新的String对象.

String对象的两种创建方式

  1. String str 1 =“aa”;

    这种方式声明的String对象,在编译时期就已字面量的形式存在于class文件常量池中,到来运行期间,class常量池会被加载到“运行时常量池中”,包括String的字面量,但同时会将“aa”的一个引用存放到字符串常量池中,“aa”本体还是和所有对象一样在堆上创建。若进行比较会先在字符串常量池中寻找引用。

  2. String str 2 =new String(“aa”),调用的是String的构造函数,在类型加载完成后,进行对象加载在运行期间才能够确定,此"aa"是位于堆内存中。

StringBuffer和StringBuilder每次更改时都是对自身进行操作,比如方法append.而不是生成新的对象.

使用:

操作少量数据:String

单线程操作字符串缓冲区下大量数据:StringBuilder

多线程操作字符串缓冲区下大量数据:StringBuffer


自动装箱和拆箱

自动装箱和拆箱

image-20210709124851690

装箱:自动将基本数据类型转换为包装类型

装箱的时候自动调用的是Integer的valueOf(int)方法

拆箱:自动将包装类型转换为基本数据类型

在拆箱的时候自动调用的是Integer的intValue方法。

面试中相关问题:

public class Main {
    public static void main(String[] args) {
         
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
         
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

输出:
true
false

查阅Integer的源码:

public static Integer valueOf(int i) {
    //IntegerCache类型的缓存
        if(i >= -128 && i <= IntegerCache.high)
            return IntegerCache.cache[i + 128];
        else
            return new Integer(i);
    }
    
  
private static class IntegerCache {
        static final int high;
        static final Integer cache[];

        static {
            final int low = -128;

            // high value may be configured by property
            int h = 127;
            if (integerCacheHighPropValue != null) {
                // Use Long.decode here to avoid invoking methods that
                // require Integer's autoboxing cache to be initialized
                int i = Long.decode(integerCacheHighPropValue).intValue();
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - -low);
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
        }

        private IntegerCache() {}
    }

在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。

因此,200已经超越了[-128,127],生成是两个不同对象,因此是false.

public class Main {
    public static void main(String[] args) {
         
        Double i1 = 100.0;
        Double i2 = 100.0;
        Double i3 = 200.0;
        Double i4 = 200.0;
         
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

输出:
false
false

在某个范围内的整型数值的个数是有限的,而浮点数却不是,因此这四个变量都是不同的对象.

Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。

Double、Float的valueOf方法的实现是类似的。

public class Main {
    public static void main(String[] args) {
         
        Boolean i1 = false;
        Boolean i2 = false;
        Boolean i3 = true;
        Boolean i4 = true;
         
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

输出:
true
true
public static final Boolean TRUE = new Boolean(true);

    /** 
     * The <code>Boolean</code> object corresponding to the primitive 
     * value <code>false</code>. 
     */
public static final Boolean FALSE = new Boolean(false);

Boolean类中定义了两个静态不可改变的变量

		Integer a = 6 ;
		Integer a1 = new Integer(6);
        Integer b = new Integer(1);
        Integer c = new Integer(2);
        Integer e = 3 ;
        Integer f = 3 ;
        Integer d = new Integer(1);
		Integer g = 6 ;

        System.out.println(e.equals(b+c));
        System.out.println(b.equals(d));
        System.out.println(a.equals(e+f));
 		System.out.println(a == a1);
		System.out.println(a == g);
/*
true
true
true
false
true

Integer包装类重写了equals方法,因此比较的是值,==若都是包装类比较的是地址,若是基本数据类型,包装类会进行自动拆箱转换成基本类型,比较的是值。

Integer g = 6 ;能够自动装箱相当于 Integer g = Integer.ValueOf(6);在Integer常量池中寻找6的引用
*/

Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别。

  1. 第一种方式不会触发自动装箱的过程;而第二种方式会触发自动装箱(java编译时会自动调用Integer.valueOf(xxx));
  2. 在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)。
public class Main {
    public static void main(String[] args) {
         
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        Long h = 2L;
         
        System.out.println(c==d);
        System.out.println(e==f);
        System.out.println(c==(a+b));
        System.out.println(c.equals(a+b));
        System.out.println(g==(a+b));
        System.out.println(g.equals(a+b));
        System.out.println(g.equals(a+h));
    }
}

输出:
true
false
true  先进行a+b运算的拆箱,转换成基本数据类型,最后比较的是数值是否相等
true  先进行a+b的拆箱(intValue),比较的是值是否相等
true  拆箱后基本数据转换成long类型(向上类型转换)
false a+b拆箱(intValue),g进行拆箱,equals不会进行类型给转换,因此longint类型不符合
true a+h拆箱进行运算,a为int、h为long a+h结果为long类型,最后比较的是值类型

当 "=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程) ,对于包装器类型,equals方法并不会进行类型转换 .

java基本数据类型转换

向上转换:

整型,字符型,浮点型的数据在混合运算中相互转换,转换时遵循以下原则:

容量小的类型可自动转换为容量大的数据类型;

byte,short,char → int → long → float → double

byte,short,char之间不会相互转换,他们在计算时首先会转换为int类型。

boolean 类型是不可以转换为其他基本数据类型。

补充:当long类型转换为float类型时 不需要进行强制类型转换,虽然long类型占8个字节,float占4个字节,float是浮点类型表示的范围比long类型要大。

向下转换

整型,字符型,浮点型的数据在混合运算中相互转换,转换时遵循以下原则:

容量小的类型可自动转换为容量大的数据类型;

byte,short,char <-----int <----- long <----- float <— double

byte,short,char之间不会相互转换,他们在计算时首先会转换为int类型。

boolean 类型是不可以转换为其他基本数据类型。

类型转化

小转大,自动!自动类型转换(也叫隐式类型转换)

大转小,强转!强制类型转换(也叫显式类型转换)


静态方法调用一个非静态方法是非法的

静态方法属于类而不属于对象,通过类去调用静态方法,静态方法不可以调用和访问非静态变量.

一个类实例化之前,首先加载其静态变量和静态代码块,其次加载普通变量和代码块,最后调用构造函数.


java中无参构造函数作用

如果一个类实例化时,没有定义构造函数,会默认提供一个无参构造函数.

如果子类实列化,子类构造函数中没有用super关键字调用父类特定的构造函数(先有父亲后有儿子),那么会去调用父类中的无参构造函数(此无参构造函数是自己定义的),如果父类中没有定义无参构造函数,则会发生编译错误.


接口和抽象类的区别

抽象类:在java中被abstract关键字修饰的类.

抽象方法: 被abstract关键字修饰的方法,抽象方法只有方法的声明没有方法体.

特点:

  • 抽象类不能被实列化只能被继承
  • 抽象类可以包含属性,方法,构造方法,但是构造方法不能被用于实例化,只是为子类实例化做服务.
  • 抽象类中抽象方法的修饰符只能为:public和protect,默认为public
  • 包含抽象方法的类一定是抽象类,但抽象类不一定有抽象方法.
  • 一个子类继承一个抽象列,则子类必须实现父类抽象方法,否则该子类只能是一个抽象类.

接口:使用interface关键字修饰

特点:

  • 包含变量,方法. 变量默认为:public static final . 方法默认为: public abstract (jdk1.8之前)

  • 支持多继承,即一个接口可以extends多个接口

  • 一个类可以实现多个接口

  • jdk1.8中对接口增加新的特性:

    • 默认方法(default method):允许接口添加非抽象的方法实现,但必须使用default关键字修饰,定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用.
    • 静态方法(static method):允许使用static关键字修饰一个方法,并提供实现,称为:接口静态方法.接口静态方法只能通过接口调用(接口名.静态方法名)

    注意:jkd1.9是允许接口中出现private修饰的默认方法和静态方法.

成员变量与局部变量区别

从语法形式上:成员变量属于类,局部变量属于方法内部或方法的参数,成员变量可以被public,protect,private,static等修饰,而局部变量只能被final关键字修饰.

从存储形式上:成员变量被static修饰,那么它属于类,否则它是属于对象实例,对象被存放在堆内存中.局部变量如果是基本数据类型,它被存储在栈内存中,如果是引用数据类型,存放的是指向堆中的对象引用或着是常量池中的地址.

从生存时间上:成员变量是对象的一部分,随着对象的创建而创建,局部变量随着方法调用而调用.

赋值:成员变量在没有赋值的情况下会自动赋予默认值,而局部变量不会自动赋值.


java中创建对象的几种方式

  1. new 关键字(调用构造函数)
  2. Class类的 newInstance方法(调用构造函数) ,java反射机制
  3. Constructor类newInstance方法(调用构造函数),java反射机制
  4. clone方法 (不调用构造函数,通过jvm创建新的对象,将对象的内容进行拷贝)
  5. 反序列化

==与equals

==:如果比较的是基本数据类型,判断的是两个数值是否相等,如果比较的是引用数据类型,判断是否指向同一内存地址.

equals:Object类中的equals方法与"=="作用相同,如果重写了equals方法,比较两个对象是否相等,通过equals比较两个对象中内容是否相同.


hashCode() 与 equals

hashCode()方法作用:获取对象的哈希码,哈希码又称为(散列码).

hashCode()和equals一样,都定义在Object类中(Object是所有类的父类),因此每个类中都继承了hashCode().

散列码:用来确定对象在散列表中的位置.

实质:将对象的内存地址转换成int类型返回,这个int整数(散列码)确定了对象在散列表中的索引位置.

散列表(哈希表):本质上是通过数组实现的.

存储的是键值对(key-value)通过键快速找到对应值.

键是通过对应散列码计算得到的

为什么要有hashCode()

以Set接口以及其实现类HashSet为例说明,Set集合特点是不允许重复元素,向Set集合中加入一个新的对象,

通过获取该对象的散列码计算该对象存放的位置,同时会和其他已经加入到集合的对象的位置作比较,如果hashCode值不相等,那么会认为没有重复对象,如果hashCode值出现相同的情况,会通过调用equals判断相同hashCode值的对象是否是相等的,如果相等那么就不允许加入,如果不相等,允许加入,重新将其散列到其他位置,减少了equals判定的次数,大大提高了执行速率.

例如:向HashSet集合中加入第101个元素,只需要对新加入的对象的散列码计算其位置加入到散列表中,不需要进行大量的equals比较.

hashCode产生作用情况

第一种:不会创建"类对应的散列表"

这里所说的“不会创建类对应的散列表”是说:我们不会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,不会创建该类的HashSet集合。 (说白了就是创建了对象之后,不使用HashSet,HashTable等等,单纯的比较它们的hashcode())

这种情况下:hashCode()和equals没有关系

比如:定义两个相同的对象它们的hashCode()值也不相同.

引用

10 public class NormalHashCodeTest{
11 
12     public static void main(String[] args) {
13         // 新建2个相同内容的Person对象,
14         // 再用equals比较它们是否相等
15         Person p1 = new Person("eee", 100);
16         Person p2 = new Person("eee", 100);
17         Person p3 = new Person("aaa", 200);
18         System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
19         System.out.printf("p1.equals(p3) : %s; p1(%d) p3(%d)\n", p1.equals(p3), p1.hashCode(), p3.hashCode());
20     }
21 
22     /**
23      * @desc Person类。
24      */
25     private static class Person {
26         int age;
27         String name;
28 
29         public Person(String name, int age) {
30             this.name = name;
31             this.age = age;
32         }
33 
34         public String toString() {
35             return name + " - " +age;
36         }
37 
38         /** 
39          * @desc 覆盖equals方法 
40          */  
41         public boolean equals(Object obj){  
42             if(obj == null){  
43                 return false;  
44             }  
45               
46             //如果是同一个对象返回true,反之返回false  
47             if(this == obj){  
48                 return true;  
49             }  
50               
51             //判断是否类型相同  
52             if(this.getClass() != obj.getClass()){  
53                 return false;  
54             }  
55               
56             Person person = (Person)obj;  
57             return name.equals(person.name) && age==person.age;  
58         } 
59     
60 }
结果:
p1.equals(p2) : true; p1(1169863946) p2(1901116749)
p1.equals(p3) : false; p1(1169863946) p3(2131949076)

第二种:会创建“类对应的散列表”

这里所说的“会创建类对应的散列表”是说:我们会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,会创建该类的HashSet集合。

在这种情况下,该类的“hashCode() 和 equals() ”是有关系的:
1)、如果两个对象相等,那么它们的hashCode()值一定相同。
这里的相等是指,通过equals()比较两个对象时返回true。
2)、如果两个对象hashCode()相等,它们并不一定相等。

比如:创建两个对象,将他们加入到HashSet集合中,如果只是重写了equals方法而不重写hashCode方法,两个相等的对象的hashCode是不同的,它们位于HashSet散列表中的位置不同,最后两个都加入了HashSet集合中,这与不允许重复元素的Set集合的规则相违背,因此重写了equals方法之后也必须重写hashCode(),保证两个相同对象的hashCode值一定相同.

 public class ConflictHashCodeTest2{
11 
12     public static void main(String[] args) {
13         // 新建Person对象,
14         Person p1 = new Person("eee", 100);
15         Person p2 = new Person("eee", 100);
16         Person p3 = new Person("aaa", 200);
17         Person p4 = new Person("EEE", 100);
18 
19         // 新建HashSet对象 
20         HashSet set = new HashSet();
21         set.add(p1);
22         set.add(p2);
23         set.add(p3);
24 
25         // 比较p1 和 p2, 并打印它们的hashCode()
26         System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
27         // 比较p1 和 p4, 并打印它们的hashCode()
28         System.out.printf("p1.equals(p4) : %s; p1(%d) p4(%d)\n", p1.equals(p4), p1.hashCode(), p4.hashCode());
29         // 打印set
30         System.out.printf("set:%s\n", set);
31     }
32 
33     /**
34      * @desc Person类。
35      */
36     private static class Person {
37         int age;
38         String name;
39 
40         public Person(String name, int age) {
41             this.name = name;
42             this.age = age;
43         }
44 
45         public String toString() {
46             return name + " - " +age;
47         }
48 
49         /** 
50          * @desc重写hashCode 
51          */  
52         @Override
53         public int hashCode(){  
54             int nameHash =  name.toUpperCase().hashCode();
55             return nameHash ^ age;
56         }
57 
58         /** 
59          * @desc 覆盖equals方法 
60          */  
61         @Override
62         public boolean equals(Object obj){  
63             if(obj == null){  
64                 return false;  
65             }  
66               
67             //如果是同一个对象返回true,反之返回false  
68             if(this == obj){  
69                 return true;  
70             }  
71               
72             //判断是否类型相同  
73             if(this.getClass() != obj.getClass()){  
74                 return false;  
75             }  
76               
77             Person person = (Person)obj;  
78             return name.equals(person.name) && age==person.age;  
79         } 
80     }
81 }

结果:
p1.equals(p2) : true; p1(68545) p2(68545)
p1.equals(p4) : false; p1(68545) p4(68545)
set:[aaa - 200, eee - 100]

这下,equals()生效了,HashSet中没有重复元素。
比较p1和p2,我们发现:它们的hashCode()相等,通过equals()比较它们也返回true。所以,p1和p2被视为相等。
比较p1和p4,我们发现:虽然它们的hashCode()相等;但是,通过equals()比较它们返回false。所以,p1和p4被视为不相等。


Java为什么只有值传递

**按值调用(call by value)**表示方法接收的是调⽤者提供的值,⽽按引用调用(call by reference)表示⽅法接收的是调用者提供的变量地址。

java中只有值传递,只不过引用类型的变量里的值是个内存地址罢了,都是复制一份做赋值操作 。

public static void main(String[] args) {
 int num1 = 10;
 int num2 = 20;
 swap(num1, num2);
 System.out.println("num1 = " + num1);
 System.out.println("num2 = " + num2);
}
public static void swap(int a, int b) {
 int temp = a;
 a = b;
 b = temp;
 System.out.println("a = " + a);
 System.out.println("b = " + b);
}

结果:
a = 20
b = 10
num1 = 10
num2 = 20
image-20210710093547068

num1和num2都是基本数据类型,而传入swap方法中的参数,只是num1和num2的拷贝,因此无论swap方法中参数如何变化,都不会影响num1和num2的数据。

 public static void main(String[] args) {
 int[] arr = { 1, 2, 3, 4, 5 };
 System.out.println(arr[0]);
 change(arr);
 System.out.println(arr[0]);
 }
 public static void change(int[] array) {
 // 将数组的第⼀个元素变为0
 array[0] = 0;
 }
 
 结果:
 1
 0
image-20210710093933633

arr指向堆中的数组对象,change方法中array是数组对象引用的拷贝,它们指向的都是同一个数组对象,因此方法中通过array[0] = 0 ,指向的就是堆中数据,因此外部对对象引用的改变反映到所对应的对象上。

Java 程序设计语⾔对对象采⽤的不是引⽤调⽤,实际上,对象引⽤是按值传递的。

下⾯再总结⼀下 Java 中⽅法参数的使⽤情况:

⼀个⽅法不能修改⼀个基本数据类型的参数(即数值型或布尔型)。

⼀个⽅法可以改变⼀个对象参数的状态。

⼀个⽅法不能让对象参数引⽤⼀个新的对象。


final关键字

主要引用范围:变量,方法,类

变量:被修饰的基本类型,如果没有被赋值(final number),一旦被赋值之后就不能重新赋值(final number = 1)

​ 被修饰的引用类型,保证了引用类型变量所指向的地址不改变,而不能保证所引用的对象不改变。

补充:

byte b1 = 1, b2 = 2, b3 ,b4
final byte b5 =4,b6=6;

//试问,下面哪里编译错误?
1. b3 = b5 + b6;
2. b4 = b1 + b2;

//答案:2编译错误
/*原因:基本数据类型:byte、char、short在做运算时,都会自动进行转换成int、b1 + b2最后的结果时int, b4确是byte类型,类型不匹配。而b3 = b5 + b6;编译成功,b3 = 10,因为final关键字修饰后,b5和b6都是常量类型byte,最后10也是btye类型因此编译成功。
*/

方法:不可以被继承的子类所重写(覆盖)。

​ 效率原因,早期final方法为内嵌调用,如果方法过于庞大,并不能得到提升。

类:不能被继承,类中所有成员方法都被隐式指定为final。


Java异常

image-20210710095851862

在 Java 中,所有的异常都有⼀个共同的祖先 java.lang 包中的 Throwable 类。 Throwable 类 有两个重要的⼦类 Exception (异常)和 Error (错误)。 Exception 能被程序本身处理( try-catch ), Error 是⽆法处理的(只能尽量避免)。

Error

  1. Error 类是指 java 运行时系统的内部错误和资源耗尽错误。应用程序不会抛出该类对象。如果 出现了这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止。

Exception(RuntimeException、CheckedException)

  1. Exception 又 有 两 个 分 支 , 一 个 是 运 行 时 异 常 RuntimeException , 一 个 是 CheckedException。

RuntimeException 如 : NullPointerException 、 ClassCastException ; 一 个 是 检 查 异 常 CheckedException,如 I/O 错误导致的 IOException、SQLException。 RuntimeException 是 那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。 如果出现 RuntimeException,那么一 定是程序员的错误(想想空指针问题、数组越界的问题).

检查异常 CheckedException:一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强制程序去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try catch。所以编译时的异常必须显示处理,运行时异常交给虚拟机处理。

异常处理方式

throws,throw

try-catch-finally

系统自动抛出异常

try{
    return  						//finally块前(try/catch)中有return,会先执行return语句,
        							//并保存下来,再执行finally块,最后return
}
catch(){
    
}finally{}
try{
return  							//finally块前(try/catch)中有return,finally块中也有
									//return会先执行前面return语句
        							//并保存下来,再执行finally块中的return,覆盖掉之前的结果,返回。
}
catch(){
    
}finally{
	return
}

补充:在tyr、catch语句中,catch语句块允许有多个,若出现父子类型的异常,需要把子类型的异常放在前面,父类型的异常放在后面。

比如:Exception是所有异常的父类,若将它放在首位,那么就没有办法定位到具体的异常类型,不知道具体的异常,只知道有异常,不好检查。

finally不会被执行情况

try或finally块中有system.exit(0)(推出虚拟机)
关闭cpu
程序所在线程死亡

throw和throws区别

  • throws位于函数上,可以定义多个异常类

    throw位于函数内部,定义异常对象

  • throws声明异常,表示函数中可能发生的异常,给出预先处理方式

    throw抛出异常,抛出具体问题对象,此时功能到此已经结束了

  • 两者都是消极处理异常方式,只是抛出异常,但是不会由函数处理异常,真正处理异常由函数上层调用处理。


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值