【轩说Java】JavaSE知识点难点汇总

2023-11-07

文章目录

JAVA SE

学习方法:菜鸟java(基础)+尚硅谷/遇见狂神说。

笔记中只有重点/难点内容,不含纯基础内容。

抽象类与其实现子类

没有函数体的方法为抽象方法。一个类中只要存在至少一个抽象方法,则这个类即为抽象类。抽象类不能被实例化。抽象方法和抽象类用abstract关键字修饰。

抽象类与其子类如下

package basicGrammer;

public abstract class SuperClass{
    public abstract void m(); //抽象方法
    public int getInt(){return 1;}
    public static String staticFunction(){
        return "SuperClass's static function";
    }
}

class SubClass extends SuperClass{
    //实现抽象方法
    public void m(){
        System.out.println("hello");
    }
    public static String staticFunction(){
        return "SubClass's static function";
    }
}

静态函数不存在重写和多态的概念

静态函数重写是没有多态作用的,因为静态的意义在于“属于类本身”而不只属于“某个对象”。不管引用变量指向的是哪个类型,静态函数不会去查找所引用对象中的复写函数,不具有多态性。其会按照引用对应的类型来调用静态函数。

SuperClass superClass = new SubClass();
SubClass subClass = new SubClass();
superClass.staticFunction()//"SuperClass's static function"
subClass.staticFunction()//"SubClass's static function"

重写的要求

重写的基本要求是:

  1. 只能重写从父类或接口继承过来的函数。父类的private的函数、final的函数、static的函数无法被重写。
  2. 函数的签名一致。即:函数名+参数列表完全一致。
  3. 返回值要兼容。
  4. 访问权限不能更加严格。
  5. 不能抛出比父类中更高级别的异常。
  6. 构造方法不能被重写
  7. synchronized 和 strictfp 关键字对重写规则没有任何影响。

所有要求的基本逻辑是:在扩充了更多的子类后,将其new的对象赋给父类的引用变量后,不能影响其余的程序段的运行。设想如果加了一个子类,重写了父类的函数后,出现了返回值赋值错误、抛出异常未能被接受的错误、访问权限受限错误等等,则在整个项目的各处都会报错。所以重写函数一定要注意以上几点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UdLuT52d-1679366188300)(./笔记.assets/image-20230315191930185.png)]

Father p = new son1();//以往的程序
Father p = new son2();
Father p = new son3();
//多态扩充后
Father p1 = new anotherSon();

接口interface

类-接口的实现(一对多)、接口-接口的继承(一对多)

java中不允许多继承(一个类有多个父类),是为了防止父类1和父类2中有同名冲突的方法,在子类调用中会出现冲突。但是可以实现多个接口,且接口间允许多继承。是由于即使有同名方法也没关系,因为这几个接口中的重复的方法声明对应于实现类中的同一个方法实现。

public interface super1 {
}
public interface super2 {
}
public interface Interface2 extends super1,super2{
    public static final int interfaceNum=2;
}

public interface Interface {
}
public class Employee{  
}
public class Salary extends Employee implements Interface,Interface2{
   
}

接口中的变量和函数

接口定义了一种协议和规则,所以应该:

  • 成员变量是:

    • 公开的:不公开则毫无意义,凡是实现该接口的都应该可以访问该成员。

    • 静态的:接口不能被实例化,所以变量都必须是放在静态存储区的。

    • 不可改变的:因为所有人都应该遵守,不能随便改。

  • 函数是:

    • 公开的
    • 抽象的
public interface Interface {
    public static final int interfaceNum=1;
    public abstract int func1();
}

简写成:

public interface Interface {
    int interfaceNum=1;
    int func1();
}

注:JDK 1.8 以后,接口里可以有静态方法和方法体了。JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。更多内容可参考 Java 8 默认方法。但是这会引起实现类中的同名函数冲突,模糊了接口和抽象类的边界。

public interface Interface {
    public static final int interfaceNum=1;
    public int func1();
    public static int func2(){
        return 0;
    }
    public default int func3(){
        return 2;
    }
}

如果出现冲突,则需要在子类中指定使用哪一个接口中的静态或默认方法。

public class C implements interfaceA,interfaceB{//假设A和B中定义了同签名的default show函数
    @Override
    public void show(String s) {
        interfaceA.super.show(s);
    }
}

注:JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。

接口作为一种标签

public class Employee {
}
public class Salary extends Employee implements Interface,Interface2{
}
Employee n=new Employee("员工C","深圳",4);;
Employee e=new Salary("员工B","上海",2,2400);

System.out.println("n继承了接口?"+(n instanceof Interface));//false
System.out.println("e继承了接口?"+(e instanceof Interface));//true

比如java.lang.Object类的clone函数就会判断子类是否是实现了Cloneable接口来决定是否抛出not

support异常。

堆、栈、静态方法区

(42条消息) jvm内存结构(堆、方法区)_方法区是在堆里面吗_懒惰的coder的博客-CSDN博客

编译原理课上曾言,栈从大地址向小地址增长,堆从小地址向大地址增长。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dMt8px1I-1679366188301)(./注解Annotation.assets/image-20230315101353339.png)]

堆heap

堆区只有一个,是被所有线程共享的。所以要注意线程安全问题。

堆用于存放对象实例本身。通过new关键字创建的对象和对象数组都会被放在堆内存。

堆中的对象实例在没有被任何栈上的变量引用时,会被垃圾回收机制(GC)释放内存。

栈stack

栈存放函数运行时动态的信息,主要用于存储局部变量。每个线程都有自己的一个栈区。栈中只保存基本数据类型的对象和自定义对象的引用。每当一个方法被调用,jvm就会为该方法分配一个栈帧,如下图所示,栈帧中存放了:函数控制链(指向调用自己的函数)、函数返回值、函数参数的值,临时数据和局部变量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wj7BggER-1679366188301)(./注解Annotation.assets/image-20230315101520969.png)]

静态方法区method

静态方法区存放字节码文件的类信息、字符串、常量、静态变量、静态函数的函数体等。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VSl3xQeF-1679366188301)(./注解Annotation.assets/堆栈静态区.jpg)]

垃圾回收

当一个对象没有任何引用类型变量去引用它时,会被析构。

package basicGrammer;

// 一个简单的构造函数
class MyClass implements Cloneable{
    int id;
    
    // 以下是构造函数
    MyClass(int id) {
        System.out.println(id+" is created");
        this.id=id;
    }
    
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println(id+" finalize");

    }
}
public class test3 extends Object{
    public static void main(String[] args) throws CloneNotSupportedException {
        MyClass t1 = new MyClass(10);
        MyClass t2 = new MyClass(20);
        t2 = null;
        System.gc();
    }
}
/*
10 is created
20 is created
20 finalize
*/

克隆clone()

实现Cloneable接口的类才可以调用或重写java.lang.Object中的clone()函数。这里的Cloneable接口仅仅作为一个认证标签。可以想象,在clone函数中有if this instanceof Cloneable的检查,确保子类是是实现了对应接口,才提供对应服务。但是实际上由于clone方法为native方法,即源码是通过底层的c++写的,所以这里没有查看源码验证。

但是clone方法为浅拷贝。如果实现深拷贝,需要自己override。下面给出典型案例。

package Clone;

public class demo2 {
    static class Body implements Cloneable{
        public Head head;
        public Body(){}
        public Body(Head head){
            this.head=head;
        }

        @Override
        public Object clone() throws CloneNotSupportedException {
            Body copy=(Body) super.clone();
            copy.head=(Head) this.head.clone();
            return copy;
        }
    }
    static class Head implements Cloneable{
        public Face face;
        public Head(){}
        public Head(Face face){
            this.face = face;
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            Head copy=(Head) super.clone();
            copy.face=(Face) this.face.clone();
            return copy;
        }
    }
    static class Face implements Cloneable{
        public Face(){}

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Body body1=new Body(new Head(new Face()));
        Body body2=(Body) body1.clone();
        System.out.println(body1==body2);
        System.out.println(body1.head==body2.head);
        System.out.println(body1.head.face==body2.head.face);

    }
}

递归下去,所以如果想完全深拷贝,需要每个成员变量对应的类都重写了clone方法。

Collection家族中的Clone()

Collection家族中的clone()都是浅拷贝,即使内部的数据类实现了Cloneable接口。所以想要深拷贝,不仅需要内部数据元素的类重写clone,并且不能直接

ArrayList<innerClass> arrayList2=(ArrayList<innerClass>)arrayList.clone();

传参

java中的传参 都是按值传递 ,但值的内容是一个引用变量或一个基本数据类型变量。Java中是值传递的,只不过对于对象参数,值的内容是对象的引用。

  • java传递引用数据类型时,按值传递。

    public class test2 {
        public static void main(String[] args) {
            Integer a = 10;
            Integer b = 20;
            swap(a, b);
            System.out.println(a + " " + b);
        }
    
        public static void swap(Integer a, Integer b) {
            Integer c = a;
            a = b;
            b = c;
        }
    }
    
    

    Integer a=10;的含义为Integer a= Integer.valueOf(10);当并不必须要在堆区new一个Integer对象时,建议用此方法。当被赋予的值在-128到127之间时,不会真的new一个Integer,而是在堆区中的cache里找到已经产生好的Integer(x)对象赋值给a。比如此时有Integer k=10;则k和a指向同一块内存地址(同一个对象的引用)。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OEyOh2fl-1679366188302)(./注解Annotation.assets/image-20230315104349386.png)]

    传参后:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5q4aSAtn-1679366188302)(./注解Annotation.assets/image-20230315104925796.png)]

    main中的a和b把引用的地址《按值》传递给了swap函数中的a和b。此后swap中做的操作为交换局部变量a和b的引用。使得swap中的a指向了堆中的Integer(20),swap中的b指向Integer(10)。显然,这并不能影响main中a和b的引用指向。所以不能交换a和b

  • 如果在swap中操作堆中的对象,则可以影响原main函数中的结果

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QdAiFi5j-1679366188302)(./注解Annotation.assets/image-20230315105701899.png)]

  • java传递基本数据类型时,也按值传递。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4iEtICBP-1679366188303)(./注解Annotation.assets/image-20230315110007629.png)]

可变长的参数

数组及其初始化

java中的变量有两种类型:基本数据类型和引用数据类型。

引用数据类型

SubClass[] sv = new SubClass[3];
for (int i = 0; i < 3; i++) {
    sv[i] = new SubClass();
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7IqQ1Urf-1679366188303)(./注解Annotation.assets/image-20230315110508046.png)]

Integer[] integers=new Integer[]{1,1,3,4,5};//自动装箱

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FqeknPB9-1679366188303)(./笔记.assets/image-20230315203429301.png)]

String[] strings=new String[]{"hello","hello","world"};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OO4yorFy-1679366188304)(./笔记.assets/image-20230315205306053.png)]

串池当中的字符串本质上也是一个String类型的对象。

基本数据类型

int[] array;
array = new int[]{2, 1, 3};

二维数组

String[][] s = new String[2][];
s[0] = new String[2];
s[1] = new String[3];
s[0][0] = new String("Good");
s[0][1] = new String("Luck");
s[1][0] = new String("to");
s[1][1] = new String("you");
s[1][2] = new String("!");

棕色的框框中的每个变量都指向静态方法区的串池中对应的String类对象。

静态初始化和动态初始化

静态初始化

String[][] v2d1 = {{"ab", "cd"}, {"ef", "gh"}};//静态初始化的省略格式。必须在数组变量声明时使用,如果已经声明过了则无法使用。
String[][] v2d2 = new String[][]{{"ab", "cd"}, {"ef", "gh"}};//静态初始化的完整形式
String[][] v2d3;
v2d3 = new String[][]{{"ab", "cd"}, {"ef", "gh"}};//静态初始化的拆分形式
String[][] v2d4;
v2d4 = {{"ab", "cd"}, {"ef", "gh"}};//错误

动态初始化

使用动态初始化数组时候,其中的元素将会自动拥有一个默认值:

* 如果是整数类型:那么默认为0;

* 如果是浮点数类型:那么默认为0.0;

* 如果是布尔类型:那么默认为false;

* 如果是引用类型:那么默认为null;

String[][] s = new String[2][];//用null填充
s[0] = new String[2];//用null填充
s[1] = new String[3];//用null填充
s[0][0] = new String("Good");//动态分配对象
s[0][1] = new String("Luck");
s[1][0] = new String("to");
s[1][1] = new String("you");
s[1][2] = new String("!");

集合Collection

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PoWv6AQr-1679366188304)(./笔记.assets/无标题.jpg)]

集合部分的使用建议查看csdn博客,有很多人总结的很详细。但是结构框图普遍使用有误,这里做出修订。

Collection2Array

.toArray()

不指定接收数组。返回的是Object类型数组,每个元素都是原list中的元素的浅拷贝(直接把字面值拷贝过来)。本质:直接调用Arrays.copyOf(element Data, size)

Object[] strArray2;
strArray2=list.toArray();
for (int i = 0; i < strArray.length; i++) 
{
    System.out.println(strArray2[i].toString());
}

.toArray(T[] a)

如果a.length小于list元素个数就直接调用Arrays.copyOf()方法进行拷贝并且返回新数组对象,新数组中也是装的list元素对象的引用(字面值复制),否则先调用System.arraycopy()将list元素对象的引用装在a数组中,如果a数组还有剩余的空间,则在a[size]放置一个null,size就是list中元素的个数,这个null值可以使得toArray(T[] a)方法调用者可以判断null后面已经没有list元素了,然后返回a数组的引用。

String[] a = new String[list.size()];
String[] strArray=list.toArray(a);
System.out.println(strArray.hashCode());//两者哈希值相同
System.out.println(a.hashCode());
for (int i = 0; i < strArray.length; i++) //这里也可以改写为  for(String str:strArray) 这种形式
{
    System.out.println(strArray[i]);
}

Arrays类和Collections类

两者都是工具类,所有成员函数都是静态的。Arrays类负责数组的操作,Collections类负责针对Collection家族的集合操作。

Arrays

排序

int[] num=new int[]{2,1,4};
Arrays.sort(array);//1,2,4

填充

String[][] s = new String[2][];
s[0] = new String[2];
s[1] = new String[3];
for (String[] i : s) {
    Arrays.fill(i, "fill");
}

Array2Collection

引用数据类型数组to集合

String[] arr={"abc","cc","kkk"};
List<String> list = Arrays.asList(arr);
System.out.println(list);
//list.add("a");被禁止,java.lang.UnsupportedOperationException。因为这是由数组转过来的集合,需要保持数组“长度不可变”的特性

基本数据类型数组to集合:无法直接转

int[] num=new int[]{2,3,4};
/**
 * 上面可以看到,数组的初始化使用了关键字new,而new关键字是用来初始化引用数据类型的,也就是说在Java中数组是引用数据类型。
 * 这解释了上面和下面的情况,当把引用数据类型组成的数组变为集合时,会拆开,每个引用变量作为一个集合中的元素,
 * 但是当把基本数据类型的数组传进去时,只认为该数组是一个引用数据类型,作为一个整体放入list中
 */
List<int[]> li =Arrays.asList(num);

Collections

public static void main(String[] args) {
        List<String> list = new ArrayList<String>();

        list.add("abcd");
        list.add("zz");
        list.add("pp");
        list.add("gg");

        Collections.shuffle(list);
        System.out.println(list);
        Collections.fill(list,"whx");
        System.out.println(list);
}

具体操作的API见jdk文档

迭代器Iterator

用遍历hashmap的例子来解释迭代器

Map<K,V>的元素是Map.Entry<K,V>

	Map<String, String> map = new HashMap<String, String>();
    map.put("1", "value1");
    map.put("2", "value2");
    map.put("3", "value3");
//方法1,用增强型for循环遍历Set
    for (Map.Entry<String, String> entry : map.entrySet()) {
        System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
    }

//方法2,用Set.iterator()返回迭代器。结合it.hasNext()和it.next()来逐个访问Set中的元素。
    Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry<String, String> entry = it.next();
        System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
    }

==和equals()和hashCode()

==比较的是变量本身的值

  • 如果双方都是基本数据类型,则比较的是两者的值。
        //基本数据类型的比较
        int num1 = 10;
        int num2 = 10;
        System.out.println(num1 == num2);   //true
  • 如果有一方是基本数据类型,则进行“拆箱”,把引用类型拆为基本数据类型。
        Integer num3=10;
        System.out.println(num1==num3);//true     
  • 如果双方都是引用类型,则比较的是也是二者本身的值,只不过这个值是所引用对象的地址。一般我们可以用System.identityHashCode(myObject)来查询地址
        Integer num4=10;
        System.out.println(num3==num4);//true
        Integer num5=200;
        Integer num6=200;
        System.out.println(num5==num6);//false
//比较二者本身的值(只不过比较的是被引用对象的地址,同一个对象则true,不同的对象则FALSE)
    Integer a = 100Integer b = 100;a == b 为true;因为这两个Integer变量引用的是缓存中的同一个Integer对象。
 * Integer a = 200Integer b = 200;a == b为false;是因为这两个Integer变量的引用是通过new出来的两个不同的对象。

装箱和拆箱

上面的

System.out.println(num3==num4);//true
System.out.println(num5==num6);//false

不同是因为,在执行

Integer num=10;//等价于Integer num=Integer.valueOf(10);

时,执行的是自动装箱过程。

什么是装箱

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

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

  1. Java1.5 之前
Integer a = Integer.valueOf(100); //手动装箱
int b = a.intValue(); //手动拆箱
  1. Java1.5 之后
Integer a = 100; //自动装箱
int b = a; //自动拆箱
装箱缓存

在堆中有Integer的cache,如果运用自动装箱,当被赋予的值在-128到127之间时,这个函数不会真的new一个Integer,而是在堆区中的cache里找到已经一个产生好的Integer(x)对象赋值给num(如果没有则产生一个,但也只有一个。记住cache中同一个数字都会对应同一个对象)。所以num3和num4都是从cache中拿到的同一个Integer对象的引用。

当变量值大于等于缓存范围值时,此时底层会new Integer(),重新分配内存地址。例子中的num5和num6不在缓存范围[-128,127]内,于是从堆中new出的两个不同的Integer对象。

缓存取值范围
int、Integer (-128,127)
long、Long(-128,127)
byte、Byte(-128,127)
short、Short(-32768,32767)
char、Character(0,127)

Integer a = 100Integer b = 100;a == b 为true;因为这两个Integer变量引用的是缓存中的同一个Integer对象。
Integer a = 200Integer b = 200;a == b为false;是因为这两个Integer变量的引用是通过new出来的两个不同的对象。

重写equals()可以丰富“相等”的含义

Object类默认的方法为:

public boolean equals(Object obj) {
    return (this == obj);
}

显然,如果不重写equals函数,则比较的依然是==,也就是二者的本身的值。这对于引用变量是不合适的。因为我们有时并不是要确定二者指向的是同一个对象(那样子用==也能判断),而是希望假如object1和object2的成员变量都一样(或者符合其他要求时),便认为二者是”相同的“,也就是object1.equals(object2)==true。所以要override这个equals函数。比如java.lang.String类中,比较两个串应该用.equals(),因为String类中重写了.equals()函数,逐个比较两个串中的字符,全都一致返回true否则false。

如果直接用静态存储区的字符串给String变量赋值,则看不出equals的作用,好似==也可以。此时s1和s2指向静态存储区的同一个String对象"hello"

  		String s1 = "hello";
        String s2 = "hello";
        System.out.println(s1 == s2);    //true,比较地址值:内容相同,因为常量池中只有一个“hello”,所以它们的地址值相同
        System.out.println(s1.equals(s2));//true,比较内容:内容相同,因为常量池中只有一个“hello”,所以它们的地址值相同
        System.out.println(s1.equals("hello")); //true

但如果改为如下代码,就体现出equals的作用了。

        String s3 = new String("hello");
        String s4 = new String("hello");  //同上,实际上是用一个静态存储区的String对象去初始化一个自定义的Stirng对象。
        System.out.println(s3 == s4);        //false,比较地址值:s3和s4在堆内存中的地址值不同
        System.out.println(s3.equals(s4));    //true,比较内容:内容相同

对于String s3 = new String(“world”);
首先在堆内存中申请内存存储String类型的对象,将地址值赋给s3;
在方法区的常量池中找,有无hello:
若没有,则在常量池中开辟空间存储hello,并将该空间的地址值赋给堆中存储对象的空间;
若有,则直接将hello所在空间的地址值给堆中存储对象的空间。

字符串作为最基础的数据类型,使用非常频繁,如果每次都通过 new 关键字进行创建,会耗费高昂的时间和空间代价。Java 虚拟机为了提高性能和减少内存开销,就设计了字符串常量池存放在静态方法区.

如果重写equals(),也需要重写hashCode()

重写equals的常见格式

public class bean {
    Integer x;
    public bean(Integer x){
        this.x=x;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;//比较两个引用是否指向堆中的同一个对象,如果是则直接返回true。
        if (o == null || getClass() != o.getClass()) return false;//否则,需要比较内部数据。所以当参数引用为null或者类型不匹配时,返回false。
        bean bean = (bean) o;//如果是两个同类型(bean)的不同对象,则将参数对象向下转换为子类对象。
        return Objects.equals(x, bean.x);//对象equals等价于内部数据equals,类似一个递归的过程。
    }
    @Override
    public int hashCode() {//重写equals必须重写hashcode
        return Objects.hash(x);//hashcode的计算保证:equal的对象必须hashcode相同。只要把equal考虑到的成员变量作为计算hashcode的参数就可以了。本例中,x相同则bean视作相同,所以hashcode由且仅由x的值唯一确定。
    }
}

为什么如果重写equals(),也需要重写hashCode()?

hashCode() 方法的存在主要是用于查找的快捷性,如Hashset,HashMap等,hashCode() 方法是用来在散列存储结构中确定对象的存储地址的。虽然equals()可以比较不同的对象,但是不能插入一个元素,就从头到尾比较一轮,确保集合中没有同样的元素。所以set和map接口的实现中,选择利用散列结构来存储(也就是实现类hashset和hashmap)。先根据object.hashCode()计算出哈希值,然后直接去散列中找hash值处是否已经存放了一个对象。如果没有,则该object可以存入散列中,如果有,可能是哈希冲突导致,再利用equals来逐个判断同位置的冲突。这样子比前者的效率高了非常多。

所以,hashcode应该符合如下约定:

  1. 同一个对象多次调用hashCode()方法应该返回相同的值;
  2. 当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()应该返回相等的(int)值;
  3. 对象中用作equals()方法比较标准的Field(成员变量(类属性)),都应该用来计算hashCode值。
  4. 两个对象的 hashCode 值相同,并不一定表示两个对象就相同,也就是不一定适用于 equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如 Hashtable,他们“存放在同一个篮子里”。

hashcode的应用

hashset

HashSet集合判断是否可以添加新元素标准是:先判断hashcode是否重复,若重复再判断是equals元素还是哈希冲突(不同元素但是计算得到的哈希值相同)。

如果两个元素通过equals()方法比较返回true,但是它们的hashCode()方法返回值不同,HashSet会把它们存储在不同的位置,依然可以添加成功。

hashmap

HashMap同理.HashMap,存储的数据是<key,value>对,key,value都是对象,被封装在Map.Entry中。即:每个集合元素都是Map.Entry对象。在Map集合中,判断key相等标准也是:两个key通过equals()方法比较返回true,两个key的hashCode的值也必须相等。而判断value是否相等,equal()相等即可。

泛型Generic

泛型只能用来表示引用类型,如果传递的参数是基本数据类型,则会自动装箱。

泛型方法

修饰符 < 泛型 > 返回值类型 方法名 ( 参数列表 ) {
	方法体;
}

在函数返回值前加入泛型声明

public class demo1 {
    public static void main(String[] args) {
        Integer[] integers=new Integer[]{1,1,3,4,5};
        Double[] doubles=new Double[]{1.1,1.1,3.3,4.4,5.5};
        String[] strings=new String[]{"hello","hello","world"};
        System.out.println( "整型数组元素为:" );
        printArray( integers  ); // 传递一个整型数组

        System.out.println( "\n双精度型数组元素为:" );
        printArray( doubles ); // 传递一个双精度型数组

        System.out.println( "\n字符型数组元素为:" );
        printArray( strings ); // 传递一个字符型数组
    }
    public static <E> void printArray(E[] input){
        for (E i :input){
            System.out.println(i+"\t"+System.identityHashCode(i));
        }
        System.out.println();
    }
}
/*
整型数组元素为:
1	1625635731
1	1625635731
3	1580066828
4	491044090
5	644117698
地址相同原因:自动装箱,cache中对应同一个Integer对象。

双精度型数组元素为:
1.1	1872034366
1.1	1581781576
3.3	1725154839
4.4	1670675563
5.5	723074861
也有cache,但是可能由于浮点数的原因,没有指向同一个。(待继续研究)

字符型数组元素为:
hello	895328852
hello	895328852
world	1304836502
地址相同原因:strings[0]和strings[1]指向的是同一个静态方法区的串。
*/

泛型类

修饰符 class类名 <代表泛型的变量> extends ClassA implements InterfaceA,InterfaceB{
	类成员
}

在类名后面加入泛型声明。

这个例子同时说明,泛型T只支持引用数据类型,如果给基本数据类型(基本数据类型不包含String),则会自动装箱。

public class demo2<T> extends Object implements Serializable,Cloneable {
    private T t;
    public void setT(T t){
        this.t=t;
    }
    public T getT(){
        return t;
    }

    public static void main(String[] args) {
        demo2<Integer> d1=new demo2<>();
        demo2<String> d2=new demo2<>();
        d1.setT(4);//new Integer(4)
        d2.setT("菜鸟教程");//"菜鸟教程"
        System.out.println(d1.getT()+" "+d1.getT().getClass());
        System.out.println(d2.getT()+" "+d2.getT().getClass());

    }
}
/*
4 class java.lang.Integer
菜鸟教程 class java.lang.String
*/

泛型通配符和泛型类型限制

泛型通配符为 ?

<? extends T> 上限通配

这里?表示一个未知的类,而T是一个具体的类,在实际使用的时候T需要替换成一个具体的类,表示实例化的时候泛型参数要是T或T的子类。

<? super T> 下限通配

这里?表示一个未知的类,而T是一个具体的类,在实际使用的时候T需要替换成一个具体的类,表示实例化的时候泛型参数要是T或T的父类。

public class demo3 {
    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);

        getData2(age);
        getData2(number);
        
        getData3(age);
        getData3(number);

    }
    //不加限制
    public static <T> void  getData(List<T> list){
        System.out.println("getData");
        for (Object i : list){
            System.out.println(i);
        }
    }
    
    //通配符+限制
    public static void  getData2(List<? extends Number> list){
        System.out.println("getData2");
        for (Object i : list){
            System.out.println(i);
        }
    }
    
    //改为泛型函数+泛型类型限制
    public static <T extends Number> void  getData3(List<T> list){
        System.out.println("getData3");
        for (T i : list){
            System.out.println(i);
        }
    }
}
/*
getData
icon
getData2
18
getData2
314
getData3
18
getData3
314
*/

泛型通配符?的作用在于,当你不想因为List<父类> a和List<子类> b传给同一个方法func时出现类型错误,而将func改为一个泛型时:可以用泛型通配符来 将参数改为可以接受泛型的参数。?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义泛型类和泛型方法。

IO流

控制台io

package basicGrammer;

import java.util.Scanner;

public class test4 {
    public static void main(String[] args) {
        Scanner scanner;
        scanner = new Scanner(System.in);
        System.out.println("nextline方式接受");
        if (scanner.hasNextLine()){
            String str1= scanner.nextLine();
            System.out.println("输入数据为 "+str1);
        }
        scanner.close();
    }
}

字节流

详细代码见工程。

FileInputStream

    File file = new File(name);
    InputStream fis = new FileInputStream(file);
    // 开始读,采用byte数组,一次读取多个字节。最多读取“数组.length”个字节。
    byte[] bytes = new byte[4];// 准备一个4个长度的byte数组,一次最多读取4个字节。
    int readCount = 0;
	// FileInputStream.read方法的返回值是:读取到的字节数量。(不是字节本身);1个字节都没有读取到返回-1(文件读到末尾)
	while ((readCount = fis.read(bytes)) != -1) {
        System.out.println(readCount);
        // 不应该全部都转换,应该是读取了多少个字节,转换多少个。
        System.out.println(new String(bytes, 0, readCount));
     }

BufferedInputStream

    File file=new File("src/main/java/text.txt");
    InputStream inputStream=new FileInputStream(file);//节点流
	BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);//处理流/包装流。

	int len=bufferedInputStream.available();	
	byte[] b=new byte[100];
	bufferedInputStream.read(b);//自带缓冲区,b只作为接收数组。
	String x=new String(b,0,len);
    System.out.println(x);

FileOutputStream

    File file=new File("src/main/java/text.txt"); 	
	OutputStream fos = new FileOutputStream(file,append=false);
	byte[] bytes = {48,49,50,51,-17, -68 ,-127};
	/*
	utf-8编码,是变长编码。48,49,50,51是ASCII码,仅每个占用一个字节。如果首个bit为1(数字是负数),则根据不同格式可以分为两字节1字符,三字节1字符等等(类比IP地址的A、B、C三种)。这里的-17,-18,-127三个一组,表示中文字符的感叹号"!"
	*/
    // 将byte数组全部写出!
    fos.write(bytes); // 0,1,2,3,!
    // 将byte数组的一部分写出!
    fos.write(bytes, 0, 2); // 再写出0,1

	
    String s = "我骄傲!!!0123aa";
    // 将字符串转换成byte数组。
    byte[] bs = s.getBytes();
    fos.write(bs);
	fos.flush();

BufferedOutputStream

    File file=new File("src/main/java/text.txt");
    FileOutputStream fileOutputStream=new FileOutputStream(file,true);    
	BufferedOutputStream bos = new BufferedOutputStream(fos);//包装类
    byte[] b={78,79,80,81};
    bs.write(b);
    bs.flush();

字符流

FileReader

    // 创建文件字符输入流
    FileReader reader = new FileReader("src/main/java/text.txt");
    // 开始读
    char[] chars = new char[4]; // 一次读取4个字符
    int readCount = 0;
    while((readCount = reader.read(chars)) != -1) {
        System.out.println(readCount);
        System.out.println(new String(chars,0,readCount));
    }

BufferedReader

	FileReader reader = new FileReader("src/main/java/text.txt");
	BufferedReader br = new BufferedReader(reader);
    String s = null;
    while((s = br.readLine()) != null){
        System.out.println(s);
    }

FileWriter

	FileWriter out = new FileWriter("src/main/java/text.txt");
    char[] chars = {'我','是','中','国','人'};
    out.write(chars);//字符数组
    out.write("我是一名java软件工程师!");//字符串
	out.flush();

BufferedWriter

    // 带有缓冲区的字符输出流
    BufferedWriter out = new BufferedWriter(new FileWriter("src/main/java/text.txt", true));
    // 开始写。
    out.write("hello world!");
    out.write("\n");
    out.write("hello kitty!");
    // 刷新
    out.flush();

序列化

serializable接口

想要序列化的类必须实现Serializable接口

public class bean implements Serializable{
	...
}

ObjectInputStream

File file = new File("src/main/java/serializable/BinaryFile.ser");
InputStream inputStream=new FileInputStream(file);//节点流
ObjectInputStream objectInputStream=new ObjectInputStream(inputStream);//包装流
object=(bean)objectInputStream.readObject();
System.out.println(object.name+"\t"+object.id);
objectInputStream.close();

ObjectOutputStream

bean object = new bean("whx", 201992391);
File file=new File("src/main/java/serializable/BinaryFile.ser");
OutputStream outputStream=new FileOutputStream(file);//节点流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);//包装流
objectOutputStream.writeObject(object);
objectOutputStream.close();

多线程multiThread

建立一个线程,只需要新建一个Thread类对象thread即可。运行这个线程,只需要调用thread对象的start()方法即可。

而我们自定义的线程内部运行的内容,放在Thread类的run()方法中。也就是说,run()方法使我们我们不需要管理线程,只关心核心业务逻辑即可。

Runnable接口:

public interface Runnable {
	public abstract void run();
}

Thread类:

class Thread implements Runnable {
    private Runnable target;
    public synchronized void start() {
        //前处理
        run();//逻辑核心
        //后处理
    }
    @Override
    public void run() {
        if (target != null) {//静态代理模式
            target.run();
        }
    }
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
    public Thread(String name) {
        init(null, null, name, 0);
    }

    public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }
    
}
    

这里很显然,我们想要自定义run方法的内部逻辑,有以下两种办法。

继承Thread类

package multiThread;
public class thread1 extends Thread {
    @Override
    public void run() {
        for(int i=0;i<20;i++){
            System.out.println("thread 1 is running "+i);

        }
    }

    public static void main(String[] args) {
        thread1 t1=new thread1();
        t1.start();
        for(int i=0;i<20;i++){
            System.out.println("thread main is running "+i);

        }
    }
}

继承Thread类后,可以重写父类的run方法以达到自定义线程运行逻辑的效果。但是不推荐,因为java单继承的要求,这个thread1类继承了Thread类后,就不能继承其他的类了。而下面的实现Runnable接口就解决了这个麻烦。

实现runnable接口

由于Thread类使用了静态代理模式,可以用一个实现了Runnable接口的类对象来初始化Thread类,成为target变量。而Thread对象中的run方法实际上调用了target的run方法。

public class runnable1 implements Runnable{
    public void run(){
        for(int i=0;i<20;i++){
            System.out.println("thread 1 is running "+i);
        }
    }

    public static void main(String[] args) {
        runnable1 r=new runnable1();
        Thread t=new Thread(r);
        t.start();
        for(int i=0;i<20;i++){
            System.out.println("thread main is running "+i);
        }
    }
}

匿名内部类方式实现runnable接口

public class runnable2 {
    public static void main(String[] args) {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<20;i++){
                    System.out.println("thread 1 is running "+i);
                }
            }
        });
        t.start();
        for(int i=0;i<20;i++){
            System.out.println("thread main is running "+i);
        }
    }
}

静态代理模式

上面提到了两个方法:一是继承Thread类,重写run方法;二是实现Runnable接口,继而赋值给Thread成为其内部的私有target变量。如果我们选择继承Thread类,则由于源码中 private Runnable target;,不能直接指定target,只能重写run函数。

而如果我们选择用一个实现了runnable接口的类来作为被代理类(Thread类是代理类,runnable1类是被代理的类),并在初始化Thread类时传给他做为target代理对象,则可以实现同样的效果。

代理的作用

避免创建大对象 通过使用一个代理小对象来代表一个真实的大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。

举例

代理类:婚庆公司;被代理类:新婚夫妻。

代理类要求

  1. 代理类和目标类(被代理类)需要实现相同的接口。——相同的运作逻辑协议。
  2. 代理类在实现接口函数时,调用被代理类的实现函数,并丰富之。

线程不安全问题

多个线程访问临界变量时不加以约束,将会导致线程不安全问题。【未完待续】

注释Comment

//这是一个单行注释

/*
这是一个多行注释
 */

/**
 * 这是一个文档注释
 * the details are as follows:
 * first...second....and then....
 *
 * @author wangh
 * @version 1.2
 */

自己的文档加了文档注释后,可以用javadoc生成和java官方文档一样的html文档。

注解Annotation

1 生成文档相关的注解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UvkPMJFv-1679366188305)(./注解Annotation.assets/image-20230315093319419.png)]

在生成文档javadoc中,会体现出annotation的作用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EU7acTpK-1679366188305)(./注解Annotation.assets/image-20230315093305009.png)]

2 编译过程中的格式检查

jdk内置的3大基本注解:

  1. Deprecated

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u7vYZxAD-1679366188305)(./注解Annotation.assets/image-20230315093658856.png)]

  2. Override

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6tgh7d7i-1679366188306)(./注解Annotation.assets/image-20230315093707001.png)]

  3. SuppressWarnings(String)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CGPHrhiD-1679366188306)(./注解Annotation.assets/image-20230315093713831.png)]

3 跟踪代码依赖性,实现替代配置文件的功能

  1. @WebServlet(“/login”)注解替代了如下web.xml中的配置信息

    <web-app>
    	<servlet>
    		<servlet-name>TestServlet</servlet-name>
    		<servlet-class>com.yiyu.servlet.showAllServlet</servlet-class>
    	</servlet>
    	<servlet-mapping>
    		<servlet-name>TestServlet</servlet-name>
    		<url-pattern>/login</url-pattern>
    	</servlet-mapping>
    </web-app>
    
  2. spring中的关于“事务”的管理

  3. 单元测试 JUnit 注解@Test

    package JUnitTest;
    import org.junit.Test;
    /*
    1 maven引入JUnit依赖,并在测试java源文件加入import org.junit.Test;
    2 创建单元测试类要求:
        2.1 类为public
        2.2 类中提供公共无参构造器
    3 测试方法要求:
        3.1方法的权限为public
        3.2没有返回值
        3.3没有形参
    4 加入@Test注解
     */
    public class demo1 {
        @Test
        public void testPrint(){
            System.out.println("hello");
        }
    }
    
    

自定义注解和元注解

自定义的注解需要配合后续的信息处理流程(使用反射)才有意义。

框架 = 注解+反射+设计模式

通过反射可以获得注解信息

自定义注解@interface

注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,
其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。自定义注解中需要用元注解来修饰。

元注解

用来标注《注解》的《注解》。常见元注解如下:

@Retention:生存期(important)。处于RetentionPolicy.RUNTIME的注解可以在jvm中运行时通过反射机制被读取。此外还有
@Target:作用域(important)。如果不写Target元注解,则该myAnnotation注解可以在任意地方使用
@Documented:可被javadoc提取到文档,因为默认情况下javadoc是不会包含注解信息的。
@Inherited:可继承的注解,父类被myAnnotation修饰了,则子类也会被同一个annotation修饰。
@Repeatable:可重复的注解。同一个注解可以重复修饰同一个作用域。在jdk8之前,只能通过显示建立《注解数组》的方式来保存多个相同注解。该注解是在jdk8之前《数组解决方案》的基础上优化而来,加上注解后,重复的注解将自动隐式的建立《注解数组》的注解。Returns: the containing annotation type

@Target(value={ElementType.METHOD,ElementType.TYPE})//ElementType是枚举enum类型
@Retention(value= RetentionPolicy.RUNTIME)//RetentionPolicy是枚举enum类型
@Documented
@Inherited
@Repeatable(myAnnotations.class)//里面的类是这个注解的《注解数组类》。

public @interface myAnnotation {
    String[] value() default "hello";//成员变量的默认值用default定义。
    //如果注解没有成员,则表明作为一个标识的作用
}
@Target(value={ElementType.METHOD,ElementType.TYPE})
@Retention(value= RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface myAnnotations {
    myAnnotation[] value();
}

注解测试

@myAnnotation(value = {"a","b"})
@myAnnotation   //支持可重复注解,支持默认value
//上述两个重复注解等价于@myAnnotations({@myAnnotation(value = {"a","b"}),@myAnnotation })

public class demo1 {
    @Deprecated
    public static void print(){
        System.out.println("hello");
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @myAnnotation//用default赋值,且该方法被两个注解修饰(不属于可重复注解,因为不是同一个)
    @SuppressWarnings("all")
    public void test(){
        List<String> list=new ArrayList<>();
    }
    public static void main(String[] args) {
        print();
    }
    @Test
    public void testGetAnnotation(){
        Class<subdemo> subdemoClass = subdemo.class;
        Annotation[] annotations = subdemoClass.getAnnotations();
        for(Annotation i :annotations){
            System.out.println(i);
            System.out.println("可见subdemo继承了父类的注解");
        }
    }

    @myAnnotation({"a","b"})
    public void test2(){}

}
class subdemo extends demo1{//继承了父类注解

}

https://blog.csdn.net/qq_38129062/article/details/88816602?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167877970616800186531183%2522%252C%2522scm%25

反射reflection

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IKuIRmcg-1679366188306)(./笔记.assets/image-20230318165804379.png)]

理解反射

反射的特征:动态性。

原来的静态代码:将代码(字节码文件)写好后交给机器,机器就会按部就班的读取,执行。

加入反射后的动态性:程序在运行中可以反过来查看类内部的代码结构,并主动选择想要实例化的对象、选择想要执行的函数进行执行,或改变这个类的某个实例中的变量值。这实际上改变了字节码文件。

反射的提出背景

在java中会出现编译时类型运行时类型 不一致的问题。比如

Object obj = new String("abc");
//obj.charAt(0);报错

String x="abc";
System.out.println(x.charAt(0));//不报错

编译时类型为Object,运行时类型为String。但是程序想要调用运行时类型的方法,比如obj.charAt(0);显然,这会报错。因为Object中没有getChar()方法。此时,我们需要将obj进行向下转型,那么应该转为什么类型呢?有以下两种方案。

  1. instanceof来判断。假如目前有很多不同的运行时对象都是被Object类型所引用,那么写一堆的if-else显然是愚蠢的。

    if (a instanceof String){
        System.out.println(((String) a).charAt(0));
    }
    
  2. 于是,我们可以通过反射a.getClass(),获取到对象对应的运行时类——Class对象aClass。进而通过aClass来得到运行时类的方法CharAt()。最后用这个方法来反射回原本的运行时对象a

    Object a="abc";
    Class<?> aClass = a.getClass();
    Method method = aClass.getMethod("charAt", int.class);
    System.out.println(method.invoke(a,0));
    

这其实提供了一种动态性 的思想。通过反射,可以获取对象的类型。类比其他动态性语言,比如javaScript:var a = 20 ;、scala:val a = 20、python:a = 20。并不需要指定引用类型,这和java中反射的思想类似,并不需要分析引用变量本身的类型来调用引用变量所指向的对象的方法和变量,而是利用反射的方法来实现。具体这些语言是如何实现动态性的,还需要进一步学习。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-afLqJ0ZQ-1679366188306)(./笔记.assets/image-20230319102953353.png)]

反射机制有什么用?

反射机制被视为是动态语言的关键,反射机制允许程序在运行期间 借助于Reflection API取得任何类的内部信息,并直接操作任意对象的内部属性和方法。

  1. 反射可以获取运行时类的所有方法、所有属性、所有构造器。
  2. 反射可以获取运行时类的父类、接口们、包、带泛型的父类、父类的泛型、注解Annotation等

类的加载过程

万物皆是对象

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子可以看到类的内部结构。所以我们将其形象的成为反射。

没加入反射前,对象有属性和方法,如同人可以吃,可以看,可以跑。

加入反射后,程序中的对象可以通过反射得知自己类的结构,如同照镜子一样,人照了镜子,就一下子得知,原来自己能吃是因为“有一张嘴”,能看是因为“有一双眼睛”。

类的加载时机

  • 创建类的实例,也就是new一个对象。
  • 访问类的静态方法或者静态变量(包含静态变量赋值)。
  • 使用Class.forName()反射类或其他反射手段。
  • 子类初始化的时候。
  • JVM启动时标明的启动类。

加载流程

  1. 程序经过javac.exe【javac Person.java】命令后,Person.java文件被编译成一个或多个字节码文件,比如Person.class等(Person.java文件中可能有其他的非public外部类或者内部类,它们也会产生对应的.class文件),一个.java类对应一个.class字节码文件。

  2. 用java.exe命令【java Person】对字节码文件Person.class进行解释运行(该字节码文件中应该具有程序入口——main方法)。首先java.exe会调用底层windows的jvm.dll文件创建jvm虚拟机,根据JVM内存配置要求,为JVM申请特定大小的内存空间。

  3. 创建一个引导类加载器(启动类加载器)实例,初步加载系统类到内存方法区区域中;JVM申请好内存空间后,JVM会创建一个引导类加载器(Bootstrap Classloader)实例,引导类加载器是使用C++语言实现的,负责加载JVM虚拟机运行时所需的基本系统级别的类,如java.lang.String, java.lang.Object等等。引导类加载器会读取 {JAVA_HOME/jre/lib/rt.jar、resources.jar 或sun.boot.class.path 路径下的内容 下的jar包和配置,然后将这些系统类加载到方法区内。

  4. 创建JVM 启动器实例 Launcher,并取得类加载器 ClassLoader。然后由于Person.class字节码文件属于上述类加载时机 中的JVM启动时标明的启动类 ,于是jvm使用类加载器 将Person.class字节码文件加载到内存中的方法区(目前,方法区已成为堆区的子区域)。此过程称为类的加载。加载过程分为装载、链接、初始化三个步骤。

  5. 加载到方法区中的类,我们就称为运行时类 。此运行时类的产生,实际上是jvm在方法区中为之产生了一个Class类的实例化对象——Person类。后续的使用new方法实例化反射调用 等操作都只会通过此Class类的实例化对象——Person类来产生或执行。一个运行时类对应一个Class类的实例对象。所以说类本身也是一个对象:Class类的对象,当类被加载后,整个类的结构都会被封装在Class对象中。 且该运行时类在方法区的存储区域不会被释放,在整个执行期间,.class文件只会被jvm加载一次(不严谨)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g5dQvOrx-1679366188307)(./笔记.assets/image-20230318161122691.png)]

每一个被加载进方法区的类,比如Person类,都可以看作是一个《更高层》的类 ——Class类所产生的对象。

Class类将被加载的类中的各种成分映射成一个个的成员对象并封装在Class对象中。在Class类看来,jvm读取Person.class的过程相当于一个新的Class类的对象Person被载入到方法区中,且Person中的构造函数,成员变量,成员函数都是这个Class类的对象(Person)的成员,它们都可以通过Class类中的API获取它们。

Class类的特点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7iJt5znN-1679366188307)(./笔记.assets/image-20230318170920230.png)]

代码示例

@Test
//通过反射让person类产生对象.
// 首先,通过反射,得到Person类的Class对象,相当于得到的不是person类的对象,而是得到了person类本身。
//Class类提供了一些api来调取Person中的各个组成部分,并把他们作为一个个的java对象返回。
//然后获取该Class对象(person类本身)的构造器constructor,然后用构造器在堆中创建新的person类的对象。
public void test2() throws Exception {
    Class<Person> personClass = Person.class;
    System.out.println(personClass);
    Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
    Person tom = constructor.newInstance("Tom", 12);
    System.out.println(tom);
    //通过反射调用对象指定的可以访问的属性和方法。
    Field age = personClass.getDeclaredField("age");
    age.set(tom,10);
    System.out.println(tom);
    Method show = personClass.getDeclaredMethod("show");
    show.invoke(tom);
    
    //通过反射调用对象的私有的属性和方法
    //通过反射用Class的对象直接new一个Instance(@deprecated)
    Class clazz = Person.class;
    Person per = (Person)clazz.newInstance();//不接受参数,只调用默认构造
    //通过反射调用私有构造器
    Constructor<Person> constructor1 = personClass.getDeclaredConstructor(String.class);
    constructor1.setAccessible(true);
    Person p = constructor1.newInstance("Jack");
    System.out.println(p);
    //通过反射直接修改私有属性
    Field name = personClass.getDeclaredField("name");
    name.setAccessible(true);
    name.set(p,"Peter");
    System.out.println(p);
    //通过反射直接调用私有的方法
    Method showNation = personClass.getDeclaredMethod("showNation",String.class);
    showNation.setAccessible(true);
    String returnString=(String) showNation.invoke(p,"China");
    System.out.println("返回值是"+returnString );//如果原函数返回值为void,则这里的returnString=null
	
    
    //通过反射调用类的静态属性和方法    
    //同过反射直接修改静态属性
    Field info = personClass.getDeclaredField("info");
    info.setAccessible(true);
    info.set(personClass,"information field");//访问静态变量,故参数不是对象p,而是Person类。
    System.out.println(info.get(personClass));
    //或者参数直接写null也可以,效果同上。
    info.set(null,"information field");
    System.out.println(info.get(null));
    //通过反射直接调用静态方法
    Method showInfo = personClass.getDeclaredMethod("showInfo");
    showInfo.setAccessible(true);
    showInfo.invoke(null);
}

代码实例的解析(重点)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-azrLTTIZ-1679366188307)(./笔记.assets/image-20230320164358062.png)]

反射的动态性的应用

在web服务中,web服务器在启动的时候,会将所有的class字节码文件加载入jvm中的方法区。根据解析url来判断需要走哪个servlet,然后服务器将通过反射机制,获取方法区中对应的Class类对象,并在堆中产生所需要的servlet对象。这也是典型的反射机制的使用。仅用来产生新对象的话,没有破坏封装性。

反射是否破坏了封装性

封装性更多的是一种设计规范,虽然私有的函数(变量)和共有的函数(变量)都可以通过反射的方法获取,但是我们应该在开发中遵守这些访问的原则。就如同单例模式,只是一种设计模式,希望调用者不要自己new对象,但是通过反射依然可以做到,只不过完全没必要。综上,反射更加强大,但建议 使用者继续遵循以前的封装约束。

获取Class对象的几种方法

方式 备注
Class.forName(“完整类名带包名”) 调用Class类的静态方法。(推荐)
对象.getClass() 调用运行时类的对象的getClass方法
任何类型.class 调用运行时类的静态属性
ClassLoader见下文 使用类加载器(不推荐)
public void test1() throws ClassNotFoundException {
    Class<Person> personClass = Person.class;
    Person person = new Person();
    Class<? extends Person> aClass = person.getClass();
    System.out.println(personClass==aClass);
    String classname="reflection.Person";
    Class<?> aClass1 = Class.forName(classname);
    System.out.println(aClass1==aClass);
    Class<?> aClass2 = ClassLoader.getSystemClassLoader().loadClass(classname);
    System.out.println(aClass2==aClass);
}

所有的java类型都可以被Class类型的变量所指向。

ClassLoader获取文件输入流

ClassLoader获取系统类加载器SystemClassLoader,其中的方法getResourceAsStream可以获取文件输入流并加载配置文件(类比Properties的加载方式)

@Test
public void test3() throws IOException {
    //通过类的加载器读取的文件的默认的根路径为:当前module的resources文件夹下
    String filename="info.properties";
    InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream(filename);
    Properties properties = new Properties();
    properties.load(resourceAsStream);
    System.out.println(properties.get("user"));
    System.out.println(properties.get("password"));
}

一些方法实例

代码见reflection包。

clazz.getFields()

获取所有的本类及其所有父类的public的属性

public void test3() throws ClassNotFoundException {
    Class clazz = Person.class;
    Field[] fields = clazz.getFields();
    for (Field f:fields){
        System.out.println(f);
    }
}
/*
public int reflection.Person.age
public int reflection.Creature.id
*/

clazz.getDeclaredFields()

获取当前运行时类中声明的所有属性。并且有更强大、细粒度的API,可以获取每个属性的“修饰符”、“类型“、”命名“等细粒度的信息。

@Test
public void test2() throws ClassNotFoundException {
    Class clazz = Person.class;
    Field[] fields = clazz.getDeclaredFields();
    for (Field f:fields){
        System.out.println(f);
    }
}
/*
private java.lang.String reflection.Person.name
public int reflection.Person.age
private static java.lang.String reflection.Person.info
*/

clazz.getMethods

类似clazz.getFields(),获取所有的本类及其所有父类的public的方法

clazz.getDeclaredMethods

类似clazz.getDeclaredFields(), 获取当前运行时类中声明的所有方法。同样,对于method,也有更强大、细粒度的API,可以获取每个方法的“修饰符”、“返回类型“、”命名“、“注解Annotation信息”、“抛出的异常”、“形参列表”、“包”等细粒度的信息。

@Test
public void test4() throws ClassNotFoundException {
    Class clazz = Person.class;
    Method[] fields = clazz.getDeclaredMethods();
    for (Method f:fields){
        System.out.println(f);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZjwUnv0W-1679366188308)(./笔记.assets/image-20230319214354194.png)]

下面以获取方法上的注解信息为例,体会反射的作用。

//获取方法的注释信息。
@Test
public void test4() throws ClassNotFoundException {
    Class clazz = Person.class;
    Method[] methods = clazz.getDeclaredMethods();
    for(Method m:methods){
        Annotation[] annotations = m.getDeclaredAnnotations();
        if(annotations.length!=0){
            System.out.println(m+"\t");
        }
        for (Annotation a : annotations){
            System.out.println(a);
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wLXO3zhC-1679366188308)(./笔记.assets/image-20230320095206353.png)]

在上述获取方法的注释信息实验中,需要注意自己定义的@myAnnotation注解必须声明Retention为RetentionPolicy.RUNTIME,才可以把注解信息带到运行时的内存中,才能够被反射获取 。

@myAnnotation定义如下

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD,ElementType.CONSTRUCTOR,ElementType.LOCAL_VARIABLE})
public @interface myAnotation {
    String value();
}
方法 返回值 备注
m.getName() String 获取方法名
m.getDeclaredAnnotations() Annotation[] 获取注释
m.getModifiers() int 返回修饰符。类似二进制的存储方式。1010表示1000(static)+10(private)
m.getParameters() Parameter[] 返回形参列表

更多的方法见尚硅谷视频。

体会架构中通过反射获取注解的过程

注解1:表示某个类对应于mysql数据库中的表的名称

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value();
}

注解2:表示某个类中的成员变量对应于mysql数据库中的表的某个字段名称和类型。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String columnName();
    String columnType();
}

被注解修饰的Customer类

@Table(value = "t_costumer")
public class Customer {
    @Column(columnName = "cust_name",columnType = "varchar(15)")
    private String name;
    @Column(columnName = "cust_age",columnType = "int")
    private int age;
}

通过反射获取Customer类中各个区域的注解信息

public class test {
    //获取类对应的注解
    @Test
    public void test1(){
        Class<Customer> customerClass = Customer.class;
        Table declaredAnnotation = customerClass.getDeclaredAnnotation(Table.class);
        System.out.println(declaredAnnotation.value());

    }
    //获取属性对应的注解
    @Test
    public void test2() throws NoSuchFieldException {
        Class<Customer> customerClass = Customer.class;
        Field name = customerClass.getDeclaredField("name");
        name.setAccessible(true);
        Column declaredAnnotation = name.getDeclaredAnnotation(Column.class);
        System.out.println(declaredAnnotation.columnName());
        System.out.println(declaredAnnotation.columnType());
    }
}

属性文件Properties

public void test1() throws IOException {
    //根地址为module根地址
    File file = new File("src/main/java/properties/info.properties");
    FileInputStream fileInputStream = new FileInputStream(file);
    Properties properties = new Properties();
    properties.load(fileInputStream);
    System.out.println(properties.get("user"));
    System.out.println(properties.get("password"));
}

文件info.properties:

user=root
password=root

JavaBean

JavaBean是一种Java语言写成的可重用组件。

所谓JavaBean,是指符合如下标准的Java类:

  • 类是公共的
  • 有一个无参的公共的构造器
    • 原因1:子类对象实例化时,构造器首行会默认调用父类的空参构造器,除非指定。
    • 原因2:在反射中,经常用于创建运行时类的对象。一般而言会使用空参构造,因为对不同的JavaBean是通用 的。故最好每个类都提供空参构造器。
  • 有属性,且有对应的get、set方法
  • 如果加上toString,equals,hashCode来丰富就更好了。

用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【轩说Java】JavaSE知识点难点汇总 的相关文章

  • Java 中等效的并行扩展

    我在 Net 开发中使用并行扩展有一些经验 但我正在考虑在 Java 中做一些工作 这些工作将受益于易于使用的并行库 JVM 是否提供任何与并行扩展类似的工具 您应该熟悉java util concurrent http java sun
  • Grails 3.x bootRun 失败

    我正在尝试在 grails 3 1 11 中运行一个项目 但出现错误 失败 构建失败并出现异常 什么地方出了错 任务 bootRun 执行失败 进程 命令 C Program Files Java jdk1 8 0 111 bin java
  • 如何使用 Java 和 Selenium WebDriver 在 C 目录中创建文件夹并需要将屏幕截图保存在该目录中?

    目前正在与硒网络驱动程序和代码Java 我有一种情况 我需要在 C 目录中创建一个文件夹 并在该文件夹中创建我通过 selenium Web 驱动程序代码拍摄的屏幕截图 它需要存储在带有时间戳的文件夹中 如果我每天按计划运行脚本 所有屏幕截
  • Java中反射是如何实现的?

    Java 7 语言规范很早就指出 本规范没有详细描述反射 我只是想知道 反射在Java中是如何实现的 我不是问它是如何使用的 我知道可能没有我正在寻找的具体答案 但任何信息将不胜感激 我在 Stackoverflow 上发现了这个 关于 C
  • 如何在 Play java 中创建数据库线程池并使用该池进行数据库查询

    我目前正在使用 play java 并使用默认线程池进行数据库查询 但了解使用数据库线程池进行数据库查询可以使我的系统更加高效 目前我的代码是 import play libs Akka import scala concurrent Ex
  • 如何找到给定字符串的最长重复子串

    我是java新手 我被分配寻找字符串的最长子字符串 我在网上研究 似乎解决这个问题的好方法是实现后缀树 请告诉我如何做到这一点或者您是否有任何其他解决方案 请记住 这应该是在 Java 知识水平较低的情况下完成的 提前致谢 附 测试仪字符串
  • 使用 Android 发送 HTTP Post 请求

    我一直在尝试从 SO 和其他网站上的大量示例中学习 但我无法弄清楚为什么我编写的示例不起作用 我正在构建一个小型概念验证应用程序 它可以识别语音并将其 文本 作为 POST 请求发送到 node js 服务器 我已确认语音识别有效 并且服务
  • 在 HTTPResponse Android 中跟踪重定向

    我需要遵循 HTTPost 给我的重定向 当我发出 HTTP post 并尝试读取响应时 我得到重定向页面 html 我怎样才能解决这个问题 代码 public void parseDoc final HttpParams params n
  • JAXb、Hibernate 和 beans

    目前我正在开发一个使用 Spring Web 服务 hibernate 和 JAXb 的项目 1 我已经使用IDE hibernate代码生成 生成了hibernate bean 2 另外 我已经使用maven编译器生成了jaxb bean
  • 加速代码 - 3D 数组

    我正在尝试提高我编写的一些代码的速度 我想知道从 3d 整数数组访问数据的效率如何 我有一个数组 int cube new int 10 10 10 我用价值观填充其中 然后我访问这些值数千次 我想知道 由于理论上所有 3d 数组都存储在内
  • Spark 1.3.1 上的 Apache Phoenix(4.3.1 和 4.4.0-HBase-0.98)ClassNotFoundException

    我正在尝试通过 Spark 连接到 Phoenix 并且在通过 JDBC 驱动程序打开连接时不断收到以下异常 为简洁起见 下面是完整的堆栈跟踪 Caused by java lang ClassNotFoundException org a
  • 如何在PreferenceActivity中添加工具栏

    我已经使用首选项创建了应用程序设置 但我注意到 我的 PreferenceActivity 中没有工具栏 如何将工具栏添加到我的 PreferenceActivity 中 My code 我的 pref xml
  • 如何为俚语和表情符号构建正则表达式 (regex)

    我需要构建一个正则表达式来匹配俚语 即 lol lmao imo 等 和表情符号 即 P 等 我按照以下示例进行操作http www coderanch com t 497238 java java Regular Expression D
  • 使用Caliper时如何指定命令行?

    我发现 Google 的微型基准测试项目 Caliper 非常有趣 但文档仍然 除了一些示例 完全不存在 我有两种不同的情况 需要影响 JVM Caliper 启动的命令行 我需要设置一些固定 最好在几个固定值之间交替 D 参数 我需要指定
  • 加密 JBoss 配置中的敏感信息

    JBoss 中的标准数据源配置要求数据库用户的用户名和密码位于 xxx ds xml 文件中 如果我将数据源定义为 c3p0 mbean 我会遇到同样的问题 是否有标准方法来加密用户和密码 保存密钥的好地方是什么 这当然也与 tomcat
  • 如何在 javadoc 中使用“<”和“>”而不进行格式化?

    如果我写
  • 如何从终端运行处理应用程序

    我目前正在使用加工 http processing org对于一个小项目 但是我不喜欢它附带的文本编辑器 我使用 vim 编写所有代码 我找到了 pde 文件的位置 并且我一直在从 vim 中编辑它们 然后重新打开它们并运行它们 重新加载脚
  • 声明的包“”与预期的包不匹配

    我可以编译并运行我的代码 但 VSCode 中始终显示错误 早些时候有一个弹出窗口 我不记得是什么了 我点击了 全局应用 从那以后一直是这样 Output is there but so is the error The declared
  • 获取 JVM 上所有引导类的列表?

    有一种方法叫做findBootstrapClass对于一个类加载器 如果它是引导的 则返回一个类 有没有办法找到类已经加载了 您可以尝试首先通过例如获取引导类加载器呼叫 ClassLoader bootstrapLoader ClassLo
  • 编译器抱怨“缺少返回语句”,即使不可能达到缺少返回语句的条件

    在下面的方法中 编译器抱怨缺少退货声明即使该方法只有一条路径 并且它包含一个return陈述 抑制错误需要另一个return陈述 public int foo if true return 5 鉴于Java编译器可以识别无限循环 https

随机推荐

  • C++的float类型数比较问题

    2020 8 17更新了一下 看到了两个float数比较的 不是0值 也加进了最末尾 相等比较 之前刷题做到一道题 看到题解很奇怪 计算一个数字的立方根 getCubeRoot double input 题解采用了二分法 但比较时并不是用直
  • 将多个HEX文件合并成一个HEX文件通过KEIL进行烧录

    首先这多个HEX文件自己已经偏移好了 hex文件已经记录了偏移的地址信息 用记事本打开第一个hex文件 test1 hex 020000040000FA 文件头记录 1000000018F09FE518F09FE518F09FE518F09
  • Docker安装以及运行第一个HelloWorld

    在安装Docker之前我们先来了解一下什么是Docker 观察Docker图标 其实很形象的解释了什么是Docker 在没有使用集装箱的情况下 我们需要考虑不同形状 尺寸的货物怎么安放 货物与货物之间是否能堆叠 这无疑是很繁琐的事情 现在有
  • 005 快排qsort库函数的用法——“C”

    文章目录 前言 一 什么是qsort快排函数 qsort的参数分析 二 使用步骤 前言 Reference C Reference cplusplus com 可在此网站查阅相关函数信息 提示 以下是本篇文章正文内容 下面案例可供参考 一
  • Reachability(判断网络是否连接)

    类似于一个网络状况的探针 NSNotificationCenter defaultCenter addObserver self selector selector reachabilityChanged name kReachabilit
  • 【ROS】学习之日志(log)消息

    参考 ROS学习之日志消息 ROS中的日志 log 消息 ROS日志级别控制 ROS日志 log 系统 通过显示进程的运行状态是好的习惯 但需要确定这样做不会影响到软件的运行效率和输出的清晰度 ROS 日志 log 系统的功能是让程序生成一
  • 傻瓜式阿里云部署java web项目步骤

    写在前面 本傻瓜式步骤适用于阿里云服务器是Windows Server 2008操作系统 一 阿里云操作步骤 1 首先提前准备好阿里云账号和密码 访问地址 阿里云 2 登录后进入首页 点击云服务器ECS 如图 3 进入云服务器ECS 点击实
  • java比较器Comparable接口和Comaprator接口

    java的比较器有两类 分别是Comparable接口和Comparator接口 在为对象数组进行排序时 比较器的作用非常明显 首先来讲解Comparable接口 让需要进行排序的对象实现Comparable接口 重写其中的compareT
  • MySQL - 普通索引

    创建和查看索引 创建索引是指在某个表的一列或多列上建立一个索引 以便提高对表的访问速度 创建索引有3种方式 分别是创建表的时候创建索引 在已经存在的表上创建索引和使用ALTER TABLE语句来创建索引 本节将根据具体的索引分类详细的讲解这
  • (最简单)使用 reset-css 初始化浏览器css样式

    目录 背景 实现 步骤一 步骤二 背景 在我们的项目初始化搭建过程中会遇到这种情况 需要我们自己清除css默认样式 但是我们不可能一周都有那个清除默认css样式的文件 实现 步骤一 在终端使用 npm 引用 reset css npm i
  • mysql linux redhat_RedHat Linux 6 下 MySQL 8.0.11安装配置

    我这里是RHEL6 5的系统 因此选择RedHat 6 x86 64bit操作系统 下载第一个RPM Bundle即可 MySQL 8 0 11 1 el6 x86 64 rpm bundle tar 目前MySQL8 0 11社区版提供了
  • C++ system()函数的常用用法 (史上最详细)

    目录 一 推荐 1 system pause 2 system color 3 system title 4 system cls 二 文件操作 1 system start 2 system del 3 system copy A B 4
  • ReactDOM.render(...) 渲染方法

    React代码的书写格式和以前的JS有很大的不同 下面通过对这段代码进行分析了解一下他 以前使用Javascript定义一个变量用var ES6加入了const关键字 用来定义一个常量 const div document createEl
  • 【Kotlin】快速理解协程与挂起

    本文不介绍协程和挂起的基础用法 如需要请移步其他博客 本文主要讲解 kotlin中的协程是什么 协程的作用 挂起是什么 挂起的作用 本文全程尽量白话 使得协程和挂起理解起来更容易 小故事or小事故 之前面试的时候 有个面试官问了我一个问题
  • 语义分割任务中的Transformer

    文章目录 语义分割中的Transformer 1 Patch based Transformer 1 1 SETR 1 2 Segformer 2 Query Based Transformer 2 1 Transformer with O
  • vscode更新时报错怎么办?

    请用管理员权限 打开试试
  • vue2组件系列:Slider 滑块

    我所接触到的Slider滑块应用的场景 主要有图片的放大缩小 调节声音的大小 不知道小伙伴们的应用场景都有哪些呢 欢迎在文章下方留言讨论哈 准备工作 创建一个页面 Slider vue在router js里配置 Slider页面的路由 pa
  • js实现文字上下滚动(到底回到顶部重复滚动)

    直接贴代码 div class scroll ul li 开始 li li 用户 li ul div
  • mybatis传参1 - 传入map类型的参数

    本章将介绍mybatis如何传入参数 传入map类型的参数 我们需要定义如下三部分 目录 1 接口部分 2 mapper文件部分 3 测试类部分 4 测试本次结果 4 1跑出来的值 4 2mysql值 1 接口部分 定义一个接口返回User
  • 【轩说Java】JavaSE知识点难点汇总

    文章目录 JAVA SE 抽象类与其实现子类 抽象类与其子类如下 静态函数不存在重写和多态的概念 重写的要求 接口interface 类 接口的实现 一对多 接口 接口的继承 一对多 接口中的变量和函数 接口作为一种标签 堆 栈 静态方法区