Java复习遗忘知识点

1.实例化对象的内存问题

堆内存:保存的是对象的具体些信息,在程序之中堆内存空间的开辟是通过"new"完成的。

栈内存:保存的是一块堆内存(对象)的地址,即通过地址找到堆内存,而后找到对象内容。

2.构造方法和普通方法的重要区别

构造方法是在类实例化(new 类)时候调用的,而普通方法是在类对象实例化产生之后调用的。

3.匿名对象

只通过实例化对象来进行类的操作也是被允许的,而new 出的对象 就是 匿名对象,因为并没有对其进行声明。

4.this关键字

this==当前对象:1this.成员属性       2this.成员函数       3在构造方法中通过this()调用其他构造方法,可带参数,且this()必须放在首行

5.static关键字(静态)

1.定义变量:static属性成为类变量,与成员变量(实例变量)不同,它可由类名直接调用,并不受到实例化对象的控制,即可以在没有实例化的时候通过 Class.类变量 进行调用(但也可以由实例化对象调用)。

2.定义方法:static定义的方法可以由类直接调用,并不受到实例化对象的控制。static方法只允许调用static属性或static方法。

6.代码块

1.普通代码块:就是类中方法的方法体    public void xxx() {   code   } 

2.构造块:用{}裹起来的代码片段,构造块在创建对象时会被调用,每次创建对象 时都会被调用,并且优先于类构造函数执行。 构造块中定义的变量是局部变量。  { code } 

3.静态块:用static{}裹起来的代码片段,只会被执行一次(第一次加载此类时执行,比如说用Class.forName("")加载类时就会执行 static  block),静态块优先于构造块执行。  static{  code } 

4.同步代码块:使用synchronized(obj){}裹起来的代码块,在多线程环境下,对共享数据进行读写操作是需要互斥进行的,否则 会导致数据的不一致性。常见的是synchronized用来修饰方法,其语义是任何线程进入synchronized需要先取得对象锁 如果被占用了,则阻塞,实现了互斥访问共享资源。而synchronized也是有代价的。 一个常见的场景是,一个冗长的方法中,其实只有一小段代码需要访问共享资源,这时使用同步块,就只将这小段代码 裹在synchronized  block,既能够实现同步访问,也能够减少同步引入的开销。 同步代码块须写在方法中。     synchronized(obj){ code } 

7.数组的定义(java中数组是引用类型,所以存储结构和引用类型一样。多维数组的话就是地址的嵌套存储)

例如二维数组的定义:

1.动态初始化: 数据类型 数组名称 [][] = new 数据类型 [行数][列数];

2.静态初始化: 数据类型 数组名称 [][] = new 数据类型 [][]{{....},{.....},....}

8.可变参数(适用于参数个数不确定,类型确定的情况,java把可变参数当做数组处理。

变种数组,可以通过数组的方式来遍历它

调用可变参数的方法时,编译器为该可变参数  隐含创建一个数组,在方法体中以  数组  的形式访问可变参数。

public static void Test(Object... params){
    for(int para : params){
    }
}

9.对象数组(对象也是引用类型,所以存储结构和引用类型一样。多维数组的话就是地址的嵌套存储)

例如二维数组的定义:

1.动态初始化: 实体类类型 数组名称 [][] = new 实体类类型 [行数][列数];

2.静态初始化: 实体类类型 数组名称 [][] = new 实体类类型 [][]{{....},{.....},....}

//1.静态初始化(直接设置数组元素)
Person per[] = new Person[]{new Person(构造方法参数),...,}; //Person 对象数组
//2.动态初始化(只申请n个空间)
Person per[] = new Person[n];

注:所有的开发都离不开对象数组。

10.数据表与Java类对应的基础规则

1.数据库实体表设计 = 类的定义 

2.表中的字段 = 类的成员属性

4.表的外键关联 = 引用关连(类的嵌套调用)

5.表的一行记录 = 类的一个实例化对象 

5.表的多行记录 = 对象数组

11.实体类之间的对应关系

例如:public class A; public class B

1.一对一

在A类中: private B b; 并提供get set方法

在B类中: private A a; 并提供get set方法

2.一对多

在A类中: private B[] b; 并提供get set方法

在B类中: private A a; 并提供get set方法

3.多对多

在A类中: private B b[]; 并提供get set方法

在B类中: private A a[]; 并提供get set方法

12.String类的使用

https://www.runoob.com/java/java-string.html

13.继承性(面向对象第二大特征) 

继承的作用就是子类可以重用父类中的结构,并且可以实现功能的扩充。

super()表示的就是子类构造调用父类构造的语句,该语句只允许放在子类构造方法的第一行,在默认情况下的实例化处理,子类只会调用父类中的无参构造方法,所以写与不写super()区别不大。但如果父类中没有提供无参构造,这时就必须在子类构造里利用super(参数)调用父类有参构造。

【注】实例化子类对象的同时一定会实例化父类对象,目的是为了所有的属性可以进行空间的分配。super与this都可以调用构造方法,super是由子类调用父类的构造,而this是调用本类对象,且双方都必须放在首行,所以两个语句不允许同时出现。

Java中不允许多重继承,只允许多层继承(一个人只有一个爹,但爹还可以有爹),private修饰的数据不允许被继承。

14.覆写(基于继承关系)

1.覆写简介

当子类中定义了与父类方法名称相同,参数类型及个数完全相同的时候(即跟父类方法一模一样),就成为方法的覆写。

在实例化子类对象时,此时调用的方法一定是子类中被覆写过的方法,如果父类中的方法没有被覆写过,那么将调用父类提供的方法。

【注】在子类调用父类方法的时候一定要在方法前追加“super”,否则将默认调用子类方法

2.方法覆写的限制

子类中覆写的方法不允许有比父类更严格的访问权限。

3.重载overloading和重写override的区别?

a.重载:方法名相同,参数类型或个数不同;   发生在一个类中;                         对返回值没有要求(但是正常都会保持- -致);                                     对访问权限没有要求(但是正常都会保持一致);

b.重写:方法名、参数类型及个数相同;          发生在继承关系中(子类和父类); 如果子类和父类访问权限--致则返回值必须要相同,否则可以不同; 子类不能拥有比父类更严格的访问权限;

4.final关键字

在java中使用final关键字可以实现:

a. 定义不能被继承的类

b.定义不能够被覆写的方法以及常量

c.定义一个不能被修改的常量

15.Annotation注解

Annotation是从JDK1.5之后提出的一个新的开发技术结构,利用Annotation可以有效减少程序的配置代码,并且可以利用Annotation进行一些结构化的定义。Annotation是以一种注解的形式实现的程序开发。

1. @override 明确覆写

开发之中经常遇到的两个问题:a. 虽然要明确继承一个父类并进行方法的覆写,但可能由于疏忽忘了写extends。   b. 在进行方法覆写的时候单词写错了。

但这些问题编译的时候不会报错,只是不构成了覆写。为了避免这些问题的出现,可以在明确覆写的方法上追加注解@override,帮助开发者在程序编译的时候检查覆写代码的错误。

2. @Deprecated 过期操作

过期操作指的是在一个软件项目的迭代开发过程之中,有某一个方法或者某一个类由于在最初的设计时考虑不周(存在缺陷),导致新版本的应用会有不适应的地方(老版本不影响),这个时候又不能直接删除,于是采用一种过期的声明,目的就是告诉新的用户这些操作不要再用了,老的用户用就用了新项目别再用了。

3. @suppressWarnings 压制警告 

以之前的过期程序为例,可以发现在进行程序编译的时候会出现一个错误提示信息,如果此时不想见到这些提示信息,那么可以进行警告信息的压制@suppressWarnings

16.多态性(面向对象第三大特征)

1. 多态性简介

多态性是在继承性的基础之上扩展出来的概念,即可以实现父子的相互转换处理。在java中对多态性有两种实现模式:

------方法的多态性:

       |--- 方法的重载(静态的多态表现形式:编译时):同一个方法可以根据传入的参数的类型或个数的不同实现执行不同的功能

       |--- 方法的覆写(动态地多态表现形式:运行时):同一个方法可能根据使用子类的不同有不同的实现

------对象的多态性:父子实例之间的转换处理

       |--- 对象向上转型:父类 父类实例= 子类实例   (自动完成转换),此种转型用的最多

       |----对象向下转型:子类 子类实例 = (子类)父类实例   (强制转换)

2. 对象向上转型

例如: B extends A;     A  a = new B();

意义:当我们需要多个同父的对象调用某个方法时,通过向上转换后,则可以确定参数的统一.方便程序设计

3. 对象向下转型(在工程中很少用到)

例如: A extends B;     B b = (B)new A();

意义:通过父类强制转换为子类,从而来调用子类独有的方法

【注】向上转型描述的是一些公共的特征,向下转型描述的是子类独有的特征。

可以这样理解向上转型可向下转型:

class Superman extends Person
//1.当地球无事时,超人即一个普通人,他有着和普通人一样的特性方法。向上转型
Person person = new Superman();
person.eat(); person.sleep(); ..............
//2.当外星人入侵,他就要发挥自己独有的技能,异于普通人。向下转型
Superman superman = (Superman)person ;
superman.fly();  superman.fire();...............

4. 超级重要

只要是发生对象的向下转型之前一定要首先向上转型,两个没有任何关系的实例如果要发生强制转换,就会出现"ClassCastException"异常,因此向下转型并不是一件安全的事情。

5. instanceof关键字

已知向下转型本身就是一件存在安全隐患的操作,所以为了保证向下转型的正确性,需要在操作前进行判断某个实例是否是某个类的对象,需要通过instanceof语句实现:

对象 instanceof 类
    该判断将返回一个boolean类型,如果是true表示实例是指定类对象
例如:
Person person = new Person();//正常实例化,不转型
System.out.println(person instanceof Person)  ---> true
System.out.println(person instanceof Superman)   ---> false

Person person = new Superman();//向上转型
System.out.println(person instanceof Person)  ---> true
System.out.println(person instanceof Superman)   ---> true

17.Object类

Object类的主要特点是 可以解决参数的统一问题,即使用Object类可以接收所有的引用数据类型(类、接口、数组)

1. Object类的基本概念

在java中,只有Object类不存在继承关系的,即所有类默认都是Object类的子类。以下两种写法完全一致: 

class Person  
class Person extends Object

例如 用 Obejct类 接收 数组:

Object obj = new int[]{1,2,3} ;  //向上转型
if(obj instanceof int[]){
    int data[] = (int []) obj; //向下转型
    System.out.println("Person对象向下转型执行完毕");
}

2. 取得对象信息:toString()

在Object类中提供了toString方法,可以获取一个对象的完整信息,在我们平时调用任何对象的toString方法时都是调用其祖宗Object类的提供的toString()方法,所以这个方法在我们开发之中可以直接通过覆写这个方法来获得具体的对象信息。

3. 对象比较:equals(),public boolean equals(Object obj)

默认情况下在Object类中只是进行了两个对象的地址比较,并没有进行内容判断。即如果开发者想要利用其实现真正的对象比较,那么就必须在子类中覆写该方法,这也是为什么String类型的equals可以进行 值比较,就是因为String类对其进行了覆写。

18.抽象类

1. 抽象类的基本定义

抽象类主要是对子类中覆写方法进行约定,在抽象类里面可以去定义一些抽象方法以实现这样的约定,抽象方法即使用了abstract关键字定义的并且没有提供方法体的方法,而抽象方法所在的类必须为抽象类,抽象类必须使用abstract关键字来进行定义(在普通类的基础上追加抽象方法就是抽象类)。

当一个抽象类定义完成之后(切记:抽象类不是一个完整的类),如果想要去使用抽象类则必须按照如下规则:

    |--- a.抽象类必须提供有子类,子类使用extends继承一个抽象类;

    |--- b.抽象类的子类(不是抽象类)一定要覆写抽象类中的全部抽象方法;

    |--- c.抽象类的对象实例化可以利用对象多态性通过子类向上转型的方式完成;

【注】抽象类只是比普通类增加了抽象方法以及对子类的强制性的覆写要求,其它使用和普通类完全相同。

2. 抽象类的相关说明

抽象类是面向对象设计的重要环节,对于抽象类的使用需要注意以下几点:

    |--- a.定义抽象类的时候绝对不能使用final关键字,因为抽象类必须有子类

    |--- b.抽象类是作为一个普通类的加强版出现的,可以提供构造方法

    |--- c.抽象类允许没有抽象方法,但即便没有抽象方法,也无法直接使用new去实例化抽象类

    |--- d.抽象类可以提供有static方法,并且该方法不受抽象类对象的局限

19.包装类(装箱,拆箱)

1. 包装类原理分析

如果基本的数据类型想要以类的方式去处理,那么就需要对其进行包装

以自定义一个int包装类Int为例:

public class Int(){
    private int data;
    public Int(int data){
        this.data = data;
    }
    public int intValue(){
        return this.data;
    }
    public static void main(String[] args){
        Int temp = new Int(10);     //装箱:将基本数据类型保存在包装类对象中
        int data = temp.intValue(); //拆箱:从包装类对象中获取基本类型数据
        System.out.println(data);
    }
}

在程序设计中经常用到一系列的数据类型,在Java中也一样包含八中数据类型,这八种数据类型又各自对应一种包装器类型。如下表:

基本类型 包装器类型
booleanBoolean
charCharacter
intInteger
byteByte
shortShort
longLong
floatFloat
doubleDouble

2. 装箱与拆箱操作

a.数据装箱:将基本的数据类型的数据保存到包装类对象中,一般可以利用构造方法完成

b.数据拆箱:从包装类对象中取出基本类型的数据,一般通过包装类对象.**Value()方法获取

以int和包装类Integer为例:

Integer temp = new Integer(10);
int data = temp.intValue();

3. 自动装箱与自动拆箱

//自动装箱
Integer total = 99;
//自动拆箱
int totalprim = total;

|--- 我们现在就以Integer为例,来分析一下它的源码: 

1.首先来看看Integer.valueOf函数
public static Integer valueOf(int i) {
    return  i >= 128 || i < -128 ? new Integer(i) : SMALL_VALUES[i + 128];
}
【注】它会首先判断i的大小:如果i小于-128或者大于等于128,就创建一个Integer对象,否则执行SMALL_VALUES[i + 128]。

【注】SMALL_VALUES[i + 128]是什么东西:private static final Integer[] SMALL_VALUES = new Integer[256];  它是一个静态的Integer数组对象


2、接着看看intValue函数
@Override
public int intValue() {
    return value;
}

举个例子:

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);  //true
        System.out.println(i3==i4);  //false
    }
}
【注】

i1和i2会进行自动装箱,执行了valueOf函数,它们的值在(-128,128]这个范围内,它们会拿到SMALL_VALUES数组里面的同一个对象SMALL_VALUES[228],它们引用到了同一个Integer对象,所以它们肯定是相等的。

i3和i4也会进行自动装箱,执行了valueOf函数,它们的值大于128,所以会执行new Integer(200),也就是说它们会分别创建两个不同的对象,所以它们肯定不等。

|--- 我们现在就以Double为例,来分析一下它的源码:

public static Double valueOf(double d) {
    return new Double(d);
}
Double里面的做法很直接,就是直接创建一个对象,所以每次创建的对象都不一样。
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); //false
        System.out.println(i3==i4); //false
    }
}
【注】
看看上面的执行结果,跟Integer不一样,这样也不必奇怪,因为它们的valueOf实现不一样,结果肯定不一样,那为什么它们不统一一下呢? 
这个很好理解,因为对于Integer,在(-128,128]之间只有固定的256个值,所以为了避免多次创建对象,我们事先就创建好一个大小为256的Integer数组SMALL_VALUES,所以如果值在这个范围内,就可以直接返回我们事先创建好的对象就可以了。
在Double里面的做法很直接,就是直接创建一个对象,所以每次创建的对象都不一样。

 下面我们进行一个归类: 
Integer派别:Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。 
Double派别:Double、Float的valueOf方法的实现是类似的。每次都返回不同的对象。

20.接口的定义与使用

1. 接口的基本定义

接口可以理解为一个纯粹的抽象类(最原始的定义中只包含抽象方法和全局常量),但从JDK1.8之后引入了Lambda表达式的概念,所以接口的定义也得到了加强,除了抽象方法和全局常量( public static final )外,还可以定义普通方法或静态方法。在Java中接口主要使用interface关键字来定义。接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。

2.接口的特性

  • 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
  • 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
  • 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
  • 接口支持多继承(一个接口继承多个接口)

3.接口定义加强

加强的内容:JDK1.8后, 接口中可以追加 普通方法(default)和静态方法(static) 。

问题的提出:假如一个接口由1000+个子类实现,那么如果相对该接口进行功能扩充,那么就需要对1000+个实现类进行功能实现。

解决:因此,JDK1.8之后,接口中允许包含普通方法(default修饰为默认方法),但该操作属于“挽救功能”,如果不是必须的情况下,不应该作为设计的首选。

【注】如果在接口中定义了普通方法以及静态方法,那么该接口的功能已经和抽象类差不多了。但是不应该将 普通方法和静态方法 作为接口的 设计原则,在平时抒写代码时,应还是奉行"接口就是抽象方法的原则”,而定义普通方法和静态方法只是后期为了拓展的一种挽救操作。

4.接口和抽象类的区别

  • 1. 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
  • 2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
  • 3. 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
  • 4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

:JDK 1.8 以后,接口里可以有静态方法和方法体了。

:JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。更多内容可参考 Java 8 默认方法

:JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。更多内容可参考 Java 9 私有接口方法

21.泛型

1.泛型基本定义

Java 泛型(generics)是 JDK1.5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。类中的属性或方法的参数与返回值的类型可以由对象实例化时决定。

假定我们有这样一个需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?

答案是可以使用 Java 泛型

使用 Java 泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序。

2.泛型通配符 ?

类型通配符一般是使用 ? 代替具体的类型参数。例如:

public class GenericTest {
     
    public static void main(String[] args) {
        List<String> name = new ArrayList<String>();
        List<Integer> age = new ArrayList<Integer>();
        List<Number> number = new ArrayList<Number>();
        
        name.add("icon");
        age.add(18);
        number.add(314);
 
        getData(name);
        getData(age);
        getData(number);
       
   }
 
   public static void getData(List<?> data) {
      System.out.println("data :" + data.get(0));
   }

可能有时候,你会想限制那些被允许传递到一个类型参数的类型种类范围。

例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例。这就是有界类型参数的目的。要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界。下届的话则首先列出类型参数的名称,后跟super关键字,最后紧跟它的下届。

< ? extends Type>    : 通配符匹配的类型只能是 Type类型或者其子类

<? super Type>        :通配符匹配的类型只能是 Type类型或者其父类

3.泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。

和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

public class Box<T> {
  private T t;
  public void add(T t) {
    this.t = t;
  }
  public T get() {
    return t;
  }
 
  public static void main(String[] args) {
    Box<Integer> integerBox = new Box<Integer>();
    Box<String> stringBox = new Box<String>();
 
    integerBox.add(new Integer(10));
    stringBox.add(new String("菜鸟教程"));
 
    System.out.printf("整型值为 :%d\n\n", integerBox.get());
    System.out.printf("字符串为 :%s\n", stringBox.get());
  }
}

4.泛型方法

public class GenericMethodTest
{
   // 泛型方法 printArray                         
   public static < E > void printArray( E[] inputArray )
   {
      // 输出数组元素            
         for ( E element : inputArray ){        
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }
    public static void main( String args[] )
    {
        // 创建不同类型数组: Integer, Double 和 Character
        Integer[] intArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
 
        System.out.println( "整型数组元素为:" );
        printArray( intArray  ); // 传递一个整型数组
        System.out.println( "\n双精度型数组元素为:" );
        printArray( doubleArray ); // 传递一个双精度型数组
        System.out.println( "\n字符型数组元素为:" );
        printArray( charArray ); // 传递一个字符型数组
    } 
}

22.包Package

1.包的定义及其使用

为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。

  • 1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。

  • 2、如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。

  • 3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。

创建包的时候,你需要为这个包取一个合适的名字。之后,如果其他的一个源文件包含了这个包提供的类、接口、枚举或者注释类型的时候,都必须将这个包的声明放在这个源文件的开头。

包声明应该在源文件的第一行,每个源文件只能有一个包声明,这个文件中的每个类型都应用于它。

如果一个源文件中没有使用包声明,那么其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)中。

2.包的引入

在 java 源文件中 import 语句应位于 package 语句之后,所有类的定义之前,可以没有,也可以有多条,其语法格式为:

import package1.package2….classname*;

如果在一个包中,一个类想要使用本包中的另一个类,那么该包名可以省略。

3.CLASSPATH

类目录的绝对路径叫做 class path。设置在系统变量 CLASSPATH 中。编译器和 java 虚拟机通过将 package 名字加到 class path 后来构造 .class 文件的路径。

假设\classes 是 class path,package 名字是 com.runoob.test,而编译器和 JVM 会在 \classes\com\runoob\test 中找 .class 文件。

一个 class path 可能会包含好几个路径,多路径应该用分隔符分开。默认情况下,编译器和 JVM 查找当前目录。JAR 文件按包含 Java 平台相关的类,所以他们的目录默认放在了 class path 中。

23.单例/多例 设计模式

单例设计模式(多例设计模式)主要是一种控制实例化对象产生个数的设计操作。

1.单例设计模式

构造方法私有化,类的内部提供static方法获取实例化对象,不管外部如何操作,永远只提供一个类的实例化对象。

单例模式的几种实现方式:

1、懒汉式,线程不安全
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

2、懒汉式,线程安全
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

3、饿汉式
public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
        return instance;  
    }  
}
  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

【注】JAVA中哪里用到了单例设计模式?

Runtime类、Spring框架

【注】懒汉式单例设计模式的问题?

懒汉式的安全问题通过同步或者同步函数也可以解决,但是每次创建对象时都要判断一次锁,效率较低,所以使用双重锁模式。当然还要注意,其所对应的锁是哪一个锁,由于是静态方法,随着类的加载而加载,优先于对象而存在,所以使用的锁是类的字节码文件,即:Single.class。

class Single{
    private Single(){
    }
    private static Single s=null;
    public static Single getInstance(){
        if(s==null){
            synchronized(Single.class){
                if(s==null)
                    s=new Single();
            }
    }
            return s;        
    }
}

2.多例设计模式

多例设计模式允许保留多个实例化对象,例如:一个描述性别的类,那么该对象只有两个:男、女。

class Sex{
    private static final Sex male = new Sex("男");
    private static final Sex female = new Sex("女");
    private String sexName;
    private Sex(String sexName){ 构造方法私有化
        this.sexName = sexName;
    }
    private Sex getInstance(String sexName){
        switch(sexName){
            case "男" : return male;
            case "女" : return female;
            defult : return null;
        }
    }
}

24.枚举

1.枚举简介

上述我们介绍了多例设计模式,通过将 构造函数 私有化 来控制 类实例化对象的个数,但过于繁琐,因此JDK1.5之后推出了 枚举enum。

Java 枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一个年的 12 个月份,一个星期的 7 天,方向有东南西北等。

Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割。

2.定义枚举类

enum Color
{
    RED, GREEN, BLUE;  *********枚举对象*********
}
 
public class Test
{
    public static void main(String[] args)
    {    
        Color c1 = Color.RED;              //打印单个枚举对象
        System.out.println(c1);

        for (Color myVar : Color.values()) {
              System.out.println(myVar);   //循环遍历枚举对象
        }

      switch(myVar) {                      //枚举支持 switch-case
          case RED:    
            System.out.println("红色");
            break;
          case GREEN:
             System.out.println("绿色");
            break;
          case BLUE:
            System.out.println("蓝色");
            break;
    }
    }
}

【面试题】请解释 enum 和 Enum 的 区别?

1.enum: 是JDK1.5之后提供的关键字,用于定义枚举类。

2.Enum:是一个抽象类,所有使用enum关键字定义的类默认继承了此类,主要定义了以下几个方法:

  • values() 返回枚举类中所有的值。
  • ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样。
  • valueOf()方法返回指定字符串值的枚举常量。

3.枚举类成员

枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用(参考多例设计模式的理念,以此控制实例化对象的个数)。

枚举既可以包含具体方法,也可以包含抽象方法。 如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它。

enum Color{
    RED{
        public String getColor(){//枚举对象实现抽象方法
            return "红色";
        }
    },
    GREEN{
        public String getColor(){//枚举对象实现抽象方法
            return "绿色";
        }
    },
    BLUE{
        public String getColor(){//枚举对象实现抽象方法
            return "蓝色";
        }
    };
    public abstract String getColor();//定义抽象方法
}

public class Test{
    public static void main(String[] args) {
        for (Color c:Color.values()){
            System.out.print(c.getColor() + "、");
        }
    }
}

25.异常(try...catch...、try...finallly...、try..catch...finally...)

代码:
try
{
   可能出现异常的程序代码
}[catch(异常类型 异常对象)
{
   异常处理
}catch(异常类型 异常对象)
{
   异常处理
}...]finally{
    不管异常是否处理都要执行
}

1.异常的分类

  • 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
  • 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
  • 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

2.异常类的层次架构

3.自定义异常

在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点:

  • 所有异常都必须是 Throwable 的子类。
  • 如果希望写一个检查性异常类,则需要继承 Exception 类。
  • 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。

面试题1】请解释 throw 和 throws 的区别?

  • throw:在代码块中使用的,主要是进行异常对象的抛出。
  • throws: 在方法定义上使用的,表示将此方法可能产生的异常明确告诉调用处,由调用处进行处理。(例如除法函数时,被除数为0产生异常,利用throws在除法函数进行声明,那么异常的处理将会发生在调用除法函数的地方)

面试题2】返回的是 2 :原因很明显,就是执行了finally后已经return了,所以catch里面的return不会被执行到。也就是说finally永远都会在catch的return前被执行。(这个是面试经常问到的问题哦!)

try{
   //待捕获代码    
}catch(Exception e){
    return 1 ;
}finally{
     return 2 ;
}

26.内部类

在 Java 中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类

1.成员内部类

成员内部类是最普通的内部类,它的定义为位于另一个类的内部。

1.1)在外部类中要访问内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问

1.2)在内部类中要访问外部类的成员,

外部类.this.成员变量
外部类.this.成员方法

2.局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

在方法定义的内部类中只能访问方法中的final类型的局部变量。

3.匿名内部类

匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。

new <类或接口>{
    类的主体
}

4.静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法。

创建静态内部类对象的一般形式为: 外部类类名.内部类类名  对象 =  new 外部类类名.内部类类名()

创建成员内部类对象的一般形式为: 外部类类名.内部类类名  对象 =  外部类对象名.new 内部类类名()

27.JAVA基础类库

1.StringBuffer类

【面试题】请解释String、StringBuffer、StringBuilder的区别?

 1.String类:字符串长度不可变。Java中String 是immutable(不可变)的。用于存放字符的数组被声明为final的,因此只能赋值一次,不可再更改。源码定义为:private final char value[];

2.StringBuffer类与StringBuilder类 允许 对字符串的内容进行修改;StringBuffer类是JDK1.0提出的,属于线程安全的操作(其内部全部使用synchronized关键字进行标注)。而StringBuilder类是JDK1.5后提出的,属于非线程安全的操作。

3.String变量在进行相加的时候 底层实际调用了StringBuilder类的append方法,因此地址是改变了的。而String常量如”~“在进行相加在编译时被自动优化合并并为动用StringBuilder因此:

int i = "ab";   int q = "a";    int w ="b";    int e = q + w;    int r = "a" + "b";

sout(i == e):   FALSE

sout(i == r):      TRUE

简短来说:

String 长度大小不可变

StringBuffer 和 StringBuilder 长度可变

StringBuffer 线程安全 StringBuilder 线程不安全

StringBuilder 速度快

【面试题】请解释 + 和 concat() 的区别?

1、 concat方法:从源码可以看到,它是通过复制数组在通过 char 数组进行拼接生成一个新的对象,所以地址值会有变动。

2、+ 号:常量相加在编译时自动优化(合并),有变量参与时 则 调用 StringBuffer类的构造函数,然后挨个的 调用append方法,最后调用toString()方法 返回。

2.CharSequence接口

CharSequence是一个接口,比较常见的String、StringBuilder、StringBuffer都实现了这个接口。

当我们看到一个API里面有CharSequence的时候,它也是可以被其子类代替的 (比如向上转型,用CharSequence的引用指向子类(String、StringBuilder、StringBuffer)实例) 。

3.AutoCloseable接口

从AutoCloseable的注释可知它的出现是为了更好的管理资源,准确说是资源的释放,当一个资源类实现了该接口close方法,在使用try-catch-resources语法创建的资源抛出异常后,JVM会自动调用close 方法进行资源释放,当没有抛出异常正常退出try-block时候也会调用close方法。像数据库链接类Connection,io类InputStream或OutputStream都直接或者间接实现了该接口

4.Runtime类

该类主要代表了应用程序的运行环境。一个RunTime就代表一个运行环境。

核心方法有:

(1) getRuntime():该方法用于返回当前应用程序的运行环境对象。
(2) maxMemory():该方法用于返回Java虚拟机试图使用的最大内存量。

(3) totalMemory():该方法用于返回Java虚拟机中的内存总量。

(4) freeMemory():该方法用于返回Java虚拟机中的空闲内存量,以字节为单位。

【面试题】什么是GC?

Garbage Collector垃圾收集器,是可以由系统自动调用的垃圾释放功能,或者使用Runtime类中的gc()手工调用,
System.gc() 和 Runtime.getRuntime().gc()是一样的,因为前者底层调用的后者。

4.System类

.System类:系统类,主要用于获取系统的属性数据。

核心方法有:

(1) arraycopy(Object src, int srcPos, Object dest, int destPos, int length):数组拷贝

(2)  currentTimeMillis():获取当前系统时间,返回的是毫秒值。

(3) gc():垃圾回收(调用Runtime.getRuntime.gc())。

5.Cleaner类

JDK9之后用cleaner机制代替了finalize机制,提供了内存清理的另一方法。我们都知道finalize机制饱受诟病,因为它回收对象前要先执行Object.finalize()中的逻辑,降低了内存回收的效率,而且它不能保证被及时执行,这点很致命,导致对象不能及时被回收。它的使用方法有点繁琐,和try-with-resource机制一样,需要实现AutoCloseable接口,这样在重写的close()方法中释放资源会被自动调用回收。cleaner机制需要创建单独的线程去执行逻辑,这与finalize机制不同。执行finalize机制的线程不可控所以cleaner机制不存在类似于先执行finalize逻辑在回收对象的问题,即只要执行了cleaner机制就不会降低垃圾回收效率,但是前提是执行了cleaner机制。因为它的clean()方法还是写在重写的close()方法中等待被自动调用,所以无法保证保证被及时执行。

6.对象克隆

所谓的克隆指的就是"对象的复制",而且属于全新的复制,即使用已有对象创建一个新对象,其实现需要使用到Object类中的clone()方法。所有的类都继承于Object类因此所有的类都具有clone()方法,但并不是所有的类都希望被克隆。因此如果想实现对象克隆,那么对象所在的类需要实现一个Cloneable接口,此接口没有任何方法,因为它描述的是一种能力 。

浅克隆:

【面试题】浅克隆和深克隆的区别是什么?

1、在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说引用类型的成员变量再浅克隆中只是引用到了同一个地址,而非再创一个新的引用类型的成员变量。

2、在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

深克隆:

28.数学操作类

1.Math数学操作类

Java 的 Math 包含了用于执行基本数学运算的属性和方法,如初等指数、对数、平方根和三角函数。

Math 的方法都被定义为 static 形式,通过 Math 类可以在主函数中直接调用。

round()
它表示四舍五入,算法为 Math.floor(x+0.5),即将原来的数字加上 0.5 后再向下取整,所以,Math.round(11.5) 的结果为12,Math.round(-11.5) 的结果为-11。

exp()
返回自然数底数e的参数次方。

log()
返回参数的自然数底数的对数值。

pow()
返回第一个参数的第二个参数次方。

sqrt()
求参数的算术平方根。

random()
返回一个随机数。随机数范围为 0.0 =< Math.random < 1.0。

abs()
返回参数的绝对值。

ceil()
返回大于等于( >= )给定参数的的最小整数,类型为双精度浮点型。

floor()
返回小于等于(<=)给定参数的最大整数 。

rint()
返回与参数最接近的整数。返回类型为double。

2.Random随机数生成类

在JDK的java.util包中有一个Random类,他可以在指定的取值范围内随机产生数字。在Random类中有两种构造方法

Random()   :无参构造方法,用于创建一个伪随机数生成器。

Random(long seed): 有参构造方法,使用一个long类型的seed种子创建伪随机数生成器。

Random r=new Random();   //不传入种子
        for(int i=0;i<8;i++){
        	System.out.println(r.nextInt(100));
}
运行两次结果不同,因为在创建Random对象时没有传入种子参数,程序会自动以当前时间为时间戳。所以每一次的运行结果都是不同的。

Random r=new Random(100);   //传入种子
        for(int i=0;i<8;i++){
        	System.out.println(r.nextInt(100));
}
运行两次结果相同

nextDouble()方法返回的是0.0至1.0之间的double类型值。

nextFloat()方法返回的是0.0至1.0之间的float类型值。

nextInt(int n)返回的是0(包括)至n(不包括)之间的int值。

【面试题】生成[n,m]之间的随机数?

        Random r = new Random();
        int num = r.nextInt(m-n+1) + n;
        //输出随机数
        System.out.println("生成的随机数是:" + num);

29.日期操作类

日期类对象之间的互转(其中sdf是SimpleDateFormat的对象,c是Calendar的对象):

1.Date

// 初始化 Date 对象
Date date = new Date();
// 使用 toString() 函数显示日期时间
System.out.println(date.toString());

结果:Mon May 04 09:51:52 CDT 2013

2.SimpleDateFormat

Date dNow = new Date( );
SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
System.out.println("当前时间为: " + ft.format(dNow));

结果:当前时间为: 2018-09-06 10:16:34

3.Calendar类

我们现在已经能够格式化并创建一个日期对象了,但是我们如何才能设置和获取日期数据的特定部分呢,比如说小时,日,或者分钟? 我们又如何在日期的这些部分加上或者减去值呢? 答案是使用Calendar 类。

GET GET GET
Calendar c1 = Calendar.getInstance();
// 获得年份
int year = c1.get(Calendar.YEAR);
// 获得月份
int month = c1.get(Calendar.MONTH) + 1;
// 获得日期
int date = c1.get(Calendar.DATE);
// 获得小时
int hour = c1.get(Calendar.HOUR_OF_DAY);
// 获得分钟
int minute = c1.get(Calendar.MINUTE);
// 获得秒
int second = c1.get(Calendar.SECOND);
// 获得星期几(注意(这个与Date类是不同的):1代表星期日、2代表星期1、3代表星期二,以此类推)
int day = c1.get(Calendar.DAY_OF_WEEK);

SET SET SET
Calendar c1 = Calendar.getInstance();
//把c1对象代表的年份设置为2008年,其他的所有数值会被重新计算
c1.set(Calendar.YEAR,2008);
//把 c1对象代表的日期设置为10号,其它所有的数值会被重新计算
c1.set(Calendar.DATE,10);

30.正则表达式

1.正则表达式的基本语法

正则表达式定义了字符串的模式。正则表达式可以用来搜索、编辑或处理文本。

正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。

blog.csdn.net/qq_39504519/article/details/107075811

2.String类对正则表达式的支持

/*
 * 1>matchers()字符串匹配
 * 2>replaceAll()替换字符
 * 3>split()拆分字符串
 */

public class string {
    public static void main(String[] args){
        //1>matchers()字符串匹配
        String s1 = "wewe中国人";
        Boolean b = s1.matches("\\w*中国人");
        System.out.println(b);
          
        //2>replaceAll()替换字符
        String s2 = "李二李三李四李五";
        String result = s2.replaceAll("李", "张");
        System.out.println(result);
          
        //3>split()拆分字符串
        String s3 = "中国人|龙的传人/李小龙";
        String arr[] = s3.split("\\||\\/");
        for(int i=0;i<arr.length;i++){
            System.out.println(arr[i]);
        }
    }
}

3.java.util.regex 包对RE的支持

java.util.regex 包主要包括以下三个类:

  • Pattern 类:

    pattern 对象是一个正则表达式的编译表示。Pattern 类没有公共构造方法。要创建一个 Pattern 对象,你必须首先调用其公共静态编译方法,它返回一个 Pattern 对象。该方法接受一个正则表达式作为它的第一个参数。

  • Matcher 类:

    Matcher 对象是对输入字符串进行解释和匹配操作的引擎。与Pattern 类一样,Matcher 也没有公共构造方法。你需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象。

  • PatternSyntaxException:

    PatternSyntaxException 是一个非强制异常类,它表示一个正则表达式模式中的语法错误。

// 按指定模式在字符串查找
      String line = "This order was placed for QT3000! OK?";
      String pattern = "(\\D*)(\\d+)(.*)";
 
      // 创建 Pattern 对象
      Pattern r = Pattern.compile(pattern);
 
      // 现在创建 matcher 对象
      Matcher m = r.matcher(line);
      if (m.find( )) {
         System.out.println("Found value: " + m.group(0) );
         System.out.println("Found value: " + m.group(1) );
         System.out.println("Found value: " + m.group(2) );
         System.out.println("Found value: " + m.group(3) ); 
      } else {
         System.out.println("NO MATCH");
      }


结果:
    Found value: This order was placed for QT3000! OK?
    Found value: This order was placed for QT
    Found value: 3000
    Found value: ! OK?

31.开发支持类库

1.UUID类

如果现在需要我们随机生成一个不会重复的字符串,我们有什么解决方案?

    一般比较好的算法是:电脑的IP地址+时间戳+加任意位数的随机数+移位操作=几乎不会重复的随机字符串。在设计程序的时候如果由用户自己来处理相关操作太过于麻烦,Java为了方便处理,设计了java.util.UUID类,这个类可以生成以上格式的字符串这个类我们主要使用一个 public static UUID randomUUID()方法。

for(int i = 0; i<3; i++) {
	UUID uid = UUID.randomUUID();
	System.out.println(uid);
}

结果:
d3acdc20-007d-421f-b205-0f73236c8d6e
9e22aa83-d516-4f85-a3f9-e4221e9552f4
76354848-4f09-4838-bf6c-4aee5f529d99

正因为UUID产生的数据几乎没有重复的信息,所以在开发中我们可以用这个方法生成唯一的字符串,这种方法可以在文件的自动命名上使用,比如上传文件自动命名,或者是数据库的primary key。一定要记住public static UUID randomUUID() 。

2.Optional类

Optional 类是一个内容可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。Optional 类的引入很好的解决空指针异常。核心方法如下:

boolean isPresent():如果值存在则方法会返回true,否则返回 false。

T get():如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException

static <T> Optional<T> empty():返回空的 Optional 实例。

T orElse(T other):如果存在该值,返回值, 否则返回 other。

static <T> Optional<T> of(T value):该方法通过一个非null的value来构造一个 Optional,返回的 Optional 包含了 value 这个值。

static <T> Optional<T> ofNullable(T value):该方法和 of 方法的区别在于,传入的参数可以为 null。该方法会判断传入的参数是否为 null,如果为 null 的话,返回的就是 Optional.empty()。

3.ThreadLocal类(很重要)

要解决的案例:当多个线程共享一个static变量进行发送消息时,有可能由于时间冲突而导致 各自的 消息 被 另一线程 覆盖,导致同步问题。

threadlocal而是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。

ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是

  • Synchronized是通过线程等待,牺牲时间来解决访问冲突
  • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。

正因为ThreadLocal的线程隔离特性,使他的应用场景相对来说更为特殊一些。在android中Looper、ActivityThread以及AMS中都用到了ThreadLocal。当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。

4.Base64的加密与解密

Java 8 内置了 Base64 编码的编码器和解码器。

序号内部类 & 描述
1static class Base64.Decoder

该类实现一个解码器用于,使用 Base64 编码来解码字节数据。

2static class Base64.Encoder

该类实现一个编码器,使用 Base64 编码来编码字节数据。

序号方法名 & 描述
1static Base64.Decoder getDecoder()

返回一个 Base64.Decoder ,解码使用基本型 base64 编码方案。

2static Base64.Encoder getEncoder()

返回一个 Base64.Encoder ,编码使用基本型 base64 编码方案。

// 使用基本编码
String base64encodedString = Base64.getEncoder().encodeToString("runoob?java8".getBytes("utf-8"));
System.out.println("Base64 编码字符串 (基本) :" + base64encodedString);
        
// 解码
byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);
System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8"));

32.比较器

1、comparable接口:提供了一个compareTo()方法,用来比较两个对象的大小,他定义在集合内部,从而实现排序
2、comparator接口:提供了一个compare()方法,定义在集合的外部,当要实现排序方法,需要在connections的sort方法中加入一个实现comparator接口的比较器参数

1.Comparable接口

Comparable可以认为是一个内比较器,实现了Comparable接口的类有一个特点,就是这些类是可以和自己比较的,至于具体和另一个实现了Comparable接口的类如何比较,则依赖compareTo方法的实现,compareTo方法也被称为自然比较方法。如果开发者add进入一个Collection的对象想要Collections的sort方法帮你自动进行排序的话,那么这个对象必须实现Comparable接口。compareTo方法的返回值是int,有三种情况:

1、比较者大于被比较者(也就是compareTo方法里面的对象),那么返回正整数
2、比较者等于被比较者,那么返回0
3、比较者小于被比较者,那么返回负整数
 

//简单封装了一个类,用作比较大小
public class People implements Comparable<People>{

    private int age;
    public People(String name, int age) {
 
        this.age = age;
    }
    覆写 comparTo方法
    @Override
    public int compareTo(People p) {
        return this.age-p.age;
    }

    public static void main(String[] args) {
        People p1 = new People(10);
        People p2 = new People(12);

        int i = p1.compareTo(p2);
        if(i>0){
            System.out.println("p1比p2大");
        }
        else if(i==0){
            System.out.println("p1和p2一样大");
        }
        else{
            System.out.println("p1比p2小");
        }

}

2.Comparator接口

Comparator可以认为是是一个外比较器,Comparator接口里面有一个compare方法,方法有两个参数T o1和T o2,是泛型的表示方式,分别表示待比较的两个对象,方法返回值和Comparable接口一样是int,有三种情况:
1、o1大于o2,返回正整数
2、o1等于o2,返回0
3、o1小于o3,返回负整数

public class People implements Comparable<People>{

    private int age;

    public People(String name, int age) {
        this.age = age;
    }


    @Override
    public int compareTo(People p) {
        return this.age-p.age;
    }
}


//创建一个实现Comparator接口的类,实现其中的compare()方法
class ComparatorUtils implements Comparator<People> {

    @Override
    public int compare(People o1, People o2) {
        return o1.getAge()-o2.getAge();
    }
}



public class ComparatorDemo {
    public static void main(String[] args) {
        List list = new ArrayList<People>();
        list.add(new People(10));
        list.add(new People(12));
        list.add(new People(14));
        list.add(new People(11));
        list.add(new People(13));
        Collections.sort(list,new ComparatorUtils());
        for(Object people:list){
            System.out.println(people);
        }
    }
}

33.文件操作

Java文件类以抽象的方式代表文件名和目录路径名。该类主要用于文件和目录的创建、文件的查找和文件的删除等。

File对象代表磁盘中实际存在的文件和目录。

通过将给定路径名字符串转换成抽象路径名来创建一个新 File 实例:new File(String pathname)

根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例:new File(String parent, String child)

常用方法如下:

序号方法描述
1public String getName()
返回由此抽象路径名表示的文件或目录的名称。
2public String getParent()
 返回此抽象路径名的父路径名的路径名字符串,如果此路径名没有指定父目录,则返回 null
3public File getParentFile()
返回此抽象路径名的父路径名的抽象路径名,如果此路径名没有指定父目录,则返回 null
 4public String getAbsolutePath()
返回抽象路径名的绝对路径名字符串。
5public boolean canRead()
测试应用程序是否可以读取此抽象路径名表示的文件。
6public boolean canWrite()
测试应用程序是否可以修改此抽象路径名表示的文件。
7public boolean exists()
测试此抽象路径名表示的文件或目录是否存在。
8public boolean isDirectory()
测试此抽象路径名表示的文件是否是一个目录。
9public boolean isFile()
测试此抽象路径名表示的文件是否是一个标准文件。
10public long length()
返回由此抽象路径名表示的文件的长度。
11public boolean createNewFile() throws IOException
当且仅当不存在具有此抽象路径名指定的名称的文件时,原子地创建由此抽象路径名指定的一个新的空文件。
12public boolean delete()
 删除此抽象路径名表示的文件或目录。
13public String[] list()
返回由此抽象路径名所表示的目录中的文件和目录的名称所组成字符串数组。
18public String[] list(FilenameFilter filter)
返回由包含在目录中的文件和目录的名称所组成的字符串数组,这一目录是通过满足指定过滤器的抽象路径名来表示的。
19public File[] listFiles()
  返回一个抽象路径名数组,这些路径名表示此抽象路径名所表示目录中的文件。
20public File[] listFiles(FileFilter filter)
返回表示此抽象路径名所表示目录中的文件和目录的抽象路径名数组,这些路径名满足特定过滤器。
21public boolean mkdir()
创建此抽象路径名指定的目录。
22public boolean mkdirs()
创建此抽象路径名指定的目录,包括创建必需但不存在的父目录。
23public boolean renameTo(File dest)
 重新命名此抽象路径名表示的文件。
public static void main(String args[]) {
        String dirname = "/java";
        File f1 = new File(dirname);
        if (f1.isDirectory()) {
            System.out.println("Directory of " + dirname);
            String s[] = f1.list();
            for (int i = 0; i < s.length; i++) {
                File f = new File(dirname + "/" + s[i]);
                if (f.isDirectory()) {
                    System.out.println(s[i] + " is a directory");
                } else {
                    System.out.println(s[i] + " is a file");
                }
            }
        } 
        else {
            System.out.println(dirname + " is not a directory");
        }

34. 字节流与字符流

  • 流是个抽象的概念,是对输入输出设备的抽象,输入流可以看作一个输入通道,输出流可以看作一个输出通道。
  • 输入流是相对程序而言的,外部传入数据给程序需要借助输入流。
  • 输出流是相对程序而言的,程序把数据传输到外部需要借助输出流。

字节流--传输过程中,传输数据的最基本单位是字节的流。

字符流--传输过程中,传输数据的最基本单位是字符的流。

字节输入流InputStream、节输出流OutputStream、字符输入流Reader、字符输出流Writer:

blog.csdn.net/qq_38737992/article/details/89763911

import java.io.*;
 
public class Main {
    public static void main(String[] args) throws IOException {
        File f = new File("/Users/sh/desktop/" + "test.txt");
        //如果文件不存在会自动创建
        OutputStream out = new FileOutputStream(f);
        String str = "你好";
        byte[] b = str.getBytes();
        //因为是字节流,所以要转化成字节数组进行输出
        out.write(b);
        out.close();
         

        File f1 = new File("/Users/sh/desktop/" + "test.txt");
        InputStream in = new FileInputStream(f1);
        byte[] b1 = new byte[1024];
        int temp = 0;
        int len = 0;
        //-1为文件读完的标志
        while ((temp = in.read()) != -1) {
            b1[len] = (byte) temp;
            len++;
        }
        in.close();
        System.out.println(new String(b1, 0, len));
    }
}

【面试题1】字节流和字符流使用是非常相似的,那么除了操作代码的不同之外,还有哪些不同呢?

字节流在操作的时候本身是不会用到缓冲区(内存)的,是与文件本身直接操作的,而字符流在操作的时候是使用到缓冲区的。

字节流在操作文件时,即使不关闭资源(close方法),文件也能输出,但是如果字符流不使用close方法的话,则不会输出任何内容,说明字符流用的是缓冲区,并且可以使用flush方法强制进行刷新缓冲区,这时才能在不close的情况下输出内容

【面试题2】那开发中究竟用字节流好还是用字符流好呢?

在所有的硬盘上保存文件或进行传输的时候都是以字节的方法进行的,包括图片也是按字节完成,而字符是只有在内存中才会形成的,所以使用字节的操作是最多的。

如果要java程序实现一个拷贝功能,应该选用字节流进行操作(可能拷贝的是图片),并且采用边读边写的方式(节省内存)。

【面试题3】next() 与 nextLine()的 区别

next():

  • 1、一定要读取到有效字符后才可以结束输入。
  • 2、对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
  • 3、只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
  • next() 不能得到带有空格的字符串。

nextLine():

  • 1、以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
  • 2、可以获得空白。

35.对象序列化与反序列化

在Java中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用此对象。但是,我们创建出来的这些对象都存在于JVM中的堆(heap)内存中,只有JVM处于运行状态的时候,这些对象才可能存在。一旦JVM停止,这些对象也就随之消失;

但是在真实的应用场景中,我们需要将这些对象持久化下来,并且在需要的时候将对象重新读取出来,Java的序列化可以帮助我们实现该功能。

对象序列化机制(object serialization)是java语言内建的一种对象持久化方式,通过对象序列化,可以将对象的状态信息保存未字节数组,并且可以在有需要的时候将这个字节数组通过反序列化的方式转换成对象,对象的序列化可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换。

Java类通过实现java.io.Serializable接口来启用序列化功能,未实现此接口的类将无法将其任何状态或者信息进行序列化或者反序列化。可序列化类的所有子类型都是可以序列化的。序列化接口没有方法或者字段,仅用于标识可序列化的语义。当试图对一个对象进行序列化时,如果遇到一个没有实现java.io.Serialization接口的对象时,将抛出NotSerializationException异常。如果要序列化的类有父类,要想将在父类中定义过的变量序列化下来,那么父类也应该实现java.io.Serializable接口。

1.案例

下面是一个实现了java.io.Serializable接口的类:

package common.lang;
import java.io.Serializable;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

public class User1 implements Serializable{
	private String name;
	private int age;
	
	public String getName() {return name;}
	public void setName(String name) {this.name = name;}
	public int getAge() {return age;}
	public void setAge(int age) {this.age = age;}
	
	@Override
	public String toString() {
		return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
								   .append("name", name)
								   .append("age", age)
								   .toString();
	}
}

通过下面的代码进行序列化及反序列化:

public class SerializableDemo1 {

	public static void main(String[] args) throws Exception, IOException {
		//初始化对象
		User1 user = new User1();
        user.setName("yaomy");
        user.setAge(23);
        System.out.println(user);
        //序列化对象到文件中
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("template"));
        oos.writeObject(user);
        oos.close();
        //反序列化
        File file = new File("template");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        User1 newUser = (User1)ois.readObject();
        System.out.println(newUser.toString());
	}
}

2.Transient 关键字使用

Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中。

在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

【注】静态变量不管是否被transient修饰,均不能被序列化。

36.认识反射机制

1.反射机制简介

AVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

概念:
1.反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。

2.反射可以在一个类运行的时候获取类的信息的机制,可以获取在编译期不可能获得的类的信息。

3.对于任意一个对象,都能调用它的任意一个方法和属性。

4.因为类的信息是保存在Class对象中的,而这个Class对象是在程序运行时被类加载器(ClassLoader)动态加载的。

5.当类加载器装载运行了类后,动态获取Class对象的信息以及动态操作Class对象的属性和方法的功能称为Java语音的反射机制。

作用:
1.反编译:.class —> .java。

2.通过反射机制访问Java对象中的属性、方法、构造方法等。

2.反射的应用场合

在 Java 程序中许多对象在运行是都会出现两种类型: 编译时类型运行时类型。 编译时的类型由 声明对象时实用的类型来决定,运行时的类型由 实际赋值给对象的类型决定 。如: Person p=new Student();
其中编译时类型为 Person,运行时类型为 Student。
程序在运行时还可能接收到外部传入的对象,该对象的 编译时类型为 Person,但是程序有需要调用该对象的 运行时类型的方法。为了解决这些问题,程序需要在运行时发现对象和类的真实信息。 然而,如果编译时根本无法预知该对象和类属于哪些类,程序只能依靠运行时信息来发现该对象 和类的真实信息,此时就必须使用到反射了。

3.JAVA反射API(用来生成 JVM 中的类、接口或则对象的信息)

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

4.反射使用步骤(获取 Class 对象、调用对象方法)

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

5.Class类对象的三种实例化方式(不同于实例化对象)

第一种,使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。

Class clz = Class.forName("完整类路径名");

第二种,使用 .class 方法。

Class clz = 类.class;

第三种,使用类对象的 getClass() 方法。

Class clz = 实例化对象.getClass();

6.通过反射创建实例化对象

第一种:通过 Class 对象的 newInstance() 方法。但是这种方法要求 该 Class 对象对应的类有默认的空构造器。

Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance();

第二种:通过 Constructor 对象的 newInstance() 方法

Class clz = Apple.class;
Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();

通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。

Class clz = Apple.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Apple apple = (Apple)constructor.newInstance("红富士", 15);

36.网络编程

1.什么是 网络编程?

网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。

java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。

java.net 包中提供了两种常见的网络协议的支持:

  • TCP:TCP(英语:Transmission Control Protocol,传输控制协议) 是一种面向连接的、可靠的、基于字节流的传输层通信协议,TCP 层是位于 IP 层之上,应用层之下的中间层。TCP 保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。

  • UDP:UDP (英语:User Datagram Protocol,用户数据报协议),位于 OSI 模型的传输层。一个无连接的协议。提供了应用程序之间要发送数据的数据报。由于UDP缺乏可靠性且属于无连接协议,所以应用程序通常必须容许一些丢失、错误或重复的数据包。

2.

37.Javadoc(Java 文档注释)

Java 支持三种注释方式。前两种分别是 // 和 /* */,第三种被称作文档注释,它以 /** 开始,以 */结束。文档注释允许你在程序中嵌入关于程序的信息。你可以使用 javadoc 工具软件来生成信息,并输出到HTML文件中。javadoc是Sun公司提供的一个技术,只要在编写程序时以一套特定的标签作注释,在程序编写完成后,通过Javadoc就可以同时形成程序的开发文档了。javadoc命令是用来生成自己API文档的。

javadoc 工具软件识别以下标签:

标签描述示例
@author标识一个类的作者@author description
@deprecated指名一个过期的类或成员@deprecated description
{@docRoot}指明当前文档根目录的路径Directory Path
@exception标志一个类抛出的异常@exception exception-name explanation
{@inheritDoc}从直接父类继承的注释Inherits a comment from the immediate surperclass.
{@link}插入一个到另一个主题的链接{@link name text}
{@linkplain}插入一个到另一个主题的链接,但是该链接显示纯文本字体Inserts an in-line link to another topic.
@param说明一个方法的参数@param parameter-name explanation
@return说明返回值类型@return explanation
@see指定一个到另一个主题的链接@see anchor
@serial说明一个序列化属性@serial description
@serialData说明通过writeObject( ) 和 writeExternal( )方法写的数据@serialData description
@serialField说明一个ObjectStreamField组件@serialField name type description
@since标记当引入一个特定的变化时@since release
@throws和 @exception标签一样.The @throws tag has the same meaning as the @exception tag.
{@value}显示常量的值,该常量必须是static属性。Displays the value of a constant, which must be a static field.
@version指定类的版本@version info

使用方式:

在开始的 /** 之后,第一行或几行是关于类、变量和方法的主要描述。

之后,你可以包含一个或多个各种各样的 @ 标签。每一个 @ 标签必须在一个新行的开始或者在一行的开始紧跟星号(*)。

多个相同类型的标签应该放成一组。例如,如果你有三个 @see 标签,可以将它们一个接一个的放在一起。

下面是一个类的说明注释的实例:

/*** 这个类绘制一个条形图
* @author runoob
* @version 1.2
*/

javadoc命令:

javadoc  文件名.java

效果:

这里写图片描述

38.Swing编程(GUI)

package myh;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class MyFrame extends JFrame implements ActionListener {
    private JPanel jp1,jp2,jp3;
    private JButton jb1,jb2,jb3;
    private JLabel jl;
    private JTextField jt;
    private JTextArea jta;

    public MyFrame(String title){
        jp1 = new JPanel();
        jp2 = new JPanel();
        jp3 = new JPanel();

        jl = new JLabel("请输入信息:");
        jt = new JTextField(10);
        jp1.add(jl);
        jp1.add(jt);

        jta = new JTextArea(200,90);
        jp2.add(jta);

        jb1 = new JButton("Append");
        jb1.addActionListener(this);
        jb2 = new JButton("Clear");
        jb2.addActionListener(this);
        jb3 = new JButton("Exit");
        jb3.addActionListener(this);
        jp3.add(jb1);
        jp3.add(jb2);
        jp3.add(jb3);

        this.add(jp1);
        this.add(jp2);
        this.add(jp3);

        this.setTitle(title);
        this.setLayout(new GridLayout(3,1));
        this.setSize(1000, 1000);
        this.setLocationRelativeTo(null);//在屏幕中间显示(居中显示)
        this.setVisible(false);
        this.setVisible(true);  //设置可见
        this.setResizable(false);	//设置不可拉伸大小
    }

    //clear点击触发按钮函数
    public void append(){
      //  this.jta.setText(this.jt.getText());
        this.jta.append(this.jt.getText());
        this.jt.setText("");
    }

    //clear点击触发按钮函数
    public void clear(){
        this.jt.setText("");
    }

    //exit点击触发按钮函数
    public void exit(){
        System.exit(0);
    }



    @Override
    public void actionPerformed(ActionEvent e) {
        if(e.getActionCommand()=="Append"){
            this.append();
            System.out.println("Append");
        }
        else if(e.getActionCommand()=="Clear"){
            this.clear();
            System.out.println("Clear");
        }
        else if(e.getActionCommand()=="Exit"){
            this.exit();
            System.out.println("Exit");
        }
    }

    public static void main(String[] args) {
        MyFrame myFrame = new MyFrame("Testcopy");
    }
}

39.JAVA Applet

Applet 是一种 Java 程序。它一般运行在支持 Java 的 Web 浏览器内。因为它有完整的 Java API支持,所以Applet 是一个全功能的 Java 应用程序

1.Applet的生命周期

  • init: 该方法的目的是为你的 Applet 提供所需的任何初始化。
  • start: 浏览器调用 init 方法后,该方法被自动调用。每当用户从其他页面返回到包含 Applet 的页面时(非活动状态到活动状态),则调用该方法。
  • stop: 当页面从活动状态到非活动状态,该方法自动被调用。
  • destroy: 此方法仅当浏览器正常关闭时调用。
  • paint: 该方法在 start() 方法之后立即被调用,或者在 Applet 需要重绘在浏览器的时候调用。paint() 方法实际上继承于 java.awt。

2.Applet的调用

<applet> 标签是在HTML文件中嵌入 Applet 的基础。以下是一个调用"Hello World"applet的例子:

<html>
<title>The Hello, World Applet</title>
<hr>
<applet code="HelloWorldApplet.class" width="320" height="120">
If your browser was Java-enabled, a "Hello, World"
message would appear here.
</applet>
<hr>
</html>

【面试题】Applet、Awt、Swing之间的区别?

      1. awt、swing都是用来开发图形用户界面GUI(Graphical User Interface)的。唯一的区别就是awt没有swing强大(比如:awt里的按钮不能添加图片,而swing里的按钮可以添加) awt是SUN的失败品,所以后来才弄了个swing
      2.applet(java小程序,主要用来开发显示在网页上的程序)

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值