Java复习笔记(5)——Java基础知识

版权声明:本文为博主原创,未经博主允许不得转载。 https://blog.csdn.net/weixin_36904568/article/details/90754577

一:异常

如果某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返回任何值。另外,调用这个方法的其他代码也无法继续执行,异常处理机制会将代码执行交给异常处理器。

1. 分类

在这里插入图片描述
Throwable 是 Java 语言中所有错误或异常的超类,下一层分为 Error 和 Exception

  • Error:Error 类是指 java 运行时系统的内部错误和资源耗尽错误。应用程序不会抛出该类对象。如果出现了这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止
  • Exception
    • 运行时异常 RuntimeException:一定是代码错误,是可能在 Java 虚拟机正常运行期间抛出的异常的超类
    • 可检查异常 CheckedException:一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强制程序去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try catch,该类异常一般包括几个方面:
      • 试图在文件尾部读取数据
      • 试图打开一个错误格式的 URL
      • 试图根据给定的字符串查找 class 对象,而这个字符串表示的类并不存在

2. 处理方式

  • 抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
  • 捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器
    • 潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。
    • 当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。
    • 当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。

捕获异常:

try-catch语句:Java方法在运行过程中出现异常,则创建异常对象。将异常抛出try语句块之外,由Java运行时系统试图寻找匹配的catch子句以捕获异常(异常对象属于catch子句的异常类,或者属于该异常类的子类)。若有匹配的catch子句,则运行其异常处理代码,try-catch语句结束。

  • 必须在 try 之后添加 catch 或 finally 块。try 块后可同时接 catch 和 finally 块,但至少有一个块

try-catch-finally语句:finally子句。它表示无论是否出现异常,都应当执行的内容

  • 当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行
  • 在finally语句块中发生了异常、在finally代码中用了System.exit()退出程序、程序所在的线程死亡、关闭CPU时,finally语句块不会执行

执行顺序:

  1. 当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;
  2. 当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;
  3. 当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句;(不管有没有出现异常,finally块中代码都会执行)

抛出异常:

throws:
如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常,抛向调用该方法的方法,该方法的调用者必须处理或者重新抛出该异常

throw:
throw总是出现在函数体中,用来抛出一个Throwable类型的异常。程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。

Throw throws
用在函数内,后面跟的是异常对象 用在函数上,后面跟的是异常类,可以跟多个
抛出具体的问题对象,执行到 throw时功能就已经结束了,跳转到调用者 声明异常,让调用者只知道该功能可能出现的问题,可以给出预先的处理方式
一定出现异常 可能出现异常

二:反射

动态语言,是指程序在运行时可以改变其结构:新的函数可以引进,已有的函数可以被删除等结构上的变化。比如常见的 JavaScript 就是动态语言,除此之外 Ruby,Python 等也属于动态语言,而 C、C++则不属于动态语言。从反射角度说 JAVA 属于半动态语言。

1. 反射机制

在这里插入图片描述
在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法,也就是动态获取信息以及动态调用对象方法的功能

  • Class 类:反射的核心类,可以获取类的属性,方法等信息。
  • Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值
  • Method 类: Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法
  • Constructor 类: Java.lang.reflec 包中的类,表示类的构造方法

2. 应用场合

  • 编译时的类型由声明对象时实用的类型来决定
  • 运行时的类型由实际赋值给对象的类型决定

由于编译时类型无法获取具体方法,而程序在运行时还可能接收到外部传入的对象,该对象的编译时类型为 Object,但是程序有需要调用该对象的运行时类型的方法。

为了解决这些问题,程序只能依靠运行时信息来发现该对象和类的真实信息,此时就必须使用到反射了

扫描二维码关注公众号,回复: 6455958 查看本文章

3. 反射使用步骤

  1. 获取想要操作的类的 Class 对象,他是反射的核心,通过 Class 对象我们可以任意调用类的方法。
  2. 调用 Class 类中的方法,既就是反射的使用阶段。
  3. 使用反射 API 来操作这些信息。

(1)获取类

调用某个对象的 getClass()方法
Person p = new Person(); 
Class clazz = p.getClass();
调用某个类的 class 属性来获取该类对应的 Class 对象
Class clazz = Person.class;
使用 Class 类中的 forName()静态方法 (最安全/性能最好)
Class clazz = Class.forName("类的全路径");

(2)使用类

//获取 Person 类的所有方法信息
Method[] method=clazz.getDeclaredMethods();
for(Method m:method){ 
	System.out.println(m.toString());
}
 
//获取 Person 类的所有成员属性信息 
Field[] field=clazz.getDeclaredFields(); 
for(Field f:field){
	System.out.println(f.toString());
}
 
//获取 Person 类的所有构造方法信息
Constructor[] constructor=clazz.getDeclaredConstructors();
for(Constructor c:constructor){ 
	System.out.println(c.toString());
}

(3)创造对象的方法

调用 Class 对象的 newInstance()

这种方法要求该 Class 对象对应的类有默认的空构造器

调用 Constructor 对象的 newInstance()

先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance()方法来创建 Class 对象对应类的实例。通过这种方法可以选定构造方法创建实例。

三:注解

Annotation(注解)是 Java 提供的一种对元程序中的元素关联信息和元数据(metadata)的途径和方法。

Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的 Annotation对象,然后通过该 Annotation 对象来获取注解中的元数据信息。

原则:注解不能直接干扰程序代码的运行

分类:

  • 标注注解
  • 单值注解
  • 完整注解

1. 系统注解

(1)元注解

元注解的作用是负责注解其他注解。 Java5.0 定义了 4 个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明。

@Target (修饰的对象范围)

@Target说明了Annotation所修饰的对象范围:

  • packages
  • types(类、接口、枚举、Annotation 类型)
  • 类型成员(方法、构造方法、成员变量、枚举值)
  • 方法参数和本地变量(如循环变量、catch 参数)

在 Annotation 类型的声明中使用了 @target 可更加明晰其修饰的目标

@Retention (定义被保留的时间长短)

Retention 定义了该 Annotation 被保留的时间长短:表示需要在什么级别保存注解信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

  • SOURCE:在源文件中有效(即源文件保留)
  • CLASS:在 class 文件中有效(即 class 保留)
  • RUNTIME:在运行时有效(即运行时保留)
@Documented (描述-javadoc)

@ Documented 用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。

@Inherited (阐述了某个被标注的类型是被继承的)

@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该class 的子类。

(2)标准注解

@Override(继承)

保证编译时候函数声明的正确性

@Deprecated(警告)

不应该再使用的过时方法或过时类

@SuppressWarnings(忽略警告)

关闭特定的警告信息

  • deprecation: 使用了不赞成使用的类或方法时的警告
  • unchecked:执行了未检查的转换时的警告
    例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型。
  • fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告。
  • path:在类路径、源文件路径等中有不存在的路径时的警告。
  • serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告。
  • finally:任何 finally 子句不能正常完成时的警告。
  • all:关于以上所有情况的警告。

2. 注解处理器

如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了

可以通过包、类、构造器、成员变量、方法获取相关的注解:

  • getAnnotation(注解类型):返回元素上的指定类型的注解
  • getAnnotations():返回元素上的所有注解
  • isAnnotationPresent(注解类型):判断元素上是否存在指定类型的注解
  • getDeclaredAnnotations():返回元素上直接存在的所有注解
/1:*** 定义注解*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider { 
	/**供应商编号*/
	public int id() default -1;
	/*** 供应商名称*/
	public String name() default "";
	/** * 供应商地址*/
	public String address() default "";
}

//2:注解使用
public class Apple {
	@FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西省西安市延安路")
	private String appleProvider;
	
	public void setAppleProvider(String appleProvider) {
		this.appleProvider = appleProvider;
	}
	
	public String getAppleProvider() {
		return appleProvider; 
	}
}

/3:*********** 注解处理器 ***************/
public class FruitInfoUtil {
 
	public static void getFruitInfo(Class<?> clazz) {
		
		String strFruitProvicer = "供应商信息:";
		 
		Field[] fields = clazz.getDeclaredFields();//通过反射获取处理注解
		 
		for (Field field : fields) {
			if (field.isAnnotationPresent(FruitProvider.class)) {
				FruitProvider fruitProvider = (FruitProvider) field.getAnnotation(FruitProvider.class);
				//注解信息的处理地方
				strFruitProvicer = " 供应商编号:" + fruitProvider.id() + " 供应商名称:"+ fruitProvider.name() + " 供应商地址:"+ fruitProvider.address(); 
				System.out.println(strFruitProvicer);
			}
		} 
	}
}

public class FruitRun {
 
	public static void main(String[] args) {
	 
		FruitInfoUtil.getFruitInfo(Apple.class);
		/***********输出结果***************/
		// 供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延
		 
	}
}

四:内部类

定义在类内部的类就称为内部类

1. 静态内部类

定义在类内部的静态类,就是静态内部类

public class Out {
 
	private static int a;
	 
	private int b;
	 
	public static class Inner {
		public void print() {
			System.out.println(a);
		}
	}
}
  • 静态内部类可以访问外部类所有的静态变量和方法,即使是 private 的也可以
  • 静态内部类和一般类一致,可以定义静态变量、方法,构造方法
  • 其它类使用静态内部类需要使用“外部类.静态内部类”方式
    Out.Inner inner = new Out.Inner(); inner.print();

Java集合类HashMap内部就有一个静态内部类Entry。Entry是HashMap存放元素的抽象,HashMap 内部维护 Entry 数组用了存放元素,但是 Entry 对使用者是透明的。
像这种和外部类关系密切的,且不依赖外部类实例的,都可以使用静态内部类。

2. 成员内部类

定义在类内部的非静态类,就是成员内部类

public class Out {
 
	private static int a;
	 
	private int b;
	 
	public class Inner {
		public void print() {
			System.out.println(a);
			System.out.println(b);
		}
	}
}

成员内部类不能定义静态方法和变量(final 修饰的除外)
这是因为成员内部类是非静态的,类初始化的时候先初始化静态成员,如果允许成员内部类定义静态变量,那么成员内部类的静态变量初始化顺序是有歧义的。

3. 局部内部类

定义在方法中的类,就是局部类。

public class Out {
 
	private static int a;
	 
	private int b;
	 
	public void test(final int c) {
	
		final int d = 1;
		 
		class Inner {
			public void print() {
				System.out.println(c);	 
			}
		}
	}
}

如果一个类只在某个方法中使用,则可以考虑使用局部类。

4. 匿名内部类

必须要继承一个父类或者实现一个接口,同时它也是没有 class 关键字,这是因为匿名内部类是直接使用 new 来生成一个对象的引用

public abstract class Bird {

	private String name; 
	
	public String getName() { return name; }
	
	public void setName(String name) { this.name = name; }
	 
	public abstract int fly();
}

public class Test {
 
	public void test(Bird bird){ System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米"); }
	 
	public static void main(String[] args) {
	
		Test test = new Test();
		 
		test.test(
			new Bird() {
				public int fly() {	return 10000; }
				public String getName() { return "大雁"; } 
			}
		);
	}
}

五:泛型

泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

1. 泛型方法(< E >)

该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

  • <? extends T>表示该通配符所代表的类型是 T 类型的子类。
  • <? super T>表示该通配符所代表的类型是 T 类型的父类。
// 泛型方法 printArray
public static < E > void printArray( E[] inputArray ){ 
	for ( E element : inputArray ){ 
		System.out.printf( "%s ", element );
	}
}

2. 泛型类< T >

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。

一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

public class Box<T> { 
	
	private T t;
	
	public void add(T t) { this.t = t; }
	
	public T get() { return t; }
}

3. 类型通配符?

类型通配符一般是使用 ? 代 替 具 体 的 类 型 参 数 。 例 如 List<?> 在逻辑上是
List< String >,List< Integer > 等所有 List<具体类型实参>的父类。

4. 类型擦除

Java 中的泛型基本上都是在编译器这个层次来实现的。在生成的 Java 字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉,这个过程就称为类型擦除。

如在代码中定义的 List< Object >和 List< String >等类型,在编译之后都会变成 List。JVM 看到的只是 List,而由泛型附加的类型信息对 JVM 来说是不可见的。

类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是 Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。

六:序列化(创建可复用的 Java 对象)

1. 概念

Java 对象序列化在保存对象时,会把其状态保存为一组字节
Java 对象反序列化在生成对象时,会把一组字节组装成对象

  • 在 Java 中,只要一个类实现了 java.io.Serializable 接口或是Externalizable接口,那么它就可以被序列化
  • 虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,还取决于两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)
  • 通过 ObjectOutputStream 和 ObjectInputStream 对对象进行序列化及反序列化
  • 在类中增加 writeObject 和 readObject 方法可以实现自定义序列化策略
  • Transient 关键字可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null

2. 作用

(1)保存(持久化)对象及其状态到内存或者磁盘

Java 平台允许我们在内存中创建可复用的 Java 对象,但一般情况下,只有当 JVM 处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比 JVM 的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。

(2)序列化用户远程对象传输

当使用 RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用。

3. 特点

  • 对象序列化不会关注类中的静态变量:对象序列化保存的是对象的”状态”,即它的成员变量
  • 序列化时,只对对象的状态进行保存,而不管对象的方法
  • 要想将父类对象也序列化,就需要让父类也实现 Serializable 接口
  • 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口
  • 当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化

七:JAVA 复制

将一个对象的引用复制给另外一个对象

(1)直接赋值复制

A a1 = a2;

实际上复制的是引用,也就是说 a1 和 a2 指向的是同一个对象。因此,当 a1 变化的时候,a2 里面的成员变量也会跟着变化

(2)浅复制

创建一个新对象,然后将当前对象的非静态字段复制到该新对象,原始对象及其副本引用同一个对象。

  • 字段是值类型的,那么对该字段执行复制
  • 字段是引用类型的话,则复制引用但不复制引用的对象。
class Resume implements Cloneable{ 

	public Object clone() { 
	 
		try { 

			return (Resume)super.clone(); 

		} catch (Exception e) { 

			e.printStackTrace(); 
		
			return null; 
		} 
	} 
}

(3)深复制

深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象。

在 Java 语言里深复制一个对象,常常可以先使对象实现 Serializable 接口,然后把对
象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。

class Student implements Cloneable {
 
	String name;
	 
	Professor p;
	 
	Student(String name, Professor p) {
	 
		this.name = name;	 
	
		this.p = p;
	}
	 
	public Object clone() {
	 
		Student o = null;
	 
		try {
			o = (Student) super.clone();
		} catch (CloneNotSupportedException e) {

		System.out.println(e.toString()); 
		}
		
		o.p = (Professor) p.clone(); 
	
		return o; 
	}
}

八:基本数据类型

1. 基本知识

在这里插入图片描述
其中,每一个基本数据类型都有对应的对象包装类型,基本数据类型和对象包装类型可以互相转换

  • 自动装箱:Java的基本数据类型转换为对象包装类型
  • 自动拆箱:Java的对象包装类型转换为基本数据类型

2. 注意事项

(1)String类型转换为Integer类型

可以通过Integer.parseInt(s)转换,但是需要处理NumberFormatException异常

  • 字符串为空串
  • 字符串为字母串
  • 字符串代表的整数超过int范围

(2)short类型和int类型

  • short s1 = 1; s1 = s1 + 1;有什么错?
    由于 s1 + 1会将s1向上转型为int,而s1=s1+1会把int类型赋值给short类型,导致类型不匹配的错误

  • short s1 = 1; s1 +=1;有什么错?
    由于+=会被java编译器特殊处理,所以没有错误

(3)int和Integer类型

参考博客:java面试题之int和Integer的区别

  • Integer是int的包装类,int则是java的一种基本数据类型
  • Integer变量必须实例化后才能使用,而int变量不需要
  • Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
  • Integer的默认值是null,int的默认值是0
public class IntegerTest {
    public static void main(String[] args) {
        Integer i1 = new Integer(10);
        Integer i2 = new Integer(10);
        System.out.println("由于Integer是一个对象的引用,通过new方法创建了两个对象,引用的内存地址不相同,结果为false");
        System.out.println("i1==i2:"+(i1==i2));
        int i3 = 10;
        System.out.println("由于Integer和int比较时会自动拆箱,只需要比较值,结果为true");
        System.out.println("i1==i3:"+(i1==i3));
        System.out.println("由于new方法会在堆中创建了新的对象,非new方法是指向常量池中的对象,所以引用的地址不相同,结果为false");
        Integer i4 = 10;
        System.out.println("i1==i4:"+(i1==i4));
        System.out.println("由于非new方法指向常量池中的对象,所以只需要比较值(在-128到127之间才会缓存,其他范围不会缓存,永远都会创建新的对象),结果为true");
        Integer i5 = 10;
        System.out.println("i4==i5:"+(i4==i5));

    }
}

(4)字节和字符的区别

  • 字节:存储容量的基本单位,一个字节=8个二进制单位
  • 字符:数字、汉字、字母等各种符号,一个字符由一个或多个字节的二进制单位组成

(5)基本类型和引用类型

  • 基本类型:保存数据的原始值
  • 引用类型:保存数据的引用值(对象在堆中的地址)

九:String、StringBuffer、StringBuilder

(1)基本概念

特点 String StringBuilder StringBuffer
可继承性 都是final类,无法被继承
长度 长度不可变 长度可变
线程安全性 线程不安全 线程安全
性能 如果在编译时确定是常量
则编译完成后,自动拼接成常量
性能最好

(2)不可变的String

不可变的原因:

  • 提高效率:多个指针指向同一个常量,彼此不会影响
  • 安全
  • 无需多次计算哈希值
public class StringTest {
    public static void main(String[] args) {
        System.out.println("在常量池创建字符串对象abc");
        String s1 = "abc";
        System.out.println("由于常量池已经有这个字符串对象了,所以不会再创建新的字符串对象");
        String s2 = "abc";
        System.out.println("由于直接赋值,都在常量池上查找字符串对象,所以只需要比较两个字符串的内容是否相同,返回true");
        System.out.println("s1==s2:"+(s1==s2));
        System.out.println("通过new方法,会在堆上创建字符串对象abc,但是常量池已经有这个对象了,所以不需要在常量池创建");
        String s3 = new String("abc");
        System.out.println("由于new方法指向的是堆上的字符串对象,而直接赋值指向的是常量池的字符串对象,内存地址不同,返回false");
        System.out.println("s1==s3:"+(s1==s3));
        System.out.println("由于两个new方法指向的是堆上的不同字符串对象,内存地址不相同,返回false");
        String s4 = new String("abc");
        System.out.println("s3==s4:"+(s3==s4));
        System.out.println("由于多个双引号形式的字符串使用+号,会通过StringBuilder的append方法自动拼接成一个字符串常量,相当于在常量池创建对象,返回true");
        String s5 = "a"+"b"+"c";
        System.out.println("s1==s5:"+(s1==s5));
        System.out.println("由于多个字符串常量使用+号,由于拼接之后会使用toString方法,相当于new方法在堆上创建对象,返回false");
        String s6_1 = "a";
        String s6_2 = s6_1+"bc";
        System.out.println("s1==s6:"+(s1==s6_2));
        System.out.println("使用intern方法,会返回常量池中相同字符串内容的引用,如果没有则在常量池中创建该字符串对象,返回true");
        String s7 = s3.intern();
        System.out.println("s1==s7:"+(s1==s7));


    }
}

String重写了Object的toString方法和hashCode方法

public String toString() {
        return this;
    }
public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

一般来说,重写equals一定要重写hashcode。

  • 两个对象相等,hashcode一定相等。两个对象不等,hashcode不一定不等
  • hashcode相等,两个对象不一定相等
  • hashcode不等,两个对象一定不等

否则在存储集合时,如果两个对象拥有不同的哈希值却相等,则在集合中会存储两个值相等的对象,导致混淆

(3)hashcode字符串攻击

当客户端提交请求并携带参数时,服务器会把参数转化成键值对Key-Value,但由于物理存储结构不同,Key会进行转换Key→哈希值→数组下标,存储数组下标-Value。多个哈希值可能发生碰撞

  • 限制post和get的参数个数
  • 限制post数据包的大小
  • WAF
  • Java7会专门使用一个treemap替换它

十:Java的基本特性

1. 基本概念

  • 抽象:将一组类的共同特征总结起来构造类的过程,只关注对象的属性和行为,不关注行为细节
    • 数据抽象
    • 行为抽象
  • 继承:从已有类得到继承信息创建新类的过程,提供信息的为父类,接收信息的为子类。让变化的软件系统具有延续性,封装程序的可变因素
  • 封装:隐藏一切可以隐藏的东西,只提供简单的编程入口
    • 方法:隐藏实现细节
    • 类:隐藏数据和对数据的操作
  • 多态:允许不同的子类型的对象对同一消息作出不同的响应

2. 注意事项

(1)多态的实现方式

  • 方法重载(overload):实现的是编译时的多态(前绑定)
  • 方法重写(override)或覆盖:实现的是运行时的多态(后绑定)
    • 子类对父类的方法进行方法重写
    • 用父类型的引用,引用子类型对象
重载 重写
可以在一个类中,或在一个继承的类中 不能存在同一个类中,可以存在具有继承或实现关系的类中
方法名相同,方法参数列表不同,与返回值类型无关 方法名,方法参数列表,方法返回值相同
子类的方法修饰符的类型大于父类的
子类的检查异常类型小于父类的

(2)覆盖static和private方法

  • 不能覆盖static方法:方法覆盖是基于运行时动态绑定的,static方法是编译时静态绑定的
  • 不能覆盖private方法:子类无法访问到private方法,无法覆盖

(3)继承的特点

优点:

  • 子类自动继承父类的接口
  • 创建子类的对象时无需创建父类对象

缺点:

  • 破坏封装
  • 子类与父类紧密耦合,子类依赖父类的实现,缺乏独立性
  • 系统结构复杂
  • 不支持动态继承
  • 子类无法改变父类的接口

3. static

  • 修饰变量:静态变量随着类加载时被初始化,内存只有一个,所有类共享静态变量
  • 修饰方法:在类加载的时候存在,static方法必须实现,不能用abstract修饰
  • 修饰代码块:类加载后执行代码块的内容

执行顺序

  • 先加载类,如果有父类则先加载父类
    • 父类静态变量
    • 父类静态代码块
    • 子类静态变量
    • 子类静态代码块
  • 创建对象,如果有父类则先创建父类对象
    • 父类非静态代码块
    • 父类构造方法
    • 子类非静态代码块
    • 子类构造方法
class A{
    static {
        System.out.println("A的静态构造块");
    }
    {
        System.out.println("A的非静态构造块");
    }

    public A() {
        System.out.println("A的构造函数");
    }
}
class B extends A{
    static {
        System.out.println("B的静态构造块");
    }
    {
        System.out.println("B的非静态构造块");
    }

    public B() {
        System.out.println("B的构造函数");
    }
}
public class staticTest {
    public static void main(String[] args) {
        B b = new B();
    }
}

输出:‘
A的静态构造块
B的静态构造块
A的非静态构造块
A的构造函数
B的非静态构造块
B的构造函数

class Parenet{
    //父类的静态变量
    private static Parenet parenet = new Parenet();
    static {
        System.out.println("父类的静态构造块");
    }
    {
        System.out.println("父类的非静态构造块");
    }

    public Parenet() {
        System.out.println("父类的构造函数");
    }
}
public class staticTest  extends Parenet{
    //子类的静态变量
    private static staticTest test = new staticTest();
    static {
        System.out.println("子类的静态构造块");
    }
    {
        System.out.println("子类的非静态构造块");
    }

    public staticTest() {
        System.out.println("子类的构造函数");
    }
    public static void main(String[] args) {
        System.out.println("输出语句1");
        staticTest b = new staticTest();
        System.out.println("输出语句2");
    }
}

输出:
//父类的静态变量即构造父类对象
父类的非静态构造块
父类的构造函数
//父类的静态构造块
父类的静态构造块
//子类的静态变量即构造子类对象(先构造父类对象)
父类的非静态构造块
父类的构造函数
子类的非静态构造块
子类的构造函数
//子类的静态构造块
子类的静态构造块
//加载类完成
输出语句1
//开始创建子类对象,先创建父类对象
父类的非静态构造块
父类的构造函数
子类的非静态构造块
子类的构造函数
//创建对象完成
输出语句2

注意:

  • static方法不能被覆盖
  • 不能在static环境下访问非static变量
  • 静态方法中不能使用this和super关键字
  • static变量是非线程安全的,需要使用volatile修饰

4. final

  • 修饰变量:不能被改变
    • 编译期:类加载过程完成初始化,编译后带入到任何计算式中,只能是基本类型
    • 运行期:基本数据类型或引用数据类型,引用不可变(引用的对象内容可变)
  • 修饰方法:不能被子类修改
  • 修饰类:不能被继承
  • 修饰形参:形参不可变

好处:

  • 提高性能(JVM和应用都会缓存变量)
  • 在多线程下可以共享
  • JVM会对方法、变量、类进行优化

5. finalize、finally

  • finalize:回收特殊申请的内存,一般情况下的内存由垃圾回收器GC处理,而JNI调用非Java程序所占内存无法处理
    • 在对象被回收前,被JVM调用
  • finally:try语句在return之前会执行finally的语句,如果finally有return语句,则会覆盖之前try中的return语句;否则finally无法改变try中的值
    • 一定会被执行,释放资源

6. equal和==的比较

参考博客:java equal和==的比较,尤其注意基本类型和基本类型的包装类型的比较

字符串

对于字符串变量来说,使用“==”和“equals()”方法比较字符串时,其比较方法不同

  • “==”比较两个变量本身的值,即两个对象在内存中的首地址
  • “equals()”比较字符串中所包含的内容是否相同

对于StringBuffer和StringBuilder,equals()和==一样,比较两个对象在内存中的首地址

其他类型

对于非字符串变量来说,"=="和"equals"方法的作用是相同的都是用来比较其对象在堆内存的首地址,即用来比较两个引用变量是否指向同一个对象。

基本类型

如果是基本类型比较,那么只能用==来比较,不能用equals

基本类型的包装类

对于基本类型的包装类型,比如Boolean、Character、Byte、Shot、Integer、Long、Float、Double等的引用变量

  • ==是比较地址的
  • equals是比较内容的

猜你喜欢

转载自blog.csdn.net/weixin_36904568/article/details/90754577
今日推荐