JAVA基础

本文介绍了Java中的抽象类、接口、抽象方法的区别,重载和重写的概念,基本类型和引用类型的特性,以及HashMap、String类的特性和操作,同时还涵盖了深浅拷贝和反射,以及线程的创建方式和Runnable/Callable接口的应用。
摘要由CSDN通过智能技术生成
 抽象类
class A {
    public A() {

    }

    public void show() {

    }
}

 一个普通类的基础上增加抽象方法,那么类便成了抽象类

abstract class A {
    public A() {

    }

    public void show() {

    }

    public abstract void  s1();

    public abstract void  s2();
}

子类必须实现父类的所有抽象方法,不然也定义为抽象类

abstract class Achild extends A {
    @Override
    public void s1() {

    }
}
class Achild extends A {
    @Override
    public void s1() {
        System.out.println("具体方法1");
    }

    @Override
    public void s2() {
        System.out.println("具体方法2");
    }

    public static void main(String[] args) {
        A a = new Achild();
        a.s1();
        a.s2();
    }
}

抽象类无法实例化,而必须通过子类来实例化

final abstract class A {}

抽象类有子类继承,因此无法使用final修饰

 接口
interface Inter {
    
}

接口中只能有全局变量和抽象方法,静态方法,默认方法

abstract class AbstractClass implements Inter{
    public static final int x = 0;//接口中只能定义全局常量,并且需要初始化

    //接口中的方法默认带有public
    public abstract void sum();//普通方法默认带有abstract

    public static void s(){
        System.out.println("静态方法");
    }

    public default void d() {
        System.out.println("默认方法");
    }
}

抽象类中可以不实现接口方法,但是子类需要全部实现上面方法

class Abstract extends AbstractClass{

    @Override
    public void show() {
        System.out.println("我是子类方法show");
    }

    @Override
    public void sum() {
        System.out.println("我是实现类方法sum");
    }
}

区别

不同点:

一个有构造方法,一个没有;

一个可以有普通方法和代码块,一个只能有抽象方法,静态方法和默认方法;

一个可以有常量和变量,一个只能有全局常量,并且需要赋值

类只能继承一个,但是接口能继承多个

相同点:

都不能被实例化,子类都需要实现或继承接口或者父类的所有抽象方法

重载与重写
public class overload {
    public void write() {
        System.out.println("我写了一本《山茶花》");
    }

    public void write(String book) {
        System.out.println("我写了一本" + book);
    }
}

class override extends overload {
    @Override
    public void write() {
        System.out.println("我写了一本《山楂树》");
    }
    
    @Override
    public void write(String book) {
        System.out.println("我写了一本《" + book + "》");
    }

    public static void main(String[] args) {
        override child = new override();
        child.write();
        child.write("山楂树");
    }
}

重载发生在同一个类中,方法名相同、参数类型和个数、访问修饰符可以不同,调用的时候根据函数的参数来区别不同的函数

重写发生在子类中,方法名、参数列表、返回类型都相同,只是函数的实现体不一样。

被重写的方法不能比父类有更严格的权限,声明异常范围要小于父类方法

final和private修饰的方法不可重写

基本类型和引用类型
  • 基本类型:就是值类型,即在变量所对应的内存区域存储的是值
  • 引用类型:就是地址类型,在变量对应区域存储的是地址
  • 存储方式:基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,成员变量存放在 Java 虚拟机的堆中,静态变量也存放在堆中,但属于类不属于对象。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中
  • 默认值:成员变量包装类型不赋值就是 null ,而基本类型有默认值且不是 null
  • 因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址,所以要用equals()方法来比较
  • Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能,范围内的对象重复使用会使用同一个缓存对象,超出对应范围才会去创建新的对象
  • Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True or False,Float,Double 不缓存
  •         Integer i1 = 127;
            Integer i2 = 127;
            Integer i3 = 128;
            Integer i4 = 128;
            System.out.println(i1 == i2);//true
            System.out.println(i3 == i4);//false
==和equals

==:对于基本类型,比较的是值是否相等;对于引用类型,比较的是地址的值是否相等

       String str = new String("z");
       String str2 = new String("z");
       String str3 = "z";
       String str4 = "z";
       System.out.println(str3 == str4);//true 同一个常量池
       System.out.println(str == str2);//flase
       System.out.println(str == str3);//flase

equals:比较的是对象是否相等,传入的是Object类所以不能比较基本类型

hashcode:比较对象的hash值是否相等

在比较两个对象是否相等时,一般先比较hashcode再比较equals,如果hashcode不相同那么对象必然不相同,如果相同就进一步调用equals()方法判断

       String str = new String("z");
       String str2 = new String("z");
       String str3 = "z";
       String str4 = "z";
       System.out.println(str3.equals(str4));//true
       System.out.println(str.equals(str2));//true
       System.out.println(str.equals(str3));//true
    public boolean equals(Object anObject) {
        if (this == anObject) {	//比较地址是否相等
            return true;
        }//判断是否是String类型,如果不是则false
        if (anObject instanceof String) {	
        	//转换为本地变量
            String anotherString = (String)anObject;
            //计算字符长度,注意Java字符串末尾没有\0,因为对象都内置的有length方法,所以不需要计算长度。
            int n = value.length;
            //如果长度不等,false
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {//核心代码,逐个字符地比较
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
HashMap

第一次调用 put 方法时创建一个长度是16 的数组

结合数组长度n采用位运算(n-1)&(h^(h>>>16))计算出向 Node 数组中存储数据的空间的索引值

如果计算出的索引空间没有数据,则直接将<key,value>存储到数组中

如果有数据,此时发生哈希碰撞,调用equals() 方法比较两个key是否相等

相等:将后添加的数据的 value 覆盖之前的 value

不相等:继续向下和其他的数据的 key 进行比较,如果都不相等则划出一个结点存储数据

如果结点长度即链表长度大于阈值 8 并且数组长度大于 64 则将链表变为红黑树

当超出阈值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的 2 倍,并将原有的数据复制过来

size 表示 HashMap 中键值对的实时数量,注意这个不等于数组的长度。
threshold(临界值)= capacity(容量)* loadFactor(负载因子)。这个值是当前已占用数组长度的最大值。size 超过这个值就重新 resize(扩容),扩容后的 HashMap 容量是之前容量的两倍。默认的临界值是12 = 16 * 0.75

String
public final class String{
    private final char value[];
}
  • final 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象
  • String是不可变的字符串,因为String类内部的char字符串数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法,同时String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
  • “+”和“+=”是专门为 String 类重载过的运算符,字符串对象通过“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。
  • String str3 = new StringBuilder().append(str1).append(str2).toString();
    
  • 赋值时不会重新赋值而是生成一个新的对象指向新的值,原对象被回收
  • 在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象,如果直接使用 StringBuilder 对象进行字符串拼接的话,就不会存在这个问题了。
  • String 中的 equals 方法是被重写过的,比较的是 String 字符串的值是否相等。 Objectequals 方法是比较的对象的内存地址。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
    char[] value;
}
  • StringBuilderStringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 finalprivate 关键字修饰,并且 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。
  • StringBuffer 对方法加了同步锁synchronized,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
  • StringBuffer对象是一个字符序列可变的字符串,它没有重新生成一个对象,而且在原来的对象中可以连接新的字符串,初始16个字符,扩容时2倍+2
深拷贝和浅拷贝

本质的区别是拷贝出来的对象的地址是否和原对象一样,也就是地址的复制还是值的复制的区别。

浅拷贝时,拷贝出来的新对象的地址和原对象是不一样的,但是新对象里面的可变元素的地址和原对象里的可变元素的地址是相同的

深拷贝的新对象的地址和里面的元素的地址跟原对象都不一样

反射

反射是通过获取类的class对象,然后动态的获取到这个类的内部结构,动态的去操作这个类的属性和方法

package streamof;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    private String name;
    private int age;
    private String code;

    private Person(String code) {
        this.code = code;
    }

    public void eat() {
        System.out.println("我是:小尘,我15岁");

    }
    public void eat(String name, int age) {
        System.out.println("我是:" + name + ",我" + age + "岁");

    }

    private void eat(String code) {
        System.out.println("我是星尘,我的编号是:" + code);
    }

}

class reflect {
    @SneakyThrows
    public static void main(String[] args) {
        Class clazz = Class.forName("streamof.Person");

        //获取本类定义的成员变量,包括私有,但不包括继承的变量
        Field[] fields = clazz.getDeclaredFields();
        for (Field f : fields) {
            System.out.println(f.getName() + ":" + f.getType());
        }

        //获取所有可见的方法,包括继承的方法
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            System.out.println(method.getName()+":"+Arrays.toString(parameterTypes));
        }

        //获取所有可见的构造
        Constructor<?>[] cs = clazz.getConstructors();
        for(Constructor c : cs){
            Class[] pt = c.getParameterTypes();//通过本轮遍历到的构造函数对象获取构造函数的参数类型
            System.out.println(c.getName() + ":" + Arrays.toString(pt));
        }

        //创建无参构造实例
        Object o = clazz.newInstance();
        System.out.println(o);
        //通过有参构造器创建实例
        Object o1 = clazz.getConstructor(String.class, int.class, String.class).newInstance("小王", 12, "akd");
        System.out.println(o1);
        //通过私有构造器创建实例
        Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class);
        declaredConstructor.setAccessible(true);
        Object o2 = declaredConstructor.newInstance("a123");
        System.out.println(o2);

        //调用无参数方法
        Method eat = clazz.getMethod("eat");
        eat.invoke(o1);
        //调用有参数方法
        Method eat1 = clazz.getMethod("eat", String.class, int.class);
        eat1.invoke(o, "星尘", 15);//实例无论是o,o1,还是o2,都是无参数的o
        //调用私有方法,注意需要用Declareed
        Method eat2 = clazz.getDeclaredMethod("eat", String.class);
        eat2.setAccessible(true);
        eat2.invoke(o, "9999ff");

        //动态修改对象的私有属性name
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(o2, "星尘");
        System.out.println(o2);
        //动态获取对象的name
        System.out.println(name.get(o1));

    }
}

反射功能非常强大,并且非常有用,比如:

  • 获取任意类的名称、package信息、所有属性、方法、注解、类型、类加载器等
  • 获取任意对象的属性,并且能改变对象的属性
  • 调用任意对象的方法
  • 判断任意一个对象所属的类
  • 实例化任意一个类的对象
  • 通过反射我们可以实现动态装配,降低代码的耦合度,动态代理等。

反射作用

  1. 运行时获取对象信息:通过反射,我们可以在运行时获取类的相关信息,比如类名、构造方法、成员变量、成员方法等,这对于某些框架、工具或者动态语言来说是非常有用的。
  2. 动态创建对象:通过反射,我们可以在运行时根据类名动态地创建对象。这使得我们可以更灵活地设计程序,比如根据配置文件中的类名动态创建对象。
  3. 动态调用方法:通过反射,我们可以在运行时动态调用对象的方法。这使得我们可以根据实际需要来动态选择需要调用的方法,而不是在编写代码时就静态固定下来了。
  4. 动态访问属性:通过反射,我们可以在运行时动态访问对象的属性。这使得我们可以更加灵活地设计程序,比如根据用户的输入来动态访问对象的属性。
线程

Thread类实现了Runnable接口并重写了接口的抽象方法run()

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
class Thread implements Runnable {

    private Runnable target;

    public Thread(Runnable target) {
        this.target = target;
    }

    @Override
    public void run() {
        target.run();
    }

    public void start() {
        start0();
    }

    public void start0() {
        run();
    }
}

使得有参构造传入一个Runnable实例并执行该实例重写的run()方法

创建线程的三种方法

  • 继承Thread类,重写run()方法,用start()调用
class Cat extends Thread {
    public void run() {
        int time = 0;
        while (true) {
            System.out.println("我是cat:" + Cat.currentThread());
            try {
//                System.out.println(++time);
                Thread.sleep(1000);
                time++;
            } catch (InterruptedException e) {

            }
            if (time == 10) {
                break;
            }
        }

    }

    public static void main(String[] args) {//启动main线程
        Cat cat = new Cat();
        cat.start();//start新开了一个线程执行
        cat.run();//普通的run方法代码,是main线程在执行
    }
}
  • 实现Runnable接口并重写run()方法,实例化一个对象,new Thread(这个实例)并调用start()
class Dog implements Runnable {
    int count = 0;
    @Override
    public void run() {
        while (true) {
            System.out.println(++count);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) {
        Dog dog = new Dog();
        //dog.start()不能使用
        Thread thread = new Thread(dog);
        thread.start();
    }
}
  • 实现Callable接口,Callable接口与Runnable接口类似,但是有返回值
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}
  • 重写call()方法,实例化,用FutureTask包装然后调用start()方法
public class callable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int i;
        for ( i = 0; i < 10; i++) {
            System.out.println(i);
        }
        return i;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        callable c = new callable();
        FutureTask<Integer> futureTask = new FutureTask<>(c);
        new Thread(futureTask,"有返回值的线程").start();
        System.out.println(futureTask.get());
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值