-
问题: 如果一个类,继承一个类 、实现多个接口后,该类的对象强转?
@Test
public void testABC(){
// 原始类
ABClass abClass = new ABClass();
// 原始类可强转为任意父类或接口。强转后,只能访问父类或接口的 api
AInterface aInterface = (AInterface)abClass;
aInterface.ma(); // 只能访问 AInterface 接口的 ma方法
BInterface bInterface = (BInterface)abClass;
bInterface.mb(); // 只能访问 BInterface 接口的 mb方法
CClass cClass = (CClass)abClass;
cClass.mc(); // 只能访问 CClass 接口的 mc方法
// 强转为某一个接口或者父类后,还可以强转为其他类型
BInterface azb = (BInterface) aInterface;
azb.mb();
CClass bzc = (CClass) azb;
bzc.mc();
AInterface cza = (AInterface) bzc;
cza.ma();
}
interface AInterface{
void ma();
}
interface BInterface{
void mb();
}
abstract class CClass{
protected void mc(){};
}
class ABClass extends CClass implements AInterface,BInterface{
@Override
public void ma() {
}
@Override
public void mb() {
}
}
ABClass 继承了 CClass 并 实现了 AInterface和BInterface,
此时,ABclass实例对象可以强转为任意类型。
-
抽象类 实现接口时,可以不用实现 接口中的 api
abstract class ABStractClass implements AInterface{
}
-
二进制位
四种组合
00
01
10
11
三种 按位 逻辑运算:
按位 与 & : 11 =》 1
按位 或 | :01/ 10/ 11 =》1
按位 异或 ^ :01/ 10 =》 1
或 | 包含 异或 ^
异或^ 是 或| 的 2种情况
因为 或| 运算 得到了 1,无法推断出 是 都为1 还是 有 0 和 1
所以,通过 异或^ 得到了1,推断的肯定是 0和1
异或 能判断出 两个值 是否相同
0^0=0
1^1=0
0^1=1
1^0=1
一个值 与 自身 异或运算,总是位 false 0
x^x=0
一个值 与 0 异或运算,还是本身
x^0=x
异或运算 可交换
x^y=y^x
异或运算 可结合
x^(y^z)=(x^y)^z
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
HashMap的这个方法,即用到了 位移,也用到了逻辑或运算。
表达的意思为:
将数据的最高1为与最高2位进行或
然后将最高1和2与最高3和4进行或
直到 将32位的整数,前16位与后16位进行或运算,以保证 所得的数据位 2的倍数
首位符号位:0 正数、1负数
以补码表示:补码=反码(原码除符号位按位取反)+1
负数补码一般都是:
1111xxx (数字的二进制取反+1)
如:
-2
1111 1110
2^n -1 表示 n 个 1,也就是 2的n次方 -1 的值,用二进制表示为 n个1
-
打印几个“a:”
public void test_(){
a:
for (int i=0;i<10;i++){
System.out.println("a:" + i);
for (int j=0;j<5;j++){
System.out.println("b"+j);
if (j==2){
break a;
}
}
}
}
a:0
b0
b1
b2
内层break 结束了整个循环
-
什么时候会出现哈希碰撞
HashMap 在计算 key 的hash值的方法中:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
可以看到,
如果key为null,则hash为0
否则,调用key的 hashCode 方法。
hashCode方法是Object的方法,是一个本地方法。也可以进行重写,重写的hashCode返回的hash值 很容易相同。这个时候就可能出现哈希碰撞了。
public native int hashCode();
如果发生了哈希碰撞,则会将元素放在该链表最后,如果链表过长,则转为红黑树。(what is 红黑树?)
-
关于环境变量
学java最开始的是配置环境变量,java中的环境变量需要配置两个:1 、path ; 2、classpath
1. path: 这个环境变量中配置的都是操作系统的可执行文件,也是我们执行 某一命令时,如 java -version ,操作系统 需要根据path配置的路径里去寻找这个命令所对应的可执行文件。
2. classpath:这个环境变量 是转属于 java的变量,java 在加载 类时,需要根据 classpath配置的路径中去加载所需要的类。
有一个问题,java是怎么使用这个classpath的呢?如何获取classpath环境变量配置的值呢?
java中有一个System 类,该类中提供了一个静态方法,Map<String,String> getenv(),只要调用 System.getenv();即可获取到当前操作系统所有的环境变量配置。按需获取就行了。
-
加载资源
java中加载资源,一般都是用ClassLoader.getResource(String path); 来加载的,这里的 path 需要注意的是,当前应用的 classpath,也就是说,如果你想加载一个静态资源,那么需要把他放在classpath下才行。
-
xml疑惑解答
XSD文件:
<xsd:schema // 开头都是这样的
xmlns="http://www.springframework.org/schema/context" // 该xsd名称空间
xmlns:xsd="http://www.w3.org/2001/XMLSchema" // 都需要引入:该XSD需要遵守的规范
xmlns:beans="http://www.springframework.org/schema/beans" // 引用了其他的XSD
xmlns:tool="http://www.springframework.org/schema/tool" // 引用的其他的XSD
targetNamespace="http://www.springframework.org/schema/context" // 该xsd的名称空间
elementFormDefault="qualified" // 该名称空间的,默认是这个值
attributeFormDefault="unqualified"> //该名称空间的,默认是这个值
<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="https://www.springframework.org/schema/beans/spring-beans-4.3.xsd"/> // 因为上述引用了其他的XSD,所以这里需要import 其位置
<xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="https://www.springframework.org/schema/tool/spring-tool-4.3.xsd"/> // 因为上述引用了其他的XSD,所以这里需要import 其位置
示例:
<xsd:schema
xmlns="http://www.wxj.org/schema/test" // 自己定义的
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.wxj.org/schema/test"
elementFormDefault="qualified"
/>
XML:
<xmlns="http://www.wxj.org/schema/test" //默认名称空间
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance" // 默认需要引入
xmlns:tt="http://www.wxj.org/schema/test" // 其他的名称空间, tt 是别名
xsi:schemaLocation=" http://www.wxj.org/schema/tes http://www.wxj.org/schema/tes/test.xsd" // 指定其位置
>
-
Spring和mybits解析xml
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance()
DocumentBuilder docBuilder = factory.newDocumentBuilder();
Document doc = builder.parse(inputSource);
都是用的saxp
-
代理
查看生产代理的方式:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); // jdk
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\");//cglib
//或者jdk 可以自己输出:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass("aaaa", new Class[]{IJdkProx.class});
File file = new File("D:\\code\\aaaa.class");
file.createNewFile();
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(proxyClassFile);
// JDK 代理
package com.wxj;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
final class $Proxy0 extends Proxy
implements IJdkProx
{
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
throws
{
try
{
// this.h 就是 构造方法传入的 paramInvocationHandler
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (RuntimeException localRuntimeException)
{
throw localRuntimeException;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void fun()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (RuntimeException localRuntimeException)
{
throw localRuntimeException;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
throws
{
try
{
return ((String)this.h.invoke(this, m2, null));
}
catch (RuntimeException localRuntimeException)
{
throw localRuntimeException;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
throws
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (RuntimeException localRuntimeException)
{
throw localRuntimeException;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("com.wxj.IJdkProx").getMethod("fun", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
// cglib
反编译打不开,使用javap 查看
会重写 toString,equals,hashCode,clone方法,
及会重写 父类方法。
方法逻辑:
调用 net.sf.cglib.proxy.MethodInterceptor#intercept(代理对象this, 重写的方法Method对象,方法参数,MethodProxy对象) 方法
-
实现接口方法的一种方式
A extends B implements C
A 实现接口C,
但C中的方法在B中被实现
比如:
Implements method in org.springframework.beans.factory.config.ConfigurableBeanFactory via sub-class org.springframework.beans.factory.support.AbstractBeanFactory
DefaultSingletonBeanRegistry#registerDependentBean 方法 就是 它的子类 实现了接口的方法
比如:
Ainterface 接口中有一个 m1方法
BClass 类中也有一个 m1 方法
此时,Ainterface 与BClass 是没有关系的,只是他们有个方法签名是相同的
CClass 继承了BClass 类,实现了 Ainterface,这时, CClass 应该实现Ainterface接口的m1方法。
但是,CClass类 可以不用实现m1方法,而是通过继承BClass 的m1方法。
因此,BClass 的 m1 方法,有一个 上下双向的 红色箭头,就表示 有子类CClass 继承了BClass ,并且 子类CClass需要实现的接口Ainterface里 有 m1方法。这个时候 子类就可以 不用 实现 m1方法,直接用了 BClass的m1方法。这也是 继承的一个特性吧,子类继承父类的所有方法。
-
实例化类
通常示例化一个类,都是用 new 关键字。
通常,在中间件的 框架 组件中,实例化类 经常用的是 反射,也就是 先拿到 Class对象,然后在获取到 构造方法,再实例化类。
拿到Class对象的 一种方式就是 使用 JDK 提供的 Class 类的 静态方法:
java.lang.Class#forName(java.lang.String)
java.lang.Class#forName(java.lang.String, boolean, java.lang.ClassLoader)
需要提供一个 String类型的参数,也就是像这样的:"com.wxj.test.A"
注解
spring处理注解,可以看:
org.springframework.core.annotation.AnnotationTypeMapping 这个类
// 通过 Class对象 可以获取当前类的 注解
// 返回的 注解实际上是一个 代理类对象
Annotation[] annotations = CA.class.getAnnotations();
// Annotation 这是一个 注解的代理对象
Annotation annotationProxy = annotations[0];
// 这才是 注解的 Class 对象, 获取到这个对象后,就可以 使用 Class 对象的一些方法了
Class<? extends Annotation> annotationClass = annotationProxy.annotationType();
// 比如: 获取当前注解上的注解
Annotation[] declaredAnnotations = annotationClass.getDeclaredAnnotations();
// 很多情况下,尤其是再 spring 框架中,都会注解上嵌套注解,嵌套又嵌套,最后搞晕了
// 实际就是 利用这种方式来获取 当前类上 的所有注解,
// 不论是直接声明的,还是 嵌套的
// 只要有这个注解,就可以进行 相应的处理逻辑了。
// 只不过spring封装的比较好,不容易找到对应的代码在那里,
// 通过断点,找到了: org.springframework.core.annotation.AnnotationsScanner.getDeclaredAnnotations
// 直接判断 当前类是否含有注解
boolean annotationPresent = CA.class.isAnnotationPresent(A1.class);
// 获取类的所有注解
Annotation[] declaredAnnotations1 = CA.class.getDeclaredAnnotations();
// 直接获取 类上 的 某个注解
A1 declaredAnnotation = CA.class.getDeclaredAnnotation(A1.class);
属性字段是否可以用is开头
因为常用的 lombok 的 @Data注解,
针对使用 @Data注解的情况下:
对于boolean类型的字段:
1. 如果不用is开头
获取:isXxx
赋值:setXxx
2. 如果用is开头
获取:isXxx
赋值:setXxx
也就是说,不论是否用 is 开头,自动解析生成的属性方法 和 不带 is 的是一样的。
再反过来推 字段,就不确定 本来是 带 is 还是 不带 is, 字段名就无法确定。
结论: 最好不要用 is 开头
对于非 boolean类型的属性,没有这种问题。
String 为空的 2 个方法
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
commons-lang3提供的2个判断方法:
空字符串 一共分为3类:
null 这种就是 最真实的 为 空
"" 这种不为空, 但是 长度为 0
" " 这种不为空,长度也不为0,但是 只包含 空格 字符
1. isBlank 这种将 空字符串定义为:
String s = null;
String s = "";
String s = " ";
public static boolean isBlank(final CharSequence cs)
2. isEmpty 这个方法 将空字符串定义为:
String s = null;
String s = "";
public static boolean isEmpty(final CharSequence cs)
Finalizer
除了常见的 强软弱性引用外,还有一种引用:Finalizer引用
如果类重写了 finalize 方法,
protected void finalize() throws Throwable { }
那么这个类在创建后,会被java.lang.ref.Finalizer 对象引用,该对象是一个链表结构
如何定义变量名称
当我们在定义变量名称时,包括 : 数据库字段名称、Java编程语言 变量名称、 网络相关 比如 域名、接口名称、 等等 ,各种各样的场景下 需要定义 名称时, 都需要记住一点:定义的名称 不要有可能是 某种场景下的关键字。
在计算机体系中,使用高级语言编程,使用计算机,都是在整个体系中使用的,所以,有些名称是 在计算机体系中 已经存在的,我们不能拿来使用。就像是 程序的端口号一样,有些端口号是系统使用的,用户不能使用。
因此,定义变量名时,需要有这方面的意识。
域名和IP是什么关系
一般来说,域名 经过 DNS 解析,会得到与之绑定的IP。
一个域名或者一个IP,可以绑定到不同的端口上,每个端口对应一个程序入口。
通常,在请求某个资源时,一般是用域名来请求,如果域名后面没有加端口号,那么会使用默认的端口号。但是,IP地址后面不能少了端口号。
因此,有可能会出现这种情况:
http://com.a.b/
http://com.a.b:8081/
http://com.a.b:8082/
http://com.a.b:8083/
或者
http://192.168.0.1:80
http://192.168.0.1:8081/
http://192.168.0.1:8082/
http://192.168.0.1:8083/
这些是 域名 或者 IP 相同,只是端口不同,那么就代表不同的资源地址。
域名和IP的绑定,可以通过 ping 命令来获取:
返回的IP地址,就是域名绑定的IP,也可以理解为 慢慢路由的起始端。 拿到这个IP,就可以访问的目标主机。
C:\Users>ping baidu.com
正在 Ping baidu.com [220.181.38.148] 具有 32 字节的数据:
来自 220.181.38.148 的回复: 字节=32 时间=29ms TTL=50
来自 220.181.38.148 的回复: 字节=32 时间=30ms TTL=50
来自 220.181.38.148 的回复: 字节=32 时间=32ms TTL=50
来自 220.181.38.148 的回复: 字节=32 时间=31ms TTL=50
因此,可以通过 ping 命令来获取具体的IP地址。
需要注意的是,ping命令 的域名后面,不要加端口号。
如果,遇到访问某个网址 访问不通, 怎么解决呢?
一般在开发的过程中,可以用一些 接口请求工具,比如postman,如果是get请求,也可以直接用浏览器 来请求我们的接口。
现在微服务盛行,在程序中发起的 对另一个服务的调用,都可以直接用 接口请求工具来访问。
当接口请求不通时,最好的方式,是用接口请求工具,把接口参数获取到后,直接请求,这样比较直观,可以根据当时的具体情况来判断问题所在。
可以从以下几点:
1. 资源的组成方面:
域名解析是否成功
是http请求还是 https请求
端口是否正确
资源路径是否正确,一般资源路径:http(s)://域名:端口号/服务名/资源路径
请求方式GET/POST 是否正确
请求参数是否正确
必要的header、cookie等
以上几点,仔细观察一下,了解清楚,保证信息都是正确的 就行。
2. 域名能否ping通这说明到目标主机的网络通不通。
如果用IP可以访问通,用域名访问不通,说明 域名解析不成功
3. 能否请求到目标主机。
现在比较流行k8s,容器,这样会涉及到不同范围的IP,可以理解成 局域网 套局域网,一层层嵌套,如果想要访问到内部的容器服务,需要从最外面的VIP 进行访问。
4. 目标主机是否存在请求资源。
典型的是404,如果报了404,一定是网络是通的,主机能找到,只是主机上不存在你要请求的资源。
资源不存在,返回404,这个时候 请求是成功的,只是返回了一个404告诉你资源不存在。所以,这里 在 编写网络通信相关的程序时,当发起了对资源的请求后,拿到结果,如果系统没有报错,不代表就是成功的,需要对返回码进行判断,只有200,才是成功。
5. 目标主机做了什么处理
另一种情况是,在请求目标主机的过程中,被其他的中间服务,比如网关、F5等进行转发或者校验,也可能被拒绝 而导致访问不到目标主机。
最终,如果请求到达目标主机,那么目标主机的一些策略,也可能导致我们的请求资源是拿不到的,比如:没有登录,被加到黑名单了等登。
因此,接口不通,原因很复杂,需要具体问题具体分析。
另外还有一点:在开发过程中,有开发环境、测试环境、灰度环境、生产环境等等各种环境,需要保证的是,环境是一致的。
判断一个类是不是另一个类的子类
public native boolean isAssignableFrom(Class<?> cls)
Class 类提供的一个本地方法,可以快速判断一个类是不是另一个类的子类。
例如:Advice.class.isAssignableFrom(beanClass)
判断 beanClass 是不是 Advice 的子类
有了这个方法,就不用 获取Class的父接口,然后遍历 再判断了,非常方便。
编程注意事项:服务器硬件资源方面
这里讨论的是使用Java高级语言编写的程序。
程序运行最小单位是线程,线程运行需要一些资源:
1. CPU
2. memory
3. 磁盘IO/网络IO(带宽)
4. 线程生成的数据存放在磁盘上
所以,再开发一个功能时,需要同时考虑上述 4 个大点,因为 一个程序不仅仅只有一个线程在运行,当新功能的线程 对上述 4 个大点 占用比较高,或者说 对服务器硬件资源 占用过高,会导致其他线程的正常执行,从而导致 某项功能无法正常提供。
有一个原则是平均分配,也就是 所有不同的线程 对资源的使用都是平均的,这样大家才能 都正常的运行,正常的提供功能。
另外一点,线程也有不同的类型、不同的执行优先级。比如:有守护线程,线程优先级有高有底。对于实际开发的功能,也存在 核心功能、辅助功能。但是 核心功能和辅助功能都是靠线程来执行的,所以,可以在编程的过程中,定制化的操作线程:比如 设置优先级、设置线程的个数、设置线程的运行频率等等。
泛型
变量和数据类型
boolean类型,hotspot 虚拟机 占4个字节,当成int来处理
在写代码的时候:
对于整数,是默认当成 int 类型的,如果超时 int 类型的范围,就会报错。
所以,在字面上面需要加上 L,标识一个 long型整数
浮点型默认的 double类型的
String.format 显示%
在Java中,如果你希望使用String.format方法输出百分号%,你需要使用两个连续的百分号%%来转义。这是因为%字符在格式化字符串中是特殊字符,用于引入格式化参数。当你想要文本中直接显示百分号时,写作%%会确保它被正确解析为一个单独的百分号字符。
String formattedText = String.format("显示百分比符号: %d%%", 100);
System.out.println(formattedText);
显示百分比符号: 100%