java笔记(十二)重新理解java基本特性

本文介绍了Java编程中的基础概念,包括数据封装,类的继承,抽象类和接口的使用,以及多态的概念。详细讲解了抽象方法、接口的默认方法、类的构造函数、方法重写与重载,以及String类的特性。同时,讨论了对象的创建、数据类型、运算规则、Object类的方法,如equals()和hashCode(),并探讨了反射机制和异常处理。此外,还提到了注解和泛型的应用。
摘要由CSDN通过智能技术生成

2023.1.10 今年26岁了 深感对未来之恐惧

一、封装

数据被保护在类的内部,尽可能隐藏内部的细节,只有一些对外接口来与外部发生联系

public class Person {

    private String name;
    private int gender;
    private int age;

    public String getName() {
        return name;
    }

    public String getGender() {
        return gender == 0 ? "man" : "woman";
    }

    public void work() {
        if (18 <= age && age <= 50) {
            System.out.println(name + " is working very hard!");
        } else {
            System.out.println(name + " can't work any more!");
        }
    }
}

二、继承

一个类可以继承自一个父类 获得非private的属性和方法 (public protected)

访问权限

private protected public 不加访问修饰符 表示包级可见
可以对类或者类中的成员(字段以及方法)加上访问修饰符

** 在类的内部提供接口来访问类的成员

抽象类和接口

1、抽象类
抽象类和抽象方法使用abstract关键字进行声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中

抽象类的特点:不能被实例化,只能先继承抽象类然后实例化

public abstract class classA {
    private int y;
    
    public abstract void func1();
    
    public void func2() {
        System.out.println("func2");
    }
}

class sampleClass extends classA{

    @Override
    public void func1() {
        System.out.println("func1");
    }
}

2、接口
接口的成员默认都是public的 并且不允许定义为private protected

public interface Interface {
	void func1();
	default void func2() {
		System.out.println("func2");
	}
	int x = 123;
	public int z = 0;
}

public class InterfaceImpl implements Interface{
	@Override
	public void func1() {
		System.out.println("func1")
	}
}

3、比较

  • 一个类可以实现多个接口 但不能继承多个抽象类
  • 接口的字段只能是static或者final的 抽象类的字段没有这个限制
  • 接口的成员只能是public的

4、用法
使用接口:

  • 需要让不相关的类都实现一个方法,如每个类都可以实现CompareTo()方法
  • 需要使用多重继承

使用抽象类

  • 需要在几个相关的类中共享代码
  • 需要能控制继承来的成员的访问权限,而不是都为public
  • 需要继承非静态和非常量字段

super

  • 访问父类的构造函数:使用super()函数访问父类的构造函数,从而委托父类完成一些初始化的工作
  • 访问父类的成员:如果子类重写了父类中的某个方法的实现,可以通过使用super关键字来引用父类的方法实现
    父类:
public class Father {
    protected int x;
    protected int y;

    public Father(int x, int y) {
        // 构造函数
        this.x = x;
        this.y = y;
    }

    public void func() {
        System.out.println("Father.func");
    }
}

子类:

public class Son extends Father {
    private int z;

    public Son(int x, int y, int z) {
        super(x,y);
        this.z = z;
    }

    @Override
    public void func() {
        super.func();
        System.out.println("Son.func()");
    }
}

主函数:

public class main {
    public static void main(String[] args) {
        Father father = new Father(1,2);
        Son son = new Son(3,4,5);
        father.func();
        son.func();
    }
}

运行结果:
运行结果

重写和重载

1、重写 Override
子类实现一个声明和父类相同的方法,需要满足里氏替换原则

  • 子类方法的访问权限必须大于父类方法
  • 子类方法的返回类型必须是父类方法返回类型或者是其子类

2、重载 Overload
存在于同一个类中 方法名与已经存在的方法名相同 但参数类型 个数 顺序至少一个不同

三、多态

编译时多态:方法重载
运行时多态:程序中定义的对象引用所指向的具体类型在运行期间才确定(比如泛型)

public class Instrument {
    public void play() {
        System.out.println("Instrument is playing...");
    }
}

public class Wind extends Instrument {
    public void play() {
        System.out.println("Wind is playing...");
    }
}

public class Percussion extends Instrument {
    public void play() {
        System.out.println("Percussion is playing...");
    }
}

public class Music {
    public static void main(String[] args) {
        List<Instrument> instruments = new ArrayList<>();
        instruments.add(new Wind());
        instruments.add(new Percussion());
        for(Instrument instrument : instruments) {
            instrument.play();
        }
    }
}

上面的代码 从静态上看 Instument对象应该调用的是自己的play方法 其实运行中调用的是子类的静态方法

四、数据类型

(1)基本类型
基本类型共8个:
boolean/1
byte/8
char/16
short/16
int/32
float/32
long/64
double/64

new Integer(1) — 创建新对象
Integer.valueOf(1) ---- 使用缓存池 得到同一个对象的引用
缓存池大小为-128 ~ 127 (java8)
在这个范围内 使用自动装箱将自动调用valueOf方法

Integer m = 123;
Integer n = 123;
System.out.println(m == n); // true

(2)String
String是不可变的:这句话经常看到,是什么意思呢?
String内部使用char数组来存储数据 并且声明为final,且String内部不提供改变value数组的方法

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

不可变有什么好处:
1、缓存hash值
String类型的hash值不变
2、String pool
如果一个String对象被创建过了 就可以直接从String pool中引用
3、安全性
String是最常用作参数的类型 不可变性保证了传输过程中的安全
4、线程安全
String可以在多个线程中安全使用

StringBuilder可变 非线程安全
StringBuffer可变 且线程安全

String.intern()
intern()方法保证两个变量引用的是同一个对象

String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2);           // false
String s3 = s1.intern();
System.out.println(s1.intern() == s3);  // true

如果使用String s1 = “aaa” 则它已经自动被加入到了String pool中

五、运算

参数传递 – 值传递而非引用传递

将参数传入一个方法时,本质是传入了对象的地址
因此在方法中,改变指针引用的对象也将指向另一个完全不同的对象
如果在方法中改变对象的字段/属性,则会发生变化

float & double

1.1 这个字面量是double类型 因此直接赋给float是错的

1.1f才是float型

隐式类型转换

字面量1是int类型,比short的精度更高,因此不能隐式地将int下转为short类型

但是在使用+=时,可以进行隐式转换

switch

条件判断语句的基本写法

String s = "a";
switch (s) {
	case "a":
		do....
		break;
	case "b":
		do....
		break
	default:
		break;
	}

六、Object通用方法

equals()

自反性 对称性 传递性 一致性

equals和== 对于基本类型 ==判断两个值是否相等,基本类型没有equals方法
对于引用类型 == 判断两个变量的引用是否是同一个对象 equals判断是否等价

实现上 需要判断每个关键域是否相等 以及对Object转型

hashCode()

返回散列值

toString()

默认返回ToStringExample@4554617c这种格式

clone()

1、cloneable
clone()是Object的protected方法 如果一个类不显示重写clone() 其他类就不能直接调用该实例的clone()

重写了clone方法后 还需要实现cloneable接口才能够使用

2、浅拷贝
拷贝对象和原始对象的引用类型引用的是同一个对象

3、深拷贝
引用非同一个对象 需要自己实现

使用clone()有很多风险 更好的实践是通过拷贝构造函数或者拷贝工厂来实现

七、关键字

final

1、数据

声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量

  • 对于基本类型,final使数值不变
  • 对于引用类型,final使引用不变,也就不能引用其他对象,但是被引用的对象本身可以修改

2、方法
声明方法表示不能被子类重写

private方法隐式被指定为final 如果在子类中定义的方法和基类中的一个private方法签名相同 此时子类的方法不是重写基类方法 而是在子类中定义了一个新方法

3、类
声明类不允许被继承

static

1、静态变量

  • 静态变量:又称为类变量,也就是说这个变量是属于类的,类的所有实例都共享静态变量,可以直接通过类名来访问它;静态变量在内存中只存在一份。
  • 实例变量:每产生一个实例对象就会产生实例变量

2、静态方法
静态方法在类加载时就存在,它不能是抽象方法
注意:静态方法只能访问所属类的静态字段和静态方法,且不能有this和super关键字

3、静态语句块
静态语句块在类初始化时运行一次

4、静态内部类
非静态内部类依赖于外部类的实例,而静态内部类不需要

注意:初始化阶段 静态变量、静态语句块优先于实例变量和普通语句块

八、反射

每个类都有一个Class对象,编译一个新类时,会产生一个同名的.class文件

每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。

public final class Class {
	private Class() {}}

一个Class实例包含了该class的所有信息

在这里插入图片描述

如果能获取到Class实例,那么我们也就得到了class对应的所有信息,这个获取Class实例的过程就是反射

如果获取Class实例?

  • 通过Class的静态变量获取
 Class cls = String.class
  • 使用getClass方法获取
String s = "hello";
Class cls = s.getClass();
  • 如果知道一个class的完整类名,通过静态方法class.forName()获取
Class cls = Class.forName("java.lang.String")

注意 Class实例 在JVM中是唯一的,可以用==进行比较

可以使用Class实例来创建对应类型的实例,但注意只能调用public的无参构造方法

Class cls = String.class;
String s = (String) cls.newInstance();

如何获取有参数的构造器方法:

Solution solution = Solution.class.getConstructor(classes).newInstance("hello1", 10);
Solution solution2 = solution.getClass().getDeclaredConstructor(String.class).newInstance("hello2");

getConstructorgetDeclaredConstructor的区别在于,getConstructor只会返回访问权限是public的构造器,而getDeclaredConstructor会返回所有的

JVM具有动态加载class的特性,当需要用到class时才加载到内存。

动态加载的特性使得我们可以在运行期根据条件加载不同的实现类,如Commons Logging:

// Commons Logging优先使用Log4j:
LogFactory factory = null;
if (isClassPresent("org.apache.logging.log4j.Logger")) {
    factory = createLog4j();
} else {
    factory = createJdkLog();
}

boolean isClassPresent(String name) {
    try {
        Class.forName(name);
        return true;
    } catch (Exception e) {
        return false;
    }
}

如何通过Class实例获取字段信息

  • Field getField(name) 根据字段名称获取某个public的field(包括父类)
  • Field getDeclaredField(name) 根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields() 获取所有public的field(包括父类)
  • Field[] getDeclaredFields() 获取当前类的所有field(不包括父类)

Field 对象包含了字段的所有信息

  • getName() 字段名称
  • getType() 字段类型 是一个Class实例
  • getModifiers() 修饰符 是int型 不同bit表示不同含义

获取字段的值

拿到Field实例只是第一步 如何获取实例某个字段的值呢

public static void main(Stirng[] args) throws Exception {
	Object p = new Person("Xiao Ming");
	Class c = p.getClass();
	Field f = c.getDeclaredField("name");
	Object vavlue = f.get(p);
	System.out.println(value);
}

先拿到Field实例,然后传入实例,得到字段的值

但上面的代码是会报错的 如果name是一个private字段,那Main类是没有办法直接访问private字段的,除非把这个字段替换成public

或者

f.setAccessible(true)

在获取之前执行,可以无视字段是否是public,允许访问

设置字段值

Field f = c.getDeclaredField("name");
f.set(p, "小明");

调用方法

通过Class实例可以获得所有Method信息

Method getMethod(name,Class)
Method getDeclaredMethod(name,Class)
Method[] getMethods()
Method[] getDeclaredMethods()

Method实例包含了一个方法的所有信息:

  • getName
  • getReturnType
  • getParameterTypes
  • getModifiers
public class Main {
    public static void main(String[] args) throws Exception {
        // String对象:
        String s = "Hello world";
        // 获取String substring(int)方法,参数为int:
        Method m = String.class.getMethod("substring", int.class);
        // 在s对象上调用该方法并获取结果:
        String r = (String) m.invoke(s, 6);
        // 打印调用结果:
        System.out.println(r);
    }
}

Method调用invoke方法就是调用了该方法,invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则报错

如果获取到的Method是一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null

public class Main {
    public static void main(String[] args) throws Exception {
        // 获取Integer.parseInt(String)方法,参数为String:
        Method m = Integer.class.getMethod("parseInt", String.class);
        // 调用该静态方法并获取结果:
        Integer n = (Integer) m.invoke(null, "12345");
        // 打印调用结果:
        System.out.println(n);
    }
}

调用非public方法时,同样需要Method.setAccessible(true)才能调用

如果获取父类的Method实例,再作用于子类实例,会调用谁的方法?

Method h = Person.class.getMethod("hello");
// 对Student实例调用hello方法:
h.invoke(new Student());

这里调用的是student的方法,即Method实例也遵循多态原则

调用构造方法

可以通过反射来创建新的实例,但是newInstance只能调用public的无参构造方法

Person p = Person.class.newInstance();

通过Constructor对象可以调用任意构造方法

public class Main {
    public static void main(String[] args) throws Exception {
        // 获取构造方法Integer(int):
        Constructor cons1 = Integer.class.getConstructor(int.class);
        // 调用构造方法:
        Integer n1 = (Integer) cons1.newInstance(123);
        System.out.println(n1);

        // 获取构造方法Integer(String)
        Constructor cons2 = Integer.class.getConstructor(String.class);
        Integer n2 = (Integer) cons2.newInstance("456");
        System.out.println(n2);
    }
}

通过Class实例获取Constructor的方法如下

getConstructor(Class..):获取某个publicConstructor
getDeclaredConstructor(Class..):获取某个Constructor
getConstructors():获取所有publicConstructor
getDeclaredConstructors():获取所有Constructor

调用非public的Constructor时,需要先设置setAccessible(true)

获取继承关系
当我们获取到某个Class对象时,实际上就获取到了一个类的类型

Class cls = String.class; // 获取到String的Class

还可以用实例的getClass()方法获取

String s = ""
Class cls = s.getClass()

通过Class.forName()

Class s = Class.forName("java.lang.String")

这几种方式获取的Class实例是同一个

如何获取父类的Class?

public class Main {
    public static void main(String[] args) throws Exception {
        Class i = Integer.class;
        Class n = i.getSuperclass();
        System.out.println(n);
        Class o = n.getSuperclass();
        System.out.println(o);
        System.out.println(o.getSuperclass());
    }
}

如何获取Interface?

public class Main {
    public static void main(String[] args) throws Exception {
        Class s = Integer.class;
        Class[] is = s.getInterfaces();
        for (Class i : is) {
            System.out.println(i);
        }
    }
}

打印出的是Integer类实现的所有接口,且只打印当前类实现的接口,不包括其父类的接口

注: getSuperClass是对类使用的,获取interface的父接口应该使用getInterfaces()

继承关系
判断一个实例是否是某个类型时,一般使用instanceOf

Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Double; // false
boolean isInteger = n instanceof Integer; // true

如果是两个Class实例,要判断向上转型是否成立,可以使用isAssignableFrom()

Integer.class.isAssignableFrom(Integer.class);  // true
Number.class.isAssignableFrom(Integer.class); // true
Integer.class.isAssignableFrom(Number.class); // false

动态代理

dynamic proxy

没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。

public class Main {
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if (method.getName().equals("morning")) {
                    System.out.println("Good morning, " + args[0]);
                }
                return null;
            }
        };
        Hello hello = (Hello) Proxy.newProxyInstance(
            Hello.class.getClassLoader(), // 传入ClassLoader
            new Class[] { Hello.class }, // 传入要实现的接口
            handler); // 传入处理调用方法的InvocationHandler
        hello.morning("Bob");
    }
}

interface Hello {
    void morning(String name);
}

动作如下:
1、定义一个 InvocationHandler实例
2、使用 Proxy.newProxyInstance创建实例,需要三个参数:
(1)ClassLoader,通常是接口类的ClassLoader
(2)需要实现的接口数组,至少需要传入一个接口
(3)用来处理接口方法调用的InvocationHandler实例
3、将返回的Object强制转换成接口

如何理解动态代理?其实就是生成了一个继承自Proxy的类,并且实现了传入的接口方法,这个类传入的是要实现的方法和参数,因此可以做到动态代理的效果

由于Method作为参数传入了,因此可以通过method.invoke调用这个传入的方法,当代理类实现的时候传入了实现了接口的某个实例对象时,就可以利用反射调用实例的方法了。

九、异常

Throwable可以用来表示任何可以作为异常抛出的分类,分为Error和Exception
其中Error用来表示JVM无法处理的错误
Exception分为两种:
1、受检异常:需要用try…catch…语句捕获并进行处理,并且可以从异常中恢复
2、非受检异常:例如除以0,此时无法恢复

java的异常是一个类

在这里插入图片描述

断言

assert x >=0

如果x满足断言的条件,就会抛出 AssertionError

注意 只能在开发和测试阶段启用断言

Log4j

log4j是一种非常流行的日志框架

在这里插入图片描述
SLF4J和logback
SLF4J是一个日志接口,Logback是一个日志的实现

logback配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
		</encoder>
	</appender>

	<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<encoder>
			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
			<charset>utf-8</charset>
		</encoder>
		<file>log/output.log</file>
		<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
			<fileNamePattern>log/output.log.%i</fileNamePattern>
		</rollingPolicy>
		<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
			<MaxFileSize>1MB</MaxFileSize>
		</triggeringPolicy>
	</appender>

	<root level="INFO">
		<appender-ref ref="CONSOLE" />
		<appender-ref ref="FILE" />
	</root>
</configuration>

十、泛型

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

十一、注解

java注解是附加在代码中的一些元信息,用于一些工具在编译运行时解析和使用,起到说明、配置的功能。

如何处理注解

1、判断某个注解是否存在于Class Field Method Constructor

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值